diff --git a/arch/.config/fuzzel/fuzzel.ini b/arch/.config/fuzzel/fuzzel.ini new file mode 100644 index 0000000..dfc1505 --- /dev/null +++ b/arch/.config/fuzzel/fuzzel.ini @@ -0,0 +1 @@ +include=~/.config/fuzzel/themes/noctalia diff --git a/arch/.config/fuzzel/themes/noctalia b/arch/.config/fuzzel/themes/noctalia new file mode 100644 index 0000000..2cfde55 --- /dev/null +++ b/arch/.config/fuzzel/themes/noctalia @@ -0,0 +1,15 @@ +# Fuzzel Colors +# Generated by Noctalia's Template Processor + +[colors] +background=111416CC +text=e2e2e5ff +prompt=b7c9d9ff +placeholder=cec0e8ff +input=8aceffff +match=cec0e8ff +selection=8aceff80 +selection-text=e2e2e5ff +selection-match=00344dff +counter=b7c9d9ff +border=8aceffff diff --git a/arch/.config/noctalia/colors.json b/arch/.config/noctalia/colors.json new file mode 100644 index 0000000..08a4913 --- /dev/null +++ b/arch/.config/noctalia/colors.json @@ -0,0 +1,25 @@ +{ + "mPrimary": "#8aceff", + "mOnPrimary": "#00344d", + + "mSecondary": "#b7c9d9", + "mOnSecondary": "#22323f", + + "mTertiary": "#cec0e8", + "mOnTertiary": "#352b4b", + + "mError": "#ffb4ab", + "mOnError": "#690005", + + "mSurface": "#111416", + "mOnSurface": "#e2e2e5", + + "mSurfaceVariant": "#1e2022", + "mOnSurfaceVariant": "#c1c7ce", + + "mOutline": "#41474d", + "mShadow": "#000000", + + "mHover": "#cec0e8", + "mOnHover": "#352b4b" +} \ No newline at end of file diff --git a/arch/.config/noctalia/plugins.json b/arch/.config/noctalia/plugins.json new file mode 100644 index 0000000..04b0a22 --- /dev/null +++ b/arch/.config/noctalia/plugins.json @@ -0,0 +1,12 @@ +{ + "sources": [ + { + "enabled": true, + "name": "Noctalia Plugins", + "url": "https://github.com/noctalia-dev/noctalia-plugins" + } + ], + "states": { + }, + "version": 2 +} diff --git a/arch/.config/noctalia/settings.json b/arch/.config/noctalia/settings.json new file mode 100644 index 0000000..603cb38 --- /dev/null +++ b/arch/.config/noctalia/settings.json @@ -0,0 +1,710 @@ +{ + "appLauncher": { + "autoPasteClipboard": false, + "clipboardWatchImageCommand": "wl-paste --type image --watch cliphist store", + "clipboardWatchTextCommand": "wl-paste --type text --watch cliphist store", + "clipboardWrapText": true, + "customLaunchPrefix": "", + "customLaunchPrefixEnabled": false, + "density": "default", + "enableClipPreview": true, + "enableClipboardChips": true, + "enableClipboardHistory": false, + "enableClipboardSmartIcons": true, + "enableSessionSearch": true, + "enableSettingsSearch": true, + "enableWindowsSearch": true, + "iconMode": "tabler", + "ignoreMouseInput": false, + "overviewLayer": false, + "pinnedApps": [ + ], + "position": "center", + "screenshotAnnotationTool": "", + "showCategories": true, + "showIconBackground": false, + "sortByMostUsed": true, + "terminalCommand": "alacritty -e", + "viewMode": "list" + }, + "audio": { + "mprisBlacklist": [ + ], + "preferredPlayer": "", + "spectrumFrameRate": 30, + "spectrumMirrored": true, + "visualizerType": "linear", + "volumeFeedback": false, + "volumeFeedbackSoundFile": "", + "volumeOverdrive": false, + "volumeStep": 5 + }, + "bar": { + "autoHideDelay": 500, + "autoShowDelay": 150, + "backgroundOpacity": 0.93, + "barType": "simple", + "capsuleColorKey": "none", + "capsuleOpacity": 1, + "contentPadding": 2, + "density": "compact", + "displayMode": "always_visible", + "enableExclusionZoneInset": true, + "fontScale": 1, + "frameRadius": 12, + "frameThickness": 8, + "hideOnOverview": false, + "marginHorizontal": 4, + "marginVertical": 4, + "middleClickAction": "none", + "middleClickCommand": "", + "middleClickFollowMouse": false, + "monitors": [ + ], + "mouseWheelAction": "none", + "mouseWheelWrap": true, + "outerCorners": true, + "position": "top", + "reverseScroll": false, + "rightClickAction": "controlCenter", + "rightClickCommand": "", + "rightClickFollowMouse": true, + "screenOverrides": [ + ], + "showCapsule": true, + "showOnWorkspaceSwitch": true, + "showOutline": false, + "useSeparateOpacity": false, + "widgetSpacing": 6, + "widgets": { + "center": [ + { + "characterCount": 2, + "colorizeIcons": false, + "emptyColor": "secondary", + "enableScrollWheel": true, + "focusedColor": "primary", + "followFocusedScreen": false, + "fontWeight": "bold", + "groupedBorderOpacity": 1, + "hideUnoccupied": false, + "iconScale": 0.8, + "id": "Workspace", + "labelMode": "index", + "occupiedColor": "secondary", + "pillSize": 0.6, + "showApplications": false, + "showApplicationsHover": false, + "showBadge": true, + "showLabelsOnlyWhenOccupied": true, + "unfocusedIconsOpacity": 1 + } + ], + "left": [ + { + "colorizeSystemIcon": "none", + "customIconPath": "", + "enableColorization": false, + "icon": "rocket", + "iconColor": "none", + "id": "Launcher", + "useDistroLogo": false + }, + { + "clockColor": "none", + "customFont": "", + "formatHorizontal": "HH:mm ddd, MMM dd", + "formatVertical": "HH mm - dd MM", + "id": "Clock", + "tooltipFormat": "HH:mm ddd, MMM dd", + "useCustomFont": false + }, + { + "compactMode": true, + "diskPath": "/", + "iconColor": "none", + "id": "SystemMonitor", + "showCpuCores": false, + "showCpuFreq": false, + "showCpuTemp": true, + "showCpuUsage": true, + "showDiskAvailable": false, + "showDiskUsage": false, + "showDiskUsageAsPercent": false, + "showGpuTemp": false, + "showLoadAverage": false, + "showMemoryAsPercent": false, + "showMemoryUsage": true, + "showNetworkStats": false, + "showSwapUsage": false, + "textColor": "none", + "useMonospaceFont": true, + "usePadding": false + }, + { + "colorizeIcons": false, + "hideMode": "hidden", + "id": "ActiveWindow", + "maxWidth": 145, + "scrollingMode": "hover", + "showIcon": true, + "showText": true, + "textColor": "none", + "useFixedWidth": false + }, + { + "compactMode": false, + "hideMode": "hidden", + "hideWhenIdle": false, + "id": "MediaMini", + "maxWidth": 145, + "panelShowAlbumArt": true, + "scrollingMode": "hover", + "showAlbumArt": true, + "showArtistFirst": true, + "showProgressRing": true, + "showVisualizer": false, + "textColor": "none", + "useFixedWidth": false, + "visualizerType": "linear" + } + ], + "right": [ + { + "blacklist": [ + ], + "chevronColor": "none", + "colorizeIcons": false, + "drawerEnabled": true, + "hidePassive": false, + "id": "Tray", + "pinned": [ + ] + }, + { + "hideWhenZero": false, + "hideWhenZeroUnread": false, + "iconColor": "none", + "id": "NotificationHistory", + "showUnreadBadge": true, + "unreadBadgeColor": "primary" + }, + { + "displayMode": "alwaysShow", + "iconColor": "none", + "id": "Network", + "textColor": "none" + }, + { + "deviceNativePath": "__default__", + "displayMode": "icon-always", + "hideIfIdle": false, + "hideIfNotDetected": true, + "id": "Battery", + "showNoctaliaPerformance": false, + "showPowerProfiles": false + }, + { + "displayMode": "onhover", + "iconColor": "none", + "id": "Volume", + "middleClickCommand": "pwvucontrol || pavucontrol", + "textColor": "none" + }, + { + "applyToAllMonitors": false, + "displayMode": "onhover", + "iconColor": "none", + "id": "Brightness", + "textColor": "none" + }, + { + "colorizeDistroLogo": false, + "colorizeSystemIcon": "none", + "customIconPath": "", + "enableColorization": false, + "icon": "noctalia", + "id": "ControlCenter", + "useDistroLogo": false + } + ] + } + }, + "brightness": { + "backlightDeviceMappings": [ + ], + "brightnessStep": 5, + "enableDdcSupport": false, + "enforceMinimum": true + }, + "calendar": { + "cards": [ + { + "enabled": true, + "id": "calendar-header-card" + }, + { + "enabled": true, + "id": "calendar-month-card" + }, + { + "enabled": true, + "id": "weather-card" + } + ] + }, + "colorSchemes": { + "darkMode": true, + "generationMethod": "tonal-spot", + "manualSunrise": "06:30", + "manualSunset": "18:30", + "monitorForColors": "", + "predefinedScheme": "Noctalia (default)", + "schedulingMode": "off", + "syncGsettings": true, + "useWallpaperColors": true + }, + "controlCenter": { + "cards": [ + { + "enabled": true, + "id": "profile-card" + }, + { + "enabled": true, + "id": "shortcuts-card" + }, + { + "enabled": true, + "id": "audio-card" + }, + { + "enabled": false, + "id": "brightness-card" + }, + { + "enabled": true, + "id": "weather-card" + }, + { + "enabled": true, + "id": "media-sysmon-card" + } + ], + "diskPath": "/", + "position": "close_to_bar_button", + "shortcuts": { + "left": [ + { + "id": "Network" + }, + { + "id": "Bluetooth" + }, + { + "id": "WallpaperSelector" + }, + { + "id": "NoctaliaPerformance" + } + ], + "right": [ + { + "id": "Notifications" + }, + { + "id": "PowerProfile" + }, + { + "id": "KeepAwake" + }, + { + "id": "NightLight" + } + ] + } + }, + "desktopWidgets": { + "enabled": false, + "gridSnap": false, + "gridSnapScale": false, + "monitorWidgets": [ + ], + "overviewEnabled": true + }, + "dock": { + "animationSpeed": 1, + "backgroundOpacity": 1, + "colorizeIcons": false, + "deadOpacity": 0.6, + "displayMode": "auto_hide", + "dockType": "floating", + "enabled": false, + "floatingRatio": 1, + "groupApps": false, + "groupClickAction": "cycle", + "groupContextMenuMode": "extended", + "groupIndicatorStyle": "dots", + "inactiveIndicators": false, + "indicatorColor": "primary", + "indicatorOpacity": 0.6, + "indicatorThickness": 3, + "launcherIcon": "", + "launcherIconColor": "none", + "launcherPosition": "end", + "launcherUseDistroLogo": false, + "monitors": [ + ], + "onlySameOutput": true, + "pinnedApps": [ + ], + "pinnedStatic": false, + "position": "bottom", + "showDockIndicator": false, + "showLauncherIcon": false, + "sitOnFrame": false, + "size": 1 + }, + "general": { + "allowPanelsOnScreenWithoutBar": true, + "allowPasswordWithFprintd": false, + "animationDisabled": false, + "animationSpeed": 1, + "autoStartAuth": false, + "avatarImage": "/home/yuri/.face", + "boxRadiusRatio": 1, + "clockFormat": "hh\\nmm", + "clockStyle": "custom", + "compactLockScreen": false, + "dimmerOpacity": 0.2, + "enableBlurBehind": true, + "enableLockScreenCountdown": true, + "enableLockScreenMediaControls": false, + "enableShadows": false, + "forceBlackScreenCorners": false, + "iRadiusRatio": 1, + "keybinds": { + "keyDown": [ + "Down" + ], + "keyEnter": [ + "Return", + "Enter" + ], + "keyEscape": [ + "Esc" + ], + "keyLeft": [ + "Left" + ], + "keyRemove": [ + "Del" + ], + "keyRight": [ + "Right" + ], + "keyUp": [ + "Up" + ] + }, + "language": "", + "lockOnSuspend": true, + "lockScreenAnimations": false, + "lockScreenBlur": 0, + "lockScreenCountdownDuration": 10000, + "lockScreenMonitors": [ + ], + "lockScreenTint": 0, + "passwordChars": false, + "radiusRatio": 1, + "reverseScroll": false, + "scaleRatio": 1, + "screenRadiusRatio": 1, + "shadowDirection": "bottom_right", + "shadowOffsetX": 2, + "shadowOffsetY": 3, + "showChangelogOnStartup": true, + "showHibernateOnLockScreen": false, + "showScreenCorners": false, + "showSessionButtonsOnLockScreen": true, + "smoothScrollEnabled": true, + "telemetryEnabled": false + }, + "hooks": { + "colorGeneration": "", + "darkModeChange": "", + "enabled": false, + "performanceModeDisabled": "", + "performanceModeEnabled": "", + "screenLock": "", + "screenUnlock": "", + "session": "", + "startup": "", + "wallpaperChange": "" + }, + "idle": { + "customCommands": "[]", + "enabled": false, + "fadeDuration": 5, + "lockCommand": "", + "lockTimeout": 660, + "resumeLockCommand": "", + "resumeScreenOffCommand": "", + "resumeSuspendCommand": "", + "screenOffCommand": "", + "screenOffTimeout": 600, + "suspendCommand": "", + "suspendTimeout": 1800 + }, + "location": { + "analogClockInCalendar": false, + "autoLocate": true, + "firstDayOfWeek": -1, + "hideWeatherCityName": false, + "hideWeatherTimezone": false, + "name": "Nieuw-Vennep", + "showCalendarEvents": true, + "showCalendarWeather": true, + "showWeekNumberInCalendar": false, + "use12hourFormat": false, + "useFahrenheit": false, + "weatherEnabled": true, + "weatherShowEffects": true, + "weatherTaliaMascotAlways": false + }, + "network": { + "bluetoothAutoConnect": true, + "bluetoothDetailsViewMode": "grid", + "bluetoothHideUnnamedDevices": false, + "bluetoothRssiPollIntervalMs": 60000, + "bluetoothRssiPollingEnabled": false, + "disableDiscoverability": false, + "networkPanelView": "wifi", + "wifiDetailsViewMode": "grid" + }, + "nightLight": { + "autoSchedule": true, + "dayTemp": "6500", + "enabled": false, + "forced": false, + "manualSunrise": "06:30", + "manualSunset": "18:30", + "nightTemp": "4000" + }, + "noctaliaPerformance": { + "disableDesktopWidgets": true, + "disableWallpaper": true + }, + "notifications": { + "backgroundOpacity": 1, + "clearDismissed": true, + "criticalUrgencyDuration": 15, + "density": "default", + "enableBatteryToast": true, + "enableKeyboardLayoutToast": true, + "enableMarkdown": false, + "enableMediaToast": false, + "enabled": true, + "location": "top_right", + "lowUrgencyDuration": 3, + "monitors": [ + ], + "normalUrgencyDuration": 8, + "overlayLayer": true, + "respectExpireTimeout": false, + "saveToHistory": { + "critical": true, + "low": true, + "normal": true + }, + "sounds": { + "criticalSoundFile": "", + "enabled": false, + "excludedApps": "discord,firefox,chrome,chromium,edge", + "lowSoundFile": "", + "normalSoundFile": "", + "separateSounds": false, + "volume": 0.5 + } + }, + "osd": { + "autoHideMs": 2000, + "backgroundOpacity": 1, + "enabled": true, + "enabledTypes": [ + 0, + 1, + 2 + ], + "location": "top_right", + "monitors": [ + ], + "overlayLayer": true + }, + "plugins": { + "autoUpdate": false, + "notifyUpdates": true + }, + "sessionMenu": { + "countdownDuration": 10000, + "enableCountdown": true, + "largeButtonsLayout": "single-row", + "largeButtonsStyle": true, + "position": "center", + "powerOptions": [ + { + "action": "lock", + "enabled": true, + "keybind": "1" + }, + { + "action": "suspend", + "enabled": true, + "keybind": "2" + }, + { + "action": "hibernate", + "enabled": true, + "keybind": "3" + }, + { + "action": "reboot", + "enabled": true, + "keybind": "4" + }, + { + "action": "logout", + "enabled": true, + "keybind": "5" + }, + { + "action": "shutdown", + "enabled": true, + "keybind": "6" + }, + { + "action": "rebootToUefi", + "enabled": true, + "keybind": "7" + } + ], + "showHeader": true, + "showKeybinds": true + }, + "settingsVersion": 59, + "systemMonitor": { + "batteryCriticalThreshold": 5, + "batteryWarningThreshold": 20, + "cpuCriticalThreshold": 90, + "cpuWarningThreshold": 80, + "criticalColor": "", + "diskAvailCriticalThreshold": 10, + "diskAvailWarningThreshold": 20, + "diskCriticalThreshold": 90, + "diskWarningThreshold": 80, + "enableDgpuMonitoring": false, + "externalMonitor": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "gpuCriticalThreshold": 90, + "gpuWarningThreshold": 80, + "memCriticalThreshold": 90, + "memWarningThreshold": 80, + "swapCriticalThreshold": 90, + "swapWarningThreshold": 80, + "tempCriticalThreshold": 90, + "tempWarningThreshold": 80, + "useCustomColors": false, + "warningColor": "" + }, + "templates": { + "activeTemplates": [ + { + "enabled": true, + "id": "btop" + }, + { + "enabled": true, + "id": "fuzzel" + }, + { + "enabled": true, + "id": "niri" + }, + { + "enabled": true, + "id": "gtk" + }, + { + "enabled": true, + "id": "code" + }, + { + "enabled": true, + "id": "qt" + } + ], + "enableUserTheming": false + }, + "ui": { + "boxBorderEnabled": false, + "fontDefault": "Sans Serif", + "fontDefaultScale": 1, + "fontFixed": "monospace", + "fontFixedScale": 1, + "panelBackgroundOpacity": 0.9, + "panelsAttachedToBar": true, + "scrollbarAlwaysVisible": true, + "settingsPanelMode": "attached", + "settingsPanelSideBarCardStyle": false, + "tooltipsEnabled": true, + "translucentWidgets": false + }, + "wallpaper": { + "automationEnabled": false, + "directory": "/mnt/DISK2/_ISO/wallpapers", + "enableMultiMonitorDirectories": false, + "enabled": true, + "favorites": [ + ], + "fillColor": "#000000", + "fillMode": "crop", + "hideWallpaperFilenames": false, + "linkLightAndDarkWallpapers": true, + "monitorDirectories": [ + ], + "overviewBlur": 0.4, + "overviewEnabled": true, + "overviewTint": 0.6, + "panelPosition": "follow_bar", + "randomIntervalSec": 300, + "setWallpaperOnAllMonitors": true, + "showHiddenFiles": false, + "skipStartupTransition": false, + "solidColor": "#1a1a2e", + "sortOrder": "name", + "transitionDuration": 1500, + "transitionEdgeSmoothness": 0.05, + "transitionType": [ + "fade", + "disc", + "stripes", + "wipe", + "pixelate", + "honeycomb" + ], + "useOriginalImages": false, + "useSolidColor": false, + "useWallhaven": false, + "viewMode": "single", + "wallhavenApiKey": "", + "wallhavenCategories": "111", + "wallhavenOrder": "desc", + "wallhavenPurity": "100", + "wallhavenQuery": "", + "wallhavenRatios": "", + "wallhavenResolutionHeight": "", + "wallhavenResolutionMode": "atleast", + "wallhavenResolutionWidth": "", + "wallhavenSorting": "relevance", + "wallpaperChangeMode": "random" + } +} diff --git a/arch/.config/quickshell/noctalia-shell/.gitignore b/arch/.config/quickshell/noctalia-shell/.gitignore new file mode 100644 index 0000000..d500121 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/.gitignore @@ -0,0 +1,12 @@ +.qmlls.ini +.zed +.idea +.vscode +*.code-workspace +RELEASE.md +__pycache__ +result +build +/debian/ +.direnv/ +.envrc diff --git a/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Ayu/Ayu.json b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Ayu/Ayu.json new file mode 100644 index 0000000..bf08032 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Ayu/Ayu.json @@ -0,0 +1,94 @@ +{ + "dark": { + "mPrimary": "#E6B450", + "mOnPrimary": "#0B0E14", + "mSecondary": "#AAD94C", + "mOnSecondary": "#0B0E14", + "mTertiary": "#39BAE6", + "mOnTertiary": "#0B0E14", + "mError": "#D95757", + "mOnError": "#0B0E14", + "mSurface": "#0B0E14", + "mOnSurface": "#D1D1C7", + "mSurfaceVariant": "#1e222a", + "mOnSurfaceVariant": "#8E959E", + "mOutline": "#565B66", + "mShadow": "#000000", + "mHover": "#39BAE6", + "mOnHover": "#0B0E14", + "terminal": { + "normal": { + "black": "#171b24", + "red": "#ed8274", + "green": "#87d96c", + "yellow": "#facc6e", + "blue": "#6dcbfa", + "magenta": "#dabafa", + "cyan": "#90e1c6", + "white": "#c7c7c7" + }, + "bright": { + "black": "#686868", + "red": "#f28779", + "green": "#d5ff80", + "yellow": "#ffd173", + "blue": "#73d0ff", + "magenta": "#dfbfff", + "cyan": "#95e6cb", + "white": "#ffffff" + }, + "foreground": "#D1D1C7", + "background": "#1f2430", + "selectionFg": "#1f2430", + "selectionBg": "#409fff", + "cursorText": "#1f2430", + "cursor": "#ffcc66" + } + }, + "light": { + "mPrimary": "#FF8F40", + "mOnPrimary": "#F8F9FA", + "mSecondary": "#86B300", + "mOnSecondary": "#F8F9FA", + "mTertiary": "#55B4D4", + "mOnTertiary": "#F8F9FA", + "mError": "#E65050", + "mOnError": "#F8F9FA", + "mSurface": "#F8F9FA", + "mOnSurface": "#42474C", + "mSurfaceVariant": "#E4E6E9", + "mOnSurfaceVariant": "#6E757C", + "mOutline": "#8A9199", + "mShadow": "#F8F9FA", + "mHover": "#55B4D4", + "mOnHover": "#F8F9FA", + "terminal": { + "normal": { + "black": "#000000", + "red": "#ea6c6d", + "green": "#6cbf43", + "yellow": "#eca944", + "blue": "#3199e1", + "magenta": "#9e75c7", + "cyan": "#46ba94", + "white": "#bababa" + }, + "bright": { + "black": "#686868", + "red": "#f07171", + "green": "#86b300", + "yellow": "#f2ae49", + "blue": "#399ee6", + "magenta": "#a37acc", + "cyan": "#4cbf99", + "white": "#d1d1d1" + }, + "foreground": "#42474C", + "background": "#f8f9fa", + "selectionFg": "#f8f9fa", + "selectionBg": "#035bd6", + "cursorText": "#f8f9fa", + "cursor": "#ffaa33" + } + } +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Catppuccin/Catppuccin.json b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Catppuccin/Catppuccin.json new file mode 100644 index 0000000..1d1f51f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Catppuccin/Catppuccin.json @@ -0,0 +1,94 @@ +{ + "dark": { + "mPrimary": "#cba6f7", + "mOnPrimary": "#11111b", + "mSecondary": "#fab387", + "mOnSecondary": "#11111b", + "mTertiary": "#94e2d5", + "mOnTertiary": "#11111b", + "mError": "#f38ba8", + "mOnError": "#11111b", + "mSurface": "#1e1e2e", + "mOnSurface": "#cdd6f4", + "mSurfaceVariant": "#313244", + "mOnSurfaceVariant": "#a3b4eb", + "mOutline": "#4c4f69", + "mShadow": "#11111b", + "mHover": "#94e2d5", + "mOnHover": "#11111b", + "terminal": { + "normal": { + "black": "#45475a", + "red": "#f38ba8", + "green": "#a6e3a1", + "yellow": "#f9e2af", + "blue": "#89b4fa", + "magenta": "#f5c2e7", + "cyan": "#94e2d5", + "white": "#a6adc8" + }, + "bright": { + "black": "#585b70", + "red": "#f37799", + "green": "#89d88b", + "yellow": "#ebd391", + "blue": "#74a8fc", + "magenta": "#f2aede", + "cyan": "#6bd7ca", + "white": "#bac2de" + }, + "foreground": "#cdd6f4", + "background": "#1e1e2e", + "selectionFg": "#cdd6f4", + "selectionBg": "#585b70", + "cursorText": "#1e1e2e", + "cursor": "#f5e0dc" + } + }, + "light": { + "mPrimary": "#8839ef", + "mOnPrimary": "#eff1f5", + "mSecondary": "#fe640b", + "mOnSecondary": "#eff1f5", + "mTertiary": "#40a02b", + "mOnTertiary": "#eff1f5", + "mError": "#d20f39", + "mOnError": "#dce0e8", + "mSurface": "#eff1f5", + "mOnSurface": "#4c4f69", + "mSurfaceVariant": "#ccd0da", + "mOnSurfaceVariant": "#6c6f85", + "mOutline": "#a5adcb", + "mShadow": "#dce0e8", + "mHover": "#40a02b", + "mOnHover": "#eff1f5", + "terminal": { + "normal": { + "black": "#51576d", + "red": "#e78284", + "green": "#a6d189", + "yellow": "#e5c890", + "blue": "#8caaee", + "magenta": "#f4b8e4", + "cyan": "#81c8be", + "white": "#a5adce" + }, + "bright": { + "black": "#626880", + "red": "#e67172", + "green": "#8ec772", + "yellow": "#d9ba73", + "blue": "#7b9ef0", + "magenta": "#f2a4db", + "cyan": "#5abfb5", + "white": "#b5bfe2" + }, + "foreground": "#c6d0f5", + "background": "#303446", + "selectionFg": "#c6d0f5", + "selectionBg": "#626880", + "cursorText": "#303446", + "cursor": "#f2d5cf" + } + } +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Dracula/Dracula.json b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Dracula/Dracula.json new file mode 100644 index 0000000..b4e5990 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Dracula/Dracula.json @@ -0,0 +1,94 @@ +{ + "dark": { + "mPrimary": "#bd93f9", + "mOnPrimary": "#282A36", + "mSecondary": "#ff79c6", + "mOnSecondary": "#4e1d32", + "mTertiary": "#8be9fd", + "mOnTertiary": "#003543", + "mError": "#FF5555", + "mOnError": "#282A36", + "mSurface": "#282A36", + "mOnSurface": "#F8F8F2", + "mSurfaceVariant": "#44475A", + "mOnSurfaceVariant": "#d6d8e0", + "mOutline": "#5a5e77", + "mShadow": "#282A36", + "mHover": "#8be9fd", + "mOnHover": "#003543", + "terminal": { + "normal": { + "black": "#21222c", + "red": "#ff5555", + "green": "#50fa7b", + "yellow": "#f1fa8c", + "blue": "#bd93f9", + "magenta": "#ff79c6", + "cyan": "#8be9fd", + "white": "#f8f8f2" + }, + "bright": { + "black": "#6272a4", + "red": "#ff6e6e", + "green": "#69ff94", + "yellow": "#ffffa5", + "blue": "#d6acff", + "magenta": "#ff92df", + "cyan": "#a4ffff", + "white": "#ffffff" + }, + "foreground": "#f8f8f2", + "background": "#282a36", + "selectionFg": "#ffffff", + "selectionBg": "#44475a", + "cursorText": "#282a36", + "cursor": "#f8f8f2" + } + }, + "light": { + "mPrimary": "#8332f4", + "mOnPrimary": "#ffffff", + "mSecondary": "#ff1399", + "mOnSecondary": "#ffffff", + "mTertiary": "#0398b9", + "mOnTertiary": "#ffffff", + "mError": "#FF5555", + "mOnError": "#282A36", + "mSurface": "#f8f8f2", + "mOnSurface": "#282a36", + "mSurfaceVariant": "#e6e6ea", + "mOnSurfaceVariant": "#44475a", + "mOutline": "#cacad3", + "mShadow": "#d6d8e0", + "mHover": "#0398b9", + "mOnHover": "#ffffff", + "terminal": { + "normal": { + "black": "#f8f8f2", + "red": "#ff5555", + "green": "#50fa7b", + "yellow": "#f1fa8c", + "blue": "#bd93f9", + "magenta": "#ff79c6", + "cyan": "#8be9fd", + "white": "#282a36" + }, + "bright": { + "black": "#6272a4", + "red": "#ff6e6e", + "green": "#69ff94", + "yellow": "#ffffa5", + "blue": "#d6acff", + "magenta": "#ff92df", + "cyan": "#a4ffff", + "white": "#000000" + }, + "foreground": "#282a36", + "background": "#ffffff", + "selectionFg": "#ffffff", + "selectionBg": "#6272a4", + "cursorText": "#ffffff", + "cursor": "#282a36" + } + } +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Eldritch/Eldritch.json b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Eldritch/Eldritch.json new file mode 100644 index 0000000..6074bf2 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Eldritch/Eldritch.json @@ -0,0 +1,94 @@ +{ + "dark": { + "mPrimary": "#37f499", + "mOnPrimary": "#171928", + "mSecondary": "#04d1f9", + "mOnSecondary": "#171928", + "mTertiary": "#a48cf2", + "mOnTertiary": "#171928", + "mError": "#f16c75", + "mOnError": "#171928", + "mSurface": "#212337", + "mOnSurface": "#ebfafa", + "mSurfaceVariant": "#292e42", + "mOnSurfaceVariant": "#ABB4DA", + "mOutline": "#3b4261", + "mShadow": "#414868", + "mHover": "#a48cf2", + "mOnHover": "#171928", + "terminal": { + "normal": { + "black": "#21222c", + "red": "#f9515d", + "green": "#37f499", + "yellow": "#e9f941", + "blue": "#9071f4", + "magenta": "#f265b5", + "cyan": "#04d1f9", + "white": "#ebfafa" + }, + "bright": { + "black": "#7081d0", + "red": "#f16c75", + "green": "#69F8B3", + "yellow": "#f1fc79", + "blue": "#a48cf2", + "magenta": "#FD92CE", + "cyan": "#66e4fd", + "white": "#ffffff" + }, + "foreground": "#ebfafa", + "background": "#212337", + "selectionFg": "#ebfafa", + "selectionBg": "#bf4f8e", + "cursorText": "#37f499", + "cursor": "#f8f8f2" + } + }, + "light": { + "mPrimary": "#37f499", + "mOnPrimary": "#171928", + "mSecondary": "#04d1f9", + "mOnSecondary": "#171928", + "mTertiary": "#a48cf2", + "mOnTertiary": "#171928", + "mError": "#f16c75", + "mOnError": "#171928", + "mSurface": "#ffffff", + "mOnSurface": "#171928", + "mSurfaceVariant": "#f2f4f8", + "mOnSurfaceVariant": "#3b4261", + "mOutline": "#b0b6c3", + "mShadow": "#e0e3e8", + "mHover": "#a48cf2", + "mOnHover": "#171928", + "terminal": { + "normal": { + "black": "#ebfafa", + "red": "#ba1a1a", + "green": "#1a7f4c", + "yellow": "#9e8c13", + "blue": "#3a3e8c", + "magenta": "#8c2a6c", + "cyan": "#1a6c8c", + "white": "#212337" + }, + "bright": { + "black": "#7081d0", + "red": "#d23b3b", + "green": "#37f499", + "yellow": "#e9f941", + "blue": "#9071f4", + "magenta": "#f265b5", + "cyan": "#04d1f9", + "white": "#212337" + }, + "foreground": "#212337", + "background": "#ebfafa", + "selectionFg": "#212337", + "selectionBg": "#bf4f8e", + "cursorText": "#1a7f4c", + "cursor": "#212337" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Gruvbox/Gruvbox.json b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Gruvbox/Gruvbox.json new file mode 100644 index 0000000..ff6f074 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Gruvbox/Gruvbox.json @@ -0,0 +1,94 @@ +{ + "dark": { + "mPrimary": "#b8bb26", + "mOnPrimary": "#282828", + "mSecondary": "#fabd2f", + "mOnSecondary": "#282828", + "mTertiary": "#83a598", + "mOnTertiary": "#282828", + "mError": "#fb4934", + "mOnError": "#282828", + "mSurface": "#282828", + "mOnSurface": "#fbf1c7", + "mSurfaceVariant": "#3c3836", + "mOnSurfaceVariant": "#ebdbb2", + "mOutline": "#57514e", + "mShadow": "#282828", + "mHover": "#83a598", + "mOnHover": "#282828", + "terminal": { + "normal": { + "black": "#282828", + "red": "#cc241d", + "green": "#98971a", + "yellow": "#d79921", + "blue": "#458588", + "magenta": "#b16286", + "cyan": "#689d6a", + "white": "#a89984" + }, + "bright": { + "black": "#928374", + "red": "#fb4934", + "green": "#b8bb26", + "yellow": "#fabd2f", + "blue": "#83a598", + "magenta": "#d3869b", + "cyan": "#8ec07c", + "white": "#ebdbb2" + }, + "foreground": "#ebdbb2", + "background": "#282828", + "selectionFg": "#ebdbb2", + "selectionBg": "#665c54", + "cursorText": "#282828", + "cursor": "#ebdbb2" + } + }, + "light": { + "mPrimary": "#98971a", + "mOnPrimary": "#fbf1c7", + "mSecondary": "#d79921", + "mOnSecondary": "#fbf1c7", + "mTertiary": "#458588", + "mOnTertiary": "#fbf1c7", + "mError": "#cc241d", + "mOnError": "#fbf1c7", + "mSurface": "#fbf1c7", + "mOnSurface": "#3c3836", + "mSurfaceVariant": "#ebdbb2", + "mOnSurfaceVariant": "#7c6f64", + "mOutline": "#bdae93", + "mShadow": "#d5c4a1", + "mHover": "#458588", + "mOnHover": "#fbf1c7", + "terminal": { + "normal": { + "black": "#fbf1c7", + "red": "#cc241d", + "green": "#98971a", + "yellow": "#d79921", + "blue": "#458588", + "magenta": "#b16286", + "cyan": "#689d6a", + "white": "#7c6f64" + }, + "bright": { + "black": "#928374", + "red": "#9d0006", + "green": "#79740e", + "yellow": "#b57614", + "blue": "#076678", + "magenta": "#8f3f71", + "cyan": "#427b58", + "white": "#3c3836" + }, + "foreground": "#3c3836", + "background": "#fbf1c7", + "selectionFg": "#fbf1c7", + "selectionBg": "#3c3836", + "cursorText": "#625e5c", + "cursor": "#3c3836" + } + } +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Kanagawa/Kanagawa.json b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Kanagawa/Kanagawa.json new file mode 100644 index 0000000..0acc151 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Kanagawa/Kanagawa.json @@ -0,0 +1,94 @@ +{ + "dark": { + "mPrimary": "#76946a", + "mOnPrimary": "#1f1f28", + "mSecondary": "#c0a36e", + "mOnSecondary": "#1f1f28", + "mTertiary": "#7e9cd8", + "mOnTertiary": "#1f1f28", + "mError": "#c34043", + "mOnError": "#1f1f28", + "mSurface": "#1f1f28", + "mOnSurface": "#c8c093", + "mSurfaceVariant": "#2a2a37", + "mOnSurfaceVariant": "#717c7c", + "mOutline": "#363646", + "mShadow": "#1f1f28", + "mHover": "#7e9cd8", + "mOnHover": "#1f1f28", + "terminal": { + "normal": { + "black": "#090618", + "red": "#c34043", + "green": "#76946a", + "yellow": "#c0a36e", + "blue": "#7e9cd8", + "magenta": "#957fb8", + "cyan": "#6a9589", + "white": "#c8c093" + }, + "bright": { + "black": "#727169", + "red": "#e82424", + "green": "#98bb6c", + "yellow": "#e6c384", + "blue": "#7fb4ca", + "magenta": "#938aa9", + "cyan": "#7aa89f", + "white": "#dcd7ba" + }, + "foreground": "#dcd7ba", + "background": "#1f1f28", + "selectionFg": "#c8c093", + "selectionBg": "#2d4f67", + "cursorText": "#1f1f28", + "cursor": "#c8c093" + } + }, + "light": { + "mPrimary": "#6f894e", + "mOnPrimary": "#f2ecbc", + "mSecondary": "#77713f", + "mOnSecondary": "#f2ecbc", + "mTertiary": "#4d699b", + "mOnTertiary": "#f2ecbc", + "mError": "#c84053", + "mOnError": "#f2ecbc", + "mSurface": "#f2ecbc", + "mOnSurface": "#545464", + "mSurfaceVariant": "#e5ddb0", + "mOnSurfaceVariant": "#8a8980", + "mOutline": "#cfc49c", + "mShadow": "#f2ecbc", + "mHover": "#4d699b", + "mOnHover": "#f2ecbc", + "terminal": { + "normal": { + "black": "#1F1F28", + "red": "#c84053", + "green": "#6f894e", + "yellow": "#77713f", + "blue": "#4d699b", + "magenta": "#b35b79", + "cyan": "#597b75", + "white": "#545464" + }, + "bright": { + "black": "#8a8980", + "red": "#d7474b", + "green": "#6e915f", + "yellow": "#836f4a", + "blue": "#6693bf", + "magenta": "#624c83", + "cyan": "#5e857a", + "white": "#43436c" + }, + "foreground": "#545464", + "background": "#f2ecbc", + "selectionFg": "#f2ecbc", + "selectionBg": "#c9cbd1", + "cursorText": "#f2ecbc", + "cursor": "#43436c" + } + } +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Noctalia-default/Noctalia-default.json b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Noctalia-default/Noctalia-default.json new file mode 100644 index 0000000..16ffab4 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Noctalia-default/Noctalia-default.json @@ -0,0 +1,94 @@ +{ + "dark": { + "mPrimary": "#fff59b", + "mOnPrimary": "#0e0e43", + "mSecondary": "#a9aefe", + "mOnSecondary": "#0e0e43", + "mTertiary": "#9BFECE", + "mOnTertiary": "#0e0e43", + "mError": "#FD4663", + "mOnError": "#0e0e43", + "mSurface": "#070722", + "mOnSurface": "#f3edf7", + "mSurfaceVariant": "#11112d", + "mOnSurfaceVariant": "#7c80b4", + "mOutline": "#21215F", + "mShadow": "#070722", + "mHover": "#9BFECE", + "mOnHover": "#0e0e43", + "terminal": { + "normal": { + "black": "#11112d", + "red": "#FD4663", + "green": "#9BFECE", + "yellow": "#fff59b", + "blue": "#a9aefe", + "magenta": "#FD4663", + "cyan": "#9BFECE", + "white": "#f3edf7" + }, + "bright": { + "black": "#21215F", + "red": "#FD4663", + "green": "#9BFECE", + "yellow": "#fff59b", + "blue": "#a9aefe", + "magenta": "#FD4663", + "cyan": "#9BFECE", + "white": "#ffffff" + }, + "foreground": "#f3edf7", + "background": "#070722", + "selectionFg": "#070722", + "selectionBg": "#f3edf7", + "cursorText": "#070722", + "cursor": "#fff59b" + } + }, + "light": { + "mPrimary": "#5d65f5", + "mOnPrimary": "#dadcff", + "mSecondary": "#8E93D8", + "mOnSecondary": "#dadcff", + "mTertiary": "#0e0e43", + "mOnTertiary": "#fef29a", + "mError": "#FD4663", + "mOnError": "#0e0e43", + "mSurface": "#e6e8fa", + "mOnSurface": "#0e0e43", + "mSurfaceVariant": "#eff0ff", + "mOnSurfaceVariant": "#4b55c8", + "mOutline": "#8288fc", + "mShadow": "#f3edf7", + "mHover": "#0e0e43", + "mOnHover": "#fef29a", + "terminal": { + "normal": { + "black": "#eff0ff", + "red": "#FD4663", + "green": "#0e0e43", + "yellow": "#5d65f5", + "blue": "#8E93D8", + "magenta": "#FD4663", + "cyan": "#0e0e43", + "white": "#4b55c8" + }, + "bright": { + "black": "#8288fc", + "red": "#FD4663", + "green": "#0e0e43", + "yellow": "#5d65f5", + "blue": "#8E93D8", + "magenta": "#FD4663", + "cyan": "#0e0e43", + "white": "#0e0e43" + }, + "foreground": "#4b55c8", + "background": "#e6e8fa", + "selectionFg": "#e6e8fa", + "selectionBg": "#4b55c8", + "cursorText": "#e6e8fa", + "cursor": "#5d65f5" + } + } +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Nord/Nord.json b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Nord/Nord.json new file mode 100644 index 0000000..9c3dbda --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Nord/Nord.json @@ -0,0 +1,94 @@ +{ + "dark": { + "mPrimary": "#8fbcbb", + "mOnPrimary": "#2e3440", + "mSecondary": "#88c0d0", + "mOnSecondary": "#2e3440", + "mTertiary": "#5e81ac", + "mOnTertiary": "#2e3440", + "mError": "#bf616a", + "mOnError": "#2e3440", + "mSurface": "#2e3440", + "mOnSurface": "#eceff4", + "mSurfaceVariant": "#3b4252", + "mOnSurfaceVariant": "#d8dee9", + "mOutline": "#505a70", + "mShadow": "#2e3440", + "mHover": "#5e81ac", + "mOnHover": "#2e3440", + "terminal": { + "normal": { + "black": "#3b4252", + "red": "#bf616a", + "green": "#a3be8c", + "yellow": "#ebcb8b", + "blue": "#81a1c1", + "magenta": "#b48ead", + "cyan": "#88c0d0", + "white": "#e5e9f0" + }, + "bright": { + "black": "#596377", + "red": "#bf616a", + "green": "#a3be8c", + "yellow": "#ebcb8b", + "blue": "#81a1c1", + "magenta": "#b48ead", + "cyan": "#8fbcbb", + "white": "#eceff4" + }, + "foreground": "#d8dee9", + "background": "#2e3440", + "selectionFg": "#4c566a", + "selectionBg": "#eceff4", + "cursorText": "#282828", + "cursor": "#eceff4" + } + }, + "light": { + "mPrimary": "#5e81ac", + "mOnPrimary": "#eceff4", + "mSecondary": "#64adc2", + "mOnSecondary": "#eceff4", + "mTertiary": "#6fa9a8", + "mOnTertiary": "#eceff4", + "mError": "#bf616a", + "mOnError": "#eceff4", + "mSurface": "#eceff4", + "mOnSurface": "#2e3440", + "mSurfaceVariant": "#e5e9f0", + "mOnSurfaceVariant": "#4c566a", + "mOutline": "#c5cedd", + "mShadow": "#d8dee9", + "mHover": "#6fa9a8", + "mOnHover": "#eceff4", + "terminal": { + "normal": { + "black": "#3b4252", + "red": "#bf616a", + "green": "#96b17f", + "yellow": "#c5a565", + "blue": "#81a1c1", + "magenta": "#b48ead", + "cyan": "#7bb3c3", + "white": "#a5abb6" + }, + "bright": { + "black": "#4c566a", + "red": "#bf616a", + "green": "#96b17f", + "yellow": "#c5a565", + "blue": "#81a1c1", + "magenta": "#b48ead", + "cyan": "#82afae", + "white": "#eceff4" + }, + "foreground": "#414858", + "background": "#eceff4", + "selectionFg": "#4c556a", + "selectionBg": "#d8dee9", + "cursorText": "#3b4252", + "cursor": "#7bb3c3" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Rosepine/Rosepine.json b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Rosepine/Rosepine.json new file mode 100644 index 0000000..ffb4fc2 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Rosepine/Rosepine.json @@ -0,0 +1,94 @@ +{ + "dark": { + "mPrimary": "#ebbcba", + "mOnPrimary": "#191724", + "mSecondary": "#9ccfd8", + "mOnSecondary": "#191724", + "mTertiary": "#31748f", + "mOnTertiary": "#e0def4", + "mError": "#eb6f92", + "mOnError": "#191724", + "mSurface": "#191724", + "mOnSurface": "#e0def4", + "mSurfaceVariant": "#26233a", + "mOnSurfaceVariant": "#908caa", + "mOutline": "#403d52", + "mShadow": "#191724", + "mHover": "#524f67", + "mOnHover": "#e0def4", + "terminal": { + "normal": { + "black": "#26233a", + "red": "#eb6f92", + "green": "#31748f", + "yellow": "#f6c177", + "blue": "#9ccfd8", + "magenta": "#c4a7e7", + "cyan": "#ebbcba", + "white": "#e0def4" + }, + "bright": { + "black": "#6e6a86", + "red": "#eb6f92", + "green": "#31748f", + "yellow": "#f6c177", + "blue": "#9ccfd8", + "magenta": "#c4a7e7", + "cyan": "#ebbcba", + "white": "#e0def4" + }, + "foreground": "#e0def4", + "background": "#191724", + "selectionFg": "#e0def4", + "selectionBg": "#403d52", + "cursorText": "#191724", + "cursor": "#e0def4" + } + }, + "light": { + "mPrimary": "#d7827e", + "mOnPrimary": "#faf4ed", + "mSecondary": "#56949f", + "mOnSecondary": "#faf4ed", + "mTertiary": "#286983", + "mOnTertiary": "#faf4ed", + "mError": "#b4637a", + "mOnError": "#faf4ed", + "mSurface": "#fffaf3", + "mOnSurface": "#575279", + "mSurfaceVariant": "#f2e9e1", + "mOnSurfaceVariant": "#797593", + "mOutline": "#dfdad9", + "mShadow": "#faf4ed", + "mHover": "#cecacd", + "mOnHover": "#575279", + "terminal": { + "normal": { + "black": "#f2e9e1", + "red": "#b4637a", + "green": "#286983", + "yellow": "#ea9d34", + "blue": "#56949f", + "magenta": "#907aa9", + "cyan": "#d7827e", + "white": "#575279" + }, + "bright": { + "black": "#9893a5", + "red": "#b4637a", + "green": "#286983", + "yellow": "#ea9d34", + "blue": "#56949f", + "magenta": "#907aa9", + "cyan": "#d7827e", + "white": "#575279" + }, + "foreground": "#575279", + "background": "#faf4ed", + "selectionFg": "#575279", + "selectionBg": "#dfdad9", + "cursorText": "#faf4ed", + "cursor": "#575279" + } + } +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Tokyo-Night/Tokyo-Night.json b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Tokyo-Night/Tokyo-Night.json new file mode 100644 index 0000000..ae96d38 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/ColorScheme/Tokyo-Night/Tokyo-Night.json @@ -0,0 +1,94 @@ +{ + "dark": { + "mPrimary": "#7aa2f7", + "mOnPrimary": "#16161e", + "mSecondary": "#bb9af7", + "mOnSecondary": "#16161e", + "mTertiary": "#9ece6a", + "mOnTertiary": "#16161e", + "mError": "#f7768e", + "mOnError": "#16161e", + "mSurface": "#1a1b26", + "mOnSurface": "#c0caf5", + "mSurfaceVariant": "#24283b", + "mOnSurfaceVariant": "#9aa5ce", + "mOutline": "#353D57", + "mShadow": "#15161e", + "mHover": "#9ece6a", + "mOnHover": "#16161e", + "terminal": { + "normal": { + "black": "#15161e", + "red": "#f7768e", + "green": "#9ece6a", + "yellow": "#e0af68", + "blue": "#7aa2f7", + "magenta": "#bb9af7", + "cyan": "#7dcfff", + "white": "#a9b1d6" + }, + "bright": { + "black": "#414868", + "red": "#f7768e", + "green": "#9ece6a", + "yellow": "#e0af68", + "blue": "#7aa2f7", + "magenta": "#bb9af7", + "cyan": "#7dcfff", + "white": "#c0caf5" + }, + "foreground": "#c0caf5", + "background": "#1a1b26", + "selectionFg": "#c0caf5", + "selectionBg": "#283457", + "cursorText": "#1a1b26", + "cursor": "#c0caf5" + } + }, + "light": { + "mPrimary": "#2e7de9", + "mOnPrimary": "#e1e2e7", + "mSecondary": "#9854f1", + "mOnSecondary": "#e1e2e7", + "mTertiary": "#587539", + "mOnTertiary": "#e1e2e7", + "mError": "#f52a65", + "mOnError": "#e1e2e7", + "mSurface": "#e1e2e7", + "mOnSurface": "#3760bf", + "mSurfaceVariant": "#d0d5e3", + "mOnSurfaceVariant": "#6172b0", + "mOutline": "#b4b5b9", + "mShadow": "#a8aecb", + "mHover": "#587539", + "mOnHover": "#e1e2e7", + "terminal": { + "normal": { + "black": "#e9e9ed", + "red": "#f52a65", + "green": "#587539", + "yellow": "#8c6c3e", + "blue": "#2e7de9", + "magenta": "#9854f1", + "cyan": "#007197", + "white": "#6172b0" + }, + "bright": { + "black": "#a1a6c5", + "red": "#f52a65", + "green": "#587539", + "yellow": "#8c6c3e", + "blue": "#2e7de9", + "magenta": "#9854f1", + "cyan": "#007197", + "white": "#3760bf" + }, + "foreground": "#3760bf", + "background": "#e1e2e7", + "selectionFg": "#3760bf", + "selectionBg": "#99a7df", + "cursorText": "#e1e2e7", + "cursor": "#3760bf" + } + } +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Fonts/tabler/noctalia-tabler-icons.ttf b/arch/.config/quickshell/noctalia-shell/Assets/Fonts/tabler/noctalia-tabler-icons.ttf new file mode 100644 index 0000000..1327521 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Assets/Fonts/tabler/noctalia-tabler-icons.ttf differ diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Fonts/tabler/tabler-icons-license.txt b/arch/.config/quickshell/noctalia-shell/Assets/Fonts/tabler/tabler-icons-license.txt new file mode 100644 index 0000000..f08aa1c --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Fonts/tabler/tabler-icons-license.txt @@ -0,0 +1,16 @@ +Tabler Licenses - Detailed Usage Rights and Guidelines + +This is a legal agreement between you, the Purchaser, and Tabler. Purchasing or downloading of any Tabler product (Tabler Admin Template, Tabler Icons, Tabler Emails, Tabler Illustrations), constitutes your acceptance of the terms of this license, Tabler terms of service and Tabler private policy. + +Tabler Admin Template and Tabler Icons License* +Tabler Admin Template and Tabler Icons are available under MIT License. + +Copyright (c) 2018-2025 Tabler + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the โ€œSoftwareโ€), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED โ€œAS ISโ€, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +See more at Tabler Admin Template MIT License See more at Tabler Icons MIT License \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Launcher/emoji.json b/arch/.config/quickshell/noctalia-shell/Assets/Launcher/emoji.json new file mode 100644 index 0000000..84b176f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Launcher/emoji.json @@ -0,0 +1,11222 @@ +[ + { + "emoji": "๐Ÿ˜€", + "name": "grinning face", + "keywords": ["smile", "happy"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ƒ", + "name": "grinning face with big eyes", + "keywords": ["happy", "joy", "haha"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜„", + "name": "grinning face with smiling eyes", + "keywords": ["happy", "joy", "laugh", "pleased"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜", + "name": "beaming face with smiling eyes", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜†", + "name": "grinning squinting face", + "keywords": ["happy", "haha"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜…", + "name": "grinning face with sweat", + "keywords": ["hot"], + "category": "people" + }, + { + "emoji": "๐Ÿคฃ", + "name": "rolling on the floor laughing", + "keywords": ["lol", "laughing"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜‚", + "name": "face with tears of joy", + "keywords": ["tears"], + "category": "people" + }, + { + "emoji": "๐Ÿ™‚", + "name": "slightly smiling face", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ™ƒ", + "name": "upside-down face", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿซ ", + "name": "melting face", + "keywords": ["sarcasm", "dread"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜‰", + "name": "winking face", + "keywords": ["flirt"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜Š", + "name": "smiling face with smiling eyes", + "keywords": ["proud"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜‡", + "name": "smiling face with halo", + "keywords": ["angel"], + "category": "people" + }, + { + "emoji": "๐Ÿฅฐ", + "name": "smiling face with hearts", + "keywords": ["love"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜", + "name": "smiling face with heart-eyes", + "keywords": ["love", "crush"], + "category": "people" + }, + { + "emoji": "๐Ÿคฉ", + "name": "star-struck", + "keywords": ["eyes"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜˜", + "name": "face blowing a kiss", + "keywords": ["flirt"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜—", + "name": "kissing face", + "keywords": [], + "category": "people" + }, + { + "emoji": "โ˜บ๏ธ", + "name": "smiling face", + "keywords": ["blush", "pleased"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜š", + "name": "kissing face with closed eyes", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜™", + "name": "kissing face with smiling eyes", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿฅฒ", + "name": "smiling face with tear", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜‹", + "name": "face savoring food", + "keywords": ["tongue", "lick"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜›", + "name": "face with tongue", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜œ", + "name": "winking face with tongue", + "keywords": ["prank", "silly"], + "category": "people" + }, + { + "emoji": "๐Ÿคช", + "name": "zany face", + "keywords": ["goofy", "wacky"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜", + "name": "squinting face with tongue", + "keywords": ["prank"], + "category": "people" + }, + { + "emoji": "๐Ÿค‘", + "name": "money-mouth face", + "keywords": ["rich"], + "category": "people" + }, + { + "emoji": "๐Ÿค—", + "name": "smiling face with open hands", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคญ", + "name": "face with hand over mouth", + "keywords": ["quiet", "whoops"], + "category": "people" + }, + { + "emoji": "๐Ÿซข", + "name": "face with open eyes and hand over mouth", + "keywords": ["gasp", "shock"], + "category": "people" + }, + { + "emoji": "๐Ÿซฃ", + "name": "face with peeking eye", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคซ", + "name": "shushing face", + "keywords": ["silence", "quiet"], + "category": "people" + }, + { + "emoji": "๐Ÿค”", + "name": "thinking face", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿซก", + "name": "saluting face", + "keywords": ["respect"], + "category": "people" + }, + { + "emoji": "๐Ÿค", + "name": "zipper-mouth face", + "keywords": ["silence", "hush"], + "category": "people" + }, + { + "emoji": "๐Ÿคจ", + "name": "face with raised eyebrow", + "keywords": ["suspicious"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜", + "name": "neutral face", + "keywords": ["meh"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜‘", + "name": "expressionless face", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ถ", + "name": "face without mouth", + "keywords": ["mute", "silence"], + "category": "people" + }, + { + "emoji": "๐Ÿซฅ", + "name": "dotted line face", + "keywords": ["invisible"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ถโ€๐ŸŒซ๏ธ", + "name": "face in clouds", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜", + "name": "smirking face", + "keywords": ["smug"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜’", + "name": "unamused face", + "keywords": ["meh"], + "category": "people" + }, + { + "emoji": "๐Ÿ™„", + "name": "face with rolling eyes", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ฌ", + "name": "grimacing face", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ฎโ€๐Ÿ’จ", + "name": "face exhaling", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคฅ", + "name": "lying face", + "keywords": ["liar"], + "category": "people" + }, + { + "emoji": "๐Ÿซจ", + "name": "shaking face", + "keywords": ["shock"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜Œ", + "name": "relieved face", + "keywords": ["whew"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜”", + "name": "pensive face", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ช", + "name": "sleepy face", + "keywords": ["tired"], + "category": "people" + }, + { + "emoji": "๐Ÿคค", + "name": "drooling face", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ด", + "name": "sleeping face", + "keywords": ["zzz"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ท", + "name": "face with medical mask", + "keywords": ["sick", "ill"], + "category": "people" + }, + { + "emoji": "๐Ÿค’", + "name": "face with thermometer", + "keywords": ["sick"], + "category": "people" + }, + { + "emoji": "๐Ÿค•", + "name": "face with head-bandage", + "keywords": ["hurt"], + "category": "people" + }, + { + "emoji": "๐Ÿคข", + "name": "nauseated face", + "keywords": ["sick", "barf", "disgusted"], + "category": "people" + }, + { + "emoji": "๐Ÿคฎ", + "name": "face vomiting", + "keywords": ["barf", "sick"], + "category": "people" + }, + { + "emoji": "๐Ÿคง", + "name": "sneezing face", + "keywords": ["achoo", "sick"], + "category": "people" + }, + { + "emoji": "๐Ÿฅต", + "name": "hot face", + "keywords": ["heat", "sweating"], + "category": "people" + }, + { + "emoji": "๐Ÿฅถ", + "name": "cold face", + "keywords": ["freezing", "ice"], + "category": "people" + }, + { + "emoji": "๐Ÿฅด", + "name": "woozy face", + "keywords": ["groggy"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ต", + "name": "face with crossed-out eyes", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ตโ€๐Ÿ’ซ", + "name": "face with spiral eyes", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคฏ", + "name": "exploding head", + "keywords": ["mind", "blown"], + "category": "people" + }, + { + "emoji": "๐Ÿค ", + "name": "cowboy hat face", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿฅณ", + "name": "partying face", + "keywords": ["celebration", "birthday"], + "category": "people" + }, + { + "emoji": "๐Ÿฅธ", + "name": "disguised face", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜Ž", + "name": "smiling face with sunglasses", + "keywords": ["cool"], + "category": "people" + }, + { + "emoji": "๐Ÿค“", + "name": "nerd face", + "keywords": ["geek", "glasses"], + "category": "people" + }, + { + "emoji": "๐Ÿง", + "name": "face with monocle", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜•", + "name": "confused face", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿซค", + "name": "face with diagonal mouth", + "keywords": ["confused"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜Ÿ", + "name": "worried face", + "keywords": ["nervous"], + "category": "people" + }, + { + "emoji": "๐Ÿ™", + "name": "slightly frowning face", + "keywords": [], + "category": "people" + }, + { + "emoji": "โ˜น๏ธ", + "name": "frowning face", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ฎ", + "name": "face with open mouth", + "keywords": ["surprise", "impressed", "wow"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ฏ", + "name": "hushed face", + "keywords": ["silence", "speechless"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ฒ", + "name": "astonished face", + "keywords": ["amazed", "gasp"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ณ", + "name": "flushed face", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿฅบ", + "name": "pleading face", + "keywords": ["puppy", "eyes"], + "category": "people" + }, + { + "emoji": "๐Ÿฅน", + "name": "face holding back tears", + "keywords": ["tears", "gratitude"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ฆ", + "name": "frowning face with open mouth", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ง", + "name": "anguished face", + "keywords": ["stunned"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜จ", + "name": "fearful face", + "keywords": ["scared", "shocked", "oops"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ฐ", + "name": "anxious face with sweat", + "keywords": ["nervous"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ฅ", + "name": "sad but relieved face", + "keywords": ["phew", "sweat", "nervous"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ข", + "name": "crying face", + "keywords": ["sad", "tear"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ญ", + "name": "loudly crying face", + "keywords": ["sad", "cry", "bawling", "sob"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ฑ", + "name": "face screaming in fear", + "keywords": ["horror", "shocked"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜–", + "name": "confounded face", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ฃ", + "name": "persevering face", + "keywords": ["struggling"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ž", + "name": "disappointed face", + "keywords": ["sad"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜“", + "name": "downcast face with sweat", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ฉ", + "name": "weary face", + "keywords": ["tired"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ซ", + "name": "tired face", + "keywords": ["upset", "whine"], + "category": "people" + }, + { + "emoji": "๐Ÿฅฑ", + "name": "yawning face", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ค", + "name": "face with steam from nose", + "keywords": ["smug"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ก", + "name": "enraged face", + "keywords": ["angry"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ ", + "name": "angry face", + "keywords": ["mad", "annoyed"], + "category": "people" + }, + { + "emoji": "๐Ÿคฌ", + "name": "face with symbols on mouth", + "keywords": ["foul"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ˆ", + "name": "smiling face with horns", + "keywords": ["devil", "evil", "horns"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฟ", + "name": "angry face with horns", + "keywords": ["angry", "devil", "evil", "horns"], + "category": "people" + }, + { + "emoji": "๐Ÿ’€", + "name": "skull", + "keywords": ["dead", "danger", "poison"], + "category": "people" + }, + { + "emoji": "โ˜ ๏ธ", + "name": "skull and crossbones", + "keywords": ["danger", "pirate"], + "category": "people" + }, + { + "emoji": "๐Ÿ’ฉ", + "name": "pile of poo", + "keywords": ["crap"], + "category": "people" + }, + { + "emoji": "๐Ÿคก", + "name": "clown face", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘น", + "name": "ogre", + "keywords": ["monster"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘บ", + "name": "goblin", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ป", + "name": "ghost", + "keywords": ["halloween"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฝ", + "name": "alien", + "keywords": ["ufo"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘พ", + "name": "alien monster", + "keywords": ["game", "retro"], + "category": "people" + }, + { + "emoji": "๐Ÿค–", + "name": "robot", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜บ", + "name": "grinning cat", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ธ", + "name": "grinning cat with smiling eyes", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜น", + "name": "cat with tears of joy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ป", + "name": "smiling cat with heart-eyes", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ผ", + "name": "cat with wry smile", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ฝ", + "name": "kissing cat", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ™€", + "name": "weary cat", + "keywords": ["horror"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜ฟ", + "name": "crying cat", + "keywords": ["sad", "tear"], + "category": "people" + }, + { + "emoji": "๐Ÿ˜พ", + "name": "pouting cat", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ™ˆ", + "name": "see-no-evil monkey", + "keywords": ["monkey", "blind", "ignore"], + "category": "people" + }, + { + "emoji": "๐Ÿ™‰", + "name": "hear-no-evil monkey", + "keywords": ["monkey", "deaf"], + "category": "people" + }, + { + "emoji": "๐Ÿ™Š", + "name": "speak-no-evil monkey", + "keywords": ["monkey", "mute", "hush"], + "category": "people" + }, + { + "emoji": "๐Ÿ’Œ", + "name": "love letter", + "keywords": ["email", "envelope"], + "category": "people" + }, + { + "emoji": "๐Ÿ’˜", + "name": "heart with arrow", + "keywords": ["love", "heart"], + "category": "people" + }, + { + "emoji": "๐Ÿ’", + "name": "heart with ribbon", + "keywords": ["chocolates"], + "category": "people" + }, + { + "emoji": "๐Ÿ’–", + "name": "sparkling heart", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’—", + "name": "growing heart", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’“", + "name": "beating heart", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’ž", + "name": "revolving hearts", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’•", + "name": "two hearts", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’Ÿ", + "name": "heart decoration", + "keywords": [], + "category": "people" + }, + { + "emoji": "โฃ๏ธ", + "name": "heart exclamation", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’”", + "name": "broken heart", + "keywords": [], + "category": "people" + }, + { + "emoji": "โค๏ธโ€๐Ÿ”ฅ", + "name": "heart on fire", + "keywords": [], + "category": "people" + }, + { + "emoji": "โค๏ธโ€๐Ÿฉน", + "name": "mending heart", + "keywords": [], + "category": "people" + }, + { + "emoji": "โค๏ธ", + "name": "red heart", + "keywords": ["love"], + "category": "people" + }, + { + "emoji": "๐Ÿฉท", + "name": "pink heart", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿงก", + "name": "orange heart", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’›", + "name": "yellow heart", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’š", + "name": "green heart", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’™", + "name": "blue heart", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿฉต", + "name": "light blue heart", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’œ", + "name": "purple heart", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐ŸคŽ", + "name": "brown heart", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ–ค", + "name": "black heart", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿฉถ", + "name": "grey heart", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿค", + "name": "white heart", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’‹", + "name": "kiss mark", + "keywords": ["lipstick"], + "category": "people" + }, + { + "emoji": "๐Ÿ’ฏ", + "name": "hundred points", + "keywords": ["score", "perfect"], + "category": "people" + }, + { + "emoji": "๐Ÿ’ข", + "name": "anger symbol", + "keywords": ["angry"], + "category": "people" + }, + { + "emoji": "๐Ÿ’ฅ", + "name": "collision", + "keywords": ["explode"], + "category": "people" + }, + { + "emoji": "๐Ÿ’ซ", + "name": "dizzy", + "keywords": ["star"], + "category": "people" + }, + { + "emoji": "๐Ÿ’ฆ", + "name": "sweat droplets", + "keywords": ["water", "workout"], + "category": "people" + }, + { + "emoji": "๐Ÿ’จ", + "name": "dashing away", + "keywords": ["wind", "blow", "fast"], + "category": "people" + }, + { + "emoji": "๐Ÿ•ณ๏ธ", + "name": "hole", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’ฌ", + "name": "speech balloon", + "keywords": ["comment"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ", + "name": "eye in speech bubble", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ—จ๏ธ", + "name": "left speech bubble", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ—ฏ๏ธ", + "name": "right anger bubble", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’ญ", + "name": "thought balloon", + "keywords": ["thinking"], + "category": "people" + }, + { + "emoji": "๐Ÿ’ค", + "name": "ZZZ", + "keywords": ["sleeping"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘‹", + "name": "waving hand", + "keywords": ["goodbye"], + "category": "people" + }, + { + "emoji": "๐Ÿคš", + "name": "raised back of hand", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ–๏ธ", + "name": "hand with fingers splayed", + "keywords": [], + "category": "people" + }, + { + "emoji": "โœ‹", + "name": "raised hand", + "keywords": ["highfive", "stop"], + "category": "people" + }, + { + "emoji": "๐Ÿ––", + "name": "vulcan salute", + "keywords": ["prosper", "spock"], + "category": "people" + }, + { + "emoji": "๐Ÿซฑ", + "name": "rightwards hand", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿซฒ", + "name": "leftwards hand", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿซณ", + "name": "palm down hand", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿซด", + "name": "palm up hand", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿซท", + "name": "leftwards pushing hand", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿซธ", + "name": "rightwards pushing hand", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘Œ", + "name": "OK hand", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐ŸคŒ", + "name": "pinched fingers", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿค", + "name": "pinching hand", + "keywords": [], + "category": "people" + }, + { + "emoji": "โœŒ๏ธ", + "name": "victory hand", + "keywords": ["victory", "peace"], + "category": "people" + }, + { + "emoji": "๐Ÿคž", + "name": "crossed fingers", + "keywords": ["luck", "hopeful"], + "category": "people" + }, + { + "emoji": "๐Ÿซฐ", + "name": "hand with index finger and thumb crossed", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐ŸคŸ", + "name": "love-you gesture", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿค˜", + "name": "sign of the horns", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿค™", + "name": "call me hand", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ˆ", + "name": "backhand index pointing left", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘‰", + "name": "backhand index pointing right", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘†", + "name": "backhand index pointing up", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ–•", + "name": "middle finger", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘‡", + "name": "backhand index pointing down", + "keywords": [], + "category": "people" + }, + { + "emoji": "โ˜๏ธ", + "name": "index pointing up", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿซต", + "name": "index pointing at the viewer", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘", + "name": "thumbs up", + "keywords": ["approve", "ok"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘Ž", + "name": "thumbs down", + "keywords": ["disapprove", "bury"], + "category": "people" + }, + { + "emoji": "โœŠ", + "name": "raised fist", + "keywords": ["power"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘Š", + "name": "oncoming fist", + "keywords": ["attack"], + "category": "people" + }, + { + "emoji": "๐Ÿค›", + "name": "left-facing fist", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคœ", + "name": "right-facing fist", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘", + "name": "clapping hands", + "keywords": ["praise", "applause"], + "category": "people" + }, + { + "emoji": "๐Ÿ™Œ", + "name": "raising hands", + "keywords": ["hooray"], + "category": "people" + }, + { + "emoji": "๐Ÿซถ", + "name": "heart hands", + "keywords": ["love"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘", + "name": "open hands", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคฒ", + "name": "palms up together", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿค", + "name": "handshake", + "keywords": ["deal"], + "category": "people" + }, + { + "emoji": "๐Ÿ™", + "name": "folded hands", + "keywords": ["please", "hope", "wish"], + "category": "people" + }, + { + "emoji": "โœ๏ธ", + "name": "writing hand", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’…", + "name": "nail polish", + "keywords": ["beauty", "manicure"], + "category": "people" + }, + { + "emoji": "๐Ÿคณ", + "name": "selfie", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’ช", + "name": "flexed biceps", + "keywords": ["flex", "bicep", "strong", "workout"], + "category": "people" + }, + { + "emoji": "๐Ÿฆพ", + "name": "mechanical arm", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿฆฟ", + "name": "mechanical leg", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿฆต", + "name": "leg", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿฆถ", + "name": "foot", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘‚", + "name": "ear", + "keywords": ["hear", "sound", "listen"], + "category": "people" + }, + { + "emoji": "๐Ÿฆป", + "name": "ear with hearing aid", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ƒ", + "name": "nose", + "keywords": ["smell"], + "category": "people" + }, + { + "emoji": "๐Ÿง ", + "name": "brain", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿซ€", + "name": "anatomical heart", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿซ", + "name": "lungs", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿฆท", + "name": "tooth", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿฆด", + "name": "bone", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘€", + "name": "eyes", + "keywords": ["look", "see", "watch"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘๏ธ", + "name": "eye", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘…", + "name": "tongue", + "keywords": ["taste"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘„", + "name": "mouth", + "keywords": ["kiss"], + "category": "people" + }, + { + "emoji": "๐Ÿซฆ", + "name": "biting lip", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ถ", + "name": "baby", + "keywords": ["child", "newborn"], + "category": "people" + }, + { + "emoji": "๐Ÿง’", + "name": "child", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฆ", + "name": "boy", + "keywords": ["child"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ง", + "name": "girl", + "keywords": ["child"], + "category": "people" + }, + { + "emoji": "๐Ÿง‘", + "name": "person", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฑ", + "name": "person: blond hair", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จ", + "name": "man", + "keywords": ["mustache", "father", "dad"], + "category": "people" + }, + { + "emoji": "๐Ÿง”", + "name": "person: beard", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง”โ€โ™‚๏ธ", + "name": "man: beard", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง”โ€โ™€๏ธ", + "name": "woman: beard", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿฆฐ", + "name": "man: red hair", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿฆฑ", + "name": "man: curly hair", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿฆณ", + "name": "man: white hair", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿฆฒ", + "name": "man: bald", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉ", + "name": "woman", + "keywords": ["girls"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿฆฐ", + "name": "woman: red hair", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿฆฐ", + "name": "person: red hair", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿฆฑ", + "name": "woman: curly hair", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿฆฑ", + "name": "person: curly hair", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿฆณ", + "name": "woman: white hair", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿฆณ", + "name": "person: white hair", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿฆฒ", + "name": "woman: bald", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿฆฒ", + "name": "person: bald", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฑโ€โ™€๏ธ", + "name": "woman: blond hair", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฑโ€โ™‚๏ธ", + "name": "man: blond hair", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง“", + "name": "older person", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ด", + "name": "old man", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ต", + "name": "old woman", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ™", + "name": "person frowning", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ™โ€โ™‚๏ธ", + "name": "man frowning", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ™โ€โ™€๏ธ", + "name": "woman frowning", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ™Ž", + "name": "person pouting", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ™Žโ€โ™‚๏ธ", + "name": "man pouting", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ™Žโ€โ™€๏ธ", + "name": "woman pouting", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ™…", + "name": "person gesturing NO", + "keywords": ["stop", "halt", "denied"], + "category": "people" + }, + { + "emoji": "๐Ÿ™…โ€โ™‚๏ธ", + "name": "man gesturing NO", + "keywords": ["stop", "halt", "denied"], + "category": "people" + }, + { + "emoji": "๐Ÿ™…โ€โ™€๏ธ", + "name": "woman gesturing NO", + "keywords": ["stop", "halt", "denied"], + "category": "people" + }, + { + "emoji": "๐Ÿ™†", + "name": "person gesturing OK", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ™†โ€โ™‚๏ธ", + "name": "man gesturing OK", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ™†โ€โ™€๏ธ", + "name": "woman gesturing OK", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’", + "name": "person tipping hand", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’โ€โ™‚๏ธ", + "name": "man tipping hand", + "keywords": ["information"], + "category": "people" + }, + { + "emoji": "๐Ÿ’โ€โ™€๏ธ", + "name": "woman tipping hand", + "keywords": ["information"], + "category": "people" + }, + { + "emoji": "๐Ÿ™‹", + "name": "person raising hand", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ™‹โ€โ™‚๏ธ", + "name": "man raising hand", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ™‹โ€โ™€๏ธ", + "name": "woman raising hand", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง", + "name": "deaf person", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿงโ€โ™‚๏ธ", + "name": "deaf man", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿงโ€โ™€๏ธ", + "name": "deaf woman", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ™‡", + "name": "person bowing", + "keywords": ["respect", "thanks"], + "category": "people" + }, + { + "emoji": "๐Ÿ™‡โ€โ™‚๏ธ", + "name": "man bowing", + "keywords": ["respect", "thanks"], + "category": "people" + }, + { + "emoji": "๐Ÿ™‡โ€โ™€๏ธ", + "name": "woman bowing", + "keywords": ["respect", "thanks"], + "category": "people" + }, + { + "emoji": "๐Ÿคฆ", + "name": "person facepalming", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคฆโ€โ™‚๏ธ", + "name": "man facepalming", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคฆโ€โ™€๏ธ", + "name": "woman facepalming", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคท", + "name": "person shrugging", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคทโ€โ™‚๏ธ", + "name": "man shrugging", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคทโ€โ™€๏ธ", + "name": "woman shrugging", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€โš•๏ธ", + "name": "health worker", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€โš•๏ธ", + "name": "man health worker", + "keywords": ["doctor", "nurse"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€โš•๏ธ", + "name": "woman health worker", + "keywords": ["doctor", "nurse"], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐ŸŽ“", + "name": "student", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐ŸŽ“", + "name": "man student", + "keywords": ["graduation"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐ŸŽ“", + "name": "woman student", + "keywords": ["graduation"], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿซ", + "name": "teacher", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿซ", + "name": "man teacher", + "keywords": ["school", "professor"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿซ", + "name": "woman teacher", + "keywords": ["school", "professor"], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€โš–๏ธ", + "name": "judge", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€โš–๏ธ", + "name": "man judge", + "keywords": ["justice"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€โš–๏ธ", + "name": "woman judge", + "keywords": ["justice"], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐ŸŒพ", + "name": "farmer", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐ŸŒพ", + "name": "man farmer", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐ŸŒพ", + "name": "woman farmer", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿณ", + "name": "cook", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿณ", + "name": "man cook", + "keywords": ["chef"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿณ", + "name": "woman cook", + "keywords": ["chef"], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿ”ง", + "name": "mechanic", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ”ง", + "name": "man mechanic", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿ”ง", + "name": "woman mechanic", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿญ", + "name": "factory worker", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿญ", + "name": "man factory worker", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿญ", + "name": "woman factory worker", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿ’ผ", + "name": "office worker", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ’ผ", + "name": "man office worker", + "keywords": ["business"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿ’ผ", + "name": "woman office worker", + "keywords": ["business"], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿ”ฌ", + "name": "scientist", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ”ฌ", + "name": "man scientist", + "keywords": ["research"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿ”ฌ", + "name": "woman scientist", + "keywords": ["research"], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿ’ป", + "name": "technologist", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ’ป", + "name": "man technologist", + "keywords": ["coder"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿ’ป", + "name": "woman technologist", + "keywords": ["coder"], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐ŸŽค", + "name": "singer", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐ŸŽค", + "name": "man singer", + "keywords": ["rockstar"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐ŸŽค", + "name": "woman singer", + "keywords": ["rockstar"], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐ŸŽจ", + "name": "artist", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐ŸŽจ", + "name": "man artist", + "keywords": ["painter"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐ŸŽจ", + "name": "woman artist", + "keywords": ["painter"], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€โœˆ๏ธ", + "name": "pilot", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€โœˆ๏ธ", + "name": "man pilot", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€โœˆ๏ธ", + "name": "woman pilot", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿš€", + "name": "astronaut", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿš€", + "name": "man astronaut", + "keywords": ["space"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿš€", + "name": "woman astronaut", + "keywords": ["space"], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿš’", + "name": "firefighter", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿš’", + "name": "man firefighter", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿš’", + "name": "woman firefighter", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฎ", + "name": "police officer", + "keywords": ["law"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฎโ€โ™‚๏ธ", + "name": "man police officer", + "keywords": ["law", "cop"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฎโ€โ™€๏ธ", + "name": "woman police officer", + "keywords": ["law", "cop"], + "category": "people" + }, + { + "emoji": "๐Ÿ•ต๏ธ", + "name": "detective", + "keywords": ["sleuth"], + "category": "people" + }, + { + "emoji": "๐Ÿ•ต๏ธโ€โ™‚๏ธ", + "name": "man detective", + "keywords": ["sleuth"], + "category": "people" + }, + { + "emoji": "๐Ÿ•ต๏ธโ€โ™€๏ธ", + "name": "woman detective", + "keywords": ["sleuth"], + "category": "people" + }, + { + "emoji": "๐Ÿ’‚", + "name": "guard", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’‚โ€โ™‚๏ธ", + "name": "man guard", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’‚โ€โ™€๏ธ", + "name": "woman guard", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿฅท", + "name": "ninja", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ท", + "name": "construction worker", + "keywords": ["helmet"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ทโ€โ™‚๏ธ", + "name": "man construction worker", + "keywords": ["helmet"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ทโ€โ™€๏ธ", + "name": "woman construction worker", + "keywords": ["helmet"], + "category": "people" + }, + { + "emoji": "๐Ÿซ…", + "name": "person with crown", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคด", + "name": "prince", + "keywords": ["crown", "royal"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ธ", + "name": "princess", + "keywords": ["crown", "royal"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ณ", + "name": "person wearing turban", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ณโ€โ™‚๏ธ", + "name": "man wearing turban", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ณโ€โ™€๏ธ", + "name": "woman wearing turban", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฒ", + "name": "person with skullcap", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง•", + "name": "woman with headscarf", + "keywords": ["hijab"], + "category": "people" + }, + { + "emoji": "๐Ÿคต", + "name": "person in tuxedo", + "keywords": ["groom", "marriage", "wedding"], + "category": "people" + }, + { + "emoji": "๐Ÿคตโ€โ™‚๏ธ", + "name": "man in tuxedo", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคตโ€โ™€๏ธ", + "name": "woman in tuxedo", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฐ", + "name": "person with veil", + "keywords": ["marriage", "wedding"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฐโ€โ™‚๏ธ", + "name": "man with veil", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฐโ€โ™€๏ธ", + "name": "woman with veil", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคฐ", + "name": "pregnant woman", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿซƒ", + "name": "pregnant man", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿซ„", + "name": "pregnant person", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคฑ", + "name": "breast-feeding", + "keywords": ["nursing"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿผ", + "name": "woman feeding baby", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿผ", + "name": "man feeding baby", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿผ", + "name": "person feeding baby", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ผ", + "name": "baby angel", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐ŸŽ…", + "name": "Santa Claus", + "keywords": ["christmas"], + "category": "people" + }, + { + "emoji": "๐Ÿคถ", + "name": "Mrs. Claus", + "keywords": ["santa"], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐ŸŽ„", + "name": "mx claus", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿฆธ", + "name": "superhero", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿฆธโ€โ™‚๏ธ", + "name": "man superhero", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿฆธโ€โ™€๏ธ", + "name": "woman superhero", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿฆน", + "name": "supervillain", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿฆนโ€โ™‚๏ธ", + "name": "man supervillain", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿฆนโ€โ™€๏ธ", + "name": "woman supervillain", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง™", + "name": "mage", + "keywords": ["wizard"], + "category": "people" + }, + { + "emoji": "๐Ÿง™โ€โ™‚๏ธ", + "name": "man mage", + "keywords": ["wizard"], + "category": "people" + }, + { + "emoji": "๐Ÿง™โ€โ™€๏ธ", + "name": "woman mage", + "keywords": ["wizard"], + "category": "people" + }, + { + "emoji": "๐Ÿงš", + "name": "fairy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿงšโ€โ™‚๏ธ", + "name": "man fairy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿงšโ€โ™€๏ธ", + "name": "woman fairy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง›", + "name": "vampire", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง›โ€โ™‚๏ธ", + "name": "man vampire", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง›โ€โ™€๏ธ", + "name": "woman vampire", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿงœ", + "name": "merperson", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿงœโ€โ™‚๏ธ", + "name": "merman", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿงœโ€โ™€๏ธ", + "name": "mermaid", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง", + "name": "elf", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿงโ€โ™‚๏ธ", + "name": "man elf", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿงโ€โ™€๏ธ", + "name": "woman elf", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿงž", + "name": "genie", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿงžโ€โ™‚๏ธ", + "name": "man genie", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿงžโ€โ™€๏ธ", + "name": "woman genie", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐ŸงŸ", + "name": "zombie", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐ŸงŸโ€โ™‚๏ธ", + "name": "man zombie", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐ŸงŸโ€โ™€๏ธ", + "name": "woman zombie", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐ŸงŒ", + "name": "troll", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’†", + "name": "person getting massage", + "keywords": ["spa"], + "category": "people" + }, + { + "emoji": "๐Ÿ’†โ€โ™‚๏ธ", + "name": "man getting massage", + "keywords": ["spa"], + "category": "people" + }, + { + "emoji": "๐Ÿ’†โ€โ™€๏ธ", + "name": "woman getting massage", + "keywords": ["spa"], + "category": "people" + }, + { + "emoji": "๐Ÿ’‡", + "name": "person getting haircut", + "keywords": ["beauty"], + "category": "people" + }, + { + "emoji": "๐Ÿ’‡โ€โ™‚๏ธ", + "name": "man getting haircut", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’‡โ€โ™€๏ธ", + "name": "woman getting haircut", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿšถ", + "name": "person walking", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿšถโ€โ™‚๏ธ", + "name": "man walking", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿšถโ€โ™€๏ธ", + "name": "woman walking", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง", + "name": "person standing", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿงโ€โ™‚๏ธ", + "name": "man standing", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿงโ€โ™€๏ธ", + "name": "woman standing", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐ŸงŽ", + "name": "person kneeling", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐ŸงŽโ€โ™‚๏ธ", + "name": "man kneeling", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐ŸงŽโ€โ™€๏ธ", + "name": "woman kneeling", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿฆฏ", + "name": "person with white cane", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿฆฏ", + "name": "man with white cane", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿฆฏ", + "name": "woman with white cane", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿฆผ", + "name": "person in motorized wheelchair", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿฆผ", + "name": "man in motorized wheelchair", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿฆผ", + "name": "woman in motorized wheelchair", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿฆฝ", + "name": "person in manual wheelchair", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿฆฝ", + "name": "man in manual wheelchair", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿฆฝ", + "name": "woman in manual wheelchair", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿƒ", + "name": "person running", + "keywords": ["exercise", "workout", "marathon"], + "category": "people" + }, + { + "emoji": "๐Ÿƒโ€โ™‚๏ธ", + "name": "man running", + "keywords": ["exercise", "workout", "marathon"], + "category": "people" + }, + { + "emoji": "๐Ÿƒโ€โ™€๏ธ", + "name": "woman running", + "keywords": ["exercise", "workout", "marathon"], + "category": "people" + }, + { + "emoji": "๐Ÿ’ƒ", + "name": "woman dancing", + "keywords": ["dress"], + "category": "people" + }, + { + "emoji": "๐Ÿ•บ", + "name": "man dancing", + "keywords": ["dancer"], + "category": "people" + }, + { + "emoji": "๐Ÿ•ด๏ธ", + "name": "person in suit levitating", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฏ", + "name": "people with bunny ears", + "keywords": ["bunny"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฏโ€โ™‚๏ธ", + "name": "men with bunny ears", + "keywords": ["bunny"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฏโ€โ™€๏ธ", + "name": "women with bunny ears", + "keywords": ["bunny"], + "category": "people" + }, + { + "emoji": "๐Ÿง–", + "name": "person in steamy room", + "keywords": ["steamy"], + "category": "people" + }, + { + "emoji": "๐Ÿง–โ€โ™‚๏ธ", + "name": "man in steamy room", + "keywords": ["steamy"], + "category": "people" + }, + { + "emoji": "๐Ÿง–โ€โ™€๏ธ", + "name": "woman in steamy room", + "keywords": ["steamy"], + "category": "people" + }, + { + "emoji": "๐Ÿง—", + "name": "person climbing", + "keywords": ["bouldering"], + "category": "people" + }, + { + "emoji": "๐Ÿง—โ€โ™‚๏ธ", + "name": "man climbing", + "keywords": ["bouldering"], + "category": "people" + }, + { + "emoji": "๐Ÿง—โ€โ™€๏ธ", + "name": "woman climbing", + "keywords": ["bouldering"], + "category": "people" + }, + { + "emoji": "๐Ÿคบ", + "name": "person fencing", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‡", + "name": "horse racing", + "keywords": [], + "category": "people" + }, + { + "emoji": "โ›ท๏ธ", + "name": "skier", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‚", + "name": "snowboarder", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐ŸŒ๏ธ", + "name": "person golfing", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐ŸŒ๏ธโ€โ™‚๏ธ", + "name": "man golfing", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐ŸŒ๏ธโ€โ™€๏ธ", + "name": "woman golfing", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ„", + "name": "person surfing", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ„โ€โ™‚๏ธ", + "name": "man surfing", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ„โ€โ™€๏ธ", + "name": "woman surfing", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿšฃ", + "name": "person rowing boat", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿšฃโ€โ™‚๏ธ", + "name": "man rowing boat", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿšฃโ€โ™€๏ธ", + "name": "woman rowing boat", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐ŸŠ", + "name": "person swimming", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐ŸŠโ€โ™‚๏ธ", + "name": "man swimming", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐ŸŠโ€โ™€๏ธ", + "name": "woman swimming", + "keywords": [], + "category": "people" + }, + { + "emoji": "โ›น๏ธ", + "name": "person bouncing ball", + "keywords": ["basketball"], + "category": "people" + }, + { + "emoji": "โ›น๏ธโ€โ™‚๏ธ", + "name": "man bouncing ball", + "keywords": [], + "category": "people" + }, + { + "emoji": "โ›น๏ธโ€โ™€๏ธ", + "name": "woman bouncing ball", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‹๏ธ", + "name": "person lifting weights", + "keywords": ["gym", "workout"], + "category": "people" + }, + { + "emoji": "๐Ÿ‹๏ธโ€โ™‚๏ธ", + "name": "man lifting weights", + "keywords": ["gym", "workout"], + "category": "people" + }, + { + "emoji": "๐Ÿ‹๏ธโ€โ™€๏ธ", + "name": "woman lifting weights", + "keywords": ["gym", "workout"], + "category": "people" + }, + { + "emoji": "๐Ÿšด", + "name": "person biking", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿšดโ€โ™‚๏ธ", + "name": "man biking", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿšดโ€โ™€๏ธ", + "name": "woman biking", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿšต", + "name": "person mountain biking", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿšตโ€โ™‚๏ธ", + "name": "man mountain biking", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿšตโ€โ™€๏ธ", + "name": "woman mountain biking", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคธ", + "name": "person cartwheeling", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคธโ€โ™‚๏ธ", + "name": "man cartwheeling", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคธโ€โ™€๏ธ", + "name": "woman cartwheeling", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคผ", + "name": "people wrestling", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคผโ€โ™‚๏ธ", + "name": "men wrestling", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคผโ€โ™€๏ธ", + "name": "women wrestling", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคฝ", + "name": "person playing water polo", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคฝโ€โ™‚๏ธ", + "name": "man playing water polo", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคฝโ€โ™€๏ธ", + "name": "woman playing water polo", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคพ", + "name": "person playing handball", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคพโ€โ™‚๏ธ", + "name": "man playing handball", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคพโ€โ™€๏ธ", + "name": "woman playing handball", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคน", + "name": "person juggling", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคนโ€โ™‚๏ธ", + "name": "man juggling", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿคนโ€โ™€๏ธ", + "name": "woman juggling", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง˜", + "name": "person in lotus position", + "keywords": ["meditation"], + "category": "people" + }, + { + "emoji": "๐Ÿง˜โ€โ™‚๏ธ", + "name": "man in lotus position", + "keywords": ["meditation"], + "category": "people" + }, + { + "emoji": "๐Ÿง˜โ€โ™€๏ธ", + "name": "woman in lotus position", + "keywords": ["meditation"], + "category": "people" + }, + { + "emoji": "๐Ÿ›€", + "name": "person taking bath", + "keywords": ["shower"], + "category": "people" + }, + { + "emoji": "๐Ÿ›Œ", + "name": "person in bed", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘", + "name": "people holding hands", + "keywords": ["couple", "date"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ญ", + "name": "women holding hands", + "keywords": ["couple", "date"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ซ", + "name": "woman and man holding hands", + "keywords": ["date"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฌ", + "name": "men holding hands", + "keywords": ["couple", "date"], + "category": "people" + }, + { + "emoji": "๐Ÿ’", + "name": "kiss", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ", + "name": "kiss: woman, man", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ", + "name": "kiss: man, man", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ", + "name": "kiss: woman, woman", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ’‘", + "name": "couple with heart", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘จ", + "name": "couple with heart: woman, man", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€โค๏ธโ€๐Ÿ‘จ", + "name": "couple with heart: man, man", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘ฉ", + "name": "couple with heart: woman, woman", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ช", + "name": "family", + "keywords": ["home", "parents", "child"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ", + "name": "family: man, woman, boy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง", + "name": "family: man, woman, girl", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", + "name": "family: man, woman, girl, boy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", + "name": "family: man, woman, boy, boy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง", + "name": "family: man, woman, girl, girl", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆ", + "name": "family: man, man, boy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ง", + "name": "family: man, man, girl", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", + "name": "family: man, man, girl, boy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", + "name": "family: man, man, boy, boy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง", + "name": "family: man, man, girl, girl", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ", + "name": "family: woman, woman, boy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ง", + "name": "family: woman, woman, girl", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", + "name": "family: woman, woman, girl, boy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", + "name": "family: woman, woman, boy, boy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง", + "name": "family: woman, woman, girl, girl", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ‘ฆ", + "name": "family: man, boy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", + "name": "family: man, boy, boy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ‘ง", + "name": "family: man, girl", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", + "name": "family: man, girl, boy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง", + "name": "family: man, girl, girl", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘ฆ", + "name": "family: woman, boy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", + "name": "family: woman, boy, boy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘ง", + "name": "family: woman, girl", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", + "name": "family: woman, girl, boy", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง", + "name": "family: woman, girl, girl", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ—ฃ๏ธ", + "name": "speaking head", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ค", + "name": "bust in silhouette", + "keywords": ["user"], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฅ", + "name": "busts in silhouette", + "keywords": ["users", "group", "team"], + "category": "people" + }, + { + "emoji": "๐Ÿซ‚", + "name": "people hugging", + "keywords": [], + "category": "people" + }, + { + "emoji": "๐Ÿ‘ฃ", + "name": "footprints", + "keywords": ["feet", "tracks"], + "category": "people" + }, + { + "emoji": "๐Ÿต", + "name": "monkey face", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ’", + "name": "monkey", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ", + "name": "gorilla", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆง", + "name": "orangutan", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿถ", + "name": "dog face", + "keywords": ["pet"], + "category": "animals" + }, + { + "emoji": "๐Ÿ•", + "name": "dog", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆฎ", + "name": "guide dog", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ•โ€๐Ÿฆบ", + "name": "service dog", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฉ", + "name": "poodle", + "keywords": ["dog"], + "category": "animals" + }, + { + "emoji": "๐Ÿบ", + "name": "wolf", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐ŸฆŠ", + "name": "fox", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ", + "name": "raccoon", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฑ", + "name": "cat face", + "keywords": ["pet"], + "category": "animals" + }, + { + "emoji": "๐Ÿˆ", + "name": "cat", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿˆโ€โฌ›", + "name": "black cat", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ", + "name": "lion", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฏ", + "name": "tiger face", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ…", + "name": "tiger", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ†", + "name": "leopard", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿด", + "name": "horse face", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐ŸซŽ", + "name": "moose", + "keywords": ["canada"], + "category": "animals" + }, + { + "emoji": "๐Ÿซ", + "name": "donkey", + "keywords": ["mule"], + "category": "animals" + }, + { + "emoji": "๐ŸŽ", + "name": "horse", + "keywords": ["speed"], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ„", + "name": "unicorn", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ“", + "name": "zebra", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐ŸฆŒ", + "name": "deer", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆฌ", + "name": "bison", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฎ", + "name": "cow face", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ‚", + "name": "ox", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿƒ", + "name": "water buffalo", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ„", + "name": "cow", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿท", + "name": "pig face", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ–", + "name": "pig", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ—", + "name": "boar", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฝ", + "name": "pig nose", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ", + "name": "ram", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ‘", + "name": "ewe", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ", + "name": "goat", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿช", + "name": "camel", + "keywords": ["desert"], + "category": "animals" + }, + { + "emoji": "๐Ÿซ", + "name": "two-hump camel", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ™", + "name": "llama", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ’", + "name": "giraffe", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ˜", + "name": "elephant", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆฃ", + "name": "mammoth", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ", + "name": "rhinoceros", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ›", + "name": "hippopotamus", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿญ", + "name": "mouse face", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ", + "name": "mouse", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ€", + "name": "rat", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿน", + "name": "hamster", + "keywords": ["pet"], + "category": "animals" + }, + { + "emoji": "๐Ÿฐ", + "name": "rabbit face", + "keywords": ["bunny"], + "category": "animals" + }, + { + "emoji": "๐Ÿ‡", + "name": "rabbit", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฟ๏ธ", + "name": "chipmunk", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆซ", + "name": "beaver", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ”", + "name": "hedgehog", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ‡", + "name": "bat", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿป", + "name": "bear", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿปโ€โ„๏ธ", + "name": "polar bear", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿจ", + "name": "koala", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿผ", + "name": "panda", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆฅ", + "name": "sloth", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆฆ", + "name": "otter", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆจ", + "name": "skunk", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ˜", + "name": "kangaroo", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆก", + "name": "badger", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿพ", + "name": "paw prints", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆƒ", + "name": "turkey", + "keywords": ["thanksgiving"], + "category": "animals" + }, + { + "emoji": "๐Ÿ”", + "name": "chicken", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ“", + "name": "rooster", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฃ", + "name": "hatching chick", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿค", + "name": "baby chick", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฅ", + "name": "front-facing baby chick", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ", + "name": "bird", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿง", + "name": "penguin", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ•Š๏ธ", + "name": "dove", + "keywords": ["peace"], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ…", + "name": "eagle", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ†", + "name": "duck", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆข", + "name": "swan", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ‰", + "name": "owl", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆค", + "name": "dodo", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿชถ", + "name": "feather", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆฉ", + "name": "flamingo", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆš", + "name": "peacock", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆœ", + "name": "parrot", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿชฝ", + "name": "wing", + "keywords": ["fly"], + "category": "animals" + }, + { + "emoji": "๐Ÿฆโ€โฌ›", + "name": "black bird", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿชฟ", + "name": "goose", + "keywords": ["honk"], + "category": "animals" + }, + { + "emoji": "๐Ÿธ", + "name": "frog", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐ŸŠ", + "name": "crocodile", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿข", + "name": "turtle", + "keywords": ["slow"], + "category": "animals" + }, + { + "emoji": "๐ŸฆŽ", + "name": "lizard", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ", + "name": "snake", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฒ", + "name": "dragon face", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ‰", + "name": "dragon", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ•", + "name": "sauropod", + "keywords": ["dinosaur"], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ–", + "name": "T-Rex", + "keywords": ["dinosaur"], + "category": "animals" + }, + { + "emoji": "๐Ÿณ", + "name": "spouting whale", + "keywords": ["sea"], + "category": "animals" + }, + { + "emoji": "๐Ÿ‹", + "name": "whale", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฌ", + "name": "dolphin", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆญ", + "name": "seal", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐ŸŸ", + "name": "fish", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ ", + "name": "tropical fish", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿก", + "name": "blowfish", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆˆ", + "name": "shark", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ™", + "name": "octopus", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿชผ", + "name": "jellyfish", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐ŸŒ", + "name": "snail", + "keywords": ["slow"], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ‹", + "name": "butterfly", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ›", + "name": "bug", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿœ", + "name": "ant", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ", + "name": "honeybee", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿชฒ", + "name": "beetle", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿž", + "name": "lady beetle", + "keywords": ["bug"], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ—", + "name": "cricket", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿชณ", + "name": "cockroach", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿ•ท๏ธ", + "name": "spider", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿฆ‚", + "name": "scorpion", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐ŸฆŸ", + "name": "mosquito", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿชฐ", + "name": "fly", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿชฑ", + "name": "worm", + "keywords": [], + "category": "animals" + }, + { + "emoji": "๐Ÿš", + "name": "spiral shell", + "keywords": ["sea", "beach"], + "category": "nature" + }, + { + "emoji": "๐Ÿชธ", + "name": "coral", + "keywords": [], + "category": "nature" + }, + { + "emoji": "๐Ÿ•ธ๏ธ", + "name": "spider web", + "keywords": [], + "category": "nature" + }, + { + "emoji": "๐Ÿฆ ", + "name": "microbe", + "keywords": ["germ"], + "category": "nature" + }, + { + "emoji": "๐Ÿ’", + "name": "bouquet", + "keywords": ["flowers"], + "category": "nature" + }, + { + "emoji": "๐ŸŒธ", + "name": "cherry blossom", + "keywords": ["flower", "spring"], + "category": "nature" + }, + { + "emoji": "๐Ÿ’ฎ", + "name": "white flower", + "keywords": [], + "category": "nature" + }, + { + "emoji": "๐Ÿชท", + "name": "lotus", + "keywords": [], + "category": "nature" + }, + { + "emoji": "๐Ÿต๏ธ", + "name": "rosette", + "keywords": [], + "category": "nature" + }, + { + "emoji": "๐ŸŒน", + "name": "rose", + "keywords": ["flower"], + "category": "nature" + }, + { + "emoji": "๐Ÿฅ€", + "name": "wilted flower", + "keywords": [], + "category": "nature" + }, + { + "emoji": "๐ŸŒบ", + "name": "hibiscus", + "keywords": [], + "category": "nature" + }, + { + "emoji": "๐ŸŒป", + "name": "sunflower", + "keywords": [], + "category": "nature" + }, + { + "emoji": "๐ŸŒผ", + "name": "blossom", + "keywords": [], + "category": "nature" + }, + { + "emoji": "๐ŸŒท", + "name": "tulip", + "keywords": ["flower"], + "category": "nature" + }, + { + "emoji": "๐Ÿชป", + "name": "hyacinth", + "keywords": [], + "category": "nature" + }, + { + "emoji": "๐ŸŒฑ", + "name": "seedling", + "keywords": ["plant"], + "category": "nature" + }, + { + "emoji": "๐Ÿชด", + "name": "potted plant", + "keywords": [], + "category": "nature" + }, + { + "emoji": "๐ŸŒฒ", + "name": "evergreen tree", + "keywords": ["wood"], + "category": "nature" + }, + { + "emoji": "๐ŸŒณ", + "name": "deciduous tree", + "keywords": ["wood"], + "category": "nature" + }, + { + "emoji": "๐ŸŒด", + "name": "palm tree", + "keywords": [], + "category": "nature" + }, + { + "emoji": "๐ŸŒต", + "name": "cactus", + "keywords": [], + "category": "nature" + }, + { + "emoji": "๐ŸŒพ", + "name": "sheaf of rice", + "keywords": [], + "category": "nature" + }, + { + "emoji": "๐ŸŒฟ", + "name": "herb", + "keywords": [], + "category": "nature" + }, + { + "emoji": "โ˜˜๏ธ", + "name": "shamrock", + "keywords": [], + "category": "nature" + }, + { + "emoji": "๐Ÿ€", + "name": "four leaf clover", + "keywords": ["luck"], + "category": "nature" + }, + { + "emoji": "๐Ÿ", + "name": "maple leaf", + "keywords": ["canada"], + "category": "nature" + }, + { + "emoji": "๐Ÿ‚", + "name": "fallen leaf", + "keywords": ["autumn"], + "category": "nature" + }, + { + "emoji": "๐Ÿƒ", + "name": "leaf fluttering in wind", + "keywords": ["leaf"], + "category": "nature" + }, + { + "emoji": "๐Ÿชน", + "name": "empty nest", + "keywords": [], + "category": "nature" + }, + { + "emoji": "๐Ÿชบ", + "name": "nest with eggs", + "keywords": [], + "category": "nature" + }, + { + "emoji": "๐Ÿ„", + "name": "mushroom", + "keywords": ["fungus"], + "category": "nature" + }, + { + "emoji": "๐Ÿ‡", + "name": "grapes", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿˆ", + "name": "melon", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿ‰", + "name": "watermelon", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐ŸŠ", + "name": "tangerine", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿ‹", + "name": "lemon", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐ŸŒ", + "name": "banana", + "keywords": ["fruit"], + "category": "food" + }, + { + "emoji": "๐Ÿ", + "name": "pineapple", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅญ", + "name": "mango", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐ŸŽ", + "name": "red apple", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿ", + "name": "green apple", + "keywords": ["fruit"], + "category": "food" + }, + { + "emoji": "๐Ÿ", + "name": "pear", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿ‘", + "name": "peach", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿ’", + "name": "cherries", + "keywords": ["fruit"], + "category": "food" + }, + { + "emoji": "๐Ÿ“", + "name": "strawberry", + "keywords": ["fruit"], + "category": "food" + }, + { + "emoji": "๐Ÿซ", + "name": "blueberries", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅ", + "name": "kiwi fruit", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿ…", + "name": "tomato", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿซ’", + "name": "olive", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅฅ", + "name": "coconut", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅ‘", + "name": "avocado", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿ†", + "name": "eggplant", + "keywords": ["aubergine"], + "category": "food" + }, + { + "emoji": "๐Ÿฅ”", + "name": "potato", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅ•", + "name": "carrot", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐ŸŒฝ", + "name": "ear of corn", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐ŸŒถ๏ธ", + "name": "hot pepper", + "keywords": ["spicy"], + "category": "food" + }, + { + "emoji": "๐Ÿซ‘", + "name": "bell pepper", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅ’", + "name": "cucumber", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅฌ", + "name": "leafy green", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅฆ", + "name": "broccoli", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿง„", + "name": "garlic", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿง…", + "name": "onion", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅœ", + "name": "peanuts", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿซ˜", + "name": "beans", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐ŸŒฐ", + "name": "chestnut", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿซš", + "name": "ginger root", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿซ›", + "name": "pea pod", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿž", + "name": "bread", + "keywords": ["toast"], + "category": "food" + }, + { + "emoji": "๐Ÿฅ", + "name": "croissant", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅ–", + "name": "baguette bread", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿซ“", + "name": "flatbread", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅจ", + "name": "pretzel", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅฏ", + "name": "bagel", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅž", + "name": "pancakes", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿง‡", + "name": "waffle", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿง€", + "name": "cheese wedge", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿ–", + "name": "meat on bone", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿ—", + "name": "poultry leg", + "keywords": ["meat", "chicken"], + "category": "food" + }, + { + "emoji": "๐Ÿฅฉ", + "name": "cut of meat", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅ“", + "name": "bacon", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿ”", + "name": "hamburger", + "keywords": ["burger"], + "category": "food" + }, + { + "emoji": "๐ŸŸ", + "name": "french fries", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿ•", + "name": "pizza", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐ŸŒญ", + "name": "hot dog", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅช", + "name": "sandwich", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐ŸŒฎ", + "name": "taco", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐ŸŒฏ", + "name": "burrito", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿซ”", + "name": "tamale", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅ™", + "name": "stuffed flatbread", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿง†", + "name": "falafel", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅš", + "name": "egg", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿณ", + "name": "cooking", + "keywords": ["breakfast"], + "category": "food" + }, + { + "emoji": "๐Ÿฅ˜", + "name": "shallow pan of food", + "keywords": ["paella", "curry"], + "category": "food" + }, + { + "emoji": "๐Ÿฒ", + "name": "pot of food", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿซ•", + "name": "fondue", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅฃ", + "name": "bowl with spoon", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅ—", + "name": "green salad", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฟ", + "name": "popcorn", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿงˆ", + "name": "butter", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿง‚", + "name": "salt", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅซ", + "name": "canned food", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฑ", + "name": "bento box", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿ˜", + "name": "rice cracker", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿ™", + "name": "rice ball", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿš", + "name": "cooked rice", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿ›", + "name": "curry rice", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿœ", + "name": "steaming bowl", + "keywords": ["noodle"], + "category": "food" + }, + { + "emoji": "๐Ÿ", + "name": "spaghetti", + "keywords": ["pasta"], + "category": "food" + }, + { + "emoji": "๐Ÿ ", + "name": "roasted sweet potato", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿข", + "name": "oden", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฃ", + "name": "sushi", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿค", + "name": "fried shrimp", + "keywords": ["tempura"], + "category": "food" + }, + { + "emoji": "๐Ÿฅ", + "name": "fish cake with swirl", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅฎ", + "name": "moon cake", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿก", + "name": "dango", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐ŸฅŸ", + "name": "dumpling", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅ ", + "name": "fortune cookie", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅก", + "name": "takeout box", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฆ€", + "name": "crab", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฆž", + "name": "lobster", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฆ", + "name": "shrimp", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฆ‘", + "name": "squid", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฆช", + "name": "oyster", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฆ", + "name": "soft ice cream", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿง", + "name": "shaved ice", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿจ", + "name": "ice cream", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฉ", + "name": "doughnut", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿช", + "name": "cookie", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐ŸŽ‚", + "name": "birthday cake", + "keywords": ["party"], + "category": "food" + }, + { + "emoji": "๐Ÿฐ", + "name": "shortcake", + "keywords": ["dessert"], + "category": "food" + }, + { + "emoji": "๐Ÿง", + "name": "cupcake", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅง", + "name": "pie", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿซ", + "name": "chocolate bar", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฌ", + "name": "candy", + "keywords": ["sweet"], + "category": "food" + }, + { + "emoji": "๐Ÿญ", + "name": "lollipop", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฎ", + "name": "custard", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฏ", + "name": "honey pot", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿผ", + "name": "baby bottle", + "keywords": ["milk"], + "category": "food" + }, + { + "emoji": "๐Ÿฅ›", + "name": "glass of milk", + "keywords": [], + "category": "food" + }, + { + "emoji": "โ˜•", + "name": "hot beverage", + "keywords": ["cafe", "espresso"], + "category": "food" + }, + { + "emoji": "๐Ÿซ–", + "name": "teapot", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿต", + "name": "teacup without handle", + "keywords": ["green", "breakfast"], + "category": "food" + }, + { + "emoji": "๐Ÿถ", + "name": "sake", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿพ", + "name": "bottle with popping cork", + "keywords": ["bottle", "bubbly", "celebration"], + "category": "food" + }, + { + "emoji": "๐Ÿท", + "name": "wine glass", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿธ", + "name": "cocktail glass", + "keywords": ["drink"], + "category": "food" + }, + { + "emoji": "๐Ÿน", + "name": "tropical drink", + "keywords": ["summer", "vacation"], + "category": "food" + }, + { + "emoji": "๐Ÿบ", + "name": "beer mug", + "keywords": ["drink"], + "category": "food" + }, + { + "emoji": "๐Ÿป", + "name": "clinking beer mugs", + "keywords": ["drinks"], + "category": "food" + }, + { + "emoji": "๐Ÿฅ‚", + "name": "clinking glasses", + "keywords": ["cheers", "toast"], + "category": "food" + }, + { + "emoji": "๐Ÿฅƒ", + "name": "tumbler glass", + "keywords": ["whisky"], + "category": "food" + }, + { + "emoji": "๐Ÿซ—", + "name": "pouring liquid", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅค", + "name": "cup with straw", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿง‹", + "name": "bubble tea", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿงƒ", + "name": "beverage box", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿง‰", + "name": "mate", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐ŸงŠ", + "name": "ice", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฅข", + "name": "chopsticks", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿฝ๏ธ", + "name": "fork and knife with plate", + "keywords": ["dining", "dinner"], + "category": "food" + }, + { + "emoji": "๐Ÿด", + "name": "fork and knife", + "keywords": ["cutlery"], + "category": "food" + }, + { + "emoji": "๐Ÿฅ„", + "name": "spoon", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿ”ช", + "name": "kitchen knife", + "keywords": ["cut", "chop"], + "category": "food" + }, + { + "emoji": "๐Ÿซ™", + "name": "jar", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐Ÿบ", + "name": "amphora", + "keywords": [], + "category": "food" + }, + { + "emoji": "๐ŸŒ", + "name": "globe showing Europe-Africa", + "keywords": ["globe", "world", "international"], + "category": "travel" + }, + { + "emoji": "๐ŸŒŽ", + "name": "globe showing Americas", + "keywords": ["globe", "world", "international"], + "category": "travel" + }, + { + "emoji": "๐ŸŒ", + "name": "globe showing Asia-Australia", + "keywords": ["globe", "world", "international"], + "category": "travel" + }, + { + "emoji": "๐ŸŒ", + "name": "globe with meridians", + "keywords": ["world", "global", "international"], + "category": "travel" + }, + { + "emoji": "๐Ÿ—บ๏ธ", + "name": "world map", + "keywords": ["travel"], + "category": "travel" + }, + { + "emoji": "๐Ÿ—พ", + "name": "map of Japan", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿงญ", + "name": "compass", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ”๏ธ", + "name": "snow-capped mountain", + "keywords": [], + "category": "travel" + }, + { + "emoji": "โ›ฐ๏ธ", + "name": "mountain", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒ‹", + "name": "volcano", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ—ป", + "name": "mount fuji", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•๏ธ", + "name": "camping", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ–๏ธ", + "name": "beach with umbrella", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿœ๏ธ", + "name": "desert", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ๏ธ", + "name": "desert island", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿž๏ธ", + "name": "national park", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŸ๏ธ", + "name": "stadium", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ›๏ธ", + "name": "classical building", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ—๏ธ", + "name": "building construction", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿงฑ", + "name": "brick", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿชจ", + "name": "rock", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿชต", + "name": "wood", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ›–", + "name": "hut", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ˜๏ธ", + "name": "houses", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš๏ธ", + "name": "derelict house", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ ", + "name": "house", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿก", + "name": "house with garden", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿข", + "name": "office building", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿฃ", + "name": "Japanese post office", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿค", + "name": "post office", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿฅ", + "name": "hospital", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿฆ", + "name": "bank", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿจ", + "name": "hotel", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿฉ", + "name": "love hotel", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿช", + "name": "convenience store", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿซ", + "name": "school", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿฌ", + "name": "department store", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿญ", + "name": "factory", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿฏ", + "name": "Japanese castle", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿฐ", + "name": "castle", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ’’", + "name": "wedding", + "keywords": ["marriage"], + "category": "travel" + }, + { + "emoji": "๐Ÿ—ผ", + "name": "Tokyo tower", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ—ฝ", + "name": "Statue of Liberty", + "keywords": [], + "category": "travel" + }, + { + "emoji": "โ›ช", + "name": "church", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•Œ", + "name": "mosque", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ›•", + "name": "hindu temple", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•", + "name": "synagogue", + "keywords": [], + "category": "travel" + }, + { + "emoji": "โ›ฉ๏ธ", + "name": "shinto shrine", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•‹", + "name": "kaaba", + "keywords": [], + "category": "travel" + }, + { + "emoji": "โ›ฒ", + "name": "fountain", + "keywords": [], + "category": "travel" + }, + { + "emoji": "โ›บ", + "name": "tent", + "keywords": ["camping"], + "category": "travel" + }, + { + "emoji": "๐ŸŒ", + "name": "foggy", + "keywords": ["karl"], + "category": "travel" + }, + { + "emoji": "๐ŸŒƒ", + "name": "night with stars", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ™๏ธ", + "name": "cityscape", + "keywords": ["skyline"], + "category": "travel" + }, + { + "emoji": "๐ŸŒ„", + "name": "sunrise over mountains", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒ…", + "name": "sunrise", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒ†", + "name": "cityscape at dusk", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒ‡", + "name": "sunset", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒ‰", + "name": "bridge at night", + "keywords": [], + "category": "travel" + }, + { + "emoji": "โ™จ๏ธ", + "name": "hot springs", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŽ ", + "name": "carousel horse", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ›", + "name": "playground slide", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŽก", + "name": "ferris wheel", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŽข", + "name": "roller coaster", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ’ˆ", + "name": "barber pole", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŽช", + "name": "circus tent", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš‚", + "name": "locomotive", + "keywords": ["train"], + "category": "travel" + }, + { + "emoji": "๐Ÿšƒ", + "name": "railway car", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš„", + "name": "high-speed train", + "keywords": ["train"], + "category": "travel" + }, + { + "emoji": "๐Ÿš…", + "name": "bullet train", + "keywords": ["train"], + "category": "travel" + }, + { + "emoji": "๐Ÿš†", + "name": "train", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš‡", + "name": "metro", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿšˆ", + "name": "light rail", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš‰", + "name": "station", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸšŠ", + "name": "tram", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš", + "name": "monorail", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿšž", + "name": "mountain railway", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš‹", + "name": "tram car", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸšŒ", + "name": "bus", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš", + "name": "oncoming bus", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸšŽ", + "name": "trolleybus", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš", + "name": "minibus", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš‘", + "name": "ambulance", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš’", + "name": "fire engine", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš“", + "name": "police car", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš”", + "name": "oncoming police car", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš•", + "name": "taxi", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš–", + "name": "oncoming taxi", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš—", + "name": "automobile", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš˜", + "name": "oncoming automobile", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš™", + "name": "sport utility vehicle", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ›ป", + "name": "pickup truck", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿšš", + "name": "delivery truck", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš›", + "name": "articulated lorry", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿšœ", + "name": "tractor", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŽ๏ธ", + "name": "racing car", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ๏ธ", + "name": "motorcycle", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ›ต", + "name": "motor scooter", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿฆฝ", + "name": "manual wheelchair", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿฆผ", + "name": "motorized wheelchair", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ›บ", + "name": "auto rickshaw", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿšฒ", + "name": "bicycle", + "keywords": ["bicycle"], + "category": "travel" + }, + { + "emoji": "๐Ÿ›ด", + "name": "kick scooter", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ›น", + "name": "skateboard", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ›ผ", + "name": "roller skate", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš", + "name": "bus stop", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ›ฃ๏ธ", + "name": "motorway", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ›ค๏ธ", + "name": "railway track", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ›ข๏ธ", + "name": "oil drum", + "keywords": [], + "category": "travel" + }, + { + "emoji": "โ›ฝ", + "name": "fuel pump", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ›ž", + "name": "wheel", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿšจ", + "name": "police car light", + "keywords": ["911", "emergency"], + "category": "travel" + }, + { + "emoji": "๐Ÿšฅ", + "name": "horizontal traffic light", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿšฆ", + "name": "vertical traffic light", + "keywords": ["semaphore"], + "category": "travel" + }, + { + "emoji": "๐Ÿ›‘", + "name": "stop sign", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿšง", + "name": "construction", + "keywords": ["wip"], + "category": "travel" + }, + { + "emoji": "โš“", + "name": "anchor", + "keywords": ["ship"], + "category": "travel" + }, + { + "emoji": "๐Ÿ›Ÿ", + "name": "ring buoy", + "keywords": ["life preserver"], + "category": "travel" + }, + { + "emoji": "โ›ต", + "name": "sailboat", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ›ถ", + "name": "canoe", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿšค", + "name": "speedboat", + "keywords": ["ship"], + "category": "travel" + }, + { + "emoji": "๐Ÿ›ณ๏ธ", + "name": "passenger ship", + "keywords": ["cruise"], + "category": "travel" + }, + { + "emoji": "โ›ด๏ธ", + "name": "ferry", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ›ฅ๏ธ", + "name": "motor boat", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿšข", + "name": "ship", + "keywords": [], + "category": "travel" + }, + { + "emoji": "โœˆ๏ธ", + "name": "airplane", + "keywords": ["flight"], + "category": "travel" + }, + { + "emoji": "๐Ÿ›ฉ๏ธ", + "name": "small airplane", + "keywords": ["flight"], + "category": "travel" + }, + { + "emoji": "๐Ÿ›ซ", + "name": "airplane departure", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ›ฌ", + "name": "airplane arrival", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿช‚", + "name": "parachute", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ’บ", + "name": "seat", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš", + "name": "helicopter", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸšŸ", + "name": "suspension railway", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿš ", + "name": "mountain cableway", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿšก", + "name": "aerial tramway", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ›ฐ๏ธ", + "name": "satellite", + "keywords": ["orbit", "space"], + "category": "travel" + }, + { + "emoji": "๐Ÿš€", + "name": "rocket", + "keywords": ["ship", "launch"], + "category": "travel" + }, + { + "emoji": "๐Ÿ›ธ", + "name": "flying saucer", + "keywords": ["ufo"], + "category": "travel" + }, + { + "emoji": "๐Ÿ›Ž๏ธ", + "name": "bellhop bell", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿงณ", + "name": "luggage", + "keywords": [], + "category": "travel" + }, + { + "emoji": "โŒ›", + "name": "hourglass done", + "keywords": ["time"], + "category": "travel" + }, + { + "emoji": "โณ", + "name": "hourglass not done", + "keywords": ["time"], + "category": "travel" + }, + { + "emoji": "โŒš", + "name": "watch", + "keywords": ["time"], + "category": "travel" + }, + { + "emoji": "โฐ", + "name": "alarm clock", + "keywords": ["morning"], + "category": "travel" + }, + { + "emoji": "โฑ๏ธ", + "name": "stopwatch", + "keywords": [], + "category": "travel" + }, + { + "emoji": "โฒ๏ธ", + "name": "timer clock", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•ฐ๏ธ", + "name": "mantelpiece clock", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•›", + "name": "twelve oโ€™clock", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•ง", + "name": "twelve-thirty", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•", + "name": "one oโ€™clock", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•œ", + "name": "one-thirty", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•‘", + "name": "two oโ€™clock", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•", + "name": "two-thirty", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•’", + "name": "three oโ€™clock", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•ž", + "name": "three-thirty", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•“", + "name": "four oโ€™clock", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•Ÿ", + "name": "four-thirty", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•”", + "name": "five oโ€™clock", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ• ", + "name": "five-thirty", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ••", + "name": "six oโ€™clock", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•ก", + "name": "six-thirty", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•–", + "name": "seven oโ€™clock", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•ข", + "name": "seven-thirty", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•—", + "name": "eight oโ€™clock", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•ฃ", + "name": "eight-thirty", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•˜", + "name": "nine oโ€™clock", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•ค", + "name": "nine-thirty", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•™", + "name": "ten oโ€™clock", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•ฅ", + "name": "ten-thirty", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•š", + "name": "eleven oโ€™clock", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ•ฆ", + "name": "eleven-thirty", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒ‘", + "name": "new moon", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒ’", + "name": "waxing crescent moon", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒ“", + "name": "first quarter moon", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒ”", + "name": "waxing gibbous moon", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒ•", + "name": "full moon", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒ–", + "name": "waning gibbous moon", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒ—", + "name": "last quarter moon", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒ˜", + "name": "waning crescent moon", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒ™", + "name": "crescent moon", + "keywords": ["night"], + "category": "travel" + }, + { + "emoji": "๐ŸŒš", + "name": "new moon face", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒ›", + "name": "first quarter moon face", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒœ", + "name": "last quarter moon face", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒก๏ธ", + "name": "thermometer", + "keywords": [], + "category": "travel" + }, + { + "emoji": "โ˜€๏ธ", + "name": "sun", + "keywords": ["weather"], + "category": "travel" + }, + { + "emoji": "๐ŸŒ", + "name": "full moon face", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒž", + "name": "sun with face", + "keywords": ["summer"], + "category": "travel" + }, + { + "emoji": "๐Ÿช", + "name": "ringed planet", + "keywords": [], + "category": "travel" + }, + { + "emoji": "โญ", + "name": "star", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒŸ", + "name": "glowing star", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒ ", + "name": "shooting star", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒŒ", + "name": "milky way", + "keywords": [], + "category": "travel" + }, + { + "emoji": "โ˜๏ธ", + "name": "cloud", + "keywords": [], + "category": "travel" + }, + { + "emoji": "โ›…", + "name": "sun behind cloud", + "keywords": ["weather", "cloud"], + "category": "travel" + }, + { + "emoji": "โ›ˆ๏ธ", + "name": "cloud with lightning and rain", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒค๏ธ", + "name": "sun behind small cloud", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒฅ๏ธ", + "name": "sun behind large cloud", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒฆ๏ธ", + "name": "sun behind rain cloud", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒง๏ธ", + "name": "cloud with rain", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒจ๏ธ", + "name": "cloud with snow", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒฉ๏ธ", + "name": "cloud with lightning", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒช๏ธ", + "name": "tornado", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒซ๏ธ", + "name": "fog", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒฌ๏ธ", + "name": "wind face", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒ€", + "name": "cyclone", + "keywords": ["swirl"], + "category": "travel" + }, + { + "emoji": "๐ŸŒˆ", + "name": "rainbow", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐ŸŒ‚", + "name": "closed umbrella", + "keywords": ["weather", "rain"], + "category": "travel" + }, + { + "emoji": "โ˜‚๏ธ", + "name": "umbrella", + "keywords": [], + "category": "travel" + }, + { + "emoji": "โ˜”", + "name": "umbrella with rain drops", + "keywords": ["rain", "weather"], + "category": "travel" + }, + { + "emoji": "โ›ฑ๏ธ", + "name": "umbrella on ground", + "keywords": ["beach_umbrella"], + "category": "travel" + }, + { + "emoji": "โšก", + "name": "high voltage", + "keywords": ["lightning", "thunder"], + "category": "travel" + }, + { + "emoji": "โ„๏ธ", + "name": "snowflake", + "keywords": ["winter", "cold", "weather"], + "category": "travel" + }, + { + "emoji": "โ˜ƒ๏ธ", + "name": "snowman", + "keywords": ["winter", "christmas"], + "category": "travel" + }, + { + "emoji": "โ›„", + "name": "snowman without snow", + "keywords": ["winter"], + "category": "travel" + }, + { + "emoji": "โ˜„๏ธ", + "name": "comet", + "keywords": [], + "category": "travel" + }, + { + "emoji": "๐Ÿ”ฅ", + "name": "fire", + "keywords": ["burn"], + "category": "travel" + }, + { + "emoji": "๐Ÿ’ง", + "name": "droplet", + "keywords": ["water"], + "category": "travel" + }, + { + "emoji": "๐ŸŒŠ", + "name": "water wave", + "keywords": ["sea"], + "category": "travel" + }, + { + "emoji": "๐ŸŽƒ", + "name": "jack-o-lantern", + "keywords": ["halloween"], + "category": "activity" + }, + { + "emoji": "๐ŸŽ„", + "name": "Christmas tree", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽ†", + "name": "fireworks", + "keywords": ["festival", "celebration"], + "category": "activity" + }, + { + "emoji": "๐ŸŽ‡", + "name": "sparkler", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿงจ", + "name": "firecracker", + "keywords": [], + "category": "activity" + }, + { + "emoji": "โœจ", + "name": "sparkles", + "keywords": ["shiny"], + "category": "activity" + }, + { + "emoji": "๐ŸŽˆ", + "name": "balloon", + "keywords": ["party", "birthday"], + "category": "activity" + }, + { + "emoji": "๐ŸŽ‰", + "name": "party popper", + "keywords": ["hooray", "party"], + "category": "activity" + }, + { + "emoji": "๐ŸŽŠ", + "name": "confetti ball", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽ‹", + "name": "tanabata tree", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽ", + "name": "pine decoration", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽŽ", + "name": "Japanese dolls", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽ", + "name": "carp streamer", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽ", + "name": "wind chime", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽ‘", + "name": "moon viewing ceremony", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿงง", + "name": "red envelope", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽ€", + "name": "ribbon", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽ", + "name": "wrapped gift", + "keywords": ["present", "birthday", "christmas"], + "category": "activity" + }, + { + "emoji": "๐ŸŽ—๏ธ", + "name": "reminder ribbon", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽŸ๏ธ", + "name": "admission tickets", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽซ", + "name": "ticket", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽ–๏ธ", + "name": "military medal", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿ†", + "name": "trophy", + "keywords": ["award", "contest", "winner"], + "category": "activity" + }, + { + "emoji": "๐Ÿ…", + "name": "sports medal", + "keywords": ["gold", "winner"], + "category": "activity" + }, + { + "emoji": "๐Ÿฅ‡", + "name": "1st place medal", + "keywords": ["gold"], + "category": "activity" + }, + { + "emoji": "๐Ÿฅˆ", + "name": "2nd place medal", + "keywords": ["silver"], + "category": "activity" + }, + { + "emoji": "๐Ÿฅ‰", + "name": "3rd place medal", + "keywords": ["bronze"], + "category": "activity" + }, + { + "emoji": "โšฝ", + "name": "soccer ball", + "keywords": ["sports"], + "category": "activity" + }, + { + "emoji": "โšพ", + "name": "baseball", + "keywords": ["sports"], + "category": "activity" + }, + { + "emoji": "๐ŸฅŽ", + "name": "softball", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿ€", + "name": "basketball", + "keywords": ["sports"], + "category": "activity" + }, + { + "emoji": "๐Ÿ", + "name": "volleyball", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿˆ", + "name": "american football", + "keywords": ["sports"], + "category": "activity" + }, + { + "emoji": "๐Ÿ‰", + "name": "rugby football", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽพ", + "name": "tennis", + "keywords": ["sports"], + "category": "activity" + }, + { + "emoji": "๐Ÿฅ", + "name": "flying disc", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽณ", + "name": "bowling", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿ", + "name": "cricket game", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿ‘", + "name": "field hockey", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿ’", + "name": "ice hockey", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿฅ", + "name": "lacrosse", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿ“", + "name": "ping pong", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿธ", + "name": "badminton", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸฅŠ", + "name": "boxing glove", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿฅ‹", + "name": "martial arts uniform", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿฅ…", + "name": "goal net", + "keywords": [], + "category": "activity" + }, + { + "emoji": "โ›ณ", + "name": "flag in hole", + "keywords": [], + "category": "activity" + }, + { + "emoji": "โ›ธ๏ธ", + "name": "ice skate", + "keywords": ["skating"], + "category": "activity" + }, + { + "emoji": "๐ŸŽฃ", + "name": "fishing pole", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿคฟ", + "name": "diving mask", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽฝ", + "name": "running shirt", + "keywords": ["marathon"], + "category": "activity" + }, + { + "emoji": "๐ŸŽฟ", + "name": "skis", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿ›ท", + "name": "sled", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸฅŒ", + "name": "curling stone", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽฏ", + "name": "bullseye", + "keywords": ["target"], + "category": "activity" + }, + { + "emoji": "๐Ÿช€", + "name": "yo-yo", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿช", + "name": "kite", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿ”ซ", + "name": "water pistol", + "keywords": ["shoot", "weapon"], + "category": "activity" + }, + { + "emoji": "๐ŸŽฑ", + "name": "pool 8 ball", + "keywords": ["pool", "billiards"], + "category": "activity" + }, + { + "emoji": "๐Ÿ”ฎ", + "name": "crystal ball", + "keywords": ["fortune"], + "category": "activity" + }, + { + "emoji": "๐Ÿช„", + "name": "magic wand", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽฎ", + "name": "video game", + "keywords": ["play", "controller", "console"], + "category": "activity" + }, + { + "emoji": "๐Ÿ•น๏ธ", + "name": "joystick", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽฐ", + "name": "slot machine", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽฒ", + "name": "game die", + "keywords": ["dice", "gambling"], + "category": "activity" + }, + { + "emoji": "๐Ÿงฉ", + "name": "puzzle piece", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿงธ", + "name": "teddy bear", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿช…", + "name": "piรฑata", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿชฉ", + "name": "mirror ball", + "keywords": ["disco", "party"], + "category": "activity" + }, + { + "emoji": "๐Ÿช†", + "name": "nesting dolls", + "keywords": [], + "category": "activity" + }, + { + "emoji": "โ™ ๏ธ", + "name": "spade suit", + "keywords": [], + "category": "activity" + }, + { + "emoji": "โ™ฅ๏ธ", + "name": "heart suit", + "keywords": [], + "category": "activity" + }, + { + "emoji": "โ™ฆ๏ธ", + "name": "diamond suit", + "keywords": [], + "category": "activity" + }, + { + "emoji": "โ™ฃ๏ธ", + "name": "club suit", + "keywords": [], + "category": "activity" + }, + { + "emoji": "โ™Ÿ๏ธ", + "name": "chess pawn", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿƒ", + "name": "joker", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿ€„", + "name": "mahjong red dragon", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽด", + "name": "flower playing cards", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽญ", + "name": "performing arts", + "keywords": ["theater", "drama"], + "category": "activity" + }, + { + "emoji": "๐Ÿ–ผ๏ธ", + "name": "framed picture", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐ŸŽจ", + "name": "artist palette", + "keywords": ["design", "paint"], + "category": "activity" + }, + { + "emoji": "๐Ÿงต", + "name": "thread", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿชก", + "name": "sewing needle", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿงถ", + "name": "yarn", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿชข", + "name": "knot", + "keywords": [], + "category": "activity" + }, + { + "emoji": "๐Ÿ‘“", + "name": "glasses", + "keywords": ["glasses"], + "category": "objects" + }, + { + "emoji": "๐Ÿ•ถ๏ธ", + "name": "sunglasses", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿฅฝ", + "name": "goggles", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿฅผ", + "name": "lab coat", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿฆบ", + "name": "safety vest", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ‘”", + "name": "necktie", + "keywords": ["shirt", "formal"], + "category": "objects" + }, + { + "emoji": "๐Ÿ‘•", + "name": "t-shirt", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ‘–", + "name": "jeans", + "keywords": ["pants"], + "category": "objects" + }, + { + "emoji": "๐Ÿงฃ", + "name": "scarf", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿงค", + "name": "gloves", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿงฅ", + "name": "coat", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿงฆ", + "name": "socks", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ‘—", + "name": "dress", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ‘˜", + "name": "kimono", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿฅป", + "name": "sari", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿฉฑ", + "name": "one-piece swimsuit", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿฉฒ", + "name": "briefs", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿฉณ", + "name": "shorts", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ‘™", + "name": "bikini", + "keywords": ["beach"], + "category": "objects" + }, + { + "emoji": "๐Ÿ‘š", + "name": "womanโ€™s clothes", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿชญ", + "name": "folding hand fan", + "keywords": ["sensu"], + "category": "objects" + }, + { + "emoji": "๐Ÿ‘›", + "name": "purse", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ‘œ", + "name": "handbag", + "keywords": ["bag"], + "category": "objects" + }, + { + "emoji": "๐Ÿ‘", + "name": "clutch bag", + "keywords": ["bag"], + "category": "objects" + }, + { + "emoji": "๐Ÿ›๏ธ", + "name": "shopping bags", + "keywords": ["bags"], + "category": "objects" + }, + { + "emoji": "๐ŸŽ’", + "name": "backpack", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿฉด", + "name": "thong sandal", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ‘ž", + "name": "manโ€™s shoe", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ‘Ÿ", + "name": "running shoe", + "keywords": ["sneaker", "sport", "running"], + "category": "objects" + }, + { + "emoji": "๐Ÿฅพ", + "name": "hiking boot", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿฅฟ", + "name": "flat shoe", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ‘ ", + "name": "high-heeled shoe", + "keywords": ["shoe"], + "category": "objects" + }, + { + "emoji": "๐Ÿ‘ก", + "name": "womanโ€™s sandal", + "keywords": ["shoe"], + "category": "objects" + }, + { + "emoji": "๐Ÿฉฐ", + "name": "ballet shoes", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ‘ข", + "name": "womanโ€™s boot", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿชฎ", + "name": "hair pick", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ‘‘", + "name": "crown", + "keywords": ["king", "queen", "royal"], + "category": "objects" + }, + { + "emoji": "๐Ÿ‘’", + "name": "womanโ€™s hat", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐ŸŽฉ", + "name": "top hat", + "keywords": ["hat", "classy"], + "category": "objects" + }, + { + "emoji": "๐ŸŽ“", + "name": "graduation cap", + "keywords": ["education", "college", "university", "graduation"], + "category": "objects" + }, + { + "emoji": "๐Ÿงข", + "name": "billed cap", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿช–", + "name": "military helmet", + "keywords": [], + "category": "objects" + }, + { + "emoji": "โ›‘๏ธ", + "name": "rescue workerโ€™s helmet", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ฟ", + "name": "prayer beads", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ’„", + "name": "lipstick", + "keywords": ["makeup"], + "category": "objects" + }, + { + "emoji": "๐Ÿ’", + "name": "ring", + "keywords": ["wedding", "marriage", "engaged"], + "category": "objects" + }, + { + "emoji": "๐Ÿ’Ž", + "name": "gem stone", + "keywords": ["diamond"], + "category": "objects" + }, + { + "emoji": "๐Ÿ”‡", + "name": "muted speaker", + "keywords": ["sound", "volume"], + "category": "objects" + }, + { + "emoji": "๐Ÿ”ˆ", + "name": "speaker low volume", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ”‰", + "name": "speaker medium volume", + "keywords": ["volume"], + "category": "objects" + }, + { + "emoji": "๐Ÿ”Š", + "name": "speaker high volume", + "keywords": ["volume"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ข", + "name": "loudspeaker", + "keywords": ["announcement"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ฃ", + "name": "megaphone", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ฏ", + "name": "postal horn", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ””", + "name": "bell", + "keywords": ["sound", "notification"], + "category": "objects" + }, + { + "emoji": "๐Ÿ”•", + "name": "bell with slash", + "keywords": ["volume", "off"], + "category": "objects" + }, + { + "emoji": "๐ŸŽผ", + "name": "musical score", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐ŸŽต", + "name": "musical note", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐ŸŽถ", + "name": "musical notes", + "keywords": ["music"], + "category": "objects" + }, + { + "emoji": "๐ŸŽ™๏ธ", + "name": "studio microphone", + "keywords": ["podcast"], + "category": "objects" + }, + { + "emoji": "๐ŸŽš๏ธ", + "name": "level slider", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐ŸŽ›๏ธ", + "name": "control knobs", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐ŸŽค", + "name": "microphone", + "keywords": ["sing"], + "category": "objects" + }, + { + "emoji": "๐ŸŽง", + "name": "headphone", + "keywords": ["music", "earphones"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ป", + "name": "radio", + "keywords": ["podcast"], + "category": "objects" + }, + { + "emoji": "๐ŸŽท", + "name": "saxophone", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿช—", + "name": "accordion", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐ŸŽธ", + "name": "guitar", + "keywords": ["rock"], + "category": "objects" + }, + { + "emoji": "๐ŸŽน", + "name": "musical keyboard", + "keywords": ["piano"], + "category": "objects" + }, + { + "emoji": "๐ŸŽบ", + "name": "trumpet", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐ŸŽป", + "name": "violin", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿช•", + "name": "banjo", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿฅ", + "name": "drum", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿช˜", + "name": "long drum", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿช‡", + "name": "maracas", + "keywords": ["shaker"], + "category": "objects" + }, + { + "emoji": "๐Ÿชˆ", + "name": "flute", + "keywords": ["recorder"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ฑ", + "name": "mobile phone", + "keywords": ["smartphone", "mobile"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ฒ", + "name": "mobile phone with arrow", + "keywords": ["call", "incoming"], + "category": "objects" + }, + { + "emoji": "โ˜Ž๏ธ", + "name": "telephone", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ž", + "name": "telephone receiver", + "keywords": ["phone", "call"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“Ÿ", + "name": "pager", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ ", + "name": "fax machine", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ”‹", + "name": "battery", + "keywords": ["power"], + "category": "objects" + }, + { + "emoji": "๐Ÿชซ", + "name": "low battery", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ”Œ", + "name": "electric plug", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ’ป", + "name": "laptop", + "keywords": ["desktop", "screen"], + "category": "objects" + }, + { + "emoji": "๐Ÿ–ฅ๏ธ", + "name": "desktop computer", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ–จ๏ธ", + "name": "printer", + "keywords": [], + "category": "objects" + }, + { + "emoji": "โŒจ๏ธ", + "name": "keyboard", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ–ฑ๏ธ", + "name": "computer mouse", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ–ฒ๏ธ", + "name": "trackball", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ’ฝ", + "name": "computer disk", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ’พ", + "name": "floppy disk", + "keywords": ["save"], + "category": "objects" + }, + { + "emoji": "๐Ÿ’ฟ", + "name": "optical disk", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“€", + "name": "dvd", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿงฎ", + "name": "abacus", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐ŸŽฅ", + "name": "movie camera", + "keywords": ["film", "video"], + "category": "objects" + }, + { + "emoji": "๐ŸŽž๏ธ", + "name": "film frames", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ฝ๏ธ", + "name": "film projector", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐ŸŽฌ", + "name": "clapper board", + "keywords": ["film"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“บ", + "name": "television", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ท", + "name": "camera", + "keywords": ["photo"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ธ", + "name": "camera with flash", + "keywords": ["photo"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“น", + "name": "video camera", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ผ", + "name": "videocassette", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ”", + "name": "magnifying glass tilted left", + "keywords": ["search", "zoom"], + "category": "objects" + }, + { + "emoji": "๐Ÿ”Ž", + "name": "magnifying glass tilted right", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ•ฏ๏ธ", + "name": "candle", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ’ก", + "name": "light bulb", + "keywords": ["idea", "light"], + "category": "objects" + }, + { + "emoji": "๐Ÿ”ฆ", + "name": "flashlight", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿฎ", + "name": "red paper lantern", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿช”", + "name": "diya lamp", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“”", + "name": "notebook with decorative cover", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“•", + "name": "closed book", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“–", + "name": "open book", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“—", + "name": "green book", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“˜", + "name": "blue book", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“™", + "name": "orange book", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“š", + "name": "books", + "keywords": ["library"], + "category": "objects" + }, + { + "emoji": "๐Ÿ““", + "name": "notebook", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“’", + "name": "ledger", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ƒ", + "name": "page with curl", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“œ", + "name": "scroll", + "keywords": ["document"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“„", + "name": "page facing up", + "keywords": ["document"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ฐ", + "name": "newspaper", + "keywords": ["press"], + "category": "objects" + }, + { + "emoji": "๐Ÿ—ž๏ธ", + "name": "rolled-up newspaper", + "keywords": ["press"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“‘", + "name": "bookmark tabs", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ”–", + "name": "bookmark", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿท๏ธ", + "name": "label", + "keywords": ["tag"], + "category": "objects" + }, + { + "emoji": "๐Ÿ’ฐ", + "name": "money bag", + "keywords": ["dollar", "cream"], + "category": "objects" + }, + { + "emoji": "๐Ÿช™", + "name": "coin", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ’ด", + "name": "yen banknote", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ’ต", + "name": "dollar banknote", + "keywords": ["money"], + "category": "objects" + }, + { + "emoji": "๐Ÿ’ถ", + "name": "euro banknote", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ’ท", + "name": "pound banknote", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ’ธ", + "name": "money with wings", + "keywords": ["dollar"], + "category": "objects" + }, + { + "emoji": "๐Ÿ’ณ", + "name": "credit card", + "keywords": ["subscription"], + "category": "objects" + }, + { + "emoji": "๐Ÿงพ", + "name": "receipt", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ’น", + "name": "chart increasing with yen", + "keywords": [], + "category": "objects" + }, + { + "emoji": "โœ‰๏ธ", + "name": "envelope", + "keywords": ["letter", "email"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ง", + "name": "e-mail", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“จ", + "name": "incoming envelope", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ฉ", + "name": "envelope with arrow", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ค", + "name": "outbox tray", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ฅ", + "name": "inbox tray", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ฆ", + "name": "package", + "keywords": ["shipping"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ซ", + "name": "closed mailbox with raised flag", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ช", + "name": "closed mailbox with lowered flag", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ฌ", + "name": "open mailbox with raised flag", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ญ", + "name": "open mailbox with lowered flag", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ฎ", + "name": "postbox", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ—ณ๏ธ", + "name": "ballot box with ballot", + "keywords": [], + "category": "objects" + }, + { + "emoji": "โœ๏ธ", + "name": "pencil", + "keywords": [], + "category": "objects" + }, + { + "emoji": "โœ’๏ธ", + "name": "black nib", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ–‹๏ธ", + "name": "fountain pen", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ–Š๏ธ", + "name": "pen", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ–Œ๏ธ", + "name": "paintbrush", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ–๏ธ", + "name": "crayon", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“", + "name": "memo", + "keywords": ["document", "note"], + "category": "objects" + }, + { + "emoji": "๐Ÿ’ผ", + "name": "briefcase", + "keywords": ["business"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“", + "name": "file folder", + "keywords": ["directory"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“‚", + "name": "open file folder", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ—‚๏ธ", + "name": "card index dividers", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“…", + "name": "calendar", + "keywords": ["calendar", "schedule"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“†", + "name": "tear-off calendar", + "keywords": ["schedule"], + "category": "objects" + }, + { + "emoji": "๐Ÿ—’๏ธ", + "name": "spiral notepad", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ—“๏ธ", + "name": "spiral calendar", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“‡", + "name": "card index", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ˆ", + "name": "chart increasing", + "keywords": ["graph", "metrics"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“‰", + "name": "chart decreasing", + "keywords": ["graph", "metrics"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“Š", + "name": "bar chart", + "keywords": ["stats", "metrics"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“‹", + "name": "clipboard", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“Œ", + "name": "pushpin", + "keywords": ["location"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“", + "name": "round pushpin", + "keywords": ["location"], + "category": "objects" + }, + { + "emoji": "๐Ÿ“Ž", + "name": "paperclip", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ–‡๏ธ", + "name": "linked paperclips", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“", + "name": "straight ruler", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“", + "name": "triangular ruler", + "keywords": [], + "category": "objects" + }, + { + "emoji": "โœ‚๏ธ", + "name": "scissors", + "keywords": ["cut"], + "category": "objects" + }, + { + "emoji": "๐Ÿ—ƒ๏ธ", + "name": "card file box", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ—„๏ธ", + "name": "file cabinet", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ—‘๏ธ", + "name": "wastebasket", + "keywords": ["trash"], + "category": "objects" + }, + { + "emoji": "๐Ÿ”’", + "name": "locked", + "keywords": ["security", "private"], + "category": "objects" + }, + { + "emoji": "๐Ÿ”“", + "name": "unlocked", + "keywords": ["security"], + "category": "objects" + }, + { + "emoji": "๐Ÿ”", + "name": "locked with pen", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ”", + "name": "locked with key", + "keywords": ["security"], + "category": "objects" + }, + { + "emoji": "๐Ÿ”‘", + "name": "key", + "keywords": ["lock", "password"], + "category": "objects" + }, + { + "emoji": "๐Ÿ—๏ธ", + "name": "old key", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ”จ", + "name": "hammer", + "keywords": ["tool"], + "category": "objects" + }, + { + "emoji": "๐Ÿช“", + "name": "axe", + "keywords": [], + "category": "objects" + }, + { + "emoji": "โ›๏ธ", + "name": "pick", + "keywords": [], + "category": "objects" + }, + { + "emoji": "โš’๏ธ", + "name": "hammer and pick", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ› ๏ธ", + "name": "hammer and wrench", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ—ก๏ธ", + "name": "dagger", + "keywords": [], + "category": "objects" + }, + { + "emoji": "โš”๏ธ", + "name": "crossed swords", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ’ฃ", + "name": "bomb", + "keywords": ["boom"], + "category": "objects" + }, + { + "emoji": "๐Ÿชƒ", + "name": "boomerang", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿน", + "name": "bow and arrow", + "keywords": ["archery"], + "category": "objects" + }, + { + "emoji": "๐Ÿ›ก๏ธ", + "name": "shield", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿชš", + "name": "carpentry saw", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ”ง", + "name": "wrench", + "keywords": ["tool"], + "category": "objects" + }, + { + "emoji": "๐Ÿช›", + "name": "screwdriver", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ”ฉ", + "name": "nut and bolt", + "keywords": [], + "category": "objects" + }, + { + "emoji": "โš™๏ธ", + "name": "gear", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ—œ๏ธ", + "name": "clamp", + "keywords": [], + "category": "objects" + }, + { + "emoji": "โš–๏ธ", + "name": "balance scale", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿฆฏ", + "name": "white cane", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ”—", + "name": "link", + "keywords": [], + "category": "objects" + }, + { + "emoji": "โ›“๏ธ", + "name": "chains", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿช", + "name": "hook", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿงฐ", + "name": "toolbox", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿงฒ", + "name": "magnet", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿชœ", + "name": "ladder", + "keywords": [], + "category": "objects" + }, + { + "emoji": "โš—๏ธ", + "name": "alembic", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿงช", + "name": "test tube", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿงซ", + "name": "petri dish", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿงฌ", + "name": "dna", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ”ฌ", + "name": "microscope", + "keywords": ["science", "laboratory", "investigate"], + "category": "objects" + }, + { + "emoji": "๐Ÿ”ญ", + "name": "telescope", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ“ก", + "name": "satellite antenna", + "keywords": ["signal"], + "category": "objects" + }, + { + "emoji": "๐Ÿ’‰", + "name": "syringe", + "keywords": ["health", "hospital", "needle"], + "category": "objects" + }, + { + "emoji": "๐Ÿฉธ", + "name": "drop of blood", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ’Š", + "name": "pill", + "keywords": ["health", "medicine"], + "category": "objects" + }, + { + "emoji": "๐Ÿฉน", + "name": "adhesive bandage", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿฉผ", + "name": "crutch", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿฉบ", + "name": "stethoscope", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿฉป", + "name": "x-ray", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿšช", + "name": "door", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ›—", + "name": "elevator", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿชž", + "name": "mirror", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐ŸชŸ", + "name": "window", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ›๏ธ", + "name": "bed", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ›‹๏ธ", + "name": "couch and lamp", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿช‘", + "name": "chair", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿšฝ", + "name": "toilet", + "keywords": ["wc"], + "category": "objects" + }, + { + "emoji": "๐Ÿช ", + "name": "plunger", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿšฟ", + "name": "shower", + "keywords": ["bath"], + "category": "objects" + }, + { + "emoji": "๐Ÿ›", + "name": "bathtub", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿชค", + "name": "mouse trap", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿช’", + "name": "razor", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿงด", + "name": "lotion bottle", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿงท", + "name": "safety pin", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿงน", + "name": "broom", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿงบ", + "name": "basket", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿงป", + "name": "roll of paper", + "keywords": ["toilet"], + "category": "objects" + }, + { + "emoji": "๐Ÿชฃ", + "name": "bucket", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿงผ", + "name": "soap", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿซง", + "name": "bubbles", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿชฅ", + "name": "toothbrush", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿงฝ", + "name": "sponge", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿงฏ", + "name": "fire extinguisher", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ›’", + "name": "shopping cart", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿšฌ", + "name": "cigarette", + "keywords": ["cigarette"], + "category": "objects" + }, + { + "emoji": "โšฐ๏ธ", + "name": "coffin", + "keywords": ["funeral"], + "category": "objects" + }, + { + "emoji": "๐Ÿชฆ", + "name": "headstone", + "keywords": [], + "category": "objects" + }, + { + "emoji": "โšฑ๏ธ", + "name": "funeral urn", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿงฟ", + "name": "nazar amulet", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿชฌ", + "name": "hamsa", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿ—ฟ", + "name": "moai", + "keywords": ["stone"], + "category": "objects" + }, + { + "emoji": "๐Ÿชง", + "name": "placard", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿชช", + "name": "identification card", + "keywords": [], + "category": "objects" + }, + { + "emoji": "๐Ÿง", + "name": "ATM sign", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿšฎ", + "name": "litter in bin sign", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿšฐ", + "name": "potable water", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ™ฟ", + "name": "wheelchair symbol", + "keywords": ["accessibility"], + "category": "symbols" + }, + { + "emoji": "๐Ÿšน", + "name": "menโ€™s room", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿšบ", + "name": "womenโ€™s room", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿšป", + "name": "restroom", + "keywords": ["toilet"], + "category": "symbols" + }, + { + "emoji": "๐Ÿšผ", + "name": "baby symbol", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿšพ", + "name": "water closet", + "keywords": ["toilet", "restroom"], + "category": "symbols" + }, + { + "emoji": "๐Ÿ›‚", + "name": "passport control", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ›ƒ", + "name": "customs", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ›„", + "name": "baggage claim", + "keywords": ["airport"], + "category": "symbols" + }, + { + "emoji": "๐Ÿ›…", + "name": "left luggage", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โš ๏ธ", + "name": "warning", + "keywords": ["wip"], + "category": "symbols" + }, + { + "emoji": "๐Ÿšธ", + "name": "children crossing", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ›”", + "name": "no entry", + "keywords": ["limit"], + "category": "symbols" + }, + { + "emoji": "๐Ÿšซ", + "name": "prohibited", + "keywords": ["block", "forbidden"], + "category": "symbols" + }, + { + "emoji": "๐Ÿšณ", + "name": "no bicycles", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿšญ", + "name": "no smoking", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿšฏ", + "name": "no littering", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿšฑ", + "name": "non-potable water", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿšท", + "name": "no pedestrians", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ“ต", + "name": "no mobile phones", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ž", + "name": "no one under eighteen", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ˜ข๏ธ", + "name": "radioactive", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ˜ฃ๏ธ", + "name": "biohazard", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โฌ†๏ธ", + "name": "up arrow", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ†—๏ธ", + "name": "up-right arrow", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โžก๏ธ", + "name": "right arrow", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ†˜๏ธ", + "name": "down-right arrow", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โฌ‡๏ธ", + "name": "down arrow", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ†™๏ธ", + "name": "down-left arrow", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โฌ…๏ธ", + "name": "left arrow", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ†–๏ธ", + "name": "up-left arrow", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ†•๏ธ", + "name": "up-down arrow", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ†”๏ธ", + "name": "left-right arrow", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ†ฉ๏ธ", + "name": "right arrow curving left", + "keywords": ["return"], + "category": "symbols" + }, + { + "emoji": "โ†ช๏ธ", + "name": "left arrow curving right", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โคด๏ธ", + "name": "right arrow curving up", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โคต๏ธ", + "name": "right arrow curving down", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ƒ", + "name": "clockwise vertical arrows", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”„", + "name": "counterclockwise arrows button", + "keywords": ["sync"], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”™", + "name": "BACK arrow", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”š", + "name": "END arrow", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”›", + "name": "ON! arrow", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”œ", + "name": "SOON arrow", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”", + "name": "TOP arrow", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ›", + "name": "place of worship", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โš›๏ธ", + "name": "atom symbol", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ•‰๏ธ", + "name": "om", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โœก๏ธ", + "name": "star of David", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ˜ธ๏ธ", + "name": "wheel of dharma", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ˜ฏ๏ธ", + "name": "yin yang", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โœ๏ธ", + "name": "latin cross", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ˜ฆ๏ธ", + "name": "orthodox cross", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ˜ช๏ธ", + "name": "star and crescent", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ˜ฎ๏ธ", + "name": "peace symbol", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ•Ž", + "name": "menorah", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ฏ", + "name": "dotted six-pointed star", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿชฏ", + "name": "khanda", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ™ˆ", + "name": "Aries", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ™‰", + "name": "Taurus", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ™Š", + "name": "Gemini", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ™‹", + "name": "Cancer", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ™Œ", + "name": "Leo", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ™", + "name": "Virgo", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ™Ž", + "name": "Libra", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ™", + "name": "Scorpio", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ™", + "name": "Sagittarius", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ™‘", + "name": "Capricorn", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ™’", + "name": "Aquarius", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ™“", + "name": "Pisces", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ›Ž", + "name": "Ophiuchus", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”€", + "name": "shuffle tracks button", + "keywords": ["shuffle"], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”", + "name": "repeat button", + "keywords": ["loop"], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”‚", + "name": "repeat single button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ–ถ๏ธ", + "name": "play button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โฉ", + "name": "fast-forward button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โญ๏ธ", + "name": "next track button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โฏ๏ธ", + "name": "play or pause button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ—€๏ธ", + "name": "reverse button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โช", + "name": "fast reverse button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โฎ๏ธ", + "name": "last track button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ผ", + "name": "upwards button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โซ", + "name": "fast up button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ฝ", + "name": "downwards button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โฌ", + "name": "fast down button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โธ๏ธ", + "name": "pause button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โน๏ธ", + "name": "stop button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โบ๏ธ", + "name": "record button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ๏ธ", + "name": "eject button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐ŸŽฆ", + "name": "cinema", + "keywords": ["film", "movie"], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”…", + "name": "dim button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”†", + "name": "bright button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ“ถ", + "name": "antenna bars", + "keywords": ["wifi"], + "category": "symbols" + }, + { + "emoji": "๐Ÿ›œ", + "name": "wireless", + "keywords": ["wifi"], + "category": "symbols" + }, + { + "emoji": "๐Ÿ“ณ", + "name": "vibration mode", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ“ด", + "name": "mobile phone off", + "keywords": ["mute", "off"], + "category": "symbols" + }, + { + "emoji": "โ™€๏ธ", + "name": "female sign", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ™‚๏ธ", + "name": "male sign", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โšง๏ธ", + "name": "transgender symbol", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โœ–๏ธ", + "name": "multiply", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โž•", + "name": "plus", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โž–", + "name": "minus", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โž—", + "name": "divide", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐ŸŸฐ", + "name": "heavy equals sign", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ™พ๏ธ", + "name": "infinity", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ€ผ๏ธ", + "name": "double exclamation mark", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ‰๏ธ", + "name": "exclamation question mark", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ“", + "name": "red question mark", + "keywords": ["confused"], + "category": "symbols" + }, + { + "emoji": "โ”", + "name": "white question mark", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ•", + "name": "white exclamation mark", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ—", + "name": "red exclamation mark", + "keywords": ["bang"], + "category": "symbols" + }, + { + "emoji": "ใ€ฐ๏ธ", + "name": "wavy dash", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ’ฑ", + "name": "currency exchange", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ’ฒ", + "name": "heavy dollar sign", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โš•๏ธ", + "name": "medical symbol", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ™ป๏ธ", + "name": "recycling symbol", + "keywords": ["environment", "green"], + "category": "symbols" + }, + { + "emoji": "โšœ๏ธ", + "name": "fleur-de-lis", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ฑ", + "name": "trident emblem", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ“›", + "name": "name badge", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ฐ", + "name": "Japanese symbol for beginner", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โญ•", + "name": "hollow red circle", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โœ…", + "name": "check mark button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ˜‘๏ธ", + "name": "check box with check", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โœ”๏ธ", + "name": "check mark", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โŒ", + "name": "cross mark", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โŽ", + "name": "cross mark button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โžฐ", + "name": "curly loop", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โžฟ", + "name": "double curly loop", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "ใ€ฝ๏ธ", + "name": "part alternation mark", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โœณ๏ธ", + "name": "eight-spoked asterisk", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โœด๏ธ", + "name": "eight-pointed star", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ‡๏ธ", + "name": "sparkle", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "ยฉ๏ธ", + "name": "copyright", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "ยฎ๏ธ", + "name": "registered", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ„ข๏ธ", + "name": "trade mark", + "keywords": ["trademark"], + "category": "symbols" + }, + { + "emoji": "#๏ธโƒฃ", + "name": "keycap: #", + "keywords": ["number"], + "category": "symbols" + }, + { + "emoji": "*๏ธโƒฃ", + "name": "keycap: *", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "0๏ธโƒฃ", + "name": "keycap: 0", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "1๏ธโƒฃ", + "name": "keycap: 1", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "2๏ธโƒฃ", + "name": "keycap: 2", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "3๏ธโƒฃ", + "name": "keycap: 3", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "4๏ธโƒฃ", + "name": "keycap: 4", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "5๏ธโƒฃ", + "name": "keycap: 5", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "6๏ธโƒฃ", + "name": "keycap: 6", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "7๏ธโƒฃ", + "name": "keycap: 7", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "8๏ธโƒฃ", + "name": "keycap: 8", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "9๏ธโƒฃ", + "name": "keycap: 9", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”Ÿ", + "name": "keycap: 10", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ” ", + "name": "input latin uppercase", + "keywords": ["letters"], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ก", + "name": "input latin lowercase", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ข", + "name": "input numbers", + "keywords": ["numbers"], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ฃ", + "name": "input symbols", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ค", + "name": "input latin letters", + "keywords": ["alphabet"], + "category": "symbols" + }, + { + "emoji": "๐Ÿ…ฐ๏ธ", + "name": "A button (blood type)", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ†Ž", + "name": "AB button (blood type)", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ…ฑ๏ธ", + "name": "B button (blood type)", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ†‘", + "name": "CL button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ†’", + "name": "COOL button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ†“", + "name": "FREE button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ„น๏ธ", + "name": "information", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ†”", + "name": "ID button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ“‚๏ธ", + "name": "circled M", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ†•", + "name": "NEW button", + "keywords": ["fresh"], + "category": "symbols" + }, + { + "emoji": "๐Ÿ†–", + "name": "NG button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ…พ๏ธ", + "name": "O button (blood type)", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ†—", + "name": "OK button", + "keywords": ["yes"], + "category": "symbols" + }, + { + "emoji": "๐Ÿ…ฟ๏ธ", + "name": "P button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ†˜", + "name": "SOS button", + "keywords": ["help", "emergency"], + "category": "symbols" + }, + { + "emoji": "๐Ÿ†™", + "name": "UP! button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ†š", + "name": "VS button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿˆ", + "name": "Japanese โ€œhereโ€ button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿˆ‚๏ธ", + "name": "Japanese โ€œservice chargeโ€ button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿˆท๏ธ", + "name": "Japanese โ€œmonthly amountโ€ button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿˆถ", + "name": "Japanese โ€œnot free of chargeโ€ button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿˆฏ", + "name": "Japanese โ€œreservedโ€ button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ‰", + "name": "Japanese โ€œbargainโ€ button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿˆน", + "name": "Japanese โ€œdiscountโ€ button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿˆš", + "name": "Japanese โ€œfree of chargeโ€ button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿˆฒ", + "name": "Japanese โ€œprohibitedโ€ button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ‰‘", + "name": "Japanese โ€œacceptableโ€ button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿˆธ", + "name": "Japanese โ€œapplicationโ€ button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿˆด", + "name": "Japanese โ€œpassing gradeโ€ button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿˆณ", + "name": "Japanese โ€œvacancyโ€ button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "ใŠ—๏ธ", + "name": "Japanese โ€œcongratulationsโ€ button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "ใŠ™๏ธ", + "name": "Japanese โ€œsecretโ€ button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿˆบ", + "name": "Japanese โ€œopen for businessโ€ button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿˆต", + "name": "Japanese โ€œno vacancyโ€ button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ด", + "name": "red circle", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐ŸŸ ", + "name": "orange circle", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐ŸŸก", + "name": "yellow circle", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐ŸŸข", + "name": "green circle", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ต", + "name": "blue circle", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐ŸŸฃ", + "name": "purple circle", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐ŸŸค", + "name": "brown circle", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โšซ", + "name": "black circle", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โšช", + "name": "white circle", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐ŸŸฅ", + "name": "red square", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐ŸŸง", + "name": "orange square", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐ŸŸจ", + "name": "yellow square", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐ŸŸฉ", + "name": "green square", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐ŸŸฆ", + "name": "blue square", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐ŸŸช", + "name": "purple square", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐ŸŸซ", + "name": "brown square", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โฌ›", + "name": "black large square", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โฌœ", + "name": "white large square", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ—ผ๏ธ", + "name": "black medium square", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ—ป๏ธ", + "name": "white medium square", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ—พ", + "name": "black medium-small square", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ—ฝ", + "name": "white medium-small square", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ–ช๏ธ", + "name": "black small square", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "โ–ซ๏ธ", + "name": "white small square", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ถ", + "name": "large orange diamond", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ท", + "name": "large blue diamond", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ธ", + "name": "small orange diamond", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”น", + "name": "small blue diamond", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”บ", + "name": "red triangle pointed up", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ป", + "name": "red triangle pointed down", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ’ ", + "name": "diamond with a dot", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”˜", + "name": "radio button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ณ", + "name": "white square button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ”ฒ", + "name": "black square button", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ", + "name": "chequered flag", + "keywords": ["milestone", "finish"], + "category": "symbols" + }, + { + "emoji": "๐Ÿšฉ", + "name": "triangular flag", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐ŸŽŒ", + "name": "crossed flags", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿด", + "name": "black flag", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿณ๏ธ", + "name": "white flag", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿณ๏ธโ€๐ŸŒˆ", + "name": "rainbow flag", + "keywords": ["pride"], + "category": "symbols" + }, + { + "emoji": "๐Ÿณ๏ธโ€โšง๏ธ", + "name": "transgender flag", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿดโ€โ˜ ๏ธ", + "name": "pirate flag", + "keywords": [], + "category": "symbols" + }, + { + "emoji": "๐Ÿ‡ฆ๐Ÿ‡จ", + "name": "Ascension Island", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฆ๐Ÿ‡ฉ", + "name": "Andorra", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฆ๐Ÿ‡ช", + "name": "United Arab Emirates", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฆ๐Ÿ‡ซ", + "name": "Afghanistan", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฆ๐Ÿ‡ฌ", + "name": "Antigua & Barbuda", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฆ๐Ÿ‡ฎ", + "name": "Anguilla", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฆ๐Ÿ‡ฑ", + "name": "Albania", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฆ๐Ÿ‡ฒ", + "name": "Armenia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฆ๐Ÿ‡ด", + "name": "Angola", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฆ๐Ÿ‡ถ", + "name": "Antarctica", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฆ๐Ÿ‡ท", + "name": "Argentina", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฆ๐Ÿ‡ธ", + "name": "American Samoa", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฆ๐Ÿ‡น", + "name": "Austria", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฆ๐Ÿ‡บ", + "name": "Australia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฆ๐Ÿ‡ผ", + "name": "Aruba", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฆ๐Ÿ‡ฝ", + "name": "ร…land Islands", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฆ๐Ÿ‡ฟ", + "name": "Azerbaijan", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ฆ", + "name": "Bosnia & Herzegovina", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ง", + "name": "Barbados", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ฉ", + "name": "Bangladesh", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ช", + "name": "Belgium", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ซ", + "name": "Burkina Faso", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ฌ", + "name": "Bulgaria", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ญ", + "name": "Bahrain", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ฎ", + "name": "Burundi", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ฏ", + "name": "Benin", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ฑ", + "name": "St. Barthรฉlemy", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ฒ", + "name": "Bermuda", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ณ", + "name": "Brunei", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ด", + "name": "Bolivia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ถ", + "name": "Caribbean Netherlands", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ท", + "name": "Brazil", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ธ", + "name": "Bahamas", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡น", + "name": "Bhutan", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ป", + "name": "Bouvet Island", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ผ", + "name": "Botswana", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡พ", + "name": "Belarus", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ง๐Ÿ‡ฟ", + "name": "Belize", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡ฆ", + "name": "Canada", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡จ", + "name": "Cocos (Keeling) Islands", + "keywords": ["keeling"], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡ฉ", + "name": "Congo - Kinshasa", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡ซ", + "name": "Central African Republic", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡ฌ", + "name": "Congo - Brazzaville", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡ญ", + "name": "Switzerland", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡ฎ", + "name": "Cรดte dโ€™Ivoire", + "keywords": ["ivory"], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡ฐ", + "name": "Cook Islands", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡ฑ", + "name": "Chile", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡ฒ", + "name": "Cameroon", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡ณ", + "name": "China", + "keywords": ["china"], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡ด", + "name": "Colombia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡ต", + "name": "Clipperton Island", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡ท", + "name": "Costa Rica", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡บ", + "name": "Cuba", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡ป", + "name": "Cape Verde", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡ผ", + "name": "Curaรงao", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡ฝ", + "name": "Christmas Island", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡พ", + "name": "Cyprus", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡จ๐Ÿ‡ฟ", + "name": "Czechia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฉ๐Ÿ‡ช", + "name": "Germany", + "keywords": ["flag", "germany"], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฉ๐Ÿ‡ฌ", + "name": "Diego Garcia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฉ๐Ÿ‡ฏ", + "name": "Djibouti", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฉ๐Ÿ‡ฐ", + "name": "Denmark", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฉ๐Ÿ‡ฒ", + "name": "Dominica", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฉ๐Ÿ‡ด", + "name": "Dominican Republic", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฉ๐Ÿ‡ฟ", + "name": "Algeria", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ช๐Ÿ‡ฆ", + "name": "Ceuta & Melilla", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ช๐Ÿ‡จ", + "name": "Ecuador", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ช๐Ÿ‡ช", + "name": "Estonia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ช๐Ÿ‡ฌ", + "name": "Egypt", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ช๐Ÿ‡ญ", + "name": "Western Sahara", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ช๐Ÿ‡ท", + "name": "Eritrea", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ช๐Ÿ‡ธ", + "name": "Spain", + "keywords": ["spain"], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ช๐Ÿ‡น", + "name": "Ethiopia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ช๐Ÿ‡บ", + "name": "European Union", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ซ๐Ÿ‡ฎ", + "name": "Finland", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ซ๐Ÿ‡ฏ", + "name": "Fiji", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ซ๐Ÿ‡ฐ", + "name": "Falkland Islands", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ซ๐Ÿ‡ฒ", + "name": "Micronesia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ซ๐Ÿ‡ด", + "name": "Faroe Islands", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ซ๐Ÿ‡ท", + "name": "France", + "keywords": ["france", "french"], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡ฆ", + "name": "Gabon", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡ง", + "name": "United Kingdom", + "keywords": ["flag", "british"], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡ฉ", + "name": "Grenada", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡ช", + "name": "Georgia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡ซ", + "name": "French Guiana", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡ฌ", + "name": "Guernsey", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡ญ", + "name": "Ghana", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡ฎ", + "name": "Gibraltar", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡ฑ", + "name": "Greenland", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡ฒ", + "name": "Gambia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡ณ", + "name": "Guinea", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡ต", + "name": "Guadeloupe", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡ถ", + "name": "Equatorial Guinea", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡ท", + "name": "Greece", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡ธ", + "name": "South Georgia & South Sandwich Islands", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡น", + "name": "Guatemala", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡บ", + "name": "Guam", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡ผ", + "name": "Guinea-Bissau", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฌ๐Ÿ‡พ", + "name": "Guyana", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ญ๐Ÿ‡ฐ", + "name": "Hong Kong SAR China", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ญ๐Ÿ‡ฒ", + "name": "Heard & McDonald Islands", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ญ๐Ÿ‡ณ", + "name": "Honduras", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ญ๐Ÿ‡ท", + "name": "Croatia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ญ๐Ÿ‡น", + "name": "Haiti", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ญ๐Ÿ‡บ", + "name": "Hungary", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฎ๐Ÿ‡จ", + "name": "Canary Islands", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฎ๐Ÿ‡ฉ", + "name": "Indonesia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฎ๐Ÿ‡ช", + "name": "Ireland", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฎ๐Ÿ‡ฑ", + "name": "Israel", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฎ๐Ÿ‡ฒ", + "name": "Isle of Man", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฎ๐Ÿ‡ณ", + "name": "India", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฎ๐Ÿ‡ด", + "name": "British Indian Ocean Territory", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฎ๐Ÿ‡ถ", + "name": "Iraq", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฎ๐Ÿ‡ท", + "name": "Iran", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฎ๐Ÿ‡ธ", + "name": "Iceland", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฎ๐Ÿ‡น", + "name": "Italy", + "keywords": ["italy"], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฏ๐Ÿ‡ช", + "name": "Jersey", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฏ๐Ÿ‡ฒ", + "name": "Jamaica", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฏ๐Ÿ‡ด", + "name": "Jordan", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฏ๐Ÿ‡ต", + "name": "Japan", + "keywords": ["japan"], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฐ๐Ÿ‡ช", + "name": "Kenya", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฐ๐Ÿ‡ฌ", + "name": "Kyrgyzstan", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฐ๐Ÿ‡ญ", + "name": "Cambodia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฐ๐Ÿ‡ฎ", + "name": "Kiribati", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฐ๐Ÿ‡ฒ", + "name": "Comoros", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฐ๐Ÿ‡ณ", + "name": "St. Kitts & Nevis", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฐ๐Ÿ‡ต", + "name": "North Korea", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฐ๐Ÿ‡ท", + "name": "South Korea", + "keywords": ["korea"], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฐ๐Ÿ‡ผ", + "name": "Kuwait", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฐ๐Ÿ‡พ", + "name": "Cayman Islands", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฐ๐Ÿ‡ฟ", + "name": "Kazakhstan", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฑ๐Ÿ‡ฆ", + "name": "Laos", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฑ๐Ÿ‡ง", + "name": "Lebanon", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฑ๐Ÿ‡จ", + "name": "St. Lucia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฑ๐Ÿ‡ฎ", + "name": "Liechtenstein", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฑ๐Ÿ‡ฐ", + "name": "Sri Lanka", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฑ๐Ÿ‡ท", + "name": "Liberia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฑ๐Ÿ‡ธ", + "name": "Lesotho", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฑ๐Ÿ‡น", + "name": "Lithuania", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฑ๐Ÿ‡บ", + "name": "Luxembourg", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฑ๐Ÿ‡ป", + "name": "Latvia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฑ๐Ÿ‡พ", + "name": "Libya", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ฆ", + "name": "Morocco", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡จ", + "name": "Monaco", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ฉ", + "name": "Moldova", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ช", + "name": "Montenegro", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ซ", + "name": "St. Martin", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ฌ", + "name": "Madagascar", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ญ", + "name": "Marshall Islands", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ฐ", + "name": "North Macedonia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ฑ", + "name": "Mali", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ฒ", + "name": "Myanmar (Burma)", + "keywords": ["burma"], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ณ", + "name": "Mongolia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ด", + "name": "Macao SAR China", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ต", + "name": "Northern Mariana Islands", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ถ", + "name": "Martinique", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ท", + "name": "Mauritania", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ธ", + "name": "Montserrat", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡น", + "name": "Malta", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡บ", + "name": "Mauritius", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ป", + "name": "Maldives", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ผ", + "name": "Malawi", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ฝ", + "name": "Mexico", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡พ", + "name": "Malaysia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฒ๐Ÿ‡ฟ", + "name": "Mozambique", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ณ๐Ÿ‡ฆ", + "name": "Namibia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ณ๐Ÿ‡จ", + "name": "New Caledonia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ณ๐Ÿ‡ช", + "name": "Niger", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ณ๐Ÿ‡ซ", + "name": "Norfolk Island", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ณ๐Ÿ‡ฌ", + "name": "Nigeria", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ณ๐Ÿ‡ฎ", + "name": "Nicaragua", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ณ๐Ÿ‡ฑ", + "name": "Netherlands", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ณ๐Ÿ‡ด", + "name": "Norway", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ณ๐Ÿ‡ต", + "name": "Nepal", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ณ๐Ÿ‡ท", + "name": "Nauru", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ณ๐Ÿ‡บ", + "name": "Niue", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ณ๐Ÿ‡ฟ", + "name": "New Zealand", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ด๐Ÿ‡ฒ", + "name": "Oman", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ต๐Ÿ‡ฆ", + "name": "Panama", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ต๐Ÿ‡ช", + "name": "Peru", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ต๐Ÿ‡ซ", + "name": "French Polynesia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ต๐Ÿ‡ฌ", + "name": "Papua New Guinea", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ต๐Ÿ‡ญ", + "name": "Philippines", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ต๐Ÿ‡ฐ", + "name": "Pakistan", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ต๐Ÿ‡ฑ", + "name": "Poland", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ต๐Ÿ‡ฒ", + "name": "St. Pierre & Miquelon", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ต๐Ÿ‡ณ", + "name": "Pitcairn Islands", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ต๐Ÿ‡ท", + "name": "Puerto Rico", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ต๐Ÿ‡ธ", + "name": "Palestinian Territories", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ต๐Ÿ‡น", + "name": "Portugal", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ต๐Ÿ‡ผ", + "name": "Palau", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ต๐Ÿ‡พ", + "name": "Paraguay", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ถ๐Ÿ‡ฆ", + "name": "Qatar", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ท๐Ÿ‡ช", + "name": "Rรฉunion", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ท๐Ÿ‡ด", + "name": "Romania", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ท๐Ÿ‡ธ", + "name": "Serbia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ท๐Ÿ‡บ", + "name": "Russia", + "keywords": ["russia"], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ท๐Ÿ‡ผ", + "name": "Rwanda", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡ฆ", + "name": "Saudi Arabia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡ง", + "name": "Solomon Islands", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡จ", + "name": "Seychelles", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡ฉ", + "name": "Sudan", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡ช", + "name": "Sweden", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡ฌ", + "name": "Singapore", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡ญ", + "name": "St. Helena", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡ฎ", + "name": "Slovenia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡ฏ", + "name": "Svalbard & Jan Mayen", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡ฐ", + "name": "Slovakia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡ฑ", + "name": "Sierra Leone", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡ฒ", + "name": "San Marino", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡ณ", + "name": "Senegal", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡ด", + "name": "Somalia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡ท", + "name": "Suriname", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡ธ", + "name": "South Sudan", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡น", + "name": "Sรฃo Tomรฉ & Prรญncipe", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡ป", + "name": "El Salvador", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡ฝ", + "name": "Sint Maarten", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡พ", + "name": "Syria", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ธ๐Ÿ‡ฟ", + "name": "Eswatini", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡น๐Ÿ‡ฆ", + "name": "Tristan da Cunha", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡น๐Ÿ‡จ", + "name": "Turks & Caicos Islands", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡น๐Ÿ‡ฉ", + "name": "Chad", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡น๐Ÿ‡ซ", + "name": "French Southern Territories", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡น๐Ÿ‡ฌ", + "name": "Togo", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡น๐Ÿ‡ญ", + "name": "Thailand", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡น๐Ÿ‡ฏ", + "name": "Tajikistan", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡น๐Ÿ‡ฐ", + "name": "Tokelau", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡น๐Ÿ‡ฑ", + "name": "Timor-Leste", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡น๐Ÿ‡ฒ", + "name": "Turkmenistan", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡น๐Ÿ‡ณ", + "name": "Tunisia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡น๐Ÿ‡ด", + "name": "Tonga", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡น๐Ÿ‡ท", + "name": "Turkey", + "keywords": ["turkey"], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡น๐Ÿ‡น", + "name": "Trinidad & Tobago", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡น๐Ÿ‡ป", + "name": "Tuvalu", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡น๐Ÿ‡ผ", + "name": "Taiwan", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡น๐Ÿ‡ฟ", + "name": "Tanzania", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡บ๐Ÿ‡ฆ", + "name": "Ukraine", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡บ๐Ÿ‡ฌ", + "name": "Uganda", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡บ๐Ÿ‡ฒ", + "name": "U.S. Outlying Islands", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡บ๐Ÿ‡ณ", + "name": "United Nations", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡บ๐Ÿ‡ธ", + "name": "United States", + "keywords": ["flag", "united", "america"], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡บ๐Ÿ‡พ", + "name": "Uruguay", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡บ๐Ÿ‡ฟ", + "name": "Uzbekistan", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ป๐Ÿ‡ฆ", + "name": "Vatican City", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ป๐Ÿ‡จ", + "name": "St. Vincent & Grenadines", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ป๐Ÿ‡ช", + "name": "Venezuela", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ป๐Ÿ‡ฌ", + "name": "British Virgin Islands", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ป๐Ÿ‡ฎ", + "name": "U.S. Virgin Islands", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ป๐Ÿ‡ณ", + "name": "Vietnam", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ป๐Ÿ‡บ", + "name": "Vanuatu", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ผ๐Ÿ‡ซ", + "name": "Wallis & Futuna", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ผ๐Ÿ‡ธ", + "name": "Samoa", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฝ๐Ÿ‡ฐ", + "name": "Kosovo", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡พ๐Ÿ‡ช", + "name": "Yemen", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡พ๐Ÿ‡น", + "name": "Mayotte", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฟ๐Ÿ‡ฆ", + "name": "South Africa", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฟ๐Ÿ‡ฒ", + "name": "Zambia", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿ‡ฟ๐Ÿ‡ผ", + "name": "Zimbabwe", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ", + "name": "England", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ", + "name": "Scotland", + "keywords": [], + "category": "flags" + }, + { + "emoji": "๐Ÿด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ", + "name": "Wales", + "keywords": [], + "category": "flags" + } +] diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Screenshots/noctalia-dark-1.png b/arch/.config/quickshell/noctalia-shell/Assets/Screenshots/noctalia-dark-1.png new file mode 100644 index 0000000..bc40edc Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Assets/Screenshots/noctalia-dark-1.png differ diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Screenshots/noctalia-dark-2.png b/arch/.config/quickshell/noctalia-shell/Assets/Screenshots/noctalia-dark-2.png new file mode 100644 index 0000000..f6a3d79 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Assets/Screenshots/noctalia-dark-2.png differ diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Screenshots/noctalia-dark-3.png b/arch/.config/quickshell/noctalia-shell/Assets/Screenshots/noctalia-dark-3.png new file mode 100644 index 0000000..4660f7b Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Assets/Screenshots/noctalia-dark-3.png differ diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Screenshots/noctalia-light-1.png b/arch/.config/quickshell/noctalia-shell/Assets/Screenshots/noctalia-light-1.png new file mode 100644 index 0000000..5046ebf Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Assets/Screenshots/noctalia-light-1.png differ diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Screenshots/noctalia-light-2.png b/arch/.config/quickshell/noctalia-shell/Assets/Screenshots/noctalia-light-2.png new file mode 100644 index 0000000..ecfd234 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Assets/Screenshots/noctalia-light-2.png differ diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Screenshots/noctalia-light-3.png b/arch/.config/quickshell/noctalia-shell/Assets/Screenshots/noctalia-light-3.png new file mode 100644 index 0000000..cb7ad2b Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Assets/Screenshots/noctalia-light-3.png differ diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Services/fastfetch/system-info.jsonc b/arch/.config/quickshell/noctalia-shell/Assets/Services/fastfetch/system-info.jsonc new file mode 100644 index 0000000..d9f5a2d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Services/fastfetch/system-info.jsonc @@ -0,0 +1,17 @@ +{ + "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", + "modules": [ + "os", + "kernel", + "title", + "board", + "host", + "uptime", + "cpu", + "gpu", + "memory", + "disk", + "wm", + "packages" + ] +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Sounds/alarm-beep.wav b/arch/.config/quickshell/noctalia-shell/Assets/Sounds/alarm-beep.wav new file mode 100644 index 0000000..002c235 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Assets/Sounds/alarm-beep.wav differ diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Sounds/notification-generic.wav b/arch/.config/quickshell/noctalia-shell/Assets/Sounds/notification-generic.wav new file mode 100644 index 0000000..aa04aa1 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Assets/Sounds/notification-generic.wav differ diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Sounds/volume-change.wav b/arch/.config/quickshell/noctalia-shell/Assets/Sounds/volume-change.wav new file mode 100644 index 0000000..24bcc44 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Assets/Sounds/volume-change.wav differ diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Talia/TaliaBlank.png b/arch/.config/quickshell/noctalia-shell/Assets/Talia/TaliaBlank.png new file mode 100644 index 0000000..b79e53c Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Assets/Talia/TaliaBlank.png differ diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Talia/TaliaDazed.png b/arch/.config/quickshell/noctalia-shell/Assets/Talia/TaliaDazed.png new file mode 100644 index 0000000..580c807 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Assets/Talia/TaliaDazed.png differ diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Talia/TaliaFear.png b/arch/.config/quickshell/noctalia-shell/Assets/Talia/TaliaFear.png new file mode 100644 index 0000000..beb366c Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Assets/Talia/TaliaFear.png differ diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Talia/TaliaParty.png b/arch/.config/quickshell/noctalia-shell/Assets/Talia/TaliaParty.png new file mode 100644 index 0000000..64c0a96 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Assets/Talia/TaliaParty.png differ diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Talia/TaliaSob.png b/arch/.config/quickshell/noctalia-shell/Assets/Talia/TaliaSob.png new file mode 100644 index 0000000..7970024 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Assets/Talia/TaliaSob.png differ diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Talia/TaliaVampire.png b/arch/.config/quickshell/noctalia-shell/Assets/Talia/TaliaVampire.png new file mode 100644 index 0000000..896109a Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Assets/Talia/TaliaVampire.png differ diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/btop.theme b/arch/.config/quickshell/noctalia-shell/Assets/Templates/btop.theme new file mode 100644 index 0000000..3139e29 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/btop.theme @@ -0,0 +1,39 @@ +# btop theme generated by noctalia + +theme[main_bg]="{{colors.surface.default.hex}}" +theme[main_fg]="{{colors.on_surface.default.hex}}" +theme[title]="{{colors.primary.default.hex}}" +theme[hi_fg]="{{colors.tertiary.default.hex}}" +theme[selected_bg]="{{colors.surface_container_highest.default.hex}}" +theme[selected_fg]="{{colors.on_surface.default.hex}}" +theme[inactive_fg]="{{colors.on_surface_variant.default.hex}}" +theme[proc_misc]="{{colors.secondary.default.hex}}" +theme[cpu_box]="{{colors.outline.default.hex}}" +theme[mem_box]="{{colors.outline.default.hex}}" +theme[net_box]="{{colors.outline.default.hex}}" +theme[proc_box]="{{colors.outline.default.hex}}" +theme[div_line]="{{colors.outline_variant.default.hex}}" +theme[temp_start]="{{colors.primary.default.hex}}" +theme[temp_mid]="{{colors.secondary.default.hex}}" +theme[temp_end]="{{colors.tertiary.default.hex}}" +theme[cpu_start]="{{colors.primary.default.hex}}" +theme[cpu_mid]="{{colors.secondary.default.hex}}" +theme[cpu_end]="{{colors.tertiary.default.hex}}" +theme[free_start]="{{colors.primary.default.hex}}" +theme[free_mid]="{{colors.secondary.default.hex}}" +theme[free_end]="{{colors.tertiary.default.hex}}" +theme[cached_start]="{{colors.primary.default.hex}}" +theme[cached_mid]="{{colors.secondary.default.hex}}" +theme[cached_end]="{{colors.tertiary.default.hex}}" +theme[available_start]="{{colors.primary.default.hex}}" +theme[available_mid]="{{colors.secondary.default.hex}}" +theme[available_end]="{{colors.tertiary.default.hex}}" +theme[used_start]="{{colors.primary.default.hex}}" +theme[used_mid]="{{colors.secondary.default.hex}}" +theme[used_end]="{{colors.tertiary.default.hex}}" +theme[download_start]="{{colors.primary.default.hex}}" +theme[download_mid]="{{colors.secondary.default.hex}}" +theme[download_end]="{{colors.tertiary.default.hex}}" +theme[upload_start]="{{colors.primary.default.hex}}" +theme[upload_mid]="{{colors.secondary.default.hex}}" +theme[upload_end]="{{colors.tertiary.default.hex}}" diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/cava.ini b/arch/.config/quickshell/noctalia-shell/Assets/Templates/cava.ini new file mode 100644 index 0000000..a036e22 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/cava.ini @@ -0,0 +1,15 @@ +[color] +foreground = '{{colors.primary.default.hex}}' + +gradient = 1 +gradient_color_1 = '{{colors.primary_container.default.hex}}' +gradient_color_2 = '{{colors.primary.default.hex}}' +gradient_color_3 = '{{colors.on_primary_container.default.hex}}' + +horizontal_gradient = 0 +horizontal_gradient_color_1 = '{{colors.primary_container.default.hex}}' +horizontal_gradient_color_2 = '{{colors.primary.default.hex}}' +horizontal_gradient_color_3 = '{{colors.on_primary_container.default.hex}}' +horizontal_gradient_color_4 = '{{colors.primary.default.hex}}' +horizontal_gradient_color_5 = '{{colors.primary_container.default.hex}}' + diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/code.json b/arch/.config/quickshell/noctalia-shell/Assets/Templates/code.json new file mode 100644 index 0000000..f78157a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/code.json @@ -0,0 +1,1987 @@ +{ + "name": "NoctaliaTheme", + "type": "dark", + "semanticHighlighting": true, + "semanticTokenColors": { + "enumMember": { + "foreground": "{{colors.primary.default.hex}}" + }, + "variable.constant": { + "foreground": "{{colors.tertiary.default.hex}}" + }, + "variable.defaultLibrary": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + "tokenColors": [ + { + "name": "unison punctuation", + "scope": "punctuation.definition.delayed.unison,punctuation.definition.list.begin.unison,punctuation.definition.list.end.unison,punctuation.definition.ability.begin.unison,punctuation.definition.ability.end.unison,punctuation.operator.assignment.as.unison,punctuation.separator.pipe.unison,punctuation.separator.delimiter.unison,punctuation.definition.hash.unison", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "haskell variable generic-type", + "scope": "variable.other.generic-type.haskell", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "haskell storage type", + "scope": "storage.type.haskell", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "support.variable.magic.python", + "scope": "support.variable.magic.python", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "punctuation.separator.parameters.python", + "scope": "punctuation.separator.period.python,punctuation.separator.element.python,punctuation.parenthesis.begin.python,punctuation.parenthesis.end.python", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "variable.parameter.function.language.special.self.python", + "scope": "variable.parameter.function.language.special.self.python", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "storage.modifier.lifetime.rust", + "scope": "storage.modifier.lifetime.rust", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "support.function.std.rust", + "scope": "support.function.std.rust", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "entity.name.lifetime.rust", + "scope": "entity.name.lifetime.rust", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "variable.language.rust", + "scope": "variable.language.rust", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "support.constant.edge", + "scope": "support.constant.edge", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "regexp constant character-class", + "scope": "constant.other.character-class.regexp", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "regexp operator.quantifier", + "scope": "keyword.operator.quantifier.regexp", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "punctuation.definition", + "scope": "punctuation.definition.string.begin,punctuation.definition.string.end", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Text", + "scope": "variable.parameter.function", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "Comment Markup Link", + "scope": "comment markup.link", + "settings": { + "foreground": "{{colors.outline.default.hex}}" + } + }, + { + "name": "markup diff", + "scope": "markup.changed.diff", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "diff", + "scope": "meta.diff.header.from-file,meta.diff.header.to-file,punctuation.definition.from-file.diff,punctuation.definition.to-file.diff", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "inserted.diff", + "scope": "markup.inserted.diff", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "deleted.diff", + "scope": "markup.deleted.diff", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "c++ function", + "scope": "meta.function.c,meta.function.cpp", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "c++ block", + "scope": "punctuation.section.block.begin.bracket.curly.cpp,punctuation.section.block.end.bracket.curly.cpp,punctuation.terminator.statement.c,punctuation.section.block.begin.bracket.curly.c,punctuation.section.block.end.bracket.curly.c,punctuation.section.parens.begin.bracket.round.c,punctuation.section.parens.end.bracket.round.c,punctuation.section.parameters.begin.bracket.round.c,punctuation.section.parameters.end.bracket.round.c", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "js/ts punctuation separator key-value", + "scope": "punctuation.separator.key-value", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "js/ts import keyword", + "scope": "keyword.operator.expression.import", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "math js/ts", + "scope": "support.constant.math", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "math property js/ts", + "scope": "support.constant.property.math", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "js/ts variable.other.constant", + "scope": "variable.other.constant", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "java type", + "scope": [ + "storage.type.annotation.java", + "storage.type.object.array.java" + ], + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "java source", + "scope": "source.java", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "java modifier.import", + "scope": "punctuation.section.block.begin.java,punctuation.section.block.end.java,punctuation.definition.method-parameters.begin.java,punctuation.definition.method-parameters.end.java,meta.method.identifier.java,punctuation.section.method.begin.java,punctuation.section.method.end.java,punctuation.terminator.java,punctuation.section.class.begin.java,punctuation.section.class.end.java,punctuation.section.inner-class.begin.java,punctuation.section.inner-class.end.java,meta.method-call.java,punctuation.section.class.begin.bracket.curly.java,punctuation.section.class.end.bracket.curly.java,punctuation.section.method.begin.bracket.curly.java,punctuation.section.method.end.bracket.curly.java,punctuation.separator.period.java,punctuation.bracket.angle.java,punctuation.definition.annotation.java,meta.method.body.java", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "java modifier.import", + "scope": "meta.method.java", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "java modifier.import", + "scope": "storage.modifier.import.java,storage.type.java,storage.type.generic.java", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "java instanceof", + "scope": "keyword.operator.instanceof.java", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "java variable.name", + "scope": "meta.definition.variable.name.java", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "operator logical", + "scope": "keyword.operator.logical", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "operator bitwise", + "scope": "keyword.operator.bitwise", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "operator channel", + "scope": "keyword.operator.channel", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "support.constant.property-value.scss", + "scope": "support.constant.property-value.scss,support.constant.property-value.css", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "CSS/SCSS/LESS Operators", + "scope": "keyword.operator.css,keyword.operator.scss,keyword.operator.less", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "keyword.operator", + "scope": "keyword.operator.arithmetic,keyword.operator.comparison,keyword.operator.decrement,keyword.operator.increment,keyword.operator.relational", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "C operator assignment", + "scope": "keyword.operator.assignment.c,keyword.operator.comparison.c,keyword.operator.c,keyword.operator.increment.c,keyword.operator.decrement.c,keyword.operator.bitwise.shift.c,keyword.operator.assignment.cpp,keyword.operator.comparison.cpp,keyword.operator.cpp,keyword.operator.increment.cpp,keyword.operator.decrement.cpp,keyword.operator.bitwise.shift.cpp", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Punctuation", + "scope": "punctuation.separator.delimiter", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "Other punctuation .c", + "scope": "punctuation.separator.c,punctuation.separator.cpp", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "C type posix-reserved", + "scope": "support.type.posix-reserved.c,support.type.posix-reserved.cpp", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "keyword.operator.sizeof.c", + "scope": "keyword.operator.sizeof.c,keyword.operator.sizeof.cpp", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "python parameter", + "scope": "variable.parameter.function.language.python", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "python type", + "scope": "support.type.python", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "python logical", + "scope": "keyword.operator.logical.python", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Meta tag", + "scope": "meta.tag", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "Strings", + "scope": "string", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "Inherited Class", + "scope": "entity.other.inherited-class", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "Constant other symbol", + "scope": "constant.other.symbol", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Integers", + "scope": "constant.numeric", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "Constants", + "scope": "constant", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "Constants", + "scope": "punctuation.definition.constant", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "Tags", + "scope": "entity.name.tag", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "Attributes", + "scope": "entity.other.attribute-name", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "Attribute IDs", + "scope": "entity.other.attribute-name.id", + "settings": { + "fontStyle": "normal", + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Attribute class", + "scope": "entity.other.attribute-name.class.css", + "settings": { + "fontStyle": "normal", + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "Selector", + "scope": "meta.selector", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Headings", + "scope": "markup.heading", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "Headings", + "scope": "markup.heading punctuation.definition.heading, entity.name.section", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Units", + "scope": "keyword.other.unit", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "Bold", + "scope": "markup.bold,todo.bold", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "Bold", + "scope": "punctuation.definition.bold", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "markup Italic", + "scope": "markup.italic, punctuation.definition.italic,todo.emphasis", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "emphasis md", + "scope": "emphasis md", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "[VSCODE-CUSTOM] Markdown headings", + "scope": "entity.name.section.markdown", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "[VSCODE-CUSTOM] Markdown heading Punctuation Definition", + "scope": "punctuation.definition.heading.markdown", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "punctuation.definition.list.begin.markdown", + "scope": "punctuation.definition.list.begin.markdown", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "[VSCODE-CUSTOM] Markdown heading setext", + "scope": "markup.heading.setext", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "[VSCODE-CUSTOM] Markdown Punctuation Definition Bold", + "scope": "punctuation.definition.bold.markdown", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "[VSCODE-CUSTOM] Markdown Inline Raw", + "scope": "markup.inline.raw.markdown", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "[VSCODE-CUSTOM] Markdown Inline Raw", + "scope": "markup.inline.raw.string.markdown", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "[VSCODE-CUSTOM] Markdown Punctuation Definition Italic", + "scope": "punctuation.definition.italic.markdown", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "[VSCODE-CUSTOM] Markdown List Punctuation Definition", + "scope": "beginning.punctuation.definition.list.markdown", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "[VSCODE-CUSTOM] Markdown Quote", + "scope": "markup.quote.markdown", + "settings": { + "foreground": "{{colors.outline.default.hex}}", + "fontStyle": "italic" + } + }, + { + "name": "[VSCODE-CUSTOM] Markdown Punctuation Definition String", + "scope": [ + "punctuation.definition.string.begin.markdown", + "punctuation.definition.string.end.markdown", + "punctuation.definition.metadata.markdown" + ], + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "[VSCODE-CUSTOM] Markdown Punctuation Definition Link", + "scope": "punctuation.definition.metadata.markdown", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "[VSCODE-CUSTOM] Markdown Underline Link/Image", + "scope": "markup.underline.link.markdown,markup.underline.link.image.markdown", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "[VSCODE-CUSTOM] Markdown Link Title/Description", + "scope": "string.other.link.title.markdown,string.other.link.description.markdown", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Escape Characters", + "scope": "constant.character.escape", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Embedded", + "scope": "punctuation.section.embedded, variable.interpolation", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "Embedded", + "scope": "punctuation.section.embedded.begin,punctuation.section.embedded.end", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "illegal", + "scope": "invalid.illegal", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "illegal", + "scope": "invalid.illegal.bad-ampersand.html", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "Broken", + "scope": "invalid.broken", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "Deprecated", + "scope": "invalid.deprecated", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "Unimplemented", + "scope": "invalid.unimplemented", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "Source Json Meta Structure Dictionary Json > String Quoted Json", + "scope": "source.json meta.structure.dictionary.json > string.quoted.json", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "Source Json Meta Structure Dictionary Json > String Quoted Json > Punctuation String", + "scope": "source.json meta.structure.dictionary.json > string.quoted.json > punctuation.string", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "Source Json Meta Structure Dictionary Json > Value Json > String Quoted Json,source Json Meta Structure Array Json > Value Json > String Quoted Json,source Json Meta Structure Dictionary Json > Value Json > String Quoted Json > Punctuation,source Json Meta Structure Array Json > Value Json > String Quoted Json > Punctuation", + "scope": "source.json meta.structure.dictionary.json > value.json > string.quoted.json,source.json meta.structure.array.json > value.json > string.quoted.json,source.json meta.structure.dictionary.json > value.json > string.quoted.json > punctuation,source.json meta.structure.array.json > value.json > string.quoted.json > punctuation", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "Source Json Meta Structure Dictionary Json > Constant Language Json,source Json Meta Structure Array Json > Constant Language Json", + "scope": "source.json meta.structure.dictionary.json > constant.language.json,source.json meta.structure.array.json > constant.language.json", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "ts primitive/builtin types", + "scope": "support.type.primitive.ts,support.type.builtin.ts,support.type.primitive.tsx,support.type.builtin.tsx", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "block scope", + "scope": "block.scope.end,block.scope.begin", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "cs storage type", + "scope": "storage.type.cs", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "cs local variable", + "scope": "entity.name.variable.local.cs", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "scope": "token.info-token", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "scope": "token.warn-token", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "scope": "token.error-token", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "scope": "token.debug-token", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "String interpolation", + "scope": [ + "punctuation.definition.template-expression.begin", + "punctuation.definition.template-expression.end", + "punctuation.section.embedded" + ], + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Reset JavaScript string interpolation expression", + "scope": [ + "meta.template.expression" + ], + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "Import module JS", + "scope": [ + "keyword.operator.module" + ], + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "js Flowtype", + "scope": [ + "support.type.type.flowtype" + ], + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "js Flow", + "scope": [ + "support.type.primitive" + ], + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "js class prop", + "scope": [ + "meta.property.object" + ], + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "js func parameter", + "scope": [ + "variable.parameter.function.js" + ], + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "js template literals begin", + "scope": [ + "keyword.other.template.begin" + ], + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "js template literals end", + "scope": [ + "keyword.other.template.end" + ], + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "js template literals variable braces begin", + "scope": [ + "keyword.other.substitution.begin" + ], + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "js template literals variable braces end", + "scope": [ + "keyword.other.substitution.end" + ], + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "js operator.assignment", + "scope": [ + "keyword.operator.assignment" + ], + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "go operator", + "scope": [ + "keyword.operator.assignment.go" + ], + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "go operator", + "scope": [ + "keyword.operator.arithmetic.go", + "keyword.operator.address.go" + ], + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "js/ts italic", + "scope": "entity.other.attribute-name.js,entity.other.attribute-name.ts,entity.other.attribute-name.jsx,entity.other.attribute-name.tsx,variable.parameter,variable.language.super", + "settings": { + "fontStyle": "italic" + } + }, + { + "name": "comment", + "scope": "comment.line.double-slash,comment.block.documentation", + "settings": { + "fontStyle": "italic" + } + }, + { + "name": "Python Keyword Control", + "scope": "keyword.control.import.python,keyword.control.flow.python", + "settings": { + "fontStyle": "italic" + } + }, + { + "name": "markup.italic.markdown", + "scope": "markup.italic.markdown", + "settings": { + "fontStyle": "italic" + } + }, + { + "name": "css color standard name", + "scope": "support.constant.color.w3c-standard-color-name.css,support.constant.color.w3c-standard-color-name.scss", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "css comma", + "scope": "punctuation.separator.list.comma.css", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "css attribute-name.id", + "scope": "support.constant.color.w3c-standard-color-name.css", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "css property-name", + "scope": "support.type.vendored.property-name.css", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "js/ts module", + "scope": "support.module.node,support.type.object.module,support.module.node", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "entity.name.type.module", + "scope": "entity.name.type.module", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "js variable readwrite", + "scope": "variable.other.readwrite,meta.object-literal.key,support.variable.property,support.variable.object.process,support.variable.object.node", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "js/ts json", + "scope": "support.constant.json", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "js/ts Keyword", + "scope": [ + "keyword.operator.expression.instanceof", + "keyword.operator.new", + "keyword.operator.ternary", + "keyword.operator.optional", + "keyword.operator.expression.keyof" + ], + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "js/ts console", + "scope": "support.type.object.console", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "js/ts support.variable.property.process", + "scope": "support.variable.property.process", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "js console function", + "scope": "entity.name.function,support.function.console", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "keyword.operator.misc.rust", + "scope": "keyword.operator.misc.rust", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "keyword.operator.sigil.rust", + "scope": "keyword.operator.sigil.rust", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "operator", + "scope": "keyword.operator.delete", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "js dom", + "scope": "support.type.object.dom", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "js dom variable", + "scope": "support.variable.dom,support.variable.property.dom", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "python placeholder reset to normal string", + "scope": "constant.character.format.placeholder.other.python", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "Operators", + "scope": "keyword.operator", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "Compound Assignment Operators", + "scope": "keyword.operator.assignment.compound", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Compound Assignment Operators js/ts", + "scope": "keyword.operator.assignment.compound.js,keyword.operator.assignment.compound.ts", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Keywords", + "scope": "keyword", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Namespaces", + "scope": "entity.name.namespace", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "Variables", + "scope": "variable", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "Variables", + "scope": "variable.c", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "Language variables", + "scope": "variable.language", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "Functions", + "scope": [ + "entity.name.function", + "meta.require", + "support.function.any-method", + "variable.function" + ], + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Classes", + "scope": "entity.name.type.namespace", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "Classes", + "scope": "support.class, entity.name.type.class", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "Class name", + "scope": "entity.name.class.identifier.namespace.type", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "Class name", + "scope": [ + "entity.name.class", + "variable.other.class.js", + "variable.other.class.ts" + ], + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "Storage", + "scope": "storage", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Storage JS TS", + "scope": "token.storage", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Source Js Keyword Operator Delete,source Js Keyword Operator In,source Js Keyword Operator Of,source Js Keyword Operator Instanceof,source Js Keyword Operator New,source Js Keyword Operator Typeof,source Js Keyword Operator Void", + "scope": "keyword.operator.expression.delete,keyword.operator.expression.in,keyword.operator.expression.of,keyword.operator.expression.instanceof,keyword.operator.new,keyword.operator.expression.typeof,keyword.operator.expression.void", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Support type", + "scope": "support.type.property-name", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "Support type", + "scope": "support.constant.property-value", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "Support type", + "scope": "support.constant.font-name", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "Regular Expressions", + "scope": "string.regexp", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Go package name", + "scope": [ + "entity.name.package.go" + ], + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "elm prelude", + "scope": [ + "support.type.prelude.elm" + ], + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "elm constant", + "scope": [ + "support.constant.elm" + ], + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "template literal", + "scope": [ + "punctuation.quasi.element" + ], + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "html/pug (jade) escaped characters and entities", + "scope": [ + "constant.character.entity" + ], + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "styling css pseudo-elements/classes to be able to differentiate from classes which are the same colour", + "scope": [ + "entity.other.attribute-name.pseudo-element", + "entity.other.attribute-name.pseudo-class" + ], + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "[VSCODE-CUSTOM] JSON Property Name", + "scope": "support.type.property-name.json", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "[VSCODE-CUSTOM] JSON Punctuation for Property Name", + "scope": "support.type.property-name.json punctuation", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "Comments", + "scope": "comment, punctuation.definition.comment", + "settings": { + "fontStyle": "italic", + "foreground": "{{colors.outline.default.hex}}" + } + }, + { + "name": "punctuation.definition.block.sequence.item.yaml", + "scope": "punctuation.definition.block.sequence.item.yaml", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "scope": [ + "constant.language.symbol.elixir" + ], + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "python block", + "scope": "punctuation.definition.arguments.begin.python,punctuation.definition.arguments.end.python,punctuation.separator.arguments.python,punctuation.definition.list.begin.python,punctuation.definition.list.end.python", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "python function-call.generic", + "scope": "meta.function-call.generic.python", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "pyCs", + "scope": "variable.parameter.function.python", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "python function decorator @", + "scope": "meta.function.decorator.python", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "python function support", + "scope": "support.token.decorator.python,meta.function.decorator.identifier.python", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "parameter function js/ts", + "scope": "function.parameter", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "brace function", + "scope": "function.brace", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "parameter function ruby cs", + "scope": "function.parameter.ruby, function.parameter.cs", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "constant.language.symbol.ruby", + "scope": "constant.language.symbol.ruby", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "rgb-value", + "scope": "rgb-value", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "rgb value", + "scope": "inline-color-decoration rgb-value", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "rgb value less", + "scope": "less rgb-value", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "Clojure globals", + "scope": [ + "entity.global.clojure" + ], + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "Clojure symbols", + "scope": [ + "meta.symbol.clojure" + ], + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "Clojure constants", + "scope": [ + "constant.keyword.clojure" + ], + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "CoffeeScript Function Argument", + "scope": [ + "meta.arguments.coffee", + "variable.parameter.function.coffee" + ], + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "Ini Default Text", + "scope": [ + "source.ini" + ], + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "Makefile prerequisities", + "scope": [ + "meta.scope.prerequisites.makefile" + ], + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "Makefile text colour", + "scope": [ + "source.makefile" + ], + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "Groovy import names", + "scope": [ + "storage.modifier.import.groovy" + ], + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "Groovy Methods", + "scope": [ + "meta.method.groovy" + ], + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Groovy Variables", + "scope": [ + "meta.definition.variable.name.groovy" + ], + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "Groovy Inheritance", + "scope": [ + "meta.definition.class.inherited.classes.groovy" + ], + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "HLSL Semantic", + "scope": [ + "support.variable.semantic.hlsl" + ], + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "HLSL Types", + "scope": [ + "support.type.texture.hlsl", + "support.type.sampler.hlsl", + "support.type.object.hlsl", + "support.type.object.rw.hlsl", + "support.type.fx.hlsl", + "support.type.object.hlsl" + ], + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "SQL Variables", + "scope": [ + "text.variable", + "text.bracketed" + ], + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "types", + "scope": [ + "support.type.swift", + "support.type.vb.asp" + ], + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "Java Variables", + "scope": "token.variable.parameter.java", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "Java Imports", + "scope": "import.storage.java", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "Packages", + "scope": "token.package.keyword", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Packages", + "scope": "token.package", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "Type Name", + "scope": "entity.name.type", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "Keyword Control", + "scope": "keyword.control", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Control Elements", + "scope": "control.elements, keyword.operator.less", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "Methods", + "scope": "keyword.other.special-method", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Java Storage", + "scope": "token.storage.type.java", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "Support", + "scope": "support.function", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "Class name php", + "scope": "variable.other.class.php", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "laravel blade tag", + "scope": "text.html.laravel-blade source.php.embedded.line.html entity.name.tag.laravel-blade", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "laravel blade @", + "scope": "text.html.laravel-blade source.php.embedded.line.html support.constant.laravel-blade", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "use statement for other classes", + "scope": "support.other.namespace.use.php,support.other.namespace.use-as.php,support.other.namespace.php,entity.other.alias.php,meta.interface.php", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "error suppression", + "scope": "keyword.operator.error-control.php", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "php instanceof", + "scope": "keyword.operator.type.php", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "style double quoted array index normal begin", + "scope": "punctuation.section.array.begin.php", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "style double quoted array index normal end", + "scope": "punctuation.section.array.end.php", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "php illegal.non-null-typehinted", + "scope": "invalid.illegal.non-null-typehinted.php", + "settings": { + "foreground": "{{colors.error.default.hex}}" + } + }, + { + "name": "php types", + "scope": "storage.type.php,meta.other.type.phpdoc.php,keyword.other.type.php,keyword.other.array.phpdoc.php", + "settings": { + "foreground": "{{colors.secondary.default.hex}}" + } + }, + { + "name": "php call-function", + "scope": "meta.function-call.php,meta.function-call.object.php,meta.function-call.static.php", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "php function-resets", + "scope": "punctuation.definition.parameters.begin.bracket.round.php,punctuation.definition.parameters.end.bracket.round.php,punctuation.separator.delimiter.php,punctuation.section.scope.begin.php,punctuation.section.scope.end.php,punctuation.terminator.expression.php,punctuation.definition.arguments.begin.bracket.round.php,punctuation.definition.arguments.end.bracket.round.php,punctuation.definition.storage-type.begin.bracket.round.php,punctuation.definition.storage-type.end.bracket.round.php,punctuation.definition.array.begin.bracket.round.php,punctuation.definition.array.end.bracket.round.php,punctuation.definition.begin.bracket.round.php,punctuation.definition.end.bracket.round.php,punctuation.definition.begin.bracket.curly.php,punctuation.definition.end.bracket.curly.php,punctuation.definition.section.switch-block.end.bracket.curly.php,punctuation.definition.section.switch-block.start.bracket.curly.php,punctuation.definition.section.switch-block.begin.bracket.curly.php,punctuation.definition.section.switch-block.end.bracket.curly.php", + "settings": { + "foreground": "{{colors.on_background.default.hex}}" + } + }, + { + "name": "support php constants", + "scope": "support.constant.core.rust", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "support php constants", + "scope": "support.constant.ext.php,support.constant.std.php,support.constant.core.php,support.constant.parser-token.php", + "settings": { + "foreground": "{{colors.tertiary.default.hex}}" + } + }, + { + "name": "php goto", + "scope": "entity.name.goto-label.php,support.other.php", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "php logical/bitwise operator", + "scope": "keyword.operator.logical.php,keyword.operator.bitwise.php,keyword.operator.arithmetic.php", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "php regexp operator", + "scope": "keyword.operator.regexp.php", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "php comparison", + "scope": "keyword.operator.comparison.php", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + }, + { + "name": "php heredoc/nowdoc", + "scope": "keyword.operator.heredoc.php,keyword.operator.nowdoc.php", + "settings": { + "foreground": "{{colors.primary.default.hex}}" + } + } + ], + "colors": { + "activityBar.background": "{{colors.surface.default.hex}}", + "activityBar.foreground": "{{colors.on_surface.default.hex}}", + "activityBar.inactiveForeground": "{{colors.on_surface_variant.default.hex}}", + "activityBarBadge.background": "{{colors.primary.default.hex}}", + "activityBarBadge.foreground": "{{colors.on_primary.default.hex}}", + "badge.background": "{{colors.primary.default.hex}}", + "badge.foreground": "{{colors.on_primary.default.hex}}", + "breadcrumb.activeSelectionForeground": "{{colors.primary.default.hex}}", + "breadcrumb.background": "{{colors.background.default.hex}}", + "breadcrumb.focusForeground": "{{colors.on_surface.default.hex}}", + "breadcrumb.foreground": "{{colors.on_surface_variant.default.hex}}", + "breadcrumbPicker.background": "{{colors.surface.default.hex}}", + "button.background": "{{colors.primary.default.hex}}", + "button.foreground": "{{colors.on_primary.default.hex}}", + "button.hoverBackground": "{{colors.primary_container.default.hex}}", + "button.secondaryBackground": "{{colors.secondary.default.hex}}", + "button.secondaryForeground": "{{colors.on_secondary.default.hex}}", + "button.secondaryHoverBackground": "{{colors.secondary_container.default.hex}}", + "checkbox.background": "{{colors.primary_container.default.hex}}", + "checkbox.foreground": "{{colors.on_primary_container.default.hex}}", + "debugExceptionWidget.background": "{{colors.surface.default.hex}}", + "debugExceptionWidget.border": "{{colors.outline.default.hex}}", + "debugToolBar.background": "{{colors.surface.default.hex}}", + "debugToolBar.border": "{{colors.outline.default.hex}}", + "diffEditor.diagonalFill": "{{colors.outline.default.hex}}40", + "diffEditor.insertedTextBackground": "{{colors.secondary.default.hex}}20", + "diffEditor.removedTextBackground": "{{colors.error.default.hex}}20", + "dropdown.background": "{{colors.surface.default.hex}}", + "dropdown.foreground": "{{colors.on_surface.default.hex}}", + "editor.background": "{{colors.background.default.hex}}", + "editor.findMatchBackground": "{{colors.tertiary.default.hex}}80", + "editor.findMatchHighlightBackground": "{{colors.tertiary.default.hex}}60", + "editor.findRangeHighlightBackground": "{{colors.surface_variant.default.hex}}", + "editor.foldBackground": "{{colors.primary.default.hex}}4d", + "editor.foreground": "{{colors.on_background.default.hex}}", + "editor.hoverHighlightBackground": "{{colors.surface_variant.default.hex}}50", + "editor.inactiveSelectionBackground": "{{colors.surface_variant.default.hex}}", + "editor.lineHighlightBackground": "{{colors.surface_variant.default.hex}}50", + "editor.lineHighlightBorder": "{{colors.outline.default.hex}}00", + "editor.selectionBackground": "{{colors.primary.default.hex}}40", + "editor.selectionHighlightBackground": "{{colors.surface_variant.default.hex}}80", + "editor.wordHighlightBackground": "{{colors.surface_variant.default.hex}}", + "editor.wordHighlightStrongBackground": "{{colors.primary_container.default.hex}}80", + "editorBracketHighlight.foreground1": "{{colors.primary.default.hex}}", + "editorBracketHighlight.foreground2": "{{colors.secondary.default.hex}}", + "editorBracketHighlight.foreground3": "{{colors.tertiary.default.hex}}", + "editorBracketHighlight.foreground4": "{{colors.primary_container.default.hex}}", + "editorBracketHighlight.foreground5": "{{colors.secondary_container.default.hex}}", + "editorBracketHighlight.foreground6": "{{colors.tertiary_container.default.hex}}", + "editorBracketHighlight.unexpectedBracket.foreground": "{{colors.error.default.hex}}", + "editorBracketMatch.background": "{{colors.primary.default.hex}}4d", + "editorBracketMatch.border": "{{colors.primary.default.hex}}", + "editorCodeLens.foreground": "{{colors.outline.default.hex}}", + "editorCursor.background": "{{colors.background.default.hex}}", + "editorCursor.foreground": "{{colors.primary.default.hex}}", + "editorError.background": "{{colors.error_container.default.hex}}00", + "editorError.border": "{{colors.outline.default.hex}}00", + "editorError.foreground": "{{colors.error.default.hex}}", + "editorGroup.border": "{{colors.outline.default.hex}}", + "editorGroup.dropBackground": "{{colors.primary_container.default.hex}}40", + "editorGroup.emptyBackground": "{{colors.background.default.hex}}", + "editorGroupHeader.noTabsBackground": "{{colors.background.default.hex}}", + "editorGroupHeader.tabsBackground": "{{colors.surface.default.hex}}", + "editorGroupHeader.tabsBorder": "{{colors.outline.default.hex}}00", + "editorGutter.addedBackground": "{{colors.secondary.default.hex}}80", + "editorGutter.background": "{{colors.background.default.hex}}", + "editorGutter.commentRangeForeground": "{{colors.on_surface_variant.default.hex}}", + "editorGutter.deletedBackground": "{{colors.error.default.hex}}80", + "editorGutter.foldingControlForeground": "{{colors.on_surface_variant.default.hex}}", + "editorGutter.modifiedBackground": "{{colors.primary.default.hex}}80", + "editorHoverWidget.background": "{{colors.surface.default.hex}}", + "editorHoverWidget.border": "{{colors.outline.default.hex}}", + "editorHoverWidget.foreground": "{{colors.on_surface.default.hex}}", + "editorIndentGuide.activeBackground": "{{colors.outline.default.hex}}", + "editorIndentGuide.background": "{{colors.outline.default.hex}}20", + "editorInfo.background": "{{colors.tertiary_container.default.hex}}00", + "editorInfo.border": "{{colors.outline.default.hex}}00", + "editorInfo.foreground": "{{colors.tertiary.default.hex}}", + "editorLineNumber.activeForeground": "{{colors.primary.default.hex}}", + "editorLineNumber.foreground": "{{colors.outline.default.hex}}", + "editorLink.activeForeground": "{{colors.primary.default.hex}}", + "editorMarkerNavigation.background": "{{colors.surface.default.hex}}", + "editorMarkerNavigationError.background": "{{colors.error.default.hex}}40", + "editorMarkerNavigationInfo.background": "{{colors.tertiary.default.hex}}40", + "editorMarkerNavigationWarning.background": "{{colors.secondary.default.hex}}40", + "editorOverviewRuler.addedForeground": "{{colors.secondary.default.hex}}80", + "editorOverviewRuler.border": "{{colors.outline.default.hex}}", + "editorOverviewRuler.deletedForeground": "{{colors.error.default.hex}}80", + "editorOverviewRuler.errorForeground": "{{colors.error.default.hex}}", + "editorOverviewRuler.findMatchForeground": "{{colors.primary.default.hex}}", + "editorOverviewRuler.infoForeground": "{{colors.tertiary.default.hex}}", + "editorOverviewRuler.modifiedForeground": "{{colors.primary.default.hex}}80", + "editorOverviewRuler.rangeHighlightForeground": "{{colors.primary.default.hex}}", + "editorOverviewRuler.selectionHighlightForeground": "{{colors.primary.default.hex}}", + "editorOverviewRuler.warningForeground": "{{colors.secondary.default.hex}}", + "editorOverviewRuler.wordHighlightForeground": "{{colors.secondary.default.hex}}", + "editorOverviewRuler.wordHighlightStrongForeground": "{{colors.secondary.default.hex}}", + "editorRuler.foreground": "{{colors.outline_variant.default.hex}}", + "editorStickyScrollHover.background": "{{colors.surface_variant.default.hex}}", + "editorSuggestWidget.background": "{{colors.surface.default.hex}}", + "editorSuggestWidget.border": "{{colors.outline.default.hex}}", + "editorSuggestWidget.foreground": "{{colors.on_surface.default.hex}}", + "editorSuggestWidget.highlightForeground": "{{colors.primary.default.hex}}", + "editorSuggestWidget.selectedBackground": "{{colors.surface_variant.default.hex}}", + "editorWarning.background": "{{colors.secondary_container.default.hex}}00", + "editorWarning.border": "{{colors.outline.default.hex}}00", + "editorWarning.foreground": "{{colors.secondary.default.hex}}", + "editorWhitespace.foreground": "{{colors.outline.default.hex}}29", + "editorWidget.background": "{{colors.surface.default.hex}}", + "editorWidget.border": "{{colors.outline.default.hex}}", + "editorWidget.foreground": "{{colors.on_surface.default.hex}}", + "editorWidget.resizeBorder": "{{colors.outline.default.hex}}", + "extensionButton.prominentBackground": "{{colors.primary.default.hex}}", + "extensionButton.prominentForeground": "{{colors.on_primary.default.hex}}", + "extensionButton.prominentHoverBackground": "{{colors.primary_container.default.hex}}", + "focusBorder": "{{colors.primary.default.hex}}", + "foreground": "{{colors.on_background.default.hex}}", + "gitDecoration.conflictingResourceForeground": "{{colors.tertiary.default.hex}}c0", + "gitDecoration.deletedResourceForeground": "{{colors.error.default.hex}}c0", + "gitDecoration.ignoredResourceForeground": "{{colors.outline.default.hex}}80", + "gitDecoration.modifiedResourceForeground": "{{colors.primary.default.hex}}c0", + "gitDecoration.submoduleResourceForeground": "{{colors.on_surface_variant.default.hex}}c0", + "gitDecoration.untrackedResourceForeground": "{{colors.secondary.default.hex}}c0", + "icon.foreground": "{{colors.on_background.default.hex}}", + "input.background": "{{colors.surface_variant.default.hex}}", + "input.border": "{{colors.outline.default.hex}}00", + "input.foreground": "{{colors.on_surface_variant.default.hex}}", + "input.placeholderForeground": "{{colors.on_surface_variant.default.hex}}9e", + "inputOption.activeBackground": "{{colors.primary.default.hex}}30", + "inputOption.activeBorder": "{{colors.primary.default.hex}}30", + "inputOption.activeForeground": "{{colors.primary.default.hex}}", + "inputValidation.errorBackground": "{{colors.error_container.default.hex}}", + "inputValidation.errorBorder": "{{colors.error.default.hex}}", + "inputValidation.errorForeground": "{{colors.on_error_container.default.hex}}", + "inputValidation.infoBackground": "{{colors.tertiary_container.default.hex}}", + "inputValidation.infoBorder": "{{colors.tertiary.default.hex}}", + "inputValidation.infoForeground": "{{colors.on_tertiary_container.default.hex}}", + "inputValidation.warningBackground": "{{colors.secondary_container.default.hex}}", + "inputValidation.warningBorder": "{{colors.secondary.default.hex}}", + "inputValidation.warningForeground": "{{colors.on_secondary_container.default.hex}}", + "list.activeSelectionBackground": "{{colors.primary.default.hex}}40", + "list.activeSelectionForeground": "{{colors.on_surface.default.hex}}", + "list.dropBackground": "{{colors.primary_container.default.hex}}40", + "list.focusBackground": "{{colors.primary.default.hex}}30", + "list.focusForeground": "{{colors.on_surface.default.hex}}", + "list.highlightForeground": "{{colors.primary.default.hex}}", + "list.hoverBackground": "{{colors.surface_variant.default.hex}}40", + "list.hoverForeground": "{{colors.on_surface_variant.default.hex}}", + "list.inactiveSelectionBackground": "{{colors.surface_variant.default.hex}}", + "list.inactiveSelectionForeground": "{{colors.on_surface_variant.default.hex}}", + "listFilterWidget.background": "{{colors.tertiary_container.default.hex}}", + "listFilterWidget.noMatchesOutline": "{{colors.error.default.hex}}", + "listFilterWidget.outline": "{{colors.outline.default.hex}}00", + "menu.background": "{{colors.surface.default.hex}}", + "menu.border": "{{colors.outline.default.hex}}85", + "menu.foreground": "{{colors.on_surface.default.hex}}", + "menu.selectionBackground": "{{colors.primary_container.default.hex}}", + "menu.selectionBorder": "{{colors.outline.default.hex}}00", + "menu.selectionForeground": "{{colors.on_primary_container.default.hex}}", + "menu.separatorBackground": "{{colors.outline.default.hex}}", + "menubar.selectionBackground": "{{colors.surface_variant.default.hex}}1a", + "menubar.selectionForeground": "{{colors.on_surface.default.hex}}", + "merge.border": "{{colors.outline.default.hex}}00", + "merge.commonContentBackground": "{{colors.outline.default.hex}}20", + "merge.commonHeaderBackground": "{{colors.outline.default.hex}}30", + "merge.currentContentBackground": "{{colors.primary.default.hex}}20", + "merge.currentHeaderBackground": "{{colors.primary.default.hex}}30", + "merge.incomingContentBackground": "{{colors.tertiary.default.hex}}20", + "merge.incomingHeaderBackground": "{{colors.tertiary.default.hex}}30", + "minimap.background": "{{colors.background.default.hex}}", + "minimap.errorHighlight": "{{colors.error.default.hex}}80", + "minimap.findMatchHighlight": "{{colors.primary.default.hex}}80", + "minimap.selectionHighlight": "{{colors.primary.default.hex}}40", + "minimap.warningHighlight": "{{colors.secondary.default.hex}}80", + "minimapGutter.addedBackground": "{{colors.secondary.default.hex}}80", + "minimapGutter.deletedBackground": "{{colors.error.default.hex}}80", + "minimapGutter.modifiedBackground": "{{colors.primary.default.hex}}80", + "notificationCenter.border": "{{colors.outline.default.hex}}", + "notificationCenterHeader.background": "{{colors.surface_variant.default.hex}}", + "notificationCenterHeader.foreground": "{{colors.on_surface_variant.default.hex}}", + "notificationToast.border": "{{colors.outline.default.hex}}", + "notifications.background": "{{colors.surface.default.hex}}", + "notifications.border": "{{colors.outline.default.hex}}", + "notifications.foreground": "{{colors.on_surface.default.hex}}", + "notificationsErrorIcon.foreground": "{{colors.error.default.hex}}", + "notificationsInfoIcon.foreground": "{{colors.tertiary.default.hex}}", + "notificationsWarningIcon.foreground": "{{colors.secondary.default.hex}}", + "panel.background": "{{colors.surface.default.hex}}", + "panel.border": "{{colors.outline.default.hex}}", + "panel.dropBorder": "{{colors.primary.default.hex}}", + "panelSection.border": "{{colors.outline.default.hex}}", + "panelTitle.activeBorder": "{{colors.primary.default.hex}}", + "panelTitle.activeForeground": "{{colors.primary.default.hex}}", + "panelTitle.inactiveForeground": "{{colors.on_surface_variant.default.hex}}", + "peekView.border": "{{colors.primary.default.hex}}", + "peekViewEditor.background": "{{colors.surface.default.hex}}", + "peekViewEditor.matchHighlightBackground": "{{colors.tertiary.default.hex}}99", + "peekViewEditor.matchHighlightBorder": "{{colors.tertiary.default.hex}}", + "peekViewEditorGutter.background": "{{colors.surface.default.hex}}", + "peekViewResult.background": "{{colors.surface_variant.default.hex}}", + "peekViewResult.fileForeground": "{{colors.on_surface_variant.default.hex}}", + "peekViewResult.lineForeground": "{{colors.on_surface_variant.default.hex}}", + "peekViewResult.matchHighlightBackground": "{{colors.tertiary.default.hex}}40", + "peekViewResult.selectionBackground": "{{colors.primary.default.hex}}40", + "peekViewResult.selectionForeground": "{{colors.on_primary_container.default.hex}}", + "peekViewTitle.background": "{{colors.surface_variant.default.hex}}", + "peekViewTitleDescription.foreground": "{{colors.on_surface_variant.default.hex}}", + "peekViewTitleLabel.foreground": "{{colors.primary.default.hex}}", + "pickerGroup.border": "{{colors.outline.default.hex}}", + "pickerGroup.foreground": "{{colors.primary.default.hex}}", + "progressBar.background": "{{colors.primary.default.hex}}", + "scrollbar.shadow": "{{colors.background.default.hex}}00", + "scrollbarSlider.activeBackground": "{{colors.on_surface.default.hex}}80", + "scrollbarSlider.background": "{{colors.on_surface.default.hex}}30", + "scrollbarSlider.hoverBackground": "{{colors.on_surface.default.hex}}50", + "selection.background": "{{colors.primary.default.hex}}66", + "settings.checkboxBackground": "{{colors.surface_variant.default.hex}}", + "settings.checkboxBorder": "{{colors.outline.default.hex}}", + "settings.checkboxForeground": "{{colors.on_surface_variant.default.hex}}", + "settings.dropdownBackground": "{{colors.surface_variant.default.hex}}", + "settings.dropdownBorder": "{{colors.outline.default.hex}}", + "settings.dropdownForeground": "{{colors.on_surface_variant.default.hex}}", + "settings.focusedRowBackground": "{{colors.surface_variant.default.hex}}50", + "settings.headerForeground": "{{colors.on_background.default.hex}}", + "settings.modifiedItemIndicator": "{{colors.primary.default.hex}}", + "settings.numberInputBackground": "{{colors.surface_variant.default.hex}}", + "settings.numberInputBorder": "{{colors.outline.default.hex}}", + "settings.numberInputForeground": "{{colors.on_surface_variant.default.hex}}", + "settings.textInputBackground": "{{colors.surface_variant.default.hex}}", + "settings.textInputBorder": "{{colors.outline.default.hex}}", + "settings.textInputForeground": "{{colors.on_surface_variant.default.hex}}", + "sideBar.background": "{{colors.surface_container.default.hex}}", + "sideBar.dropBackground": "{{colors.surface_variant.default.hex}}40", + "sideBar.foreground": "{{colors.on_surface_variant.default.hex}}", + "sideBarSectionHeader.background": "{{colors.surface.default.hex}}", + "sideBarSectionHeader.foreground": "{{colors.on_surface.default.hex}}", + "sideBarTitle.foreground": "{{colors.primary.default.hex}}", + "statusBar.background": "{{colors.surface_variant.default.hex}}", + "statusBar.debuggingBackground": "{{colors.tertiary.default.hex}}", + "statusBar.debuggingForeground": "{{colors.on_tertiary.default.hex}}", + "statusBar.foreground": "{{colors.on_surface_variant.default.hex}}", + "statusBar.noFolderBackground": "{{colors.surface.default.hex}}", + "statusBar.noFolderForeground": "{{colors.on_surface.default.hex}}", + "statusBarItem.activeBackground": "{{colors.primary.default.hex}}25", + "statusBarItem.hoverBackground": "{{colors.primary.default.hex}}1f", + "statusBarItem.remoteBackground": "{{colors.primary.default.hex}}", + "statusBarItem.remoteForeground": "{{colors.on_primary.default.hex}}", + "tab.activeBackground": "{{colors.surface_variant.default.hex}}", + "tab.activeBorder": "{{colors.primary.default.hex}}", + "tab.activeForeground": "{{colors.on_surface_variant.default.hex}}", + "tab.activeModifiedBorder": "{{colors.tertiary.default.hex}}", + "tab.border": "{{colors.outline.default.hex}}00", + "tab.inactiveBackground": "{{colors.surface.default.hex}}", + "tab.inactiveForeground": "{{colors.on_surface.default.hex}}", + "tab.inactiveModifiedBorder": "{{colors.tertiary.default.hex}}80", + "tab.unfocusedActiveBackground": "{{colors.surface.default.hex}}99", + "tab.unfocusedActiveBorder": "{{colors.primary.default.hex}}80", + "tab.unfocusedActiveForeground": "{{colors.primary.default.hex}}99", + "tab.unfocusedActiveModifiedBorder": "{{colors.tertiary.default.hex}}40", + "tab.unfocusedInactiveBackground": "{{colors.surface_variant.default.hex}}99", + "tab.unfocusedInactiveForeground": "{{colors.on_surface_variant.default.hex}}99", + "tab.unfocusedInactiveModifiedBorder": "{{colors.tertiary.default.hex}}20", + "terminal.ansiBlack": "{{colors.outline.default.hex}}", + "terminal.ansiBlue": "{{colors.primary.default.hex}}", + "terminal.ansiBrightBlack": "{{colors.on_surface_variant.default.hex}}", + "terminal.ansiBrightBlue": "{{colors.primary_container.default.hex}}", + "terminal.ansiBrightCyan": "{{colors.tertiary.default.hex}}", + "terminal.ansiBrightGreen": "{{colors.secondary_container.default.hex}}", + "terminal.ansiBrightMagenta": "{{colors.tertiary_container.default.hex}}", + "terminal.ansiBrightRed": "{{colors.error_container.default.hex}}", + "terminal.ansiBrightWhite": "{{colors.on_background.default.hex}}", + "terminal.ansiBrightYellow": "{{colors.secondary.default.hex}}", + "terminal.ansiCyan": "{{colors.tertiary_container.default.hex}}", + "terminal.ansiGreen": "{{colors.secondary.default.hex}}", + "terminal.ansiMagenta": "{{colors.tertiary.default.hex}}", + "terminal.ansiRed": "{{colors.error.default.hex}}", + "terminal.ansiWhite": "{{colors.on_surface.default.hex}}", + "terminal.ansiYellow": "{{colors.secondary_container.default.hex}}", + "terminal.background": "{{colors.background.default.hex}}", + "terminal.foreground": "{{colors.on_background.default.hex}}", + "terminalCursor.background": "{{colors.background.default.hex}}", + "terminalCursor.foreground": "{{colors.primary.default.hex}}", + "textLink.activeForeground": "{{colors.primary_container.default.hex}}", + "textLink.foreground": "{{colors.primary.default.hex}}", + "titleBar.activeBackground": "{{colors.surface.default.hex}}", + "titleBar.activeForeground": "{{colors.on_surface.default.hex}}", + "titleBar.border": "{{colors.outline.default.hex}}00", + "titleBar.inactiveBackground": "{{colors.surface_variant.default.hex}}", + "titleBar.inactiveForeground": "{{colors.on_surface_variant.default.hex}}", + "tree.indentGuidesStroke": "{{colors.outline.default.hex}}", + "walkThrough.embeddedEditorBackground": "{{colors.surface.default.hex}}50", + "welcomePage.buttonBackground": "{{colors.surface_variant.default.hex}}", + "welcomePage.buttonHoverBackground": "{{colors.primary.default.hex}}20", + "widget.shadow": "{{colors.background.default.hex}}5c" + } +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/discord-material.css b/arch/.config/quickshell/noctalia-shell/Assets/Templates/discord-material.css new file mode 100644 index 0000000..35927da --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/discord-material.css @@ -0,0 +1,94 @@ +/** + * @name Noctalia Material + * @version 3.0.6 + * @description A fork of 'Material Discord' for Noctalia Shell + * @author CapnKitten + * + * @website http://github.com/CapnKitten + * @source https://github.com/CapnKitten/BetterDiscord/blob/master/Themes/Material-Discord/css/source.css + * @donate https://paypal.me/capnkitten + * @invite jzJkA6Z + */ + +@import url("https://capnkitten.github.io/BetterDiscord/Themes/Material-Discord/css/source.css"); + +/* Material You addon */ +@import url("https://capnkitten.github.io/BetterDiscord/Themes/Material-Discord/css/addons/material-you/source.css"); + + + + +/* MATERIAL YOU DARK THEME SETTINGS */ +.theme-dark { + --saturation-modifier: 0.31; + --lightness-modifier: 0.225; + --text-lightness-modifier: 1.0; +} + +/* MATERIAL YOU LIGHT THEME SETTINGS */ +.theme-light { + --saturation-modifier: 0.74; + --lightness-modifier: 2.125; + --text-lightness-modifier: 1.0; +} + +:root, .theme-dark, .theme-light { + /* See options at https://github.com/CapnKitten/Material-Discord */ + /* Accent */ + --accent-hue: {{colors.primary.default.hue}}; + --accent-saturation: {{colors.primary.default.saturation}}%; + --accent-lightness: {{colors.primary.default.lightness}}%; + --accent-text-color: {{colors.on_primary.default.hex}}; + + /* App */ + --app-bg: {{colors.surface.default.hex}}; + --main-color: {{colors.surface.default.hex}}; + --main-alt: {{colors.surface.default.hex}}; + --server-color: {{colors.surface.default.hex}}; + --status-picker-color: {{colors.surface_container.default.hex}}; + --sidebar-panel-color: {{colors.surface_container.default.hex}}; + --chat-color: {{colors.surface.default.hex}}; + --tooltip-color: {{colors.surface.default.hex}}; + --tooltip-text-color: {{colors.on_surface.default.hex}}; + + /* Message */ + --message-color: {{colors.surface_container.default.hex}}; + --message-color-hover: hsl( + var(--accent-hue), + calc(var(--accent-saturation) * var(--saturation-modifier) * var(--saturation-factor)), + calc(var(--accent-lightness) * var(--lightness-modifier) * var(--ui-darkness-modifier) * 4) + ) !important; + --message-color-alt: {{colors.on_secondary.default.hex}}; + --attachment-color: {{colors.on_primary.default.hex}}; + --main-textarea-color: {{colors.primary.default.hex}}; + --main-textarea-button-color: {{colors.on_primary.default.hex}}; + --main-textarea-color-alt: {{colors.secondary.default.hex}}; + --main-textarea-border: {{colors.surface_container.default.hex}}; + + /* Card */ + --card-color-filled: {{colors.surface_container.default.hex}}; + --card-color-hover: {{colors.inverse_primary.default.hex}}; + --card-color-active: {{colors.inverse_primary.default.hex}}; + --card-color-alt: {{colors.secondary_container.default.hex}}; + --card-color-alt-hover: {{colors.secondary.default.hex}}; + --card-border-color: {{colors.surface_container.default.hex}}; + --card-header-text-color: {{colors.on_surface.default.hex}}; + + /* Slider & Switch */ + --switch-knob-color: {{colors.surface_container.default.hex}}; + --switch-slider-color: {{colors.surface.default.hex}}; + + /* Input */ + --input-text-color: {{colors.on_surface.default.hex}}; + + /* Misc */ + --app-watermark-one: "Noctalia"; + --app-watermark-two: "Discord"; + + /* Ensure channels-default is different from the active one - works in dark mode / light mode is another story... */ + --text-tertiary: hsl( + var(--accent-hue), + calc(var(--accent-saturation) * var(--saturation-modifier) * 0.78 * var(--saturation-factor)), + calc(var(--accent-lightness) * var(--lightness-modifier) * var(--ui-darkness-modifier) * var(--text-lightness-modifier) * 3.0) + ) !important; +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/discord-midnight.css b/arch/.config/quickshell/noctalia-shell/Assets/Templates/discord-midnight.css new file mode 100644 index 0000000..fc79d1a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/discord-midnight.css @@ -0,0 +1,370 @@ +/** +* @name Noctalia Midnight +* @description A fork of 'Midnight' for Noctalia Shell +* @author refact0r +* @version 2.1.1 +* @invite nz87hXyvcy +* @website https://github.com/refact0r/midnight-discord +* @source https://github.com/refact0r/midnight-discord/blob/master/themes/midnight.theme.css +* @authorId 508863359777505290 +* @authorLink https://www.refact0r.dev +*/ + +/* import theme modules */ +@import url('https://refact0r.github.io/midnight-discord/build/midnight.css'); + +body { + /* font options */ + --font: ''; /* change to '' for default discord font */ + --code-font: ''; /* change to '' for default discord font */ + font-weight: 400; /* normal text font weight. DOES NOT AFFECT BOLD TEXT */ + + /* sizes */ + --gap: 12px; /* spacing between panels */ + --divider-thickness: 4px; /* thickness of unread messages divider and highlighted message borders */ + --border-thickness: 1px; /* thickness of borders around main panels. DOES NOT AFFECT OTHER BORDERS */ + + /* animation/transition options */ + --animations: on; /* off: disable animations/transitions, on: enable animations/transitions */ + --list-item-transition: 0.2s ease; /* transition for list items */ + --dms-icon-svg-transition: 0.4s ease; /* transition for the dms icon */ + --border-hover-transition: 0.2s ease; /* transition for borders when hovered */ + + /* top bar options */ + --top-bar-height: var(--gap); /* height of the top bar (discord default is 36px, old discord style is 24px, var(--gap) recommended if button position is set to titlebar) */ + --top-bar-button-position: titlebar; /* off: default position, hide: hide buttons completely, serverlist: move inbox button to server list, titlebar: move inbox button to channel titlebar (will hide title) */ + --top-bar-title-position: off; /* off: default centered position, hide: hide title completely, left: left align title (like old discord) */ + --subtle-top-bar-title: off; /* off: default, on: hide the icon and use subtle text color (like old discord) */ + + /* window controls */ + --custom-window-controls: on; /* off: default window controls, on: custom window controls */ + --window-control-size: 14px; /* size of custom window controls */ + + /* dms button options */ + --custom-dms-icon: custom; /* off: use default discord icon, hide: remove icon entirely, custom: use custom icon */ + --dms-icon-svg-url: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB3aWR0aD0iMjU2IgogICBoZWlnaHQ9IjI1NiIKICAgdmlld0JveD0iMCAwIDY3LjczMzMzNCA2Ny43MzMzMzQiCiAgIHZlcnNpb249IjEuMSIKICAgaWQ9InN2ZzEiCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPGRlZnMKICAgICBpZD0iZGVmczEiIC8+CiAgPGcKICAgICBpZD0iZzEiCiAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMS4xMDEwODY5LDAsMCwxLjEwMTA4NjksLTMuNDIzODM0NywtMy40MjM0ODkzKSI+CiAgICA8ZwogICAgICAgaWQ9Imc1NSIKICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjEiCiAgICAgICB0cmFuc2Zvcm09Im1hdHJpeCg0LjIwNTU5MDUsMCwwLDQuMjA1NTkwNSwtMTQuNzg4ODk4LC0xODAuNDMzNjIpIj4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGg1NSIKICAgICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MTtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6OS4xO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtwYWludC1vcmRlcjpmaWxsIG1hcmtlcnMgc3Ryb2tlIgogICAgICAgICBkPSJtIDExLjY0NjQ4NCw0My42NDI1NzggYSA3LjMxNDEzNTYsNy4zMTQxMzU2IDAgMCAwIC03LjMxMjQ5OTYsNy4zMTI1IDcuMzE0MTM1Niw3LjMxNDEzNTYgMCAwIDAgNy4zMTI0OTk2LDcuMzE0NDUzIDcuMzE0MTM1Niw3LjMxNDEzNTYgMCAwIDAgNy4xNTgyMDQsLTUuODk4NDM3IDYuMjIwMzMwMiw2LjIyMDMzMDIgMCAwIDEgLTMuNDE2MDE2LDMuNDY0ODQ0IGMgMC4wMDE1LDAuMjc3NDA2IDAuMDAzOSwwLjUzMjU1IDAuMDAzOSwwLjcyNjU2MiAtMC43Mzk0NDUsMCAtMS4zNzU4MzUsLTAuMTAxODg2IC0xLjkyMTg3NSwtMC4yNzE0ODQgYSA2LjIyMDMzMDIsNi4yMjAzMzAyIDAgMCAxIC0wLjQ0MTQwNiwwLjAzNTE2IDYuMjIwMzMwMiw2LjIyMDMzMDIgMCAwIDEgLTYuMjIwNzAzMiwtNi4yMjA3MDMgNi4yMjAzMzAyLDYuMjIwMzMwMiAwIDAgMSA2LjIyMDcwMzIsLTYuMjIwNzAzIDYuMjIwMzMwMiw2LjIyMDMzMDIgMCAwIDEgMC41NzYxNzIsMC4wMjczNCA3LjMxNDEzNTYsNy4zMTQxMzU2IDAgMCAwIC0xLjk1ODk4NSwtMC4yNjk1MzEgeiIgLz4KICAgIDwvZz4KICAgIDxwYXRoCiAgICAgICBpZD0icGF0aDU2IgogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MS4zNDYwODtzdHJva2UtbWl0ZXJsaW1pdDo5LjE7cGFpbnQtb3JkZXI6ZmlsbCBtYXJrZXJzIHN0cm9rZSIKICAgICAgIGQ9Im0gNDIuNjY0NDQsMzEuMjkxNDc0IGMgLTEuODAyODgyLDIuMDIzMTA1IC00Ljk1MDEzOSwyLjQ5MjY2MSAtOC41MjU0LDIuNTAyOCAtMi43MTgxNiwtMC4wMDc3IC01LjQ1MDQ3NiwtMC4xMjgyMjcgLTcuMjY5NTYsLTEuMzg5NTUxIC0xLjgxNjU0MiwwLjMyNDE4MiAtMi41NzkxNTcsMi4zNDQwMzMgLTIuNDE1ODY2LDQuNzAyNzc3IDEuMTY0NjQ3LDE2LjgyMjk2NyAxMS44MTY3MiwxNS43MzQ2NDUgMTEuOTEyMTk4LDE1LjcwNzE0NSAtNy43OTMzNjYsLTMuODAyNDQ3IC02LjUxNjQ5OCwtMTAuMDAwNDggLTYuNzcyNjIyLC0xMS45NzUyOTMgMC4zMjA5MjgsMy4zNDIzMyAzLjg5NTkyNiwxMi44NTY3NjQgMTcuMjM1NDk0LDEyLjg1Njc2NCAwLC00LjAwOTY1NSAwLjAxOTkyLC0xNC4zMTEwMzUgLTEuMjc5MDgsLTE3LjQ4NzAyNCAtMC40MDkxMjQsLTEuMjM3MzEyIC0xLjUyNTI2LC00LjU0Njk2MyAtMi44ODUxNjQsLTQuOTE3NjE4IHoiIC8+CiAgICA8cGF0aAogICAgICAgaWQ9InBhdGg1NyIKICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjMuNzY1NjU7c3Ryb2tlLW1pdGVybGltaXQ6OS4xO3BhaW50LW9yZGVyOmZpbGwgbWFya2VycyBzdHJva2UiCiAgICAgICBkPSJtIDIwLjk5MjAxMywxNi4wODcwMDQgYyAwLjYwODk0NCwxLjg2NTU0NSAxLjgzNTMzNCwzLjcwNDMxNCAzLjQ4MDA4Niw0LjQwMTc0NSAtMC40NTg3ODEsMS4zODI4MDQgLTAuNDYwODMxLDMuMDI0OTQ5IC0wLjQ2MDgzMSw0Ljc4MzEyNiAwLDQuOTI3ODk1IDQuNTMzNjk4LDguOTE5NDEzIDEwLjEzMDM1OSw4LjkzODU1OSA1LjU5NjY2NCwtMC4wMTkxMiAxMC4xMzAzNjcsLTQuMDEwNjY3IDEwLjEzMDM2NywtOC45Mzg1NTkgMCwtMS43NTgxNzcgLTAuMDAyMSwtMy40MDAzMjIgLTAuNDYwODMzLC00Ljc4MzEyNiAxLjY0NDc1MywtMC42OTc0MzEgMi44NzExNDIsLTIuNTM2MiAzLjQ4MDA4NCwtNC40MDE3NDUgLTEuOTg3Nzc2LC0wLjAzMDUgLTUuMTUwOTA4LDAuNzAwNzU3IC03Ljg1MDA0NywwLjc4NjU5NSAtMS4zNTE1MzksLTAuMzUxODQ1IC0zLjA4NjEzNywtMC41NDU4NjggLTUuMjk5NTcxLC0wLjU0ODIzMyAtMi4yMTM0MzEsMC4wMDI0IC0zLjk0ODAyNywwLjE5NjM2NyAtNS4yOTk1NjgsMC41NDgyMzMgLTIuNjk5MTM1LC0wLjA4NTg0IC01Ljg2MjI2NiwtMC44MTcxODcgLTcuODUwMDQ2LC0wLjc4NjU5NSB6IG0gNi43Nzc0MTksNC42NDgwNTMgYyAwLjU1NTQ3LC0wLjAxMzgzIDEuMjk5Nzc5LDAuMDYyMDQgMi4zMDQxNjEsMC4yNDYzMTkgMC42NzIxOTgsMC4xMjMzMDIgMS41MjM1MzYsMC40NTI0MDIgMi4wMzQwMTksMC43NTQ4MTMgMC45NTA4MDcsMC41NjMyNjQgMS4wNzM1OTUsMS4yMDc2OTcgMi4wMzQwMTUsMS4yMDc2OTcgMC45NjA0MiwwIDEuMDgzMjA3LC0wLjY0NDQzMyAyLjAzNDAxOCwtMS4yMDc2OTcgMC41MTA0ODMsLTAuMzAyNDE4IDEuMzYxODIyLC0wLjYzMTUxMSAyLjAzNDAyMSwtMC43NTQ4MTMgNC41OTE0NTksLTAuODQyMzI4IDMuODIxNzMyLDAuNTIzOTYyIDMuODIxNzMyLDIuODM2NTA1IDAsMi4zODcxMzIgLTEuOTM1MTU1LDQuMzIyMjkgLTQuMzIyMjkxLDQuMzIyMjkgLTAuNzYyOTUsLTAuMDAzNyAtMS41MTEzMzIsLTAuMjA5MzQgLTIuMTY5MDkxLC0wLjU5NTkwNSAtMC4wMTU0NiwwLjAzMzc1IC0wLjAyODg3LDAuMDY0ODQgLTAuMDQ3NjgsMC4wOTUzNSBsIC0wLjk2OTMzOCwxLjU3MzE4OSBjIC0wLjEwNTIzOSwwLjE3MTIyMiAtMC4yNDM3OTcsMC4yNTc0NjcgLTAuMzgxMzc4LDAuMjU0MjUxIC0wLjEzNzU4LDAuMDAzMSAtMC4yNzYxMzksLTAuMDgzMDMgLTAuMzgxMzc4LC0wLjI1NDI1MSBsIC0wLjk2OTMzOCwtMS41NzMxODkgYyAtMC4wMTg3MSwtMC4wMzA1IC0wLjAzMjEyLC0wLjA2MTU5IC0wLjA0NzY4LC0wLjA5NTM1IC0wLjY1Nzc2MywwLjM4NjU4NSAtMS40MDYxNDIsMC41OTIxODggLTIuMTY5MDkxLDAuNTk1OTA1IC0yLjM4NzEzNywwIC00LjMyMjI5LC0xLjkzNTE1OCAtNC4zMjIyOSwtNC4zMjIyOSAwLC0xLjgwNjY3MyAtMC40NjYyNzEsLTMuMDMzMTczIDEuNTE3NTY5LC0zLjA4MjgxNCB6IiAvPgogICAgPHBhdGgKICAgICAgIGlkPSJwYXRoNTgiCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxLjIyMDUyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjkuMTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLW9wYWNpdHk6MTtwYWludC1vcmRlcjpmaWxsIG1hcmtlcnMgc3Ryb2tlIgogICAgICAgZD0iTSAzMi4zMDQyLDI1LjMwMDU5NiBBIDEuOTkxNDQzOCwxLjk5MTQ0MzggMCAwIDEgMzAuNTc5NTU5LDI2LjI5NjMyIDEuOTkxNDQzOCwxLjk5MTQ0MzggMCAwIDEgMjguODU0OTE4LDI1LjMwMDU5NiIgLz4KICAgIDxwYXRoCiAgICAgICBpZD0icGF0aDU5IgogICAgICAgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MS4yMjA1MjtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDo5LjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1vcGFjaXR5OjE7cGFpbnQtb3JkZXI6ZmlsbCBtYXJrZXJzIHN0cm9rZSIKICAgICAgIGQ9Ik0gMzkuNDI4MzM0LDI1LjMwMDU5NiBBIDEuOTkxNDQzOCwxLjk5MTQ0MzggMCAwIDEgMzcuNzAzNjk2LDI2LjI5NjMyIDEuOTkxNDQzOCwxLjk5MTQ0MzggMCAwIDEgMzUuOTc5MDUzLDI1LjMwMDU5NiIgLz4KICA8L2c+Cjwvc3ZnPg=='); /* icon svg url. MUST BE A SVG. */ + --dms-icon-svg-size: 90%; /* size of the svg (css mask-size property) */ + --dms-icon-color-before: var(--icon-subtle); /* normal icon color */ + --dms-icon-color-after: var(--white); /* icon color when button is hovered/selected */ + --custom-dms-background: off; /* off to disable, image to use a background image (must set url variable below), color to use a custom color/gradient */ + --dms-background-image-url: url(''); /* url of the background image */ + --dms-background-image-size: cover; /* size of the background image (css background-size property) */ + --dms-background-color: linear-gradient(70deg, var(--blue-2), var(--purple-2), var(--red-2)); /* fixed color/gradient (css background property) */ + + /* background image options */ + --background-image: off; /* off: no background image, on: enable background image (must set url variable below) */ + --background-image-url: url(''); /* url of the background image */ + + /* transparency/blur options */ + /* NOTE: TO USE TRANSPARENCY/BLUR, YOU MUST HAVE TRANSPARENT BG COLORS. FOR EXAMPLE: --bg-4: hsla(220, 15%, 10%, 0.7); */ + --transparency-tweaks: off; /* off: no changes, on: remove some elements for better transparency */ + --remove-bg-layer: off; /* off: no changes, on: remove the base --bg-3 layer for use with window transparency (WILL OVERRIDE BACKGROUND IMAGE) */ + --panel-blur: off; /* off: no changes, on: blur the background of panels */ + --blur-amount: 12px; /* amount of blur */ + --bg-floating: var(--bg-3); /* set this to a more opaque color if floating panels look too transparent. only applies if panel blur is on */ + + /* chatbar options */ + --custom-chatbar: separated; /* off: default chatbar, separated: chatbar separated from chat */ + --chatbar-height: 56px; /* height of the chatbar (56px by default, 47px to align with user panel, 56px recommended for separated) */ + + /* other options */ + --small-user-panel: off; /* off: default user panel, on: smaller user panel like in old discord */ + +} + +/* color options */ +:root { + --colors: on; /* off: discord default colors, on: midnight custom colors */ + + /* text colors */ + --text-0: {{colors.background.default.hex}}; /* text on colored elements */ + --text-1: {{colors.on_background.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 */ + --text-4: {{colors.outline.default.hex}}; /* icon buttons and channels */ + --text-5: {{colors.outline_variant.default.hex}}; /* muted channels/chats and timestamps */ + + /* background and dark colors */ + --bg-1: {{colors.surface_container_lowest.default.hex}}; /* dark buttons when clicked */ + --bg-2: {{colors.surface_container_low.default.hex}}; /* dark buttons */ + --bg-3: {{colors.surface_container.default.hex}}; /* spacing, secondary elements */ + --bg-4: {{colors.surface.default.hex}}; /* main background color */ + --hover: {{colors.on_surface.default.hex}}15; /* channels and buttons when hovered */ + --active: {{colors.surface_container_highest.default.hex}}; /* channels and buttons when clicked or selected */ + --active-2: {{colors.surface.default.hex}}; /* extra state for transparent buttons */ + --message-hover: {{colors.surface_variant.default.hex}}40; /* messages when hovered */ + + /* accent colors */ + --accent-1: {{colors.primary.default.hex}}; /* links and other accent text */ + --accent-2: {{colors.primary_container.default.hex}}; /* small accent elements */ + --accent-3: {{colors.secondary.default.hex}}; /* accent buttons */ + --accent-4: {{colors.on_secondary_container.default.hex}}; /* accent buttons when hovered */ + --accent-5: {{colors.secondary.default.hex}}; /* accent buttons when clicked */ + --accent-new: {{colors.error.default.hex}}; /* stuff that's normally red like mute/deafen buttons */ + + --mention: linear-gradient(to right, color-mix(in hsl, {{colors.primary_container.default.hex}}, transparent 90%) 40%, transparent); /* background of messages that mention you */ + --mention-hover: linear-gradient(to right, color-mix(in hsl, {{colors.primary_container.default.hex}}, transparent 95%) 40%, transparent); /* background of messages that mention you when hovered */ + + --reply: linear-gradient(to right, color-mix(in hsl, {{colors.tertiary_container.default.hex}}, transparent 90%) 40%, transparent); /* background of messages that reply to you */ + --reply-hover: linear-gradient(to right, color-mix(in hsl, {{colors.tertiary_container.default.hex}}, transparent 95%) 40%, transparent); /* background of messages that reply to you when hovered */ + + /* status indicator colors */ + --online: var(--green-2); /* change to #43a25a for default */ + --dnd: var(--red-2); /* change to #d83a42 for default */ + --idle: var(--yellow-2); /* change to #ca9654 for default */ + --streaming: var(--purple-2); /* change to #593695 for default */ + --offline: var(--text-4); /* change to #83838b for default offline color */ + + /* border colors */ + --border-light: var(--hover); /* general light border color */ + --border: var(--active); /* general normal border color */ + --border-hover: var(--active); /* border color of panels when hovered */ + --button-border: hsl(230, 0%, 100%, 0.1); /* neutral border color of buttons */ + + /* base colors */ + --red-1: hsl(351deg, 74%, 78%); + --red-2: hsl(351deg, 74%, 73%); + --red-3: hsl(351deg, 74%, 73%); + --red-4: hsl(351deg, 74%, 68%); + --red-5: hsl(351deg, 74%, 63%); + + --green-1: hsl(105deg, 48%, 77%); + --green-2: hsl(105deg, 48%, 72%); + --green-3: hsl(105deg, 48%, 72%); + --green-4: hsl(105deg, 48%, 67%); + --green-5: hsl(105deg, 48%, 62%); + + --blue-1: hsl(199deg, 66%, 74%); + --blue-2: hsl(199deg, 66%, 69%); + --blue-3: hsl(199deg, 66%, 69%); + --blue-4: hsl(199deg, 66%, 64%); + --blue-5: hsl(199deg, 66%, 59%); + + --yellow-1: hsl(40deg, 70%, 83%); + --yellow-2: hsl(40deg, 70%, 78%); + --yellow-3: hsl(40deg, 70%, 78%); + --yellow-4: hsl(40deg, 70%, 73%); + --yellow-5: hsl(40deg, 70%, 68%); + + --purple-1: hsl(267deg, 83%, 85%); + --purple-2: hsl(267deg, 83%, 80%); + --purple-3: hsl(267deg, 83%, 80%); + --purple-4: hsl(267deg, 83%, 75%); + --purple-5: hsl(267deg, 83%, 70%); +} + +/* Dividers (Channel list and messages) */ +[class*="divider"]:not([class*="isUnread"]) { + border-color: var(--bg-2) !important; +} + +/* Profile Banners */ +[class*="bannerButton"], +[class*="bannerColor"] { + color: var(--text-1) !important; +} + +/* Prevent scaling animation on Home/DMS icon */ +[class*="guildsnav"] [class*="wrapper"][data-list-item-id='guildsnav___home']:hover > [class*="childWrapper"]::before, +[class*="guildsnav"] [class*="wrapper"][data-list-item-id='guildsnav___home'][class*="selected"] > [class*="childWrapper"]::before { + transform: rotate(-360deg) scale(1) !important; +} + +/* Timestamps */ +[class*="timestamp"], +[class*="timestampInline"], +[class*="timestamp"] time { + color: var(--text-4) !important; +} + +/* Forum/Thread Titles */ +[class*="postTitleText"], +[class*="postTitleText"] [class*="highlight"] { + color: var(--text-2) !important; +} + +/* Message Content */ +[class*="messageContent"] { + color: var(--text-3) !important; +} + +[class*="messageContent"] [class*="highlight"] { + color: var(--text-2) !important; +} + +/* Slowmode/Cooldown Text */ +[class*="cooldownText"] { + color: var(--text-4) !important; +} + +/* Checkbox styling overrides */ +[class*="checkboxIndicator"] { + background-color: transparent !important; + border-color: var(--text-4) !important; +} + +[class*="checkboxOption"]:hover [class*="checkboxIndicator"] { + background-color: color-mix(in srgb, var(--accent-1) 20%, transparent) !important; + border-color: var(--accent-3) !important; +} + +[class*="checkboxOption"][data-selected] [class*="checkboxIndicator"] { + background-color: var(--accent-1) !important; + border-color: var(--accent-3) !important; +} + +[class*="checkboxOption"][data-selected]:hover [class*="checkboxIndicator"] { + background-color: var(--accent-1) !important; + border-color: var(--accent-3) !important; +} + +[data-slate-node="element"], +[data-slate-node="text"], +[data-slate-string="true"] { + color: var(--text-3) !important; +} + +[class*="eyebrow"][class*="badge"][class*="expressive"] { + color: var(--accent-1) !important; + background-color: color-mix(in srgb, var(--accent-1) 15%, transparent) !important; +} + +/* Outer circle (non-selected background) */ +[class*="outerRadioBase"] { + fill: var(--bg-1) !important; /* background for non-selected radios */ + stroke: var(--border) !important; /* ring border */ + stroke-width: 1px; + transition: stroke 0.2s ease; +} + +/* Outer circle fill (selected) */ +[class*="outerRadioFill"] { + fill: var(--bg-1) !important; /* accent color for selected */ + stroke: var(--border) !important; + opacity: 0; /* hidden by default */ + transition: opacity 0.2s ease; +} + +/* Inner dot (selected) */ +[class*="innerDotRadio"] { + fill: var(--accent-3) !important; /* contrast dot */ + opacity: 0; /* hidden by default */ + transition: opacity 0.2s ease; +} + +/* Show outer fill + inner dot when selected */ +[class*="radioSelection"][data-selected="true"] [class*="outerRadioFill"], +[class*="radioSelection"][data-selected="true"] [class*="innerDotRadio"], +[class*="radioSelection"][aria-checked="true"] [class*="outerRadioFill"], +[class*="radioSelection"][aria-checked="true"] [class*="innerDotRadio"], +[class*="parentSelected"] [class*="outerRadioFill"], +[class*="parentSelected"] [class*="innerDotRadio"] { + opacity: 1 !important; +} + +/* Hover effect for base ring */ +[class*="outerRadioBase"]:hover { + stroke: var(--accent-1) !important; +} + +/* Checkbox checkmark and background for selected */ +[class*="checkboxOption"][data-selected="true"] [class*="checkboxIndicator"] { + background-color: transparent !important; /* Removes the solid fill when selected */ + border-color: var(--accent-1) !important; /* Changes border to accent color when selected */ +} + +[class*="checkboxIndicator"] { + background-color: transparent !important; + border-color: var(--border) !important; + transition: border-color 0.2s ease; +} + +[class*="checkboxOption"][data-selected="true"] [class*="checkStroke"], +[class*="checkboxOption"][data-selected="true"] [class*="dot"] { + color: var(--accent-3) !important; /* checkmark color */ +} + +/* Hover effect for selected checkbox */ +[class*="checkboxOption"]:hover [class*="checkboxIndicator"] { + border-color: var(--accent-1) /* keep background consistent on hover */ +} + +/* Fix hover color for channel mentions */ +.channelMention:hover { + color: var(--text-1) !important; +} + +[class*="unreadTop"], +[class*="unreadBottom"], +[class*="unreadMentionsIndicator"] { + background-color: transparent !important; +} + +[class*="unreadBar"], +[class*="unreadMentionsBar"] { + background-color: var(--accent-1) !important; + border: 1px solid var(--border); +} + +[class*="unreadBar"][class*="mention"], +[class*="unreadMentionsBar"][class*="mention"] { + background-color: var(--accent-1) !important; +} + +[class*="unreadBar"][class*="unread"], +[class*="unreadMentionsBar"][class*="unread"] { + background-color: var(--bg-1) !important; + border-color: var(--accent-1) !important; +} + +[class*="unreadBar"] [class*="text"], +[class*="unreadMentionsBar"] [class*="text"] { + color: var(--text-1) !important; + font-weight: 700; +} + +/* Fix polls visibility */ +[class*="pollContainer_"] [class*="selected__"] [class*="radioForeground_"] { + fill: var(--accent-3) !important; +} + +[class*="pollContainer_"] [class*="selected__"] { + border: 2px solid var(--active) !important; +} + +[class*="votedStyles_"] [class*="votePercentageBar_"] { + background-color: var(--active-2) !important; + opacity: 0.5; +} + +[class*="pollContainer_"] [class*="answerSelectionIcon_"] circle { + fill: var(--accent-3) !important; +} + +[class*="pollContainer_"] [class*="votedStyles_"] [class*="answerSelectionIcon_"] path { + fill: var(--bg-1) !important; +} + +[class*="pollContainer_"] [class*="answer__"]:not([class*="votedStyles_"]) [class*="answerSelectionIcon_"] path { + fill: var(--accent-1) !important; +} + +[class*="pollContainer_"] [class*="votesData_"] { + color: var(--text-2) !important; +} + +[class*="pollContainer_"] [class*="votedStyles_"] [class*="answerInner_"] { + border: 2px solid var(--active) !important; +} + +/* Invite to server button override*/ +[class*="inviteButton_"] svg path, +[class*="actionIcon_"] path[d^="M14.5 8a3 3 0"] { + fill: var(--accent-1) !important; + color: var(--accent-1) !important; +} + +[class*="inviteButton_"]:hover svg path { + fill: var(--accent-3) !important; + color: var(--accent-3) !important; +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/emacs.el b/arch/.config/quickshell/noctalia-shell/Assets/Templates/emacs.el new file mode 100644 index 0000000..f4461fc --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/emacs.el @@ -0,0 +1,361 @@ +;;; noctalia-theme.el --- Theme using Template SCSS variables -*- lexical-binding: t -*- + +;; Copyright (C) 2025 + +;; Author: Generated (Improved) +;; Version: 1.2 +;; Package-Requires: ((emacs "24.1")) +;; Keywords: faces + +;;; Commentary: + +;; A theme using Template SCSS variables with quality of life improvements: +;; - Better source block distinction +;; - Improved text visibility when selected +;; - Refined org-mode styling with hidden asterisks +;; - Enhanced contrast and readability +;; - Seamless integration of source blocks with consistent styling + +;;; Code: + +(deftheme noctalia "Theme using Template variables with quality of life improvements.") + +;; Define all the color variables (replaced by template processor) +(let* ((bg "{{colors.background.default.hex}}") + (err "{{colors.error.default.hex}}") + (err-container "{{colors.error_container.default.hex}}") + (on-background "{{colors.on_background.default.hex}}") + (on-err "{{colors.on_error.default.hex}}") + (on-err-container "{{colors.on_error_container.default.hex}}") + (on-primary "{{colors.on_primary.default.hex}}") + (on-primary-container "{{colors.on_primary_container.default.hex}}") + (on-secondary "{{colors.on_secondary.default.hex}}") + (on-secondary-container "{{colors.on_secondary_container.default.hex}}") + (on-surface "{{colors.on_surface.default.hex}}") + (on-surface-variant "{{colors.on_surface_variant.default.hex}}") + (on-tertiary "{{colors.on_tertiary.default.hex}}") + (on-tertiary-container "{{colors.on_tertiary_container.default.hex}}") + (outline-color "{{colors.outline.default.hex}}") + (outline-variant "{{colors.outline_variant.default.hex}}") + (primary "{{colors.primary.default.hex}}") + (primary-container "{{colors.primary_container.default.hex}}") + (secondary "{{colors.secondary.default.hex}}") + (secondary-container "{{colors.secondary_container.default.hex}}") + (shadow "{{colors.shadow.default.hex}}") + (surface "{{colors.surface.default.hex}}") + (surface-container "{{colors.surface_container.default.hex}}") + (surface-container-high "{{colors.surface_container_high.default.hex}}") + (surface-container-highest "{{colors.surface_container_highest.default.hex}}") + (surface-container-low "{{colors.surface_container_low.default.hex}}") + (surface-container-lowest "{{colors.surface_container_lowest.default.hex}}") + (surface-variant "{{colors.surface_variant.default.hex}}") + (tertiary "{{colors.tertiary.default.hex}}") + (tertiary-container "{{colors.tertiary_container.default.hex}}") + ;; Map success colors to tertiary (as used in other templates) + (success "{{colors.tertiary.default.hex}}") + (on-success "{{colors.on_tertiary.default.hex}}") + (success-container "{{colors.tertiary_container.default.hex}}") + (on-success-container "{{colors.on_tertiary_container.default.hex}}") + ;; Map fixed colors to regular colors + (primary-fixed "{{colors.primary.default.hex}}") + (primary-fixed-dim "{{colors.primary_container.default.hex}}") + (secondary-fixed "{{colors.secondary.default.hex}}") + (secondary-fixed-dim "{{colors.secondary_container.default.hex}}") + (tertiary-fixed "{{colors.tertiary.default.hex}}") + (tertiary-fixed-dim "{{colors.tertiary_container.default.hex}}") + (on-primary-fixed "{{colors.on_primary.default.hex}}") + (on-primary-fixed-variant "{{colors.on_primary_container.default.hex}}") + (on-secondary-fixed "{{colors.on_secondary.default.hex}}") + (on-secondary-fixed-variant "{{colors.on_secondary_container.default.hex}}") + (on-tertiary-fixed "{{colors.on_tertiary.default.hex}}") + (on-tertiary-fixed-variant "{{colors.on_tertiary_container.default.hex}}") + ;; Map inverse colors to surface variants + (inverse-on-surface "{{colors.on_surface.default.hex}}") + (inverse-primary "{{colors.primary.default.hex}}") + (inverse-surface "{{colors.surface.default.hex}}") + ;; Map terminal colors (term0-term15) to available colors + (term0 "{{colors.surface.default.hex}}") + (term1 "{{colors.error.default.hex}}") + (term2 "{{colors.tertiary.default.hex}}") + (term3 "{{colors.secondary.default.hex}}") + (term4 "{{colors.primary.default.hex}}") + (term5 "{{colors.tertiary_container.default.hex}}") + (term6 "{{colors.secondary_container.default.hex}}") + (term7 "{{colors.on_surface.default.hex}}") + (term8 "{{colors.outline.default.hex}}") + (term9 "{{colors.error.default.hex}}") + (term10 "{{colors.tertiary.default.hex}}") + (term11 "{{colors.secondary.default.hex}}") + (term12 "{{colors.primary.default.hex}}") + (term13 "{{colors.tertiary_container.default.hex}}") + (term14 "{{colors.secondary_container.default.hex}}") + (term15 "{{colors.on_surface.default.hex}}")) + + (custom-theme-set-faces + 'noctalia + ;; Basic faces + `(default ((t (:background ,bg :foreground ,on-background)))) + `(cursor ((t (:background ,primary)))) + `(highlight ((t (:background ,surface-container-high)))) + `(region ((t (:background ,primary-container :foreground ,on-primary-container :extend t)))) + `(secondary-selection ((t (:background ,secondary-container :foreground ,on-secondary-container :extend t)))) + `(isearch ((t (:background ,tertiary-container :foreground ,on-tertiary-container :weight bold)))) + `(lazy-highlight ((t (:background ,secondary-container :foreground ,on-secondary-container)))) + `(vertical-border ((t (:foreground ,surface-variant)))) + `(border ((t (:background ,surface-variant :foreground ,surface-variant)))) + `(fringe ((t (:background ,surface :foreground ,outline-variant)))) + `(shadow ((t (:foreground ,outline-variant)))) + `(link ((t (:foreground ,primary :underline t)))) + `(link-visited ((t (:foreground ,tertiary :underline t)))) + `(success ((t (:foreground ,success)))) + `(warning ((t (:foreground ,secondary)))) + `(error ((t (:foreground ,err)))) + `(match ((t (:background ,secondary-container :foreground ,on-secondary-container)))) + + ;; Font-lock + `(font-lock-builtin-face ((t (:foreground ,primary)))) + `(font-lock-comment-face ((t (:foreground ,outline-color :slant italic)))) + `(font-lock-comment-delimiter-face ((t (:foreground ,outline-variant)))) + `(font-lock-constant-face ((t (:foreground ,tertiary :weight bold)))) + `(font-lock-doc-face ((t (:foreground ,on-surface-variant :slant italic)))) + `(font-lock-function-name-face ((t (:foreground ,primary :weight bold)))) + `(font-lock-keyword-face ((t (:foreground ,secondary :weight bold)))) + `(font-lock-string-face ((t (:foreground ,tertiary)))) + `(font-lock-type-face ((t (:foreground ,primary-fixed)))) + `(font-lock-variable-name-face ((t (:foreground ,on-surface)))) + `(font-lock-warning-face ((t (:foreground ,err :weight bold)))) + `(font-lock-preprocessor-face ((t (:foreground ,secondary-fixed-dim)))) + `(font-lock-negation-char-face ((t (:foreground ,tertiary-fixed)))) + + ;; Show paren + `(show-paren-match ((t (:background ,primary-container :foreground ,on-primary-container :weight bold)))) + `(show-paren-mismatch ((t (:background ,err-container :foreground ,on-err-container :weight bold)))) + + ;; Mode line - improved status bar styling + `(mode-line ((t (:background ,surface-container-high :foreground ,on-surface :box nil)))) + `(mode-line-inactive ((t (:background ,surface :foreground ,on-surface-variant :box nil)))) + `(mode-line-buffer-id ((t (:foreground ,primary :weight bold)))) + `(mode-line-emphasis ((t (:foreground ,primary :weight bold)))) + `(mode-line-highlight ((t (:foreground ,primary :box nil)))) + + ;; Improved Source blocks - make them integrated with the theme + `(org-block ((t (:background ,surface-container-low :extend t :inherit fixed-pitch)))) + `(org-block-begin-line ((t (:background ,surface-container-low :foreground ,primary-fixed-dim :extend t :slant italic :inherit fixed-pitch)))) + `(org-block-end-line ((t (:background ,surface-container-low :foreground ,primary-fixed-dim :extend t :slant italic :inherit fixed-pitch)))) + `(org-code ((t (:background ,surface-container-low :foreground ,tertiary-fixed :inherit fixed-pitch)))) + `(org-verbatim ((t (:background ,surface-container-low :foreground ,primary-fixed :inherit fixed-pitch)))) + `(org-meta-line ((t (:foreground ,outline-color :slant italic)))) + + ;; Org mode with hidden asterisks + `(org-level-1 ((t (:foreground ,primary :weight bold :height 1.2)))) + `(org-level-2 ((t (:foreground ,secondary :weight bold :height 1.1)))) + `(org-level-3 ((t (:foreground ,tertiary :weight bold)))) + `(org-level-4 ((t (:foreground ,primary :weight bold)))) + `(org-level-5 ((t (:foreground ,secondary :weight bold)))) + `(org-level-6 ((t (:foreground ,tertiary :weight bold)))) + `(org-level-7 ((t (:foreground ,primary :weight bold)))) + `(org-level-8 ((t (:foreground ,secondary :weight bold)))) + `(org-document-title ((t (:foreground ,primary :weight bold :height 1.3)))) + `(org-document-info ((t (:foreground ,primary-container)))) + `(org-todo ((t (:foreground ,err :weight bold)))) + `(org-done ((t (:foreground ,success :weight bold)))) + `(org-headline-done ((t (:foreground ,on-surface-variant)))) + `(org-hide ((t (:foreground ,bg)))) ;; Hide leading asterisks + `(org-ellipsis ((t (:foreground ,tertiary :underline nil)))) ;; Style for folded content indicator + `(org-table ((t (:foreground ,secondary-fixed :inherit fixed-pitch)))) + `(org-formula ((t (:foreground ,tertiary :inherit fixed-pitch)))) + `(org-checkbox ((t (:foreground ,primary :weight bold :inherit fixed-pitch)))) + `(org-date ((t (:foreground ,secondary-fixed :underline t)))) + `(org-special-keyword ((t (:foreground ,on-surface-variant :slant italic)))) + `(org-tag ((t (:foreground ,outline-color :weight normal)))) + + ;; Magit + `(magit-section-highlight ((t (:background ,surface-container-low)))) + `(magit-diff-hunk-heading ((t (:background ,surface-container :foreground ,on-surface-variant)))) + `(magit-diff-hunk-heading-highlight ((t (:background ,surface-container-high :foreground ,on-surface)))) + `(magit-diff-context ((t (:foreground ,on-surface-variant)))) + `(magit-diff-context-highlight ((t (:background ,surface-container-low :foreground ,on-surface)))) + `(magit-diff-added ((t (:background ,success-container :foreground ,on-success-container)))) + `(magit-diff-added-highlight ((t (:background ,success-container :foreground ,on-success-container :weight bold)))) + `(magit-diff-removed ((t (:background ,err-container :foreground ,on-err-container)))) + `(magit-diff-removed-highlight ((t (:background ,err-container :foreground ,on-err-container :weight bold)))) + `(magit-hash ((t (:foreground ,outline-color)))) + `(magit-branch-local ((t (:foreground ,tertiary :weight bold)))) + `(magit-branch-remote ((t (:foreground ,primary :weight bold)))) + + ;; Company + `(company-tooltip ((t (:background ,surface-container :foreground ,on-surface)))) + `(company-tooltip-selection ((t (:background ,primary-container :foreground ,on-primary-container)))) + `(company-tooltip-common ((t (:foreground ,primary)))) + `(company-tooltip-common-selection ((t (:foreground ,on-primary-container :weight bold)))) + `(company-tooltip-annotation ((t (:foreground ,tertiary)))) + `(company-scrollbar-fg ((t (:background ,primary)))) + `(company-scrollbar-bg ((t (:background ,surface-variant)))) + `(company-preview ((t (:foreground ,on-surface-variant :slant italic)))) + `(company-preview-common ((t (:foreground ,primary :slant italic)))) + + ;; Ido + `(ido-first-match ((t (:foreground ,primary :weight bold)))) + `(ido-only-match ((t (:foreground ,tertiary :weight bold)))) + `(ido-subdir ((t (:foreground ,secondary)))) + `(ido-indicator ((t (:foreground ,err)))) + `(ido-virtual ((t (:foreground ,outline-color)))) + + ;; Helm + `(helm-selection ((t (:background ,primary-container :foreground ,on-primary-container)))) + `(helm-match ((t (:foreground ,primary :weight bold)))) + `(helm-source-header ((t (:background ,surface-container-high :foreground ,primary :weight bold :height 1.1)))) + `(helm-candidate-number ((t (:foreground ,tertiary :weight bold)))) + `(helm-ff-directory ((t (:foreground ,primary :weight bold)))) + `(helm-ff-file ((t (:foreground ,on-surface)))) + `(helm-ff-executable ((t (:foreground ,tertiary)))) + + ;; corfu + `(corfu-default ((t (:background ,surface-container :foreground ,on-surface)))) + `(corfu-current ((t (:background ,primary-container :foreground ,on-primary-container)))) + + ;; Which-key + `(which-key-key-face ((t (:foreground ,primary :weight bold)))) + `(which-key-separator-face ((t (:foreground ,outline-variant)))) + `(which-key-command-description-face ((t (:foreground ,on-surface)))) + `(which-key-group-description-face ((t (:foreground ,secondary)))) + `(which-key-special-key-face ((t (:foreground ,tertiary :weight bold)))) + + ;; Line numbers + `(line-number ((t (:foreground ,outline-variant :inherit fixed-pitch)))) + `(line-number-current-line ((t (:foreground ,primary :weight bold :inherit fixed-pitch)))) + + ;; Parenthesis matching + `(sp-show-pair-match-face ((t (:background ,primary-container :foreground ,on-primary-container)))) + `(sp-show-pair-mismatch-face ((t (:background ,err-container :foreground ,on-err-container)))) + + ;; Rainbow delimiters + `(rainbow-delimiters-depth-1-face ((t (:foreground ,primary)))) + `(rainbow-delimiters-depth-2-face ((t (:foreground ,secondary)))) + `(rainbow-delimiters-depth-3-face ((t (:foreground ,tertiary)))) + `(rainbow-delimiters-depth-4-face ((t (:foreground ,primary-fixed)))) + `(rainbow-delimiters-depth-5-face ((t (:foreground ,secondary-fixed)))) + `(rainbow-delimiters-depth-6-face ((t (:foreground ,tertiary-fixed)))) + `(rainbow-delimiters-depth-7-face ((t (:foreground ,primary-fixed-dim)))) + `(rainbow-delimiters-depth-8-face ((t (:foreground ,secondary-fixed-dim)))) + `(rainbow-delimiters-depth-9-face ((t (:foreground ,tertiary-fixed-dim)))) + `(rainbow-delimiters-mismatched-face ((t (:foreground ,err :weight bold)))) + `(rainbow-delimiters-unmatched-face ((t (:foreground ,err :weight bold)))) + + ;; Dired + `(dired-directory ((t (:foreground ,primary :weight bold)))) + `(dired-ignored ((t (:foreground ,outline-variant)))) + `(dired-flagged ((t (:foreground ,err)))) + `(dired-marked ((t (:foreground ,tertiary :weight bold)))) + `(dired-symlink ((t (:foreground ,secondary :slant italic)))) + `(dired-header ((t (:foreground ,primary :weight bold :height 1.1)))) + + ;; Terminal colors + `(term-color-black ((t (:foreground ,term0 :background ,term0)))) + `(term-color-red ((t (:foreground ,term1 :background ,term1)))) + `(term-color-green ((t (:foreground ,term2 :background ,term2)))) + `(term-color-yellow ((t (:foreground ,term3 :background ,term3)))) + `(term-color-blue ((t (:foreground ,term4 :background ,term4)))) + `(term-color-magenta ((t (:foreground ,term5 :background ,term5)))) + `(term-color-cyan ((t (:foreground ,term6 :background ,term6)))) + `(term-color-white ((t (:foreground ,term7 :background ,term7)))) + + ;; EShell + `(eshell-prompt ((t (:foreground ,primary :weight bold)))) + `(eshell-ls-directory ((t (:foreground ,primary :weight bold)))) + `(eshell-ls-symlink ((t (:foreground ,secondary :slant italic)))) + `(eshell-ls-executable ((t (:foreground ,tertiary)))) + `(eshell-ls-archive ((t (:foreground ,on-tertiary-container)))) + `(eshell-ls-backup ((t (:foreground ,outline-variant)))) + `(eshell-ls-clutter ((t (:foreground ,err)))) + `(eshell-ls-missing ((t (:foreground ,err)))) + `(eshell-ls-product ((t (:foreground ,on-surface-variant)))) + `(eshell-ls-readonly ((t (:foreground ,on-surface-variant)))) + `(eshell-ls-special ((t (:foreground ,secondary-fixed)))) + `(eshell-ls-unreadable ((t (:foreground ,outline-variant)))) + + ;; Improved markdown mode + `(markdown-header-face ((t (:foreground ,primary :weight bold)))) + `(markdown-header-face-1 ((t (:foreground ,primary :weight bold :height 1.2)))) + `(markdown-header-face-2 ((t (:foreground ,primary-container :weight bold :height 1.1)))) + `(markdown-header-face-3 ((t (:foreground ,secondary :weight bold)))) + `(markdown-header-face-4 ((t (:foreground ,secondary-container :weight bold)))) + `(markdown-inline-code-face ((t (:foreground ,tertiary-fixed :background ,surface-container-low :inherit fixed-pitch)))) + `(markdown-code-face ((t (:background ,surface-container-low :extend t :inherit fixed-pitch)))) + `(markdown-pre-face ((t (:background ,surface-container-low :inherit fixed-pitch)))) + `(markdown-table-face ((t (:foreground ,secondary-fixed :inherit fixed-pitch)))) + + ;; Web mode + `(web-mode-html-tag-face ((t (:foreground ,primary)))) + `(web-mode-html-tag-bracket-face ((t (:foreground ,on-surface-variant)))) + `(web-mode-html-attr-name-face ((t (:foreground ,secondary)))) + `(web-mode-html-attr-value-face ((t (:foreground ,tertiary)))) + `(web-mode-css-selector-face ((t (:foreground ,primary)))) + `(web-mode-css-property-name-face ((t (:foreground ,secondary)))) + `(web-mode-css-string-face ((t (:foreground ,tertiary)))) + + ;; Flycheck + `(flycheck-error ((t (:underline (:style wave :color ,err))))) + `(flycheck-warning ((t (:underline (:style wave :color ,secondary))))) + `(flycheck-info ((t (:underline (:style wave :color ,tertiary))))) + `(flycheck-fringe-error ((t (:foreground ,err)))) + `(flycheck-fringe-warning ((t (:foreground ,secondary)))) + `(flycheck-fringe-info ((t (:foreground ,tertiary)))) + + ;; Mini-buffer customization + `(minibuffer-prompt ((t (:foreground ,primary :weight bold)))) + + ;; Improved search highlighting + `(lsp-face-highlight-textual ((t (:background ,primary-container :foreground ,on-primary-container :weight bold)))) + `(lsp-face-highlight-read ((t (:background ,secondary-container :foreground ,on-secondary-container :weight bold)))) + `(lsp-face-highlight-write ((t (:background ,tertiary-container :foreground ,on-tertiary-container :weight bold)))) + + ;; Info and help modes + `(info-title-1 ((t (:foreground ,primary :weight bold :height 1.3)))) + `(info-title-2 ((t (:foreground ,primary-container :weight bold :height 1.2)))) + `(info-title-3 ((t (:foreground ,secondary :weight bold :height 1.1)))) + `(info-title-4 ((t (:foreground ,secondary-container :weight bold)))) + `(Info-quoted ((t (:foreground ,tertiary)))) + `(info-menu-header ((t (:foreground ,primary :weight bold)))) + `(info-menu-star ((t (:foreground ,primary)))) + `(info-node ((t (:foreground ,tertiary :weight bold)))) + + ;; Tabs + `(tab-bar ((t (:background ,surface-container-high :foreground ,on-surface :box nil)))) + `(tab-bar-tab ((t (:background ,surface-container-high :foreground ,on-surface :weight bold :box nil)))) + `(tab-bar-tab-inactive ((t (:background ,surface :foreground ,on-surface-variant :box nil)))) + + `(tab-line ((t (:background ,surface-container-high :foreground ,on-surface :box nil)))) + `(tab-line-tab ((t (:background ,surface :foreground ,on-surface-variant :box nil)))) + `(tab-line-tab-current ((t (:background ,surface-container-high :foreground ,on-surface :weight bold :box nil)))) + `(tab-line-tab-inactive ((t (:background ,surface :foreground ,on-surface-variant :box nil)))) + `(tab-line-highlight ((t (:background ,surface-container-highest :foreground ,on-surface)))) + + `(centaur-tabs-default ((t (:background ,surface-container-high :foreground ,on-surface)))) + `(centaur-tabs-selected ((t (:background ,surface-container-high :foreground ,on-surface :weight bold)))) + `(centaur-tabs-unselected ((t (:background ,surface :foreground ,on-surface-variant)))) + `(centaur-tabs-selected-modified ((t (:background ,surface-container-high :foreground ,tertiary :weight bold)))) + `(centaur-tabs-unselected-modified ((t (:background ,surface :foreground ,tertiary)))) + `(centaur-tabs-active-bar-face ((t (:background ,primary)))) + + ;; Fixed-pitch faces + `(fixed-pitch ((t (:family "monospace")))) + `(fixed-pitch-serif ((t (:family "monospace serif")))) + + ;; Variable-pitch face + `(variable-pitch ((t (:family "sans serif")))) + )) + +;; Add org-mode hooks for hiding leading stars +(with-eval-after-load 'org + (setq org-hide-leading-stars t) + (setq org-startup-indented t)) + +;;;###autoload +(when load-file-name + (add-to-list 'custom-theme-load-path + (file-name-as-directory (file-name-directory load-file-name)))) + +(provide-theme 'noctalia) +;;; noctalia-theme.el ends here + diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/fuzzel.conf b/arch/.config/quickshell/noctalia-shell/Assets/Templates/fuzzel.conf new file mode 100644 index 0000000..dc519a8 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/fuzzel.conf @@ -0,0 +1,15 @@ +# Fuzzel Colors +# Generated by Noctalia's Template Processor + +[colors] +background={{colors.background.default.hex_stripped}}CC +text={{colors.on_surface.default.hex_stripped}}ff +prompt={{colors.secondary.default.hex_stripped}}ff +placeholder={{colors.tertiary.default.hex_stripped}}ff +input={{colors.primary.default.hex_stripped}}ff +match={{colors.tertiary.default.hex_stripped}}ff +selection={{colors.primary.default.hex_stripped}}80 +selection-text={{colors.on_surface.default.hex_stripped}}ff +selection-match={{colors.on_primary.default.hex_stripped}}ff +counter={{colors.secondary.default.hex_stripped}}ff +border={{colors.primary.default.hex_stripped}}ff diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/gtk3.css b/arch/.config/quickshell/noctalia-shell/Assets/Templates/gtk3.css new file mode 100644 index 0000000..dd3a420 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/gtk3.css @@ -0,0 +1,53 @@ +/* +* GTK Colors (GTK3) +* Generated by Noctalia's Template Processor +* stylelint-disable at-rule-no-unknown +*/ + +@define-color accent_color {{colors.primary.default.hex}}; +@define-color accent_bg_color {{colors.primary.default.hex}}; +@define-color accent_fg_color {{colors.on_primary.default.hex}}; + +@define-color destructive_bg_color {{colors.error.default.hex}}; +@define-color destructive_fg_color {{colors.on_error.default.hex}}; + +@define-color error_bg_color {{colors.error.default.hex}}; +@define-color error_fg_color {{colors.on_error.default.hex}}; + +@define-color window_bg_color {{colors.surface.default.hex}}; +@define-color window_fg_color {{colors.on_surface.default.hex}}; + +@define-color view_bg_color {{colors.surface.default.hex}}; +@define-color view_fg_color {{colors.on_surface.default.hex}}; + +@define-color headerbar_bg_color {{colors.surface.default.hex}}; +@define-color headerbar_fg_color {{colors.on_surface.default.hex}}; +@define-color headerbar_backdrop_color @window_bg_color; + +@define-color popover_bg_color {{colors.surface_container.default.hex}}; +@define-color popover_fg_color {{colors.on_surface.default.hex}}; + +@define-color card_bg_color {{colors.surface_container.default.hex}}; +@define-color card_fg_color {{colors.on_surface.default.hex}}; + +@define-color dialog_bg_color {{colors.surface.default.hex}}; +@define-color dialog_fg_color {{colors.on_surface.default.hex}}; + +@define-color overview_bg_color {{colors.surface_container.default.hex}}; +@define-color overview_fg_color {{colors.on_surface.default.hex}}; + +@define-color sidebar_bg_color {{colors.surface_container.default.hex}}; +@define-color sidebar_fg_color {{colors.on_surface.default.hex}}; +@define-color sidebar_backdrop_color @window_bg_color; +@define-color sidebar_border_color @window_bg_color; + +@define-color secondary_sidebar_bg_color {{colors.surface.default.hex}}; +@define-color secondary_sidebar_fg_color {{colors.on_surface.default.hex}}; + +/* Backdrop/unfocused states */ +@define-color theme_unfocused_fg_color @window_fg_color; +@define-color theme_unfocused_text_color @view_fg_color; +@define-color theme_unfocused_bg_color @window_bg_color; +@define-color theme_unfocused_base_color @window_bg_color; +@define-color theme_unfocused_selected_bg_color @accent_bg_color; +@define-color theme_unfocused_selected_fg_color @accent_fg_color; diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/gtk4.css b/arch/.config/quickshell/noctalia-shell/Assets/Templates/gtk4.css new file mode 100644 index 0000000..7f6f4d0 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/gtk4.css @@ -0,0 +1,103 @@ +/* +* GTK Colors (GTK4) +* Generated by Noctalia's Template Processor +* stylelint-disable at-rule-no-unknown +*/ + +@define-color accent_color {{colors.primary.default.hex}}; +@define-color accent_bg_color {{colors.primary.default.hex}}; +@define-color accent_fg_color {{colors.on_primary.default.hex}}; + +@define-color destructive_bg_color {{colors.error.default.hex}}; +@define-color destructive_fg_color {{colors.on_error.default.hex}}; + +@define-color error_bg_color {{colors.error.default.hex}}; +@define-color error_fg_color {{colors.on_error.default.hex}}; + +@define-color window_bg_color {{colors.surface.default.hex}}; +@define-color window_fg_color {{colors.on_surface.default.hex}}; + +@define-color view_bg_color {{colors.surface.default.hex}}; +@define-color view_fg_color {{colors.on_surface.default.hex}}; + +@define-color headerbar_bg_color {{colors.surface.default.hex}}; +@define-color headerbar_fg_color {{colors.on_surface.default.hex}}; +@define-color headerbar_backdrop_color @window_bg_color; + +@define-color popover_bg_color {{colors.surface_container.default.hex}}; +@define-color popover_fg_color {{colors.on_surface.default.hex}}; + +@define-color card_bg_color {{colors.surface_container.default.hex}}; +@define-color card_fg_color {{colors.on_surface.default.hex}}; + +@define-color dialog_bg_color {{colors.surface.default.hex}}; +@define-color dialog_fg_color {{colors.on_surface.default.hex}}; + +@define-color overview_bg_color {{colors.surface_container.default.hex}}; +@define-color overview_fg_color {{colors.on_surface.default.hex}}; + +@define-color sidebar_bg_color {{colors.surface_container.default.hex}}; +@define-color sidebar_fg_color {{colors.on_surface.default.hex}}; +@define-color sidebar_backdrop_color @window_bg_color; +@define-color sidebar_border_color @window_bg_color; + +@define-color secondary_sidebar_bg_color {{colors.surface.default.hex}}; +@define-color secondary_sidebar_fg_color {{colors.on_surface.default.hex}}; + +/* Backdrop/unfocused states */ +@define-color theme_unfocused_fg_color @window_fg_color; +@define-color theme_unfocused_text_color @view_fg_color; +@define-color theme_unfocused_bg_color @window_bg_color; +@define-color theme_unfocused_base_color @window_bg_color; +@define-color theme_unfocused_selected_bg_color @accent_bg_color; +@define-color theme_unfocused_selected_fg_color @accent_fg_color; + +:root { + --accent-color: {{colors.primary.default.hex}}; + --accent-bg-color: {{colors.primary.default.hex}}; + --accent-fg-color: {{colors.on_primary.default.hex}}; + + --destructive-bg-color: {{colors.error.default.hex}}; + --destructive-fg-color: {{colors.on_error.default.hex}}; + + --error-bg-color: {{colors.error.default.hex}}; + --error-fg-color: {{colors.on_error.default.hex}}; + --error-color: {{colors.error.default.hex}}; + + --window-bg-color: {{colors.surface.default.hex}}; + --window-fg-color: {{colors.on_surface.default.hex}}; + + --view-bg-color: {{colors.surface.default.hex}}; + --view-fg-color: {{colors.on_surface.default.hex}}; + + --headerbar-bg-color: {{colors.surface.default.hex}}; + --headerbar-fg-color: {{colors.on_surface.default.hex}}; + --headerbar-backdrop-color: @window_bg_color; + + --popover-bg-color: {{colors.surface_container.default.hex}}; + --popover-fg-color: {{colors.on_surface.default.hex}}; + + --card-bg-color: {{colors.surface_container.default.hex}}; + --card-fg-color: {{colors.on_surface.default.hex}}; + + --dialog-bg-color: {{colors.surface.default.hex}}; + --dialog-fg-color: {{colors.on_surface.default.hex}}; + + --overview-bg-color: {{colors.surface_container.default.hex}}; + --overview-fg-color: {{colors.on_surface.default.hex}}; + + --sidebar-bg-color: {{colors.surface_container.default.hex}}; + --sidebar-fg-color: {{colors.on_surface.default.hex}}; + --sidebar-backdrop-color: @window_bg_color; + --sidebar-border-color: @window_bg_color; + + --warning-bg-color: {{colors.tertiary_container.default.hex}}; + --warning-fg-color: {{colors.on_tertiary_container.default.hex}}; + --warning-color: {{colors.tertiary.default.hex}}; + + --success-color: {{colors.secondary.default.hex}}; + --success-bg-color: {{colors.secondary_container.default.hex}}; + --success-fg-color: {{colors.on_secondary_container.default.hex}}; + + --shade-color: rgba(0, 0, 0, 0.36); +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/helix.toml b/arch/.config/quickshell/noctalia-shell/Assets/Templates/helix.toml new file mode 100644 index 0000000..93c6e54 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/helix.toml @@ -0,0 +1,162 @@ +# Syntax highlighting +# ------------------- +"attribute" = { fg = "primary", modifiers = ["bold"] } + +"type" = "primary" +"type.enum.variant" = "secondary" + +"constructor" = "tertiary" + +"constant" = "secondary" +"constant.character" = "secondaryContainer" +"constant.character.escape" = "tertiaryContainer" + +"string" = { fg = "tertiary", bg = "surfaceContainerHigh" } +"string.regexp" = { fg = "tertiaryContainer", bg = "surfaceContainerHigh" } +"string.special" = { fg = "tertiary", bg = "surfaceContainerHigh" } +"string.special.symbol" = { fg = "tertiary", bg = "surfaceContainerHigh" } +"string.special.path" = { fg = "secondary", underline = { color = "secondary", style = "line" } } + +"path" = { fg = "secondary", underline = { color = "secondary", style = "line" } } + +"comment" = { fg = "outline", bg = "surfaceContainerHigh", modifiers = ["italic"] } + +"variable" = "onSurface" +"variable.builtin" = "secondary" +"variable.parameter" = { fg = "onSurfaceVariant", modifiers = ["italic"] } +"variable.other.member" = "primary" + +"label" = "secondary" + +"punctuation" = "outlineVariant" +"punctuation.special" = "outline" + +"keyword" = { fg = "primary", modifiers = ["bold"] } +"keyword.control.conditional" = { fg = "tertiary", modifiers = ["italic", "bold"] } + +"operator" = { fg = "secondary", modifiers = ["bold"] } + +"function" = "tertiary" +"function.macro" = "tertiary" + +"tag" = "secondary" + +"namespace" = { fg = "secondary", modifiers = ["italic"] } + +"special" = "secondary" + +"markup.heading" = "primary" +"markup.list" = "onSurface" +"markup.bold" = { fg = "secondary", modifiers = ["bold"] } +"markup.italic" = { fg = "secondary", modifiers = ["italic"] } +"markup.link.url" = { fg = "secondary", modifiers = ["italic", "underlined"] } +"markup.link.label" = "primary" +"markup.raw" = "onSurface" +"markup.quote" = "secondary" + +"diff.plus" = "plus" +"diff.minus" = "error" +"diff.delta" = "delta" + +# User Interface +# -------------- +"ui.background" = "none" + +"ui.cursor" = { fg = "onSecondary", bg = "secondary" } +"ui.cursor.match" = { fg = "onTertiary", bg = "tertiary", modifiers = ["bold"] } +"ui.cursor.primary" = { fg = "onPrimary", bg = "primary" } + +"ui.linenr" = { fg = "onSurfaceVariant" } +"ui.linenr.selected" = { fg = "primary" } + +"ui.statusline" = { fg = "onSurface", bg = "surfaceContainerLow" } +"ui.statusline.inactive" = { fg = "onSurface", bg = "surfaceContainerLowest" } +"ui.statusline.normal" = { fg = "onPrimary", bg = "primary", modifiers = ["bold"] } +"ui.statusline.insert" = { fg = "onTertiary", bg = "tertiary", modifiers = ["bold"] } +"ui.statusline.select" = { fg = "onSecondary", bg = "secondary", modifiers = ["bold"] } + +"ui.bufferline" = { fg = "onSurface", bg = "surfaceContainerLowest" } +"ui.bufferline.active" = { fg = "onSurface", bg = "surfaceContainer", underline = { color = "primary", style = "line" } } +"ui.bufferline.background" = { bg = "surfaceContainerLowest" } + +"ui.popup" = { fg = "onSurface", bg = "surfaceContainerLow" } + +"ui.window" = { fg = "onSurface" } +"ui.help" = { fg = "onSurface", bg = "surfaceContainerLow" } + +"ui.text" = { fg = "onBackground" } +"ui.text.focus" = { fg = "onSurface", bg = "surfaceContainer", modifiers = ["bold"] } + +"ui.virtual" = "surfaceVariant" +"ui.virtual.ruler" = { bg = "surfaceContainerLow" } +"ui.virtual.indent-guide" = "surfaceVariant" +"ui.virtual.inlay-hint" = { fg = "onSurfaceVariant", bg = "surfaceContainer", modifiers = ["dim"] } +"ui.virtual.jump-label" = { fg = "primary", modifiers = ["bold"] } + +"ui.menu" = { fg = "onSurface", bg = "surfaceContainer" } +"ui.menu.selected" = { fg = "onPrimary", bg = "primary", modifiers = ["bold"] } + +"ui.selection" = { bg = "surfaceContainerHigh" } + +"ui.highlight" = { bg = "primaryContainer", modifiers = ["bold"] } + +"ui.cursorline" = { bg = "surfaceContainerLowest" } +"ui.cursorline.primary" = { bg = "surfaceContainerLow" } +"ui.cursorline.secondary" = { bg = "surfaceContainerLow" } + +error = "error" +warning = "warning" +info = "info" +hint = "hint" + +"diagnostic.hint" = { underline = { color = "hint", style = "curl" } } +"diagnostic.info" = { underline = { color = "info", style = "curl" } } +"diagnostic.warning" = { underline = { color = "warning", style = "curl" } } +"diagnostic.error" = { underline = { color = "error", style = "curl" } } +"diagnostic.unnecessary" = { modifiers = ["dim"] } + +[palette] +# Constants to be used in syntax highlighting, not meant for template processor. +warning = "#f9e2af" +info = "#89dceb" +hint = "#94e2d5" +plus = "#a6e3a1" +delta = "#89b4fa" + +# Template colors +primary = "{{colors.primary.default.hex}}" +surfaceTint = "{{colors.primary.default.hex}}" +onPrimary = "{{colors.on_primary.default.hex}}" +primaryContainer = "{{colors.primary_container.default.hex}}" +onPrimaryContainer = "{{colors.on_primary_container.default.hex}}" + +secondary = "{{colors.secondary.default.hex}}" +onSecondary = "{{colors.on_secondary.default.hex}}" +secondaryContainer = "{{colors.secondary_container.default.hex}}" +onSecondaryContainer = "{{colors.on_secondary_container.default.hex}}" + +tertiary = "{{colors.tertiary.default.hex}}" +onTertiary = "{{colors.on_tertiary.default.hex}}" +tertiaryContainer = "{{colors.tertiary_container.default.hex}}" +onTertiaryContainer = "{{colors.on_tertiary_container.default.hex}}" + +error = "{{colors.error.default.hex}}" +onError = "{{colors.on_error.default.hex}}" +errorContainer = "{{colors.error_container.default.hex}}" +onErrorContainer = "{{colors.on_error_container.default.hex}}" + +background = "{{colors.background.default.hex}}" +onBackground = "{{colors.on_background.default.hex}}" +surface = "{{colors.surface.default.hex}}" +onSurface = "{{colors.on_surface.default.hex}}" +surfaceVariant = "{{colors.surface_variant.default.hex}}" +onSurfaceVariant = "{{colors.on_surface_variant.default.hex}}" + +outline = "{{colors.outline.default.hex}}" +outlineVariant = "{{colors.outline_variant.default.hex}}" + +surfaceContainerLowest = "{{colors.surface_container_lowest.default.hex}}" +surfaceContainerLow = "{{colors.surface_container_low.default.hex}}" +surfaceContainer = "{{colors.surface_container.default.hex}}" +surfaceContainerHigh = "{{colors.surface_container_high.default.hex}}" +surfaceContainerHighest = "{{colors.surface_container_highest.default.hex}}" diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/hyprland.conf b/arch/.config/quickshell/noctalia-shell/Assets/Templates/hyprland.conf new file mode 100644 index 0000000..ad8d291 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/hyprland.conf @@ -0,0 +1,25 @@ +$primary = rgb({{colors.primary.default.hex_stripped}}) +$surface = rgb({{colors.surface.default.hex_stripped}}) +$secondary = rgb({{colors.secondary.default.hex_stripped}}) +$error = rgb({{colors.error.default.hex_stripped}}) +$tertiary = rgb({{colors.tertiary.default.hex_stripped}}) +$surface_lowest = rgb({{colors.surface_container_lowest.default.hex_stripped}}) + +general { + col.active_border = $primary + col.inactive_border = $surface +} + +group { + col.border_active = $secondary + col.border_inactive = $surface + col.border_locked_active = $error + col.border_locked_inactive = $surface + + groupbar { + col.active = $secondary + col.inactive = $surface + col.locked_active = $error + col.locked_inactive = $surface + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/hyprtoolkit.conf b/arch/.config/quickshell/noctalia-shell/Assets/Templates/hyprtoolkit.conf new file mode 100644 index 0000000..5af3568 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/hyprtoolkit.conf @@ -0,0 +1,7 @@ +background = rgba({{colors.background.default.hex_stripped}}ff) +base = rgba({{colors.surface.default.hex_stripped}}ff) +text = rgba({{colors.on_surface.default.hex_stripped}}ff) +alternate_base = rgba({{colors.surface_variant.default.hex_stripped}}ff) +bright_text = rgba({{colors.on_secondary.default.hex_stripped}}ff) +accent = rgba({{colors.primary.default.hex_stripped}}ff) +accent_secondary = rgba({{colors.secondary.default.hex_stripped}}ff) \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/kcolorscheme.colors b/arch/.config/quickshell/noctalia-shell/Assets/Templates/kcolorscheme.colors new file mode 100644 index 0000000..b4a61a7 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/kcolorscheme.colors @@ -0,0 +1,146 @@ +[KDE] +contrast=4 + +[General] +ColorScheme=Noctalia +Name=noctalia + +[ColorEffects:Disabled] +Color={{colors.surface_container_lowest.default.hex}} +ColorAmount=0 +ColorEffect=0 +ContrastAmount=0.65 +ContrastEffect=1 +IntensityAmount=0.1 +IntensityEffect=2 + +[ColorEffects:Inactive] +ChangeSelectionColor=true +Color={{colors.surface_variant.default.hex}} +ColorAmount=0.025 +ColorEffect=2 +ContrastAmount=0.1 +ContrastEffect=2 +Enable=false +IntensityAmount=0 +IntensityEffect=0 + +[Colors:Button] +BackgroundAlternate={{colors.surface_container_low.default.hex}} +BackgroundNormal={{colors.surface_container_high.default.hex}} +DecorationFocus={{colors.primary.default.hex}} +DecorationHover={{colors.primary.default.hex}} +ForegroundActive={{colors.primary.default.hex}} +ForegroundInactive={{colors.on_surface_variant.default.hex}} +ForegroundLink={{colors.secondary.default.hex}} +ForegroundNegative={{colors.error.default.hex}} +ForegroundNeutral={{colors.tertiary.default.hex}} +ForegroundNormal={{colors.on_surface.default.hex}} +ForegroundPositive={{colors.tertiary.default.hex}} +ForegroundVisited={{colors.on_secondary_container.default.hex}} + +[Colors:Complementary] +BackgroundAlternate={{colors.surface_container_low.default.hex}} +BackgroundNormal={{colors.surface.default.hex}} +DecorationFocus={{colors.primary.default.hex}} +DecorationHover={{colors.primary.default.hex}} +ForegroundActive={{colors.primary.default.hex}} +ForegroundInactive={{colors.on_surface_variant.default.hex}} +ForegroundLink={{colors.secondary.default.hex}} +ForegroundNegative={{colors.error.default.hex}} +ForegroundNeutral={{colors.tertiary.default.hex}} +ForegroundNormal={{colors.on_primary_container.default.hex}} +ForegroundPositive={{colors.tertiary.default.hex}} +ForegroundVisited={{colors.on_secondary_container.default.hex}} + +[Colors:Header] +BackgroundAlternate={{colors.surface.default.hex}} +BackgroundNormal={{colors.surface_container.default.hex}} +DecorationFocus={{colors.primary.default.hex}} +DecorationHover={{colors.primary.default.hex}} +ForegroundActive={{colors.primary.default.hex}} +ForegroundInactive={{colors.on_surface_variant.default.hex}} +ForegroundLink={{colors.secondary.default.hex}} +ForegroundNegative={{colors.error.default.hex}} +ForegroundNeutral={{colors.tertiary.default.hex}} +ForegroundNormal={{colors.on_surface.default.hex}} +ForegroundPositive={{colors.tertiary.default.hex}} +ForegroundVisited={{colors.on_secondary_container.default.hex}} + +[Colors:Header][Inactive] +BackgroundAlternate={{colors.surface_container.default.hex}} +BackgroundNormal={{colors.surface.default.hex}} +DecorationFocus={{colors.primary.default.hex}} +DecorationHover={{colors.primary.default.hex}} +ForegroundActive={{colors.primary.default.hex}} +ForegroundInactive={{colors.on_surface_variant.default.hex}} +ForegroundLink={{colors.secondary.default.hex}} +ForegroundNegative={{colors.error.default.hex}} +ForegroundNeutral={{colors.tertiary.default.hex}} +ForegroundNormal={{colors.on_surface.default.hex}} +ForegroundPositive={{colors.tertiary.default.hex}} +ForegroundVisited={{colors.on_secondary_container.default.hex}} + +[Colors:Selection] +BackgroundAlternate={{colors.surface_container_low.default.hex}} +BackgroundNormal={{colors.primary.default.hex}} +DecorationFocus={{colors.primary.default.hex}} +DecorationHover={{colors.primary.default.hex}} +ForegroundActive={{colors.on_primary.default.hex}} +ForegroundInactive={{colors.on_surface_variant.default.hex}} +ForegroundLink={{colors.secondary.default.hex}} +ForegroundNegative={{colors.error_container.default.hex}} +ForegroundNeutral={{colors.tertiary_container.default.hex}} +ForegroundNormal={{colors.on_primary.default.hex}} +ForegroundPositive={{colors.tertiary_container.default.hex}} +ForegroundVisited={{colors.on_secondary_container.default.hex}} + +[Colors:Tooltip] +BackgroundAlternate={{colors.surface.default.hex}} +BackgroundNormal={{colors.surface_container.default.hex}} +DecorationFocus={{colors.primary.default.hex}} +DecorationHover={{colors.primary.default.hex}} +ForegroundActive={{colors.primary.default.hex}} +ForegroundInactive={{colors.on_surface_variant.default.hex}} +ForegroundLink={{colors.secondary.default.hex}} +ForegroundNegative={{colors.error.default.hex}} +ForegroundNeutral={{colors.tertiary.default.hex}} +ForegroundNormal={{colors.on_background.default.hex}} +ForegroundPositive={{colors.tertiary.default.hex}} +ForegroundVisited={{colors.on_secondary_container.default.hex}} + +[Colors:View] +BackgroundAlternate={{colors.surface_container.default.hex}} +BackgroundNormal={{colors.background.default.hex}} +DecorationFocus={{colors.on_primary_container.default.hex}} +DecorationHover={{colors.on_primary.default.hex}} +ForegroundActive={{colors.primary.default.hex}} +ForegroundInactive={{colors.on_surface_variant.default.hex}} +ForegroundLink={{colors.secondary.default.hex}} +ForegroundNegative={{colors.error.default.hex}} +ForegroundNeutral={{colors.tertiary.default.hex}} +ForegroundNormal={{colors.on_background.default.hex}} +ForegroundPositive={{colors.tertiary.default.hex}} +ForegroundVisited={{colors.on_secondary_container.default.hex}} + +[Colors:Window] +BackgroundAlternate={{colors.primary_container.default.hex}} +BackgroundNormal={{colors.surface_container.default.hex}} +DecorationFocus={{colors.primary.default.hex}} +DecorationHover={{colors.primary.default.hex}} +ForegroundActive={{colors.primary.default.hex}} +ForegroundInactive={{colors.on_surface_variant.default.hex}} +ForegroundLink={{colors.secondary.default.hex}} +ForegroundNegative={{colors.error.default.hex}} +ForegroundNeutral={{colors.tertiary.default.hex}} +ForegroundNormal={{colors.on_background.default.hex}} +ForegroundPositive={{colors.tertiary.default.hex}} +ForegroundVisited={{colors.on_secondary_container.default.hex}} + +[WM] +activeBackground={{colors.primary_container.default.hex}} +activeBlend={{colors.on_primary_container.default.hex}} +activeForeground={{colors.on_primary_container.default.hex}} +inactiveBackground={{colors.surface.default.hex}} +inactiveBlend={{colors.on_surface_variant.default.hex}} +inactiveForeground={{colors.on_surface_variant.default.hex}} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/labwc.conf b/arch/.config/quickshell/noctalia-shell/Assets/Templates/labwc.conf new file mode 100644 index 0000000..82f7faa --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/labwc.conf @@ -0,0 +1,37 @@ +# window border +window.active.border.color: {{colors.primary.default.hex}} +window.inactive.border.color: {{colors.secondary.default.hex}} + +# ToggleKeybinds status indicator +window.active.indicator.toggled-keybind.color: {{colors.error.default.hex}} + +# window titlebar background +window.active.title.bg.color: {{colors.primary.default.hex}} +window.inactive.title.bg.color: {{colors.secondary.default.hex}} + +window.active.label.text.color: {{colors.on_primary.default.hex}} +window.inactive.label.text.color: {{colors.on_secondary.default.hex}} +window.label.text.justify: center + +# window button hover overlay +window.button.hover.bg.color: {{colors.hover.default.hex}} + +# window buttons +window.active.button.unpressed.image.color: {{colors.on_primary.default.hex}} +window.inactive.button.unpressed.image.color: {{colors.on_primary.default.hex}} + +# menu +menu.border.color: {{colors.on_primary.default.hex}} +menu.items.bg.color: {{colors.primary.default.hex}} +menu.items.text.color: {{colors.on_primary.default.hex}} +menu.items.active.bg.color: {{colors.secondary.default.hex}} +menu.items.active.text.color: {{colors.on_secondary.default.hex}} +menu.separator.color: {{colors.on_primary.default.hex}} +menu.title.bg.color: {{colors.primary.default.hex}} +menu.title.text.color: {{colors.on_primary.default.hex}} + +# on screen display (window-cycle dialog) +osd.bg.color: {{colors.primary.default.hex}} +osd.border.color: {{colors.on_primary.default.hex}} +osd.label.text.color: {{colors.on_primary.default.hex}} +osd.window-switcher.preview.border.color: {{colors.outline.default.hex}} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/mango.conf b/arch/.config/quickshell/noctalia-shell/Assets/Templates/mango.conf new file mode 100644 index 0000000..324ba48 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/mango.conf @@ -0,0 +1,12 @@ +# Noctalia Theme for Mango +# Generated automatically by Noctalia Shell + +shadowscolor = 0x{{colors.shadow.default.hex_stripped}}ff +rootcolor = 0x{{colors.surface.default.hex_stripped}}ff +bordercolor = 0x{{colors.outline.default.hex_stripped}}ff +focuscolor = 0x{{colors.primary.default.hex_stripped}}ff +maximizescreencolor = 0x{{colors.secondary.default.hex_stripped}}ff +urgentcolor = 0x{{colors.error.default.hex_stripped}}ff +scratchpadcolor = 0x{{colors.tertiary.default.hex_stripped}}ff +globalcolor = 0x{{colors.primary_container.default.hex_stripped}}ff +overlaycolor = 0x{{colors.secondary_container.default.hex_stripped}}ff diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/niri.kdl b/arch/.config/quickshell/noctalia-shell/Assets/Templates/niri.kdl new file mode 100644 index 0000000..c724a7c --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/niri.kdl @@ -0,0 +1,35 @@ +layout { + + focus-ring { + active-color "{{colors.primary.default.hex}}" + inactive-color "{{colors.surface.default.hex}}" + urgent-color "{{colors.error.default.hex}}" + } + + border { + active-color "{{colors.primary.default.hex}}" + inactive-color "{{colors.surface.default.hex}}" + urgent-color "{{colors.error.default.hex}}" + } + + shadow { + color "{{colors.shadow.default.hex}}70" + } + + tab-indicator { + active-color "{{colors.primary.default.hex}}" + inactive-color "{{colors.primary_container.default.hex}}" + urgent-color "{{colors.error.default.hex}}" + } + + insert-hint { + color "{{colors.primary.default.hex}}80" + } +} + +recent-windows { + highlight { + active-color "{{colors.primary.default.hex}}" + urgent-color "{{colors.error.default.hex}}" + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/noctalia.json b/arch/.config/quickshell/noctalia-shell/Assets/Templates/noctalia.json new file mode 100644 index 0000000..1fe2097 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/noctalia.json @@ -0,0 +1,25 @@ +{ + "mPrimary": "{{colors.primary.default.hex}}", + "mOnPrimary": "{{colors.on_primary.default.hex}}", + + "mSecondary": "{{colors.secondary.default.hex}}", + "mOnSecondary": "{{colors.on_secondary.default.hex}}", + + "mTertiary": "{{colors.tertiary.default.hex}}", + "mOnTertiary": "{{colors.on_tertiary.default.hex}}", + + "mError": "{{colors.error.default.hex}}", + "mOnError": "{{colors.on_error.default.hex}}", + + "mSurface": "{{colors.surface.default.hex}}", + "mOnSurface": "{{colors.on_surface.default.hex}}", + + "mSurfaceVariant": "{{colors.surface_container.default.hex}}", + "mOnSurfaceVariant": "{{colors.on_surface_variant.default.hex}}", + + "mOutline": "{{colors.outline_variant.default.hex}}", + "mShadow": "{{colors.shadow.default.hex}}", + + "mHover": "{{colors.tertiary.default.hex}}", + "mOnHover": "{{colors.on_tertiary.default.hex}}" +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/pywalfox.json b/arch/.config/quickshell/noctalia-shell/Assets/Templates/pywalfox.json new file mode 100644 index 0000000..8bda0db --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/pywalfox.json @@ -0,0 +1,22 @@ +{ + "wallpaper": "{{image}}", + "alpha": "100", + "colors": { + "color0": "{{colors.surface.dark.hex}}", + "color1": "", + "color2": "", + "color3": "{{colors.primary.default.hex}}", + "color4": "", + "color5": "{{colors.tertiary.default.hex}}", + "color6": "", + "color7": "{{colors.surface_container_highest.default.hex}}", + "color8": "", + "color9": "", + "color10": "{{colors.primary.default.hex}}", + "color11": "", + "color12": "", + "color13": "{{colors.secondary.default.hex}}", + "color14": "", + "color15": "{{colors.on_background.default.hex}}" + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/qtct.conf b/arch/.config/quickshell/noctalia-shell/Assets/Templates/qtct.conf new file mode 100644 index 0000000..b0f5f7d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/qtct.conf @@ -0,0 +1,8 @@ +[ColorScheme] +#https://doc.qt.io/archives/qt-5.15/qpalette.html +#https://doc.qt.io/qt-6/qpalette.html - Qt6 didn't add/delete/change any of the color functions or docs +# functions that correspond to the colors definition location +# windowText,button,light,midlight,dark,mid,text,brightText,buttonText,base,window,shadow,highlight,highlightedText,link,linkVisited,alternateBase,NO_IDEA (just use some sane default),toolTipBase,toolTipText,placeholderText,accent +active_colors={{colors.on_background.default.hex}}, {{colors.surface.default.hex}}, #ffffff, #cacaca, #9f9f9f, #b8b8b8, {{colors.on_background.default.hex}}, #ffffff, {{colors.on_surface.default.hex}}, {{colors.background.default.hex}}, {{colors.background.default.hex}}, {{colors.shadow.default.hex}}, {{colors.primary_container.default.hex}}, {{colors.on_primary_container.default.hex}}, {{colors.secondary.default.hex}}, {{colors.primary.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.primary.default.hex}} +disabled_colors={{colors.on_background.default.hex}}, {{colors.surface.default.hex}}, #ffffff, #cacaca, #9f9f9f, #b8b8b8, {{colors.on_background.default.hex}}, #ffffff, {{colors.on_surface.default.hex}}, {{colors.background.default.hex}}, {{colors.background.default.hex}}, {{colors.shadow.default.hex}}, {{colors.primary_container.default.hex}}, {{colors.on_primary_container.default.hex}}, {{colors.secondary.default.hex}}, {{colors.primary.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.primary.default.hex}} +inactive_colors={{colors.on_background.default.hex}}, {{colors.surface.default.hex}}, #ffffff, #cacaca, #9f9f9f, #b8b8b8, {{colors.on_background.default.hex}}, #ffffff, {{colors.on_surface.default.hex}}, {{colors.background.default.hex}}, {{colors.background.default.hex}}, {{colors.shadow.default.hex}}, {{colors.primary_container.default.hex}}, {{colors.on_primary_container.default.hex}}, {{colors.secondary.default.hex}}, {{colors.primary.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.primary.default.hex}} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/scroll b/arch/.config/quickshell/noctalia-shell/Assets/Templates/scroll new file mode 100644 index 0000000..bcd8357 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/scroll @@ -0,0 +1,22 @@ +set $primary #{{colors.primary.default.hex_stripped}} +set $on_primary #{{colors.on_primary.default.hex_stripped}} +set $secondary #{{colors.secondary.default.hex_stripped}} +set $on_secondary_transp #{{colors.on_secondary.default.hex_stripped}}AA +set $tertiary #{{colors.tertiary.default.hex_stripped}} +set $error #{{colors.error.default.hex_stripped}} +set $surface #{{colors.surface.default.hex_stripped}} +set $on_surface #{{colors.on_surface.default.hex_stripped}} +set $on_surface_variant #{{colors.on_surface_variant.default.hex_stripped}} +set $outline #{{colors.outline.default.hex_stripped}} + +## Window Colours +# class border backgr. text indicator child_border +client.focused $primary $surface $on_surface $primary $primary +client.focused_inactive $outline $surface $on_surface_variant $outline $outline +client.unfocused $outline $surface $on_surface_variant $outline $outline +client.urgent $error $surface $on_surface $error $error +client.placeholder $surface $surface $on_surface_variant $surface $surface +client.background $surface + +jump_labels_color $secondary +jump_labels_background $on_secondary_transp \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/spicetify.ini b/arch/.config/quickshell/noctalia-shell/Assets/Templates/spicetify.ini new file mode 100644 index 0000000..55c6865 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/spicetify.ini @@ -0,0 +1,27 @@ +[Comfy] +text = {{colors.on_surface.default.hex_stripped}} +subtext = {{colors.on_surface_variant.default.hex_stripped}} +main = {{colors.surface.default.hex_stripped}} +main-elevated = {{colors.surface.default.hex_stripped}} +main-transition = {{colors.surface_container_lowest.default.hex_stripped}} +highlight = {{colors.surface_container_low.default.hex_stripped}} +highlight-elevated = {{colors.surface_container_highest.default.hex_stripped}} +sidebar = {{colors.surface.default.hex_stripped}} +player = {{colors.surface.default.hex_stripped}} +card = {{colors.surface.default.hex_stripped}} +shadow = {{colors.surface.default.hex_stripped}} +selected-row = {{colors.on_surface.default.hex_stripped}} +button = {{colors.primary.default.hex_stripped}} +button-active = {{colors.primary.default.hex_stripped}} +button-disabled = {{colors.primary.default.hex_stripped}} +tab-active = {{colors.surface.default.hex_stripped}} +notification = {{colors.tertiary.default.hex_stripped}} +notification-error = {{colors.error.default.hex_stripped}} +misc = {{colors.surface.default.hex_stripped}} +play-button = {{colors.secondary.default.hex_stripped}} +play-button-active = {{colors.secondary.default.hex_stripped}} +progress-fg = {{colors.primary.default.hex_stripped}} +progress-bg = {{colors.surface.default.hex_stripped}} +heart = {{colors.error.default.hex_stripped}} +pagelink-active = {{colors.on_tertiary_container.default.hex_stripped}} +radio-btn-active = {{colors.on_tertiary_container.default.hex_stripped}} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/steam.css b/arch/.config/quickshell/noctalia-shell/Assets/Templates/steam.css new file mode 100644 index 0000000..5a7f63c --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/steam.css @@ -0,0 +1,7 @@ +:root { + --theme-color: "Matugen"; + --hue-rotate: 220deg; + <* for name, value in colors *> + --md-sys-color-{{name | replace: "_", "-" }}: {{value.default.rgb}}; + <* endfor *> +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/sway b/arch/.config/quickshell/noctalia-shell/Assets/Templates/sway new file mode 100644 index 0000000..fa4978f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/sway @@ -0,0 +1,18 @@ +set $primary #{{colors.primary.default.hex_stripped}} +set $on_primary #{{colors.on_primary.default.hex_stripped}} +set $secondary #{{colors.secondary.default.hex_stripped}} +set $tertiary #{{colors.tertiary.default.hex_stripped}} +set $error #{{colors.error.default.hex_stripped}} +set $surface #{{colors.surface.default.hex_stripped}} +set $on_surface #{{colors.on_surface.default.hex_stripped}} +set $on_surface_variant #{{colors.on_surface_variant.default.hex_stripped}} +set $outline #{{colors.outline.default.hex_stripped}} + +## Window Colours +# class border backgr. text indicator child_border +client.focused $primary $surface $on_surface $primary $primary +client.focused_inactive $outline $surface $on_surface_variant $outline $outline +client.unfocused $outline $surface $on_surface_variant $outline $outline +client.urgent $error $surface $on_surface $error $error +client.placeholder $surface $surface $on_surface_variant $surface $surface +client.background $surface diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/telegram.tdesktop-theme b/arch/.config/quickshell/noctalia-shell/Assets/Templates/telegram.tdesktop-theme new file mode 100644 index 0000000..f2d9e67 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/telegram.tdesktop-theme @@ -0,0 +1,139 @@ +// Material You theme for Telegram Desktop +// Generated by Noctalia's Template Processor + +COLOR_GRAY: {{colors.outline.default.hex}}; +COLOR_DARK: {{colors.surface_variant.default.hex}}; + +windowBg: {{colors.background.default.hex}}; // Main background +windowFg: {{colors.on_background.default.hex}}; // Main text +windowBgOver: {{colors.surface_variant.default.hex}}; // Generic background on hover +windowBgRipple: {{colors.surface_variant.default.hex}}; // Ripple effect +windowFgOver: {{colors.on_surface_variant.default.hex}}; // Text on hover +windowSubTextFg: {{colors.outline.default.hex}}; // Minor text +windowSubTextFgOver: {{colors.outline.default.hex}}; // Minor text on hover +windowBoldFg: {{colors.on_background.default.hex}}; // Bold text +windowBoldFgOver: {{colors.on_surface_variant.default.hex}}; // Bold text on hover +windowBgActive: {{colors.primary.default.hex}}; // Active items background +windowFgActive: {{colors.on_primary.default.hex}}; // Active items text +windowActiveTextFg: {{colors.primary.default.hex}}; // Active items text +windowShadowFg: {{colors.shadow.default.hex}}; // Window shadow +windowShadowFgFallback: {{colors.shadow.default.hex}}; // Fallback for shadow +historyOutIconFg: {{colors.primary.default.hex}}; +historyIconFgInverted: {{colors.on_surface.default.hex}}; + +msgServiceBg: {{colors.primary_container.default.hex}}; +msgServiceFg: {{colors.on_surface.default.hex}}; +msgOutBg: {{colors.primary_container.default.hex}}; +msgOutBgSelected : {{colors.tertiary_container.default.hex}}; +msgOutServiceFg: {{colors.on_surface.default.hex}}; +msgOutDateFg: {{colors.on_surface.default.hex}}; +historySentIconFg: {{colors.on_surface.default.hex}}; +msgOutDateFgSelected: {{colors.on_surface.default.hex}}; +msgDateImgFg: {{colors.on_surface.default.hex}}; +dialogsSentIconFg: {{colors.primary.default.hex}}; +dialogsSentIconFgOver: {{colors.primary.default.hex}}; +dialogsOnlineBadgeFg: {{colors.primary.default.hex}}; + + +shadowFg: {{colors.shadow.default.hex}}; // General shadow +slideFadeOutBg: {{colors.background.default.hex}}; +slideFadeOutShadowFg: {{colors.shadow.default.hex}}; + +imageBg: {{colors.surface.default.hex}}; +imageBgTransparent: {{colors.surface.default.hex}}; + +activeButtonBg: {{colors.primary.default.hex}}; // Active button background +activeButtonBgOver: {{colors.primary_container.default.hex}}; // Active button hover background +activeButtonBgRipple: {{colors.on_primary_container.default.hex}}; // Active button ripple +activeButtonFg: {{colors.on_primary.default.hex}}; // Active button text +activeButtonFgOver: {{colors.on_primary_container.default.hex}}; // Active button hover text +activeButtonSecondaryFg: {{colors.on_primary.default.hex}}; // Active button secondary text +activeButtonSecondaryFgOver: {{colors.on_primary_container.default.hex}}; // Active button secondary hover text +activeLineFg: {{colors.on_surface.default.hex}}; +dialogsBgActive: {{colors.primary.default.hex}}; + +lightButtonBg: {{colors.surface.default.hex}}; // Light button background +lightButtonBgOver: {{colors.surface_variant.default.hex}}; // Light button hover background +lightButtonBgRipple: {{colors.primary.default.hex}}; // Light button ripple +lightButtonFg: {{colors.on_surface.default.hex}}; // Light button text +lightButtonFgOver: {{colors.on_surface_variant.default.hex}}; // Light button hover text + +attentionButtonFg: {{colors.error.default.hex}}; +attentionButtonFgOver: {{colors.error.default.hex}}; +attentionButtonBgOver: {{colors.error_container.default.hex}}; +attentionButtonBgRipple: {{colors.on_error_container.default.hex}}; + +outlineButtonBg: {{colors.surface.default.hex}}; // Outline button background +outlineButtonBgOver: {{colors.surface_variant.default.hex}}; // Outline button hover background +outlineButtonOutlineFg: {{colors.primary.default.hex}}; // Outline button color +outlineButtonBgRipple: {{colors.primary.default.hex}}; // Outline button ripple + +menuBg: {{colors.surface.default.hex}}; +menuBgOver: {{colors.surface_variant.default.hex}}; +menuBgRipple: {{colors.primary.default.hex}}; +menuIconFg: {{colors.on_surface.default.hex}}; +menuIconFgOver: {{colors.on_surface_variant.default.hex}}; +menuSubmenuArrowFg: {{colors.outline.default.hex}}; +menuFgDisabled: {{colors.outline.default.hex}}; +menuSeparatorFg: {{colors.outline.default.hex}}; + +scrollBarBg: {{colors.primary.default.hex}}40; // Scroll bar background (40% opacity) +scrollBarBgOver: {{colors.primary.default.hex}}60; // Scroll bar hover background (60% opacity) +scrollBg: {{colors.surface_variant.default.hex}}40; // Scroll bar track (40% opacity) +scrollBgOver: {{colors.surface_variant.default.hex}}60; // Scroll bar track on hover (60% opacity) + +smallCloseIconFg: {{colors.outline.default.hex}}; +smallCloseIconFgOver: {{colors.on_surface_variant.default.hex}}; + +radialFg: {{colors.primary.default.hex}}; +radialBg: {{colors.surface.default.hex}}; + +placeholderFg: {{colors.outline.default.hex}}; // Placeholder text +placeholderFgActive: {{colors.primary.default.hex}}; // Active placeholder text +inputBorderFg: {{colors.outline.default.hex}}; // Input border +filterInputBorderFg: {{colors.outline.default.hex}}; // Search input border +filterInputInactiveBg: {{colors.surface.default.hex}}; // Inactive search input background +checkboxFg: {{colors.primary.default.hex}}; // Checkbox color + +titleBg: {{colors.surface.default.hex}}; // Window title background +titleShadow: {{colors.shadow.default.hex}}; +titleButtonFg: {{colors.on_surface.default.hex}}; // Title button color +titleButtonBgOver: {{colors.surface_variant.default.hex}}; // Title button hover background +titleButtonFgOver: {{colors.on_surface_variant.default.hex}}; // Title button hover color +titleButtonCloseBgOver: {{colors.error.default.hex}}; +titleButtonCloseFgOver: {{colors.on_error.default.hex}}; +titleFgActive: {{colors.on_surface.default.hex}}; // Active title text +titleFg: {{colors.on_surface.default.hex}}; // Inactive title text + +trayCounterBg: {{colors.error.default.hex}}; // Tray counter background +trayCounterBgMute: {{colors.outline.default.hex}}; // Muted tray counter background +trayCounterFg: {{colors.on_error.default.hex}}; // Tray counter text +trayCounterBgMacInvert: {{colors.error.default.hex}}; // Mac tray counter +trayCounterFgMacInvert: {{colors.on_error.default.hex}}; // Mac tray counter text + +layerBg: {{colors.surface.default.hex}}99; // Layer background (60% opacity) + +cancelIconFg: {{colors.error.default.hex}}; // Cancel icon +cancelIconFgOver: {{colors.error.default.hex}}; // Cancel icon on hover + +boxBg: {{colors.surface.default.hex}}; // Box background +boxTextFg: {{colors.on_surface.default.hex}}; // Box text +boxTextFgGood: {{colors.primary.default.hex}}; // Box good text +boxTextFgError: {{colors.error.default.hex}}; // Box error text +boxTitleFg: {{colors.on_surface.default.hex}}; // Box title text +boxSearchBg: {{colors.surface.default.hex}}; // Box search field background +boxSearchCancelIconFg: {{colors.error.default.hex}}; // Box search cancel icon +boxSearchCancelIconFgOver: {{colors.error.default.hex}}; // Box search cancel icon on hover + +contactsBg: {{colors.surface.default.hex}}; // Contacts background +contactsBgOver: {{colors.surface_variant.default.hex}}; // Contacts background on hover +contactsNameFg: {{colors.on_surface.default.hex}}; // Contact name +contactsStatusFg: {{colors.outline.default.hex}}; // Contact status +contactsStatusFgOver: {{colors.on_surface_variant.default.hex}}; // Contact status on hover +contactsStatusFgOnline: {{colors.primary.default.hex}}; // Online contact status + +photoCropFadeBg: {{colors.surface.default.hex}}cc; // Photo crop fade background +photoCropPointFg: {{colors.primary.default.hex}}; // Photo crop points + +chat_inBubbleSelected: #313244; // inbox selected chat background +chat_outBubbleSelected: #313244; // outbox selected chat background diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/alacritty-predefined.toml b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/alacritty-predefined.toml new file mode 100644 index 0000000..a3ba707 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/alacritty-predefined.toml @@ -0,0 +1,33 @@ +# Colors (Noctalia) + +[colors.bright] +black = '{{colors.terminal_bright_black.default.hex}}' +blue = '{{colors.terminal_bright_blue.default.hex}}' +cyan = '{{colors.terminal_bright_cyan.default.hex}}' +green = '{{colors.terminal_bright_green.default.hex}}' +magenta = '{{colors.terminal_bright_magenta.default.hex}}' +red = '{{colors.terminal_bright_red.default.hex}}' +white = '{{colors.terminal_bright_white.default.hex}}' +yellow = '{{colors.terminal_bright_yellow.default.hex}}' + +[colors.cursor] +cursor = '{{colors.terminal_cursor.default.hex}}' +text = '{{colors.terminal_cursor_text.default.hex}}' + +[colors.normal] +black = '{{colors.terminal_normal_black.default.hex}}' +blue = '{{colors.terminal_normal_blue.default.hex}}' +cyan = '{{colors.terminal_normal_cyan.default.hex}}' +green = '{{colors.terminal_normal_green.default.hex}}' +magenta = '{{colors.terminal_normal_magenta.default.hex}}' +red = '{{colors.terminal_normal_red.default.hex}}' +white = '{{colors.terminal_normal_white.default.hex}}' +yellow = '{{colors.terminal_normal_yellow.default.hex}}' + +[colors.primary] +background = '{{colors.terminal_background.default.hex}}' +foreground = '{{colors.terminal_foreground.default.hex}}' + +[colors.selection] +background = '{{colors.terminal_selection_bg.default.hex}}' +text = '{{colors.terminal_selection_fg.default.hex}}' diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/alacritty.toml b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/alacritty.toml new file mode 100644 index 0000000..f0bb7cd --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/alacritty.toml @@ -0,0 +1,68 @@ +[colors.primary] +background = '{{colors.background.default.hex}}' +foreground = '{{colors.on_surface.default.hex}}' + +[colors.cursor] +text = '{{colors.on_surface.default.hex}}' +cursor = '{{colors.on_surface_variant.default.hex}}' + +[colors.vi_mode_cursor] +text = '{{colors.background.default.hex}}' +cursor = '{{colors.primary.default.hex}}' + +[colors.search.matches] +foreground = '{{colors.surface_variant.default.hex}}' +background = '{{colors.tertiary.default.hex}}' + +[colors.search.focused_match] +foreground = '{{colors.surface_variant.default.hex}}' +background = '{{colors.primary.default.hex}}' + +[colors.footer_bar] +foreground = '{{colors.surface_variant.default.hex}}' +background = '{{colors.inverse_surface.default.hex}}' + +[colors.hints.start] +foreground = '{{colors.surface_variant.default.hex}}' +background = '{{colors.secondary.default.hex}}' + +[colors.hints.end] +foreground = '{{colors.surface_variant.default.hex}}' +background = '{{colors.secondary.default.hex}}' + +[colors.selection] +text = '{{colors.background.default.hex}}' +background = '{{colors.primary.default.hex}}' + + +[colors.normal] +black = '#181818' +red = '{{colors.error.default.hex}}' +green = '{{colors.primary.default.hex}}' +yellow = '{{colors.inverse_primary.default.hex}}' +blue = '{{colors.primary.default.hex}}' +magenta = '{{colors.tertiary.default.hex}}' +cyan = '{{colors.secondary.default.hex}}' +white = '#BAC2DE' + + +[colors.bright] +black = '#585B70' +red = '#F38BA8' +green = '#A6E3A1' +yellow = '#F9E2AF' +blue = '#89B4FA' +magenta = '#F5C2E7' +cyan = '#94E2D5' +white = '#A6ADC8' + + +[colors.dim] +black = '#45475A' +red = '#F38BA8' +green = '#A6E3A1' +yellow = '#F9E2AF' +blue = '#89B4FA' +magenta = '#F5C2E7' +cyan = '#94E2D5' +white = '#BAC2DE' \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/foot b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/foot new file mode 100644 index 0000000..ee4d7b1 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/foot @@ -0,0 +1,24 @@ +[colors-dark] +background = {{colors.surface.default.hex_stripped}} +foreground = {{colors.on_surface.default.hex_stripped}} + +selection-background = {{colors.tertiary.default.hex_stripped}} +selection-foreground = {{colors.on_tertiary.default.hex_stripped}} + +regular0 = {{colors.surface_variant.default.hex_stripped}} +regular1 = {{colors.error.default.hex_stripped}} +regular2 = {{colors.primary.default.hex_stripped}} +regular3 = {{colors.secondary.default.hex_stripped}} +regular4 = {{colors.tertiary.default.hex_stripped}} +regular5 = {{colors.primary_fixed_dim.default.hex_stripped}} +regular6 = {{colors.secondary_fixed_dim.default.hex_stripped}} +regular7 = {{colors.on_surface.default.hex_stripped}} + +bright0 = {{colors.on_surface_variant.default.hex_stripped}} +bright1 = {{colors.error.default.hex_stripped}} +bright2 = {{colors.primary.default.hex_stripped}} +bright3 = {{colors.secondary.default.hex_stripped}} +bright4 = {{colors.tertiary.default.hex_stripped}} +bright5 = {{colors.primary_fixed_dim.default.hex_stripped}} +bright6 = {{colors.secondary_fixed_dim.default.hex_stripped}} +bright7 = {{colors.on_surface.default.hex_stripped}} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/foot-predefined b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/foot-predefined new file mode 100644 index 0000000..ccee2e3 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/foot-predefined @@ -0,0 +1,22 @@ +[colors-dark] +foreground={{colors.terminal_foreground.default.hex_stripped}} +background={{colors.terminal_background.default.hex_stripped}} +regular0={{colors.terminal_normal_black.default.hex_stripped}} +regular1={{colors.terminal_normal_red.default.hex_stripped}} +regular2={{colors.terminal_normal_green.default.hex_stripped}} +regular3={{colors.terminal_normal_yellow.default.hex_stripped}} +regular4={{colors.terminal_normal_blue.default.hex_stripped}} +regular5={{colors.terminal_normal_magenta.default.hex_stripped}} +regular6={{colors.terminal_normal_cyan.default.hex_stripped}} +regular7={{colors.terminal_normal_white.default.hex_stripped}} +bright0={{colors.terminal_bright_black.default.hex_stripped}} +bright1={{colors.terminal_bright_red.default.hex_stripped}} +bright2={{colors.terminal_bright_green.default.hex_stripped}} +bright3={{colors.terminal_bright_yellow.default.hex_stripped}} +bright4={{colors.terminal_bright_blue.default.hex_stripped}} +bright5={{colors.terminal_bright_magenta.default.hex_stripped}} +bright6={{colors.terminal_bright_cyan.default.hex_stripped}} +bright7={{colors.terminal_bright_white.default.hex_stripped}} +selection-foreground={{colors.terminal_selection_fg.default.hex_stripped}} +selection-background={{colors.terminal_selection_bg.default.hex_stripped}} +cursor={{colors.terminal_cursor_text.default.hex_stripped}} {{colors.terminal_cursor.default.hex_stripped}} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/ghostty b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/ghostty new file mode 100644 index 0000000..131561d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/ghostty @@ -0,0 +1,22 @@ +palette = 0= {{colors.surface_variant.default.hex}} +palette = 1= {{colors.error.default.hex}} +palette = 2= {{colors.primary.default.hex}} +palette = 3= {{colors.secondary.default.hex}} +palette = 4= {{colors.tertiary.default.hex}} +palette = 5= {{colors.primary_fixed_dim.default.hex}} +palette = 6= {{colors.secondary_fixed_dim.default.hex}} +palette = 7= {{colors.on_surface.default.hex}} +palette = 8= {{colors.on_surface_variant.default.hex}} +palette = 9= {{colors.error.default.hex}} +palette = 10= {{colors.primary.default.hex}} +palette = 11= {{colors.secondary.default.hex}} +palette = 12= {{colors.tertiary.default.hex}} +palette = 13= {{colors.primary_fixed_dim.default.hex}} +palette = 14= {{colors.secondary_fixed_dim.default.hex}} +palette = 15= {{colors.on_surface.default.hex}} +background = {{colors.surface.default.hex}} +foreground = {{colors.on_surface.default.hex}} +cursor-color = {{colors.on_surface.default.hex}} +cursor-text = {{colors.surface.default.hex}} +selection-background = {{colors.tertiary.default.hex}} +selection-foreground = {{colors.on_tertiary.default.hex}} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/ghostty-predefined b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/ghostty-predefined new file mode 100644 index 0000000..6f79e9b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/ghostty-predefined @@ -0,0 +1,22 @@ +palette = 0={{colors.terminal_normal_black.default.hex}} +palette = 1={{colors.terminal_normal_red.default.hex}} +palette = 2={{colors.terminal_normal_green.default.hex}} +palette = 3={{colors.terminal_normal_yellow.default.hex}} +palette = 4={{colors.terminal_normal_blue.default.hex}} +palette = 5={{colors.terminal_normal_magenta.default.hex}} +palette = 6={{colors.terminal_normal_cyan.default.hex}} +palette = 7={{colors.terminal_normal_white.default.hex}} +palette = 8={{colors.terminal_bright_black.default.hex}} +palette = 9={{colors.terminal_bright_red.default.hex}} +palette = 10={{colors.terminal_bright_green.default.hex}} +palette = 11={{colors.terminal_bright_yellow.default.hex}} +palette = 12={{colors.terminal_bright_blue.default.hex}} +palette = 13={{colors.terminal_bright_magenta.default.hex}} +palette = 14={{colors.terminal_bright_cyan.default.hex}} +palette = 15={{colors.terminal_bright_white.default.hex}} +background = {{colors.terminal_background.default.hex}} +foreground = {{colors.terminal_foreground.default.hex}} +cursor-color = {{colors.terminal_cursor.default.hex}} +cursor-text = {{colors.terminal_cursor_text.default.hex}} +selection-background = {{colors.terminal_selection_bg.default.hex}} +selection-foreground = {{colors.terminal_selection_fg.default.hex}} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/kitty-predefined.conf b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/kitty-predefined.conf new file mode 100644 index 0000000..7b3087d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/kitty-predefined.conf @@ -0,0 +1,30 @@ +color0 {{colors.terminal_normal_black.default.hex}} +color1 {{colors.terminal_normal_red.default.hex}} +color2 {{colors.terminal_normal_green.default.hex}} +color3 {{colors.terminal_normal_yellow.default.hex}} +color4 {{colors.terminal_normal_blue.default.hex}} +color5 {{colors.terminal_normal_magenta.default.hex}} +color6 {{colors.terminal_normal_cyan.default.hex}} +color7 {{colors.terminal_normal_white.default.hex}} +color8 {{colors.terminal_bright_black.default.hex}} +color9 {{colors.terminal_bright_red.default.hex}} +color10 {{colors.terminal_bright_green.default.hex}} +color11 {{colors.terminal_bright_yellow.default.hex}} +color12 {{colors.terminal_bright_blue.default.hex}} +color13 {{colors.terminal_bright_magenta.default.hex}} +color14 {{colors.terminal_bright_cyan.default.hex}} +color15 {{colors.terminal_bright_white.default.hex}} +background {{colors.terminal_background.default.hex}} +selection_foreground {{colors.terminal_selection_fg.default.hex}} +cursor {{colors.terminal_cursor.default.hex}} +cursor_text_color {{colors.terminal_cursor_text.default.hex}} +foreground {{colors.terminal_foreground.default.hex}} +selection_background {{colors.terminal_selection_bg.default.hex}} +active_border_color {{colors.primary.default.hex}} +inactive_border_color {{colors.secondary.default.hex}} + +active_tab_foreground {{colors.on_primary.default.hex}} +active_tab_background {{colors.primary.default.hex}} +inactive_tab_foreground {{colors.on_surface_variant.default.hex}} +inactive_tab_background {{colors.surface_variant.default.hex}} +cursor_trail_color {{colors.on_surface_variant.default.hex}} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/kitty.conf b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/kitty.conf new file mode 100644 index 0000000..56eb0eb --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/kitty.conf @@ -0,0 +1,32 @@ +color0 {{colors.surface.default.hex}} +color1 {{colors.error.default.hex}} +color2 {{colors.primary.default.hex}} +color3 {{colors.secondary.default.hex}} +color4 {{colors.tertiary.default.hex}} +color5 {{colors.primary_fixed_dim.default.hex}} +color6 {{colors.secondary_fixed_dim.default.hex}} +color7 {{colors.on_surface.default.hex}} +color8 {{colors.outline.default.hex}} +color9 {{colors.error.default.hex}} +color10 {{colors.primary.default.hex}} +color11 {{colors.secondary.default.hex}} +color12 {{colors.tertiary.default.hex}} +color13 {{colors.primary_fixed_dim.default.hex}} +color14 {{colors.secondary_fixed_dim.default.hex}} +color15 {{colors.on_surface.default.hex}} + +cursor {{colors.on_surface.default.hex}} +cursor_text_color {{colors.surface.default.hex}} +background {{colors.surface.default.hex}} +foreground {{colors.on_surface.default.hex}} +selection_foreground {{colors.on_surface_variant.default.hex}} +selection_background {{colors.surface_variant.default.hex}} +active_border_color {{colors.primary.default.hex}} +inactive_border_color {{colors.surface_variant.default.hex}} +url_color {{colors.primary.default.hex}} + +active_tab_foreground {{colors.on_primary.default.hex}} +active_tab_background {{colors.primary.default.hex}} +inactive_tab_foreground {{colors.on_surface_variant.default.hex}} +inactive_tab_background {{colors.surface_variant.default.hex}} +cursor_trail_color {{colors.on_surface_variant.default.hex}} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/wezterm-predefined.toml b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/wezterm-predefined.toml new file mode 100644 index 0000000..da4c226 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/wezterm-predefined.toml @@ -0,0 +1,84 @@ +[colors] +ansi = [ + "{{colors.terminal_normal_black.default.hex}}", + "{{colors.terminal_normal_red.default.hex}}", + "{{colors.terminal_normal_green.default.hex}}", + "{{colors.terminal_normal_yellow.default.hex}}", + "{{colors.terminal_normal_blue.default.hex}}", + "{{colors.terminal_normal_magenta.default.hex}}", + "{{colors.terminal_normal_cyan.default.hex}}", + "{{colors.terminal_normal_white.default.hex}}", +] +background = "{{colors.terminal_background.default.hex}}" +brights = [ + "{{colors.terminal_bright_black.default.hex}}", + "{{colors.terminal_bright_red.default.hex}}", + "{{colors.terminal_bright_green.default.hex}}", + "{{colors.terminal_bright_yellow.default.hex}}", + "{{colors.terminal_bright_blue.default.hex}}", + "{{colors.terminal_bright_magenta.default.hex}}", + "{{colors.terminal_bright_cyan.default.hex}}", + "{{colors.terminal_bright_white.default.hex}}", +] +compose_cursor = "{{colors.terminal_cursor.default.hex}}" +cursor_bg = "{{colors.terminal_cursor.default.hex}}" +cursor_border = "{{colors.terminal_cursor.default.hex}}" +cursor_fg = "{{colors.terminal_cursor_text.default.hex}}" +foreground = "{{colors.terminal_foreground.default.hex}}" +scrollbar_thumb = "{{colors.terminal_selection_bg.default.hex}}" +selection_bg = "{{colors.terminal_selection_bg.default.hex}}" +selection_fg = "{{colors.terminal_selection_fg.default.hex}}" +split = "{{colors.terminal_bright_black.default.hex}}" +visual_bell = "{{colors.terminal_normal_black.default.hex}}" + +[colors.indexed] +16 = "{{colors.secondary.default.hex}}" +17 = "{{colors.terminal_cursor.default.hex}}" + +[colors.tab_bar] +background = "{{colors.terminal_background.default.hex | darken 0.1}}" +inactive_tab_edge = "{{colors.terminal_selection_bg.default.hex}}" + +[colors.tab_bar.active_tab] +bg_color = "{{colors.primary.default.hex}}" +fg_color = "{{colors.on_primary.default.hex}}" +intensity = "Normal" +italic = false +strikethrough = false +underline = "None" + +[colors.tab_bar.inactive_tab] +bg_color = "{{colors.terminal_background.default.hex | darken 0.05}}" +fg_color = "{{colors.terminal_foreground.default.hex}}" +intensity = "Normal" +italic = false +strikethrough = false +underline = "None" + +[colors.tab_bar.inactive_tab_hover] +bg_color = "{{colors.terminal_background.default.hex}}" +fg_color = "{{colors.terminal_foreground.default.hex}}" +intensity = "Normal" +italic = false +strikethrough = false +underline = "None" + +[colors.tab_bar.new_tab] +bg_color = "{{colors.terminal_selection_bg.default.hex}}" +fg_color = "{{colors.terminal_foreground.default.hex}}" +intensity = "Normal" +italic = false +strikethrough = false +underline = "None" + +[colors.tab_bar.new_tab_hover] +bg_color = "{{colors.terminal_bright_black.default.hex}}" +fg_color = "{{colors.terminal_foreground.default.hex}}" +intensity = "Normal" +italic = false +strikethrough = false +underline = "None" + +[metadata] +author = "Noctalia" +name = "Noctalia" diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/wezterm.toml b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/wezterm.toml new file mode 100644 index 0000000..4c73b0e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/terminal/wezterm.toml @@ -0,0 +1,31 @@ +[colors] +ansi = [ + "{{colors.surface.default.hex}}", + "{{colors.error.default.hex}}", + "{{colors.tertiary.default.hex}}", + "{{colors.secondary.default.hex}}", + "{{colors.primary.default.hex}}", + "{{colors.tertiary_fixed_dim.default.hex}}", + "{{colors.secondary_fixed_dim.default.hex}}", + "{{colors.on_surface.default.hex}}", +] +background = "{{colors.surface.default.hex}}" +brights = [ + "{{colors.outline.default.hex}}", + "{{colors.error.default.hex}}", + "{{colors.tertiary.default.hex}}", + "{{colors.secondary.default.hex}}", + "{{colors.primary.default.hex}}", + "{{colors.tertiary_fixed_dim.default.hex}}", + "{{colors.secondary_fixed_dim.default.hex}}", + "{{colors.on_surface.default.hex}}", +] +cursor_bg = "{{colors.on_surface.default.hex}}" +cursor_border = "{{colors.on_surface.default.hex}}" +cursor_fg = "{{colors.surface.default.hex}}" +foreground = "{{colors.on_surface.default.hex}}" +selection_bg = "{{colors.surface_variant.default.hex}}" +selection_fg = "{{colors.on_surface_variant.default.hex}}" + +[metadata] +name = "Noctalia" diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/vicinae.toml b/arch/.config/quickshell/noctalia-shell/Assets/Templates/vicinae.toml new file mode 100644 index 0000000..35c4d70 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/vicinae.toml @@ -0,0 +1,127 @@ +# Vicinae Noctalia's Theme Template + +[meta] +name = "Noctalia" +description = "Theme generated by Noctalia - {{mode}} variant" +variant = "{{mode}}" +icon = "noctalia.svg" + +# ============================================================================ +# Core Colors +# ============================================================================ + +[colors.core] +accent = "{{colors.primary.default.hex}}" +accent_foreground = "{{colors.on_primary.default.hex}}" +background = "{{colors.surface.default.hex}}" +foreground = "{{colors.on_surface.default.hex}}" +secondary_background = "{{colors.surface_container.default.hex}}" +border = "{{colors.outline_variant.default.hex}}" + +# ============================================================================ +# Window Borders +# ============================================================================ + +[colors.main_window] +border = "{{colors.outline_variant.default.hex}}" # Softer for main app + +[colors.settings_window] +border = "{{colors.outline.default.hex}}" # Stronger but not primary (less distracting) + +# ============================================================================ +# Accent Palette +# ============================================================================ + +[colors.accents] +blue = "{{colors.primary.default.hex}}" +green = "{{colors.tertiary.default.hex}}" +magenta = "{{colors.secondary.default.hex}}" +orange = { name = "{{colors.error.default.hex}}", lighter = 40 } +red = "{{colors.error.default.hex}}" +yellow = { name = "{{colors.tertiary.default.hex}}", lighter = 80 } +cyan = { name = "{{colors.primary.default.hex}}", lighter = 50 } +purple = "{{colors.secondary.default.hex}}" + +# ============================================================================ +# Text System +# ============================================================================ + +[colors.text] +default = "{{colors.on_surface.default.hex}}" +muted = "{{colors.on_surface_variant.default.hex}}" +danger = "{{colors.error.default.hex}}" +success = "{{colors.tertiary.default.hex}}" +placeholder = { name = "{{colors.on_surface_variant.default.hex}}", opacity = 0.6 } + +[colors.text.selection] +background = "{{colors.primary.default.hex}}" +foreground = "{{colors.on_primary.default.hex}}" + +[colors.text.links] +default = "{{colors.primary.default.hex}}" +visited = { name = "{{colors.tertiary.default.hex}}", darker = 20 } + +# ============================================================================ +# Input Fields +# ============================================================================ + +[colors.input] +border = "{{colors.outline.default.hex}}" +border_focus = "{{colors.primary.default.hex}}" +border_error = "{{colors.error.default.hex}}" + +# ============================================================================ +# Buttons +# ============================================================================ + +[colors.button.primary] +background = "{{colors.surface_container_high.default.hex}}" +foreground = "{{colors.on_surface.default.hex}}" + +[colors.button.primary.hover] +background = "{{colors.surface_container_highest.default.hex}}" + +[colors.button.primary.focus] +outline = "{{colors.primary.default.hex}}" + +# ============================================================================ +# Lists +# ============================================================================ + +[colors.list.item.hover] +background = { name = "{{colors.primary_container.default.hex}}", opacity = 0.25 } +foreground = "{{colors.on_surface.default.hex}}" + +[colors.list.item.selection] +background = { name = "{{colors.primary_container.default.hex}}", opacity = 0.50 } +foreground = "{{colors.on_primary_container.default.hex}}" +secondary_background = "{{colors.primary_container.default.hex}}" +secondary_foreground = "{{colors.on_primary_container.default.hex}}" + +# ============================================================================ +# Grid Items +# ============================================================================ + +[colors.grid.item] +background = "{{colors.surface_container.default.hex}}" + +[colors.grid.item.hover] +outline = { name = "{{colors.secondary.default.hex}}", opacity = 0.8 } + +[colors.grid.item.selection] +outline = { name = "{{colors.primary.default.hex}}" } + +# ============================================================================ +# Scrollbars +# ============================================================================ + +[colors.scrollbars] +background = { name = "{{colors.primary.default.hex}}", opacity = 0.2 } + +# ============================================================================ +# Loading States +# ============================================================================ + +[colors.loading] +bar = "{{colors.primary.default.hex}}" +spinner = "{{colors.primary.default.hex}}" diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/walker.css b/arch/.config/quickshell/noctalia-shell/Assets/Templates/walker.css new file mode 100644 index 0000000..3452f7f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/walker.css @@ -0,0 +1,189 @@ +@define-color surface {{colors.surface.default.hex}}; +@define-color surfaceVariant {{colors.surface_variant.default.hex}}; + +@define-color onSurface {{colors.on_surface.default.hex}}; +@define-color onSurfaceVariant {{colors.on_surface_variant.default.hex}}; + +@define-color primary {{colors.primary.default.hex}}; +@define-color secondary {{colors.secondary.default.hex}}; +@define-color error {{colors.error.default.hex}}; +@define-color outline {{colors.outline.default.hex}}; + +* { + all: unset; +} + +.normal-icons { + -gtk-icon-size: 16px; +} + +.large-icons { + -gtk-icon-size: 36px; +} + +scrollbar { + opacity: 0; +} + +.box-wrapper { + box-shadow: + 0 19px 38px rgba(0, 0, 0, 0.3), + 0 15px 12px rgba(0, 0, 0, 0.22); + background: @surface; + padding: 12px; + border-radius: 20px; + border: 2px solid @outline; +} + +.preview-box, +.elephant-hint, +.placeholder { + color: @onSurface; +} + +.box { + background: @surfaceVariant; + padding: 20px; + border-radius: 20px; + border: 2px solid @outline; +} + +.search-container { + border-radius: 10px; + border: 2px solid @outline; +} + +.input placeholder { + opacity: 0.5; +} + +.input { + background: @surface; + color: @onSurface; + caret-color: @onSurface; + padding: 10px; +} + +.input:focus, +.input:active { +} + +.content-container { +} + +.placeholder { +} + +.scroll { +} + +.list { + color: @onSurface; +} + +child { +} + +.item-box { + border-radius: 10px; + padding: 10px; +} + +.item-quick-activation { + background: @secondary; + border-radius: 10px; + padding: 10px; + color: @surface; +} + +child:hover .item-box, +child:selected .item-box { + background: alpha(@primary, 0.4); +} + +.item-text-box { +} + +.item-subtext { + font-size: 12px; + opacity: 0.5; +} + +.providerlist .item-subtext { + font-size: unset; + opacity: 0.75; +} + +.item-image-text { + font-size: 28px; +} + +.preview { + border-top: 1px solid @primary; + padding: 10px; + border-radius: 10px; + color: @onSurface; +} + +.calc .item-text { + font-size: 24px; +} + +.calc .item-subtext { +} + +.symbols .item-image { + font-size: 24px; +} + +.todo.done .item-text-box { + opacity: 0.25; +} + +.todo.urgent { + font-size: 24px; +} + +.todo.active { + font-weight: bold; +} + +.primarytooth.disconnected { + opacity: 0.5; +} + +.preview .large-icons { + -gtk-icon-size: 64px; +} + +.keybinds-wrapper { + border-top: 1px solid @primary; + font-size: 12px; + color: @primary; +} + +.keybinds { +} + +.keybind { + color: @onSurfaceVariant; +} + +.keybind-bind { + font-weight: bold; + text-transform: lowercase; +} + +.keybind-label { +} + +.error { + padding: 10px; + background: @error; + color: @surface; +} + +:not(.calc).current { + font-style: italic; +} + diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/yazi.toml b/arch/.config/quickshell/noctalia-shell/Assets/Templates/yazi.toml new file mode 100644 index 0000000..3fc26cf --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/yazi.toml @@ -0,0 +1,917 @@ +# : Manager [[[ + +[mgr] +cwd = { fg = "{{colors.on_surface.default.hex}}" } + +# Find +find_keyword = { fg = "{{colors.error.default.hex}}", bold = true, italic = true, underline = true } +find_position = { fg = "{{colors.error.default.hex}}", bold = true, italic = true } + +# Marker +marker_copied = { fg = "{{colors.tertiary_container.default.hex}}", bg = "{{colors.tertiary_container.default.hex}}" } +marker_cut = { fg = "{{colors.tertiary_container.default.hex}}", bg = "{{colors.tertiary_container.default.hex}}" } +marker_marked = { fg = "{{colors.error.default.hex}}", bg = "{{colors.error.default.hex}}" } +marker_selected = { fg = "{{colors.tertiary.default.hex}}", bg = "{{colors.tertiary.default.hex}}" } + +# Count +count_copied = { fg = "{{colors.on_tertiary_container.default.hex}}", bg = "{{colors.tertiary_container.default.hex}}" } +count_cut = { fg = "{{colors.on_tertiary_container.default.hex}}", bg = "{{colors.tertiary_container.default.hex}}" } +count_selected = { fg = "{{colors.on_primary.default.hex}}", bg = "{{colors.tertiary.default.hex}}" } + +# Border +border_style = { fg = "{{colors.primary.default.hex}}" } + +# : ]]] + + +# : Status [[[ + +[status] +overall = { fg = "{{colors.primary.default.hex}}" } +sep_left = { open = "๎‚ถ", close = "๎‚ด" } +sep_right = { open = "๎‚ถ", close = "๎‚ด" } + +# Progress +progress_label = { bold = true } +progress_normal = { fg = "{{colors.primary.default.hex}}", bg = "{{colors.surface.default.hex}}" } +progress_error = { fg = "{{colors.error.default.hex}}", bg = "{{colors.surface.default.hex}}" } + +# Permissions +perm_type = { fg = "{{colors.secondary.default.hex}}" } +perm_write = { fg = "{{colors.tertiary.default.hex}}" } +perm_exec = { fg = "{{colors.error.default.hex}}" } +perm_read = { fg = "{{colors.tertiary_container.default.hex}}" } +perm_sep = { fg = "{{colors.primary_container.default.hex}}" } + +# : ]]] + + +# : Mode [[[ + +[mode] + +normal_main = { bg = "{{colors.primary.default.hex}}", fg = "{{colors.on_primary.default.hex}}", bold = true } +normal_alt = { bg = "{{colors.surface_variant.default.hex}}", fg = "{{colors.on_surface_variant.default.hex}}" } + +select_main = { bg = "{{colors.secondary.default.hex}}", fg = "{{colors.on_secondary.default.hex}}", bold = true } +select_alt = { bg = "{{colors.surface_variant.default.hex}}", fg = "{{colors.on_surface_variant.default.hex}}" } + +unset_main = { bg = "{{colors.tertiary.default.hex}}", fg = "{{colors.on_tertiary.default.hex}}", bold = true } +unset_alt = { bg = "{{colors.surface_variant.default.hex}}", fg = "{{colors.on_surface_variant.default.hex}}" } + +# : ]]] + + +# : Input [[[ + +[input] +border = { fg = "{{colors.primary.default.hex}}" } +title = {} +value = { fg = "{{colors.on_surface.default.hex}}" } +selected = { reversed = true } + +# : ]]] + + +# : Tabs [[[ + +[tabs] +active = { fg = "{{colors.surface.default.hex}}", bold = true, bg = "{{colors.primary.default.hex}}" } +inactive = { fg = "{{colors.secondary.default.hex}}", bg = "{{colors.surface.default.hex}}" } +sep_inner = { open = "", close = "" } + +# : ]]] + + +# : Completion [[[ + +[cmp] +border = { fg = "{{colors.primary.default.hex}}", bg = "{{colors.on_primary.default.hex}}" } + +# : ]]] + + +# : Tasks [[[ + +[tasks] +border = { fg = "{{colors.primary.default.hex}}" } +title = {} +hovered = { fg = "{{colors.tertiary_container.default.hex}}", underline = true } + +# : ]]] + + +# : Which [[[ + +[which] +cols = 3 +mask = { bg = "{{colors.surface.default.hex}}" } +cand = { fg = "{{colors.primary.default.hex}}" } +rest = { fg = "{{colors.on_primary.default.hex}}" } +desc = { fg = "{{colors.on_surface.default.hex}}" } +separator = " โ–ถ " +separator_style = { fg = "{{colors.on_surface.default.hex}}" } + +# : ]]] + +# : Spotter [[[ + +[spot] +border = { fg = "{{colors.primary.default.hex}}" } +title = { fg = "{{colors.primary.default.hex}}" } +tbl_col = { fg = "{{colors.on_surface.default.hex}}" } +tbl_cell = { fg = "{{colors.on_surface.default.hex}}", bg = "{{colors.surface.default.hex}}" } + +# : ]]] + + +# : Help [[[ + +[help] +on = { fg = "{{colors.on_surface.default.hex}}" } +run = { fg = "{{colors.on_surface.default.hex}}" } +hovered = { reversed = true, bold = true } +footer = { fg = "{{colors.on_secondary.default.hex}}", bg = "{{colors.secondary.default.hex}}" } + +# : ]]] + + +# : Notify [[[ + +[notify] +title_info = { fg = "{{colors.tertiary.default.hex}}" } +title_warn = { fg = "{{colors.primary.default.hex}}" } +title_error = { fg = "{{colors.error.default.hex}}" } + +# : ]]] + + +# : File-specific styles [[[ + +[filetype] + +rules = [ + # Images + { mime = "image/*", fg = "#94e2d5" }, + + # Media + { mime = "{audio,video}/*", fg = "#f9e2af" }, + + # Archives + { mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", fg = "#f5c2e7" }, + + # Documents + { mime = "application/{pdf,doc,rtf}", fg = "#a6e3a1" }, + + # Special files + { mime = "*", is = "orphan", fg = "{{colors.on_error_container.default.hex}}", bg = "{{colors.error_container.default.hex}}" }, + { mime = "application/*exec*", fg = "{{colors.error.default.hex}}" }, + + # Fallback + { url = "*", fg = "{{colors.on_surface.default.hex}}" }, + { url = "*/", fg = "{{colors.primary.default.hex}}" }, +] + +# : ]]] + +# : Icons {{{ + +[icon] +globs = [] +dirs = [ + { name = ".config", text = "๎—ผ", fg = "{{colors.primary.default.hex}}" }, + { name = ".git", text = "๎—ป", fg = "{{colors.primary.default.hex}}" }, + { name = ".github", text = "๎—ฝ", fg = "{{colors.primary.default.hex}}" }, + { name = ".npm", text = "๎—บ", fg = "{{colors.primary.default.hex}}" }, + { name = "Desktop", text = "๏„ˆ", fg = "{{colors.primary.default.hex}}" }, + { name = "Development", text = "๎œŒ", fg = "{{colors.primary.default.hex}}" }, + { name = "Documents", text = "๏", fg = "{{colors.primary.default.hex}}" }, + { name = "Downloads", text = "๏’˜", fg = "{{colors.primary.default.hex}}" }, + { name = "Library", text = "๎ฎœ", fg = "{{colors.primary.default.hex}}" }, + { name = "Movies", text = "๏‘‡", fg = "{{colors.primary.default.hex}}" }, + { name = "Music", text = "๏€ฅ", fg = "{{colors.primary.default.hex}}" }, + { name = "Pictures", text = "๎‰„", fg = "{{colors.primary.default.hex}}" }, + { name = "Public", text = "๏ซ", fg = "{{colors.primary.default.hex}}" }, + { name = "Videos", text = "๏‘‡", fg = "{{colors.primary.default.hex}}" }, +] +files = [ + { name = ".babelrc", text = "๎˜น", fg = "{{colors.primary.default.hex}}" }, + { name = ".bash_profile", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = ".bashrc", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = ".clang-format", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = ".clang-tidy", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = ".codespellrc", text = "๓ฐ“†", fg = "{{colors.primary.default.hex}}" }, + { name = ".condarc", text = "๎œ•", fg = "{{colors.primary.default.hex}}" }, + { name = ".dockerignore", text = "๓ฐกจ", fg = "{{colors.primary.default.hex}}" }, + { name = ".ds_store", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = ".editorconfig", text = "๎™’", fg = "{{colors.primary.default.hex}}" }, + { name = ".env", text = "๏‘ข", fg = "{{colors.primary.default.hex}}" }, + { name = ".eslintignore", text = "๎™•", fg = "{{colors.primary.default.hex}}" }, + { name = ".eslintrc", text = "๎™•", fg = "{{colors.primary.default.hex}}" }, + { name = ".git-blame-ignore-revs", text = "๎œ‚", fg = "{{colors.primary.default.hex}}" }, + { name = ".gitattributes", text = "๎œ‚", fg = "{{colors.primary.default.hex}}" }, + { name = ".gitconfig", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = ".gitignore", text = "๎œ‚", fg = "{{colors.primary.default.hex}}" }, + { name = ".gitlab-ci.yml", text = "๏Š–", fg = "{{colors.primary.default.hex}}" }, + { name = ".gitmodules", text = "๎œ‚", fg = "{{colors.primary.default.hex}}" }, + { name = ".gtkrc-2.0", text = "๏ข", fg = "{{colors.primary.default.hex}}" }, + { name = ".gvimrc", text = "๎˜ซ", fg = "{{colors.primary.default.hex}}" }, + { name = ".justfile", text = "๏‚ญ", fg = "{{colors.primary.default.hex}}" }, + { name = ".luacheckrc", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = ".luaurc", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = ".mailmap", text = "๓ฐŠข", fg = "{{colors.primary.default.hex}}" }, + { name = ".nanorc", text = "๎ ธ", fg = "{{colors.primary.default.hex}}" }, + { name = ".npmignore", text = "๎œž", fg = "{{colors.primary.default.hex}}" }, + { name = ".npmrc", text = "๎œž", fg = "{{colors.primary.default.hex}}" }, + { name = ".nuxtrc", text = "๓ฑ„†", fg = "{{colors.primary.default.hex}}" }, + { name = ".nvmrc", text = "๎œ˜", fg = "{{colors.primary.default.hex}}" }, + { name = ".pnpmfile.cjs", text = "๎กฅ", fg = "{{colors.primary.default.hex}}" }, + { name = ".pre-commit-config.yaml", text = "๓ฐ›ข", fg = "{{colors.primary.default.hex}}" }, + { name = ".prettierignore", text = "๎šด", fg = "{{colors.primary.default.hex}}" }, + { name = ".prettierrc", text = "๎šด", fg = "{{colors.primary.default.hex}}" }, + { name = ".prettierrc.cjs", text = "๎šด", fg = "{{colors.primary.default.hex}}" }, + { name = ".prettierrc.js", text = "๎šด", fg = "{{colors.primary.default.hex}}" }, + { name = ".prettierrc.json", text = "๎šด", fg = "{{colors.primary.default.hex}}" }, + { name = ".prettierrc.json5", text = "๎šด", fg = "{{colors.primary.default.hex}}" }, + { name = ".prettierrc.mjs", text = "๎šด", fg = "{{colors.primary.default.hex}}" }, + { name = ".prettierrc.toml", text = "๎šด", fg = "{{colors.primary.default.hex}}" }, + { name = ".prettierrc.yaml", text = "๎šด", fg = "{{colors.primary.default.hex}}" }, + { name = ".prettierrc.yml", text = "๎šด", fg = "{{colors.primary.default.hex}}" }, + { name = ".pylintrc", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = ".settings.json", text = "๎œŒ", fg = "{{colors.primary.default.hex}}" }, + { name = ".SRCINFO", text = "๓ฐฃ‡", fg = "{{colors.primary.default.hex}}" }, + { name = ".vimrc", text = "๎˜ซ", fg = "{{colors.primary.default.hex}}" }, + { name = ".Xauthority", text = "๏ฉ", fg = "{{colors.primary.default.hex}}" }, + { name = ".xinitrc", text = "๏ฉ", fg = "{{colors.primary.default.hex}}" }, + { name = ".Xresources", text = "๏ฉ", fg = "{{colors.primary.default.hex}}" }, + { name = ".xsession", text = "๏ฉ", fg = "{{colors.primary.default.hex}}" }, + { name = ".zprofile", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = ".zshenv", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = ".zshrc", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = "_gvimrc", text = "๎˜ซ", fg = "{{colors.primary.default.hex}}" }, + { name = "_vimrc", text = "๎˜ซ", fg = "{{colors.primary.default.hex}}" }, + { name = "AUTHORS", text = "๎ทŠ", fg = "{{colors.primary.default.hex}}" }, + { name = "AUTHORS.txt", text = "๎ทŠ", fg = "{{colors.primary.default.hex}}" }, + { name = "brewfile", text = "๎ž‘", fg = "{{colors.primary.default.hex}}" }, + { name = "bspwmrc", text = "๏•", fg = "{{colors.primary.default.hex}}" }, + { name = "build", text = "๎˜บ", fg = "{{colors.primary.default.hex}}" }, + { name = "build.gradle", text = "๎™ ", fg = "{{colors.primary.default.hex}}" }, + { name = "build.zig.zon", text = "๎šฉ", fg = "{{colors.primary.default.hex}}" }, + { name = "bun.lock", text = "๎ฏ", fg = "{{colors.primary.default.hex}}" }, + { name = "bun.lockb", text = "๎ฏ", fg = "{{colors.primary.default.hex}}" }, + { name = "cantorrc", text = "๏ณ", fg = "{{colors.primary.default.hex}}" }, + { name = "checkhealth", text = "๓ฐ“™", fg = "{{colors.primary.default.hex}}" }, + { name = "cmakelists.txt", text = "๎ž”", fg = "{{colors.primary.default.hex}}" }, + { name = "code_of_conduct", text = "๏’ฎ", fg = "{{colors.primary.default.hex}}" }, + { name = "code_of_conduct.md", text = "๏’ฎ", fg = "{{colors.primary.default.hex}}" }, + { name = "commit_editmsg", text = "๎œ‚", fg = "{{colors.primary.default.hex}}" }, + { name = "commitlint.config.js", text = "๓ฐœ˜", fg = "{{colors.primary.default.hex}}" }, + { name = "commitlint.config.ts", text = "๓ฐœ˜", fg = "{{colors.primary.default.hex}}" }, + { name = "compose.yaml", text = "๓ฐกจ", fg = "{{colors.primary.default.hex}}" }, + { name = "compose.yml", text = "๓ฐกจ", fg = "{{colors.primary.default.hex}}" }, + { name = "config", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = "containerfile", text = "๓ฐกจ", fg = "{{colors.primary.default.hex}}" }, + { name = "copying", text = "๎˜Š", fg = "{{colors.primary.default.hex}}" }, + { name = "copying.lesser", text = "๎˜Š", fg = "{{colors.primary.default.hex}}" }, + { name = "Directory.Build.props", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = "Directory.Build.targets", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = "Directory.Packages.props", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = "docker-compose.yaml", text = "๓ฐกจ", fg = "{{colors.primary.default.hex}}" }, + { name = "docker-compose.yml", text = "๓ฐกจ", fg = "{{colors.primary.default.hex}}" }, + { name = "dockerfile", text = "๓ฐกจ", fg = "{{colors.primary.default.hex}}" }, + { name = "eslint.config.cjs", text = "๎™•", fg = "{{colors.primary.default.hex}}" }, + { name = "eslint.config.js", text = "๎™•", fg = "{{colors.primary.default.hex}}" }, + { name = "eslint.config.mjs", text = "๎™•", fg = "{{colors.primary.default.hex}}" }, + { name = "eslint.config.ts", text = "๎™•", fg = "{{colors.primary.default.hex}}" }, + { name = "ext_typoscript_setup.txt", text = "๎ฒ", fg = "{{colors.primary.default.hex}}" }, + { name = "favicon.ico", text = "๎˜ฃ", fg = "{{colors.primary.default.hex}}" }, + { name = "fp-info-cache", text = "๏Œ", fg = "{{colors.primary.default.hex}}" }, + { name = "fp-lib-table", text = "๏Œ", fg = "{{colors.primary.default.hex}}" }, + { name = "FreeCAD.conf", text = "๏Œถ", fg = "{{colors.primary.default.hex}}" }, + { name = "Gemfile", text = "๎ž‘", fg = "{{colors.primary.default.hex}}" }, + { name = "gnumakefile", text = "๎น", fg = "{{colors.primary.default.hex}}" }, + { name = "go.mod", text = "๎˜ง", fg = "{{colors.primary.default.hex}}" }, + { name = "go.sum", text = "๎˜ง", fg = "{{colors.primary.default.hex}}" }, + { name = "go.work", text = "๎˜ง", fg = "{{colors.primary.default.hex}}" }, + { name = "gradle-wrapper.properties", text = "๎™ ", fg = "{{colors.primary.default.hex}}" }, + { name = "gradle.properties", text = "๎™ ", fg = "{{colors.primary.default.hex}}" }, + { name = "gradlew", text = "๎™ ", fg = "{{colors.primary.default.hex}}" }, + { name = "groovy", text = "๎ต", fg = "{{colors.primary.default.hex}}" }, + { name = "gruntfile.babel.js", text = "๎˜‘", fg = "{{colors.primary.default.hex}}" }, + { name = "gruntfile.coffee", text = "๎˜‘", fg = "{{colors.primary.default.hex}}" }, + { name = "gruntfile.js", text = "๎˜‘", fg = "{{colors.primary.default.hex}}" }, + { name = "gruntfile.ts", text = "๎˜‘", fg = "{{colors.primary.default.hex}}" }, + { name = "gtkrc", text = "๏ข", fg = "{{colors.primary.default.hex}}" }, + { name = "gulpfile.babel.js", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "gulpfile.coffee", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "gulpfile.js", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "gulpfile.ts", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "hypridle.conf", text = "๏™", fg = "{{colors.primary.default.hex}}" }, + { name = "hyprland.conf", text = "๏™", fg = "{{colors.primary.default.hex}}" }, + { name = "hyprlandd.conf", text = "๏™", fg = "{{colors.primary.default.hex}}" }, + { name = "hyprlock.conf", text = "๏™", fg = "{{colors.primary.default.hex}}" }, + { name = "hyprpaper.conf", text = "๏™", fg = "{{colors.primary.default.hex}}" }, + { name = "hyprsunset.conf", text = "๏™", fg = "{{colors.primary.default.hex}}" }, + { name = "i18n.config.js", text = "๓ฐ—Š", fg = "{{colors.primary.default.hex}}" }, + { name = "i18n.config.ts", text = "๓ฐ—Š", fg = "{{colors.primary.default.hex}}" }, + { name = "i3blocks.conf", text = "๏š", fg = "{{colors.primary.default.hex}}" }, + { name = "i3status.conf", text = "๏š", fg = "{{colors.primary.default.hex}}" }, + { name = "index.theme", text = "๎นฒ", fg = "{{colors.primary.default.hex}}" }, + { name = "ionic.config.json", text = "๎žฉ", fg = "{{colors.primary.default.hex}}" }, + { name = "Jenkinsfile", text = "๏‹ฌ", fg = "{{colors.primary.default.hex}}" }, + { name = "justfile", text = "๏‚ญ", fg = "{{colors.primary.default.hex}}" }, + { name = "kalgebrarc", text = "๏ณ", fg = "{{colors.primary.default.hex}}" }, + { name = "kdeglobals", text = "๏ณ", fg = "{{colors.primary.default.hex}}" }, + { name = "kdenlive-layoutsrc", text = "๏Œผ", fg = "{{colors.primary.default.hex}}" }, + { name = "kdenliverc", text = "๏Œผ", fg = "{{colors.primary.default.hex}}" }, + { name = "kritadisplayrc", text = "๏Œฝ", fg = "{{colors.primary.default.hex}}" }, + { name = "kritarc", text = "๏Œฝ", fg = "{{colors.primary.default.hex}}" }, + { name = "license", text = "๎˜Š", fg = "{{colors.primary.default.hex}}" }, + { name = "license.md", text = "๎˜Š", fg = "{{colors.primary.default.hex}}" }, + { name = "lxde-rc.xml", text = "๏ฃ", fg = "{{colors.primary.default.hex}}" }, + { name = "lxqt.conf", text = "๏ค", fg = "{{colors.primary.default.hex}}" }, + { name = "makefile", text = "๎น", fg = "{{colors.primary.default.hex}}" }, + { name = "mix.lock", text = "๎˜ญ", fg = "{{colors.primary.default.hex}}" }, + { name = "mpv.conf", text = "๏ฎ", fg = "{{colors.primary.default.hex}}" }, + { name = "next.config.cjs", text = "๎ พ", fg = "{{colors.primary.default.hex}}" }, + { name = "next.config.js", text = "๎ พ", fg = "{{colors.primary.default.hex}}" }, + { name = "next.config.ts", text = "๎ พ", fg = "{{colors.primary.default.hex}}" }, + { name = "node_modules", text = "๎œ˜", fg = "{{colors.primary.default.hex}}" }, + { name = "nuxt.config.cjs", text = "๓ฑ„†", fg = "{{colors.primary.default.hex}}" }, + { name = "nuxt.config.js", text = "๓ฑ„†", fg = "{{colors.primary.default.hex}}" }, + { name = "nuxt.config.mjs", text = "๓ฑ„†", fg = "{{colors.primary.default.hex}}" }, + { name = "nuxt.config.ts", text = "๓ฑ„†", fg = "{{colors.primary.default.hex}}" }, + { name = "package-lock.json", text = "๎œž", fg = "{{colors.primary.default.hex}}" }, + { name = "package.json", text = "๎œž", fg = "{{colors.primary.default.hex}}" }, + { name = "PKGBUILD", text = "๏Œƒ", fg = "{{colors.primary.default.hex}}" }, + { name = "platformio.ini", text = "๎š‚", fg = "{{colors.primary.default.hex}}" }, + { name = "playwright.config.cjs", text = "๎บถ", fg = "{{colors.primary.default.hex}}" }, + { name = "playwright.config.cts", text = "๎บถ", fg = "{{colors.primary.default.hex}}" }, + { name = "playwright.config.js", text = "๎บถ", fg = "{{colors.primary.default.hex}}" }, + { name = "playwright.config.mjs", text = "๎บถ", fg = "{{colors.primary.default.hex}}" }, + { name = "playwright.config.mts", text = "๎บถ", fg = "{{colors.primary.default.hex}}" }, + { name = "playwright.config.ts", text = "๎บถ", fg = "{{colors.primary.default.hex}}" }, + { name = "pnpm-lock.yaml", text = "๎กฅ", fg = "{{colors.primary.default.hex}}" }, + { name = "pnpm-workspace.yaml", text = "๎กฅ", fg = "{{colors.primary.default.hex}}" }, + { name = "pom.xml", text = "๎™ด", fg = "{{colors.primary.default.hex}}" }, + { name = "prettier.config.cjs", text = "๎šด", fg = "{{colors.primary.default.hex}}" }, + { name = "prettier.config.js", text = "๎šด", fg = "{{colors.primary.default.hex}}" }, + { name = "prettier.config.mjs", text = "๎šด", fg = "{{colors.primary.default.hex}}" }, + { name = "prettier.config.ts", text = "๎šด", fg = "{{colors.primary.default.hex}}" }, + { name = "prisma.config.mts", text = "๎š„", fg = "{{colors.primary.default.hex}}" }, + { name = "prisma.config.ts", text = "๎š„", fg = "{{colors.primary.default.hex}}" }, + { name = "procfile", text = "๎˜‡", fg = "{{colors.primary.default.hex}}" }, + { name = "PrusaSlicer.ini", text = "๏‘", fg = "{{colors.primary.default.hex}}" }, + { name = "PrusaSlicerGcodeViewer.ini", text = "๏‘", fg = "{{colors.primary.default.hex}}" }, + { name = "py.typed", text = "๎˜†", fg = "{{colors.primary.default.hex}}" }, + { name = "QtProject.conf", text = "๏ต", fg = "{{colors.primary.default.hex}}" }, + { name = "rakefile", text = "๎ž‘", fg = "{{colors.primary.default.hex}}" }, + { name = "readme", text = "๓ฐ‚บ", fg = "{{colors.primary.default.hex}}" }, + { name = "readme.md", text = "๓ฐ‚บ", fg = "{{colors.primary.default.hex}}" }, + { name = "rmd", text = "๎˜‰", fg = "{{colors.primary.default.hex}}" }, + { name = "robots.txt", text = "๓ฐšฉ", fg = "{{colors.primary.default.hex}}" }, + { name = "security", text = "๓ฐ’ƒ", fg = "{{colors.primary.default.hex}}" }, + { name = "security.md", text = "๓ฐ’ƒ", fg = "{{colors.primary.default.hex}}" }, + { name = "settings.gradle", text = "๎™ ", fg = "{{colors.primary.default.hex}}" }, + { name = "svelte.config.js", text = "๎š—", fg = "{{colors.primary.default.hex}}" }, + { name = "sxhkdrc", text = "๏•", fg = "{{colors.primary.default.hex}}" }, + { name = "sym-lib-table", text = "๏Œ", fg = "{{colors.primary.default.hex}}" }, + { name = "tailwind.config.js", text = "๓ฑฟ", fg = "{{colors.primary.default.hex}}" }, + { name = "tailwind.config.mjs", text = "๓ฑฟ", fg = "{{colors.primary.default.hex}}" }, + { name = "tailwind.config.ts", text = "๓ฑฟ", fg = "{{colors.primary.default.hex}}" }, + { name = "tmux.conf", text = "๎ฏˆ", fg = "{{colors.primary.default.hex}}" }, + { name = "tmux.conf.local", text = "๎ฏˆ", fg = "{{colors.primary.default.hex}}" }, + { name = "tsconfig.json", text = "๎š", fg = "{{colors.primary.default.hex}}" }, + { name = "unlicense", text = "๎˜Š", fg = "{{colors.primary.default.hex}}" }, + { name = "vagrantfile", text = "๏Šธ", fg = "{{colors.primary.default.hex}}" }, + { name = "vercel.json", text = "๎ฃ“", fg = "{{colors.primary.default.hex}}" }, + { name = "vite.config.cjs", text = "๎ฃ™", fg = "{{colors.primary.default.hex}}" }, + { name = "vite.config.cts", text = "๎ฃ™", fg = "{{colors.primary.default.hex}}" }, + { name = "vite.config.js", text = "๎ฃ™", fg = "{{colors.primary.default.hex}}" }, + { name = "vite.config.mjs", text = "๎ฃ™", fg = "{{colors.primary.default.hex}}" }, + { name = "vite.config.mts", text = "๎ฃ™", fg = "{{colors.primary.default.hex}}" }, + { name = "vite.config.ts", text = "๎ฃ™", fg = "{{colors.primary.default.hex}}" }, + { name = "vitest.config.cjs", text = "๎ฃ™", fg = "{{colors.primary.default.hex}}" }, + { name = "vitest.config.cts", text = "๎ฃ™", fg = "{{colors.primary.default.hex}}" }, + { name = "vitest.config.js", text = "๎ฃ™", fg = "{{colors.primary.default.hex}}" }, + { name = "vitest.config.mjs", text = "๎ฃ™", fg = "{{colors.primary.default.hex}}" }, + { name = "vitest.config.mts", text = "๎ฃ™", fg = "{{colors.primary.default.hex}}" }, + { name = "vitest.config.ts", text = "๎ฃ™", fg = "{{colors.primary.default.hex}}" }, + { name = "vlcrc", text = "๓ฐ•ผ", fg = "{{colors.primary.default.hex}}" }, + { name = "webpack", text = "๓ฐœซ", fg = "{{colors.primary.default.hex}}" }, + { name = "weston.ini", text = "๏ง", fg = "{{colors.primary.default.hex}}" }, + { name = "workspace", text = "๎˜บ", fg = "{{colors.primary.default.hex}}" }, + { name = "wrangler.jsonc", text = "๎ž’", fg = "{{colors.primary.default.hex}}" }, + { name = "wrangler.toml", text = "๎ž’", fg = "{{colors.primary.default.hex}}" }, + { name = "xdph.conf", text = "๏™", fg = "{{colors.primary.default.hex}}" }, + { name = "xmobarrc", text = "๏ž", fg = "{{colors.primary.default.hex}}" }, + { name = "xmobarrc.hs", text = "๏ž", fg = "{{colors.primary.default.hex}}" }, + { name = "xmonad.hs", text = "๏ž", fg = "{{colors.primary.default.hex}}" }, + { name = "xorg.conf", text = "๏ฉ", fg = "{{colors.primary.default.hex}}" }, + { name = "xsettingsd.conf", text = "๏ฉ", fg = "{{colors.primary.default.hex}}" }, +] +exts = [ + { name = "3gp", text = "๎šŸ", fg = "{{colors.primary.default.hex}}" }, + { name = "3mf", text = "๓ฐ†ง", fg = "{{colors.primary.default.hex}}" }, + { name = "7z", text = "๏", fg = "{{colors.primary.default.hex}}" }, + { name = "a", text = "๎ฎœ", fg = "{{colors.primary.default.hex}}" }, + { name = "aac", text = "๏€", fg = "{{colors.primary.default.hex}}" }, + { name = "ada", text = "๎šต", fg = "{{colors.primary.default.hex}}" }, + { name = "adb", text = "๎šต", fg = "{{colors.primary.default.hex}}" }, + { name = "ads", text = "๎šต", fg = "{{colors.primary.default.hex}}" }, + { name = "ai", text = "๎žด", fg = "{{colors.primary.default.hex}}" }, + { name = "aif", text = "๏€", fg = "{{colors.primary.default.hex}}" }, + { name = "aiff", text = "๏€", fg = "{{colors.primary.default.hex}}" }, + { name = "android", text = "๎œŽ", fg = "{{colors.primary.default.hex}}" }, + { name = "ape", text = "๏€", fg = "{{colors.primary.default.hex}}" }, + { name = "apk", text = "๎œŽ", fg = "{{colors.primary.default.hex}}" }, + { name = "apl", text = "๎œฐ", fg = "{{colors.primary.default.hex}}" }, + { name = "app", text = "๎ซจ", fg = "{{colors.primary.default.hex}}" }, + { name = "applescript", text = "๏…น", fg = "{{colors.primary.default.hex}}" }, + { name = "asc", text = "๓ฐฆ", fg = "{{colors.primary.default.hex}}" }, + { name = "asm", text = "๎˜ท", fg = "{{colors.primary.default.hex}}" }, + { name = "ass", text = "๓ฐจ–", fg = "{{colors.primary.default.hex}}" }, + { name = "astro", text = "๎šณ", fg = "{{colors.primary.default.hex}}" }, + { name = "avif", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "awk", text = "๎ž•", fg = "{{colors.primary.default.hex}}" }, + { name = "azcli", text = "๎ฏจ", fg = "{{colors.primary.default.hex}}" }, + { name = "bak", text = "๓ฐฏ", fg = "{{colors.primary.default.hex}}" }, + { name = "bash", text = "๎ ", fg = "{{colors.primary.default.hex}}" }, + { name = "bat", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = "bazel", text = "๎˜บ", fg = "{{colors.primary.default.hex}}" }, + { name = "bib", text = "๓ฑ‰Ÿ", fg = "{{colors.primary.default.hex}}" }, + { name = "bicep", text = "๎˜ป", fg = "{{colors.primary.default.hex}}" }, + { name = "bicepparam", text = "๎˜ป", fg = "{{colors.primary.default.hex}}" }, + { name = "bin", text = "๎ซจ", fg = "{{colors.primary.default.hex}}" }, + { name = "blade.php", text = "๏‹ท", fg = "{{colors.primary.default.hex}}" }, + { name = "blend", text = "๓ฐ‚ซ", fg = "{{colors.primary.default.hex}}" }, + { name = "blp", text = "๓ฐบพ", fg = "{{colors.primary.default.hex}}" }, + { name = "bmp", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "bqn", text = "๎œฐ", fg = "{{colors.primary.default.hex}}" }, + { name = "brep", text = "๓ฐปซ", fg = "{{colors.primary.default.hex}}" }, + { name = "bz", text = "๏", fg = "{{colors.primary.default.hex}}" }, + { name = "bz2", text = "๏", fg = "{{colors.primary.default.hex}}" }, + { name = "bz3", text = "๏", fg = "{{colors.primary.default.hex}}" }, + { name = "bzl", text = "๎˜บ", fg = "{{colors.primary.default.hex}}" }, + { name = "c", text = "๎˜ž", fg = "{{colors.primary.default.hex}}" }, + { name = "c++", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "cache", text = "๏’›", fg = "{{colors.primary.default.hex}}" }, + { name = "cast", text = "๎šŸ", fg = "{{colors.primary.default.hex}}" }, + { name = "cbl", text = "๏ฃ", fg = "{{colors.primary.default.hex}}" }, + { name = "cc", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "ccm", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "cfc", text = "๎™…", fg = "{{colors.primary.default.hex}}" }, + { name = "cfg", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = "cfm", text = "๎™…", fg = "{{colors.primary.default.hex}}" }, + { name = "cjs", text = "๎˜Œ", fg = "{{colors.primary.default.hex}}" }, + { name = "clj", text = "๎จ", fg = "{{colors.primary.default.hex}}" }, + { name = "cljc", text = "๎จ", fg = "{{colors.primary.default.hex}}" }, + { name = "cljd", text = "๎ช", fg = "{{colors.primary.default.hex}}" }, + { name = "cljs", text = "๎ช", fg = "{{colors.primary.default.hex}}" }, + { name = "cmake", text = "๎ž”", fg = "{{colors.primary.default.hex}}" }, + { name = "cob", text = "๏ฃ", fg = "{{colors.primary.default.hex}}" }, + { name = "cobol", text = "๏ฃ", fg = "{{colors.primary.default.hex}}" }, + { name = "coffee", text = "๎˜›", fg = "{{colors.primary.default.hex}}" }, + { name = "conda", text = "๎œ•", fg = "{{colors.primary.default.hex}}" }, + { name = "conf", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = "config.ru", text = "๎ž‘", fg = "{{colors.primary.default.hex}}" }, + { name = "cow", text = "๓ฐ†š", fg = "{{colors.primary.default.hex}}" }, + { name = "cp", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "cpp", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "cppm", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "cpy", text = "๏ฃ", fg = "{{colors.primary.default.hex}}" }, + { name = "cr", text = "๎˜ฏ", fg = "{{colors.primary.default.hex}}" }, + { name = "crdownload", text = "๏€™", fg = "{{colors.primary.default.hex}}" }, + { name = "cs", text = "๓ฐŒ›", fg = "{{colors.primary.default.hex}}" }, + { name = "csh", text = "๎ž•", fg = "{{colors.primary.default.hex}}" }, + { name = "cshtml", text = "๓ฑฆ—", fg = "{{colors.primary.default.hex}}" }, + { name = "cson", text = "๎˜‹", fg = "{{colors.primary.default.hex}}" }, + { name = "csproj", text = "๓ฐชฎ", fg = "{{colors.primary.default.hex}}" }, + { name = "css", text = "๎šธ", fg = "{{colors.primary.default.hex}}" }, + { name = "csv", text = "๎™Š", fg = "{{colors.primary.default.hex}}" }, + { name = "cts", text = "๎˜จ", fg = "{{colors.primary.default.hex}}" }, + { name = "cu", text = "๎™‹", fg = "{{colors.primary.default.hex}}" }, + { name = "cue", text = "๓ฐฒน", fg = "{{colors.primary.default.hex}}" }, + { name = "cuh", text = "๎™‹", fg = "{{colors.primary.default.hex}}" }, + { name = "cxx", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "cxxm", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "d", text = "๎žฏ", fg = "{{colors.primary.default.hex}}" }, + { name = "d.ts", text = "๎˜จ", fg = "{{colors.primary.default.hex}}" }, + { name = "dart", text = "๎ž˜", fg = "{{colors.primary.default.hex}}" }, + { name = "db", text = "๎œ†", fg = "{{colors.primary.default.hex}}" }, + { name = "dconf", text = "๏ก", fg = "{{colors.primary.default.hex}}" }, + { name = "desktop", text = "๏„ˆ", fg = "{{colors.primary.default.hex}}" }, + { name = "diff", text = "๎œจ", fg = "{{colors.primary.default.hex}}" }, + { name = "dll", text = "๎ฎœ", fg = "{{colors.primary.default.hex}}" }, + { name = "doc", text = "๓ฐˆฌ", fg = "{{colors.primary.default.hex}}" }, + { name = "Dockerfile", text = "๓ฐกจ", fg = "{{colors.primary.default.hex}}" }, + { name = "dockerignore", text = "๓ฐกจ", fg = "{{colors.primary.default.hex}}" }, + { name = "docx", text = "๓ฐˆฌ", fg = "{{colors.primary.default.hex}}" }, + { name = "dot", text = "๓ฑ‰", fg = "{{colors.primary.default.hex}}" }, + { name = "download", text = "๏€™", fg = "{{colors.primary.default.hex}}" }, + { name = "drl", text = "๎ŠŒ", fg = "{{colors.primary.default.hex}}" }, + { name = "dropbox", text = "๎œ‡", fg = "{{colors.primary.default.hex}}" }, + { name = "dump", text = "๎œ†", fg = "{{colors.primary.default.hex}}" }, + { name = "dwg", text = "๓ฐปซ", fg = "{{colors.primary.default.hex}}" }, + { name = "dxf", text = "๓ฐปซ", fg = "{{colors.primary.default.hex}}" }, + { name = "ebook", text = "๎Š‹", fg = "{{colors.primary.default.hex}}" }, + { name = "ebuild", text = "๏Œ", fg = "{{colors.primary.default.hex}}" }, + { name = "edn", text = "๎ช", fg = "{{colors.primary.default.hex}}" }, + { name = "eex", text = "๎˜ญ", fg = "{{colors.primary.default.hex}}" }, + { name = "ejs", text = "๎˜Ž", fg = "{{colors.primary.default.hex}}" }, + { name = "el", text = "๎˜ฒ", fg = "{{colors.primary.default.hex}}" }, + { name = "elc", text = "๎˜ฒ", fg = "{{colors.primary.default.hex}}" }, + { name = "elf", text = "๎ซจ", fg = "{{colors.primary.default.hex}}" }, + { name = "elm", text = "๎˜ฌ", fg = "{{colors.primary.default.hex}}" }, + { name = "eln", text = "๎˜ฒ", fg = "{{colors.primary.default.hex}}" }, + { name = "env", text = "๏‘ข", fg = "{{colors.primary.default.hex}}" }, + { name = "eot", text = "๏€ฑ", fg = "{{colors.primary.default.hex}}" }, + { name = "epp", text = "๎˜ฑ", fg = "{{colors.primary.default.hex}}" }, + { name = "epub", text = "๎Š‹", fg = "{{colors.primary.default.hex}}" }, + { name = "erb", text = "๎˜Ž", fg = "{{colors.primary.default.hex}}" }, + { name = "erl", text = "๎žฑ", fg = "{{colors.primary.default.hex}}" }, + { name = "ex", text = "๎˜ญ", fg = "{{colors.primary.default.hex}}" }, + { name = "exe", text = "๎ซจ", fg = "{{colors.primary.default.hex}}" }, + { name = "exs", text = "๎˜ญ", fg = "{{colors.primary.default.hex}}" }, + { name = "f#", text = "๎žง", fg = "{{colors.primary.default.hex}}" }, + { name = "f3d", text = "๓ฐปซ", fg = "{{colors.primary.default.hex}}" }, + { name = "f90", text = "๓ฑˆš", fg = "{{colors.primary.default.hex}}" }, + { name = "fbx", text = "๓ฐ†ง", fg = "{{colors.primary.default.hex}}" }, + { name = "fcbak", text = "๏Œถ", fg = "{{colors.primary.default.hex}}" }, + { name = "fcmacro", text = "๏Œถ", fg = "{{colors.primary.default.hex}}" }, + { name = "fcmat", text = "๏Œถ", fg = "{{colors.primary.default.hex}}" }, + { name = "fcparam", text = "๏Œถ", fg = "{{colors.primary.default.hex}}" }, + { name = "fcscript", text = "๏Œถ", fg = "{{colors.primary.default.hex}}" }, + { name = "fcstd", text = "๏Œถ", fg = "{{colors.primary.default.hex}}" }, + { name = "fcstd1", text = "๏Œถ", fg = "{{colors.primary.default.hex}}" }, + { name = "fctb", text = "๏Œถ", fg = "{{colors.primary.default.hex}}" }, + { name = "fctl", text = "๏Œถ", fg = "{{colors.primary.default.hex}}" }, + { name = "fdmdownload", text = "๏€™", fg = "{{colors.primary.default.hex}}" }, + { name = "feature", text = "๎žท", fg = "{{colors.primary.default.hex}}" }, + { name = "fish", text = "๎ž•", fg = "{{colors.primary.default.hex}}" }, + { name = "flac", text = "๏€", fg = "{{colors.primary.default.hex}}" }, + { name = "flc", text = "๏€ฑ", fg = "{{colors.primary.default.hex}}" }, + { name = "flf", text = "๏€ฑ", fg = "{{colors.primary.default.hex}}" }, + { name = "fnl", text = "๎šฏ", fg = "{{colors.primary.default.hex}}" }, + { name = "fodg", text = "๏น", fg = "{{colors.primary.default.hex}}" }, + { name = "fodp", text = "๏บ", fg = "{{colors.primary.default.hex}}" }, + { name = "fods", text = "๏ธ", fg = "{{colors.primary.default.hex}}" }, + { name = "fodt", text = "๏ผ", fg = "{{colors.primary.default.hex}}" }, + { name = "frag", text = "๎ก•", fg = "{{colors.primary.default.hex}}" }, + { name = "fs", text = "๎žง", fg = "{{colors.primary.default.hex}}" }, + { name = "fsi", text = "๎žง", fg = "{{colors.primary.default.hex}}" }, + { name = "fsscript", text = "๎žง", fg = "{{colors.primary.default.hex}}" }, + { name = "fsx", text = "๎žง", fg = "{{colors.primary.default.hex}}" }, + { name = "gcode", text = "๓ฐซ", fg = "{{colors.primary.default.hex}}" }, + { name = "gd", text = "๎™Ÿ", fg = "{{colors.primary.default.hex}}" }, + { name = "gemspec", text = "๎ž‘", fg = "{{colors.primary.default.hex}}" }, + { name = "geom", text = "๎ก•", fg = "{{colors.primary.default.hex}}" }, + { name = "gif", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "git", text = "๎œ‚", fg = "{{colors.primary.default.hex}}" }, + { name = "glb", text = "๏†ฒ", fg = "{{colors.primary.default.hex}}" }, + { name = "gleam", text = "๏€…", fg = "{{colors.primary.default.hex}}" }, + { name = "glsl", text = "๎ก•", fg = "{{colors.primary.default.hex}}" }, + { name = "gnumakefile", text = "๎น", fg = "{{colors.primary.default.hex}}" }, + { name = "go", text = "๎˜ง", fg = "{{colors.primary.default.hex}}" }, + { name = "godot", text = "๎™Ÿ", fg = "{{colors.primary.default.hex}}" }, + { name = "gpr", text = "๎šต", fg = "{{colors.primary.default.hex}}" }, + { name = "gql", text = "๏ˆŽ", fg = "{{colors.primary.default.hex}}" }, + { name = "gradle", text = "๎™ ", fg = "{{colors.primary.default.hex}}" }, + { name = "graphql", text = "๏ˆŽ", fg = "{{colors.primary.default.hex}}" }, + { name = "gresource", text = "๏ข", fg = "{{colors.primary.default.hex}}" }, + { name = "gv", text = "๓ฑ‰", fg = "{{colors.primary.default.hex}}" }, + { name = "gz", text = "๏", fg = "{{colors.primary.default.hex}}" }, + { name = "h", text = "๏ƒฝ", fg = "{{colors.primary.default.hex}}" }, + { name = "haml", text = "๎˜Ž", fg = "{{colors.primary.default.hex}}" }, + { name = "hbs", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "heex", text = "๎˜ญ", fg = "{{colors.primary.default.hex}}" }, + { name = "hex", text = "๎™ง", fg = "{{colors.primary.default.hex}}" }, + { name = "hh", text = "๏ƒฝ", fg = "{{colors.primary.default.hex}}" }, + { name = "hpp", text = "๏ƒฝ", fg = "{{colors.primary.default.hex}}" }, + { name = "hrl", text = "๎žฑ", fg = "{{colors.primary.default.hex}}" }, + { name = "hs", text = "๎˜Ÿ", fg = "{{colors.primary.default.hex}}" }, + { name = "htm", text = "๎˜Ž", fg = "{{colors.primary.default.hex}}" }, + { name = "html", text = "๎œถ", fg = "{{colors.primary.default.hex}}" }, + { name = "http", text = "๏‡˜", fg = "{{colors.primary.default.hex}}" }, + { name = "huff", text = "๓ฐก˜", fg = "{{colors.primary.default.hex}}" }, + { name = "hurl", text = "๏ƒฌ", fg = "{{colors.primary.default.hex}}" }, + { name = "hx", text = "๎™ฆ", fg = "{{colors.primary.default.hex}}" }, + { name = "hxx", text = "๏ƒฝ", fg = "{{colors.primary.default.hex}}" }, + { name = "ical", text = "๏ณ", fg = "{{colors.primary.default.hex}}" }, + { name = "icalendar", text = "๏ณ", fg = "{{colors.primary.default.hex}}" }, + { name = "ico", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "ics", text = "๏ณ", fg = "{{colors.primary.default.hex}}" }, + { name = "ifb", text = "๏ณ", fg = "{{colors.primary.default.hex}}" }, + { name = "ifc", text = "๓ฐปซ", fg = "{{colors.primary.default.hex}}" }, + { name = "ige", text = "๓ฐปซ", fg = "{{colors.primary.default.hex}}" }, + { name = "iges", text = "๓ฐปซ", fg = "{{colors.primary.default.hex}}" }, + { name = "igs", text = "๓ฐปซ", fg = "{{colors.primary.default.hex}}" }, + { name = "image", text = "๎‰ฑ", fg = "{{colors.primary.default.hex}}" }, + { name = "img", text = "๎‰ฑ", fg = "{{colors.primary.default.hex}}" }, + { name = "import", text = "๏ƒ†", fg = "{{colors.primary.default.hex}}" }, + { name = "info", text = "๏„ฉ", fg = "{{colors.primary.default.hex}}" }, + { name = "ini", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = "ino", text = "๏‹", fg = "{{colors.primary.default.hex}}" }, + { name = "ipynb", text = "๎ ", fg = "{{colors.primary.default.hex}}" }, + { name = "iso", text = "๎‰ฑ", fg = "{{colors.primary.default.hex}}" }, + { name = "ixx", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "jar", text = "๎œธ", fg = "{{colors.primary.default.hex}}" }, + { name = "java", text = "๎œธ", fg = "{{colors.primary.default.hex}}" }, + { name = "jl", text = "๎˜ค", fg = "{{colors.primary.default.hex}}" }, + { name = "jpeg", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "jpg", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "js", text = "๎˜Œ", fg = "{{colors.primary.default.hex}}" }, + { name = "json", text = "๎˜‹", fg = "{{colors.primary.default.hex}}" }, + { name = "json5", text = "๎˜‹", fg = "{{colors.primary.default.hex}}" }, + { name = "jsonc", text = "๎˜‹", fg = "{{colors.primary.default.hex}}" }, + { name = "jsx", text = "๎˜ฅ", fg = "{{colors.primary.default.hex}}" }, + { name = "jwmrc", text = "๏›", fg = "{{colors.primary.default.hex}}" }, + { name = "jxl", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "kbx", text = "๓ฐฏ„", fg = "{{colors.primary.default.hex}}" }, + { name = "kdb", text = "๏ˆพ", fg = "{{colors.primary.default.hex}}" }, + { name = "kdbx", text = "๏ˆพ", fg = "{{colors.primary.default.hex}}" }, + { name = "kdenlive", text = "๏Œผ", fg = "{{colors.primary.default.hex}}" }, + { name = "kdenlivetitle", text = "๏Œผ", fg = "{{colors.primary.default.hex}}" }, + { name = "kicad_dru", text = "๏Œ", fg = "{{colors.primary.default.hex}}" }, + { name = "kicad_mod", text = "๏Œ", fg = "{{colors.primary.default.hex}}" }, + { name = "kicad_pcb", text = "๏Œ", fg = "{{colors.primary.default.hex}}" }, + { name = "kicad_prl", text = "๏Œ", fg = "{{colors.primary.default.hex}}" }, + { name = "kicad_pro", text = "๏Œ", fg = "{{colors.primary.default.hex}}" }, + { name = "kicad_sch", text = "๏Œ", fg = "{{colors.primary.default.hex}}" }, + { name = "kicad_sym", text = "๏Œ", fg = "{{colors.primary.default.hex}}" }, + { name = "kicad_wks", text = "๏Œ", fg = "{{colors.primary.default.hex}}" }, + { name = "ko", text = "๏…ผ", fg = "{{colors.primary.default.hex}}" }, + { name = "kpp", text = "๏Œฝ", fg = "{{colors.primary.default.hex}}" }, + { name = "kra", text = "๏Œฝ", fg = "{{colors.primary.default.hex}}" }, + { name = "krz", text = "๏Œฝ", fg = "{{colors.primary.default.hex}}" }, + { name = "ksh", text = "๎ž•", fg = "{{colors.primary.default.hex}}" }, + { name = "kt", text = "๎˜ด", fg = "{{colors.primary.default.hex}}" }, + { name = "kts", text = "๎˜ด", fg = "{{colors.primary.default.hex}}" }, + { name = "lck", text = "๎™ฒ", fg = "{{colors.primary.default.hex}}" }, + { name = "leex", text = "๎˜ญ", fg = "{{colors.primary.default.hex}}" }, + { name = "less", text = "๎˜”", fg = "{{colors.primary.default.hex}}" }, + { name = "lff", text = "๏€ฑ", fg = "{{colors.primary.default.hex}}" }, + { name = "lhs", text = "๎˜Ÿ", fg = "{{colors.primary.default.hex}}" }, + { name = "lib", text = "๎ฎœ", fg = "{{colors.primary.default.hex}}" }, + { name = "license", text = "๎˜Š", fg = "{{colors.primary.default.hex}}" }, + { name = "liquid", text = "๎™ฐ", fg = "{{colors.primary.default.hex}}" }, + { name = "lock", text = "๎™ฒ", fg = "{{colors.primary.default.hex}}" }, + { name = "log", text = "๓ฐŒฑ", fg = "{{colors.primary.default.hex}}" }, + { name = "lrc", text = "๓ฐจ–", fg = "{{colors.primary.default.hex}}" }, + { name = "lua", text = "๎˜ ", fg = "{{colors.primary.default.hex}}" }, + { name = "luac", text = "๎˜ ", fg = "{{colors.primary.default.hex}}" }, + { name = "luau", text = "๎˜ ", fg = "{{colors.primary.default.hex}}" }, + { name = "m", text = "๎˜ž", fg = "{{colors.primary.default.hex}}" }, + { name = "m3u", text = "๓ฐฒน", fg = "{{colors.primary.default.hex}}" }, + { name = "m3u8", text = "๓ฐฒน", fg = "{{colors.primary.default.hex}}" }, + { name = "m4a", text = "๏€", fg = "{{colors.primary.default.hex}}" }, + { name = "m4v", text = "๎šŸ", fg = "{{colors.primary.default.hex}}" }, + { name = "magnet", text = "๏ถ", fg = "{{colors.primary.default.hex}}" }, + { name = "makefile", text = "๎น", fg = "{{colors.primary.default.hex}}" }, + { name = "markdown", text = "๎˜‰", fg = "{{colors.primary.default.hex}}" }, + { name = "material", text = "๎Šฆ", fg = "{{colors.primary.default.hex}}" }, + { name = "md", text = "๏’Š", fg = "{{colors.primary.default.hex}}" }, + { name = "md5", text = "๓ฐ•ฅ", fg = "{{colors.primary.default.hex}}" }, + { name = "mdx", text = "๏’Š", fg = "{{colors.primary.default.hex}}" }, + { name = "mint", text = "๓ฐŒช", fg = "{{colors.primary.default.hex}}" }, + { name = "mjs", text = "๎˜Œ", fg = "{{colors.primary.default.hex}}" }, + { name = "mk", text = "๎น", fg = "{{colors.primary.default.hex}}" }, + { name = "mkv", text = "๎šŸ", fg = "{{colors.primary.default.hex}}" }, + { name = "ml", text = "๎™บ", fg = "{{colors.primary.default.hex}}" }, + { name = "mli", text = "๎™บ", fg = "{{colors.primary.default.hex}}" }, + { name = "mm", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "mo", text = "๎ทพ", fg = "{{colors.primary.default.hex}}" }, + { name = "mobi", text = "๎Š‹", fg = "{{colors.primary.default.hex}}" }, + { name = "mojo", text = "๏ญ", fg = "{{colors.primary.default.hex}}" }, + { name = "mov", text = "๎šŸ", fg = "{{colors.primary.default.hex}}" }, + { name = "mp3", text = "๏€", fg = "{{colors.primary.default.hex}}" }, + { name = "mp4", text = "๎šŸ", fg = "{{colors.primary.default.hex}}" }, + { name = "mpp", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "msf", text = "๏ฐ", fg = "{{colors.primary.default.hex}}" }, + { name = "mts", text = "๎˜จ", fg = "{{colors.primary.default.hex}}" }, + { name = "mustache", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "nfo", text = "๏„ฉ", fg = "{{colors.primary.default.hex}}" }, + { name = "nim", text = "๎™ท", fg = "{{colors.primary.default.hex}}" }, + { name = "nix", text = "๏Œ“", fg = "{{colors.primary.default.hex}}" }, + { name = "norg", text = "๎ก‡", fg = "{{colors.primary.default.hex}}" }, + { name = "nswag", text = "๎˜‹", fg = "{{colors.primary.default.hex}}" }, + { name = "nu", text = "๏”", fg = "{{colors.primary.default.hex}}" }, + { name = "o", text = "๎ซจ", fg = "{{colors.primary.default.hex}}" }, + { name = "obj", text = "๓ฐ†ง", fg = "{{colors.primary.default.hex}}" }, + { name = "odf", text = "๏ป", fg = "{{colors.primary.default.hex}}" }, + { name = "odg", text = "๏น", fg = "{{colors.primary.default.hex}}" }, + { name = "odin", text = "๓ฐŸข", fg = "{{colors.primary.default.hex}}" }, + { name = "odp", text = "๏บ", fg = "{{colors.primary.default.hex}}" }, + { name = "ods", text = "๏ธ", fg = "{{colors.primary.default.hex}}" }, + { name = "odt", text = "๏ผ", fg = "{{colors.primary.default.hex}}" }, + { name = "oga", text = "๏€", fg = "{{colors.primary.default.hex}}" }, + { name = "ogg", text = "๏€", fg = "{{colors.primary.default.hex}}" }, + { name = "ogv", text = "๎šŸ", fg = "{{colors.primary.default.hex}}" }, + { name = "ogx", text = "๎šŸ", fg = "{{colors.primary.default.hex}}" }, + { name = "opus", text = "๏€", fg = "{{colors.primary.default.hex}}" }, + { name = "org", text = "๎˜ณ", fg = "{{colors.primary.default.hex}}" }, + { name = "otf", text = "๏€ฑ", fg = "{{colors.primary.default.hex}}" }, + { name = "out", text = "๎ซจ", fg = "{{colors.primary.default.hex}}" }, + { name = "part", text = "๏€™", fg = "{{colors.primary.default.hex}}" }, + { name = "patch", text = "๎œจ", fg = "{{colors.primary.default.hex}}" }, + { name = "pck", text = "๏’‡", fg = "{{colors.primary.default.hex}}" }, + { name = "pcm", text = "๏€", fg = "{{colors.primary.default.hex}}" }, + { name = "pdf", text = "๎ซซ", fg = "{{colors.primary.default.hex}}" }, + { name = "php", text = "๎˜ˆ", fg = "{{colors.primary.default.hex}}" }, + { name = "pl", text = "๎ฉ", fg = "{{colors.primary.default.hex}}" }, + { name = "pls", text = "๓ฐฒน", fg = "{{colors.primary.default.hex}}" }, + { name = "ply", text = "๓ฐ†ง", fg = "{{colors.primary.default.hex}}" }, + { name = "pm", text = "๎ฉ", fg = "{{colors.primary.default.hex}}" }, + { name = "png", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "po", text = "๏†ซ", fg = "{{colors.primary.default.hex}}" }, + { name = "pot", text = "๏†ซ", fg = "{{colors.primary.default.hex}}" }, + { name = "pp", text = "๎˜ฑ", fg = "{{colors.primary.default.hex}}" }, + { name = "ppt", text = "๓ฐˆง", fg = "{{colors.primary.default.hex}}" }, + { name = "pptx", text = "๓ฐˆง", fg = "{{colors.primary.default.hex}}" }, + { name = "prisma", text = "๎š„", fg = "{{colors.primary.default.hex}}" }, + { name = "pro", text = "๎žก", fg = "{{colors.primary.default.hex}}" }, + { name = "ps1", text = "๓ฐจŠ", fg = "{{colors.primary.default.hex}}" }, + { name = "psb", text = "๎žธ", fg = "{{colors.primary.default.hex}}" }, + { name = "psd", text = "๎žธ", fg = "{{colors.primary.default.hex}}" }, + { name = "psd1", text = "๓ฐจŠ", fg = "{{colors.primary.default.hex}}" }, + { name = "psm1", text = "๓ฐจŠ", fg = "{{colors.primary.default.hex}}" }, + { name = "pub", text = "๓ฐท–", fg = "{{colors.primary.default.hex}}" }, + { name = "pxd", text = "๎˜†", fg = "{{colors.primary.default.hex}}" }, + { name = "pxi", text = "๎˜†", fg = "{{colors.primary.default.hex}}" }, + { name = "py", text = "๎˜†", fg = "{{colors.primary.default.hex}}" }, + { name = "pyc", text = "๎˜†", fg = "{{colors.primary.default.hex}}" }, + { name = "pyd", text = "๎˜†", fg = "{{colors.primary.default.hex}}" }, + { name = "pyi", text = "๎˜†", fg = "{{colors.primary.default.hex}}" }, + { name = "pyo", text = "๎˜†", fg = "{{colors.primary.default.hex}}" }, + { name = "pyw", text = "๎˜†", fg = "{{colors.primary.default.hex}}" }, + { name = "pyx", text = "๎˜†", fg = "{{colors.primary.default.hex}}" }, + { name = "qm", text = "๏†ซ", fg = "{{colors.primary.default.hex}}" }, + { name = "qml", text = "๏ต", fg = "{{colors.primary.default.hex}}" }, + { name = "qrc", text = "๏ต", fg = "{{colors.primary.default.hex}}" }, + { name = "qss", text = "๏ต", fg = "{{colors.primary.default.hex}}" }, + { name = "query", text = "๎ˆœ", fg = "{{colors.primary.default.hex}}" }, + { name = "R", text = "๓ฐŸ”", fg = "{{colors.primary.default.hex}}" }, + { name = "r", text = "๓ฐŸ”", fg = "{{colors.primary.default.hex}}" }, + { name = "rake", text = "๎ž‘", fg = "{{colors.primary.default.hex}}" }, + { name = "rar", text = "๏", fg = "{{colors.primary.default.hex}}" }, + { name = "rasi", text = "๎ฏถ", fg = "{{colors.primary.default.hex}}" }, + { name = "razor", text = "๓ฑฆ˜", fg = "{{colors.primary.default.hex}}" }, + { name = "rb", text = "๎ž‘", fg = "{{colors.primary.default.hex}}" }, + { name = "res", text = "๎šˆ", fg = "{{colors.primary.default.hex}}" }, + { name = "resi", text = "๎šˆ", fg = "{{colors.primary.default.hex}}" }, + { name = "rlib", text = "๎žจ", fg = "{{colors.primary.default.hex}}" }, + { name = "rmd", text = "๎˜‰", fg = "{{colors.primary.default.hex}}" }, + { name = "rproj", text = "๓ฐ—†", fg = "{{colors.primary.default.hex}}" }, + { name = "rs", text = "๎š‹", fg = "{{colors.primary.default.hex}}" }, + { name = "rss", text = "๎˜™", fg = "{{colors.primary.default.hex}}" }, + { name = "s", text = "๎˜ท", fg = "{{colors.primary.default.hex}}" }, + { name = "sass", text = "๎˜ƒ", fg = "{{colors.primary.default.hex}}" }, + { name = "sbt", text = "๎œท", fg = "{{colors.primary.default.hex}}" }, + { name = "sc", text = "๎œท", fg = "{{colors.primary.default.hex}}" }, + { name = "scad", text = "๏Ž", fg = "{{colors.primary.default.hex}}" }, + { name = "scala", text = "๎œท", fg = "{{colors.primary.default.hex}}" }, + { name = "scm", text = "๓ฐ˜ง", fg = "{{colors.primary.default.hex}}" }, + { name = "scss", text = "๎˜ƒ", fg = "{{colors.primary.default.hex}}" }, + { name = "sh", text = "๎ž•", fg = "{{colors.primary.default.hex}}" }, + { name = "sha1", text = "๓ฐ•ฅ", fg = "{{colors.primary.default.hex}}" }, + { name = "sha224", text = "๓ฐ•ฅ", fg = "{{colors.primary.default.hex}}" }, + { name = "sha256", text = "๓ฐ•ฅ", fg = "{{colors.primary.default.hex}}" }, + { name = "sha384", text = "๓ฐ•ฅ", fg = "{{colors.primary.default.hex}}" }, + { name = "sha512", text = "๓ฐ•ฅ", fg = "{{colors.primary.default.hex}}" }, + { name = "sig", text = "๓ฐ˜ง", fg = "{{colors.primary.default.hex}}" }, + { name = "signature", text = "๓ฐ˜ง", fg = "{{colors.primary.default.hex}}" }, + { name = "skp", text = "๓ฐปซ", fg = "{{colors.primary.default.hex}}" }, + { name = "sldasm", text = "๓ฐปซ", fg = "{{colors.primary.default.hex}}" }, + { name = "sldprt", text = "๓ฐปซ", fg = "{{colors.primary.default.hex}}" }, + { name = "slim", text = "๎˜Ž", fg = "{{colors.primary.default.hex}}" }, + { name = "sln", text = "๎œŒ", fg = "{{colors.primary.default.hex}}" }, + { name = "slnx", text = "๎œŒ", fg = "{{colors.primary.default.hex}}" }, + { name = "slvs", text = "๓ฐปซ", fg = "{{colors.primary.default.hex}}" }, + { name = "sml", text = "๓ฐ˜ง", fg = "{{colors.primary.default.hex}}" }, + { name = "so", text = "๎ฎœ", fg = "{{colors.primary.default.hex}}" }, + { name = "sol", text = "๎™–", fg = "{{colors.primary.default.hex}}" }, + { name = "spec.js", text = "๏’™", fg = "{{colors.primary.default.hex}}" }, + { name = "spec.jsx", text = "๏’™", fg = "{{colors.primary.default.hex}}" }, + { name = "spec.ts", text = "๏’™", fg = "{{colors.primary.default.hex}}" }, + { name = "spec.tsx", text = "๏’™", fg = "{{colors.primary.default.hex}}" }, + { name = "spx", text = "๏€", fg = "{{colors.primary.default.hex}}" }, + { name = "sql", text = "๎œ†", fg = "{{colors.primary.default.hex}}" }, + { name = "sqlite", text = "๎œ†", fg = "{{colors.primary.default.hex}}" }, + { name = "sqlite3", text = "๎œ†", fg = "{{colors.primary.default.hex}}" }, + { name = "srt", text = "๓ฐจ–", fg = "{{colors.primary.default.hex}}" }, + { name = "ssa", text = "๓ฐจ–", fg = "{{colors.primary.default.hex}}" }, + { name = "ste", text = "๓ฐปซ", fg = "{{colors.primary.default.hex}}" }, + { name = "step", text = "๓ฐปซ", fg = "{{colors.primary.default.hex}}" }, + { name = "stl", text = "๓ฐ†ง", fg = "{{colors.primary.default.hex}}" }, + { name = "stories.js", text = "๎ขณ", fg = "{{colors.primary.default.hex}}" }, + { name = "stories.jsx", text = "๎ขณ", fg = "{{colors.primary.default.hex}}" }, + { name = "stories.mjs", text = "๎ขณ", fg = "{{colors.primary.default.hex}}" }, + { name = "stories.svelte", text = "๎ขณ", fg = "{{colors.primary.default.hex}}" }, + { name = "stories.ts", text = "๎ขณ", fg = "{{colors.primary.default.hex}}" }, + { name = "stories.tsx", text = "๎ขณ", fg = "{{colors.primary.default.hex}}" }, + { name = "stories.vue", text = "๎ขณ", fg = "{{colors.primary.default.hex}}" }, + { name = "stp", text = "๓ฐปซ", fg = "{{colors.primary.default.hex}}" }, + { name = "strings", text = "๏†ซ", fg = "{{colors.primary.default.hex}}" }, + { name = "styl", text = "๎˜€", fg = "{{colors.primary.default.hex}}" }, + { name = "sub", text = "๓ฐจ–", fg = "{{colors.primary.default.hex}}" }, + { name = "sublime", text = "๎žช", fg = "{{colors.primary.default.hex}}" }, + { name = "suo", text = "๎œŒ", fg = "{{colors.primary.default.hex}}" }, + { name = "sv", text = "๓ฐ›", fg = "{{colors.primary.default.hex}}" }, + { name = "svelte", text = "๎š—", fg = "{{colors.primary.default.hex}}" }, + { name = "svg", text = "๓ฐœก", fg = "{{colors.primary.default.hex}}" }, + { name = "svgz", text = "๓ฐœก", fg = "{{colors.primary.default.hex}}" }, + { name = "svh", text = "๓ฐ›", fg = "{{colors.primary.default.hex}}" }, + { name = "swift", text = "๎•", fg = "{{colors.primary.default.hex}}" }, + { name = "t", text = "๎ฉ", fg = "{{colors.primary.default.hex}}" }, + { name = "tbc", text = "๓ฐ›“", fg = "{{colors.primary.default.hex}}" }, + { name = "tcl", text = "๓ฐ›“", fg = "{{colors.primary.default.hex}}" }, + { name = "templ", text = "๎ซ„", fg = "{{colors.primary.default.hex}}" }, + { name = "terminal", text = "๏’‰", fg = "{{colors.primary.default.hex}}" }, + { name = "test.js", text = "๏’™", fg = "{{colors.primary.default.hex}}" }, + { name = "test.jsx", text = "๏’™", fg = "{{colors.primary.default.hex}}" }, + { name = "test.ts", text = "๏’™", fg = "{{colors.primary.default.hex}}" }, + { name = "test.tsx", text = "๏’™", fg = "{{colors.primary.default.hex}}" }, + { name = "tex", text = "๎š›", fg = "{{colors.primary.default.hex}}" }, + { name = "tf", text = "๎šš", fg = "{{colors.primary.default.hex}}" }, + { name = "tfvars", text = "๏…›", fg = "{{colors.primary.default.hex}}" }, + { name = "tgz", text = "๏", fg = "{{colors.primary.default.hex}}" }, + { name = "tmpl", text = "๎ซ„", fg = "{{colors.primary.default.hex}}" }, + { name = "tmux", text = "๎ฏˆ", fg = "{{colors.primary.default.hex}}" }, + { name = "toml", text = "๎šฒ", fg = "{{colors.primary.default.hex}}" }, + { name = "torrent", text = "๏€™", fg = "{{colors.primary.default.hex}}" }, + { name = "tres", text = "๎™Ÿ", fg = "{{colors.primary.default.hex}}" }, + { name = "ts", text = "๎˜จ", fg = "{{colors.primary.default.hex}}" }, + { name = "tscn", text = "๎™Ÿ", fg = "{{colors.primary.default.hex}}" }, + { name = "tsconfig", text = "๎ฒ", fg = "{{colors.primary.default.hex}}" }, + { name = "tsx", text = "๎žบ", fg = "{{colors.primary.default.hex}}" }, + { name = "ttf", text = "๏€ฑ", fg = "{{colors.primary.default.hex}}" }, + { name = "twig", text = "๎˜œ", fg = "{{colors.primary.default.hex}}" }, + { name = "txt", text = "๓ฐˆ™", fg = "{{colors.primary.default.hex}}" }, + { name = "txz", text = "๏", fg = "{{colors.primary.default.hex}}" }, + { name = "typ", text = "๏ฟ", fg = "{{colors.primary.default.hex}}" }, + { name = "typoscript", text = "๎ฒ", fg = "{{colors.primary.default.hex}}" }, + { name = "ui", text = "๏‹", fg = "{{colors.primary.default.hex}}" }, + { name = "v", text = "๓ฐ›", fg = "{{colors.primary.default.hex}}" }, + { name = "vala", text = "๎ฃ‘", fg = "{{colors.primary.default.hex}}" }, + { name = "vert", text = "๎ก•", fg = "{{colors.primary.default.hex}}" }, + { name = "vh", text = "๓ฐ›", fg = "{{colors.primary.default.hex}}" }, + { name = "vhd", text = "๓ฐ›", fg = "{{colors.primary.default.hex}}" }, + { name = "vhdl", text = "๓ฐ›", fg = "{{colors.primary.default.hex}}" }, + { name = "vi", text = "๎ ž", fg = "{{colors.primary.default.hex}}" }, + { name = "vim", text = "๎˜ซ", fg = "{{colors.primary.default.hex}}" }, + { name = "vsh", text = "๎šฌ", fg = "{{colors.primary.default.hex}}" }, + { name = "vsix", text = "๎œŒ", fg = "{{colors.primary.default.hex}}" }, + { name = "vue", text = "๎š ", fg = "{{colors.primary.default.hex}}" }, + { name = "wasm", text = "๎šก", fg = "{{colors.primary.default.hex}}" }, + { name = "wav", text = "๏€", fg = "{{colors.primary.default.hex}}" }, + { name = "webm", text = "๎šŸ", fg = "{{colors.primary.default.hex}}" }, + { name = "webmanifest", text = "๎˜‹", fg = "{{colors.primary.default.hex}}" }, + { name = "webp", text = "๎˜", fg = "{{colors.primary.default.hex}}" }, + { name = "webpack", text = "๓ฐœซ", fg = "{{colors.primary.default.hex}}" }, + { name = "wma", text = "๏€", fg = "{{colors.primary.default.hex}}" }, + { name = "wmv", text = "๎šŸ", fg = "{{colors.primary.default.hex}}" }, + { name = "woff", text = "๏€ฑ", fg = "{{colors.primary.default.hex}}" }, + { name = "woff2", text = "๏€ฑ", fg = "{{colors.primary.default.hex}}" }, + { name = "wrl", text = "๓ฐ†ง", fg = "{{colors.primary.default.hex}}" }, + { name = "wrz", text = "๓ฐ†ง", fg = "{{colors.primary.default.hex}}" }, + { name = "wv", text = "๏€", fg = "{{colors.primary.default.hex}}" }, + { name = "wvc", text = "๏€", fg = "{{colors.primary.default.hex}}" }, + { name = "x", text = "๎š‘", fg = "{{colors.primary.default.hex}}" }, + { name = "xaml", text = "๓ฐ™ณ", fg = "{{colors.primary.default.hex}}" }, + { name = "xcf", text = "๏Œธ", fg = "{{colors.primary.default.hex}}" }, + { name = "xcplayground", text = "๎•", fg = "{{colors.primary.default.hex}}" }, + { name = "xcstrings", text = "๏†ซ", fg = "{{colors.primary.default.hex}}" }, + { name = "xls", text = "๓ฐˆ›", fg = "{{colors.primary.default.hex}}" }, + { name = "xlsx", text = "๓ฐˆ›", fg = "{{colors.primary.default.hex}}" }, + { name = "xm", text = "๎š‘", fg = "{{colors.primary.default.hex}}" }, + { name = "xml", text = "๓ฐ—€", fg = "{{colors.primary.default.hex}}" }, + { name = "xpi", text = "๎ž†", fg = "{{colors.primary.default.hex}}" }, + { name = "xslt", text = "๓ฐ—€", fg = "{{colors.primary.default.hex}}" }, + { name = "xul", text = "๎…", fg = "{{colors.primary.default.hex}}" }, + { name = "xz", text = "๏", fg = "{{colors.primary.default.hex}}" }, + { name = "yaml", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = "yml", text = "๎˜•", fg = "{{colors.primary.default.hex}}" }, + { name = "zig", text = "๎šฉ", fg = "{{colors.primary.default.hex}}" }, + { name = "zip", text = "๏", fg = "{{colors.primary.default.hex}}" }, + { name = "zsh", text = "๎ž•", fg = "{{colors.primary.default.hex}}" }, + { name = "zst", text = "๏", fg = "{{colors.primary.default.hex}}" }, + { name = "๐Ÿ”ฅ", text = "๏ญ", fg = "{{colors.primary.default.hex}}" }, +] +conds = [ + # Special files + { if = "orphan", text = "๏„ง", fg = "{{colors.primary.default.hex}}" }, + { if = "link", text = "๏’", fg = "{{colors.primary.default.hex}}" }, + { if = "block", text = "๏ƒ‰", fg = "{{colors.primary.default.hex}}" }, + { if = "char", text = "๏‡€", fg = "{{colors.primary.default.hex}}" }, + { if = "fifo", text = "๏‡‘", fg = "{{colors.primary.default.hex}}" }, + { if = "sock", text = "๏‡ค", fg = "{{colors.primary.default.hex}}" }, + { if = "sticky", text = "๏‚", fg = "{{colors.primary.default.hex}}" }, + { if = "dummy", text = "๏—", fg = "{{colors.primary.default.hex}}" }, + + # Fallback + { if = "dir", text = "๎—ฟ", fg = "{{colors.primary.default.hex}}" }, + { if = "exec", text = "๏’‰", fg = "{{colors.primary.default.hex}}" }, + { if = "!dir", text = "๏…›", fg = "{{colors.primary.default.hex}}" }, +] + +# : }}} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/zathurarc b/arch/.config/quickshell/noctalia-shell/Assets/Templates/zathurarc new file mode 100644 index 0000000..248a52c --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/zathurarc @@ -0,0 +1,31 @@ +set default-bg "{{colors.surface.default.rgba | set_alpha: 0.8}}" +set default-fg "{{colors.on_surface.default.hex}}" +set recolor-lightcolor "rgba(0,0,0,0)" +set recolor-darkcolor "{{colors.on_surface.default.hex}}" + +set statusbar-bg "{{colors.on_primary.default.hex}}" +set statusbar-fg "{{colors.primary.default.hex}}" +set inputbar-bg "{{colors.on_primary.default.hex}}" +set inputbar-fg "{{colors.primary.default.hex}}" + +set notification-bg "{{colors.on_primary.default.hex}}" +set notification-fg "{{colors.primary.default.hex}}" +set notification-error-bg "{{colors.on_error.default.hex}}" +set notification-error-fg "{{colors.error.default.hex}}" +set notification-warning-bg "{{colors.primary_fixed.default.hex}}" +set notification-warning-fg "{{colors.error_container.default.hex}}" + +set index-bg "rgba(0,0,0,0)" +set index-fg "{{colors.on_surface.default.hex}}" +set index-active-bg "{{colors.primary.default.hex}}" +set index-active-fg "{{colors.on_primary.default.hex}}" +set highlight-color "{{colors.primary_fixed.default.rgba | set_alpha: 0.5}}" +set highlight-active-color "{{colors.primary_fixed_dim.default.rgba | set_alpha: 0.5}}" +set highlight-fg "{{colors.on_primary.default.hex}}" + +set completion-bg "{{colors.on_primary.default.hex}}" +set completion-fg "{{colors.primary.default.hex}}" +set completion-group-bg "{{colors.on_primary.default.hex}}" +set completion-group-fg "{{colors.primary.default.hex}}" +set completion-highlight-fg "{{colors.on_primary.default.hex}}" +set completion-highlight-bg "{{colors.primary.default.hex}}" diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/zed.json b/arch/.config/quickshell/noctalia-shell/Assets/Templates/zed.json new file mode 100644 index 0000000..b44d9dd --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/zed.json @@ -0,0 +1,1175 @@ +{ + "$schema": "https://zed.dev/schema/themes/v0.2.0.json", + "name": "Noctalia", + "author": "Noctalia", + "themes": [ + { + "name": "Noctalia Dark", + "appearance": "dark", + "style": { + "accents": [ + "{{colors.primary.dark.hex}}", + "{{colors.secondary.dark.hex}}", + "{{colors.tertiary.dark.hex}}" + ], + "background.appearance": "opaque", + "border": "{{colors.outline_variant.dark.hex}}", + "border.variant": "{{colors.outline.dark.hex}}", + "border.focused": "{{colors.primary.dark.hex}}", + "border.selected": "{{colors.primary.dark.hex}}", + "border.transparent": "{{colors.outline_variant.dark.hex}}40", + "border.disabled": "{{colors.outline_variant.dark.hex}}60", + "elevated_surface.background": "{{colors.surface_container_high.dark.hex}}", + "surface.background": "{{colors.surface.dark.hex}}", + "background": "{{colors.background.dark.hex}}", + "element.background": "{{colors.surface_container.dark.hex}}", + "element.hover": "{{colors.surface_container_high.dark.hex}}", + "element.active": "{{colors.surface_container_highest.dark.hex}}", + "element.selected": "{{colors.secondary_container.dark.hex}}", + "element.disabled": "{{colors.surface_variant.dark.hex}}", + "drop_target.background": "{{colors.primary_container.dark.hex}}80", + "ghost_element.background": null, + "ghost_element.hover": "{{colors.surface_container.dark.hex}}80", + "ghost_element.active": "{{colors.surface_container_high.dark.hex}}", + "ghost_element.selected": "{{colors.secondary_container.dark.hex}}80", + "ghost_element.disabled": "{{colors.surface_variant.dark.hex}}60", + "text": "{{colors.on_surface.dark.hex}}", + "text.muted": "{{colors.on_surface_variant.dark.hex}}", + "text.placeholder": "{{colors.on_surface_variant.dark.hex}}99", + "text.disabled": "{{colors.on_surface.dark.hex}}60", + "text.accent": "{{colors.primary.dark.hex}}", + "icon": "{{colors.on_surface.dark.hex}}", + "icon.muted": "{{colors.on_surface_variant.dark.hex}}", + "icon.disabled": "{{colors.on_surface.dark.hex}}60", + "icon.placeholder": "{{colors.on_surface_variant.dark.hex}}80", + "icon.accent": "{{colors.primary.dark.hex}}", + "status_bar.background": "{{colors.surface.dark.hex}}", + "title_bar.background": "{{colors.surface.dark.hex}}", + "title_bar.inactive_background": "{{colors.surface_dim.dark.hex}}", + "toolbar.background": "{{colors.surface_container_low.dark.hex}}", + "tab_bar.background": "{{colors.surface_container.dark.hex}}", + "tab.inactive_background": "{{colors.surface_container_low.dark.hex}}", + "tab.active_background": "{{colors.surface_container_high.dark.hex}}", + "search.match_background": "{{colors.tertiary_container.dark.hex}}80", + "panel.background": "{{colors.surface_container_low.dark.hex}}", + "panel.focused_border": "{{colors.primary.dark.hex}}", + "pane.focused_border": "{{colors.primary.dark.hex}}", + "scrollbar.thumb.background": "{{colors.on_surface_variant.dark.hex}}80", + "scrollbar.thumb.hover_background": "{{colors.on_surface_variant.dark.hex}}c0", + "scrollbar.thumb.border": "{{colors.outline_variant.dark.hex}}40", + "scrollbar.track.background": "{{colors.surface_container.dark.hex}}", + "scrollbar.track.border": "{{colors.outline_variant.dark.hex}}20", + "editor.foreground": "{{colors.on_surface.dark.hex}}", + "editor.background": "{{colors.surface_container_low.dark.hex}}", + "editor.gutter.background": "{{colors.surface_container_low.dark.hex}}", + "editor.subheader.background": "{{colors.surface_container.dark.hex}}", + "editor.indent_guide": "{{colors.outline_variant.dark.hex}}60", + "editor.indent_guide_active": "{{colors.outline.dark.hex}}", + "editor.active_line.background": "{{colors.surface_container_high.dark.hex}}80", + "editor.highlighted_line.background": "{{colors.surface_container_high.dark.hex}}60", + "editor.line_number": "{{colors.on_surface_variant.dark.hex}}", + "editor.active_line_number": "{{colors.primary.dark.hex}}", + "editor.invisible": "{{colors.outline_variant.dark.hex}}80", + "editor.wrap_guide": "{{colors.outline_variant.dark.hex}}40", + "editor.active_wrap_guide": "{{colors.outline.dark.hex}}80", + "editor.document_highlight.read_background": "{{colors.primary_container.dark.hex}}60", + "editor.document_highlight.write_background": "{{colors.secondary_container.dark.hex}}80", + "terminal.background": "{{colors.surface_container_low.dark.hex}}", + "terminal.foreground": "{{colors.on_surface.dark.hex}}", + "terminal.bright_foreground": "{{colors.on_surface.dark.hex}}", + "terminal.dim_foreground": "{{colors.on_surface_variant.dark.hex}}", + "terminal.ansi.black": "{{colors.surface_dim.dark.hex}}", + "terminal.ansi.bright_black": "{{colors.surface_container_high.dark.hex}}", + "terminal.ansi.dim_black": "{{colors.surface.dark.hex}}", + "terminal.ansi.red": "{{colors.error.dark.hex}}", + "terminal.ansi.bright_red": "{{colors.error.dark.hex}}", + "terminal.ansi.dim_red": "{{colors.on_error_container.dark.hex}}", + "terminal.ansi.green": "{{colors.tertiary.dark.hex}}", + "terminal.ansi.bright_green": "{{colors.tertiary.dark.hex}}", + "terminal.ansi.dim_green": "{{colors.tertiary_fixed_dim.dark.hex}}", + "terminal.ansi.yellow": "{{colors.tertiary_fixed_dim.dark.hex}}", + "terminal.ansi.bright_yellow": "{{colors.tertiary_fixed.dark.hex}}", + "terminal.ansi.dim_yellow": "{{colors.on_tertiary_fixed.dark.hex}}", + "terminal.ansi.blue": "{{colors.primary.dark.hex}}", + "terminal.ansi.bright_blue": "{{colors.primary.dark.hex}}", + "terminal.ansi.dim_blue": "{{colors.on_primary_container.dark.hex}}", + "terminal.ansi.magenta": "{{colors.secondary.dark.hex}}", + "terminal.ansi.bright_magenta": "{{colors.secondary.dark.hex}}", + "terminal.ansi.dim_magenta": "{{colors.on_secondary_container.dark.hex}}", + "terminal.ansi.cyan": "{{colors.primary_fixed_dim.dark.hex}}", + "terminal.ansi.bright_cyan": "{{colors.primary_fixed.dark.hex}}", + "terminal.ansi.dim_cyan": "{{colors.on_primary_fixed_variant.dark.hex}}", + "terminal.ansi.white": "{{colors.on_surface.dark.hex}}", + "terminal.ansi.bright_white": "{{colors.on_surface.dark.hex}}", + "terminal.ansi.dim_white": "{{colors.on_surface_variant.dark.hex}}", + "link_text.hover": "{{colors.primary.dark.hex}}", + "conflict": "{{colors.error.dark.hex}}", + "conflict.background": "{{colors.error_container.dark.hex}}80", + "conflict.border": "{{colors.on_error_container.dark.hex}}", + "created": "{{colors.tertiary.dark.hex}}", + "created.background": "{{colors.tertiary_container.dark.hex}}80", + "created.border": "{{colors.on_tertiary_container.dark.hex}}", + "deleted": "{{colors.error.dark.hex}}", + "deleted.background": "{{colors.error_container.dark.hex}}80", + "deleted.border": "{{colors.on_error_container.dark.hex}}", + "error": "{{colors.error.dark.hex}}", + "error.background": "{{colors.error_container.dark.hex}}", + "error.border": "{{colors.on_error_container.dark.hex}}", + "hidden": "{{colors.outline_variant.dark.hex}}", + "hidden.border": "{{colors.outline_variant.dark.hex}}60", + "hint": "{{colors.primary.dark.hex}}", + "hint.background": "{{colors.primary_container.dark.hex}}80", + "hint.border": "{{colors.on_primary_container.dark.hex}}", + "ignored": "{{colors.on_surface_variant.dark.hex}}60", + "ignored.background": "{{colors.surface_variant.dark.hex}}40", + "ignored.border": "{{colors.outline_variant.dark.hex}}40", + "info": "{{colors.primary.dark.hex}}", + "info.background": "{{colors.primary_container.dark.hex}}80", + "info.border": "{{colors.on_primary_container.dark.hex}}", + "modified": "{{colors.secondary.dark.hex}}", + "modified.background": "{{colors.secondary_container.dark.hex}}80", + "modified.border": "{{colors.on_secondary_container.dark.hex}}", + "predictive": "{{colors.on_surface_variant.dark.hex}}80", + "predictive.border": "{{colors.outline.dark.hex}}", + "predictive.background": "{{colors.surface_container_highest.dark.hex}}80", + "renamed": "{{colors.secondary.dark.hex}}", + "renamed.border": "{{colors.on_secondary_container.dark.hex}}", + "renamed.background": "{{colors.secondary_container.dark.hex}}80", + "success": "{{colors.tertiary.dark.hex}}", + "success.background": "{{colors.tertiary_container.dark.hex}}80", + "success.border": "{{colors.on_tertiary_container.dark.hex}}", + "unreachable": "{{colors.on_surface_variant.dark.hex}}60", + "unreachable.background": "{{colors.surface_variant.dark.hex}}40", + "unreachable.border": "{{colors.outline_variant.dark.hex}}60", + "warning": "{{colors.tertiary_fixed_dim.dark.hex}}", + "warning.background": "{{colors.tertiary_container.dark.hex}}80", + "warning.border": "{{colors.on_tertiary_container.dark.hex}}", + "players": [ + { + "cursor": "{{colors.primary.dark.hex}}", + "background": "{{colors.primary_container.dark.hex}}80", + "selection": "{{colors.primary_container.dark.hex}}60" + }, + { + "cursor": "{{colors.secondary.dark.hex}}", + "background": "{{colors.secondary_container.dark.hex}}80", + "selection": "{{colors.secondary_container.dark.hex}}60" + } + ], + "syntax": { + "boolean": { + "color": "{{colors.tertiary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "comment": { + "color": "{{colors.on_surface_variant.dark.hex}}", + "font_style": "italic", + "font_weight": null + }, + "comment.doc": { + "color": "{{colors.on_surface_variant.dark.hex}}", + "font_style": "italic", + "font_weight": null + }, + "constant": { + "color": "{{colors.tertiary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "constructor": { + "color": "{{colors.secondary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "emphasis": { + "color": "{{colors.primary.dark.hex}}", + "font_style": "italic", + "font_weight": null + }, + "emphasis.strong": { + "color": "{{colors.primary.dark.hex}}", + "font_style": null, + "font_weight": 700 + }, + "function": { + "color": "{{colors.primary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "keyword": { + "color": "{{colors.secondary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "number": { + "color": "{{colors.tertiary_fixed.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "operator": { + "color": "{{colors.on_surface_variant.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "property": { + "color": "{{colors.on_surface.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation": { + "color": "{{colors.on_surface_variant.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation.bracket": { + "color": "{{colors.secondary_fixed.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation.delimiter": { + "color": "{{colors.on_surface_variant.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation.list_marker": { + "color": "{{colors.on_surface_variant.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation.special": { + "color": "{{colors.secondary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "string": { + "color": "{{colors.tertiary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "string.escape": { + "color": "{{colors.tertiary_fixed_dim.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "string.regex": { + "color": "{{colors.tertiary_fixed.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "string.special": { + "color": "{{colors.secondary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "string.special.symbol": { + "color": "{{colors.tertiary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "tag": { + "color": "{{colors.secondary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "text.literal": { + "color": "{{colors.tertiary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "type": { + "color": "{{colors.primary_fixed.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "variable": { + "color": "{{colors.on_surface.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "variable.special": { + "color": "{{colors.primary.dark.hex}}", + "font_style": null, + "font_weight": null + } + } + } + }, + { + "name": "Noctalia Light", + "appearance": "light", + "style": { + "accents": [ + "{{colors.primary.light.hex}}", + "{{colors.secondary.light.hex}}", + "{{colors.tertiary.light.hex}}" + ], + "background.appearance": "opaque", + "border": "{{colors.outline_variant.light.hex}}", + "border.variant": "{{colors.outline.light.hex}}", + "border.focused": "{{colors.primary.light.hex}}", + "border.selected": "{{colors.primary.light.hex}}", + "border.transparent": "{{colors.outline_variant.light.hex}}40", + "border.disabled": "{{colors.outline_variant.light.hex}}60", + "elevated_surface.background": "{{colors.surface_container_high.light.hex}}", + "surface.background": "{{colors.surface.light.hex}}", + "background": "{{colors.background.light.hex}}", + "element.background": "{{colors.surface_container.light.hex}}", + "element.hover": "{{colors.surface_container_high.light.hex}}", + "element.active": "{{colors.surface_container_highest.light.hex}}", + "element.selected": "{{colors.secondary_container.light.hex}}", + "element.disabled": "{{colors.surface_variant.light.hex}}", + "drop_target.background": "{{colors.primary_container.light.hex}}80", + "ghost_element.background": null, + "ghost_element.hover": "{{colors.surface_container.light.hex}}80", + "ghost_element.active": "{{colors.surface_container_high.light.hex}}", + "ghost_element.selected": "{{colors.secondary_container.light.hex}}80", + "ghost_element.disabled": "{{colors.surface_variant.light.hex}}60", + "text": "{{colors.on_surface.light.hex}}", + "text.muted": "{{colors.on_surface_variant.light.hex}}", + "text.placeholder": "{{colors.on_surface_variant.light.hex}}99", + "text.disabled": "{{colors.on_surface.light.hex}}60", + "text.accent": "{{colors.primary.light.hex}}", + "icon": "{{colors.on_surface.light.hex}}", + "icon.muted": "{{colors.on_surface_variant.light.hex}}", + "icon.disabled": "{{colors.on_surface.light.hex}}60", + "icon.placeholder": "{{colors.on_surface_variant.light.hex}}80", + "icon.accent": "{{colors.primary.light.hex}}", + "status_bar.background": "{{colors.surface.light.hex}}", + "title_bar.background": "{{colors.surface.light.hex}}", + "title_bar.inactive_background": "{{colors.surface_dim.light.hex}}", + "toolbar.background": "{{colors.surface_container_low.light.hex}}", + "tab_bar.background": "{{colors.surface_container.light.hex}}", + "tab.inactive_background": "{{colors.surface_container_low.light.hex}}", + "tab.active_background": "{{colors.surface_container_high.light.hex}}", + "search.match_background": "{{colors.tertiary_container.light.hex}}80", + "panel.background": "{{colors.surface_container_low.light.hex}}", + "panel.focused_border": "{{colors.primary.light.hex}}", + "pane.focused_border": "{{colors.primary.light.hex}}", + "scrollbar.thumb.background": "{{colors.on_surface_variant.light.hex}}80", + "scrollbar.thumb.hover_background": "{{colors.on_surface_variant.light.hex}}c0", + "scrollbar.thumb.border": "{{colors.outline_variant.light.hex}}40", + "scrollbar.track.background": "{{colors.surface_container.light.hex}}", + "scrollbar.track.border": "{{colors.outline_variant.light.hex}}20", + "editor.foreground": "{{colors.on_surface.light.hex}}", + "editor.background": "{{colors.surface_container_low.light.hex}}", + "editor.gutter.background": "{{colors.surface_container_low.light.hex}}", + "editor.subheader.background": "{{colors.surface_container.light.hex}}", + "editor.indent_guide": "{{colors.outline_variant.light.hex}}60", + "editor.indent_guide_active": "{{colors.outline.light.hex}}", + "editor.active_line.background": "{{colors.surface_container_high.light.hex}}80", + "editor.highlighted_line.background": "{{colors.surface_container_high.light.hex}}60", + "editor.line_number": "{{colors.on_surface_variant.light.hex}}", + "editor.active_line_number": "{{colors.primary.light.hex}}", + "editor.invisible": "{{colors.outline_variant.light.hex}}80", + "editor.wrap_guide": "{{colors.outline_variant.light.hex}}40", + "editor.active_wrap_guide": "{{colors.outline.light.hex}}80", + "editor.document_highlight.read_background": "{{colors.primary_container.light.hex}}60", + "editor.document_highlight.write_background": "{{colors.secondary_container.light.hex}}80", + "terminal.background": "{{colors.surface_container_low.light.hex}}", + "terminal.foreground": "{{colors.on_surface.light.hex}}", + "terminal.bright_foreground": "{{colors.on_surface.light.hex}}", + "terminal.dim_foreground": "{{colors.on_surface_variant.light.hex}}", + "terminal.ansi.black": "{{colors.surface_dim.light.hex}}", + "terminal.ansi.bright_black": "{{colors.surface_container_high.light.hex}}", + "terminal.ansi.dim_black": "{{colors.surface.light.hex}}", + "terminal.ansi.red": "{{colors.error.light.hex}}", + "terminal.ansi.bright_red": "{{colors.error.light.hex}}", + "terminal.ansi.dim_red": "{{colors.on_error_container.light.hex}}", + "terminal.ansi.green": "{{colors.tertiary.light.hex}}", + "terminal.ansi.bright_green": "{{colors.tertiary.light.hex}}", + "terminal.ansi.dim_green": "{{colors.tertiary_fixed_dim.light.hex}}", + "terminal.ansi.yellow": "{{colors.tertiary_fixed_dim.light.hex}}", + "terminal.ansi.bright_yellow": "{{colors.tertiary_fixed.light.hex}}", + "terminal.ansi.dim_yellow": "{{colors.on_tertiary_fixed.light.hex}}", + "terminal.ansi.blue": "{{colors.primary.light.hex}}", + "terminal.ansi.bright_blue": "{{colors.primary.light.hex}}", + "terminal.ansi.dim_blue": "{{colors.on_primary_container.light.hex}}", + "terminal.ansi.magenta": "{{colors.secondary.light.hex}}", + "terminal.ansi.bright_magenta": "{{colors.secondary.light.hex}}", + "terminal.ansi.dim_magenta": "{{colors.on_secondary_container.light.hex}}", + "terminal.ansi.cyan": "{{colors.primary_fixed_dim.light.hex}}", + "terminal.ansi.bright_cyan": "{{colors.primary_fixed.light.hex}}", + "terminal.ansi.dim_cyan": "{{colors.on_primary_fixed_variant.light.hex}}", + "terminal.ansi.white": "{{colors.on_surface.light.hex}}", + "terminal.ansi.bright_white": "{{colors.on_surface.light.hex}}", + "terminal.ansi.dim_white": "{{colors.on_surface_variant.light.hex}}", + "link_text.hover": "{{colors.primary.light.hex}}", + "conflict": "{{colors.error.light.hex}}", + "conflict.background": "{{colors.error_container.light.hex}}80", + "conflict.border": "{{colors.on_error_container.light.hex}}", + "created": "{{colors.tertiary.light.hex}}", + "created.background": "{{colors.tertiary_container.light.hex}}80", + "created.border": "{{colors.on_tertiary_container.light.hex}}", + "deleted": "{{colors.error.light.hex}}", + "deleted.background": "{{colors.error_container.light.hex}}80", + "deleted.border": "{{colors.on_error_container.light.hex}}", + "error": "{{colors.error.light.hex}}", + "error.background": "{{colors.error_container.light.hex}}", + "error.border": "{{colors.on_error_container.light.hex}}", + "hidden": "{{colors.outline_variant.light.hex}}", + "hidden.border": "{{colors.outline_variant.light.hex}}60", + "hint": "{{colors.primary.light.hex}}", + "hint.background": "{{colors.primary_container.light.hex}}80", + "hint.border": "{{colors.on_primary_container.light.hex}}", + "ignored": "{{colors.on_surface_variant.light.hex}}60", + "ignored.background": "{{colors.surface_variant.light.hex}}40", + "ignored.border": "{{colors.outline_variant.light.hex}}40", + "info": "{{colors.primary.light.hex}}", + "info.background": "{{colors.primary_container.light.hex}}80", + "info.border": "{{colors.on_primary_container.light.hex}}", + "modified": "{{colors.secondary.light.hex}}", + "modified.background": "{{colors.secondary_container.light.hex}}80", + "modified.border": "{{colors.on_secondary_container.light.hex}}", + "predictive": "{{colors.on_surface_variant.light.hex}}80", + "predictive.border": "{{colors.outline.light.hex}}", + "predictive.background": "{{colors.surface_container_highest.light.hex}}80", + "renamed": "{{colors.secondary.light.hex}}", + "renamed.border": "{{colors.on_secondary_container.light.hex}}", + "renamed.background": "{{colors.secondary_container.light.hex}}80", + "success": "{{colors.tertiary.light.hex}}", + "success.background": "{{colors.tertiary_container.light.hex}}80", + "success.border": "{{colors.on_tertiary_container.light.hex}}", + "unreachable": "{{colors.on_surface_variant.light.hex}}60", + "unreachable.background": "{{colors.surface_variant.light.hex}}40", + "unreachable.border": "{{colors.outline_variant.light.hex}}60", + "warning": "{{colors.tertiary_fixed_dim.light.hex}}", + "warning.background": "{{colors.tertiary_container.light.hex}}80", + "warning.border": "{{colors.on_tertiary_container.light.hex}}", + "players": [ + { + "cursor": "{{colors.primary.light.hex}}", + "background": "{{colors.primary_container.light.hex}}80", + "selection": "{{colors.primary_container.light.hex}}60" + }, + { + "cursor": "{{colors.secondary.light.hex}}", + "background": "{{colors.secondary_container.light.hex}}80", + "selection": "{{colors.secondary_container.light.hex}}60" + } + ], + "syntax": { + "boolean": { + "color": "{{colors.tertiary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "comment": { + "color": "{{colors.on_surface_variant.light.hex}}", + "font_style": "italic", + "font_weight": null + }, + "comment.doc": { + "color": "{{colors.on_surface_variant.light.hex}}", + "font_style": "italic", + "font_weight": null + }, + "constant": { + "color": "{{colors.tertiary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "constructor": { + "color": "{{colors.secondary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "emphasis": { + "color": "{{colors.primary.light.hex}}", + "font_style": "italic", + "font_weight": null + }, + "emphasis.strong": { + "color": "{{colors.primary.light.hex}}", + "font_style": null, + "font_weight": 700 + }, + "function": { + "color": "{{colors.primary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "keyword": { + "color": "{{colors.secondary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "number": { + "color": "{{colors.tertiary_fixed.light.hex}}", + "font_style": null, + "font_weight": null + }, + "operator": { + "color": "{{colors.on_surface_variant.light.hex}}", + "font_style": null, + "font_weight": null + }, + "property": { + "color": "{{colors.on_surface.light.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation": { + "color": "{{colors.on_surface_variant.light.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation.bracket": { + "color": "{{colors.secondary_fixed.light.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation.delimiter": { + "color": "{{colors.on_surface_variant.light.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation.list_marker": { + "color": "{{colors.on_surface_variant.light.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation.special": { + "color": "{{colors.secondary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "string": { + "color": "{{colors.tertiary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "string.escape": { + "color": "{{colors.tertiary_fixed_dim.light.hex}}", + "font_style": null, + "font_weight": null + }, + "string.regex": { + "color": "{{colors.tertiary_fixed.light.hex}}", + "font_style": null, + "font_weight": null + }, + "string.special": { + "color": "{{colors.secondary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "string.special.symbol": { + "color": "{{colors.tertiary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "tag": { + "color": "{{colors.secondary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "text.literal": { + "color": "{{colors.tertiary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "type": { + "color": "{{colors.primary_fixed.light.hex}}", + "font_style": null, + "font_weight": null + }, + "variable": { + "color": "{{colors.on_surface.light.hex}}", + "font_style": null, + "font_weight": null + }, + "variable.special": { + "color": "{{colors.primary.light.hex}}", + "font_style": null, + "font_weight": null + } + } + } + }, + { + "name": "Noctalia Dark Transparent", + "appearance": "dark", + "style": { + "accents": [ + "{{colors.primary.dark.hex}}", + "{{colors.secondary.dark.hex}}", + "{{colors.tertiary.dark.hex}}" + ], + "background.appearance": "opaque", + "border": "{{colors.outline_variant.dark.hex}}", + "border.variant": "{{colors.outline.dark.hex}}", + "border.focused": "{{colors.primary.dark.hex}}", + "border.selected": "{{colors.primary.dark.hex}}", + "border.transparent": "{{colors.outline_variant.dark.hex}}40", + "border.disabled": "{{colors.outline_variant.dark.hex}}60", + "elevated_surface.background": "{{colors.surface_container_high.dark.hex}}", + "surface.background": "{{colors.surface.dark.hex}}", + "background": "{{colors.background.dark.hex}}B6", + "element.background": "{{colors.surface_container.dark.hex}}", + "element.hover": "{{colors.surface_container_high.dark.hex}}", + "element.active": "{{colors.surface_container_highest.dark.hex}}", + "element.selected": "{{colors.secondary_container.dark.hex}}", + "element.disabled": "{{colors.surface_variant.dark.hex}}", + "drop_target.background": "{{colors.primary_container.dark.hex}}80", + "ghost_element.background": null, + "ghost_element.hover": "{{colors.surface_container.dark.hex}}80", + "ghost_element.active": "{{colors.surface_container_high.dark.hex}}", + "ghost_element.selected": "{{colors.secondary_container.dark.hex}}80", + "ghost_element.disabled": "{{colors.surface_variant.dark.hex}}60", + "text": "{{colors.on_surface.dark.hex}}", + "text.muted": "{{colors.on_surface_variant.dark.hex}}", + "text.placeholder": "{{colors.on_surface_variant.dark.hex}}99", + "text.disabled": "{{colors.on_surface.dark.hex}}60", + "text.accent": "{{colors.primary.dark.hex}}", + "icon": "{{colors.on_surface.dark.hex}}", + "icon.muted": "{{colors.on_surface_variant.dark.hex}}", + "icon.disabled": "{{colors.on_surface.dark.hex}}60", + "icon.placeholder": "{{colors.on_surface_variant.dark.hex}}80", + "icon.accent": "{{colors.primary.dark.hex}}", + "status_bar.background": "{{colors.surface.dark.hex}}B6", + "title_bar.background": "{{colors.surface.dark.hex}}B6", + "title_bar.inactive_background": "{{colors.surface_dim.dark.hex}}B6", + "toolbar.background": "#00000000", + "tab_bar.background": "#00000000", + "tab.inactive_background": "{{colors.surface_container_low.dark.hex}}B6", + "tab.active_background": "{{colors.surface_container_high.dark.hex}}B6", + "search.match_background": "{{colors.tertiary_container.dark.hex}}80", + "panel.background": "#00000000", + "panel.focused_border": "{{colors.primary.dark.hex}}", + "pane.focused_border": "{{colors.primary.dark.hex}}", + "scrollbar.thumb.background": "{{colors.on_surface_variant.dark.hex}}80", + "scrollbar.thumb.hover_background": "{{colors.on_surface_variant.dark.hex}}c0", + "scrollbar.thumb.border": "{{colors.outline_variant.dark.hex}}40", + "scrollbar.track.background": "#00000000", + "scrollbar.track.border": "{{colors.outline_variant.dark.hex}}20", + "editor.foreground": "{{colors.on_surface.dark.hex}}", + "editor.background": "#00000000", + "editor.gutter.background": "#00000000", + "editor.subheader.background": "#00000000", + "editor.indent_guide": "{{colors.outline_variant.dark.hex}}60", + "editor.indent_guide_active": "{{colors.outline.dark.hex}}", + "editor.active_line.background": "{{colors.surface_container_high.dark.hex}}80", + "editor.highlighted_line.background": "{{colors.surface_container_high.dark.hex}}60", + "editor.line_number": "{{colors.on_surface_variant.dark.hex}}", + "editor.active_line_number": "{{colors.primary.dark.hex}}", + "editor.invisible": "{{colors.outline_variant.dark.hex}}80", + "editor.wrap_guide": "{{colors.outline_variant.dark.hex}}40", + "editor.active_wrap_guide": "{{colors.outline.dark.hex}}80", + "editor.document_highlight.read_background": "{{colors.primary_container.dark.hex}}60", + "editor.document_highlight.write_background": "{{colors.secondary_container.dark.hex}}80", + "terminal.background": "#00000000", + "terminal.foreground": "{{colors.on_surface.dark.hex}}", + "terminal.bright_foreground": "{{colors.on_surface.dark.hex}}", + "terminal.dim_foreground": "{{colors.on_surface_variant.dark.hex}}", + "terminal.ansi.black": "{{colors.surface_dim.dark.hex}}", + "terminal.ansi.bright_black": "{{colors.surface_container_high.dark.hex}}", + "terminal.ansi.dim_black": "{{colors.surface.dark.hex}}", + "terminal.ansi.red": "{{colors.error.dark.hex}}", + "terminal.ansi.bright_red": "{{colors.error.dark.hex}}", + "terminal.ansi.dim_red": "{{colors.on_error_container.dark.hex}}", + "terminal.ansi.green": "{{colors.tertiary.dark.hex}}", + "terminal.ansi.bright_green": "{{colors.tertiary.dark.hex}}", + "terminal.ansi.dim_green": "{{colors.tertiary_fixed_dim.dark.hex}}", + "terminal.ansi.yellow": "{{colors.tertiary_fixed_dim.dark.hex}}", + "terminal.ansi.bright_yellow": "{{colors.tertiary_fixed.dark.hex}}", + "terminal.ansi.dim_yellow": "{{colors.on_tertiary_fixed.dark.hex}}", + "terminal.ansi.blue": "{{colors.primary.dark.hex}}", + "terminal.ansi.bright_blue": "{{colors.primary.dark.hex}}", + "terminal.ansi.dim_blue": "{{colors.on_primary_container.dark.hex}}", + "terminal.ansi.magenta": "{{colors.secondary.dark.hex}}", + "terminal.ansi.bright_magenta": "{{colors.secondary.dark.hex}}", + "terminal.ansi.dim_magenta": "{{colors.on_secondary_container.dark.hex}}", + "terminal.ansi.cyan": "{{colors.primary_fixed_dim.dark.hex}}", + "terminal.ansi.bright_cyan": "{{colors.primary_fixed.dark.hex}}", + "terminal.ansi.dim_cyan": "{{colors.on_primary_fixed_variant.dark.hex}}", + "terminal.ansi.white": "{{colors.on_surface.dark.hex}}", + "terminal.ansi.bright_white": "{{colors.on_surface.dark.hex}}", + "terminal.ansi.dim_white": "{{colors.on_surface_variant.dark.hex}}", + "link_text.hover": "{{colors.primary.dark.hex}}", + "conflict": "{{colors.error.dark.hex}}", + "conflict.background": "{{colors.error_container.dark.hex}}80", + "conflict.border": "{{colors.on_error_container.dark.hex}}", + "created": "{{colors.tertiary.dark.hex}}", + "created.background": "{{colors.tertiary_container.dark.hex}}80", + "created.border": "{{colors.on_tertiary_container.dark.hex}}", + "deleted": "{{colors.error.dark.hex}}", + "deleted.background": "{{colors.error_container.dark.hex}}80", + "deleted.border": "{{colors.on_error_container.dark.hex}}", + "error": "{{colors.error.dark.hex}}", + "error.background": "{{colors.error_container.dark.hex}}", + "error.border": "{{colors.on_error_container.dark.hex}}", + "hidden": "{{colors.outline_variant.dark.hex}}", + "hidden.border": "{{colors.outline_variant.dark.hex}}60", + "hint": "{{colors.primary.dark.hex}}", + "hint.background": "{{colors.primary_container.dark.hex}}80", + "hint.border": "{{colors.on_primary_container.dark.hex}}", + "ignored": "{{colors.on_surface_variant.dark.hex}}60", + "ignored.background": "{{colors.surface_variant.dark.hex}}40", + "ignored.border": "{{colors.outline_variant.dark.hex}}40", + "info": "{{colors.primary.dark.hex}}", + "info.background": "{{colors.primary_container.dark.hex}}80", + "info.border": "{{colors.on_primary_container.dark.hex}}", + "modified": "{{colors.secondary.dark.hex}}", + "modified.background": "{{colors.secondary_container.dark.hex}}80", + "modified.border": "{{colors.on_secondary_container.dark.hex}}", + "predictive": "{{colors.on_surface_variant.dark.hex}}80", + "predictive.border": "{{colors.outline.dark.hex}}", + "predictive.background": "{{colors.surface_container_highest.dark.hex}}80", + "renamed": "{{colors.secondary.dark.hex}}", + "renamed.border": "{{colors.on_secondary_container.dark.hex}}", + "renamed.background": "{{colors.secondary_container.dark.hex}}80", + "success": "{{colors.tertiary.dark.hex}}", + "success.background": "{{colors.tertiary_container.dark.hex}}80", + "success.border": "{{colors.on_tertiary_container.dark.hex}}", + "unreachable": "{{colors.on_surface_variant.dark.hex}}60", + "unreachable.background": "{{colors.surface_variant.dark.hex}}40", + "unreachable.border": "{{colors.outline_variant.dark.hex}}60", + "warning": "{{colors.tertiary_fixed_dim.dark.hex}}", + "warning.background": "{{colors.tertiary_container.dark.hex}}80", + "warning.border": "{{colors.on_tertiary_container.dark.hex}}", + "players": [ + { + "cursor": "{{colors.primary.dark.hex}}", + "background": "{{colors.primary_container.dark.hex}}80", + "selection": "{{colors.primary_container.dark.hex}}60" + }, + { + "cursor": "{{colors.secondary.dark.hex}}", + "background": "{{colors.secondary_container.dark.hex}}80", + "selection": "{{colors.secondary_container.dark.hex}}60" + } + ], + "syntax": { + "boolean": { + "color": "{{colors.tertiary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "comment": { + "color": "{{colors.on_surface_variant.dark.hex}}", + "font_style": "italic", + "font_weight": null + }, + "comment.doc": { + "color": "{{colors.on_surface_variant.dark.hex}}", + "font_style": "italic", + "font_weight": null + }, + "constant": { + "color": "{{colors.tertiary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "constructor": { + "color": "{{colors.secondary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "emphasis": { + "color": "{{colors.primary.dark.hex}}", + "font_style": "italic", + "font_weight": null + }, + "emphasis.strong": { + "color": "{{colors.primary.dark.hex}}", + "font_style": null, + "font_weight": 700 + }, + "function": { + "color": "{{colors.primary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "keyword": { + "color": "{{colors.secondary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "number": { + "color": "{{colors.tertiary_fixed.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "operator": { + "color": "{{colors.on_surface_variant.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "property": { + "color": "{{colors.on_surface.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation": { + "color": "{{colors.on_surface_variant.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation.bracket": { + "color": "{{colors.secondary_fixed.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation.delimiter": { + "color": "{{colors.on_surface_variant.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation.list_marker": { + "color": "{{colors.on_surface_variant.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation.special": { + "color": "{{colors.secondary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "string": { + "color": "{{colors.tertiary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "string.escape": { + "color": "{{colors.tertiary_fixed_dim.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "string.regex": { + "color": "{{colors.tertiary_fixed.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "string.special": { + "color": "{{colors.secondary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "string.special.symbol": { + "color": "{{colors.tertiary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "tag": { + "color": "{{colors.secondary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "text.literal": { + "color": "{{colors.tertiary.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "type": { + "color": "{{colors.primary_fixed.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "variable": { + "color": "{{colors.on_surface.dark.hex}}", + "font_style": null, + "font_weight": null + }, + "variable.special": { + "color": "{{colors.primary.dark.hex}}", + "font_style": null, + "font_weight": null + } + } + } + }, + { + "name": "Noctalia Light Transparent", + "appearance": "light", + "style": { + "accents": [ + "{{colors.primary.light.hex}}", + "{{colors.secondary.light.hex}}", + "{{colors.tertiary.light.hex}}" + ], + "background.appearance": "opaque", + "border": "{{colors.outline_variant.light.hex}}", + "border.variant": "{{colors.outline.light.hex}}", + "border.focused": "{{colors.primary.light.hex}}", + "border.selected": "{{colors.primary.light.hex}}", + "border.transparent": "{{colors.outline_variant.light.hex}}40", + "border.disabled": "{{colors.outline_variant.light.hex}}60", + "elevated_surface.background": "{{colors.surface_container_high.light.hex}}", + "surface.background": "{{colors.surface.light.hex}}", + "background": "{{colors.background.light.hex}}B6", + "element.background": "{{colors.surface_container.light.hex}}", + "element.hover": "{{colors.surface_container_high.light.hex}}", + "element.active": "{{colors.surface_container_highest.light.hex}}", + "element.selected": "{{colors.secondary_container.light.hex}}", + "element.disabled": "{{colors.surface_variant.light.hex}}", + "drop_target.background": "{{colors.primary_container.light.hex}}80", + "ghost_element.background": null, + "ghost_element.hover": "{{colors.surface_container.light.hex}}80", + "ghost_element.active": "{{colors.surface_container_high.light.hex}}", + "ghost_element.selected": "{{colors.secondary_container.light.hex}}80", + "ghost_element.disabled": "{{colors.surface_variant.light.hex}}60", + "text": "{{colors.on_surface.light.hex}}", + "text.muted": "{{colors.on_surface_variant.light.hex}}", + "text.placeholder": "{{colors.on_surface_variant.light.hex}}99", + "text.disabled": "{{colors.on_surface.light.hex}}60", + "text.accent": "{{colors.primary.light.hex}}", + "icon": "{{colors.on_surface.light.hex}}", + "icon.muted": "{{colors.on_surface_variant.light.hex}}", + "icon.disabled": "{{colors.on_surface.light.hex}}60", + "icon.placeholder": "{{colors.on_surface_variant.light.hex}}80", + "icon.accent": "{{colors.primary.light.hex}}", + "status_bar.background": "{{colors.surface.light.hex}}B6", + "title_bar.background": "{{colors.surface.light.hex}}B6", + "title_bar.inactive_background": "{{colors.surface_dim.light.hex}}B6", + "toolbar.background": "#00000000", + "tab_bar.background": "#00000000", + "tab.inactive_background": "{{colors.surface_container_low.light.hex}}B6", + "tab.active_background": "{{colors.surface_container_high.light.hex}}B6", + "search.match_background": "{{colors.tertiary_container.light.hex}}80", + "panel.background": "#00000000", + "panel.focused_border": "{{colors.primary.light.hex}}", + "pane.focused_border": "{{colors.primary.light.hex}}", + "scrollbar.thumb.background": "{{colors.on_surface_variant.light.hex}}80", + "scrollbar.thumb.hover_background": "{{colors.on_surface_variant.light.hex}}c0", + "scrollbar.thumb.border": "{{colors.outline_variant.light.hex}}40", + "scrollbar.track.background": "#00000000", + "scrollbar.track.border": "{{colors.outline_variant.light.hex}}20", + "editor.foreground": "{{colors.on_surface.light.hex}}", + "editor.background": "#00000000", + "editor.gutter.background": "#00000000", + "editor.subheader.background": "#00000000", + "editor.indent_guide": "{{colors.outline_variant.light.hex}}60", + "editor.indent_guide_active": "{{colors.outline.light.hex}}", + "editor.active_line.background": "{{colors.surface_container_high.light.hex}}80", + "editor.highlighted_line.background": "{{colors.surface_container_high.light.hex}}60", + "editor.line_number": "{{colors.on_surface_variant.light.hex}}", + "editor.active_line_number": "{{colors.primary.light.hex}}", + "editor.invisible": "{{colors.outline_variant.light.hex}}80", + "editor.wrap_guide": "{{colors.outline_variant.light.hex}}40", + "editor.active_wrap_guide": "{{colors.outline.light.hex}}80", + "editor.document_highlight.read_background": "{{colors.primary_container.light.hex}}60", + "editor.document_highlight.write_background": "{{colors.secondary_container.light.hex}}80", + "terminal.background": "#00000000", + "terminal.foreground": "{{colors.on_surface.light.hex}}", + "terminal.bright_foreground": "{{colors.on_surface.light.hex}}", + "terminal.dim_foreground": "{{colors.on_surface_variant.light.hex}}", + "terminal.ansi.black": "{{colors.surface_dim.light.hex}}", + "terminal.ansi.bright_black": "{{colors.surface_container_high.light.hex}}", + "terminal.ansi.dim_black": "{{colors.surface.light.hex}}", + "terminal.ansi.red": "{{colors.error.light.hex}}", + "terminal.ansi.bright_red": "{{colors.error.light.hex}}", + "terminal.ansi.dim_red": "{{colors.on_error_container.light.hex}}", + "terminal.ansi.green": "{{colors.tertiary.light.hex}}", + "terminal.ansi.bright_green": "{{colors.tertiary.light.hex}}", + "terminal.ansi.dim_green": "{{colors.tertiary_fixed_dim.light.hex}}", + "terminal.ansi.yellow": "{{colors.tertiary_fixed_dim.light.hex}}", + "terminal.ansi.bright_yellow": "{{colors.tertiary_fixed.light.hex}}", + "terminal.ansi.dim_yellow": "{{colors.on_tertiary_fixed.light.hex}}", + "terminal.ansi.blue": "{{colors.primary.light.hex}}", + "terminal.ansi.bright_blue": "{{colors.primary.light.hex}}", + "terminal.ansi.dim_blue": "{{colors.on_primary_container.light.hex}}", + "terminal.ansi.magenta": "{{colors.secondary.light.hex}}", + "terminal.ansi.bright_magenta": "{{colors.secondary.light.hex}}", + "terminal.ansi.dim_magenta": "{{colors.on_secondary_container.light.hex}}", + "terminal.ansi.cyan": "{{colors.primary_fixed_dim.light.hex}}", + "terminal.ansi.bright_cyan": "{{colors.primary_fixed.light.hex}}", + "terminal.ansi.dim_cyan": "{{colors.on_primary_fixed_variant.light.hex}}", + "terminal.ansi.white": "{{colors.on_surface.light.hex}}", + "terminal.ansi.bright_white": "{{colors.on_surface.light.hex}}", + "terminal.ansi.dim_white": "{{colors.on_surface_variant.light.hex}}", + "link_text.hover": "{{colors.primary.light.hex}}", + "conflict": "{{colors.error.light.hex}}", + "conflict.background": "{{colors.error_container.light.hex}}80", + "conflict.border": "{{colors.on_error_container.light.hex}}", + "created": "{{colors.tertiary.light.hex}}", + "created.background": "{{colors.tertiary_container.light.hex}}80", + "created.border": "{{colors.on_tertiary_container.light.hex}}", + "deleted": "{{colors.error.light.hex}}", + "deleted.background": "{{colors.error_container.light.hex}}80", + "deleted.border": "{{colors.on_error_container.light.hex}}", + "error": "{{colors.error.light.hex}}", + "error.background": "{{colors.error_container.light.hex}}", + "error.border": "{{colors.on_error_container.light.hex}}", + "hidden": "{{colors.outline_variant.light.hex}}", + "hidden.border": "{{colors.outline_variant.light.hex}}60", + "hint": "{{colors.primary.light.hex}}", + "hint.background": "{{colors.primary_container.light.hex}}80", + "hint.border": "{{colors.on_primary_container.light.hex}}", + "ignored": "{{colors.on_surface_variant.light.hex}}60", + "ignored.background": "{{colors.surface_variant.light.hex}}40", + "ignored.border": "{{colors.outline_variant.light.hex}}40", + "info": "{{colors.primary.light.hex}}", + "info.background": "{{colors.primary_container.light.hex}}80", + "info.border": "{{colors.on_primary_container.light.hex}}", + "modified": "{{colors.secondary.light.hex}}", + "modified.background": "{{colors.secondary_container.light.hex}}80", + "modified.border": "{{colors.on_secondary_container.light.hex}}", + "predictive": "{{colors.on_surface_variant.light.hex}}80", + "predictive.border": "{{colors.outline.light.hex}}", + "predictive.background": "{{colors.surface_container_highest.light.hex}}80", + "renamed": "{{colors.secondary.light.hex}}", + "renamed.border": "{{colors.on_secondary_container.light.hex}}", + "renamed.background": "{{colors.secondary_container.light.hex}}80", + "success": "{{colors.tertiary.light.hex}}", + "success.background": "{{colors.tertiary_container.light.hex}}80", + "success.border": "{{colors.on_tertiary_container.light.hex}}", + "unreachable": "{{colors.on_surface_variant.light.hex}}60", + "unreachable.background": "{{colors.surface_variant.light.hex}}40", + "unreachable.border": "{{colors.outline_variant.light.hex}}60", + "warning": "{{colors.tertiary_fixed_dim.light.hex}}", + "warning.background": "{{colors.tertiary_container.light.hex}}80", + "warning.border": "{{colors.on_tertiary_container.light.hex}}", + "players": [ + { + "cursor": "{{colors.primary.light.hex}}", + "background": "{{colors.primary_container.light.hex}}80", + "selection": "{{colors.primary_container.light.hex}}60" + }, + { + "cursor": "{{colors.secondary.light.hex}}", + "background": "{{colors.secondary_container.light.hex}}80", + "selection": "{{colors.secondary_container.light.hex}}60" + } + ], + "syntax": { + "boolean": { + "color": "{{colors.tertiary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "comment": { + "color": "{{colors.on_surface_variant.light.hex}}", + "font_style": "italic", + "font_weight": null + }, + "comment.doc": { + "color": "{{colors.on_surface_variant.light.hex}}", + "font_style": "italic", + "font_weight": null + }, + "constant": { + "color": "{{colors.tertiary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "constructor": { + "color": "{{colors.secondary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "emphasis": { + "color": "{{colors.primary.light.hex}}", + "font_style": "italic", + "font_weight": null + }, + "emphasis.strong": { + "color": "{{colors.primary.light.hex}}", + "font_style": null, + "font_weight": 700 + }, + "function": { + "color": "{{colors.primary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "keyword": { + "color": "{{colors.secondary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "number": { + "color": "{{colors.tertiary_fixed.light.hex}}", + "font_style": null, + "font_weight": null + }, + "operator": { + "color": "{{colors.on_surface_variant.light.hex}}", + "font_style": null, + "font_weight": null + }, + "property": { + "color": "{{colors.on_surface.light.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation": { + "color": "{{colors.on_surface_variant.light.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation.bracket": { + "color": "{{colors.secondary_fixed.light.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation.delimiter": { + "color": "{{colors.on_surface_variant.light.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation.list_marker": { + "color": "{{colors.on_surface_variant.light.hex}}", + "font_style": null, + "font_weight": null + }, + "punctuation.special": { + "color": "{{colors.secondary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "string": { + "color": "{{colors.tertiary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "string.escape": { + "color": "{{colors.tertiary_fixed_dim.light.hex}}", + "font_style": null, + "font_weight": null + }, + "string.regex": { + "color": "{{colors.tertiary_fixed.light.hex}}", + "font_style": null, + "font_weight": null + }, + "string.special": { + "color": "{{colors.secondary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "string.special.symbol": { + "color": "{{colors.tertiary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "tag": { + "color": "{{colors.secondary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "text.literal": { + "color": "{{colors.tertiary.light.hex}}", + "font_style": null, + "font_weight": null + }, + "type": { + "color": "{{colors.primary_fixed.light.hex}}", + "font_style": null, + "font_weight": null + }, + "variable": { + "color": "{{colors.on_surface.light.hex}}", + "font_style": null, + "font_weight": null + }, + "variable.special": { + "color": "{{colors.primary.light.hex}}", + "font_style": null, + "font_weight": null + } + } + } + } + ] +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/zen-browser/zen-userChrome.css b/arch/.config/quickshell/noctalia-shell/Assets/Templates/zen-browser/zen-userChrome.css new file mode 100644 index 0000000..f909038 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/zen-browser/zen-userChrome.css @@ -0,0 +1,177 @@ +* { + --base: {{colors.surface_container_lowest.default.hex}}; + --surface: {{colors.surface_container_low.default.hex}}; + --overlay: {{colors.surface_container.default.hex}}; + --muted: {{colors.surface_container_high.default.hex}}; + --subtle: {{colors.surface_container_highest.default.hex}}; + --text: {{colors.on_surface.default.hex}}; + --secondary: {{colors.secondary.default.hex}}; + --primary: {{colors.primary.default.hex}}; + --on_primary: {{colors.on_primary.default.hex}}; +} + +:root { + /* --- Core Palette Overrides --- */ + --zen-colors-primary: var(--base) !important; + --zen-primary-color: var(--base) !important; + --zen-colors-secondary: var(--surface) !important; + --zen-colors-tertiary: var(--overlay) !important; + --zen-colors-border: var(--primary) !important; + + /* --- Internal Zen Color Variables --- */ + --zen-primary-color: var(--primary) !important; + --zen-colors-primary: var(--base) !important; + --zen-colors-secondary: var(--surface) !important; + --zen-colors-tertiary: var(--base) !important; + + /* --- Backgrounds --- */ + --toolbar-bgcolor: var(--base) !important; + --newtab-background-color: var(--overlay) !important; + --zen-themed-toolbar-bg: var(--base) !important; + --zen-main-browser-background: var(--base) !important; + --toolbox-bgcolor-inactive: var(--base) !important; + --zen-urlbar-background: var(--surface) !important; + --zen-colors-input-bg: var(--surface) !important; + --zen-themed-toolbar-bg-transparent: transparent !important; + + /* --- Text & Icons --- */ + --lwt-text-color: var(--text) !important; + --toolbarbutton-icon-fill: var(--primary) !important; + --toolbar-field-color: var(--text) !important; + --toolbar-field-focus-color: var(--text) !important; + --toolbar-color: var(--text) !important; + --toolbox-textcolor: var(--text) !important; + --toolbox-textcolor-inactive: var(--text) !important; + --tab-selected-textcolor: var(--primary) !important; + --newtab-text-primary-color: var(--text) !important; + + /* --- Popups, Menus & Sidebars --- */ + --arrowpanel-color: var(--text) !important; + --arrowpanel-background: var(--overlay) !important; + --sidebar-text-color: var(--text) !important; + --lwt-sidebar-text-color: var(--text) !important; + --lwt-sidebar-background-color: var(--base) !important; +} + +/* --- Sidebar & Layout --- */ + +#navigator-toolbox, +#zen-tabbox-wrapper, +.sidebar-panel { + background-color: var(--base) !important; +} + +#zen-sidebar-splitter { + background-color: var(--base) !important; + opacity: 1 !important; +} + +.sidebar-placesTree { + background-color: var(--surface) !important; +} + +#zen-workspaces-button { + background-color: var(--surface) !important; +} + +#zen-appcontent-navbar-container { + background-color: var(--base) !important; +} + +/* --- Navigation Controls --- */ + +#back-button .toolbarbutton-icon, +#forward-button .toolbarbutton-icon, +#reload-button .toolbarbutton-icon, +#stop-button .toolbarbutton-icon, +#PanelUI-menu-button .toolbarbutton-icon, +#unified-extensions-button .toolbarbutton-icon, +#nav-bar-overflow-button .toolbarbutton-icon, +#zen-toggle-compact-mode .toolbarbutton-icon { + fill: var(--primary) !important; + color: var(--primary) !important; +} + +#back-button[disabled] .toolbarbutton-icon, +#forward-button[disabled] .toolbarbutton-icon { + opacity: 0.3 !important; +} + +toolbar .toolbarbutton-1 { + &:not([disabled]) { + &:is([open], [checked]) > :is( + .toolbarbutton-icon, + .toolbarbutton-text, + .toolbarbutton-badge-stack + ) { + fill: var(--base); + } + } +} + +/* --- URL Bar --- */ + +#urlbar-input { + color: var(--text) !important; +} + +#urlbar-input::selection { + background-color: var(--primary) !important; + color: var(--on_primary) !important; +} + +.urlbar-background { + background-color: var(--base) !important; +} + +#urlbar-input-container { + background-color: var(--base) !important; +} + +.urlbarView-url { + color: var(--primary) !important; +} + +.urlbarView-favicon { + background-color: var(--base) !important; +} + +#urlbar-results { + background-color: var(--overlay) !important; + color: var(--text) !important; +} + +/* --- UI Components --- */ + +#zen-toast-container { + color: var(--primary) !important; +} + +#PanelUI-menu-button[open] .toolbarbutton-icon { + fill: var(--primary) !important; +} + +#permissions-granted-icon { + color: var(--base) !important; +} + +.tab-background[selected=""] { + background: var(--overlay) !important; +} + +.content-shortcuts { + background-color: var(--surface) !important; + border-color: var(--secondary) !important; +} + +#zenEditBookmarkPanelFaviconContainer { + background: var(--base) !important; +} + +#TabsToolbar { + background-color: var(--base) !important; +} + +hbox#titlebar { + background-color: var(--base) !important; +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Templates/zen-browser/zen-userContent.css b/arch/.config/quickshell/noctalia-shell/Assets/Templates/zen-browser/zen-userContent.css new file mode 100644 index 0000000..dc75d17 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Templates/zen-browser/zen-userContent.css @@ -0,0 +1,83 @@ +/* Common variables affecting all pages */ + @-moz-document url-prefix("about:") { + :root { + --in-content-page-color: {{colors.secondary.default.hex}} !important; + --color-accent-primary: {{colors.primary.default.hex}} !important; + /* --color-accent-primary-hover: rgb(249, 217, 240) !important; + --color-accent-primary-active: rgb(245, 195, 219) !important; */ + background-color: {{colors.surface_container.default.hex}} !important; + --in-content-page-background: {{colors.surface_container.default.hex}} !important; + } + + } + + /* Variables and styles specific to about:newtab and about:home */ + @-moz-document url("about:newtab"), url("about:home") { + + :root { + --newtab-background-color: {{colors.surface_container.default.hex}} !important; + --newtab-background-color-secondary: {{colors.surface_container_high.default.hex}} !important; + --newtab-element-hover-color: {{colors.surface_container_high.default.hex}} !important; + --newtab-text-primary-color: {{colors.secondary.default.hex}} !important; + --newtab-wordmark-color: {{colors.secondary.default.hex}} !important; + --newtab-primary-action-background: {{colors.primary.default.hex}} !important; + } + + .icon { + color: {{colors.primary.default.hex}} !important; + } + + .card-outer:is(:hover, :focus, .active):not(.placeholder) .card-title { + color: {{colors.primary.default.hex}} !important; + } + } + + /* Variables and styles specific to about:preferences */ + @-moz-document url-prefix("about:preferences") { + :root { + --zen-colors-tertiary: {{colors.surface_container_low.default.hex}} !important; + --in-content-text-color: {{colors.secondary.default.hex}} !important; + --link-color: {{colors.primary.default.hex}} !important; + /* --link-color-hover: rgb(249, 217, 240) !important; */ + --zen-colors-primary: {{colors.surface_container_high.default.hex}} !important; + --in-content-box-background: {{colors.surface_container_high.default.hex}} !important; + --zen-primary-color: {{colors.primary.default.hex}} !important; + } + + button, + groupbox menulist { + background: {{colors.surface_container_high.default.hex}} !important; + color: {{colors.secondary.default.hex}} !important; + } + + .main-content { + background-color: {{colors.surface_container_lowest.default.hex}} !important; + } + } + + /* Variables and styles specific to about:addons */ + @-moz-document url-prefix("about:addons") { + :root { + --zen-dark-color-mix-base: {{colors.surface_container_low.default.hex}} !important; + --background-color-box: {{colors.surface_container.default.hex}} !important; + } + } + + /* Variables and styles specific to about:protections */ + @-moz-document url-prefix("about:protections") { + :root { + --zen-primary-color: {{colors.surface_container.default.hex}} !important; + --in-content-primary-button-text-color-hover: {{colors.secondary.default.hex}} !important; + --in-content-primary-button-background: {{colors.surface_container.default.hex}} !important; + --in-content-primary-button-text-color: {{colors.secondary.default.hex}} !important; + } + + .card { + background-color: {{colors.surface_container_high.default.hex}} !important; + } + } + /* Pane UI button visibility issue fix */ + #PanelUI-menu-button[open] .toolbarbutton-icon { + fill: {{colors.primary.default.hex}} !important; + } +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/cs.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/cs.json new file mode 100644 index 0000000..fec5e20 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/cs.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "Vymazat historii", + "close-app": "Zavล™รญt {app}", + "connect-vpn": "Pล™ipojit se k {name}", + "cycle-visualizer": "Pล™epnout vizualizรฉr", + "disable-bluetooth": "Vypnout Bluetooth", + "disable-dnd": "Vypnout Neruลกit", + "disable-wifi": "Vypnout Wi-Fi", + "disconnect-vpn": "Odpojit {name}", + "dock-settings": "Nastavenรญ docku", + "enable-bluetooth": "Zapnout Bluetooth", + "enable-dnd": "Zapnout Neruลกit", + "enable-wifi": "Zapnout Wi-Fi", + "launcher-settings": "Nastavenรญ spouลกtฤ›ฤe", + "lower-to-bottom": "Pล™esunout na spodek", + "open-calendar": "Otevล™รญt kalendรกล™", + "open-display-settings": "Nastavenรญ displeje", + "open-launcher": "Otevล™รญt spouลกtฤ›ฤ", + "open-settings": "Otevล™รญt nastavenรญ", + "raise-to-top": "Pล™esunout na vrchol", + "random-wallpaper": "Nรกhodnรก tapeta", + "run-custom-command": "Spustit vlastnรญ pล™รญkaz", + "show-active-only": "Zobrazit jen aktivnรญ", + "show-all": "Zobrazit vลกe", + "toggle-mute": "Pล™epnout ztlumenรญ", + "widget-settings": "Nastavenรญ widgetu" + }, + "authentication": { + "error": "Chyba ovฤ›ล™enรญ", + "failed": "Ovฤ›ล™enรญ selhalo" + }, + "bar": { + "active-window": { + "colorize-icons-description": "Pouลพรญt barvy motivu na ikonu aktivnรญho okna.", + "hide-mode-description": "Urฤuje chovรกnรญ widgetu, kdyลพ nenรญ ลพรกdnรฉ okno aktivnรญ.", + "scrolling-mode-description": "ล˜รญdรญ, kdy je povoleno posouvรกnรญ textu pro dlouhรฉ nรกzvy oken.", + "show-app-icon-description": "Zobrazit ikonu aplikace vedle nรกzvu okna.", + "show-app-icon-label": "Zobrazit ikonu aplikace", + "show-app-text-description": "Zobrazit nรกzev aktivnรญho okna.", + "show-app-text-label": "Zobrazit nรกzev okna" + }, + "audio-visualizer": { + "color-name-description": "Vybrat barvu vizualizรฉru.", + "color-name-label": "Barva vรฝplnฤ›", + "height-description": "Vlastnรญ vรฝลกka komponenty.", + "hide-when-idle-description": "Pokud je povoleno, vizualizรฉr je skrytรฝ, dokud nepล™ehrรกvรก ลพรกdnรฝ pล™ehrรกvaฤ.", + "hide-when-idle-label": "Skrรฝt pล™i neฤinnosti pล™ehrรกvaฤe", + "width-description": "Vlastnรญ ลกรญล™ka komponenty." + }, + "battery": { + "device-default": "Vรฝchozรญ (zobrazovacรญ zaล™รญzenรญ)", + "device-description": "Vybrat, kterรฉ bateriovรฉ zaล™รญzenรญ zobrazit.", + "device-label": "Bateriovรฉ zaล™รญzenรญ", + "display-mode-description": "Vybrat zpลฏsob zobrazenรญ baterie v panelu.", + "display-mode-graphic": "Grafickรก baterie", + "display-mode-graphic-clean": "Grafickรก baterie (bez %)", + "display-mode-icon-always": "Ikona โ€“ vลพdy zobrazit %", + "display-mode-icon-hover": "Ikona โ€“ zobrazit pล™i najetรญ myลกรญ", + "display-mode-icon-only": "Jen ikona", + "hide-if-idle-description": "Skrรฝt widget, pokud se baterie nenabรญjรญ ani nevybรญjรญ.", + "hide-if-idle-label": "Skrรฝt pล™i neฤinnosti", + "hide-if-not-detected-description": "Skrรฝt widget, pokud nenรญ detekovรกna ลพรกdnรก baterie.", + "hide-if-not-detected-label": "Skrรฝt pokud nenรญ detekovรกna", + "low-battery-threshold-label": "Prรกh upozornฤ›nรญ na nรญzkรฝ stav baterie", + "show-noctalia-performance-description": "Zobrazit pล™epรญnaฤ vรฝkonnostnรญho reลพimu Noctalia uvnitล™ panelu baterie.
Vypne stรญny a animace v Noctalia pro snรญลพenรญ spotล™eby zdrojลฏ.", + "show-noctalia-performance-label": "Zobrazit pล™epรญnaฤ vรฝkonu Noctalia", + "show-power-profile-description": "Zobrazit vรฝbฤ›r profilu napรกjenรญ uvnitล™ panelu baterie.", + "show-power-profile-label": "Zobrazit ovlรกdรกnรญ profilu napรกjenรญ" + }, + "brightness": { + "apply-all-description": "Zmฤ›nit jas pro kaลพdรฝ monitor pล™i pouลพitรญ koleฤka myลกi.", + "apply-all-label": "Pouลพรญt zmฤ›ny koleฤkem na vลกechny monitory" + }, + "clock": { + "clock-display-description": "Pล™izpลฏsobit zobrazenรญ hodin pล™idรกnรญm tokenลฏ ze seznamu nรญลพe. Pro 12hodinovรฝ formรกt musรญte zahrnout token 'AP'.", + "clock-display-label": "Zobrazenรญ hodin", + "custom-font-description": "Vybrat vlastnรญ pรญsmo pro zobrazenรญ hodin.", + "custom-font-label": "Vlastnรญ pรญsmo", + "custom-font-placeholder": "Vybrat vlastnรญ pรญsmo...", + "custom-font-search-placeholder": "Hledat pรญsma...", + "horizontal-bar-description": "Tip: Pouลพijte \\n pro odล™รกdkovรกnรญ.", + "horizontal-bar-label": "Vodorovnรฝ panel", + "preview": "Nรกhled", + "tooltip-format-description": "Formรกtovacรญ ล™etฤ›zec pro tooltip zobrazenรฝ pล™i najetรญ na hodiny. Ponechte prรกzdnรฉ pro vรฝchozรญ tooltip.", + "tooltip-format-label": "Formรกt tooltipu", + "use-custom-font-description": "Nahradit vรฝchozรญ vรฝbฤ›r pรญsma vlastnรญm pรญsmem pro hodiny.", + "use-custom-font-label": "Pouลพรญt vlastnรญ pรญsmo", + "use-monospaced-font-description": "Pokud je povoleno, hodiny pouลพijรญ neproporcionรกlnรญ pรญsmo.", + "use-monospaced-font-label": "Pouลพรญt neproporcionรกlnรญ pรญsmo", + "vertical-bar-description": "Oddฤ›lte jednotlivรฉ ฤรกsti mezerou pro zobrazenรญ na novรฉm ล™รกdku.", + "vertical-bar-label": "Svislรฝ panel" + }, + "control-center": { + "browse-file": "Prochรกzet soubor", + "browse-library": "Prochรกzet knihovnu", + "color-selection-description": "Pouลพรญt barvy motivu na ikony.", + "enable-colorization-description": "Povolit barevnรฉ zvรฝraznฤ›nรญ ikony s pouลพitรญm barev motivu.", + "icon-description": "Vybrat ikonu z knihovny nebo vlastnรญho souboru.", + "select-custom-icon": "Vybrat vlastnรญ ikonu", + "use-distro-logo-description": "Pouลพรญt logo vaลกรญ distribuce mรญsto vlastnรญ ikony.", + "use-distro-logo-label": "Pouลพรญt logo distribuce mรญsto ikony" + }, + "custom-button": { + "collapse-condition-description": "Pokud vรฝstup pล™รญkazu odpovรญdรก tรฉto hodnotฤ›, tlaฤรญtko se sbalรญ.", + "collapse-condition-label": "Podmรญnka sbalenรญ", + "color-selection-description": "Pouลพรญt barvy motivu na ikonu a text.", + "default-tooltip": "Vlastnรญ tlaฤรญtko, nastavte v nastavenรญ", + "display-command-output-description": "Zadejte pล™รญkaz, kterรฝ se spustรญ v pravidelnรฝch intervalech. Prvnรญ ล™รกdek jeho vรฝstupu bude zobrazen jako text.", + "display-command-output-label": "Zobrazit vรฝstup pล™รญkazu", + "display-command-output-stream-description": "Zadejte pล™รญkaz, kterรฝ pobฤ›ลพรญ nepล™etrลพitฤ›.", + "dynamic-text": "Dynamickรฝ text", + "enable-colorization-description": "Povolit barevnรฉ zvรฝraznฤ›nรญ ikony a textu vlastnรญho tlaฤรญtka s pouลพitรญm barev motivu.", + "enable-colorization-label": "Povolit barevnรฉ zvรฝraznฤ›nรญ", + "general-tooltip-text-description": "Vlastnรญ text zobrazenรฝ v tooltipu tlaฤรญtka.", + "general-tooltip-text-label": "Vlastnรญ text tooltipu", + "hide-mode-always-expanded": "Vลพdy rozbaleno", + "hide-mode-description": "ล˜รญdรญ viditelnost widgetu, kdyลพ pล™รญkaz nemรก vรฝstup.", + "hide-mode-expand-with-output": "Rozbalit pล™i vรฝstupu", + "hide-mode-label": "Reลพim skrytรญ", + "hide-mode-max-transparent": "Maximรกlnฤ› rozbaleno, ale prลฏhlednรฉ", + "icon-description": "Vybrat ikonu z knihovny.", + "ipc-identifier-description": "Jedineฤnรฝ identifikรกtor pro pล™รญkazy IPC. Pouลพijte tento identifikรกtor s 'qs -c noctalia-shell ipc call cb [action] [identifier]' pro ovlรกdรกnรญ tlaฤรญtka pล™es IPC.", + "ipc-identifier-label": "Identifikรกtor IPC", + "left-click-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™i kliknutรญ levรฝm tlaฤรญtkem myลกi.", + "left-click-label": "Levรฉ kliknutรญ", + "left-click-update-text": "Aktualizovat zobrazenรฝ text pล™i levรฉm kliknutรญ", + "max-text-length-horizontal-description": "Maximรกlnรญ poฤet znakลฏ zobrazenรฝch ve vodorovnรฉm panelu (0 = skrรฝt text).", + "max-text-length-horizontal-label": "Max. dรฉlka textu (vodorovnฤ›)", + "max-text-length-vertical-description": "Maximรกlnรญ poฤet znakลฏ zobrazenรฝch ve svislรฉm panelu (0 = skrรฝt text).", + "max-text-length-vertical-label": "Max. dรฉlka textu (svisle)", + "middle-click-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™i kliknutรญ prostล™ednรญm tlaฤรญtkem myลกi.", + "middle-click-label": "Prostล™ednรญ kliknutรญ", + "middle-click-update-text": "Aktualizovat zobrazenรฝ text pล™i prostล™ednรญm kliknutรญ", + "parse-json-description": "Zpracovat vรฝstup pล™รญkazu jako JSON objekt pro dynamickรฉ nastavenรญ textu a ikony.", + "parse-json-label": "Zpracovat vรฝstup jako JSON", + "refresh-interval-description": "Interval v milisekundรกch.", + "refresh-interval-label": "Interval obnovenรญ", + "right-click-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™i kliknutรญ pravรฝm tlaฤรญtkem myลกi.", + "right-click-label": "Pravรฉ kliknutรญ", + "right-click-update-text": "Aktualizovat zobrazenรฝ text pล™i pravรฉm kliknutรญ", + "show-exec-tooltip-description": "Zobrazit tooltipy s podrobnostmi o pล™รญkazech (levรฉ/pravรฉ/prostล™ednรญ kliknutรญ, koleฤko).", + "show-exec-tooltip-label": "Zobrazit tooltipy pล™รญkazลฏ", + "show-icon-description": "Pล™epรญnรก viditelnost ikony widgetu.", + "show-icon-label": "Zobrazit ikonu", + "show-text-tooltip-description": "Zobrazit tooltipy s vรฝstupem textovรฉho pล™รญkazu.", + "show-text-tooltip-label": "Zobrazit tooltipy dynamickรฉho textu", + "text-stream-description": "Streamovanรฉ ล™รกdky z pล™รญkazu budou zobrazeny jako text na tlaฤรญtku.", + "text-stream-label": "Proud dat", + "wheel-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™i pouลพitรญ koleฤka myลกi.
Pouลพijte $delta pro delta hodnotu koleฤka v pล™รญkazu.", + "wheel-down": "Posouvรกnรญ dolลฏ", + "wheel-down-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™i posouvรกnรญ koleฤkem dolลฏ.", + "wheel-down-label": "Pล™รญkaz koleฤka dolลฏ", + "wheel-label": "Koleฤko myลกi", + "wheel-mode-separate-description": "Povolit samostatnรฉ pล™รญkazy pro koleฤko nahoru a dolลฏ.", + "wheel-mode-separate-label": "Oddฤ›lenรฉ pล™รญkazy koleฤka", + "wheel-up": "Posouvรกnรญ nahoru", + "wheel-up-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™i posouvรกnรญ koleฤkem nahoru.", + "wheel-up-label": "Pล™รญkaz koleฤka nahoru", + "wheel-update-text": "Aktualizovat zobrazenรฝ text pล™i posouvรกnรญ" + }, + "keyboard-layout": { + "show-icon-description": "Zobrazit ikonu rozloลพenรญ klรกvesnice." + }, + "lock-keys": { + "hide-when-off-description": "Skrรฝt indikรกtor, pokud klรกvesa nenรญ aktivnรญ.", + "hide-when-off-label": "Skrรฝt pล™i vypnutรญ", + "show-caps-lock-description": "Zobrazit stav Caps Lock.", + "show-caps-lock-label": "Caps Lock", + "show-num-lock-description": "Zobrazit stav Num Lock.", + "show-num-lock-label": "Num Lock", + "show-scroll-lock-description": "Zobrazit stav Scroll Lock.", + "show-scroll-lock-label": "Scroll Lock" + }, + "media-mini": { + "compact-mode-description": "Povolit รบspornรฉ rozloลพenรญ panelu pล™ehrรกvaฤe mรฉdiรญ.", + "compact-mode-label": "Kompaktnรญ reลพim", + "hide-mode-description": "ล˜รญdรญ chovรกnรญ widgetu, kdyลพ nehrajรญ ลพรกdnรก mรฉdia.", + "max-width-description": "Nastavรญ maximรกlnรญ vodorovnou velikost widgetu. Widget se zmenลกรญ na kratลกรญ obsah.", + "no-active-player": "ลฝรกdnรฝ aktivnรญ pล™ehrรกvaฤ", + "panel-section-description": "Nastavit vzhled a chovรกnรญ panelu pล™ehrรกvaฤe mรฉdiรญ.", + "panel-section-label": "Panel pล™ehrรกvaฤe mรฉdiรญ", + "scrolling-mode-description": "ล˜รญdรญ, kdy je povoleno posouvรกnรญ textu pro dlouhรฉ nรกzvy stop.", + "scrolling-mode-label": "Reลพim posouvรกnรญ", + "show-album-art-description": "Zobrazit obal alba pro aktuรกlnฤ› pล™ehrรกvanou stopu.", + "show-album-art-label": "Zobrazit obal alba", + "show-artist-first-description": "Zobrazit interpreta โ€“ nรกzev mรญsto nรกzev โ€“ interpret.", + "show-artist-first-label": "Zobrazit interpreta jako prvnรญ", + "show-progress-ring-description": "Zobrazit kruhovรฝ indikรกtor prลฏbฤ›hu pล™ehrรกvรกnรญ stopy.", + "show-progress-ring-label": "Zobrazit kruhovรฝ prลฏbฤ›h", + "show-visualizer-description": "Zobrazit audio vizualizรฉr pล™i pล™ehrรกvรกnรญ hudby.", + "show-visualizer-label": "Zobrazit vizualizรฉr", + "use-fixed-width-description": "Pokud je povoleno, widget vลพdy pouลพije maximรกlnรญ ลกรญล™ku mรญsto dynamickรฉho pล™izpลฏsobenรญ obsahu.", + "use-fixed-width-label": "Pouลพรญt pevnou ลกรญล™ku", + "visualizer-type-description": "Vybrat styl audio vizualizรฉru.", + "visualizer-type-label": "Typ vizualizรฉru" + }, + "notification-history": { + "hide-widget-when-zero-description": "Skrรฝt ikonu oznรกmenรญ, pokud nejsou ลพรกdnรก oznรกmenรญ.", + "hide-widget-when-zero-label": "Skrรฝt ikonu bez oznรกmenรญ", + "hide-widget-when-zero-unread-description": "Skrรฝt ikonu oznรกmenรญ, pokud nejsou ลพรกdnรก nepล™eฤtenรก oznรกmenรญ.", + "hide-widget-when-zero-unread-label": "Skrรฝt ikonu bez nepล™eฤtenรฝch oznรกmenรญ", + "show-unread-badge-description": "Zobrazit odznak s poฤtem nepล™eฤtenรฝch oznรกmenรญ.", + "show-unread-badge-label": "Zobrazit odznak nepล™eฤtenรฝch", + "unread-badge-color-description": "Vybrat barvu odznaku nepล™eฤtenรฝch oznรกmenรญ.", + "unread-badge-color-label": "Barva odznaku nepล™eฤtenรฝch" + }, + "section-editor": { + "placeholder": "Vyberte widget k pล™idรกnรญ...", + "search-placeholder": "Hledat widget..." + }, + "spacer": { + "width-description": "ล รญล™ka mezery v pixelech." + }, + "system-monitor": { + "compact-mode-description": "Zobrazit statistiky jako mini sloupcovรฉ grafy mรญsto textovรฝch hodnot. Zabraลˆuje posunu rozloลพenรญ.", + "compact-mode-label": "Kompaktnรญ reลพim", + "cpu-cores-description": "Zobrazit vyuลพitรญ jednotlivรฝch jader CPU.", + "cpu-cores-label": "Jรกdra CPU", + "cpu-frequency-description": "Zobrazit aktuรกlnรญ takt procesoru v GHz.", + "cpu-frequency-label": "Zobrazit frekvenci CPU", + "cpu-temperature-description": "Zobrazit teplotu CPU, pokud je dostupnรก.", + "cpu-temperature-label": "Teplota CPU", + "cpu-usage-description": "Zobrazit aktuรกlnรญ procentuรกlnรญ vyuลพitรญ CPU.", + "cpu-usage-label": "Vyuลพitรญ CPU", + "disk-path-description": "Vybrat pล™รญpojnรฝ bod disku ke sledovรกnรญ.", + "disk-path-label": "Cesta k disku", + "gpu-temperature-description": "Zobrazit teplotu GPU, pokud je dostupnรก.", + "load-average-description": "Zobrazit prลฏmฤ›rnรฉ zatรญลพenรญ systรฉmu.", + "load-average-label": "Prลฏmฤ›rnรฉ zatรญลพenรญ", + "memory-percentage-description": "Zobrazit vyuลพitรญ pamฤ›ti v procentech mรญsto absolutnรญch hodnot.", + "memory-percentage-label": "Pamฤ›ลฅ v procentech", + "memory-usage-description": "Zobrazit aktuรกlnรญ informace o vyuลพitรญ RAM.", + "memory-usage-label": "Vyuลพitรญ pamฤ›ti", + "network-traffic-description": "Zobrazit rychlosti odesรญlรกnรญ a stahovรกnรญ sรญtฤ›.", + "network-traffic-label": "Sรญลฅovรฝ provoz", + "storage-as-percentage-description": "Zobrazit mรญsto na disku v procentech mรญsto absolutnรญch hodnot.", + "storage-as-percentage-label": "Disk v procentech", + "storage-available-description": "Zobrazuje, kolik mรญsta na disku je dostupnรฉ, mรญsto kolik je vyuลพito.", + "storage-available-label": "Dostupnรฉ mรญsto na disku", + "storage-usage-description": "Zobrazit informace o vyuลพitรญ mรญsta na disku.", + "storage-usage-label": "Vyuลพitรญ รบloลพiลกtฤ›", + "swap-usage-description": "Zobrazit vyuลพitรญ odklรกdacรญho prostoru.", + "swap-usage-label": "Vyuลพitรญ swapu", + "use-monospace-font-description": "Pouลพรญt neproporcionรกlnรญ pรญsmo pro konzistentnรญ ลกรญล™ku znakลฏ.", + "use-monospace-font-label": "Neproporcionรกlnรญ pรญsmo", + "use-padding-description": "Doplnรญ textovรฉ hodnoty mezerami, aby se zabrรกnilo posunu rozloลพenรญ.", + "use-padding-description-disabled-monospace-font": "Pro tuto funkci je potล™eba neproporcionรกlnรญ pรญsmo.", + "use-padding-description-disabled-vertical": "Svislรฝ panel รบloh nepodporuje doplลˆovรกnรญ textu.", + "use-padding-label": "Doplnit text" + }, + "taskbar": { + "colorize-icons-description": "Pouลพรญt barvy motivu na ikony panelu รบloh.", + "hide-mode-description": "ล˜รญdรญ chovรกnรญ widgetu, kdyลพ nejsou ลพรกdnรก odpovรญdajรญcรญ okna.", + "hide-mode-label": "Reลพim skrytรญ", + "icon-scale-description": "Nastavรญ faktor ลกkรกlovรกnรญ ikon panelu รบloh.", + "icon-scale-label": "ล kรกlovรกnรญ ikon", + "max-width-description": "Maximรกlnรญ ลกรญล™ka panelu รบloh jako procento ลกรญล™ky obrazovky.", + "max-width-label": "Maximรกlnรญ ลกรญล™ka", + "only-active-workspaces-description": "Zobrazit pouze aplikace z aktivnรญch pracovnรญch ploch.", + "only-active-workspaces-label": "Jen z aktivnรญch pracovnรญch ploch", + "only-same-monitor-description": "Zobrazit pouze aplikace z monitoru, kde se panel nachรกzรญ.", + "only-same-monitor-label": "Jen ze stejnรฉho monitoru", + "show-pinned-apps-description": "Zobrazit pล™ipnutรฉ aplikace z docku v panelu รบloh.", + "show-pinned-apps-label": "Zobrazit pล™ipnutรฉ aplikace", + "show-title-description": "Zobrazit nรกzvy oken v panelu รบloh.", + "show-title-description-disabled": "Svislรฝ panel รบloh nepodporuje zobrazenรญ nรกzvลฏ.", + "show-title-label": "Zobrazit nรกzev", + "smart-width-description": "Automaticky upravit ลกรญล™ku poloลพek podle jejich poฤtu.", + "smart-width-label": "Chytrรก ลกรญล™ka", + "title-width-description": "Nastavit ลกรญล™ku nรกzvลฏ oken v panelu รบloh (v pixelech).", + "title-width-label": "ล รญล™ka nรกzvu", + "title-width-reset-tooltip": "Obnovit ลกรญล™ku nรกzvu" + }, + "tray": { + "chevron-color-description": "Pouลพรญt barvy motivu na ikonu chevron zรกsuvky.", + "chevron-color-label": "Barva chevron", + "colorize-icons-description": "Pouลพรญt barvy motivu na ikony zรกsobnรญku.", + "colorize-icons-label": "Obarvit ikony", + "drawer-enabled-description": "Pokud je povoleno, nepล™ipnutรฉ poloลพky zรกsobnรญku jsou zobrazeny v zรกsuvkovรฉm panelu.
Pokud je vypnuto, vลกechny poloลพky zรกsobnรญku jsou zobrazeny pล™รญmo.", + "drawer-enabled-label": "Povolit zรกsuvku", + "hide-passive-description": "Pokud je povoleno, poloลพky zรกsobnรญku se stavem 'Pasivnรญ' budou skryty.", + "hide-passive-label": "Skrรฝt pasivnรญ poloลพky" + }, + "volume": { + "display-mode-description": "Zvolte, jak chcete tuto hodnotu zobrazit." + }, + "workspace": { + "character-count-description": "Poฤet znakลฏ zobrazenรฝch z nรกzvลฏ pracovnรญch ploch (1โ€“10).", + "character-count-label": "Poฤet znakลฏ", + "empty-color-description": "Nastavit barvu pozadรญ prรกzdnรฝch pracovnรญch ploch.", + "empty-color-label": "Barva prรกzdnรฉ pracovnรญ plochy", + "enable-scrollwheel-description": "Pล™epรญnat mezi pracovnรญmi plochami pomocรญ koleฤka myลกi.", + "enable-scrollwheel-label": "Koleฤkem pล™epรญnat pracovnรญ plochy", + "focused-color-description": "Nastavit barvu pozadรญ aktivnรญ pracovnรญ plochy.", + "focused-color-label": "Barva aktivnรญ pracovnรญ plochy", + "follow-focused-screen-description": "Zobrazit pracovnรญ plochy z aktuรกlnฤ› aktivnรญ obrazovky, nikoli z tรฉ, kde se panel nachรกzรญ.", + "follow-focused-screen-label": "Sledovat aktivnรญ obrazovku", + "font-weight-description": "Nastavit tlouลกลฅku pรญsma textu v pracovnรญ ploลกe.", + "font-weight-label": "Tlouลกลฅka pรญsma", + "grouped-border-opacity-description": "Nastavit prลฏhlednost okrajลฏ kontejneru pracovnรญ plochy.", + "grouped-border-opacity-label": "Prลฏhlednost okraje", + "hide-unoccupied-description": "Nezobrazovat pracovnรญ plochy bez oken.", + "hide-unoccupied-label": "Skrรฝt prรกzdnรฉ", + "label-mode-description": "Vybrat zpลฏsob zobrazenรญ popiskลฏ pracovnรญch ploch.", + "label-mode-label": "Reลพim popiskลฏ", + "occupied-color-description": "Nastavit barvu pozadรญ obsazenรฝch pracovnรญch ploch.", + "occupied-color-label": "Barva obsazenรฉ pracovnรญ plochy", + "pill-size-description": "Upravit velikost pilulek pracovnรญ plochy.", + "pill-size-label": "Velikost pilulky", + "reverse-scrolling-description": "Obrรกtit smฤ›r pล™epรญnรกnรญ pracovnรญch ploch pล™i posouvรกnรญ.", + "reverse-scrolling-label": "Obrรกcenรฉ posouvรกnรญ", + "show-applications-description": "Zobrazit ikony aplikacรญ uvnitล™ kaลพdรฉ pracovnรญ plochy.", + "show-applications-hover-description": "Zobrazit ikony aplikacรญ uvnitล™ pracovnรญ plochy pล™i najetรญ myลกรญ.", + "show-applications-hover-label": "Zobrazit aplikace pล™i najetรญ", + "show-applications-label": "Zobrazit aplikace", + "show-badge-description": "Zobrazit odznak ฤรญsla pracovnรญ plochy ve skupinovรฉm reลพimu.", + "show-badge-label": "Zobrazit odznak pracovnรญ plochy", + "show-labels-only-when-occupied-description": "Zobrazovat popisky pracovnรญch ploch jen pokud obsahujรญ okna.", + "show-labels-only-when-occupied-label": "Popisky jen pล™i obsazenรญ", + "unfocused-icons-opacity-description": "Nastavit prลฏhlednost ikon neaktivnรญch aplikacรญ.", + "unfocused-icons-opacity-label": "Prลฏhlednost neaktivnรญch ikon" + } + }, + "battery": { + "all-batteries": "Vลกechny baterie (kombinovanฤ›)", + "battery-health": "Zdravรญ baterie", + "battery-level": "Stav baterie", + "capacity-level": "Kapacita: {level}", + "charging-rate": "Rychlost nabรญjenรญ: {rate} W", + "discharging-rate": "Rychlost vybรญjenรญ: {rate} W", + "health": "Zdravรญ: {percent}%", + "inhibit-idle-description": "Udrลพuje systรฉm v chodu.", + "no-battery-detected": "Nebyla detekovรกna ลพรกdnรก baterie", + "plugged-in": "Pล™ipojen napรกjecรญ kabel", + "power-profile": "Profil napรกjenรญ", + "time-left": "Zbรฝvajรญcรญ ฤas: {time}", + "time-until-full": "ฤŒas do รบplnรฉho nabitรญ: {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "Dostupnรก zaล™รญzenรญ", + "blocked": "Blokovรกno", + "connected-devices": "Pล™ipojenรก zaล™รญzenรญ", + "device-address": "Adresa zaล™รญzenรญ", + "disabled": "Bluetooth je vypnuto", + "discoverable": "Viditelnรฉ", + "enable-message": "Zapnฤ›te Bluetooth pro zobrazenรญ dostupnรฝch zaล™รญzenรญ.", + "known-devices": "Znรกmรก zaล™รญzenรญ", + "no-devices": "ลฝรกdnรก dostupnรก zaล™รญzenรญ", + "paired-devices": "Spรกrovanรก zaล™รญzenรญ", + "pairing-mode": "Ujistฤ›te se, ลพe je vaลกe zaล™รญzenรญ v reลพimu pรกrovรกnรญ.", + "scanning": "Vyhledรกvรกnรญ zaล™รญzenรญ...", + "signal-text-excellent": "Signรกl: Vรฝbornรฝ", + "signal-text-fair": "Signรกl: Pล™ijatelnรฝ", + "signal-text-good": "Signรกl: Dobrรฝ", + "signal-text-poor": "Signรกl: Slabรฝ", + "signal-text-unknown": "Signรกl: Neznรกmรฝ", + "signal-text-very-poor": "Signรกl: Velmi slabรฝ" + } + }, + "changelog": { + "error": { + "fetch-failed": "Nelze naฤรญst data changelogu. Zkuste to prosรญm znovu.", + "rate-limit": "Byl pล™ekroฤen limit poลพadavkลฏ na GitHub. Zkuste to znovu za nฤ›kolik minut." + }, + "panel": { + "buttons-discord": "Pล™idat se na Discord", + "buttons-dismiss": "OK", + "buttons-feedback": "Poskytnout zpฤ›tnou vazbu", + "empty": "Poznรกmky k vydรกnรญ zatรญm nejsou dostupnรฉ.", + "highlight-title": "Hlavnรญ novinky", + "released": "Vydรกno {date}", + "subtitle-fresh": "Dฤ›kujeme za instalaci Noctalia! Zde je obsah tohoto buildu.", + "subtitle-updated": "Aktualizovรกno z {previousVersion}", + "title": "Co je novรฉho ve verzi {version}", + "version": "Verze {version}", + "version-new-user": "ฤŒerstvรก instalace" + } + }, + "common": { + "actions": "Akce", + "active": "Aktivnรญ", + "add": "Pล™idat", + "appearance": "Vzhled", + "apply": "Pouลพรญt", + "auto-connect": "Automatickรฉ pล™ipojenรญ", + "automation": "Automatizace", + "available": "Dostupnรฉ", + "back": "Zpฤ›t", + "battery": "Baterie", + "behavior": "Chovรกnรญ", + "bluetooth": "Bluetooth", + "brightness": "Jas", + "browse": "Prochรกzet", + "calendar": "Kalendรกล™", + "calendar-panel": "Panel kalendรกล™e", + "cancel": "Zruลกit", + "cards": "Karty", + "charging": "Nabรญjenรญ", + "clear": "Vymazat", + "clipboard": "Schrรกnka", + "close": "Zavล™รญt", + "color-muted": "Tlumenรก", + "colors": "Barvy", + "command": "Pล™รญkaz", + "confirm": "Potvrdit", + "connect": "Pล™ipojit", + "connected": "Pล™ipojeno", + "connecting": "Pล™ipojovรกnรญ...", + "continue": "Pokraฤovat", + "contributors": "Pล™ispฤ›vatelรฉ", + "copied-to-clipboard": "Zkopรญrovรกno do schrรกnky", + "countdown": "Odpoฤet", + "customize": "Pล™izpลฏsobit", + "date": "Datum", + "default": "Vรฝchozรญ", + "delete": "Smazat", + "devices": "Zaล™รญzenรญ", + "disabled": "Vypnuto", + "discharging": "Vybรญjenรญ", + "disconnect": "Odpojit", + "disconnected": "Odpojeno", + "disconnecting": "Odpojovรกnรญ...", + "display-mode": "Reลพim zobrazenรญ", + "documentation": "Dokumentace", + "download": "Stรกhnout", + "duration": "Trvรกnรญ", + "dysfunctional": "Dysfunkฤnรญ", + "edit": "Upravit", + "enabled": "Zapnuto", + "error": "Chyba", + "ethernet": "Ethernet", + "events": "Udรกlosti", + "execute": "Spustit", + "faithful": "Vฤ›rnรฝ", + "focus": "Zamฤ›ล™enรญ", + "font-weight-bold": "Tuฤnรฉ", + "font-weight-light": "Tenkรฉ", + "font-weight-medium": "Stล™ednรญ", + "font-weight-regular": "Normรกlnรญ", + "font-weight-semibold": "Polotuฤnรฉ", + "frequency": "Frekvence", + "gateway": "Brรกna", + "general": "Obecnรฉ", + "height": "Vรฝลกka", + "hibernate": "Hibernace", + "history": "Historie", + "icon": "Ikona", + "idle": "Neฤinnost", + "info": "Informace", + "input": "Vstup", + "install": "Nainstalovat", + "installed": "Nainstalovรกno", + "interface": "Rozhranรญ", + "internet": "Internet", + "keybind": "Klรกvesovรก zkratka", + "language": "Jazyk", + "loading": "Naฤรญtรกnรญ...", + "local": "Mรญstnรญ", + "location": "Poloha", + "lock": "Zamknout", + "logout": "Odhlรกsit", + "look": "Vzhled", + "margins": "Okraje", + "media": "Mรฉdia", + "media-player": "Pล™ehrรกvaฤ mรฉdiรญ", + "memory": "Pamฤ›ลฅ", + "monitors": "Monitory", + "move-down": "Posunout dolลฏ", + "move-up": "Posunout nahoru", + "network": "Sรญลฅ", + "next": "Dalลกรญ", + "night-light": "Noฤnรญ svฤ›tlo", + "no": "Ne", + "no-results": "ลฝรกdnรฉ vรฝsledky", + "no-summary": "Bez shrnutรญ", + "none": "Nic", + "not-found": "Nenalezeno", + "notifications": "Oznรกmenรญ", + "official": "Oficiรกlnรญ", + "on-surface": "Na povrchu", + "output": "Vรฝstup", + "pair": "Spรกrovat", + "paired": "Spรกrovรกno", + "pairing": "Pรกrovรกnรญ...", + "panels": "Panely", + "password": "Heslo", + "pause": "Pozastavit", + "performance": "Vรฝkon", + "pin": "Pล™ipnout", + "play": "Pล™ehrรกt", + "polling": "Dotazovรกnรญ", + "position": "Pozice", + "previous": "Pล™edchozรญ", + "primary": "Primรกrnรญ", + "random": "Nรกhodnรฉ", + "reboot": "Restartovat", + "reboot-to-uefi": "Restartovat do UEFI", + "record": "Zahรกjit nahrรกvรกnรญ", + "refresh": "Obnovit", + "required": "(povinnรฉ)", + "reset": "Obnovit vรฝchozรญ", + "reset-to-default": "Obnovit vรฝchozรญ", + "result-count": "{count} vรฝsledek", + "result-count-plural": "{count} vรฝsledkลฏ", + "resume": "Obnovit", + "save": "Uloลพit", + "scanning": "Vyhledรกvรกnรญ...", + "screen-corners": "Rohy obrazovky", + "search": "Hledat", + "second": "{count} sekunda", + "second-plural": "{count} sekund", + "secondary": "Sekundรกrnรญ", + "security": "Zabezpeฤenรญ", + "select": "Vybrat", + "select-color": "Vybrat barvu", + "select-color-description": "Pouลพรญt barvy motivu pro zvรฝraznฤ›nรญ.", + "select-icon-color": "Vybrat barvu ikony", + "settings": "Nastavenรญ", + "shortcuts": "Zkratky", + "shutdown": "Vypnout", + "signal": "Signรกl", + "sound": "Zvuk", + "sources": "Zdroje", + "start": "Spustit", + "stop": "Zastavit", + "supporters": "Podporovatelรฉ", + "suspend": "Uspat", + "templates": "ล ablony", + "tertiary": "Terciรกrnรญ", + "test": "Testovat", + "thresholds": "Prahovรฉ hodnoty", + "title": "Nรกzev", + "toast": "Vyskakovacรญ oznรกmenรญ", + "trusted": "Dลฏvฤ›ryhodnรฉ", + "uninstall": "Odinstalovat", + "unknown": "Neznรกmรฉ", + "unpair": "Zruลกit pรกrovรกnรญ", + "unpin": "Odepnout", + "update": "Aktualizovat", + "upload": "Nahrรกt", + "username": "Uลพivatelskรฉ jmรฉno", + "userspace-reboot": "Restart uลพivatelskรฉho prostoru", + "version": "Verze", + "vibrant": "ลฝivรฉ", + "visualizer": "Vizualizรฉr", + "volume": "Hlasitost", + "volumes": "Hlasitosti", + "wallpaper": "Tapeta", + "weather": "Poฤasรญ", + "weather-loading": "Naฤรญtรกnรญ poฤasรญ...", + "weather-no-location": "Nastavte polohu v Nastavenรญ, abyste vidฤ›li poฤasรญ", + "week": "Tรฝden", + "widgets": "Widgety", + "width": "ล รญล™ka", + "wifi": "Wi-Fi", + "windows": "Okna", + "yes": "Ano" + }, + "control-center": { + "power-profile": { + "tooltip-action": "Profil napรกjenรญ", + "tooltip-disabled": "Pro pouลพitรญ profilลฏ napรกjenรญ nainstalujte power-profiles-daemon" + }, + "wifi": { + "label-disconnected": "Wi-Fi odpojeno", + "label-ethernet": "Ethernet" + } + }, + "display-modes": { + "always-hide": "Vลพdy skrรฝt", + "always-show": "Vลพdy zobrazit", + "force-open": "Vynutit otevล™enรญ", + "on-hover": "Pล™i najetรญ myลกรญ" + }, + "hide-modes": { + "auto-hide": "Automaticky skrรฝt", + "hidden": "Skrรฝt kdyลพ prรกzdnรฝ", + "idle": "Skrรฝt pล™i neฤinnosti", + "non-exclusive": "Neexkluzivnรญ", + "transparent": "Prลฏhlednรฝ kdyลพ prรกzdnรฝ", + "visible": "Vลพdy viditelnรฝ" + }, + "launcher": { + "categories": { + "all": "Vลกe", + "audiovideo": "Audio a video", + "chat": "Chat", + "development": "Vรฝvoj", + "education": "Vzdฤ›lรกvรกnรญ", + "emoji-activity": "Aktivita", + "emoji-animals": "Zvรญล™ata", + "emoji-flags": "Vlajky", + "emoji-food": "Jรญdlo a pitรญ", + "emoji-nature": "Pล™รญroda", + "emoji-objects": "Objekty", + "emoji-people": "Lidรฉ a tฤ›lo", + "emoji-recent": "Nedรกvnรฉ", + "emoji-symbols": "Symboly", + "emoji-travel": "Cestovรกnรญ a mรญsta", + "game": "Hry", + "graphics": "Grafika", + "misc": "Rลฏznรฉ", + "office": "Kancelรกล™", + "pinned": "Pล™ipnutรฉ", + "system": "Systรฉm", + "webbrowser": "Webovรฝ prohlรญลพeฤ" + }, + "providers": { + "applications": "Aplikace", + "calculator": "Kalkulaฤka", + "calculator-deprecated": "Pล™รญkaz >calc je zastaralรฝ a brzy bude odebrรกn. Zadรกvejte matematickรฉ vรฝrazy pล™รญmo do vyhledรกvรกnรญ.", + "calculator-description": "Kalkulaฤka โ€“ vyhodnocenรญ matematickรฝch vรฝrazลฏ", + "calculator-enter-expression": "Zadejte matematickรฝ vรฝraz", + "calculator-press-enter-to-copy": "Stisknฤ›te Enter pro zkopรญrovรกnรญ vรฝsledku", + "clipboard": "Historie schrรกnky", + "clipboard-clear-description": "Vymazat celou historii schrรกnky", + "clipboard-clear-description-full": "Odebrat vลกechny poloลพky z historie schrรกnky", + "clipboard-clear-history": "Vymazat historii schrรกnky", + "clipboard-delete": "Smazat poloลพku schrรกnky", + "clipboard-history-disabled": "Historie schrรกnky je vypnuta", + "clipboard-history-disabled-description": "Povolte historii schrรกnky v nastavenรญ nebo nainstalujte cliphist", + "clipboard-loading": "Naฤรญtรกnรญ historie schrรกnky...", + "clipboard-search-description": "Prohledat historii schrรกnky", + "command-description": "Spouลกtฤ›t pล™รญkazy shellu", + "emoji": "Vรฝbฤ›r emoji", + "emoji-loading": "Naฤรญtรกnรญ emoji...", + "emoji-loading-description": "ฤŒekejte prosรญm", + "emoji-no-recent": "Zatรญm ลพรกdnรก nedรกvnรก emoji", + "emoji-search-description": "Vyhledat a zkopรญrovat emoji", + "settings-search-description": "Vyhledat a pล™ejรญt na nastavenรญ", + "windows-search-description": "Vyhledat a aktivovat otevล™enรก okna" + } + }, + "lock-screen": { + "authenticating": "Ovฤ›ล™ovรกnรญ...", + "password": "Zadejte heslo...", + "restart": "Restartovat", + "shut-down": "Vypnout" + }, + "notifications": { + "panel": { + "click-to-collapse": "Kliknutรญm sbalit", + "click-to-expand": "Kliknutรญm rozbalit", + "description": "Vaลกe oznรกmenรญ se zobrazรญ zde po pล™รญchodu.", + "no-notifications": "ลฝรกdnรก oznรกmenรญ" + }, + "range": { + "earlier": "Dล™รญve", + "today": "Dnes", + "yesterday": "Vฤera" + }, + "time": { + "diff-d": "pล™ed 1 dnem", + "diff-dd": "pล™ed {diff} dny", + "diff-h": "pล™ed 1 hodinou", + "diff-hh": "pล™ed {diff} hodinami", + "diff-m": "pล™ed 1 minutou", + "diff-mm": "pล™ed {diff} minutami", + "now": "prรกvฤ› teฤ" + } + }, + "options": { + "bar": { + "density-comfortable": "Pohodlnรฉ", + "density-compact": "Kompaktnรญ", + "density-default": "Vรฝchozรญ", + "density-mini": "Mini", + "density-spacious": "Prostornรฉ", + "display-mode-always-visible": "Vลพdy viditelnรฝ", + "type-floating": "Plovoucรญ", + "type-framed": "Orรกmovanรฝ", + "type-simple": "Jednoduchรฝ" + }, + "control-center": { + "quick-settings-style-classic": "Klasickรฝ", + "quick-settings-style-modern": "Modernรญ" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "Pohodlnรฉ", + "compact": "Kompaktnรญ", + "default": "Vรฝchozรญ" + }, + "launcher-view-mode": { + "grid": "Mล™รญลพka", + "list": "Seznam" + }, + "notification-density": { + "compact": "Kompaktnรญ", + "default": "Vรฝchozรญ" + }, + "scrolling-modes": { + "always": "Vลพdy posouvat", + "hover": "Posouvat pล™i najetรญ", + "never": "Nikdy neposouvat" + }, + "session-menu-grid-layout": { + "grid": "Mล™รญลพka", + "single-row": "Jeden ล™รกdek" + }, + "settings-panel-mode": { + "attached": "Panel pล™ipojen k liลกtu", + "centered": "Vystล™edฤ›nรฝ panel", + "window": "Samostatnรฉ okno" + }, + "visualizer-types": { + "linear": "Lineรกrnรญ", + "mirrored": "Zrcadlenรฝ", + "wave": "Vlna" + }, + "visualizer-visibility": { + "with-background": "Jen s pozadรญm" + }, + "workspace-labels": { + "index": "Index", + "index-and-name": "Index a nรกzev", + "name": "Nรกzev" + } + }, + "panels": { + "about": { + "become-supporter": "Stรกt se podporovatelem", + "changelog": "Zobrazit changelog", + "changelog-on-startup": "Zobrazit changelog pล™i aktualizaci", + "changelog-on-startup-desc": "Automaticky zobrazit changelog pล™i aktualizaci Noctalia", + "contributors-description": "Dรญky naลกemu {count} skvฤ›lรฉmu pล™ispฤ›vateli!", + "contributors-description-plural": "Dรญky naลกim {count} skvฤ›lรฝm pล™ispฤ›vatelลฏm!", + "copy-info": "Zkopรญrovat informace", + "debug-disabled": "Ladicรญ reลพim vypnut", + "debug-enabled": "Ladicรญ reลพim zapnut", + "info-copied": "Informace zkopรญrovรกny do schrรกnky", + "noctalia-available": "Dostupnรฉ:", + "noctalia-desc": "Elegantnรญ a minimalistickรฝ desktopovรฝ shell navrลพenรฝ pro Wayland, postavenรฝ na Quickshell.", + "noctalia-git-commit": "Git commit:", + "noctalia-installed-version": "Nainstalovanรก verze:", + "noctalia-latest-version": "Nejnovฤ›jลกรญ verze:", + "noctalia-qs-version": "Verze Noctalia QS:", + "noctalia-title": "Noctalia Shell", + "privacy-policy": "Zรกsady ochrany soukromรญ", + "support": "Podpoล™te nรกs", + "supporter-badge": "Podporovatel", + "supporters-desc": "Velkรฉ dรญky naลกemu รบลพasnรฉmu podporovateli!", + "supporters-desc-plural": "Velkรฉ dรญky naลกim {count} รบลพasnรฝm podporovatelลฏm!", + "supporters-loading": "Naฤรญtรกnรญ podporovatelลฏ...", + "system-board": "Zรกkladnรญ deska:", + "system-cpu": "CPU:", + "system-disk": "Disk:", + "system-gpu": "GPU:", + "system-host": "Hostitel:", + "system-install-hint": "Nainstalujte fastfetch pro zobrazenรญ systรฉmovรฝch informacรญ", + "system-kernel": "Jรกdro:", + "system-loading": "Naฤรญtรกnรญ systรฉmovรฝch informacรญ...", + "system-memory": "Pamฤ›ลฅ:", + "system-monitor": "Monitor:", + "system-not-installed": "fastfetch nenรญ nainstalovรกn", + "system-os": "OS:", + "system-packages": "Balรญฤky:", + "system-product": "Produkt:", + "system-title": "Systรฉmovรฉ informace", + "system-uptime": "Provoznรญ doba:", + "system-wm": "Sprรกvce oken:", + "telemetry-data-copied": "Data telemetrie zkopรญrovรกna do schrรกnky", + "telemetry-desc": "Pomozte zlepลกit Noctalia sdรญlenรญm anonymnรญch systรฉmovรฝch informacรญ (rozliลกenรญ obrazovky, compositor, distribuce). Odeslรกno jednou pล™i startu, bez sledovรกnรญ, data automaticky smazรกna po 30 dnech.", + "telemetry-enabled": "Odesรญlat anonymnรญ systรฉmovรฉ informace", + "telemetry-show-data": "Zobrazit data", + "telemetry-title": "Soukromรญ", + "title": "O aplikaci", + "up-to-date": "Mรกte nejnovฤ›jลกรญ verzi!", + "update-available": "Dostupnรก aktualizace", + "view-commit": "Zobrazit commit na GitHubu" + }, + "audio": { + "devices-desc": "Nastavit dostupnรก zvukovรก vstupnรญ a vรฝstupnรญ zaล™รญzenรญ.", + "devices-input-device-description": "Vybrat poลพadovanรฉ zvukovรฉ vstupnรญ zaล™รญzenรญ.", + "devices-input-device-label": "Vstupnรญ zaล™รญzenรญ", + "devices-output-device-description": "Vybrat poลพadovanรฉ zvukovรฉ vรฝstupnรญ zaล™รญzenรญ.", + "devices-output-device-label": "Vรฝstupnรญ zaล™รญzenรญ", + "devices-title": "Zvukovรก zaล™รญzenรญ", + "external-mixer-description": "Zadejte pล™รญkaz nebo cestu k aplikaci pro spuลกtฤ›nรญ externรญho audio mixรฉru.", + "external-mixer-label": "Pล™รญkaz externรญho audio mixรฉru", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "Nastavit preferovanรฉ a ignorovanรฉ mediรกlnรญ aplikace.", + "media-excluded-player-description": "Pล™idat klรญฤovรก slova pล™ehrรกvaฤลฏ, kterรฉ mรก systรฉm ignorovat. Kaลพdรฉ klรญฤovรฉ slovo na novรฝ ล™รกdek.", + "media-excluded-player-label": "Vylouฤenรฝ pล™ehrรกvaฤ", + "media-excluded-player-placeholder": "zadejte podล™etฤ›zec a stisknฤ›te +", + "media-frame-rate-description": "Vyลกลกรญ snรญmkovรฝ kmitoฤet je plynulejลกรญ, ale vyuลพรญvรก vรญce zdrojลฏ.", + "media-frame-rate-label": "Snรญmkovรฝ kmitoฤet", + "media-primary-player-description": "Zadejte klรญฤovรฉ slovo pro identifikaci hlavnรญho pล™ehrรกvaฤe.", + "media-primary-player-label": "Primรกrnรญ pล™ehrรกvaฤ", + "media-primary-player-placeholder": "napล™. spotify, vlc, mpv", + "media-scrolling-speed-description": "ฤŒas v sekundรกch pro posouvรกnรญ nรกzvu od zaฤรกtku do konce.", + "media-scrolling-speed-label": "Rychlost posouvรกnรญ", + "media-scrolling-title-description": "Povolit kontinuรกlnรญ posouvรกnรญ pro dlouhรฉ nรกzvy mรฉdiรญ.", + "media-scrolling-title-label": "Posouvรกnรญ nรกzvu", + "media-title": "Pล™ehrรกvaฤe mรฉdiรญ", + "on-middle-clicked-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™i kliknutรญ prostล™ednรญm tlaฤรญtkem myลกi.", + "panel-applications-empty": "ลฝรกdnรฉ aplikace momentรกlnฤ› nepล™ehrรกvajรญ zvuk", + "spectrum-mirrored-description": "Zrcadlit spektrum tak, aby nรญzkรฉ frekvence byly uprostล™ed. Pล™i vypnutรญ jsou nรญzkรฉ frekvence vlevo a vysokรฉ vpravo.", + "spectrum-mirrored-label": "Zrcadlit spektrum", + "title": "Zvuk", + "visualizer-type-description": "Vybrat typ vizualizace pro pล™ehrรกvรกnรญ mรฉdiรญ.", + "visualizer-type-label": "Typ vizualizace", + "volumes-desc": "Upravit ovlรกdรกnรญ hlasitosti a รบrovnฤ› zvuku.", + "volumes-feedback-sound-file-description": "Cesta ke zvukovรฉmu souboru pล™ehrรกvanรฉmu pล™i zmฤ›nฤ› hlasitosti.", + "volumes-feedback-sound-file-label": "Zvukovรฝ soubor zpฤ›tnรฉ vazby hlasitosti", + "volumes-feedback-sound-file-select-title": "Vybrat zvukovรฝ soubor zpฤ›tnรฉ vazby hlasitosti", + "volumes-input-volume-description": "รšroveลˆ hlasitosti mikrofonu.", + "volumes-mute-input-description": "Ztlumit vรฝchozรญ zvukovรฝ vstup (mikrofon).", + "volumes-mute-input-label": "Ztlumit zvukovรฝ vstup", + "volumes-mute-output-description": "Ztlumit hlavnรญ zvukovรฝ vรฝstup systรฉmu.", + "volumes-mute-output-label": "Ztlumit zvukovรฝ vรฝstup", + "volumes-output-volume-description": "รšroveลˆ hlasitosti celรฉho systรฉmu.", + "volumes-step-size-description": "Upravit velikost kroku pro zmฤ›ny hlasitosti (koleฤko myลกi, klรกvesovรฉ zkratky).", + "volumes-step-size-label": "Velikost kroku hlasitosti", + "volumes-volume-feedback-description": "Pล™ehrรกt zpฤ›tnovazebnรญ zvuk pล™i zmฤ›nฤ› hlasitosti.", + "volumes-volume-feedback-label": "Pล™ehrรกt zvuk zpฤ›tnรฉ vazby hlasitosti", + "volumes-volume-overdrive-description": "Umoลพnit zvรฝลกenรญ hlasitosti nad 100 %. Nemusรญ bรฝt podporovรกno vลกemi zaล™รญzenรญmi.", + "volumes-volume-overdrive-label": "Povolit pล™ekroฤenรญ hlasitosti" + }, + "bar": { + "appearance-auto-hide-delay-description": "ฤŒas pล™ed skrytรญm liลกty po opuลกtฤ›nรญ myลกรญ", + "appearance-auto-hide-delay-label": "Zpoลพdฤ›nรญ skrytรญ", + "appearance-auto-hide-exclusive-note": "Poznรกmka: Exkluzivnรญ zรณna je automaticky zakรกzรกna pล™i aktivnรญm automatickรฉm skrรฝvรกnรญ", + "appearance-auto-show-delay-description": "ฤŒas pล™ed zobrazenรญm liลกty pล™i najetรญ myลกรญ na okraj", + "appearance-auto-show-delay-label": "Zpoลพdฤ›nรญ zobrazenรญ", + "appearance-background-opacity-description": "Nastavit prลฏhlednost pozadรญ liลกty.", + "appearance-background-opacity-label": "Prลฏhlednost pozadรญ liลกty", + "appearance-capsule-color-description": "Vybrat barvu pro kapsle liลกty, nebo ponechat vรฝchozรญ barvu povrchu.", + "appearance-capsule-color-label": "Barva kapsle", + "appearance-capsule-opacity-description": "Nastavit prลฏhlednost pozadรญ widgetลฏ pล™i zobrazenรญ kapsle.", + "appearance-capsule-opacity-label": "Prลฏhlednost kapsle", + "appearance-content-padding-description": "Upravit odsazenรญ mezi okraji liลกty a widgety.", + "appearance-content-padding-label": "Odsazenรญ obsahu", + "appearance-density-description": "Upravit odsazenรญ liลกty pro kompaktnรญ nebo prostornรฝ vzhled.", + "appearance-density-label": "Hustota liลกty", + "appearance-desc": "Pล™izpลฏsobit vzhled a pozici liลกty.", + "appearance-display-mode-description": "Vybrat, kdy je liลกta viditelnรก.", + "appearance-enable-exclusion-zone-inset-description": "Zmenลกit exkluzivnรญ zรณnu o 1 fyzickรฝ pixel, aby okna zarovnanรก na okraj pล™esnฤ› podtรฉkala pod okraj liลกty.", + "appearance-enable-exclusion-zone-inset-label": "Vloลพit exkluzivnรญ zรณnu", + "appearance-floating-description": "Zobrazit liลกtu jako plovoucรญ 'pilulku'.", + "appearance-floating-label": "Plovoucรญ liลกta", + "appearance-font-scale-description": "Upravit mฤ›ล™รญtko velikosti pรญsma textu zobrazenรฉho v liลกtฤ›.", + "appearance-font-scale-label": "Mฤ›ล™รญtko pรญsma", + "appearance-frame-radius": "Vnitล™nรญ polomฤ›r", + "appearance-frame-settings-description": "Upravit tlouลกลฅku rรกmu a polomฤ›r vnitล™nรญch rohลฏ", + "appearance-frame-settings-label": "Nastavenรญ rรกmu", + "appearance-frame-thickness": "Tlouลกลฅka", + "appearance-hide-on-overview-description": "Skrรฝt liลกtu a zavล™รญt panely pล™i aktivnรญm pล™ehledu compositoru.", + "appearance-hide-on-overview-label": "Skrรฝt liลกtu v pล™ehledu", + "appearance-margins-description": "Upravit okraje kolem plovoucรญ liลกty.", + "appearance-margins-horizontal": "Vodorovnรฝ okraj", + "appearance-margins-vertical": "Svislรฝ okraj", + "appearance-outer-corners-description": "Zobrazit vnฤ›jลกรญ zaoblenรฉ rohy na liลกtฤ›.", + "appearance-outer-corners-label": "Vnฤ›jลกรญ rohy", + "appearance-position-description": "Vybrat umรญstฤ›nรญ liลกty na obrazovce.", + "appearance-position-label": "Pozice liลกty", + "appearance-show-capsule-description": "Zobrazit pozadรญ widgetลฏ.", + "appearance-show-capsule-label": "Zobrazit kapsle", + "appearance-show-on-workspace-switch-description": "Automaticky krรกtce zobrazit liลกtu pล™i pล™epnutรญ pracovnรญ plochy.", + "appearance-show-on-workspace-switch-label": "Zobrazit liลกtu pล™i pล™epnutรญ pracovnรญ plochy", + "appearance-show-outline-description": "Zobrazit viditelnรฝ okraj kolem kaลพdรฉho widgetu.", + "appearance-show-outline-label": "Zobrazit obrysy widgetลฏ", + "appearance-type-description": "Vybrat styl liลกty: Jednoduchรฝ, Plovoucรญ nebo Orรกmovanรฝ.", + "appearance-type-label": "Typ liลกty", + "appearance-use-separate-opacity-description": "Povolit pouลพitรญ samostatnรฉ hodnoty prลฏhlednosti pro pozadรญ liลกty.", + "appearance-use-separate-opacity-label": "Pouลพรญt samostatnou prลฏhlednost liลกty", + "appearance-widget-spacing-description": "Upravit mezery mezi widgety v liลกtฤ›.", + "appearance-widget-spacing-label": "Mezery widgetลฏ", + "behavior-middle-click-command-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™i stล™ednรญm kliknutรญ.", + "behavior-middle-click-command-label": "Pล™รญkaz stล™ednรญho kliknutรญ", + "behavior-middle-click-command-placeholder": "niri msg action toggle-overview", + "behavior-middle-click-description": "Vybrat akci stล™ednรญho kliknutรญ na prรกzdnรฉ oblasti liลกty.", + "behavior-middle-click-follow-mouse-description": "Otevล™รญt vybranรฝ panel stล™ednรญho kliknutรญ na pozici kurzoru.", + "behavior-middle-click-follow-mouse-label": "Stล™ednรญ kliknutรญ sleduje myลก", + "behavior-middle-click-label": "Akce stล™ednรญho kliknutรญ na liลกtu", + "behavior-right-click-command-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™i pravรฉm kliknutรญ.", + "behavior-right-click-command-label": "Pล™รญkaz pravรฉho kliknutรญ", + "behavior-right-click-command-placeholder": "notify-send \"Pravรฉ kliknutรญ\"", + "behavior-right-click-description": "Vybrat akci pravรฉho kliknutรญ na prรกzdnรฉ oblasti liลกty.", + "behavior-right-click-follow-mouse-description": "Otevล™รญt vybranรฝ panel pravรฉho kliknutรญ na pozici kurzoru.", + "behavior-right-click-follow-mouse-label": "Pravรฉ kliknutรญ sleduje myลก", + "behavior-right-click-label": "Akce pravรฉho kliknutรญ na liลกtu", + "behavior-wheel-wrap-description": "Pokud je povoleno, posouvรกnรญ pokraฤuje od poslednรญ poloลพky na prvnรญ.", + "behavior-wheel-wrap-label": "Pล™etoฤenรญ", + "behavior-workspace-scroll-description": "Vybrat akci koleฤka myลกi na prรกzdnรฉ oblasti liลกty.", + "behavior-workspace-scroll-label": "Akce koleฤka myลกi na liลกtu", + "behavior-workspace-scroll-option-content": "Obsah", + "behavior-workspace-scroll-option-workspace": "Pracovnรญ plocha", + "monitor-configure-widgets": "Konfigurovat widgety", + "monitor-override-settings": "Pล™epsat globรกlnรญ nastavenรญ", + "monitor-override-settings-description": "Pouลพรญt vlastnรญ nastavenรญ pro tento monitor.", + "monitor-reset-all": "Obnovit vลกe", + "monitor-widgets-title": "Konfigurace widgetลฏ โ€“ {monitor}", + "monitors-desc": "Zobrazit liลกtu na konkrรฉtnรญch monitorech. Vรฝchozรญ je zobrazenรญ na vลกech.", + "monitors-desc-new": "Nastavit, na kterรฝch monitorech se liลกta zobrazuje, a pล™izpลฏsobit nastavenรญ pro kaลพdรฝ monitor.", + "monitors-title": "Zobrazenรญ na monitorech", + "title": "Liลกta", + "tray-blacklist-description": "Pล™idat pravidla vรฝjimek zรกsobnรญku, podporuje zรกstupnรฉ znaky (*).", + "tray-blacklist-label": "ฤŒernรก listina", + "tray-blacklist-placeholder": "napล™. nm-applet, Fcitx*", + "tray-pin-application": "Pล™ipnout aplikaci", + "tray-unpin-application": "Odepnout aplikaci", + "use-global-widgets": "Pouลพรญt globรกlnรญ widgety", + "widgets-desc": "Pล™etรกhnฤ›te widgety pro zmฤ›nu jejich poล™adรญ. Pomocรญ kontextovรฉho menu je pล™esuลˆte mezi sekcemi nebo odeberte.", + "widgets-title": "Umรญstฤ›nรญ widgetลฏ" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "Generovat barevnรก schรฉmata z vaลกรญ tapety. Automaticky extrahuje barvy pro vytvoล™enรญ soudrลพnรฉho motivu.", + "color-source-use-wallpaper-colors-label": "Pouลพรญt barvy tapety", + "dark-mode-mode-description": "Povolรญ automatickรฉ pล™epรญnรกnรญ mezi svฤ›tlรฝm a tmavรฝm reลพimem.", + "dark-mode-mode-label": "Rozvrh tmavรฉho reลพimu", + "dark-mode-mode-manual": "Ruฤnฤ›", + "dark-mode-mode-off": "Vypnuto", + "dark-mode-switch-description": "Pล™epne na tmavลกรญ motiv pro pohodlnฤ›jลกรญ noฤnรญ sledovรกnรญ.", + "delete-error-description": "Nepodaล™ilo se smazat {scheme}", + "delete-error-title": "Mazรกnรญ selhalo", + "delete-success-description": "รšspฤ›ลกnฤ› smazรกno {scheme}", + "delete-success-title": "Barevnรฉ schรฉma smazรกno", + "download-button": "Stรกhnout dalลกรญ", + "download-downloading": "Stahovรกnรญ...", + "download-empty": "ลฝรกdnรก barevnรก schรฉmata nejsou dostupnรก", + "download-error-api-error": "Chyba API: {status}", + "download-error-description": "Nepodaล™ilo se stรกhnout {scheme}", + "download-error-download-failed": "Stahovรกnรญ selhalo s kรณdem: {code}", + "download-error-invalid-response": "Neplatnรฝ formรกt odpovฤ›di API", + "download-error-no-files": "Pro schรฉma nebyly nalezeny ลพรกdnรฉ soubory", + "download-error-parse-failed": "Nepodaล™ilo se zpracovat odpovฤ›ฤ API: {error}", + "download-error-rate-limit": "Pล™ekroฤen limit poลพadavkลฏ GitHub API", + "download-error-title": "Stahovรกnรญ selhalo", + "download-fetching": "Naฤรญtรกnรญ dostupnรฝch barevnรฝch schรฉmat...", + "download-success-description": "รšspฤ›ลกnฤ› staลพeno {scheme}", + "download-success-title": "Barevnรฉ schรฉma staลพeno", + "download-title": "Stรกhnout barevnรก schรฉmata", + "method-description": { + "content": "Schรฉma Material Design s vysokou vฤ›rnostรญ extrakce barev, kterรฉ รบzce odpovรญdรก skuteฤnรฝm barvรกm zdrojovรฉho obsahu.", + "dysfunctional": "Jako Vฤ›rnรฝ, ale jako primรกrnรญ vybere druhou nejdominantnฤ›jลกรญ rodinu barev.", + "faithful": "Snaลพรญ se zลฏstat blรญzko zdrojovรฉ barvฤ› pล™i generovรกnรญ harmonickรฉ palety.", + "fruit-salad": "Schรฉma Material Design produkujรญcรญ hravou, ลพivou paletu s rลฏznorodรฝmi odstรญny.", + "monochrome": "Schรฉma Material Design pouลพรญvajรญcรญ jednobarevnou ลกedou ลกkรกlu s minimรกlnรญm chromatickรฝm obsahem.", + "muted": "Generuje mฤ›kkรฉ, nenasycenรฉ tรณny pro klidnรฝ a nenรกpadnรฝ vzhled.", + "rainbow": "Schรฉma Material Design generujรญcรญ barvy napล™รญฤ celรฝm spektrem pro maximรกlnรญ barevnou rozmanitost.", + "tonal-spot": "Vรฝchozรญ algoritmus Material Design. Vytvรกล™รญ vyvรกลพenรฉ palety pomocรญ tรณnovรฝch variacรญ zdrojovรฉ barvy.", + "vibrant": "Maximalizuje sytost barev pro vรฝraznรฉ, vizuรกlnฤ› silnรฉ palety s ลพivรฝmi tรณny." + }, + "predefined-desc": "Vybrat z kolekce pล™eddefinovanรฝch barevnรฝch schรฉmat.", + "predefined-generate-templates-label": "Generovat ลกablony pro pล™eddefinovanรก schรฉmata", + "predefined-title": "Pล™eddefinovanรก barevnรก schรฉmata", + "sync-gsettings-description": "Pล™izpลฏsobit systรฉmovรฉ tรฉma aktivnรญ svฤ›tlรฉ nebo tmavรฉ variantฤ›.", + "sync-gsettings-label": "Synchronizovat tรฉma systรฉmu", + "templates-desc": "Aplikovat barvy na externรญ aplikace.", + "templates-filter-description": "Zobrazit ลกablony z konkrรฉtnรญ kategorie.", + "templates-filter-label": "Filtrovat podle kategorie", + "templates-misc-description": "Vytvoล™te vlastnรญ ลกablony.", + "templates-misc-label": "Pokroฤilรฉ", + "templates-misc-user-templates-description": "Povolte jen pokud vรญte, co dฤ›lรกte โ€” viz online dokumentace.", + "templates-misc-user-templates-label": "Povolit uลพivatelskรฉ ลกablony", + "templates-none-detected": "Nic nedetekovรกno", + "templates-write-path": "Zapisuje do: {filepath}", + "title": "Barevnรฉ schรฉma", + "wallpaper-method-description": "Vybrat oblรญbenou metodu generovรกnรญ palety.", + "wallpaper-method-label": "Metoda generovรกnรญ palety", + "wallpaper-monitor-source-description": "Vybrat monitor pro extrakci barev tapety.", + "wallpaper-monitor-source-label": "Zdroj pro generovรกnรญ barev" + }, + "connections": { + "authentication-required": "Vyลพadovรกno ovฤ›ล™enรญ", + "bluetooth-auto-connect-description": "Automaticky se pล™ipojit k dลฏvฤ›ryhodnรฝm spรกrovanรฝm zaล™รญzenรญm pล™i zapnutรญ Bluetooth.", + "bluetooth-auto-connect-label": "Automaticky pล™ipojit spรกrovanรก zaล™รญzenรญ", + "bluetooth-devices-unnamed": "Nepojmenovanรก zaล™รญzenรญ nejsou zobrazena.", + "bluetooth-discoverable": "Toto zaล™รญzenรญ je viditelnรฉ jako {hostName}, dokud je tato zรกloลพka nastavenรญ otevล™ena.", + "bluetooth-rssi-polling-description": "Pravidelnฤ› vzorkovat RSSI pล™ipojenรฝch zaล™รญzenรญ pล™es bluetoothctl. Nemusรญ bรฝt dostupnรฉ pro vลกechna zaล™รญzenรญ; pล™i zapnutรญ vyuลพรญvรก minimรกlnรญ prostล™edky.", + "bluetooth-rssi-polling-interval-description": "Nastavit, jak ฤasto se aktualizuje sรญla signรกlu pล™ipojenรฝch zaล™รญzenรญ.", + "bluetooth-rssi-polling-interval-label": "Interval dotazovรกnรญ", + "bluetooth-rssi-polling-label": "Dotazovรกnรญ na signรกl Bluetooth", + "disable-discoverability-description": "Skrรฝt zaล™รญzenรญ pล™ed okolnรญmi Bluetooth zaล™รญzenรญmi.", + "disable-discoverability-label": "Vypnout viditelnost zaล™รญzenรญ", + "hide-unnamed-devices-description": "Skrรฝt zaล™รญzenรญ, kterรก se zobrazujรญ pouze jako Bluetooth adresy.", + "hide-unnamed-devices-label": "Skrรฝt nepojmenovanรก zaล™รญzenรญ", + "pin-instructions": "Zadejte prosรญm PIN kรณd zobrazenรฝ na vaลกem zaล™รญzenรญ.", + "title": "Pล™ipojenรญ", + "wifi-header-text": "Pouลพijte Wi-Fi pro zobrazenรญ dostupnรฝch sรญtรญ a pล™ipojenรญ k internetu." + }, + "control-center": { + "cards-desc": "Pล™izpลฏsobit, kterรฉ ovlรกdacรญ prvky se zobrazujรญ v ล™รญdicรญm centru a v jakรฉm poล™adรญ.", + "desc": "Nastavit pozici a chovรกnรญ panelu ล™รญdicรญho centra.", + "open-at-mouse-description": "Pล™i pravรฉm kliknutรญ na prรกzdnou oblast liลกty otevล™รญt ล™รญdicรญ centrum u kurzoru. Pล™i vypnutรญ se pouลพije nastavenรญ pozice vรฝลกe.", + "open-at-mouse-label": "Otevล™รญt u kurzoru pล™i pravรฉm kliknutรญ", + "position-description": "Vybrat, kde se panel ล™รญdicรญho centra zobrazรญ pล™i otevล™enรญ.", + "shortcuts-custom-button-command-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™i kliknutรญ na tlaฤรญtko.", + "shortcuts-custom-button-enable-on-state-logic-description": "Povolit druhou ikonu a stav 'zapnuto' na zรกkladฤ› pล™รญkazu kontroly.", + "shortcuts-custom-button-enable-on-state-logic-label": "Povolit logiku stavu zapnuto", + "shortcuts-custom-button-icon-description": "Vybrat ikonu z knihovny.", + "shortcuts-custom-button-on-clicked-label": "Pล™รญkaz levรฉho kliknutรญ", + "shortcuts-custom-button-on-middle-clicked-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™i kliknutรญ prostล™ednรญm tlaฤรญtkem myลกi.", + "shortcuts-custom-button-on-state-command-description": "Pล™รญkaz pro kontrolu, zda mรก bรฝt tlaฤรญtko ve stavu 'zapnuto'. Vrรกtรญ 0 pro zapnuto, nenulovรฉ pro vypnuto.", + "shortcuts-custom-button-on-state-command-label": "Pล™รญkaz kontroly stavu zapnuto", + "shortcuts-custom-button-on-state-icon-description": "Ikona tlaฤรญtka ve stavu 'zapnuto'.", + "shortcuts-custom-button-on-state-icon-label": "Ikona stavu zapnuto", + "shortcuts-custom-button-state-checks-add": "Pล™idat kontrolu stavu", + "shortcuts-custom-button-state-checks-command": "Pล™รญkaz pro tuto kontrolu stavu", + "shortcuts-custom-button-state-checks-label": "Kontroly stavu", + "shortcuts-custom-button-state-checks-remove": "Odebrat", + "shortcuts-custom-button-tooltip-description": "Tooltip zobrazenรฝ pล™i najetรญ na tlaฤรญtko.", + "shortcuts-custom-button-tooltip-label": "Tooltip", + "shortcuts-title": "Widgety zkratek", + "system-monitor-disk-path-description": "Vybrat pล™รญpojnรฝ bod disku, kterรฝ mรก sledovat karta systรฉmovรฉho monitoru v ล™รญdicรญm centru.", + "system-monitor-disk-path-label": "Cesta k disku systรฉmovรฉho monitoru", + "title": "ล˜รญdicรญ centrum" + }, + "desktop-widgets": { + "clock-enabled-description": "Zobrazit widget hodin na ploลกe.", + "clock-enabled-label": "Povolit widget hodin", + "clock-format-label": "Formรกt", + "clock-height-description": "Vรฝลกka widgetu hodin v pixelech.", + "clock-height-label": "Vรฝลกka", + "clock-minimal-mode-description": "Pouลพรญt minimรกlnรญ zobrazenรญ hodin s ฤasem a datem.", + "clock-minimal-mode-label": "Minimรกlnรญ reลพim", + "clock-rounded-corners-description": "Pouลพรญt zaoblenรฉ rohy pro pozadรญ widgetu.", + "clock-rounded-corners-label": "Zaoblenรฉ rohy", + "clock-show-background-description": "Zobrazit kontejner pozadรญ pro widget hodin.", + "clock-show-background-label": "Zobrazit pozadรญ", + "clock-show-date-description": "Zobrazit aktuรกlnรญ datum pod ฤasem.", + "clock-show-date-label": "Zobrazit datum", + "clock-show-seconds-description": "Zobrazit sekundy v ฤase.", + "clock-show-seconds-label": "Zobrazit sekundy", + "clock-style-analog": "Analogovรฝ", + "clock-style-binary": "Binรกrnรญ", + "clock-style-description": "Vybrat styl zobrazenรญ hodin.", + "clock-style-digital": "Digitรกlnรญ", + "clock-style-label": "Styl hodin", + "clock-style-minimal": "Minimรกlnรญ", + "clock-width-description": "ล รญล™ka widgetu hodin v pixelech.", + "cpu-intensive-note": "Widgety oznaฤenรฉ ! vyuลพรญvajรญ vรญce CPU a mฤ›ly by bรฝt povoleny uvรกลพlivฤ›.", + "edit-mode-button-label": "Vstoupit do reลพimu รบprav", + "edit-mode-controls-explanation": "Levรฉ kliknutรญ a pล™etaลพenรญ: Pล™esunout nebo zmฤ›nit velikost widgetu.\nPravรฉ kliknutรญ: Otevล™รญt moลพnosti kontextovรฉho menu.", + "edit-mode-description": "Povolit reลพim รบprav pro pล™esouvรกnรญ a pล™emรญsลฅovรกnรญ widgetลฏ plochy. Pล™i aktivaci widgety zobrazรญ obrys pro pล™etaลพenรญ.", + "edit-mode-exit-button": "Opustit reลพim รบprav", + "edit-mode-grid-snap-label": "Pล™ichytit k mล™รญลพce", + "edit-mode-grid-snap-scale-label": "Mฤ›ล™รญtko pล™ichycenรญ k mล™รญลพce", + "edit-mode-label": "Reลพim รบprav", + "enabled-description": "Povolit nebo zakรกzat widgety plochy รบplnฤ›.", + "enabled-label": "Povolit widgety plochy", + "general-desc": "Nastavit widgety zobrazenรฉ na ploลกe.", + "general-title": "Widgety plochy", + "media-player-enabled-description": "Zobrazit widget pล™ehrรกvaฤe mรฉdiรญ na ploลกe.", + "media-player-enabled-label": "Povolit widget pล™ehrรกvaฤe mรฉdiรญ", + "media-player-rounded-corners-description": "Povolit zaoblenรฉ rohy na okrajรญch widgetu.", + "media-player-show-album-art-description": "Zobrazit obal alba a informace o stopฤ› (nรกzev a interpret).", + "media-player-show-album-art-label": "Zobrazit obal alba a nรกzev", + "media-player-show-background-description": "Zobrazit kontejner pozadรญ.", + "media-player-show-buttons-description": "Zobrazit tlaฤรญtka ovlรกdรกnรญ mรฉdiรญ (pล™ehrรกt/pozastavit, pล™edchozรญ, dalลกรญ).", + "media-player-show-buttons-label": "Zobrazit tlaฤรญtka", + "media-player-show-visualizer-description": "Zobrazit pล™ekryv audio vizualizรฉru.", + "media-player-visualizer-type-description": "Vybrat typ vizualizace.", + "overview-enabled-description": "Zobrazit widgety plochy pล™i otevล™enรฉm pล™ehledu compositoru.", + "overview-enabled-label": "Zobrazit v pล™ehledu", + "system-stat-layout-bottom": "Dole", + "system-stat-layout-description": "Vybrat zpลฏsob zobrazenรญ legendy vลฏฤi grafu.", + "system-stat-layout-label": "Rozloลพenรญ", + "system-stat-layout-side": "Po stranฤ›", + "system-stat-rounded-corners-description": "Pouลพรญt zaoblenรฉ rohy pro pozadรญ widgetu.", + "system-stat-rounded-corners-label": "Zaoblenรฉ rohy", + "system-stat-show-background-description": "Zobrazit kontejner pozadรญ pro widget systรฉmovรฝch statistik.", + "system-stat-show-background-label": "Zobrazit pozadรญ", + "system-stat-stat-type-description": "Vybrat, kterรก systรฉmovรก statistika se zobrazรญ.", + "system-stat-stat-type-label": "Typ statistiky", + "title": "Widgety plochy", + "weather-enabled-description": "Zobrazit widget poฤasรญ na ploลกe.", + "weather-enabled-label": "Povolit widget poฤasรญ", + "weather-show-background-description": "Zobrazit kontejner pozadรญ pro widget poฤasรญ." + }, + "display": { + "monitors-backlight-device-auto-option": "Vรฝchozรญ", + "monitors-backlight-device-description": "Vybrat zaล™รญzenรญ podsvรญcenรญ pro tento vรฝstup.", + "monitors-backlight-device-label": "Zaล™รญzenรญ podsvรญcenรญ", + "monitors-brightness-step-description": "Upravit velikost kroku pro zmฤ›ny jasu (koleฤko myลกi a klรกvesovรฉ zkratky).", + "monitors-brightness-step-label": "Velikost kroku jasu", + "monitors-brightness-unavailable-ddc-disabled": "Ovlรกdรกnรญ jasu nenรญ dostupnรฉ. Povolte Podpora externรญho jasu pro ovlรกdรกnรญ jasu tohoto displeje.", + "monitors-brightness-unavailable-generic": "Ovlรกdรกnรญ jasu pro tento displej nenรญ dostupnรฉ.", + "monitors-desc": "Upravit nastavenรญ jasu pro kaลพdรฝ displej.", + "monitors-enforce-minimum-description": "ล˜eลกรญ problรฉm s รบplnรฝm vypnutรญm podsvรญcenรญ na nฤ›kterรฝch displejรญch pล™i 0 % jasu.", + "monitors-enforce-minimum-label": "Vynutit minimรกlnรญ jas (1 %)", + "monitors-external-brightness-description": "Povolit podporu DDCUtil pro ovlรกdรกnรญ jasu externรญch displejลฏ pล™es protokol DDC/CI.", + "monitors-external-brightness-label": "Podpora externรญho jasu", + "monitors-global-brightness-description": "Upravit jas vลกech monitorลฏ najednou.", + "monitors-global-brightness-label": "Vลกechny monitory", + "monitors-title": "Nastavenรญ pro kaลพdรฝ monitor", + "night-light-auto-schedule-description": "Na zรกkladฤ› ฤasu zรกpadu a vรฝchodu slunce v {location} โ€“ doporuฤeno.", + "night-light-auto-schedule-label": "Automatickรฝ rozvrh", + "night-light-desc": "Sniลพte emisi modrรฉho svฤ›tla pro lepลกรญ spรกnek a mรฉnฤ› namรกhanรฉ oฤi.", + "night-light-enable-description": "Aplikovat teplรฝ barevnรฝ filtr pro snรญลพenรญ emise modrรฉho svฤ›tla.", + "night-light-enable-label": "Povolit noฤnรญ svฤ›tlo", + "night-light-force-activation-description": "Ignoruje rozvrh a okamลพitฤ› aplikuje noฤnรญ filtr.", + "night-light-force-activation-label": "Vynutit aktivaci", + "night-light-manual-schedule-description": "Nastavit vlastnรญ ฤasy vรฝchodu a zรกpadu slunce.", + "night-light-manual-schedule-label": "Ruฤnรญ rozvrh", + "night-light-manual-schedule-select-start": "Vybrat ฤas zaฤรกtku", + "night-light-manual-schedule-select-stop": "Vybrat ฤas konce", + "night-light-manual-schedule-sunrise": "ฤŒas vรฝchodu slunce", + "night-light-manual-schedule-sunset": "ฤŒas zรกpadu slunce", + "night-light-temperature-day": "Den", + "night-light-temperature-day-description": "ล˜รญdรญ teplotu barvy bฤ›hem dne.", + "night-light-temperature-description": "Nastavit barevnou teplotu pro noc a den.", + "night-light-temperature-label": "Barevnรก teplota", + "night-light-temperature-night": "Noc", + "night-light-temperature-night-description": "ล˜รญdรญ teplotu barvy bฤ›hem noci.", + "title": "Displej" + }, + "dock": { + "appearance-background-opacity-description": "Upravit prลฏhlednost pozadรญ docku.", + "appearance-border-radius-description": "Upravit polomฤ›r rohลฏ docku.", + "appearance-border-radius-label": "Polomฤ›r rohลฏ", + "appearance-colorize-icons-description": "Pouลพรญt barvy motivu na ikony aplikacรญ docku (pouze neaktivnรญ aplikace).", + "appearance-colorize-icons-label": "Obarvit ikony", + "appearance-dead-opacity-description": "Upravit prลฏhlednost ikon aplikacรญ, kterรฉ nejsou spuลกtฤ›ny.", + "appearance-dead-opacity-label": "Prลฏhlednost neaktivnรญch", + "appearance-desc": "Pล™izpลฏsobit chovรกnรญ a vzhled docku.", + "appearance-display-auto-hide": "Automaticky skrรฝt", + "appearance-display-description": "Vybrat zpลฏsob chovรกnรญ docku.", + "appearance-display-exclusive": "Exkluzivnรญ", + "appearance-dock-indicator-description": "Zobrazit malรฝ indikรกtor pล™i skrytรฉm docku.", + "appearance-dock-indicator-label": "Indikรกtor docku", + "appearance-floating-distance-description": "Nastavit vzdรกlenost mezi dockem a okrajem obrazovky.", + "appearance-floating-distance-label": "Plovoucรญ vzdรกlenost docku", + "appearance-group-apps-description": "Seskupit vรญce oken stejnรฉ aplikace do jednรฉ poloลพky docku.", + "appearance-group-apps-label": "Seskupit stejnรฉ aplikace", + "appearance-group-click-action-cycle": "Cyklovat okna", + "appearance-group-click-action-description": "Vybrat akci levรฉho kliknutรญ pro seskupenรฉ aplikace.", + "appearance-group-click-action-label": "Akce kliknutรญ na skupinu", + "appearance-group-click-action-list": "Otevล™รญt seznam oken", + "appearance-group-context-menu-mode-description": "Vybrat chovรกnรญ kontextovรฉho menu pro seskupenรฉ aplikace.", + "appearance-group-context-menu-mode-extended": "Rozลกรญล™enรฉ", + "appearance-group-context-menu-mode-label": "Reลพim menu skupiny", + "appearance-group-context-menu-mode-list": "Seznam oken", + "appearance-group-indicator-style-description": "Vybrat zpลฏsob zobrazenรญ indikรกtorลฏ seskupenรฝch spuลกtฤ›nรฝch aplikacรญ.", + "appearance-group-indicator-style-dots": "Teฤky", + "appearance-group-indicator-style-label": "Styl indikรกtoru skupiny", + "appearance-group-indicator-style-number": "ฤŒรญslo", + "appearance-hide-show-speed-description": "Upravit rychlost animace skrytรญ/zobrazenรญ docku.", + "appearance-hide-show-speed-label": "Rychlost skrytรญ/zobrazenรญ", + "appearance-icon-size-description": "Upravit celkovou velikost docku.", + "appearance-icon-size-label": "Velikost docku", + "appearance-inactive-indicators-description": "Zobrazit indikaฤnรญ pilulky pro vลกechny aplikace, nejen pro aktivnรญ.", + "appearance-inactive-indicators-label": "Indikรกtory spuลกtฤ›nรฝch", + "appearance-indicator-color-description": "Vybrat barvu indikรกtoru skrytรฉho docku.", + "appearance-indicator-color-label": "Barva indikรกtoru", + "appearance-indicator-opacity-description": "Upravit prลฏhlednost indikรกtoru skrytรฉho docku.", + "appearance-indicator-opacity-label": "Prลฏhlednost indikรกtoru", + "appearance-indicator-thickness-description": "Pล™epnout na silnฤ›jลกรญ indikรกtor skrytรฉho docku (6 px mรญsto 3 px).", + "appearance-indicator-thickness-label": "Silnฤ›jลกรญ indikรกtor", + "appearance-launcher-icon-description": "Vybrat ikonu pouลพitou pro spouลกtฤ›ฤ docku.", + "appearance-launcher-icon-label": "Ikona spouลกtฤ›ฤe", + "appearance-launcher-position-description": "Vybrat, kde se ikona spouลกtฤ›ฤe zobrazรญ v docku.", + "appearance-launcher-position-end": "Konec", + "appearance-launcher-position-label": "Pozice spouลกtฤ›ฤe", + "appearance-launcher-position-start": "Zaฤรกtek", + "appearance-launcher-use-distro-logo-description": "Pouลพรญt logo vaลกรญ distribuce jako ikonu spouลกtฤ›ฤe docku.", + "appearance-launcher-use-distro-logo-label": "Pouลพรญt logo distribuce mรญsto ikony", + "appearance-pinned-static-description": "Vลพdy posunout ikony pล™ipnutรฝch aplikacรญ vlevo ve statickรฉm poล™adรญ.", + "appearance-pinned-static-label": "Statickรฉ pล™ipnutรฉ aplikace", + "appearance-position-description": "Vybrat, kde se dock zobrazรญ na obrazovce.", + "appearance-position-label": "Pozice", + "appearance-show-launcher-icon-description": "Zobrazit ikonu spouลกtฤ›ฤe aplikacรญ v docku.", + "appearance-show-launcher-icon-label": "Zobrazit spouลกtฤ›ฤ aplikacรญ", + "appearance-sit-on-frame-description": "Zarovnat dock uvnitล™ okraje rรกmu mรญsto umรญstฤ›nรญ nad nรญm.", + "appearance-sit-on-frame-label": "Dock na rรกmu", + "appearance-type-attached": "Pล™ipojen", + "appearance-type-description": "Vybrat mezi plovoucรญ pilulkou nebo statickou liลกtou pล™ipojenou k okraji.", + "appearance-type-floating": "Plovoucรญ", + "appearance-type-label": "Styl docku", + "enabled-description": "Zobrazit nebo skrรฝt dock รบplnฤ›.", + "enabled-label": "Povolit dock", + "monitors-desc": "Zobrazit dock na konkrรฉtnรญch monitorech. Vรฝchozรญ je zobrazenรญ na vลกech.", + "monitors-only-same-monitor-description": "Zobrazit pouze aplikace z monitoru, kde se dock nachรกzรญ.", + "monitors-only-same-monitor-label": "Pouze aplikace ze stejnรฉho monitoru", + "monitors-title": "Zobrazenรญ na monitorech", + "title": "Dock" + }, + "general": { + "copy-settings": "Zkopรญrovat nastavenรญ", + "fonts-default-description": "Hlavnรญ pรญsmo pouลพรญvanรฉ v celรฉm rozhranรญ.", + "fonts-default-label": "Vรฝchozรญ pรญsmo", + "fonts-default-placeholder": "Vybrat vรฝchozรญ pรญsmo...", + "fonts-default-scale-description": "Zvฤ›tลกit nebo zmenลกit velikost standardnรญho textu.", + "fonts-default-scale-label": "Velikost vรฝchozรญho pรญsma", + "fonts-default-search-placeholder": "Hledat pรญsmo...", + "fonts-desc": "Vybrat pรญsma pouลพรญvanรก v celรฉm rozhranรญ.", + "fonts-monospace-description": "Neproporcionรกlnรญ pรญsmo pouลพรญvanรฉ pro ฤรญsla a statistiky.", + "fonts-monospace-label": "Neproporcionรกlnรญ pรญsmo", + "fonts-monospace-placeholder": "Vybrat neproporcionรกlnรญ pรญsmo...", + "fonts-monospace-scale-description": "Zvฤ›tลกit nebo zmenลกit velikost neproporcionรกlnรญho textu.", + "fonts-monospace-scale-label": "Velikost neproporcionรกlnรญho pรญsma", + "fonts-monospace-search-placeholder": "Hledat neproporcionรกlnรญ pรญsmo...", + "fonts-title": "Pรญsma", + "keybinds-conflict-description": "Kombinace klรกves je jiลพ pล™iล™azena k {action}.", + "keybinds-conflict-title": "Konflikt klรกvesovรฉ zkratky", + "keybinds-description": "Nastavit globรกlnรญ navigaฤnรญ klรกvesy pro panely a spouลกtฤ›ฤ.", + "keybinds-down": "Pล™esunout dolลฏ", + "keybinds-enter": "Potvrdit / Akce", + "keybinds-escape": "Zavล™รญt / Zpฤ›t", + "keybinds-left": "Pล™esunout vlevo", + "keybinds-modifier-description": "Zkratky musรญ obsahovat modifikaฤnรญ klรกvesu (Ctrl nebo Alt).", + "keybinds-modifier-title": "Vyลพadovรกn modifikรกtor", + "keybinds-remove": "Odebrat / Smazat", + "keybinds-right": "Pล™esunout vpravo", + "keybinds-title": "Navigaฤnรญ klรกvesovรฉ zkratky", + "keybinds-up": "Pล™esunout nahoru", + "language-desc": "Vybrat preferovanรฝ jazyk aplikace.", + "language-select-auto-detect": "Automaticky", + "language-select-description": "Vybrat jazyk pouลพรญvanรฝ v rozhranรญ aplikace.", + "language-select-label": "Jazyk aplikace", + "launch-setup-wizard": "Prลฏvodce nastavenรญm", + "profile-desc": "Upravit uลพivatelskรฉ รบdaje a avatar.", + "profile-picture-description": "Vรกลก profilovรฝ obrรกzek zobrazovanรฝ v celรฉm rozhranรญ.", + "profile-picture-label": "Profilovรฝ obrรกzek", + "profile-select-avatar": "Vybrat obrรกzek avatara", + "profile-title": "Profil", + "profile-tooltip": "Profilovรฝ obrรกzek", + "reverse-scrolling-description": "Obrรกtit interpretovanรฝ smฤ›r posouvรกnรญ", + "reverse-scrolling-label": "Obrรกcenรฉ posouvรกnรญ", + "screen-corners-desc": "Pล™izpลฏsobit zaoblenรญ rohลฏ obrazovky a vizuรกlnรญ efekty.", + "screen-corners-radius-description": "Upravit zaoblenรญ rohลฏ obrazovky.", + "screen-corners-radius-label": "Polomฤ›r rohลฏ obrazovky", + "screen-corners-radius-reset": "Obnovit polomฤ›r rohลฏ obrazovky", + "screen-corners-show-corners-description": "Zobrazit zaoblenรฉ rohy na okrajรญch obrazovky.", + "screen-corners-show-corners-label": "Zobrazit rohy obrazovky", + "screen-corners-solid-black-description": "Pouลพรญt plnou ฤernou mรญsto barvy pozadรญ liลกty.", + "screen-corners-solid-black-label": "Plnฤ› ฤernรฉ rohy", + "screen-corners-title": "Rohy obrazovky", + "settings-copied": "Nastavenรญ zkopรญrovรกno do schrรกnky", + "smooth-scrolling-description": "Animovat posouvรกnรญ seznamลฏ pro plynulejลกรญ zรกลพitek s koleฤkem.", + "smooth-scrolling-label": "Plynulรฉ posouvรกnรญ", + "tab-basics": "Zรกklady", + "tab-keybinds": "Klรกvesovรฉ zkratky" + }, + "hooks": { + "color-generation-description": "Pล™รญkaz spuลกtฤ›nรฝ po dokonฤenรญ generovรกnรญ barev (napล™. po motivu na zรกkladฤ› tapety).", + "color-generation-label": "Barvy vygenerovรกny", + "color-generation-placeholder": "napล™. notify-send \"Barvy\" \"Vygenerovรกny\"", + "info-command-info-description": "โ€ข Pล™รญkazy jsou spouลกtฤ›ny pล™es shell (sh -lc)
โ€ข Pล™รญkazy bฤ›ลพรญ na pozadรญ (odpojen)
โ€ข Tlaฤรญtka testu spustรญ s aktuรกlnรญmi hodnotami", + "info-parameters-description": "โ€ข Hook tapety: $1 = cesta k tapetฤ›, $2 = nรกzev obrazovky, $3 = motiv (dark/light)
โ€ข Hook generovรกnรญ barev: $1 = motiv (dark/light)
โ€ข Hook pล™epnutรญ motivu: $1 = true/false (stav tmavรฉho reลพimu)
โ€ข Hooky zamknutรญ/odemknutรญ: $1 = lock/unlock (stav uzamฤenรญ)
โ€ข Hooky vรฝkonnostnรญho reลพimu: Bez parametrลฏ
โ€ข Hook relace: $1 = akce (shutdown/reboot)", + "info-parameters-label": "Dostupnรฉ parametry", + "noctalia-started-description": "Pล™รญkaz spuลกtฤ›nรฝ po dokonฤenรญ naฤรญtรกnรญ Noctalia.", + "noctalia-started-label": "Noctalia spuลกtฤ›na", + "noctalia-started-placeholder": "napล™. notify-send 'Noctalia naฤtena'", + "performance-mode-disabled-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™i vypnutรญ vรฝkonnostnรญho reลพimu Noctalia.", + "performance-mode-disabled-label": "Vรฝkonnostnรญ reลพim vypnut", + "performance-mode-disabled-placeholder": "napล™. notify-send \"Vรฝkon\" \"Reลพim vypnut\"", + "performance-mode-enabled-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™i zapnutรญ vรฝkonnostnรญho reลพimu Noctalia.", + "performance-mode-enabled-label": "Vรฝkonnostnรญ reลพim zapnut", + "performance-mode-enabled-placeholder": "napล™. notify-send \"Vรฝkon\" \"Reลพim zapnut\"", + "screen-lock-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™i zamknutรญ obrazovky.", + "screen-lock-label": "Obrazovka zamknuta", + "screen-lock-placeholder": "napล™. notify-send \"Obrazovka\" \"Zamknuta\"", + "screen-unlock-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™i odemknutรญ zamykacรญ obrazovky.", + "screen-unlock-label": "Obrazovka odemknuta", + "screen-unlock-placeholder": "napล™. notify-send \"Obrazovka\" \"Odemknuta\"", + "session-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™ed vypnutรญm nebo restartem. Pล™ijรญmรก typ akce jako $1 (shutdown/reboot).", + "session-label": "Konec relace", + "session-placeholder": "napล™. notify-send \"Relace\" \"$1\"", + "system-hooks-desc": "Nastavit pล™รญkazy spouลกtฤ›nรฉ pล™i systรฉmovรฝch udรกlostech.", + "system-hooks-enable-description": "Povolit nebo zakรกzat vลกechny hookovรฉ pล™รญkazy.", + "system-hooks-enable-label": "Povolit hooky", + "system-hooks-title": "Systรฉmovรฉ hooky", + "theme-changed-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™i pล™epnutรญ motivu mezi tmavรฝm a svฤ›tlรฝm reลพimem.", + "theme-changed-label": "Motiv zmฤ›nฤ›n", + "theme-changed-placeholder": "napล™. notify-send \"Motiv\" \"Pล™epnut\"", + "title": "Hooky", + "wallpaper-changed-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™i zmฤ›nฤ› tapety.", + "wallpaper-changed-label": "Tapeta zmฤ›nฤ›na", + "wallpaper-changed-placeholder": "napล™. notify-send \"Tapeta\" \"Zmฤ›nฤ›na\"" + }, + "idle": { + "custom-add": "Pล™idat pล™รญkaz", + "custom-description": "Spustit pล™รญkaz shellu po obdobรญ neฤinnosti.", + "custom-entry-command": "Pล™รญkaz", + "custom-entry-delete": "Smazat", + "custom-entry-edit": "Upravit vlastnรญ pล™รญkaz", + "custom-entry-name": "Nรกzev", + "custom-entry-name-placeholder": "napล™. Vypnout svฤ›tla", + "custom-entry-new": "Novรฝ vlastnรญ pล™รญkaz", + "custom-entry-timeout": "ฤŒas neฤinnosti", + "custom-entry-timeout-format": "{count} sekunda", + "custom-entry-timeout-format-plural": "{count} sekund", + "custom-entry-unnamed": "Nepojmenovanรฝ pล™รญkaz", + "custom-label": "Vlastnรญ pล™รญkazy neฤinnosti", + "enable-description": "Automaticky vypnout obrazovku, zamknout nebo uspat po obdobรญ neฤinnosti.", + "enable-label": "Povolit sprรกvu neฤinnosti", + "fade-duration-description": "Sekundy animace zatmฤ›nรญ pล™ed spuลกtฤ›nรญm kaลพdรฉ akce. Jakรฝkoli pohyb myลกรญ animaci zruลกรญ.", + "fade-duration-label": "Trvรกnรญ zatmฤ›nรญ", + "lock-description": "Sekundy neฤinnosti pล™ed aktivacรญ zamykacรญ obrazovky.", + "lock-label": "Zamykacรญ obrazovka", + "resume-command-label": "Pล™รญkaz obnovenรญ", + "screen-off-description": "Sekundy neฤinnosti pล™ed vypnutรญm monitorลฏ.", + "screen-off-label": "Vypnout obrazovku", + "status-description": "ฤŒas neฤinnosti hlรกลกenรฝ compositorem.", + "status-label": "ฤŒas neฤinnosti", + "suspend-description": "Sekundy neฤinnosti pล™ed uspรกnรญm systรฉmu.", + "tab-behavior": "Chovรกnรญ", + "tab-custom": "Vlastnรญ", + "timeouts-description": "Nastavte na 0 pro zakรกzรกnรญ fรกze. ฤŒasovaฤe jsou pozastaveny pล™i aktivnรญm Udrลพet vzhลฏru.", + "timeouts-label": "ฤŒasovรฉ limity", + "title": "Neฤinnost", + "unavailable": "Nativnรญ sledovรกnรญ neฤinnosti nenรญ na tomto compositoru dostupnรฉ." + }, + "indicator": { + "default-value": "Vรฝchozรญ: {value}", + "system-default": "Vรฝchozรญ systรฉmu" + }, + "launcher": { + "clipboard-desc": "Pล™istupovat a spravovat historii schrรกnky ze spouลกtฤ›ฤe.", + "execute-desc": "Nastavit zpลฏsob spouลกtฤ›nรญ aplikacรญ.", + "execute-title": "Spouลกtฤ›nรญ", + "settings-annotation-tool-description": "Pล™รญkaz spuลกtฤ›nรฝ pล™i kliknutรญ na tlaฤรญtko anotace v historii schrรกnky. Obrรกzek bude pล™edรกn tomuto pล™รญkazu.", + "settings-annotation-tool-label": "Nรกstroj anotace", + "settings-annotation-tool-placeholder": "napล™. 'gradia', 'satty -f -'", + "settings-auto-paste-description": "Automaticky vloลพit vybranou poloลพku schrรกnky. Vyลพaduje wtype.", + "settings-auto-paste-label": "Automatickรฉ vloลพenรญ", + "settings-clip-chips-description": "Zobrazit panel zรกloลพek pro filtrovรกnรญ historie schrรกnky podle typu (Obrรกzky, Odkazy, Soubory, Kรณd atd.).", + "settings-clip-chips-label": "Povolit kategorie", + "settings-clip-preview-description": "Zobrazit nรกhled obsahu schrรกnky pล™i pouลพitรญ pล™รญkazu >clip.", + "settings-clip-preview-label": "Povolit nรกhled schrรกnky", + "settings-clip-smart-icons-description": "Zobrazit specifickรฉ ikony pro odkazy, soubory, barvy a kรณd mรญsto obecnรฉ ikony schrรกnky.", + "settings-clip-smart-icons-label": "Povolit chytrรฉ ikony", + "settings-clip-wrap-text-description": "Zalamovat text v seznamu schrรกnky mรญsto oล™ezรกvรกnรญ.", + "settings-clip-wrap-text-label": "Zalamovat text schrรกnky", + "settings-clipboard-history-description": "Pล™istupovat k dล™รญve zkopรญrovanรฝm poloลพkรกm ze spouลกtฤ›ฤe.", + "settings-clipboard-history-label": "Povolit historii schrรกnky", + "settings-clipboard-watch-image-description": "รšplnรฝ ล™etฤ›zec pล™รญkazu pล™edรกvanรฝ wl-paste pro zmฤ›ny obrรกzkลฏ. (vyลพaduje restart)", + "settings-clipboard-watch-image-label": "Pล™รญkaz sledovรกnรญ obrรกzkลฏ", + "settings-clipboard-watch-text-description": "รšplnรฝ ล™etฤ›zec pล™รญkazu pล™edรกvanรฝ wl-paste pro zmฤ›ny textu. (vyลพaduje restart)", + "settings-clipboard-watch-text-label": "Pล™รญkaz sledovรกnรญ textu", + "settings-custom-launch-prefix-description": "Pล™idat vlastnรญ prefix k pล™รญkazลฏm spouลกtฤ›nรญ (napล™. 'runapp' pro integraci systemd).", + "settings-custom-launch-prefix-enabled-description": "Pouลพรญt vlastnรญ prefix pro spouลกtฤ›nรญ aplikacรญ mรญsto vรฝchozรญ metody.", + "settings-custom-launch-prefix-enabled-label": "Povolit vlastnรญ prefix spouลกtฤ›nรญ", + "settings-custom-launch-prefix-label": "Vlastnรญ prefix spouลกtฤ›nรญ", + "settings-density-description": "Upravit velikost ikon aplikacรญ a hustotu spouลกtฤ›ฤe.", + "settings-density-label": "Hustota", + "settings-desc": "Pล™izpลฏsobit chovรกnรญ a vzhled spouลกtฤ›ฤe.", + "settings-enable-session-search-description": "Zobrazit akce relace (zamknout, vypnout, restartovat atd.) pล™i vyhledรกvรกnรญ ve spouลกtฤ›ฤi.", + "settings-enable-session-search-label": "Povolit vyhledรกvรกnรญ relace", + "settings-enable-settings-search-description": "Zobrazit vรฝsledky nastavenรญ pล™i vyhledรกvรกnรญ ve spouลกtฤ›ฤi.", + "settings-enable-settings-search-label": "Povolit vyhledรกvรกnรญ nastavenรญ", + "settings-enable-windows-search-description": "Vyhledat a aktivovat aktivnรญ okna.", + "settings-enable-windows-search-label": "Povolit vyhledรกvรกnรญ oken", + "settings-grid-view-description": "Zobrazit poloลพky v mล™รญลพkovรฉm rozloลพenรญ mรญsto seznamu.", + "settings-icon-mode-description": "Pouลพรญt nativnรญ systรฉmovรฉ ikony mรญsto ikon Tabler.", + "settings-icon-mode-label": "Pouลพรญt nativnรญ ikony", + "settings-ignore-mouse-input-description": "Zakรกzat interakci myลกรญ a koleฤka ve spouลกtฤ›ฤi.", + "settings-ignore-mouse-input-label": "Ignorovat vstup myลกi", + "settings-overlay-layer-description": "Zobrazit spouลกtฤ›ฤ na pล™ekryvnรฉ vrstvฤ›, nad celooobrazovkovรฝmi okny. Pล™i zapnutรญ se spouลกtฤ›ฤ nepล™ipojรญ k liลกtฤ›.", + "settings-overlay-layer-label": "Zobrazit nad celou obrazovkou", + "settings-position-description": "Vybrat, kde se panel spouลกtฤ›ฤe zobrazรญ.", + "settings-show-categories-description": "Zobrazit zรกloลพky kategoriรญ pro filtrovรกnรญ aplikacรญ.", + "settings-show-categories-label": "Zobrazit kategorie", + "settings-show-icon-background-description": "Zobrazit zaoblenรฉ obdรฉlnรญkovรฉ pozadรญ za ikonami.", + "settings-show-icon-background-label": "Zobrazit pozadรญ ikony", + "settings-sort-by-usage-description": "Pokud je povoleno, ฤasto spouลกtฤ›nรฉ aplikace se zobrazรญ jako prvnรญ.", + "settings-sort-by-usage-label": "ล˜adit podle nejpouลพรญvanฤ›jลกรญch", + "settings-terminal-command-description": "Pล™รญkaz pro spuลกtฤ›nรญ terminรกlu. Napล™. 'kitty -e' nebo 'gnome-terminal --'.", + "settings-terminal-command-label": "Pล™รญkaz terminรกlu", + "settings-use-app2unit-description": "Pouลพรญvรก alternativnรญ metodu spouลกtฤ›nรญ pro lepลกรญ sprรกvu procesลฏ aplikacรญ a prevenci problรฉmลฏ.", + "settings-use-app2unit-label": "Pouลพรญt App2Unit pro spouลกtฤ›nรญ aplikacรญ", + "settings-view-mode-description": "Vybrat rozloลพenรญ poloลพek spouลกtฤ›ฤe.", + "settings-view-mode-label": "Reลพim zobrazenรญ", + "title": "Spouลกtฤ›ฤ" + }, + "location": { + "auto-locate-description": "Automaticky detekovat vaลกi polohu pravidelnฤ› pomocรญ vaลกรญ IP adresy.", + "auto-locate-label": "Automatickรฉ Zjiลกtฤ›nรญ", + "calendar-cards-desc": "Organizovat a povolit/zakรกzat karty v panelu kalendรกล™e.", + "calendar-cards-title": "Karty kalendรกล™e", + "calendar-header-label": "Zรกhlavรญ kalendรกล™e", + "calendar-month-label": "Mฤ›sรญc kalendรกล™e", + "date-time-12hour-format-description": "Zobrazit ฤas ve 12hodinovรฉm formรกtu na zamykacรญ obrazovce a v kalendรกล™i. Hodiny v liลกtฤ› majรญ vlastnรญ nastavenรญ.", + "date-time-12hour-format-label": "Pouลพรญt 12hodinovรฝ formรกt", + "date-time-desc": "Pล™izpลฏsobit zpลฏsob zobrazenรญ data a ฤasu.", + "date-time-first-day-of-week-automatic": "Automaticky (pouลพรญt nรกrodnรญ prostล™edรญ systรฉmu)", + "date-time-first-day-of-week-description": "Vybrat, kterรฝ den zaฤรญnรก tรฝden v kalendรกล™i.", + "date-time-first-day-of-week-label": "Prvnรญ den v tรฝdnu", + "date-time-show-events-description": "Zobrazit udรกlosti v panelu kalendรกล™e.", + "date-time-show-events-label": "Zobrazit udรกlosti kalendรกล™e", + "date-time-title": "Datum a ฤas", + "date-time-use-analog-description": "Zobrazit analogovรฉ hodiny v oknฤ› kalendรกล™e.", + "date-time-use-analog-label": "Pouลพรญt analogovรฝ styl hodin", + "date-time-week-numbers-description": "Zobrazit ฤรญslo tรฝdne v roce (napล™. tรฝden 38) v kalendรกล™i.", + "date-time-week-numbers-label": "Zobrazit ฤรญsla tรฝdnลฏ", + "geolocate-now-button": "Najรญt nynรญ", + "location-desc": "Zรญskejte pล™esnรฉ poฤasรญ a rozvrh noฤnรญho svฤ›tla nastavenรญm polohy.", + "location-search-description": "napล™. Praha, CZ", + "location-search-label": "Hledat polohu", + "location-search-placeholder": "Zadejte nรกzev mรญsta", + "location-title": "Vaลกe poloha", + "weather-desc": "Vybrat preferovanou jednotku teploty.", + "weather-enabled-description": "Zobrazit informace o poฤasรญ v celรฉm rozhranรญ a naฤรญtat data poฤasรญ. Pล™i vypnutรญ budou skryty vลกechny prvky poฤasรญ a nebudou provรกdฤ›ny ลพรกdnรฉ sรญลฅovรฉ poลพadavky.", + "weather-enabled-label": "Povolit poฤasรญ", + "weather-fahrenheit-description": "Zobrazit teplotu ve Fahrenheitech mรญsto Celsia.", + "weather-fahrenheit-label": "Zobrazit teplotu ve Fahrenheitech (ยฐF)", + "weather-hide-city-description": "Skrรฝt nรกzev mฤ›sta z zobrazenรญ poฤasรญ v celรฉm rozhranรญ.", + "weather-hide-city-label": "Skrรฝt nรกzev mฤ›sta", + "weather-hide-timezone-description": "Skrรฝt zkratku ฤasovรฉho pรกsma ze zobrazenรญ poฤasรญ v celรฉm rozhranรญ.", + "weather-hide-timezone-label": "Skrรฝt ฤasovรฉ pรกsmo", + "weather-show-effects-description": "Zobrazit dalลกรญ vizuรกlnรญ efekty (jako dรฉลกลฅ, snรญh nebo blesk) na kartฤ› poฤasรญ.", + "weather-show-effects-label": "Zobrazit efekty poฤasรญ", + "weather-show-in-calendar-description": "Zobrazit dennรญ pล™edpovฤ›ฤ poฤasรญ pล™รญmo v zobrazenรญ kalendรกล™e.", + "weather-show-in-calendar-label": "Zobrazit poฤasรญ v kalendรกล™i", + "weather-talia-mascot-always-description": "Vลพdy pouลพรญvejte Talia emojis namรญsto tabler icons.", + "weather-talia-mascot-always-label": "Vลพdy zobrazovat emotikony poฤasรญ Talia" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "Pokud je aktivnรญ fprintd (otisky prstลฏ), tato moลพnost umoลพลˆuje pล™ihlรกsit se heslem mรญsto otisku prstu.", + "allow-password-with-fprintd-label": "Povolit pล™ihlรกลกenรญ heslem s fprintd", + "auto-start-auth-description": "Automaticky zahรกjรญ ovฤ›ล™enรญ otiskem prstu bez nutnosti stisknout klรกvesu nebo tlaฤรญtko.", + "auto-start-auth-label": "Automatickรฉ spuลกtฤ›nรญ ovฤ›ล™enรญ", + "clock-format-description": "Pล™izpลฏsobit formรกt hodin pomocรญ tokenลฏ syntaxe data/ฤasu.", + "clock-format-label": "Formรกt hodin", + "clock-style-analog": "Analogovรฝ", + "clock-style-custom": "Vlastnรญ", + "clock-style-description": "Vybrat vizuรกlnรญ styl hodin na zamykacรญ obrazovce.", + "clock-style-digital": "Digitรกlnรญ", + "clock-style-label": "Styl hodin", + "compact-lockscreen-description": "Zobrazit pouze vstup pro pล™ihlรกลกenรญ a systรฉmovรฉ ovlรกdacรญ prvky, skrรฝt widgety poฤasรญ a mรฉdiรญ.", + "compact-lockscreen-label": "Kompaktnรญ zamykacรญ obrazovka", + "enable-lockscreen-media-controls-description": "Zobrazit interaktivnรญ ovlรกdรกnรญ pล™ehrรกvรกnรญ mรฉdiรญ na zamykacรญ obrazovce.", + "enable-lockscreen-media-controls-label": "Ovlรกdรกnรญ mรฉdiรญ na zamykacรญ obrazovce", + "lock-on-suspend-description": "Automaticky zamknout obrazovku pล™i uspรกnรญ systรฉmu.", + "lock-on-suspend-label": "Zamknout pล™i uspรกnรญ", + "lock-screen-animations-description": "Povolit nebo zakรกzat animace zamykacรญ obrazovky.", + "lock-screen-animations-label": "Animace zamykacรญ obrazovky", + "lock-screen-blur-strength-description": "Aplikovat efekt rozmazรกnรญ na tapetu zamykacรญ obrazovky.", + "lock-screen-blur-strength-label": "Sรญla rozmazรกnรญ zamykacรญ obrazovky", + "lock-screen-tint-strength-description": "Aplikovat barevnรฝ pล™ekryv na tapetu zamykacรญ obrazovky.", + "lock-screen-tint-strength-label": "Sรญla tรณnu zamykacรญ obrazovky", + "monitors-desc": "Zobrazit zamykacรญ obrazovku na konkrรฉtnรญch monitorech. Vรฝchozรญ je zobrazenรญ na vลกech.", + "password-chars-description": "Roztomilรฉ ikony pro skrytรญ vaลกeho hesla.", + "password-chars-label": "Nรกhodnรฉ ikony hesla", + "show-hibernate-description": "Zobrazit moลพnost 'hibernace' v ovlรกdรกnรญ napรกjenรญ.", + "show-hibernate-label": "Zobrazit hibernaci", + "show-session-buttons-description": "Umoลพnit pล™รญstup k nastavenรญ napรกjenรญ ze zamykacรญ obrazovky.", + "show-session-buttons-label": "Ovlรกdรกnรญ napรกjenรญ", + "title": "Zamykacรญ obrazovka" + }, + "network": { + "bluetooth-description": "Aktivovat sprรกvu Bluetooth.", + "bluetooth-rssi-polling-description": "Pravidelnฤ› vzorkovat RSSI pล™ipojenรฝch zaล™รญzenรญ pล™es bluetoothctl. Nemusรญ bรฝt dostupnรฉ pro vลกechna zaล™รญzenรญ; pล™i zapnutรญ vyuลพรญvรก minimรกlnรญ prostล™edky.", + "bluetooth-rssi-polling-label": "Dotazovรกnรญ na signรกl Bluetooth", + "desc": "Spravovat Wi-Fi a Bluetooth pล™ipojenรญ.", + "wifi-description": "Spravovat bezdrรกtovรฉ sรญtฤ› (vyลพaduje NetworkManager)." + }, + "notifications": { + "duration-critical-urgency-description": "Jak dlouho zลฏstรกvajรญ viditelnรก kritickรก oznรกmenรญ.", + "duration-critical-urgency-label": "Kritickรก nalรฉhavost", + "duration-desc": "Nastavit, jak dlouho zลฏstรกvajรญ oznรกmenรญ viditelnรก podle jejich nalรฉhavosti.", + "duration-low-urgency-description": "Jak dlouho zลฏstรกvajรญ viditelnรก oznรกmenรญ s nรญzkou prioritou.", + "duration-low-urgency-label": "Nรญzkรก nalรฉhavost", + "duration-normal-urgency-description": "Jak dlouho zลฏstรกvajรญ viditelnรก normรกlnรญ oznรกmenรญ.", + "duration-normal-urgency-label": "Normรกlnรญ nalรฉhavost", + "duration-respect-expire-description": "Pouลพรญt ฤas vyprลกenรญ nastavenรฝ v oznรกmenรญ.", + "duration-respect-expire-label": "Respektovat ฤas vyprลกenรญ", + "duration-title": "Trvรกnรญ oznรกmenรญ", + "history-clear-dismiss-description": "Smazat oznรกmenรญ z historie pล™i zavล™enรญ.", + "history-clear-dismiss-label": "Smazat pล™i zavล™enรญ", + "history-critical-urgency-description": "Uklรกdat kritickรก oznรกmenรญ do historie.", + "history-critical-urgency-label": "Uklรกdat kritickรก do historie", + "history-desc": "ล˜รญdit, kterรก oznรกmenรญ se uklรกdajรญ do historie podle jejich nalรฉhavosti.", + "history-low-urgency-description": "Uklรกdat oznรกmenรญ s nรญzkou prioritou do historie.", + "history-low-urgency-label": "Uklรกdat nรญzkรก do historie", + "history-normal-urgency-description": "Uklรกdat normรกlnรญ oznรกmenรญ do historie.", + "history-normal-urgency-label": "Uklรกdat normรกlnรญ do historie", + "monitors-desc": "Zobrazit oznรกmenรญ na konkrรฉtnรญch monitorech. Vรฝchozรญ je zobrazenรญ na vลกech.", + "rules-action-block": "Blokovat", + "rules-action-block-desc": "รšplnฤ› pล™eskoฤรญ.", + "rules-action-hide": "Skrรฝt", + "rules-action-hide-desc": "ลฝรกdnรฉ vyskakovacรญ okno, ลพรกdnรฝ zvuk, pล™idรก do historie.", + "rules-action-label": "Akce", + "rules-action-mute": "Ztlumit", + "rules-action-mute-desc": "ลฝรกdnรฝ zvuk, stรกle zobrazรญ vyskakovacรญ okno a pล™idรก do historie.", + "rules-add": "Pล™idat pravidlo", + "rules-delete": "Smazat pravidlo", + "rules-description": "Porovnat nรกzev aplikace nebo obsah. Platรญ prvnรญ shoda.", + "rules-edit": "Upravit pravidlo", + "rules-label": "Pravidla filtrovรกnรญ", + "rules-pattern-label": "Vzor", + "rules-pattern-placeholder": "firefox, discord, nebo /regex/", + "rules-tab": "Pravidla", + "settings-always-on-top-description": "Zobrazit oznรกmenรญ nad celooobrazovkovรฝmi okny a ostatnรญmi vrstvami.", + "settings-background-opacity-description": "Upravit prลฏhlednost pozadรญ oznรกmenรญ.", + "settings-density-description": "Vybrat hustotu karet oznรกmenรญ.", + "settings-density-label": "Hustota", + "settings-desc": "Nastavit vzhled a chovรกnรญ oznรกmenรญ.", + "settings-do-not-disturb-description": "Zakรกลพe vลกechna vyskakovacรญ oznรกmenรญ pล™i zapnutรญ.", + "settings-enabled-description": "Povolit nebo zakรกzat dรฉmona oznรกmenรญ, vyลพaduje restart shellu Noctalia.", + "settings-enabled-label": "Povolit oznรกmenรญ", + "settings-location-description": "Kde se oznรกmenรญ zobrazujรญ na obrazovce.", + "settings-markdown-description": "Vykreslovat obsah oznรกmenรญ pomocรญ formรกtovรกnรญ Markdown.", + "settings-markdown-label": "Povolit Markdown", + "sounds-desc": "Nastavit zvukovรฉ efekty oznรกmenรญ a hlasitost.", + "sounds-enabled-description": "Povolit zvukovรฉ efekty pro pล™รญchozรญ oznรกmenรญ.", + "sounds-enabled-label": "Povolit zvuky oznรกmenรญ", + "sounds-excluded-apps-description": "Pล™eskoฤit pล™ehrรกvรกnรญ nastavenรฉho zvuku oznรกmenรญ pro konkrรฉtnรญ aplikace, kterรฉ majรญ vlastnรญ zvuky.", + "sounds-excluded-apps-label": "Vylouฤenรฉ aplikace", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "Cesta ke zvukovรฉmu souboru pล™ehrรกvanรฉmu pro kritickรก oznรกmenรญ.", + "sounds-files-critical-label": "Zvuk kritickรฉ nalรฉhavosti", + "sounds-files-critical-select-title": "Vybrat zvukovรฝ soubor pro kritickรก oznรกmenรญ", + "sounds-files-desc": "Nastavit vlastnรญ zvukovรฉ soubory pro rลฏznรฉ รบrovnฤ› nalรฉhavosti oznรกmenรญ.", + "sounds-files-low-description": "Cesta ke zvukovรฉmu souboru pล™ehrรกvanรฉmu pro oznรกmenรญ s nรญzkou prioritou.", + "sounds-files-low-label": "Zvuk nรญzkรฉ nalรฉhavosti", + "sounds-files-low-select-title": "Vybrat zvukovรฝ soubor pro nรญzkรก oznรกmenรญ", + "sounds-files-normal-description": "Cesta ke zvukovรฉmu souboru pล™ehrรกvanรฉmu pro normรกlnรญ oznรกmenรญ.", + "sounds-files-normal-label": "Zvuk normรกlnรญ nalรฉhavosti", + "sounds-files-normal-select-title": "Vybrat zvukovรฝ soubor pro normรกlnรญ oznรกmenรญ", + "sounds-files-placeholder": "Zadejte cestu ke zvukovรฉmu souboru", + "sounds-files-select-file": "Vybrat zvukovรฝ soubor", + "sounds-files-title": "Zvukovรฉ soubory", + "sounds-files-unified-description": "Cesta ke zvukovรฉmu souboru pล™ehrรกvanรฉmu pro oznรกmenรญ.", + "sounds-files-unified-label": "Zvuk oznรกmenรญ", + "sounds-files-unified-select-title": "Vybrat zvukovรฝ soubor pro oznรกmenรญ", + "sounds-separate-description": "Pouลพรญt rลฏznรฉ zvukovรฉ soubory pro oznรกmenรญ s nรญzkou, normรกlnรญ a kritickou prioritou.", + "sounds-separate-label": "Pouลพรญt rลฏznรฉ zvuky podle priority", + "sounds-title": "Nastavenรญ zvuku", + "sounds-unavailable-description": "Nainstalujte Qt6 Multimedia pro povolenรญ zvukลฏ oznรกmenรญ.", + "sounds-unavailable-label": "Zvuky oznรกmenรญ nejsou dostupnรฉ", + "sounds-volume-description": "Upravit รบroveลˆ hlasitosti zvukลฏ oznรกmenรญ.", + "sounds-volume-label": "Hlasitost zvuku", + "toast-battery-description": "Zobrazit upozornฤ›nรญ, kdyลพ stav baterie klesne pod toto procento.", + "toast-battery-label": "Upozornฤ›nรญ na baterii", + "toast-desc": "Nastavit vzhled a chovรกnรญ vyskakovacรญch oznรกmenรญ.", + "toast-keyboard-description": "Zobrazit vyskakovacรญ oznรกmenรญ pล™i zmฤ›nฤ› rozloลพenรญ klรกvesnice.", + "toast-keyboard-label": "Rozloลพenรญ klรกvesnice", + "toast-media-description": "Zobrazit vyskakovacรญ oznรกmenรญ pล™i zmฤ›nฤ› stavu pล™ehrรกvรกnรญ mรฉdiรญ.", + "toast-media-label": "Mรฉdia" + }, + "osd": { + "always-on-top-description": "Zobrazit OSD nad celooobrazovkovรฝmi okny a ostatnรญmi vrstvami.", + "always-on-top-label": "Vลพdy nahoล™e", + "background-opacity-description": "ล˜รญdรญ prลฏhlednost pozadรญ OSD.", + "background-opacity-label": "Prลฏhlednost pozadรญ", + "description": "Nastavit indikรกtory na obrazovce, jako jsou pล™ekryvy hlasitosti a jasu.", + "duration-auto-hide-description": "Upravit ฤas pล™ed zmizenรญm OSD.", + "duration-auto-hide-label": "Automaticky skrรฝt po", + "duration-desc": "Jak dlouho OSD zลฏstane viditelnรฉ pล™ed automatickรฝm skrytรญm.", + "duration-title": "ฤŒasovรฝ limit automatickรฉho skrytรญ", + "enabled-description": "Zobrazit zmฤ›ny hlasitosti a jasu v reรกlnรฉm ฤase.", + "enabled-label": "Povolit OSD", + "events-desc": "Vybrat, kterรฉ udรกlosti spustรญ OSD.", + "general-desc": "Nastavit viditelnost a chovรกnรญ OSD.", + "location-description": "Kde se OSD zobrazuje na obrazovce.", + "monitors-desc": "Zobrazit OSD na konkrรฉtnรญch monitorech. Vรฝchozรญ je zobrazenรญ na vลกech.", + "title": "Zobrazenรญ na obrazovce (OSD)", + "types-brightness-description": "Zobrazit OSD pล™i zmฤ›nฤ› jasu obrazovky.", + "types-brightness-label": "Jas", + "types-custom-text-description": "Zobrazit OSD pro vlastnรญ textovรฉ zprรกvy z IPC.", + "types-custom-text-label": "Vlastnรญ text", + "types-desc": "Vybrat udรกlosti, kterรฉ spustรญ OSD.", + "types-input-volume-description": "Zobrazit OSD pล™i zmฤ›nฤ› hlasitosti mikrofonu.", + "types-input-volume-label": "Vstupnรญ hlasitost", + "types-lockkey-description": "Zobrazit OSD pล™i pล™epnutรญ Caps Lock, Num Lock nebo Scroll Lock.", + "types-lockkey-label": "Zamykacรญ klรกvesy", + "types-media-description": "Zobrazit OSD pล™i zmฤ›nฤ› stavu pล™ehrรกvรกnรญ mรฉdiรญ (pล™ehrรกt, pozastavit, pล™eskoฤit).", + "types-media-label": "Pล™ehrรกvรกnรญ mรฉdiรญ", + "types-title": "Udรกlosti spouลกtฤ›jรญcรญ OSD", + "types-volume-description": "Zobrazit OSD pล™i zmฤ›nฤ› hlasitosti zvukovรฉho vรฝstupu.", + "types-volume-label": "Vรฝstupnรญ hlasitost" + }, + "plugins": { + "auto-update": "Automaticky aktualizovat pluginy", + "auto-update-description": "Automaticky aktualizovat vลกechny pluginy pล™i spuลกtฤ›nรญ shellu.", + "available-description": "Prochรกzet a instalovat pluginy z nakonfigurovanรฝch zdrojลฏ.", + "available-label": "Dostupnรฉ pluginy", + "available-no-plugins-description": "Zkontrolujte zdroje pluginลฏ nebo obnovte seznam.", + "available-no-plugins-label": "ลฝรกdnรฉ dostupnรฉ pluginy", + "check-for-updates": "Zkontrolovat aktualizace", + "checking-for-updates": "Kontrola aktualizacรญ...", + "collision-already-installed": "Tento plugin je jiลพ nainstalovรกn", + "collision-custom-version-exists": "Vlastnรญ verze ze zdroje \"{source}\" je jiลพ nainstalovรกna", + "collision-official-version-exists": "Oficiรกlnรญ verze tohoto pluginu je jiลพ nainstalovรกna", + "development-disable": "Zakรกzat vรฝvojovรฝ reลพim pro tento plugin", + "development-enable": "Povolit vรฝvojovรฝ reลพim pro tento plugin", + "filter-downloaded": "Staลพenรฉ", + "filter-not-downloaded": "Nestaลพenรฉ", + "filter-tags-description": "Filtrovat pluginy podle kategorie nebo stavu staลพenรญ.", + "filter-tags-label": "ล tรญtky", + "hot-reload-description": "Automaticky znovu naฤรญst pluginy pล™i zmฤ›nฤ› jejich souborลฏ. Uลพiteฤnรฉ pro vรฝvoj pluginลฏ.", + "hot-reload-label": "Horkรฉ naฤtenรญ (vรฝvojovรฝ reลพim)", + "hot-reloaded": "Plugin znovu naฤten: {name}", + "install-error": "Instalace selhala: {error}", + "install-incompatible": "{plugin} vyลพaduje Noctalia v{version} nebo novฤ›jลกรญ", + "install-success": "รšspฤ›ลกnฤ› nainstalovรกn {plugin}", + "installed-description": "Spravovat a konfigurovat vลกechny mรญstnฤ› nainstalovanรฉ pluginy.", + "installed-label": "Nainstalovanรฉ pluginy", + "installed-no-plugins-description": "Nainstalujte pluginy ze sekce Dostupnรฉ.", + "installed-no-plugins-label": "ลฝรกdnรฉ nainstalovanรฉ pluginy", + "installing": "Instalace {plugin}...", + "notify-updates": "Oznรกmenรญ o aktualizacรญch pluginลฏ", + "notify-updates-description": "Zobrazit vyskakovacรญ oznรกmenรญ pล™i dostupnosti novรฝch aktualizacรญ pluginลฏ.", + "open-plugin-page": "Otevล™รญt strรกnku pluginu", + "open-plugins-tab": "Otevล™รญt nastavenรญ pluginลฏ", + "plugin-settings-title": "Nastavenรญ {plugin}", + "refresh-refreshing": "Obnovovรกnรญ seznamu pluginลฏ...", + "refresh-tooltip": "Obnovit dostupnรฉ pluginy", + "settings-error-not-loaded": "Plugin nenรญ naฤten", + "settings-saved": "Nastavenรญ pluginu uloลพeno", + "settings-tooltip": "Nastavenรญ pluginu", + "source-custom": "Vlastnรญ zdroj", + "sources-add-custom": "Pล™idat vlastnรญ repozitรกล™", + "sources-add-dialog-description": "Pล™idat repozitรกล™ GitHub jako zdroj pluginลฏ.", + "sources-add-dialog-error": "Pล™idรกnรญ zdroje pluginu selhalo", + "sources-add-dialog-name": "Nรกzev repozitรกล™e", + "sources-add-dialog-name-placeholder": "Moje vlastnรญ pluginy", + "sources-add-dialog-success": "Zdroj pluginu รบspฤ›ลกnฤ› pล™idรกn", + "sources-add-dialog-title": "Pล™idat zdroj pluginu", + "sources-add-dialog-url": "URL repozitรกล™e", + "sources-description": "Spravovat repozitรกล™e pluginลฏ.", + "sources-placeholder": "Mลฏj skvฤ›lรฝ repozitรกล™", + "sources-remove-tooltip": "Odebrat zdroj pluginu", + "title": "Pluginy", + "translations-reloaded": "Pล™eklady znovu naฤteny: {name}", + "uninstall-dialog-description": "Opravdu chcete odinstalovat {plugin}? Tรญm se odstranรญ vลกechna data pluginu.", + "uninstall-dialog-title": "Odinstalovat plugin", + "uninstall-error": "Odinstalovรกnรญ selhalo: {error}", + "uninstall-success": "รšspฤ›ลกnฤ› odinstalovรกn {plugin}", + "uninstalling": "Odinstalovรกnรญ {plugin}...", + "update-all": "Aktualizovat vลกe ({count})", + "update-all-success": "Vลกechny pluginy รบspฤ›ลกnฤ› aktualizovรกny", + "update-available": "Dostupnรก novรก aktualizace pluginu", + "update-available-plural": "Dostupnรฉ aktualizace pluginลฏ ({count})", + "update-error": "Aktualizace pluginu selhala: {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (vyลพaduje Noctalia v{required})", + "update-success": "Plugin {plugin} aktualizovรกn na v{version}", + "update-version": "v{current} โ†’ v{new}", + "updating": "Aktualizovรกnรญ..." + }, + "region": { + "title": "Region" + }, + "session-menu": { + "countdown-duration-description": "Nastavit dรฉlku odpoฤtu pล™ed provedenรญm akcรญ napรกjenรญ.", + "countdown-duration-label": "Dรฉlka odpoฤtu", + "enable-countdown-description": "Zobrazit odpoฤet pล™ed provedenรญm akcรญ napรกjenรญ.", + "enable-countdown-label": "Povolit odpoฤet", + "entries-desc": "Pล™izpลฏsobit, kterรฉ akce napรกjenรญ se zobrazujรญ v menu relace a v jakรฉm poล™adรญ.", + "entries-title": "Akce napรกjenรญ", + "entry-settings-command-description": "Vlastnรญ pล™รญkaz pro tuto akci. Ponechte prรกzdnรฉ pro pouลพitรญ vรฝchozรญho systรฉmovรฉho pล™รญkazu.", + "entry-settings-command-placeholder": "napล™. systemctl poweroff", + "entry-settings-default-command-lock": "Internรญ zamykacรญ obrazovka (bez pล™รญkazu)", + "entry-settings-default-command-logout": "Internรญ odhlรกลกenรญ (bez pล™รญkazu)", + "entry-settings-default-info-description": "Pokud nenรญ zadรกn vlastnรญ pล™รญkaz, pouลพije se vรฝchozรญ systรฉmovรฝ pล™รญkaz.", + "entry-settings-default-info-label": "Vรฝchozรญ pล™รญkaz", + "entry-settings-keybind-description": "Pล™iล™adit kombinaci klรกves pro spuลกtฤ›nรญ tรฉto akce pล™i otevล™enรฉm menu relace.", + "entry-settings-keybind-placeholder": "Stisknฤ›te Nahrรกt pro pล™iล™azenรญ klรกvesy...", + "entry-settings-keybind-recording": "Nahrรกvรกnรญ... Stisknฤ›te libovolnou kombinaci klรกves", + "entry-settings-title": "Konfigurovat {entry}", + "entry-settings-tooltip": "Konfigurovat pล™รญkaz", + "general-desc": "Nastavit chovรกnรญ a vzhled panelu menu relace.", + "large-buttons-layout-description": "Vybrat zpลฏsob zobrazenรญ tlaฤรญtek menu relace.", + "large-buttons-layout-label": "Rozloลพenรญ velkรฝch tlaฤรญtek", + "large-buttons-style-description": "Zobrazit menu relace s velkรฝmi tlaฤรญtky v mล™รญลพkovรฉm rozloลพenรญ.", + "large-buttons-style-label": "Styl velkรฝch tlaฤรญtek", + "position-description": "Vybrat, kde se panel menu relace zobrazรญ pล™i otevล™enรญ.", + "show-header-description": "Zobrazit nรกzev a tlaฤรญtko zavล™enรญ v hornรญ ฤรกsti menu relace.", + "show-header-label": "Zobrazit zรกhlavรญ", + "show-keybinds-description": "Zobrazit nรกpovฤ›du klรกvesovรฝch zkratek u moลพnostรญ relace.", + "show-keybinds-label": "Zobrazit klรกvesovรฉ zkratky" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Udrลพet widgety plochy viditelnรฉ pล™i zapnutรฉm vรฝkonnostnรญm reลพimu Noctalia.", + "noctalia-performance-disable-desktop-widgets-label": "Povolit widgety plochy ve vรฝkonnostnรญm reลพimu", + "noctalia-performance-disable-wallpaper-description": "Udrลพet tapety plochy, pล™ehledu a zamykacรญ obrazovky viditelnรฉ pล™i zapnutรฉm vรฝkonnostnรญm reลพimu Noctalia.", + "noctalia-performance-disable-wallpaper-label": "Povolit vykreslovรกnรญ tapety ve vรฝkonnostnรญm reลพimu", + "title": "Systรฉm" + }, + "system-monitor": { + "critical-color-label": "Kritickรก barva", + "custom-highlight-colors-title-label": "Vlastnรญ barvy zvรฝraznฤ›nรญ", + "disk-available-label": "Dostupnรฉ mรญsto na disku", + "disk-section-label": "Vyuลพitรญ disku", + "enable-dgpu-monitoring-description": "Upozornฤ›nรญ: Toto probudรญ vaลกi diskrรฉtnรญ GPU (NVIDIA/AMD), coลพ mลฏลพe vรฝraznฤ› ovlivnit vรฝdrลพ baterie na laptopech s hybridnรญ grafikou.", + "enable-dgpu-monitoring-label": "Povolit sledovรกnรญ diskrรฉtnรญ GPU", + "external-monitor-description": "Zadejte pล™รญkaz nebo cestu k aplikaci pro spuลกtฤ›nรญ externรญho systรฉmovรฉho monitoru.", + "external-monitor-label": "Pล™รญkaz externรญho systรฉmovรฉho monitoru", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "Nastavit chovรกnรญ a vzhled systรฉmovรฉho monitoru.", + "gpu-section-label": "Teplota GPU", + "highlight-colors-section-label": "Barvy zvรฝraznฤ›nรญ", + "polling-interval-label": "Interval dotazovรกnรญ", + "polling-section-description": "Nastavit, jak ฤasto se aktualizuje kaลพdรก systรฉmovรก metrika.", + "polling-section-label": "Intervaly dotazovรกnรญ", + "threshold-critical": "Kritickรก prahovรก hodnota", + "threshold-warning": "Prahovรก hodnota varovรกnรญ", + "thresholds-section-description": "Upravit prahovรฉ hodnoty varovรกnรญ/kritickรฉ pro kaลพdou systรฉmovou metriku.", + "use-custom-highlight-colors-description": "Pล™i vypnutรญ se pouลพijรญ vรฝchozรญ barvy zvรฝraznฤ›nรญ motivu.", + "use-custom-highlight-colors-label": "Pouลพรญt vlastnรญ barvy zvรฝraznฤ›nรญ", + "warning-color-label": "Barva varovรกnรญ" + }, + "user-interface": { + "allow-panels-without-bar-description": "Pokud je povoleno, panely se mohou otevล™รญt na libovolnรฉ obrazovce. Pล™i vypnutรญ se panely otevล™ou pouze na obrazovkรกch s liลกtou, coลพ mลฏลพe snรญลพit spotล™ebu pamฤ›ti.", + "allow-panels-without-bar-label": "Povolit panely na obrazovkรกch bez liลกty", + "animation-disable-description": "Zakรกzat vลกechny animace pro rychlejลกรญ a responzivnฤ›jลกรญ zรกลพitek.", + "animation-disable-label": "Zakรกzat animace UI", + "animation-speed-description": "Upravit globรกlnรญ rychlost animacรญ.", + "animation-speed-label": "Rychlost animacรญ", + "animation-speed-reset": "Obnovit rychlost animacรญ", + "appearance-desc": "Pล™izpลฏsobit vizuรกlnรญ prvky, jako jsou tooltips, okraje a stรญny.", + "blur-behind-description": "Rozmazรกvรก oblast za liลกtami a panely pomocรญ protocolu blur compositoru.", + "blur-behind-label": "Rozmazรกnรญ za objekty", + "box-border-description": "Zobrazit obrys kolem obsahovรฝch oblastรญ.", + "box-border-label": "Obrys kontejneru", + "box-border-radius-description": "Upravuje zaoblenรญ rohลฏ hlavnรญch ฤรกstรญ rozloลพenรญ, jako jsou postrannรญ panely, karty a obsahovรฉ panely.", + "box-border-radius-label": "Polomฤ›r kontejneru", + "control-border-radius-description": "ล˜รญdรญ zaoblenรญ interaktivnรญch prvkลฏ, vฤetnฤ› tlaฤรญtek, pล™epรญnaฤลฏ a textovรฝch polรญ.", + "control-border-radius-label": "Polomฤ›r vstupลฏ", + "desc": "Pล™izpลฏsobit vzhled, pocit a chovรกnรญ rozhranรญ.", + "dim-desktop-description": "Ztmavit plochu pล™i otevล™enรฝch panelech nebo menu.", + "dim-desktop-label": "Ztmavit plochu", + "dimmer-opacity-description": "Nastavit รบroveลˆ prลฏhlednosti ztmavenรญ plochy.", + "dimmer-opacity-label": "Prลฏhlednost ztmavenรฉ plochy", + "panel-background-opacity-description": "Nastavit prลฏhlednost pozadรญ pro vลกechny panely (liลกta, spouลกtฤ›ฤ, nastavenรญ atd.).", + "panel-background-opacity-label": "Prลฏhlednost pozadรญ panelลฏ", + "panels-attached-to-bar-description": "Panely se uzamknou k liลกtฤ› a okrajลฏm obrazovky, ฤรญmลพ vytvoล™รญ plynulรฝ vzhled se stylovรฝmi zaoblenรฝmi rohy.", + "panels-attached-to-bar-label": "Pล™ichytit panely k okrajลฏm", + "panels-overlay-description": "Zajiลกลฅuje, aby panely a liลกta zลฏstaly viditelnรฉ i nad celooobrazovkovรฝmi aplikacemi.", + "panels-overlay-label": "Udrลพet panely a liลกtu nahoล™e", + "scaling-description": "Mฤ›nรญ velikost obecnรฉho uลพivatelskรฉho rozhranรญ, s vรฝjimkou liลกty.", + "scaling-label": "ล kรกlovรกnรญ rozhranรญ", + "scrollbar-always-visible-description": "Udrลพet posuvnรญky vลพdy viditelnรฉ, kdyลพ lze obsah posouvat, mรญsto zobrazenรญ jen pล™i najetรญ.", + "scrollbar-always-visible-label": "Vลพdy zobrazit posuvnรญky", + "settings-panel-header": "Panel nastavenรญ", + "settings-panel-mode-description": "Vybrat rozloลพenรญ nastavenรญ (mลฏลพe vyลพadovat znovu otevล™enรญ).", + "settings-panel-mode-label": "Reลพim panelu nastavenรญ", + "settings-panel-sidebar-card-style-description": "Zabalรญ postrannรญ panel nastavenรญ do vyplnฤ›nรฉho pozadรญ se zaoblenรฝmi rohy.", + "settings-panel-sidebar-card-style-label": "Styl karty postrannรญho panelu", + "shadows-description": "Povolรญ vrhรกnรญ stรญnลฏ pod liลกty a panely.", + "shadows-direction-description": "Vybrat smฤ›r vrhรกnรญ stรญnu.", + "shadows-direction-label": "Smฤ›r stรญnu", + "shadows-label": "Vrhรกnรญ stรญnลฏ", + "title": "Uลพivatelskรฉ rozhranรญ", + "tooltips-description": "Povolit nebo zakรกzat tooltips v celรฉm rozhranรญ.", + "tooltips-label": "Zobrazit tooltips", + "translucent-widgets-description": "Udฤ›lat tlaฤรญtka, zรกloลพky a dalลกรญ widgety uvnitล™ panelลฏ poloprลฏhlednรฉ.", + "translucent-widgets-label": "Poloprลฏhlednรฉ widgety" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "Abecednฤ›", + "automation-change-mode-description": "Vybrat zpลฏsob vรฝbฤ›ru tapet pล™i automatickรฉ zmฤ›nฤ›.", + "automation-change-mode-label": "Reลพim zmฤ›ny", + "automation-custom-interval-description": "Zadejte ฤas ve formรกtu HH:MM (napล™. 01:30).", + "automation-custom-interval-label": "Vlastnรญ interval", + "automation-interval-description": "Jak ฤasto automaticky mฤ›nit tapety.", + "automation-interval-label": "Interval tapety", + "automation-random-wallpaper-description": "Naplรกnovat nรกhodnรฉ zmฤ›ny tapety v pravidelnรฝch intervalech.", + "automation-scheduled-change-description": "Automaticky mฤ›nit tapety v pravidelnรฝch intervalech.", + "automation-scheduled-change-label": "Plรกnovanรก zmฤ›na", + "look-feel-edge-smoothness-description": "Aplikuje mฤ›kkรฝ, rozostล™enรฝ efekt na okraj pล™echodลฏ.", + "look-feel-edge-smoothness-label": "Zmฤ›kฤit okraj pล™echodu", + "look-feel-fill-color-description": "Vybrat barvu vรฝplnฤ›, kterรก se mลฏลพe zobrazit za tapetou.", + "look-feel-fill-mode-description": "Vybrat zpลฏsob ลกkรกlovรกnรญ obrรกzku pro pล™izpลฏsobenรญ rozliลกenรญ monitoru.", + "look-feel-fill-mode-label": "Reลพim vyplnฤ›nรญ", + "look-feel-skip-startup-transition-description": "Pล™eskoฤit animaci tapety pล™i spuลกtฤ›nรญ shellu.", + "look-feel-skip-startup-transition-label": "Pล™eskoฤit pล™echod pล™i spuลกtฤ›nรญ", + "look-feel-title": "Vzhled a pocit", + "look-feel-transition-duration-description": "Trvรกnรญ animacรญ pล™echodu v sekundรกch.", + "look-feel-transition-duration-label": "Trvรกnรญ pล™echodu", + "look-feel-transition-type-description": "Typ animace pล™i pล™epรญnรกnรญ tapety.", + "look-feel-transition-type-label": "Typ pล™echodu", + "settings-clear-cache-button": "Vymazat mezipamฤ›ลฅ", + "settings-clear-cache-description": "Vymazat mezipamฤ›ลฅ zmฤ›nฤ›nรฝch velikostรญ tapet pro uvolnฤ›nรญ mรญsta na disku.", + "settings-clear-cache-label": "Mezipamฤ›ลฅ tapety", + "settings-clear-cache-toast": "Mezipamฤ›ลฅ tapety vymazรกna", + "settings-desc": "ล˜รญdit sprรกvu a zobrazenรญ tapet.", + "settings-enable-management-description": "Spravovat tapety pล™es Noctalia. Odลกkrtnฤ›te, pokud preferujete jinou aplikaci.", + "settings-enable-management-label": "Povolit sprรกvu tapety", + "settings-enable-overview-description": "Aplikuje rozmazanou a ztmavenou tapetu na obrazovku pล™ehledu.", + "settings-enable-overview-label": "Povolit tapetu pล™ehledu", + "settings-folder-description": "Cesta k hlavnรญ sloลพce tapet.", + "settings-folder-label": "Sloลพka tapet", + "settings-hide-wallpaper-filenames-tooltip-hide": "Skrรฝt nรกzvy souborลฏ", + "settings-hide-wallpaper-filenames-tooltip-show": "Zobrazit nรกzvy souborลฏ", + "settings-monitor-specific-description": "Nastavit jinou sloลพku tapet pro kaลพdรฝ monitor.", + "settings-monitor-specific-label": "Sloลพky pro kaลพdรฝ monitor", + "settings-monitor-specific-tooltip": "Sloลพka tapet monitoru", + "settings-overview-blur-strength-description": "Aplikuje sรญlu rozmazรกnรญ na pล™ehled.", + "settings-overview-blur-strength-label": "Sรญla rozmazรกnรญ pล™ehledu", + "settings-overview-tint-description": "Aplikuje sรญlu tรณnu na pล™ehled.", + "settings-overview-tint-label": "Sรญla tรณnu pล™ehledu", + "settings-recursive-search-description": "Hledat tapety takรฉ v podsloลพkรกch sloลพky tapet.", + "settings-recursive-search-label": "Prohledรกvat podsloลพky", + "settings-select-monitor-folder": "Vybrat sloลพku tapet monitoru", + "settings-selector-description": "Vybrat tapetu.", + "settings-selector-position-description": "Vybrat, kde se panel vรฝbฤ›ru tapety zobrazรญ.", + "settings-show-hidden-files-tooltip-hide": "Skrรฝt skrytรฉ soubory", + "settings-show-hidden-files-tooltip-show": "Zobrazit skrytรฉ soubory", + "settings-title": "Nastavenรญ tapety", + "settings-use-original-images-description": "Pล™eskoฤit zmฤ›nu velikosti tapet pล™ed zobrazenรญm. ล etล™รญ mรญsto na disku a sniลพuje vyuลพitรญ CPU pล™i zmฤ›nฤ› tapety, ale mลฏลพe vyuลพรญvat vรญce pamฤ›ti pro velmi velkรฉ obrรกzky.", + "settings-use-original-images-label": "Pouลพรญt originรกlnรญ obrรกzky", + "settings-view-mode-description": "Vybrat zpลฏsob zobrazenรญ tapet z vaลกรญ sloลพky.", + "settings-view-mode-label": "Reลพim zobrazenรญ", + "view-mode-browse": "Prochรกzet sloลพky", + "view-mode-cycle-tooltip": "Reลพim zobrazenรญ: {mode} (kliknutรญm zmฤ›nit)", + "view-mode-recursive": "Slouฤenรฉ podsloลพky", + "view-mode-single": "Koล™enovรก sloลพka" + } + }, + "placeholders": { + "add-new-keybind": "Pล™idat novou klรกvesovou zkratku", + "command-example": "echo \"Ahoj svฤ›te\"", + "enter-command": "Zadejte pล™รญkaz (aplikace nebo vlastnรญ skript)", + "enter-ipc-identifier": "Zadejte jedineฤnรฝ identifikรกtor pro pล™รญkazy IPC", + "enter-path": "Zadejte cestu...", + "enter-text-to-collapse": "napล™. 'nic nehraje'. Pouลพijte /regex/ pro vzory.", + "enter-tooltip": "Zadejte tooltip", + "enter-width-pixels": "Zadejte ลกรญล™ku v pixelech", + "keybind-recording": "Nahrรกvรกnรญ zkratky...", + "search": "Hledat...", + "search-icons": "napล™. noctalia, niri, baterie, oblak", + "search-launcher": "Hledat... nebo pouลพijte > pro pล™รญkazy", + "search-wallhaven": "Hledat na Wallhaven...", + "search-wallpapers": "Zadejte pro filtrovรกnรญ tapet..." + }, + "positions": { + "bottom": "Dole", + "bottom-center": "Dole uprostล™ed", + "bottom-left": "Dole vlevo", + "bottom-right": "Dole vpravo", + "center": "Uprostล™ed", + "center-left": "Vlevo uprostล™ed", + "center-right": "Vpravo uprostล™ed", + "close-to-bar": "Blรญzko tlaฤรญtka liลกty", + "follow-bar": "Sledovat liลกtu", + "left": "Vlevo", + "right": "Vpravo", + "top": "Nahoล™e", + "top-center": "Nahoล™e uprostล™ed", + "top-left": "Nahoล™e vlevo", + "top-right": "Nahoล™e vpravo" + }, + "session-menu": { + "action-in-seconds": "{action} za {seconds} sekund...", + "cancel-timer": "Zruลกit odpoฤet", + "title": "Menu relace" + }, + "setup": { + "all-done": "Hotovo!", + "appearance": { + "subheader": "Vyberte tmavรฝ reลพim a zdroje barev (tapeta nebo pล™eddefinovanรฉ)." + }, + "customize": { + "header": "Pล™izpลฏsobte si zรกลพitek", + "subheader": "Upravte pozici liลกty, hustotu, ลกkรกlovรกnรญ a dalลกรญ." + }, + "skip-setup": "Pล™eskoฤit nastavenรญ", + "telemetry-wizard-done": "Rozumรญm!", + "telemetry-wizard-note": "Mรกte plnou kontrolu โ€“ kdykoli to zapnฤ›te nebo vypnฤ›te v nastavenรญ", + "telemetry-wizard-subtitle": "Pล™idali jsme anonymnรญ analytiku pro zlepลกenรญ Noctalia", + "telemetry-wizard-title": "Aktualizace soukromรญ", + "wallpaper": { + "choose-dir": "Vyberte sloลพku obsahujรญcรญ obrรกzky tapet", + "dir-browse": "Prochรกzet sloลพku tapet", + "dir-description": "Vyberte sloลพku obsahujรญcรญ vaลกe tapety.", + "dir-label": "Sloลพka tapet", + "dir-select-title": "Vybrat sloลพku tapet", + "header": "Vyberte tapetu", + "no-dir": "Nenรญ vybrรกna ลพรกdnรก sloลพka tapet", + "no-valid": "Ve sloลพce nebyly nalezeny ลพรกdnรฉ platnรฉ obrรกzky: {dir}", + "none-in-dir": "Ve sloลพce nebyly nalezeny ลพรกdnรฉ tapety", + "preview-error": "Nepodaล™ilo se naฤรญst obrรกzek", + "select-prompt": "Vyberte tapetu nรญลพe", + "subheader": "Nastavte nรกladu s krรกsnรฝm pozadรญm." + }, + "welcome": "Vรญtejte", + "welcome-note": "Jen pรกr zรกkladลฏ pro zaฤรกtek โ€“ รบplnรฉ moลพnosti najdete v nastavenรญ", + "welcome-subtitle": "Pojฤme udฤ›lat vรกลก desktop jedineฤnรฝm", + "welcome-title": "Vรญtejte v Noctalia!" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "ลฝรกdnรฝ pล™ehrรกvaฤ mรฉdiรญ nenalezen", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "Neznรกmรก aplikace", + "uptime": "Provoznรญ doba: {uptime}", + "user-requested": "Poลพadavek uลพivatele", + "welcome-back": "Vรญtej zpฤ›t,", + "widget-settings-title": "Nastavenรญ {widget}" + }, + "system-monitor": { + "core-usage": "Vyuลพitรญ jรกdra {id}", + "cpu-temp": "Teplota CPU", + "cpu-usage": "Vyuลพitรญ CPU", + "disk": "Disk", + "download-speed": "Rychlost stahovรกnรญ", + "gpu-temp": "Teplota GPU", + "load-average": "Prลฏmฤ›rnรฉ zatรญลพenรญ", + "title": "Systรฉmovรฝ monitor", + "upload-speed": "Rychlost odesรญlรกnรญ" + }, + "toast": { + "airplane-mode": { + "description": "Zakรกzat veลกkerou bezdrรกtovou komunikaci.", + "title": "Reลพim letadlo" + }, + "battery": { + "critical": "Kritickรฝ stav baterie", + "critical-desc": "Baterie je na {percent}% โ€“ okamลพitฤ› pล™ipojte nabรญjeฤku", + "low": "Nรญzkรฝ stav baterie", + "low-desc": "Baterie je na {percent}% โ€“ pล™ipojte nabรญjeฤku" + }, + "bluetooth": { + "address-copied": "Adresa zkopรญrovรกna do schrรกnky", + "auto-connect-disabled": "Automatickรฉ pล™ipojenรญ zakรกzรกno", + "auto-connect-enabled": "Automatickรฉ pล™ipojenรญ povoleno", + "auto-connecting": "Pล™ipojovรกnรญ k {count} zaล™รญzenรญ(m)...", + "confirm-code": "Potvrฤte kรณd {value} na druhรฉm zaล™รญzenรญ.", + "connect-failed": "Pล™ipojenรญ k zaล™รญzenรญ selhalo", + "disconnect-failed": "Odpojenรญ od zaล™รญzenรญ selhalo", + "discoverable-change-failed": "Zmฤ›na stavu viditelnosti selhala", + "discoverable-disabled": "Viditelnost zakรกzรกna", + "discoverable-enabled": "Viditelnost povolena", + "display-code": "Zadejte kรณd {value} na druhรฉm zaล™รญzenรญ.", + "forget-failed": "Zapomenutรญ zaล™รญzenรญ selhalo", + "pair-failed": "Pรกrovรกnรญ zaล™รญzenรญ selhalo", + "passkey-required": "Zaล™รญzenรญ vyลพaduje pล™รญstupovรฝ klรญฤ.", + "pincode-required": "Zaล™รญzenรญ vyลพaduje PIN kรณd.", + "state-change-failed": "Zmฤ›na stavu Bluetooth selhala" + }, + "clipboard": { + "long-text": "Dlouhรฝ text", + "unavailable": "Historie schrรกnky nenรญ dostupnรก", + "unavailable-desc": "Aplikace 'cliphist' nenรญ nainstalovรกna. Nainstalujte ji pro pouลพitรญ funkcรญ historie schrรกnky" + }, + "custom-command-failed": { + "description": "Pล™รญkaz selhal: {command}\nKรณd ukonฤenรญ: {code}", + "title": "Vlastnรญ pล™รญkaz selhal" + }, + "do-not-disturb": { + "disabled": "Neruลกit zakรกzรกno", + "disabled-desc": "Zobrazuji vลกechna oznรกmenรญ", + "enabled": "Neruลกit povoleno", + "enabled-desc": "Tato oznรกmenรญ najdete v historii" + }, + "donation-opened": "Strรกnka pro podporu otevล™ena v prohlรญลพeฤi", + "internet-limited": "Pล™ipojeno bez internetu", + "keyboard-layout": { + "changed": "Rozloลพenรญ klรกvesnice zmฤ›nฤ›no na {layout}", + "title": "Klรกvesnice" + }, + "missing-control-center": { + "description": "Widget ล™รญdicรญho centra byl odebrรกn z liลกty. Pro pล™รญstup z liลกty znovu pล™idejte widget. Mลฏลพete ho otevล™รญt takรฉ pravรฝm kliknutรญm na liลกtu.", + "label": "Poslednรญ widget ล™รญdicรญho centra odebrรกn" + }, + "night-light": { + "forced": "Vynucenรก aktivace", + "normal": "Normรกlnรญ reลพim", + "not-installed": "wlsunset nenรญ nainstalovรกn" + }, + "noctalia-performance": { + "disabled": "Vรฝkonnostnรญ reลพim zakรกzรกn", + "enabled": "Vรฝkonnostnรญ reลพim povolen", + "label": "Vรฝkon Noctalia" + }, + "power-profile": { + "changed": "Profil napรกjenรญ zmฤ›nฤ›n", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "Pล™i zpracovรกnรญ ลกablon nastala chyba", + "title": "Zpracovรกnรญ ลกablon selhalo" + }, + "vpn": { + "connected": "Pล™ipojeno k '{name}'", + "disconnected": "Odpojeno od '{name}'" + }, + "wallpaper-colors": { + "disabled": "Barvy tapety zakรกzรกny", + "enabled": "Barvy tapety povoleny", + "label": "Barvy tapety" + }, + "wifi": { + "connected": "Pล™ipojeno k '{ssid}'", + "connection-failed": "Pล™ipojenรญ selhalo", + "connection-timeout": "ฤŒasovรฝ limit pล™ipojenรญ", + "disconnected": "Odpojeno od '{ssid}'", + "incorrect-password": "Nesprรกvnรฉ heslo", + "network-not-found": "Sรญลฅ nenalezena" + } + }, + "tooltips": { + "add-widget": "Pล™idat widget", + "auto-connect": "Pล™epnout automatickรฉ pล™ipojenรญ pro toto zaล™รญzenรญ", + "bluetooth-auto-connect-off": "Automatickรฉ pล™ipojenรญ je vypnuto", + "bluetooth-auto-connect-on": "Automatickรฉ pล™ipojenรญ je zapnuto", + "bluetooth-devices": "Bluetooth zaล™รญzenรญ", + "brightness-at": "Jas: {brightness}%", + "click-to-start-recording": "Zรกznam obrazovky (zahรกjit nahrรกvรกnรญ)", + "click-to-stop-recording": "Zรกznam obrazovky (zastavit nahrรกvรกnรญ)", + "collapse": "Sbalit postrannรญ panel", + "copy-address": "Zkopรญrovat adresu", + "delete-notification": "Smazat oznรกmenรญ", + "dismiss-notification": "Zavล™รญt oznรกmenรญ", + "do-not-disturb-enabled": "Neruลกit", + "expand": "Rozbalit postrannรญ panel", + "forget-network": "Zapomenout sรญลฅ", + "grid-view": "Mล™รญลพkovรฉ zobrazenรญ", + "hidden-files-hide": "Skrytรฉ soubory", + "hide-unnamed-devices": "Skrรฝt nepojmenovanรก zaล™รญzenรญ", + "home": "Domovskรฝ adresรกล™", + "input-muted": "Pล™epnout ztlumenรญ vstupu", + "keep-awake": "Udrลพet vzhลฏru", + "keyboard-layout": "Rozloลพenรญ klรกvesnice {layout}", + "list-view": "Zobrazenรญ seznamu", + "manage-vpn": "VPN pล™ipojenรญ", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "Dosaลพen maximรกlnรญ poฤet widgetลฏ", + "microphone-volume-at": "Hlasitost mikrofonu: {volume}%", + "move-to-section": "Pล™esunout do {section}", + "mute": "Ztlumit", + "next-media": "Dalลกรญ stopa", + "next-month": "Dalลกรญ mฤ›sรญc", + "night-light-not-installed": "Noฤnรญ svฤ›tlo (nedostupnรฉ)", + "noctalia-performance-enabled": "Vรฝkonnostnรญ reลพim Noctalia", + "open-annotation-tool": "Otevล™รญt s nรกstrojem anotace", + "open-control-center": "ล˜รญdicรญ centrum", + "open-notification-history-enable-dnd": "Historie oznรกmenรญ", + "open-settings": "Nastavenรญ", + "open-tray-dropdown": "Systรฉmovรฝ zรกsobnรญk", + "output-muted": "Pล™epnout ztlumenรญ vรฝstupu", + "power-profile": "Profil napรกjenรญ {profile}", + "previous-media": "Pล™edchozรญ stopa", + "previous-month": "Pล™edchozรญ mฤ›sรญc", + "refresh-devices": "Obnovit zaล™รญzenรญ", + "refresh-wallhaven": "Obnovit vรฝsledky Wallhaven", + "refresh-wallpaper-list": "Obnovit seznam tapet", + "remove-widget": "Odebrat widget", + "search-close": "Zavล™รญt hledรกnรญ", + "session-menu": "Menu relace", + "show-all-devices": "Zobrazit vลกechna zaล™รญzenรญ", + "switch-to-dark-mode": "Tmavรฝ reลพim", + "switch-to-light-mode": "Svฤ›tlรฝ reลพim", + "unmute": "Zruลกit ztlumenรญ", + "up": "Nadล™azenรก sloลพka", + "volume-at": "Vรฝstupnรญ hlasitost: {volume}%", + "wallpaper-selector": "Vรฝbฤ›r tapety" + }, + "wallpaper": { + "browse": { + "empty-directory": "Tato sloลพka je prรกzdnรก.", + "go-root": "Pล™ejรญt do koล™enovรฉ sloลพky tapet", + "go-up": "Pล™ejรญt do nadล™azenรฉ sloลพky", + "go-up-hint": "Pro pล™echod nahoru pouลพijte tlaฤรญtko zpฤ›t." + }, + "configure-directory": "Nastavte sloลพku tapet s obrรกzky.", + "fill-modes": { + "crop": "Oล™รญznout (vyplnit)", + "fit": "Pล™izpลฏsobit (obsahovat)", + "repeat": "Opakovat (dlaลพdice)", + "stretch": "Roztรกhnout" + }, + "no-match": "ลฝรกdnรก shoda nenalezena.", + "no-wallpaper": "ลฝรกdnรก tapeta nenalezena.", + "panel": { + "apikey-help": "Pro pล™รญstup k obsahu NSFW je vyลพadovรกn klรญฤ API.", + "apikey-label": "Klรญฤ API", + "apikey-managed-by-env": "Spravovรกno pล™es promฤ›nnou prostล™edรญ NOCTALIA_WALLHAVEN_API_KEY.", + "apikey-placeholder": "Zadejte vรกลก klรญฤ Wallhaven API", + "appearance-dark-tab": "Tmavรฝ", + "appearance-light-tab": "Svฤ›tlรฝ", + "categories-anime": "Anime", + "categories-label": "Kategorie", + "categories-people": "Lidรฉ", + "color-extraction-disabled": "Pouลพรญt extrakci barev tapety", + "color-extraction-enabled": "Pouลพรญt pล™eddefinovanรก barevnรก schรฉmata", + "header-devices-apply-all-tooltip": "Vybrat tapetu pro kaลพdรฝ monitor", + "header-devices-per-monitor-tooltip": "Pouลพรญt na vลกechny monitory", + "header-link-light-dark-tooltip": "Zachovat tapetu pล™i zmฤ›nฤ› motivu", + "header-separate-light-dark-tooltip": "Automaticky pล™epรญnat tapetu s tรฉmatem", + "header-sun-linked-tooltip": "Oddฤ›lit svฤ›tlรฉ a tmavรฉ tapety", + "header-sun-separate-tooltip": "Stejnรก tapeta pro svฤ›tlรฝ a tmavรฝ reลพim", + "order-asc": "Vzestupnฤ›", + "order-desc": "Sestupnฤ›", + "order-label": "Poล™adรญ", + "purity-label": "Filtr obsahu", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "Pochybnรฉ", + "ratios-all-portrait": "Vลกe na vรฝลกku", + "ratios-all-wide": "Vลกe na ลกรญล™ku", + "ratios-any": "Libovolnรฝ", + "ratios-label": "Pomฤ›r stran", + "resolution-atleast": "Alespoลˆ", + "resolution-exact": "Pล™esnฤ›", + "resolution-label": "Rozliลกenรญ", + "resolution-mode-label": "Reลพim", + "solid-color-tooltip": "Jednobarevnรฉ pozadรญ", + "sort-date-asc": "ล˜adit od nejstarลกรญho", + "sort-date-desc": "ล˜adit od nejnovฤ›jลกรญho", + "sort-name-asc": "ล˜adit podle nรกzvu (Aโ€“Z)", + "sort-name-desc": "ล˜adit podle nรกzvu (Zโ€“A)", + "sort-random": "Nรกhodnรฉ poล™adรญ", + "sorting-date-added": "Datum pล™idรกnรญ", + "sorting-favorites": "Oblรญbenรฉ", + "sorting-label": "ล˜adit podle", + "sorting-relevance": "Relevance", + "sorting-toplist": "ลฝebล™รญฤek", + "sorting-views": "Zobrazenรญ", + "source-label": "Zdroj", + "source-wallhaven": "Wallhaven", + "title": "Vรฝbฤ›r tapety", + "wallhaven-settings-title": "Nastavenรญ Wallhaven" + }, + "transitions": { + "disc": "Disk", + "fade": "Pล™echod", + "honeycomb": "Plรกstev", + "pixelate": "Pixelizace", + "stripes": "Pruhy", + "wipe": "Stรญrรกnรญ" + }, + "try-different-search": "Zkuste jinรฝ vyhledรกvacรญ dotaz.", + "wallhaven": { + "loading": "Naฤรญtรกnรญ tapet...", + "no-results": "ลฝรกdnรฉ tapety nenalezeny. Zkuste jinรฝ vyhledรกvacรญ dotaz.", + "page": "{current} z {total}", + "page-prefix": "Strรกnka", + "page-suffix": "z {total}" + } + }, + "weather": { + "clear-sky": "Jasnรก obloha", + "drizzle": "Mrholenรญ", + "fog": "Mlha", + "mainly-clear": "Pล™evรกลพnฤ› jasno", + "overcast": "Zataลพeno", + "partly-cloudy": "ฤŒรกsteฤnฤ› oblaฤno", + "rain-showers": "Pล™ehรกลˆky", + "snow": "Snรญh", + "thunderstorm": "Bouล™ka" + }, + "widgets": { + "color-picker": { + "palette-description": "Vybรญrejte z ลกirokรฉ ลกkรกly pล™eddefinovanรฝch barev.", + "palette-label": "Paleta", + "palette-theme-colors": "Rychlรฝ pล™รญstup k barevnรฉ paletฤ› vaลกeho motivu.", + "title": "Vรฝbฤ›r barvy" + }, + "datetime-tokens": { + "ampm-lowercase": "am/pm malรฝmi pรญsmeny", + "ampm-uppercase": "AM/PM velkรฝmi pรญsmeny", + "common-12hour-time-minutes": "12hodinovรฝ ฤas s minutami", + "common-24hour-time-minutes": "24hodinovรฝ ฤas s minutami", + "common-24hour-time-seconds": "24hodinovรฝ ฤas s sekundami", + "common-european-date": "Evropskรฝ formรกt data", + "common-iso-date": "ISO formรกt data", + "common-us-date": "Americkรฝ formรกt data", + "common-weekday-date": "Den v tรฝdnu s datem", + "common-weekday-month-day": "Den v tรฝdnu, mฤ›sรญc a den", + "day-abbreviated": "Zkrรกcenรฝ nรกzev dne", + "day-full": "Plnรฝ nรกzev dne", + "day-leading-zero": "Den s vedoucรญ nulou (01โ€“31)", + "day-no-leading-zero": "Den bez vedoucรญ nuly (1โ€“31)", + "hour-leading-zero": "Hodina s vedoucรญ nulou (00โ€“23) โ€“ 24hodinovรฝ formรกt", + "hour-no-leading-zero": "Hodina bez vedoucรญ nuly (0โ€“23) โ€“ 24hodinovรฝ formรกt", + "minute-leading-zero": "Minuta s vedoucรญ nulou (00โ€“59)", + "minute-no-leading-zero": "Minuta bez vedoucรญ nuly (0โ€“59)", + "month-abbreviated": "Zkrรกcenรฝ nรกzev mฤ›sรญce", + "month-full": "Plnรฝ nรกzev mฤ›sรญce", + "month-number-leading-zero": "Mฤ›sรญc jako ฤรญslo s vedoucรญ nulou (01โ€“12)", + "month-number-no-zero": "Mฤ›sรญc jako ฤรญslo bez vedoucรญ nuly (1โ€“12)", + "second-leading-zero": "Sekunda s vedoucรญ nulou (00โ€“59)", + "second-no-leading-zero": "Sekunda bez vedoucรญ nuly (0โ€“59)", + "timezone-abbreviation": "Zkratka ฤasovรฉho pรกsma", + "year-four-digit": "Rok jako ฤtyล™cifernรฉ ฤรญslo", + "year-two-digit": "Rok jako dvoucifernรฉ ฤรญslo (00โ€“99)" + }, + "file-picker": { + "item": "poloลพka", + "items": "poloลพek", + "search-placeholder": "Hledat soubory a sloลพky...", + "select-current": "Vybrat aktuรกlnรญ", + "select-file": "Vybrat soubor", + "select-folder": "Vybrat sloลพku", + "selected": "Vybrรกno:", + "title": "Vรฝbฤ›r souboru" + }, + "file-picker-title": "Vybrat soubor", + "icon-picker": { + "title": "Vรฝbฤ›r ikony" + }, + "tooltip-placeholder": "Zรกstupnรฝ symbol" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "Anonymnรญ identita", + "ca-cert": "Certifikรกt CA", + "eap-method": "Metoda EAP", + "password": "Heslo uลพivatele", + "phase2-auth": "Ovฤ›ล™enรญ fรกze 2", + "username": "Identita / Uลพivatelskรฉ jmรฉno" + }, + "panel": { + "action-required": "Vyลพadovรกna akce", + "add-hidden-network": "Pล™idat skrytou sรญลฅ", + "add-network": "Pล™idat sรญลฅ", + "available-interfaces": "Dostupnรก rozhranรญ", + "available-networks": "Dostupnรฉ sรญtฤ›", + "disabled": "Wiโ€‘Fi je vypnuto", + "disconnecting": "Odpojovรกnรญ...", + "dns": "DNS", + "enable-message": "Zapnฤ›te Wiโ€‘Fi pro zobrazenรญ dostupnรฝch sรญtรญ.", + "enter-password": "Zadejte heslo Wi-Fi...", + "forget": "Zapomenout", + "forget-network": "Zapomenout tuto sรญลฅ", + "forgetting": "Zapomรญnรกnรญ...", + "hidden-network": "Skrytรก sรญลฅ", + "interface": "Sรญลฅovรฉ rozhranรญ", + "internet-connected": "Internet pล™ipojen", + "internet-limited": "Bez internetu", + "internet-status": "Stav internetu", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "Znรกmรฉ sรญtฤ›", + "link-speed": "Rychlost pล™ipojenรญ", + "network-name-ssid": "Nรกzev sรญtฤ› (SSID)", + "no-ethernet-devices": "ลฝรกdnรก ethernetovรก zaล™รญzenรญ nenalezena", + "no-networks": "ลฝรกdnรฉ Wiโ€‘Fi sรญtฤ› nenalezeny", + "saved": "Uloลพeno", + "scan-again": "Vyhledat znovu", + "searching": "Vyhledรกvรกnรญ sรญtรญ...", + "security-open": "Otevล™enรก", + "security-wep": "WEP", + "security-wpa": "WPA", + "security-wpa-ent": "WPA Enterprise", + "security-wpa2-ent": "WPA2 Enterprise", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 Enterprise", + "show-password": "Zobrazit heslo", + "title": "Wiโ€‘Fi" + }, + "signal": { + "excellent": "Vรฝbornรฝ", + "fair": "Pล™ijatelnรฝ", + "good": "Dobrรฝ", + "poor": "Slabรฝ", + "weak": "Velmi slabรฝ" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/de.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/de.json new file mode 100644 index 0000000..3b8819e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/de.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "Verlauf lรถschen", + "close-app": "{app} schlieรŸen", + "connect-vpn": "Mit {name} verbinden", + "cycle-visualizer": "Zyklus-Visualisierer", + "disable-bluetooth": "Bluetooth deaktivieren", + "disable-dnd": "'Nicht stรถren' deaktivieren", + "disable-wifi": "WLAN deaktivieren", + "disconnect-vpn": "Verbindung zu {name} trennen", + "dock-settings": "Dock-Einstellungen", + "enable-bluetooth": "Bluetooth aktivieren", + "enable-dnd": "'Nicht stรถren' aktivieren", + "enable-wifi": "WLAN aktivieren", + "launcher-settings": "Launcher-Einstellungen", + "lower-to-bottom": "Nach unten verschieben", + "open-calendar": "Kalender รถffnen", + "open-display-settings": "Anzeigeeinstellungen", + "open-launcher": "Launcher รถffnen", + "open-settings": "Einstellungen รถffnen", + "raise-to-top": "Nach oben verschieben", + "random-wallpaper": "Zufรคlliges Hintergrundbild", + "run-custom-command": "Benutzerdefinierten Befehl ausfรผhren", + "show-active-only": "Nur aktive anzeigen", + "show-all": "Alle anzeigen", + "toggle-mute": "Stummschaltung umschalten", + "widget-settings": "Widget-Einstellungen" + }, + "authentication": { + "error": "Authentifizierungsfehler", + "failed": "Authentifizierung fehlgeschlagen" + }, + "bar": { + "active-window": { + "colorize-icons-description": "Themenfarben auf das aktive Fenster-Symbol anwenden.", + "hide-mode-description": "Steuert das Verhalten des Widgets, wenn kein Fenster aktiv ist.", + "scrolling-mode-description": "Steuern, wann Textscrolling fรผr lange Fenstertitel aktiviert ist.", + "show-app-icon-description": "Anwendungssymbol neben dem Fenstertitel anzeigen.", + "show-app-icon-label": "App-Symbol anzeigen", + "show-app-text-description": "Den Titel des aktiven Fensters anzeigen.", + "show-app-text-label": "Fenstertitel anzeigen" + }, + "audio-visualizer": { + "color-name-description": "Wรคhlen Sie die Farbe fรผr den Visualisierer aus.", + "color-name-label": "Fรผllfarbe", + "height-description": "Benutzerdefinierte Komponentenbreite.", + "hide-when-idle-description": "Wenn aktiviert, wird der Visualizer ausgeblendet, sofern keine Wiedergabe lรคuft.", + "hide-when-idle-label": "Ausblenden, wenn keine Medien wiedergegeben werden", + "width-description": "Benutzerdefinierte Komponentenbreite." + }, + "battery": { + "device-default": "Standard (Anzeigegerรคt)", + "device-description": "Wรคhlen Sie das anzuzeigende Akkugerรคt aus.", + "device-label": "Akkugerรคt", + "display-mode-description": "Wรคhlen Sie, wie der Akku in der Leiste angezeigt wird.", + "display-mode-graphic": "Grafischer Akku", + "display-mode-graphic-clean": "Grafischer Akku (ohne %)", + "display-mode-icon-always": "Symbol - Immer % anzeigen", + "display-mode-icon-hover": "Symbol - Beim Hover anzeigen", + "display-mode-icon-only": "Nur Symbol", + "hide-if-idle-description": "Das Widget ausblenden, wenn der Akku nicht geladen oder entladen wird.", + "hide-if-idle-label": "Ausblenden, wenn im Leerlauf", + "hide-if-not-detected-description": "Widget ausblenden, wenn kein Akku im System erkannt wird.", + "hide-if-not-detected-label": "Ausblenden, wenn nicht erkannt", + "low-battery-threshold-label": "Schwellenwert fรผr niedrigen Akkustand", + "show-noctalia-performance-description": "Zeige den Noctalia-Leistungsmodus-Schalter im Akku-Panel an.
Deaktiviert Schatten und Animationen in Noctalia, um Ressourcen zu sparen.", + "show-noctalia-performance-label": "Noctalia-Performance-Schalter anzeigen", + "show-power-profile-description": "Zeige die Auswahl des Energieprofils im Akku-Panel an.", + "show-power-profile-label": "Energieprofil-Steuerung anzeigen" + }, + "brightness": { + "apply-all-description": "Helligkeit fรผr alle Monitore รคndern, wenn das Scrollrad verwendet wird.", + "apply-all-label": "ร„nderungen beim Scrollen auf alle Monitore anwenden" + }, + "clock": { + "clock-display-description": "Passen Sie die Anzeige Ihrer Uhr an, indem Sie Token aus der Liste unten hinzufรผgen. Um das 12-Stunden-Format zu verwenden, mรผssen Sie das 'AP'-Token einschlieรŸen.", + "clock-display-label": "Uhrenanzeige", + "custom-font-description": "Wรคhlen Sie eine benutzerdefinierte Schriftart fรผr die Uhrenanzeige aus.", + "custom-font-label": "Benutzerdefinierte Schriftart", + "custom-font-placeholder": "Benutzerdefinierte Schriftart auswรคhlen...", + "custom-font-search-placeholder": "Schriftarten suchen...", + "horizontal-bar-description": "Tipp: Verwenden Sie \\n, um einen Zeilenumbruch zu erstellen.", + "horizontal-bar-label": "Horizontale Leiste", + "preview": "Vorschau", + "tooltip-format-description": "Formatzeichenfolge fรผr den Tooltip, der beim Bewegen des Mauszeigers รผber die Uhr angezeigt wird. Leer lassen, um den Standard-Tooltip zu verwenden.", + "tooltip-format-label": "Tooltip-Format", + "use-custom-font-description": "รœberschreibt die Standard-Schriftart mit einer benutzerdefinierten Schriftart fรผr die Uhr.", + "use-custom-font-label": "Benutzerdefinierte Schriftart verwenden", + "use-monospaced-font-description": "Wenn aktiviert, verwendet die Uhr die Monospace-Schriftart.", + "use-monospaced-font-label": "Monospace-Schriftart verwenden", + "vertical-bar-description": "Verwenden Sie ein Leerzeichen, um jeden Teil in eine neue Zeile zu trennen.", + "vertical-bar-label": "Vertikale Leiste" + }, + "control-center": { + "browse-file": "Datei durchsuchen", + "browse-library": "Bibliothek durchsuchen", + "color-selection-description": "Wendet Themenfarben auf Symbole an.", + "enable-colorization-description": "Farbgebung fรผr das Symbol aktivieren, Designfarben anwenden.", + "icon-description": "Symbol aus der Bibliothek oder eine benutzerdefinierte Datei auswรคhlen.", + "select-custom-icon": "Benutzerdefiniertes Symbol auswรคhlen", + "use-distro-logo-description": "Logo Ihrer Distribution anstelle eines benutzerdefinierten Symbols verwenden.", + "use-distro-logo-label": "Distro-Logo anstelle von Symbol verwenden" + }, + "custom-button": { + "collapse-condition-description": "Wenn der Ausgabetext diesem Wert entspricht, wird die Schaltflรคche eingeklappt.", + "collapse-condition-label": "Einklappbedingung", + "color-selection-description": "Themenfarben auf Symbol und Text anwenden.", + "default-tooltip": "Benutzerdefinierte Schaltflรคche, in den Einstellungen konfigurieren", + "display-command-output-description": "Befehl eingeben, der in regelmรครŸigen Abstรคnden ausgefรผhrt wird. Die erste Zeile seiner Ausgabe wird als Text angezeigt.", + "display-command-output-label": "Befehlsausgabe anzeigen", + "display-command-output-stream-description": "Geben Sie einen Befehl ein, der kontinuierlich ausgefรผhrt werden soll.", + "dynamic-text": "Dynamischer Text", + "enable-colorization-description": "Farbgebung durch Themenfarben fรผr das benutzerdefinierte Schaltflรคchensymbol und den Text aktivieren.", + "enable-colorization-label": "Farbgebung aktivieren", + "general-tooltip-text-description": "Benutzerdefinierter Text, der im Tooltip der Schaltflรคche angezeigt wird.", + "general-tooltip-text-label": "Benutzerdefinierter Tooltip-Text", + "hide-mode-always-expanded": "Immer erweitert", + "hide-mode-description": "Steuert die Sichtbarkeit des Widgets, wenn der Befehl keine Ausgabe hat.", + "hide-mode-expand-with-output": "Mit Ausgabe erweitern", + "hide-mode-label": "Ausblendmodus", + "hide-mode-max-transparent": "Maximal erweitert aber transparent", + "icon-description": "Symbol aus der Bibliothek auswรคhlen.", + "ipc-identifier-description": "Eindeutige Kennung fรผr IPC-Befehle. Verwenden Sie diese Kennung mit 'qs -c noctalia-shell ipc call cb [Aktion] [Kennung]', um diese Schaltflรคche รผber IPC zu steuern.", + "ipc-identifier-label": "IPC-Kennung", + "left-click-description": "Befehl, der ausgefรผhrt wird, wenn die Schaltflรคche links angeklickt wird.", + "left-click-label": "Linksklick", + "left-click-update-text": "Text auf Linksklick aktualisieren", + "max-text-length-horizontal-description": "Maximale Anzahl an Zeichen, die in horizontaler Leiste angezeigt werden (0 zum Ausblenden des Textes).", + "max-text-length-horizontal-label": "Max. Textlรคnge (horizontal)", + "max-text-length-vertical-description": "Maximale Anzahl an Zeichen, die in vertikaler Leiste angezeigt werden (0 zum Ausblenden des Textes).", + "max-text-length-vertical-label": "Max. Textlรคnge (vertikal)", + "middle-click-description": "Befehl, der ausgefรผhrt wird, wenn die Schaltflรคche mit der mittleren Maustaste angeklickt wird.", + "middle-click-label": "Mittelklick", + "middle-click-update-text": "Text auf Mittelklick aktualisieren", + "parse-json-description": "Die Befehlsausgabe als JSON-Objekt parsen, um Text und Symbol dynamisch festzulegen.", + "parse-json-label": "Ausgabe als JSON parsen", + "refresh-interval-description": "Intervall in Millisekunden.", + "refresh-interval-label": "Aktualisierungsintervall", + "right-click-description": "Befehl, der ausgefรผhrt wird, wenn die Schaltflรคche rechts angeklickt wird.", + "right-click-label": "Rechtsklick", + "right-click-update-text": "Text beim Rechtsklick aktualisieren", + "show-exec-tooltip-description": "Tooltips mit Befehlsdetails anzeigen (Linksklick, Rechtsklick, Mittelklick, Mausrad).", + "show-exec-tooltip-label": "Befehls-Tooltips anzeigen", + "show-icon-description": "Schaltet die Sichtbarkeit des Widget-Symbols um.", + "show-icon-label": "Symbol anzeigen", + "show-text-tooltip-description": "Zeige Tooltips mit der Ausgabe des Textbefehls.", + "show-text-tooltip-label": "Dynamische Text-Tooltips anzeigen", + "text-stream-description": "Gestreamte Zeilen aus dem Befehl werden als Text auf der Schaltflรคche angezeigt.", + "text-stream-label": "Stream", + "wheel-description": "Befehl, der bei Verwendung des Scrollrads ausgefรผhrt wird.
Verwenden Sie $delta fรผr die Scrollrad-Delta im Befehl.", + "wheel-down": "Scrollrad runter", + "wheel-down-description": "Befehl, der ausgefรผhrt wird, wenn das Scrollrad heruntergescrollt wird.", + "wheel-down-label": "Scrollrad runter Befehl", + "wheel-label": "Scrollrad", + "wheel-mode-separate-description": "Separate Befehle fรผr Scrollrad hoch und runter aktivieren.", + "wheel-mode-separate-label": "Separate Scrollrad-Befehle", + "wheel-up": "Scrollrad hoch", + "wheel-up-description": "Befehl, der ausgefรผhrt wird, wenn das Scrollrad hochgescrollt wird.", + "wheel-up-label": "Scrollrad hoch Befehl", + "wheel-update-text": "Anzeigetext beim Scrollen aktualisieren" + }, + "keyboard-layout": { + "show-icon-description": "Das Tastaturlayout-Symbol anzeigen." + }, + "lock-keys": { + "hide-when-off-description": "Verstecke die Anzeige, wenn die Taste nicht aktiv ist.", + "hide-when-off-label": "Ausblenden, wenn deaktiviert", + "show-caps-lock-description": "Caps Lock-Status anzeigen.", + "show-caps-lock-label": "Caps Lock", + "show-num-lock-description": "Num Lock-Status anzeigen.", + "show-num-lock-label": "Num Lock", + "show-scroll-lock-description": "Scroll Lock-Status anzeigen.", + "show-scroll-lock-label": "Scroll Lock" + }, + "media-mini": { + "compact-mode-description": "Aktiviere ein platzsparendes Layout fรผr das Medienplayer-Panel.", + "compact-mode-label": "Kompaktmodus", + "hide-mode-description": "Steuert das Verhalten des Widgets, wenn keine Medien abgespielt werden.", + "max-width-description": "Stellt die maximale HorizontalgrรถรŸe des Widgets ein. Das Widget wird sich an kรผrzere Inhalte anpassen.", + "no-active-player": "Kein aktiver Player", + "panel-section-description": "Konfigurieren Sie das Aussehen und Verhalten des Medienplayer-Panels.", + "panel-section-label": "Medienplayer-Panel", + "scrolling-mode-description": "Steuern, wann Textscrolling fรผr lange Track-Titel aktiviert ist.", + "scrolling-mode-label": "Scrollmodus", + "show-album-art-description": "Albumcover des aktuell abgespielten Tracks anzeigen.", + "show-album-art-label": "Albumcover anzeigen", + "show-artist-first-description": "Kรผnstler - Titel anstatt Titel - Kรผnstler anzeigen.", + "show-artist-first-label": "Kรผnstler zuerst anzeigen", + "show-progress-ring-description": "Runden Fortschrittsindikator anzeigen, der den Titelfortschritt anzeigt.", + "show-progress-ring-label": "Fortschrittsring anzeigen", + "show-visualizer-description": "Audio-Visualizer anzeigen, wenn Musik abgespielt wird.", + "show-visualizer-label": "Visualizer anzeigen", + "use-fixed-width-description": "Wenn aktiviert, verwendet das Widget immer die maximale Breite, anstatt sich dynamisch an den Inhalt anzupassen.", + "use-fixed-width-label": "Feste Breite verwenden", + "visualizer-type-description": "Stil des Audio-Visualizers auswรคhlen.", + "visualizer-type-label": "Visualizer-Typ" + }, + "notification-history": { + "hide-widget-when-zero-description": "Benachrichtigungssymbol ausblenden, wenn keine Benachrichtigungen vorhanden sind.", + "hide-widget-when-zero-label": "Symbol bei keinen Benachrichtigungen ausblenden", + "hide-widget-when-zero-unread-description": "Benachrichtigungssymbol ausblenden, wenn keine ungelesenen Benachrichtigungen vorhanden sind.", + "hide-widget-when-zero-unread-label": "Symbol bei keinen ungelesenen Benachrichtigungen ausblenden", + "show-unread-badge-description": "Abzeichen mit der Anzahl ungelesener Benachrichtigungen anzeigen.", + "show-unread-badge-label": "Ungelesen-Abzeichen anzeigen", + "unread-badge-color-description": "Wรคhlen Sie die Farbe fรผr das Abzeichen ungelesener Benachrichtigungen.", + "unread-badge-color-label": "Farbe des Ungelesen-Abzeichens" + }, + "section-editor": { + "placeholder": "Widget auswรคhlen...", + "search-placeholder": "Widgets suchen..." + }, + "spacer": { + "width-description": "Abstandsbreite in Pixeln." + }, + "system-monitor": { + "compact-mode-description": "Statistiken als Mini-Balkendiagramme anstelle von Textwerten anzeigen. Verhindert Layoutverschiebungen.", + "compact-mode-label": "Kompaktmodus", + "cpu-cores-description": "Die Nutzung der CPU-Kerne einzeln anzeigen.", + "cpu-cores-label": "CPU-Kerne", + "cpu-frequency-description": "Die aktuelle CPU-Taktrate in GHz anzeigen.", + "cpu-frequency-label": "CPU-Frequenz anzeigen", + "cpu-temperature-description": "CPU-Temperaturwerte anzeigen, falls verfรผgbar.", + "cpu-temperature-label": "CPU-Temperatur", + "cpu-usage-description": "Aktuelle CPU-Auslastung in Prozent anzeigen.", + "cpu-usage-label": "CPU-Auslastung", + "disk-path-description": "Wรคhlen Sie den Festplatten-Mountpunkt aus, der รผberwacht werden soll.", + "disk-path-label": "Festplattenpfad", + "gpu-temperature-description": "Zeige GPU-Temperaturwerte an, falls verfรผgbar.", + "load-average-description": "Systemlastdurchschnitt anzeigen.", + "load-average-label": "Lastdurchschnitt", + "memory-percentage-description": "Speicherverbrauch als Prozentsatz statt absolute Werte anzeigen.", + "memory-percentage-label": "Speicher als Prozentsatz", + "memory-usage-description": "Aktuelle RAM-Nutzungsinformationen anzeigen.", + "memory-usage-label": "Speicherauslastung", + "network-traffic-description": "Upload- und Download-Geschwindigkeiten anzeigen.", + "network-traffic-label": "Netzwerkverkehr", + "storage-as-percentage-description": "Festplattenspeicher als Prozentsatz statt absoluter Werte anzeigen.", + "storage-as-percentage-label": "Festplatte als Prozentsatz", + "storage-available-description": "Zeigt an, wie viel Speicherplatz verfรผgbar ist, anstatt wie viel belegt ist.", + "storage-available-label": "Verfรผgbarer Speicherplatz", + "storage-usage-description": "Festplattenspeicher-Nutzungsinformationen anzeigen.", + "storage-usage-label": "Datentrรคgerauslastung", + "swap-usage-description": "Swap-Speichernutzung anzeigen.", + "swap-usage-label": "Swap-Auslastung", + "use-monospace-font-description": "Verwende eine Monospace-Schriftart fรผr eine konsistente Zeichenbreite.", + "use-monospace-font-label": "Schriftart mit fester Breite", + "use-padding-description": "Fรผllt die Textwerte mit Leerzeichen auf. Verhindert Layoutverschiebungen.", + "use-padding-description-disabled-monospace-font": "Eine Monospace-Schriftart ist notwendig, um dieses Option wรคhlen zu kรถnnen.", + "use-padding-description-disabled-vertical": "Vertikale Taskleiste unterstรผtzt keine Titelanzeige.", + "use-padding-label": "Text mit fรผhrenden Leerzeichen auffรผllen" + }, + "taskbar": { + "colorize-icons-description": "Themenfarben auf Taskbar-Symbole anwenden.", + "hide-mode-description": "Steuert das Verhalten des Widgets, wenn keine รผbereinstimmenden Fenster vorhanden sind.", + "hide-mode-label": "Ausblendmodus", + "icon-scale-description": "Legt den Skalierungsfaktor fรผr Taskleistensymbole fest.", + "icon-scale-label": "Symbolskalierung", + "max-width-description": "Maximale Breite der Taskleiste als Prozentsatz der Bildschirmbreite.", + "max-width-label": "Maximale Breite", + "only-active-workspaces-description": "Zeige nur Apps von aktiven Arbeitsflรคchen.", + "only-active-workspaces-label": "Nur von aktiven Arbeitsflรคchen", + "only-same-monitor-description": "Zeige nur Apps von dem Monitor an, auf dem sich die Leiste befindet.", + "only-same-monitor-label": "Nur vom selben Monitor", + "show-pinned-apps-description": "Angeheftete Apps aus dem Dock in der Taskleiste anzeigen.", + "show-pinned-apps-label": "Angeheftete Apps anzeigen", + "show-title-description": "Fenstertitel in der Taskleiste anzeigen.", + "show-title-description-disabled": "Vertikale Taskleiste unterstรผtzt keine Titelanzeige.", + "show-title-label": "Titel anzeigen", + "smart-width-description": "Breite der Eintrรคge automatisch basierend auf der Anzahl der Eintrรคge anpassen.", + "smart-width-label": "Intelligente Breite", + "title-width-description": "Breite der Fenstertitel in der Taskleiste festlegen (in Pixel).", + "title-width-label": "Titelbreite", + "title-width-reset-tooltip": "Titelbreite zurรผcksetzen" + }, + "tray": { + "chevron-color-description": "Wendet die Themenfarben auf das Pfeilsymbol des Drawers an.", + "chevron-color-label": "Pfeil-Farbe", + "colorize-icons-description": "Themenfarben auf Tray-Symbole anwenden.", + "colorize-icons-label": "Symbole einfรคrben", + "drawer-enabled-description": "Wenn aktiviert, werden nicht angeheftete Tray-Elemente in einem Drawer-Panel angezeigt.
Wenn deaktiviert, werden alle Tray-Elemente inline angezeigt.", + "drawer-enabled-label": "Drawer aktivieren", + "hide-passive-description": "Wenn aktiviert, werden Tray-Elemente mit dem Status 'Passiv' ausgeblendet.", + "hide-passive-label": "Passive Elemente ausblenden" + }, + "volume": { + "display-mode-description": "Wรคhlen Sie, wie dieser Wert angezeigt werden soll." + }, + "workspace": { + "character-count-description": "Anzahl der Zeichen, die von Arbeitsflรคchennamen angezeigt werden (1-10).", + "character-count-label": "Zeichenanzahl", + "empty-color-description": "Lege die Hintergrundfarbe fรผr leere Arbeitsflรคchen fest.", + "empty-color-label": "Farbe fรผr leere Arbeitsflรคchen", + "enable-scrollwheel-description": "Zwischen Arbeitsflรคchen mit dem Mausrad wechseln.", + "enable-scrollwheel-label": "Zum Wechseln der Arbeitsflรคchen scrollen", + "focused-color-description": "Lege die Hintergrundfarbe fรผr die fokussierte Arbeitsflรคche fest.", + "focused-color-label": "Farbe der fokussierten Arbeitsflรคche", + "follow-focused-screen-description": "Zeige Arbeitsflรคchen vom aktuell fokussierten Bildschirm an, statt vom Bildschirm, auf dem sich die Leiste befindet.", + "follow-focused-screen-label": "Fokussiertem Bildschirm folgen", + "font-weight-description": "Visuelles Gewicht fรผr Text im Arbeitsbereich festlegen.", + "font-weight-label": "Schriftstรคrke", + "grouped-border-opacity-description": "Legen Sie die Deckkraft fรผr Arbeitsflรคchen-Container-Rahmen fest.", + "grouped-border-opacity-label": "Rahmen-Deckkraft", + "hide-unoccupied-description": "Arbeitsflรคchen ohne Fenster nicht anzeigen.", + "hide-unoccupied-label": "Unbesetzte ausblenden", + "label-mode-description": "Wรคhlen Sie, wie Arbeitsflรคchen-Beschriftungen angezeigt werden.", + "label-mode-label": "Beschriftungsmodus", + "occupied-color-description": "Lege die Hintergrundfarbe fรผr belegte Arbeitsflรคchen fest.", + "occupied-color-label": "Farbe fรผr belegte Arbeitsflรคche", + "pill-size-description": "Passen Sie die GrรถรŸe der Arbeitsbereichs-Pillen an (50%-100%).", + "pill-size-label": "PillengrรถรŸe", + "reverse-scrolling-description": "Die Richtung des Arbeitsflรคchenwechsels beim Scrollen umkehren.", + "reverse-scrolling-label": "Scrollen umkehren", + "show-applications-description": "Anwendungssymbole in jeder Arbeitsflรคche anzeigen.", + "show-applications-hover-description": "Anwendungssymbole in jedem Arbeitsbereich anzeigen, wenn der Arbeitsbereich รผberfahren wird.", + "show-applications-hover-label": "Anwendungen beim Hovern anzeigen", + "show-applications-label": "Anwendungen anzeigen", + "show-badge-description": "Zeige die Arbeitsflรคchennummer als Abzeichen im gruppierten Modus an.", + "show-badge-label": "Arbeitsflรคchen-Abzeichen anzeigen", + "show-labels-only-when-occupied-description": "Arbeitsflรคchenbezeichnungen nur anzeigen, wenn sie Fenster enthalten.", + "show-labels-only-when-occupied-label": "Beschriftungen nur anzeigen, wenn belegt", + "unfocused-icons-opacity-description": "Transparenzgrad fรผr nicht fokussierte App-Symbole festlegen.", + "unfocused-icons-opacity-label": "Deckkraft unscharfer Symbole" + } + }, + "battery": { + "all-batteries": "Alle Akkus (kombiniert)", + "battery-health": "Akkuzustand", + "battery-level": "Ladezustand", + "capacity-level": "Kapazitรคt: {level}", + "charging-rate": "Laderate: {rate} W", + "discharging-rate": "Entladerate: {rate} W", + "health": "Zustand: {percent}%", + "inhibit-idle-description": "Hรคlt das System wach.", + "no-battery-detected": "Kein Akku erkannt", + "plugged-in": "Angeschlossen", + "power-profile": "Energieprofil", + "time-left": "Verbleibende Zeit: {time}", + "time-until-full": "Verbleibende Ladezeit: {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "Verfรผgbare Gerรคte", + "blocked": "Blockiert", + "connected-devices": "Verbundene Gerรคte", + "device-address": "Gerรคteadresse", + "disabled": "Bluetooth ist deaktiviert", + "discoverable": "Sichtbar", + "enable-message": "Aktivieren Sie Bluetooth, um verfรผgbare Gerรคte zu sehen.", + "known-devices": "Bekannte Gerรคte", + "no-devices": "Keine Gerรคte verfรผgbar", + "paired-devices": "Gekoppelte Gerรคte", + "pairing-mode": "Stellen Sie sicher, dass sich Ihr Gerรคt im Kopplungsmodus befindet.", + "scanning": "Scannen nach Gerรคten...", + "signal-text-excellent": "Signal: Ausgezeichnet", + "signal-text-fair": "Signal: Schwach", + "signal-text-good": "Signal: Gut", + "signal-text-poor": "Signal: Schlecht", + "signal-text-unknown": "Signal: Unbekannt", + "signal-text-very-poor": "Signal: Sehr schlecht" + } + }, + "changelog": { + "error": { + "fetch-failed": "Changelog-Daten konnten nicht geladen werden. Bitte versuche es spรคter erneut.", + "rate-limit": "GitHub-Limit erreicht. Bitte versuche es in ein paar Minuten erneut." + }, + "panel": { + "buttons-discord": "Unserem Discord beitreten", + "buttons-dismiss": "OK", + "buttons-feedback": "Feedback geben", + "empty": "Es sind noch keine Versionshinweise verfรผgbar.", + "highlight-title": "Highlights", + "released": "Verรถffentlicht am {date}", + "subtitle-fresh": "Danke, dass du Noctalia installiert hast! Das ist in diesem Build enthalten.", + "subtitle-updated": "Aktualisiert von {previousVersion}", + "title": "Was ist neu in {version}", + "version": "Version {version}", + "version-new-user": "Neuinstallation" + } + }, + "common": { + "actions": "Aktionen", + "active": "Aktiv", + "add": "Hinzufรผgen", + "appearance": "Aussehen", + "apply": "Anwenden", + "auto-connect": "Automatisches Verbinden", + "automation": "Automatisierung", + "available": "Verfรผgbar", + "back": "Zurรผck", + "battery": "Akku", + "behavior": "Verhalten", + "bluetooth": "Bluetooth", + "brightness": "Helligkeit", + "browse": "Durchsuchen", + "calendar": "Kalender", + "calendar-panel": "Kalender-Panel", + "cancel": "Abbrechen", + "cards": "Karten", + "charging": "Laden", + "clear": "Lรถschen", + "clipboard": "Zwischenablage", + "close": "SchlieรŸen", + "color-muted": "Gedรคmpft", + "colors": "Farben", + "command": "Befehl", + "confirm": "Bestรคtigen", + "connect": "Verbinden", + "connected": "Verbunden", + "connecting": "Verbinden...", + "continue": "Fortsetzen", + "contributors": "Mitwirkende", + "copied-to-clipboard": "In die Zwischenablage kopiert", + "countdown": "Countdown", + "customize": "Anpassen", + "date": "Datum", + "default": "Standard", + "delete": "Lรถschen", + "devices": "Gerรคte", + "disabled": "Deaktiviert", + "discharging": "Entladen", + "disconnect": "Trennen", + "disconnected": "Getrennt", + "disconnecting": "Verbindung wird getrennt...", + "display-mode": "Anzeigemodus", + "documentation": "Dokumentation", + "download": "Herunterladen", + "duration": "Dauer", + "dysfunctional": "Dysfunktional", + "edit": "Bearbeiten", + "enabled": "Aktiviert", + "error": "Fehler", + "ethernet": "Ethernet", + "events": "Ereignisse", + "execute": "Ausfรผhren", + "faithful": "Originalgetreu", + "focus": "Fokus", + "font-weight-bold": "Fett", + "font-weight-light": "Leicht", + "font-weight-medium": "Mittel", + "font-weight-regular": "Regulรคr", + "font-weight-semibold": "Halbfett", + "frequency": "Frequenz", + "gateway": "Gateway", + "general": "Allgemein", + "height": "Hรถhe", + "hibernate": "Ruhezustand", + "history": "Verlauf", + "icon": "Symbol", + "idle": "Leerlauf", + "info": "Info", + "input": "Eingabe", + "install": "Installieren", + "installed": "Installiert", + "interface": "Schnittstelle", + "internet": "Internet", + "keybind": "Tastenbelegung", + "language": "Sprache", + "loading": "Wird geladen...", + "local": "Lokal", + "location": "Standort", + "lock": "Sperren", + "logout": "Abmelden", + "look": "Aussehen", + "margins": "Rรคnder", + "media": "Medien", + "media-player": "Medienplayer", + "memory": "Arbeitsspeicher", + "monitors": "Monitore", + "move-down": "Nach unten verschieben", + "move-up": "Hoch", + "network": "Netzwerk", + "next": "Weiter", + "night-light": "Nachtlicht", + "no": "Nein", + "no-results": "Keine Ergebnisse", + "no-summary": "Keine Zusammenfassung", + "none": "Keine", + "not-found": "Nicht gefunden", + "notifications": "Benachrichtigungen", + "official": "Offiziell", + "on-surface": "Auf Oberflรคche", + "output": "Ausgabe", + "pair": "Koppeln", + "paired": "Gekoppelt", + "pairing": "Koppeln...", + "panels": "Panels", + "password": "Passwort", + "pause": "Pause", + "performance": "Leistung", + "pin": "Anpinnen", + "play": "Wiedergabe", + "polling": "Abfrageintervall", + "position": "Position", + "previous": "Zurรผck", + "primary": "Primรคr", + "random": "Zufรคllig", + "reboot": "Neu starten", + "reboot-to-uefi": "In UEFI neu starten", + "record": "Aufnahme starten", + "refresh": "Aktualisieren", + "required": "(erforderlich)", + "reset": "Zurรผcksetzen", + "reset-to-default": "Auf Standard zurรผcksetzen", + "result-count": "{count} Ergebnis", + "result-count-plural": "{count} Ergebnisse", + "resume": "Fortsetzen", + "save": "Speichern", + "scanning": "Scannen...", + "screen-corners": "Bildschirmecken", + "search": "Suche", + "second": "{count} Sekunde", + "second-plural": "{count} Sekunden", + "secondary": "Sekundรคr", + "security": "Sicherheit", + "select": "Auswรคhlen", + "select-color": "Farbe auswรคhlen", + "select-color-description": "Wendet Themenfarben zur Hervorhebung an.", + "select-icon-color": "Symbolfarbe auswรคhlen", + "settings": "Einstellungen", + "shortcuts": "Kurzbefehle", + "shutdown": "Herunterfahren", + "signal": "Signal", + "sound": "Ton", + "sources": "Quellen", + "start": "Start", + "stop": "Stopp", + "supporters": "Unterstรผtzer", + "suspend": "Energie sparen", + "templates": "Vorlagen", + "tertiary": "Tertiรคr", + "test": "Test", + "thresholds": "Schwellenwerte", + "title": "Titel", + "toast": "Toast", + "trusted": "Vertrauenswรผrdig", + "uninstall": "Deinstallieren", + "unknown": "Unbekannt", + "unpair": "Entkoppeln", + "unpin": "Loslรถsen", + "update": "Aktualisieren", + "upload": "Hochladen", + "username": "Benutzername", + "userspace-reboot": "Userspace neu starten", + "version": "Version", + "vibrant": "Lebhaft", + "visualizer": "Visualisierer", + "volume": "Lautstรคrke", + "volumes": "Lautstรคrken", + "wallpaper": "Hintergrundbild", + "weather": "Wetter", + "weather-loading": "Wetter wird geladen...", + "weather-no-location": "Lege einen Standort in den Einstellungen fest, um das Wetter zu sehen", + "week": "Woche", + "widgets": "Widgets", + "width": "Breite", + "wifi": "Wi-Fi", + "windows": "Fenster", + "yes": "Ja" + }, + "control-center": { + "power-profile": { + "tooltip-action": "Energieprofil", + "tooltip-disabled": "Installieren Sie power-profiles-daemon, um Energieprofile zu verwenden" + }, + "wifi": { + "label-disconnected": "WLAN getrennt", + "label-ethernet": "Ethernet" + } + }, + "display-modes": { + "always-hide": "Immer verstecken", + "always-show": "Immer anzeigen", + "force-open": "Aufbrechen", + "on-hover": "Beim Hover" + }, + "hide-modes": { + "auto-hide": "Automatisch ausblenden", + "hidden": "Ausblenden, wenn leer", + "idle": "Ausblenden, wenn inaktiv", + "non-exclusive": "Nicht-exklusiv", + "transparent": "Transparent, wenn leer", + "visible": "Immer sichtbar" + }, + "launcher": { + "categories": { + "all": "Alle", + "audiovideo": "Audio & Video", + "chat": "Chat", + "development": "Entwicklung", + "education": "Bildung", + "emoji-activity": "Aktivitรคt", + "emoji-animals": "Tiere", + "emoji-flags": "Flaggen", + "emoji-food": "Essen & Trinken", + "emoji-nature": "Natur", + "emoji-objects": "Objekte", + "emoji-people": "Menschen & Kรถrper", + "emoji-recent": "Kรผrzlich", + "emoji-symbols": "Symbole", + "emoji-travel": "Reisen & Orte", + "game": "Spiele", + "graphics": "Grafiken", + "misc": "Verschiedenes", + "office": "Bรผro", + "pinned": "Angeheftet", + "system": "System", + "webbrowser": "Webbrowser" + }, + "providers": { + "applications": "Anwendungen", + "calculator": "Rechner", + "calculator-deprecated": "Der Befehl >calc ist veraltet und wird bald entfernt. Bitte geben Sie stattdessen mathematische Ausdrรผcke direkt in die Suche ein.", + "calculator-description": "Rechner โ€” mathematische Ausdrรผcke auswerten", + "calculator-enter-expression": "Mathematischen Ausdruck eingeben", + "calculator-press-enter-to-copy": "Drรผcken Sie die Eingabetaste, um das Ergebnis zu kopieren", + "clipboard": "Zwischenablageverlauf", + "clipboard-clear-description": "Gesamten Zwischenablageverlauf lรถschen", + "clipboard-clear-description-full": "Alle Elemente aus dem Zwischenablageverlauf entfernen", + "clipboard-clear-history": "Zwischenablageverlauf lรถschen", + "clipboard-delete": "Zwischenablageeintrag lรถschen", + "clipboard-history-disabled": "Zwischenablageverlauf deaktiviert", + "clipboard-history-disabled-description": "Zwischenablageverlauf in den Einstellungen aktivieren oder cliphist installieren", + "clipboard-loading": "Lade Zwischenablageverlauf...", + "clipboard-search-description": "Zwischenablageverlauf durchsuchen", + "command-description": "Shell-Befehle ausfรผhren", + "emoji": "Emoji-Auswahl", + "emoji-loading": "Lade Emojis...", + "emoji-loading-description": "Bitte warten", + "emoji-no-recent": "Noch keine kรผrzlich verwendeten Emojis", + "emoji-search-description": "Emojis suchen und kopieren", + "settings-search-description": "Suchen und zu den Einstellungen navigieren", + "windows-search-description": "Offene Fenster suchen und fokussieren" + } + }, + "lock-screen": { + "authenticating": "Authentifizierung...", + "password": "Passwort eingeben...", + "restart": "Neu starten", + "shut-down": "Herunterfahren" + }, + "notifications": { + "panel": { + "click-to-collapse": "Klicken zum Einklappen", + "click-to-expand": "Zum Erweitern klicken", + "description": "Ihre Benachrichtigungen werden hier angezeigt, sobald sie eintreffen.", + "no-notifications": "Keine Benachrichtigungen" + }, + "range": { + "earlier": "ร„lter", + "today": "Heute", + "yesterday": "Gestern" + }, + "time": { + "diff-d": "vor 1 Tag", + "diff-dd": "vor {diff} Tagen", + "diff-h": "vor 1 Stunde", + "diff-hh": "vor {diff} Stunden", + "diff-m": "vor 1 Minute", + "diff-mm": "vor {diff} Minuten", + "now": "jetzt" + } + }, + "options": { + "bar": { + "density-comfortable": "Bequem", + "density-compact": "Kompakt", + "density-default": "Standard", + "density-mini": "Mini", + "density-spacious": "Gerรคumig", + "display-mode-always-visible": "Immer sichtbar", + "type-floating": "Schwebend", + "type-framed": "Gerahmt", + "type-simple": "Einfach" + }, + "control-center": { + "quick-settings-style-classic": "Klassisch", + "quick-settings-style-modern": "Modern" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "Bequem", + "compact": "Kompakt", + "default": "Standard" + }, + "launcher-view-mode": { + "grid": "Gitter", + "list": "Liste" + }, + "notification-density": { + "compact": "Kompakt", + "default": "Standard" + }, + "scrolling-modes": { + "always": "Immer scrollen", + "hover": "Beim Hover scrollen", + "never": "Nie scrollen" + }, + "session-menu-grid-layout": { + "grid": "Raster", + "single-row": "Einzelne Reihe" + }, + "settings-panel-mode": { + "attached": "Panel an der Leiste angeheftet", + "centered": "Zentriertes Feld", + "window": "Eigenes Fenster" + }, + "visualizer-types": { + "linear": "Linear", + "mirrored": "Gespiegelt", + "wave": "Welle" + }, + "visualizer-visibility": { + "with-background": "Nur mit Hintergrund" + }, + "workspace-labels": { + "index": "Index", + "index-and-name": "Index und Name", + "name": "Name" + } + }, + "panels": { + "about": { + "become-supporter": "Werde Unterstรผtzer", + "changelog": "ร„nderungsprotokoll anzeigen", + "changelog-on-startup": "ร„nderungsprotokoll bei Update anzeigen", + "changelog-on-startup-desc": "Automatisch den Changelog anzeigen, wenn Noctalia aktualisiert wird.", + "contributors-description": "Ein Dankeschรถn an unseren {count} groรŸartigen Mitwirkenden!", + "contributors-description-plural": "Ein Dankeschรถn an unsere {count} groรŸartigen Mitwirkenden!", + "copy-info": "Informationen kopieren", + "debug-disabled": "Debug-Modus deaktiviert", + "debug-enabled": "Debug-Modus aktiviert", + "info-copied": "Info in die Zwischenablage kopiert", + "noctalia-available": "Verfรผgbar:", + "noctalia-desc": "Eine elegante und minimalistische Desktop-Shell, sorgfรคltig fรผr Wayland entwickelt, gebaut mit Quickshell.", + "noctalia-git-commit": "Git-Commit:", + "noctalia-installed-version": "Installierte Version:", + "noctalia-latest-version": "Neueste Version:", + "noctalia-qs-version": "Noctalia QS Version:", + "noctalia-title": "Noctalia Shell", + "privacy-policy": "Datenschutzbestimmungen", + "support": "Unterstรผtze uns", + "supporter-badge": "Unterstรผtzer", + "supporters-desc": "Ein riesiges Dankeschรถn an unseren groรŸartigen Unterstรผtzer!", + "supporters-desc-plural": "Ein riesiges Dankeschรถn an unsere {count} groรŸartigen Unterstรผtzer!", + "supporters-loading": "Supporter werden geladen...", + "system-board": "Motherboard:", + "system-cpu": "CPU:", + "system-disk": "Festplatte:", + "system-gpu": "GPU:", + "system-host": "Host:", + "system-install-hint": "Installieren Sie fastfetch, um Systeminformationen anzuzeigen", + "system-kernel": "Kernel:", + "system-loading": "Systeminformationen werden geladen...", + "system-memory": "Speicher:", + "system-monitor": "Monitor:", + "system-not-installed": "fastfetch ist nicht installiert", + "system-os": "Betriebssystem:", + "system-packages": "Pakete:", + "system-product": "Produkt:", + "system-title": "Systeminformationen", + "system-uptime": "Betriebszeit:", + "system-wm": "WM:", + "telemetry-data-copied": "Telemetriedaten in die Zwischenablage kopiert", + "telemetry-desc": "Hilf mit, Noctalia zu verbessern, indem du anonyme Systeminformationen (Bildschirmauflรถsung, Compositor, Distribution) teilst. Einmaliger Versand beim Start, kein Tracking, Daten werden nach 30 Tagen automatisch gelรถscht.", + "telemetry-enabled": "Anonyme Systeminformationen senden", + "telemetry-show-data": "Daten anzeigen", + "telemetry-title": "Datenschutz", + "title": "รœber", + "up-to-date": "Auf dem neuesten Stand!", + "update-available": "Update verfรผgbar", + "view-commit": "Commit auf GitHub ansehen" + }, + "audio": { + "devices-desc": "Verfรผgbare Audio-Ein- und Ausgabegerรคte konfigurieren.", + "devices-input-device-description": "Gewรผnschtes Audio-Eingabegerรคt auswรคhlen.", + "devices-input-device-label": "Eingabegerรคt", + "devices-output-device-description": "Gewรผnschtes Audio-Ausgabegerรคt auswรคhlen.", + "devices-output-device-label": "Ausgabegerรคt", + "devices-title": "Audio-Gerรคte", + "external-mixer-description": "Geben Sie den Befehl oder den Anwendungspfad ein, der beim Aktivieren der externen Audiomixer-Funktion gestartet werden soll.", + "external-mixer-label": "Externer Audio-Mixer-Befehl", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "Bevorzugte und ignorierte Medienanwendungen festlegen.", + "media-excluded-player-description": "Stichwรถrter fรผr Player hinzufรผgen, die das System ignorieren soll. Jedes Stichwort sollte in einer neuen Zeile stehen.", + "media-excluded-player-label": "Ausgeschlossener Player", + "media-excluded-player-placeholder": "Teilstring eingeben und + drรผcken", + "media-frame-rate-description": "Hรถhere Raten sind flรผssiger, verbrauchen aber mehr Ressourcen.", + "media-frame-rate-label": "Bildrate", + "media-primary-player-description": "Stichwort eingeben, um den Hauptplayer zu identifizieren.", + "media-primary-player-label": "Hauptplayer", + "media-primary-player-placeholder": "z.B. spotify, vlc, mpv", + "media-scrolling-speed-description": "Zeit in Sekunden, die der Titel zum Scrollen vom Anfang bis zum Ende benรถtigt.", + "media-scrolling-speed-label": "Scrollgeschwindigkeit", + "media-scrolling-title-description": "Kontinuierliches Scrollen fรผr lange Medientitel aktivieren.", + "media-scrolling-title-label": "Scrollender Titel", + "media-title": "Medienplayer", + "on-middle-clicked-description": "Befehl, der ausgefรผhrt wird, wenn die Schaltflรคche mit der mittleren Maustaste angeklickt wird.", + "panel-applications-empty": "Derzeit geben keine Anwendungen Audio wieder", + "spectrum-mirrored-description": "Spiegele das Spektrum, sodass tiefe Frequenzen in der Mitte sind. Wenn ausgeschaltet, sind tiefe Frequenzen links und hohe rechts.", + "spectrum-mirrored-label": "Spektrum spiegeln", + "title": "Audio", + "visualizer-type-description": "Wรคhlen Sie einen Visualisierungstyp fรผr die Medienwiedergabe.", + "visualizer-type-label": "Visualisierungsart", + "volumes-desc": "Lautstรคrkeregler und Audiopegel anpassen.", + "volumes-feedback-sound-file-description": "Pfad zur Sounddatei, die beim ร„ndern der Lautstรคrke abgespielt wird.", + "volumes-feedback-sound-file-label": "Audiodatei fรผr Lautstรคrke-Feedback", + "volumes-feedback-sound-file-select-title": "Audiodatei fรผr Lautstรคrkerรผckmeldung auswรคhlen", + "volumes-input-volume-description": "Mikrofon-Eingabe-Lautstรคrke.", + "volumes-mute-input-description": "Die Standard-Audio-Eingabe (Mikrofon) stummschalten.", + "volumes-mute-input-label": "Audio-Eingabe stummschalten", + "volumes-mute-output-description": "Die Haupt-Audio-Ausgabe des Systems stummschalten.", + "volumes-mute-output-label": "Audio-Ausgabe stummschalten", + "volumes-output-volume-description": "Systemweite Lautstรคrke.", + "volumes-step-size-description": "SchrittgrรถรŸe fรผr Lautstรคrkeรคnderungen anpassen (Mausrad, Tastenkรผrzel).", + "volumes-step-size-label": "Lautstรคrke-SchrittgrรถรŸe", + "volumes-volume-feedback-description": "Einen Feedback-Sound abspielen, wenn die Lautstรคrke angepasst wird.", + "volumes-volume-feedback-label": "Lautstรคrke-Feedback-Sound abspielen", + "volumes-volume-overdrive-description": "Erlaubt das Anheben der Lautstรคrke รผber 100%. Wird mรถglicherweise nicht von aller Hardware unterstรผtzt.", + "volumes-volume-overdrive-label": "Lautstรคrke-รœbersteuerung erlauben" + }, + "bar": { + "appearance-auto-hide-delay-description": "Zeit, bis die Bar ausgeblendet wird, nachdem die Maus sie verlassen hat.", + "appearance-auto-hide-delay-label": "Ausblende-Verzรถgerung", + "appearance-auto-hide-exclusive-note": "Hinweis: Die exklusive Zone wird automatisch deaktiviert, wenn Auto-Hide aktiv ist.", + "appearance-auto-show-delay-description": "Zeit, bevor die Bar angezeigt wird, wenn die Maus den Rand betritt.", + "appearance-auto-show-delay-label": "Anzeigeverzรถgerung", + "appearance-background-opacity-description": "Legen Sie die Hintergrundtransparenz speziell fรผr die Leiste fest.", + "appearance-background-opacity-label": "Leisten-Hintergrundtransparenz", + "appearance-capsule-color-description": "Wรคhlen Sie eine Farbe fรผr Leistenkapseln oder keine, um die Standard-Oberflรคchenfarbe zu verwenden.", + "appearance-capsule-color-label": "Kapselfarbe", + "appearance-capsule-opacity-description": "Deckkraft der Widget-Hintergrรผnde festlegen, wenn Kapsel angezeigt wird.", + "appearance-capsule-opacity-label": "Kapsel-Deckkraft", + "appearance-content-padding-description": "Passen Sie den Abstand zwischen Leistenrรคndern und Widgets an.", + "appearance-content-padding-label": "Inhaltsabstand", + "appearance-density-description": "Innenabstand der Leiste fรผr kompaktes oder gerรคumiges Aussehen anpassen.", + "appearance-density-label": "Leistendichte", + "appearance-desc": "Erscheinungsbild und Position der Leiste anpassen.", + "appearance-display-mode-description": "Wรคhlen Sie, wann die Leiste sichtbar ist.", + "appearance-enable-exclusion-zone-inset-description": "Reduziere die Ausschlusszone um 1 physisches Pixel, damit bรผndige Fenster perfekt unter den Rand der Leiste รผberlappen.", + "appearance-enable-exclusion-zone-inset-label": "Innenabstand der Ausschlusszone", + "appearance-floating-description": "Leiste als schwebende 'Pille' anzeigen.", + "appearance-floating-label": "Schwebende Leiste", + "appearance-font-scale-description": "Die Skalierung der SchriftgrรถรŸe fรผr Text in der Leiste anpassen.", + "appearance-font-scale-label": "Schriftskalierung", + "appearance-frame-radius": "Innenradius", + "appearance-frame-settings-description": "Rahmenstรคrke und inneren Eckenradius anpassen", + "appearance-frame-settings-label": "Rahmeneinstellungen", + "appearance-frame-thickness": "Stรคrke", + "appearance-hide-on-overview-description": "Die Leiste ausblenden und Panels schlieรŸen, wenn die Compositor-รœbersicht aktiv ist.", + "appearance-hide-on-overview-label": "Leiste in der รœbersicht ausblenden", + "appearance-margins-description": "Rรคnder um die schwebende Leiste anpassen.", + "appearance-margins-horizontal": "Horizontaler Rand", + "appearance-margins-vertical": "Vertikaler Rand", + "appearance-outer-corners-description": "Nach auรŸen gewรถlbte Ecken auf der Leiste anzeigen.", + "appearance-outer-corners-label": "ร„uรŸere Ecken", + "appearance-position-description": "Wรคhlen Sie, wo die Leiste auf dem Bildschirm platziert werden soll.", + "appearance-position-label": "Leistenposition", + "appearance-show-capsule-description": "Widget-Hintergrรผnde anzeigen.", + "appearance-show-capsule-label": "Kapsel anzeigen", + "appearance-show-on-workspace-switch-description": "Die Bar automatisch kurz einblenden, wenn der Arbeitsbereich wechselt.", + "appearance-show-on-workspace-switch-label": "Leiste bei Arbeitsflรคchenwechsel anzeigen", + "appearance-show-outline-description": "Zeige eine sichtbare Umrandung um jedes Widget an.", + "appearance-show-outline-label": "Widget-Umrisse anzeigen", + "appearance-type-description": "Wรคhlen Sie den Stil der Leiste: Einfach, Schwebend oder Gerahmt.", + "appearance-type-label": "Leistentyp", + "appearance-use-separate-opacity-description": "Ermรถglicht die Verwendung eines separaten Deckkraftwerts fรผr den Leistenhintergrund.", + "appearance-use-separate-opacity-label": "Separate Leisten-Deckkraft", + "appearance-widget-spacing-description": "Den Abstand zwischen jedem Widget in der Leiste anpassen.", + "appearance-widget-spacing-label": "Widget-Abstand", + "behavior-middle-click-command-description": "Befehl, der bei Mittelklick ausgefรผhrt wird.", + "behavior-middle-click-command-label": "Mittelklick-Befehl", + "behavior-middle-click-command-placeholder": "niri msg action รœbersicht umschalten", + "behavior-middle-click-description": "Wรคhlen Sie, was ein mittlerer Klick auf leeren Bereichen der Leiste bewirkt.", + "behavior-middle-click-follow-mouse-description": "ร–ffne das ausgewรคhlte Mittelklick-Panel an der Cursorposition.", + "behavior-middle-click-follow-mouse-label": "Mittelklick folgt Maus", + "behavior-middle-click-label": "Aktion bei Mittelklick auf die Leiste", + "behavior-right-click-command-description": "Befehl, der beim Rechtsklick ausgefรผhrt wird.", + "behavior-right-click-command-label": "Rechtsklick-Befehl", + "behavior-right-click-command-placeholder": "notify-send \"Rechtsklick\"", + "behavior-right-click-description": "Wรคhlen Sie, was ein Rechtsklick auf leere Bereiche der Leiste bewirkt.", + "behavior-right-click-follow-mouse-description": "ร–ffne das ausgewรคhlte Rechtsklick-Panel an der Cursorposition.", + "behavior-right-click-follow-mouse-label": "Rechtsklick folgt Maus", + "behavior-right-click-label": "Aktion bei Rechtsklick auf die Leiste", + "behavior-wheel-wrap-description": "Wenn aktiviert, wird das Scrollen vom letzten zum ersten Element fortgesetzt.", + "behavior-wheel-wrap-label": "Umlaufend", + "behavior-workspace-scroll-description": "Wรคhlen Sie, was das Mausrad auf leeren Bereichen der Leiste tut.", + "behavior-workspace-scroll-label": "Mausrad-Aktion der Leiste", + "behavior-workspace-scroll-option-content": "Inhalt", + "behavior-workspace-scroll-option-workspace": "Arbeitsbereich", + "monitor-configure-widgets": "Widgets konfigurieren", + "monitor-override-settings": "Globale Einstellungen รผberschreiben", + "monitor-override-settings-description": "Benutzerdefinierte Einstellungen fรผr diesen Monitor verwenden.", + "monitor-reset-all": "Alles zurรผcksetzen", + "monitor-widgets-title": "Widget-Konfiguration - {monitor}", + "monitors-desc": "Leiste auf bestimmten Monitoren anzeigen. StandardmรครŸig auf allen.", + "monitors-desc-new": "Konfiguriere, welche Monitore die Bar anzeigen und passe die Einstellungen pro Monitor an.", + "monitors-title": "Monitor-Anzeige", + "title": "Leiste", + "tray-blacklist-description": "Ausschlussregeln fรผr die Tray-Symbolleiste hinzufรผgen, unterstรผtzt Platzhalter (*).", + "tray-blacklist-label": "Ausschlussliste", + "tray-blacklist-placeholder": "z.B. nm-applet, Fcitx*", + "tray-pin-application": "Anwendung anheften", + "tray-unpin-application": "Anheftung aufheben", + "use-global-widgets": "Globale Widgets verwenden", + "widgets-desc": "Ziehen Sie Widgets, um ihre Reihenfolge zu รคndern. Verwenden Sie das Rechtsklick-Menรผ, um Widgets zwischen Abschnitten zu verschieben oder sie zu entfernen.", + "widgets-title": "Widget-Positionierung" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "Generiere Farbschemata aus deinem Hintergrundbild. Extrahiert automatisch Farben, um ein stimmiges Theme zu erstellen.", + "color-source-use-wallpaper-colors-label": "Hintergrundbild-Farben verwenden", + "dark-mode-mode-description": "Ermรถglicht einen automatischen Wechsel zwischem hellen und dunklen Modus.", + "dark-mode-mode-label": "Automatischer Dunkelmodus", + "dark-mode-mode-manual": "Manuell", + "dark-mode-mode-off": "Aus", + "dark-mode-switch-description": "Wechselt zu einem dunkleren Theme fรผr einfachere Betrachtung bei Nacht.", + "delete-error-description": "Fehler beim Lรถschen von {scheme}", + "delete-error-title": "Lรถschen fehlgeschlagen", + "delete-success-description": "{scheme} wurde erfolgreich gelรถscht", + "delete-success-title": "Farbschema gelรถscht", + "download-button": "Mehr herunterladen", + "download-downloading": "Wird heruntergeladen...", + "download-empty": "Keine Farbschemata verfรผgbar", + "download-error-api-error": "API-Fehler: {status}", + "download-error-description": "Fehler beim Herunterladen von {scheme}", + "download-error-download-failed": "Download fehlgeschlagen mit Exit-Code: {code}", + "download-error-invalid-response": "Ungรผltiges API-Antwortformat", + "download-error-no-files": "Keine Dateien fรผr Schema gefunden", + "download-error-parse-failed": "Fehler beim Parsen der API-Antwort: {error}", + "download-error-rate-limit": "GitHub API-Ratenlimit รผberschritten", + "download-error-title": "Download fehlgeschlagen", + "download-fetching": "Verfรผgbare Farbschemata werden abgerufen...", + "download-success-description": "{scheme} wurde erfolgreich heruntergeladen", + "download-success-title": "Farbschema heruntergeladen", + "download-title": "Farbschemata herunterladen", + "method-description": { + "content": "Material Design Schema mit hochauflรถsender Farbextraktion, das die tatsรคchlichen Farben des Quellinhalts genau wiedergibt.", + "dysfunctional": "Wie Faithful, wรคhlt aber die zweitdominanteste Farbfamilie als primรคr.", + "faithful": "Versucht, nah an der Ausgangsfarbe zu bleiben, wรคhrend gleichzeitig eine harmonische Palette erzeugt wird.", + "fruit-salad": "Material Design-Schema, das eine spielerische, lebendige Palette mit vielfรคltigen Farbtรถnen erzeugt.", + "monochrome": "Material Design-Schema mit einer einfarbigen Grauskala und minimalem Farbanteil.", + "muted": "Erzeugt weichere, entsรคttigte Farbtรถne fรผr ein ruhiges und dezentes Erscheinungsbild.", + "rainbow": "Material Design Schema, das Farben รผber das gesamte Spektrum erzeugt, um maximale Farbvielfalt zu erzielen.", + "tonal-spot": "StandardmรครŸiger Material Design Algorithmus. Erstellt ausgewogene Paletten mithilfe von tonalen Variationen der Quellfarbe.", + "vibrant": "Maximiert die Farbsรคttigung fรผr krรคftige, auffรคllige Paletten mit lebendigen Tรถnen." + }, + "predefined-desc": "Wรคhlen Sie aus einer Sammlung vordefinierter Farbschemata.", + "predefined-generate-templates-label": "Templates fรผr vordefinierte Schemata generieren", + "predefined-title": "Vordefinierte Farbschemata", + "sync-gsettings-description": "Das Systemdesign an die aktive helle oder dunkle Variante anpassen.", + "sync-gsettings-label": "Systemdesign synchronisieren", + "templates-desc": "Farben auf externe Anwendungen anwenden.", + "templates-filter-description": "Vorlagen aus einer bestimmten Kategorie anzeigen.", + "templates-filter-label": "Nach Kategorie filtern", + "templates-misc-description": "Erstellen Sie Ihre eigenen Vorlagen.", + "templates-misc-label": "Erweitert", + "templates-misc-user-templates-description": "Nur aktivieren, wenn Sie wissen, was Sie tun โ€” weitere Informationen finden Sie in unserer Online-Dokumentation.", + "templates-misc-user-templates-label": "Benutzer-Vorlagen aktivieren", + "templates-none-detected": "Keine erkannt", + "templates-write-path": "Schreibt: {filepath}", + "title": "Farbschema", + "wallpaper-method-description": "Wรคhlen Sie Ihre bevorzugte Methode zur Palettengenerierung.", + "wallpaper-method-label": "Methode zur Palettengenerierung", + "wallpaper-monitor-source-description": "Wรคhlen Sie den Monitor aus, der fรผr die Extraktion der Wallpaper-Farben verwendet werden soll.", + "wallpaper-monitor-source-label": "Quelle der Farbgenerierung" + }, + "connections": { + "authentication-required": "Authentifizierung erforderlich", + "bluetooth-auto-connect-description": "Automatisch mit vertrauenswรผrdigen gekoppelten Gerรคten verbinden, wenn Bluetooth aktiviert ist.", + "bluetooth-auto-connect-label": "Gekoppelte Gerรคte automatisch verbinden", + "bluetooth-devices-unnamed": "Unbenannte Gerรคte werden nicht angezeigt.", + "bluetooth-discoverable": "Dieses Gerรคt ist auffindbar als {hostName}, solange dieser Einstellungs-Tab geรถffnet ist.", + "bluetooth-rssi-polling-description": "Periodisch RSSI fรผr verbundene Gerรคte รผber bluetoothctl abfragen. Mรถglicherweise nicht fรผr alle Gerรคte verfรผgbar; verbraucht bei Aktivierung minimale Ressourcen.", + "bluetooth-rssi-polling-interval-description": "Konfigurieren, wie oft die Signalstรคrke fรผr verbundene Gerรคte aktualisiert werden soll.", + "bluetooth-rssi-polling-interval-label": "Abfrageintervall", + "bluetooth-rssi-polling-label": "Bluetooth-Signalabfrage", + "disable-discoverability-description": "Gerรคt vor Bluetooth-Gerรคten in der Nรคhe verbergen.", + "disable-discoverability-label": "Gerรคtesichtbarkeit deaktivieren", + "hide-unnamed-devices-description": "Verstecke Gerรคte, die nur als Bluetooth-Adressen erscheinen.", + "hide-unnamed-devices-label": "Unbenannte Gerรคte ausblenden", + "pin-instructions": "Bitte geben Sie den auf Ihrem Gerรคt angezeigten PIN-Code ein.", + "title": "Verbindungen", + "wifi-header-text": "Verwenden Sie Wi-Fi, um verfรผgbare Netzwerke anzuzeigen und sich mit dem Internet zu verbinden." + }, + "control-center": { + "cards-desc": "Passen Sie an, welche Steuerelemente im Kontrollzentrum angezeigt werden und in welcher Reihenfolge.", + "desc": "Konfigurieren Sie die Positionierung und das Verhalten des Kontrollzentrum-Panels.", + "open-at-mouse-description": "Beim Rechtsklick auf einen leeren Bereich der Leiste, รถffne das Kontrollzentrum nahe dem Cursor. Wenn deaktiviert, wird stattdessen die obige Positionseinstellung verwendet.", + "open-at-mouse-label": "Am Mauszeiger bei Rechtsklick รถffnen", + "position-description": "Wรคhlen Sie aus, wo das Kontrollzentrum-Panel angezeigt wird, wenn es geรถffnet wird.", + "shortcuts-custom-button-command-description": "Der Befehl, der ausgefรผhrt werden soll, wenn die Schaltflรคche geklickt wird.", + "shortcuts-custom-button-enable-on-state-logic-description": "Aktiviert ein zweites Symbol und 'Aktiv'-Zustand basierend auf einem Prรผfbefehl.", + "shortcuts-custom-button-enable-on-state-logic-label": "Aktiv-Zustand-Logik aktivieren", + "shortcuts-custom-button-icon-description": "Wรคhlen Sie ein Symbol aus der Bibliothek.", + "shortcuts-custom-button-on-clicked-label": "Linksklick-Befehl", + "shortcuts-custom-button-on-middle-clicked-description": "Befehl, der ausgefรผhrt wird, wenn die Schaltflรคche mit der mittleren Maustaste angeklickt wird.", + "shortcuts-custom-button-on-state-command-description": "Befehl, der ausgefรผhrt wird, um zu prรผfen, ob sich die Schaltflรคche im 'Aktiv'-Zustand befinden soll. Gibt 0 fรผr aktiv, ungleich 0 fรผr inaktiv zurรผck.", + "shortcuts-custom-button-on-state-command-label": "Befehl fรผr Aktiv-Zustand-Prรผfung", + "shortcuts-custom-button-on-state-icon-description": "Das Symbol fรผr die Schaltflรคche, wenn sie sich im 'Aktiv'-Zustand befindet.", + "shortcuts-custom-button-on-state-icon-label": "Symbol im Aktiv-Zustand", + "shortcuts-custom-button-state-checks-add": "Zustandsprรผfung hinzufรผgen", + "shortcuts-custom-button-state-checks-command": "Befehl, der fรผr diese Zustandsprรผfung ausgefรผhrt werden soll", + "shortcuts-custom-button-state-checks-label": "Zustandsprรผfungen", + "shortcuts-custom-button-state-checks-remove": "Entfernen", + "shortcuts-custom-button-tooltip-description": "Der Tooltip, der beim Bewegen des Mauszeigers รผber die Schaltflรคche angezeigt wird.", + "shortcuts-custom-button-tooltip-label": "Tooltip", + "shortcuts-title": "Widgets fรผr Kurzbefehle", + "system-monitor-disk-path-description": "Wรคhlen Sie, welchen Festplatten-Mountpunkt die Systemmonitor-Karte im Kontrollzentrum รผberwachen soll.", + "system-monitor-disk-path-label": "Systemmonitor Festplattenpfad", + "title": "Kontrollzentrum" + }, + "desktop-widgets": { + "clock-enabled-description": "Eine Uhr als Widget auf dem Desktop anzeigen.", + "clock-enabled-label": "Uhr-Widget aktivieren", + "clock-format-label": "Format", + "clock-height-description": "Hรถhe des Uhr-Widgets in Pixeln.", + "clock-height-label": "Hรถhe", + "clock-minimal-mode-description": "Verwende eine minimale Uhr-Anzeige mit Zeit und Datum.", + "clock-minimal-mode-label": "Minimaler Modus", + "clock-rounded-corners-description": "Verwende abgerundete Ecken fรผr den Widget-Hintergrund.", + "clock-rounded-corners-label": "Abgerundete Ecken", + "clock-show-background-description": "Zeige den Hintergrundcontainer fรผr das Uhr-Widget an.", + "clock-show-background-label": "Hintergrund anzeigen", + "clock-show-date-description": "Zeige das aktuelle Datum unter der Uhrzeit an.", + "clock-show-date-label": "Datum anzeigen", + "clock-show-seconds-description": "Sekunden in der Zeit anzeigen.", + "clock-show-seconds-label": "Sekunden anzeigen", + "clock-style-analog": "Analog", + "clock-style-binary": "Binรคr", + "clock-style-description": "Wรคhlen Sie den Anzeigestil der Uhr.", + "clock-style-digital": "Digital", + "clock-style-label": "Uhrstil", + "clock-style-minimal": "Minimal", + "clock-width-description": "Breite des Uhr-Widgets in Pixeln.", + "cpu-intensive-note": "Widgets, die mit ! markiert sind, verbrauchen mehr CPU und sollten bewusst aktiviert werden.", + "edit-mode-button-label": "Bearbeitungsmodus aktivieren", + "edit-mode-controls-explanation": "Linksklick & Ziehen: Widget verschieben oder GrรถรŸe รคndern.\nRechtsklick: Kontextmenรผ-Optionen รถffnen.", + "edit-mode-description": "Aktiviere den Bearbeitungsmodus, um Desktop-Widgets zu verschieben und neu zu positionieren. Im aktivierten Zustand zeigen Widgets eine Ziehumrandung und kรถnnen neu positioniert werden.", + "edit-mode-exit-button": "Bearbeitungsmodus verlassen", + "edit-mode-grid-snap-label": "Raster einrasten", + "edit-mode-grid-snap-scale-label": "Rasterfangskala", + "edit-mode-label": "Bearbeitungsmodus", + "enabled-description": "Desktop-Widgets vollstรคndig aktivieren oder deaktivieren.", + "enabled-label": "Desktop-Widgets aktivieren", + "general-desc": "Konfigurieren Sie Widgets, die auf Ihrem Desktop angezeigt werden.", + "general-title": "Desktop-Widgets", + "media-player-enabled-description": "Zeige ein Medienplayer-Widget auf dem Desktop an.", + "media-player-enabled-label": "Medienplayer-Widget aktivieren", + "media-player-rounded-corners-description": "Aktiviere abgerundete Ecken an den Widget-Kanten.", + "media-player-show-album-art-description": "Zeige das Albumcover und die Titelinformationen (Titel und Kรผnstler).", + "media-player-show-album-art-label": "Albumcover & Titel anzeigen", + "media-player-show-background-description": "Zeige den Hintergrundcontainer fรผr das Medienplayer-Widget an.", + "media-player-show-buttons-description": "Mediensteuerungstasten (Wiedergabe/Pause, Zurรผck, Weiter) im Medienplayer-Widget anzeigen.", + "media-player-show-buttons-label": "Tasten anzeigen", + "media-player-show-visualizer-description": "Audiovisualisierungs-Overlay anzeigen.", + "media-player-visualizer-type-description": "Wรคhlen Sie einen Visualisierungstyp fรผr den Hintergrund des Desktop-Medienplayers.", + "overview-enabled-description": "Desktop-Widgets anzeigen, wรคhrend die Compositor-รœbersicht geรถffnet ist.", + "overview-enabled-label": "In รœbersicht anzeigen", + "system-stat-layout-bottom": "Unten", + "system-stat-layout-description": "Wรคhlen Sie, wie die Legende relativ zum Diagramm angezeigt wird.", + "system-stat-layout-label": "Layout", + "system-stat-layout-side": "Seite", + "system-stat-rounded-corners-description": "Abgerundete Ecken fรผr den Widget-Hintergrund verwenden.", + "system-stat-rounded-corners-label": "Abgerundete Ecken", + "system-stat-show-background-description": "Zeige den Hintergrundcontainer fรผr das Systemstatistik-Widget.", + "system-stat-show-background-label": "Hintergrund anzeigen", + "system-stat-stat-type-description": "Wรคhlen Sie, welche Systemstatistik angezeigt werden soll.", + "system-stat-stat-type-label": "Statistiktyp", + "title": "Desktop-Widgets", + "weather-enabled-description": "Zeige ein Wetter-Widget auf dem Desktop an.", + "weather-enabled-label": "Wetter-Widget aktivieren", + "weather-show-background-description": "Zeige den Hintergrundcontainer fรผr das Wetter-Widget an." + }, + "display": { + "monitors-backlight-device-auto-option": "Standard", + "monitors-backlight-device-description": "Wรคhlen Sie ein Gerรคt fรผr die Hintergrundbeleuchtung fรผr diesen Ausgang.", + "monitors-backlight-device-label": "Hintergrundbeleuchtungsgerรคt", + "monitors-brightness-step-description": "SchrittgrรถรŸe fรผr Helligkeitsรคnderungen anpassen (Mausrad und Tastenkรผrzel).", + "monitors-brightness-step-label": "Helligkeits-SchrittgrรถรŸe", + "monitors-brightness-unavailable-ddc-disabled": "Helligkeitssteuerung nicht verfรผgbar. Aktivieren Sie \"Externe Helligkeitsunterstรผtzung\", um die Helligkeit dieses Displays zu steuern.", + "monitors-brightness-unavailable-generic": "Die Helligkeitssteuerung ist fรผr dieses Display nicht verfรผgbar.", + "monitors-desc": "Helligkeitseinstellungen fรผr jedes Display anpassen.", + "monitors-enforce-minimum-description": "Lรถst das Problem, dass die Hintergrundbeleuchtung auf einigen Displays bei 0% Helligkeit vollstรคndig ausgeschaltet wird.", + "monitors-enforce-minimum-label": "Mindesthelligkeit erzwingen (1%)", + "monitors-external-brightness-description": "DDCUtil-Unterstรผtzung zum Steuern der Helligkeit externer Displays รผber das DDC/CI-Protokoll aktivieren.", + "monitors-external-brightness-label": "Externe Helligkeitsunterstรผtzung", + "monitors-global-brightness-description": "Helligkeit fรผr alle Monitore gleichzeitig anpassen.", + "monitors-global-brightness-label": "Alle Monitore", + "monitors-title": "Monitor-spezifische Einstellungen", + "night-light-auto-schedule-description": "Basierend auf Sonnenuntergang und Sonnenaufgang in {location} โ€” empfohlen.", + "night-light-auto-schedule-label": "Automatische Planung", + "night-light-desc": "Blaulicht-Emission reduzieren fรผr besseren Schlaf und weniger Augenbelastung.", + "night-light-enable-description": "Warmen Farbfilter anwenden, um Blaulicht-Emission zu reduzieren.", + "night-light-enable-label": "Nachtlicht aktivieren", + "night-light-force-activation-description": "Ignoriert die Planung und wendet den Nachtfilter sofort an.", + "night-light-force-activation-label": "Aktivierung erzwingen", + "night-light-manual-schedule-description": "Benutzerdefinierte Zeiten fรผr Sonnenaufgang und Sonnenuntergang festlegen.", + "night-light-manual-schedule-label": "Manuelle Planung", + "night-light-manual-schedule-select-start": "Startzeit auswรคhlen", + "night-light-manual-schedule-select-stop": "Endzeit auswรคhlen", + "night-light-manual-schedule-sunrise": "Sonnenaufgang", + "night-light-manual-schedule-sunset": "Sonnenuntergang", + "night-light-temperature-day": "Tag", + "night-light-temperature-day-description": "Steuert die Farbtemperatur tagsรผber.", + "night-light-temperature-description": "Farbwรคrme fรผr Nacht- und Tageszeit einstellen.", + "night-light-temperature-label": "Farbtemperatur", + "night-light-temperature-night": "Nacht", + "night-light-temperature-night-description": "Steuert die Farbtemperatur nachts.", + "title": "Anzeige" + }, + "dock": { + "appearance-background-opacity-description": "Hintergrundtransparenz des Docks anpassen.", + "appearance-border-radius-description": "Den Radius der Dock-Umrandung anpassen.", + "appearance-border-radius-label": "Eckenradius", + "appearance-colorize-icons-description": "Themenfarben auf Dock-App-Symbole anwenden (nur nicht fokussierte Apps).", + "appearance-colorize-icons-label": "Symbole einfรคrben", + "appearance-dead-opacity-description": "Die Deckkraft von App-Symbolen anpassen, die nicht ausgefรผhrt werden.", + "appearance-dead-opacity-label": "Tote Opazitรคt", + "appearance-desc": "Verhalten und Erscheinungsbild des Docks anpassen.", + "appearance-display-auto-hide": "Automatisch ausblenden", + "appearance-display-description": "Wรคhlen Sie, wie sich das Dock verhรคlt.", + "appearance-display-exclusive": "Exklusiv", + "appearance-dock-indicator-description": "Zeige eine kleine Anzeige, wenn das Dock ausgeblendet ist.", + "appearance-dock-indicator-label": "Dock-Indikator", + "appearance-floating-distance-description": "Schwebeabstand vom Bildschirmrand anpassen.", + "appearance-floating-distance-label": "Dock-Schwebeabstand", + "appearance-group-apps-description": "Gruppiere mehrere Fenster derselben App zu einem Dock-Eintrag.", + "appearance-group-apps-label": "Gleiche Apps gruppieren", + "appearance-group-click-action-cycle": "Fenster durchwechseln", + "appearance-group-click-action-description": "Wรคhlen Sie, was ein Linksklick fรผr gruppierte Apps tut.", + "appearance-group-click-action-label": "Aktion beim Klick auf gruppierte App", + "appearance-group-click-action-list": "Fensterliste รถffnen", + "appearance-group-context-menu-mode-description": "Wรคhlen Sie, wie sich das Kontextmenรผ fรผr gruppierte Apps verhรคlt.", + "appearance-group-context-menu-mode-extended": "Erweitert", + "appearance-group-context-menu-mode-label": "Gruppierter App-Menรผ-Modus", + "appearance-group-context-menu-mode-list": "Fensterliste", + "appearance-group-indicator-style-description": "Wรคhlen Sie, wie gruppierte laufende Indikatoren den Zustand des fokussierten Fensters anzeigen.", + "appearance-group-indicator-style-dots": "Punkte", + "appearance-group-indicator-style-label": "Gruppierter Anzeigestil", + "appearance-group-indicator-style-number": "Nummer", + "appearance-hide-show-speed-description": "Geschwindigkeit der Ein-/Ausblend-Animation des Docks anpassen.", + "appearance-hide-show-speed-label": "Ein-/Ausblend-Geschwindigkeit", + "appearance-icon-size-description": "GesamtgrรถรŸe des Docks anpassen.", + "appearance-icon-size-label": "Dock-GrรถรŸe", + "appearance-inactive-indicators-description": "Anzeige von Indikator-Pillen fรผr alle Apps, nicht nur fรผr die aktuell aktive.", + "appearance-inactive-indicators-label": "Laufende Indikatoren", + "appearance-indicator-color-description": "Wรคhlen Sie die Farbe des Indikators fรผr das versteckte Dock.", + "appearance-indicator-color-label": "Indikatorfarbe", + "appearance-indicator-opacity-description": "Passen Sie die Deckkraft des Indikators fรผr das versteckte Dock an.", + "appearance-indicator-opacity-label": "Indikator-Deckkraft", + "appearance-indicator-thickness-description": "Aktivieren Sie einen dickeren Indikator fรผr das versteckte Dock (6px statt 3px).", + "appearance-indicator-thickness-label": "Dickerer Indikator", + "appearance-launcher-icon-description": "Wรคhle das Symbol, das fรผr den Dock Launcher verwendet wird.", + "appearance-launcher-icon-label": "Launcher-Symbol", + "appearance-launcher-position-description": "Wรคhlen Sie, wo das Launcher-Symbol im Dock erscheint.", + "appearance-launcher-position-end": "Ende", + "appearance-launcher-position-label": "Position des Starters", + "appearance-launcher-position-start": "Start", + "appearance-launcher-use-distro-logo-description": "Verwende das Logo deiner Distribution als Dock-Launcher-Symbol.", + "appearance-launcher-use-distro-logo-label": "Distro-Logo anstelle des Icons verwenden", + "appearance-pinned-static-description": "Gepinnte App-Symbole immer in statischer Reihenfolge nach links schieben.", + "appearance-pinned-static-label": "Statische angeheftete Apps", + "appearance-position-description": "Wรคhlen Sie, wo das Dock auf dem Bildschirm angezeigt wird.", + "appearance-position-label": "Position", + "appearance-show-launcher-icon-description": "Das Anwendungsstarter-Symbol im Dock anzeigen.", + "appearance-show-launcher-icon-label": "App-Launcher anzeigen", + "appearance-sit-on-frame-description": "Richten Sie das Dock innerhalb des Rahmenrands aus, anstatt es oben zu platzieren.", + "appearance-sit-on-frame-label": "Dock sitzt am Rahmen", + "appearance-type-attached": "Angedockt", + "appearance-type-description": "Wรคhlen Sie zwischen einer schwebenden Pillenform oder einer statischen, am Rand befestigten Leiste.", + "appearance-type-floating": "Schwebend", + "appearance-type-label": "Dock-Stil", + "enabled-description": "Dock vollstรคndig anzeigen oder ausblenden.", + "enabled-label": "Dock aktivieren", + "monitors-desc": "Dock auf bestimmten Monitoren anzeigen. StandardmรครŸig werden alle angezeigt, wenn keine ausgewรคhlt wurden.", + "monitors-only-same-monitor-description": "Zeige nur Apps von dem Monitor an, auf dem sich das Dock befindet.", + "monitors-only-same-monitor-label": "Nur Apps vom selben Monitor", + "monitors-title": "Monitor-Anzeige", + "title": "Dock" + }, + "general": { + "copy-settings": "Einstellungen kopieren", + "fonts-default-description": "Hauptschriftart fรผr die gesamte Benutzeroberflรคche.", + "fonts-default-label": "Standard-Schriftart", + "fonts-default-placeholder": "Standard-Schriftart auswรคhlen...", + "fonts-default-scale-description": "ร„ndern Sie die GrรถรŸe des Standardtextes.", + "fonts-default-scale-label": "StandardmรครŸige SchriftgrรถรŸe", + "fonts-default-search-placeholder": "Schriftarten suchen...", + "fonts-desc": "Wรคhlen Sie die Schriftart der Benutzeroberflรคche.", + "fonts-monospace-description": "Monospace-Schriftart fรผr Zahlen und Statistik-Anzeigen.", + "fonts-monospace-label": "Monospace-Schriftart", + "fonts-monospace-placeholder": "Monospace-Schriftart auswรคhlen...", + "fonts-monospace-scale-description": "ร„ndern Sie die GrรถรŸe des Monotextes.", + "fonts-monospace-scale-label": "SchriftgrรถรŸe mit fester Breite", + "fonts-monospace-search-placeholder": "Monospace-Schriftarten suchen...", + "fonts-title": "Schriftarten", + "keybinds-conflict-description": "Die Tastenkombination ist bereits {action} zugewiesen.", + "keybinds-conflict-title": "Tastenbelegungskonflikt", + "keybinds-description": "Globale Navigationstasten fรผr Panels und Launcher konfigurieren.", + "keybinds-down": "Nach unten", + "keybinds-enter": "Bestรคtigen / Aktion", + "keybinds-escape": "SchlieรŸen / Zurรผck", + "keybinds-left": "Nach links", + "keybinds-modifier-description": "Tastenkรผrzel mรผssen eine Modifikatortaste (Strg oder Alt) enthalten.", + "keybinds-modifier-title": "Modifikator erforderlich", + "keybinds-remove": "Entfernen / Lรถschen", + "keybinds-right": "Nach rechts", + "keybinds-title": "Navigations-Tastenkombinationen", + "keybinds-up": "Nach oben", + "language-desc": "ร„ndern Sie die Sprache von Noctalia.", + "language-select-auto-detect": "Automatisch", + "language-select-description": "Wรคhlen Sie die Sprache der Anwendungsoberflรคche.", + "language-select-label": "Anwendungssprache", + "launch-setup-wizard": "Setup-Assistent", + "profile-desc": "ร„ndern Sie Ihren Avatar.", + "profile-picture-description": "Der Avatar, der in der gesamten Benutzeroberflรคche angezeigt wird.", + "profile-picture-label": "Profilbild", + "profile-select-avatar": "Avatar auswรคhlen", + "profile-title": "Profil", + "profile-tooltip": "Profilbild", + "reverse-scrolling-description": "Die interpretierte Scrollrichtung umkehren.", + "reverse-scrolling-label": "Scrollen umkehren", + "screen-corners-desc": "Bildschirmecken-Rundung und visuelle Effekte anpassen.", + "screen-corners-radius-description": "Rundung der Bildschirmecken anpassen.", + "screen-corners-radius-label": "Bildschirmecken-Radius", + "screen-corners-radius-reset": "Eckenradius des Bildschirms zurรผcksetzen", + "screen-corners-show-corners-description": "Gerundete Ecken am Rand des Bildschirms anzeigen.", + "screen-corners-show-corners-label": "Bildschirmecken anzeigen", + "screen-corners-solid-black-description": "Schwarze Ecken anstelle der Leisten-Hintergrundfarbe verwenden.", + "screen-corners-solid-black-label": "Schwarze Ecken", + "screen-corners-title": "Bildschirmecken", + "settings-copied": "Einstellungen in die Zwischenablage kopiert", + "smooth-scrolling-description": "Listen-Scrolling animieren fรผr ein flรผssigeres Mausrad-Erlebnis.", + "smooth-scrolling-label": "Sanftes Scrollen", + "tab-basics": "Grundlagen", + "tab-keybinds": "Tastenkombinationen" + }, + "hooks": { + "color-generation-description": "Befehl, der ausgefรผhrt wird, wenn die Farberzeugung abgeschlossen ist (z.B. nach der Hintergrundbild-basierten Thematisierung).", + "color-generation-label": "Farben generiert", + "color-generation-placeholder": "z.B. notify-send \"Farben\" \"Generiert\"", + "info-command-info-description": "โ€ข Befehle werden รผber Shell ausgefรผhrt (sh -lc)
โ€ข Befehle laufen im Hintergrund (getrennt)
โ€ข Test-Buttons fรผhren mit aktuellen Werten aus", + "info-parameters-description": "โ€ข Wallpaper hook: $1 = Hintergrundbildpfad, $2 = Bildschirmname, $3 = Thema (dunkel/hell)
โ€ข Theme toggle hook: $1 = true/false (Dunkelmodus-Status)
โ€ข Screen lock/unlock hooks: $1 = lock/unlock (Bildschirmsperrstatus)
โ€ข Performance mode hooks: Keine Parameter
โ€ข Session hook: $1 = action (shutdown/reboot)", + "info-parameters-label": "Verfรผgbare Parameter", + "noctalia-started-description": "Befehl, der ausgefรผhrt wird, wenn Noctalia vollstรคndig geladen wurde.", + "noctalia-started-label": "Noctalia gestartet", + "noctalia-started-placeholder": "z.B. notify-send 'Noctalia geladen'", + "performance-mode-disabled-description": "Befehl, der ausgefรผhrt wird, wenn der Noctalia-Leistungsmodus deaktiviert wird.", + "performance-mode-disabled-label": "Leistungsmodus deaktiviert", + "performance-mode-disabled-placeholder": "z.B. notify-send \"Leistung\" \"Modus deaktiviert\"", + "performance-mode-enabled-description": "Befehl, der ausgefรผhrt wird, wenn der Noctalia-Leistungsmodus aktiviert wird.", + "performance-mode-enabled-label": "Leistungsmodus aktiviert", + "performance-mode-enabled-placeholder": "z.B. notify-send \"Leistung\" \"Modus aktiviert\"", + "screen-lock-description": "Befehl, der ausgefรผhrt wird, wenn der Bildschirm gesperrt wird.", + "screen-lock-label": "Bildschirm gesperrt", + "screen-lock-placeholder": "z.B. notify-send \"Bildschirm\" \"Gesperrt\"", + "screen-unlock-description": "Befehl, der ausgefรผhrt wird, wenn der Bildschirm entsperrt wird.", + "screen-unlock-label": "Bildschirm entsperrt", + "screen-unlock-placeholder": "z.B. notify-send \"Bildschirm\" \"Entsperrt\"", + "session-description": "Befehl, der vor dem Herunterfahren oder Neustart ausgefรผhrt werden soll. Empfรคngt den Aktionstyp als $1 (Herunterfahren/Neustart).", + "session-label": "Sitzungsende", + "session-placeholder": "z.B. notify-send \"Session\" \"$1\"", + "system-hooks-desc": "Befehle konfigurieren, die bei Systemereignissen ausgefรผhrt werden.", + "system-hooks-enable-description": "Alle Hook-Befehle aktivieren oder deaktivieren.", + "system-hooks-enable-label": "Hooks aktivieren", + "system-hooks-title": "System-Hooks", + "theme-changed-description": "Befehl, der ausgefรผhrt wird, wenn das Thema zwischen dunklem und hellem Modus wechselt.", + "theme-changed-label": "Thema geรคndert", + "theme-changed-placeholder": "z.B. notify-send \"Thema\" \"Gewechselt\"", + "title": "Hooks", + "wallpaper-changed-description": "Befehl, der ausgefรผhrt wird, wenn sich das Hintergrundbild รคndert.", + "wallpaper-changed-label": "Hintergrundbild geรคndert", + "wallpaper-changed-placeholder": "z.B. notify-send \"Hintergrundbild\" \"Geรคndert\"" + }, + "idle": { + "custom-add": "Befehl hinzufรผgen", + "custom-description": "Fรผhre einen Shell-Befehl nach einer Zeit der Inaktivitรคt aus.", + "custom-entry-command": "Befehl", + "custom-entry-delete": "Lรถschen", + "custom-entry-edit": "Benutzerdefinierten Befehl bearbeiten", + "custom-entry-name": "Name", + "custom-entry-name-placeholder": "z.B. Lichter ausschalten", + "custom-entry-new": "Neuer Benutzerdefinierter Befehl", + "custom-entry-timeout": "Inaktivitรคtszeit", + "custom-entry-timeout-format": "{count} Sekunde", + "custom-entry-timeout-format-plural": "{count} Sekunden", + "custom-entry-unnamed": "Unbenannter Befehl", + "custom-label": "Benutzerdefinierte Inaktivitรคtsbefehle", + "enable-description": "Automatisch den Bildschirm ausschalten, sperren oder in den Energiesparmodus versetzen nach einer Zeit der Inaktivitรคt.", + "enable-label": "Inaktivitรคtsverwaltung aktivieren", + "fade-duration-description": "Sekunden fรผr die Schwarzblende-Animation, bevor jede Aktion ausgelรถst wird. Jede Mausbewegung bricht die รœberblendung ab.", + "fade-duration-label": "Einblenddauer", + "lock-description": "Sekunden der Inaktivitรคt, bevor der Sperrbildschirm aktiviert wird.", + "lock-label": "Bildschirmsperre", + "resume-command-label": "Befehl fortsetzen", + "screen-off-description": "Sekunden der Inaktivitรคt, bevor die Monitore ausgeschaltet werden.", + "screen-off-label": "Bildschirm ausschalten", + "status-description": "Inaktivitรคtszeit, wie vom Compositor gemeldet.", + "status-label": "Inaktivitรคtszeit", + "suspend-description": "Sekunden der Inaktivitรคt, bevor das System in den Energiesparmodus geht.", + "tab-behavior": "Verhalten", + "tab-custom": "Benutzerdefiniert", + "timeouts-description": "Auf 0 setzen, um eine Stufe zu deaktivieren. Timeouts werden pausiert, wรคhrend 'Wach halten' aktiv ist.", + "timeouts-label": "Timeouts", + "title": "Inaktivitรคt", + "unavailable": "Native Inaktivitรคtsรผberwachung ist auf diesem Compositor nicht verfรผgbar." + }, + "indicator": { + "default-value": "Standard: {value}", + "system-default": "Systemstandard" + }, + "launcher": { + "clipboard-desc": "Greifen Sie รผber den Launcher auf Ihren Zwischenablageverlauf zu und verwalten Sie ihn.", + "execute-desc": "Konfigurieren Sie, wie Anwendungen gestartet werden.", + "execute-title": "Ausfรผhrung", + "settings-annotation-tool-description": "Befehl, der beim Klicken auf die Annotieren-Schaltflรคche im Zwischenablageverlauf ausgefรผhrt wird. Das Bild wird an diesen Befehl weitergeleitet.", + "settings-annotation-tool-label": "Annotationstool", + "settings-annotation-tool-placeholder": "z.B. 'gradia', 'satty -f -'", + "settings-auto-paste-description": "Automatisches Einfรผgen des ausgewรคhlten Zwischenablageinhalts. Benรถtigt wtype.", + "settings-auto-paste-label": "Automatisches Einfรผgen", + "settings-clip-chips-description": "Eine Registerkartenleiste anzeigen, um die Zwischenablagehistorie nach Typ zu filtern (Bilder, Links, Dateien, Code usw.).", + "settings-clip-chips-label": "Kategorie-Chips aktivieren", + "settings-clip-preview-description": "Zeige eine Vorschau des Inhalts der Zwischenablage an, wenn der Befehl >clip verwendet wird.", + "settings-clip-preview-label": "Clip-Vorschau aktivieren", + "settings-clip-smart-icons-description": "Spezifische Symbole fรผr Links, Dateien, Farben und Code anzeigen, anstatt eines generischen Zwischenablagesymbols.", + "settings-clip-smart-icons-label": "Intelligente Symbole aktivieren", + "settings-clip-wrap-text-description": "Text in der Zwischenablage-Liste umbrechen, anstatt ihn abzuschneiden.", + "settings-clip-wrap-text-label": "Zwischenablage-Text umbrechen", + "settings-clipboard-history-description": "Zugriff auf zuvor kopierte Elemente รผber den Launcher.", + "settings-clipboard-history-label": "Zwischenablageverlauf aktivieren", + "settings-clipboard-watch-image-description": "Vollstรคndiger Befehlsstring, der an wl-paste fรผr Bildรคnderungen รผbergeben wird (Neustart erforderlich).", + "settings-clipboard-watch-image-label": "Bildรผberwachungsbefehl", + "settings-clipboard-watch-text-description": "Vollstรคndiger Befehlsstring, der an wl-paste fรผr Textรคnderungen รผbergeben wird (Neustart erforderlich).", + "settings-clipboard-watch-text-label": "Textรผberwachungsbefehl", + "settings-custom-launch-prefix-description": "Befehle mit einem benutzerdefinierten Launcher prรคfixieren (z.B. 'runapp' fรผr systemd-Integration).", + "settings-custom-launch-prefix-enabled-description": "Verwenden Sie ein benutzerdefiniertes Prรคfix zum Starten von Anwendungen anstelle der Standardmethode.", + "settings-custom-launch-prefix-enabled-label": "Benutzerdefiniertes Start-Prรคfix aktivieren", + "settings-custom-launch-prefix-label": "Benutzerdefiniertes Start-Prรคfix", + "settings-density-description": "GrรถรŸe der Anwendungssymbole und Dichte des Launchers anpassen.", + "settings-density-label": "Dichte", + "settings-desc": "Verhalten und Erscheinungsbild des Starters anpassen.", + "settings-enable-session-search-description": "Sitzungsaktionen (Sperren, Herunterfahren, Neustarten usw.) beim Suchen im Launcher anzeigen.", + "settings-enable-session-search-label": "Sitzungssuche aktivieren", + "settings-enable-settings-search-description": "Einstellungen in den Suchergebnissen des Launchers anzeigen.", + "settings-enable-settings-search-label": "Einstellungen-Suche aktivieren", + "settings-enable-windows-search-description": "Aktive Fenster suchen und fokussieren.", + "settings-enable-windows-search-label": "Fenstersuche aktivieren", + "settings-grid-view-description": "Elemente in einem Raster statt in einer Liste anzeigen.", + "settings-icon-mode-description": "Verwende native System-Icons anstelle von Tabler-Icons.", + "settings-icon-mode-label": "Native Icons verwenden", + "settings-ignore-mouse-input-description": "Deaktivieren Sie die Mausinteraktion und das Mausrad im Launcher.", + "settings-ignore-mouse-input-label": "Maus-Eingabe ignorieren", + "settings-overlay-layer-description": "Zeigt das Startprogramm auf der รœberlagerungsebene an, รผber Vollbildfenstern. Wenn aktiviert, wird das Startprogramm nicht an der Leiste befestigt.", + "settings-overlay-layer-label": "รœber Vollbild anzeigen", + "settings-position-description": "Wรคhlen Sie, wo das Starter-Panel erscheint.", + "settings-show-categories-description": "Kategorienregisterkarten zum Filtern von Anwendungen anzeigen.", + "settings-show-categories-label": "Kategorien anzeigen", + "settings-show-icon-background-description": "Zeige einen abgerundeten Rechteck-Hintergrund hinter Icons.", + "settings-show-icon-background-label": "Symbolhintergrund anzeigen", + "settings-sort-by-usage-description": "Wenn aktiviert, erscheinen hรคufig gestartete Apps zuerst in der Liste.", + "settings-sort-by-usage-label": "Nach Hรคufigkeit sortieren", + "settings-terminal-command-description": "Befehl zum Starten eines Terminals. Z.B. 'kitty -e' oder 'gnome-terminal --'.", + "settings-terminal-command-label": "Terminalbefehl", + "settings-use-app2unit-description": "Verwendet eine alternative Startmethode zur besseren Verwaltung von App-Prozessen und Problemvermeidung.", + "settings-use-app2unit-label": "App2Unit zum Starten von Anwendungen verwenden", + "settings-view-mode-description": "Wรคhlen Sie das Layout fรผr die Anwendungsstarter-Eintrรคge.", + "settings-view-mode-label": "Ansichtsmodus", + "title": "Starter" + }, + "location": { + "auto-locate-description": "Ermittelt Ihren Standort automatisch und regelmรครŸig anhand Ihrer IP-Adresse.", + "auto-locate-label": "Automatische Ortung", + "calendar-cards-desc": "Karten im Kalender-Panel organisieren und aktivieren/deaktivieren.", + "calendar-cards-title": "Kalenderkarten", + "calendar-header-label": "Kalenderkopfzeile", + "calendar-month-label": "Kalendermonat", + "date-time-12hour-format-description": "Zeige die Uhrzeit im 12-Stunden-Format auf dem Sperrbildschirm und im Kalender an. Die Uhr in der Leiste hat eigene Einstellungen.", + "date-time-12hour-format-label": "12-Stunden-Zeitformat benutzen", + "date-time-desc": "Anpassen, wie Datum und Zeit erscheinen.", + "date-time-first-day-of-week-automatic": "Automatisch (Systemgebietsschema verwenden)", + "date-time-first-day-of-week-description": "Wรคhlen Sie, welcher Tag die Woche im Kalender beginnen soll.", + "date-time-first-day-of-week-label": "Erster Tag der Woche", + "date-time-show-events-description": "Ereignisse im Kalender-Panel anzeigen.", + "date-time-show-events-label": "Kalenderereignisse anzeigen", + "date-time-title": "Datum & Zeit", + "date-time-use-analog-description": "Zeige eine Analoguhr im Kalenderfenster an.", + "date-time-use-analog-label": "Analoge Uhr verwenden", + "date-time-week-numbers-description": "Zeige die Woche des Jahres (z.B. Woche 38) im Kalender an.", + "date-time-week-numbers-label": "Wochennummern anzeigen", + "geolocate-now-button": "Jetzt orten", + "location-desc": "Genaues Wetter und Nachtlicht-Planung durch Festlegung Ihres Standorts erhalten.", + "location-search-description": "z.B. Dortmund, Deutschland", + "location-search-label": "Nach einem Standort suchen", + "location-search-placeholder": "Standortnamen eingeben", + "location-title": "Ihr Standort", + "weather-desc": "Bevorzugte Temperatureinheit wรคhlen.", + "weather-enabled-description": "Wetterinformationen in der gesamten Benutzeroberflรคche anzeigen und Wetterdaten abrufen. Wenn deaktiviert, werden alle Wetterelemente ausgeblendet und es werden keine Netzwerkanfragen gestellt.", + "weather-enabled-label": "Wetter aktivieren", + "weather-fahrenheit-description": "Temperatur in Fahrenheit statt Celsius anzeigen.", + "weather-fahrenheit-label": "Temperatur in Fahrenheit (ยฐF) anzeigen", + "weather-hide-city-description": "Verberge den Stadtnamen in Wetteranzeigen in der gesamten Benutzeroberflรคche.", + "weather-hide-city-label": "Stadtname ausblenden", + "weather-hide-timezone-description": "Die Zeitzonenabkรผrzung in Wetteranzeigen in der gesamten Benutzeroberflรคche ausblenden.", + "weather-hide-timezone-label": "Zeitzone ausblenden", + "weather-show-effects-description": "Zeige zusรคtzliche visuelle Effekte (wie Regen, Schnee oder Blitze) auf der Wetterkarte an.", + "weather-show-effects-label": "Wettereffekte anzeigen", + "weather-show-in-calendar-description": "Zeige die tรคgliche Wettervorhersage direkt in deiner Kalenderansicht an.", + "weather-show-in-calendar-label": "Wetter im Kalender anzeigen", + "weather-talia-mascot-always-description": "Verwenden Sie immer Talia emojis anstelle von tabler icons.", + "weather-talia-mascot-always-label": "Immer Talia Wetter-Emojis anzeigen" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "Wenn fprintd (Fingerabdruck-Authentifizierung) aktiv ist, kรถnnen Sie mit dieser Option weiterhin Ihr Passwort anstelle eines Fingerabdrucks verwenden.", + "allow-password-with-fprintd-label": "Passwort-Anmeldung mit fprintd erlauben", + "auto-start-auth-description": "Startet die Fingerabdruckauthentifizierung automatisch, ohne dass ein Tastendruck oder ein Klick erforderlich ist.", + "auto-start-auth-label": "Automatische Authentifizierung beim Start", + "clock-format-description": "Passe das Uhrformat mit Datums-/Uhrzeit-Syntax-Tokens an.", + "clock-format-label": "Uhrenformat", + "clock-style-analog": "Analog", + "clock-style-custom": "Benutzerdefiniert", + "clock-style-description": "Wรคhlen Sie den visuellen Stil der Uhr auf dem Sperrbildschirm.", + "clock-style-digital": "Digital", + "clock-style-label": "Uhrstil", + "compact-lockscreen-description": "Zeige nur die Login-Eingabe und Systemsteuerung, blende Wetter- und Medien-Widgets aus.", + "compact-lockscreen-label": "Kompakter Sperrbildschirm", + "enable-lockscreen-media-controls-description": "Interaktive Medienwiedergabesteuerung auf dem Sperrbildschirm anzeigen.", + "enable-lockscreen-media-controls-label": "Sperrbildschirm-Mediensteuerung", + "lock-on-suspend-description": "Den Bildschirm beim Energiesparen automatisch sperren.", + "lock-on-suspend-label": "Sperren beim Energiesparen", + "lock-screen-animations-description": "Sperrbildschirm-Animationen aktivieren oder deaktivieren.", + "lock-screen-animations-label": "Sperrbildschirm-Animationen", + "lock-screen-blur-strength-description": "Wendet einen Weichzeichnungseffekt auf das Hintergrundbild des Sperrbildschirms an.", + "lock-screen-blur-strength-label": "Sperrbildschirm-Unschรคrfestรคrke", + "lock-screen-tint-strength-description": "Wendet eine Farbรผberlagerung auf das Hintergrundbild des Sperrbildschirms an.", + "lock-screen-tint-strength-label": "Stรคrke der Sperrbildschirmtรถnung", + "monitors-desc": "Sperrbildschirm auf bestimmten Monitoren anzeigen. StandardmรครŸig auf alle, wenn keine ausgewรคhlt sind.", + "password-chars-description": "Niedliche Symbole, die dein Passwort verstecken.", + "password-chars-label": "Zufรคllige Passwortsymbole", + "show-hibernate-description": "Die Option 'Ruhezustand' in den Energieoptionen anzeigen.", + "show-hibernate-label": "Ruhezustand anzeigen", + "show-session-buttons-description": "Zugriff auf die Energieeinstellungen vom Sperrbildschirm aus erlauben.", + "show-session-buttons-label": "Energieoptionen", + "title": "Sperrbildschirm" + }, + "network": { + "bluetooth-description": "Bluetooth-Verwaltung aktivieren.", + "bluetooth-rssi-polling-description": "Periodisch RSSI fรผr verbundene Gerรคte รผber bluetoothctl abfragen. Ist mรถglicherweise nicht fรผr alle Gerรคte verfรผgbar; verbraucht bei Aktivierung minimale Ressourcen.", + "bluetooth-rssi-polling-label": "Bluetooth-Signalabfrage", + "desc": "WLAN- und Bluetooth-Verbindungen verwalten.", + "wifi-description": "Drahtlosnetzwerke verwalten (erfordert NetworkManager)." + }, + "notifications": { + "duration-critical-urgency-description": "Wie lange kritische Benachrichtigungen sichtbar bleiben.", + "duration-critical-urgency-label": "Kritische Dringlichkeit", + "duration-desc": "Konfigurieren, wie lange Benachrichtigungen je nach Dringlichkeitsstufe sichtbar bleiben.", + "duration-low-urgency-description": "Wie lange Benachrichtigungen niedriger Prioritรคt sichtbar bleiben.", + "duration-low-urgency-label": "Niedrige Dringlichkeit", + "duration-normal-urgency-description": "Wie lange Benachrichtigungen normaler Prioritรคt sichtbar bleiben.", + "duration-normal-urgency-label": "Normale Dringlichkeit", + "duration-respect-expire-description": "Die in der Benachrichtigung gesetzte Ablaufzeit verwenden.", + "duration-respect-expire-label": "Ablaufzeit respektieren", + "duration-title": "Benachrichtigungsdauer", + "history-clear-dismiss-description": "Benachrichtigung aus dem Verlauf lรถschen, wenn sie verworfen wird.", + "history-clear-dismiss-label": "Lรถschen beim SchlieรŸen", + "history-critical-urgency-description": "Kritische Prioritรคtsbenachrichtigungen im Verlauf speichern.", + "history-critical-urgency-label": "Kritische Dringlichkeit im Verlauf speichern", + "history-desc": "Steuern, welche Benachrichtigungen basierend auf ihrer Dringlichkeitsstufe im Verlauf gespeichert werden.", + "history-low-urgency-description": "Niedrige Prioritรคtsbenachrichtigungen im Verlauf speichern.", + "history-low-urgency-label": "Niedrige Dringlichkeit im Verlauf speichern", + "history-normal-urgency-description": "Normale Prioritรคtsbenachrichtigungen im Verlauf speichern.", + "history-normal-urgency-label": "Normale Dringlichkeit im Verlauf speichern", + "monitors-desc": "Benachrichtigungen auf bestimmten Monitoren anzeigen. StandardmรครŸig werden sie auf allen Monitoren angezeigt.", + "rules-action-block": "Block", + "rules-action-block-desc": "รœberspringt komplett.", + "rules-action-hide": "Ausblenden", + "rules-action-hide-desc": "Kein Popup, kein Ton, wird zur Historie hinzugefรผgt.", + "rules-action-label": "Aktion", + "rules-action-mute": "Stumm", + "rules-action-mute-desc": "Kein Ton, Pop-up wird trotzdem angezeigt und ist im Verlauf.", + "rules-add": "Regel hinzufรผgen", + "rules-delete": "Regel lรถschen", + "rules-description": "App-Namen oder Inhalt abgleichen. Regeln werden der Reihe nach geprรผft, und die erste รœbereinstimmung wird angewendet.", + "rules-edit": "Regel bearbeiten", + "rules-label": "Filterregeln", + "rules-pattern-label": "Muster", + "rules-pattern-placeholder": "firefox, discord, oder /regex/", + "rules-tab": "Regeln", + "settings-always-on-top-description": "Benachrichtigungen รผber Vollbildfenstern und anderen Ebenen anzeigen.", + "settings-background-opacity-description": "Transparenz der Benachrichtigungshintergrรผnde anpassen.", + "settings-density-description": "Wรคhlen Sie die Dichte der Benachrichtigungskarte.", + "settings-density-label": "Dichte", + "settings-desc": "Erscheinungsbild und Verhalten von Benachrichtigungen konfigurieren.", + "settings-do-not-disturb-description": "Alle Benachrichtigungs-Popups deaktivieren.", + "settings-enabled-description": "Benachrichtigungs-Daemon aktivieren oder deaktivieren, erfordert einen Neustart von Noctalia Shell.", + "settings-enabled-label": "Benachrichtigungen aktivieren", + "settings-location-description": "Wo Benachrichtigungen auf dem Bildschirm erscheinen.", + "settings-markdown-description": "Benachrichtigungsinhalt mit Markdown-Formatierung rendern.", + "settings-markdown-label": "Markdown aktivieren", + "sounds-desc": "Benachrichtigungs-Soundeffekte und Lautstรคrke konfigurieren.", + "sounds-enabled-description": "Soundeffekte fรผr eingehende Benachrichtigungen aktivieren.", + "sounds-enabled-label": "Benachrichtigungstรถne aktivieren", + "sounds-excluded-apps-description": "รœberspringen Sie das Abspielen der konfigurierten Benachrichtigungstรถne fรผr bestimmte Anwendungen, die ihre eigenen integrierten Tรถne haben.", + "sounds-excluded-apps-label": "Ausgeschlossene Anwendungen", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "Pfad zur Sound-Datei, die fรผr Benachrichtigungen mit kritischer Prioritรคt abgespielt wird.", + "sounds-files-critical-label": "Sound bei kritischer Dringlichkeit", + "sounds-files-critical-select-title": "Sound-Datei fรผr kritische Dringlichkeit auswรคhlen", + "sounds-files-desc": "Benutzerdefinierte Sound-Dateien fรผr verschiedene Benachrichtigungsdringlichkeitsstufen konfigurieren.", + "sounds-files-low-description": "Pfad zur Sound-Datei, die fรผr Benachrichtigungen mit niedriger Prioritรคt abgespielt wird.", + "sounds-files-low-label": "Sound bei niedriger Dringlichkeit", + "sounds-files-low-select-title": "Sound-Datei fรผr niedrige Dringlichkeit auswรคhlen", + "sounds-files-normal-description": "Pfad zur Sound-Datei, die fรผr Benachrichtigungen mit normaler Prioritรคt abgespielt wird.", + "sounds-files-normal-label": "Sound bei normaler Dringlichkeit", + "sounds-files-normal-select-title": "Sound-Datei fรผr normale Dringlichkeit auswรคhlen", + "sounds-files-placeholder": "Pfad zur Sound-Datei eingeben", + "sounds-files-select-file": "Sound-Datei auswรคhlen", + "sounds-files-title": "Sound-Dateien", + "sounds-files-unified-description": "Pfad zur Sound-Datei, die fรผr Benachrichtigungen abgespielt wird.", + "sounds-files-unified-label": "Benachrichtigungssound", + "sounds-files-unified-select-title": "Benachrichtigungssound-Datei auswรคhlen", + "sounds-separate-description": "Unterschiedliche Sound-Dateien fรผr niedrige, normale und kritische Benachrichtigungsprioritรคten verwenden.", + "sounds-separate-label": "Verschiedene Sounds pro Prioritรคt verwenden", + "sounds-title": "Sound-Einstellungen", + "sounds-unavailable-description": "Installieren Sie Qt6 Multimedia, um Benachrichtigungstรถne zu aktivieren.", + "sounds-unavailable-label": "Benachrichtigungstรถne nicht verfรผgbar", + "sounds-volume-description": "Lautstรคrke fรผr Benachrichtigungstรถne anpassen.", + "sounds-volume-label": "Sound-Lautstรคrke", + "toast-battery-description": "Zeige eine Warnung an, wenn der Akkustand unter diesen Prozentsatz fรคllt.", + "toast-battery-label": "Akkuwarnung", + "toast-desc": "Konfigurieren Sie das Aussehen und Verhalten von Toast-Benachrichtigungen.", + "toast-keyboard-description": "Zeige eine Toast-Benachrichtigung an, wenn sich das Tastaturlayout รคndert.", + "toast-keyboard-label": "Tastaturlayout", + "toast-media-description": "Eine Toast-Benachrichtigung anzeigen, wenn sich der Wiedergabestatus von Medien รคndert.", + "toast-media-label": "Medien" + }, + "osd": { + "always-on-top-description": "On-Screen Display รผber Vollbildfenstern und anderen Ebenen anzeigen.", + "always-on-top-label": "Immer im Vordergrund", + "background-opacity-description": "Kontrolliert die Transparenz des OSD Hintergrunds.", + "background-opacity-label": "Hintergrundtransparenz", + "description": "Bildschirm-Overlays wie Lautstรคrke- und Helligkeitsanzeigen konfigurieren.", + "duration-auto-hide-description": "Zeitspanne bis zum automatischen Ausblenden anpassen.", + "duration-auto-hide-label": "Ausblenden nach", + "duration-desc": "Wie lange das On-Screen Display sichtbar bleibt, bevor es automatisch ausgeblendet wird.", + "duration-title": "Automatisches Ausblenden", + "enabled-description": "Lautstรคrke- und Helligkeitsรคnderungen in Echtzeit anzeigen.", + "enabled-label": "On-Screen Display aktivieren", + "events-desc": "Wรคhlen Sie aus, welche Ereignisse die Bildschirmanzeige auslรถsen.", + "general-desc": "OSD-Sichtbarkeit und -Verhalten konfigurieren.", + "location-description": "Wo On-Screen Displays erscheinen.", + "monitors-desc": "On-Screen Display auf bestimmten Monitoren anzeigen. StandardmรครŸig auf allen angezeigt.", + "title": "On-Screen Display", + "types-brightness-description": "OSD anzeigen, wenn sich die Bildschirmhelligkeit รคndert.", + "types-brightness-label": "Helligkeit", + "types-custom-text-description": "OSD fรผr benutzerdefinierte Textnachrichten aus IPC anzeigen.", + "types-custom-text-label": "Benutzerdefinierter Text", + "types-desc": "Wรคhlen Sie die Ereignisse aus, die das OSD auslรถsen.", + "types-input-volume-description": "OSD anzeigen, wenn sich die Mikrofonlautstรคrke รคndert.", + "types-input-volume-label": "Eingangslautstรคrke", + "types-lockkey-description": "OSD anzeigen, wenn Caps Lock, Num Lock oder Scroll Lock umgeschaltet werden.", + "types-lockkey-label": "Feststelltasten", + "types-media-description": "OSD anzeigen, wenn sich der Medienwiedergabestatus รคndert (Wiedergabe, Pause, รœberspringen).", + "types-media-label": "Medienwiedergabe", + "types-title": "OSD-Auslรถseereignisse", + "types-volume-description": "OSD anzeigen, wenn sich die Ausgabelautstรคrke รคndert.", + "types-volume-label": "Ausgangslautstรคrke" + }, + "plugins": { + "auto-update": "Plugins automatisch aktualisieren", + "auto-update-description": "Automatisch alle Plugins aktualisieren, wenn die Shell startet.", + "available-description": "Plugins aus konfigurierten Quellen durchsuchen und installieren.", + "available-label": "Verfรผgbare Plugins", + "available-no-plugins-description": "รœberprรผfen Sie Ihre Plugin-Quellen oder aktualisieren Sie die Liste.", + "available-no-plugins-label": "Keine Plugins verfรผgbar", + "check-for-updates": "Nach Updates suchen", + "checking-for-updates": "Prรผfe auf Updates...", + "collision-already-installed": "Dieses Plugin ist bereits installiert", + "collision-custom-version-exists": "Eine benutzerdefinierte Version von \"{source}\" ist bereits installiert", + "collision-official-version-exists": "Die offizielle Version dieses Plugins ist bereits installiert", + "development-disable": "Entwicklungsmodus fรผr dieses Plugin deaktivieren", + "development-enable": "Entwicklungsmodus fรผr dieses Plugin aktivieren.", + "filter-downloaded": "Heruntergeladen", + "filter-not-downloaded": "Nicht heruntergeladen", + "filter-tags-description": "Plugins nach Kategorie oder Download-Status filtern.", + "filter-tags-label": "Schlagwรถrter", + "hot-reload-description": "Plugins automatisch neu laden, wenn sich ihre Dateien รคndern. Nรผtzlich fรผr die Plugin-Entwicklung.", + "hot-reload-label": "Hot Reload (Entwicklermodus)", + "hot-reloaded": "Plugin neu geladen: {name}", + "install-error": "Installation fehlgeschlagen: {error}", + "install-incompatible": "{plugin} benรถtigt Noctalia v{version} oder hรถher", + "install-success": "{plugin} erfolgreich installiert", + "installed-description": "Alle lokal installierten Plugins verwalten und konfigurieren.", + "installed-label": "Installierte Plugins", + "installed-no-plugins-description": "Installieren Sie Plugins aus dem Abschnitt \"Verfรผgbar\".", + "installed-no-plugins-label": "Keine Plugins installiert", + "installing": "{plugin} wird installiert...", + "notify-updates": "Plugin-Update-Benachrichtigungen", + "notify-updates-description": "Zeige ein Benachrichtigungs-Popup an, wenn neue Plugin-Updates verfรผgbar sind.", + "open-plugin-page": "Plugin-Seite รถffnen", + "open-plugins-tab": "Plugin-Einstellungen รถffnen", + "plugin-settings-title": "{plugin} Einstellungen", + "refresh-refreshing": "Aktualisiere Plugin-Liste...", + "refresh-tooltip": "Verfรผgbare Plugins aktualisieren", + "settings-error-not-loaded": "Plugin nicht geladen", + "settings-saved": "Plugin-Einstellungen gespeichert", + "settings-tooltip": "Plugin-Einstellungen", + "source-custom": "Benutzerdefinierte Quelle", + "sources-add-custom": "Benutzerdefiniertes Repository hinzufรผgen", + "sources-add-dialog-description": "Ein GitHub-Repository als Plugin-Quelle hinzufรผgen.", + "sources-add-dialog-error": "Fehler beim Hinzufรผgen der Plugin-Quelle", + "sources-add-dialog-name": "Repository-Name", + "sources-add-dialog-name-placeholder": "Meine benutzerdefinierten Plugins", + "sources-add-dialog-success": "Plugin-Quelle erfolgreich hinzugefรผgt", + "sources-add-dialog-title": "Plugin-Quelle hinzufรผgen", + "sources-add-dialog-url": "Repository-URL", + "sources-description": "Plugin-Repositories verwalten.", + "sources-placeholder": "Mein cooles Repository", + "sources-remove-tooltip": "Plugin-Quelle entfernen", + "title": "Plugins", + "translations-reloaded": "รœbersetzungen neu geladen: {name}", + "uninstall-dialog-description": "Sind Sie sicher, dass Sie {plugin} deinstallieren mรถchten? Dadurch werden alle Plugin-Daten entfernt.", + "uninstall-dialog-title": "Plugin deinstallieren", + "uninstall-error": "Deinstallation fehlgeschlagen: {error}", + "uninstall-success": "{plugin} wurde erfolgreich deinstalliert", + "uninstalling": "{Plugin} wird deinstalliert...", + "update-all": "Alle aktualisieren ({count})", + "update-all-success": "Alle Plugins wurden erfolgreich aktualisiert", + "update-available": "Neues Plugin-Update verfรผgbar", + "update-available-plural": "Neue Plugin-Updates verfรผgbar ({count})", + "update-error": "Fehler beim Aktualisieren des Plugins: {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (benรถtigt Noctalia v{required})", + "update-success": "{plugin} wurde auf v{version} aktualisiert", + "update-version": "v{current} โ†’ v{new}", + "updating": "Aktualisierung lรคuft..." + }, + "region": { + "title": "Region" + }, + "session-menu": { + "countdown-duration-description": "Legen Sie fest, wie lange der Countdown-Timer vor der Ausfรผhrung von Energieaktionen dauert.", + "countdown-duration-label": "Countdown-Dauer", + "enable-countdown-description": "Zeige einen Countdown-Timer vor der Ausfรผhrung von Energieaktionen.", + "enable-countdown-label": "Countdown-Timer aktivieren", + "entries-desc": "Anpassen, welche Energieaktionen im Sitzungsmenรผ angezeigt werden und in welcher Reihenfolge.", + "entries-title": "Energieaktionen", + "entry-settings-command-description": "Benutzerdefinierter Befehl, der fรผr diese Aktion ausgefรผhrt werden soll. Leer lassen, um den Standard-Systembefehl zu verwenden.", + "entry-settings-command-placeholder": "z.B. systemctl poweroff", + "entry-settings-default-command-lock": "Interner Sperrbildschirm (kein Befehl)", + "entry-settings-default-command-logout": "Interne Abmeldung (kein Befehl)", + "entry-settings-default-info-description": "Wenn kein benutzerdefinierter Befehl angegeben ist, wird der standardmรครŸige Systembefehl verwendet.", + "entry-settings-default-info-label": "Standardbefehl", + "entry-settings-keybind-description": "Weisen Sie eine Tastenkombination zu, um diese Aktion auszulรถsen, wenn das Sitzungsmenรผ geรถffnet ist.", + "entry-settings-keybind-placeholder": "Drรผcke Aufnehmen, um eine Taste zuzuweisen...", + "entry-settings-keybind-recording": "Aufnahme lรคuft... Drรผcken Sie eine beliebige Tastenkombination", + "entry-settings-title": "Konfiguriere {entry}", + "entry-settings-tooltip": "Konfigurationsbefehl", + "general-desc": "Konfigurieren Sie das Verhalten und Erscheinungsbild des Sitzungsmenรผ-Panels.", + "large-buttons-layout-description": "Wรคhlen Sie, wie die Sitzungsmenรผ-Schaltflรคchen angezeigt werden.", + "large-buttons-layout-label": "Layout mit groรŸen Schaltflรคchen", + "large-buttons-style-description": "Das Sitzungsmenรผ mit groรŸen Schaltflรคchen in einem Rasterlayout anzeigen.", + "large-buttons-style-label": "Stil: GroรŸe Schaltflรคchen", + "position-description": "Wรคhlen Sie, wo das Sitzungsmenรผ-Panel beim ร–ffnen erscheint.", + "show-header-description": "Zeige den Titel und die SchlieรŸen-Schaltflรคche oben im Sitzungsmenรผ.", + "show-header-label": "Kopfzeile anzeigen", + "show-keybinds-description": "Tastenkombinationshinweise bei den Sitzungsoptionen anzeigen.", + "show-keybinds-label": "Tastenkombinationen anzeigen" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Desktop-Widgets sichtbar lassen, wรคhrend der Noctalia Leistungsmodus aktiviert ist.", + "noctalia-performance-disable-desktop-widgets-label": "Aktiviere Desktop-Widgets im Leistungsmodus", + "noctalia-performance-disable-wallpaper-description": "Halte Desktop-, รœbersicht- und Sperrbildschirm-Hintergrundbilder sichtbar, wรคhrend der Noctalia Leistungsmodus aktiviert ist.", + "noctalia-performance-disable-wallpaper-label": "Hintergrundbild-Rendering im Leistungsmodus aktivieren", + "title": "System" + }, + "system-monitor": { + "critical-color-label": "Kritische Farbe", + "custom-highlight-colors-title-label": "Benutzerdefinierte Hervorhebungsfarben", + "disk-available-label": "Verfรผgbarer Speicherplatz", + "disk-section-label": "Festplattennutzung", + "enable-dgpu-monitoring-description": "Warnung: Dies aktiviert Ihre dedizierte GPU (NVIDIA/AMD), was die Akkulaufzeit von Laptops mit Hybridgrafik erheblich beeintrรคchtigen kann.", + "enable-dgpu-monitoring-label": "Dedizierte GPU-รœberwachung aktivieren", + "external-monitor-description": "Geben Sie den Befehl oder Anwendungspfad ein, um die externe Systemmonitor-Anwendung zu starten.", + "external-monitor-label": "Externer Systemmonitor-Befehl", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "Konfigurieren Sie das Verhalten und Aussehen des Systemmonitors.", + "gpu-section-label": "GPU-Temperatur", + "highlight-colors-section-label": "Hervorhebungsfarben", + "polling-interval-label": "Abfrageintervall", + "polling-section-description": "Konfigurieren Sie, wie oft jede Systemmetrik aktualisiert wird.", + "polling-section-label": "Abfrageintervalle", + "threshold-critical": "Kritisch", + "threshold-warning": "Warnung", + "thresholds-section-description": "Passe die Warn-/Kritisch-Schwellen und die Abfrageintervalle fรผr jede Systemmetrik an.", + "use-custom-highlight-colors-description": "Wenn deaktiviert, werden die Standard-Hervorhebungsfarben des Themes verwendet.", + "use-custom-highlight-colors-label": "Benutzerdefinierte Hervorhebungsfarben verwenden", + "warning-color-label": "Warnfarbe" + }, + "user-interface": { + "allow-panels-without-bar-description": "Wenn aktiviert, kรถnnen sich Panels auf jedem Bildschirm รถffnen. Wenn deaktiviert, รถffnen sich Panels nur auf Bildschirmen mit einer Leiste, was den Speicherverbrauch reduzieren kann.", + "allow-panels-without-bar-label": "Panels auf Bildschirmen ohne Leiste zulassen", + "animation-disable-description": "Deaktivieren Sie alle Animationen fรผr eine schnellere und reaktionsfreudigere Erfahrung.", + "animation-disable-label": "UI-Animationen deaktivieren", + "animation-speed-description": "Globale Animationsgeschwindigkeit anpassen.", + "animation-speed-label": "Animationsgeschwindigkeit", + "animation-speed-reset": "Animationsgeschwindigkeit zurรผcksetzen", + "appearance-desc": "Visuelle Elemente wie Tooltips, Rahmen und Schatten anpassen.", + "blur-behind-description": "Macht den Bereich hinter Leisten und Panels unscharf mithilfe des Compositor-Blur-Protokolls.", + "blur-behind-label": "Hintergrundunschรคrfe", + "box-border-description": "Zeige eine Umrandung um Inhaltsbereiche an.", + "box-border-label": "Containerumriss", + "box-border-radius-description": "Passt die Eckenrundung wichtiger Layoutbereiche an, wie z.B. Seitenleisten, Karten und Inhaltsbereiche.", + "box-border-radius-label": "Eckenradius", + "control-border-radius-description": "Steuert die Krรผmmung interaktiver Elemente, einschlieรŸlich Schaltflรคchen, Umschalter und Textfelder.", + "control-border-radius-label": "Eingaberadius", + "desc": "Passen Sie das Aussehen, die Haptik und das Verhalten der Benutzeroberflรคche an.", + "dim-desktop-description": "Den Desktop abdunkeln, wenn Fenster oder Menรผs geรถffnet sind.", + "dim-desktop-label": "Desktop abdunkeln", + "dimmer-opacity-description": "Deckkraft fรผr das Abdunkeln des Desktops festlegen.", + "dimmer-opacity-label": "Deckkraft des gedimmten Desktops", + "panel-background-opacity-description": "Legen Sie die Hintergrundtransparenz fรผr alle Panels fest (Leiste, Starter, Einstellungen, usw.).", + "panel-background-opacity-label": "Panel-Hintergrunddeckkraft", + "panels-attached-to-bar-description": "Die Panels rasten an der Stange und den Bildschirmrรคndern ein und sorgen so fรผr ein nahtloses Erscheinungsbild mit stilvollen, umgekehrten Ecken.", + "panels-attached-to-bar-label": "Panels an Kanten ausrichten", + "panels-overlay-description": "Stellt sicher, dass Panels und die Leiste sichtbar bleiben, auch รผber Vollbildanwendungen.", + "panels-overlay-label": "Panels & Leiste oben behalten", + "scaling-description": "ร„ndert die GrรถรŸe der allgemeinen Benutzeroberflรคche, mit Ausnahme der Leiste.", + "scaling-label": "Oberflรคchenskalierung", + "scrollbar-always-visible-description": "Bildlaufleisten immer sichtbar lassen, wenn Inhalte scrollbar sind, anstatt sie nur beim Darรผberfahren anzuzeigen.", + "scrollbar-always-visible-label": "Bildlaufleisten immer anzeigen", + "settings-panel-header": "Einstellungs-Panel", + "settings-panel-mode-description": "Wรคhlen Sie das Layout der Einstellungen (mรถglicherweise ist ein Neustart erforderlich).", + "settings-panel-mode-label": "Einstellungs-Panel-Modus", + "settings-panel-sidebar-card-style-description": "Umhรผllt die Einstellungs-Seitenleiste mit einem gefรผllten Hintergrund und abgerundeten Ecken.", + "settings-panel-sidebar-card-style-label": "Seitenleisten-Kartenstil", + "shadows-description": "Aktiviert Schlagschatten unter Leisten und Panels.", + "shadows-direction-description": "Wรคhlen Sie, wohin der Schatten geworfen wird.", + "shadows-direction-label": "Schattenrichtung", + "shadows-label": "Schlagschatten", + "title": "Benutzeroberflรคche", + "tooltips-description": "Tooltips in der gesamten Benutzeroberflรคche aktivieren oder deaktivieren.", + "tooltips-label": "Tooltips anzeigen", + "translucent-widgets-description": "Mache Schaltflรคchen, Tabs und andere Widgets in Panels halbtransparent.", + "translucent-widgets-label": "Transluzente Widgets" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "Alphabetisch", + "automation-change-mode-description": "Wรคhlen Sie, wie Hintergrundbilder bei automatischem Wechsel ausgewรคhlt werden.", + "automation-change-mode-label": "Wechselmodus", + "automation-custom-interval-description": "Zeit als HH:MM eingeben (z.B. 01:30).", + "automation-custom-interval-label": "Benutzerdefiniertes Intervall", + "automation-interval-description": "Wie oft Hintergrundbilder automatisch gewechselt werden sollen.", + "automation-interval-label": "Hintergrundbild-Intervall", + "automation-random-wallpaper-description": "Zufรคllige Hintergrundbild-Wechsel in regelmรครŸigen Abstรคnden planen.", + "automation-scheduled-change-description": "Hintergrundbilder automatisch in regelmรครŸigen Abstรคnden wechseln.", + "automation-scheduled-change-label": "Geplanter Wechsel", + "look-feel-edge-smoothness-description": "Wendet einen weichen, gefiederten Effekt auf die Kante von รœbergรคngen an.", + "look-feel-edge-smoothness-label": "รœbergangseffekt weichzeichnen", + "look-feel-fill-color-description": "Fรผllfarbe wรคhlen, die hinter dem Hintergrundbild erscheinen kann.", + "look-feel-fill-mode-description": "Wรคhlen Sie, wie das Bild skaliert werden soll, um zur Auflรถsung Ihres Monitors zu passen.", + "look-feel-fill-mode-label": "Fรผllmodus", + "look-feel-skip-startup-transition-description": "รœberspringe die Hintergrundbild-Animation, wenn die Shell startet.", + "look-feel-skip-startup-transition-label": "Startรผbergang รผberspringen", + "look-feel-title": "Aussehen & Verhalten", + "look-feel-transition-duration-description": "Dauer der รœbergangsanimationen in Sekunden.", + "look-feel-transition-duration-label": "รœbergangsdauer", + "look-feel-transition-type-description": "Animationstyp beim Wechsel zwischen Hintergrundbildern.", + "look-feel-transition-type-label": "รœbergangstyp", + "settings-clear-cache-button": "Cache leeren", + "settings-clear-cache-description": "Zwischengespeicherte, angepasste Hintergrundbilder lรถschen, um Speicherplatz freizugeben.", + "settings-clear-cache-label": "Hintergrundbild-Cache", + "settings-clear-cache-toast": "Hintergrundbild-Cache geleert", + "settings-desc": "Steuern Sie, wie Hintergrundbilder verwaltet und angezeigt werden.", + "settings-enable-management-description": "Hintergrundbilder mit Noctalia verwalten. Deaktivieren, wenn Sie eine andere Anwendung bevorzugen.", + "settings-enable-management-label": "Hintergrundbild-Verwaltung aktivieren", + "settings-enable-overview-description": "Wendet ein verschwommenes und abgedunkeltes Hintergrundbild auf den รœbersichts-Bildschirm an.", + "settings-enable-overview-label": "รœbersichtshintergrund aktivieren", + "settings-folder-description": "Pfad zu Ihrem Haupt-Hintergrundbild-Ordner.", + "settings-folder-label": "Hintergrundbild-Ordner", + "settings-hide-wallpaper-filenames-tooltip-hide": "Dateinamen ausblenden", + "settings-hide-wallpaper-filenames-tooltip-show": "Dateinamen anzeigen", + "settings-monitor-specific-description": "Unterschiedlichen Hintergrundbild-Ordner fรผr jeden Monitor festlegen.", + "settings-monitor-specific-label": "Monitor-spezifische Verzeichnisse", + "settings-monitor-specific-tooltip": "Monitor-Hintergrundbild-Ordner", + "settings-overview-blur-strength-description": "Wendet die Unschรคrfestรคrke auf die รœbersicht an.", + "settings-overview-blur-strength-label": "Unschรคrfestรคrke der รœbersicht", + "settings-overview-tint-description": "Wendet die Tรถnungsstรคrke auf die รœbersicht an.", + "settings-overview-tint-label": "Stรคrke der รœbersichts-Tรถnung", + "settings-recursive-search-description": "Auch nach Hintergrundbildern in Unterordnern des Hintergrundbild-Verzeichnisses suchen.", + "settings-recursive-search-label": "Unterordner durchsuchen", + "settings-select-monitor-folder": "Monitor-Hintergrundbild-Ordner auswรคhlen", + "settings-selector-description": "Wรคhlen Sie Ihr Hintergrundbild aus.", + "settings-selector-position-description": "Wรคhlen Sie aus, wo das Hintergrundbild-Auswahlfeld angezeigt wird.", + "settings-show-hidden-files-tooltip-hide": "Versteckte Dateien ausblenden", + "settings-show-hidden-files-tooltip-show": "Versteckte Dateien anzeigen", + "settings-title": "Hintergrundbild-Einstellungen", + "settings-use-original-images-description": "รœberspringe das Skalieren von Hintergrundbildern vor der Anzeige. Spart Speicherplatz und reduziert die CPU-Auslastung wรคhrend des Hintergrundbildwechsels, kann aber bei sehr groรŸen Bildern mehr Arbeitsspeicher verbrauchen.", + "settings-use-original-images-label": "Originalbilder verwenden", + "settings-view-mode-description": "Wรคhlen Sie aus, wie Hintergrundbilder aus Ihrem Verzeichnis angezeigt werden.", + "settings-view-mode-label": "Ansichtsmodus", + "view-mode-browse": "Verzeichnisse durchsuchen", + "view-mode-cycle-tooltip": "Ansichtsmodus: {mode} (Klicken zum ร„ndern)", + "view-mode-recursive": "Vereinfachte Unterverzeichnisse", + "view-mode-single": "Stammverzeichnis" + } + }, + "placeholders": { + "add-new-keybind": "Neue Tastenkombination hinzufรผgen", + "command-example": "echo \"Hallo Welt\"", + "enter-command": "Befehl eingeben (App oder benutzerdefiniertes Skript)", + "enter-ipc-identifier": "Geben Sie eine eindeutige Kennung fรผr IPC-Befehle ein", + "enter-path": "Pfad eingeben...", + "enter-text-to-collapse": "z.B. 'nothing is playing'. Verwenden Sie /regex/ fรผr Muster.", + "enter-tooltip": "Tooltip eingeben", + "enter-width-pixels": "Breite in Pixeln eingeben", + "keybind-recording": "Tastenkombination aufzeichnen...", + "search": "Suchen...", + "search-icons": "z.B. noctalia, niri, battery, cloud", + "search-launcher": "Eintrรคge suchen... oder > fรผr Befehle verwenden", + "search-wallhaven": "Wallhaven durchsuchen...", + "search-wallpapers": "Zum Filtern von Hintergrundbildern eingeben..." + }, + "positions": { + "bottom": "Unten", + "bottom-center": "Unten mittig", + "bottom-left": "Unten links", + "bottom-right": "Unten rechts", + "center": "Zentriert", + "center-left": "Links mittig", + "center-right": "Rechts mittig", + "close-to-bar": "Nah zur Leisten-Schaltflรคche", + "follow-bar": "Folge der Leiste", + "left": "Links", + "right": "Rechts", + "top": "Oben", + "top-center": "Oben mittig", + "top-left": "Oben links", + "top-right": "Oben rechts" + }, + "session-menu": { + "action-in-seconds": "{action} in {seconds} Sekunden...", + "cancel-timer": "Timer abbrechen", + "title": "Sitzungsmenรผ" + }, + "setup": { + "all-done": "Fertig!", + "appearance": { + "subheader": "Wรคhlen Sie den Dunkelmodus und Farbquellen (Hintergrundbild oder vordefiniert)." + }, + "customize": { + "header": "Erlebnis anpassen", + "subheader": "Leistenposition, Dichte, Skalierung und mehr einstellen." + }, + "skip-setup": "Einrichtung รผberspringen", + "telemetry-wizard-done": "Verstanden!", + "telemetry-wizard-note": "Sie haben die Kontrolle โ€“ aktivieren oder deaktivieren Sie dies jederzeit in den Einstellungen.", + "telemetry-wizard-subtitle": "Wir haben anonyme Analysen hinzugefรผgt, um Noctalia zu verbessern", + "telemetry-wizard-title": "Datenschutz-Update", + "wallpaper": { + "choose-dir": "Wรคhlen Sie ein Verzeichnis mit Ihren Hintergrundbildern", + "dir-browse": "Ordner auswรคhlen", + "dir-description": "Wรคhlen Sie den Ordner mit Ihren Hintergrundbildern.", + "dir-label": "Hintergrundbild-Verzeichnis", + "dir-select-title": "Hintergrundbild-Ordner wรคhlen", + "header": "Wรคhlen Sie Ihr Hintergrundbild", + "no-dir": "Kein Hintergrundbild-Verzeichnis ausgewรคhlt", + "no-valid": "Keine gรผltigen Bilddateien gefunden in: {dir}", + "none-in-dir": "Keine Hintergrundbilder im Verzeichnis gefunden", + "preview-error": "Bild konnte nicht geladen werden", + "select-prompt": "Wรคhlen Sie unten ein Hintergrundbild", + "subheader": "Bestimmen Sie die Stimmung mit einem schรถnen Hintergrund." + }, + "welcome": "Willkommen", + "welcome-note": "Nur ein paar Grundeinstellungen โ€” alle Optionen befinden sich in den Einstellungen", + "welcome-subtitle": "Gestalten wir Ihren Desktop einzigartig", + "welcome-title": "Willkommen bei Noctalia!" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "Kein Medienplayer erkannt", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "Unbekannte App", + "uptime": "Laufzeit: {uptime}", + "user-requested": "Vom Benutzer angefordert", + "welcome-back": "Willkommen zurรผck,", + "widget-settings-title": "{widget} Einstellungen" + }, + "system-monitor": { + "core-usage": "Kern {id} Nutzung", + "cpu-temp": "CPU-Temperatur", + "cpu-usage": "CPU-Auslastung", + "disk": "Festplatte", + "download-speed": "Download-Speed", + "gpu-temp": "GPU-Temperatur", + "load-average": "Lastdurchschnitt", + "title": "Systemmonitor", + "upload-speed": "Upload-Speed" + }, + "toast": { + "airplane-mode": { + "description": "Alle drahtlosen Kommunikationen deaktivieren.", + "title": "Flugmodus" + }, + "battery": { + "critical": "Kritischer Akkustand", + "critical-desc": "Akku ist bei {percent}% โ€” bitte sofort das Ladegerรคt anschlieรŸen", + "low": "Niedriger Akkustand", + "low-desc": "Akku ist bei {percent}% โ€” bitte das Ladegerรคt anschlieรŸen" + }, + "bluetooth": { + "address-copied": "Adresse in die Zwischenablage kopiert", + "auto-connect-disabled": "Automatisches Verbinden deaktiviert", + "auto-connect-enabled": "Automatisches Verbinden aktiviert", + "auto-connecting": "Verbinde mit {count} Gerรคt(en)...", + "confirm-code": "Bestรคtigungscode {value} auf dem anderen Gerรคt bestรคtigen.", + "connect-failed": "Verbindung mit dem Gerรคt fehlgeschlagen", + "disconnect-failed": "Trennen vom Gerรคt fehlgeschlagen", + "discoverable-change-failed": "ร„ndern des Sichtbarkeitsstatus fehlgeschlagen", + "discoverable-disabled": "Sichtbar deaktiviert", + "discoverable-enabled": "Sichtbar aktiviert", + "display-code": "Geben Sie den Code {value} auf dem anderen Gerรคt ein.", + "forget-failed": "Entfernen des Gerรคts fehlgeschlagen", + "pair-failed": "Koppeln des Gerรคts fehlgeschlagen", + "passkey-required": "Passwortschlรผssel vom Gerรคt erforderlich.", + "pincode-required": "PIN-Code vom Gerรคt erforderlich.", + "state-change-failed": "ร„ndern des Bluetooth-Status fehlgeschlagen" + }, + "clipboard": { + "long-text": "Langer Text", + "unavailable": "Zwischenablageverlauf nicht verfรผgbar", + "unavailable-desc": "Die 'cliphist' Anwendung ist nicht installiert. Bitte installieren Sie sie, um Zwischenablageverlauf-Features zu nutzen" + }, + "custom-command-failed": { + "description": "Befehl fehlgeschlagen: {command}\\nExit-Code: {code}", + "title": "Benutzerdefinierter Befehl fehlgeschlagen" + }, + "do-not-disturb": { + "disabled": "'Nicht stรถren' deaktiviert", + "disabled-desc": "Alle Benachrichtigungen werden angezeigt", + "enabled": "'Nicht stรถren' aktiviert", + "enabled-desc": "Sie finden diese Benachrichtigungen in Ihrem Verlauf" + }, + "donation-opened": "Spendenseite in Ihrem Browser geรถffnet", + "internet-limited": "Verbunden ohne Internet", + "keyboard-layout": { + "changed": "Tastaturlayout geรคndert zu {layout}", + "title": "Tastatur" + }, + "missing-control-center": { + "description": "Das Kontrollzentrum-Widget wurde aus der Leiste entfernt. Um es erneut รผber die Leiste zu รถffnen, fรผgen Sie das Widget wieder hinzu. Sie kรถnnen es auch durch Rechtsklick auf die Leiste รถffnen", + "label": "Letztes Kontrollzentrum-Widget entfernt" + }, + "night-light": { + "forced": "Aktivierung erzwungen", + "normal": "Normaler Modus", + "not-installed": "wlsunset nicht installiert" + }, + "noctalia-performance": { + "disabled": "Leistungsmodus deaktiviert", + "enabled": "Leistungsmodus aktiviert", + "label": "Noctalia-Performance" + }, + "power-profile": { + "changed": "Energieprofil geรคndert", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "Beim Verarbeiten von Vorlagen ist ein Fehler aufgetreten", + "title": "Vorlagenverarbeitung fehlgeschlagen" + }, + "vpn": { + "connected": "Verbunden mit '{name}'", + "disconnected": "Verbindung zu '{name}' getrennt" + }, + "wallpaper-colors": { + "disabled": "Hintergrundbild-Farben deaktiviert", + "enabled": "Hintergrundbild-Farben aktiviert", + "label": "Hintergrundfarbengenerierung" + }, + "wifi": { + "connected": "Verbunden mit '{ssid}'", + "connection-failed": "Verbindung fehlgeschlagen", + "connection-timeout": "Zeitรผberschreitung bei der Verbindung", + "disconnected": "Getrennt von '{ssid}'", + "incorrect-password": "Falsches Passwort", + "network-not-found": "Netzwerk nicht gefunden" + } + }, + "tooltips": { + "add-widget": "Widget hinzufรผgen", + "auto-connect": "Automatisches Verbinden fรผr dieses Gerรคt umschalten", + "bluetooth-auto-connect-off": "Automatisches Verbinden deaktiviert", + "bluetooth-auto-connect-on": "Automatisches Verbinden aktiviert", + "bluetooth-devices": "Bluetooth-Gerรคte", + "brightness-at": "Helligkeit: {brightness}%", + "click-to-start-recording": "Bildschirmrekorder (Aufnahme starten)", + "click-to-stop-recording": "Bildschirmrekorder (Aufnahme stoppen)", + "collapse": "Seitenleiste einklappen", + "copy-address": "Adresse kopieren", + "delete-notification": "Benachrichtigung lรถschen", + "dismiss-notification": "Benachrichtigung schlieรŸen", + "do-not-disturb-enabled": "Nicht stรถren", + "expand": "Seitenleiste ausklappen", + "forget-network": "Netzwerk vergessen", + "grid-view": "Rasteransicht", + "hidden-files-hide": "Versteckte Dateien", + "hide-unnamed-devices": "Unbenannte Gerรคte ausblenden", + "home": "Home-Verzeichnis", + "input-muted": "Eingangsstummschaltung umschalten", + "keep-awake": "Wach halten", + "keyboard-layout": "{layout} Tastaturlayout", + "list-view": "Listenansicht", + "manage-vpn": "VPN-Verbindungen", + "manage-wifi": "WLAN", + "max-widgets-reached": "Maximale Anzahl an Widgets erreicht", + "microphone-volume-at": "Mikrofonlautstรคrke: {volume}%", + "move-to-section": "Gehe zu {section}", + "mute": "Stummschalten", + "next-media": "Nรคchster Titel", + "next-month": "Nรคchster Monat", + "night-light-not-installed": "Nachtlicht (nicht verfรผgbar)", + "noctalia-performance-enabled": "Noctalia-Leistungsmodus", + "open-annotation-tool": "Mit dem Anmerkungswerkzeug รถffnen", + "open-control-center": "Kontrollzentrum", + "open-notification-history-enable-dnd": "Benachrichtigungsverlauf", + "open-settings": "Einstellungen", + "open-tray-dropdown": "System-Tray", + "output-muted": "Ausgabe stummschalten", + "power-profile": "{profile} Energieprofil", + "previous-media": "Vorheriger Titel", + "previous-month": "Vorheriger Monat", + "refresh-devices": "Gerรคte aktualisieren", + "refresh-wallhaven": "Wallhaven-Ergebnisse aktualisieren", + "refresh-wallpaper-list": "Hintergrundbild-Liste aktualisieren", + "remove-widget": "Widget entfernen", + "search-close": "Suche schlieรŸen", + "session-menu": "Sitzungsmenรผ", + "show-all-devices": "Alle Gerรคte anzeigen", + "switch-to-dark-mode": "Dunkler Modus", + "switch-to-light-mode": "Heller Modus", + "unmute": "Stummschaltung aufheben", + "up": "รœbergeordnetes Verzeichnis", + "volume-at": "Lautstรคrke: {volume}%", + "wallpaper-selector": "Hintergrundbild-Auswahl" + }, + "wallpaper": { + "browse": { + "empty-directory": "Dieses Verzeichnis ist leer.", + "go-root": "Zum Wallpaper-Stammverzeichnis gehen", + "go-up": "Zum รผbergeordneten Ordner gehen", + "go-up-hint": "Verwende die Zurรผck-Taste, um nach oben zu navigieren." + }, + "configure-directory": "Konfigurieren Sie Ihr Hintergrundbild-Verzeichnis mit Bildern.", + "fill-modes": { + "crop": "Zuschneiden (Fรผllen)", + "fit": "Einpassen (Beibehalten)", + "repeat": "Wiederholen (Kachel)", + "stretch": "Strecken" + }, + "no-match": "Keine รœbereinstimmung gefunden.", + "no-wallpaper": "Kein Hintergrundbild gefunden.", + "panel": { + "apikey-help": "Ein API-Schlรผssel ist erforderlich, um auf NSFW-Inhalte zuzugreifen.", + "apikey-label": "API-Schlรผssel", + "apikey-managed-by-env": "Verwaltet รผber die Umgebungsvariable NOCTALIA_WALLHAVEN_API_KEY.", + "apikey-placeholder": "Geben Sie Ihren Wallhaven API-Schlรผssel ein", + "appearance-dark-tab": "Dunkel", + "appearance-light-tab": "Hell", + "categories-anime": "Anime", + "categories-label": "Kategorien", + "categories-people": "Personen", + "color-extraction-disabled": "Hintergrundbild-Farbenextraktion verwenden", + "color-extraction-enabled": "Vordefinierte Farbschemata nutzen", + "header-devices-apply-all-tooltip": "Hintergrundbild pro Monitor wรคhlen", + "header-devices-per-monitor-tooltip": "Auf alle Monitore anwenden", + "header-link-light-dark-tooltip": "Hintergrundbild beim Themenwechsel beibehalten", + "header-separate-light-dark-tooltip": "Hintergrundbild automatisch mit dem Thema wechseln", + "header-sun-linked-tooltip": "Helle und dunkle Hintergrundbilder trennen", + "header-sun-separate-tooltip": "Gleiches Hintergrundbild fรผr hell und dunkel", + "order-asc": "Aufsteigend", + "order-desc": "Absteigend", + "order-label": "Reihenfolge", + "purity-label": "Inhaltsfilter", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "Sketchy", + "ratios-all-portrait": "Alle Hochformate", + "ratios-all-wide": "Alle Querformate", + "ratios-any": "Alle", + "ratios-label": "Seitenverhรคltnis", + "resolution-atleast": "Mindestens", + "resolution-exact": "Exakt", + "resolution-label": "Auflรถsung", + "resolution-mode-label": "Modus", + "solid-color-tooltip": "Einfarbiger Hintergrund", + "sort-date-asc": "Nach ร„ltestem zuerst sortieren", + "sort-date-desc": "Nach Neuestem zuerst sortieren", + "sort-name-asc": "Nach Name sortieren (A-Z)", + "sort-name-desc": "Nach Name sortieren (Z-A)", + "sort-random": "Nach Zufall sortieren", + "sorting-date-added": "Hinzugefรผgt am", + "sorting-favorites": "Favoriten", + "sorting-label": "Sortiere nach", + "sorting-relevance": "Relevanz", + "sorting-toplist": "Topliste", + "sorting-views": "Aufrufe", + "source-label": "Quelle", + "source-wallhaven": "Wallhaven", + "title": "Hintergrundbild-Auswahl", + "wallhaven-settings-title": "Wallhaven-Einstellungen" + }, + "transitions": { + "disc": "Scheibe", + "fade": "รœberblenden", + "honeycomb": "Honigwabe", + "pixelate": "Verpixeln", + "stripes": "Streifen", + "wipe": "Wischen" + }, + "try-different-search": "Versuchen Sie eine andere Suchanfrage.", + "wallhaven": { + "loading": "Hintergrundbilder werden geladen...", + "no-results": "Keine Hintergrundbilder gefunden. Versuchen Sie eine andere Suchanfrage.", + "page": "{current} von {total}", + "page-prefix": "Seite", + "page-suffix": "von {total}" + } + }, + "weather": { + "clear-sky": "Klarer Himmel", + "drizzle": "Nieselregen", + "fog": "Nebelig", + "mainly-clear": "รœberwiegend klar", + "overcast": "Bedeckt", + "partly-cloudy": "Teilweise bewรถlkt", + "rain-showers": "Regenschauer", + "snow": "Schnee", + "thunderstorm": "Gewitter" + }, + "widgets": { + "color-picker": { + "palette-description": "Wรคhlen Sie aus einer groรŸen Auswahl vordefinierter Farben.", + "palette-label": "Palette", + "palette-theme-colors": "Schneller Zugriff auf die Farbpalette Ihres Themes.", + "title": "Farbauswahl" + }, + "datetime-tokens": { + "ampm-lowercase": "am/pm in Kleinbuchstaben", + "ampm-uppercase": "AM/PM in GroรŸbuchstaben", + "common-12hour-time-minutes": "12-Stunden-Zeit mit Minuten", + "common-24hour-time-minutes": "24-Stunden-Zeit mit Minuten", + "common-24hour-time-seconds": "24-Stunden-Zeit mit Sekunden", + "common-european-date": "Europรคisches Datumsformat", + "common-iso-date": "ISO-Datumsformat", + "common-us-date": "US-Datumsformat", + "common-weekday-date": "Wochentag mit Datum", + "common-weekday-month-day": "Wochentag, Monat und Tag", + "day-abbreviated": "Abgekรผrzter Tagesname", + "day-full": "Vollstรคndiger Tagesname", + "day-leading-zero": "Tag mit fรผhrender Null (01-31)", + "day-no-leading-zero": "Tag ohne fรผhrende Null (1-31)", + "hour-leading-zero": "Stunde mit fรผhrender Null (00-23) โ€” 24-Stunden-Format", + "hour-no-leading-zero": "Stunde ohne fรผhrende Null (0-23) โ€” 24-Stunden-Format", + "minute-leading-zero": "Minute mit fรผhrender Null (00-59)", + "minute-no-leading-zero": "Minute ohne fรผhrende Null (0-59)", + "month-abbreviated": "Abgekรผrzter Monatsname", + "month-full": "Vollstรคndiger Monatsname", + "month-number-leading-zero": "Monat als Zahl mit fรผhrender Null (01-12)", + "month-number-no-zero": "Monat als Zahl ohne fรผhrende Null (1-12)", + "second-leading-zero": "Sekunde mit fรผhrender Null (00-59)", + "second-no-leading-zero": "Sekunde ohne fรผhrende Null (0-59)", + "timezone-abbreviation": "Zeitzonenabkรผrzung", + "year-four-digit": "Jahr als vierstellige Zahl", + "year-two-digit": "Jahr als zweistellige Zahl (00-99)" + }, + "file-picker": { + "item": "Element", + "items": "Elemente", + "search-placeholder": "Dateien und Ordner suchen...", + "select-current": "Aktuelles Objekt auswรคhlen", + "select-file": "Datei auswรคhlen", + "select-folder": "Ordner auswรคhlen", + "selected": "Ausgewรคhlt:", + "title": "Dateiauswahl" + }, + "file-picker-title": "Datei auswรคhlen", + "icon-picker": { + "title": "Symbol-Auswahl" + }, + "tooltip-placeholder": "Platzhalter" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "Anonyme Identitรคt", + "ca-cert": "CA-Zertifikat", + "eap-method": "EAP-Methode", + "password": "Benutzerpasswort", + "phase2-auth": "Phase 2 Authentifizierung", + "username": "Identitรคt / Benutzername" + }, + "panel": { + "action-required": "Aktion erforderlich", + "add-hidden-network": "Verstecktes Netzwerk hinzufรผgen", + "add-network": "Netzwerk hinzufรผgen", + "available-interfaces": "Verfรผgbare Schnittstellen", + "available-networks": "Verfรผgbare Netzwerke", + "disabled": "WLAN ist deaktiviert", + "disconnecting": "Verbindung wird getrennt...", + "dns": "DNS", + "enable-message": "Aktivieren Sie WLAN, um verfรผgbare Netzwerke zu sehen.", + "enter-password": "Passwort eingeben...", + "forget": "Vergessen", + "forget-network": "Dieses Netzwerk vergessen?", + "forgetting": "Wird vergessen...", + "hidden-network": "Verstecktes Netzwerk", + "interface": "Netzwerkschnittstelle", + "internet-connected": "Mit dem Internet verbunden", + "internet-limited": "Kein Internet", + "internet-status": "Internetstatus", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "Bekannte Netzwerke", + "link-speed": "Verbindungsgeschwindigkeit", + "network-name-ssid": "Netzwerkname (SSID)", + "no-ethernet-devices": "Keine Ethernet-Gerรคte erkannt", + "no-networks": "Keine Netzwerke gefunden", + "saved": "Gespeichert", + "scan-again": "Erneut scannen", + "searching": "Suche nach nahegelegenen Netzwerken...", + "security-open": "ร–ffnen", + "security-wep": "WEP", + "security-wpa": "WPA/WPA2/WPA3 Persรถnlich", + "security-wpa-ent": "WPA Enterprise", + "security-wpa2-ent": "WPA2 Enterprise", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 Enterprise", + "show-password": "Passwort anzeigen", + "title": "WLAN" + }, + "signal": { + "excellent": "Ausgezeichnet", + "fair": "MรครŸig", + "good": "Gut", + "poor": "Schlecht", + "weak": "Schwach" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/en-GB.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/en-GB.json new file mode 100644 index 0000000..a311162 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/en-GB.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "Clear history", + "close-app": "Close {app}", + "connect-vpn": "Connect to {name}", + "cycle-visualizer": "Cycle visualizer", + "disable-bluetooth": "Disable Bluetooth", + "disable-dnd": "Disable Do Not Disturb", + "disable-wifi": "Disable Wi-Fi", + "disconnect-vpn": "Disconnect {name}", + "dock-settings": "Dock settings", + "enable-bluetooth": "Enable Bluetooth", + "enable-dnd": "Enable Do Not Disturb", + "enable-wifi": "Enable Wi-Fi", + "launcher-settings": "Launcher settings", + "lower-to-bottom": "Lower to bottom", + "open-calendar": "Open calendar", + "open-display-settings": "Display settings", + "open-launcher": "Open launcher", + "open-settings": "Open settings", + "raise-to-top": "Raise to top", + "random-wallpaper": "Random wallpaper", + "run-custom-command": "Run custom command", + "show-active-only": "Show active only", + "show-all": "Show all", + "toggle-mute": "Toggle mute", + "widget-settings": "Widget settings" + }, + "authentication": { + "error": "Authentication error", + "failed": "Authentication failed" + }, + "bar": { + "active-window": { + "colorize-icons-description": "Apply theme colors to active window icon.", + "hide-mode-description": "Controls how the widget behaves when no window is active.", + "scrolling-mode-description": "Control when text scrolling is enabled for long window titles.", + "show-app-icon-description": "Display the application icon next to the window title.", + "show-app-icon-label": "Show app icon", + "show-app-text-description": "Display the title of the active window.", + "show-app-text-label": "Show window title" + }, + "audio-visualizer": { + "color-name-description": "Select the colour for the visualizer.", + "color-name-label": "Fill colour", + "height-description": "Custom component width.", + "hide-when-idle-description": "When enabled, the visualizer is hidden unless a player is actively playing.", + "hide-when-idle-label": "Hide when no media is playing", + "width-description": "Custom component width." + }, + "battery": { + "device-default": "Default (Display Device)", + "device-description": "Select which battery device to display.", + "device-label": "Battery device", + "display-mode-description": "Choose how the battery is displayed in the bar.", + "display-mode-graphic": "Graphical battery", + "display-mode-graphic-clean": "Graphical battery (no %)", + "display-mode-icon-always": "Icon - Always show %", + "display-mode-icon-hover": "Icon - Show on hover", + "display-mode-icon-only": "Icon only", + "hide-if-idle-description": "Hide the widget when the battery is not charging or discharging.", + "hide-if-idle-label": "Hide when idle", + "hide-if-not-detected-description": "Hide the widget when no battery is detected on the system.", + "hide-if-not-detected-label": "Hide when not detected", + "low-battery-threshold-label": "Low battery warning threshold", + "show-noctalia-performance-description": "Display the Noctalia performance mode toggle inside the battery panel.
Disables shadows and animations in Noctalia to reduce resource usage.", + "show-noctalia-performance-label": "Show Noctalia Performance toggle", + "show-power-profile-description": "Display the power profile selection inside the battery panel.", + "show-power-profile-label": "Show power profile controls" + }, + "brightness": { + "apply-all-description": "Change brightness for every monitor when using the scroll wheel.", + "apply-all-label": "Apply scroll changes to all monitors" + }, + "clock": { + "clock-display-description": "Customise your clock's display by adding tokens from the list below. To use the 12-hour format, you must include the 'AP' token.", + "clock-display-label": "Clock display", + "custom-font-description": "Select a custom font for the clock display.", + "custom-font-label": "Custom font", + "custom-font-placeholder": "Select custom font...", + "custom-font-search-placeholder": "Search fonts...", + "horizontal-bar-description": "Tip: Use \\n to create a line break.", + "horizontal-bar-label": "Horizontal bar", + "preview": "Preview", + "tooltip-format-description": "Format string for the tooltip shown when hovering over the clock. Leave empty to use the default tooltip.", + "tooltip-format-label": "Tooltip format", + "use-custom-font-description": "Override the default font selection with a custom font for the clock.", + "use-custom-font-label": "Use custom font", + "use-monospaced-font-description": "When enabled, the clock will use the monospaced font.", + "use-monospaced-font-label": "Use monospaced font", + "vertical-bar-description": "Use a space to separate each part onto a new line.", + "vertical-bar-label": "Vertical bar" + }, + "control-center": { + "browse-file": "Browse File", + "browse-library": "Browse Library", + "color-selection-description": "Apply theme colors to icons.", + "enable-colorization-description": "Enable colourisation for icon, applying theme colors.", + "icon-description": "Select an icon from the library or a custom file.", + "select-custom-icon": "Select a custom icon", + "use-distro-logo-description": "Use your distribution's logo instead of a custom icon.", + "use-distro-logo-label": "Use distro logo instead of icon" + }, + "custom-button": { + "collapse-condition-description": "If the output text matches this value, the button will collapse.", + "collapse-condition-label": "Collapse condition", + "color-selection-description": "Apply theme colors to icon and text.", + "default-tooltip": "Custom button, configure in settings", + "display-command-output-description": "Enter a command to run at a regular interval. The first line of its output will be displayed as text.", + "display-command-output-label": "Display command output", + "display-command-output-stream-description": "Enter a command to run continuously.", + "dynamic-text": "Dynamic text", + "enable-colorization-description": "Enable colourisation for the custom button icon and text, applying theme colors.", + "enable-colorization-label": "Enable colourisation", + "general-tooltip-text-description": "Custom text to display in the button's tooltip.", + "general-tooltip-text-label": "Custom tooltip text", + "hide-mode-always-expanded": "Always expanded", + "hide-mode-description": "Controls widget visibility when the command has no output.", + "hide-mode-expand-with-output": "Expand when has output", + "hide-mode-label": "Hide mode", + "hide-mode-max-transparent": "Max expanded but transparent", + "icon-description": "Select an icon from the library.", + "ipc-identifier-description": "Unique identifier for IPC commands. Use this identifier with 'qs -c noctalia-shell ipc call cb [action] [identifier]' to control this button via IPC.", + "ipc-identifier-label": "IPC Identifier", + "left-click-description": "Command to execute when the button is left-clicked.", + "left-click-label": "Left click", + "left-click-update-text": "Update displayed text on left-click", + "max-text-length-horizontal-description": "Maximum number of characters to show in horizontal bar (0 to hide text).", + "max-text-length-horizontal-label": "Max text length (horizontal)", + "max-text-length-vertical-description": "Maximum number of characters to show in vertical bar (0 to hide text).", + "max-text-length-vertical-label": "Max text length (vertical)", + "middle-click-description": "Command to execute when the button is middle-clicked.", + "middle-click-label": "Middle click", + "middle-click-update-text": "Update displayed text on middle-click", + "parse-json-description": "Parse the command output as a JSON object to dynamically set text and icon.", + "parse-json-label": "Parse output as JSON", + "refresh-interval-description": "Interval in milliseconds.", + "refresh-interval-label": "Refresh interval", + "right-click-description": "Command to execute when the button is right-clicked.", + "right-click-label": "Right click", + "right-click-update-text": "Update displayed text on right-click", + "show-exec-tooltip-description": "Show tooltips with command details (left/right/middle click, wheel).", + "show-exec-tooltip-label": "Show command tooltips", + "show-icon-description": "Toggles the visibility of the widget's icon.", + "show-icon-label": "Show icon", + "show-text-tooltip-description": "Show tooltips with the output from the text command.", + "show-text-tooltip-label": "Show dynamic text tooltips", + "text-stream-description": "Streamed lines from the command will be displayed as text on the button.", + "text-stream-label": "Stream", + "wheel-description": "Command to execute when the scroll wheel is used.
Use $delta for the scroll wheel delta in the command.", + "wheel-down": "Scroll down", + "wheel-down-description": "Command to execute when the scroll wheel is scrolled down.", + "wheel-down-label": "Wheel down command", + "wheel-label": "Scroll wheel", + "wheel-mode-separate-description": "Enable separate commands for wheel up and down.", + "wheel-mode-separate-label": "Separate wheel commands", + "wheel-up": "Scroll up", + "wheel-up-description": "Command to execute when the scroll wheel is scrolled up.", + "wheel-up-label": "Wheel up command", + "wheel-update-text": "Update displayed text on scroll" + }, + "keyboard-layout": { + "show-icon-description": "Display the keyboard layout icon." + }, + "lock-keys": { + "hide-when-off-description": "Hide the indicator when the key is not active.", + "hide-when-off-label": "Hide when off", + "show-caps-lock-description": "Display Caps Lock status.", + "show-caps-lock-label": "Caps Lock", + "show-num-lock-description": "Display Num Lock status.", + "show-num-lock-label": "Num Lock", + "show-scroll-lock-description": "Display Scroll Lock status.", + "show-scroll-lock-label": "Scroll Lock" + }, + "media-mini": { + "compact-mode-description": "Enable a space-saving layout for the media player panel.", + "compact-mode-label": "Compact mode", + "hide-mode-description": "Controls how the widget behaves when no media is playing.", + "max-width-description": "Sets the maximum horizontal size of the widget. The widget will shrink to fit shorter content.", + "no-active-player": "No active player", + "panel-section-description": "Configure the appearance and behaviour of the media player panel.", + "panel-section-label": "Media player panel", + "scrolling-mode-description": "Control when text scrolling is enabled for long track titles.", + "scrolling-mode-label": "Scrolling mode", + "show-album-art-description": "Display the album artwork for the currently playing track.", + "show-album-art-label": "Show album art", + "show-artist-first-description": "Display artist - title instead of title - artist.", + "show-artist-first-label": "Show artist first", + "show-progress-ring-description": "Display a circular progress indicator showing track progress.", + "show-progress-ring-label": "Show progress ring", + "show-visualizer-description": "Display an audio visualizer when music is playing.", + "show-visualizer-label": "Show visualizer", + "use-fixed-width-description": "When enabled, the widget will always use the maximum width instead of dynamically adjusting to content.", + "use-fixed-width-label": "Use fixed width", + "visualizer-type-description": "Choose the style of audio visualizer to display.", + "visualizer-type-label": "Visualizer type" + }, + "notification-history": { + "hide-widget-when-zero-description": "Hide the notification icon when there are no notifications.", + "hide-widget-when-zero-label": "Hide icon when no notifications", + "hide-widget-when-zero-unread-description": "Hide the notification icon when there are no unread notifications.", + "hide-widget-when-zero-unread-label": "Hide icon when no unread notifications", + "show-unread-badge-description": "Display a badge showing the number of unread notifications.", + "show-unread-badge-label": "Show unread badge", + "unread-badge-color-description": "Select the colour for the unread notification badge.", + "unread-badge-color-label": "Unread badge colour" + }, + "section-editor": { + "placeholder": "Select a widget to add...", + "search-placeholder": "Search widget..." + }, + "spacer": { + "width-description": "Spacing width in pixels." + }, + "system-monitor": { + "compact-mode-description": "Display stats as mini bar charts instead of text values. Prevents layout shifting.", + "compact-mode-label": "Compact mode", + "cpu-cores-description": "Display CPU cores usage individually.", + "cpu-cores-label": "CPU Cores", + "cpu-frequency-description": "Display the current CPU clock speed in GHz.", + "cpu-frequency-label": "Show CPU frequency", + "cpu-temperature-description": "Show CPU temperature readings if available.", + "cpu-temperature-label": "CPU temperature", + "cpu-usage-description": "Display current CPU usage percentage.", + "cpu-usage-label": "CPU usage", + "disk-path-description": "Select which disk mount point to monitor.", + "disk-path-label": "Disk path", + "gpu-temperature-description": "Show GPU temperature readings if available.", + "load-average-description": "Display system load average.", + "load-average-label": "Load average", + "memory-percentage-description": "Show memory usage as a percentage instead of absolute values.", + "memory-percentage-label": "Memory as percentage", + "memory-usage-description": "Display current RAM usage information.", + "memory-usage-label": "Memory usage", + "network-traffic-description": "Display network upload and download speeds.", + "network-traffic-label": "Network traffic", + "storage-as-percentage-description": "Show disk space as percentage instead of absolute values.", + "storage-as-percentage-label": "Disk as percentage", + "storage-available-description": "Shows how much disk space is available instead of how much is used.", + "storage-available-label": "Disk space available", + "storage-usage-description": "Show disk space usage information.", + "storage-usage-label": "Storage usage", + "swap-usage-description": "Show swap memory usage.", + "swap-usage-label": "Swap usage", + "use-monospace-font-description": "Use monospace font for consistent character width.", + "use-monospace-font-label": "Monospace font", + "use-padding-description": "Pads the text values with leading spaces to prevent layout shifting.", + "use-padding-description-disabled-monospace-font": "A monospace font is needed for this feature.", + "use-padding-description-disabled-vertical": "Vertical taskbar does not support padding text.", + "use-padding-label": "Pad text" + }, + "taskbar": { + "colorize-icons-description": "Apply theme colors to taskbar icons.", + "hide-mode-description": "Controls how the widget behaves when there are no matching windows.", + "hide-mode-label": "Hiding mode", + "icon-scale-description": "Sets the scaling factor for taskbar icons.", + "icon-scale-label": "Icon scaling", + "max-width-description": "Maximum width of the taskbar as a percentage of screen width.", + "max-width-label": "Maximum width", + "only-active-workspaces-description": "Show only apps from active workspaces.", + "only-active-workspaces-label": "Only from active workspaces", + "only-same-monitor-description": "Show only apps from the monitor where the bar is located.", + "only-same-monitor-label": "Only from same monitor", + "show-pinned-apps-description": "Show pinned apps from the dock in the taskbar.", + "show-pinned-apps-label": "Show pinned apps", + "show-title-description": "Display window titles in the taskbar.", + "show-title-description-disabled": "Vertical taskbar does not support showing titles.", + "show-title-label": "Show title", + "smart-width-description": "Automatically adjust entry width based on the number of entries.", + "smart-width-label": "Smart width", + "title-width-description": "Set the width of window titles in the taskbar (in pixels).", + "title-width-label": "Title width", + "title-width-reset-tooltip": "Reset title width" + }, + "tray": { + "chevron-color-description": "Apply theme colors to the drawer chevron icon.", + "chevron-color-label": "Chevron colour", + "colorize-icons-description": "Apply theme colors to tray icons.", + "colorize-icons-label": "Colourise icons", + "drawer-enabled-description": "When enabled, unpinned tray items are shown in a drawer panel.
When disabled, all tray items are shown inline.", + "drawer-enabled-label": "Enable drawer", + "hide-passive-description": "When enabled, tray items with 'Passive' status will be hidden.", + "hide-passive-label": "Hide passive items" + }, + "volume": { + "display-mode-description": "Choose how you'd like this value to appear." + }, + "workspace": { + "character-count-description": "Number of characters to display from workspace names (1-10).", + "character-count-label": "Character count", + "empty-color-description": "Set the background colour for empty workspaces.", + "empty-color-label": "Empty workspace colour", + "enable-scrollwheel-description": "Switch between workspaces using the mouse scroll wheel.", + "enable-scrollwheel-label": "Scroll to switch workspaces", + "focused-color-description": "Set the background colour for the focused workspace.", + "focused-color-label": "Focused workspace colour", + "follow-focused-screen-description": "Display workspaces from the currently focused screen, rather than the screen where the bar is located.", + "follow-focused-screen-label": "Follow focused screen", + "font-weight-description": "Set the visual weight for text within workspace.", + "font-weight-label": "Font weight", + "grouped-border-opacity-description": "Set the opacity level for workspace container borders.", + "grouped-border-opacity-label": "Border opacity", + "hide-unoccupied-description": "Don't display workspaces without windows.", + "hide-unoccupied-label": "Hide unoccupied", + "label-mode-description": "Choose how workspace labels are displayed.", + "label-mode-label": "Label mode", + "occupied-color-description": "Set the background colour for occupied workspaces.", + "occupied-color-label": "Occupied workspace colour", + "pill-size-description": "Adjust the size of workspace pills.", + "pill-size-label": "Pill size", + "reverse-scrolling-description": "Reverse the direction of workspace switching when scrolling.", + "reverse-scrolling-label": "Reverse scrolling", + "show-applications-description": "Display application icons inside each workspace.", + "show-applications-hover-description": "Display application icons inside each workspace when the workspace is hovered.", + "show-applications-hover-label": "Show applications when hovered", + "show-applications-label": "Show applications", + "show-badge-description": "Show the workspace number badge in grouped mode.", + "show-badge-label": "Show workspace badge", + "show-labels-only-when-occupied-description": "Only show workspace labels when they contain windows.", + "show-labels-only-when-occupied-label": "Show labels only when occupied", + "unfocused-icons-opacity-description": "Set the opacity level for unfocused app icons.", + "unfocused-icons-opacity-label": "Unfocused icons opacity" + } + }, + "battery": { + "all-batteries": "All batteries (combined)", + "battery-health": "Battery health", + "battery-level": "Battery level", + "capacity-level": "Capacity: {level}", + "charging-rate": "Charging rate: {rate} W", + "discharging-rate": "Discharging rate: {rate} W", + "health": "Health: {percent}%", + "inhibit-idle-description": "Keeps the system awake.", + "no-battery-detected": "No battery detected", + "plugged-in": "Plugged in", + "power-profile": "Power profile", + "time-left": "Time left: {time}", + "time-until-full": "Time until full: {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "Available devices", + "blocked": "Blocked", + "connected-devices": "Connected devices", + "device-address": "Device address", + "disabled": "Bluetooth is disabled", + "discoverable": "Discoverable", + "enable-message": "Enable Bluetooth to see available devices.", + "known-devices": "Known devices", + "no-devices": "No devices available", + "paired-devices": "Paired devices", + "pairing-mode": "Make sure your device is in pairing mode.", + "scanning": "Scanning for devices...", + "signal-text-excellent": "Signal: Excellent", + "signal-text-fair": "Signal: Fair", + "signal-text-good": "Signal: Good", + "signal-text-poor": "Signal: Poor", + "signal-text-unknown": "Signal: Unknown", + "signal-text-very-poor": "Signal: Very poor" + } + }, + "changelog": { + "error": { + "fetch-failed": "Unable to load changelog data. Please try again later.", + "rate-limit": "GitHub rate limit exceeded. Please try again in a few minutes." + }, + "panel": { + "buttons-discord": "Join our Discord", + "buttons-dismiss": "OK", + "buttons-feedback": "Give feedback", + "empty": "Release notes are not available yet.", + "highlight-title": "Highlights", + "released": "Released on {date}", + "subtitle-fresh": "Thanks for installing Noctalia! Here is whatโ€™s included in this build.", + "subtitle-updated": "Updated from {previousVersion}", + "title": "What's new in {version}", + "version": "Version {version}", + "version-new-user": "Fresh install" + } + }, + "common": { + "actions": "Actions", + "active": "Active", + "add": "Add", + "appearance": "Appearance", + "apply": "Apply", + "auto-connect": "Auto-connect", + "automation": "Automation", + "available": "Available", + "back": "Back", + "battery": "Battery", + "behavior": "Behaviour", + "bluetooth": "Bluetooth", + "brightness": "Brightness", + "browse": "Browse", + "calendar": "Calendar", + "calendar-panel": "Calendar Panel", + "cancel": "Cancel", + "cards": "Cards", + "charging": "Charging", + "clear": "Clear", + "clipboard": "Clipboard", + "close": "Close", + "color-muted": "Muted", + "colors": "Colors", + "command": "Command", + "confirm": "Confirm", + "connect": "Connect", + "connected": "Connected", + "connecting": "Connecting...", + "continue": "Continue", + "contributors": "Contributors", + "copied-to-clipboard": "Copied to clipboard", + "countdown": "Countdown", + "customize": "Customise", + "date": "Date", + "default": "Default", + "delete": "Delete", + "devices": "Devices", + "disabled": "Disabled", + "discharging": "Discharging", + "disconnect": "Disconnect", + "disconnected": "Disconnected", + "disconnecting": "Disconnecting...", + "display-mode": "Display mode", + "documentation": "Documentation", + "download": "Download", + "duration": "Duration", + "dysfunctional": "Dysfunctional", + "edit": "Edit", + "enabled": "Enabled", + "error": "Error", + "ethernet": "Ethernet", + "events": "Events", + "execute": "Execute", + "faithful": "Faithful", + "focus": "Focus", + "font-weight-bold": "Bold", + "font-weight-light": "Light", + "font-weight-medium": "Medium", + "font-weight-regular": "Regular", + "font-weight-semibold": "Semi-bold", + "frequency": "Frequency", + "gateway": "Gateway", + "general": "General", + "height": "Height", + "hibernate": "Hibernate", + "history": "History", + "icon": "Icon", + "idle": "Idle", + "info": "Info", + "input": "Input", + "install": "Install", + "installed": "Installed", + "interface": "Interface", + "internet": "Internet", + "keybind": "Keybind", + "language": "Language", + "loading": "Loading...", + "local": "Local", + "location": "Location", + "lock": "Lock", + "logout": "Logout", + "look": "Look", + "margins": "Margins", + "media": "Media", + "media-player": "Media Player", + "memory": "Memory", + "monitors": "Monitors", + "move-down": "Move down", + "move-up": "Move up", + "network": "Network", + "next": "Next", + "night-light": "Night Light", + "no": "No", + "no-results": "No results", + "no-summary": "No summary", + "none": "None", + "not-found": "Not found", + "notifications": "Notifications", + "official": "Official", + "on-surface": "On Surface", + "output": "Output", + "pair": "Pair", + "paired": "Paired", + "pairing": "Pairing...", + "panels": "Panels", + "password": "Password", + "pause": "Pause", + "performance": "Performance", + "pin": "Pin", + "play": "Play", + "polling": "Polling", + "position": "Position", + "previous": "Previous", + "primary": "Primary", + "random": "Random", + "reboot": "Reboot", + "reboot-to-uefi": "Reboot to UEFI", + "record": "Start recording", + "refresh": "Refresh", + "required": "(required)", + "reset": "Reset", + "reset-to-default": "Reset to Default", + "result-count": "{count} result", + "result-count-plural": "{count} results", + "resume": "Resume", + "save": "Save", + "scanning": "Scanning...", + "screen-corners": "Screen Corners", + "search": "Search", + "second": "{count} second", + "second-plural": "{count} seconds", + "secondary": "Secondary", + "security": "Security", + "select": "Select", + "select-color": "Select colour", + "select-color-description": "Apply theme colors for emphasis.", + "select-icon-color": "Select icon colour", + "settings": "Settings", + "shortcuts": "Shortcuts", + "shutdown": "Shutdown", + "signal": "Signal", + "sound": "Sound", + "sources": "Sources", + "start": "Start", + "stop": "Stop", + "supporters": "Supporters", + "suspend": "Suspend", + "templates": "Templates", + "tertiary": "Tertiary", + "test": "Test", + "thresholds": "Thresholds", + "title": "Title", + "toast": "Toast", + "trusted": "Trusted", + "uninstall": "Uninstall", + "unknown": "Unknown", + "unpair": "Unpair", + "unpin": "Unpin", + "update": "Update", + "upload": "Upload", + "username": "Username", + "userspace-reboot": "Userspace Reboot", + "version": "Version", + "vibrant": "Vibrant", + "visualizer": "Visualizer", + "volume": "Volume", + "volumes": "Volumes", + "wallpaper": "Wallpaper", + "weather": "Weather", + "weather-loading": "Loading weather...", + "weather-no-location": "Set a location in Settings to see weather", + "week": "Week", + "widgets": "Widgets", + "width": "Width", + "wifi": "Wi-Fi", + "windows": "Windows", + "yes": "Yes" + }, + "control-center": { + "power-profile": { + "tooltip-action": "Power Profile", + "tooltip-disabled": "Install power-profiles-daemon to use power profiles" + }, + "wifi": { + "label-disconnected": "Wi-Fi disconnected", + "label-ethernet": "Ethernet" + } + }, + "display-modes": { + "always-hide": "Always hide", + "always-show": "Always show", + "force-open": "Force open", + "on-hover": "On hover" + }, + "hide-modes": { + "auto-hide": "Auto-hide", + "hidden": "Hide when empty", + "idle": "Hide when idle", + "non-exclusive": "Non-exclusive", + "transparent": "Transparent when empty", + "visible": "Always visible" + }, + "launcher": { + "categories": { + "all": "All", + "audiovideo": "Audio & Video", + "chat": "Chat", + "development": "Development", + "education": "Education", + "emoji-activity": "Activity", + "emoji-animals": "Animals", + "emoji-flags": "Flags", + "emoji-food": "Food & Drink", + "emoji-nature": "Nature", + "emoji-objects": "Objects", + "emoji-people": "People & Body", + "emoji-recent": "Recent", + "emoji-symbols": "Symbols", + "emoji-travel": "Travel & Places", + "game": "Games", + "graphics": "Graphics", + "misc": "Misc", + "office": "Office", + "pinned": "Pinned", + "system": "System", + "webbrowser": "Web Browser" + }, + "providers": { + "applications": "Applications", + "calculator": "Calculator", + "calculator-deprecated": "The >calc command is deprecated and will be removed soon. Please type math expressions directly in search instead.", + "calculator-description": "Calculator โ€” evaluate mathematical expressions", + "calculator-enter-expression": "Enter a mathematical expression", + "calculator-press-enter-to-copy": "Press Enter to copy result", + "clipboard": "Clipboard history", + "clipboard-clear-description": "Clear all clipboard history", + "clipboard-clear-description-full": "Remove all items from clipboard history", + "clipboard-clear-history": "Clear clipboard history", + "clipboard-delete": "Delete clipboard entry", + "clipboard-history-disabled": "Clipboard history disabled", + "clipboard-history-disabled-description": "Enable clipboard history in settings or install cliphist", + "clipboard-loading": "Loading clipboard history...", + "clipboard-search-description": "Search clipboard history", + "command-description": "Run shell commands", + "emoji": "Emoji picker", + "emoji-loading": "Loading emojis...", + "emoji-loading-description": "Please wait", + "emoji-no-recent": "No recent emojis yet", + "emoji-search-description": "Search and copy emojis", + "settings-search-description": "Search and navigate to settings", + "windows-search-description": "Search and focus open windows" + } + }, + "lock-screen": { + "authenticating": "Authenticating...", + "password": "Enter your password...", + "restart": "Restart", + "shut-down": "Shut down" + }, + "notifications": { + "panel": { + "click-to-collapse": "Click to collapse", + "click-to-expand": "Click to expand", + "description": "Your notifications will show up here as they arrive.", + "no-notifications": "No notifications" + }, + "range": { + "earlier": "Earlier", + "today": "Today", + "yesterday": "Yesterday" + }, + "time": { + "diff-d": "1 day ago", + "diff-dd": "{diff} days ago", + "diff-h": "1 hour ago", + "diff-hh": "{diff} hours ago", + "diff-m": "1 minute ago", + "diff-mm": "{diff} minutes ago", + "now": "now" + } + }, + "options": { + "bar": { + "density-comfortable": "Comfortable", + "density-compact": "Compact", + "density-default": "Default", + "density-mini": "Mini", + "density-spacious": "Spacious", + "display-mode-always-visible": "Always Visible", + "type-floating": "Floating", + "type-framed": "Framed", + "type-simple": "Simple" + }, + "control-center": { + "quick-settings-style-classic": "Classic", + "quick-settings-style-modern": "Modern" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "Comfortable", + "compact": "Compact", + "default": "Default" + }, + "launcher-view-mode": { + "grid": "Grid", + "list": "List" + }, + "notification-density": { + "compact": "Compact", + "default": "Default" + }, + "scrolling-modes": { + "always": "Scroll always", + "hover": "Scroll on hover", + "never": "Never scroll" + }, + "session-menu-grid-layout": { + "grid": "Grid", + "single-row": "Single row" + }, + "settings-panel-mode": { + "attached": "Panel attached to bar", + "centered": "Centred panel", + "window": "Separate window" + }, + "visualizer-types": { + "linear": "Linear", + "mirrored": "Mirrored", + "wave": "Wave" + }, + "visualizer-visibility": { + "with-background": "With background only" + }, + "workspace-labels": { + "index": "Index", + "index-and-name": "Index and name", + "name": "Name" + } + }, + "panels": { + "about": { + "become-supporter": "Become a supporter", + "changelog": "View changelog", + "changelog-on-startup": "Show changelog on update", + "changelog-on-startup-desc": "Automatically show the changelog when Noctalia is updated", + "contributors-description": "Shout-out to our {count} awesome contributor!", + "contributors-description-plural": "Shout-out to our {count} awesome contributors!", + "copy-info": "Copy info", + "debug-disabled": "Debug mode disabled", + "debug-enabled": "Debug mode enabled", + "info-copied": "Info copied to clipboard", + "noctalia-available": "Available:", + "noctalia-desc": "A sleek and minimal desktop shell thoughtfully crafted for Wayland, built with Quickshell.", + "noctalia-git-commit": "Git commit:", + "noctalia-installed-version": "Installed version:", + "noctalia-latest-version": "Latest version:", + "noctalia-qs-version": "Noctalia QS version:", + "noctalia-title": "Noctalia Shell", + "privacy-policy": "Privacy policy", + "support": "Support us", + "supporter-badge": "Supporter", + "supporters-desc": "A huge thank you to our amazing supporter!", + "supporters-desc-plural": "A huge thank you to our {count} amazing supporters!", + "supporters-loading": "Loading supporters...", + "system-board": "Board:", + "system-cpu": "CPU:", + "system-disk": "Disk:", + "system-gpu": "GPU:", + "system-host": "Host:", + "system-install-hint": "Install fastfetch to view system information", + "system-kernel": "Kernel:", + "system-loading": "Loading system info...", + "system-memory": "Memory:", + "system-monitor": "Monitor:", + "system-not-installed": "fastfetch is not installed", + "system-os": "OS:", + "system-packages": "Packages:", + "system-product": "Product:", + "system-title": "System Information", + "system-uptime": "Uptime:", + "system-wm": "WM:", + "telemetry-data-copied": "Telemetry data copied to clipboard", + "telemetry-desc": "Help improve Noctalia by sharing anonymous system info (screen resolution, compositor, distro). Sent once at startup, no tracking, data auto-deleted after 30 days.", + "telemetry-enabled": "Send anonymous system information", + "telemetry-show-data": "View data", + "telemetry-title": "Privacy", + "title": "About", + "up-to-date": "You're up to date!", + "update-available": "Update available", + "view-commit": "View commit on GitHub" + }, + "audio": { + "devices-desc": "Configure available audio input and output devices.", + "devices-input-device-description": "Select the desired audio input device.", + "devices-input-device-label": "Input device", + "devices-output-device-description": "Select the desired audio output device.", + "devices-output-device-label": "Output device", + "devices-title": "Audio devices", + "external-mixer-description": "Enter the command or application path to launch when activating the external audio mixer feature.", + "external-mixer-label": "External audio mixer command", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "Set your preferred and ignored media applications.", + "media-excluded-player-description": "Add keywords for players you want the system to ignore. Each keyword should be on a new line.", + "media-excluded-player-label": "Excluded player", + "media-excluded-player-placeholder": "type substring and press +", + "media-frame-rate-description": "Higher rates are smoother but use more resources.", + "media-frame-rate-label": "Frame rate", + "media-primary-player-description": "Enter a keyword to identify your main player.", + "media-primary-player-label": "Primary player", + "media-primary-player-placeholder": "e.g. spotify, vlc, mpv", + "media-scrolling-speed-description": "Time in seconds for the title to scroll from start to end.", + "media-scrolling-speed-label": "Scrolling speed", + "media-scrolling-title-description": "Enable continuous scrolling for long media titles.", + "media-scrolling-title-label": "Scrolling title", + "media-title": "Media players", + "on-middle-clicked-description": "Command to execute when the button is middle-clicked.", + "panel-applications-empty": "No applications are currently playing audio", + "spectrum-mirrored-description": "Mirror the spectrum so low frequencies are in the centre. When off, low frequencies are on the left and high on the right.", + "spectrum-mirrored-label": "Mirror spectrum", + "title": "Audio", + "visualizer-type-description": "Choose a visualization type for media playback.", + "visualizer-type-label": "Visualization type", + "volumes-desc": "Adjust volume controls and audio levels.", + "volumes-feedback-sound-file-description": "Path to the sound file played when changing volume.", + "volumes-feedback-sound-file-label": "Volume feedback sound file", + "volumes-feedback-sound-file-select-title": "Select volume feedback sound file", + "volumes-input-volume-description": "Microphone input volume level.", + "volumes-mute-input-description": "Mute the default audio input (microphone).", + "volumes-mute-input-label": "Mute audio input", + "volumes-mute-output-description": "Mute the system's main audio output.", + "volumes-mute-output-label": "Mute audio output", + "volumes-output-volume-description": "System-wide volume level.", + "volumes-step-size-description": "Adjust the step size for volume changes (scroll wheel, keyboard shortcuts).", + "volumes-step-size-label": "Volume step size", + "volumes-volume-feedback-description": "Play a feedback sound when adjusting volume.", + "volumes-volume-feedback-label": "Play volume feedback sound", + "volumes-volume-overdrive-description": "Allow raising volume above 100%. May not be supported by all hardware.", + "volumes-volume-overdrive-label": "Allow volume overdrive" + }, + "bar": { + "appearance-auto-hide-delay-description": "Time before bar hides after mouse leaves", + "appearance-auto-hide-delay-label": "Hide Delay", + "appearance-auto-hide-exclusive-note": "Note: Exclusive zone is automatically disabled when auto-hide is active", + "appearance-auto-show-delay-description": "Time before bar shows when mouse enters edge", + "appearance-auto-show-delay-label": "Show Delay", + "appearance-background-opacity-description": "Set the background opacity specifically for the bar.", + "appearance-background-opacity-label": "Bar background opacity", + "appearance-capsule-color-description": "Choose a colour for bar capsules, or use none for the default surface colour.", + "appearance-capsule-color-label": "Capsule colour", + "appearance-capsule-opacity-description": "Set the opacity level for widget backgrounds when capsule is shown.", + "appearance-capsule-opacity-label": "Capsule opacity", + "appearance-content-padding-description": "Adjust the padding between bar edges and widgets.", + "appearance-content-padding-label": "Content padding", + "appearance-density-description": "Adjust the bar's padding for a compact or spacious look.", + "appearance-density-label": "Bar density", + "appearance-desc": "Customise the bar's appearance and position.", + "appearance-display-mode-description": "Choose when the bar is visible.", + "appearance-enable-exclusion-zone-inset-description": "Reduce the exclusion zone by 1 physical pixel so flush windows bleed perfectly under the bar edge.", + "appearance-enable-exclusion-zone-inset-label": "Inset exclusion zone", + "appearance-floating-description": "Display the bar as a floating 'pill'.", + "appearance-floating-label": "Floating bar", + "appearance-font-scale-description": "Adjust the font size scale for text displayed in the bar.", + "appearance-font-scale-label": "Font scale", + "appearance-frame-radius": "Inner Radius", + "appearance-frame-settings-description": "Adjust frame thickness and inner corner radius", + "appearance-frame-settings-label": "Frame Settings", + "appearance-frame-thickness": "Thickness", + "appearance-hide-on-overview-description": "Hide the bar and close panels when the compositor overview is active.", + "appearance-hide-on-overview-label": "Hide bar on overview", + "appearance-margins-description": "Adjust the margins around the floating bar.", + "appearance-margins-horizontal": "Horizontal Margin ", + "appearance-margins-vertical": "Vertical Margin", + "appearance-outer-corners-description": "Display outwardly curved corners on the bar.", + "appearance-outer-corners-label": "Outer corners", + "appearance-position-description": "Choose where to place the bar on the screen.", + "appearance-position-label": "Bar position", + "appearance-show-capsule-description": "Show widget backgrounds.", + "appearance-show-capsule-label": "Show capsule", + "appearance-show-on-workspace-switch-description": "Automatically show the bar briefly when the workspace changes.", + "appearance-show-on-workspace-switch-label": "Show bar on workspace switch", + "appearance-show-outline-description": "Displays a visible border around every widget.", + "appearance-show-outline-label": "Show widget outlines", + "appearance-type-description": "Choose the style of the bar: Simple, Floating or Framed.", + "appearance-type-label": "Bar type", + "appearance-use-separate-opacity-description": "Enable to use a separate opacity value for the bar background.", + "appearance-use-separate-opacity-label": "Use separate bar opacity", + "appearance-widget-spacing-description": "Adjust the spacing between each widget in the bar.", + "appearance-widget-spacing-label": "Widget spacing", + "behavior-middle-click-command-description": "Command to execute on middle click.", + "behavior-middle-click-command-label": "Middle click command", + "behavior-middle-click-command-placeholder": "niri msg action toggle-overview", + "behavior-middle-click-description": "Choose what middle click does on empty areas of the bar.", + "behavior-middle-click-follow-mouse-description": "Open the selected middle-click panel at the cursor position.", + "behavior-middle-click-follow-mouse-label": "Middle click follow mouse", + "behavior-middle-click-label": "Bar middle click action", + "behavior-right-click-command-description": "Command to execute on right click.", + "behavior-right-click-command-label": "Right click command", + "behavior-right-click-command-placeholder": "notify-send \"Right click\"", + "behavior-right-click-description": "Choose what right click does on empty areas of the bar.", + "behavior-right-click-follow-mouse-description": "Open the selected right-click panel at the cursor position.", + "behavior-right-click-follow-mouse-label": "Right click follow mouse", + "behavior-right-click-label": "Bar right click action", + "behavior-wheel-wrap-description": "When enabled, scrolling continues from the last item to the first.", + "behavior-wheel-wrap-label": "Wrap around", + "behavior-workspace-scroll-description": "Choose what the mouse wheel does on empty areas of the bar.", + "behavior-workspace-scroll-label": "Bar mouse wheel action", + "behavior-workspace-scroll-option-content": "Content", + "behavior-workspace-scroll-option-workspace": "Workspace", + "monitor-configure-widgets": "Configure widgets", + "monitor-override-settings": "Override global settings", + "monitor-override-settings-description": "Use custom settings for this monitor.", + "monitor-reset-all": "Reset all", + "monitor-widgets-title": "Widget Configuration - {monitor}", + "monitors-desc": "Show bar on specific monitors. Defaults to all if none are chosen.", + "monitors-desc-new": "Configure which monitors display the bar and customise settings per monitor.", + "monitors-title": "Monitors display", + "title": "Bar", + "tray-blacklist-description": "Add tray exclusion rules, supports wildcards (*).", + "tray-blacklist-label": "Blacklist", + "tray-blacklist-placeholder": "e.g. nm-applet, Fcitx*", + "tray-pin-application": "Pin Application", + "tray-unpin-application": "Unpin Application", + "use-global-widgets": "Use global widgets", + "widgets-desc": "Drag widgets to change their order. Use the right-click menu to transfer widgets between sections or remove them.", + "widgets-title": "Widgets positioning" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "Generate colour schemes from your wallpaper. Automatically extracts colors to create a cohesive theme.", + "color-source-use-wallpaper-colors-label": "Use wallpaper colors", + "dark-mode-mode-description": "Enables automatic switching between Light and Dark Mode.", + "dark-mode-mode-label": "Dark Mode schedule", + "dark-mode-mode-manual": "Manual", + "dark-mode-mode-off": "Off", + "dark-mode-switch-description": "Switches to a darker theme for easier viewing at night.", + "delete-error-description": "Failed to delete {scheme}", + "delete-error-title": "Delete failed", + "delete-success-description": "Successfully deleted {scheme}", + "delete-success-title": "Colour scheme deleted", + "download-button": "Download more", + "download-downloading": "Downloading...", + "download-empty": "No colour schemes available", + "download-error-api-error": "API error: {status}", + "download-error-description": "Failed to download {scheme}", + "download-error-download-failed": "Download failed with exit code: {code}", + "download-error-invalid-response": "Invalid API response format", + "download-error-no-files": "No files found for scheme", + "download-error-parse-failed": "Failed to parse API response: {error}", + "download-error-rate-limit": "GitHub API rate limit exceeded", + "download-error-title": "Download failed", + "download-fetching": "Fetching available colour schemes...", + "download-success-description": "Successfully downloaded {scheme}", + "download-success-title": "Colour scheme downloaded", + "download-title": "Download Colour Schemes", + "method-description": { + "content": "Material Design scheme with high-fidelity colour extraction that closely matches the source content's actual colors.", + "dysfunctional": "Like Faithful but picks the second most dominant colour family as primary.", + "faithful": "Attempts to stay close to the source colour while still generating a harmonious palette.", + "fruit-salad": "Material Design scheme that produces a playful, vibrant palette with diverse and varied hues.", + "monochrome": "Material Design scheme using a single-hue grayscale with minimal chromatic content.", + "muted": "Generates softer, desaturated tones for a calm and understated appearance.", + "rainbow": "Material Design scheme that generates colors across the full spectrum for maximum colour diversity.", + "tonal-spot": "Material Design default algorithm. Creates balanced palettes using tonal variations of the source colour.", + "vibrant": "Maximizes colour saturation for bold, eye-catching palettes with vivid tones." + }, + "predefined-desc": "Choose from a collection of predefined colour schemes.", + "predefined-generate-templates-label": "Generate templates for predefined schemes", + "predefined-title": "Predefined colour schemes", + "sync-gsettings-description": "Match the system theme to the active light or dark variant.", + "sync-gsettings-label": "Sync system theme", + "templates-desc": "Apply colors to external applications.", + "templates-filter-description": "Show templates from a specific category.", + "templates-filter-label": "Filter by category", + "templates-misc-description": "Create your own templates.", + "templates-misc-label": "Advanced", + "templates-misc-user-templates-description": "Only enable if you know what you are doing โ€” refer to our online documentation.", + "templates-misc-user-templates-label": "Enable user templates", + "templates-none-detected": "None detected", + "templates-write-path": "Writes: {filepath}", + "title": "Colour Scheme", + "wallpaper-method-description": "Choose your favorite palette generation method.", + "wallpaper-method-label": "Palette generation method", + "wallpaper-monitor-source-description": "Select which monitor to use for extracting wallpaper colors.", + "wallpaper-monitor-source-label": "Colour generation source" + }, + "connections": { + "authentication-required": "Authentication required", + "bluetooth-auto-connect-description": "Automatically connect to trusted paired devices when Bluetooth is enabled.", + "bluetooth-auto-connect-label": "Auto-connect paired devices", + "bluetooth-devices-unnamed": "Unnamed devices are not shown.", + "bluetooth-discoverable": "This device is discoverable as {hostName} while this settings tab is open.", + "bluetooth-rssi-polling-description": "Periodically sample RSSI for connected devices via bluetoothctl. May not be available for all devices; uses minimal resources when enabled.", + "bluetooth-rssi-polling-interval-description": "Configure how often to update signal strength for connected devices.", + "bluetooth-rssi-polling-interval-label": "Polling interval", + "bluetooth-rssi-polling-label": "Bluetooth signal polling", + "disable-discoverability-description": "Hide your device from nearby Bluetooth devices.", + "disable-discoverability-label": "Disable device visibility", + "hide-unnamed-devices-description": "Hide devices that appear only as Bluetooth addresses.", + "hide-unnamed-devices-label": "Hide unnamed devices", + "pin-instructions": "Please enter the PIN code displayed on your device.", + "title": "Connections", + "wifi-header-text": "Use Wi-Fi to see available networks and connect to the internet." + }, + "control-center": { + "cards-desc": "Customise which controls appear in the control centre and in what order.", + "desc": "Configure the control centre panel positioning and behaviour.", + "open-at-mouse-description": "When right-clicking an empty area of the bar, open the control centre near the cursor. When disabled, the position setting above is used instead.", + "open-at-mouse-label": "Open at cursor on right-click", + "position-description": "Choose where the control centre panel appears when opened.", + "shortcuts-custom-button-command-description": "The command to execute when the button is clicked.", + "shortcuts-custom-button-enable-on-state-logic-description": "Enable a second icon and 'hot' state based on a check command.", + "shortcuts-custom-button-enable-on-state-logic-label": "Enable on-state logic", + "shortcuts-custom-button-icon-description": "Select an icon from the library.", + "shortcuts-custom-button-on-clicked-label": "Left click command", + "shortcuts-custom-button-on-middle-clicked-description": "Command to execute when the button is middle-clicked.", + "shortcuts-custom-button-on-state-command-description": "Command to execute to check if the button should be in the 'on' state. Returns 0 for on, non-zero for off.", + "shortcuts-custom-button-on-state-command-label": "On-state check command", + "shortcuts-custom-button-on-state-icon-description": "The icon for the button when it's in the 'on' state.", + "shortcuts-custom-button-on-state-icon-label": "On-state icon", + "shortcuts-custom-button-state-checks-add": "Add state check", + "shortcuts-custom-button-state-checks-command": "Command to execute for this state check", + "shortcuts-custom-button-state-checks-label": "State checks", + "shortcuts-custom-button-state-checks-remove": "Remove", + "shortcuts-custom-button-tooltip-description": "The tooltip to show when hovering over the button.", + "shortcuts-custom-button-tooltip-label": "Tooltip", + "shortcuts-title": "Shortcuts widgets", + "system-monitor-disk-path-description": "Select which disk mount point the system monitor card in the control centre should monitor.", + "system-monitor-disk-path-label": "System monitor disk path", + "title": "Control Centre" + }, + "desktop-widgets": { + "clock-enabled-description": "Show a clock widget on the desktop.", + "clock-enabled-label": "Enable clock widget", + "clock-format-label": "Format", + "clock-height-description": "Height of the clock widget in pixels.", + "clock-height-label": "Height", + "clock-minimal-mode-description": "Use a minimal clock display with time and date.", + "clock-minimal-mode-label": "Minimal mode", + "clock-rounded-corners-description": "Use rounded corners for the widget background.", + "clock-rounded-corners-label": "Rounded corners", + "clock-show-background-description": "Show the background container for the clock widget.", + "clock-show-background-label": "Show background", + "clock-show-date-description": "Display the current date below the time.", + "clock-show-date-label": "Show date", + "clock-show-seconds-description": "Display seconds in the time.", + "clock-show-seconds-label": "Show seconds", + "clock-style-analog": "Analog", + "clock-style-binary": "Binary", + "clock-style-description": "Choose the clock display style.", + "clock-style-digital": "Digital", + "clock-style-label": "Clock style", + "clock-style-minimal": "Minimal", + "clock-width-description": "Width of the clock widget in pixels.", + "cpu-intensive-note": "Widgets marked with ! use more CPU and should be enabled mindfully.", + "edit-mode-button-label": "Enter edit mode", + "edit-mode-controls-explanation": "Left-click & drag: Move or resize the widget.\nRight-click: Open the context menu options.", + "edit-mode-description": "Enable edit mode to move and reposition desktop widgets. When enabled, widgets show a drag outline and can be repositioned.", + "edit-mode-exit-button": "Exit edit mode", + "edit-mode-grid-snap-label": "Grid snap", + "edit-mode-grid-snap-scale-label": "Grid snap scale", + "edit-mode-label": "Edit mode", + "enabled-description": "Enable or disable desktop widgets entirely.", + "enabled-label": "Enable desktop widgets", + "general-desc": "Configure widgets that appear on your desktop.", + "general-title": "Desktop widgets", + "media-player-enabled-description": "Show a media player widget on the desktop.", + "media-player-enabled-label": "Enable media player widget", + "media-player-rounded-corners-description": "Enable rounded corners on the widget edges.", + "media-player-show-album-art-description": "Show the album artwork and track information (title and artist).", + "media-player-show-album-art-label": "Show album art & title", + "media-player-show-background-description": "Show the background container.", + "media-player-show-buttons-description": "Show media control buttons (play/pause, previous, next).", + "media-player-show-buttons-label": "Show buttons", + "media-player-show-visualizer-description": "Show the audio visualizer overlay.", + "media-player-visualizer-type-description": "Choose a visualization type.", + "overview-enabled-description": "Show desktop widgets while the compositor overview is open.", + "overview-enabled-label": "Show in overview", + "system-stat-layout-bottom": "Bottom", + "system-stat-layout-description": "Choose how the legend is displayed relative to the graph.", + "system-stat-layout-label": "Layout", + "system-stat-layout-side": "Side", + "system-stat-rounded-corners-description": "Use rounded corners for the widget background.", + "system-stat-rounded-corners-label": "Rounded corners", + "system-stat-show-background-description": "Show the background container for the system stat widget.", + "system-stat-show-background-label": "Show background", + "system-stat-stat-type-description": "Choose which system statistic to display.", + "system-stat-stat-type-label": "Statistic Type", + "title": "Desktop Widgets", + "weather-enabled-description": "Show a weather widget on the desktop.", + "weather-enabled-label": "Enable weather widget", + "weather-show-background-description": "Show the background container for the weather widget." + }, + "display": { + "monitors-backlight-device-auto-option": "Default", + "monitors-backlight-device-description": "Select a backlight device for this output.", + "monitors-backlight-device-label": "Backlight device", + "monitors-brightness-step-description": "Adjust the step size for brightness changes (scroll wheel and keyboard shortcuts).", + "monitors-brightness-step-label": "Brightness step size", + "monitors-brightness-unavailable-ddc-disabled": "Brightness control unavailable. Enable \"External brightness support\" to control this display's brightness.", + "monitors-brightness-unavailable-generic": "Brightness control is not available for this display.", + "monitors-desc": "Adjust settings brightness for each display.", + "monitors-enforce-minimum-description": "Solves the problem of backlight completely turning off on some displays at 0% brightness.", + "monitors-enforce-minimum-label": "Enforce minimum brightness (1%)", + "monitors-external-brightness-description": "Enable DDCUtil support for controlling brightness on external displays via DDC/CI protocol.", + "monitors-external-brightness-label": "External brightness support", + "monitors-global-brightness-description": "Adjust brightness for all monitors at once.", + "monitors-global-brightness-label": "All monitors", + "monitors-title": "Per-monitor settings", + "night-light-auto-schedule-description": "Based on the sunset and sunrise time in {location} โ€” recommended.", + "night-light-auto-schedule-label": "Automatic scheduling", + "night-light-desc": "Reduce blue light emission to help you sleep better and reduce eye strain.", + "night-light-enable-description": "Apply a warm colour filter to reduce blue light emission.", + "night-light-enable-label": "Enable Night Light", + "night-light-force-activation-description": "Ignores the schedule and applies the night filter immediately.", + "night-light-force-activation-label": "Force activation", + "night-light-manual-schedule-description": "Set custom times for sunrise and sunset.", + "night-light-manual-schedule-label": "Manual scheduling", + "night-light-manual-schedule-select-start": "Select start time", + "night-light-manual-schedule-select-stop": "Select stop time", + "night-light-manual-schedule-sunrise": "Sunrise time", + "night-light-manual-schedule-sunset": "Sunset time", + "night-light-temperature-day": "Day", + "night-light-temperature-day-description": "Controls the temperature during daytime.", + "night-light-temperature-description": "Set the colour warmth for nighttime and daytime.", + "night-light-temperature-label": "Colour temperature", + "night-light-temperature-night": "Night", + "night-light-temperature-night-description": "Controls the temperature during nighttime.", + "title": "Display" + }, + "dock": { + "appearance-background-opacity-description": "Adjust the dock's background opacity.", + "appearance-border-radius-description": "Adjust the dock's border radius.", + "appearance-border-radius-label": "Border radius", + "appearance-colorize-icons-description": "Apply theme colors to dock app icons (non-focused apps only).", + "appearance-colorize-icons-label": "Colourise icons", + "appearance-dead-opacity-description": "Adjust the opacity of app icons that are not running.", + "appearance-dead-opacity-label": "Dead opacity", + "appearance-desc": "Customise the dock's behaviour and appearance.", + "appearance-display-auto-hide": "Auto hide", + "appearance-display-description": "Choose how the dock behaves.", + "appearance-display-exclusive": "Exclusive", + "appearance-dock-indicator-description": "Show a small indicator when the dock is hidden.", + "appearance-dock-indicator-label": "Dock indicator", + "appearance-floating-distance-description": "Set the distance between the dock and the edge of the screen.", + "appearance-floating-distance-label": "Dock floating distance", + "appearance-group-apps-description": "Group multiple windows from the same app into one dock entry.", + "appearance-group-apps-label": "Group same apps", + "appearance-group-click-action-cycle": "Cycle windows", + "appearance-group-click-action-description": "Choose what left-click does for grouped apps.", + "appearance-group-click-action-label": "Grouped app click action", + "appearance-group-click-action-list": "Open window list", + "appearance-group-context-menu-mode-description": "Choose how the context menu behaves for grouped apps.", + "appearance-group-context-menu-mode-extended": "Extended", + "appearance-group-context-menu-mode-label": "Grouped app menu mode", + "appearance-group-context-menu-mode-list": "Window list", + "appearance-group-indicator-style-description": "Choose how grouped running indicators display focused window state.", + "appearance-group-indicator-style-dots": "Dots", + "appearance-group-indicator-style-label": "Grouped indicator style", + "appearance-group-indicator-style-number": "Number", + "appearance-hide-show-speed-description": "Adjust the speed of the dock hide/show animation.", + "appearance-hide-show-speed-label": "Hide/show speed", + "appearance-icon-size-description": "Adjust the overall size of the dock.", + "appearance-icon-size-label": "Dock size", + "appearance-inactive-indicators-description": "Display indicator pills for all apps, not just the currently active one.", + "appearance-inactive-indicators-label": "Running indicators", + "appearance-indicator-color-description": "Choose the colour of the hidden dock indicator.", + "appearance-indicator-color-label": "Indicator colour", + "appearance-indicator-opacity-description": "Adjust the opacity of the hidden dock indicator.", + "appearance-indicator-opacity-label": "Indicator opacity", + "appearance-indicator-thickness-description": "Toggle a thicker hidden dock indicator (6px instead of 3px).", + "appearance-indicator-thickness-label": "Thicker indicator", + "appearance-launcher-icon-description": "Choose the icon used for the dock launcher.", + "appearance-launcher-icon-label": "Launcher icon", + "appearance-launcher-position-description": "Choose where the launcher icon appears in the dock.", + "appearance-launcher-position-end": "End", + "appearance-launcher-position-label": "Launcher position", + "appearance-launcher-position-start": "Start", + "appearance-launcher-use-distro-logo-description": "Use your distribution logo as the dock launcher icon.", + "appearance-launcher-use-distro-logo-label": "Use distro logo instead of icon", + "appearance-pinned-static-description": "Always push pinned app icons to the left in static order.", + "appearance-pinned-static-label": "Static pinned apps", + "appearance-position-description": "Choose where the dock appears on screen.", + "appearance-position-label": "Position", + "appearance-show-launcher-icon-description": "Show the application launcher icon in the dock.", + "appearance-show-launcher-icon-label": "Show app launcher", + "appearance-sit-on-frame-description": "Align the dock inside the frame border instead of sitting on top.", + "appearance-sit-on-frame-label": "Dock sits on frame", + "appearance-type-attached": "Attached", + "appearance-type-description": "Choose between a floating pill or a static bar attached to the edge.", + "appearance-type-floating": "Floating", + "appearance-type-label": "Dock style", + "enabled-description": "Show or hide the dock entirely.", + "enabled-label": "Enable dock", + "monitors-desc": "Show dock on specific monitors. Defaults to all if none are chosen.", + "monitors-only-same-monitor-description": "Show only apps from the monitor where the dock is located.", + "monitors-only-same-monitor-label": "Only apps from same monitor", + "monitors-title": "Monitor display", + "title": "Dock" + }, + "general": { + "copy-settings": "Copy settings", + "fonts-default-description": "Main font used throughout the interface.", + "fonts-default-label": "Default font", + "fonts-default-placeholder": "Select default font...", + "fonts-default-scale-description": "Increase or decrease the size of the standard text.", + "fonts-default-scale-label": "Default font size", + "fonts-default-search-placeholder": "Search font...", + "fonts-desc": "Choose the fonts used throughout the interface.", + "fonts-monospace-description": "Monospaced font used for numbers and stats display.", + "fonts-monospace-label": "Monospaced font", + "fonts-monospace-placeholder": "Select monospace font...", + "fonts-monospace-scale-description": "Increase or decrease the size of the monospaced text.", + "fonts-monospace-scale-label": "Monospaced font size", + "fonts-monospace-search-placeholder": "Search monospace font...", + "fonts-title": "Fonts", + "keybinds-conflict-description": "The key combination is already assigned to {action}.", + "keybinds-conflict-title": "Keybind conflict", + "keybinds-description": "Configure global navigation keys for panels and launcher.", + "keybinds-down": "Move down", + "keybinds-enter": "Confirm / Action", + "keybinds-escape": "Close / Back", + "keybinds-left": "Move left", + "keybinds-modifier-description": "Shortcuts must include a modifier key (Ctrl or Alt).", + "keybinds-modifier-title": "Modifier required", + "keybinds-remove": "Remove / Delete", + "keybinds-right": "Move right", + "keybinds-title": "Navigation keybinds", + "keybinds-up": "Move up", + "language-desc": "Choose your preferred language for the application.", + "language-select-auto-detect": "Automatic", + "language-select-description": "Select the language used in the application's interface.", + "language-select-label": "Application language", + "launch-setup-wizard": "Setup wizard", + "profile-desc": "Edit your user details and avatar.", + "profile-picture-description": "Your profile picture that appears throughout the interface.", + "profile-picture-label": "Profile picture", + "profile-select-avatar": "Select avatar image", + "profile-title": "Profile", + "profile-tooltip": "Profile picture", + "reverse-scrolling-description": "Reverse the interpreted scroll direction", + "reverse-scrolling-label": "Reverse scrolling", + "screen-corners-desc": "Customise screen corner rounding and visual effects.", + "screen-corners-radius-description": "Adjust the rounded corners of the screen.", + "screen-corners-radius-label": "Screen corners radius", + "screen-corners-radius-reset": "Reset screen corners radius", + "screen-corners-show-corners-description": "Display rounded corners on the edge of the screen.", + "screen-corners-show-corners-label": "Show screen corners", + "screen-corners-solid-black-description": "Use solid black instead of the bar background colour.", + "screen-corners-solid-black-label": "Solid black corners", + "screen-corners-title": "Screen corners", + "settings-copied": "Settings copied to clipboard", + "smooth-scrolling-description": "Animate list scrolling for a smoother wheel experience.", + "smooth-scrolling-label": "Smooth scrolling", + "tab-basics": "Basics", + "tab-keybinds": "Keybinds" + }, + "hooks": { + "color-generation-description": "Command to be executed when colour generation completes (e.g. after wallpaper-based theming).", + "color-generation-label": "Colors generated", + "color-generation-placeholder": "e.g. notify-send \"Colors\" \"Generated\"", + "info-command-info-description": "โ€ข Commands are executed via shell (sh -lc)
โ€ข Commands run in background (detached)
โ€ข Test buttons execute with current values", + "info-parameters-description": "โ€ข Wallpaper hook: $1 = wallpaper path, $2 = screen name, $3 = theme (dark/light)
โ€ข Colour generation hook: $1 = theme (dark/light)
โ€ข Theme toggle hook: $1 = true/false (Dark Mode state)
โ€ข Screen lock/unlock hooks: $1 = lock/unlock (screen lock state)
โ€ข Performance mode hooks: No parameters
โ€ข Session hook: $1 = action (shutdown/reboot)", + "info-parameters-label": "Available parameters", + "noctalia-started-description": "Command to execute when Noctalia has finished loading.", + "noctalia-started-label": "Noctalia started", + "noctalia-started-placeholder": "e.g. notify-send 'Noctalia Loaded'", + "performance-mode-disabled-description": "Command to be executed when Noctalia performance mode is disabled.", + "performance-mode-disabled-label": "Performance mode disabled", + "performance-mode-disabled-placeholder": "e.g. notify-send \"Performance\" \"Mode disabled\"", + "performance-mode-enabled-description": "Command to be executed when Noctalia performance mode is enabled.", + "performance-mode-enabled-label": "Performance mode enabled", + "performance-mode-enabled-placeholder": "e.g., notify-send \"Performance\" \"Mode enabled\"", + "screen-lock-description": "Command to be executed when the screen is locked.", + "screen-lock-label": "Screen locked", + "screen-lock-placeholder": "e.g. notify-send \"Screen\" \"Locked\"", + "screen-unlock-description": "Command to be executed when the lock screen is unlocked.", + "screen-unlock-label": "Screen unlocked", + "screen-unlock-placeholder": "e.g. notify-send \"Screen\" \"Unlocked\"", + "session-description": "Command to be executed before shutdown or reboot. Receives action type as $1 (shutdown/reboot).", + "session-label": "Session end", + "session-placeholder": "e.g. notify-send \"Session\" \"$1\"", + "system-hooks-desc": "Configure commands to be executed when system events occur.", + "system-hooks-enable-description": "Enable or disable all hook commands.", + "system-hooks-enable-label": "Enable hooks", + "system-hooks-title": "System hooks", + "theme-changed-description": "Command to be executed when theme toggles between Dark and Light Mode.", + "theme-changed-label": "Theme changed", + "theme-changed-placeholder": "e.g. notify-send \"Theme\" \"Toggled\"", + "title": "Hooks", + "wallpaper-changed-description": "Command to be executed when wallpaper changes.", + "wallpaper-changed-label": "Wallpaper changed", + "wallpaper-changed-placeholder": "e.g. notify-send \"Wallpaper\" \"Changed\"" + }, + "idle": { + "custom-add": "Add command", + "custom-description": "Run a shell command after a period of inactivity.", + "custom-entry-command": "Command", + "custom-entry-delete": "Delete", + "custom-entry-edit": "Edit Custom Command", + "custom-entry-name": "Name", + "custom-entry-name-placeholder": "e.g. Turn off lights", + "custom-entry-new": "New Custom Command", + "custom-entry-timeout": "Inactivity time", + "custom-entry-timeout-format": "{count} second", + "custom-entry-timeout-format-plural": "{count} seconds", + "custom-entry-unnamed": "Unnamed command", + "custom-label": "Custom idle commands", + "enable-description": "Automatically turn off the screen, lock, or suspend after a period of inactivity.", + "enable-label": "Enable idle management", + "fade-duration-description": "Seconds for the fade-to-black animation before each action fires. Any mouse movement cancels the fade.", + "fade-duration-label": "Fade duration", + "lock-description": "Seconds of inactivity before the lock screen activates.", + "lock-label": "Lock screen", + "resume-command-label": "Resume command", + "screen-off-description": "Seconds of inactivity before monitors are turned off.", + "screen-off-label": "Turn off screen", + "status-description": "Idle time as reported by the compositor.", + "status-label": "Idle time", + "suspend-description": "Seconds of inactivity before the system suspends.", + "tab-behavior": "Behaviour", + "tab-custom": "Custom", + "timeouts-description": "Set to 0 to disable a stage. Timeouts are paused while Keep Awake is active.", + "timeouts-label": "Timeouts", + "title": "Idle", + "unavailable": "Native idle monitoring is not available on this compositor." + }, + "indicator": { + "default-value": "Default: {value}", + "system-default": "System Default" + }, + "launcher": { + "clipboard-desc": "Access and manage your clipboard history from the launcher.", + "execute-desc": "Configure how applications are launched.", + "execute-title": "Execution", + "settings-annotation-tool-description": "Command to run when clicking the annotate button in clipboard history. The image will be piped to this command.", + "settings-annotation-tool-label": "Annotation tool", + "settings-annotation-tool-placeholder": "e.g. 'gradia', 'satty -f -'", + "settings-auto-paste-description": "Automatically paste the selected clipboard item. Requires wtype.", + "settings-auto-paste-label": "Auto paste", + "settings-clip-chips-description": "Show a tab bar to filter clipboard history by type (Images, Links, Files, Code, etc).", + "settings-clip-chips-label": "Enable Category Chips", + "settings-clip-preview-description": "Show a preview of the clipboard content when using the >clip command.", + "settings-clip-preview-label": "Enable clip preview", + "settings-clip-smart-icons-description": "Show specific icons for links, files, colors, and code instead of a generic clipboard icon.", + "settings-clip-smart-icons-label": "Enable Smart Icons", + "settings-clip-wrap-text-description": "Wrap text in the clipboard list instead of truncating it.", + "settings-clip-wrap-text-label": "Wrap clipboard text", + "settings-clipboard-history-description": "Access previously copied items from the launcher.", + "settings-clipboard-history-label": "Enable clipboard history", + "settings-clipboard-watch-image-description": "Full command string passed to wl-paste for image changes. (requires restart)", + "settings-clipboard-watch-image-label": "Image watch command", + "settings-clipboard-watch-text-description": "Full command string passed to wl-paste for text changes. (requires restart)", + "settings-clipboard-watch-text-label": "Text watch command", + "settings-custom-launch-prefix-description": "Prefix commands with a custom launcher (e.g. 'runapp' for systemd integration).", + "settings-custom-launch-prefix-enabled-description": "Use a custom prefix for launching applications instead of the default method.", + "settings-custom-launch-prefix-enabled-label": "Enable custom launch prefix", + "settings-custom-launch-prefix-label": "Custom launch prefix", + "settings-density-description": "Adjust the size of application icons and the density of the launcher.", + "settings-density-label": "Density", + "settings-desc": "Customise the launcher's behaviour and appearance.", + "settings-enable-session-search-description": "Show session actions (lock, shutdown, reboot, etc.) when searching in the launcher.", + "settings-enable-session-search-label": "Enable session search", + "settings-enable-settings-search-description": "Show settings results when searching in the launcher.", + "settings-enable-settings-search-label": "Enable settings search", + "settings-enable-windows-search-description": "Search and focus active windows.", + "settings-enable-windows-search-label": "Enable windows search", + "settings-grid-view-description": "Display items in a grid layout instead of a list.", + "settings-icon-mode-description": "Use native system icons instead of Tabler icons.", + "settings-icon-mode-label": "Use native icons", + "settings-ignore-mouse-input-description": "Disable mouse interaction and scrollwheel in the launcher.", + "settings-ignore-mouse-input-label": "Ignore mouse input", + "settings-overlay-layer-description": "Display the launcher on the overlay layer, above fullscreen windows. When enabled, the launcher will not attach to the bar.", + "settings-overlay-layer-label": "Show above fullscreen", + "settings-position-description": "Choose where the launcher panel appears.", + "settings-show-categories-description": "Show category tabs for filtering applications.", + "settings-show-categories-label": "Show categories", + "settings-show-icon-background-description": "Show a rounded rectangle background behind icons.", + "settings-show-icon-background-label": "Show icon background", + "settings-sort-by-usage-description": "When enabled, frequently launched apps appear first in the list.", + "settings-sort-by-usage-label": "Sort by most used", + "settings-terminal-command-description": "Command to launch a terminal. E.g. 'kitty -e' or 'gnome-terminal --'.", + "settings-terminal-command-label": "Terminal command", + "settings-use-app2unit-description": "Uses an alternative launch method to better manage app processes and prevent issues.", + "settings-use-app2unit-label": "Use App2Unit to launch applications", + "settings-view-mode-description": "Choose the layout for the launcher entries.", + "settings-view-mode-label": "View mode", + "title": "Launcher" + }, + "location": { + "auto-locate-description": "Automatically detect your location periodically using your IP address.", + "auto-locate-label": "Auto-Locate", + "calendar-cards-desc": "Organize and enable/disable cards in the calendar panel.", + "calendar-cards-title": "Calendar cards", + "calendar-header-label": "Calendar Header", + "calendar-month-label": "Calendar Month", + "date-time-12hour-format-description": "Display time in 12-hour format on the lock screen and calendar. The bar clock has its own settings.", + "date-time-12hour-format-label": "Use 12-hour time format", + "date-time-desc": "Customise how date and time appear.", + "date-time-first-day-of-week-automatic": "Automatic (use system locale)", + "date-time-first-day-of-week-description": "Choose which day starts the week in the calendar.", + "date-time-first-day-of-week-label": "First day of week", + "date-time-show-events-description": "Display events in the calendar panel.", + "date-time-show-events-label": "Show calendar events", + "date-time-title": "Date & time", + "date-time-use-analog-description": "Show an analog style clock on the calendar window.", + "date-time-use-analog-label": "Use analog style clock", + "date-time-week-numbers-description": "Display the week of the year (e.g. week 38) in the calendar.", + "date-time-week-numbers-label": "Show week numbers", + "geolocate-now-button": "Locate now", + "location-desc": "Get accurate weather and Night Light scheduling by setting your location.", + "location-search-description": "e.g. Toronto, ON", + "location-search-label": "Search for a location", + "location-search-placeholder": "Enter the location name", + "location-title": "Your location", + "weather-desc": "Choose your preferred temperature unit.", + "weather-enabled-description": "Show weather information throughout the interface and fetch weather data. When disabled, all weather elements will be hidden and no network requests will be made.", + "weather-enabled-label": "Enable weather", + "weather-fahrenheit-description": "Display temperature in Fahrenheit instead of Celsius.", + "weather-fahrenheit-label": "Display temperature in Fahrenheit (ยฐF)", + "weather-hide-city-description": "Hide the city name from weather displays throughout the interface.", + "weather-hide-city-label": "Hide city name", + "weather-hide-timezone-description": "Hide the timezone abbreviation from weather displays throughout the interface.", + "weather-hide-timezone-label": "Hide timezone", + "weather-show-effects-description": "Show additional visual effects (like rain, snow, or lightning) on the weather card.", + "weather-show-effects-label": "Display weather effects", + "weather-show-in-calendar-description": "Show the daily weather forecast directly in your calendar view.", + "weather-show-in-calendar-label": "Display weather in calendar", + "weather-talia-mascot-always-description": "Always use Talia emojis instead of tabler icons.", + "weather-talia-mascot-always-label": "Always show Talia weather emojis" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "When fprintd (fingerprint authentication) is active, this option lets you still login using your password instead of a fingerprint", + "allow-password-with-fprintd-label": "Allow password login with fprintd", + "auto-start-auth-description": "Automatically starts fingerprint authentication without requiring a key press or button click.", + "auto-start-auth-label": "Auto-start authentication", + "clock-format-description": "Customise the clock format using date/time syntax tokens.", + "clock-format-label": "Clock format", + "clock-style-analog": "Analog", + "clock-style-custom": "Custom", + "clock-style-description": "Choose the visual style of the clock on the lock screen.", + "clock-style-digital": "Digital", + "clock-style-label": "Clock style", + "compact-lockscreen-description": "Show only the login input and system controls, hiding weather and media widgets.", + "compact-lockscreen-label": "Compact lock screen", + "enable-lockscreen-media-controls-description": "Show interactive media playback controls on the lock screen.", + "enable-lockscreen-media-controls-label": "Lock screen media controls", + "lock-on-suspend-description": "Automatically lock the screen when suspending the system.", + "lock-on-suspend-label": "Lock on suspend", + "lock-screen-animations-description": "Enable or disable lockscreen animations.", + "lock-screen-animations-label": "Lockscreen animations", + "lock-screen-blur-strength-description": "Applies a blur effect to the lock screen wallpaper.", + "lock-screen-blur-strength-label": "Lock screen blur strength", + "lock-screen-tint-strength-description": "Applies a tint overlay to the lock screen wallpaper.", + "lock-screen-tint-strength-label": "Lock screen tint strength", + "monitors-desc": "Show lock screen on specific monitors. Defaults to all if none are chosen.", + "password-chars-description": "Cute icons used to hide your password.", + "password-chars-label": "Random password icons", + "show-hibernate-description": "Show the option 'hibernate' in the power controls.", + "show-hibernate-label": "Show hibernate", + "show-session-buttons-description": "Allow access to power settings from the lock screen.", + "show-session-buttons-label": "Power controls", + "title": "Lock Screen" + }, + "network": { + "bluetooth-description": "Activate Bluetooth management.", + "bluetooth-rssi-polling-description": "Periodically sample RSSI for connected devices via bluetoothctl. May not be available for all devices; uses minimal resources when enabled.", + "bluetooth-rssi-polling-label": "Bluetooth signal polling", + "desc": "Manage Wi-Fi and Bluetooth connections.", + "wifi-description": "Manage wireless networks (requires NetworkManager)." + }, + "notifications": { + "duration-critical-urgency-description": "How long critical priority notifications stay visible.", + "duration-critical-urgency-label": "Critical urgency", + "duration-desc": "Configure how long notifications stay visible based on their urgency level.", + "duration-low-urgency-description": "How long low priority notifications stay visible.", + "duration-low-urgency-label": "Low urgency", + "duration-normal-urgency-description": "How long normal priority notifications stay visible.", + "duration-normal-urgency-label": "Normal urgency", + "duration-respect-expire-description": "Use the expire timeout set in the notification.", + "duration-respect-expire-label": "Respect expire timeout", + "duration-title": "Notification duration", + "history-clear-dismiss-description": "Clear notification from history when dismissed.", + "history-clear-dismiss-label": "Clear on dismissed", + "history-critical-urgency-description": "Save critical priority notifications to history.", + "history-critical-urgency-label": "Save critical urgency to history", + "history-desc": "Control which notifications are saved to history based on their urgency level.", + "history-low-urgency-description": "Save low priority notifications to history.", + "history-low-urgency-label": "Save low urgency to history", + "history-normal-urgency-description": "Save normal priority notifications to history.", + "history-normal-urgency-label": "Save normal urgency to history", + "monitors-desc": "Show notification on specific monitors. Defaults to all if none are chosen.", + "rules-action-block": "Block", + "rules-action-block-desc": "Skips completely.", + "rules-action-hide": "Hide", + "rules-action-hide-desc": "No popup, no sound, adds to history.", + "rules-action-label": "Action", + "rules-action-mute": "Mute", + "rules-action-mute-desc": "No sound, still shows popup and in history.", + "rules-add": "Add rule", + "rules-delete": "Delete rule", + "rules-description": "Match app name or content. Rules are checked in order, and the first match is applied.", + "rules-edit": "Edit rule", + "rules-label": "Filter rules", + "rules-pattern-label": "Pattern", + "rules-pattern-placeholder": "firefox, discord, or /regex/", + "rules-tab": "Rules", + "settings-always-on-top-description": "Display notifications above fullscreen windows and other layers.", + "settings-background-opacity-description": "Adjust the opacity of notification backgrounds.", + "settings-density-description": "Choose the notification card density.", + "settings-density-label": "Density", + "settings-desc": "Configure notifications appearance and behaviour.", + "settings-do-not-disturb-description": "Disable all notification popups when enabled.", + "settings-enabled-description": "Enable or disable the notification daemon, requires a restart of Noctalia shell.", + "settings-enabled-label": "Enable notifications", + "settings-location-description": "Where notifications appear on screen.", + "settings-markdown-description": "Render notification content using Markdown formatting.", + "settings-markdown-label": "Enable Markdown", + "sounds-desc": "Configure notification sound effects and volume.", + "sounds-enabled-description": "Enable sound effects for incoming notifications.", + "sounds-enabled-label": "Enable notification sounds", + "sounds-excluded-apps-description": "Skip playing the configured notification sound for specific applications that have their own built-in sounds.", + "sounds-excluded-apps-label": "Excluded applications", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "Path to the sound file played for critical priority notifications.", + "sounds-files-critical-label": "Critical urgency sound", + "sounds-files-critical-select-title": "Select critical urgency sound file", + "sounds-files-desc": "Configure custom sound files for different notification urgency levels.", + "sounds-files-low-description": "Path to the sound file played for low priority notifications.", + "sounds-files-low-label": "Low urgency sound", + "sounds-files-low-select-title": "Select low urgency sound file", + "sounds-files-normal-description": "Path to the sound file played for normal priority notifications.", + "sounds-files-normal-label": "Normal urgency sound", + "sounds-files-normal-select-title": "Select normal urgency sound file", + "sounds-files-placeholder": "Enter path to sound file", + "sounds-files-select-file": "Select sound file", + "sounds-files-title": "Sound files", + "sounds-files-unified-description": "Path to the sound file played for notifications.", + "sounds-files-unified-label": "Notification sound", + "sounds-files-unified-select-title": "Select notification sound file", + "sounds-separate-description": "Use different sound files for low, normal, and critical priority notifications.", + "sounds-separate-label": "Use different sounds per priority", + "sounds-title": "Sound settings", + "sounds-unavailable-description": "Install Qt6 Multimedia to enable notification sounds.", + "sounds-unavailable-label": "Notification sounds unavailable", + "sounds-volume-description": "Adjust the volume level for notification sounds.", + "sounds-volume-label": "Sound volume", + "toast-battery-description": "Show a warning when the battery level falls below this percentage.", + "toast-battery-label": "Battery warning", + "toast-desc": "Configure toast appearance and behaviour.", + "toast-keyboard-description": "Show a toast when the keyboard layout changes.", + "toast-keyboard-label": "Keyboard layout", + "toast-media-description": "Show a toast when media playback state changes.", + "toast-media-label": "Media" + }, + "osd": { + "always-on-top-description": "Display OSD above fullscreen windows and other layers.", + "always-on-top-label": "Always on top", + "background-opacity-description": "Controls the transparency of the OSD background.", + "background-opacity-label": "Background opacity", + "description": "Configure on-screen indicators such as volume and brightness overlays.", + "duration-auto-hide-description": "Adjust the time before OSD disappears.", + "duration-auto-hide-label": "Auto-hide after", + "duration-desc": "How long OSD remains visible before hiding automatically.", + "duration-title": "Auto-hide timeout", + "enabled-description": "Show volume and brightness changes in real-time.", + "enabled-label": "Enable on-screen display", + "events-desc": "Select which events trigger the on-screen display.", + "general-desc": "Configure visibility and behaviour of OSD.", + "location-description": "Where on-screen displays appear.", + "monitors-desc": "Show OSD on specific monitors. Defaults to all if none are chosen.", + "title": "On-Screen Display", + "types-brightness-description": "Show OSD when screen brightness changes.", + "types-brightness-label": "Brightness", + "types-custom-text-description": "Show OSD for custom text messages from IPC.", + "types-custom-text-label": "Custom Text", + "types-desc": "Select the events that trigger the OSD.", + "types-input-volume-description": "Show OSD when microphone volume changes.", + "types-input-volume-label": "Input volume", + "types-lockkey-description": "Show OSD when Caps Lock, Num Lock, or Scroll Lock are toggled.", + "types-lockkey-label": "Lock keys", + "types-media-description": "Show OSD when media playback state changes (play, pause, skip).", + "types-media-label": "Media playback", + "types-title": "OSD trigger events", + "types-volume-description": "Show OSD when audio output volume changes.", + "types-volume-label": "Output volume" + }, + "plugins": { + "auto-update": "Auto-update plugins", + "auto-update-description": "Automatically update all plugins when the shell starts.", + "available-description": "Browse and install plugins from configured sources.", + "available-label": "Available plugins", + "available-no-plugins-description": "Check your plugin sources or refresh the list.", + "available-no-plugins-label": "No plugins available", + "check-for-updates": "Check for updates", + "checking-for-updates": "Checking for updates...", + "collision-already-installed": "This plugin is already installed", + "collision-custom-version-exists": "A custom version from \"{source}\" is already installed", + "collision-official-version-exists": "The official version of this plugin is already installed", + "development-disable": "Disable development mode for this plugin", + "development-enable": "Enable development mode for this plugin", + "filter-downloaded": "Downloaded", + "filter-not-downloaded": "Not Downloaded", + "filter-tags-description": "Filter plugins by category or download status.", + "filter-tags-label": "Tags", + "hot-reload-description": "Automatically reload plugins when their files change. Useful for plugin development.", + "hot-reload-label": "Hot reload (dev mode)", + "hot-reloaded": "Reloaded plugin: {name}", + "install-error": "Failed to install: {error}", + "install-incompatible": "{plugin} requires Noctalia v{version} or higher", + "install-success": "Successfully installed {plugin}", + "installed-description": "Manage and configure all locally installed plugins.", + "installed-label": "Installed plugins", + "installed-no-plugins-description": "Install plugins from the \"Available\" section.", + "installed-no-plugins-label": "No plugins installed", + "installing": "Installing {plugin}...", + "notify-updates": "Plugin update notifications", + "notify-updates-description": "Show a notification popup when new plugin updates are available.", + "open-plugin-page": "Open plugin page", + "open-plugins-tab": "Open plugins settings", + "plugin-settings-title": "{plugin} Settings", + "refresh-refreshing": "Refreshing plugins list...", + "refresh-tooltip": "Refresh available plugins", + "settings-error-not-loaded": "Plugin not loaded", + "settings-saved": "Plugin settings saved", + "settings-tooltip": "Plugin settings", + "source-custom": "Custom source", + "sources-add-custom": "Add custom repository", + "sources-add-dialog-description": "Add a GitHub repository as a plugin source.", + "sources-add-dialog-error": "Failed to add plugin source", + "sources-add-dialog-name": "Repository name", + "sources-add-dialog-name-placeholder": "My Custom Plugins", + "sources-add-dialog-success": "Plugin source added successfully", + "sources-add-dialog-title": "Add plugin source", + "sources-add-dialog-url": "Repository URL", + "sources-description": "Manage plugin repositories.", + "sources-placeholder": "My cool repository", + "sources-remove-tooltip": "Remove plugin source", + "title": "Plugins", + "translations-reloaded": "Reloaded translations: {name}", + "uninstall-dialog-description": "Are you sure you want to uninstall {plugin}? This will remove all plugin data.", + "uninstall-dialog-title": "Uninstall plugin", + "uninstall-error": "Failed to uninstall: {error}", + "uninstall-success": "Successfully uninstalled {plugin}", + "uninstalling": "Uninstalling {plugin}...", + "update-all": "Update All ({count})", + "update-all-success": "All plugins updated successfully", + "update-available": "New plugin update available", + "update-available-plural": "Plugin updates available ({count})", + "update-error": "Failed to update plugin: {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (requires Noctalia v{required})", + "update-success": "Updated {plugin} to v{version}", + "update-version": "v{current} โ†’ v{new}", + "updating": "Updating..." + }, + "region": { + "title": "Region" + }, + "session-menu": { + "countdown-duration-description": "Set how long the countdown timer lasts before executing power actions.", + "countdown-duration-label": "Countdown duration", + "enable-countdown-description": "Show a countdown timer before executing power actions.", + "enable-countdown-label": "Enable countdown timer", + "entries-desc": "Customise which power actions appear in the session menu and in what order.", + "entries-title": "Power actions", + "entry-settings-command-description": "Custom command to execute for this action. Leave empty to use the default system command.", + "entry-settings-command-placeholder": "e.g. systemctl poweroff", + "entry-settings-default-command-lock": "Internal lock screen (no command)", + "entry-settings-default-command-logout": "Internal logout (no command)", + "entry-settings-default-info-description": "If no custom command is specified, the default system command will be used.", + "entry-settings-default-info-label": "Default command", + "entry-settings-keybind-description": "Assign a key combination to trigger this action when the session menu is open.", + "entry-settings-keybind-placeholder": "Press Record to assign a key...", + "entry-settings-keybind-recording": "Recording... Press any key combination", + "entry-settings-title": "Configure {entry}", + "entry-settings-tooltip": "Configure command", + "general-desc": "Configure the session menu panel behaviour and appearance.", + "large-buttons-layout-description": "Choose how session menu buttons are displayed.", + "large-buttons-layout-label": "Large buttons layout", + "large-buttons-style-description": "Display the session menu with large buttons in a grid layout.", + "large-buttons-style-label": "Large buttons style", + "position-description": "Choose where the session menu panel appears when opened.", + "show-header-description": "Display the title and close button at the top of the session menu.", + "show-header-label": "Show header", + "show-keybinds-description": "Display keybind hints on session options.", + "show-keybinds-label": "Show keybinds" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Keep desktop widgets visible while Noctalia performance mode is enabled.", + "noctalia-performance-disable-desktop-widgets-label": "Enable desktop widgets in performance mode", + "noctalia-performance-disable-wallpaper-description": "Keep desktop, overview, and lock screen wallpapers visible while Noctalia performance mode is enabled.", + "noctalia-performance-disable-wallpaper-label": "Enable wallpaper rendering in performance mode", + "title": "System" + }, + "system-monitor": { + "critical-color-label": "Critical colour", + "custom-highlight-colors-title-label": "Custom highlight colors", + "disk-available-label": "Disk available", + "disk-section-label": "Disk usage", + "enable-dgpu-monitoring-description": "Warning: This will wake up your discrete GPU (NVIDIA/AMD), which may significantly impact battery life on laptops with hybrid graphics.", + "enable-dgpu-monitoring-label": "Enable discrete GPU monitoring", + "external-monitor-description": "Enter the command or application path to launch when activating the external system monitor application.", + "external-monitor-label": "External system monitor command", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-centre || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "Configure the system monitor behaviour and appearance.", + "gpu-section-label": "GPU temperature", + "highlight-colors-section-label": "Highlight colors", + "polling-interval-label": "Polling interval", + "polling-section-description": "Configure how often each system metric is updated.", + "polling-section-label": "Polling intervals", + "threshold-critical": "Critical threshold", + "threshold-warning": "Warning threshold", + "thresholds-section-description": "Adjust warning/critical thresholds for each system metric.", + "use-custom-highlight-colors-description": "When disabled, theme default highlight colors are used.", + "use-custom-highlight-colors-label": "Use custom highlight colors", + "warning-color-label": "Warning colour" + }, + "user-interface": { + "allow-panels-without-bar-description": "When enabled, panels can open on any screen. When disabled, panels will only open on screens that have a bar, which can reduce memory usage.", + "allow-panels-without-bar-label": "Allow panels on screens without a bar", + "animation-disable-description": "Disable all animations for a faster, more responsive experience.", + "animation-disable-label": "Disable UI animations", + "animation-speed-description": "Adjust global animation speed.", + "animation-speed-label": "Animation speed", + "animation-speed-reset": "Reset animation speed", + "appearance-desc": "Customise visual elements like tooltips, borders, and shadows.", + "blur-behind-description": "Blurs the area behind bars and panels using the compositor blur protocol.", + "blur-behind-label": "Blur behind", + "box-border-description": "Display an outline around content areas.", + "box-border-label": "Container outline", + "box-border-radius-description": "Adjusts the corner roundness of major layout sections, such as sidebars, cards, and content panels.", + "box-border-radius-label": "Container radius", + "control-border-radius-description": "Controls the curvature of interactive elements, including buttons, toggles, and text fields.", + "control-border-radius-label": "Input radius", + "desc": "Customise the look, feel, and behaviour of the interface.", + "dim-desktop-description": "Dim the desktop when panels or menus are open.", + "dim-desktop-label": "Dim desktop", + "dimmer-opacity-description": "Set the opacity level for desktop dimming.", + "dimmer-opacity-label": "Dimmed desktop opacity", + "panel-background-opacity-description": "Set the background opacity for all panels (bar, launcher, settings, etc...).", + "panel-background-opacity-label": "Panel background opacity", + "panels-attached-to-bar-description": "Panels lock to the bar and screen edges, creating a seamless look with stylish inverted corners.", + "panels-attached-to-bar-label": "Snap panels to edges", + "panels-overlay-description": "Ensures panels and the bar remain visible, even over fullscreen applications.", + "panels-overlay-label": "Keep panels & bar on top", + "scaling-description": "Changes the size of the general user interface, excluding the bar.", + "scaling-label": "Interface scaling", + "scrollbar-always-visible-description": "Keep scrollbars visible whenever content is scrollable, instead of only showing them on hover.", + "scrollbar-always-visible-label": "Always show scrollbars", + "settings-panel-header": "Settings panel", + "settings-panel-mode-description": "Choose settings layout (may require reopening).", + "settings-panel-mode-label": "Settings panel mode", + "settings-panel-sidebar-card-style-description": "Wraps the settings sidebar in a filled background with rounded corners.", + "settings-panel-sidebar-card-style-label": "Sidebar card style", + "shadows-description": "Enables drop shadows under bars and panels.", + "shadows-direction-description": "Choose where the shadow is cast to.", + "shadows-direction-label": "Shadow direction", + "shadows-label": "Drop shadows", + "title": "User Interface", + "tooltips-description": "Enable or disable tooltips throughout the interface.", + "tooltips-label": "Show tooltips", + "translucent-widgets-description": "Make buttons, tabs, and other widgets inside panels semi-transparent.", + "translucent-widgets-label": "Translucent widgets" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "Alphabetical", + "automation-change-mode-description": "Choose how wallpapers are selected when changing automatically.", + "automation-change-mode-label": "Change mode", + "automation-custom-interval-description": "Enter time as HH:MM (e.g. 01:30).", + "automation-custom-interval-label": "Custom interval", + "automation-interval-description": "How often to change wallpapers automatically.", + "automation-interval-label": "Wallpaper interval", + "automation-random-wallpaper-description": "Schedule random wallpaper changes at regular intervals.", + "automation-scheduled-change-description": "Automatically change wallpapers at regular intervals.", + "automation-scheduled-change-label": "Scheduled change", + "look-feel-edge-smoothness-description": "Applies a soft, feathered effect to the edge of transitions.", + "look-feel-edge-smoothness-label": "Soften transition edge", + "look-feel-fill-color-description": "Choose a fill colour that may appear behind the wallpaper.", + "look-feel-fill-mode-description": "Select how the image should scale to match your monitor's resolution.", + "look-feel-fill-mode-label": "Fill mode", + "look-feel-skip-startup-transition-description": "Skip the wallpaper animation when the shell starts.", + "look-feel-skip-startup-transition-label": "Skip startup transition", + "look-feel-title": "Look & feel", + "look-feel-transition-duration-description": "Duration of transition animations in seconds.", + "look-feel-transition-duration-label": "Transition duration", + "look-feel-transition-type-description": "Animation type when switching between wallpapers.", + "look-feel-transition-type-label": "Transition type", + "settings-clear-cache-button": "Clear cache", + "settings-clear-cache-description": "Clear cached resized wallpapers to free disk space.", + "settings-clear-cache-label": "Wallpaper cache", + "settings-clear-cache-toast": "Wallpaper cache cleared", + "settings-desc": "Control how wallpapers are managed and displayed.", + "settings-enable-management-description": "Manage wallpapers with Noctalia. Uncheck if you prefer using another application.", + "settings-enable-management-label": "Enable wallpaper management", + "settings-enable-overview-description": "Applies a blurred and dimmed wallpaper to the overview screen.", + "settings-enable-overview-label": "Enable overview wallpaper", + "settings-folder-description": "Path to your main wallpaper folder.", + "settings-folder-label": "Wallpaper folder", + "settings-hide-wallpaper-filenames-tooltip-hide": "Hide filenames", + "settings-hide-wallpaper-filenames-tooltip-show": "Show filenames", + "settings-monitor-specific-description": "Set a different wallpaper folder for each monitor.", + "settings-monitor-specific-label": "Monitor-specific directories", + "settings-monitor-specific-tooltip": "Monitor wallpaper folder", + "settings-overview-blur-strength-description": "Applies the blur strength to the overview.", + "settings-overview-blur-strength-label": "Overview blur strength", + "settings-overview-tint-description": "Applies the tint strength to the overview.", + "settings-overview-tint-label": "Overview tint strength", + "settings-recursive-search-description": "Also search for wallpapers in subfolders of the wallpaper directory.", + "settings-recursive-search-label": "Search subfolders", + "settings-select-monitor-folder": "Select monitor wallpaper folder", + "settings-selector-description": "Choose your wallpaper.", + "settings-selector-position-description": "Choose where the wallpaper selector panel appears.", + "settings-show-hidden-files-tooltip-hide": "Hide hidden files", + "settings-show-hidden-files-tooltip-show": "Show hidden files", + "settings-title": "Wallpaper settings", + "settings-use-original-images-description": "Skip resizing wallpapers before display. Saves disk space and reduces CPU usage during wallpaper changes, but may use more memory for very large images.", + "settings-use-original-images-label": "Use original images", + "settings-view-mode-description": "Choose how wallpapers are displayed from your directory.", + "settings-view-mode-label": "Viewing mode", + "view-mode-browse": "Browse directories", + "view-mode-cycle-tooltip": "View mode: {mode} (click to change)", + "view-mode-recursive": "Flattened subdirectories", + "view-mode-single": "Root directory" + } + }, + "placeholders": { + "add-new-keybind": "Add a new keybind", + "command-example": "echo \"Hello World\"", + "enter-command": "Enter command to execute (app or custom script)", + "enter-ipc-identifier": "Enter unique identifier for IPC commands", + "enter-path": "Enter path...", + "enter-text-to-collapse": "e.g. 'nothing is playing'. Use /regex/ for patterns.", + "enter-tooltip": "Enter tooltip", + "enter-width-pixels": "Enter width in pixels", + "keybind-recording": "Recording keybind...", + "search": "Search...", + "search-icons": "e.g. noctalia, niri, battery, cloud", + "search-launcher": "Search entries... or use > for commands", + "search-wallhaven": "Search Wallhaven...", + "search-wallpapers": "Type to filter wallpapers..." + }, + "positions": { + "bottom": "Bottom", + "bottom-center": "Bottom centre", + "bottom-left": "Bottom left", + "bottom-right": "Bottom right", + "center": "Centre", + "center-left": "Left centre", + "center-right": "Right centre", + "close-to-bar": "Close to bar button", + "follow-bar": "Follow bar", + "left": "Left", + "right": "Right", + "top": "Top", + "top-center": "Top centre", + "top-left": "Top left", + "top-right": "Top right" + }, + "session-menu": { + "action-in-seconds": "{action} in {seconds} seconds...", + "cancel-timer": "Cancel timer", + "title": "Session Menu" + }, + "setup": { + "all-done": "All Done!", + "appearance": { + "subheader": "Choose Dark Mode and colour sources (wallpaper or predefined)." + }, + "customize": { + "header": "Customise your experience", + "subheader": "Adjust bar position, density, scaling and more." + }, + "skip-setup": "Skip Setup", + "telemetry-wizard-done": "Got it!", + "telemetry-wizard-note": "You're in control โ€” enable or disable this anytime in settings", + "telemetry-wizard-subtitle": "We've added anonymous analytics to help improve Noctalia", + "telemetry-wizard-title": "Privacy Update", + "wallpaper": { + "choose-dir": "Choose a directory containing your wallpaper images", + "dir-browse": "Browse for wallpaper folder", + "dir-description": "Choose the folder containing your wallpapers.", + "dir-label": "Wallpaper directory", + "dir-select-title": "Select wallpaper folder", + "header": "Choose your wallpaper", + "no-dir": "No wallpaper directory selected", + "no-valid": "No valid image files found in: {dir}", + "none-in-dir": "No wallpapers found in directory", + "preview-error": "Failed to load image", + "select-prompt": "Select a wallpaper below", + "subheader": "Set the mood with a beautiful background." + }, + "welcome": "Welcome", + "welcome-note": "Just a few basics to get you started โ€” full options are in the settings", + "welcome-subtitle": "Let's make your desktop uniquely yours", + "welcome-title": "Welcome to Noctalia!" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "No media player detected", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "Unknown app", + "uptime": "Uptime: {uptime}", + "user-requested": "User requested", + "welcome-back": "Welcome back,", + "widget-settings-title": "{widget} Settings" + }, + "system-monitor": { + "core-usage": "Core {id} usage", + "cpu-temp": "CPU temp", + "cpu-usage": "CPU usage", + "disk": "Disk", + "download-speed": "Download speed", + "gpu-temp": "GPU temp", + "load-average": "Load average", + "title": "System Monitor", + "upload-speed": "Upload speed" + }, + "toast": { + "airplane-mode": { + "description": "Disable all wireless communications.", + "title": "Aeroplane Mode" + }, + "battery": { + "critical": "Critical battery", + "critical-desc": "Battery is at {percent}% โ€” please connect the charger immediately", + "low": "Low battery", + "low-desc": "Battery is at {percent}% โ€” please connect the charger" + }, + "bluetooth": { + "address-copied": "Address copied to clipboard", + "auto-connect-disabled": "Auto-connect disabled", + "auto-connect-enabled": "Auto-connect enabled", + "auto-connecting": "Connecting to {count} device(s)...", + "confirm-code": "Confirm code {value} on the other device.", + "connect-failed": "Failed to connect to device", + "disconnect-failed": "Failed to disconnect from device", + "discoverable-change-failed": "Failed to change discoverable state", + "discoverable-disabled": "Discoverable disabled", + "discoverable-enabled": "Discoverable enabled", + "display-code": "Enter code {value} on the other device.", + "forget-failed": "Failed to forget device", + "pair-failed": "Failed to pair device", + "passkey-required": "Passkey required by the device.", + "pincode-required": "PIN code required by the device.", + "state-change-failed": "Failed to change Bluetooth state" + }, + "clipboard": { + "long-text": "Long text", + "unavailable": "Clipboard history unavailable", + "unavailable-desc": "The 'cliphist' application is not installed. Please install it to use clipboard history features" + }, + "custom-command-failed": { + "description": "Command failed: {command}\nExit code: {code}", + "title": "Custom command failed" + }, + "do-not-disturb": { + "disabled": "Do Not Disturb disabled", + "disabled-desc": "Showing all notifications", + "enabled": "Do Not Disturb enabled", + "enabled-desc": "You'll find these notifications in your history" + }, + "donation-opened": "Donation page opened in your browser", + "internet-limited": "Connected without internet", + "keyboard-layout": { + "changed": "Keyboard layout changed to {layout}", + "title": "Keyboard" + }, + "missing-control-center": { + "description": "The control centre widget has been removed from the bar. To access it from the bar again, you will need to re-add the widget. You can open it with right clicking on the bar too", + "label": "Last control centre widget removed" + }, + "night-light": { + "forced": "Forced activation", + "normal": "Normal mode", + "not-installed": "wlsunset not installed" + }, + "noctalia-performance": { + "disabled": "Performance mode disabled", + "enabled": "Performance mode enabled", + "label": "Noctalia Performance" + }, + "power-profile": { + "changed": "Power profile changed", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "Encountered an error while processing templates", + "title": "Templating processing failed" + }, + "vpn": { + "connected": "Connected to '{name}'", + "disconnected": "Disconnected from '{name}'" + }, + "wallpaper-colors": { + "disabled": "Wallpaper colors disabled", + "enabled": "Wallpaper colors enabled", + "label": "Wallpaper colors" + }, + "wifi": { + "connected": "Connected to '{ssid}'", + "connection-failed": "Connection failed", + "connection-timeout": "Connection timeout", + "disconnected": "Disconnected from '{ssid}'", + "incorrect-password": "Incorrect password", + "network-not-found": "Network not found" + } + }, + "tooltips": { + "add-widget": "Add widget", + "auto-connect": "Toggle auto-connection for this device", + "bluetooth-auto-connect-off": "Auto-connect is off", + "bluetooth-auto-connect-on": "Auto-connect is on", + "bluetooth-devices": "Bluetooth devices", + "brightness-at": "Brightness: {brightness}%", + "click-to-start-recording": "Screen recorder (start recording)", + "click-to-stop-recording": "Screen recorder (stop recording)", + "collapse": "Collapse sidebar", + "copy-address": "Copy address", + "delete-notification": "Delete notification", + "dismiss-notification": "Dismiss notification", + "do-not-disturb-enabled": "Do Not Disturb", + "expand": "Expand sidebar", + "forget-network": "Forget network", + "grid-view": "Grid view", + "hidden-files-hide": "Hidden files", + "hide-unnamed-devices": "Hide unnamed devices", + "home": "Home directory", + "input-muted": "Toggle input mute", + "keep-awake": "Keep Awake", + "keyboard-layout": "{layout} keyboard layout", + "list-view": "List view", + "manage-vpn": "VPN connections", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "Maximum widgets reached", + "microphone-volume-at": "Microphone volume: {volume}%", + "move-to-section": "Move to {section}", + "mute": "Mute", + "next-media": "Next track", + "next-month": "Next month", + "night-light-not-installed": "Night Light (not available)", + "noctalia-performance-enabled": "Noctalia performance mode", + "open-annotation-tool": "Open with annotation tool", + "open-control-center": "Control centre", + "open-notification-history-enable-dnd": "Notification history", + "open-settings": "Settings", + "open-tray-dropdown": "System tray", + "output-muted": "Toggle output mute", + "power-profile": "{profile} power profile", + "previous-media": "Previous track", + "previous-month": "Previous month", + "refresh-devices": "Refresh devices", + "refresh-wallhaven": "Refresh Wallhaven results", + "refresh-wallpaper-list": "Refresh wallpaper list", + "remove-widget": "Remove widget", + "search-close": "Close search", + "session-menu": "Session menu", + "show-all-devices": "Show all devices", + "switch-to-dark-mode": "Dark Mode", + "switch-to-light-mode": "Light Mode", + "unmute": "Unmute", + "up": "Parent directory", + "volume-at": "Output volume: {volume}%", + "wallpaper-selector": "Wallpaper selector" + }, + "wallpaper": { + "browse": { + "empty-directory": "This directory is empty.", + "go-root": "Go to wallpaper root", + "go-up": "Go to parent folder", + "go-up-hint": "Use the back button to navigate up." + }, + "configure-directory": "Configure your wallpaper directory with images.", + "fill-modes": { + "crop": "Crop (Fill)", + "fit": "Fit (Contain)", + "repeat": "Repeat (Tile)", + "stretch": "Stretch" + }, + "no-match": "No match found.", + "no-wallpaper": "No wallpaper found.", + "panel": { + "apikey-help": "An API key is required to access NSFW content.", + "apikey-label": "API Key", + "apikey-managed-by-env": "Managed via NOCTALIA_WALLHAVEN_API_KEY environment variable.", + "apikey-placeholder": "Enter your Wallhaven API Key", + "appearance-dark-tab": "Dark", + "appearance-light-tab": "Light", + "categories-anime": "Anime", + "categories-label": "Categories", + "categories-people": "People", + "color-extraction-disabled": "Use wallpaper colour extraction", + "color-extraction-enabled": "Use predefined colour schemes", + "header-devices-apply-all-tooltip": "Choose wallpaper per monitor", + "header-devices-per-monitor-tooltip": "Apply to all monitors", + "header-link-light-dark-tooltip": "Keep wallpaper when switching theme", + "header-separate-light-dark-tooltip": "Auto-switch wallpaper with theme", + "header-sun-linked-tooltip": "Separate light and dark wallpapers", + "header-sun-separate-tooltip": "Same wallpaper for light and dark", + "order-asc": "Ascending", + "order-desc": "Descending", + "order-label": "Order", + "purity-label": "Content filter", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "Sketchy", + "ratios-all-portrait": "All portrait", + "ratios-all-wide": "All wide", + "ratios-any": "Any", + "ratios-label": "Aspect ratio", + "resolution-atleast": "At least", + "resolution-exact": "Exact", + "resolution-label": "Resolution", + "resolution-mode-label": "Mode", + "solid-color-tooltip": "Solid colour background", + "sort-date-asc": "Sort by oldest first", + "sort-date-desc": "Sort by newest first", + "sort-name-asc": "Sort by name (A-Z)", + "sort-name-desc": "Sort by name (Z-A)", + "sort-random": "Sort by random", + "sorting-date-added": "Date added", + "sorting-favorites": "Favorites", + "sorting-label": "Sort by", + "sorting-relevance": "Relevance", + "sorting-toplist": "Toplist", + "sorting-views": "Views", + "source-label": "Source", + "source-wallhaven": "Wallhaven", + "title": "Wallpaper Selector", + "wallhaven-settings-title": "Wallhaven Settings" + }, + "transitions": { + "disc": "Disc", + "fade": "Fade", + "honeycomb": "Honeycomb", + "pixelate": "Pixelate", + "stripes": "Stripes", + "wipe": "Wipe" + }, + "try-different-search": "Try a different search query.", + "wallhaven": { + "loading": "Loading wallpapers...", + "no-results": "No wallpapers found. Try a different search query.", + "page": "{current} of {total}", + "page-prefix": "Page", + "page-suffix": "of {total}" + } + }, + "weather": { + "clear-sky": "Clear sky", + "drizzle": "Drizzle", + "fog": "Fog", + "mainly-clear": "Mainly clear", + "overcast": "Overcast", + "partly-cloudy": "Partly cloudy", + "rain-showers": "Rain showers", + "snow": "Snow", + "thunderstorm": "Thunderstorm" + }, + "widgets": { + "color-picker": { + "palette-description": "Choose from a wide range of predefined colors.", + "palette-label": "Palette", + "palette-theme-colors": "Quick access to your theme's colour palette.", + "title": "Colour picker" + }, + "datetime-tokens": { + "ampm-lowercase": "am/pm in lowercase", + "ampm-uppercase": "AM/PM in uppercase", + "common-12hour-time-minutes": "12-hour time with minutes", + "common-24hour-time-minutes": "24-hour time with minutes", + "common-24hour-time-seconds": "24-hour time with seconds", + "common-european-date": "European date format", + "common-iso-date": "ISO date format", + "common-us-date": "US date format", + "common-weekday-date": "Weekday with date", + "common-weekday-month-day": "Weekday, month and day", + "day-abbreviated": "Abbreviated day name", + "day-full": "Full day name", + "day-leading-zero": "Day with leading zero (01-31)", + "day-no-leading-zero": "Day without leading zero (1-31)", + "hour-leading-zero": "Hour with leading zero (00-23) โ€” 24-hour format", + "hour-no-leading-zero": "Hour without leading zero (0-23) โ€” 24-hour format", + "minute-leading-zero": "Minute with leading zero (00-59)", + "minute-no-leading-zero": "Minute without leading zero (0-59)", + "month-abbreviated": "Abbreviated month name", + "month-full": "Full month name", + "month-number-leading-zero": "Month as number with leading zero (01-12)", + "month-number-no-zero": "Month as number without leading zero (1-12)", + "second-leading-zero": "Second with leading zero (00-59)", + "second-no-leading-zero": "Second without leading zero (0-59)", + "timezone-abbreviation": "Timezone abbreviation", + "year-four-digit": "Year as four-digit number", + "year-two-digit": "Year as two-digit number (00-99)" + }, + "file-picker": { + "item": "item", + "items": "items", + "search-placeholder": "Search files and folders...", + "select-current": "Select Current", + "select-file": "Select File", + "select-folder": "Select Folder", + "selected": "Selected:", + "title": "File Picker" + }, + "file-picker-title": "Select a file", + "icon-picker": { + "title": "Icon picker" + }, + "tooltip-placeholder": "Placeholder" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "Anonymous Identity", + "ca-cert": "CA Certificate", + "eap-method": "EAP Method", + "password": "User password", + "phase2-auth": "Phase 2 Authentication", + "username": "Identity / Username" + }, + "panel": { + "action-required": "Action required", + "add-hidden-network": "Add hidden network", + "add-network": "Add network", + "available-interfaces": "Available interfaces", + "available-networks": "Available networks", + "disabled": "Wiโ€‘Fi is disabled", + "disconnecting": "Disconnecting...", + "dns": "DNS", + "enable-message": "Enable Wiโ€‘Fi to see available networks.", + "enter-password": "Enter Wiโ€‘Fi password...", + "forget": "Forget", + "forget-network": "Forget this network", + "forgetting": "Forgetting...", + "hidden-network": "Hidden network", + "interface": "Network interface", + "internet-connected": "Internet connected", + "internet-limited": "No internet", + "internet-status": "Internet status", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "Known networks", + "link-speed": "Link speed", + "network-name-ssid": "Network name (SSID)", + "no-ethernet-devices": "No Ethernet devices detected", + "no-networks": "No Wiโ€‘Fi networks found", + "saved": "Saved", + "scan-again": "Scan again", + "searching": "Searching for networks...", + "security-open": "Open", + "security-wep": "WEP", + "security-wpa": "WPA", + "security-wpa-ent": "WPA Enterprise", + "security-wpa2-ent": "WPA2 Enterprise", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 Enterprise", + "show-password": "Show password", + "title": "Wiโ€‘Fi" + }, + "signal": { + "excellent": "Excellent", + "fair": "Fair", + "good": "Good", + "poor": "Poor", + "weak": "Weak" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/en.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/en.json new file mode 100644 index 0000000..c28ee37 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/en.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "Clear history", + "close-app": "Close {app}", + "connect-vpn": "Connect to {name}", + "cycle-visualizer": "Cycle visualizer", + "disable-bluetooth": "Disable Bluetooth", + "disable-dnd": "Disable Do Not Disturb", + "disable-wifi": "Disable Wi-Fi", + "disconnect-vpn": "Disconnect {name}", + "dock-settings": "Dock settings", + "enable-bluetooth": "Enable Bluetooth", + "enable-dnd": "Enable Do Not Disturb", + "enable-wifi": "Enable Wi-Fi", + "launcher-settings": "Launcher settings", + "lower-to-bottom": "Lower to bottom", + "open-calendar": "Open calendar", + "open-display-settings": "Display settings", + "open-launcher": "Open launcher", + "open-settings": "Open settings", + "raise-to-top": "Raise to top", + "random-wallpaper": "Random wallpaper", + "run-custom-command": "Run custom command", + "show-active-only": "Show active only", + "show-all": "Show all", + "toggle-mute": "Toggle mute", + "widget-settings": "Widget settings" + }, + "authentication": { + "error": "Authentication error", + "failed": "Authentication failed" + }, + "bar": { + "active-window": { + "colorize-icons-description": "Apply theme colors to active window icon.", + "hide-mode-description": "Controls how the widget behaves when no window is active.", + "scrolling-mode-description": "Control when text scrolling is enabled for long window titles.", + "show-app-icon-description": "Display the application icon next to the window title.", + "show-app-icon-label": "Show app icon", + "show-app-text-description": "Display the window title.", + "show-app-text-label": "Show window title" + }, + "audio-visualizer": { + "color-name-description": "Select the color for the visualizer.", + "color-name-label": "Fill color", + "height-description": "Custom component width.", + "hide-when-idle-description": "When enabled, the visualizer is hidden unless a player is actively playing.", + "hide-when-idle-label": "Hide when no media is playing", + "width-description": "Custom component width." + }, + "battery": { + "device-default": "Default (Display Device)", + "device-description": "Select which battery device to display.", + "device-label": "Battery device", + "display-mode-description": "Choose how the battery is displayed in the bar.", + "display-mode-graphic": "Graphical battery", + "display-mode-graphic-clean": "Graphical battery (no %)", + "display-mode-icon-always": "Icon - Always show %", + "display-mode-icon-hover": "Icon - Show on hover", + "display-mode-icon-only": "Icon only", + "hide-if-idle-description": "Hide the widget when the battery is not charging or discharging.", + "hide-if-idle-label": "Hide when idle", + "hide-if-not-detected-description": "Hide the widget when no battery is detected on the system.", + "hide-if-not-detected-label": "Hide when not detected", + "low-battery-threshold-label": "Low battery warning threshold", + "show-noctalia-performance-description": "Display the Noctalia performance mode toggle inside the battery panel.
Disables shadows and animations in Noctalia to reduce resource usage.", + "show-noctalia-performance-label": "Show Noctalia Performance toggle", + "show-power-profile-description": "Display the power profile selection inside the battery panel.", + "show-power-profile-label": "Show power profile controls" + }, + "brightness": { + "apply-all-description": "Change brightness for every monitor when using the scroll wheel.", + "apply-all-label": "Apply scroll changes to all monitors" + }, + "clock": { + "clock-display-description": "Customize your clock's display by adding tokens from the list below. To use the 12-hour format, you must include the 'AP' token.", + "clock-display-label": "Clock display", + "custom-font-description": "Select a custom font for the clock display.", + "custom-font-label": "Custom font", + "custom-font-placeholder": "Select custom font...", + "custom-font-search-placeholder": "Search fonts...", + "horizontal-bar-description": "Tip: Use \\n to create a line break.", + "horizontal-bar-label": "Horizontal bar", + "preview": "Preview", + "tooltip-format-description": "Format string for the tooltip shown when hovering over the clock. Leave empty to use the default tooltip.", + "tooltip-format-label": "Tooltip format", + "use-custom-font-description": "Override the default font selection with a custom font for the clock.", + "use-custom-font-label": "Use custom font", + "use-monospaced-font-description": "When enabled, the clock will use the monospaced font.", + "use-monospaced-font-label": "Use monospaced font", + "vertical-bar-description": "Use a space to separate each part onto a new line.", + "vertical-bar-label": "Vertical bar" + }, + "control-center": { + "browse-file": "Browse File", + "browse-library": "Browse Library", + "color-selection-description": "Apply theme colors to icons.", + "enable-colorization-description": "Enable colorization for icon, applying theme colors.", + "icon-description": "Select an icon from the library or a custom file.", + "select-custom-icon": "Select a custom icon", + "use-distro-logo-description": "Use your distribution's logo instead of a custom icon.", + "use-distro-logo-label": "Use distro logo instead of icon" + }, + "custom-button": { + "collapse-condition-description": "If the output text matches this value, the button will collapse.", + "collapse-condition-label": "Collapse condition", + "color-selection-description": "Apply theme colors to icon and text.", + "default-tooltip": "Custom button, configure in settings", + "display-command-output-description": "Enter a command to run at a regular interval. The first line of its output will be displayed as text.", + "display-command-output-label": "Display command output", + "display-command-output-stream-description": "Enter a command to run continuously.", + "dynamic-text": "Dynamic text", + "enable-colorization-description": "Enable colorization for the custom button icon and text, applying theme colors.", + "enable-colorization-label": "Enable colorization", + "general-tooltip-text-description": "Custom text to display in the button's tooltip.", + "general-tooltip-text-label": "Custom tooltip text", + "hide-mode-always-expanded": "Always expanded", + "hide-mode-description": "Controls widget visibility when the command has no output.", + "hide-mode-expand-with-output": "Expand when has output", + "hide-mode-label": "Hide mode", + "hide-mode-max-transparent": "Max expanded but transparent", + "icon-description": "Select an icon from the library.", + "ipc-identifier-description": "Unique identifier for IPC commands. Use this identifier with 'qs -c noctalia-shell ipc call cb [action] [identifier]' to control this button via IPC.", + "ipc-identifier-label": "IPC Identifier", + "left-click-description": "Command to execute when the button is left-clicked.", + "left-click-label": "Left click", + "left-click-update-text": "Update displayed text on left-click", + "max-text-length-horizontal-description": "Maximum number of characters to show in horizontal bar (0 to hide text).", + "max-text-length-horizontal-label": "Max text length (horizontal)", + "max-text-length-vertical-description": "Maximum number of characters to show in vertical bar (0 to hide text).", + "max-text-length-vertical-label": "Max text length (vertical)", + "middle-click-description": "Command to execute when the button is middle-clicked.", + "middle-click-label": "Middle click", + "middle-click-update-text": "Update displayed text on middle-click", + "parse-json-description": "Parse the command output as a JSON object to dynamically set text and icon.", + "parse-json-label": "Parse output as JSON", + "refresh-interval-description": "Interval in milliseconds.", + "refresh-interval-label": "Refresh interval", + "right-click-description": "Command to execute when the button is right-clicked.", + "right-click-label": "Right click", + "right-click-update-text": "Update displayed text on right-click", + "show-exec-tooltip-description": "Show tooltips with command details (left/right/middle click, wheel).", + "show-exec-tooltip-label": "Show command tooltips", + "show-icon-description": "Toggles the visibility of the widget's icon.", + "show-icon-label": "Show icon", + "show-text-tooltip-description": "Show tooltips with the output from the text command.", + "show-text-tooltip-label": "Show dynamic text tooltips", + "text-stream-description": "Streamed lines from the command will be displayed as text on the button.", + "text-stream-label": "Stream", + "wheel-description": "Command to execute when the scroll wheel is used.
Use $delta for the scroll wheel delta in the command.", + "wheel-down": "Scroll down", + "wheel-down-description": "Command to execute when the scroll wheel is scrolled down.", + "wheel-down-label": "Wheel down command", + "wheel-label": "Scroll wheel", + "wheel-mode-separate-description": "Enable separate commands for wheel up and down.", + "wheel-mode-separate-label": "Separate wheel commands", + "wheel-up": "Scroll up", + "wheel-up-description": "Command to execute when the scroll wheel is scrolled up.", + "wheel-up-label": "Wheel up command", + "wheel-update-text": "Update displayed text on scroll" + }, + "keyboard-layout": { + "show-icon-description": "Display the keyboard layout icon." + }, + "lock-keys": { + "hide-when-off-description": "Hide the indicator when the key is not active.", + "hide-when-off-label": "Hide when off", + "show-caps-lock-description": "Display Caps Lock status.", + "show-caps-lock-label": "Caps Lock", + "show-num-lock-description": "Display Num Lock status.", + "show-num-lock-label": "Num Lock", + "show-scroll-lock-description": "Display Scroll Lock status.", + "show-scroll-lock-label": "Scroll Lock" + }, + "media-mini": { + "compact-mode-description": "Enable a space-saving layout for the media player panel.", + "compact-mode-label": "Compact mode", + "hide-mode-description": "Controls how the widget behaves when no media is playing.", + "max-width-description": "Sets the maximum horizontal size of the widget. The widget will shrink to fit shorter content.", + "no-active-player": "No active player", + "panel-section-description": "Configure the appearance and behavior of the media player panel.", + "panel-section-label": "Media player panel", + "scrolling-mode-description": "Control when text scrolling is enabled for long track titles.", + "scrolling-mode-label": "Scrolling mode", + "show-album-art-description": "Display the album artwork for the currently playing track.", + "show-album-art-label": "Show album art", + "show-artist-first-description": "Display artist - title instead of title - artist.", + "show-artist-first-label": "Show artist first", + "show-progress-ring-description": "Display a circular progress indicator showing track progress.", + "show-progress-ring-label": "Show progress ring", + "show-visualizer-description": "Display an audio visualizer when music is playing.", + "show-visualizer-label": "Show visualizer", + "use-fixed-width-description": "When enabled, the widget will always use the maximum width instead of dynamically adjusting to content.", + "use-fixed-width-label": "Use fixed width", + "visualizer-type-description": "Choose the style of audio visualizer to display.", + "visualizer-type-label": "Visualizer type" + }, + "notification-history": { + "hide-widget-when-zero-description": "Hide the notification icon when there are no notifications.", + "hide-widget-when-zero-label": "Hide icon when no notifications", + "hide-widget-when-zero-unread-description": "Hide the notification icon when there are no unread notifications.", + "hide-widget-when-zero-unread-label": "Hide icon when no unread notifications", + "show-unread-badge-description": "Display a badge showing the number of unread notifications.", + "show-unread-badge-label": "Show unread badge", + "unread-badge-color-description": "Select the color for the unread notification badge.", + "unread-badge-color-label": "Unread badge color" + }, + "section-editor": { + "placeholder": "Select a widget to add...", + "search-placeholder": "Search widget..." + }, + "spacer": { + "width-description": "Spacing width in pixels." + }, + "system-monitor": { + "compact-mode-description": "Display stats as mini bar charts instead of text values. Prevents layout shifting.", + "compact-mode-label": "Compact mode", + "cpu-cores-description": "Display CPU cores usage individually.", + "cpu-cores-label": "CPU Cores", + "cpu-frequency-description": "Display the current CPU clock speed in GHz.", + "cpu-frequency-label": "Show CPU frequency", + "cpu-temperature-description": "Show CPU temperature readings if available.", + "cpu-temperature-label": "CPU temperature", + "cpu-usage-description": "Display current CPU usage percentage.", + "cpu-usage-label": "CPU usage", + "disk-path-description": "Select which disk mount point to monitor.", + "disk-path-label": "Disk path", + "gpu-temperature-description": "Show GPU temperature readings if available.", + "load-average-description": "Display system load average.", + "load-average-label": "Load average", + "memory-percentage-description": "Show memory usage as a percentage instead of absolute values.", + "memory-percentage-label": "Memory as percentage", + "memory-usage-description": "Display current RAM usage information.", + "memory-usage-label": "Memory usage", + "network-traffic-description": "Display network upload and download speeds.", + "network-traffic-label": "Network traffic", + "storage-as-percentage-description": "Show disk space as percentage instead of absolute values.", + "storage-as-percentage-label": "Disk as percentage", + "storage-available-description": "Shows how much disk space is available instead of how much is used.", + "storage-available-label": "Disk space available", + "storage-usage-description": "Show disk space usage information.", + "storage-usage-label": "Storage usage", + "swap-usage-description": "Show swap memory usage.", + "swap-usage-label": "Swap usage", + "use-monospace-font-description": "Use monospace font for consistent character width.", + "use-monospace-font-label": "Monospace font", + "use-padding-description": "Pads the text values with leading spaces to prevent layout shifting.", + "use-padding-description-disabled-monospace-font": "A monospace font is needed for this feature.", + "use-padding-description-disabled-vertical": "Vertical taskbar does not support padding text.", + "use-padding-label": "Pad text" + }, + "taskbar": { + "colorize-icons-description": "Apply theme colors to taskbar icons.", + "hide-mode-description": "Controls how the widget behaves when there are no matching windows.", + "hide-mode-label": "Hiding mode", + "icon-scale-description": "Sets the scaling factor for taskbar icons.", + "icon-scale-label": "Icon scaling", + "max-width-description": "Maximum width of the taskbar as a percentage of screen width.", + "max-width-label": "Maximum width", + "only-active-workspaces-description": "Show only apps from active workspaces.", + "only-active-workspaces-label": "Only from active workspaces", + "only-same-monitor-description": "Show only apps from the monitor where the bar is located.", + "only-same-monitor-label": "Only from same monitor", + "show-pinned-apps-description": "Show pinned apps from the dock in the taskbar.", + "show-pinned-apps-label": "Show pinned apps", + "show-title-description": "Display window titles in the taskbar.", + "show-title-description-disabled": "Vertical taskbar does not support showing titles.", + "show-title-label": "Show title", + "smart-width-description": "Automatically adjust entry width based on the number of entries.", + "smart-width-label": "Smart width", + "title-width-description": "Set the width of window titles in the taskbar (in pixels).", + "title-width-label": "Title width", + "title-width-reset-tooltip": "Reset title width" + }, + "tray": { + "chevron-color-description": "Apply theme colors to the drawer chevron icon.", + "chevron-color-label": "Chevron color", + "colorize-icons-description": "Apply theme colors to tray icons.", + "colorize-icons-label": "Colorize icons", + "drawer-enabled-description": "When enabled, unpinned tray items are shown in a drawer panel.
When disabled, all tray items are shown inline.", + "drawer-enabled-label": "Enable drawer", + "hide-passive-description": "When enabled, tray items with 'Passive' status will be hidden.", + "hide-passive-label": "Hide passive items" + }, + "volume": { + "display-mode-description": "Choose how you'd like this value to appear." + }, + "workspace": { + "character-count-description": "Number of characters to display from workspace names (1-10).", + "character-count-label": "Character count", + "empty-color-description": "Set the background color for empty workspaces.", + "empty-color-label": "Empty workspace color", + "enable-scrollwheel-description": "Switch between workspaces using the mouse scroll wheel.", + "enable-scrollwheel-label": "Scroll to switch workspaces", + "focused-color-description": "Set the background color for the focused workspace.", + "focused-color-label": "Focused workspace color", + "follow-focused-screen-description": "Display workspaces from the currently focused screen, rather than the screen where the bar is located.", + "follow-focused-screen-label": "Follow focused screen", + "font-weight-description": "Set the visual weight for text within workspace.", + "font-weight-label": "Font weight", + "grouped-border-opacity-description": "Set the opacity level for workspace container borders.", + "grouped-border-opacity-label": "Border opacity", + "hide-unoccupied-description": "Don't display workspaces without windows.", + "hide-unoccupied-label": "Hide unoccupied", + "label-mode-description": "Choose how workspace labels are displayed.", + "label-mode-label": "Label mode", + "occupied-color-description": "Set the background color for occupied workspaces.", + "occupied-color-label": "Occupied workspace color", + "pill-size-description": "Adjust the size of workspace pills.", + "pill-size-label": "Pill size", + "reverse-scrolling-description": "Reverse the direction of workspace switching when scrolling.", + "reverse-scrolling-label": "Reverse scrolling", + "show-applications-description": "Display application icons inside each workspace.", + "show-applications-hover-description": "Display application icons inside each workspace when the workspace is hovered.", + "show-applications-hover-label": "Show applications when hovered", + "show-applications-label": "Show applications", + "show-badge-description": "Show the workspace number badge in grouped mode.", + "show-badge-label": "Show workspace badge", + "show-labels-only-when-occupied-description": "Only show workspace labels when they contain windows.", + "show-labels-only-when-occupied-label": "Show labels only when occupied", + "unfocused-icons-opacity-description": "Set the opacity level for unfocused app icons.", + "unfocused-icons-opacity-label": "Unfocused icons opacity" + } + }, + "battery": { + "all-batteries": "All batteries (combined)", + "battery-health": "Battery health", + "battery-level": "Battery level", + "capacity-level": "Capacity: {level}", + "charging-rate": "Charging rate: {rate} W", + "discharging-rate": "Discharging rate: {rate} W", + "health": "Health: {percent}%", + "inhibit-idle-description": "Keeps the system awake.", + "no-battery-detected": "No battery detected", + "plugged-in": "Plugged in", + "power-profile": "Power profile", + "time-left": "Time left: {time}", + "time-until-full": "Time until full: {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "Available devices", + "blocked": "Blocked", + "connected-devices": "Connected devices", + "device-address": "Device address", + "disabled": "Bluetooth is disabled", + "discoverable": "Discoverable", + "enable-message": "Enable Bluetooth to see available devices.", + "known-devices": "Known devices", + "no-devices": "No devices available", + "paired-devices": "Paired devices", + "pairing-mode": "Make sure your device is in pairing mode.", + "scanning": "Scanning for devices...", + "signal-text-excellent": "Signal: Excellent", + "signal-text-fair": "Signal: Fair", + "signal-text-good": "Signal: Good", + "signal-text-poor": "Signal: Poor", + "signal-text-unknown": "Signal: Unknown", + "signal-text-very-poor": "Signal: Very poor" + } + }, + "changelog": { + "error": { + "fetch-failed": "Unable to load changelog data. Please try again later.", + "rate-limit": "GitHub rate limit exceeded. Please try again in a few minutes." + }, + "panel": { + "buttons-discord": "Join our Discord", + "buttons-dismiss": "OK", + "buttons-feedback": "Give feedback", + "empty": "Release notes are not available yet.", + "highlight-title": "Highlights", + "released": "Released on {date}", + "subtitle-fresh": "Thanks for installing Noctalia! Here is whatโ€™s included in this build.", + "subtitle-updated": "Updated from {previousVersion}", + "title": "What's new in {version}", + "version": "Version {version}", + "version-new-user": "Fresh install" + } + }, + "common": { + "actions": "Actions", + "active": "Active", + "add": "Add", + "appearance": "Appearance", + "apply": "Apply", + "auto-connect": "Auto-connect", + "automation": "Automation", + "available": "Available", + "back": "Back", + "battery": "Battery", + "behavior": "Behavior", + "bluetooth": "Bluetooth", + "brightness": "Brightness", + "browse": "Browse", + "calendar": "Calendar", + "calendar-panel": "Calendar Panel", + "cancel": "Cancel", + "cards": "Cards", + "charging": "Charging", + "clear": "Clear", + "clipboard": "Clipboard", + "close": "Close", + "color-muted": "Muted", + "colors": "Colors", + "command": "Command", + "confirm": "Confirm", + "connect": "Connect", + "connected": "Connected", + "connecting": "Connecting...", + "continue": "Continue", + "contributors": "Contributors", + "copied-to-clipboard": "Copied to clipboard", + "countdown": "Countdown", + "customize": "Customize", + "date": "Date", + "default": "Default", + "delete": "Delete", + "devices": "Devices", + "disabled": "Disabled", + "discharging": "Discharging", + "disconnect": "Disconnect", + "disconnected": "Disconnected", + "disconnecting": "Disconnecting...", + "display-mode": "Display mode", + "documentation": "Documentation", + "download": "Download", + "duration": "Duration", + "dysfunctional": "Dysfunctional", + "edit": "Edit", + "enabled": "Enabled", + "error": "Error", + "ethernet": "Ethernet", + "events": "Events", + "execute": "Execute", + "faithful": "Faithful", + "focus": "Focus", + "font-weight-bold": "Bold", + "font-weight-light": "Light", + "font-weight-medium": "Medium", + "font-weight-regular": "Regular", + "font-weight-semibold": "Semi-bold", + "frequency": "Frequency", + "gateway": "Gateway", + "general": "General", + "height": "Height", + "hibernate": "Hibernate", + "history": "History", + "icon": "Icon", + "idle": "Idle", + "info": "Info", + "input": "Input", + "install": "Install", + "installed": "Installed", + "interface": "Interface", + "internet": "Internet", + "keybind": "Keybind", + "language": "Language", + "loading": "Loading...", + "local": "Local", + "location": "Location", + "lock": "Lock", + "logout": "Logout", + "look": "Look", + "margins": "Margins", + "media": "Media", + "media-player": "Media Player", + "memory": "Memory", + "monitors": "Monitors", + "move-down": "Move down", + "move-up": "Move up", + "network": "Network", + "next": "Next", + "night-light": "Night Light", + "no": "No", + "no-results": "No results", + "no-summary": "No summary", + "none": "None", + "not-found": "Not found", + "notifications": "Notifications", + "official": "Official", + "on-surface": "On Surface", + "output": "Output", + "pair": "Pair", + "paired": "Paired", + "pairing": "Pairing...", + "panels": "Panels", + "password": "Password", + "pause": "Pause", + "performance": "Performance", + "pin": "Pin", + "play": "Play", + "polling": "Polling", + "position": "Position", + "previous": "Previous", + "primary": "Primary", + "random": "Random", + "reboot": "Reboot", + "reboot-to-uefi": "Reboot to UEFI", + "record": "Start recording", + "refresh": "Refresh", + "required": "(required)", + "reset": "Reset", + "reset-to-default": "Reset to Default", + "result-count": "{count} result", + "result-count-plural": "{count} results", + "resume": "Resume", + "save": "Save", + "scanning": "Scanning...", + "screen-corners": "Screen Corners", + "search": "Search", + "second": "{count} second", + "second-plural": "{count} seconds", + "secondary": "Secondary", + "security": "Security", + "select": "Select", + "select-color": "Select color", + "select-color-description": "Apply theme colors for emphasis.", + "select-icon-color": "Select icon color", + "settings": "Settings", + "shortcuts": "Shortcuts", + "shutdown": "Shutdown", + "signal": "Signal", + "sound": "Sound", + "sources": "Sources", + "start": "Start", + "stop": "Stop", + "supporters": "Supporters", + "suspend": "Suspend", + "templates": "Templates", + "tertiary": "Tertiary", + "test": "Test", + "thresholds": "Thresholds", + "title": "Title", + "toast": "Toast", + "trusted": "Trusted", + "uninstall": "Uninstall", + "unknown": "Unknown", + "unpair": "Unpair", + "unpin": "Unpin", + "update": "Update", + "upload": "Upload", + "username": "Username", + "userspace-reboot": "Userspace Reboot", + "version": "Version", + "vibrant": "Vibrant", + "visualizer": "Visualizer", + "volume": "Volume", + "volumes": "Volumes", + "wallpaper": "Wallpaper", + "weather": "Weather", + "weather-loading": "Loading weather...", + "weather-no-location": "Set a location in Settings to see weather", + "week": "Week", + "widgets": "Widgets", + "width": "Width", + "wifi": "Wi-Fi", + "windows": "Windows", + "yes": "Yes" + }, + "control-center": { + "power-profile": { + "tooltip-action": "Power Profile", + "tooltip-disabled": "Install power-profiles-daemon to use power profiles" + }, + "wifi": { + "label-disconnected": "Wi-Fi disconnected", + "label-ethernet": "Ethernet" + } + }, + "display-modes": { + "always-hide": "Always hide", + "always-show": "Always show", + "force-open": "Force open", + "on-hover": "On hover" + }, + "hide-modes": { + "auto-hide": "Auto-hide", + "hidden": "Hide when empty", + "idle": "Hide when idle", + "non-exclusive": "Non-exclusive", + "transparent": "Transparent when empty", + "visible": "Always visible" + }, + "launcher": { + "categories": { + "all": "All", + "audiovideo": "Audio & Video", + "chat": "Chat", + "development": "Development", + "education": "Education", + "emoji-activity": "Activity", + "emoji-animals": "Animals", + "emoji-flags": "Flags", + "emoji-food": "Food & Drink", + "emoji-nature": "Nature", + "emoji-objects": "Objects", + "emoji-people": "People & Body", + "emoji-recent": "Recent", + "emoji-symbols": "Symbols", + "emoji-travel": "Travel & Places", + "game": "Games", + "graphics": "Graphics", + "misc": "Misc", + "office": "Office", + "pinned": "Pinned", + "system": "System", + "webbrowser": "Web Browser" + }, + "providers": { + "applications": "Applications", + "calculator": "Calculator", + "calculator-deprecated": "The >calc command is deprecated and will be removed soon. Please type math expressions directly in search instead.", + "calculator-description": "Calculator โ€” evaluate mathematical expressions", + "calculator-enter-expression": "Enter a mathematical expression", + "calculator-press-enter-to-copy": "Press Enter to copy result", + "clipboard": "Clipboard history", + "clipboard-clear-description": "Clear all clipboard history", + "clipboard-clear-description-full": "Remove all items from clipboard history", + "clipboard-clear-history": "Clear clipboard history", + "clipboard-delete": "Delete clipboard entry", + "clipboard-history-disabled": "Clipboard history disabled", + "clipboard-history-disabled-description": "Enable clipboard history in settings or install cliphist", + "clipboard-loading": "Loading clipboard history...", + "clipboard-search-description": "Search clipboard history", + "command-description": "Run shell commands", + "emoji": "Emoji picker", + "emoji-loading": "Loading emojis...", + "emoji-loading-description": "Please wait", + "emoji-no-recent": "No recent emojis yet", + "emoji-search-description": "Search and copy emojis", + "settings-search-description": "Search and navigate to settings", + "windows-search-description": "Search and focus open windows" + } + }, + "lock-screen": { + "authenticating": "Authenticating...", + "password": "Enter your password...", + "restart": "Restart", + "shut-down": "Shut down" + }, + "notifications": { + "panel": { + "click-to-collapse": "Click to collapse", + "click-to-expand": "Click to expand", + "description": "Your notifications will show up here as they arrive.", + "no-notifications": "No notifications" + }, + "range": { + "earlier": "Earlier", + "today": "Today", + "yesterday": "Yesterday" + }, + "time": { + "diff-d": "1 day ago", + "diff-dd": "{diff} days ago", + "diff-h": "1 hour ago", + "diff-hh": "{diff} hours ago", + "diff-m": "1 minute ago", + "diff-mm": "{diff} minutes ago", + "now": "now" + } + }, + "options": { + "bar": { + "density-comfortable": "Comfortable", + "density-compact": "Compact", + "density-default": "Default", + "density-mini": "Mini", + "density-spacious": "Spacious", + "display-mode-always-visible": "Always Visible", + "type-floating": "Floating", + "type-framed": "Framed", + "type-simple": "Simple" + }, + "control-center": { + "quick-settings-style-classic": "Classic", + "quick-settings-style-modern": "Modern" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "Comfortable", + "compact": "Compact", + "default": "Default" + }, + "launcher-view-mode": { + "grid": "Grid", + "list": "List" + }, + "notification-density": { + "compact": "Compact", + "default": "Default" + }, + "scrolling-modes": { + "always": "Scroll always", + "hover": "Scroll on hover", + "never": "Never scroll" + }, + "session-menu-grid-layout": { + "grid": "Grid", + "single-row": "Single row" + }, + "settings-panel-mode": { + "attached": "Panel attached to bar", + "centered": "Centered panel", + "window": "Separate window" + }, + "visualizer-types": { + "linear": "Linear", + "mirrored": "Mirrored", + "wave": "Wave" + }, + "visualizer-visibility": { + "with-background": "With background only" + }, + "workspace-labels": { + "index": "Index", + "index-and-name": "Index and name", + "name": "Name" + } + }, + "panels": { + "about": { + "become-supporter": "Become a supporter", + "changelog": "View changelog", + "changelog-on-startup": "Show changelog on update", + "changelog-on-startup-desc": "Automatically show the changelog when Noctalia is updated", + "contributors-description": "Shout-out to our {count} awesome contributor!", + "contributors-description-plural": "Shout-out to our {count} awesome contributors!", + "copy-info": "Copy info", + "debug-disabled": "Debug mode disabled", + "debug-enabled": "Debug mode enabled", + "info-copied": "Info copied to clipboard", + "noctalia-available": "Available:", + "noctalia-desc": "A sleek and minimal desktop shell thoughtfully crafted for Wayland, built with Quickshell.", + "noctalia-git-commit": "Git commit:", + "noctalia-installed-version": "Installed version:", + "noctalia-latest-version": "Latest version:", + "noctalia-qs-version": "Noctalia QS version:", + "noctalia-title": "Noctalia Shell", + "privacy-policy": "Privacy policy", + "support": "Support us", + "supporter-badge": "Supporter", + "supporters-desc": "A huge thank you to our amazing supporter!", + "supporters-desc-plural": "A huge thank you to our {count} amazing supporters!", + "supporters-loading": "Loading supporters...", + "system-board": "Board:", + "system-cpu": "CPU:", + "system-disk": "Disk:", + "system-gpu": "GPU:", + "system-host": "Host:", + "system-install-hint": "Install fastfetch to view system information", + "system-kernel": "Kernel:", + "system-loading": "Loading system info...", + "system-memory": "Memory:", + "system-monitor": "Monitor:", + "system-not-installed": "fastfetch is not installed", + "system-os": "OS:", + "system-packages": "Packages:", + "system-product": "Product:", + "system-title": "System Information", + "system-uptime": "Uptime:", + "system-wm": "WM:", + "telemetry-data-copied": "Telemetry data copied to clipboard", + "telemetry-desc": "Help improve Noctalia by sharing anonymous system info (screen resolution, compositor, distro). Sent once at startup, no tracking, data auto-deleted after 30 days.", + "telemetry-enabled": "Send anonymous system information", + "telemetry-show-data": "View data", + "telemetry-title": "Privacy", + "title": "About", + "up-to-date": "You're up to date!", + "update-available": "Update available", + "view-commit": "View commit on GitHub" + }, + "audio": { + "devices-desc": "Configure available audio input and output devices.", + "devices-input-device-description": "Select the desired audio input device.", + "devices-input-device-label": "Input device", + "devices-output-device-description": "Select the desired audio output device.", + "devices-output-device-label": "Output device", + "devices-title": "Audio devices", + "external-mixer-description": "Enter the command or application path to launch when activating the external audio mixer feature.", + "external-mixer-label": "External audio mixer command", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "Set your preferred and ignored media applications.", + "media-excluded-player-description": "Add keywords for players you want the system to ignore. Each keyword should be on a new line.", + "media-excluded-player-label": "Excluded player", + "media-excluded-player-placeholder": "type substring and press +", + "media-frame-rate-description": "Higher rates are smoother but use more resources.", + "media-frame-rate-label": "Frame rate", + "media-primary-player-description": "Enter a keyword to identify your main player.", + "media-primary-player-label": "Primary player", + "media-primary-player-placeholder": "e.g. spotify, vlc, mpv", + "media-scrolling-speed-description": "Time in seconds for the title to scroll from start to end.", + "media-scrolling-speed-label": "Scrolling speed", + "media-scrolling-title-description": "Enable continuous scrolling for long media titles.", + "media-scrolling-title-label": "Scrolling title", + "media-title": "Media players", + "on-middle-clicked-description": "Command to execute when the button is middle-clicked.", + "panel-applications-empty": "No applications are currently playing audio", + "spectrum-mirrored-description": "Mirror the spectrum so low frequencies are in the center. When off, low frequencies are on the left and high on the right.", + "spectrum-mirrored-label": "Mirror spectrum", + "title": "Audio", + "visualizer-type-description": "Choose a visualization type for media playback.", + "visualizer-type-label": "Visualization type", + "volumes-desc": "Adjust volume controls and audio levels.", + "volumes-feedback-sound-file-description": "Path to the sound file played when changing volume.", + "volumes-feedback-sound-file-label": "Volume feedback sound file", + "volumes-feedback-sound-file-select-title": "Select volume feedback sound file", + "volumes-input-volume-description": "Microphone input volume level.", + "volumes-mute-input-description": "Mute the default audio input (microphone).", + "volumes-mute-input-label": "Mute audio input", + "volumes-mute-output-description": "Mute the system's main audio output.", + "volumes-mute-output-label": "Mute audio output", + "volumes-output-volume-description": "System-wide volume level.", + "volumes-step-size-description": "Adjust the step size for volume changes (scroll wheel, keyboard shortcuts).", + "volumes-step-size-label": "Volume step size", + "volumes-volume-feedback-description": "Play a feedback sound when adjusting volume.", + "volumes-volume-feedback-label": "Play volume feedback sound", + "volumes-volume-overdrive-description": "Allow raising volume above 100%. May not be supported by all hardware.", + "volumes-volume-overdrive-label": "Allow volume overdrive" + }, + "bar": { + "appearance-auto-hide-delay-description": "Time before bar hides after mouse leaves", + "appearance-auto-hide-delay-label": "Hide Delay", + "appearance-auto-hide-exclusive-note": "Note: Exclusive zone is automatically disabled when auto-hide is active", + "appearance-auto-show-delay-description": "Time before bar shows when mouse enters edge", + "appearance-auto-show-delay-label": "Show Delay", + "appearance-background-opacity-description": "Set the background opacity specifically for the bar.", + "appearance-background-opacity-label": "Bar background opacity", + "appearance-capsule-color-description": "Choose a color for bar capsules, or use none for the default surface color.", + "appearance-capsule-color-label": "Capsule color", + "appearance-capsule-opacity-description": "Set the opacity level for widget backgrounds when capsule is shown.", + "appearance-capsule-opacity-label": "Capsule opacity", + "appearance-content-padding-description": "Adjust the padding between bar edges and widgets.", + "appearance-content-padding-label": "Content padding", + "appearance-density-description": "Adjust the bar's padding for a compact or spacious look.", + "appearance-density-label": "Bar density", + "appearance-desc": "Customize the bar's appearance and position.", + "appearance-display-mode-description": "Choose when the bar is visible.", + "appearance-enable-exclusion-zone-inset-description": "Reduce the exclusion zone by 1 physical pixel so flush windows bleed perfectly under the bar edge.", + "appearance-enable-exclusion-zone-inset-label": "Inset exclusion zone", + "appearance-floating-description": "Display the bar as a floating 'pill'.", + "appearance-floating-label": "Floating bar", + "appearance-font-scale-description": "Adjust the font size scale for text displayed in the bar.", + "appearance-font-scale-label": "Font scale", + "appearance-frame-radius": "Inner Radius", + "appearance-frame-settings-description": "Adjust frame thickness and inner corner radius", + "appearance-frame-settings-label": "Frame Settings", + "appearance-frame-thickness": "Thickness", + "appearance-hide-on-overview-description": "Hide the bar and close panels when the compositor overview is active.", + "appearance-hide-on-overview-label": "Hide bar on overview", + "appearance-margins-description": "Adjust the margins around the floating bar.", + "appearance-margins-horizontal": "Horizontal Margin ", + "appearance-margins-vertical": "Vertical Margin", + "appearance-outer-corners-description": "Display outwardly curved corners on the bar.", + "appearance-outer-corners-label": "Outer corners", + "appearance-position-description": "Choose where to place the bar on the screen.", + "appearance-position-label": "Bar position", + "appearance-show-capsule-description": "Show widget backgrounds.", + "appearance-show-capsule-label": "Show capsule", + "appearance-show-on-workspace-switch-description": "Automatically show the bar briefly when the workspace changes.", + "appearance-show-on-workspace-switch-label": "Show bar on workspace switch", + "appearance-show-outline-description": "Displays a visible border around every widget.", + "appearance-show-outline-label": "Show widget outlines", + "appearance-type-description": "Choose the style of the bar: Simple, Floating or Framed.", + "appearance-type-label": "Bar type", + "appearance-use-separate-opacity-description": "Enable to use a separate opacity value for the bar background.", + "appearance-use-separate-opacity-label": "Use separate bar opacity", + "appearance-widget-spacing-description": "Adjust the spacing between each widget in the bar.", + "appearance-widget-spacing-label": "Widget spacing", + "behavior-middle-click-command-description": "Command to execute on middle click.", + "behavior-middle-click-command-label": "Middle click command", + "behavior-middle-click-command-placeholder": "niri msg action toggle-overview", + "behavior-middle-click-description": "Choose what middle click does on empty areas of the bar.", + "behavior-middle-click-follow-mouse-description": "Open the selected middle-click panel at the cursor position.", + "behavior-middle-click-follow-mouse-label": "Middle click follow mouse", + "behavior-middle-click-label": "Bar middle click action", + "behavior-right-click-command-description": "Command to execute on right click.", + "behavior-right-click-command-label": "Right click command", + "behavior-right-click-command-placeholder": "notify-send \"Right click\"", + "behavior-right-click-description": "Choose what right click does on empty areas of the bar.", + "behavior-right-click-follow-mouse-description": "Open the selected right-click panel at the cursor position.", + "behavior-right-click-follow-mouse-label": "Right click follow mouse", + "behavior-right-click-label": "Bar right click action", + "behavior-wheel-wrap-description": "When enabled, scrolling continues from the last item to the first.", + "behavior-wheel-wrap-label": "Wrap around", + "behavior-workspace-scroll-description": "Choose what the mouse wheel does on empty areas of the bar.", + "behavior-workspace-scroll-label": "Bar mouse wheel action", + "behavior-workspace-scroll-option-content": "Content", + "behavior-workspace-scroll-option-workspace": "Workspace", + "monitor-configure-widgets": "Configure widgets", + "monitor-override-settings": "Override global settings", + "monitor-override-settings-description": "Use custom settings for this monitor.", + "monitor-reset-all": "Reset all", + "monitor-widgets-title": "Widget Configuration - {monitor}", + "monitors-desc": "Show bar on specific monitors. Defaults to all if none are chosen.", + "monitors-desc-new": "Configure which monitors display the bar and customize settings per monitor.", + "monitors-title": "Monitors display", + "title": "Bar", + "tray-blacklist-description": "Add tray exclusion rules, supports wildcards (*).", + "tray-blacklist-label": "Blacklist", + "tray-blacklist-placeholder": "e.g. nm-applet, Fcitx*", + "tray-pin-application": "Pin Application", + "tray-unpin-application": "Unpin Application", + "use-global-widgets": "Use global widgets", + "widgets-desc": "Drag widgets to change their order. Use the right-click menu to transfer widgets between sections or remove them.", + "widgets-title": "Widgets positioning" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "Generate color schemes from your wallpaper. Automatically extracts colors to create a cohesive theme.", + "color-source-use-wallpaper-colors-label": "Use wallpaper colors", + "dark-mode-mode-description": "Enables automatic switching between Light and Dark Mode.", + "dark-mode-mode-label": "Dark Mode schedule", + "dark-mode-mode-manual": "Manual", + "dark-mode-mode-off": "Off", + "dark-mode-switch-description": "Switches to a darker theme for easier viewing at night.", + "delete-error-description": "Failed to delete {scheme}", + "delete-error-title": "Delete failed", + "delete-success-description": "Successfully deleted {scheme}", + "delete-success-title": "Color scheme deleted", + "download-button": "Download more", + "download-downloading": "Downloading...", + "download-empty": "No color schemes available", + "download-error-api-error": "API error: {status}", + "download-error-description": "Failed to download {scheme}", + "download-error-download-failed": "Download failed with exit code: {code}", + "download-error-invalid-response": "Invalid API response format", + "download-error-no-files": "No files found for scheme", + "download-error-parse-failed": "Failed to parse API response: {error}", + "download-error-rate-limit": "GitHub API rate limit exceeded", + "download-error-title": "Download failed", + "download-fetching": "Fetching available color schemes...", + "download-success-description": "Successfully downloaded {scheme}", + "download-success-title": "Color scheme downloaded", + "download-title": "Download Color Schemes", + "method-description": { + "content": "Material Design scheme with high-fidelity color extraction that closely matches the source content's actual colors.", + "dysfunctional": "Like Faithful but picks the second most dominant color family as primary.", + "faithful": "Attempts to stay close to the source color while still generating a harmonious palette.", + "fruit-salad": "Material Design scheme that produces a playful, vibrant palette with diverse and varied hues.", + "monochrome": "Material Design scheme using a single-hue grayscale with minimal chromatic content.", + "muted": "Generates softer, desaturated tones for a calm and understated appearance.", + "rainbow": "Material Design scheme that generates colors across the full spectrum for maximum color diversity.", + "tonal-spot": "Material Design default algorithm. Creates balanced palettes using tonal variations of the source color.", + "vibrant": "Maximizes color saturation for bold, eye-catching palettes with vivid tones." + }, + "predefined-desc": "Choose from a collection of predefined color schemes.", + "predefined-generate-templates-label": "Generate templates for predefined schemes", + "predefined-title": "Predefined color schemes", + "sync-gsettings-description": "Match the system theme to the active light or dark variant.", + "sync-gsettings-label": "Sync system theme", + "templates-desc": "Apply colors to external applications.", + "templates-filter-description": "Show templates from a specific category.", + "templates-filter-label": "Filter by category", + "templates-misc-description": "Create your own templates.", + "templates-misc-label": "Advanced", + "templates-misc-user-templates-description": "Only enable if you know what you are doing โ€” refer to our online documentation.", + "templates-misc-user-templates-label": "Enable user templates", + "templates-none-detected": "None detected", + "templates-write-path": "Writes: {filepath}", + "title": "Color Scheme", + "wallpaper-method-description": "Choose your favorite palette generation method.", + "wallpaper-method-label": "Palette generation method", + "wallpaper-monitor-source-description": "Select which monitor to use for extracting wallpaper colors.", + "wallpaper-monitor-source-label": "Color generation source" + }, + "connections": { + "authentication-required": "Authentication required", + "bluetooth-auto-connect-description": "Automatically connect to trusted paired devices when Bluetooth is enabled.", + "bluetooth-auto-connect-label": "Auto-connect paired devices", + "bluetooth-devices-unnamed": "Unnamed devices are not shown.", + "bluetooth-discoverable": "This device is discoverable as {hostName} while this settings tab is open.", + "bluetooth-rssi-polling-description": "Periodically sample RSSI for connected devices via bluetoothctl. May not be available for all devices; uses minimal resources when enabled.", + "bluetooth-rssi-polling-interval-description": "Configure how often to update signal strength for connected devices.", + "bluetooth-rssi-polling-interval-label": "Polling interval", + "bluetooth-rssi-polling-label": "Bluetooth signal polling", + "disable-discoverability-description": "Hide your device from nearby Bluetooth devices.", + "disable-discoverability-label": "Disable device visibility", + "hide-unnamed-devices-description": "Hide devices that appear only as Bluetooth addresses.", + "hide-unnamed-devices-label": "Hide unnamed devices", + "pin-instructions": "Please enter the PIN code displayed on your device.", + "title": "Connections", + "wifi-header-text": "Use Wi-Fi to see available networks and connect to the internet." + }, + "control-center": { + "cards-desc": "Customize which controls appear in the control center and in what order.", + "desc": "Configure the control center panel positioning and behavior.", + "open-at-mouse-description": "When right-clicking an empty area of the bar, open the control center near the cursor. When disabled, the position setting above is used instead.", + "open-at-mouse-label": "Open at cursor on right-click", + "position-description": "Choose where the control center panel appears when opened.", + "shortcuts-custom-button-command-description": "The command to execute when the button is clicked.", + "shortcuts-custom-button-enable-on-state-logic-description": "Enable a second icon and 'hot' state based on a check command.", + "shortcuts-custom-button-enable-on-state-logic-label": "Enable on-state logic", + "shortcuts-custom-button-icon-description": "Select an icon from the library.", + "shortcuts-custom-button-on-clicked-label": "Left click command", + "shortcuts-custom-button-on-middle-clicked-description": "Command to execute when the button is middle-clicked.", + "shortcuts-custom-button-on-state-command-description": "Command to execute to check if the button should be in the 'on' state. Returns 0 for on, non-zero for off.", + "shortcuts-custom-button-on-state-command-label": "On-state check command", + "shortcuts-custom-button-on-state-icon-description": "The icon for the button when it's in the 'on' state.", + "shortcuts-custom-button-on-state-icon-label": "On-state icon", + "shortcuts-custom-button-state-checks-add": "Add state check", + "shortcuts-custom-button-state-checks-command": "Command to execute for this state check", + "shortcuts-custom-button-state-checks-label": "State checks", + "shortcuts-custom-button-state-checks-remove": "Remove", + "shortcuts-custom-button-tooltip-description": "The tooltip to show when hovering over the button.", + "shortcuts-custom-button-tooltip-label": "Tooltip", + "shortcuts-title": "Shortcuts widgets", + "system-monitor-disk-path-description": "Select which disk mount point the system monitor card in the control center should monitor.", + "system-monitor-disk-path-label": "System monitor disk path", + "title": "Control Center" + }, + "desktop-widgets": { + "clock-enabled-description": "Show a clock widget on the desktop.", + "clock-enabled-label": "Enable clock widget", + "clock-format-label": "Format", + "clock-height-description": "Height of the clock widget in pixels.", + "clock-height-label": "Height", + "clock-minimal-mode-description": "Use a minimal clock display with time and date.", + "clock-minimal-mode-label": "Minimal mode", + "clock-rounded-corners-description": "Use rounded corners for the widget background.", + "clock-rounded-corners-label": "Rounded corners", + "clock-show-background-description": "Show the background container for the clock widget.", + "clock-show-background-label": "Show background", + "clock-show-date-description": "Display the current date below the time.", + "clock-show-date-label": "Show date", + "clock-show-seconds-description": "Display seconds in the time.", + "clock-show-seconds-label": "Show seconds", + "clock-style-analog": "Analog", + "clock-style-binary": "Binary", + "clock-style-description": "Choose the clock display style.", + "clock-style-digital": "Digital", + "clock-style-label": "Clock style", + "clock-style-minimal": "Minimal", + "clock-width-description": "Width of the clock widget in pixels.", + "cpu-intensive-note": "Widgets marked with ! use more CPU and should be enabled mindfully.", + "edit-mode-button-label": "Enter edit mode", + "edit-mode-controls-explanation": "Left-click & drag: Move or resize the widget.\nRight-click: Open the context menu options.", + "edit-mode-description": "Enable edit mode to move and reposition desktop widgets. When enabled, widgets show a drag outline and can be repositioned.", + "edit-mode-exit-button": "Exit edit mode", + "edit-mode-grid-snap-label": "Grid snap", + "edit-mode-grid-snap-scale-label": "Grid snap scale", + "edit-mode-label": "Edit mode", + "enabled-description": "Enable or disable desktop widgets entirely.", + "enabled-label": "Enable desktop widgets", + "general-desc": "Configure widgets that appear on your desktop.", + "general-title": "Desktop widgets", + "media-player-enabled-description": "Show a media player widget on the desktop.", + "media-player-enabled-label": "Enable media player widget", + "media-player-rounded-corners-description": "Enable rounded corners on the widget edges.", + "media-player-show-album-art-description": "Show the album artwork and track information (title and artist).", + "media-player-show-album-art-label": "Show album art & title", + "media-player-show-background-description": "Show the background container.", + "media-player-show-buttons-description": "Show media control buttons (play/pause, previous, next).", + "media-player-show-buttons-label": "Show buttons", + "media-player-show-visualizer-description": "Show the audio visualizer overlay.", + "media-player-visualizer-type-description": "Choose a visualization type.", + "overview-enabled-description": "Show desktop widgets while the compositor overview is open.", + "overview-enabled-label": "Show in overview", + "system-stat-layout-bottom": "Bottom", + "system-stat-layout-description": "Choose how the legend is displayed relative to the graph.", + "system-stat-layout-label": "Layout", + "system-stat-layout-side": "Side", + "system-stat-rounded-corners-description": "Use rounded corners for the widget background.", + "system-stat-rounded-corners-label": "Rounded corners", + "system-stat-show-background-description": "Show the background container for the system stat widget.", + "system-stat-show-background-label": "Show background", + "system-stat-stat-type-description": "Choose which system statistic to display.", + "system-stat-stat-type-label": "Statistic Type", + "title": "Desktop Widgets", + "weather-enabled-description": "Show a weather widget on the desktop.", + "weather-enabled-label": "Enable weather widget", + "weather-show-background-description": "Show the background container for the weather widget." + }, + "display": { + "monitors-backlight-device-auto-option": "Default", + "monitors-backlight-device-description": "Select a backlight device for this output.", + "monitors-backlight-device-label": "Backlight device", + "monitors-brightness-step-description": "Adjust the step size for brightness changes (scroll wheel and keyboard shortcuts).", + "monitors-brightness-step-label": "Brightness step size", + "monitors-brightness-unavailable-ddc-disabled": "Brightness control unavailable. Enable \"External brightness support\" to control this display's brightness.", + "monitors-brightness-unavailable-generic": "Brightness control is not available for this display.", + "monitors-desc": "Adjust settings brightness for each display.", + "monitors-enforce-minimum-description": "Solves the problem of backlight completely turning off on some displays at 0% brightness.", + "monitors-enforce-minimum-label": "Enforce minimum brightness (1%)", + "monitors-external-brightness-description": "Enable DDCUtil support for controlling brightness on external displays via DDC/CI protocol.", + "monitors-external-brightness-label": "External brightness support", + "monitors-global-brightness-description": "Adjust brightness for all monitors at once.", + "monitors-global-brightness-label": "All monitors", + "monitors-title": "Per-monitor settings", + "night-light-auto-schedule-description": "Based on the sunset and sunrise time in {location} โ€” recommended.", + "night-light-auto-schedule-label": "Automatic scheduling", + "night-light-desc": "Reduce blue light emission to help you sleep better and reduce eye strain.", + "night-light-enable-description": "Apply a warm color filter to reduce blue light emission.", + "night-light-enable-label": "Enable Night Light", + "night-light-force-activation-description": "Ignores the schedule and applies the night filter immediately.", + "night-light-force-activation-label": "Force activation", + "night-light-manual-schedule-description": "Set custom times for sunrise and sunset.", + "night-light-manual-schedule-label": "Manual scheduling", + "night-light-manual-schedule-select-start": "Select start time", + "night-light-manual-schedule-select-stop": "Select stop time", + "night-light-manual-schedule-sunrise": "Sunrise time", + "night-light-manual-schedule-sunset": "Sunset time", + "night-light-temperature-day": "Day", + "night-light-temperature-day-description": "Controls the temperature during daytime.", + "night-light-temperature-description": "Set the color warmth for nighttime and daytime.", + "night-light-temperature-label": "Color temperature", + "night-light-temperature-night": "Night", + "night-light-temperature-night-description": "Controls the temperature during nighttime.", + "title": "Display" + }, + "dock": { + "appearance-background-opacity-description": "Adjust the dock's background opacity.", + "appearance-border-radius-description": "Adjust the dock's border radius.", + "appearance-border-radius-label": "Border radius", + "appearance-colorize-icons-description": "Apply theme colors to dock app icons (non-focused apps only).", + "appearance-colorize-icons-label": "Colorize icons", + "appearance-dead-opacity-description": "Adjust the opacity of app icons that are not running.", + "appearance-dead-opacity-label": "Dead opacity", + "appearance-desc": "Customize the dock's behavior and appearance.", + "appearance-display-auto-hide": "Auto hide", + "appearance-display-description": "Choose how the dock behaves.", + "appearance-display-exclusive": "Exclusive", + "appearance-dock-indicator-description": "Show a small indicator when the dock is hidden.", + "appearance-dock-indicator-label": "Dock indicator", + "appearance-floating-distance-description": "Set the distance between the dock and the edge of the screen.", + "appearance-floating-distance-label": "Dock floating distance", + "appearance-group-apps-description": "Group multiple windows from the same app into one dock entry.", + "appearance-group-apps-label": "Group same apps", + "appearance-group-click-action-cycle": "Cycle windows", + "appearance-group-click-action-description": "Choose what left-click does for grouped apps.", + "appearance-group-click-action-label": "Grouped app click action", + "appearance-group-click-action-list": "Open window list", + "appearance-group-context-menu-mode-description": "Choose how the context menu behaves for grouped apps.", + "appearance-group-context-menu-mode-extended": "Extended", + "appearance-group-context-menu-mode-label": "Grouped app menu mode", + "appearance-group-context-menu-mode-list": "Window list", + "appearance-group-indicator-style-description": "Choose how grouped running indicators display focused window state.", + "appearance-group-indicator-style-dots": "Dots", + "appearance-group-indicator-style-label": "Grouped indicator style", + "appearance-group-indicator-style-number": "Number", + "appearance-hide-show-speed-description": "Adjust the speed of the dock hide/show animation.", + "appearance-hide-show-speed-label": "Hide/show speed", + "appearance-icon-size-description": "Adjust the overall size of the dock.", + "appearance-icon-size-label": "Dock size", + "appearance-inactive-indicators-description": "Display indicator pills for all apps, not just the currently active one.", + "appearance-inactive-indicators-label": "Running indicators", + "appearance-indicator-color-description": "Choose the color of the hidden dock indicator.", + "appearance-indicator-color-label": "Indicator color", + "appearance-indicator-opacity-description": "Adjust the opacity of the hidden dock indicator.", + "appearance-indicator-opacity-label": "Indicator opacity", + "appearance-indicator-thickness-description": "Toggle a thicker hidden dock indicator (6px instead of 3px).", + "appearance-indicator-thickness-label": "Thicker indicator", + "appearance-launcher-icon-description": "Choose the icon used for the dock launcher.", + "appearance-launcher-icon-label": "Launcher icon", + "appearance-launcher-position-description": "Choose where the launcher icon appears in the dock.", + "appearance-launcher-position-end": "End", + "appearance-launcher-position-label": "Launcher position", + "appearance-launcher-position-start": "Start", + "appearance-launcher-use-distro-logo-description": "Use your distribution logo as the dock launcher icon.", + "appearance-launcher-use-distro-logo-label": "Use distro logo instead of icon", + "appearance-pinned-static-description": "Always push pinned app icons to the left in static order.", + "appearance-pinned-static-label": "Static pinned apps", + "appearance-position-description": "Choose where the dock appears on screen.", + "appearance-position-label": "Position", + "appearance-show-launcher-icon-description": "Show the application launcher icon in the dock.", + "appearance-show-launcher-icon-label": "Show app launcher", + "appearance-sit-on-frame-description": "Align the dock inside the frame border instead of sitting on top.", + "appearance-sit-on-frame-label": "Dock sits on frame", + "appearance-type-attached": "Attached", + "appearance-type-description": "Choose between a floating pill or a static bar attached to the edge.", + "appearance-type-floating": "Floating", + "appearance-type-label": "Dock style", + "enabled-description": "Show or hide the dock entirely.", + "enabled-label": "Enable dock", + "monitors-desc": "Show dock on specific monitors. Defaults to all if none are chosen.", + "monitors-only-same-monitor-description": "Show only apps from the monitor where the dock is located.", + "monitors-only-same-monitor-label": "Only apps from same monitor", + "monitors-title": "Monitor display", + "title": "Dock" + }, + "general": { + "copy-settings": "Copy settings", + "fonts-default-description": "Main font used throughout the interface.", + "fonts-default-label": "Default font", + "fonts-default-placeholder": "Select default font...", + "fonts-default-scale-description": "Increase or decrease the size of the standard text.", + "fonts-default-scale-label": "Default font size", + "fonts-default-search-placeholder": "Search font...", + "fonts-desc": "Choose the fonts used throughout the interface.", + "fonts-monospace-description": "Monospaced font used for numbers and stats display.", + "fonts-monospace-label": "Monospaced font", + "fonts-monospace-placeholder": "Select monospace font...", + "fonts-monospace-scale-description": "Increase or decrease the size of the monospaced text.", + "fonts-monospace-scale-label": "Monospaced font size", + "fonts-monospace-search-placeholder": "Search monospace font...", + "fonts-title": "Fonts", + "keybinds-conflict-description": "The key combination is already assigned to {action}.", + "keybinds-conflict-title": "Keybind conflict", + "keybinds-description": "Configure global navigation keys for panels and launcher.", + "keybinds-down": "Move down", + "keybinds-enter": "Confirm / Action", + "keybinds-escape": "Close / Back", + "keybinds-left": "Move left", + "keybinds-modifier-description": "Shortcuts must include a modifier key (Ctrl or Alt).", + "keybinds-modifier-title": "Modifier required", + "keybinds-remove": "Remove / Delete", + "keybinds-right": "Move right", + "keybinds-title": "Navigation keybinds", + "keybinds-up": "Move up", + "language-desc": "Choose your preferred language for the application.", + "language-select-auto-detect": "Automatic", + "language-select-description": "Select the language used in the application's interface.", + "language-select-label": "Application language", + "launch-setup-wizard": "Setup wizard", + "profile-desc": "Edit your user details and avatar.", + "profile-picture-description": "Your profile picture that appears throughout the interface.", + "profile-picture-label": "Profile picture", + "profile-select-avatar": "Select avatar image", + "profile-title": "Profile", + "profile-tooltip": "Profile picture", + "reverse-scrolling-description": "Reverse the interpreted scroll direction", + "reverse-scrolling-label": "Reverse scrolling", + "screen-corners-desc": "Customize screen corner rounding and visual effects.", + "screen-corners-radius-description": "Adjust the rounded corners of the screen.", + "screen-corners-radius-label": "Screen corners radius", + "screen-corners-radius-reset": "Reset screen corners radius", + "screen-corners-show-corners-description": "Display rounded corners on the edge of the screen.", + "screen-corners-show-corners-label": "Show screen corners", + "screen-corners-solid-black-description": "Use solid black instead of the bar background color.", + "screen-corners-solid-black-label": "Solid black corners", + "screen-corners-title": "Screen corners", + "settings-copied": "Settings copied to clipboard", + "smooth-scrolling-description": "Animate list scrolling for a smoother wheel experience.", + "smooth-scrolling-label": "Smooth scrolling", + "tab-basics": "Basics", + "tab-keybinds": "Keybinds" + }, + "hooks": { + "color-generation-description": "Command to be executed when color generation completes (e.g. after wallpaper-based theming).", + "color-generation-label": "Colors generated", + "color-generation-placeholder": "e.g. notify-send \"Colors\" \"Generated\"", + "info-command-info-description": "โ€ข Commands are executed via shell (sh -lc)
โ€ข Commands run in background (detached)
โ€ข Test buttons execute with current values", + "info-parameters-description": "โ€ข Wallpaper hook: $1 = wallpaper path, $2 = screen name, $3 = theme (dark/light)
โ€ข Color generation hook: $1 = theme (dark/light)
โ€ข Theme toggle hook: $1 = true/false (Dark Mode state)
โ€ข Screen lock/unlock hooks: $1 = lock/unlock (screen lock state)
โ€ข Performance mode hooks: No parameters
โ€ข Session hook: $1 = action (shutdown/reboot)", + "info-parameters-label": "Available parameters", + "noctalia-started-description": "Command to execute when Noctalia has finished loading.", + "noctalia-started-label": "Noctalia started", + "noctalia-started-placeholder": "e.g. notify-send 'Noctalia Loaded'", + "performance-mode-disabled-description": "Command to be executed when Noctalia performance mode is disabled.", + "performance-mode-disabled-label": "Performance mode disabled", + "performance-mode-disabled-placeholder": "e.g. notify-send \"Performance\" \"Mode disabled\"", + "performance-mode-enabled-description": "Command to be executed when Noctalia performance mode is enabled.", + "performance-mode-enabled-label": "Performance mode enabled", + "performance-mode-enabled-placeholder": "e.g., notify-send \"Performance\" \"Mode enabled\"", + "screen-lock-description": "Command to be executed when the screen is locked.", + "screen-lock-label": "Screen locked", + "screen-lock-placeholder": "e.g. notify-send \"Screen\" \"Locked\"", + "screen-unlock-description": "Command to be executed when the lock screen is unlocked.", + "screen-unlock-label": "Screen unlocked", + "screen-unlock-placeholder": "e.g. notify-send \"Screen\" \"Unlocked\"", + "session-description": "Command to be executed before shutdown or reboot. Receives action type as $1 (shutdown/reboot).", + "session-label": "Session end", + "session-placeholder": "e.g. notify-send \"Session\" \"$1\"", + "system-hooks-desc": "Configure commands to be executed when system events occur.", + "system-hooks-enable-description": "Enable or disable all hook commands.", + "system-hooks-enable-label": "Enable hooks", + "system-hooks-title": "System hooks", + "theme-changed-description": "Command to be executed when theme toggles between Dark and Light Mode.", + "theme-changed-label": "Theme changed", + "theme-changed-placeholder": "e.g. notify-send \"Theme\" \"Toggled\"", + "title": "Hooks", + "wallpaper-changed-description": "Command to be executed when wallpaper changes.", + "wallpaper-changed-label": "Wallpaper changed", + "wallpaper-changed-placeholder": "e.g. notify-send \"Wallpaper\" \"Changed\"" + }, + "idle": { + "custom-add": "Add command", + "custom-description": "Run a shell command after a period of inactivity.", + "custom-entry-command": "Command", + "custom-entry-delete": "Delete", + "custom-entry-edit": "Edit Custom Command", + "custom-entry-name": "Name", + "custom-entry-name-placeholder": "e.g. Turn off lights", + "custom-entry-new": "New Custom Command", + "custom-entry-timeout": "Inactivity time", + "custom-entry-timeout-format": "{count} second", + "custom-entry-timeout-format-plural": "{count} seconds", + "custom-entry-unnamed": "Unnamed command", + "custom-label": "Custom idle commands", + "enable-description": "Automatically turn off the screen, lock, or suspend after a period of inactivity.", + "enable-label": "Enable idle management", + "fade-duration-description": "Seconds for the fade-to-black animation before each action fires. Any mouse movement cancels the fade.", + "fade-duration-label": "Fade duration", + "lock-description": "Seconds of inactivity before the lock screen activates.", + "lock-label": "Lock screen", + "resume-command-label": "Resume command", + "screen-off-description": "Seconds of inactivity before monitors are turned off.", + "screen-off-label": "Turn off screen", + "status-description": "Idle time as reported by the compositor.", + "status-label": "Idle time", + "suspend-description": "Seconds of inactivity before the system suspends.", + "tab-behavior": "Behavior", + "tab-custom": "Custom", + "timeouts-description": "Set to 0 to disable a stage. Timeouts are paused while Keep Awake is active.", + "timeouts-label": "Timeouts", + "title": "Idle", + "unavailable": "Native idle monitoring is not available on this compositor." + }, + "indicator": { + "default-value": "Default: {value}", + "system-default": "System Default" + }, + "launcher": { + "clipboard-desc": "Access and manage your clipboard history from the launcher.", + "execute-desc": "Configure how applications are launched.", + "execute-title": "Execution", + "settings-annotation-tool-description": "Command to run when clicking the annotate button in clipboard history. The image will be piped to this command.", + "settings-annotation-tool-label": "Annotation tool", + "settings-annotation-tool-placeholder": "e.g. 'gradia', 'satty -f -'", + "settings-auto-paste-description": "Automatically paste the selected clipboard item. Requires wtype.", + "settings-auto-paste-label": "Auto paste", + "settings-clip-chips-description": "Show a tab bar to filter clipboard history by type (Images, Links, Files, Code, etc).", + "settings-clip-chips-label": "Enable Category Chips", + "settings-clip-preview-description": "Show a preview of the clipboard content when using the >clip command.", + "settings-clip-preview-label": "Enable clip preview", + "settings-clip-smart-icons-description": "Show specific icons for links, files, colors, and code instead of a generic clipboard icon.", + "settings-clip-smart-icons-label": "Enable Smart Icons", + "settings-clip-wrap-text-description": "Wrap text in the clipboard list instead of truncating it.", + "settings-clip-wrap-text-label": "Wrap clipboard text", + "settings-clipboard-history-description": "Access previously copied items from the launcher.", + "settings-clipboard-history-label": "Enable clipboard history", + "settings-clipboard-watch-image-description": "Full command string passed to wl-paste for image changes. (requires restart)", + "settings-clipboard-watch-image-label": "Image watch command", + "settings-clipboard-watch-text-description": "Full command string passed to wl-paste for text changes. (requires restart)", + "settings-clipboard-watch-text-label": "Text watch command", + "settings-custom-launch-prefix-description": "Prefix commands with a custom launcher (e.g. 'runapp' for systemd integration).", + "settings-custom-launch-prefix-enabled-description": "Use a custom prefix for launching applications instead of the default method.", + "settings-custom-launch-prefix-enabled-label": "Enable custom launch prefix", + "settings-custom-launch-prefix-label": "Custom launch prefix", + "settings-density-description": "Adjust the size of application icons and the density of the launcher.", + "settings-density-label": "Density", + "settings-desc": "Customize the launcher's behavior and appearance.", + "settings-enable-session-search-description": "Show session actions (lock, shutdown, reboot, etc.) when searching in the launcher.", + "settings-enable-session-search-label": "Enable session search", + "settings-enable-settings-search-description": "Show settings results when searching in the launcher.", + "settings-enable-settings-search-label": "Enable settings search", + "settings-enable-windows-search-description": "Search and focus active windows.", + "settings-enable-windows-search-label": "Enable windows search", + "settings-grid-view-description": "Display items in a grid layout instead of a list.", + "settings-icon-mode-description": "Use native system icons instead of Tabler icons.", + "settings-icon-mode-label": "Use native icons", + "settings-ignore-mouse-input-description": "Disable mouse interaction and scrollwheel in the launcher.", + "settings-ignore-mouse-input-label": "Ignore mouse input", + "settings-overlay-layer-description": "Display the launcher on the overlay layer, above fullscreen windows. When enabled, the launcher will not attach to the bar.", + "settings-overlay-layer-label": "Show above fullscreen", + "settings-position-description": "Choose where the launcher panel appears.", + "settings-show-categories-description": "Show category tabs for filtering applications.", + "settings-show-categories-label": "Show categories", + "settings-show-icon-background-description": "Show a rounded rectangle background behind icons.", + "settings-show-icon-background-label": "Show icon background", + "settings-sort-by-usage-description": "When enabled, frequently launched apps appear first in the list.", + "settings-sort-by-usage-label": "Sort by most used", + "settings-terminal-command-description": "Command to launch a terminal. E.g. 'kitty -e' or 'gnome-terminal --'.", + "settings-terminal-command-label": "Terminal command", + "settings-use-app2unit-description": "Uses an alternative launch method to better manage app processes and prevent issues.", + "settings-use-app2unit-label": "Use App2Unit to launch applications", + "settings-view-mode-description": "Choose the layout for the launcher entries.", + "settings-view-mode-label": "View mode", + "title": "Launcher" + }, + "location": { + "auto-locate-description": "Automatically detect your location periodically using your IP address.", + "auto-locate-label": "Auto-locate", + "calendar-cards-desc": "Organize and enable/disable cards in the calendar panel.", + "calendar-cards-title": "Calendar cards", + "calendar-header-label": "Calendar Header", + "calendar-month-label": "Calendar Month", + "date-time-12hour-format-description": "Display time in 12-hour format on the lock screen and calendar. The bar clock has its own settings.", + "date-time-12hour-format-label": "Use 12-hour time format", + "date-time-desc": "Customize how date and time appear.", + "date-time-first-day-of-week-automatic": "Automatic (use system locale)", + "date-time-first-day-of-week-description": "Choose which day starts the week in the calendar.", + "date-time-first-day-of-week-label": "First day of week", + "date-time-show-events-description": "Display events in the calendar panel.", + "date-time-show-events-label": "Show calendar events", + "date-time-title": "Date & time", + "date-time-use-analog-description": "Show an analog style clock on the calendar window.", + "date-time-use-analog-label": "Use analog style clock", + "date-time-week-numbers-description": "Display the week of the year (e.g. week 38) in the calendar.", + "date-time-week-numbers-label": "Show week numbers", + "geolocate-now-button": "Locate now", + "location-desc": "Get accurate weather and Night Light scheduling by setting your location.", + "location-search-description": "e.g. Toronto, ON", + "location-search-label": "Search for a location", + "location-search-placeholder": "Enter the location name", + "location-title": "Your location", + "weather-desc": "Choose your preferred temperature unit.", + "weather-enabled-description": "Show weather information throughout the interface and fetch weather data. When disabled, all weather elements will be hidden and no network requests will be made.", + "weather-enabled-label": "Enable weather", + "weather-fahrenheit-description": "Display temperature in Fahrenheit instead of Celsius.", + "weather-fahrenheit-label": "Display temperature in Fahrenheit (ยฐF)", + "weather-hide-city-description": "Hide the city name from weather displays throughout the interface.", + "weather-hide-city-label": "Hide city name", + "weather-hide-timezone-description": "Hide the timezone abbreviation from weather displays throughout the interface.", + "weather-hide-timezone-label": "Hide timezone", + "weather-show-effects-description": "Show additional visual effects (like rain, snow, or lightning) on the weather card.", + "weather-show-effects-label": "Display weather effects", + "weather-show-in-calendar-description": "Show the daily weather forecast directly in your calendar view.", + "weather-show-in-calendar-label": "Display weather in calendar", + "weather-talia-mascot-always-description": "Always use Talia emojis instead of tabler icons.", + "weather-talia-mascot-always-label": "Always show Talia weather emojis" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "When fprintd (fingerprint authentication) is active, this option lets you still login using your password instead of a fingerprint", + "allow-password-with-fprintd-label": "Allow password login with fprintd", + "auto-start-auth-description": "Automatically starts fingerprint authentication without requiring a key press or button click.", + "auto-start-auth-label": "Auto-start authentication", + "clock-format-description": "Customize the clock format using date/time syntax tokens.", + "clock-format-label": "Clock format", + "clock-style-analog": "Analog", + "clock-style-custom": "Custom", + "clock-style-description": "Choose the visual style of the clock on the lock screen.", + "clock-style-digital": "Digital", + "clock-style-label": "Clock style", + "compact-lockscreen-description": "Show only the login input and system controls, hiding weather and media widgets.", + "compact-lockscreen-label": "Compact lock screen", + "enable-lockscreen-media-controls-description": "Show interactive media playback controls on the lock screen.", + "enable-lockscreen-media-controls-label": "Lock screen media controls", + "lock-on-suspend-description": "Automatically lock the screen when suspending the system.", + "lock-on-suspend-label": "Lock on suspend", + "lock-screen-animations-description": "Enable or disable lockscreen animations.", + "lock-screen-animations-label": "Lockscreen animations", + "lock-screen-blur-strength-description": "Applies a blur effect to the lock screen wallpaper.", + "lock-screen-blur-strength-label": "Lock screen blur strength", + "lock-screen-tint-strength-description": "Applies a tint overlay to the lock screen wallpaper.", + "lock-screen-tint-strength-label": "Lock screen tint strength", + "monitors-desc": "Show lock screen on specific monitors. Defaults to all if none are chosen.", + "password-chars-description": "Cute icons used to hide your password.", + "password-chars-label": "Random password icons", + "show-hibernate-description": "Show the option 'hibernate' in the power controls.", + "show-hibernate-label": "Show hibernate", + "show-session-buttons-description": "Allow access to power settings from the lock screen.", + "show-session-buttons-label": "Power controls", + "title": "Lock Screen" + }, + "network": { + "bluetooth-description": "Activate Bluetooth management.", + "bluetooth-rssi-polling-description": "Periodically sample RSSI for connected devices via bluetoothctl. May not be available for all devices; uses minimal resources when enabled.", + "bluetooth-rssi-polling-label": "Bluetooth signal polling", + "desc": "Manage Wi-Fi and Bluetooth connections.", + "wifi-description": "Manage wireless networks (requires NetworkManager)." + }, + "notifications": { + "duration-critical-urgency-description": "How long critical priority notifications stay visible.", + "duration-critical-urgency-label": "Critical urgency", + "duration-desc": "Configure how long notifications stay visible based on their urgency level.", + "duration-low-urgency-description": "How long low priority notifications stay visible.", + "duration-low-urgency-label": "Low urgency", + "duration-normal-urgency-description": "How long normal priority notifications stay visible.", + "duration-normal-urgency-label": "Normal urgency", + "duration-respect-expire-description": "Use the expire timeout set in the notification.", + "duration-respect-expire-label": "Respect expire timeout", + "duration-title": "Notification duration", + "history-clear-dismiss-description": "Clear notification from history when dismissed.", + "history-clear-dismiss-label": "Clear on dismissed", + "history-critical-urgency-description": "Save critical priority notifications to history.", + "history-critical-urgency-label": "Save critical urgency to history", + "history-desc": "Control which notifications are saved to history based on their urgency level.", + "history-low-urgency-description": "Save low priority notifications to history.", + "history-low-urgency-label": "Save low urgency to history", + "history-normal-urgency-description": "Save normal priority notifications to history.", + "history-normal-urgency-label": "Save normal urgency to history", + "monitors-desc": "Show notification on specific monitors. Defaults to all if none are chosen.", + "rules-action-block": "Block", + "rules-action-block-desc": "Skips completely.", + "rules-action-hide": "Hide", + "rules-action-hide-desc": "No popup, no sound, adds to history.", + "rules-action-label": "Action", + "rules-action-mute": "Mute", + "rules-action-mute-desc": "No sound, still shows popup and in history.", + "rules-add": "Add rule", + "rules-delete": "Delete rule", + "rules-description": "Match app name or content. Rules are checked in order, and the first match is applied.", + "rules-edit": "Edit rule", + "rules-label": "Filter rules", + "rules-pattern-label": "Pattern", + "rules-pattern-placeholder": "firefox, discord, or /regex/", + "rules-tab": "Rules", + "settings-always-on-top-description": "Display notifications above fullscreen windows and other layers.", + "settings-background-opacity-description": "Adjust the opacity of notification backgrounds.", + "settings-density-description": "Choose the notification card density.", + "settings-density-label": "Density", + "settings-desc": "Configure notifications appearance and behavior.", + "settings-do-not-disturb-description": "Disable all notification popups when enabled.", + "settings-enabled-description": "Enable or disable the notification daemon, requires a restart of Noctalia shell.", + "settings-enabled-label": "Enable notifications", + "settings-location-description": "Where notifications appear on screen.", + "settings-markdown-description": "Render notification content using Markdown formatting.", + "settings-markdown-label": "Enable Markdown", + "sounds-desc": "Configure notification sound effects and volume.", + "sounds-enabled-description": "Enable sound effects for incoming notifications.", + "sounds-enabled-label": "Enable notification sounds", + "sounds-excluded-apps-description": "Skip playing the configured notification sound for specific applications that have their own built-in sounds.", + "sounds-excluded-apps-label": "Excluded applications", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "Path to the sound file played for critical priority notifications.", + "sounds-files-critical-label": "Critical urgency sound", + "sounds-files-critical-select-title": "Select critical urgency sound file", + "sounds-files-desc": "Configure custom sound files for different notification urgency levels.", + "sounds-files-low-description": "Path to the sound file played for low priority notifications.", + "sounds-files-low-label": "Low urgency sound", + "sounds-files-low-select-title": "Select low urgency sound file", + "sounds-files-normal-description": "Path to the sound file played for normal priority notifications.", + "sounds-files-normal-label": "Normal urgency sound", + "sounds-files-normal-select-title": "Select normal urgency sound file", + "sounds-files-placeholder": "Enter path to sound file", + "sounds-files-select-file": "Select sound file", + "sounds-files-title": "Sound files", + "sounds-files-unified-description": "Path to the sound file played for notifications.", + "sounds-files-unified-label": "Notification sound", + "sounds-files-unified-select-title": "Select notification sound file", + "sounds-separate-description": "Use different sound files for low, normal, and critical priority notifications.", + "sounds-separate-label": "Use different sounds per priority", + "sounds-title": "Sound settings", + "sounds-unavailable-description": "Install Qt6 Multimedia to enable notification sounds.", + "sounds-unavailable-label": "Notification sounds unavailable", + "sounds-volume-description": "Adjust the volume level for notification sounds.", + "sounds-volume-label": "Sound volume", + "toast-battery-description": "Show a warning when the battery level falls below this percentage.", + "toast-battery-label": "Battery warning", + "toast-desc": "Configure toast appearance and behavior.", + "toast-keyboard-description": "Show a toast when the keyboard layout changes.", + "toast-keyboard-label": "Keyboard layout", + "toast-media-description": "Show a toast when media playback state changes.", + "toast-media-label": "Media" + }, + "osd": { + "always-on-top-description": "Display OSD above fullscreen windows and other layers.", + "always-on-top-label": "Always on top", + "background-opacity-description": "Controls the transparency of the OSD background.", + "background-opacity-label": "Background opacity", + "description": "Configure on-screen indicators such as volume and brightness overlays.", + "duration-auto-hide-description": "Adjust the time before OSD disappears.", + "duration-auto-hide-label": "Auto-hide after", + "duration-desc": "How long OSD remains visible before hiding automatically.", + "duration-title": "Auto-hide timeout", + "enabled-description": "Show volume and brightness changes in real-time.", + "enabled-label": "Enable on-screen display", + "events-desc": "Select which events trigger the on-screen display.", + "general-desc": "Configure visibility and behavior of OSD.", + "location-description": "Where on-screen displays appear.", + "monitors-desc": "Show OSD on specific monitors. Defaults to all if none are chosen.", + "title": "On-Screen Display", + "types-brightness-description": "Show OSD when screen brightness changes.", + "types-brightness-label": "Brightness", + "types-custom-text-description": "Show OSD for custom text messages from IPC.", + "types-custom-text-label": "Custom Text", + "types-desc": "Select the events that trigger the OSD.", + "types-input-volume-description": "Show OSD when microphone volume changes.", + "types-input-volume-label": "Input volume", + "types-lockkey-description": "Show OSD when Caps Lock, Num Lock, or Scroll Lock are toggled.", + "types-lockkey-label": "Lock keys", + "types-media-description": "Show OSD when media playback state changes (play, pause, skip).", + "types-media-label": "Media playback", + "types-title": "OSD trigger events", + "types-volume-description": "Show OSD when audio output volume changes.", + "types-volume-label": "Output volume" + }, + "plugins": { + "auto-update": "Auto-update plugins", + "auto-update-description": "Automatically update all plugins when the shell starts.", + "available-description": "Browse and install plugins from configured sources.", + "available-label": "Available plugins", + "available-no-plugins-description": "Check your plugin sources or refresh the list.", + "available-no-plugins-label": "No plugins available", + "check-for-updates": "Check for updates", + "checking-for-updates": "Checking for updates...", + "collision-already-installed": "This plugin is already installed", + "collision-custom-version-exists": "A custom version from \"{source}\" is already installed", + "collision-official-version-exists": "The official version of this plugin is already installed", + "development-disable": "Disable development mode for this plugin", + "development-enable": "Enable development mode for this plugin", + "filter-downloaded": "Downloaded", + "filter-not-downloaded": "Not Downloaded", + "filter-tags-description": "Filter plugins by category or download status.", + "filter-tags-label": "Tags", + "hot-reload-description": "Automatically reload plugins when their files change. Useful for plugin development.", + "hot-reload-label": "Hot reload (dev mode)", + "hot-reloaded": "Reloaded plugin: {name}", + "install-error": "Failed to install: {error}", + "install-incompatible": "{plugin} requires Noctalia v{version} or higher", + "install-success": "Successfully installed {plugin}", + "installed-description": "Manage and configure all locally installed plugins.", + "installed-label": "Installed plugins", + "installed-no-plugins-description": "Install plugins from the \"Available\" section.", + "installed-no-plugins-label": "No plugins installed", + "installing": "Installing {plugin}...", + "notify-updates": "Plugin update notifications", + "notify-updates-description": "Show a notification popup when new plugin updates are available.", + "open-plugin-page": "Open plugin page", + "open-plugins-tab": "Open plugins settings", + "plugin-settings-title": "{plugin} Settings", + "refresh-refreshing": "Refreshing plugins list...", + "refresh-tooltip": "Refresh available plugins", + "settings-error-not-loaded": "Plugin not loaded", + "settings-saved": "Plugin settings saved", + "settings-tooltip": "Plugin settings", + "source-custom": "Custom source", + "sources-add-custom": "Add custom repository", + "sources-add-dialog-description": "Add a GitHub repository as a plugin source.", + "sources-add-dialog-error": "Failed to add plugin source", + "sources-add-dialog-name": "Repository name", + "sources-add-dialog-name-placeholder": "My Custom Plugins", + "sources-add-dialog-success": "Plugin source added successfully", + "sources-add-dialog-title": "Add plugin source", + "sources-add-dialog-url": "Repository URL", + "sources-description": "Manage plugin repositories.", + "sources-placeholder": "My cool repository", + "sources-remove-tooltip": "Remove plugin source", + "title": "Plugins", + "translations-reloaded": "Reloaded translations: {name}", + "uninstall-dialog-description": "Are you sure you want to uninstall {plugin}? This will remove all plugin data.", + "uninstall-dialog-title": "Uninstall plugin", + "uninstall-error": "Failed to uninstall: {error}", + "uninstall-success": "Successfully uninstalled {plugin}", + "uninstalling": "Uninstalling {plugin}...", + "update-all": "Update All ({count})", + "update-all-success": "All plugins updated successfully", + "update-available": "New plugin update available", + "update-available-plural": "Plugin updates available ({count})", + "update-error": "Failed to update plugin: {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (requires Noctalia v{required})", + "update-success": "Updated {plugin} to v{version}", + "update-version": "v{current} โ†’ v{new}", + "updating": "Updating..." + }, + "region": { + "title": "Region" + }, + "session-menu": { + "countdown-duration-description": "Set how long the countdown timer lasts before executing power actions.", + "countdown-duration-label": "Countdown duration", + "enable-countdown-description": "Show a countdown timer before executing power actions.", + "enable-countdown-label": "Enable countdown timer", + "entries-desc": "Customize which power actions appear in the session menu and in what order.", + "entries-title": "Power actions", + "entry-settings-command-description": "Custom command to execute for this action. Leave empty to use the default system command.", + "entry-settings-command-placeholder": "e.g. systemctl poweroff", + "entry-settings-default-command-lock": "Internal lock screen (no command)", + "entry-settings-default-command-logout": "Internal logout (no command)", + "entry-settings-default-info-description": "If no custom command is specified, the default system command will be used.", + "entry-settings-default-info-label": "Default command", + "entry-settings-keybind-description": "Assign a key combination to trigger this action when the session menu is open.", + "entry-settings-keybind-placeholder": "Press Record to assign a key...", + "entry-settings-keybind-recording": "Recording... Press any key combination", + "entry-settings-title": "Configure {entry}", + "entry-settings-tooltip": "Configure command", + "general-desc": "Configure the session menu panel behavior and appearance.", + "large-buttons-layout-description": "Choose how session menu buttons are displayed.", + "large-buttons-layout-label": "Large buttons layout", + "large-buttons-style-description": "Display the session menu with large buttons in a grid layout.", + "large-buttons-style-label": "Large buttons style", + "position-description": "Choose where the session menu panel appears when opened.", + "show-header-description": "Display the title and close button at the top of the session menu.", + "show-header-label": "Show header", + "show-keybinds-description": "Display keybind hints on session options.", + "show-keybinds-label": "Show keybinds" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Keep desktop widgets visible while Noctalia performance mode is enabled.", + "noctalia-performance-disable-desktop-widgets-label": "Enable desktop widgets in performance mode", + "noctalia-performance-disable-wallpaper-description": "Keep desktop, overview, and lock screen wallpapers visible while Noctalia performance mode is enabled.", + "noctalia-performance-disable-wallpaper-label": "Enable wallpaper rendering in performance mode", + "title": "System" + }, + "system-monitor": { + "critical-color-label": "Critical color", + "custom-highlight-colors-title-label": "Custom highlight colors", + "disk-available-label": "Disk available", + "disk-section-label": "Disk usage", + "enable-dgpu-monitoring-description": "Warning: This will wake up your discrete GPU (NVIDIA/AMD), which may significantly impact battery life on laptops with hybrid graphics.", + "enable-dgpu-monitoring-label": "Enable discrete GPU monitoring", + "external-monitor-description": "Enter the command or application path to launch when activating the external system monitor application.", + "external-monitor-label": "External system monitor command", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "Configure the system monitor behavior and appearance.", + "gpu-section-label": "GPU temperature", + "highlight-colors-section-label": "Highlight colors", + "polling-interval-label": "Polling interval", + "polling-section-description": "Configure how often each system metric is updated.", + "polling-section-label": "Polling intervals", + "threshold-critical": "Critical threshold", + "threshold-warning": "Warning threshold", + "thresholds-section-description": "Adjust warning/critical thresholds for each system metric.", + "use-custom-highlight-colors-description": "When disabled, theme default highlight colors are used.", + "use-custom-highlight-colors-label": "Use custom highlight colors", + "warning-color-label": "Warning color" + }, + "user-interface": { + "allow-panels-without-bar-description": "When enabled, panels can open on any screen. When disabled, panels will only open on screens that have a bar, which can reduce memory usage.", + "allow-panels-without-bar-label": "Allow panels on screens without a bar", + "animation-disable-description": "Disable all animations for a faster, more responsive experience.", + "animation-disable-label": "Disable UI animations", + "animation-speed-description": "Adjust global animation speed.", + "animation-speed-label": "Animation speed", + "animation-speed-reset": "Reset animation speed", + "appearance-desc": "Customize visual elements like tooltips, borders, and shadows.", + "blur-behind-description": "Blurs the area behind bars and panels using the compositor blur protocol.", + "blur-behind-label": "Blur behind", + "box-border-description": "Display an outline around content areas.", + "box-border-label": "Container outline", + "box-border-radius-description": "Adjusts the corner roundness of major layout sections, such as sidebars, cards, and content panels.", + "box-border-radius-label": "Container radius", + "control-border-radius-description": "Controls the curvature of interactive elements, including buttons, toggles, and text fields.", + "control-border-radius-label": "Input radius", + "desc": "Customize the look, feel, and behavior of the interface.", + "dim-desktop-description": "Dim the desktop when panels or menus are open.", + "dim-desktop-label": "Dim desktop", + "dimmer-opacity-description": "Set the opacity level for desktop dimming.", + "dimmer-opacity-label": "Dimmed desktop opacity", + "panel-background-opacity-description": "Set the background opacity for all panels (bar, launcher, settings, etc...).", + "panel-background-opacity-label": "Panel background opacity", + "panels-attached-to-bar-description": "Panels lock to the bar and screen edges, creating a seamless look with stylish inverted corners.", + "panels-attached-to-bar-label": "Snap panels to edges", + "panels-overlay-description": "Ensures panels and the bar remain visible, even over fullscreen applications.", + "panels-overlay-label": "Keep panels & bar on top", + "scaling-description": "Changes the size of the general user interface, excluding the bar.", + "scaling-label": "Interface scaling", + "scrollbar-always-visible-description": "Keep scrollbars visible whenever content is scrollable, instead of only showing them on hover.", + "scrollbar-always-visible-label": "Always show scrollbars", + "settings-panel-header": "Settings panel", + "settings-panel-mode-description": "Choose settings layout (may require reopening).", + "settings-panel-mode-label": "Settings panel mode", + "settings-panel-sidebar-card-style-description": "Wraps the settings sidebar in a filled background with rounded corners.", + "settings-panel-sidebar-card-style-label": "Sidebar card style", + "shadows-description": "Enables drop shadows under bars and panels.", + "shadows-direction-description": "Choose where the shadow is cast to.", + "shadows-direction-label": "Shadow direction", + "shadows-label": "Drop shadows", + "title": "User Interface", + "tooltips-description": "Enable or disable tooltips throughout the interface.", + "tooltips-label": "Show tooltips", + "translucent-widgets-description": "Make buttons, tabs, and other widgets inside panels semi-transparent.", + "translucent-widgets-label": "Translucent widgets" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "Alphabetical", + "automation-change-mode-description": "Choose how wallpapers are selected when changing automatically.", + "automation-change-mode-label": "Change mode", + "automation-custom-interval-description": "Enter time as HH:MM (e.g. 01:30).", + "automation-custom-interval-label": "Custom interval", + "automation-interval-description": "How often to change wallpapers automatically.", + "automation-interval-label": "Wallpaper interval", + "automation-random-wallpaper-description": "Schedule random wallpaper changes at regular intervals.", + "automation-scheduled-change-description": "Automatically change wallpapers at regular intervals.", + "automation-scheduled-change-label": "Scheduled change", + "look-feel-edge-smoothness-description": "Applies a soft, feathered effect to the edge of transitions.", + "look-feel-edge-smoothness-label": "Soften transition edge", + "look-feel-fill-color-description": "Choose a fill color that may appear behind the wallpaper.", + "look-feel-fill-mode-description": "Select how the image should scale to match your monitor's resolution.", + "look-feel-fill-mode-label": "Fill mode", + "look-feel-skip-startup-transition-description": "Skip the wallpaper animation when the shell starts.", + "look-feel-skip-startup-transition-label": "Skip startup transition", + "look-feel-title": "Look & feel", + "look-feel-transition-duration-description": "Duration of transition animations in seconds.", + "look-feel-transition-duration-label": "Transition duration", + "look-feel-transition-type-description": "Animation type when switching between wallpapers.", + "look-feel-transition-type-label": "Transition type", + "settings-clear-cache-button": "Clear cache", + "settings-clear-cache-description": "Clear cached resized wallpapers to free disk space.", + "settings-clear-cache-label": "Wallpaper cache", + "settings-clear-cache-toast": "Wallpaper cache cleared", + "settings-desc": "Control how wallpapers are managed and displayed.", + "settings-enable-management-description": "Manage wallpapers with Noctalia. Uncheck if you prefer using another application.", + "settings-enable-management-label": "Enable wallpaper management", + "settings-enable-overview-description": "Applies a blurred and dimmed wallpaper to the overview screen.", + "settings-enable-overview-label": "Enable overview wallpaper", + "settings-folder-description": "Path to your main wallpaper folder.", + "settings-folder-label": "Wallpaper folder", + "settings-hide-wallpaper-filenames-tooltip-hide": "Hide filenames", + "settings-hide-wallpaper-filenames-tooltip-show": "Show filenames", + "settings-monitor-specific-description": "Set a different wallpaper folder for each monitor.", + "settings-monitor-specific-label": "Monitor-specific directories", + "settings-monitor-specific-tooltip": "Monitor wallpaper folder", + "settings-overview-blur-strength-description": "Applies the blur strength to the overview.", + "settings-overview-blur-strength-label": "Overview blur strength", + "settings-overview-tint-description": "Applies the tint strength to the overview.", + "settings-overview-tint-label": "Overview tint strength", + "settings-recursive-search-description": "Also search for wallpapers in subfolders of the wallpaper directory.", + "settings-recursive-search-label": "Search subfolders", + "settings-select-monitor-folder": "Select monitor wallpaper folder", + "settings-selector-description": "Choose your wallpaper.", + "settings-selector-position-description": "Choose where the wallpaper selector panel appears.", + "settings-show-hidden-files-tooltip-hide": "Hide hidden files", + "settings-show-hidden-files-tooltip-show": "Show hidden files", + "settings-title": "Wallpaper settings", + "settings-use-original-images-description": "Skip resizing wallpapers before display. Saves disk space and reduces CPU usage during wallpaper changes, but may use more memory for very large images.", + "settings-use-original-images-label": "Use original images", + "settings-view-mode-description": "Choose how wallpapers are displayed from your directory.", + "settings-view-mode-label": "Viewing mode", + "view-mode-browse": "Browse directories", + "view-mode-cycle-tooltip": "View mode: {mode} (click to change)", + "view-mode-recursive": "Flattened subdirectories", + "view-mode-single": "Root directory" + } + }, + "placeholders": { + "add-new-keybind": "Add a new keybind", + "command-example": "echo \"Hello World\"", + "enter-command": "Enter command to execute (app or custom script)", + "enter-ipc-identifier": "Enter unique identifier for IPC commands", + "enter-path": "Enter path...", + "enter-text-to-collapse": "e.g. 'nothing is playing'. Use /regex/ for patterns.", + "enter-tooltip": "Enter tooltip", + "enter-width-pixels": "Enter width in pixels", + "keybind-recording": "Recording keybind...", + "search": "Search...", + "search-icons": "e.g. noctalia, niri, battery, cloud", + "search-launcher": "Search entries... or use > for commands", + "search-wallhaven": "Search Wallhaven...", + "search-wallpapers": "Type to filter wallpapers..." + }, + "positions": { + "bottom": "Bottom", + "bottom-center": "Bottom center", + "bottom-left": "Bottom left", + "bottom-right": "Bottom right", + "center": "Center", + "center-left": "Left center", + "center-right": "Right center", + "close-to-bar": "Close to bar button", + "follow-bar": "Follow bar", + "left": "Left", + "right": "Right", + "top": "Top", + "top-center": "Top center", + "top-left": "Top left", + "top-right": "Top right" + }, + "session-menu": { + "action-in-seconds": "{action} in {seconds} seconds...", + "cancel-timer": "Cancel timer", + "title": "Session Menu" + }, + "setup": { + "all-done": "All Done!", + "appearance": { + "subheader": "Choose Dark Mode and color sources (wallpaper or predefined)." + }, + "customize": { + "header": "Customize your experience", + "subheader": "Adjust bar position, density, scaling and more." + }, + "skip-setup": "Skip Setup", + "telemetry-wizard-done": "Got it!", + "telemetry-wizard-note": "You're in control โ€” enable or disable this anytime in settings", + "telemetry-wizard-subtitle": "We've added anonymous analytics to help improve Noctalia", + "telemetry-wizard-title": "Privacy Update", + "wallpaper": { + "choose-dir": "Choose a directory containing your wallpaper images", + "dir-browse": "Browse for wallpaper folder", + "dir-description": "Choose the folder containing your wallpapers.", + "dir-label": "Wallpaper directory", + "dir-select-title": "Select wallpaper folder", + "header": "Choose your wallpaper", + "no-dir": "No wallpaper directory selected", + "no-valid": "No valid image files found in: {dir}", + "none-in-dir": "No wallpapers found in directory", + "preview-error": "Failed to load image", + "select-prompt": "Select a wallpaper below", + "subheader": "Set the mood with a beautiful background." + }, + "welcome": "Welcome", + "welcome-note": "Just a few basics to get you started โ€” full options are in the settings", + "welcome-subtitle": "Let's make your desktop uniquely yours", + "welcome-title": "Welcome to Noctalia!" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "No media player detected", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "Unknown app", + "uptime": "Uptime: {uptime}", + "user-requested": "User requested", + "welcome-back": "Welcome back,", + "widget-settings-title": "{widget} Settings" + }, + "system-monitor": { + "core-usage": "Core {id} usage", + "cpu-temp": "CPU temp", + "cpu-usage": "CPU usage", + "disk": "Disk", + "download-speed": "Download speed", + "gpu-temp": "GPU temp", + "load-average": "Load average", + "title": "System Monitor", + "upload-speed": "Upload speed" + }, + "toast": { + "airplane-mode": { + "description": "Disable all wireless communications.", + "title": "Airplane Mode" + }, + "battery": { + "critical": "Critical battery", + "critical-desc": "Battery is at {percent}% โ€” please connect the charger immediately", + "low": "Low battery", + "low-desc": "Battery is at {percent}% โ€” please connect the charger" + }, + "bluetooth": { + "address-copied": "Address copied to clipboard", + "auto-connect-disabled": "Auto-connect disabled", + "auto-connect-enabled": "Auto-connect enabled", + "auto-connecting": "Connecting to {count} device(s)...", + "confirm-code": "Confirm code {value} on the other device.", + "connect-failed": "Failed to connect to device", + "disconnect-failed": "Failed to disconnect from device", + "discoverable-change-failed": "Failed to change discoverable state", + "discoverable-disabled": "Discoverable disabled", + "discoverable-enabled": "Discoverable enabled", + "display-code": "Enter code {value} on the other device.", + "forget-failed": "Failed to forget device", + "pair-failed": "Failed to pair device", + "passkey-required": "Passkey required by the device.", + "pincode-required": "PIN code required by the device.", + "state-change-failed": "Failed to change Bluetooth state" + }, + "clipboard": { + "long-text": "Long text", + "unavailable": "Clipboard history unavailable", + "unavailable-desc": "The 'cliphist' application is not installed. Please install it to use clipboard history features" + }, + "custom-command-failed": { + "description": "Command failed: {command}\nExit code: {code}", + "title": "Custom command failed" + }, + "do-not-disturb": { + "disabled": "Do Not Disturb disabled", + "disabled-desc": "Showing all notifications", + "enabled": "Do Not Disturb enabled", + "enabled-desc": "You'll find these notifications in your history" + }, + "donation-opened": "Donation page opened in your browser", + "internet-limited": "Connected without internet", + "keyboard-layout": { + "changed": "Keyboard layout changed to {layout}", + "title": "Keyboard" + }, + "missing-control-center": { + "description": "The control center widget has been removed from the bar. To access it from the bar again, you will need to re-add the widget. You can open it with right clicking on the bar too", + "label": "Last control center widget removed" + }, + "night-light": { + "forced": "Forced activation", + "normal": "Normal mode", + "not-installed": "wlsunset not installed" + }, + "noctalia-performance": { + "disabled": "Performance mode disabled", + "enabled": "Performance mode enabled", + "label": "Noctalia Performance" + }, + "power-profile": { + "changed": "Power profile changed", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "Encountered an error while processing templates", + "title": "Templating processing failed" + }, + "vpn": { + "connected": "Connected to '{name}'", + "disconnected": "Disconnected from '{name}'" + }, + "wallpaper-colors": { + "disabled": "Wallpaper colors disabled", + "enabled": "Wallpaper colors enabled", + "label": "Wallpaper colors" + }, + "wifi": { + "connected": "Connected to '{ssid}'", + "connection-failed": "Connection failed", + "connection-timeout": "Connection timeout", + "disconnected": "Disconnected from '{ssid}'", + "incorrect-password": "Incorrect password", + "network-not-found": "Network not found" + } + }, + "tooltips": { + "add-widget": "Add widget", + "auto-connect": "Toggle auto-connection for this device", + "bluetooth-auto-connect-off": "Auto-connect is off", + "bluetooth-auto-connect-on": "Auto-connect is on", + "bluetooth-devices": "Bluetooth devices", + "brightness-at": "Brightness: {brightness}%", + "click-to-start-recording": "Screen recorder (start recording)", + "click-to-stop-recording": "Screen recorder (stop recording)", + "collapse": "Collapse sidebar", + "copy-address": "Copy address", + "delete-notification": "Delete notification", + "dismiss-notification": "Dismiss notification", + "do-not-disturb-enabled": "Do Not Disturb", + "expand": "Expand sidebar", + "forget-network": "Forget network", + "grid-view": "Grid view", + "hidden-files-hide": "Hidden files", + "hide-unnamed-devices": "Hide unnamed devices", + "home": "Home directory", + "input-muted": "Toggle input mute", + "keep-awake": "Keep Awake", + "keyboard-layout": "{layout} keyboard layout", + "list-view": "List view", + "manage-vpn": "VPN connections", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "Maximum widgets reached", + "microphone-volume-at": "Microphone volume: {volume}%", + "move-to-section": "Move to {section}", + "mute": "Mute", + "next-media": "Next track", + "next-month": "Next month", + "night-light-not-installed": "Night Light (not available)", + "noctalia-performance-enabled": "Noctalia performance mode", + "open-annotation-tool": "Open with annotation tool", + "open-control-center": "Control center", + "open-notification-history-enable-dnd": "Notification history", + "open-settings": "Settings", + "open-tray-dropdown": "System tray", + "output-muted": "Toggle output mute", + "power-profile": "{profile} power profile", + "previous-media": "Previous track", + "previous-month": "Previous month", + "refresh-devices": "Refresh devices", + "refresh-wallhaven": "Refresh Wallhaven results", + "refresh-wallpaper-list": "Refresh wallpaper list", + "remove-widget": "Remove widget", + "search-close": "Close search", + "session-menu": "Session menu", + "show-all-devices": "Show all devices", + "switch-to-dark-mode": "Dark Mode", + "switch-to-light-mode": "Light Mode", + "unmute": "Unmute", + "up": "Parent directory", + "volume-at": "Output volume: {volume}%", + "wallpaper-selector": "Wallpaper selector" + }, + "wallpaper": { + "browse": { + "empty-directory": "This directory is empty.", + "go-root": "Go to wallpaper root", + "go-up": "Go to parent folder", + "go-up-hint": "Use the back button to navigate up." + }, + "configure-directory": "Configure your wallpaper directory with images.", + "fill-modes": { + "crop": "Crop (Fill)", + "fit": "Fit (Contain)", + "repeat": "Repeat (Tile)", + "stretch": "Stretch" + }, + "no-match": "No match found.", + "no-wallpaper": "No wallpaper found.", + "panel": { + "apikey-help": "An API key is required to access NSFW content.", + "apikey-label": "API Key", + "apikey-managed-by-env": "Managed via NOCTALIA_WALLHAVEN_API_KEY environment variable.", + "apikey-placeholder": "Enter your Wallhaven API Key", + "appearance-dark-tab": "Dark", + "appearance-light-tab": "Light", + "categories-anime": "Anime", + "categories-label": "Categories", + "categories-people": "People", + "color-extraction-disabled": "Use wallpaper color extraction", + "color-extraction-enabled": "Use predefined color schemes", + "header-devices-apply-all-tooltip": "Choose wallpaper per monitor", + "header-devices-per-monitor-tooltip": "Apply to all monitors", + "header-link-light-dark-tooltip": "Keep wallpaper when switching theme", + "header-separate-light-dark-tooltip": "Auto-switch wallpaper with theme", + "header-sun-linked-tooltip": "Separate light and dark wallpapers", + "header-sun-separate-tooltip": "Same wallpaper for light and dark", + "order-asc": "Ascending", + "order-desc": "Descending", + "order-label": "Order", + "purity-label": "Content filter", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "Sketchy", + "ratios-all-portrait": "All portrait", + "ratios-all-wide": "All wide", + "ratios-any": "Any", + "ratios-label": "Aspect ratio", + "resolution-atleast": "At least", + "resolution-exact": "Exact", + "resolution-label": "Resolution", + "resolution-mode-label": "Mode", + "solid-color-tooltip": "Solid color background", + "sort-date-asc": "Sort by oldest first", + "sort-date-desc": "Sort by newest first", + "sort-name-asc": "Sort by name (A-Z)", + "sort-name-desc": "Sort by name (Z-A)", + "sort-random": "Sort by random", + "sorting-date-added": "Date added", + "sorting-favorites": "Favorites", + "sorting-label": "Sort by", + "sorting-relevance": "Relevance", + "sorting-toplist": "Toplist", + "sorting-views": "Views", + "source-label": "Source", + "source-wallhaven": "Wallhaven", + "title": "Wallpaper Selector", + "wallhaven-settings-title": "Wallhaven Settings" + }, + "transitions": { + "disc": "Disc", + "fade": "Fade", + "honeycomb": "Honeycomb", + "pixelate": "Pixelate", + "stripes": "Stripes", + "wipe": "Wipe" + }, + "try-different-search": "Try a different search query.", + "wallhaven": { + "loading": "Loading wallpapers...", + "no-results": "No wallpapers found. Try a different search query.", + "page": "{current} of {total}", + "page-prefix": "Page", + "page-suffix": "of {total}" + } + }, + "weather": { + "clear-sky": "Clear sky", + "drizzle": "Drizzle", + "fog": "Fog", + "mainly-clear": "Mainly clear", + "overcast": "Overcast", + "partly-cloudy": "Partly cloudy", + "rain-showers": "Rain showers", + "snow": "Snow", + "thunderstorm": "Thunderstorm" + }, + "widgets": { + "color-picker": { + "palette-description": "Choose from a wide range of predefined colors.", + "palette-label": "Palette", + "palette-theme-colors": "Quick access to your theme's color palette.", + "title": "Color picker" + }, + "datetime-tokens": { + "ampm-lowercase": "am/pm in lowercase", + "ampm-uppercase": "AM/PM in uppercase", + "common-12hour-time-minutes": "12-hour time with minutes", + "common-24hour-time-minutes": "24-hour time with minutes", + "common-24hour-time-seconds": "24-hour time with seconds", + "common-european-date": "European date format", + "common-iso-date": "ISO date format", + "common-us-date": "US date format", + "common-weekday-date": "Weekday with date", + "common-weekday-month-day": "Weekday, month and day", + "day-abbreviated": "Abbreviated day name", + "day-full": "Full day name", + "day-leading-zero": "Day with leading zero (01-31)", + "day-no-leading-zero": "Day without leading zero (1-31)", + "hour-leading-zero": "Hour with leading zero (00-23) โ€” 24-hour format", + "hour-no-leading-zero": "Hour without leading zero (0-23) โ€” 24-hour format", + "minute-leading-zero": "Minute with leading zero (00-59)", + "minute-no-leading-zero": "Minute without leading zero (0-59)", + "month-abbreviated": "Abbreviated month name", + "month-full": "Full month name", + "month-number-leading-zero": "Month as number with leading zero (01-12)", + "month-number-no-zero": "Month as number without leading zero (1-12)", + "second-leading-zero": "Second with leading zero (00-59)", + "second-no-leading-zero": "Second without leading zero (0-59)", + "timezone-abbreviation": "Timezone abbreviation", + "year-four-digit": "Year as four-digit number", + "year-two-digit": "Year as two-digit number (00-99)" + }, + "file-picker": { + "item": "item", + "items": "items", + "search-placeholder": "Search files and folders...", + "select-current": "Select Current", + "select-file": "Select File", + "select-folder": "Select Folder", + "selected": "Selected:", + "title": "File Picker" + }, + "file-picker-title": "Select a file", + "icon-picker": { + "title": "Icon picker" + }, + "tooltip-placeholder": "Placeholder" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "Anonymous Identity", + "ca-cert": "CA Certificate", + "eap-method": "EAP Method", + "password": "User password", + "phase2-auth": "Phase 2 Authentication", + "username": "Identity / Username" + }, + "panel": { + "action-required": "Action required", + "add-hidden-network": "Add hidden network", + "add-network": "Add network", + "available-interfaces": "Available interfaces", + "available-networks": "Available networks", + "disabled": "Wiโ€‘Fi is disabled", + "disconnecting": "Disconnecting...", + "dns": "DNS", + "enable-message": "Enable Wiโ€‘Fi to see available networks.", + "enter-password": "Enter Wiโ€‘Fi password...", + "forget": "Forget", + "forget-network": "Forget this network", + "forgetting": "Forgetting...", + "hidden-network": "Hidden network", + "interface": "Network interface", + "internet-connected": "Internet connected", + "internet-limited": "No internet", + "internet-status": "Internet status", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "Known networks", + "link-speed": "Link speed", + "network-name-ssid": "Network name (SSID)", + "no-ethernet-devices": "No Ethernet devices detected", + "no-networks": "No Wiโ€‘Fi networks found", + "saved": "Saved", + "scan-again": "Scan again", + "searching": "Searching for networks...", + "security-open": "Open", + "security-wep": "WEP", + "security-wpa": "WPA", + "security-wpa-ent": "WPA Enterprise", + "security-wpa2-ent": "WPA2 Enterprise", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 Enterprise", + "show-password": "Show password", + "title": "Wiโ€‘Fi" + }, + "signal": { + "excellent": "Excellent", + "fair": "Fair", + "good": "Good", + "poor": "Poor", + "weak": "Weak" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/es.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/es.json new file mode 100644 index 0000000..4d7bfc5 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/es.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "Borrar historial", + "close-app": "Cerrar {app}", + "connect-vpn": "Conectarse a {name}", + "cycle-visualizer": "Visualizador de ciclos", + "disable-bluetooth": "Desactivar Bluetooth", + "disable-dnd": "Desactivar 'No molestar'", + "disable-wifi": "Desactivar Wi-Fi", + "disconnect-vpn": "Desconectar {name}", + "dock-settings": "Ajustes del dock", + "enable-bluetooth": "Activar Bluetooth", + "enable-dnd": "Activar 'No molestar'", + "enable-wifi": "Activar Wi-Fi", + "launcher-settings": "Ajustes del lanzador", + "lower-to-bottom": "Bajar hasta el fondo", + "open-calendar": "Abrir calendario", + "open-display-settings": "Configuraciรณn de pantalla", + "open-launcher": "Abrir lanzador", + "open-settings": "Abrir ajustes", + "raise-to-top": "Subir al principio", + "random-wallpaper": "Fondo de pantalla aleatorio", + "run-custom-command": "Ejecutar comando personalizado", + "show-active-only": "Mostrar solo activos", + "show-all": "Mostrar todo", + "toggle-mute": "Activar/desactivar silencio", + "widget-settings": "Configuraciรณn del widget" + }, + "authentication": { + "error": "Error de autentificaciรณn", + "failed": "Autentificaciรณn fallida" + }, + "bar": { + "active-window": { + "colorize-icons-description": "Aplicar colores del tema al icono de la ventana activa.", + "hide-mode-description": "Controla el comportamiento del widget cuando no hay ventana activa.", + "scrolling-mode-description": "Controlar cuรกndo estรก habilitado el desplazamiento de texto para tรญtulos de ventana largos.", + "show-app-icon-description": "Mostrar el icono de la aplicaciรณn junto al tรญtulo de la ventana.", + "show-app-icon-label": "Mostrar icono de la aplicaciรณn", + "show-app-text-description": "Mostrar el tรญtulo de la ventana activa.", + "show-app-text-label": "Mostrar tรญtulo de ventana" + }, + "audio-visualizer": { + "color-name-description": "Selecciona el color para el visualizador.", + "color-name-label": "Color de relleno", + "height-description": "Ancho de componente personalizado.", + "hide-when-idle-description": "Si estรก activado, el visualizador se oculta salvo que haya reproducciรณn activa.", + "hide-when-idle-label": "Ocultar cuando no se reproduce", + "width-description": "Ancho del componente personalizado." + }, + "battery": { + "device-default": "Predeterminado (Dispositivo de visualizaciรณn)", + "device-description": "Seleccione quรฉ dispositivo de baterรญa mostrar.", + "device-label": "Dispositivo de baterรญa", + "display-mode-description": "Elige cรณmo se muestra la baterรญa en la barra.", + "display-mode-graphic": "Baterรญa grรกfica", + "display-mode-graphic-clean": "Baterรญa grรกfica (sin %)", + "display-mode-icon-always": "Icono - Siempre mostrar %", + "display-mode-icon-hover": "Icono - Mostrar al pasar", + "display-mode-icon-only": "Solo icono", + "hide-if-idle-description": "Ocultar el widget cuando la baterรญa no se estรฉ cargando ni descargando.", + "hide-if-idle-label": "Ocultar cuando inactivo", + "hide-if-not-detected-description": "Ocultar el widget cuando no se detecte baterรญa en el sistema.", + "hide-if-not-detected-label": "Ocultar si no se detecta", + "low-battery-threshold-label": "Umbral de advertencia de baterรญa baja", + "show-noctalia-performance-description": "Muestra el interruptor del modo de rendimiento Noctalia en el panel de baterรญa.
Desactiva sombras y animaciones en Noctalia para reducir el uso de recursos.", + "show-noctalia-performance-label": "Mostrar interruptor Noctalia Rendimiento", + "show-power-profile-description": "Muestra la selecciรณn de perfil de energรญa en el panel de baterรญa.", + "show-power-profile-label": "Mostrar controles de perfil de energรญa" + }, + "brightness": { + "apply-all-description": "Cambiar el brillo de todos los monitores al usar la rueda de desplazamiento.", + "apply-all-label": "Aplicar cambios de desplazamiento a todos los monitores" + }, + "clock": { + "clock-display-description": "Personaliza la visualizaciรณn del reloj aรฑadiendo tokens de la lista de abajo. Para usar el formato de 12 horas, debes incluir el token 'AP'.", + "clock-display-label": "Visualizaciรณn del reloj", + "custom-font-description": "Seleccionar una fuente personalizada para la visualizaciรณn del reloj.", + "custom-font-label": "Fuente personalizada", + "custom-font-placeholder": "Seleccionar fuente personalizada...", + "custom-font-search-placeholder": "Buscar fuentes...", + "horizontal-bar-description": "Consejo: Usa \\n para crear un salto de lรญnea.", + "horizontal-bar-label": "Barra horizontal", + "preview": "Vista previa", + "tooltip-format-description": "Formato de la informaciรณn mostrada al pasar el cursor sobre el reloj. Dรฉjalo vacรญo para usar el formato por defecto.", + "tooltip-format-label": "Formato del texto emergente", + "use-custom-font-description": "Anular la fuente predeterminada del sistema con una fuente especรญfica para el reloj.", + "use-custom-font-label": "Usar fuente personalizada", + "use-monospaced-font-description": "Cuando estรก activada, el reloj usarรก la fuente monoespaciada.", + "use-monospaced-font-label": "Usar fuente monoespaciada", + "vertical-bar-description": "Usa un espacio para separar cada parte en una nueva lรญnea.", + "vertical-bar-label": "Barra vertical" + }, + "control-center": { + "browse-file": "Explorar archivo", + "browse-library": "Explorar biblioteca", + "color-selection-description": "Aplica colores del tema a los iconos.", + "enable-colorization-description": "Habilitar la colorizaciรณn para el icono, aplicando los colores del tema.", + "icon-description": "Selecciona un icono de la biblioteca o un archivo personalizado.", + "select-custom-icon": "Seleccionar un icono personalizado", + "use-distro-logo-description": "Usar el logo de tu distribuciรณn en lugar de un icono personalizado.", + "use-distro-logo-label": "Usar logo de la distribuciรณn en lugar de icono" + }, + "custom-button": { + "collapse-condition-description": "Si el texto de salida coincide con este valor, el botรณn se colapsarรก.", + "collapse-condition-label": "Condiciรณn de colapso", + "color-selection-description": "Aplicar colores del tema al icono y al texto.", + "default-tooltip": "Botรณn personalizado, configurar en ajustes", + "display-command-output-description": "Introduce un comando para ejecutar a intervalos regulares. La primera lรญnea de su salida se mostrarรก como texto.", + "display-command-output-label": "Mostrar salida del comando", + "display-command-output-stream-description": "Introduce un comando para ejecutar continuamente.", + "dynamic-text": "Texto dinรกmico", + "enable-colorization-description": "Habilitar el coloreado para el icono y el texto del botรณn personalizado, aplicando los colores del tema.", + "enable-colorization-label": "Habilitar coloreado", + "general-tooltip-text-description": "Texto personalizado para mostrar en la descripciรณn emergente del botรณn.", + "general-tooltip-text-label": "Texto emergente personalizado", + "hide-mode-always-expanded": "Siempre expandido", + "hide-mode-description": "Controla la visibilidad del widget cuando el comando no tiene salida.", + "hide-mode-expand-with-output": "Expandir cuando tiene salida", + "hide-mode-label": "Modo de ocultaciรณn", + "hide-mode-max-transparent": "Mรกximo expandido pero transparente", + "icon-description": "Selecciona un icono de la biblioteca.", + "ipc-identifier-description": "Identificador รบnico para comandos IPC. Utiliza este identificador con 'qs -c noctalia-shell ipc call cb [acciรณn] [identificador]' para controlar este botรณn a travรฉs de IPC.", + "ipc-identifier-label": "Identificador IPC", + "left-click-description": "Comando a ejecutar cuando se hace clic izquierdo en el botรณn.", + "left-click-label": "Clic izquierdo", + "left-click-update-text": "Actualizar el texto mostrado al hacer clic izquierdo", + "max-text-length-horizontal-description": "Nรบmero mรกximo de caracteres a mostrar en la barra horizontal (0 para ocultar el texto).", + "max-text-length-horizontal-label": "Longitud mรกxima de texto (horizontal)", + "max-text-length-vertical-description": "Nรบmero mรกximo de caracteres a mostrar en la barra vertical (0 para ocultar el texto).", + "max-text-length-vertical-label": "Longitud mรกxima de texto (vertical)", + "middle-click-description": "Comando a ejecutar cuando se hace clic central en el botรณn.", + "middle-click-label": "Clic central", + "middle-click-update-text": "Actualizar el texto mostrado al hacer clic con el botรณn central", + "parse-json-description": "Analizar la salida del comando como un objeto JSON para establecer dinรกmicamente el texto y el รญcono.", + "parse-json-label": "Interpretar salida como JSON", + "refresh-interval-description": "Intervalo en milisegundos.", + "refresh-interval-label": "Intervalo de actualizaciรณn", + "right-click-description": "Comando a ejecutar cuando se hace clic derecho en el botรณn.", + "right-click-label": "Clic derecho", + "right-click-update-text": "Actualizar el texto mostrado al hacer clic derecho", + "show-exec-tooltip-description": "Mostrar texto emergente con los detalles del comandos (clic izquierdo/derecho/central, rueda).", + "show-exec-tooltip-label": "Mostrar texto emergente de comandos", + "show-icon-description": "Alterna la visibilidad del icono del widget.", + "show-icon-label": "Mostrar icono", + "show-text-tooltip-description": "Mostrar texto emergente con la salida del comando de texto.", + "show-text-tooltip-label": "Mostrar textos emergentes dinรกmicos", + "text-stream-description": "Las lรญneas transmitidas desde el comando se mostrarรกn como texto en el botรณn.", + "text-stream-label": "Salida del comando", + "wheel-description": "Comando a ejecutar cuando se usa la rueda de desplazamiento.
Usa $delta para el delta de la rueda de desplazamiento en el comando.", + "wheel-down": "Rueda abajo", + "wheel-down-description": "Comando a ejecutar cuando la rueda de desplazamiento se desplaza hacia abajo.", + "wheel-down-label": "Comando de rueda hacia abajo", + "wheel-label": "Rueda de desplazamiento", + "wheel-mode-separate-description": "Habilitar comandos separados para rueda arriba y abajo.", + "wheel-mode-separate-label": "Comandos de rueda separados", + "wheel-up": "Rueda arriba", + "wheel-up-description": "Comando a ejecutar cuando la rueda de desplazamiento se desplaza hacia arriba.", + "wheel-up-label": "Comando de rueda hacia arriba", + "wheel-update-text": "Actualizar texto mostrado al desplazarse" + }, + "keyboard-layout": { + "show-icon-description": "Mostrar el icono de la distribuciรณn del teclado." + }, + "lock-keys": { + "hide-when-off-description": "Ocultar el indicador cuando la tecla no estรก activa.", + "hide-when-off-label": "Ocultar cuando desactivado", + "show-caps-lock-description": "Mostrar el estado de Bloq Mayรบs.", + "show-caps-lock-label": "Bloq Mayรบs", + "show-num-lock-description": "Mostrar el estado de Bloq Num.", + "show-num-lock-label": "Bloq Num", + "show-scroll-lock-description": "Mostrar el estado de Bloq Despl.", + "show-scroll-lock-label": "Bloq Despl" + }, + "media-mini": { + "compact-mode-description": "Habilitar un diseรฑo compacto para el panel del reproductor multimedia.", + "compact-mode-label": "Modo compacto", + "hide-mode-description": "Controla el comportamiento del widget cuando no se estรก reproduciendo ningรบn medio.", + "max-width-description": "Establece el tamaรฑo horizontal mรกximo del widget. El widget se reducirรก para adaptarse a contenido mรกs corto.", + "no-active-player": "Sin reproductor activo", + "panel-section-description": "Configurar la apariencia y el comportamiento del panel del reproductor multimedia.", + "panel-section-label": "Panel del reproductor multimedia", + "scrolling-mode-description": "Controla cuรกndo estรก habilitado el desplazamiento de texto para tรญtulos de pista largos.", + "scrolling-mode-label": "Modo de desplazamiento", + "show-album-art-description": "Mostrar la portada del รกlbum de la pista que se estรก reproduciendo.", + "show-album-art-label": "Mostrar arte del รกlbum", + "show-artist-first-description": "Mostrar 'artista - tรญtulo' en lugar de 'tรญtulo - artista'.", + "show-artist-first-label": "Mostrar primero el artista", + "show-progress-ring-description": "Mostrar un indicador de progreso circular que muestre el progreso de la pista.", + "show-progress-ring-label": "Mostrar anillo de progreso", + "show-visualizer-description": "Mostrar un visualizador de audio cuando se reproduce mรบsica.", + "show-visualizer-label": "Mostrar visualizador", + "use-fixed-width-description": "Cuando estรก activado, el widget siempre usarรก el ancho mรกximo en lugar de ajustarse dinรกmicamente al contenido.", + "use-fixed-width-label": "Usar ancho fijo", + "visualizer-type-description": "Elegir el estilo de visualizador de audio a mostrar.", + "visualizer-type-label": "Tipo de visualizador" + }, + "notification-history": { + "hide-widget-when-zero-description": "Ocultar el icono de notificaciones cuando no haya notificaciones.", + "hide-widget-when-zero-label": "Ocultar icono sin notificaciones", + "hide-widget-when-zero-unread-description": "Ocultar el icono de notificaciones cuando no haya notificaciones pendientes de leer.", + "hide-widget-when-zero-unread-label": "Ocultar icono sin notificaciones pendientes", + "show-unread-badge-description": "Mostrar una insignia con el nรบmero de notificaciones no leรญdas.", + "show-unread-badge-label": "Mostrar insignia de no leรญdos", + "unread-badge-color-description": "Selecciona el color para la insignia de notificaciรณn no leรญda.", + "unread-badge-color-label": "Color de la insignia de no leรญdos" + }, + "section-editor": { + "placeholder": "Selecciona un widget para aรฑadir...", + "search-placeholder": "Buscar widgets..." + }, + "spacer": { + "width-description": "Ancho del espaciado en pรญxeles." + }, + "system-monitor": { + "compact-mode-description": "Mostrar las estadรญsticas como mini grรกficos de barras en lugar de valores de texto. Evita el desplazamiento del diseรฑo.", + "compact-mode-label": "Modo compacto", + "cpu-cores-description": "Mostrar el uso de los nรบcleos de la CPU individualmente.", + "cpu-cores-label": "Nรบcleos de CPU", + "cpu-frequency-description": "Mostrar la velocidad de reloj actual de la CPU en GHz.", + "cpu-frequency-label": "Mostrar frecuencia de la CPU", + "cpu-temperature-description": "Mostrar las lecturas de temperatura de CPU si estรกn disponibles.", + "cpu-temperature-label": "Temperatura de la CPU", + "cpu-usage-description": "Mostrar el porcentaje actual de uso de CPU.", + "cpu-usage-label": "Uso de CPU", + "disk-path-description": "Seleccione quรฉ punto de montaje de disco desea supervisar.", + "disk-path-label": "Ruta del disco", + "gpu-temperature-description": "Mostrar las lecturas de temperatura de la GPU si estรกn disponibles.", + "load-average-description": "Mostrar la carga promedio del sistema.", + "load-average-label": "Carga promedio", + "memory-percentage-description": "Mostrar el uso de memoria como porcentaje en lugar de valores absolutos.", + "memory-percentage-label": "Memoria como porcentaje", + "memory-usage-description": "Mostrar la informaciรณn actual del uso de RAM.", + "memory-usage-label": "Uso de memoria", + "network-traffic-description": "Mostrar las velocidades de carga y descarga de la red.", + "network-traffic-label": "Trรกfico de red", + "storage-as-percentage-description": "Mostrar el espacio en disco como porcentaje en lugar de valores absolutos.", + "storage-as-percentage-label": "Disco como porcentaje", + "storage-available-description": "Muestra cuรกnto espacio en disco estรก disponible en lugar de cuรกnto se usa.", + "storage-available-label": "Espacio en disco disponible", + "storage-usage-description": "Mostrar la informaciรณn del uso del espacio en disco.", + "storage-usage-label": "Uso de almacenamiento", + "swap-usage-description": "Mostrar el uso de la memoria swap.", + "swap-usage-label": "Uso de swap", + "use-monospace-font-description": "Utilice fuente monoespaciada para un ancho de carรกcter consistente.", + "use-monospace-font-label": "Fuente monoespaciada", + "use-padding-description": "Rellena los valores de texto con espacios iniciales para evitar el desplazamiento del diseรฑo.", + "use-padding-description-disabled-monospace-font": "Se necesita una fuente monoespaciada para esta caracterรญstica.", + "use-padding-description-disabled-vertical": "La barra de tareas vertical no admite relleno de texto.", + "use-padding-label": "Relleno de Texto" + }, + "taskbar": { + "colorize-icons-description": "Aplicar colores del tema a los iconos de la barra de tareas.", + "hide-mode-description": "Controla cรณmo se comporta el widget cuando no hay ventanas coincidentes.", + "hide-mode-label": "Modo de ocultaciรณn", + "icon-scale-description": "Establece el factor de escala para los iconos de la barra de tareas.", + "icon-scale-label": "Escalado de iconos", + "max-width-description": "Ancho mรกximo de la barra de tareas como porcentaje del ancho de la pantalla.", + "max-width-label": "Ancho mรกximo", + "only-active-workspaces-description": "Mostrar solo las aplicaciones de los espacios de trabajo activos.", + "only-active-workspaces-label": "Solo desde espacios de trabajo activos", + "only-same-monitor-description": "Mostrar solo las aplicaciones del monitor donde se encuentra la barra.", + "only-same-monitor-label": "Solo desde el mismo monitor", + "show-pinned-apps-description": "Mostrar las aplicaciones ancladas desde el dock en la barra de tareas.", + "show-pinned-apps-label": "Mostrar aplicaciones ancladas", + "show-title-description": "Mostrar tรญtulos de ventanas en la barra de tareas.", + "show-title-description-disabled": "La barra de tareas vertical no admite mostrar tรญtulos.", + "show-title-label": "Mostrar tรญtulo", + "smart-width-description": "Ajustar automรกticamente el ancho de las entradas segรบn el nรบmero de entradas.", + "smart-width-label": "Ancho inteligente", + "title-width-description": "Establecer el ancho de los tรญtulos de ventanas en la barra de tareas (en pรญxeles).", + "title-width-label": "Ancho del tรญtulo", + "title-width-reset-tooltip": "Restablecer ancho del tรญtulo" + }, + "tray": { + "chevron-color-description": "Aplicar los colores del tema al icono de flecha del cajรณn.", + "chevron-color-label": "Color de la flecha", + "colorize-icons-description": "Aplicar colores del tema a los iconos de la bandeja del sistema.", + "colorize-icons-label": "Colorear iconos", + "drawer-enabled-description": "Cuando estรก habilitado, los elementos de la bandeja no anclados se muestran en un panel cajรณn.
Cuando estรก deshabilitado, todos los elementos de la bandeja se muestran en lรญnea.", + "drawer-enabled-label": "Habilitar cajรณn", + "hide-passive-description": "Cuando estรก activado, los elementos de la bandeja con estado 'Pasivo' se ocultarรกn.", + "hide-passive-label": "Ocultar objetos pasivos" + }, + "volume": { + "display-mode-description": "Elige cรณmo te gustarรญa que apareciera este valor." + }, + "workspace": { + "character-count-description": "Nรบmero de caracteres a mostrar de los nombres de espacios de trabajo (1-10).", + "character-count-label": "Nรบmero de caracteres", + "empty-color-description": "Establecer el color de fondo para los Workspaces vacรญos.", + "empty-color-label": "Color de espacio de trabajo vacรญo", + "enable-scrollwheel-description": "Cambia entre espacios de trabajo usando la rueda de desplazamiento del ratรณn.", + "enable-scrollwheel-label": "Desplรกzate para cambiar de espacio de trabajo", + "focused-color-description": "Establece el color de fondo para el Workspace enfocado.", + "focused-color-label": "Color del espacio de trabajo enfocado", + "follow-focused-screen-description": "Mostrar espacios de trabajo de la pantalla enfocada, en lugar de la pantalla donde se encuentra la barra.", + "follow-focused-screen-label": "Seguir pantalla enfocada", + "font-weight-description": "Establecer el peso visual para el texto dentro del espacio de trabajo.", + "font-weight-label": "Peso de fuente", + "grouped-border-opacity-description": "Establecer el nivel de opacidad para los bordes del contenedor del espacio de trabajo.", + "grouped-border-opacity-label": "Opacidad del borde", + "hide-unoccupied-description": "No mostrar espacios de trabajo sin ventanas.", + "hide-unoccupied-label": "Ocultar desocupados", + "label-mode-description": "Elegir cรณmo se muestran las etiquetas de los espacios de trabajo.", + "label-mode-label": "Modo de etiqueta", + "occupied-color-description": "Establecer el color de fondo para los Workspaces ocupados.", + "occupied-color-label": "Color del espacio de trabajo ocupado", + "pill-size-description": "Ajusta el tamaรฑo de las pรญldoras del espacio de trabajo (50%-100%).", + "pill-size-label": "Tamaรฑo de la pรญldora", + "reverse-scrolling-description": "Invertir la direcciรณn del cambio de espacios de trabajo al desplazarse.", + "reverse-scrolling-label": "Desplazamiento inverso", + "show-applications-description": "Mostrar los iconos de las aplicaciones dentro de cada espacio de trabajo.", + "show-applications-hover-description": "Mostrar iconos de aplicaciรณn dentro de cada espacio de trabajo cuando se pasa el ratรณn por encima.", + "show-applications-hover-label": "Mostrar aplicaciones al pasar", + "show-applications-label": "Mostrar aplicaciones", + "show-badge-description": "Mostrar la insignia del nรบmero de espacio de trabajo en modo agrupado.", + "show-badge-label": "Mostrar insignia del espacio de trabajo", + "show-labels-only-when-occupied-description": "Mostrar solo las etiquetas del espacio de trabajo cuando contengan ventanas.", + "show-labels-only-when-occupied-label": "Mostrar etiquetas solo cuando haya contenido", + "unfocused-icons-opacity-description": "Establecer el nivel de opacidad para los iconos de aplicaciones no enfocadas.", + "unfocused-icons-opacity-label": "Opacidad de iconos de aplicaciones no enfocadas" + } + }, + "battery": { + "all-batteries": "Todas las baterรญas (combinadas)", + "battery-health": "Estado de la baterรญa", + "battery-level": "Nivel de baterรญa", + "capacity-level": "Capacidad: {level}", + "charging-rate": "Tasa de carga: {rate} W", + "discharging-rate": "Tasa de descarga: {rate} W", + "health": "Salud: {percent}%", + "inhibit-idle-description": "Mantiene el sistema despierto.", + "no-battery-detected": "No se detectรณ ninguna baterรญa", + "plugged-in": "Conectado", + "power-profile": "Perfil de energรญa", + "time-left": "Tiempo restante: {time}", + "time-until-full": "Tiempo hasta carga completa: {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "Dispositivos disponibles", + "blocked": "Bloqueado", + "connected-devices": "Dispositivos conectados", + "device-address": "Direcciรณn del dispositivo", + "disabled": "Bluetooth estรก desactivado", + "discoverable": "Visible", + "enable-message": "Activa Bluetooth para ver los dispositivos disponibles.", + "known-devices": "Dispositivos conocidos", + "no-devices": "No hay dispositivos disponibles", + "paired-devices": "Dispositivos emparejados", + "pairing-mode": "Asegรบrate de que tu dispositivo estรฉ en modo de emparejamiento.", + "scanning": "Buscando dispositivos...", + "signal-text-excellent": "Seรฑal: Excelente", + "signal-text-fair": "Seรฑal: Razonable", + "signal-text-good": "Seรฑal: Buena", + "signal-text-poor": "Seรฑal: Dรฉbil", + "signal-text-unknown": "Seรฑal: Desconocida", + "signal-text-very-poor": "Seรฑal: Muy mala" + } + }, + "changelog": { + "error": { + "fetch-failed": "No se pudieron cargar los datos del registro de cambios. Intรฉntalo de nuevo mรกs tarde.", + "rate-limit": "Se alcanzรณ el lรญmite de GitHub. Intรฉntalo de nuevo en unos minutos." + }, + "panel": { + "buttons-discord": "รšnete a nuestro Discord", + "buttons-dismiss": "OK", + "buttons-feedback": "Dejar comentario", + "empty": "Las notas de la versiรณn aรบn no estรกn disponibles.", + "highlight-title": "Cambios destacados", + "released": "Publicado el {date}", + "subtitle-fresh": "ยกGracias por instalar Noctalia! Esto es lo que incluye esta compilaciรณn.", + "subtitle-updated": "Actualizado desde {previousVersion}", + "title": "Novedades en {version}", + "version": "Versiรณn {version}", + "version-new-user": "Instalaciรณn nueva" + } + }, + "common": { + "actions": "Acciones", + "active": "Activo", + "add": "Aรฑadir", + "appearance": "Apariencia", + "apply": "Aplicar", + "auto-connect": "Conexiรณn automรกtica", + "automation": "Automatizaciรณn", + "available": "Disponible", + "back": "Atrรกs", + "battery": "Baterรญa", + "behavior": "Comportamiento", + "bluetooth": "Bluetooth", + "brightness": "Brillo", + "browse": "Navegar", + "calendar": "Calendario", + "calendar-panel": "Panel del calendario", + "cancel": "Cancelar", + "cards": "Tarjetas", + "charging": "Cargando", + "clear": "Borrar", + "clipboard": "Portapapeles", + "close": "Cerrar", + "color-muted": "Silenciado", + "colors": "Colores", + "command": "Comando", + "confirm": "Confirmar", + "connect": "Conectar", + "connected": "Conectado", + "connecting": "Conectando...", + "continue": "Continuar", + "contributors": "Colaboradores", + "copied-to-clipboard": "Copiado al portapapeles", + "countdown": "Cuenta regresiva", + "customize": "Personalizar", + "date": "Fecha", + "default": "Predeterminado", + "delete": "Borrar", + "devices": "Dispositivos", + "disabled": "Desactivado", + "discharging": "Descarga", + "disconnect": "Desconectar", + "disconnected": "Desconectado", + "disconnecting": "Desconectando...", + "display-mode": "Modo de pantalla", + "documentation": "Documentaciรณn", + "download": "Descargar", + "duration": "Duraciรณn", + "dysfunctional": "Disfuncional", + "edit": "Editar", + "enabled": "Activado", + "error": "Error", + "ethernet": "Ethernet", + "events": "Eventos", + "execute": "Ejecutar", + "faithful": "Fiel", + "focus": "Enfoque", + "font-weight-bold": "Negrita", + "font-weight-light": "Fina", + "font-weight-medium": "Medio", + "font-weight-regular": "Regular", + "font-weight-semibold": "Seminegrita", + "frequency": "Frecuencia", + "gateway": "Puerta de enlace", + "general": "General", + "height": "Altura", + "hibernate": "Hibernar", + "history": "Historia", + "icon": "Icono", + "idle": "Inactivo", + "info": "Informaciรณn", + "input": "Entrada", + "install": "Instalar", + "installed": "Instalado", + "interface": "Interfaz", + "internet": "Internet", + "keybind": "Atajo de teclado", + "language": "Idioma", + "loading": "Cargando...", + "local": "Local", + "location": "Ubicaciรณn", + "lock": "Bloquear", + "logout": "Cerrar sesiรณn", + "look": "Aspecto", + "margins": "Mรกrgenes", + "media": "Medios", + "media-player": "Reproductor multimedia", + "memory": "Memoria", + "monitors": "Monitores", + "move-down": "Mover hacia abajo", + "move-up": "Mover arriba", + "network": "Red", + "next": "Siguiente", + "night-light": "Luz nocturna", + "no": "No", + "no-results": "Sin resultados", + "no-summary": "Sin resumen", + "none": "Ninguno", + "not-found": "No encontrado", + "notifications": "Notificaciones", + "official": "Oficial", + "on-surface": "En superficie", + "output": "Salida", + "pair": "Emparejar", + "paired": "Emparejado", + "pairing": "Emparejando...", + "panels": "Paneles", + "password": "Contraseรฑa", + "pause": "Pausa", + "performance": "Rendimiento", + "pin": "Anclar", + "play": "Reproducir", + "polling": "Sondeo", + "position": "Posiciรณn", + "previous": "Anterior", + "primary": "Primario", + "random": "Aleatorio", + "reboot": "Reiniciar", + "reboot-to-uefi": "Reiniciar a UEFI", + "record": "Iniciar grabaciรณn", + "refresh": "Refrescar", + "required": "(obligatorio)", + "reset": "Restablecer", + "reset-to-default": "Restablecer a Valores Predeterminados", + "result-count": "{count} resultado", + "result-count-plural": "{count} resultados", + "resume": "Reanudar", + "save": "Guardar", + "scanning": "Escaneando...", + "screen-corners": "Esquinas de la pantalla", + "search": "Buscar", + "second": "{count} segundo", + "second-plural": "{count} segundos", + "secondary": "Secundario", + "security": "Seguridad", + "select": "Seleccionar", + "select-color": "Seleccionar color", + "select-color-description": "Aplicar colores del tema para enfatizar.", + "select-icon-color": "Seleccionar color de icono", + "settings": "Ajustes", + "shortcuts": "Atajos", + "shutdown": "Apagar", + "signal": "Seรฑal", + "sound": "Sonido", + "sources": "Fuentes", + "start": "Comienzo", + "stop": "Parar", + "supporters": "Patrocinadores", + "suspend": "Suspender", + "templates": "Plantillas", + "tertiary": "Terciario", + "test": "Prueba", + "thresholds": "Umbrales", + "title": "Tรญtulo", + "toast": "Emergente", + "trusted": "De confianza", + "uninstall": "Desinstalar", + "unknown": "Desconocido", + "unpair": "Desvincular", + "unpin": "Desanclar", + "update": "Actualizar", + "upload": "Subir", + "username": "Nombre de usuario", + "userspace-reboot": "Reiniciar espacio de usuario", + "version": "Versiรณn", + "vibrant": "Vibrante", + "visualizer": "Visualizador", + "volume": "Volumen", + "volumes": "Volรบmenes", + "wallpaper": "Fondo de pantalla", + "weather": "Clima", + "weather-loading": "Cargando el clima...", + "weather-no-location": "Establece una ubicaciรณn en Ajustes para ver el tiempo", + "week": "Semana", + "widgets": "Widgets", + "width": "Ancho", + "wifi": "Wi-Fi", + "windows": "Ventanas", + "yes": "Sรญ" + }, + "control-center": { + "power-profile": { + "tooltip-action": "Perfil de energรญa", + "tooltip-disabled": "Instala power-profiles-daemon para usar perfiles de energรญa" + }, + "wifi": { + "label-disconnected": "Wi-Fi desconectado", + "label-ethernet": "Ethernet" + } + }, + "display-modes": { + "always-hide": "Ocultar siempre", + "always-show": "Mostrar siempre", + "force-open": "Forzar abierto", + "on-hover": "Al pasar" + }, + "hide-modes": { + "auto-hide": "Ocultaciรณn Automรกtica", + "hidden": "Ocultar cuando vacรญo", + "idle": "Ocultar cuando inactivo", + "non-exclusive": "No exclusivo", + "transparent": "Transparente cuando estรฉ vacรญo", + "visible": "Mostrar siempre" + }, + "launcher": { + "categories": { + "all": "Todo", + "audiovideo": "Audio y video", + "chat": "Chat", + "development": "Desarrollo", + "education": "Educaciรณn", + "emoji-activity": "Actividad", + "emoji-animals": "Animales", + "emoji-flags": "Banderas", + "emoji-food": "Comida y bebida", + "emoji-nature": "Naturaleza", + "emoji-objects": "Objetos", + "emoji-people": "Personas y cuerpo", + "emoji-recent": "Recientes", + "emoji-symbols": "Sรญmbolos", + "emoji-travel": "Viajes y lugares", + "game": "Juegos", + "graphics": "Grรกficos", + "misc": "Varios", + "office": "Oficina", + "pinned": "Anclado", + "system": "Sistema", + "webbrowser": "Navegador web" + }, + "providers": { + "applications": "Aplicaciones", + "calculator": "Calculadora", + "calculator-deprecated": "El comando >calc estรก obsoleto y serรก eliminado pronto. Por favor, en su lugar escribe expresiones matemรกticas directamente en la bรบsqueda.", + "calculator-description": "Calculadora โ€” evalรบa expresiones matemรกticas", + "calculator-enter-expression": "Introduce una expresiรณn matemรกtica", + "calculator-press-enter-to-copy": "Pulsa Enter para copiar el resultado", + "clipboard": "Historial del portapapeles", + "clipboard-clear-description": "Limpiar todo el historial del portapapeles", + "clipboard-clear-description-full": "Eliminar todos los elementos del historial del portapapeles", + "clipboard-clear-history": "Limpiar historial del portapapeles", + "clipboard-delete": "Borrar entrada del portapapeles", + "clipboard-history-disabled": "Historial del portapapeles desactivado", + "clipboard-history-disabled-description": "Activa el historial del portapapeles en la configuraciรณn o instala cliphist", + "clipboard-loading": "Cargando historial del portapapeles...", + "clipboard-search-description": "Buscar en el historial del portapapeles", + "command-description": "Ejecutar comandos de shell", + "emoji": "Selector de emojis", + "emoji-loading": "Cargando emojis...", + "emoji-loading-description": "Por favor espera", + "emoji-no-recent": "No hay emojis recientes", + "emoji-search-description": "Buscar y copiar emojis", + "settings-search-description": "Buscar en los ajustes", + "windows-search-description": "Buscar y enfocar ventanas abiertas" + } + }, + "lock-screen": { + "authenticating": "Autentificando...", + "password": "Ingresa tu contraseรฑa...", + "restart": "Reiniciar", + "shut-down": "Apagar" + }, + "notifications": { + "panel": { + "click-to-collapse": "Clic para contraer", + "click-to-expand": "Haz clic para expandir", + "description": "Tus notificaciones aparecerรกn aquรญ a medida que lleguen.", + "no-notifications": "No hay notificaciones" + }, + "range": { + "earlier": "Anteriores", + "today": "Hoy", + "yesterday": "Ayer" + }, + "time": { + "diff-d": "hace 1 dรญa", + "diff-dd": "hace {diff} dรญas", + "diff-h": "hace 1 hora", + "diff-hh": "hace {diff} horas", + "diff-m": "hace 1 minuto", + "diff-mm": "hace {diff} minutos", + "now": "ahora" + } + }, + "options": { + "bar": { + "density-comfortable": "Cรณmoda", + "density-compact": "Compacta", + "density-default": "Predeterminada", + "density-mini": "Mini", + "density-spacious": "Espaciosa", + "display-mode-always-visible": "Siempre visible", + "type-floating": "Flotante", + "type-framed": "Con marco", + "type-simple": "Simple" + }, + "control-center": { + "quick-settings-style-classic": "Clรกsico", + "quick-settings-style-modern": "Moderno" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "Cรณmodo", + "compact": "Compacto", + "default": "Predeterminado" + }, + "launcher-view-mode": { + "grid": "Cuadrรญcula", + "list": "Lista" + }, + "notification-density": { + "compact": "Compacto", + "default": "Predeterminado" + }, + "scrolling-modes": { + "always": "Desplazar siempre", + "hover": "Desplazar encima", + "never": "No desplazar nunca" + }, + "session-menu-grid-layout": { + "grid": "Cuadrรญcula", + "single-row": "Fila รบnica" + }, + "settings-panel-mode": { + "attached": "Panel adjunto a la barra", + "centered": "Panel centrado", + "window": "Ventana separada" + }, + "visualizer-types": { + "linear": "Lineal", + "mirrored": "Espejado", + "wave": "Onda" + }, + "visualizer-visibility": { + "with-background": "Solo con fondo" + }, + "workspace-labels": { + "index": "รndice", + "index-and-name": "รndice y Nombre", + "name": "Nombre" + } + }, + "panels": { + "about": { + "become-supporter": "Hazte patrocinador", + "changelog": "Ver registro de cambios", + "changelog-on-startup": "Mostrar registro de cambios al actualizar", + "changelog-on-startup-desc": "Mostrar automรกticamente el registro de cambios cuando Noctalia se actualice.", + "contributors-description": "ยกUn saludo a nuestro increรญble colaborador nรบmero {count}!", + "contributors-description-plural": "ยกUn saludo a nuestros {count} increรญbles colaboradores!", + "copy-info": "Copiar informaciรณn", + "debug-disabled": "Modo de depuraciรณn deshabilitado", + "debug-enabled": "Modo de depuraciรณn activado", + "info-copied": "Informaciรณn copiada al portapapeles", + "noctalia-available": "Disponible:", + "noctalia-desc": "Un shell de escritorio elegante y minimalista cuidadosamente diseรฑado para Wayland, construido con Quickshell.", + "noctalia-git-commit": "Commit de Git:", + "noctalia-installed-version": "Versiรณn instalada:", + "noctalia-latest-version": "รšltima versiรณn:", + "noctalia-qs-version": "Noctalia QS versiรณn:", + "noctalia-title": "Shell Noctalia", + "privacy-policy": "Polรญtica de privacidad", + "support": "Apรณyanos", + "supporter-badge": "Colaborador", + "supporters-desc": "ยกUn enorme agradecimiento a nuestro increรญble colaborador!", + "supporters-desc-plural": "ยกMuchรญsimas gracias a nuestros {count} increรญbles seguidores!", + "supporters-loading": "Cargando patrocinadores...", + "system-board": "Placa:", + "system-cpu": "CPU:", + "system-disk": "Disco:", + "system-gpu": "GPU:", + "system-host": "Host:", + "system-install-hint": "Instala fastfetch para ver la informaciรณn del sistema", + "system-kernel": "Kernel:", + "system-loading": "Cargando informaciรณn del sistema...", + "system-memory": "Memoria:", + "system-monitor": "Monitor:", + "system-not-installed": "fastfetch no estรก instalado", + "system-os": "SO:", + "system-packages": "Paquetes:", + "system-product": "Producto:", + "system-title": "Informaciรณn del sistema", + "system-uptime": "Tiempo de actividad:", + "system-wm": "WM:", + "telemetry-data-copied": "Datos de telemetrรญa copiados al portapapeles", + "telemetry-desc": "Ayuda a mejorar Noctalia compartiendo informaciรณn anรณnima del sistema (resoluciรณn de pantalla, compositor, distribuciรณn). Se envรญa una vez al inicio, sin seguimiento, los datos se eliminan automรกticamente despuรฉs de 30 dรญas.", + "telemetry-enabled": "Enviar informaciรณn anรณnima del sistema", + "telemetry-show-data": "Ver datos", + "telemetry-title": "Privacidad", + "title": "Acerca de", + "up-to-date": "ยกEstรกs al dรญa!", + "update-available": "Actualizaciรณn disponible", + "view-commit": "Ver commit en GitHub" + }, + "audio": { + "devices-desc": "Configura los dispositivos de entrada y salida de audio disponibles.", + "devices-input-device-description": "Selecciona el dispositivo preferido de entrada de audio.", + "devices-input-device-label": "Dispositivo de entrada", + "devices-output-device-description": "Selecciona el dispositivo preferido de salida de audio.", + "devices-output-device-label": "Dispositivo de salida", + "devices-title": "Dispositivos de audio", + "external-mixer-description": "Ingrese el comando o la ruta de la aplicaciรณn para ejecutar al activar la funciรณn de mezclador de audio externo.", + "external-mixer-label": "Comando de mezclador de audio externo", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "Establece tus aplicaciones multimedia preferidas e ignoradas.", + "media-excluded-player-description": "Agrega palabras clave para los reproductores que deseas que el sistema ignore. Cada palabra clave debe estar en una lรญnea nueva.", + "media-excluded-player-label": "Reproductor excluido", + "media-excluded-player-placeholder": "escribe una subcadena y presiona +", + "media-frame-rate-description": "Tasas mรกs altas son mรกs fluidas pero usan mรกs recursos.", + "media-frame-rate-label": "Tasa de fotogramas", + "media-primary-player-description": "Introduce una palabra clave para identificar tu reproductor principal.", + "media-primary-player-label": "Reproductor principal", + "media-primary-player-placeholder": "p. ej. spotify, vlc, mpv", + "media-scrolling-speed-description": "Tiempo en segundos para que el tรญtulo se desplace del inicio al final.", + "media-scrolling-speed-label": "Velocidad de desplazamiento", + "media-scrolling-title-description": "Activar desplazamiento continuo para tรญtulos de medios largos.", + "media-scrolling-title-label": "Tรญtulo con desplazamiento", + "media-title": "Reproductores multimedia", + "on-middle-clicked-description": "Comando para ejecutar cuando se hace clic con el botรณn central.", + "panel-applications-empty": "Actualmente no hay aplicaciones reproduciendo audio", + "spectrum-mirrored-description": "Refleja el espectro para que las frecuencias bajas estรฉn en el centro. Cuando estรก desactivado, las frecuencias bajas estรกn a la izquierda y las altas a la derecha.", + "spectrum-mirrored-label": "Espectro espejo", + "title": "Audio", + "visualizer-type-description": "Elige un tipo de visualizaciรณn para la reproducciรณn de medios.", + "visualizer-type-label": "Tipo de visualizaciรณn", + "volumes-desc": "Ajusta los controles de volumen y los niveles de audio.", + "volumes-feedback-sound-file-description": "Ruta al archivo de sonido que se reproduce al cambiar el volumen.", + "volumes-feedback-sound-file-label": "Archivo de sonido de feedback de volumen", + "volumes-feedback-sound-file-select-title": "Seleccionar archivo de sonido de retroalimentaciรณn de volumen", + "volumes-input-volume-description": "Nivel de volumen de entrada del micrรณfono.", + "volumes-mute-input-description": "Silencia la entrada de audio predeterminada (micrรณfono).", + "volumes-mute-input-label": "Silenciar entrada de audio", + "volumes-mute-output-description": "Silencia la salida de audio principal del sistema.", + "volumes-mute-output-label": "Silenciar salida de audio", + "volumes-output-volume-description": "Nivel de volumen de todo el sistema.", + "volumes-step-size-description": "Ajusta el tamaรฑo del paso para los cambios de volumen (rueda del ratรณn, atajos de teclado).", + "volumes-step-size-label": "Tamaรฑo del paso de volumen", + "volumes-volume-feedback-description": "Reproducir un sonido cuando se ajusta el volumen.", + "volumes-volume-feedback-label": "Reproducir sonido de cambio de volumen", + "volumes-volume-overdrive-description": "Permite subir el volumen por encima del 100 %. Es posible que no sea compatible con todo el hardware.", + "volumes-volume-overdrive-label": "Permitir sobreamplificaciรณn de volumen" + }, + "bar": { + "appearance-auto-hide-delay-description": "Tiempo que tarda la barra en ocultarse cuando se aparta el ratรณn", + "appearance-auto-hide-delay-label": "Demora de Ocultaciรณn", + "appearance-auto-hide-exclusive-note": "Nota: La zona exclusiva se desactiva automรกticamente cuando la ocultaciรณn automรกtica estรก activa", + "appearance-auto-show-delay-description": "Tiempo que tarda la barra en mostrarse cuando el ratรณn llega al borde", + "appearance-auto-show-delay-label": "Demora de Despliegue", + "appearance-background-opacity-description": "Establece la opacidad del fondo especรญficamente para la barra.", + "appearance-background-opacity-label": "Opacidad del fondo de la barra", + "appearance-capsule-color-description": "Elige un color para las cรกpsulas de la barra, o no uses ninguno para el color de superficie predeterminado.", + "appearance-capsule-color-label": "Color de la cรกpsula", + "appearance-capsule-opacity-description": "Establecer el nivel de opacidad para los fondos de los widgets cuando se muestra la cรกpsula.", + "appearance-capsule-opacity-label": "Opacidad de la cรกpsula", + "appearance-content-padding-description": "Ajusta el espacio entre los bordes de la barra y los widgets.", + "appearance-content-padding-label": "Espaciado del contenido", + "appearance-density-description": "Ajusta el relleno de la barra para un aspecto compacto o espacioso.", + "appearance-density-label": "Densidad de la barra", + "appearance-desc": "Personaliza la apariencia y posiciรณn de la barra.", + "appearance-display-mode-description": "Elige cuando serรก visible la barra.", + "appearance-enable-exclusion-zone-inset-description": "Reduce la zona de exclusiรณn en 1 pรญxel fรญsico para que las ventanas a ras se extiendan perfectamente por debajo del borde de la barra.", + "appearance-enable-exclusion-zone-inset-label": "Zona de exclusiรณn interior", + "appearance-floating-description": "Muestra la barra como una 'pรญldora' flotante.", + "appearance-floating-label": "Barra flotante", + "appearance-font-scale-description": "Ajustar la escala del tamaรฑo de fuente para el texto mostrado en la barra.", + "appearance-font-scale-label": "Escala de fuente", + "appearance-frame-radius": "Radio interno", + "appearance-frame-settings-description": "Ajustar el grosor del marco y el radio de la esquina interior", + "appearance-frame-settings-label": "Ajustes del marco", + "appearance-frame-thickness": "Grosor", + "appearance-hide-on-overview-description": "Ocultar la barra y cerrar los paneles cuando la vista general del compositor estรฉ activa.", + "appearance-hide-on-overview-label": "Ocultar barra en la vista general", + "appearance-margins-description": "Ajusta los mรกrgenes alrededor de la barra flotante.", + "appearance-margins-horizontal": "Margen horizontal", + "appearance-margins-vertical": "Margen vertical", + "appearance-outer-corners-description": "Muestra esquinas curvadas hacia afuera en la barra.", + "appearance-outer-corners-label": "Esquinas exteriores", + "appearance-position-description": "Elige dรณnde colocar la barra en la pantalla.", + "appearance-position-label": "Posiciรณn de la barra", + "appearance-show-capsule-description": "Mostrar fondos de los widgets.", + "appearance-show-capsule-label": "Mostrar cรกpsula", + "appearance-show-on-workspace-switch-description": "Mostrar la barra brevemente de forma automรกtica cuando el espacio de trabajo cambie.", + "appearance-show-on-workspace-switch-label": "Mostrar barra al cambiar de espacio de trabajo", + "appearance-show-outline-description": "Muestra un borde visible alrededor de cada widget.", + "appearance-show-outline-label": "Mostrar contornos de widgets", + "appearance-type-description": "Elige el estilo de la barra: Simple, Flotante o Con marco.", + "appearance-type-label": "Tipo de barra", + "appearance-use-separate-opacity-description": "Permitir usar un valor de opacidad distinto para el fondo de la barra.", + "appearance-use-separate-opacity-label": "La barra usa opacidad independiente", + "appearance-widget-spacing-description": "Ajusta el espaciado entre cada widget en la barra.", + "appearance-widget-spacing-label": "Espaciado de widgets", + "behavior-middle-click-command-description": "Comando a ejecutar al hacer clic central.", + "behavior-middle-click-command-label": "Comando de Clic Central", + "behavior-middle-click-command-placeholder": "niri msg action alternar vista general", + "behavior-middle-click-description": "Elige quรฉ hace el clic central en las รกreas vacรญas de la Bar.", + "behavior-middle-click-follow-mouse-description": "Abrir el panel de clic central seleccionado en la posiciรณn del cursor.", + "behavior-middle-click-follow-mouse-label": "Clic central sigue al ratรณn", + "behavior-middle-click-label": "Acciรณn de clic central en la Barra", + "behavior-right-click-command-description": "Comando a ejecutar al hacer clic derecho.", + "behavior-right-click-command-label": "Comando de Clic Derecho", + "behavior-right-click-command-placeholder": "notify-send \"Clic derecho\"", + "behavior-right-click-description": "Elige quรฉ hace el clic derecho en las รกreas vacรญas de la barra.", + "behavior-right-click-follow-mouse-description": "Abre el panel de clic derecho seleccionado en la posiciรณn del cursor.", + "behavior-right-click-follow-mouse-label": "Clic derecho sigue al ratรณn", + "behavior-right-click-label": "Acciรณn de clic derecho en la Barra", + "behavior-wheel-wrap-description": "Cuando estรก activado, el desplazamiento continรบa del รบltimo elemento al primero.", + "behavior-wheel-wrap-label": "Circular", + "behavior-workspace-scroll-description": "Elige quรฉ hace la rueda del ratรณn en las zonas vacรญas de la barra.", + "behavior-workspace-scroll-label": "Acciรณn de la rueda del ratรณn en la barra", + "behavior-workspace-scroll-option-content": "Contenido", + "behavior-workspace-scroll-option-workspace": "Espacio de trabajo", + "monitor-configure-widgets": "Configurar widgets", + "monitor-override-settings": "Anular la configuraciรณn global", + "monitor-override-settings-description": "Usar ajustes personalizados para este monitor.", + "monitor-reset-all": "Restablecer todo", + "monitor-widgets-title": "Configuraciรณn del Widget - {monitor}", + "monitors-desc": "Muestra la barra en monitores especรญficos. Por defecto, se muestra en todos si no se elige ninguno.", + "monitors-desc-new": "Configura quรฉ monitores muestran la barra y personaliza los ajustes por monitor.", + "monitors-title": "Visualizaciรณn en monitores", + "title": "Barra", + "tray-blacklist-description": "Agregar reglas de exclusiรณn de la bandeja, admite comodines (*).", + "tray-blacklist-label": "Lista negra", + "tray-blacklist-placeholder": "p. ej., nm-applet, Fcitx*", + "tray-pin-application": "Fijar aplicaciรณn", + "tray-unpin-application": "Desfijar aplicaciรณn", + "use-global-widgets": "Usar widgets globales", + "widgets-desc": "Arrastra los widgets para cambiar su orden. Usa el menรบ contextual (clic derecho) para transferir widgets entre secciones o eliminarlos.", + "widgets-title": "Posicionamiento de widgets" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "Genera esquemas de color a partir de tu fondo de pantalla. Extrae automรกticamente los colores para crear un tema cohesivo.", + "color-source-use-wallpaper-colors-label": "Usar colores del fondo de pantalla", + "dark-mode-mode-description": "Permite el cambio automรกtico entre el modo claro y el modo oscuro.", + "dark-mode-mode-label": "Programaciรณn del modo oscuro", + "dark-mode-mode-manual": "Manual", + "dark-mode-mode-off": "Apagado", + "dark-mode-switch-description": "Cambia a un tema mรกs oscuro para una visualizaciรณn mรกs cรณmoda por la noche.", + "delete-error-description": "Error al eliminar {scheme}", + "delete-error-title": "Error al eliminar", + "delete-success-description": "{scheme} eliminado correctamente", + "delete-success-title": "Esquema de colores eliminado", + "download-button": "Descargar mรกs", + "download-downloading": "Descargando...", + "download-empty": "No hay esquemas de colores disponibles", + "download-error-api-error": "Error de API: {status}", + "download-error-description": "Error al descargar {scheme}", + "download-error-download-failed": "Error al descargar con cรณdigo de salida: {code}", + "download-error-invalid-response": "Formato de respuesta de API invรกlido", + "download-error-no-files": "No se encontraron archivos para el esquema", + "download-error-parse-failed": "Error al analizar la respuesta de la API: {error}", + "download-error-rate-limit": "Lรญmite de velocidad de la API de GitHub excedido", + "download-error-title": "Error al descargar", + "download-fetching": "Obteniendo esquemas de colores disponibles...", + "download-success-description": "{scheme} descargado correctamente", + "download-success-title": "Esquema de colores descargado", + "download-title": "Descargar Esquemas de Color", + "method-description": { + "content": "Esquema de Material Design con extracciรณn de color de alta fidelidad que coincide estrechamente con los colores reales del contenido de origen.", + "dysfunctional": "Como Fiel, pero elige la segunda familia de colores mรกs dominante como primaria.", + "faithful": "Intenta mantenerse cerca del color original mientras genera una paleta armoniosa.", + "fruit-salad": "Esquema de Material Design que produce una paleta alegre y vibrante con matices diversos y variados.", + "monochrome": "Esquema de Material Design que utiliza una escala de grises de un solo tono con un contenido cromรกtico mรญnimo.", + "muted": "Genera tonos mรกs suaves y desaturados para una apariencia tranquila y discreta.", + "rainbow": "Esquema de Material Design que genera colores en todo el espectro para una mรกxima diversidad de color.", + "tonal-spot": "Algoritmo predeterminado de Material Design. Crea paletas equilibradas utilizando variaciones tonales del color de origen.", + "vibrant": "Maximiza la saturaciรณn del color para paletas llamativas y audaces con tonos vรญvidos." + }, + "predefined-desc": "Elige entre una colecciรณn de esquemas de colores predefinidos.", + "predefined-generate-templates-label": "Generar plantillas para esquemas predefinidos", + "predefined-title": "Esquemas de colores predefinidos", + "sync-gsettings-description": "Adaptar el tema del sistema a la variante clara u oscura activa.", + "sync-gsettings-label": "Sincronizar tema del sistema", + "templates-desc": "Aplicar colores a aplicaciones externas.", + "templates-filter-description": "Mostrar plantillas de una categorรญa especรญfica.", + "templates-filter-label": "Filtrar por categorรญa", + "templates-misc-description": "Crea tus propias plantillas.", + "templates-misc-label": "Avanzado", + "templates-misc-user-templates-description": "Habilita sรณlamente si sabes lo que estรกs haciendo โ€” consulta nuestra documentaciรณn en lรญnea.", + "templates-misc-user-templates-label": "Habilitar plantillas de usuario", + "templates-none-detected": "Ninguno detectado", + "templates-write-path": "Escribe: {filepath}", + "title": "Esquema de colores", + "wallpaper-method-description": "Elige tu mรฉtodo de generaciรณn de paleta favorito.", + "wallpaper-method-label": "Mรฉtodo de generaciรณn de paleta", + "wallpaper-monitor-source-description": "Selecciona quรฉ monitor usar para extraer los colores del fondo de pantalla.", + "wallpaper-monitor-source-label": "Fuente de generaciรณn de color" + }, + "connections": { + "authentication-required": "Autenticaciรณn requerida", + "bluetooth-auto-connect-description": "Conectarse automรกticamente a dispositivos emparejados de confianza cuando Bluetooth estรฉ activado.", + "bluetooth-auto-connect-label": "Conectar automรกticamente dispositivos emparejados", + "bluetooth-devices-unnamed": "Los dispositivos sin nombre no se muestran.", + "bluetooth-discoverable": "Este dispositivo es detectable como {hostName} mientras esta pestaรฑa de ajustes estรฉ abierta.", + "bluetooth-rssi-polling-description": "Muestrea periรณdicamente el RSSI de los dispositivos conectados a travรฉs de bluetoothctl. Puede que no estรฉ disponible para todos los dispositivos; usa recursos mรญnimos cuando estรก activado.", + "bluetooth-rssi-polling-interval-description": "Configura con quรฉ frecuencia actualizar la intensidad de la seรฑal para los dispositivos conectados.", + "bluetooth-rssi-polling-interval-label": "Intervalo de sondeo", + "bluetooth-rssi-polling-label": "Sondeo de seรฑal Bluetooth", + "disable-discoverability-description": "Ocultar tu dispositivo de dispositivos Bluetooth cercanos.", + "disable-discoverability-label": "Desactivar visibilidad del dispositivo", + "hide-unnamed-devices-description": "Ocultar dispositivos que aparecen solo como direcciones Bluetooth.", + "hide-unnamed-devices-label": "Ocultar dispositivos sin nombre", + "pin-instructions": "Por favor, introduce el cรณdigo PIN que aparece en tu dispositivo.", + "title": "Conexiones", + "wifi-header-text": "Usa Wi-Fi para ver las redes disponibles y conectarte a internet." + }, + "control-center": { + "cards-desc": "Personaliza quรฉ controles aparecen en el centro de control y en quรฉ orden.", + "desc": "Configurar el posicionamiento y el comportamiento del panel del centro de control.", + "open-at-mouse-description": "Al hacer clic derecho en un รกrea vacรญa de la Bar, abre el Centro de Control cerca del cursor. Cuando estรก deshabilitado, se usa la configuraciรณn de posiciรณn anterior en su lugar.", + "open-at-mouse-label": "Abrir en el cursor al hacer clic derecho", + "position-description": "Elige dรณnde aparece el panel del centro de control cuando se abre.", + "shortcuts-custom-button-command-description": "El comando a ejecutar cuando se hace clic en el botรณn.", + "shortcuts-custom-button-enable-on-state-logic-description": "Habilita un segundo icono y un estado 'caliente' basado en un comando de comprobaciรณn.", + "shortcuts-custom-button-enable-on-state-logic-label": "Habilitar lรณgica de estado activado", + "shortcuts-custom-button-icon-description": "Selecciona un icono de la biblioteca.", + "shortcuts-custom-button-on-clicked-label": "Comando de clic izquierdo", + "shortcuts-custom-button-on-middle-clicked-description": "Comando para ejecutar al hacer clic con el botรณn central.", + "shortcuts-custom-button-on-state-command-description": "Comando a ejecutar para comprobar si el botรณn debe estar en el estado 'encendido'. Devuelve 0 para encendido, no cero para apagado.", + "shortcuts-custom-button-on-state-command-label": "Comando de comprobaciรณn de estado activado", + "shortcuts-custom-button-on-state-icon-description": "El icono para el botรณn cuando estรก en el estado 'encendido'.", + "shortcuts-custom-button-on-state-icon-label": "Icono de estado activado", + "shortcuts-custom-button-state-checks-add": "Aรฑadir comprobaciรณn de estado", + "shortcuts-custom-button-state-checks-command": "Comando a ejecutar para esta comprobaciรณn de estado", + "shortcuts-custom-button-state-checks-label": "Comprobaciones de estado", + "shortcuts-custom-button-state-checks-remove": "Eliminar", + "shortcuts-custom-button-tooltip-description": "El texto emergente a mostrar al pasar el cursor sobre el botรณn.", + "shortcuts-custom-button-tooltip-label": "Texto emergente", + "shortcuts-title": "Widgets de accesos directos", + "system-monitor-disk-path-description": "Selecciona quรฉ punto de montaje de disco debe monitorear la tarjeta del monitor del sistema en el centro de control.", + "system-monitor-disk-path-label": "Ruta del disco del monitor del sistema", + "title": "Centro de control" + }, + "desktop-widgets": { + "clock-enabled-description": "Mostrar un widget de reloj en el escritorio.", + "clock-enabled-label": "Activar widget de reloj", + "clock-format-label": "Formato", + "clock-height-description": "Altura del widget de reloj en pรญxeles.", + "clock-height-label": "Altura", + "clock-minimal-mode-description": "Usar una visualizaciรณn de reloj minimalista con hora y fecha.", + "clock-minimal-mode-label": "Modo minimalista", + "clock-rounded-corners-description": "Usa esquinas redondeadas para el fondo del widget.", + "clock-rounded-corners-label": "Esquinas redondeadas", + "clock-show-background-description": "Mostrar el contenedor de fondo para el widget de reloj.", + "clock-show-background-label": "Mostrar fondo", + "clock-show-date-description": "Muestra la fecha actual debajo de la hora.", + "clock-show-date-label": "Mostrar fecha", + "clock-show-seconds-description": "Mostrar los segundos en la hora.", + "clock-show-seconds-label": "Mostrar segundos", + "clock-style-analog": "Analรณgico", + "clock-style-binary": "Binario", + "clock-style-description": "Elige el estilo de visualizaciรณn del reloj.", + "clock-style-digital": "Digital", + "clock-style-label": "Estilo de reloj", + "clock-style-minimal": "Minimal", + "clock-width-description": "Ancho del widget de reloj en pรญxeles.", + "cpu-intensive-note": "Los widgets marcados con ! usan mรกs CPU y deberรญan activarse con precauciรณn.", + "edit-mode-button-label": "Entrar en modo de ediciรณn", + "edit-mode-controls-explanation": "Clic izquierdo y arrastrar: Mover o redimensionar el widget.\nClic derecho: Abrir las opciones del menรบ contextual.", + "edit-mode-description": "Habilita el modo de ediciรณn para mover y cambiar la posiciรณn de los widgets del escritorio. Cuando estรก habilitado, los widgets muestran un contorno de arrastre y se pueden reposicionar.", + "edit-mode-exit-button": "Salir del modo de ediciรณn", + "edit-mode-grid-snap-label": "Ajustar a cuadrรญcula", + "edit-mode-grid-snap-scale-label": "Escala de ajuste a la cuadrรญcula", + "edit-mode-label": "Modo de ediciรณn", + "enabled-description": "Activar o desactivar los widgets de escritorio por completo.", + "enabled-label": "Habilitar widgets de escritorio", + "general-desc": "Configura los widgets que aparecen en tu escritorio.", + "general-title": "Widgets de escritorio", + "media-player-enabled-description": "Mostrar un widget de reproductor multimedia en el escritorio.", + "media-player-enabled-label": "Habilitar widget de reproductor multimedia", + "media-player-rounded-corners-description": "Habilitar esquinas redondeadas en los bordes del widget.", + "media-player-show-album-art-description": "Mostrar la carรกtula del รกlbum y la informaciรณn de las pistas (tรญtulo y artista).", + "media-player-show-album-art-label": "Mostrar la carรกtula y el tรญtulo del รกlbum", + "media-player-show-background-description": "Mostrar el contenedor de fondo para el widget del reproductor multimedia.", + "media-player-show-buttons-description": "Mostrar botones de control de medios (reproducir/pausar, anterior, siguiente) en el widget del reproductor multimedia.", + "media-player-show-buttons-label": "Mostrar botones", + "media-player-show-visualizer-description": "Mostrar la superposiciรณn del visualizador de audio.", + "media-player-visualizer-type-description": "Elige un tipo de visualizaciรณn para el fondo del reproductor multimedia de escritorio.", + "overview-enabled-description": "Mostrar widgets de escritorio mientras la vista general del compositor estรฉ abierta.", + "overview-enabled-label": "Mostrar en la vista general", + "system-stat-layout-bottom": "Abajo", + "system-stat-layout-description": "Elige cรณmo se muestra la leyenda en relaciรณn con el grรกfico.", + "system-stat-layout-label": "Diseรฑo", + "system-stat-layout-side": "Lado", + "system-stat-rounded-corners-description": "Usar esquinas redondeadas para el fondo del widget.", + "system-stat-rounded-corners-label": "Esquinas Redondeadas", + "system-stat-show-background-description": "Mostrar el contenedor de fondo para el widget de estadรญsticas del sistema.", + "system-stat-show-background-label": "Mostrar fondo", + "system-stat-stat-type-description": "Elige quรฉ estadรญstica del sistema mostrar.", + "system-stat-stat-type-label": "Tipo de Estadรญstica", + "title": "Widgets de escritorio", + "weather-enabled-description": "Mostrar un widget del clima en el escritorio.", + "weather-enabled-label": "Activar widget del clima", + "weather-show-background-description": "Mostrar el contenedor de fondo para el widget del clima." + }, + "display": { + "monitors-backlight-device-auto-option": "Predeterminado", + "monitors-backlight-device-description": "Selecciona un dispositivo de retroiluminaciรณn para esta salida.", + "monitors-backlight-device-label": "Dispositivo de Retroiluminaciรณn", + "monitors-brightness-step-description": "Ajusta el tamaรฑo del paso para los cambios de brillo (rueda de desplazamiento y atajos de teclado).", + "monitors-brightness-step-label": "Tamaรฑo del paso de brillo", + "monitors-brightness-unavailable-ddc-disabled": "Control de brillo no disponible. Habilita \"Soporte de brillo externo\" para controlar el brillo de esta pantalla.", + "monitors-brightness-unavailable-generic": "El control de brillo no estรก disponible para esta pantalla.", + "monitors-desc": "Ajustar el brillo de la configuraciรณn para cada pantalla.", + "monitors-enforce-minimum-description": "Resuelve el problema de la retroiluminaciรณn que se apaga completamente en algunas pantallas al 0% de brillo.", + "monitors-enforce-minimum-label": "Forzar brillo mรญnimo (1%)", + "monitors-external-brightness-description": "Habilitar soporte DDCUtil para controlar el brillo en pantallas externas mediante el protocolo DDC/CI.", + "monitors-external-brightness-label": "Soporte de brillo externo", + "monitors-global-brightness-description": "Ajustar el brillo de todos los monitores a la vez.", + "monitors-global-brightness-label": "Todos los monitores", + "monitors-title": "Configuraciรณn por monitor", + "night-light-auto-schedule-description": "Basado en la hora del atardecer y amanecer en {location} โ€” recomendado.", + "night-light-auto-schedule-label": "Programaciรณn automรกtica", + "night-light-desc": "Reduce la emisiรณn de luz azul para ayudarte a dormir mejor y reducir la fatiga visual.", + "night-light-enable-description": "Aplica un filtro de color cรกlido para reducir la emisiรณn de luz azul.", + "night-light-enable-label": "Activar luz nocturna", + "night-light-force-activation-description": "Ignora la programaciรณn y aplica el filtro nocturno inmediatamente.", + "night-light-force-activation-label": "Forzar activaciรณn", + "night-light-manual-schedule-description": "Establece horas personalizadas para el amanecer y el atardecer.", + "night-light-manual-schedule-label": "Programaciรณn manual", + "night-light-manual-schedule-select-start": "Seleccionar hora de inicio", + "night-light-manual-schedule-select-stop": "Seleccionar hora de finalizaciรณn", + "night-light-manual-schedule-sunrise": "Hora del amanecer", + "night-light-manual-schedule-sunset": "Hora del anochecer", + "night-light-temperature-day": "Dรญa", + "night-light-temperature-day-description": "Controla la temperatura durante el dรญa.", + "night-light-temperature-description": "Establece la calidez del color para la noche y el dรญa.", + "night-light-temperature-label": "Temperatura de color", + "night-light-temperature-night": "Noche", + "night-light-temperature-night-description": "Controla la temperatura durante la noche.", + "title": "Pantalla" + }, + "dock": { + "appearance-background-opacity-description": "Ajusta la opacidad del fondo del dock.", + "appearance-border-radius-description": "Ajustar el radio del borde del dock.", + "appearance-border-radius-label": "Radio de borde", + "appearance-colorize-icons-description": "Aplicar colores del tema a los iconos de aplicaciones del dock (solo aplicaciones no enfocadas).", + "appearance-colorize-icons-label": "Colorear iconos", + "appearance-dead-opacity-description": "Ajustar la opacidad de los iconos de las aplicaciones que no estรกn en ejecuciรณn.", + "appearance-dead-opacity-label": "Opacidad inactivas", + "appearance-desc": "Personaliza el comportamiento y la apariencia del dock.", + "appearance-display-auto-hide": "Ocultar autom.", + "appearance-display-description": "Elige cรณmo se comporta el dock.", + "appearance-display-exclusive": "Exclusivo", + "appearance-dock-indicator-description": "Mostrar un pequeรฑo indicador cuando el dock estรก oculto.", + "appearance-dock-indicator-label": "Indicador del dock", + "appearance-floating-distance-description": "Ajusta la distancia de flotaciรณn desde el borde de la pantalla.", + "appearance-floating-distance-label": "Distancia de flotaciรณn del dock", + "appearance-group-apps-description": "Agrupar varias ventanas de la misma app en una entrada del Dock.", + "appearance-group-apps-label": "Agrupar aplicaciones similares", + "appearance-group-click-action-cycle": "Alternar ventanas", + "appearance-group-click-action-description": "Elige quรฉ hace el clic izquierdo para las aplicaciones agrupadas.", + "appearance-group-click-action-label": "Acciรณn al hacer clic en aplicaciรณn agrupada", + "appearance-group-click-action-list": "Abrir lista de ventanas", + "appearance-group-context-menu-mode-description": "Elige cรณmo se comporta el menรบ contextual para las aplicaciones agrupadas.", + "appearance-group-context-menu-mode-extended": "Extendido", + "appearance-group-context-menu-mode-label": "Modo de menรบ de aplicaciones agrupado", + "appearance-group-context-menu-mode-list": "Lista de ventanas", + "appearance-group-indicator-style-description": "Elige cรณmo los indicadores de aplicaciones agrupadas muestran el estado de la ventana enfocada.", + "appearance-group-indicator-style-dots": "Puntos", + "appearance-group-indicator-style-label": "Estilo de indicador agrupado", + "appearance-group-indicator-style-number": "Nรบmero", + "appearance-hide-show-speed-description": "Ajusta la velocidad de la animaciรณn de ocultar/mostrar del dock.", + "appearance-hide-show-speed-label": "Velocidad de ocultar/mostrar", + "appearance-icon-size-description": "Ajusta el tamaรฑo general del dock.", + "appearance-icon-size-label": "Tamaรฑo del dock", + "appearance-inactive-indicators-description": "Mostrar indicadores en forma de pastillas para todas las aplicaciones, no solo para la que estรก activa actualmente.", + "appearance-inactive-indicators-label": "Indicadores de ejecuciรณn", + "appearance-indicator-color-description": "Elige el color del indicador del dock oculto.", + "appearance-indicator-color-label": "Color del indicador", + "appearance-indicator-opacity-description": "Ajusta la opacidad del indicador del dock oculto.", + "appearance-indicator-opacity-label": "Opacidad del indicador", + "appearance-indicator-thickness-description": "Usa un indicador del dock oculto mรกs grueso (6 px en lugar de 3 px).", + "appearance-indicator-thickness-label": "Indicador mรกs grueso", + "appearance-launcher-icon-description": "Elige el icono usado para el Dock Launcher.", + "appearance-launcher-icon-label": "Icono del Lanzador", + "appearance-launcher-position-description": "Elige dรณnde aparece el icono del lanzador en el dock.", + "appearance-launcher-position-end": "Fin", + "appearance-launcher-position-label": "Posiciรณn del lanzador", + "appearance-launcher-position-start": "Iniciar", + "appearance-launcher-use-distro-logo-description": "Usa el logo de tu distribuciรณn como icono del Dock Launcher.", + "appearance-launcher-use-distro-logo-label": "Usar el logo de la distro en lugar del icono", + "appearance-pinned-static-description": "Siempre empuja los iconos de las aplicaciones ancladas a la izquierda en orden estรกtico.", + "appearance-pinned-static-label": "Aplicaciones ancladas estรกticas", + "appearance-position-description": "Elige dรณnde aparece el dock en la pantalla.", + "appearance-position-label": "Posiciรณn", + "appearance-show-launcher-icon-description": "Mostrar el icono del lanzador de aplicaciones en el dock.", + "appearance-show-launcher-icon-label": "Mostrar lanzador de aplicaciones", + "appearance-sit-on-frame-description": "Alinear el dock dentro del borde del marco en lugar de que se superponga.", + "appearance-sit-on-frame-label": "El Dock se asienta en el marco", + "appearance-type-attached": "Anclado", + "appearance-type-description": "Elige entre una barra flotante con forma de pรญldora o una barra estรกtica anclada al borde.", + "appearance-type-floating": "Flotante", + "appearance-type-label": "Estilo del dock", + "enabled-description": "Mostrar u ocultar el dock por completo.", + "enabled-label": "Habilitar dock", + "monitors-desc": "Mostrar el dock en monitores especรญficos. Por defecto, se muestra en todos si no se elige ninguno.", + "monitors-only-same-monitor-description": "Mostrar solo las aplicaciones del monitor donde se encuentra el dock.", + "monitors-only-same-monitor-label": "Solo aplicaciones del mismo monitor", + "monitors-title": "Visualizaciรณn en monitor", + "title": "Dock" + }, + "general": { + "copy-settings": "Copiar ajustes", + "fonts-default-description": "Fuente principal utilizada en toda la interfaz.", + "fonts-default-label": "Fuente predeterminada", + "fonts-default-placeholder": "Seleccionar fuente predeterminada...", + "fonts-default-scale-description": "Aumentar o disminuir el tamaรฑo del texto estรกndar.", + "fonts-default-scale-label": "Tamaรฑo de fuente predeterminado", + "fonts-default-search-placeholder": "Buscar fuentes...", + "fonts-desc": "Elige las fuentes utilizadas en toda la interfaz.", + "fonts-monospace-description": "Fuente monoespaciada utilizada para la visualizaciรณn de nรบmeros y estadรญsticas.", + "fonts-monospace-label": "Fuente monoespaciada", + "fonts-monospace-placeholder": "Seleccionar fuente monoespaciada...", + "fonts-monospace-scale-description": "Aumentar o disminuir el tamaรฑo del texto monoespaciado.", + "fonts-monospace-scale-label": "Tamaรฑo de fuente monoespaciada", + "fonts-monospace-search-placeholder": "Buscar fuentes monoespaciadas...", + "fonts-title": "Fuentes", + "keybinds-conflict-description": "La combinaciรณn de teclas ya estรก asignada a {action}.", + "keybinds-conflict-title": "Conflicto de atajos de teclado", + "keybinds-description": "Configura las teclas de navegaciรณn globales para paneles y el lanzador.", + "keybinds-down": "Mover hacia abajo", + "keybinds-enter": "Confirmar / Acciรณn", + "keybinds-escape": "Cerrar / Volver", + "keybinds-left": "Mover a la izquierda", + "keybinds-modifier-description": "Los atajos deben incluir una tecla modificadora (Ctrl o Alt).", + "keybinds-modifier-title": "Modificador requerido", + "keybinds-remove": "Eliminar / Quitar", + "keybinds-right": "Mover a la derecha", + "keybinds-title": "Atajos de teclado de navegaciรณn", + "keybinds-up": "Mover arriba", + "language-desc": "Elige tu idioma preferido para la aplicaciรณn.", + "language-select-auto-detect": "Automรกtico", + "language-select-description": "Selecciona el idioma utilizado en la interfaz de la aplicaciรณn.", + "language-select-label": "Idioma de la aplicaciรณn", + "launch-setup-wizard": "Asistente de configuraciรณn", + "profile-desc": "Edita los detalles de tu usuario y tu avatar.", + "profile-picture-description": "Tu foto de perfil que aparece en toda la interfaz.", + "profile-picture-label": "Foto de perfil", + "profile-select-avatar": "Seleccionar imagen de avatar", + "profile-title": "Perfil", + "profile-tooltip": "Foto de perfil", + "reverse-scrolling-description": "Invertir direcciรณn de desplazamiento interpretada.", + "reverse-scrolling-label": "Desplazamiento inverso", + "screen-corners-desc": "Personaliza el redondeo de las esquinas de la pantalla y los efectos visuales.", + "screen-corners-radius-description": "Ajusta las esquinas redondeadas de la pantalla.", + "screen-corners-radius-label": "Radio de las esquinas de la pantalla", + "screen-corners-radius-reset": "Restablecer el radio de las esquinas de la pantalla", + "screen-corners-show-corners-description": "Muestra esquinas redondeadas en el borde de la pantalla.", + "screen-corners-show-corners-label": "Mostrar esquinas de la pantalla", + "screen-corners-solid-black-description": "Usa negro sรณlido en lugar del color de fondo de la barra.", + "screen-corners-solid-black-label": "Esquinas negras sรณlidas", + "screen-corners-title": "Esquinas de la pantalla", + "settings-copied": "Ajustes copiados al portapapeles", + "smooth-scrolling-description": "Animar el desplazamiento de listas para una experiencia de rueda mรกs fluida.", + "smooth-scrolling-label": "Desplazamiento suave", + "tab-basics": "Bรกsico", + "tab-keybinds": "Atajos de teclado" + }, + "hooks": { + "color-generation-description": "Comando a ejecutar cuando la generaciรณn de color se complete (p. ej., despuรฉs de la tematizaciรณn basada en el fondo de pantalla).", + "color-generation-label": "Colores generados", + "color-generation-placeholder": "p. ej. notify-send \"Colores\" \"Generados\"", + "info-command-info-description": "โ€ข Los comandos se ejecutan a travรฉs de shell (sh -lc)
โ€ข Los comandos se ejecutan en segundo plano (desvinculados)
โ€ข Los botones de prueba se ejecutan con los valores actuales", + "info-parameters-description": "โ€ข Wallpaper hook: $1 = ruta del fondo de pantalla, $2 = nombre de la pantalla, $3 = tema (oscuro/claro)
โ€ข Theme toggle hook: $1 = true/false (estado del modo oscuro)
โ€ข Screen lock/unlock hooks: $1 = lock/unlock (estado de bloqueo de pantalla)
โ€ข Performance mode hooks: Sin parรกmetros
โ€ข Session hook: $1 = action (shutdown/reboot)", + "info-parameters-label": "Parรกmetros disponibles", + "noctalia-started-description": "Comando para ejecutar cuando Noctalia ha terminado de cargarse.", + "noctalia-started-label": "Noctalia iniciado", + "noctalia-started-placeholder": "p. ej. notify-send 'Noctalia cargado'", + "performance-mode-disabled-description": "Comando a ejecutar cuando se deshabilita el modo de rendimiento Noctalia.", + "performance-mode-disabled-label": "Modo de rendimiento deshabilitado", + "performance-mode-disabled-placeholder": "p. ej. notify-send \"Rendimiento\" \"Modo deshabilitado\"", + "performance-mode-enabled-description": "Comando a ejecutar cuando se habilita el modo de rendimiento Noctalia.", + "performance-mode-enabled-label": "Modo de rendimiento habilitado", + "performance-mode-enabled-placeholder": "p. ej. notify-send \"Rendimiento\" \"Modo habilitado\"", + "screen-lock-description": "Comando a ejecutar cuando se bloquea la pantalla.", + "screen-lock-label": "Pantalla bloqueada", + "screen-lock-placeholder": "p. ej. notify-send \"Pantalla\" \"Bloqueada\"", + "screen-unlock-description": "Comando a ejecutar cuando se desbloquea la pantalla.", + "screen-unlock-label": "Pantalla desbloqueada", + "screen-unlock-placeholder": "p. ej. notify-send \"Pantalla\" \"Desbloqueada\"", + "session-description": "Comando que se ejecutarรก antes del apagado o reinicio. Recibe el tipo de acciรณn como $1 (apagado/reinicio).", + "session-label": "Fin de la sesiรณn", + "session-placeholder": "p. ej. notify-send \"Session\" \"$1\"", + "system-hooks-desc": "Configura comandos para que se ejecuten cuando ocurran eventos del sistema.", + "system-hooks-enable-description": "Activa o desactiva todos los comandos hook.", + "system-hooks-enable-label": "Activar hooks", + "system-hooks-title": "Hooks del sistema", + "theme-changed-description": "Comando a ejecutar cuando el tema cambia entre modo oscuro y claro.", + "theme-changed-label": "Tema cambiado", + "theme-changed-placeholder": "p. ej. notify-send \"Tema\" \"Cambiado\"", + "title": "Hooks", + "wallpaper-changed-description": "Comando a ejecutar cuando cambia el fondo de pantalla.", + "wallpaper-changed-label": "Fondo de pantalla cambiado", + "wallpaper-changed-placeholder": "p. ej. notify-send \"Fondo de pantalla\" \"Cambiado\"" + }, + "idle": { + "custom-add": "Aรฑadir comando", + "custom-description": "Ejecuta un comando de shell despuรฉs de un perรญodo de inactividad.", + "custom-entry-command": "Comando", + "custom-entry-delete": "Eliminar", + "custom-entry-edit": "Editar Comando Personalizado", + "custom-entry-name": "Nombre", + "custom-entry-name-placeholder": "p. ej. Apagar luces", + "custom-entry-new": "Nuevo Comando Personalizado", + "custom-entry-timeout": "Tiempo de inactividad", + "custom-entry-timeout-format": "{count} segundo", + "custom-entry-timeout-format-plural": "{count} segundos", + "custom-entry-unnamed": "Comando sin nombre", + "custom-label": "Comandos de inactividad personalizados", + "enable-description": "Apagar la pantalla, bloquear o suspender automรกticamente despuรฉs de un perรญodo de inactividad.", + "enable-label": "Activar gestiรณn de inactividad", + "fade-duration-description": "Segundos para la animaciรณn de fundido a negro antes de que se active cada acciรณn. Cualquier movimiento del ratรณn cancela el fundido.", + "fade-duration-label": "Duraciรณn del fundido", + "lock-description": "Segundos de inactividad antes de que se active la pantalla de bloqueo.", + "lock-label": "Bloquear pantalla", + "resume-command-label": "Reanudar comando", + "screen-off-description": "Segundos de inactividad antes de que los monitores se apaguen.", + "screen-off-label": "Apagar pantalla", + "status-description": "Tiempo de inactividad segรบn lo informado por el compositor.", + "status-label": "Tiempo de inactividad", + "suspend-description": "Segundos de inactividad antes de que el sistema se suspenda.", + "tab-behavior": "Comportamiento", + "tab-custom": "Personalizado", + "timeouts-description": "Establece en 0 para deshabilitar una etapa. Los tiempos de espera se pausan mientras 'Mantener despierto' estรก activo.", + "timeouts-label": "Tiempos de espera", + "title": "Inactividad", + "unavailable": "La monitorizaciรณn nativa de inactividad no estรก disponible en este Compositor." + }, + "indicator": { + "default-value": "Predeterminado: {value}", + "system-default": "Predeterminado del sistema" + }, + "launcher": { + "clipboard-desc": "Accede y gestiona tu historial del portapapeles desde el lanzador.", + "execute-desc": "Configurar cรณmo se inician las aplicaciones.", + "execute-title": "Ejecuciรณn", + "settings-annotation-tool-description": "Comando para ejecutar al hacer clic en el botรณn de anotar en el historial del portapapeles. La imagen se enviarรก a este comando.", + "settings-annotation-tool-label": "Herramienta de anotaciรณn", + "settings-annotation-tool-placeholder": "p. ej. 'gradia', 'satty -f -'", + "settings-auto-paste-description": "Pegar automรกticamente el elemento seleccionado del portapapeles. Requiere wtype.", + "settings-auto-paste-label": "Pegado automรกtico", + "settings-clip-chips-description": "Mostrar una barra de pestaรฑas para filtrar el historial del portapapeles por tipo (Imรกgenes, Enlaces, Archivos, Cรณdigo, etc.).", + "settings-clip-chips-label": "Habilitar chips de categorรญa", + "settings-clip-preview-description": "Muestra una vista previa del contenido del portapapeles al usar el comando >clip.", + "settings-clip-preview-label": "Activar vista previa del portapapeles", + "settings-clip-smart-icons-description": "Mostrar iconos especรญficos para enlaces, archivos, colores y cรณdigo en lugar de un icono de portapapeles genรฉrico.", + "settings-clip-smart-icons-label": "Habilitar iconos inteligentes", + "settings-clip-wrap-text-description": "Ajustar el texto en la lista del portapapeles en lugar de truncarlo.", + "settings-clip-wrap-text-label": "Ajustar texto del portapapeles", + "settings-clipboard-history-description": "Accede a los elementos copiados anteriormente desde el lanzador.", + "settings-clipboard-history-label": "Activar historial del portapapeles", + "settings-clipboard-watch-image-description": "Cadena de comando completa pasada a wl-paste para cambios de imagen (requiere reinicio).", + "settings-clipboard-watch-image-label": "Comando de vigilancia de imรกgenes", + "settings-clipboard-watch-text-description": "Cadena de comando completa pasada a wl-paste para cambios de texto (requiere reinicio).", + "settings-clipboard-watch-text-label": "Comando de vigilancia de texto", + "settings-custom-launch-prefix-description": "Prefijar comandos con un lanzador personalizado (p. ej. 'runapp' para integraciรณn con systemd).", + "settings-custom-launch-prefix-enabled-description": "Usar un prefijo personalizado para lanzar aplicaciones en lugar del mรฉtodo predeterminado.", + "settings-custom-launch-prefix-enabled-label": "Habilitar prefijo de lanzamiento personalizado", + "settings-custom-launch-prefix-label": "Prefijo de lanzamiento personalizado", + "settings-density-description": "Ajusta el tamaรฑo de los iconos de las aplicaciones y la densidad del Launcher.", + "settings-density-label": "Densidad", + "settings-desc": "Personaliza el comportamiento y la apariencia del lanzador.", + "settings-enable-session-search-description": "Mostrar acciones de sesiรณn (bloquear, apagar, reiniciar, etc.) al buscar en el Launcher.", + "settings-enable-session-search-label": "Habilitar bรบsqueda de sesiรณn", + "settings-enable-settings-search-description": "Mostrar los resultados de configuraciรณn al buscar en el lanzador.", + "settings-enable-settings-search-label": "Activar la bรบsqueda en la configuraciรณn", + "settings-enable-windows-search-description": "Buscar y enfocar ventanas activas.", + "settings-enable-windows-search-label": "Habilitar bรบsqueda de ventanas", + "settings-grid-view-description": "Mostrar elementos en una cuadrรญcula en lugar de una lista.", + "settings-icon-mode-description": "Usa iconos nativos del sistema en lugar de iconos Tabler.", + "settings-icon-mode-label": "Usar iconos nativos", + "settings-ignore-mouse-input-description": "Deshabilitar la interacciรณn del ratรณn y la rueda de desplazamiento en el lanzador.", + "settings-ignore-mouse-input-label": "Ignorar la entrada del ratรณn", + "settings-overlay-layer-description": "Muestra el lanzador en la capa de superposiciรณn, encima de las ventanas a pantalla completa. Cuando estรก activado, el lanzador no se adjuntarรก a la barra.", + "settings-overlay-layer-label": "Mostrar sobre pantalla completa", + "settings-position-description": "Elige dรณnde aparece el panel del lanzador.", + "settings-show-categories-description": "Mostrar pestaรฑas de categorรญas para filtrar aplicaciones.", + "settings-show-categories-label": "Mostrar categorรญas", + "settings-show-icon-background-description": "Mostrar un fondo rectangular redondeado detrรกs de los iconos.", + "settings-show-icon-background-label": "Mostrar fondo del icono", + "settings-sort-by-usage-description": "Cuando estรก activado, las aplicaciones mรกs utilizadas aparecen primero en la lista.", + "settings-sort-by-usage-label": "Ordenar por mรกs usados", + "settings-terminal-command-description": "Comando para iniciar el terminal. P. ej. 'kitty -e' o 'gnome-terminal --'.", + "settings-terminal-command-label": "Comando de terminal", + "settings-use-app2unit-description": "Usa un mรฉtodo de lanzamiento alternativo para gestionar mejor los procesos de las aplicaciones y prevenir problemas.", + "settings-use-app2unit-label": "Usar App2Unit para lanzar aplicaciones", + "settings-view-mode-description": "Elige la disposiciรณn para las entradas del Lanzador.", + "settings-view-mode-label": "Modo de vista", + "title": "Lanzador" + }, + "location": { + "auto-locate-description": "Detecta automรกticamente tu ubicaciรณn periรณdicamente usando tu direcciรณn IP.", + "auto-locate-label": "Auto-Localizar", + "calendar-cards-desc": "Organizar y activar/desactivar tarjetas en el panel del calendario.", + "calendar-cards-title": "Tarjetas de calendario", + "calendar-header-label": "Encabezado del calendario", + "calendar-month-label": "Mes del calendario", + "date-time-12hour-format-description": "Muestra la hora en formato de 12 horas en la pantalla de bloqueo y el calendario. El reloj de la barra tiene su propia configuraciรณn.", + "date-time-12hour-format-label": "Utiliza el formato de 12 horas", + "date-time-desc": "Personaliza cรณmo aparecen la fecha y la hora.", + "date-time-first-day-of-week-automatic": "Automรกtico (usar la configuraciรณn regional del sistema)", + "date-time-first-day-of-week-description": "Elige quรฉ dรญa empieza la semana en el calendario.", + "date-time-first-day-of-week-label": "Primer dรญa de la semana", + "date-time-show-events-description": "Mostrar eventos en el panel del calendario.", + "date-time-show-events-label": "Mostrar eventos del calendario", + "date-time-title": "Fecha y hora", + "date-time-use-analog-description": "Mostrar un reloj de estilo analรณgico en la ventana del calendario.", + "date-time-use-analog-label": "Usar reloj de estilo analรณgico", + "date-time-week-numbers-description": "Muestra la semana del aรฑo (p. ej. Semana 38) en el calendario.", + "date-time-week-numbers-label": "Mostrar nรบmeros de semana", + "geolocate-now-button": "Localizar ahora", + "location-desc": "Obtรฉn el clima y la programaciรณn de la luz nocturna precisos estableciendo tu ubicaciรณn.", + "location-search-description": "p. ej. Toronto, ON", + "location-search-label": "Buscar una ubicaciรณn", + "location-search-placeholder": "Ingresa el nombre de la ubicaciรณn", + "location-title": "Tu ubicaciรณn", + "weather-desc": "Elige tu unidad de temperatura preferida.", + "weather-enabled-description": "Mostrar informaciรณn del clima en toda la interfaz y obtener datos meteorolรณgicos. Cuando estรก desactivado, todos los elementos del clima estarรกn ocultos y no se realizarรกn solicitudes de red.", + "weather-enabled-label": "Activar clima", + "weather-fahrenheit-description": "Muestra la temperatura en Fahrenheit en lugar de Celsius.", + "weather-fahrenheit-label": "Mostrar temperatura en Fahrenheit (ยฐF)", + "weather-hide-city-description": "Ocultar el nombre de la ciudad en las pantallas del tiempo en toda la interfaz.", + "weather-hide-city-label": "Ocultar nombre de la ciudad", + "weather-hide-timezone-description": "Ocultar la abreviatura de la zona horaria en las pantallas del tiempo en toda la interfaz.", + "weather-hide-timezone-label": "Ocultar zona horaria", + "weather-show-effects-description": "Muestra efectos visuales adicionales (como lluvia, nieve o relรกmpagos) en la tarjeta del clima.", + "weather-show-effects-label": "Mostrar efectos climรกticos", + "weather-show-in-calendar-description": "Muestra el pronรณstico del tiempo diario directamente en la vista de tu calendario.", + "weather-show-in-calendar-label": "Mostrar el clima en el calendario", + "weather-talia-mascot-always-description": "Siempre usa Talia emojis en lugar de tabler icons.", + "weather-talia-mascot-always-label": "Mostrar siempre los emojis del tiempo de Talia" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "Cuando fprintd (autenticaciรณn por huella dactilar) estรก activo, esta opciรณn te permite iniciar sesiรณn con tu contraseรฑa en lugar de una huella dactilar.", + "allow-password-with-fprintd-label": "Permitir inicio de sesiรณn con contraseรฑa usando fprintd", + "auto-start-auth-description": "Inicia automรกticamente la autenticaciรณn de huellas dactilares sin requerir una pulsaciรณn de tecla o un clic de botรณn.", + "auto-start-auth-label": "Autentificaciรณn de inicio automรกtica", + "clock-format-description": "Personaliza el formato del reloj usando tokens de sintaxis de fecha/hora.", + "clock-format-label": "Formato del reloj", + "clock-style-analog": "Analรณgico", + "clock-style-custom": "Personalizado", + "clock-style-description": "Elige el estilo visual del reloj en la Pantalla de Bloqueo.", + "clock-style-digital": "Digital", + "clock-style-label": "Estilo de Reloj", + "compact-lockscreen-description": "Mostrar solo el campo de inicio de sesiรณn y los controles del sistema, ocultando los widgets del clima y multimedia.", + "compact-lockscreen-label": "Pantalla de bloqueo compacta", + "enable-lockscreen-media-controls-description": "Mostrar controles interactivos de reproducciรณn multimedia en la pantalla de bloqueo.", + "enable-lockscreen-media-controls-label": "Controles Multimedia de la Pantalla de Bloqueo", + "lock-on-suspend-description": "Bloquear la pantalla automรกticamente al suspender el sistema.", + "lock-on-suspend-label": "Bloquear al suspender", + "lock-screen-animations-description": "Activar o desactivar las animaciones de la pantalla de bloqueo.", + "lock-screen-animations-label": "Animaciones de la Pantalla de Bloqueo", + "lock-screen-blur-strength-description": "Aplica un efecto de desenfoque al fondo de pantalla de la pantalla de bloqueo.", + "lock-screen-blur-strength-label": "Intensidad del desenfoque de la pantalla de bloqueo", + "lock-screen-tint-strength-description": "Aplica una superposiciรณn de tinte al fondo de pantalla de la pantalla de bloqueo.", + "lock-screen-tint-strength-label": "Intensidad del tinte de la pantalla de bloqueo", + "monitors-desc": "Mostrar pantalla de bloqueo en monitores especรญficos. Por defecto, se mostrarรก en todos si no se elige ninguno.", + "password-chars-description": "Iconos bonitos usados para ocultar tu contraseรฑa.", + "password-chars-label": "Iconos de contraseรฑa aleatorios", + "show-hibernate-description": "Mostrar la opciรณn 'hibernar' en los controles de energรญa.", + "show-hibernate-label": "Mostrar hibernar", + "show-session-buttons-description": "Permitir el acceso a las opciones de sesiรณn desde la pantalla de bloqueo.", + "show-session-buttons-label": "Opciones de sesiรณn", + "title": "Pantalla de bloqueo" + }, + "network": { + "bluetooth-description": "Activar la gestiรณn de Bluetooth.", + "bluetooth-rssi-polling-description": "Muestrear periรณdicamente el RSSI de los dispositivos conectados a travรฉs de bluetoothctl. Puede no estar disponible para todos los dispositivos; utiliza recursos mรญnimos cuando estรก habilitado.", + "bluetooth-rssi-polling-label": "Sondeo de seรฑal Bluetooth", + "desc": "Gestiona las conexiones Wi-Fi y Bluetooth.", + "wifi-description": "Administrar redes inalรกmbricas (requiere NetworkManager)." + }, + "notifications": { + "duration-critical-urgency-description": "Cuรกnto tiempo permanecen visibles las notificaciones de prioridad crรญtica.", + "duration-critical-urgency-label": "Urgencia crรญtica", + "duration-desc": "Configura cuรกnto tiempo permanecen visibles las notificaciones segรบn su nivel de urgencia.", + "duration-low-urgency-description": "Cuรกnto tiempo permanecen visibles las notificaciones de baja prioridad.", + "duration-low-urgency-label": "Urgencia baja", + "duration-normal-urgency-description": "Cuรกnto tiempo permanecen visibles las notificaciones de prioridad normal.", + "duration-normal-urgency-label": "Urgencia normal", + "duration-respect-expire-description": "Usa el tiempo de expiraciรณn establecido en la notificaciรณn.", + "duration-respect-expire-label": "Respetar tiempo de expiraciรณn", + "duration-title": "Duraciรณn de la notificaciรณn", + "history-clear-dismiss-description": "Borrar notificaciรณn del historial al descartarla.", + "history-clear-dismiss-label": "Borrar al descartar", + "history-critical-urgency-description": "Guardar notificaciones de prioridad crรญtica en el historial.", + "history-critical-urgency-label": "Guardar urgencia crรญtica en el historial", + "history-desc": "Controlar quรฉ notificaciones se guardan en el historial segรบn su nivel de urgencia.", + "history-low-urgency-description": "Guardar notificaciones de prioridad baja en el historial.", + "history-low-urgency-label": "Guardar urgencia baja en el historial", + "history-normal-urgency-description": "Guardar notificaciones de prioridad normal en el historial.", + "history-normal-urgency-label": "Guardar urgencia normal en el historial", + "monitors-desc": "Muestra notificaciones en monitores especรญficos. Por defecto, se muestra en todos si no se elige ninguno.", + "rules-action-block": "Bloque", + "rules-action-block-desc": "Salta completamente.", + "rules-action-hide": "Ocultar", + "rules-action-hide-desc": "Sin ventana emergente, sin sonido, se aรฑade al historial.", + "rules-action-label": "Acciรณn", + "rules-action-mute": "Silenciar", + "rules-action-mute-desc": "Sin sonido, aรบn muestra la ventana emergente y en el historial.", + "rules-add": "Aรฑadir regla", + "rules-delete": "Eliminar regla", + "rules-description": "Coincidir nombre de la aplicaciรณn o contenido. Las reglas se comprueban en orden, y la primera coincidencia se aplica.", + "rules-edit": "Editar regla", + "rules-label": "Reglas de Filtrado", + "rules-pattern-label": "Patrรณn", + "rules-pattern-placeholder": "firefox, discord, o /regex/", + "rules-tab": "Reglas", + "settings-always-on-top-description": "Muestra las notificaciones sobre ventanas a pantalla completa y otras capas.", + "settings-background-opacity-description": "Ajustar la opacidad de los fondos de las notificaciones.", + "settings-density-description": "Elige la densidad de la tarjeta de notificaciรณn.", + "settings-density-label": "Densidad", + "settings-desc": "Configura la apariencia y el comportamiento de las notificaciones.", + "settings-do-not-disturb-description": "Desactiva todas las ventanas emergentes de notificaciรณn cuando estรก activado.", + "settings-enabled-description": "Activar o desactivar el demonio de notificaciones, requiere reiniciar Noctalia shell.", + "settings-enabled-label": "Activar notificaciones", + "settings-location-description": "Dรณnde aparecen las notificaciones en la pantalla.", + "settings-markdown-description": "Renderizar el contenido de las notificaciones usando formato Markdown.", + "settings-markdown-label": "Habilitar Markdown", + "sounds-desc": "Configurar efectos de sonido y volumen de notificaciones.", + "sounds-enabled-description": "Activar efectos de sonido para notificaciones entrantes.", + "sounds-enabled-label": "Activar sonidos de notificaciรณn", + "sounds-excluded-apps-description": "Omitir la reproducciรณn del sonido de notificaciรณn configurado para aplicaciones especรญficas que tienen sus propios sonidos integrados.", + "sounds-excluded-apps-label": "Aplicaciones excluidas", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "Ruta al archivo de sonido reproducido para notificaciones de prioridad crรญtica.", + "sounds-files-critical-label": "Sonido de urgencia crรญtica", + "sounds-files-critical-select-title": "Seleccionar archivo de sonido de urgencia crรญtica", + "sounds-files-desc": "Configurar archivos de sonido personalizados para diferentes niveles de urgencia de notificaciรณn.", + "sounds-files-low-description": "Ruta al archivo de sonido reproducido para notificaciones de prioridad baja.", + "sounds-files-low-label": "Sonido de urgencia baja", + "sounds-files-low-select-title": "Seleccionar archivo de sonido de urgencia baja", + "sounds-files-normal-description": "Ruta al archivo de sonido reproducido para notificaciones de prioridad normal.", + "sounds-files-normal-label": "Sonido de urgencia normal", + "sounds-files-normal-select-title": "Seleccionar archivo de sonido de urgencia normal", + "sounds-files-placeholder": "Introducir ruta al archivo de sonido", + "sounds-files-select-file": "Seleccionar archivo de sonido", + "sounds-files-title": "Archivos de sonido", + "sounds-files-unified-description": "Ruta al archivo de sonido reproducido para notificaciones.", + "sounds-files-unified-label": "Sonido de notificaciรณn", + "sounds-files-unified-select-title": "Seleccionar archivo de sonido de notificaciรณn", + "sounds-separate-description": "Usar diferentes archivos de sonido para notificaciones de prioridad baja, normal y crรญtica.", + "sounds-separate-label": "Usar sonidos diferentes por prioridad", + "sounds-title": "Configuraciรณn de sonido", + "sounds-unavailable-description": "Instala Qt6 Multimedia para habilitar los sonidos de notificaciรณn.", + "sounds-unavailable-label": "Sonidos de notificaciรณn no disponibles", + "sounds-volume-description": "Ajustar el nivel de volumen para los sonidos de notificaciรณn.", + "sounds-volume-label": "Volumen del sonido", + "toast-battery-description": "Mostrar una advertencia cuando el nivel de baterรญa caiga por debajo de este porcentaje.", + "toast-battery-label": "Advertencia de baterรญa", + "toast-desc": "Configurar la apariencia y el comportamiento de las notificaciones toast.", + "toast-keyboard-description": "Mostrar una notificaciรณn cuando cambie la distribuciรณn del teclado.", + "toast-keyboard-label": "Distribuciรณn del teclado", + "toast-media-description": "Mostrar un toast cuando cambie el estado de reproducciรณn multimedia.", + "toast-media-label": "Multimedia" + }, + "osd": { + "always-on-top-description": "Mostrar OSD por encima de ventanas de pantalla completa y otras capas.", + "always-on-top-label": "Siempre encima", + "background-opacity-description": "Controls the transparency of the OSD background.", + "background-opacity-label": "Opacidad del fondo", + "description": "Configura indicadores superpuestos como volumen y brillo.", + "duration-auto-hide-description": "Ajusta el tiempo antes de que el OSD desaparezca.", + "duration-auto-hide-label": "Ocultar despuรฉs de", + "duration-desc": "Cuรกnto tiempo permanece visible el OSD antes de ocultarse automรกticamente.", + "duration-title": "Tiempo de ocultaciรณn automรกtica", + "enabled-description": "Mostrar cambios de volumen y brillo en tiempo real.", + "enabled-label": "Activar visualizaciรณn en pantalla (OSD)", + "events-desc": "Seleccione quรฉ eventos activan la visualizaciรณn en pantalla.", + "general-desc": "Configurar la visibilidad y el comportamiento del OSD.", + "location-description": "Dรณnde aparece la visualizaciรณn en pantalla.", + "monitors-desc": "Mostrar el OSD en monitores especรญficos. Por defecto, en todos si no se elige ninguno.", + "title": "Visualizaciรณn en pantalla", + "types-brightness-description": "Mostrar el OSD cuando cambie el brillo de la pantalla.", + "types-brightness-label": "Brillo", + "types-custom-text-description": "Mostrar el OSD para mensajes de texto personalizados desde IPC.", + "types-custom-text-label": "Texto personalizado", + "types-desc": "Seleccione los eventos que activan el OSD.", + "types-input-volume-description": "Mostrar el OSD cuando cambie el volumen del micrรณfono.", + "types-input-volume-label": "Volumen de entrada", + "types-lockkey-description": "Mostrar el OSD cuando se activen o desactiven Bloq Mayรบs, Bloq Num o Bloq Despl.", + "types-lockkey-label": "Teclas de bloqueo", + "types-media-description": "Mostrar OSD cuando cambie el estado de reproducciรณn multimedia (reproducir, pausar, saltar).", + "types-media-label": "Reproducciรณn de medios", + "types-title": "Eventos de activaciรณn OSD", + "types-volume-description": "Mostrar el OSD cuando cambie el volumen de salida de audio.", + "types-volume-label": "Volumen de salida" + }, + "plugins": { + "auto-update": "Actualizar plugins automรกticamente", + "auto-update-description": "Actualizar automรกticamente todos los plugins cuando se inicie la shell.", + "available-description": "Navegar e instalar plugins desde fuentes configuradas.", + "available-label": "Plugins disponibles", + "available-no-plugins-description": "Comprueba las fuentes de tu plugin o actualiza la lista.", + "available-no-plugins-label": "No hay plugins disponibles", + "check-for-updates": "Buscar actualizaciones", + "checking-for-updates": "Buscando actualizaciones...", + "collision-already-installed": "Este plugin ya estรก instalado", + "collision-custom-version-exists": "Ya hay instalada una versiรณn personalizada de \"{source}\"", + "collision-official-version-exists": "La versiรณn oficial de este plugin ya estรก instalada", + "development-disable": "Desactivar el modo de desarrollo para este complemento", + "development-enable": "Habilitar el modo de desarrollo para este plugin.", + "filter-downloaded": "Descargado", + "filter-not-downloaded": "No descargado", + "filter-tags-description": "Filtrar plugins por categorรญa o estado de descarga.", + "filter-tags-label": "Etiquetas", + "hot-reload-description": "Recarga automรกticamente los plugins cuando sus archivos cambian. รštil para el desarrollo de plugins.", + "hot-reload-label": "Recarga en caliente (modo desarrollo)", + "hot-reloaded": "Plugin recargado: {name}", + "install-error": "Error al instalar: {error}", + "install-incompatible": "{plugin} requiere Noctalia v{version} o superior", + "install-success": "Se instalรณ {plugin} correctamente", + "installed-description": "Gestiona y configura todos los plugins instalados localmente.", + "installed-label": "Plugins instalados", + "installed-no-plugins-description": "Instale los plugins de la secciรณn \"Disponible\".", + "installed-no-plugins-label": "No hay plugins instalados", + "installing": "Instalando {plugin}...", + "notify-updates": "Notificaciones de actualizaciรณn de plugins", + "notify-updates-description": "Mostrar una notificaciรณn emergente cuando haya nuevas actualizaciones de plugins disponibles.", + "open-plugin-page": "Abrir pรกgina del plugin", + "open-plugins-tab": "Abrir la configuraciรณn de los plugins", + "plugin-settings-title": "Ajustes de {plugin}", + "refresh-refreshing": "Actualizando la lista de plugins...", + "refresh-tooltip": "Actualizar plugins disponibles", + "settings-error-not-loaded": "Plugin no cargado", + "settings-saved": "Ajustes del plugin guardados", + "settings-tooltip": "Ajustes del plugin", + "source-custom": "Fuente personalizada", + "sources-add-custom": "Aรฑadir repositorio personalizado", + "sources-add-dialog-description": "Aรฑadir un repositorio de GitHub como fuente de plugin.", + "sources-add-dialog-error": "Error al agregar la fuente del plugin", + "sources-add-dialog-name": "Nombre del repositorio", + "sources-add-dialog-name-placeholder": "Mis plugins personalizados", + "sources-add-dialog-success": "Fuente del plugin aรฑadida con รฉxito", + "sources-add-dialog-title": "Aรฑadir fuente del plugin", + "sources-add-dialog-url": "URL del repositorio", + "sources-description": "Gestionar repositorios de plugins.", + "sources-placeholder": "Mi repositorio genial", + "sources-remove-tooltip": "Eliminar fuente del plugin", + "title": "Plugins", + "translations-reloaded": "Traducciones recargadas: {name}", + "uninstall-dialog-description": "ยฟEstรก seguro de que quiere desinstalar {plugin}? Esto eliminarรก todos los datos del plugin.", + "uninstall-dialog-title": "Desinstalar plugin", + "uninstall-error": "Error al desinstalar: {error}", + "uninstall-success": "{plugin} desinstalado correctamente", + "uninstalling": "Desinstalando {plugin}...", + "update-all": "Actualizar todo ({count})", + "update-all-success": "Todos los plugins se actualizaron correctamente", + "update-available": "Nueva actualizaciรณn de plugin disponible", + "update-available-plural": "Nuevas actualizaciones de plugins disponibles ({count})", + "update-error": "Error al actualizar el plugin: {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (requiere Noctalia v{required})", + "update-success": "{plugin} actualizado a la versiรณn {version}", + "update-version": "v{current} โ†’ v{new}", + "updating": "Actualizando..." + }, + "region": { + "title": "Regiรณn" + }, + "session-menu": { + "countdown-duration-description": "Establecer cuรกnto tiempo dura el temporizador de cuenta regresiva antes de ejecutar acciones de energรญa.", + "countdown-duration-label": "Duraciรณn de la cuenta regresiva", + "enable-countdown-description": "Mostrar un temporizador de cuenta regresiva antes de ejecutar acciones de energรญa.", + "enable-countdown-label": "Activar temporizador de cuenta regresiva", + "entries-desc": "Personalizar quรฉ acciones de energรญa aparecen en el menรบ de sesiรณn y en quรฉ orden.", + "entries-title": "Acciones de energรญa", + "entry-settings-command-description": "Comando personalizado para ejecutar para esta acciรณn. Dejar vacรญo para usar el comando del sistema predeterminado.", + "entry-settings-command-placeholder": "p. ej. systemctl poweroff", + "entry-settings-default-command-lock": "Pantalla de bloqueo interna (sin comando)", + "entry-settings-default-command-logout": "Cierre de sesiรณn interno (sin comando)", + "entry-settings-default-info-description": "Si no se especifica un comando personalizado, se utilizarรก el comando predeterminado del sistema.", + "entry-settings-default-info-label": "Comando predeterminado", + "entry-settings-keybind-description": "Asigna una combinaciรณn de teclas para activar esta acciรณn cuando el menรบ de sesiรณn estรฉ abierto.", + "entry-settings-keybind-placeholder": "Pulsa Grabar para asignar una tecla...", + "entry-settings-keybind-recording": "Grabando... Pulsa cualquier combinaciรณn de teclas", + "entry-settings-title": "Configurar {entry}", + "entry-settings-tooltip": "Configurar comando", + "general-desc": "Configurar el comportamiento y la apariencia del panel del menรบ de sesiรณn.", + "large-buttons-layout-description": "Elige cรณmo se muestran los botones del menรบ de sesiรณn.", + "large-buttons-layout-label": "Diseรฑo de botones grandes", + "large-buttons-style-description": "Mostrar el menรบ de sesiรณn con botones grandes en un diseรฑo de cuadrรญcula.", + "large-buttons-style-label": "Estilo de botones grandes", + "position-description": "Elige dรณnde aparece el panel del menรบ de sesiรณn cuando se abre.", + "show-header-description": "Mostrar el tรญtulo y el botรณn de cerrar en la parte superior del menรบ de sesiรณn.", + "show-header-label": "Mostrar encabezado", + "show-keybinds-description": "Mostrar sugerencias de atajos de teclado en las opciones de sesiรณn.", + "show-keybinds-label": "Mostrar atajos de teclado" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Mantener los widgets de escritorio visibles mientras el modo de rendimiento Noctalia estรฉ habilitado.", + "noctalia-performance-disable-desktop-widgets-label": "Habilitar widgets de escritorio en modo de rendimiento", + "noctalia-performance-disable-wallpaper-description": "Mantรฉn visibles los fondos de pantalla del escritorio, la vista general y la pantalla de bloqueo mientras el modo de rendimiento Noctalia estรฉ activado.", + "noctalia-performance-disable-wallpaper-label": "Habilitar el renderizado de fondo de pantalla en modo de rendimiento", + "title": "Sistema" + }, + "system-monitor": { + "critical-color-label": "Color crรญtico", + "custom-highlight-colors-title-label": "Colores de resaltado personalizados", + "disk-available-label": "Espacio en disco disponible", + "disk-section-label": "Uso de disco", + "enable-dgpu-monitoring-description": "Advertencia: Esto activarรก tu GPU dedicada (NVIDIA/AMD), lo que podrรญa afectar significativamente la duraciรณn de la baterรญa en portรกtiles con grรกficos hรญbridos.", + "enable-dgpu-monitoring-label": "Habilitar la monitorizaciรณn de la GPU dedicada", + "external-monitor-description": "Ingrese el comando o ruta de la aplicaciรณn para ejecutar al activar la aplicaciรณn externa del monitor del sistema.", + "external-monitor-label": "Comando del monitor del sistema externo", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "Configurar el comportamiento y la apariencia del monitor del sistema.", + "gpu-section-label": "Temperatura de la GPU", + "highlight-colors-section-label": "Colores de resaltado", + "polling-interval-label": "Intervalo de sondeo", + "polling-section-description": "Configure la frecuencia con la que se actualiza cada mรฉtrica del sistema.", + "polling-section-label": "Intervalos de sondeo", + "threshold-critical": "Crรญtico", + "threshold-warning": "Advertencia", + "thresholds-section-description": "Ajusta los umbrales de advertencia/crรญtico y los intervalos de sondeo para cada mรฉtrica del sistema.", + "use-custom-highlight-colors-description": "Cuando estรก desactivado, se usan los colores de resaltado predeterminados del tema.", + "use-custom-highlight-colors-label": "Usar colores de resaltado personalizados", + "warning-color-label": "Color de advertencia" + }, + "user-interface": { + "allow-panels-without-bar-description": "Cuando estรก activado, los paneles se pueden abrir en cualquier pantalla. Cuando estรก desactivado, los paneles solo se abrirรกn en las pantallas que tengan una barra, lo que puede reducir el uso de memoria.", + "allow-panels-without-bar-label": "Permitir paneles en pantallas sin barra", + "animation-disable-description": "Desactiva todas las animaciones para una experiencia mรกs rรกpida y con mayor capacidad de respuesta.", + "animation-disable-label": "Desactivar animaciones de la interfaz de usuario", + "animation-speed-description": "Ajustar la velocidad global de la animaciรณn.", + "animation-speed-label": "Velocidad de animaciรณn", + "animation-speed-reset": "Restablecer la velocidad de la animaciรณn", + "appearance-desc": "Personaliza elementos visuales como texto emergente, bordes y sombras.", + "blur-behind-description": "Difumina el รกrea detrรกs de las barras y los paneles usando el protocolo de desenfoque del compositor.", + "blur-behind-label": "Desenfoque de fondo", + "box-border-description": "Muestra un contorno alrededor de las รกreas de contenido.", + "box-border-label": "Contorno del contenedor", + "box-border-radius-description": "Ajusta la redondez de las esquinas de las secciones principales del diseรฑo, como barras laterales, tarjetas y paneles de contenido.", + "box-border-radius-label": "Radio del contenedor", + "control-border-radius-description": "Controla la curvatura de los elementos interactivos, incluyendo botones, interruptores y campos de texto.", + "control-border-radius-label": "Radio de entrada", + "desc": "Personaliza la apariencia, el ambiente y el comportamiento de la interfaz.", + "dim-desktop-description": "Atenuar el escritorio cuando los paneles o menรบs estรฉn abiertos.", + "dim-desktop-label": "Atenuar escritorio", + "dimmer-opacity-description": "Define el nivel de opacidad para el atenuado del escritorio.", + "dimmer-opacity-label": "Opacidad del escritorio atenuado", + "panel-background-opacity-description": "Establecer la opacidad del fondo para todos los paneles (barra, lanzador, ajustes, etc...).", + "panel-background-opacity-label": "Opacidad del fondo del panel", + "panels-attached-to-bar-description": "Los paneles se fijan a la barra y a los bordes de la pantalla, creando una apariencia integrada con elegantes esquinas invertidas.", + "panels-attached-to-bar-label": "Ajustar paneles a los bordes", + "panels-overlay-description": "Garantiza que los paneles y la barra permanezcan visibles, incluso sobre aplicaciones en pantalla completa.", + "panels-overlay-label": "Mantener paneles y barra en la parte superior", + "scaling-description": "Cambia el tamaรฑo de la interfaz de usuario general, excluyendo la barra.", + "scaling-label": "Escalado de la interfaz", + "scrollbar-always-visible-description": "Mantener las barras de desplazamiento visibles siempre que el contenido sea desplazable, en lugar de mostrarlas solo al pasar el cursor.", + "scrollbar-always-visible-label": "Mostrar siempre barras de desplazamiento", + "settings-panel-header": "Panel de Configuraciรณn", + "settings-panel-mode-description": "Elegir diseรฑo de configuraciรณn (puede requerir reapertura).", + "settings-panel-mode-label": "Modo del panel de configuraciรณn", + "settings-panel-sidebar-card-style-description": "Envuelve la barra lateral de ajustes en un fondo relleno con esquinas redondeadas.", + "settings-panel-sidebar-card-style-label": "Estilo de Tarjeta de la Barra Lateral", + "shadows-description": "Habilita sombras paralelas debajo de las barras y los paneles.", + "shadows-direction-description": "Elige dรณnde se proyecta la sombra.", + "shadows-direction-label": "Direcciรณn de la sombra", + "shadows-label": "Sombras paralelas", + "title": "Interfaz de usuario", + "tooltips-description": "Activar o desactivar los avisos emergentes en toda la interfaz.", + "tooltips-label": "Mostrar textos emergentes", + "translucent-widgets-description": "Haz que los botones, pestaรฑas y otros widgets dentro de los paneles sean semitransparentes.", + "translucent-widgets-label": "Widgets translรบcidos" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "Alfabรฉtico", + "automation-change-mode-description": "Elija cรณmo se seleccionan los fondos de pantalla al cambiar automรกticamente.", + "automation-change-mode-label": "Modo de cambio", + "automation-custom-interval-description": "Introduce el tiempo en formato HH:MM (p. ej. 01:30).", + "automation-custom-interval-label": "Intervalo personalizado", + "automation-interval-description": "Con quรฉ frecuencia cambiar los fondos de pantalla automรกticamente.", + "automation-interval-label": "Intervalo del fondo de pantalla", + "automation-random-wallpaper-description": "Programa cambios aleatorios del fondo de pantalla a intervalos regulares.", + "automation-scheduled-change-description": "Cambiar automรกticamente los fondos de pantalla a intervalos regulares.", + "automation-scheduled-change-label": "Cambio programado", + "look-feel-edge-smoothness-description": "Aplica un efecto suave y difuminado al borde de las transiciones.", + "look-feel-edge-smoothness-label": "Suavizar borde de la transiciรณn", + "look-feel-fill-color-description": "Elige un color de relleno que pueda aparecer detrรกs del fondo de pantalla.", + "look-feel-fill-mode-description": "Selecciona cรณmo debe escalar la imagen para coincidir con la resoluciรณn de tu monitor.", + "look-feel-fill-mode-label": "Modo de relleno", + "look-feel-skip-startup-transition-description": "Omitir la animaciรณn del fondo de pantalla al iniciar la shell.", + "look-feel-skip-startup-transition-label": "Omitir transiciรณn de inicio", + "look-feel-title": "Apariencia y estilo", + "look-feel-transition-duration-description": "Duraciรณn de las animaciones de transiciรณn en segundos.", + "look-feel-transition-duration-label": "Duraciรณn de la transiciรณn", + "look-feel-transition-type-description": "Tipo de animaciรณn al cambiar entre fondos de pantalla.", + "look-feel-transition-type-label": "Tipo de transiciรณn", + "settings-clear-cache-button": "Borrar cachรฉ", + "settings-clear-cache-description": "Limpiar fondos de pantalla redimensionados en cachรฉ para liberar espacio en disco.", + "settings-clear-cache-label": "Cachรฉ de fondos de pantalla", + "settings-clear-cache-toast": "Cachรฉ de fondos de pantalla borrada", + "settings-desc": "Controla cรณmo se gestionan y muestran los fondos de pantalla.", + "settings-enable-management-description": "Gestiona fondos de pantalla con Noctalia. Desmarca si prefieres usar otra aplicaciรณn.", + "settings-enable-management-label": "Activar gestiรณn de fondos de pantalla", + "settings-enable-overview-description": "Aplica un fondo de pantalla difuminado y atenuado a la pantalla de vista general.", + "settings-enable-overview-label": "Activar fondo de pantalla de vista general", + "settings-folder-description": "Ruta a tu carpeta principal de fondos de pantalla.", + "settings-folder-label": "Carpeta de fondos de pantalla", + "settings-hide-wallpaper-filenames-tooltip-hide": "Ocultar nombres de archivo", + "settings-hide-wallpaper-filenames-tooltip-show": "Mostrar nombres de archivo", + "settings-monitor-specific-description": "Establece una carpeta de fondos de pantalla diferente para cada monitor.", + "settings-monitor-specific-label": "Directorios especรญficos por monitor", + "settings-monitor-specific-tooltip": "Carpeta de fondos de pantalla del monitor", + "settings-overview-blur-strength-description": "Aplica la intensidad del desenfoque a la vista general.", + "settings-overview-blur-strength-label": "Intensidad del desenfoque de la vista general", + "settings-overview-tint-description": "Aplica la intensidad del tinte a la vista general.", + "settings-overview-tint-label": "Intensidad del tinte de la vista general", + "settings-recursive-search-description": "Buscar fondos de pantalla tambiรฉn en subcarpetas del directorio de fondos de pantalla.", + "settings-recursive-search-label": "Buscar en subcarpetas", + "settings-select-monitor-folder": "Seleccionar carpeta de fondos de pantalla del monitor", + "settings-selector-description": "Elige tu fondo de pantalla.", + "settings-selector-position-description": "Elige dรณnde aparece el panel selector de fondo de pantalla.", + "settings-show-hidden-files-tooltip-hide": "Ocultar archivos ocultos", + "settings-show-hidden-files-tooltip-show": "Mostrar archivos ocultos", + "settings-title": "Configuraciรณn del fondo de pantalla", + "settings-use-original-images-description": "Omitir el redimensionamiento de fondos de pantalla antes de mostrarlos. Ahorra espacio en disco y reduce el uso de CPU durante los cambios de fondo de pantalla, pero puede usar mรกs memoria para imรกgenes muy grandes.", + "settings-use-original-images-label": "Usar imรกgenes originales", + "settings-view-mode-description": "Elige cรณmo se muestran los fondos de pantalla desde tu directorio.", + "settings-view-mode-label": "Modo de visualizaciรณn", + "view-mode-browse": "Explorar directorios", + "view-mode-cycle-tooltip": "Modo de visualizaciรณn: {mode} (clic para cambiar)", + "view-mode-recursive": "Incluir subdirectorios", + "view-mode-single": "Directorio raรญz" + } + }, + "placeholders": { + "add-new-keybind": "Aรฑadir atajo de teclado", + "command-example": "echo \"Hola, mundo\"", + "enter-command": "Introduce el comando a ejecutar (aplicaciรณn o script personalizado)", + "enter-ipc-identifier": "Introduce un identificador รบnico para los comandos IPC", + "enter-path": "Introduce la ruta...", + "enter-text-to-collapse": "p. ej. 'no se estรก reproduciendo nada'. Usa /regex/ para patrones.", + "enter-tooltip": "Introduce texto emergente", + "enter-width-pixels": "Introduce el ancho en pรญxeles", + "keybind-recording": "Grabando atajo de teclado...", + "search": "Buscar...", + "search-icons": "p. ej. noctalia, niri, battery, cloud", + "search-launcher": "Buscar entradas... o usa > para comandos", + "search-wallhaven": "Buscar en Wallhaven...", + "search-wallpapers": "Escribe para filtrar fondos de pantalla..." + }, + "positions": { + "bottom": "Inferior", + "bottom-center": "Inferior central", + "bottom-left": "Inferior izquierda", + "bottom-right": "Inferior derecha", + "center": "Centro", + "center-left": "Izquierda centrado", + "center-right": "Derecha centrado", + "close-to-bar": "Cerca del botรณn de la barra", + "follow-bar": "Seguir barra", + "left": "Izquierda", + "right": "Derecha", + "top": "Superior", + "top-center": "Superior central", + "top-left": "Superior izquierda", + "top-right": "Superior derecha" + }, + "session-menu": { + "action-in-seconds": "{action} en {seconds} segundos...", + "cancel-timer": "Cancelar temporizador", + "title": "Menรบ de sesiรณn" + }, + "setup": { + "all-done": "ยกListo!", + "appearance": { + "subheader": "Elige el modo oscuro y las fuentes de color (fondo de pantalla o predefinidas)." + }, + "customize": { + "header": "Personaliza tu experiencia", + "subheader": "Ajusta la posiciรณn de la barra, densidad, escala y mรกs." + }, + "skip-setup": "Omitir la configuraciรณn", + "telemetry-wizard-done": "ยกEntendido!", + "telemetry-wizard-note": "Tienes el control โ€” activa o desactiva esto cuando quieras en los ajustes", + "telemetry-wizard-subtitle": "Hemos aรฑadido analรญticas anรณnimas para ayudar a mejorar Noctalia", + "telemetry-wizard-title": "Actualizaciรณn de privacidad", + "wallpaper": { + "choose-dir": "Elige un directorio que contenga tus fondos de pantalla", + "dir-browse": "Seleccionar carpeta", + "dir-description": "Elige la carpeta que contiene tus fondos de pantalla.", + "dir-label": "Directorio de fondos de pantalla", + "dir-select-title": "Seleccionar carpeta de fondos", + "header": "Elige tu fondo", + "no-dir": "No se seleccionรณ un directorio de fondos", + "no-valid": "No se encontraron imรกgenes vรกlidas en: {dir}", + "none-in-dir": "No se encontraron fondos en el directorio", + "preview-error": "No se pudo cargar la imagen", + "select-prompt": "Selecciona un fondo abajo", + "subheader": "Define el ambiente con un bonito fondo." + }, + "welcome": "Bienvenido", + "welcome-note": "Solo algunos ajustes bรกsicos para empezar โ€” las opciones al completo estรกn en configuraciรณn", + "welcome-subtitle": "Hagamos que tu escritorio sea รบnico", + "welcome-title": "ยกBienvenido a Noctalia!" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "No se detectรณ ningรบn reproductor multimedia", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "Aplicaciรณn desconocida", + "uptime": "Actividad: {uptime}", + "user-requested": "Solicitado por el usuario", + "welcome-back": "ยกBienvenido de nuevo,", + "widget-settings-title": "Configuraciรณn de {widget}" + }, + "system-monitor": { + "core-usage": "Uso del nรบcleo {id}", + "cpu-temp": "Temperatura de CPU", + "cpu-usage": "Uso de CPU", + "disk": "Disco", + "download-speed": "Velocidad de descarga", + "gpu-temp": "Temperatura de GPU", + "load-average": "Carga promedio", + "title": "Monitor del sistema", + "upload-speed": "Velocidad de subida" + }, + "toast": { + "airplane-mode": { + "description": "Desactivar todas las comunicaciones inalรกmbricas.", + "title": "Modo aviรณn" + }, + "battery": { + "critical": "Baterรญa crรญtica", + "critical-desc": "Baterรญa al {percent}% โ€” por favor, conecta el cargador inmediatamente", + "low": "Baterรญa baja", + "low-desc": "Baterรญa al {percent}% โ€” por favor, conecta el cargador" + }, + "bluetooth": { + "address-copied": "Direcciรณn copiada al portapapeles", + "auto-connect-disabled": "Conexiรณn automรกtica desactivada", + "auto-connect-enabled": "Conexiรณn automรกtica activada", + "auto-connecting": "Conectando a {count} dispositivo(s)...", + "confirm-code": "Confirma el cรณdigo {value} en el otro dispositivo.", + "connect-failed": "Error al conectar con el dispositivo", + "disconnect-failed": "Error al desconectar del dispositivo", + "discoverable-change-failed": "Error al cambiar el estado de visibilidad", + "discoverable-disabled": "Visible desactivado", + "discoverable-enabled": "Visible activado", + "display-code": "Introduce el cรณdigo {value} en el otro dispositivo.", + "forget-failed": "Error al olvidar el dispositivo", + "pair-failed": "Error al emparejar el dispositivo", + "passkey-required": "El dispositivo requiere una clave de acceso.", + "pincode-required": "El dispositivo requiere cรณdigo PIN.", + "state-change-failed": "Error al cambiar el estado de Bluetooth" + }, + "clipboard": { + "long-text": "Texto largo", + "unavailable": "Historial del portapapeles no disponible", + "unavailable-desc": "La aplicaciรณn 'cliphist' no estรก instalada. Por favor, instรกlala para usar las funciones de historial del portapapeles" + }, + "custom-command-failed": { + "description": "El comando fallรณ: {command}\\nCรณdigo de salida: {code}", + "title": "Comando personalizado fallรณ" + }, + "do-not-disturb": { + "disabled": "'No molestar' desactivado", + "disabled-desc": "Mostrando todas las notificaciones", + "enabled": "'No molestar' activado", + "enabled-desc": "Encontrarรกs estas notificaciones en tu historial" + }, + "donation-opened": "Pรกgina de donaciรณn abierta en tu navegador", + "internet-limited": "Conectado sin Internet", + "keyboard-layout": { + "changed": "Distribuciรณn de teclado cambiada a {layout}", + "title": "Teclado" + }, + "missing-control-center": { + "description": "El widget del centro de control se eliminรณ de la barra. Para acceder a รฉl nuevamente desde la barra, debes volver a aรฑadir el widget. Tambiรฉn puedes abrirlo haciendo clic derecho en la barra", + "label": "Se eliminรณ el รบltimo widget del centro de control" + }, + "night-light": { + "forced": "Activaciรณn forzada", + "normal": "Modo normal", + "not-installed": "wlsunset no estรก instalado" + }, + "noctalia-performance": { + "disabled": "Modo de rendimiento desactivado", + "enabled": "Modo de rendimiento activado", + "label": "Noctalia Rendimiento" + }, + "power-profile": { + "changed": "Perfil de energรญa cambiado", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "Se encontrรณ un error al procesar las plantillas", + "title": "Fallรณ el procesamiento de plantillas" + }, + "vpn": { + "connected": "Conectado a '{name}'", + "disconnected": "Desconectado de '{name}'" + }, + "wallpaper-colors": { + "disabled": "Colores del fondo de pantalla desactivados", + "enabled": "Colores del fondo de pantalla activados", + "label": "Generaciรณn de colores para fondos de pantalla" + }, + "wifi": { + "connected": "Conectado a '{ssid}'", + "connection-failed": "Error de conexiรณn", + "connection-timeout": "Tiempo de conexiรณn agotado", + "disconnected": "Desconectado de '{ssid}'", + "incorrect-password": "Contraseรฑa incorrecta", + "network-not-found": "Red no encontrada" + } + }, + "tooltips": { + "add-widget": "Aรฑadir widget", + "auto-connect": "Alternar conexiรณn automรกtica para este dispositivo", + "bluetooth-auto-connect-off": "Conexiรณn automรกtica desactivada", + "bluetooth-auto-connect-on": "Conexiรณn automรกtica activada", + "bluetooth-devices": "Dispositivos Bluetooth", + "brightness-at": "Brillo: {brightness}%", + "click-to-start-recording": "Grabadora de pantalla (iniciar grabaciรณn)", + "click-to-stop-recording": "Grabadora de pantalla (detener grabaciรณn)", + "collapse": "Colapsar barra lateral", + "copy-address": "Copiar direcciรณn", + "delete-notification": "Eliminar notificaciรณn", + "dismiss-notification": "Descartar notificaciรณn", + "do-not-disturb-enabled": "No molestar", + "expand": "Expandir barra lateral", + "forget-network": "Olvidar red", + "grid-view": "Vista de cuadrรญcula", + "hidden-files-hide": "Archivos ocultos", + "hide-unnamed-devices": "Ocultar dispositivos sin nombre", + "home": "Directorio home", + "input-muted": "Activar/desactivar silencio de entrada", + "keep-awake": "Mantener despierto", + "keyboard-layout": "Distribuciรณn de teclado {layout}", + "list-view": "Vista de lista", + "manage-vpn": "Conexiones VPN", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "Se ha alcanzado el nรบmero mรกximo de widgets", + "microphone-volume-at": "Volumen del micrรณfono: {volume}%", + "move-to-section": "Mover a {section}", + "mute": "Silenciar", + "next-media": "Siguiente pista", + "next-month": "Mes siguiente", + "night-light-not-installed": "Luz nocturna (no disponible)", + "noctalia-performance-enabled": "Modo de rendimiento Noctalia", + "open-annotation-tool": "Abrir con herramienta de anotaciรณn", + "open-control-center": "Centro de control", + "open-notification-history-enable-dnd": "Historial de notificaciones", + "open-settings": "Configuraciรณn", + "open-tray-dropdown": "Bandeja del sistema", + "output-muted": "Alternar silencio de salida", + "power-profile": "Perfil de energรญa {profile}", + "previous-media": "Pista anterior", + "previous-month": "Mes anterior", + "refresh-devices": "Actualizar dispositivos", + "refresh-wallhaven": "Actualizar resultados de Wallhaven", + "refresh-wallpaper-list": "Actualizar lista de fondos de pantalla", + "remove-widget": "Eliminar widget", + "search-close": "Cerrar bรบsqueda", + "session-menu": "Menรบ de sesiรณn", + "show-all-devices": "Mostrar todos los dispositivos", + "switch-to-dark-mode": "Modo oscuro", + "switch-to-light-mode": "Modo claro", + "unmute": "Activar el audio", + "up": "Directorio superior", + "volume-at": "Volumen de salida: {volume}%", + "wallpaper-selector": "Selector de fondos de pantalla" + }, + "wallpaper": { + "browse": { + "empty-directory": "Este directorio estรก vacรญo.", + "go-root": "Ir a la carpeta raรญz de fondos de pantalla", + "go-up": "Ir a la carpeta superior", + "go-up-hint": "Usa el botรณn de atrรกs para navegar hacia arriba." + }, + "configure-directory": "Configura tu directorio de fondos de pantalla con imรกgenes.", + "fill-modes": { + "crop": "Recortar (Rellenar)", + "fit": "Ajustar (Contener)", + "repeat": "Repetir (Mosaico)", + "stretch": "Estirar" + }, + "no-match": "No se encontraron coincidencias.", + "no-wallpaper": "No se encontraron fondos de pantalla.", + "panel": { + "apikey-help": "Se requiere una clave API para acceder a contenido NSFW.", + "apikey-label": "Clave API", + "apikey-managed-by-env": "Gestionado a travรฉs de la variable de entorno NOCTALIA_WALLHAVEN_API_KEY.", + "apikey-placeholder": "Introduce tu clave API de Wallhaven", + "appearance-dark-tab": "Oscuro", + "appearance-light-tab": "Claro", + "categories-anime": "Anime", + "categories-label": "Categorรญas", + "categories-people": "Personas", + "color-extraction-disabled": "Usar extracciรณn de colores del fondo de pantalla", + "color-extraction-enabled": "Usar esquemas de color predefinidos", + "header-devices-apply-all-tooltip": "Elegir fondo de pantalla por monitor", + "header-devices-per-monitor-tooltip": "Aplicar a todos los monitores", + "header-link-light-dark-tooltip": "Mantener fondo de pantalla al cambiar de tema", + "header-separate-light-dark-tooltip": "Cambiar fondo de pantalla automรกticamente con el tema", + "header-sun-linked-tooltip": "Separar fondos de pantalla claros y oscuros", + "header-sun-separate-tooltip": "Mismo fondo de pantalla para claro y oscuro", + "order-asc": "Ascendente", + "order-desc": "Descendente", + "order-label": "Orden", + "purity-label": "Filtro de contenido", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "Sketchy", + "ratios-all-portrait": "Todos verticales", + "ratios-all-wide": "Todos horizontales", + "ratios-any": "Cualquiera", + "ratios-label": "Relaciรณn de aspecto", + "resolution-atleast": "Al menos", + "resolution-exact": "Exacto", + "resolution-label": "Resoluciรณn", + "resolution-mode-label": "Modo", + "solid-color-tooltip": "Fondo de color sรณlido", + "sort-date-asc": "Ordenar por mรกs antiguos primero", + "sort-date-desc": "Ordenar por mรกs reciente primero", + "sort-name-asc": "Ordenar por nombre (A-Z)", + "sort-name-desc": "Ordenar por nombre (Z-A)", + "sort-random": "Ordenar aleatoriamente", + "sorting-date-added": "Fecha de adiciรณn", + "sorting-favorites": "Favoritos", + "sorting-label": "Ordenar por", + "sorting-relevance": "Relevancia", + "sorting-toplist": "Mejores", + "sorting-views": "Visualizaciones", + "source-label": "Fuente", + "source-wallhaven": "Wallhaven", + "title": "Selector de fondo de pantalla", + "wallhaven-settings-title": "Configuraciรณn de Wallhaven" + }, + "transitions": { + "disc": "Disco", + "fade": "Desvanecer", + "honeycomb": "Panal", + "pixelate": "Pixelar", + "stripes": "Rayas", + "wipe": "Barrido" + }, + "try-different-search": "Intenta con una bรบsqueda diferente.", + "wallhaven": { + "loading": "Cargando fondos de pantalla...", + "no-results": "No se encontraron fondos de pantalla. Intenta con una bรบsqueda diferente.", + "page": "{current} de {total}", + "page-prefix": "Pรกgina", + "page-suffix": "de {total}" + } + }, + "weather": { + "clear-sky": "Cielo despejado", + "drizzle": "Llovizna", + "fog": "Niebla", + "mainly-clear": "Mayormente despejado", + "overcast": "Nublado", + "partly-cloudy": "Parcialmente nublado", + "rain-showers": "Chubascos", + "snow": "Nieve", + "thunderstorm": "Tormenta" + }, + "widgets": { + "color-picker": { + "palette-description": "Elige entre una amplia gama de colores predefinidos.", + "palette-label": "Paleta", + "palette-theme-colors": "Acceso rรกpido a la paleta de colores de tu tema.", + "title": "Selector de colores" + }, + "datetime-tokens": { + "ampm-lowercase": "am/pm en minรบsculas", + "ampm-uppercase": "AM/PM en mayรบsculas", + "common-12hour-time-minutes": "Hora en formato de 12 horas con minutos", + "common-24hour-time-minutes": "Hora en formato de 24 horas con minutos", + "common-24hour-time-seconds": "Hora en formato de 24 horas con segundos", + "common-european-date": "Formato de fecha europeo", + "common-iso-date": "Formato de fecha ISO", + "common-us-date": "Formato de fecha estadounidense", + "common-weekday-date": "Dรญa de la semana con fecha", + "common-weekday-month-day": "Dรญa de la semana, mes y dรญa", + "day-abbreviated": "Nombre del dรญa abreviado", + "day-full": "Nombre completo del dรญa", + "day-leading-zero": "Dรญa con cero inicial (01-31)", + "day-no-leading-zero": "Dรญa sin cero inicial (1-31)", + "hour-leading-zero": "Hora con cero inicial (00-23) โ€” formato de 24 horas", + "hour-no-leading-zero": "Hora sin cero inicial (0-23) โ€” formato de 24 horas", + "minute-leading-zero": "Minuto con cero inicial (00-59)", + "minute-no-leading-zero": "Minuto sin cero inicial (0-59)", + "month-abbreviated": "Nombre del mes abreviado", + "month-full": "Nombre del mes completo", + "month-number-leading-zero": "Mes como nรบmero con cero inicial (01-12)", + "month-number-no-zero": "Mes como nรบmero sin cero inicial (1-12)", + "second-leading-zero": "Segundo con cero inicial (00-59)", + "second-no-leading-zero": "Segundo sin cero inicial (0-59)", + "timezone-abbreviation": "Abreviatura de la zona horaria", + "year-four-digit": "Aรฑo como nรบmero de cuatro dรญgitos", + "year-two-digit": "Aรฑo como nรบmero de dos dรญgitos (00-99)" + }, + "file-picker": { + "item": "elemento", + "items": "elementos", + "search-placeholder": "Buscar archivos y carpetas...", + "select-current": "Seleccionar Actual", + "select-file": "Seleccionar Archivo", + "select-folder": "Seleccionar Carpeta", + "selected": "Seleccionado:", + "title": "Selector de Archivos" + }, + "file-picker-title": "Seleccionar un archivo", + "icon-picker": { + "title": "Selector de Iconos" + }, + "tooltip-placeholder": "Marcador de posiciรณn" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "Identidad Anรณnima", + "ca-cert": "Certificado CA", + "eap-method": "Mรฉtodo EAP", + "password": "Contraseรฑa de Usuario", + "phase2-auth": "Autenticaciรณn de Fase 2", + "username": "Identidad / Nombre de Usuario" + }, + "panel": { + "action-required": "Acciรณn requerida", + "add-hidden-network": "Aรฑadir red oculta", + "add-network": "Aรฑadir red", + "available-interfaces": "Interfaces disponibles", + "available-networks": "Redes disponibles", + "disabled": "Wi-Fi estรก desactivado", + "disconnecting": "Desconectando...", + "dns": "DNS", + "enable-message": "Activa Wi-Fi para ver las redes disponibles.", + "enter-password": "Ingresa la contraseรฑa...", + "forget": "Olvidar", + "forget-network": "ยฟOlvidar esta red?", + "forgetting": "Olvidando...", + "hidden-network": "Red oculta", + "interface": "Interfaz de red", + "internet-connected": "Conectado a internet", + "internet-limited": "Sin internet", + "internet-status": "Estado de internet", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "Redes conocidas", + "link-speed": "Velocidad de enlace", + "network-name-ssid": "Nombre de red (SSID)", + "no-ethernet-devices": "No se detectaron dispositivos Ethernet", + "no-networks": "No se encontraron redes Wi-Fi", + "saved": "Guardado", + "scan-again": "Escanear de nuevo", + "searching": "Buscando redes cercanas...", + "security-open": "Abrir", + "security-wep": "WEP", + "security-wpa": "WPA/WPA2/WPA3 Personal", + "security-wpa-ent": "WPA Enterprise", + "security-wpa2-ent": "WPA2 Enterprise", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 Enterprise", + "show-password": "Mostrar contraseรฑa", + "title": "Wi-Fi" + }, + "signal": { + "excellent": "Excelente", + "fair": "Razonable", + "good": "Bueno", + "poor": "Malo", + "weak": "Dรฉbil" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/fr.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/fr.json new file mode 100644 index 0000000..8a03b23 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/fr.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "Effacer l'historique", + "close-app": "Fermer {app}", + "connect-vpn": "Se connecter ร  {name}", + "cycle-visualizer": "Changer de visualization", + "disable-bluetooth": "Dรฉsactiver le Bluetooth", + "disable-dnd": "Dรฉsactiver le mode 'Ne pas dรฉranger'", + "disable-wifi": "Dรฉsactiver le Wi-Fi", + "disconnect-vpn": "Se dรฉconnecter de {name}", + "dock-settings": "Paramรจtres du dock", + "enable-bluetooth": "Activer le Bluetooth", + "enable-dnd": "Activer le mode 'Ne pas dรฉranger'", + "enable-wifi": "Activer le Wi-Fi", + "launcher-settings": "Paramรจtres du lanceur", + "lower-to-bottom": "Abaisser au plus bas / Descendre au plus bas", + "open-calendar": "Ouvrir le calendrier", + "open-display-settings": "Paramรจtres d'affichage", + "open-launcher": "Ouvrir le lanceur", + "open-settings": "Ouvrir les paramรจtres", + "raise-to-top": "Mettre en avant", + "random-wallpaper": "Fond d'รฉcran alรฉatoire", + "run-custom-command": "Exรฉcuter une commande personnalisรฉe", + "show-active-only": "Afficher uniquement les actifs", + "show-all": "Tout afficher", + "toggle-mute": "Activer/dรฉsactiver le mode muet", + "widget-settings": "Paramรจtres du widget" + }, + "authentication": { + "error": "Erreur d'authentification", + "failed": "ร‰chec de l'authentification" + }, + "bar": { + "active-window": { + "colorize-icons-description": "Appliquer les couleurs du thรจme ร  l'icรดne de la fenรชtre active.", + "hide-mode-description": "Contrรดle le comportement du widget lorsqu'aucune fenรชtre n'est active.", + "scrolling-mode-description": "Contrรดler quand le dรฉfilement de texte est activรฉ pour les titres de fenรชtre longs.", + "show-app-icon-description": "Afficher l'icรดne de l'application ร  cรดtรฉ du titre de la fenรชtre.", + "show-app-icon-label": "Afficher l'icรดne de l'application", + "show-app-text-description": "Afficher le titre de la fenรชtre active.", + "show-app-text-label": "Afficher le titre de la fenรชtre" + }, + "audio-visualizer": { + "color-name-description": "Sรฉlectionnez la couleur du visualiseur.", + "color-name-label": "Couleur de remplissage", + "height-description": "Largeur de composant personnalisรฉe.", + "hide-when-idle-description": "Si activรฉ, le visualiseur est masquรฉ sauf lorsqu'un lecteur est en lecture.", + "hide-when-idle-label": "Masquer lorsqu'aucun mรฉdia n'est en lecture", + "width-description": "Largeur personnalisรฉe du composant." + }, + "battery": { + "device-default": "Par dรฉfaut (pรฉriphรฉrique d'affichage)", + "device-description": "Sรฉlectionnez le pรฉriphรฉrique de batterie ร  afficher.", + "device-label": "Dispositif ร  pile", + "display-mode-description": "Choisissez comment la batterie est affichรฉe dans la barre.", + "display-mode-graphic": "Batterie graphique", + "display-mode-graphic-clean": "Batterie graphique (sans %)", + "display-mode-icon-always": "Icรดne - Toujours afficher %", + "display-mode-icon-hover": "Icรดne - Afficher au survol", + "display-mode-icon-only": "Icรดne seulement", + "hide-if-idle-description": "Masquer le widget lorsque la batterie n'est ni en charge ni en dรฉcharge.", + "hide-if-idle-label": "Masquer en cas d'inactivitรฉ", + "hide-if-not-detected-description": "Masquer le widget lorsqu'aucune batterie n'est dรฉtectรฉe sur le systรจme.", + "hide-if-not-detected-label": "Cachรฉ si non dรฉtectรฉ", + "low-battery-threshold-label": "Seuil d'avertissement de batterie faible", + "show-noctalia-performance-description": "Afficher le basculement du mode performance Noctalia dans le panneau batterie.
Dรฉsactive les ombres et les animations dans Noctalia pour rรฉduire l'utilisation des ressources.", + "show-noctalia-performance-label": "Afficher le basculement Noctalia Performance", + "show-power-profile-description": "Afficher la sรฉlection du profil d'alimentation dans le panneau batterie.", + "show-power-profile-label": "Afficher les contrรดles de profil d'alimentation" + }, + "brightness": { + "apply-all-description": "Changer la luminositรฉ de tous les moniteurs lors de l'utilisation de la molette de dรฉfilement.", + "apply-all-label": "Appliquer les modifications de dรฉfilement ร  tous les moniteurs" + }, + "clock": { + "clock-display-description": "Personnalisez l'affichage de votre horloge en ajoutant des jetons de la liste ci-dessous. Pour utiliser le format 12 heures, vous devez inclure le jeton 'AP'.", + "clock-display-label": "Affichage de l'horloge", + "custom-font-description": "Sรฉlectionner une police personnalisรฉe pour l'affichage de l'horloge.", + "custom-font-label": "Police personnalisรฉe", + "custom-font-placeholder": "Sรฉlectionner une police personnalisรฉe...", + "custom-font-search-placeholder": "Rechercher des polices...", + "horizontal-bar-description": "Astuce : Utilisez \\n pour crรฉer un saut de ligne.", + "horizontal-bar-label": "Barre horizontale", + "preview": "Aperรงu", + "tooltip-format-description": "Chaรฎne de format pour l'infobulle affichรฉe lors du survol de l'horloge. Laissez vide pour utiliser l'infobulle par dรฉfaut.", + "tooltip-format-label": "Format de l'infobulle", + "use-custom-font-description": "Remplacez la police par dรฉfaut par une police personnalisรฉe pour l'horloge.", + "use-custom-font-label": "Utiliser une police personnalisรฉe", + "use-monospaced-font-description": "Lorsque cette option est activรฉe, l'horloge utilisera la police monospace.", + "use-monospaced-font-label": "Utiliser une police monospace", + "vertical-bar-description": "Utilisez un espace pour sรฉparer chaque partie sur une nouvelle ligne.", + "vertical-bar-label": "Barre verticale" + }, + "control-center": { + "browse-file": "Parcourir les fichiers", + "browse-library": "Parcourir la bibliothรจque", + "color-selection-description": "Applique les couleurs du thรจme aux icรดnes.", + "enable-colorization-description": "Activer la colorisation pour l'icรดne, en appliquant les couleurs du thรจme.", + "icon-description": "Sรฉlectionnez une icรดne de la bibliothรจque ou un fichier personnalisรฉ.", + "select-custom-icon": "Sรฉlectionner une icรดne personnalisรฉe", + "use-distro-logo-description": "Utiliser le logo de votre distribution au lieu d'une icรดne personnalisรฉe.", + "use-distro-logo-label": "Utiliser le logo de la distribution au lieu de l'icรดne" + }, + "custom-button": { + "collapse-condition-description": "Si le texte de sortie correspond ร  cette valeur, le bouton se repliera.", + "collapse-condition-label": "Condition dโ€™effondrement", + "color-selection-description": "Appliquer les couleurs du thรจme ร  l'icรดne et au texte.", + "default-tooltip": "Bouton personnalisรฉ, configurer dans les paramรจtres", + "display-command-output-description": "Entrez une commande ร  exรฉcuter ร  intervalle rรฉgulier. La premiรจre ligne de sa sortie sera affichรฉe sous forme de texte.", + "display-command-output-label": "Afficher la sortie de la commande", + "display-command-output-stream-description": "Entrez une commande ร  exรฉcuter en continu.", + "dynamic-text": "Texte dynamique", + "enable-colorization-description": "Activer la colorisation pour l'icรดne et le texte du bouton personnalisรฉ, en appliquant les couleurs du thรจme.", + "enable-colorization-label": "Activer la colorisation", + "general-tooltip-text-description": "Texte personnalisรฉ ร  afficher dans l'infobulle du bouton.", + "general-tooltip-text-label": "Texte d'infobulle personnalisรฉ", + "hide-mode-always-expanded": "Toujours รฉtendu", + "hide-mode-description": "Contrรดle la visibilitรฉ du widget quand la commande n'a pas de sortie.", + "hide-mode-expand-with-output": "ร‰tendre quand il y a une sortie", + "hide-mode-label": "Mode masquรฉ", + "hide-mode-max-transparent": "ร‰tendu au maximum mais transparent", + "icon-description": "Sรฉlectionnez une icรดne dans la bibliothรจque.", + "ipc-identifier-description": "Identifiant unique pour les commandes IPC. Utilisez cet identifiant avec 'qs -c noctalia-shell ipc call cb [action] [identifiant]' pour contrรดler ce bouton via IPC.", + "ipc-identifier-label": "Identifiant IPC", + "left-click-description": "Commande ร  exรฉcuter quand le bouton est cliquรฉ ร  gauche.", + "left-click-label": "Clic gauche", + "left-click-update-text": "Mettre ร  jour le texte affichรฉ au clic gauche", + "max-text-length-horizontal-description": "Nombre maximal de caractรจres ร  afficher dans la barre horizontale (0 pour masquer le texte).", + "max-text-length-horizontal-label": "Longueur max du texte (horizontal)", + "max-text-length-vertical-description": "Nombre maximal de caractรจres ร  afficher dans la barre verticale (0 pour masquer le texte).", + "max-text-length-vertical-label": "Longueur max du texte (vertical)", + "middle-click-description": "Commande ร  exรฉcuter quand le bouton est cliquรฉ au milieu.", + "middle-click-label": "Clic du milieu", + "middle-click-update-text": "Mettre ร  jour le texte affichรฉ lors d'un clic molette", + "parse-json-description": "Analyser la sortie de la commande en tant qu'objet JSON pour dรฉfinir dynamiquement le texte et l'icรดne.", + "parse-json-label": "Analyser la sortie en JSON", + "refresh-interval-description": "Intervalle en millisecondes.", + "refresh-interval-label": "Intervalle d'actualisation", + "right-click-description": "Commande ร  exรฉcuter quand le bouton est cliquรฉ ร  droite.", + "right-click-label": "Clic droit", + "right-click-update-text": "Mettre ร  jour le texte affichรฉ lors d'un clic droit", + "show-exec-tooltip-description": "Afficher les infobulles avec les dรฉtails de la commande (clic gauche/droit/milieu, molette).", + "show-exec-tooltip-label": "Afficher les infobulles de commande", + "show-icon-description": "Bascule la visibilitรฉ de l'icรดne du widget.", + "show-icon-label": "Afficher l'icรดne", + "show-text-tooltip-description": "Afficher les infobulles avec la sortie de la commande texte.", + "show-text-tooltip-label": "Afficher les infobulles de texte dynamiques", + "text-stream-description": "Les lignes diffusรฉes depuis la commande seront affichรฉes sous forme de texte sur le bouton.", + "text-stream-label": "Flux", + "wheel-description": "Commande ร  exรฉcuter lorsque la molette est utilisรฉe.
Utilisez $delta pour le delta de la molette dans la commande.", + "wheel-down": "Molette bas", + "wheel-down-description": "Commande ร  exรฉcuter lorsque la molette est dรฉfilรฉe vers le bas.", + "wheel-down-label": "Commande molette bas", + "wheel-label": "Molette", + "wheel-mode-separate-description": "Activer des commandes sรฉparรฉes pour la molette haut et bas.", + "wheel-mode-separate-label": "Commandes de molette sรฉparรฉes", + "wheel-up": "Molette haut", + "wheel-up-description": "Commande ร  exรฉcuter lorsque la molette est dรฉfilรฉe vers le haut.", + "wheel-up-label": "Commande molette haut", + "wheel-update-text": "Mettre ร  jour le texte affichรฉ au dรฉfilement" + }, + "keyboard-layout": { + "show-icon-description": "Afficher l'icรดne de disposition du clavier." + }, + "lock-keys": { + "hide-when-off-description": "Masquer l'indicateur lorsque la touche n'est pas active.", + "hide-when-off-label": "Masquer lorsque dรฉsactivรฉ", + "show-caps-lock-description": "Afficher l'รฉtat du verrouillage majuscule.", + "show-caps-lock-label": "Verr Maj", + "show-num-lock-description": "Afficher l'รฉtat du verrouillage numรฉrique.", + "show-num-lock-label": "Verr Num", + "show-scroll-lock-description": "Afficher l'รฉtat du verrouillage du dรฉfilement.", + "show-scroll-lock-label": "Verr Maj" + }, + "media-mini": { + "compact-mode-description": "Activer une disposition compacte pour le panneau du lecteur multimรฉdia.", + "compact-mode-label": "Mode compact", + "hide-mode-description": "Contrรดle le comportement du widget lorsqu'aucun mรฉdia n'est en cours de lecture.", + "max-width-description": "Dรฉfinit la taille horizontale maximale du widget. Le widget se rรฉtrรฉcira pour s'adapter ร  un contenu plus court.", + "no-active-player": "Aucun lecteur actif", + "panel-section-description": "Configurer l'apparence et le comportement du panneau du lecteur multimรฉdia.", + "panel-section-label": "Panneau du lecteur multimรฉdia", + "scrolling-mode-description": "Contrรดler quand le dรฉfilement de texte est activรฉ pour les titres de piste longs.", + "scrolling-mode-label": "Mode de dรฉfilement", + "show-album-art-description": "Afficher la pochette d'album de la piste en cours de lecture.", + "show-album-art-label": "Afficher la pochette d'album", + "show-artist-first-description": "Afficher artiste - titre au lieu de titre - artiste.", + "show-artist-first-label": "Afficher l'artiste en premier", + "show-progress-ring-description": "Afficher un indicateur de progression circulaire montrant la progression de la piste.", + "show-progress-ring-label": "Afficher l'anneau de progression", + "show-visualizer-description": "Afficher un visualiseur audio quand la musique est en cours de lecture.", + "show-visualizer-label": "Afficher le visualiseur", + "use-fixed-width-description": "Lorsque activรฉ, le widget utilisera toujours la largeur maximale au lieu de s'ajuster dynamiquement au contenu.", + "use-fixed-width-label": "Utiliser une largeur fixe", + "visualizer-type-description": "Choisir le style de visualiseur audio ร  afficher.", + "visualizer-type-label": "Type de visualiseur" + }, + "notification-history": { + "hide-widget-when-zero-description": "Masquer lโ€™icรดne de notification lorsquโ€™il nโ€™y a aucune notification.", + "hide-widget-when-zero-label": "Masquer lโ€™icรดne sans notifications", + "hide-widget-when-zero-unread-description": "Masquer lโ€™icรดne de notification lorsquโ€™il nโ€™y a aucune notification non lue.", + "hide-widget-when-zero-unread-label": "Masquer lโ€™icรดne sans notifications non lues", + "show-unread-badge-description": "Afficher un badge montrant le nombre de notifications non lues.", + "show-unread-badge-label": "Afficher le badge non lu", + "unread-badge-color-description": "Sรฉlectionnez la couleur pour le badge de notification non lue.", + "unread-badge-color-label": "Couleur du badge non lu" + }, + "section-editor": { + "placeholder": "Sรฉlectionnez un widget ร  ajouter...", + "search-placeholder": "Rechercher des widgets..." + }, + "spacer": { + "width-description": "Largeur de l'espacement en pixels." + }, + "system-monitor": { + "compact-mode-description": "Afficher les statistiques sous forme de mini-graphiques ร  barres au lieu de valeurs textuelles. Empรชche les dรฉcalages de mise en page.", + "compact-mode-label": "Mode compact", + "cpu-cores-description": "Afficher l'utilisation des cล“urs du CPU individuellement.", + "cpu-cores-label": "Cล“urs du CPU", + "cpu-frequency-description": "Afficher la vitesse d'horloge actuelle du CPU en GHz.", + "cpu-frequency-label": "Afficher la frรฉquence du CPU", + "cpu-temperature-description": "Afficher les lectures de tempรฉrature du CPU si disponibles.", + "cpu-temperature-label": "Tempรฉrature du CPU", + "cpu-usage-description": "Afficher le pourcentage d'utilisation actuel du CPU.", + "cpu-usage-label": "Utilisation du CPU", + "disk-path-description": "Sรฉlectionnez le point de montage du disque ร  surveiller.", + "disk-path-label": "Chemin du disque", + "gpu-temperature-description": "Afficher les relevรฉs de tempรฉrature du GPU, si disponibles.", + "load-average-description": "Afficher la charge moyenne du systรจme.", + "load-average-label": "Charge moyenne", + "memory-percentage-description": "Afficher l'utilisation de la mรฉmoire en pourcentage au lieu de valeurs absolues.", + "memory-percentage-label": "Mรฉmoire en pourcentage", + "memory-usage-description": "Afficher les informations actuelles d'utilisation de la RAM.", + "memory-usage-label": "Utilisation de la mรฉmoire", + "network-traffic-description": "Afficher les vitesses de tรฉlรฉchargement et de tรฉlรฉversement du rรฉseau.", + "network-traffic-label": "Trafic rรฉseau", + "storage-as-percentage-description": "Afficher l'espace disque en pourcentage au lieu des valeurs absolues.", + "storage-as-percentage-label": "Disque en pourcentage", + "storage-available-description": "Affiche l'espace disque disponible au lieu de l'espace utilisรฉ.", + "storage-available-label": "Espace disque disponible", + "storage-usage-description": "Afficher les informations d'utilisation de l'espace disque.", + "storage-usage-label": "Utilisation du stockage", + "swap-usage-description": "Afficher l'utilisation de la mรฉmoire swap.", + "swap-usage-label": "Utilisation du swap", + "use-monospace-font-description": "Utilisez une police de caractรจres monospace pour une largeur de caractรจre uniforme.", + "use-monospace-font-label": "Police monospace", + "use-padding-description": "Remplit les valeurs de texte avec des espaces en dรฉbut de ligne pour รฉviter le dรฉcalage de la mise en page.", + "use-padding-description-disabled-monospace-font": "Une police monospace est nรฉcessaire pour cette fonctionnalitรฉ.", + "use-padding-description-disabled-vertical": "La barre des tรขches verticale ne prend pas en charge le remplissage de texte.", + "use-padding-label": "Rembourrage du Texte" + }, + "taskbar": { + "colorize-icons-description": "Appliquer les couleurs du thรจme aux icรดnes de la barre des tรขches.", + "hide-mode-description": "Contrรดle le comportement du widget lorsqu'il n'y a pas de fenรชtres correspondantes.", + "hide-mode-label": "Mode de masquage", + "icon-scale-description": "Dรฉfinit le facteur d'รฉchelle pour les icรดnes de la barre des tรขches.", + "icon-scale-label": "Mise ร  l'รฉchelle des icรดnes", + "max-width-description": "Largeur maximale de la barre des tรขches en pourcentage de la largeur de l'รฉcran.", + "max-width-label": "Largeur maximale", + "only-active-workspaces-description": "Afficher uniquement les applications des espaces de travail actifs.", + "only-active-workspaces-label": "Seulement depuis les espaces de travail actifs", + "only-same-monitor-description": "Afficher uniquement les applications de l'รฉcran oรน se trouve la barre.", + "only-same-monitor-label": "Seulement ร  partir du mรชme รฉcran", + "show-pinned-apps-description": "Afficher les applications รฉpinglรฉes du dock dans la barre des tรขches.", + "show-pinned-apps-label": "Afficher les applications รฉpinglรฉes", + "show-title-description": "Afficher les titres des fenรชtres dans la barre des tรขches.", + "show-title-description-disabled": "La barre des tรขches verticale ne prend pas en charge l'affichage des titres.", + "show-title-label": "Afficher le titre", + "smart-width-description": "Ajuster automatiquement la largeur des entrรฉes en fonction du nombre d'entrรฉes.", + "smart-width-label": "Largeur intelligente", + "title-width-description": "Dรฉfinir la largeur des titres de fenรชtres dans la barre des tรขches (en pixels).", + "title-width-label": "Largeur du titre", + "title-width-reset-tooltip": "Rรฉinitialiser la largeur du titre" + }, + "tray": { + "chevron-color-description": "Appliquer les couleurs du thรจme ร  l'icรดne chevron du tiroir.", + "chevron-color-label": "Couleur du chevron", + "colorize-icons-description": "Appliquer les couleurs du thรจme aux icรดnes de la barre systรจme.", + "colorize-icons-label": "Coloriser les icรดnes", + "drawer-enabled-description": "Lorsqu'elle est activรฉe, les รฉlรฉments de la barre systรจme non รฉpinglรฉs sont affichรฉs dans un panneau tiroir.
Lorsqu'elle est dรฉsactivรฉe, tous les รฉlรฉments de la barre systรจme sont affichรฉs en ligne.", + "drawer-enabled-label": "Activer le tiroir", + "hide-passive-description": "Lorsque cette option est activรฉe, les รฉlรฉments de la barre d'รฉtat systรจme ayant le statut 'Passif' seront masquรฉs.", + "hide-passive-label": "Masquer les objets passifs" + }, + "volume": { + "display-mode-description": "Choisissez comment vous souhaitez que cette valeur apparaisse." + }, + "workspace": { + "character-count-description": "Nombre de caractรจres ร  afficher des noms d'espaces de travail (1-10).", + "character-count-label": "Nombre de caractรจres", + "empty-color-description": "Dรฉfinir la couleur d'arriรจre-plan pour les espaces de travail vides.", + "empty-color-label": "Couleur d'espace de travail vide", + "enable-scrollwheel-description": "Basculer entre les espaces de travail avec la molette de la souris.", + "enable-scrollwheel-label": "Faites dรฉfiler pour changer d'espace de travail", + "focused-color-description": "Dรฉfinir la couleur d'arriรจre-plan pour l'espace de travail sรฉlectionnรฉ.", + "focused-color-label": "Couleur de l'espace de travail sรฉlectionnรฉ", + "follow-focused-screen-description": "Afficher les espaces de travail de l'รฉcran actuellement ciblรฉ, plutรดt que de l'รฉcran oรน se trouve la barre.", + "follow-focused-screen-label": "Suivre l'รฉcran ciblรฉ", + "font-weight-description": "Dรฉfinir le poids visuel du texte dans l'espace de travail.", + "font-weight-label": "Graisse de la police", + "grouped-border-opacity-description": "Dรฉfinir le niveau d'opacitรฉ des bordures des conteneurs d'espace de travail.", + "grouped-border-opacity-label": "Opacitรฉ de la bordure", + "hide-unoccupied-description": "Ne pas afficher les espaces de travail sans fenรชtres.", + "hide-unoccupied-label": "Masquer les inoccupรฉs", + "label-mode-description": "Choisir comment les รฉtiquettes d'espace de travail sont affichรฉes.", + "label-mode-label": "Mode d'รฉtiquette", + "occupied-color-description": "Dรฉfinir la couleur d'arriรจre-plan pour les espaces de travail occupรฉs.", + "occupied-color-label": "Couleur de l'espace de travail occupรฉ", + "pill-size-description": "Ajustez la taille des pilules d'espace de travail (50%-100%).", + "pill-size-label": "Taille de la pilule", + "reverse-scrolling-description": "Inverser la direction du changement d'espace de travail lors du dรฉfilement.", + "reverse-scrolling-label": "Dรฉfilement inversรฉ", + "show-applications-description": "Afficher les icรดnes des applications dans chaque espace de travail.", + "show-applications-hover-description": "Afficher les icรดnes d'application dans chaque espace de travail lorsque celui-ci est survolรฉ.", + "show-applications-hover-label": "Afficher les applications au survol", + "show-applications-label": "Afficher les applications", + "show-badge-description": "Afficher le badge du numรฉro d'espace de travail en mode groupรฉ.", + "show-badge-label": "Afficher le badge de l'espace de travail", + "show-labels-only-when-occupied-description": "Afficher uniquement les รฉtiquettes d'espace de travail lorsqu'elles contiennent des fenรชtres.", + "show-labels-only-when-occupied-label": "Afficher les รฉtiquettes uniquement lorsque le champ est occupรฉ", + "unfocused-icons-opacity-description": "Dรฉfinir le niveau d'opacitรฉ des icรดnes d'applications non focalisรฉes.", + "unfocused-icons-opacity-label": "Opacitรฉ des icรดnes non ciblรฉes" + } + }, + "battery": { + "all-batteries": "Toutes les batteries (combinรฉes)", + "battery-health": "ร‰tat de la batterie", + "battery-level": "Niveau de batterie", + "capacity-level": "Capacitรฉย : {level}", + "charging-rate": "Taux de charge : {rate} W", + "discharging-rate": "Taux de dรฉcharge : {rate} W", + "health": "ร‰tat : {percent}%", + "inhibit-idle-description": "Maintient le systรจme รฉveillรฉ.", + "no-battery-detected": "Aucune batterie dรฉtectรฉe", + "plugged-in": "Branchรฉ", + "power-profile": "Profil dโ€™alimentation", + "time-left": "Temps restant : {time}", + "time-until-full": "Temps jusqu'ร  charge complรจte : {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "Appareils disponibles", + "blocked": "Bloquรฉ", + "connected-devices": "Appareils connectรฉs", + "device-address": "Adresse de lโ€™appareil", + "disabled": "Le Bluetooth est dรฉsactivรฉ", + "discoverable": "Dรฉtectable", + "enable-message": "Activez le Bluetooth pour voir les appareils disponibles.", + "known-devices": "Appareils connus", + "no-devices": "Aucun appareil disponible", + "paired-devices": "Appareils appairรฉs", + "pairing-mode": "Assurez-vous que votre appareil est en mode d'appairage.", + "scanning": "Recherche d'appareils en cours...", + "signal-text-excellent": "Signal : Excellent", + "signal-text-fair": "Signal : Moyen", + "signal-text-good": "Signal : Bon", + "signal-text-poor": "Signal : Faible", + "signal-text-unknown": "Signal : Inconnu", + "signal-text-very-poor": "Signal : Trรจs faible" + } + }, + "changelog": { + "error": { + "fetch-failed": "Impossible de charger les donnรฉes du journal des modifications. Veuillez rรฉessayer plus tard.", + "rate-limit": "Limite de GitHub atteinte. Rรฉessayez dans quelques minutes." + }, + "panel": { + "buttons-discord": "Rejoindre notre Discord", + "buttons-dismiss": "OK", + "buttons-feedback": "Donner un avis", + "empty": "Les notes de version ne sont pas encore disponibles.", + "highlight-title": "Points importants", + "released": "Publiรฉ le {date}", + "subtitle-fresh": "Merci dโ€™avoir installรฉ Noctalia ! Voici ce que contient cette version.", + "subtitle-updated": "Mise ร  jour depuis {previousVersion}", + "title": "Quoi de neuf dans {version}", + "version": "Version {version}", + "version-new-user": "Nouvelle installation" + } + }, + "common": { + "actions": "Actions", + "active": "Actif", + "add": "Ajouter", + "appearance": "Apparence", + "apply": "Appliquer", + "auto-connect": "Connexion automatique", + "automation": "Automatisation", + "available": "Disponible", + "back": "Retour", + "battery": "Batterie", + "behavior": "Comportement", + "bluetooth": "Bluetooth", + "brightness": "Luminositรฉ", + "browse": "Parcourir", + "calendar": "Calendrier", + "calendar-panel": "Panneau du calendrier", + "cancel": "Annuler", + "cards": "Cartes", + "charging": "Chargement", + "clear": "Effacer", + "clipboard": "Presse-papiers", + "close": "Fermer", + "color-muted": "Muet", + "colors": "Couleurs", + "command": "Commande", + "confirm": "Confirmer", + "connect": "Connecter", + "connected": "Connectรฉ", + "connecting": "Connexion en cours...", + "continue": "Continuer", + "contributors": "Contributeurs", + "copied-to-clipboard": "Copiรฉ dans le presse-papier", + "countdown": "Compte ร  rebours", + "customize": "Personnaliser", + "date": "Date", + "default": "Par dรฉfaut", + "delete": "Supprimer", + "devices": "Appareils", + "disabled": "Dรฉsactivรฉ", + "discharging": "Dรฉcharge", + "disconnect": "Dรฉconnecter", + "disconnected": "Dรฉconnectรฉ", + "disconnecting": "Dรฉconnexion...", + "display-mode": "Mode d'affichage", + "documentation": "Documentation", + "download": "Tรฉlรฉcharger", + "duration": "Durรฉe", + "dysfunctional": "Dysfonctionnel", + "edit": "Modifier", + "enabled": "Activรฉ", + "error": "Erreur", + "ethernet": "Ethernet", + "events": "ร‰vรฉnements", + "execute": "Exรฉcuter", + "faithful": "Fidรจle", + "focus": "Concentration", + "font-weight-bold": "Gras", + "font-weight-light": "Lรฉgรจre", + "font-weight-medium": "Moyen", + "font-weight-regular": "Rรฉgulier", + "font-weight-semibold": "Semi-gras", + "frequency": "Frรฉquence", + "gateway": "Passerelle", + "general": "Gรฉnรฉral", + "height": "Hauteur", + "hibernate": "Hiberner", + "history": "Historique", + "icon": "Icรดne", + "idle": "Inactif", + "info": "Info", + "input": "Entrรฉe", + "install": "Installer", + "installed": "Installรฉ", + "interface": "Interface", + "internet": "Internet", + "keybind": "Raccourci clavier", + "language": "Langue", + "loading": "Chargement...", + "local": "Local", + "location": "Emplacement", + "lock": "Verrouiller", + "logout": "Dรฉconnexion", + "look": "Apparence", + "margins": "Marges", + "media": "Mรฉdias", + "media-player": "Lecteur multimรฉdia", + "memory": "Mรฉmoire", + "monitors": "ร‰crans", + "move-down": "Dรฉplacer vers le bas", + "move-up": "Dรฉplacer vers le haut", + "network": "Rรฉseau", + "next": "Suivant", + "night-light": "ร‰clairage nocturne", + "no": "Non", + "no-results": "Aucun rรฉsultat", + "no-summary": "Aucun rรฉsumรฉ", + "none": "Aucun", + "not-found": "Non trouvรฉ", + "notifications": "Notifications", + "official": "Officiel", + "on-surface": "Sur la surface", + "output": "Sortie", + "pair": "Appairer", + "paired": "Appariรฉ", + "pairing": "Appairage...", + "panels": "Panneaux", + "password": "Mot de passe", + "pause": "Pause", + "performance": "Performances", + "pin": "ร‰pingle", + "play": "Lire", + "polling": "Interrogation", + "position": "Position", + "previous": "Prรฉcรฉdent", + "primary": "Primaire", + "random": "Alรฉatoire", + "reboot": "Redรฉmarrer", + "reboot-to-uefi": "Redรฉmarrer en UEFI", + "record": "Dรฉmarrer l'enregistrement", + "refresh": "Actualiser", + "required": "(requis)", + "reset": "Rรฉinitialiser", + "reset-to-default": "Rรฉinitialiser par Dรฉfaut", + "result-count": "{count} rรฉsultat", + "result-count-plural": "{count} rรฉsultats", + "resume": "Reprendrer", + "save": "Enregistrer", + "scanning": "Analyse en cours...", + "screen-corners": "Coins d'รฉcran", + "search": "Rechercher", + "second": "{count} seconde", + "second-plural": "{count} secondes", + "secondary": "Secondaire", + "security": "Sรฉcuritรฉ", + "select": "Sรฉlectionner", + "select-color": "Sรฉlectionner une couleur", + "select-color-description": "Appliquer les couleurs du thรจme pour mettre en valeur.", + "select-icon-color": "Sรฉlectionner la couleur de l'icรดne", + "settings": "Paramรจtres", + "shortcuts": "Raccourcis", + "shutdown": "ร‰teindre", + "signal": "Signal", + "sound": "Son", + "sources": "Sources", + "start": "Commencer", + "stop": "Arrรชtez", + "supporters": "Soutiens", + "suspend": "Mettre en veille", + "templates": "Modรจles", + "tertiary": "Tertiaire", + "test": "Test", + "thresholds": "Seuils", + "title": "Titre", + "toast": "Toast", + "trusted": "De confiance", + "uninstall": "Dรฉsinstaller", + "unknown": "Inconnu", + "unpair": "Dรฉjumeler", + "unpin": "Dรฉtacher", + "update": "Mise ร  jour", + "upload": "Tรฉlรฉverser", + "username": "Nom d'utilisateur", + "userspace-reboot": "Redรฉmarrer l'espace utilisateur", + "version": "Version", + "vibrant": "Vibrant", + "visualizer": "Visualiseur", + "volume": "Volume", + "volumes": "Volumes", + "wallpaper": "Fond dโ€™รฉcran", + "weather": "Mรฉtรฉo", + "weather-loading": "Chargement de la mรฉtรฉo...", + "weather-no-location": "Dรฉfinissez un emplacement dans les Paramรจtres pour voir la mรฉtรฉo", + "week": "Semaine", + "widgets": "Widgets", + "width": "Largeur", + "wifi": "Wi-Fi", + "windows": "Fenรชtres", + "yes": "Oui" + }, + "control-center": { + "power-profile": { + "tooltip-action": "Profil d'alimentation", + "tooltip-disabled": "Installez power-profiles-daemon pour utiliser les profils d'alimentation" + }, + "wifi": { + "label-disconnected": "Wi-Fi dรฉconnectรฉ", + "label-ethernet": "Ethernet" + } + }, + "display-modes": { + "always-hide": "Toujours cachรฉ", + "always-show": "Toujours afficher", + "force-open": "Forcer l'ouverture", + "on-hover": "Au survol" + }, + "hide-modes": { + "auto-hide": "Masquer automatiquement ", + "hidden": "Masquer quand vide", + "idle": "Masquer quand inactif", + "non-exclusive": "Non exclusif", + "transparent": "Transparent quand vide", + "visible": "Toujours visible" + }, + "launcher": { + "categories": { + "all": "Tout", + "audiovideo": "Audio et vidรฉo", + "chat": "Conversation", + "development": "Dรฉveloppement", + "education": "ร‰ducation", + "emoji-activity": "Activitรฉ", + "emoji-animals": "Animaux", + "emoji-flags": "Drapeaux", + "emoji-food": "Nourriture et boissons", + "emoji-nature": "Nature", + "emoji-objects": "Objets", + "emoji-people": "Personnes et corps", + "emoji-recent": "Rรฉcent", + "emoji-symbols": "Symboles", + "emoji-travel": "Voyages et lieux", + "game": "Jeux", + "graphics": "Graphiques", + "misc": "Divers", + "office": "Bureau", + "pinned": "ร‰pinglรฉ", + "system": "Systรจme", + "webbrowser": "Navigateur web" + }, + "providers": { + "applications": "Applications", + "calculator": "Calculatrice", + "calculator-deprecated": "La commande >calc est obsolรจte et sera bientรดt supprimรฉe. Veuillez saisir les expressions mathรฉmatiques directement dans la recherche ร  la place.", + "calculator-description": "Calculatrice โ€” รฉvaluez des expressions mathรฉmatiques", + "calculator-enter-expression": "Entrez une expression mathรฉmatique", + "calculator-press-enter-to-copy": "Appuyez sur Entrรฉe pour copier le rรฉsultat", + "clipboard": "Historique du presse-papiers", + "clipboard-clear-description": "Effacer tout l'historique du presse-papiers", + "clipboard-clear-description-full": "Supprimer tous les รฉlรฉments de l'historique du presse-papiers", + "clipboard-clear-history": "Effacer l'historique du presse-papiers", + "clipboard-delete": "Supprimer l'entrรฉe du presse-papiers", + "clipboard-history-disabled": "Historique du presse-papiers dรฉsactivรฉ", + "clipboard-history-disabled-description": "Activez l'historique du presse-papiers dans les paramรจtres ou installez cliphist", + "clipboard-loading": "Chargement de l'historique du presse-papiers...", + "clipboard-search-description": "Rechercher dans l'historique du presse-papiers", + "command-description": "Exรฉcuter des commandes shell", + "emoji": "Sรฉlecteur d'รฉmojis", + "emoji-loading": "Chargement des รฉmojis...", + "emoji-loading-description": "Veuillez patienter", + "emoji-no-recent": "Aucun emoji rรฉcent pour le moment", + "emoji-search-description": "Rechercher et copier des รฉmojis", + "settings-search-description": "Trouvez et explorez les paramรจtres", + "windows-search-description": "Rechercher et focaliser les fenรชtres ouvertes" + } + }, + "lock-screen": { + "authenticating": "Authentification...", + "password": "Entrez votre mot de passe...", + "restart": "Redรฉmarrer", + "shut-down": "ร‰teindre" + }, + "notifications": { + "panel": { + "click-to-collapse": "Cliquer pour rรฉduire", + "click-to-expand": "Cliquez pour dรฉvelopper", + "description": "Vos notifications apparaรฎtront ici ร  mesure qu'elles arriveront.", + "no-notifications": "Aucune notification" + }, + "range": { + "earlier": "Plus anciennes", + "today": "Aujourd'hui", + "yesterday": "Hier" + }, + "time": { + "diff-d": "il y a 1 jour", + "diff-dd": "il y a {diff} jours", + "diff-h": "il y a 1 heure", + "diff-hh": "il y a {diff} heures", + "diff-m": "il y a 1 minute", + "diff-mm": "il y a {diff} minutes", + "now": "maintenant" + } + }, + "options": { + "bar": { + "density-comfortable": "Confortable", + "density-compact": "Compact", + "density-default": "Dรฉfaut", + "density-mini": "Mini", + "density-spacious": "Spacieux", + "display-mode-always-visible": "Toujours Visible", + "type-floating": "Flottante", + "type-framed": "Encadrรฉe", + "type-simple": "Simple" + }, + "control-center": { + "quick-settings-style-classic": "Classique", + "quick-settings-style-modern": "Moderne" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "Confortable", + "compact": "Compact", + "default": "Par dรฉfaut" + }, + "launcher-view-mode": { + "grid": "Grille", + "list": "Liste" + }, + "notification-density": { + "compact": "Compact", + "default": "Par dรฉfaut" + }, + "scrolling-modes": { + "always": "Dรฉfiler toujours", + "hover": "Dรฉfiler au survol", + "never": "Ne jamais dรฉfiler" + }, + "session-menu-grid-layout": { + "grid": "Grille", + "single-row": "Une seule rangรฉe" + }, + "settings-panel-mode": { + "attached": "Panneau attachรฉ ร  la barre", + "centered": "Panneau centrรฉ", + "window": "Fenรชtre sรฉparรฉe" + }, + "visualizer-types": { + "linear": "Linรฉaire", + "mirrored": "Miroir", + "wave": "Vague" + }, + "visualizer-visibility": { + "with-background": "Avec fond uniquement" + }, + "workspace-labels": { + "index": "Index", + "index-and-name": "Index et Nom", + "name": "Nom" + } + }, + "panels": { + "about": { + "become-supporter": "Devenir un soutien", + "changelog": "Afficher le journal des modifications", + "changelog-on-startup": "Afficher le journal des modifications lors de la mise ร  jour", + "changelog-on-startup-desc": "Afficher automatiquement le journal des modifications lors de la mise ร  jour de Noctalia.", + "contributors-description": "Un grand merci ร  notre {count} super contributeur !", + "contributors-description-plural": "Un grand merci ร  nos {count} super contributeurs !", + "copy-info": "Copier les informations", + "debug-disabled": "Mode dรฉbogage dรฉsactivรฉ", + "debug-enabled": "Mode dรฉbogage activรฉ", + "info-copied": "Info copiรฉe dans le presse-papiers", + "noctalia-available": "Disponible :", + "noctalia-desc": "Un shell de bureau รฉlรฉgant et minimaliste, conรงu avec soin pour Wayland et bรขti avec Quickshell.", + "noctalia-git-commit": "Commit Git :", + "noctalia-installed-version": "Version installรฉe :", + "noctalia-latest-version": "Derniรจre version :", + "noctalia-qs-version": "Noctalia QS version :", + "noctalia-title": "Shell Noctalia", + "privacy-policy": "Politique de confidentialitรฉ", + "support": "Soutenez-nous", + "supporter-badge": "Soutien", + "supporters-desc": "Un immense merci ร  notre incroyable soutien !", + "supporters-desc-plural": "Un grand merci ร  nos {count} incroyables supporters !", + "supporters-loading": "Chargement des contributeurs...", + "system-board": "Carte mรจre :", + "system-cpu": "Processeur :", + "system-disk": "Disque :", + "system-gpu": "GPU :", + "system-host": "Hรดte :", + "system-install-hint": "Installer fastfetch pour afficher les informations systรจme", + "system-kernel": "Noyau :", + "system-loading": "Chargement des informations systรจme...", + "system-memory": "Mรฉmoire :", + "system-monitor": "Moniteur :", + "system-not-installed": "fastfetch n'est pas installรฉ", + "system-os": "Systรจme d'exploitation :", + "system-packages": "Paquets :", + "system-product": "Produitย :", + "system-title": "Informations systรจme", + "system-uptime": "Temps de disponibilitรฉ :", + "system-wm": "WM :", + "telemetry-data-copied": "Donnรฉes de tรฉlรฉmรฉtrie copiรฉes dans le presse-papiers", + "telemetry-desc": "Aidez ร  amรฉliorer Noctalia en partageant des informations systรจme anonymes (rรฉsolution d'รฉcran, compositeur, distribution). Envoyรฉ une fois au dรฉmarrage, pas de suivi, les donnรฉes sont automatiquement supprimรฉes aprรจs 30 jours.", + "telemetry-enabled": "Envoyer des informations systรจme anonymes", + "telemetry-show-data": "Afficher les donnรฉes", + "telemetry-title": "Confidentialitรฉ", + "title": "ร€ propos", + "up-to-date": "Vous รชtes ร  jour !", + "update-available": "Mise ร  jour disponible", + "view-commit": "Voir le commit sur GitHub" + }, + "audio": { + "devices-desc": "Configurez les pรฉriphรฉriques d'entrรฉe et de sortie audio disponibles.", + "devices-input-device-description": "Sรฉlectionnez le pรฉriphรฉrique d'entrรฉe audio souhaitรฉ.", + "devices-input-device-label": "Pรฉriphรฉrique d'entrรฉe", + "devices-output-device-description": "Sรฉlectionnez le pรฉriphรฉrique de sortie audio souhaitรฉ.", + "devices-output-device-label": "Pรฉriphรฉrique de sortie", + "devices-title": "Pรฉriphรฉriques audio", + "external-mixer-description": "Entrez la commande ou le chemin d'accรจs de l'application ร  lancer lors de l'activation de la fonctionnalitรฉ de mixeur audio externe.", + "external-mixer-label": "Commande de mixeur audio externe", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "Dรฉfinissez vos applications multimรฉdias prรฉfรฉrรฉes et ignorรฉes.", + "media-excluded-player-description": "Ajoutez des mots-clรฉs pour les lecteurs que le systรจme doit ignorer. Chaque mot-clรฉ doit รชtre sur une nouvelle ligne.", + "media-excluded-player-label": "Lecteur exclu", + "media-excluded-player-placeholder": "tapez la sous-chaรฎne et appuyez sur +", + "media-frame-rate-description": "Des frรฉquences plus รฉlevรฉes sont plus fluides mais utilisent plus de ressources.", + "media-frame-rate-label": "Frรฉquence d'images", + "media-primary-player-description": "Entrez un mot-clรฉ pour identifier votre lecteur principal.", + "media-primary-player-label": "Lecteur principal", + "media-primary-player-placeholder": "ex : spotify, vlc, mpv", + "media-scrolling-speed-description": "Temps en secondes pour que le titre dรฉfile du dรฉbut ร  la fin.", + "media-scrolling-speed-label": "Vitesse de dรฉfilement", + "media-scrolling-title-description": "Activer le dรฉfilement continu pour les titres multimรฉdias longs.", + "media-scrolling-title-label": "Titre dรฉfilant", + "media-title": "Lecteurs multimรฉdias", + "on-middle-clicked-description": "Commande ร  exรฉcuter lorsque le bouton du milieu est cliquรฉ.", + "panel-applications-empty": "Aucune application ne lit actuellement de l'audio", + "spectrum-mirrored-description": "Inverse le spectre pour que les basses frรฉquences soient au centre. Quand dรฉsactivรฉ, les basses frรฉquences sont ร  gauche et les hautes ร  droite.", + "spectrum-mirrored-label": "Spectre miroir", + "title": "Audio", + "visualizer-type-description": "Choisissez un type de visualisation pour la lecture de mรฉdias.", + "visualizer-type-label": "Type de visualisation", + "volumes-desc": "Ajustez les contrรดles de volume et les niveaux audio.", + "volumes-feedback-sound-file-description": "Chemin du fichier son lu lors du changement de volume.", + "volumes-feedback-sound-file-label": "Fichier audio de retour du volume", + "volumes-feedback-sound-file-select-title": "Sรฉlectionner le fichier audio de retour de volume", + "volumes-input-volume-description": "Niveau du volume d'entrรฉe du microphone.", + "volumes-mute-input-description": "Couper l'entrรฉe audio par dรฉfaut (microphone).", + "volumes-mute-input-label": "Couper l'entrรฉe audio", + "volumes-mute-output-description": "Couper la sortie audio principale du systรจme.", + "volumes-mute-output-label": "Couper la sortie audio", + "volumes-output-volume-description": "Niveau de volume de l'ensemble du systรจme.", + "volumes-step-size-description": "Ajustez l'incrรฉment pour les changements de volume (molette de la souris, raccourcis clavier).", + "volumes-step-size-label": "Incrรฉment de volume", + "volumes-volume-feedback-description": "ร‰mettre un son de retour lors du rรฉglage du volume.", + "volumes-volume-feedback-label": "Jouer le son de retour de volume", + "volumes-volume-overdrive-description": "Permet d'augmenter le volume au-delร  de 100 %. Cette fonctionnalitรฉ peut ne pas รชtre prise en charge par tout le matรฉriel.", + "volumes-volume-overdrive-label": "Autoriser le dรฉpassement du volume" + }, + "bar": { + "appearance-auto-hide-delay-description": "Dรฉlai avant que la barre ne se masque aprรจs le dรฉpart de la souris", + "appearance-auto-hide-delay-label": "Dรฉlai de masquage ", + "appearance-auto-hide-exclusive-note": "Remarqueย : La zone exclusive est automatiquement dรฉsactivรฉe lorsque le masquage automatique est actif", + "appearance-auto-show-delay-description": "Dรฉlai avant que la barre ne sโ€™affiche lorsque la souris entre en bordure", + "appearance-auto-show-delay-label": "Dรฉlai dโ€™apparition ", + "appearance-background-opacity-description": "Dรฉfinir l'opacitรฉ de l'arriรจre-plan spรฉcifiquement pour la barre.", + "appearance-background-opacity-label": "Opacitรฉ de l'arriรจre-plan de la barre", + "appearance-capsule-color-description": "Choisissez une couleur pour les capsules de la barre, ou n'en utilisez aucune pour la couleur de surface par dรฉfaut.", + "appearance-capsule-color-label": "Couleur de la capsule", + "appearance-capsule-opacity-description": "Dรฉfinir le niveau d'opacitรฉ des arriรจre-plans des widgets quand la capsule est affichรฉe.", + "appearance-capsule-opacity-label": "Opacitรฉ de la capsule", + "appearance-content-padding-description": "Ajustez l'espacement entre les bords de la barre et les widgets.", + "appearance-content-padding-label": "Espacement du contenu", + "appearance-density-description": "Ajustez le remplissage de la barre pour un aspect compact ou spacieux.", + "appearance-density-label": "Densitรฉ de la barre", + "appearance-desc": "Personnalisez l'apparence et la position de la barre.", + "appearance-display-mode-description": "Choisir quand la barre est visible.", + "appearance-enable-exclusion-zone-inset-description": "Rรฉduisez la zone d'exclusion de 1 pixel physique afin que les fenรชtres affleurantes se fondent parfaitement sous le bord de la barre.", + "appearance-enable-exclusion-zone-inset-label": "Zone d'exclusion en retrait", + "appearance-floating-description": "Afficher la barre sous forme de 'pilule' flottante.", + "appearance-floating-label": "Barre flottante", + "appearance-font-scale-description": "Ajuster l'รฉchelle de la taille de police pour le texte affichรฉ dans la barre.", + "appearance-font-scale-label": "ร‰chelle de police", + "appearance-frame-radius": "Rayon interne", + "appearance-frame-settings-description": "Ajuster l'รฉpaisseur du cadre et le rayon interne", + "appearance-frame-settings-label": "Paramรจtres du cadre", + "appearance-frame-thickness": "ร‰paisseur", + "appearance-hide-on-overview-description": "Masquer la barre et fermer les panneaux lorsque l'aperรงu du compositeur est actif.", + "appearance-hide-on-overview-label": "Masquer la barre dans l'aperรงu", + "appearance-margins-description": "Ajustez les marges autour de la barre flottante.", + "appearance-margins-horizontal": "Marge horizontale", + "appearance-margins-vertical": "Marge verticale", + "appearance-outer-corners-description": "Afficher des coins incurvรฉs vers l'extรฉrieur sur la barre.", + "appearance-outer-corners-label": "Coins extรฉrieurs", + "appearance-position-description": "Choisissez oรน placer la barre sur l'รฉcran.", + "appearance-position-label": "Position de la barre", + "appearance-show-capsule-description": "Afficher les arriรจre-plans des widgets.", + "appearance-show-capsule-label": "Afficher la capsule", + "appearance-show-on-workspace-switch-description": "Afficher la Bar briรจvement et automatiquement lorsque l'espace de travail change.", + "appearance-show-on-workspace-switch-label": "Afficher la barre lors du changement d'espace de travail", + "appearance-show-outline-description": "Affiche une bordure visible autour de chaque widget.", + "appearance-show-outline-label": "Afficher les contours des widgets", + "appearance-type-description": "Choisissez le style de la barre : Simple, Flottante ou Encadrรฉe.", + "appearance-type-label": "Type de barre", + "appearance-use-separate-opacity-description": "Activer l'utilisation d'une valeur d'opacitรฉ distincte pour l'arriรจre-plan de la barre.", + "appearance-use-separate-opacity-label": "Utiliser l'opacitรฉ des barres sรฉparรฉment", + "appearance-widget-spacing-description": "Ajustez l'espacement entre chaque widget dans la barre.", + "appearance-widget-spacing-label": "Espacement des widgets", + "behavior-middle-click-command-description": "Commande ร  exรฉcuter au clic du milieu.", + "behavior-middle-click-command-label": "Commande du clic du Milieu", + "behavior-middle-click-command-placeholder": "niri msg action basculer l'aperรงu", + "behavior-middle-click-description": "Choisissez ce que fait le clic du milieu sur les zones vides de la Barre.", + "behavior-middle-click-follow-mouse-description": "Ouvrir le panneau de clic central sรฉlectionnรฉ ร  la position du curseur.", + "behavior-middle-click-follow-mouse-label": "Clic molette suit la souris", + "behavior-middle-click-label": "Action du clic du milieu sur la Barre", + "behavior-right-click-command-description": "Commande ร  exรฉcuter au clic droit.", + "behavior-right-click-command-label": "Commande du Clic Droit", + "behavior-right-click-command-placeholder": "notify-send \"Clic droit\"", + "behavior-right-click-description": "Choisissez ce que fait un clic droit sur les zones vides de la barre.", + "behavior-right-click-follow-mouse-description": "Ouvre le panneau de clic droit sรฉlectionnรฉ ร  la position du curseur.", + "behavior-right-click-follow-mouse-label": "Clic droit suit la souris", + "behavior-right-click-label": "Action du clic droit sur la Barre", + "behavior-wheel-wrap-description": "Lorsque activรฉ, le dรฉfilement continue du dernier รฉlรฉment au premier.", + "behavior-wheel-wrap-label": "Boucler", + "behavior-workspace-scroll-description": "Choisissez ce que fait la molette de la souris sur les zones vides de la barre.", + "behavior-workspace-scroll-label": "Action de la molette de la souris de la Barre", + "behavior-workspace-scroll-option-content": "Contenu", + "behavior-workspace-scroll-option-workspace": "Espace de travail", + "monitor-configure-widgets": "Configurer les widgets", + "monitor-override-settings": "Remplacer les paramรจtres globaux", + "monitor-override-settings-description": "Utiliser des paramรจtres personnalisรฉs pour ce moniteur.", + "monitor-reset-all": "Rรฉinitialiser tout", + "monitor-widgets-title": "Configuration du widget - {monitor}", + "monitors-desc": "Afficher la barre sur des moniteurs spรฉcifiques. Par dรฉfaut, sur tous si aucun n'est choisi.", + "monitors-desc-new": "Configurez les moniteurs qui affichent la barre et personnalisez les paramรจtres pour chaque moniteur.", + "monitors-title": "Affichage sur les moniteurs", + "title": "Barre", + "tray-blacklist-description": "Ajouter des rรจgles d'exclusion pour la boรฎte ร  miniatures, prend en charge les caractรจres gรฉnรฉriques (*).", + "tray-blacklist-label": "Liste noire", + "tray-blacklist-placeholder": "ex : nm-applet, Fcitx*", + "tray-pin-application": "ร‰pingler l'application", + "tray-unpin-application": "Dรฉsรฉpingler l'application", + "use-global-widgets": "Utiliser les widgets globaux", + "widgets-desc": "Faites glisser les widgets pour modifier leur ordre. Utilisez le menu contextuel (clic droit) pour transfรฉrer les widgets entre les sections ou les supprimer.", + "widgets-title": "Positionnement des widgets" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "Gรฉnรฉrez des schรฉmas de couleurs ร  partir de votre fond d'รฉcran. Extrait automatiquement les couleurs pour crรฉer un thรจme cohรฉrent.", + "color-source-use-wallpaper-colors-label": "Utiliser les couleurs du fond d'รฉcran", + "dark-mode-mode-description": "Active la commutation automatique entre le mode clair et le mode sombre.", + "dark-mode-mode-label": "Programmation du mode sombre", + "dark-mode-mode-manual": "Manuel", + "dark-mode-mode-off": "ร‰teint", + "dark-mode-switch-description": "Passe ร  un thรจme plus sombre pour une visualisation plus facile la nuit.", + "delete-error-description": "ร‰chec de la suppression de {scheme}", + "delete-error-title": "ร‰chec de la suppression", + "delete-success-description": "{scheme} supprimรฉ avec succรจs", + "delete-success-title": "Jeu de couleurs supprimรฉ", + "download-button": "Tรฉlรฉcharger plus", + "download-downloading": "Tรฉlรฉchargement...", + "download-empty": "Aucun jeu de couleurs disponible", + "download-error-api-error": "Erreur API: {status}", + "download-error-description": "ร‰chec du tรฉlรฉchargement de {scheme}", + "download-error-download-failed": "ร‰chec du tรฉlรฉchargement avec le code de sortie: {code}", + "download-error-invalid-response": "Format de rรฉponse API invalide", + "download-error-no-files": "Aucun fichier trouvรฉ pour le jeu de couleurs", + "download-error-parse-failed": "ร‰chec de l'analyse de la rรฉponse API: {error}", + "download-error-rate-limit": "Limite de dรฉbit de l'API GitHub dรฉpassรฉe", + "download-error-title": "ร‰chec du tรฉlรฉchargement", + "download-fetching": "Rรฉcupรฉration des jeux de couleurs disponibles...", + "download-success-description": "{scheme} tรฉlรฉchargรฉ avec succรจs", + "download-success-title": "Jeu de couleurs tรฉlรฉchargรฉ", + "download-title": "Tรฉlรฉcharger des jeux de couleurs", + "method-description": { + "content": "Schรฉma Material Design avec extraction de couleurs haute fidรฉlitรฉ qui correspond รฉtroitement aux couleurs rรฉelles du contenu source.", + "dysfunctional": "Comme Faithful, mais choisit la deuxiรจme famille de couleurs la plus dominante comme principale.", + "faithful": "Tente de rester proche de la couleur source tout en gรฉnรฉrant une palette harmonieuse.", + "fruit-salad": "Schรฉma Material Design qui produit une palette ludique et vibrante avec des teintes diverses et variรฉes.", + "monochrome": "Schรฉma Material Design utilisant une รฉchelle de gris ร  teinte unique avec un contenu chromatique minimal.", + "muted": "Gรฉnรจre des tons plus doux et dรฉsaturรฉs pour une apparence calme et discrรจte.", + "rainbow": "Schรฉma Material Design qui gรฉnรจre des couleurs sur tout le spectre pour une diversitรฉ de couleurs maximale.", + "tonal-spot": "Algorithme par dรฉfaut de Material Design. Crรฉe des palettes รฉquilibrรฉes en utilisant les variations tonales de la couleur source.", + "vibrant": "Maximise la saturation des couleurs pour des palettes audacieuses et accrocheuses aux tons vifs." + }, + "predefined-desc": "Choisissez parmi une collection de jeux de couleurs prรฉdรฉfinis.", + "predefined-generate-templates-label": "Gรฉnรฉrer des modรจles pour les schรฉmas prรฉdรฉfinis", + "predefined-title": "Jeux de couleurs prรฉdรฉfinis", + "sync-gsettings-description": "Adapter le thรจme du systรจme ร  la variante claire ou sombre active.", + "sync-gsettings-label": "Synchroniser le thรจme systรจme", + "templates-desc": "Appliquer des couleurs aux applications externes.", + "templates-filter-description": "Afficher les modรจles d'une catรฉgorie spรฉcifique.", + "templates-filter-label": "Filtrer par catรฉgorie", + "templates-misc-description": "Crรฉez vos propres modรจles.", + "templates-misc-label": "Avancรฉ", + "templates-misc-user-templates-description": "N'activez que si vous savez ce que vous faites โ€” consultez notre documentation en ligne.", + "templates-misc-user-templates-label": "Activer les modรจles utilisateur", + "templates-none-detected": "Aucun dรฉtectรฉ", + "templates-write-path": "ร‰critย : {filepath}", + "title": "Jeu de couleurs", + "wallpaper-method-description": "Choisissez votre mรฉthode de gรฉnรฉration de palette prรฉfรฉrรฉe.", + "wallpaper-method-label": "Mรฉthode de gรฉnรฉration de palette", + "wallpaper-monitor-source-description": "Sรฉlectionnez le moniteur ร  utiliser pour extraire les couleurs du fond d'รฉcran.", + "wallpaper-monitor-source-label": "Source de gรฉnรฉration des couleurs" + }, + "connections": { + "authentication-required": "Authentification requise", + "bluetooth-auto-connect-description": "Se connecter automatiquement aux appareils appairรฉs de confiance lorsque le Bluetooth est activรฉ.", + "bluetooth-auto-connect-label": "Connecter automatiquement les appareils appairรฉs", + "bluetooth-devices-unnamed": "Les appareils sans nom ne sont pas affichรฉs.", + "bluetooth-discoverable": "Cet appareil est dรฉtectable en tant que {hostName} tant que cet onglet de paramรจtres est ouvert.", + "bluetooth-rssi-polling-description": "ร‰chantillonne pรฉriodiquement le RSSI des appareils connectรฉs via bluetoothctl. Peut ne pas รชtre disponible pour tous les appareils ; utilise des ressources minimales lorsqu'il est activรฉ.", + "bluetooth-rssi-polling-interval-description": "Configurer la frรฉquence de mise ร  jour de la force du signal pour les appareils connectรฉs.", + "bluetooth-rssi-polling-interval-label": "Intervalle d'interrogation", + "bluetooth-rssi-polling-label": "Interrogation du signal Bluetooth", + "disable-discoverability-description": "Masquer votre appareil des appareils Bluetooth ร  proximitรฉ.", + "disable-discoverability-label": "Dรฉsactiver la visibilitรฉ de l'appareil", + "hide-unnamed-devices-description": "Masquer les appareils qui n'apparaissent que sous forme d'adresses Bluetooth.", + "hide-unnamed-devices-label": "Masquer les appareils sans nom", + "pin-instructions": "Veuillez saisir le code PIN affichรฉ sur votre appareil.", + "title": "Connexions", + "wifi-header-text": "Utilisez le Wi-Fi pour voir les rรฉseaux disponibles et vous connecter ร  internet." + }, + "control-center": { + "cards-desc": "Personnalisez les commandes qui apparaissent dans le centre de contrรดle et leur ordre d'affichage.", + "desc": "Configurer le positionnement et le comportement du panneau du centre de contrรดle.", + "open-at-mouse-description": "Lors d'un clic droit sur une zone vide de la Bar, ouvre le Centre de Contrรดle prรจs du curseur. Si dรฉsactivรฉ, le rรฉglage de position ci-dessus est utilisรฉ ร  la place.", + "open-at-mouse-label": "Ouvrir au niveau du curseur lors d'un clic droit", + "position-description": "Choisissez oรน le panneau du centre de contrรดle apparaรฎt lorsqu'il est ouvert.", + "shortcuts-custom-button-command-description": "La commande ร  exรฉcuter lorsque le bouton est cliquรฉ.", + "shortcuts-custom-button-enable-on-state-logic-description": "Active une deuxiรจme icรดne et un รฉtat 'chaud' basรฉs sur une commande de vรฉrification.", + "shortcuts-custom-button-enable-on-state-logic-label": "Activer la logique d'รฉtat activรฉ", + "shortcuts-custom-button-icon-description": "Sรฉlectionnez une icรดne de la bibliothรจque.", + "shortcuts-custom-button-on-clicked-label": "Commande de clic gauche", + "shortcuts-custom-button-on-middle-clicked-description": "Commande ร  exรฉcuter lors d'un clic central sur le bouton.", + "shortcuts-custom-button-on-state-command-description": "Commande ร  exรฉcuter pour vรฉrifier si le bouton doit รชtre dans l'รฉtat 'activรฉ'. Retourne 0 pour activรฉ, non-zรฉro pour dรฉsactivรฉ.", + "shortcuts-custom-button-on-state-command-label": "Commande de vรฉrification de l'รฉtat activรฉ", + "shortcuts-custom-button-on-state-icon-description": "L'icรดne du bouton lorsqu'il est dans l'รฉtat 'activรฉ'.", + "shortcuts-custom-button-on-state-icon-label": "Icรดne d'รฉtat activรฉ", + "shortcuts-custom-button-state-checks-add": "Ajouter une vรฉrification d'รฉtat", + "shortcuts-custom-button-state-checks-command": "Commande ร  exรฉcuter pour cette vรฉrification d'รฉtat", + "shortcuts-custom-button-state-checks-label": "Vรฉrifications d'รฉtat", + "shortcuts-custom-button-state-checks-remove": "Supprimer", + "shortcuts-custom-button-tooltip-description": "L'infobulle ร  afficher lors du survol du bouton.", + "shortcuts-custom-button-tooltip-label": "Infobulle", + "shortcuts-title": "Widgets de raccourcis", + "system-monitor-disk-path-description": "Sรฉlectionnez le point de montage du disque que la carte du moniteur systรจme dans le centre de contrรดle doit surveiller.", + "system-monitor-disk-path-label": "Chemin du disque du moniteur systรจme", + "title": "Centre de contrรดle" + }, + "desktop-widgets": { + "clock-enabled-description": "Afficher un widget d'horloge sur le bureau.", + "clock-enabled-label": "Activer le widget horloge", + "clock-format-label": "Format", + "clock-height-description": "Hauteur du widget horloge en pixels.", + "clock-height-label": "Taille", + "clock-minimal-mode-description": "Utiliser un affichage d'horloge minimaliste avec l'heure et la date.", + "clock-minimal-mode-label": "Mode minimaliste", + "clock-rounded-corners-description": "Utiliser des coins arrondis pour l'arriรจre-plan du widget.", + "clock-rounded-corners-label": "Coins arrondis", + "clock-show-background-description": "Afficher le conteneur d'arriรจre-plan pour le widget d'horloge.", + "clock-show-background-label": "Afficher l'arriรจre-plan", + "clock-show-date-description": "Afficher la date actuelle sous l'heure.", + "clock-show-date-label": "Afficher la date", + "clock-show-seconds-description": "Afficher les secondes dans l'heure.", + "clock-show-seconds-label": "Afficher les secondes", + "clock-style-analog": "Analogique", + "clock-style-binary": "Binaire", + "clock-style-description": "Choisissez le style d'affichage de l'horloge.", + "clock-style-digital": "Numรฉrique", + "clock-style-label": "Style d'horloge", + "clock-style-minimal": "Minimal", + "clock-width-description": "Largeur du widget horloge en pixels.", + "cpu-intensive-note": "Les widgets marquรฉs d'un ! utilisent plus de CPU et devraient รชtre activรฉs avec prudence.", + "edit-mode-button-label": "Entrer en mode รฉdition", + "edit-mode-controls-explanation": "Clic gauche et glisser-dรฉposer : Dรฉplacer ou redimensionner le widget.\nClic droit : Ouvrir les options du menu contextuel.", + "edit-mode-description": "Activer le mode รฉdition pour dรฉplacer et repositionner les widgets du bureau. Une fois activรฉ, les widgets affichent un contour de glissement et peuvent รชtre repositionnรฉs.", + "edit-mode-exit-button": "Quitter le mode รฉdition", + "edit-mode-grid-snap-label": "Aligner sur la grille", + "edit-mode-grid-snap-scale-label": "ร‰chelle d'accrochage ร  la grille", + "edit-mode-label": "Mode รฉdition", + "enabled-description": "Activer ou dรฉsactiver complรจtement les widgets du bureau.", + "enabled-label": "Activer les widgets de bureau", + "general-desc": "Configurer les widgets qui apparaissent sur votre bureau.", + "general-title": "Widgets de bureau", + "media-player-enabled-description": "Afficher un widget de lecteur multimรฉdia sur le bureau.", + "media-player-enabled-label": "Activer le widget lecteur multimรฉdia", + "media-player-rounded-corners-description": "Activer les coins arrondis sur les bords du widget.", + "media-player-show-album-art-description": "Afficher la pochette de l'album et les informations sur les pistes (titre et artiste).", + "media-player-show-album-art-label": "Afficher la pochette et le titre de l'album", + "media-player-show-background-description": "Afficher le conteneur d'arriรจre-plan pour le widget lecteur multimรฉdia.", + "media-player-show-buttons-description": "Afficher les boutons de contrรดle multimรฉdia (lecture/pause, prรฉcรฉdent, suivant) sur le widget lecteur multimรฉdia.", + "media-player-show-buttons-label": "Afficher les boutons", + "media-player-show-visualizer-description": "Afficher la superposition de l'visualiseur audio.", + "media-player-visualizer-type-description": "Choisissez un type de visualisation pour l'arriรจre-plan du lecteur multimรฉdia de bureau.", + "overview-enabled-description": "Afficher les widgets du bureau lorsque l'aperรงu du compositeur est ouvert.", + "overview-enabled-label": "Afficher dans l'aperรงu", + "system-stat-layout-bottom": "Bas", + "system-stat-layout-description": "Choisissez comment la lรฉgende est affichรฉe par rapport au graphique.", + "system-stat-layout-label": "Disposition", + "system-stat-layout-side": "Cรดtรฉ", + "system-stat-rounded-corners-description": "Utiliser des coins arrondis pour l'arriรจre-plan du widget.", + "system-stat-rounded-corners-label": "Coins Arrondis", + "system-stat-show-background-description": "Afficher le conteneur d'arriรจre-plan pour le widget de statistiques systรจme.", + "system-stat-show-background-label": "Afficher l'arriรจre-plan", + "system-stat-stat-type-description": "Choisis quelle statistique systรจme afficher.", + "system-stat-stat-type-label": "Type de Statistique", + "title": "Widgets de bureau", + "weather-enabled-description": "Afficher un widget mรฉtรฉo sur le bureau.", + "weather-enabled-label": "Activer le widget mรฉtรฉo", + "weather-show-background-description": "Afficher le conteneur d'arriรจre-plan pour le widget mรฉtรฉo." + }, + "display": { + "monitors-backlight-device-auto-option": "Par dรฉfaut", + "monitors-backlight-device-description": "Sรฉlectionnez un appareil de rรฉtroรฉclairage pour cette sortie.", + "monitors-backlight-device-label": "Appareil de Rรฉtroรฉclairage", + "monitors-brightness-step-description": "Ajustez l'incrรฉment pour les changements de luminositรฉ (molette de la souris et raccourcis clavier).", + "monitors-brightness-step-label": "Incrรฉment de luminositรฉ", + "monitors-brightness-unavailable-ddc-disabled": "Contrรดle de la luminositรฉ indisponible. Activez \"Prise en charge de la luminositรฉ externe\" pour contrรดler la luminositรฉ de cet รฉcran.", + "monitors-brightness-unavailable-generic": "Le contrรดle de la luminositรฉ n'est pas disponible pour cet รฉcran.", + "monitors-desc": "Ajustez la luminositรฉ pour chaque รฉcran.", + "monitors-enforce-minimum-description": "Rรฉsout le problรจme de l'extinction complรจte du rรฉtroรฉclairage sur certains รฉcrans ร  0% de luminositรฉ.", + "monitors-enforce-minimum-label": "Imposer une luminositรฉ minimale (1%)", + "monitors-external-brightness-description": "Activer le support DDCUtil pour contrรดler la luminositรฉ des รฉcrans externes via le protocole DDC/CI.", + "monitors-external-brightness-label": "Prise en charge de la luminositรฉ externe", + "monitors-global-brightness-description": "Ajuster la luminositรฉ de tous les moniteurs en mรชme temps.", + "monitors-global-brightness-label": "Tous les moniteurs", + "monitors-title": "Paramรจtres par moniteur", + "night-light-auto-schedule-description": "Basรฉ sur l'heure du coucher et du lever du soleil ร  {location} โ€” recommandรฉ.", + "night-light-auto-schedule-label": "Planification automatique", + "night-light-desc": "Rรฉduit l'รฉmission de lumiรจre bleue pour vous aider ร  mieux dormir et rรฉduire la fatigue oculaire.", + "night-light-enable-description": "Applique un filtre de couleur chaude pour rรฉduire l'รฉmission de lumiรจre bleue.", + "night-light-enable-label": "Activer l'รฉclairage nocturne", + "night-light-force-activation-description": "Ignore la planification et applique le filtre nocturne immรฉdiatement.", + "night-light-force-activation-label": "Forcer l'activation", + "night-light-manual-schedule-description": "Dรฉfinissez des heures personnalisรฉes pour le lever et le coucher du soleil.", + "night-light-manual-schedule-label": "Planification manuelle", + "night-light-manual-schedule-select-start": "Sรฉlectionner l'heure de dรฉbut", + "night-light-manual-schedule-select-stop": "Sรฉlectionner l'heure de fin", + "night-light-manual-schedule-sunrise": "Heure du lever du soleil", + "night-light-manual-schedule-sunset": "Heure du coucher du soleil", + "night-light-temperature-day": "Jour", + "night-light-temperature-day-description": "Contrรดle la tempรฉrature pendant la journรฉe.", + "night-light-temperature-description": "Rรฉglez la chaleur des couleurs pour la nuit et le jour.", + "night-light-temperature-label": "Tempรฉrature de couleur", + "night-light-temperature-night": "Nuit", + "night-light-temperature-night-description": "Contrรดle la tempรฉrature pendant la nuit.", + "title": "Affichage" + }, + "dock": { + "appearance-background-opacity-description": "Ajustez l'opacitรฉ de l'arriรจre-plan du dock.", + "appearance-border-radius-description": "Ajuster le rayon de bordure du dock.", + "appearance-border-radius-label": "Rayon de bordure", + "appearance-colorize-icons-description": "Appliquer les couleurs du thรจme aux icรดnes d'applications du dock (applications non focalisรฉes uniquement).", + "appearance-colorize-icons-label": "Coloriser les icรดnes", + "appearance-dead-opacity-description": "Ajuster l'opacitรฉ des icรดnes d'applications qui ne sont pas en cours d'exรฉcution.", + "appearance-dead-opacity-label": "Opacitรฉ morte", + "appearance-desc": "Personnalisez le comportement et l'apparence du dock.", + "appearance-display-auto-hide": "Masquer automatiquement", + "appearance-display-description": "Choisissez comment le dock se comporte.", + "appearance-display-exclusive": "Exclusif", + "appearance-dock-indicator-description": "Afficher un petit indicateur lorsque le dock est masquรฉ.", + "appearance-dock-indicator-label": "Indicateur du dock", + "appearance-floating-distance-description": "Ajustez la distance de flottaison par rapport au bord de l'รฉcran.", + "appearance-floating-distance-label": "Distance de flottaison du dock", + "appearance-group-apps-description": "Regrouper plusieurs fenรชtres de la mรชme app en une seule entrรฉe du Dock.", + "appearance-group-apps-label": "Regrouper les mรชmes applications", + "appearance-group-click-action-cycle": "Basculer entre les fenรชtres", + "appearance-group-click-action-description": "Choisissez ce que fait le clic gauche pour les applications groupรฉes.", + "appearance-group-click-action-label": "Action au clic d'application groupรฉe", + "appearance-group-click-action-list": "Ouvrir la liste des fenรชtres", + "appearance-group-context-menu-mode-description": "Choisissez comment le menu contextuel se comporte pour les applications groupรฉes.", + "appearance-group-context-menu-mode-extended": "ร‰tendu", + "appearance-group-context-menu-mode-label": "Mode de menu d'applications groupรฉ", + "appearance-group-context-menu-mode-list": "Liste des fenรชtres", + "appearance-group-indicator-style-description": "Choisissez comment les indicateurs d'exรฉcution groupรฉs affichent l'รฉtat de la fenรชtre active.", + "appearance-group-indicator-style-dots": "Points", + "appearance-group-indicator-style-label": "Style d'indicateur groupรฉ", + "appearance-group-indicator-style-number": "Nombre", + "appearance-hide-show-speed-description": "Ajustez la vitesse de l'animation d'affichage/masquage du dock.", + "appearance-hide-show-speed-label": "Vitesse d'affichage/masquage", + "appearance-icon-size-description": "Ajuster la taille globale du dock.", + "appearance-icon-size-label": "Taille du dock", + "appearance-inactive-indicators-description": "Afficher les pastilles d'indicateur pour toutes les applications, pas seulement celle qui est active.", + "appearance-inactive-indicators-label": "Indicateurs de fonctionnement", + "appearance-indicator-color-description": "Choisissez la couleur de l'indicateur du dock masquรฉ.", + "appearance-indicator-color-label": "Couleur de l'indicateur", + "appearance-indicator-opacity-description": "Ajustez l'opacitรฉ de l'indicateur du dock masquรฉ.", + "appearance-indicator-opacity-label": "Opacitรฉ de l'indicateur", + "appearance-indicator-thickness-description": "Activez un indicateur du dock masquรฉ plus รฉpais (6 px au lieu de 3 px).", + "appearance-indicator-thickness-label": "Indicateur plus รฉpais", + "appearance-launcher-icon-description": "Choisissez l'icรดne utilisรฉe pour le Dock Launcher.", + "appearance-launcher-icon-label": "Icรดne du Lanceur", + "appearance-launcher-position-description": "Choisissez oรน l'icรดne du lanceur apparaรฎt dans le dock.", + "appearance-launcher-position-end": "Fin", + "appearance-launcher-position-label": "Position du lanceur", + "appearance-launcher-position-start": "Dรฉmarrer", + "appearance-launcher-use-distro-logo-description": "Utilise le logo de ta distribution comme icรดne du Dock Launcher.", + "appearance-launcher-use-distro-logo-label": "Utiliser le logo de la distribution au lieu de l'icรดne", + "appearance-pinned-static-description": "Toujours pousser les icรดnes d'applications รฉpinglรฉes vers la gauche dans un ordre statique.", + "appearance-pinned-static-label": "Applications รฉpinglรฉes statiques", + "appearance-position-description": "Choisissez l'emplacement du Dock ร  l'รฉcran.", + "appearance-position-label": "Position", + "appearance-show-launcher-icon-description": "Afficher l'icรดne du lanceur d'applications dans le dock.", + "appearance-show-launcher-icon-label": "Afficher le Lanceur d'applications", + "appearance-sit-on-frame-description": "Aligner le dock ร  l'intรฉrieur de la bordure du cadre au lieu qu'il se superpose.", + "appearance-sit-on-frame-label": "Le dock est sur le cadre", + "appearance-type-attached": "Ancrรฉ", + "appearance-type-description": "Choisissez entre une barre flottante en forme de pilule ou une barre statique attachรฉe au bord.", + "appearance-type-floating": "Flottant", + "appearance-type-label": "Style du dock", + "enabled-description": "Afficher ou masquer complรจtement le dock.", + "enabled-label": "Activer le dock", + "monitors-desc": "Afficher le dock sur des รฉcrans spรฉcifiques. Par dรฉfaut, il s'affiche sur tous les รฉcrans si aucun n'est sรฉlectionnรฉ.", + "monitors-only-same-monitor-description": "Afficher uniquement les applications de l'รฉcran oรน se trouve le dock.", + "monitors-only-same-monitor-label": "Seulement les applications du mรชme รฉcran", + "monitors-title": "Affichage sur les moniteur", + "title": "Dock" + }, + "general": { + "copy-settings": "Copier les paramรจtres", + "fonts-default-description": "Police principale utilisรฉe dans toute l'interface.", + "fonts-default-label": "Police par dรฉfaut", + "fonts-default-placeholder": "Sรฉlectionner la police par dรฉfaut...", + "fonts-default-scale-description": "Augmenter ou diminuer la taille du texte standard.", + "fonts-default-scale-label": "Taille de police par dรฉfaut", + "fonts-default-search-placeholder": "Rechercher des polices...", + "fonts-desc": "Choisissez les polices utilisรฉes dans toute l'interface.", + "fonts-monospace-description": "Police monospace utilisรฉe pour l'affichage des chiffres et des statistiques.", + "fonts-monospace-label": "Police monospace", + "fonts-monospace-placeholder": "Sรฉlectionner la police monospace...", + "fonts-monospace-scale-description": "Augmenter ou diminuer la taille du texte monospace.", + "fonts-monospace-scale-label": "Taille de police monospace", + "fonts-monospace-search-placeholder": "Rechercher des polices monospace...", + "fonts-title": "Polices", + "keybinds-conflict-description": "La combinaison de touches est dรฉjร  attribuรฉe ร  {action}.", + "keybinds-conflict-title": "Conflit de raccourcis clavier", + "keybinds-description": "Configurez les touches de navigation globales pour les panneaux et le lanceur.", + "keybinds-down": "Dรฉplacer vers le bas", + "keybinds-enter": "Confirmer / Action", + "keybinds-escape": "Fermer / Retour", + "keybinds-left": "Dรฉplacer ร  gauche", + "keybinds-modifier-description": "Les raccourcis doivent inclure une touche de modification (Ctrl ou Alt).", + "keybinds-modifier-title": "Modificateur requis", + "keybinds-remove": "Supprimer", + "keybinds-right": "Dรฉplacer ร  droite", + "keybinds-title": "Raccourcis clavier de navigation", + "keybinds-up": "Dรฉplacer vers le haut", + "language-desc": "Choisissez votre langue prรฉfรฉrรฉe pour l'application.", + "language-select-auto-detect": "Automatique", + "language-select-description": "Sรฉlectionnez la langue utilisรฉe dans l'interface de l'application.", + "language-select-label": "Langue de l'application", + "launch-setup-wizard": "Assistant de configuration", + "profile-desc": "Modifiez vos informations utilisateur et votre avatar.", + "profile-picture-description": "Votre photo de profil qui apparaรฎt dans toute l'interface.", + "profile-picture-label": "Photo de profil", + "profile-select-avatar": "Sรฉlectionner une image d'avatar", + "profile-title": "Profil", + "profile-tooltip": "Photo de profil", + "reverse-scrolling-description": "Inverser le sens de dรฉfilement interprรฉtรฉ.", + "reverse-scrolling-label": "Dรฉfilement inversรฉ", + "screen-corners-desc": "Personnalisez l'arrondi des coins de l'รฉcran et les effets visuels.", + "screen-corners-radius-description": "Ajustez l'arrondi des coins de l'รฉcran.", + "screen-corners-radius-label": "Rayon des coins de l'รฉcran", + "screen-corners-radius-reset": "Rรฉinitialiser le rayon des coins de l'รฉcran", + "screen-corners-show-corners-description": "Affichez des coins arrondis sur le bord de l'รฉcran.", + "screen-corners-show-corners-label": "Afficher les coins de l'รฉcran", + "screen-corners-solid-black-description": "Utilisez du noir uni au lieu de la couleur de fond de la barre.", + "screen-corners-solid-black-label": "Coins noirs unis", + "screen-corners-title": "Coins de l'รฉcran", + "settings-copied": "Paramรจtres copiรฉs dans le presse-papier", + "smooth-scrolling-description": "Animer le dรฉfilement des listes pour une expรฉrience de molette plus fluide.", + "smooth-scrolling-label": "Dรฉfilement fluide", + "tab-basics": "Bases", + "tab-keybinds": "Raccourcis clavier" + }, + "hooks": { + "color-generation-description": "Commande ร  exรฉcuter lorsque la gรฉnรฉration de couleurs est terminรฉe (par ex. aprรจs la thรฉmatisation basรฉe sur le fond d'รฉcran).", + "color-generation-label": "Couleurs gรฉnรฉrรฉes", + "color-generation-placeholder": "par ex. notify-send \"Couleurs\" \"Gรฉnรฉrรฉes\"", + "info-command-info-description": "โ€ข Les commandes sont exรฉcutรฉes via le shell (sh -lc)
โ€ข Les commandes s'exรฉcutent en arriรจre-plan (dรฉtachรฉes)
โ€ข Les boutons de test s'exรฉcutent avec les valeurs actuelles", + "info-parameters-description": "โ€ข Wallpaper hook: $1 = chemin du fond d'รฉcran, $2 = nom de l'รฉcran, $3 = thรจme (sombre/clair)
โ€ข Theme toggle hook: $1 = true/false (รฉtat du mode sombre)
โ€ข Screen lock/unlock hooks: $1 = lock/unlock (รฉtat de verrouillage de l'รฉcran)
โ€ข Performance mode hooks: Aucun paramรจtre
โ€ข Session hook: $1 = action (shutdown/reboot)", + "info-parameters-label": "Paramรจtres disponibles", + "noctalia-started-description": "Commande ร  exรฉcuter lorsque Noctalia a fini de charger.", + "noctalia-started-label": "Noctalia dรฉmarrรฉ", + "noctalia-started-placeholder": "ex : notify-send 'Noctalia chargรฉ'", + "performance-mode-disabled-description": "Commande ร  exรฉcuter lorsque le mode performance Noctalia est dรฉsactivรฉ.", + "performance-mode-disabled-label": "Mode performance dรฉsactivรฉ", + "performance-mode-disabled-placeholder": "ex : notify-send \"Performance\" \"Mode dรฉsactivรฉ\"", + "performance-mode-enabled-description": "Commande ร  exรฉcuter lorsque le mode performance Noctalia est activรฉ.", + "performance-mode-enabled-label": "Mode performance activรฉ", + "performance-mode-enabled-placeholder": "ex : notify-send \"Performance\" \"Mode activรฉ\"", + "screen-lock-description": "Commande ร  exรฉcuter lorsque l'รฉcran est verrouillรฉ.", + "screen-lock-label": "ร‰cran verrouillรฉ", + "screen-lock-placeholder": "ex : notify-send \"ร‰cran\" \"Verrouillรฉ\"", + "screen-unlock-description": "Commande ร  exรฉcuter lorsque l'รฉcran est dรฉverrouillรฉ.", + "screen-unlock-label": "ร‰cran dรฉverrouillรฉ", + "screen-unlock-placeholder": "ex : notify-send \"ร‰cran\" \"Dรฉverrouillรฉ\"", + "session-description": "Commande ร  exรฉcuter avant l'arrรชt ou le redรฉmarrage. Reรงoit le type d'action comme $1 (arrรชt/redรฉmarrage).", + "session-label": "Fin de session", + "session-placeholder": "ex : notify-send \"Session\" \"$1\"", + "system-hooks-desc": "Configurez des commandes ร  exรฉcuter lors d'รฉvรฉnements systรจme.", + "system-hooks-enable-description": "Activer ou dรฉsactiver toutes les commandes de hook.", + "system-hooks-enable-label": "Activer les hooks", + "system-hooks-title": "Hooks systรจme", + "theme-changed-description": "Commande ร  exรฉcuter lorsque le thรจme bascule entre le mode sombre et clair.", + "theme-changed-label": "Thรจme changรฉ", + "theme-changed-placeholder": "ex : notify-send \"Thรจme\" \"Basculรฉ\"", + "title": "Hooks", + "wallpaper-changed-description": "Commande ร  exรฉcuter lorsque le fond d'รฉcran change.", + "wallpaper-changed-label": "Fond d'รฉcran changรฉ", + "wallpaper-changed-placeholder": "ex : notify-send \"Fond d'รฉcran\" \"Changรฉ\"" + }, + "idle": { + "custom-add": "Ajouter une commande", + "custom-description": "Exรฉcute une commande shell aprรจs une pรฉriode d'inactivitรฉ.", + "custom-entry-command": "Commande", + "custom-entry-delete": "Supprimer", + "custom-entry-edit": "Modifier la Commande Personnalisรฉe", + "custom-entry-name": "Nom", + "custom-entry-name-placeholder": "ex : ร‰teindre les lumiรจres", + "custom-entry-new": "Nouvelle Commande Personnalisรฉe", + "custom-entry-timeout": "Temps d'inactivitรฉ", + "custom-entry-timeout-format": "{count} seconde", + "custom-entry-timeout-format-plural": "{count} secondes", + "custom-entry-unnamed": "Commande sans nom", + "custom-label": "Commandes d'inactivitรฉ personnalisรฉes", + "enable-description": "ร‰teindre l'รฉcran, verrouiller ou suspendre automatiquement aprรจs une pรฉriode d'inactivitรฉ.", + "enable-label": "Activer la gestion de l'inactivitรฉ", + "fade-duration-description": "Secondes pour l'animation de fondu au noir avant que chaque action ne se dรฉclenche. Tout mouvement de souris annule le fondu.", + "fade-duration-label": "Durรฉe du fondu", + "lock-description": "Secondes d'inactivitรฉ avant l'activation de l'รฉcran de verrouillage.", + "lock-label": "Verrouiller l'รฉcran", + "resume-command-label": "Reprendre la commande", + "screen-off-description": "Secondes d'inactivitรฉ avant que les moniteurs ne s'รฉteignent.", + "screen-off-label": "ร‰teindre l'รฉcran", + "status-description": "Temps d'inactivitรฉ tel que rapportรฉ par le compositeur.", + "status-label": "Temps d'inactivitรฉ", + "suspend-description": "Secondes d'inactivitรฉ avant que le systรจme ne se suspende.", + "tab-behavior": "Comportement", + "tab-custom": "Personnalisรฉ", + "timeouts-description": "Rรฉglez sur 0 pour dรฉsactiver une รฉtape. Les dรฉlais d'attente sont mis en pause tant que 'Rester รฉveillรฉ' est actif.", + "timeouts-label": "Dรฉlais d'expiration", + "title": "Inactivitรฉ", + "unavailable": "La surveillance native de l'inactivitรฉ n'est pas disponible sur ce compositeur." + }, + "indicator": { + "default-value": "Par dรฉfaut : {value}", + "system-default": "Par dรฉfaut du systรจme" + }, + "launcher": { + "clipboard-desc": "Accรฉdez ร  et gรฉrez votre historique du presse-papiers depuis le lanceur.", + "execute-desc": "Configurer la faรงon dont les applications sont lancรฉes.", + "execute-title": "Exรฉcution", + "settings-annotation-tool-description": "Commande ร  exรฉcuter lorsque l'on clique sur le bouton d'annotation dans l'historique du presse-papiers. L'image sera envoyรฉe ร  cette commande.", + "settings-annotation-tool-label": "Outil d'annotation", + "settings-annotation-tool-placeholder": "ex : 'gradia', 'satty -f -'", + "settings-auto-paste-description": "Coller automatiquement l'รฉlรฉment sรฉlectionnรฉ du presse-papiers. Nรฉcessite wtype.", + "settings-auto-paste-label": "Collage automatique", + "settings-clip-chips-description": "Afficher une barre d'onglets pour filtrer l'historique du presse-papiers par type (Images, Liens, Fichiers, Code, etc.).", + "settings-clip-chips-label": "Activer les puces de catรฉgorie", + "settings-clip-preview-description": "Afficher un aperรงu du contenu du presse-papiers lors de l'utilisation de la commande >clip.", + "settings-clip-preview-label": "Activer l'aperรงu du presse-papiers", + "settings-clip-smart-icons-description": "Afficher des icรดnes spรฉcifiques pour les liens, les fichiers, les couleurs et le code au lieu d'une icรดne de presse-papiers gรฉnรฉrique.", + "settings-clip-smart-icons-label": "Activer les icรดnes intelligentes", + "settings-clip-wrap-text-description": "Envelopper le texte dans la liste du presse-papiers au lieu de le tronquer.", + "settings-clip-wrap-text-label": "Envelopper le texte du presse-papiers", + "settings-clipboard-history-description": "Accรฉdez aux รฉlรฉments prรฉcรฉdemment copiรฉs depuis le lanceur.", + "settings-clipboard-history-label": "Activer l'historique du presse-papiers", + "settings-clipboard-watch-image-description": "Chaรฎne de commande complรจte passรฉe ร  wl-paste pour les changements d'image (redรฉmarrage requis).", + "settings-clipboard-watch-image-label": "Commande de Surveillance d'Images", + "settings-clipboard-watch-text-description": "Chaรฎne de commande complรจte passรฉe ร  wl-paste pour les changements de texte (nรฉcessite un redรฉmarrage).", + "settings-clipboard-watch-text-label": "Commande de surveillance de texte", + "settings-custom-launch-prefix-description": "Prรฉfixer les commandes avec un lanceur personnalisรฉ (ex : 'runapp' pour l'intรฉgration systemd).", + "settings-custom-launch-prefix-enabled-description": "Utiliser un prรฉfixe personnalisรฉ pour lancer les applications au lieu de la mรฉthode par dรฉfaut.", + "settings-custom-launch-prefix-enabled-label": "Activer le prรฉfixe de lancement personnalisรฉ", + "settings-custom-launch-prefix-label": "Prรฉfixe de lancement personnalisรฉ", + "settings-density-description": "Ajustez la taille des icรดnes d'application et la densitรฉ du Launcher.", + "settings-density-label": "Densitรฉ", + "settings-desc": "Personnalisez le comportement et l'apparence du lanceur.", + "settings-enable-session-search-description": "Afficher les actions de session (verrouiller, รฉteindre, redรฉmarrer, etc.) lors de la recherche dans le Launcher.", + "settings-enable-session-search-label": "Activer la recherche de session", + "settings-enable-settings-search-description": "Afficher les rรฉsultats des paramรจtres lors de la recherche dans le lanceur.", + "settings-enable-settings-search-label": "Activer la recherche dans les paramรจtres", + "settings-enable-windows-search-description": "Rechercher et focaliser les fenรชtres actives.", + "settings-enable-windows-search-label": "Activer la recherche de fenรชtres", + "settings-grid-view-description": "Afficher les รฉlรฉments dans une grille au lieu d'une liste.", + "settings-icon-mode-description": "Utiliser les icรดnes natives du systรจme au lieu des icรดnes Tabler.", + "settings-icon-mode-label": "Utiliser les icรดnes natives", + "settings-ignore-mouse-input-description": "Dรฉsactiver l'interaction de la souris et la molette de dรฉfilement dans le lanceur.", + "settings-ignore-mouse-input-label": "Ignorer l'entrรฉe de la souris", + "settings-overlay-layer-description": "Affiche le lanceur sur la couche de superposition, au-dessus des fenรชtres en plein รฉcran. Lorsqu'il est activรฉ, le lanceur ne s'attachera pas ร  la barre.", + "settings-overlay-layer-label": "Afficher au-dessus du plein รฉcran", + "settings-position-description": "Choisissez oรน le panneau du lanceur apparaรฎt.", + "settings-show-categories-description": "Afficher les onglets de catรฉgories pour filtrer les applications.", + "settings-show-categories-label": "Afficher les catรฉgories", + "settings-show-icon-background-description": "Afficher un arriรจre-plan rectangulaire arrondi derriรจre les icรดnes.", + "settings-show-icon-background-label": "Afficher l'arriรจre-plan de l'icรดne", + "settings-sort-by-usage-description": "Lorsque cette option est activรฉe, les applications frรฉquemment lancรฉes apparaissent en premier dans la liste.", + "settings-sort-by-usage-label": "Trier par les plus utilisรฉs", + "settings-terminal-command-description": "Commande pour lancer un terminal. Ex : 'kitty -e' ou 'gnome-terminal --'.", + "settings-terminal-command-label": "Commande du terminal", + "settings-use-app2unit-description": "Utilise une mรฉthode de lancement alternative pour mieux gรฉrer les processus des applications et prรฉvenir les problรจmes.", + "settings-use-app2unit-label": "Utiliser App2Unit pour lancer les applications", + "settings-view-mode-description": "Choisis la disposition des entrรฉes du Lanceur.", + "settings-view-mode-label": "Mode d'affichage", + "title": "Lanceur" + }, + "location": { + "auto-locate-description": "Dรฉtecte automatiquement votre position pรฉriodiquement en utilisant votre adresse IP.", + "auto-locate-label": "Localisation Automatique", + "calendar-cards-desc": "Organiser et activer/dรฉsactiver les cartes dans le panneau de calendrier.", + "calendar-cards-title": "Cartes de calendrier", + "calendar-header-label": "En-tรชte du calendrier", + "calendar-month-label": "Mois calendaire", + "date-time-12hour-format-description": "Afficher l'heure au format 12 heures sur l'รฉcran de verrouillage et dans le calendrier. L'horloge de la barre possรจde ses propres paramรจtres.", + "date-time-12hour-format-label": "Utiliser le format horaire de 12 heures", + "date-time-desc": "Personnalisez l'affichage de la date et de l'heure.", + "date-time-first-day-of-week-automatic": "Automatique (utiliser les paramรจtres rรฉgionaux du systรจme)", + "date-time-first-day-of-week-description": "Choisissez quel jour commence la semaine dans le calendrier.", + "date-time-first-day-of-week-label": "Premier jour de la semaine", + "date-time-show-events-description": "Afficher les รฉvรฉnements dans le panneau du calendrier.", + "date-time-show-events-label": "Afficher les รฉvรฉnements du calendrier", + "date-time-title": "Date & heure", + "date-time-use-analog-description": "Afficher une horloge de style analogique sur la fenรชtre du calendrier.", + "date-time-use-analog-label": "Utiliser une horloge de style analogique", + "date-time-week-numbers-description": "Afficher la semaine de l'annรฉe (ex : Semaine 38) dans le calendrier.", + "date-time-week-numbers-label": "Afficher les numรฉros de semaine", + "geolocate-now-button": "Localiser maintenant", + "location-desc": "Obtenez une mรฉtรฉo et une planification de l'รฉclairage nocturne prรฉcises en dรฉfinissant votre emplacement.", + "location-search-description": "ex : Toronto, ON", + "location-search-label": "Rechercher un emplacement", + "location-search-placeholder": "Entrez le nom de l'emplacement", + "location-title": "Votre emplacement", + "weather-desc": "Choisissez votre unitรฉ de tempรฉrature prรฉfรฉrรฉe.", + "weather-enabled-description": "Afficher les informations mรฉtรฉorologiques dans toute l'interface et rรฉcupรฉrer les donnรฉes mรฉtรฉo. Lorsque dรฉsactivรฉ, tous les รฉlรฉments mรฉtรฉo seront masquรฉs et aucune requรชte rรฉseau ne sera effectuรฉe.", + "weather-enabled-label": "Activer la mรฉtรฉo", + "weather-fahrenheit-description": "Afficher la tempรฉrature en Fahrenheit au lieu de Celsius.", + "weather-fahrenheit-label": "Afficher la tempรฉrature en Fahrenheit (ยฐF)", + "weather-hide-city-description": "Masquer le nom de la ville dans les affichages mรฉtรฉo de toute l'interface.", + "weather-hide-city-label": "Cacher le nom de la ville", + "weather-hide-timezone-description": "Masquer l'abrรฉviation du fuseau horaire des affichages mรฉtรฉorologiques dans toute l'interface.", + "weather-hide-timezone-label": "Masquer le fuseau horaire", + "weather-show-effects-description": "Afficher des effets visuels supplรฉmentaires (comme la pluie, la neige ou la foudre) sur la carte mรฉtรฉo.", + "weather-show-effects-label": "Afficher les effets mรฉtรฉorologiques", + "weather-show-in-calendar-description": "Afficher les prรฉvisions mรฉtรฉo quotidiennes directement dans votre vue calendrier.", + "weather-show-in-calendar-label": "Afficher la mรฉtรฉo dans le calendrier", + "weather-talia-mascot-always-description": "Toujours utiliser les Talia emojis au lieu des tabler icons.", + "weather-talia-mascot-always-label": "Toujours afficher les emojis mรฉtรฉo de Talia" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "Lorsque fprintd (authentification par empreinte digitale) est actif, cette option vous permet de vous connecter avec votre mot de passe au lieu d'une empreinte digitale.", + "allow-password-with-fprintd-label": "Autoriser la connexion par mot de passe avec fprintd", + "auto-start-auth-description": "Dรฉmarre automatiquement l'authentification par empreinte digitale sans nรฉcessiter de pression sur une touche ou un clic de bouton.", + "auto-start-auth-label": "Authentification au dรฉmarrage automatique", + "clock-format-description": "Personnalisez le format de l'horloge en utilisant les jetons de syntaxe de date/heure.", + "clock-format-label": "Format de l'horloge", + "clock-style-analog": "Analogique", + "clock-style-custom": "Personnalisรฉ", + "clock-style-description": "Choisissez le style visuel de l'horloge sur l'รฉcran de verrouillage.", + "clock-style-digital": "Numรฉrique", + "clock-style-label": "Style de l'Horloge", + "compact-lockscreen-description": "Afficher uniquement le champ de saisie de connexion et les commandes systรจme, en masquant les widgets mรฉtรฉo et multimรฉdia.", + "compact-lockscreen-label": "ร‰cran de verrouillage compact", + "enable-lockscreen-media-controls-description": "Afficher les commandes interactives de lecture multimรฉdia sur l'รฉcran de verrouillage.", + "enable-lockscreen-media-controls-label": "Commandes Multimรฉdia de l'ร‰cran de Verrouillage", + "lock-on-suspend-description": "Verrouiller automatiquement l'รฉcran lors de la mise en veille du systรจme.", + "lock-on-suspend-label": "Verrouiller ร  la suspension", + "lock-screen-animations-description": "Activer ou dรฉsactiver les animations de l'รฉcran de verrouillage.", + "lock-screen-animations-label": "Animations de l'ร‰cran de Verrouillage", + "lock-screen-blur-strength-description": "Applique un effet de flou au fond d'รฉcran de l'รฉcran de verrouillage.", + "lock-screen-blur-strength-label": "Intensitรฉ du flou de l'รฉcran de verrouillage", + "lock-screen-tint-strength-description": "Applique une superposition de teinte au fond d'รฉcran de l'รฉcran de verrouillage.", + "lock-screen-tint-strength-label": "Force de la teinte de l'รฉcran de verrouillage", + "monitors-desc": "Afficher l'รฉcran de verrouillage sur des moniteurs spรฉcifiques. Par dรฉfaut, tous sont utilisรฉs si aucun n'est choisi.", + "password-chars-description": "De jolies icรดnes utilisรฉes pour masquer votre mot de passe.", + "password-chars-label": "Icรดnes de mot de passe alรฉatoires", + "show-hibernate-description": "Afficher lโ€™option 'hiberner' dans les commandes d'alimentation.", + "show-hibernate-label": "Afficher lโ€™hibernation", + "show-session-buttons-description": "Autoriser l'accรจs aux paramรจtres d'alimentation depuis l'รฉcran de verrouillage.", + "show-session-buttons-label": "Commandes d'alimentation", + "title": "ร‰cran de verrouillage" + }, + "network": { + "bluetooth-description": "Activer la gestion Bluetooth.", + "bluetooth-rssi-polling-description": "ร‰chantillonner pรฉriodiquement le RSSI des appareils connectรฉs via bluetoothctl. Peut ne pas รชtre disponible pour tous les appareils ; utilise un minimum de ressources lorsqu'il est activรฉ.", + "bluetooth-rssi-polling-label": "Interrogation du signal Bluetooth", + "desc": "Gรฉrez les connexions Wi-Fi et Bluetooth.", + "wifi-description": "Gรฉrer les rรฉseaux sans fil (nรฉcessite NetworkManager)." + }, + "notifications": { + "duration-critical-urgency-description": "Durรฉe de visibilitรฉ des notifications de prioritรฉ critique.", + "duration-critical-urgency-label": "Urgence critique", + "duration-desc": "Configurer la durรฉe d'affichage des notifications en fonction de leur niveau d'urgence.", + "duration-low-urgency-description": "Durรฉe de visibilitรฉ des notifications de faible prioritรฉ.", + "duration-low-urgency-label": "Urgence faible", + "duration-normal-urgency-description": "Durรฉe de visibilitรฉ des notifications de prioritรฉ normale.", + "duration-normal-urgency-label": "Urgence normale", + "duration-respect-expire-description": "Utiliser le dรฉlai d'expiration dรฉfini dans la notification.", + "duration-respect-expire-label": "Respecter le dรฉlai d'expiration", + "duration-title": "Durรฉe des notifications", + "history-clear-dismiss-description": "Effacer la notification de l'historique lorsqu'elle est ignorรฉe.", + "history-clear-dismiss-label": "Effacer si ignorรฉe", + "history-critical-urgency-description": "Enregistrer les notifications de prioritรฉ critique dans l'historique.", + "history-critical-urgency-label": "Enregistrer l'urgence critique dans l'historique", + "history-desc": "Contrรดler quelles notifications sont enregistrรฉes dans l'historique en fonction de leur niveau d'urgence.", + "history-low-urgency-description": "Enregistrer les notifications de prioritรฉ faible dans l'historique.", + "history-low-urgency-label": "Enregistrer l'urgence faible dans l'historique", + "history-normal-urgency-description": "Enregistrer les notifications de prioritรฉ normale dans l'historique.", + "history-normal-urgency-label": "Enregistrer l'urgence normale dans l'historique", + "monitors-desc": "Afficher les notifications sur des moniteurs spรฉcifiques. Par dรฉfaut, sur tous si aucun n'est choisi.", + "rules-action-block": "Bloc", + "rules-action-block-desc": "Ignore complรจtement.", + "rules-action-hide": "Masquer", + "rules-action-hide-desc": "Pas de popup, pas de son, ajoute ร  l'historique.", + "rules-action-label": "Action", + "rules-action-mute": "Muet", + "rules-action-mute-desc": "Pas de son, affiche toujours le pop-up et dans l'historique.", + "rules-add": "Ajouter une rรจgle", + "rules-delete": "Supprimer la rรจgle", + "rules-description": "Faire correspondre le nom de l'application ou le contenu. Les rรจgles sont vรฉrifiรฉes dans l'ordre, et la premiรจre correspondance est appliquรฉe.", + "rules-edit": "Modifier la rรจgle", + "rules-label": "Rรจgles de Filtrage", + "rules-pattern-label": "Motif", + "rules-pattern-placeholder": "firefox, discord, ou /regex/", + "rules-tab": "Rรจgles", + "settings-always-on-top-description": "Afficher les notifications au-dessus des fenรชtres en plein รฉcran et des autres applications.", + "settings-background-opacity-description": "Ajuster l'opacitรฉ des arriรจre-plans de notifications.", + "settings-density-description": "Choisissez la densitรฉ de la carte de notification.", + "settings-density-label": "Densitรฉ", + "settings-desc": "Configurez l'apparence et le comportement des notifications.", + "settings-do-not-disturb-description": "Dรฉsactive toutes les fenรชtres de notification lorsque cette option est activรฉe.", + "settings-enabled-description": "Activer ou dรฉsactiver le dรฉmon de notifications, nรฉcessite un redรฉmarrage de Noctalia shell.", + "settings-enabled-label": "Activer les notifications", + "settings-location-description": "Oรน les notifications apparaissent ร  l'รฉcran.", + "settings-markdown-description": "Afficher le contenu des notifications en utilisant le formatage Markdown.", + "settings-markdown-label": "Activer Markdown", + "sounds-desc": "Configurer les effets sonores et le volume des notifications.", + "sounds-enabled-description": "Activer les effets sonores pour les notifications entrantes.", + "sounds-enabled-label": "Activer les sons de notification", + "sounds-excluded-apps-description": "Ne pas jouer le son de notification configurรฉ pour les applications spรฉcifiques qui ont leurs propres sons intรฉgrรฉs.", + "sounds-excluded-apps-label": "Applications exclues", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "Chemin vers le fichier sonore jouรฉ pour les notifications de prioritรฉ critique.", + "sounds-files-critical-label": "Son d'urgence critique", + "sounds-files-critical-select-title": "Sรฉlectionner le fichier son d'urgence critique", + "sounds-files-desc": "Configurer des fichiers sonores personnalisรฉs pour diffรฉrents niveaux d'urgence de notification.", + "sounds-files-low-description": "Chemin vers le fichier sonore jouรฉ pour les notifications de prioritรฉ basse.", + "sounds-files-low-label": "Son d'urgence basse", + "sounds-files-low-select-title": "Sรฉlectionner le fichier son d'urgence basse", + "sounds-files-normal-description": "Chemin vers le fichier sonore jouรฉ pour les notifications de prioritรฉ normale.", + "sounds-files-normal-label": "Son d'urgence normale", + "sounds-files-normal-select-title": "Sรฉlectionner le fichier son d'urgence normale", + "sounds-files-placeholder": "Entrer le chemin vers le fichier sonore", + "sounds-files-select-file": "Sรฉlectionner un fichier son", + "sounds-files-title": "Fichiers sonores", + "sounds-files-unified-description": "Chemin vers le fichier sonore jouรฉ pour les notifications.", + "sounds-files-unified-label": "Son de notification", + "sounds-files-unified-select-title": "Sรฉlectionner le fichier son de notification", + "sounds-separate-description": "Utiliser diffรฉrents fichiers sonores pour les notifications de prioritรฉ basse, normale et critique.", + "sounds-separate-label": "Utiliser des sons diffรฉrents par prioritรฉ", + "sounds-title": "Paramรจtres sonores", + "sounds-unavailable-description": "Installez Qt6 Multimedia pour activer les sons de notification.", + "sounds-unavailable-label": "Sons de notification non disponibles", + "sounds-volume-description": "Ajuster le niveau de volume pour les sons de notification.", + "sounds-volume-label": "Volume sonore", + "toast-battery-description": "Afficher un avertissement lorsque le niveau de batterie descend en dessous de ce pourcentage.", + "toast-battery-label": "Avertissement de batterie", + "toast-desc": "Configurer l'apparence et le comportement des notifications toast.", + "toast-keyboard-description": "Afficher une notification lorsque la disposition du clavier change.", + "toast-keyboard-label": "Disposition du clavier", + "toast-media-description": "Afficher une notification toast lorsque l'รฉtat de lecture du mรฉdia change.", + "toast-media-label": "Mรฉdia" + }, + "osd": { + "always-on-top-description": "Afficher l'OSD au-dessus des fenรชtres plein รฉcran et autres couches.", + "always-on-top-label": "Toujours au premier plan", + "background-opacity-description": "Contrรดle la transparence de l'arriรจre-plan de l'OSD.", + "background-opacity-label": "Opacitรฉ de l'arriรจre-plan", + "description": "Configurer les indicateurs ร  l'รฉcran tels que le volume et la luminositรฉ.", + "duration-auto-hide-description": "Ajuster le dรฉlai avant disparition de l'OSD.", + "duration-auto-hide-label": "Masquer aprรจs", + "duration-desc": "Temps d'affichage avant masquage automatique de l'OSD.", + "duration-title": "Dรฉlai d'autoโ€‘masquage", + "enabled-description": "Afficher en temps rรฉel les changements de volume et de luminositรฉ.", + "enabled-label": "Activer l'affichage ร  l'รฉcran (OSD)", + "events-desc": "Sรฉlectionnez les รฉvรฉnements qui dรฉclenchent l'affichage ร  l'รฉcran.", + "general-desc": "Configurer la visibilitรฉ et le comportement de l'OSD.", + "location-description": "Emplacement des affichages ร  l'รฉcran.", + "monitors-desc": "Afficher l'OSD sur des moniteurs spรฉcifiques. Par dรฉfaut sur tous si aucun n'est choisi.", + "title": "Affichage ร  l'รฉcran", + "types-brightness-description": "Afficher l'OSD lorsque la luminositรฉ de l'รฉcran change.", + "types-brightness-label": "Luminositรฉ", + "types-custom-text-description": "Afficher lโ€™OSD pour les messages texte personnalisรฉs provenant de lโ€™IPC.", + "types-custom-text-label": "Texte personnalisรฉ", + "types-desc": "Sรฉlectionnez les รฉvรฉnements qui dรฉclenchent l'OSD.", + "types-input-volume-description": "Afficher l'OSD lorsque le volume du microphone change.", + "types-input-volume-label": "Volume d'entrรฉe", + "types-lockkey-description": "Afficher l'OSD lorsque Verr Maj, Verr Num ou Arrรชt dรฉfil est activรฉe ou dรฉsactivรฉe.", + "types-lockkey-label": "Touches de verrouillage", + "types-media-description": "Afficher l'OSD lorsque l'รฉtat de lecture multimรฉdia change (lecture, pause, saut).", + "types-media-label": "Lecture de mรฉdia", + "types-title": "ร‰vรฉnements de dรฉclenchement OSD", + "types-volume-description": "Afficher l'OSD lorsque le volume de sortie audio change.", + "types-volume-label": "Volume de sortie" + }, + "plugins": { + "auto-update": "Mettre ร  jour les plugins automatiquement", + "auto-update-description": "Mettre ร  jour automatiquement tous les plugins au dรฉmarrage du shell.", + "available-description": "Parcourir et installer des plugins ร  partir des sources configurรฉes.", + "available-label": "Plugins disponibles", + "available-no-plugins-description": "Vรฉrifiez les sources de vos plugins ou actualisez la liste.", + "available-no-plugins-label": "Aucun plugin disponible", + "check-for-updates": "Rechercher des mises ร  jour", + "checking-for-updates": "Recherche de mises ร  jour...", + "collision-already-installed": "Ce plugin est dรฉjร  installรฉ", + "collision-custom-version-exists": "Une version personnalisรฉe de \"{source}\" est dรฉjร  installรฉe", + "collision-official-version-exists": "La version officielle de ce plugin est dรฉjร  installรฉe", + "development-disable": "Dรฉsactiver le mode dรฉveloppeur pour ce plugin", + "development-enable": "Activer le mode de dรฉveloppement pour ce plugin.", + "filter-downloaded": "Tรฉlรฉchargรฉ", + "filter-not-downloaded": "Non tรฉlรฉchargรฉ", + "filter-tags-description": "Filtrer les plugins par catรฉgorie ou รฉtat de tรฉlรฉchargement.", + "filter-tags-label": "Mots-clรฉs", + "hot-reload-description": "Recharger automatiquement les plugins lorsque leurs fichiers sont modifiรฉss. Utile pour le dรฉveloppement de plugins.", + "hot-reload-label": "Rechargement ร  chaud (mode dรฉveloppement)", + "hot-reloaded": "Plugin rechargรฉย : {name}", + "install-error": "ร‰chec de l'installationย : {error}", + "install-incompatible": "{plugin} nรฉcessite Noctalia v{version} ou une version ultรฉrieure", + "install-success": "Installation de {plugin} rรฉussie", + "installed-description": "Gรฉrer et configurer tous les plugins installรฉs localement.", + "installed-label": "Plugins installรฉs", + "installed-no-plugins-description": "Installez les plugins depuis la section \"Disponible\".", + "installed-no-plugins-label": "Aucun plugin installรฉ", + "installing": "Installation de {plugin}...", + "notify-updates": "Notifications de mise ร  jour des plugins", + "notify-updates-description": "Afficher une notification contextuelle lorsque de nouvelles mises ร  jour de plugins sont disponibles.", + "open-plugin-page": "Ouvrir la page du plugin", + "open-plugins-tab": "Ouvrir les paramรจtres des plugins", + "plugin-settings-title": "Paramรจtres de {plugin}", + "refresh-refreshing": "Actualisation de la liste des plugins...", + "refresh-tooltip": "Actualiser les plugins disponibles", + "settings-error-not-loaded": "Plugin non chargรฉ", + "settings-saved": "Paramรจtres du plugin enregistrรฉs", + "settings-tooltip": "Paramรจtres du plugin", + "source-custom": "Source personnalisรฉe", + "sources-add-custom": "Ajouter un dรฉpรดt personnalisรฉ", + "sources-add-dialog-description": "Ajouter un dรฉpรดt GitHub comme source de plugin.", + "sources-add-dialog-error": "ร‰chec de l'ajout de la source du plugin", + "sources-add-dialog-name": "Nom du dรฉpรดt", + "sources-add-dialog-name-placeholder": "Mes plugins personnalisรฉs", + "sources-add-dialog-success": "Source du plugin ajoutรฉe avec succรจs", + "sources-add-dialog-title": "Ajouter la source du plugin", + "sources-add-dialog-url": "URL du dรฉpรดt", + "sources-description": "Gรฉrer les dรฉpรดts de plugins.", + "sources-placeholder": "Mon dรฉpรดt cool", + "sources-remove-tooltip": "Supprimer la source du plugin", + "title": "Plugins", + "translations-reloaded": "Traductions rechargรฉes : {name}", + "uninstall-dialog-description": "รŠtes-vous sรปr de vouloir dรฉsinstaller {plugin}ย ? Cette action supprimera toutes les donnรฉes du plugin.", + "uninstall-dialog-title": "Dรฉsinstaller le plugin", + "uninstall-error": "ร‰chec de la dรฉsinstallationย : {error}", + "uninstall-success": "{plugin} a รฉtรฉ dรฉsinstallรฉ avec succรจs", + "uninstalling": "Dรฉsinstallation de {plugin}...", + "update-all": "Tout mettre ร  jour ({count})", + "update-all-success": "Tous les plugins ont รฉtรฉ mis ร  jour avec succรจs", + "update-available": "Nouvelle mise ร  jour de plugin disponible", + "update-available-plural": "Mises ร  jour de plugins disponibles ({count})", + "update-error": "ร‰chec de la mise ร  jour du pluginย : {plugin}ย : {error}", + "update-pending": "v{current} โ†’ v{new} (nรฉcessite Noctalia v{required})", + "update-success": "Mise ร  jour du plugin {plugin} vers la version {version}", + "update-version": "v{current} โ†’ v{new}", + "updating": "Mise ร  jour..." + }, + "region": { + "title": "Rรฉgion" + }, + "session-menu": { + "countdown-duration-description": "Dรฉfinir la durรฉe du compte ร  rebours avant d'exรฉcuter les actions d'alimentation.", + "countdown-duration-label": "Durรฉe du compte ร  rebours", + "enable-countdown-description": "Afficher un compte ร  rebours avant d'exรฉcuter les actions d'alimentation.", + "enable-countdown-label": "Activer le compte ร  rebours", + "entries-desc": "Personnaliser les actions d'alimentation qui apparaissent dans le menu de session et leur ordre.", + "entries-title": "Actions d'alimentation", + "entry-settings-command-description": "Commande personnalisรฉe ร  exรฉcuter pour cette action. Laisser vide pour utiliser la commande systรจme par dรฉfaut.", + "entry-settings-command-placeholder": "ex : systemctl poweroff", + "entry-settings-default-command-lock": "ร‰cran de verrouillage interne (sans commande)", + "entry-settings-default-command-logout": "Dรฉconnexion interne (sans commande)", + "entry-settings-default-info-description": "Si aucune commande personnalisรฉe n'est spรฉcifiรฉe, la commande systรจme par dรฉfaut sera utilisรฉe.", + "entry-settings-default-info-label": "Commande par dรฉfaut", + "entry-settings-keybind-description": "Attribuez une combinaison de touches pour dรฉclencher cette action lorsque le menu de session est ouvert.", + "entry-settings-keybind-placeholder": "Appuyez sur Enregistrer pour assigner une touche...", + "entry-settings-keybind-recording": "Enregistrement... Appuyez sur n'importe quelle combinaison de touches", + "entry-settings-title": "Configurer {entry}", + "entry-settings-tooltip": "Configurer la commande", + "general-desc": "Configurer le comportement et l'apparence du panneau du menu de session.", + "large-buttons-layout-description": "Choisissez comment les boutons du menu de session sont affichรฉs.", + "large-buttons-layout-label": "Disposition des grands boutons", + "large-buttons-style-description": "Afficher le menu de session avec de grands boutons dans une disposition en grille.", + "large-buttons-style-label": "Style boutons larges", + "position-description": "Choisissez oรน le panneau du menu de session apparaรฎt lorsqu'il est ouvert.", + "show-header-description": "Afficher le titre et le bouton de fermeture en haut du menu de session.", + "show-header-label": "Afficher l'en-tรชte", + "show-keybinds-description": "Afficher les indices de raccourcis clavier sur les options de session.", + "show-keybinds-label": "Afficher les raccourcis clavier" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Garder les widgets de bureau visibles tant que le mode performance Noctalia est activรฉ.", + "noctalia-performance-disable-desktop-widgets-label": "Activer les widgets de bureau en mode performance", + "noctalia-performance-disable-wallpaper-description": "Gardez les fonds d'รฉcran du bureau, de l'aperรงu et de l'รฉcran de verrouillage visibles lorsque le mode performance Noctalia est activรฉ.", + "noctalia-performance-disable-wallpaper-label": "Activer le rendu du fond d'รฉcran en mode performance", + "title": "Systรจme" + }, + "system-monitor": { + "critical-color-label": "Couleur critique", + "custom-highlight-colors-title-label": "Couleurs de surbrillance personnalisรฉes", + "disk-available-label": "Espace disque disponible", + "disk-section-label": "Utilisation disque", + "enable-dgpu-monitoring-description": "Attention : Ceci va activer votre GPU dรฉdiรฉ (NVIDIA/AMD), ce qui peut avoir un impact significatif sur l'autonomie de la batterie des ordinateurs portables dotรฉs de cartes graphiques hybrides.", + "enable-dgpu-monitoring-label": "Activer la surveillance du GPU dรฉdiรฉ", + "external-monitor-description": "Entrez la commande ou le chemin de l'application ร  lancer lors de l'activation de l'application externe de surveillance du systรจme.", + "external-monitor-label": "Commande du moniteur systรจme externe", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "Configurer le comportement et l'apparence du moniteur systรจme.", + "gpu-section-label": "Tempรฉrature du GPU", + "highlight-colors-section-label": "Couleurs de surlignage", + "polling-interval-label": "Intervalle d'interrogation", + "polling-section-description": "Configurer la frรฉquence de mise ร  jour de chaque mรฉtrique systรจme.", + "polling-section-label": "Intervalles d'interrogation", + "threshold-critical": "Critique", + "threshold-warning": "Avertissement", + "thresholds-section-description": "Ajustez les seuils dโ€™avertissement/critiques et les intervalles dโ€™interrogation pour chaque mรฉtrique systรจme.", + "use-custom-highlight-colors-description": "Lorsque cette option est dรฉsactivรฉe, les couleurs de surbrillance par dรฉfaut du thรจme sont utilisรฉes.", + "use-custom-highlight-colors-label": "Utiliser des couleurs de surlignage personnalisรฉes", + "warning-color-label": "Couleur d'avertissement" + }, + "user-interface": { + "allow-panels-without-bar-description": "Quand cette option est activรฉe, les panneaux peuvent s'ouvrir sur n'importe quel รฉcran. Quand elle est dรฉsactivรฉe, les panneaux s'ouvriront uniquement sur les รฉcrans qui ont une barre, ce qui peut rรฉduire l'utilisation de la mรฉmoire.", + "allow-panels-without-bar-label": "Autoriser les panneaux sur les รฉcrans sans barre", + "animation-disable-description": "Dรฉsactiver toutes les animations pour une expรฉrience plus rapide et plus rรฉactive.", + "animation-disable-label": "Dรฉsactiver les animations de l'interface utilisateur", + "animation-speed-description": "Ajuster la vitesse globale de l'animation.", + "animation-speed-label": "Vitesse d'animation", + "animation-speed-reset": "Rรฉinitialiser la vitesse de l'animation", + "appearance-desc": "Personnaliser les รฉlรฉments visuels tels que les infobulles, les bordures et les ombres.", + "blur-behind-description": "Floute la zone derriรจre les barres et les panneaux en utilisant le protocole de flou du compositeur.", + "blur-behind-label": "Flou d'arriรจre-plan", + "box-border-description": "Affiche un contour autour des zones de contenu.", + "box-border-label": "Contour du conteneur", + "box-border-radius-description": "Ajuste de l'arrondi des coins des principales sections de la mise en page, telles que les barres latรฉrales, les cartes et les panneaux de contenu.", + "box-border-radius-label": "Rayon du conteneur", + "control-border-radius-description": "Contrรดle la courbure des รฉlรฉments interactifs, y compris les boutons, les bascules et les champs de texte.", + "control-border-radius-label": "Rayon d'entrรฉe", + "desc": "Personnaliser l'apparence, l'ergonomie et le comportement de l'interface.", + "dim-desktop-description": "Attรฉnuer le bureau lorsque des panneaux ou des menus sont ouverts.", + "dim-desktop-label": "Dim bureau", + "dimmer-opacity-description": "Dรฉfinir le niveau d'opacitรฉ pour l'assombrissement du bureau.", + "dimmer-opacity-label": "Opacitรฉ du bureau attรฉnuรฉ", + "panel-background-opacity-description": "Dรฉfinir l'opacitรฉ de l'arriรจre-plan pour tous les panneaux (barre, lanceur, paramรจtres, etc.).", + "panel-background-opacity-label": "Opacitรฉ de fond des panneaux", + "panels-attached-to-bar-description": "Les panneaux se verrouillent sur la barre et les bords de l'รฉcran, crรฉant un aspect homogรจne avec d'รฉlรฉgants coins inversรฉs.", + "panels-attached-to-bar-label": "Aligner les panneaux aux bords", + "panels-overlay-description": "Garantit que les panneaux et la barre restent visibles, mรชme au-dessus des applications en plein รฉcran.", + "panels-overlay-label": "Garder les panneaux et la barre en haut", + "scaling-description": "Modifie la taille de l'interface utilisateur gรฉnรฉrale, ร  l'exception de la barre.", + "scaling-label": "Mise ร  l'รฉchelle de l'interface", + "scrollbar-always-visible-description": "Garder les barres de dรฉfilement visibles chaque fois que le contenu est dรฉfilable, au lieu de les afficher uniquement au survol.", + "scrollbar-always-visible-label": "Toujours afficher les barres de dรฉfilement", + "settings-panel-header": "Panneau des Paramรจtres", + "settings-panel-mode-description": "Choisir la disposition des paramรจtres (peut nรฉcessiter une rรฉouverture).", + "settings-panel-mode-label": "Mode du panneau des paramรจtres", + "settings-panel-sidebar-card-style-description": "Enveloppe la barre latรฉrale des paramรจtres dans un arriรจre-plan rempli avec des coins arrondis.", + "settings-panel-sidebar-card-style-label": "Style de Carte de la Barre Latรฉrale", + "shadows-description": "Active les ombres portรฉes sous les barres et les panneaux.", + "shadows-direction-description": "Choisissez oรน l'ombre est projetรฉe.", + "shadows-direction-label": "Direction de lโ€™ombre", + "shadows-label": "Ombres portรฉes", + "title": "Interface utilisateur", + "tooltips-description": "Activer ou dรฉsactiver les infobulles dans toute l'interface.", + "tooltips-label": "Afficher les infobulles", + "translucent-widgets-description": "Rendre les boutons, onglets et autres widgets ร  l'intรฉrieur des panneaux semi-transparents.", + "translucent-widgets-label": "Widgets translucides" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "Alphabรฉtique", + "automation-change-mode-description": "Choisissez comment les fonds d'รฉcran sont sรฉlectionnรฉs lors du changement automatique.", + "automation-change-mode-label": "Mode de changement", + "automation-custom-interval-description": "Entrez l'heure au format HH:MM (ex : 01:30).", + "automation-custom-interval-label": "Intervalle personnalisรฉ", + "automation-interval-description": "Frรฉquence de changement automatique des fonds d'รฉcran.", + "automation-interval-label": "Intervalle du fond d'รฉcran", + "automation-random-wallpaper-description": "Planifiez des changements de fond d'รฉcran alรฉatoires ร  intervalles rรฉguliers.", + "automation-scheduled-change-description": "Changer automatiquement les fonds d'รฉcran ร  intervalles rรฉguliers.", + "automation-scheduled-change-label": "Changement programmรฉ", + "look-feel-edge-smoothness-description": "Applique un effet de flou progressif au bord des transitions.", + "look-feel-edge-smoothness-label": "Adoucir le bord de la transition", + "look-feel-fill-color-description": "Choisissez une couleur de remplissage qui peut apparaรฎtre derriรจre le fond d'รฉcran.", + "look-feel-fill-mode-description": "Sรฉlectionnez comment l'image doit รชtre mise ร  l'รฉchelle pour correspondre ร  la rรฉsolution de votre moniteur.", + "look-feel-fill-mode-label": "Mode de remplissage", + "look-feel-skip-startup-transition-description": "Ignorer l'animation du fond d'รฉcran au dรฉmarrage du shell.", + "look-feel-skip-startup-transition-label": "Ignorer la transition de dรฉmarrage", + "look-feel-title": "Apparence et ergonomie", + "look-feel-transition-duration-description": "Durรฉe des animations de transition en secondes.", + "look-feel-transition-duration-label": "Durรฉe de la transition", + "look-feel-transition-type-description": "Type d'animation lors du changement de fond d'รฉcran.", + "look-feel-transition-type-label": "Type de transition", + "settings-clear-cache-button": "Vider le cache", + "settings-clear-cache-description": "Effacer les fonds d'รฉcran redimensionnรฉs en cache pour libรฉrer de l'espace disque.", + "settings-clear-cache-label": "Cache des fonds d'รฉcran", + "settings-clear-cache-toast": "Cache de fond d'รฉcran effacรฉ", + "settings-desc": "Contrรดlez comment les fonds d'รฉcran sont gรฉrรฉs et affichรฉs.", + "settings-enable-management-description": "Gรฉrez les fonds d'รฉcran avec Noctalia. Dรฉcochez si vous prรฉfรฉrez utiliser une autre application.", + "settings-enable-management-label": "Activer la gestion des fonds d'รฉcran", + "settings-enable-overview-description": "Applique un fond d'รฉcran flou et attรฉnuรฉ ร  l'รฉcran de prรฉsentation.", + "settings-enable-overview-label": "Activer le fond d'รฉcran de l'aperรงu", + "settings-folder-description": "Chemin vers votre dossier principal de fonds d'รฉcran.", + "settings-folder-label": "Dossier des fonds d'รฉcran", + "settings-hide-wallpaper-filenames-tooltip-hide": "Masquer les noms de fichiers", + "settings-hide-wallpaper-filenames-tooltip-show": "Afficher les noms de fichiers", + "settings-monitor-specific-description": "Dรฉfinissez un dossier de fond d'รฉcran diffรฉrent pour chaque moniteur.", + "settings-monitor-specific-label": "Dossiers spรฉcifiques au moniteur", + "settings-monitor-specific-tooltip": "Dossier des fonds d'รฉcran du moniteur", + "settings-overview-blur-strength-description": "Applique la force du flou ร  l'aperรงu.", + "settings-overview-blur-strength-label": "Force du flou de l'aperรงu", + "settings-overview-tint-description": "Applique la force de la teinte ร  l'aperรงu.", + "settings-overview-tint-label": "Force de la teinte de l'aperรงu", + "settings-recursive-search-description": "Rechercher รฉgalement des fonds d'รฉcran dans les sous-dossiers du rรฉpertoire des fonds d'รฉcran.", + "settings-recursive-search-label": "Rechercher dans les sous-dossiers", + "settings-select-monitor-folder": "Sรฉlectionner le dossier des fonds d'รฉcran du moniteur", + "settings-selector-description": "Choisissez votre fond dโ€™รฉcran.", + "settings-selector-position-description": "Choisissez l'emplacement d'affichage du panneau de sรฉlection du fond d'รฉcran.", + "settings-show-hidden-files-tooltip-hide": "Masquer les fichiers cachรฉs", + "settings-show-hidden-files-tooltip-show": "Afficher les fichiers cachรฉs", + "settings-title": "Paramรจtres du fond d'รฉcran", + "settings-use-original-images-description": "Ignorer le redimensionnement des fonds d'รฉcran avant l'affichage. ร‰conomise de l'espace disque et rรฉduit l'utilisation du CPU lors des changements de fond d'รฉcran, mais peut utiliser plus de mรฉmoire pour les trรจs grandes images.", + "settings-use-original-images-label": "Utiliser les images originales", + "settings-view-mode-description": "Choisissez comment les fonds d'รฉcran sont affichรฉs depuis votre rรฉpertoire.", + "settings-view-mode-label": "Mode d'affichage", + "view-mode-browse": "Parcourir les rรฉpertoires", + "view-mode-cycle-tooltip": "Mode d'affichageย : {mode} (cliquez pour modifier)", + "view-mode-recursive": "Sous-rรฉpertoires aplatis", + "view-mode-single": "Rรฉpertoire racine" + } + }, + "placeholders": { + "add-new-keybind": "Ajouter un nouveau raccourci clavier", + "command-example": "echo \"Bonjour le monde\"", + "enter-command": "Entrez la commande ร  exรฉcuter (application ou script personnalisรฉ)", + "enter-ipc-identifier": "Entrez un identifiant unique pour les commandes IPC", + "enter-path": "Entrez le chemin...", + "enter-text-to-collapse": "ex : 'rien nโ€™est en lecture'. Utilisez /regex/ pour les modรจles.", + "enter-tooltip": "Entrer l'infobulle", + "enter-width-pixels": "Entrez la largeur en pixels", + "keybind-recording": "Enregistrement de la combinaison de touches...", + "search": "Rechercher...", + "search-icons": "ex : noctalia, niri, batterie, nuage", + "search-launcher": "Rechercher des entrรฉes... ou utilisez > pour les commandes", + "search-wallhaven": "Rechercher sur Wallhaven...", + "search-wallpapers": "Tapez pour filtrer les fonds d'รฉcran..." + }, + "positions": { + "bottom": "Bas", + "bottom-center": "En bas au centre", + "bottom-left": "En bas ร  gauche", + "bottom-right": "En bas ร  droite", + "center": "Centre", + "center-left": "Gauche centre", + "center-right": "Droite centre", + "close-to-bar": "Prรจs du bouton de la barre", + "follow-bar": "Suivre la barre", + "left": "Gauche", + "right": "Droite", + "top": "Haut", + "top-center": "En haut au centre", + "top-left": "En haut ร  gauche", + "top-right": "En haut ร  droite" + }, + "session-menu": { + "action-in-seconds": "{action} dans {seconds} secondes...", + "cancel-timer": "Annuler le minuteur", + "title": "Menu de session" + }, + "setup": { + "all-done": "Terminรฉ !", + "appearance": { + "subheader": "Choisissez le mode sombre et les sources de couleur (fond d'รฉcran ou prรฉdรฉfinies)." + }, + "customize": { + "header": "Personnaliser votre expรฉrience", + "subheader": "Ajustez la position de la barre, la densitรฉ, l'รฉchelle et plus encore." + }, + "skip-setup": "Ignorer la configuration", + "telemetry-wizard-done": "Bien reรงu !", + "telemetry-wizard-note": "Vous avez le contrรดle โ€” activez ou dรฉsactivez cette option ร  tout moment dans les paramรจtres", + "telemetry-wizard-subtitle": "Nous avons ajoutรฉ des analyses anonymes pour aider ร  amรฉliorer Noctalia", + "telemetry-wizard-title": "Mise ร  jour de la confidentialitรฉ", + "wallpaper": { + "choose-dir": "Choisissez un rรฉpertoire contenant vos fonds d'รฉcran", + "dir-browse": "Parcourir le dossier", + "dir-description": "Choisissez le dossier contenant vos fonds d'รฉcran.", + "dir-label": "Rรฉpertoire des fonds d'รฉcran", + "dir-select-title": "Sรฉlectionner le dossier des fonds d'รฉcran", + "header": "Choisissez votre fond d'รฉcran", + "no-dir": "Aucun rรฉpertoire de fonds d'รฉcran sรฉlectionnรฉ", + "no-valid": "Aucun fichier image valide trouvรฉ dans : {dir}", + "none-in-dir": "Aucun fond d'รฉcran trouvรฉ dans le rรฉpertoire", + "preview-error": "ร‰chec du chargement de l'image", + "select-prompt": "Sรฉlectionnez un fond ci-dessous", + "subheader": "Dรฉfinissez l'ambiance avec un joli fond." + }, + "welcome": "Bienvenue", + "welcome-note": "Quelques rรฉglages de base pour dรฉmarrer โ€” toutes les options sont dans paramรจtres", + "welcome-subtitle": "Personnalisons votre bureau pour qu'il soit unique", + "welcome-title": "Bienvenue dans Noctalia !" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "Aucun lecteur multimรฉdia dรฉtectรฉ", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "Application inconnue", + "uptime": "Activitรฉ : {uptime}", + "user-requested": "Demandรฉ par l'utilisateur", + "welcome-back": "Bon retour,", + "widget-settings-title": "Paramรจtres de {widget}" + }, + "system-monitor": { + "core-usage": "Utilisation du cล“ur {id}", + "cpu-temp": "Tempรฉrature du processeur", + "cpu-usage": "Utilisation du processeur", + "disk": "Disque", + "download-speed": "Vitesse de tรฉlรฉchargement", + "gpu-temp": "Tempรฉrature du GPU", + "load-average": "Charge moyenne", + "title": "Moniteur systรจme", + "upload-speed": "Vitesse dโ€™envoi" + }, + "toast": { + "airplane-mode": { + "description": "Dรฉsactiver toutes les communications sans fil.", + "title": "Mode avion" + }, + "battery": { + "critical": "Batterie critique", + "critical-desc": "Batterie ร  {percent}% โ€” veuillez connecter le chargeur immรฉdiatement", + "low": "Batterie faible", + "low-desc": "Batterie ร  {percent}% โ€” veuillez connecter le chargeur" + }, + "bluetooth": { + "address-copied": "Adresse copiรฉe dans le presse-papiers", + "auto-connect-disabled": "Connexion automatique dรฉsactivรฉe", + "auto-connect-enabled": "Connexion automatique activรฉe", + "auto-connecting": "Connexion ร  {count} appareil(s)...", + "confirm-code": "Confirmer le code {value} sur l'autre appareil.", + "connect-failed": "ร‰chec de la connexion ร  lโ€™appareil", + "disconnect-failed": "ร‰chec de la dรฉconnexion de lโ€™appareil", + "discoverable-change-failed": "ร‰chec du changement dโ€™รฉtat dรฉtectable", + "discoverable-disabled": "Dรฉtectable dรฉsactivรฉ", + "discoverable-enabled": "Dรฉtectable activรฉ", + "display-code": "Entrez le code {value} sur l'autre appareil.", + "forget-failed": "ร‰chec de lโ€™oubli de lโ€™appareil", + "pair-failed": "ร‰chec de lโ€™appairage de lโ€™appareil", + "passkey-required": "Clรฉ d'identification requise par l'appareil.", + "pincode-required": "Code PIN requis par l'appareil.", + "state-change-failed": "ร‰chec du changement dโ€™รฉtat du Bluetooth" + }, + "clipboard": { + "long-text": "Texte long", + "unavailable": "Historique du presse-papiers indisponible", + "unavailable-desc": "L'application 'cliphist' n'est pas installรฉe. Veuillez l'installer pour utiliser les fonctionnalitรฉs d'historique du presse-papiers" + }, + "custom-command-failed": { + "description": "La commande a รฉchouรฉ : {command}\\nCode de sortie : {code}", + "title": "La commande personnalisรฉe a รฉchouรฉ" + }, + "do-not-disturb": { + "disabled": "'Ne pas dรฉranger' dรฉsactivรฉ", + "disabled-desc": "Affichage de toutes les notifications", + "enabled": "'Ne pas dรฉranger' activรฉ", + "enabled-desc": "Vous trouverez ces notifications dans votre historique" + }, + "donation-opened": "Page de donation ouverte dans votre navigateur", + "internet-limited": "Connectรฉ sans Internet", + "keyboard-layout": { + "changed": "Disposition du clavier changรฉe vers {layout}", + "title": "Clavier" + }, + "missing-control-center": { + "description": "Le widget du centre de contrรดle a รฉtรฉ retirรฉ de la barre. Pour y accรฉder ร  nouveau depuis la barre, veuillez rรฉโ€‘ajouter le widget. Vous pouvez aussi l'ouvrir en cliquant avec le bouton droit sur la barre", + "label": "Dernier widget du centre de contrรดle supprimรฉ" + }, + "night-light": { + "forced": "Activation forcรฉe", + "normal": "Mode normal", + "not-installed": "wlsunset non installรฉ" + }, + "noctalia-performance": { + "disabled": "Mode performance dรฉsactivรฉ", + "enabled": "Mode performance activรฉ", + "label": "Noctalia Performance" + }, + "power-profile": { + "changed": "Profil d'alimentation changรฉ", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "Une erreur s'est produite lors du traitement des modรจles", + "title": "Le traitement du modรจle a รฉchouรฉ" + }, + "vpn": { + "connected": "Connectรฉ ร  '{name}'", + "disconnected": "Dรฉconnectรฉ de '{name}'" + }, + "wallpaper-colors": { + "disabled": "Couleurs du fond d'รฉcran dรฉsactivรฉes", + "enabled": "Couleurs du fond d'รฉcran activรฉes", + "label": "Gรฉnรฉration de couleurs pour fonds d'รฉcran" + }, + "wifi": { + "connected": "Connectรฉ ร  '{ssid}'", + "connection-failed": "ร‰chec de la connexion", + "connection-timeout": "Dรฉlai de connexion dรฉpassรฉ", + "disconnected": "Dรฉconnectรฉ de '{ssid}'", + "incorrect-password": "Mot de passe incorrect", + "network-not-found": "Rรฉseau introuvable" + } + }, + "tooltips": { + "add-widget": "Ajouter un widget", + "auto-connect": "Activer/dรฉsactiver la connexion automatique pour cet appareil", + "bluetooth-auto-connect-off": "Connexion automatique dรฉsactivรฉe", + "bluetooth-auto-connect-on": "Connexion automatique activรฉe", + "bluetooth-devices": "Appareils Bluetooth", + "brightness-at": "Luminositรฉ : {brightness}%", + "click-to-start-recording": "Enregistreur d'รฉcran (dรฉmarrer l'enregistrement)", + "click-to-stop-recording": "Enregistreur d'รฉcran (arrรชter l'enregistrement)", + "collapse": "Rรฉduire la barre latรฉrale", + "copy-address": "Copier l'adresse", + "delete-notification": "Supprimer la notification", + "dismiss-notification": "Ignorer la notification", + "do-not-disturb-enabled": "Ne pas dรฉranger", + "expand": "Dรฉvelopper la barre latรฉrale", + "forget-network": "Oublier le rรฉseau", + "grid-view": "Vue en grille", + "hidden-files-hide": "Fichiers cachรฉs", + "hide-unnamed-devices": "Masquer les appareils sans nom", + "home": "Rรฉpertoire home", + "input-muted": "Activer/dรฉsactiver la sourdine de l'entrรฉe", + "keep-awake": "Rester รฉveillรฉ", + "keyboard-layout": "Disposition du clavier {layout}", + "list-view": "Vue en liste", + "manage-vpn": "Connexions VPN", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "Nombre maximal de widgets atteint", + "microphone-volume-at": "Volume du microphone : {volume}%", + "move-to-section": "Dรฉplacer : {section}", + "mute": "Muet / Couper le son", + "next-media": "Piste suivante", + "next-month": "Mois suivant", + "night-light-not-installed": "ร‰clairage nocturne (non disponible)", + "noctalia-performance-enabled": "Mode performance Noctalia", + "open-annotation-tool": "Ouvrir avec l'outil d'annotation", + "open-control-center": "Centre de contrรดle", + "open-notification-history-enable-dnd": "Historique des notifications", + "open-settings": "Paramรจtres", + "open-tray-dropdown": "Barre d'รฉtat systรจme", + "output-muted": "Activer/dรฉsactiver la sourdine de la sortie", + "power-profile": "{profile} profil d'alimentation", + "previous-media": "Piste prรฉcรฉdente", + "previous-month": "Mois prรฉcรฉdent", + "refresh-devices": "Actualiser les appareils", + "refresh-wallhaven": "Actualiser les rรฉsultats de Wallhaven", + "refresh-wallpaper-list": "Actualiser la liste des fonds d'รฉcran", + "remove-widget": "Supprimer le widget", + "search-close": "Fermer la recherche", + "session-menu": "Menu de session", + "show-all-devices": "Afficher tous les appareils", + "switch-to-dark-mode": "Mode sombre", + "switch-to-light-mode": "Mode clair", + "unmute": "Rรฉactiver le micro", + "up": "Rรฉpertoire parent", + "volume-at": "Volume de sortie : {volume}%", + "wallpaper-selector": "Sรฉlecteur de fond d'รฉcran" + }, + "wallpaper": { + "browse": { + "empty-directory": "Ce rรฉpertoire est vide.", + "go-root": "Aller ร  la racine du fond d'รฉcran", + "go-up": "Aller au dossier parent", + "go-up-hint": "Utilisez le bouton de retour pour naviguer vers le haut." + }, + "configure-directory": "Configurez votre rรฉpertoire de fonds d'รฉcran avec des images.", + "fill-modes": { + "crop": "Recadrer (Remplir)", + "fit": "Ajuster (Contenir)", + "repeat": "Rรฉpรฉter (Mosaรฏque)", + "stretch": "ร‰tirer" + }, + "no-match": "Aucun rรฉsultat trouvรฉ.", + "no-wallpaper": "Aucun fond d'รฉcran trouvรฉ.", + "panel": { + "apikey-help": "Une clรฉ API est requise pour accรฉder au contenu NSFW.", + "apikey-label": "Clรฉ API", + "apikey-managed-by-env": "Gรฉrรฉ via la variable d'environnement NOCTALIA_WALLHAVEN_API_KEY.", + "apikey-placeholder": "Entrez votre clรฉ API Wallhaven", + "appearance-dark-tab": "Sombre", + "appearance-light-tab": "Clair", + "categories-anime": "Anime", + "categories-label": "Catรฉgories", + "categories-people": "Personnes", + "color-extraction-disabled": "Utiliser l'extraction des couleurs du fond d'รฉcran", + "color-extraction-enabled": "Utiliser les schรฉmas de couleurs prรฉdรฉfinis", + "header-devices-apply-all-tooltip": "Choisir le fond d'รฉcran par moniteur", + "header-devices-per-monitor-tooltip": "Appliquer ร  tous les moniteurs", + "header-link-light-dark-tooltip": "Garder le fond d'รฉcran lors du changement de thรจme", + "header-separate-light-dark-tooltip": "Changer automatiquement le fond d'รฉcran avec le thรจme", + "header-sun-linked-tooltip": "Sรฉparer les fonds d'รฉcran clairs et sombres", + "header-sun-separate-tooltip": "Mรชme fond d'รฉcran pour clair et sombre", + "order-asc": "Croissant", + "order-desc": "Dรฉcroissant", + "order-label": "Ordre", + "purity-label": "Filtre de contenu", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "Sketchy", + "ratios-all-portrait": "Portrait", + "ratios-all-wide": "Paysage", + "ratios-any": "Tous", + "ratios-label": "Format d'image", + "resolution-atleast": "Au moins", + "resolution-exact": "Exact", + "resolution-label": "Rรฉsolution", + "resolution-mode-label": "Mode", + "solid-color-tooltip": "Fond uni", + "sort-date-asc": "Trier du plus ancien au plus rรฉcent", + "sort-date-desc": "Trier du plus rรฉcent au plus ancien", + "sort-name-asc": "Trier par nom (A-Z)", + "sort-name-desc": "Trier par nom (Z-A)", + "sort-random": "Trier alรฉatoirement", + "sorting-date-added": "Date d'ajout", + "sorting-favorites": "Favoris", + "sorting-label": "Trier par", + "sorting-relevance": "Pertinence", + "sorting-toplist": "Top liste", + "sorting-views": "Vues", + "source-label": "Source", + "source-wallhaven": "Wallhaven", + "title": "Sรฉlecteur de fond d'รฉcran", + "wallhaven-settings-title": "Paramรจtres Wallhaven" + }, + "transitions": { + "disc": "Disque", + "fade": "Fondu", + "honeycomb": "Nid d'abeille", + "pixelate": "Pixรฉliser", + "stripes": "Rayures", + "wipe": "Balayage" + }, + "try-different-search": "Essayez une autre requรชte de recherche.", + "wallhaven": { + "loading": "Chargement des fonds d'รฉcran...", + "no-results": "Aucun fond d'รฉcran trouvรฉ. Essayez une autre requรชte de recherche.", + "page": "{current} sur {total}", + "page-prefix": "Page", + "page-suffix": "de {total}" + } + }, + "weather": { + "clear-sky": "Ciel dรฉgagรฉ", + "drizzle": "Bruine", + "fog": "Brouillard", + "mainly-clear": "Gรฉnรฉralement dรฉgagรฉ", + "overcast": "Couvert", + "partly-cloudy": "Partiellement nuageux", + "rain-showers": "Averses de pluie", + "snow": "Neige", + "thunderstorm": "Orage" + }, + "widgets": { + "color-picker": { + "palette-description": "Choisissez parmi une large gamme de couleurs prรฉdรฉfinies.", + "palette-label": "Palette", + "palette-theme-colors": "Accรจs rapide ร  la palette de couleurs de votre thรจme.", + "title": "Sรฉlecteur de couleurs" + }, + "datetime-tokens": { + "ampm-lowercase": "am/pm en minuscules", + "ampm-uppercase": "AM/PM en majuscules", + "common-12hour-time-minutes": "Heure sur 12 heures avec minutes", + "common-24hour-time-minutes": "Heure sur 24 heures avec minutes", + "common-24hour-time-seconds": "Heure sur 24 heures avec secondes", + "common-european-date": "Format de date europรฉen", + "common-iso-date": "Format de date ISO", + "common-us-date": "Format de date amรฉricain", + "common-weekday-date": "Jour de la semaine avec date", + "common-weekday-month-day": "Jour de la semaine, mois et jour", + "day-abbreviated": "Nom du jour abrรฉgรฉ", + "day-full": "Nom complet du jour", + "day-leading-zero": "Jour avec zรฉro initial (01-31)", + "day-no-leading-zero": "Jour sans zรฉro initial (1-31)", + "hour-leading-zero": "Heure avec zรฉro initial (00-23) โ€” format 24 heures", + "hour-no-leading-zero": "Heure sans zรฉro initial (0-23) โ€” format 24 heures", + "minute-leading-zero": "Minute avec zรฉro initial (00-59)", + "minute-no-leading-zero": "Minute sans zรฉro initial (0-59)", + "month-abbreviated": "Nom du mois abrรฉgรฉ", + "month-full": "Nom complet du mois", + "month-number-leading-zero": "Mois en chiffre avec zรฉro initial (01-12)", + "month-number-no-zero": "Mois en chiffre sans zรฉro initial (1-12)", + "second-leading-zero": "Seconde avec zรฉro initial (00-59)", + "second-no-leading-zero": "Seconde sans zรฉro initial (0-59)", + "timezone-abbreviation": "Abrรฉviation du fuseau horaire", + "year-four-digit": "Annรฉe sur quatre chiffres", + "year-two-digit": "Annรฉe sur deux chiffres (00-99)" + }, + "file-picker": { + "item": "รฉlรฉment", + "items": "รฉlรฉments", + "search-placeholder": "Rechercher des fichiers et des dossiers...", + "select-current": "Sรฉlectionner Actuel", + "select-file": "Sรฉlectionner un fichier", + "select-folder": "Sรฉlectionner un dossier", + "selected": "Sรฉlectionnรฉ:", + "title": "Sรฉlecteur de fichiers" + }, + "file-picker-title": "Sรฉlectionner un fichier", + "icon-picker": { + "title": "Sรฉlecteur d'icรดnes" + }, + "tooltip-placeholder": "Espace rรฉservรฉ" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "Identitรฉ Anonyme", + "ca-cert": "Certificat CA", + "eap-method": "Mรฉthode EAP", + "password": "Mot de passe utilisateur", + "phase2-auth": "Authentification Phase 2", + "username": "Identitรฉ / Nom d'utilisateur" + }, + "panel": { + "action-required": "Action requise", + "add-hidden-network": "Ajouter un rรฉseau masquรฉ", + "add-network": "Ajouter un rรฉseau", + "available-interfaces": "Interfaces disponibles", + "available-networks": "Rรฉseaux disponibles", + "disabled": "Le Wi-Fi est dรฉsactivรฉ", + "disconnecting": "Dรฉconnexion en cours...", + "dns": "DNS", + "enable-message": "Activez le Wi-Fi pour voir les rรฉseaux disponibles.", + "enter-password": "Entrez le mot de passe...", + "forget": "Oublier", + "forget-network": "Oublier ce rรฉseau ?", + "forgetting": "Oubli en cours...", + "hidden-network": "Rรฉseau masquรฉ", + "interface": "Interface rรฉseau", + "internet-connected": "Connectรฉ ร  Internet", + "internet-limited": "Pas d'internet", + "internet-status": "ร‰tat de la connexion Internet", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "Rรฉseaux connus", + "link-speed": "Vitesse de liaison", + "network-name-ssid": "Nom du rรฉseau (SSID)", + "no-ethernet-devices": "Aucun pรฉriphรฉrique Ethernet dรฉtectรฉ", + "no-networks": "Aucun rรฉseau trouvรฉ", + "saved": "Enregistrรฉ", + "scan-again": "Analyser ร  nouveau", + "searching": "Recherche de rรฉseaux ร  proximitรฉ en cours...", + "security-open": "Ouvrir", + "security-wep": "WEP", + "security-wpa": "WPA/WPA2/WPA3 Personnel", + "security-wpa-ent": "WPA Enterprise", + "security-wpa2-ent": "WPA2 Enterprise", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 Enterprise", + "show-password": "Afficher le mot de passe", + "title": "Wi-Fi" + }, + "signal": { + "excellent": "Excellent", + "fair": "Moyen", + "good": "Bien", + "poor": "Mauvais", + "weak": "Faible" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/hu.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/hu.json new file mode 100644 index 0000000..6d61edb --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/hu.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "Elล‘zmรฉnyek tรถrlรฉse", + "close-app": "{app} bezรกrรกsa", + "connect-vpn": "Csatlakozรกs ehhez: {name}", + "cycle-visualizer": "Vรกltรกs a vizualizรกciรณk kรถzรถtt", + "disable-bluetooth": "Bluetooth letiltรกsa", + "disable-dnd": "Ne zavarjanak letiltรกsa", + "disable-wifi": "Wi-Fi letiltรกsa", + "disconnect-vpn": "Kapcsolat bontรกsa ezzel: {name}", + "dock-settings": "Dokk beรกllรญtรกsok", + "enable-bluetooth": "Bluetooth engedรฉlyezรฉse", + "enable-dnd": "Ne zavarjanak engedรฉlyezรฉse", + "enable-wifi": "Wi-Fi engedรฉlyezรฉse", + "launcher-settings": "Indรญtรณ beรกllรญtรกsok", + "lower-to-bottom": "Legaljรกra sรผllyesztรฉs", + "open-calendar": "Naptรกr megnyitรกsa", + "open-display-settings": "Kijelzล‘beรกllรญtรกsok", + "open-launcher": "Indรญtรณ megnyitรกsa", + "open-settings": "Beรกllรญtรกsok megnyitรกsa", + "raise-to-top": "Felรผlre emelรฉs", + "random-wallpaper": "Vรฉletlenszerลฑ hรกttรฉrkรฉp", + "run-custom-command": "Egyรฉni parancs futtatรกsa", + "show-active-only": "Csak az aktรญvakat jelenรญtse meg", + "show-all": "ร–sszes megjelenรญtรฉse", + "toggle-mute": "Nรฉmรญtรกs be/ki", + "widget-settings": "Widget beรกllรญtรกsok" + }, + "authentication": { + "error": "Hitelesรญtรฉsi hiba", + "failed": "A hitelesรญtรฉs sikertelen" + }, + "bar": { + "active-window": { + "colorize-icons-description": "Tรฉmaszรญnek alkalmazรกsa az aktรญv ablak ikonra.", + "hide-mode-description": "A widget viselkedรฉse, ha nincs aktรญv ablak.", + "scrolling-mode-description": "Mikor engedรฉlyezett a szรถveggรถrgetรฉs a hosszรบ ablakcรญmek esetรฉn.", + "show-app-icon-description": "Az alkalmazรกs ikonjรกnak megjelenรญtรฉse az ablakcรญm mellett.", + "show-app-icon-label": "Alkalmazรกsikon megjelenรญtรฉse", + "show-app-text-description": "Ablakcรญm megjelenรญtรฉse.", + "show-app-text-label": "Ablakcรญm megjelenรญtรฉse" + }, + "audio-visualizer": { + "color-name-description": "A vizualizรกtor szรญne.", + "color-name-label": "Kitรถltรฉsi szรญn", + "height-description": "Egyรฉni komponensszรฉlessรฉg.", + "hide-when-idle-description": "Ha engedรฉlyezve van, a vizualizรกtor el van rejtve, kivรฉve, ha egy lejรกtszรณ aktรญvan jรกtszik.", + "hide-when-idle-label": "Elrejtรฉs, ha nincs mรฉdia lejรกtszva", + "width-description": "Egyรฉni komponens szรฉlessรฉge." + }, + "battery": { + "device-default": "Alapรฉrtelmezett (Megjelenรญtล‘ eszkรถz)", + "device-description": "Melyik akkumulรกtor eszkรถzt jelenรญtse meg.", + "device-label": "Akkumulรกtor eszkรถz", + "display-mode-description": "Hogyan jelenjen meg az akkumulรกtor a sรกvban.", + "display-mode-graphic": "Grafikus akkumulรกtor", + "display-mode-graphic-clean": "Grafikus akkumulรกtor (szรกzalรฉk nรฉlkรผl)", + "display-mode-icon-always": "Ikon - Mindig jelenรญtse meg a szรกzalรฉkot", + "display-mode-icon-hover": "Ikon - Rรกmutatรกsra megjelenik", + "display-mode-icon-only": "Csak ikon", + "hide-if-idle-description": "Widget elrejtรฉse, ha az akkumulรกtor nincs tรถltรฉs alatt vagy lemerรผlล‘ben.", + "hide-if-idle-label": "Elrejtรฉs tรฉtlensรฉg esetรฉn", + "hide-if-not-detected-description": "Widget elrejtรฉse, ha a rendszer nem รฉszlel akkumulรกtort.", + "hide-if-not-detected-label": "Elrejtรฉs, ha nincs รฉszlelve", + "low-battery-threshold-label": "Alacsony akkumulรกtor tรถltรถttsรฉgi figyelmeztetรฉsi kรผszรถb", + "show-noctalia-performance-description": "A Noctalia teljesรญtmรฉnymรณd kapcsolรณ megjelenรญtรฉse az akkumulรกtor panelen belรผl.
Letiltja az รกrnyรฉkokat รฉs animรกciรณkat a Noctaliaban az erล‘forrรกsfelhasznรกlรกs csรถkkentรฉse รฉrdekรฉben.", + "show-noctalia-performance-label": "Noctalia Teljesรญtmรฉny kapcsolรณ megjelenรญtรฉse", + "show-power-profile-description": "Az energiaprofil kivรกlasztรกsรกnak megjelenรญtรฉse az akkumulรกtor panelen belรผl.", + "show-power-profile-label": "Energiaprofil vezรฉrlล‘k" + }, + "brightness": { + "apply-all-description": "A fรฉnyerล‘ mรณdosรญtรกsa minden monitoron a gรถrgล‘ hasznรกlatรกval.", + "apply-all-label": "Gรถrgetรฉsi vรกltozรกsok alkalmazรกsa minden monitorra" + }, + "clock": { + "clock-display-description": "Testre szabhatod az รณra kijelzล‘jรฉt a lenti listรกban szereplล‘ tokenek hozzรกadรกsรกval. A 12 รณrรกs formรกtum hasznรกlatรกhoz tartalmaznia kell az โ€žAPโ€ tokent.", + "clock-display-label": "ร“ra kijelzล‘", + "custom-font-description": "Egyรฉni betลฑtรญpus az รณra kijelzล‘jรฉhez.", + "custom-font-label": "Egyรฉni betลฑtรญpus", + "custom-font-placeholder": "Egyรฉni betลฑtรญpus kivรกlasztรกsaโ€ฆ", + "custom-font-search-placeholder": "Betลฑtรญpus keresรฉseโ€ฆ", + "horizontal-bar-description": "Tipp: Hasznรกld a \\n karaktert sortรถrรฉs lรฉtrehozรกsรกhoz.", + "horizontal-bar-label": "Vรญzszintes sรกv", + "preview": "Elล‘nรฉzet", + "tooltip-format-description": "Formรกtumsztring az รณra fรถlรฉ hรบzva megjelenล‘ eszkรถztipphez. Hagyd รผresen az alapรฉrtelmezett eszkรถztipp hasznรกlatรกhoz.", + "tooltip-format-label": "Eszkรถztipp formรกtum", + "use-custom-font-description": "Felรผlbรญrรกlja az alapรฉrtelmezett betลฑtรญpus kivรกlasztรกsรกt egy egyรฉni betลฑtรญpussal az รณrรกhoz.", + "use-custom-font-label": "Egyรฉni betลฑtรญpus hasznรกlata", + "use-monospaced-font-description": "Ha engedรฉlyezve van, az รณra a monospace betลฑtรญpust fogja hasznรกlni.", + "use-monospaced-font-label": "Monospace betลฑtรญpus hasznรกlata", + "vertical-bar-description": "Szรณkรถz az egyes rรฉszek รบj sorba valรณ elkรผlรถnรญtรฉsรฉhez.", + "vertical-bar-label": "Fรผggล‘leges sรกv" + }, + "control-center": { + "browse-file": "Fรกjl tallรณzรกsa", + "browse-library": "Kรถnyvtรกr tallรณzรกsa", + "color-selection-description": "Tรฉmaszรญnek alkalmazรกsa az ikonokra.", + "enable-colorization-description": "Az ikon szรญnezรฉsรฉnek engedรฉlyezรฉse tรฉmaszรญnek alkalmazรกsรกval.", + "icon-description": "Ikon kivรกlasztรกsa a kรถnyvtรกrbรณl vagy egy egyรฉni fรกjlbรณl.", + "select-custom-icon": "Egyรฉni ikon kivรกlasztรกsa", + "use-distro-logo-description": "Disztribรบciรณ logรณ hasznรกlata egy egyรฉni ikon helyett.", + "use-distro-logo-label": "Disztribรบciรณ logรณ hasznรกlata ikon helyett" + }, + "custom-button": { + "collapse-condition-description": "Ha a kimeneti szรถveg megegyezik ezzel az รฉrtรฉkkel, a gomb รถsszecsukรณdik.", + "collapse-condition-label": "ร–sszecsukรกsi feltรฉtel", + "color-selection-description": "Tรฉmรกk szรญneinek alkalmazรกsa az ikonra รฉs a szรถvegre.", + "default-tooltip": "Egyรฉni gomb, konfigurรกlรกs a beรกllรญtรกsokban", + "display-command-output-description": "Egy parancs, amelyet rendszeres idล‘kรถzรถnkรฉnt futtatni szeretnรฉl. A kimenetรฉnek elsล‘ sora szรถvegkรฉnt jelenik meg.", + "display-command-output-label": "Parancs kimenetรฉnek megjelenรญtรฉse", + "display-command-output-stream-description": "Egy parancs, amelyet folyamatosan futtatni szeretnรฉl.", + "dynamic-text": "Dinamikus szรถveg", + "enable-colorization-description": "Szรญnezรฉs engedรฉlyezรฉse az egyรฉni gomb ikonjรกhoz รฉs szรถvegรฉhez, tรฉmaszรญnek alkalmazรกsรกval.", + "enable-colorization-label": "Szรญnezรฉs engedรฉlyezรฉse", + "general-tooltip-text-description": "Egyรฉni szรถveg, ami megjelenik a gomb eszkรถztippjรฉben.", + "general-tooltip-text-label": "Egyรฉni eszkรถztipp szรถveg", + "hide-mode-always-expanded": "Mindig kibontva", + "hide-mode-description": "A widget lรกthatรณsรกga, ha a parancsnak nincs kimenete.", + "hide-mode-expand-with-output": "Kibontรกs, ha van kimenet", + "hide-mode-label": "Elrejtรฉsi mรณd", + "hide-mode-max-transparent": "Maximรกlisan kibontva, de รกtlรกtszรณ", + "icon-description": "Ikon kivรกlasztรกsa a kรถnyvtรกrbรณl.", + "ipc-identifier-description": "Egyedi azonosรญtรณ az IPC parancsokhoz. Hasznรกld ezt az azonosรญtรณt a 'qs -c noctalia-shell ipc call cb [akciรณ] [azonosรญtรณ]' paranccsal, hogy vezรฉreld ezt a gombot IPC-n keresztรผl.", + "ipc-identifier-label": "IPC-azonosรญtรณ", + "left-click-description": "Vรฉgrehajtandรณ parancs a gomb bal egรฉrgombbal tรถrtรฉnล‘ kattintรกsakor.", + "left-click-label": "Bal kattintรกs", + "left-click-update-text": "Megjelenรญtett szรถveg frissรญtรฉse bal kattintรกsra", + "max-text-length-horizontal-description": "Maximรกlis karakterszรกm a vรญzszintes sรกvban (0 a szรถveg elrejtรฉsรฉhez).", + "max-text-length-horizontal-label": "Maximรกlis szรถveghossz (vรญzszintes)", + "max-text-length-vertical-description": "Maximรกlis karakterszรกm a fรผggล‘leges sรกvban (0 a szรถveg elrejtรฉsรฉhez).", + "max-text-length-vertical-label": "Maximรกlis szรถveghossz (fรผggล‘leges)", + "middle-click-description": "Vรฉgrehajtandรณ parancs a gomb kรถzรฉpsล‘ egรฉrgombbal tรถrtรฉnล‘ kattintรกsakor.", + "middle-click-label": "Kรถzรฉpsล‘ kattintรกs", + "middle-click-update-text": "Megjelenรญtett szรถveg frissรญtรฉse kรถzรฉpsล‘ kattintรกsra", + "parse-json-description": "A parancs kimenetรฉnek JSON objektumkรฉnt tรถrtรฉnล‘ รฉrtelmezรฉse a szรถveg รฉs az ikon dinamikus beรกllรญtรกsรกhoz.", + "parse-json-label": "Kimenet รฉrtelmezรฉse JSON-kรฉnt", + "refresh-interval-description": "Intervallum milliszekundumban.", + "refresh-interval-label": "Frissรญtรฉsi intervallum", + "right-click-description": "Vรฉgrehajtandรณ parancs a gomb jobb egรฉrgombbal tรถrtรฉnล‘ kattintรกsakor.", + "right-click-label": "Jobb kattintรกs", + "right-click-update-text": "Megjelenรญtett szรถveg frissรญtรฉse jobb kattintรกsra", + "show-exec-tooltip-description": "Eszkรถztippek megjelenรญtรฉse parancs rรฉszleteivel (bal/jobb/kรถzรฉpsล‘ kattintรกs, gรถrgล‘).", + "show-exec-tooltip-label": "Parancsleรญrรกsok megjelenรญtรฉse", + "show-icon-description": "A widget ikonjรกnak lรกthatรณsรกgรกt kapcsolja.", + "show-icon-label": "Ikon megjelenรญtรฉse", + "show-text-tooltip-description": "Eszkรถztippek megjelenรญtรฉse a szรถveges parancs kimenetรฉvel.", + "show-text-tooltip-label": "Dinamikus szรถveges eszkรถztippek megjelenรญtรฉse", + "text-stream-description": "A parancsbรณl streamelt sorok szรถvegkรฉnt jelennek meg a gombon.", + "text-stream-label": "Stream", + "wheel-description": "Vรฉgrehajtandรณ parancs a gรถrgล‘ hasznรกlatakor.
Hasznรกld a $delta vรกltozรณt a parancsban a gรถrgล‘ elmozdulรกsรกhoz.", + "wheel-down": "Gรถrgล‘ le", + "wheel-down-description": "Vรฉgrehajtandรณ parancs, amikor a gรถrgล‘t lefelรฉ gรถrgetik.", + "wheel-down-label": "Gรถrgล‘ le parancs", + "wheel-label": "Egรฉrgรถrgล‘", + "wheel-mode-separate-description": "Kรผlรถn parancsok engedรฉlyezรฉse a gรถrgล‘ fel รฉs le mozgรกsรกhoz.", + "wheel-mode-separate-label": "Kรผlรถn gรถrgล‘ parancsok", + "wheel-up": "Gรถrgล‘ fel", + "wheel-up-description": "Vรฉgrehajtandรณ parancs, amikor a gรถrgล‘t felfelรฉ gรถrgetik.", + "wheel-up-label": "Gรถrgล‘ fel parancs", + "wheel-update-text": "Megjelenรญtett szรถveg frissรญtรฉse gรถrgetรฉsre" + }, + "keyboard-layout": { + "show-icon-description": "A billentyลฑzetkiosztรกs ikon megjelenรญtรฉse." + }, + "lock-keys": { + "hide-when-off-description": "Az indikรกtor elrejtรฉse, ha a billentyลฑ nem aktรญv.", + "hide-when-off-label": "Elrejtรฉs kikapcsolt รกllapotban", + "show-caps-lock-description": "Caps Lock รกllapot megjelenรญtรฉse.", + "show-caps-lock-label": "Caps Lock", + "show-num-lock-description": "Num Lock รกllapot megjelenรญtรฉse.", + "show-num-lock-label": "Num Lock", + "show-scroll-lock-description": "Scroll Lock รกllapot megjelenรญtรฉse.", + "show-scroll-lock-label": "Scroll Lock" + }, + "media-mini": { + "compact-mode-description": "Helytakarรฉkos elrendezรฉs engedรฉlyezรฉse a mรฉdialejรกtszรณ panelhez.", + "compact-mode-label": "Kompakt mรณd", + "hide-mode-description": "A widget viselkedรฉsรฉt, ha nincs mรฉdia lejรกtszva.", + "max-width-description": "Beรกllรญtja a widget maximรกlis vรญzszintes mรฉretรฉt. A widget zsugorodik, hogy illeszkedjen a rรถvidebb tartalomhoz.", + "no-active-player": "Nincs aktรญv lejรกtszรณ", + "panel-section-description": "A mรฉdialejรกtszรณ paneljรฉnek megjelenรฉsรฉnek รฉs viselkedรฉsรฉnek konfigurรกlรกsa.", + "panel-section-label": "Mรฉdialejรกtszรณ panel", + "scrolling-mode-description": "Mikor engedรฉlyezett a szรถveggรถrgetรฉs hosszรบ zenecรญmek esetรฉn.", + "scrolling-mode-label": "Gรถrgetรฉsi mรณd", + "show-album-art-description": "A borรญtรณkรฉp megjelenรญtรฉse az aktuรกlisan jรกtszott szรกmhoz.", + "show-album-art-label": "Borรญtรณ megjelenรญtรฉse", + "show-artist-first-description": "Elล‘adรณ - cรญm megjelenรญtรฉse cรญm - elล‘adรณ helyett.", + "show-artist-first-label": "Elล‘adรณ megjelenรญtรฉse elรถl", + "show-progress-ring-description": "Jelenรญtsen meg egy kรถr alakรบ folyamatjelzล‘t, amely mutatja a zene elล‘rehaladรกsรกt.", + "show-progress-ring-label": "Folyamatgyลฑrลฑ megjelenรญtรฉse", + "show-visualizer-description": "Jelenรญtsen meg egy hangvizualizรกtor, amikor zene szรณl.", + "show-visualizer-label": "Megjelenรญtล‘ mutatรกsa", + "use-fixed-width-description": "Ha engedรฉlyezve van, a widget mindig a maximรกlis szรฉlessรฉget hasznรกlja a tartalomhoz valรณ dinamikus igazodรกs helyett.", + "use-fixed-width-label": "Rรถgzรญtett szรฉlessรฉg hasznรกlata", + "visualizer-type-description": "A megjelenรญtendล‘ hangvizualizรกtor stรญlusa.", + "visualizer-type-label": "Megjelenรญtล‘ tรญpusa" + }, + "notification-history": { + "hide-widget-when-zero-description": "Az รฉrtesรญtรฉsi ikon elrejtรฉse, ha nincs รฉrtesรญtรฉs.", + "hide-widget-when-zero-label": "Ikon elrejtรฉse, amikor nincs รฉrtesรญtรฉs", + "hide-widget-when-zero-unread-description": "Az รฉrtesรญtรฉsi ikon elrejtรฉse, ha nincs olvasatlan รฉrtesรญtรฉs.", + "hide-widget-when-zero-unread-label": "Ikon elrejtรฉse, amikor nincs olvasatlan รฉrtesรญtรฉs", + "show-unread-badge-description": "Jelenรญtsen meg egy jelvรฉnyt, amely mutatja az olvasatlan รฉrtesรญtรฉsek szรกmรกt.", + "show-unread-badge-label": "Olvasatlan jelvรฉny megjelenรญtรฉse", + "unread-badge-color-description": "Az olvasatlan รฉrtesรญtรฉsi jelvรฉny szรญne.", + "unread-badge-color-label": "Olvasatlan jelvรฉny szรญne" + }, + "section-editor": { + "placeholder": "Hozzรกadรกsโ€ฆ", + "search-placeholder": "Widget keresรฉseโ€ฆ" + }, + "spacer": { + "width-description": "Tรกvolsรกg szรฉlessรฉge pixelben." + }, + "system-monitor": { + "compact-mode-description": "A statisztikรกkat mini sรกvdiagramokkรฉnt jelenรญtse meg a szรถveges รฉrtรฉkek helyett. Megakadรกlyozza az elrendezรฉs eltolรณdรกsรกt.", + "compact-mode-label": "Kompakt mรณd", + "cpu-cores-description": "A CPU magok hasznรกlatรกnak kรผlรถn-kรผlรถn megjelenรญtรฉse.", + "cpu-cores-label": "CPU magok", + "cpu-frequency-description": "Az aktuรกlis CPU-รณrajel megjelenรญtรฉse GHz-ben.", + "cpu-frequency-label": "CPU-frekvencia megjelenรญtรฉse", + "cpu-temperature-description": "CPU hล‘mรฉrsรฉklet-adatok megjelenรญtรฉse, ha elรฉrhetล‘ek.", + "cpu-temperature-label": "CPU-hล‘mรฉrsรฉklet", + "cpu-usage-description": "Az aktuรกlis CPU-hasznรกlati szรกzalรฉk megjelenรญtรฉse.", + "cpu-usage-label": "CPU-hasznรกlat", + "disk-path-description": "Melyik lemez csatolรกsi pontot figyelje.", + "disk-path-label": "Lemez รบtvonala", + "gpu-temperature-description": "GPU hล‘mรฉrsรฉklet-รฉrtรฉkek megjelenรญtรฉse, ha elรฉrhetล‘ek.", + "load-average-description": "Rendszerterhelรฉs รกtlagรกnak megjelenรญtรฉse.", + "load-average-label": "รtlagos terhelรฉs", + "memory-percentage-description": "A memรณria hasznรกlatรกnak megjelenรญtรฉse szรกzalรฉkos รฉrtรฉkben, abszolรบt รฉrtรฉkek helyett.", + "memory-percentage-label": "Memรณria szรกzalรฉkban", + "memory-usage-description": "Jelenlegi RAM hasznรกlati informรกciรณk megjelenรญtรฉse.", + "memory-usage-label": "Memรณriahasznรกlat", + "network-traffic-description": "Hรกlรณzati feltรถltรฉsi รฉs letรถltรฉsi sebessรฉgek megjelenรญtรฉse.", + "network-traffic-label": "Hรกlรณzati forgalom", + "storage-as-percentage-description": "Lemezterรผlet megjelenรญtรฉse szรกzalรฉkban abszolรบt รฉrtรฉkek helyett.", + "storage-as-percentage-label": "Lemez szรกzalรฉkban", + "storage-available-description": "A rendelkezรฉsre รกllรณ lemezterรผletet jelenรญti meg a hasznรกlt helyett", + "storage-available-label": "Rendelkezรฉsre รกllรณ lemezterรผlet", + "storage-usage-description": "Lemezterรผlet-hasznรกlati informรกciรณk megjelenรญtรฉse.", + "storage-usage-label": "Tรกrhelyhasznรกlat", + "swap-usage-description": "Cserehely memรณria hasznรกlatรกnak megjelenรญtรฉse.", + "swap-usage-label": "Cserehely hasznรกlat", + "use-monospace-font-description": "Monospace betลฑtรญpus hasznรกlata az egysรฉges karakter-szรฉlessรฉghez.", + "use-monospace-font-label": "Monospace betลฑtรญpus", + "use-padding-description": "Kitรถlti a szรถvegรฉrtรฉkeket vezetล‘ szรณkรถzรถkkel, hogy megakadรกlyozza az elrendezรฉs eltolรณdรกsรกt.", + "use-padding-description-disabled-monospace-font": "Ehhez a funkciรณhoz monospace betลฑtรญpus szรผksรฉges.", + "use-padding-description-disabled-vertical": "A fรผggล‘leges tรกlca nem tรกmogatja a szรถveges kitรถltรฉst.", + "use-padding-label": "Szรถvegkitรถltรฉs" + }, + "taskbar": { + "colorize-icons-description": "Tรฉmaszรญnek alkalmazรกsa a tรกlca ikonjaira.", + "hide-mode-description": "A widget viselkedรฉse, ha nincsenek megfelelล‘ ablakok.", + "hide-mode-label": "Elrejtรฉsi mรณd", + "icon-scale-description": "Beรกllรญtja a tรกlca ikonjainak skรกlรกzรกsi tรฉnyezล‘jรฉt.", + "icon-scale-label": "Ikon skรกlรกzรกsa", + "max-width-description": "A tรกlca maximรกlis szรฉlessรฉge a kรฉpernyล‘szรฉlessรฉg szรกzalรฉkรกban.", + "max-width-label": "Maximรกlis szรฉlessรฉg", + "only-active-workspaces-description": "Csak az aktรญv munkaterรผletekrล‘l szรกrmazรณ alkalmazรกsok megjelenรญtรฉse.", + "only-active-workspaces-label": "Csak az aktรญv munkaterรผletekrล‘l", + "only-same-monitor-description": "Csak a sรกv helyรฉn lรฉvล‘ monitorrรณl szรกrmazรณ alkalmazรกsok megjelenรญtรฉse.", + "only-same-monitor-label": "Csak ugyanarrรณl a monitorrรณl", + "show-pinned-apps-description": "Rรถgzรญtett alkalmazรกsok megjelenรญtรฉse a tรกlcรกn.", + "show-pinned-apps-label": "Rรถgzรญtett alkalmazรกsok megjelenรญtรฉse", + "show-title-description": "Ablakcรญmek megjelenรญtรฉse a tรกlcรกn.", + "show-title-description-disabled": "A fรผggล‘leges tรกlca nem tรกmogatja a cรญmek megjelenรญtรฉsรฉt.", + "show-title-label": "Cรญm megjelenรญtรฉse", + "smart-width-description": "A bejegyzรฉs szรฉlessรฉgรฉnek automatikus beรกllรญtรกsa a bejegyzรฉsek szรกma alapjรกn.", + "smart-width-label": "Intelligens szรฉlessรฉg", + "title-width-description": "Az ablakcรญmek szรฉlessรฉge a tรกlcรกn (kรฉppontokban).", + "title-width-label": "Cรญm szรฉlessรฉge", + "title-width-reset-tooltip": "Cรญm szรฉlessรฉgรฉnek alaphelyzetbe รกllรญtรกsa" + }, + "tray": { + "chevron-color-description": "Tรฉmaszรญnek a fiรณk nyรญl ikonjรกra.", + "chevron-color-label": "Nyรญl szรญne", + "colorize-icons-description": "Tรฉmaszรญnek alkalmazรกsa a tรกlcaikonokra.", + "colorize-icons-label": "Ikonok szรญnezรฉse", + "drawer-enabled-description": "Ha engedรฉlyezve van, a nem rรถgzรญtett tรกlcaelemek egy fiรณkpanelen jelennek meg.
Letiltva az รถsszes tรกlcaelem sorban jelenik meg.", + "drawer-enabled-label": "Tรกlca-fiรณk engedรฉlyezรฉse", + "hide-passive-description": "Engedรฉlyezve a โ€žPasszรญvโ€ รกllapotรบ tรกlcaelemek el lesznek rejtve.", + "hide-passive-label": "Passzรญv elemek elrejtรฉse" + }, + "volume": { + "display-mode-description": "Hogyan jelenjen meg ez az รฉrtรฉk." + }, + "workspace": { + "character-count-description": "A munkaterรผlet nevekbล‘l megjelenรญtendล‘ karakterek szรกma (1-10).", + "character-count-label": "Karakterek szรกma", + "empty-color-description": "รœres munkaterรผlet hรกttรฉrszรญnรฉnek beรกllรญtรกsa.", + "empty-color-label": "รœres munkaterรผlet szรญne", + "enable-scrollwheel-description": "Vรกltรกs a munkaterรผletek kรถzรถtt az egรฉrgรถrgล‘vel.", + "enable-scrollwheel-label": "Gรถrgetรฉs a munkaterรผletek vรกltรกsรกhoz", + "focused-color-description": "A fรณkuszรกlt munkaterรผlet hรกttรฉrszรญne.", + "focused-color-label": "Fรณkuszรกlt munkaterรผlet szรญne", + "follow-focused-screen-description": "Munkaterรผletek megjelenรญtรฉse az aktuรกlisan fรณkuszรกlt kรฉpernyล‘rล‘l, nem arrรณl a kรฉpernyล‘rล‘l, ahol a sรกv talรกlhatรณ.", + "follow-focused-screen-label": "Fรณkuszรกlt kรฉpernyล‘ kรถvetรฉse", + "font-weight-description": "A szรถveg vizuรกlis sรบlya a munkaterรผleten belรผl.", + "font-weight-label": "Betลฑvastagsรกg", + "grouped-border-opacity-description": "A munkaterรผlet-kontรฉnerek szegรฉlyeinek รกtlรกtszรณsรกgi szintjรฉnek beรกllรญtรกsa.", + "grouped-border-opacity-label": "Szegรฉly รกtlรกtszรณsรกga", + "hide-unoccupied-description": "Ne jelenรญtse meg az ablak nรฉlkรผli munkaterรผleteket.", + "hide-unoccupied-label": "Foglalatlan elrejtรฉse", + "label-mode-description": "Hogyan jelenjenek meg a munkaterรผlet cรญmkรฉi.", + "label-mode-label": "Cรญmke mรณd", + "occupied-color-description": "A foglalt munkaterรผlet hรกttรฉrszรญnรฉnek beรกllรญtรกsa.", + "occupied-color-label": "Foglalt munkaterรผlet szรญne", + "pill-size-description": "A munkaterรผlet jelzล‘k mรฉrete (50%-100%).", + "pill-size-label": "Kapszula mรฉret", + "reverse-scrolling-description": "Gรถrgetรฉskor fordรญtott irรกnyba vรกltson a munkaterรผletek kรถzรถtt.", + "reverse-scrolling-label": "Gรถrgetรฉs megfordรญtรกsa", + "show-applications-description": "Alkalmazรกsikonok megjelenรญtรฉse minden munkaterรผleten belรผl.", + "show-applications-hover-description": "Alkalmazรกsikonok megjelenรญtรฉse minden munkaterรผleten belรผl, amikor a munkaterรผlet fรถlรฉ viszed az egeret.", + "show-applications-hover-label": "Alkalmazรกsok megjelenรญtรฉse rรกmutatรกskor", + "show-applications-label": "Alkalmazรกsok megjelenรญtรฉse", + "show-badge-description": "Munkaterรผlet szรกmรกnak jelzรฉse csoportosรญtott mรณdban.", + "show-badge-label": "Munkafelรผlet jelvรฉny megjelenรญtรฉse", + "show-labels-only-when-occupied-description": "Csak akkor jelenรญtse meg a munkaterรผlet cรญmkรฉket, ha azok tartalmaznak ablakokat.", + "show-labels-only-when-occupied-label": "Cรญmkรฉk megjelenรญtรฉse csak foglalt รกllapotban", + "unfocused-icons-opacity-description": "A nem fรณkuszรกlt alkalmazรกsikonok รกtlรกtszรณsรกgรกnak szintje.", + "unfocused-icons-opacity-label": "Fรณkuszรกlatlan ikonok รกtlรกtszรณsรกga" + } + }, + "battery": { + "all-batteries": "ร–sszes akkumulรกtor (kombinรกlva)", + "battery-health": "Akkumulรกtor รกllapota", + "battery-level": "Akkumulรกtor tรถltรถttsรฉgi szintje", + "capacity-level": "Kapacitรกs: {level}", + "charging-rate": "Tรถltรฉsi sebessรฉg: {rate} W", + "discharging-rate": "Merรผlรฉsi sebessรฉg: {rate} W", + "health": "รllapot: {percent}%", + "inhibit-idle-description": "ร‰bren tartja a rendszert.", + "no-battery-detected": "Nincs akkumulรกtor รฉrzรฉkelve", + "plugged-in": "Csatlakoztatva", + "power-profile": "Energiaprofil", + "time-left": "Hรกtralรฉvล‘ idล‘: {time}", + "time-until-full": "Teljes feltรถltรฉsig hรกtralรฉvล‘ idล‘: {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "Elรฉrhetล‘ eszkรถzรถk", + "blocked": "Blokkolva", + "connected-devices": "Csatlakoztatott eszkรถzรถk", + "device-address": "Eszkรถz cรญme", + "disabled": "A Bluetooth le van tiltva", + "discoverable": "Felfedezhetล‘", + "enable-message": "Kapcsold be a Bluetooth-t az elรฉrhetล‘ eszkรถzรถk megtekintรฉsรฉhez.", + "known-devices": "Ismert eszkรถzรถk", + "no-devices": "Nincsenek elรฉrhetล‘ eszkรถzรถk", + "paired-devices": "Pรกrosรญtott eszkรถzรถk", + "pairing-mode": "Gyล‘zล‘djรถn meg arrรณl, hogy eszkรถze pรกrosรญtรกsi mรณdban van.", + "scanning": "Eszkรถzรถk keresรฉseโ€ฆ", + "signal-text-excellent": "Jel: Kivรกlรณ", + "signal-text-fair": "Jel: Gyenge", + "signal-text-good": "Jel: Jรณ", + "signal-text-poor": "Jel: Gyenge", + "signal-text-unknown": "Jel: Ismeretlen", + "signal-text-very-poor": "Jelerล‘ssรฉg: Nagyon gyenge" + } + }, + "changelog": { + "error": { + "fetch-failed": "Nem sikerรผlt betรถlteni a vรกltozรกsi naplรณ adatait. Prรณbรกlja รบjra kรฉsล‘bb.", + "rate-limit": "Tรบllรฉpte a GitHub sebessรฉgkorlรกtjรกt. Prรณbรกlja รบjra nรฉhรกny perc mรบlva." + }, + "panel": { + "buttons-discord": "Csatlakozz a Discordunkhoz", + "buttons-dismiss": "OK", + "buttons-feedback": "Adj visszajelzรฉst", + "empty": "A kiadรกsi jegyzetek mรฉg nem รฉrhetล‘k el.", + "highlight-title": "Fล‘bb รบjdonsรกgok", + "released": "Kiadva: {date}", + "subtitle-fresh": "Kรถszรถnjรผk, hogy telepรญtette a Noctalia-t! Itt van, mi talรกlhatรณ ebben az รถsszeรกllรญtรกsban.", + "subtitle-updated": "Frissรญtve errล‘l: {previousVersion}", + "title": "รšjdonsรกgok a {version} verziรณban", + "version": "verziรณ {version}", + "version-new-user": "รšj telepรญtรฉs" + } + }, + "common": { + "actions": "Mลฑveletek", + "active": "Aktรญv", + "add": "Hozzรกadรกs", + "appearance": "Megjelenรฉs", + "apply": "Alkalmazรกs", + "auto-connect": "Automatikus csatlakozรกs", + "automation": "Automatizรกlรกs", + "available": "Elรฉrhetล‘", + "back": "Vissza", + "battery": "Akkumulรกtor", + "behavior": "Viselkedรฉs", + "bluetooth": "Bluetooth", + "brightness": "Fรฉnyerล‘", + "browse": "Bรถngรฉszรฉs", + "calendar": "Naptรกr", + "calendar-panel": "Naptรกr panel", + "cancel": "Mรฉgse", + "cards": "Kรกrtyรกk", + "charging": "Tรถltรฉs", + "clear": "Tรถrlรฉs", + "clipboard": "Vรกgรณlap", + "close": "Bezรกrรกs", + "color-muted": "Nรฉmรญtva", + "colors": "Szรญnek", + "command": "Parancs", + "confirm": "Megerล‘sรญtรฉs", + "connect": "Csatlakozรกs", + "connected": "Csatlakoztatva", + "connecting": "Csatlakozรกsโ€ฆ", + "continue": "Folytatรกs", + "contributors": "Kรถzremลฑkรถdล‘k", + "copied-to-clipboard": "Vรกgรณlapra mรกsolva", + "countdown": "Visszaszรกmlรกlรกs", + "customize": "Testreszabรกs", + "date": "Dรกtum", + "default": "Alapรฉrtelmezett", + "delete": "Tรถrlรฉs", + "devices": "Eszkรถzรถk", + "disabled": "Letiltva", + "discharging": "Kisรผtรฉs", + "disconnect": "Kapcsolat bontรกsa", + "disconnected": "Kapcsolat bontva", + "disconnecting": "Kapcsolat bontรกsaโ€ฆ", + "display-mode": "Kijelzล‘ mรณd", + "documentation": "Dokumentรกciรณ", + "download": "Letรถltรฉs", + "duration": "Idล‘tartam", + "dysfunctional": "Hibรกs", + "edit": "Szerkesztรฉs", + "enabled": "Engedรฉlyezve", + "error": "Hiba", + "ethernet": "Ethernet", + "events": "Esemรฉnyek", + "execute": "Vรฉgrehajtรกs", + "faithful": "Hลฑsรฉges", + "focus": "Fรณkusz", + "font-weight-bold": "Fรฉlkรถvรฉr", + "font-weight-light": "Vรฉkony", + "font-weight-medium": "Kรถzepes", + "font-weight-regular": "Normรกl", + "font-weight-semibold": "Fรฉlkรถvรฉr", + "frequency": "Frekvencia", + "gateway": "รtjรกrรณ", + "general": "รltalรกnos", + "height": "Magassรกg", + "hibernate": "Hibernรกlรกs", + "history": "Elล‘zmรฉnyek", + "icon": "Ikon", + "idle": "Tรฉtlen", + "info": "Informรกciรณ", + "input": "Bemenet", + "install": "Telepรญtรฉs", + "installed": "Telepรญtve", + "interface": "Felรผlet", + "internet": "Internet", + "keybind": "Gyorsbillentyลฑ", + "language": "Nyelv", + "loading": "Betรถltรฉsโ€ฆ", + "local": "Helyi", + "location": "Helyszรญn", + "lock": "Zรกrolรกs", + "logout": "Kijelentkezรฉs", + "look": "Megjelenรฉs", + "margins": "Margรณk", + "media": "Mรฉdia", + "media-player": "Mรฉdialejรกtszรณ", + "memory": "Memรณria", + "monitors": "Monitorok", + "move-down": "Lejjebb mozgatรกs", + "move-up": "Feljebb", + "network": "Hรกlรณzat", + "next": "Kรถvetkezล‘", + "night-light": "ร‰jszakai fรฉny", + "no": "Nem", + "no-results": "Nincs talรกlat", + "no-summary": "Nincs รถsszefoglalรณ", + "none": "Nincs", + "not-found": "Nem talรกlhatรณ", + "notifications": "ร‰rtesรญtรฉsek", + "official": "Hivatalos", + "on-surface": "Felรผleten", + "output": "Kimenet", + "pair": "Pรกrosรญtรกs", + "paired": "Pรกrosรญtva", + "pairing": "Pรกrosรญtรกsโ€ฆ", + "panels": "Panelek", + "password": "Jelszรณ", + "pause": "Szรผnet", + "performance": "Teljesรญtmรฉny", + "pin": "Rรถgzรญtรฉs", + "play": "Lejรกtszรกs", + "polling": "Lekรฉrdezรฉs", + "position": "Pozรญciรณ", + "previous": "Elล‘zล‘", + "primary": "Elsล‘dleges", + "random": "Vรฉletlen", + "reboot": "รšjraindรญtรกs", + "reboot-to-uefi": "รšjraindรญtรกs UEFI-be", + "record": "Felvรฉtel indรญtรกsa", + "refresh": "Frissรญtรฉs", + "required": "(kรถtelezล‘)", + "reset": "Visszaรกllรญtรกs", + "reset-to-default": "Visszaรกllรญtรกs Alapรฉrtelmezettre", + "result-count": "{count} eredmรฉny", + "result-count-plural": "eredmรฉnyek", + "resume": "Folytatรกs", + "save": "Mentรฉs", + "scanning": "Szkennelรฉsโ€ฆ", + "screen-corners": "Kรฉpernyล‘sarkok", + "search": "Keresรฉs", + "second": "{count} mรกsodperc", + "second-plural": "{count} mรกsodperc", + "secondary": "Mรกsodlagos", + "security": "Biztonsรกg", + "select": "Kivรกlasztรกs", + "select-color": "Szรญn kivรกlasztรกsa", + "select-color-description": "Tรฉmaszรญnek a kiemelรฉshez.", + "select-icon-color": "Ikon szรญnรฉnek kivรกlasztรกsa", + "settings": "Beรกllรญtรกsok", + "shortcuts": "Gyorsbillentyลฑk", + "shutdown": "Leรกllรญtรกs", + "signal": "Jelzรฉs", + "sound": "Hang", + "sources": "Forrรกsok", + "start": "Kezdรฉs", + "stop": "รllj", + "supporters": "Tรกmogatรณk", + "suspend": "Felfรผggesztรฉs", + "templates": "Sablonok", + "tertiary": "Harmadlagos", + "test": "Teszt", + "thresholds": "Kรผszรถbรฉrtรฉkek", + "title": "Cรญm", + "toast": "ร‰rtesรญtล‘", + "trusted": "Megbรญzhatรณ", + "uninstall": "Eltรกvolรญtรกs", + "unknown": "Ismeretlen", + "unpair": "Pรกrosรญtรกs bontรกsa", + "unpin": "Rรถgzรญtรฉs megszรผntetรฉse", + "update": "Frissรญtรฉs", + "upload": "Feltรถltรฉs", + "username": "Felhasznรกlรณnรฉv", + "userspace-reboot": "Felhasznรกlรณi tรฉr รบjraindรญtรกsa", + "version": "Verziรณ", + "vibrant": "ร‰lรฉnk", + "visualizer": "Vizualizรกlรณ", + "volume": "Hangerล‘", + "volumes": "Hangerล‘", + "wallpaper": "Hรกttรฉrkรฉp", + "weather": "Idล‘jรกrรกs", + "weather-loading": "Idล‘jรกrรกs betรถltรฉseโ€ฆ", + "weather-no-location": "รllรญtson be egy helyet a Beรกllรญtรกsokban az idล‘jรกrรกs megtekintรฉsรฉhez", + "week": "Hรฉt", + "widgets": "Widgetek", + "width": "Szรฉlessรฉg", + "wifi": "Wi-Fi", + "windows": "Ablakok", + "yes": "Igen" + }, + "control-center": { + "power-profile": { + "tooltip-action": "Energiaprofil", + "tooltip-disabled": "Telepรญtsd a power-profiles-daemon csomagot az energiaprofilok hasznรกlatรกhoz" + }, + "wifi": { + "label-disconnected": "Wi-Fi levรกlasztva", + "label-ethernet": "Ethernet" + } + }, + "display-modes": { + "always-hide": "Mindig rejtve legyen", + "always-show": "Mindig megjelenรญtve legyen", + "force-open": "Felfeszรญt", + "on-hover": "Rรกmutatรกskor" + }, + "hide-modes": { + "auto-hide": "Automatikus elrejtรฉs", + "hidden": "Elrejtรฉs, ha รผres", + "idle": "Elrejtรฉs, ha tรฉtlen", + "non-exclusive": "Nem kizรกrรณlagos", + "transparent": "รtlรกtszรณ, ha รผres", + "visible": "Mindig lรกthatรณ" + }, + "launcher": { + "categories": { + "all": "ร–sszes", + "audiovideo": "Hang รฉs videรณ", + "chat": "Csevegรฉs", + "development": "Fejlesztรฉs", + "education": "Oktatรกs", + "emoji-activity": "Tevรฉkenysรฉg", + "emoji-animals": "รllatok", + "emoji-flags": "Zรกszlรณk", + "emoji-food": "ร‰tel รฉs ital", + "emoji-nature": "Termรฉszet", + "emoji-objects": "Tรกrgyak", + "emoji-people": "Emberek รฉs test", + "emoji-recent": "Legutรณbbi", + "emoji-symbols": "Szimbรณlumok", + "emoji-travel": "Utazรกs รฉs helyek", + "game": "Jรกtรฉkok", + "graphics": "Grafika", + "misc": "Egyรฉb", + "office": "Iroda", + "pinned": "Rรถgzรญtett", + "system": "Rendszer", + "webbrowser": "Webbรถngรฉszล‘" + }, + "providers": { + "applications": "Alkalmazรกsok", + "calculator": "Szรกmolรณgรฉp", + "calculator-deprecated": "A >calc parancs elavult, รฉs hamarosan el lesz tรกvolรญtva. A matematikai kifejezรฉseket kรถzvetlenรผl a keresรฉsbe รญrd be helyette.", + "calculator-description": "Szรกmolรณgรฉp โ€” matematikai kifejezรฉsek kiรฉrtรฉkelรฉse", + "calculator-enter-expression": "รrjon be egy matematikai kifejezรฉst", + "calculator-press-enter-to-copy": "Nyomd meg az Entert az eredmรฉny mรกsolรกsรกhoz", + "clipboard": "Vรกgรณlapelล‘zmรฉnyek", + "clipboard-clear-description": "ร–sszes vรกgรณlapelล‘zmรฉny tรถrlรฉse", + "clipboard-clear-description-full": "Az รถsszes elem eltรกvolรญtรกsa a vรกgรณlapelล‘zmรฉnyekbล‘l", + "clipboard-clear-history": "Vรกgรณlapelล‘zmรฉnyek tรถrlรฉse", + "clipboard-delete": "Vรกgรณlap bejegyzรฉs tรถrlรฉse", + "clipboard-history-disabled": "Vรกgรณlapelล‘zmรฉnyek letiltva", + "clipboard-history-disabled-description": "Engedรฉlyezd a vรกgรณlapelล‘zmรฉnyeket a beรกllรญtรกsokban, vagy telepรญtsd a cliphist csomagot", + "clipboard-loading": "Vรกgรณlapelล‘zmรฉnyek betรถltรฉseโ€ฆ", + "clipboard-search-description": "Keresรฉs a vรกgรณlapelล‘zmรฉnyekben", + "command-description": "Shell parancsok futtatรกsa", + "emoji": "Emoji vรกlasztรณ", + "emoji-loading": "Emojik betรถltรฉseโ€ฆ", + "emoji-loading-description": "Kis tรผrelmet", + "emoji-no-recent": "Nincsenek mรฉg legutรณbbi emojik", + "emoji-search-description": "Emojik keresรฉse รฉs mรกsolรกsa", + "settings-search-description": "Keresรฉs รฉs navigรกlรกs a beรกllรญtรกsokhoz", + "windows-search-description": "Nyitott ablakok keresรฉse รฉs fรณkuszรกlรกsa" + } + }, + "lock-screen": { + "authenticating": "Hitelesรญtรฉsโ€ฆ", + "password": "Add meg a jelszavadatโ€ฆ", + "restart": "รšjraindรญtรกs", + "shut-down": "Leรกllรญtรกs" + }, + "notifications": { + "panel": { + "click-to-collapse": "Kattints az รถsszecsukรกshoz", + "click-to-expand": "Kattints a kibontรกshoz", + "description": "Az รฉrtesรญtรฉsek itt jelennek meg, amint megรฉrkeznek.", + "no-notifications": "Nincs รฉrtesรญtรฉs" + }, + "range": { + "earlier": "Korรกbban", + "today": "Ma", + "yesterday": "Tegnap" + }, + "time": { + "diff-d": "1 napja", + "diff-dd": "{diff} napja", + "diff-h": "1 รณrรกja", + "diff-hh": "{diff} รณrรกja", + "diff-m": "1 perce", + "diff-mm": "{diff} perce", + "now": "most" + } + }, + "options": { + "bar": { + "density-comfortable": "Kรฉnyelmes", + "density-compact": "Kompakt", + "density-default": "Alapรฉrtelmezett", + "density-mini": "Mini", + "density-spacious": "Tรกgas", + "display-mode-always-visible": "Mindig lรกthatรณ", + "type-floating": "Lebegล‘", + "type-framed": "Keretes", + "type-simple": "Egyszerลฑ" + }, + "control-center": { + "quick-settings-style-classic": "Klasszikus", + "quick-settings-style-modern": "Modern" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "Kรฉnyelmes", + "compact": "Kompakt", + "default": "Alapรฉrtelmezett" + }, + "launcher-view-mode": { + "grid": "Rรกcs", + "list": "Lista" + }, + "notification-density": { + "compact": "Kompakt", + "default": "Alapรฉrtelmezett" + }, + "scrolling-modes": { + "always": "Mindig gรถrgetรฉs", + "hover": "Gรถrgetรฉs rรกmutatรกskor", + "never": "Soha ne gรถrgessen" + }, + "session-menu-grid-layout": { + "grid": "Rรกcs", + "single-row": "Egy sor" + }, + "settings-panel-mode": { + "attached": "Panel a sรกvhoz rรถgzรญtve", + "centered": "Kรถzรฉpre igazรญtott panel", + "window": "Kรผlรถn ablak" + }, + "visualizer-types": { + "linear": "Lineรกris", + "mirrored": "Tรผkrรถzรถtt", + "wave": "Hullรกm" + }, + "visualizer-visibility": { + "with-background": "Csak hรกttรฉrrel" + }, + "workspace-labels": { + "index": "Index", + "index-and-name": "Index รฉs nรฉv", + "name": "Nรฉv" + } + }, + "panels": { + "about": { + "become-supporter": "Legyรฉl tรกmogatรณ", + "changelog": "Vรกltozรกsok megtekintรฉse", + "changelog-on-startup": "Vรกltozรกsi naplรณ megjelenรญtรฉse frissรญtรฉskor", + "changelog-on-startup-desc": "Automatikusan jelenรญtse meg a vรกltozรกsi naplรณt, amikor a Noctalia frissรผl.", + "contributors-description": "Kรถszรถnet a(z) {count} fantasztikus kรถzremลฑkรถdล‘nknek!", + "contributors-description-plural": "Kรถszรถnet a(z) {count} fantasztikus kรถzremลฑkรถdล‘nknek!", + "copy-info": "Informรกciรณk mรกsolรกsa", + "debug-disabled": "Hibakeresรฉsi mรณd letiltva", + "debug-enabled": "Hibakeresรฉsi mรณd engedรฉlyezve", + "info-copied": "Informรกciรณ a vรกgรณlapra mรกsolva", + "noctalia-available": "Elรฉrhetล‘:", + "noctalia-desc": "Egy elegรกns รฉs minimalista asztali felรผlet, Waylandra tervezve, Quickshell-en alapulva.", + "noctalia-git-commit": "Git commit:", + "noctalia-installed-version": "Telepรญtett verziรณ:", + "noctalia-latest-version": "Legรบjabb verziรณ:", + "noctalia-qs-version": "Noctalia QS verziรณ:", + "noctalia-title": "Noctalia shell", + "privacy-policy": "Adatvรฉdelmi irรกnyelvek", + "support": "Tรกmogass minket", + "supporter-badge": "Tรกmogatรณ", + "supporters-desc": "Hatalmas kรถszรถnet csodรกlatos tรกmogatรณinknak!", + "supporters-desc-plural": "Hatalmas kรถszรถnet a {count} csodรกlatos tรกmogatรณinknak!", + "supporters-loading": "Tรกmogatรณk betรถltรฉseโ€ฆ", + "system-board": "Alaplap:", + "system-cpu": "CPU:", + "system-disk": "Lemez:", + "system-gpu": "GPU:", + "system-host": "Gรฉpnรฉv:", + "system-install-hint": "Telepรญtsd a fastfetch programot a rendszerinformรกciรณk megtekintรฉsรฉhez", + "system-kernel": "Kernel:", + "system-loading": "Rendszerinformรกciรณk betรถltรฉseโ€ฆ", + "system-memory": "Memรณria:", + "system-monitor": "Monitor:", + "system-not-installed": "A fastfetch nincs telepรญtve", + "system-os": "Operรกciรณs rendszer:", + "system-packages": "Csomagok:", + "system-product": "Termรฉk:", + "system-title": "Rendszerinformรกciรณk", + "system-uptime": "Mลฑkรถdรฉsi idล‘:", + "system-wm": "WM:", + "telemetry-data-copied": "Telemetriai adatok a vรกgรณlapra mรกsolva", + "telemetry-desc": "Segรญts a Noctalia fejlesztรฉsรฉben nรฉvtelen rendszerinformรกciรณk (kรฉpernyล‘felbontรกs, kompozitor, disztribรบciรณ) megosztรกsรกval. Egyszer kerรผl elkรผldรฉsre indรญtรกskor, nincs nyomon kรถvetรฉs, az adatok 30 nap utรกn automatikusan tรถrlล‘dnek.", + "telemetry-enabled": "Nรฉvtelen rendszerinformรกciรณk kรผldรฉse", + "telemetry-show-data": "Adatok megtekintรฉse", + "telemetry-title": "Adatvรฉdelem", + "title": "Nรฉvjegy", + "up-to-date": "Naprakรฉsz vagy!", + "update-available": "Frissรญtรฉs elรฉrhetล‘", + "view-commit": "Commit megtekintรฉse a GitHubon" + }, + "audio": { + "devices-desc": "A rendelkezรฉsre รกllรณ hangbemeneti รฉs kimeneti eszkรถzรถk konfigurรกlรกsa.", + "devices-input-device-description": "A kรญvรกnt hangbemeneti eszkรถz.", + "devices-input-device-label": "Bemeneti eszkรถz", + "devices-output-device-description": "A kรญvรกnt hangkimeneti eszkรถz.", + "devices-output-device-label": "Kimeneti eszkรถz", + "devices-title": "Hangeszkรถzรถk", + "external-mixer-description": "A parancs vagy alkalmazรกs รบtvonala, amit el kell indรญtani a kรผlsล‘ hangkeverล‘ funkciรณ aktivรกlรกsakor.", + "external-mixer-label": "Kรผlsล‘ hangkeverล‘ parancs", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "A preferรกlt รฉs figyelmen kรญvรผl hagyott mรฉdiaalkalmazรกsok.", + "media-excluded-player-description": "Kulcsszavak hozzรกadรกsa azokhoz a lejรกtszรณkhoz, amelyeket figyelmen kรญvรผl szeretnรฉl hagyni. Minden kulcsszรณ รบj sorban szerepeljen.", + "media-excluded-player-label": "Kizรกrt lejรกtszรณ", + "media-excluded-player-placeholder": "รrj be egy rรฉszszรถveget รฉs nyomd meg a + gombot", + "media-frame-rate-description": "A magasabb sebessรฉg simรกbb, de tรถbb erล‘forrรกst hasznรกl.", + "media-frame-rate-label": "Kรฉpkockasebessรฉg", + "media-primary-player-description": "Egy kulcsszรณ a fล‘ lejรกtszรณ azonosรญtรกsรกhoz.", + "media-primary-player-label": "Elsล‘dleges lejรกtszรณ", + "media-primary-player-placeholder": "pl. spotify, vlc, mpv", + "media-scrolling-speed-description": "A cรญm mรกsodpercben megadott ideje, amรญg a cรญmsor az elejรฉtล‘l a vรฉgรฉig gรถrget.", + "media-scrolling-speed-label": "Gรถrgetรฉsi sebessรฉg", + "media-scrolling-title-description": "Folyamatos gรถrgetรฉs a hosszรบ mรฉdiacรญmekhez.", + "media-scrolling-title-label": "Gรถrgetล‘ cรญm", + "media-title": "Mรฉdialejรกtszรณk", + "on-middle-clicked-description": "A gomb kรถzรฉpsล‘ kattintรกsakor vรฉgrehajtandรณ parancs.", + "panel-applications-empty": "Jelenleg egyetlen alkalmazรกs sem jรกtszik le hangot", + "spectrum-mirrored-description": "รšgy tรผkrรถzi a spektrumot, hogy az alacsony frekvenciรกk kรถzรฉpen legyenek. Kikapcsolva az alacsony frekvenciรกk balra, a magasak jobbra vannak.", + "spectrum-mirrored-label": "Spektrum tรผkrรถzรฉse", + "title": "Hang", + "visualizer-type-description": "Vizualizรกciรณs tรญpus a mรฉdia lejรกtszรกsรกhoz.", + "visualizer-type-label": "Vizualizรกciรณs tรญpus", + "volumes-desc": "Beรกllรญtja a hangerล‘szabรกlyzรณkat รฉs a hangszinteket.", + "volumes-feedback-sound-file-description": "Annak a hangfรกjlnak az elรฉrรฉsi รบtja, amely a hangerล‘ vรกltoztatรกsakor jรกtszรณdik le.", + "volumes-feedback-sound-file-label": "Hangerล‘ visszajelzล‘ hangfรกjl", + "volumes-feedback-sound-file-select-title": "Hangerล‘ visszajelzล‘ hangfรกjl kivรกlasztรกsa", + "volumes-input-volume-description": "Mikrofon bemeneti hangerล‘szintje.", + "volumes-mute-input-description": "Elnรฉmรญtja az alapรฉrtelmezett hangbemenetet (mikrofon).", + "volumes-mute-input-label": "Hangbemenet nรฉmรญtรกsa", + "volumes-mute-output-description": "Elnรฉmรญtja a rendszer fล‘ hangkimenetรฉt.", + "volumes-mute-output-label": "Hangkimenet nรฉmรญtรกsa", + "volumes-output-volume-description": "Rendszerszintลฑ hangerล‘szint.", + "volumes-step-size-description": "Beรกllรญtja a hangerล‘ vรกltoztatรกsรกnak lรฉpรฉsmรฉretรฉt (gรถrgล‘, gyorsbillentyลฑk).", + "volumes-step-size-label": "Hangerล‘ lรฉpรฉsmรฉrete", + "volumes-volume-feedback-description": "Hangerล‘ รกllรญtรกsakor visszajelzล‘ hang lejรกtszรกsa.", + "volumes-volume-feedback-label": "Hangerล‘ visszajelzล‘ hang lejรกtszรกsa", + "volumes-volume-overdrive-description": "Engedรฉlyezi a hangerล‘ 100% fรถlรฉ emelรฉsรฉt. Elล‘fordulhat, hogy nem minden hardver tรกmogatja.", + "volumes-volume-overdrive-label": "Hangerล‘ tรบllรฉpรฉsรฉnek engedรฉlyezรฉse" + }, + "bar": { + "appearance-auto-hide-delay-description": "Idล‘, amรญg a Bar elrejtล‘zik, miutรกn az egรฉr elhagyja.", + "appearance-auto-hide-delay-label": "Elrejtรฉsi kรฉsleltetรฉs", + "appearance-auto-hide-exclusive-note": "Megjegyzรฉs: Az exkluzรญv zรณna automatikusan letiltรกsra kerรผl, ha az automatikus elrejtรฉs aktรญv.", + "appearance-auto-show-delay-description": "Idล‘, miutรกn a sรกv megjelenik, ha az egรฉr a szรฉlre kerรผl.", + "appearance-auto-show-delay-label": "Megjelenรญtรฉsi Kรฉsleltetรฉs", + "appearance-background-opacity-description": "A sรกv hรกttรฉrรฉnek รกtlรกtszรณsรกga.", + "appearance-background-opacity-label": "Sรกv hรกttรฉr รกtlรกtszรณsรกga", + "appearance-capsule-color-description": "Szรญn a sรกvkapszulรกkhoz, vagy semmi az alapรฉrtelmezett felรผletszรญnhez.", + "appearance-capsule-color-label": "Kapszula szรญne", + "appearance-capsule-opacity-description": "Beรกllรญtja a widget hรกtterรฉnek รกtlรกtszรณsรกgi szintjรฉt, amikor a kapszula megjelenik.", + "appearance-capsule-opacity-label": "Kapszula รกtlรกtszรณsรกga", + "appearance-content-padding-description": "A tรกvolsรกg a sรกv szรฉlei รฉs a widgetek kรถzรถtt.", + "appearance-content-padding-label": "Tartalom tรกvolsรกg", + "appearance-density-description": "A sรกv belsล‘ margรณinak beรกllรญtรกsa a kompakt vagy tรกgas megjelenรฉs รฉrdekรฉben.", + "appearance-density-label": "Sรกv sลฑrลฑsรฉge", + "appearance-desc": "A sรกv megjelenรฉsรฉnek รฉs pozรญciรณjรกnak testreszabรกsa.", + "appearance-display-mode-description": "Mikor lรกthatรณ a sรกv.", + "appearance-enable-exclusion-zone-inset-description": "Csรถkkenti a kizรกrรกsi zรณnรกt 1 fizikai pixellel, hogy a sรกv szรฉle alรก tรถkรฉletesen illeszkedjenek a szรฉlรฉn lรฉvล‘ ablakok.", + "appearance-enable-exclusion-zone-inset-label": "Behรบzott kizรกrรกsi zรณna", + "appearance-floating-description": "Megjelenรญti a sรกvot lebegล‘ โ€žkapszulakรฉntโ€.", + "appearance-floating-label": "Lebegล‘ sรกv", + "appearance-font-scale-description": "A betลฑmรฉret skรกlรกjรกnak beรกllรญtรกsa a sรกvon megjelenล‘ szรถveghez.", + "appearance-font-scale-label": "Betลฑmรฉret-skรกla", + "appearance-frame-radius": "Belsล‘ sugรกr", + "appearance-frame-settings-description": "A keret vastagsรกga รฉs a belsล‘ sarok sugara", + "appearance-frame-settings-label": "Keret beรกllรญtรกsai", + "appearance-frame-thickness": "Vastagsรกg", + "appearance-hide-on-overview-description": "A sรกv elrejtรฉse รฉs a panelek bezรกrรกsa, amikor a kompozitor รกttekintรฉse aktรญv.", + "appearance-hide-on-overview-label": "Sรกv elrejtรฉse az รกttekintรฉsben", + "appearance-margins-description": "A lebegล‘ sรกv kรถrรผli margรณk.", + "appearance-margins-horizontal": "Vรญzszintes margรณ", + "appearance-margins-vertical": "Fรผggล‘leges margรณ", + "appearance-outer-corners-description": "Megjelenรญt kifelรฉ รญvelt sarkokat a sรกvon.", + "appearance-outer-corners-label": "Kรผlsล‘ sarkok", + "appearance-position-description": "A sรกv kรฉpernyล‘n valรณ elhelyezรฉse.", + "appearance-position-label": "Sรกv pozรญciรณja", + "appearance-show-capsule-description": "Widget hรกtterek megjelenรญtรฉse.", + "appearance-show-capsule-label": "Kapszula megjelenรญtรฉse", + "appearance-show-on-workspace-switch-description": "Automatikusan jelenรญtse meg a sรกvot egy pillanatra, amikor a munkaterรผlet vรกlt.", + "appearance-show-on-workspace-switch-label": "Sรกv megjelenรญtรฉse munkaterรผlet vรกltรกskor", + "appearance-show-outline-description": "Lรกthatรณ szegรฉly megjelenรญtรฉse minden widget kรถrรผl.", + "appearance-show-outline-label": "Widget kรถrvonalak megjelenรญtรฉse", + "appearance-type-description": "A sรกv stรญlusa: Egyszerลฑ, lebegล‘ vagy keretes.", + "appearance-type-label": "Sรกv tรญpusa", + "appearance-use-separate-opacity-description": "Lehetล‘vรฉ teszi egy kรผlรถn รกtlรกtszatlansรกgi รฉrtรฉk hasznรกlatรกt a sรกv hรกtterรฉhez.", + "appearance-use-separate-opacity-label": "Kรผlรถn oszlop รกtlรกtszรณsรกg hasznรกlata", + "appearance-widget-spacing-description": "Az egyes widgetek kรถzรถtti tรกvolsรกg a sรกvon.", + "appearance-widget-spacing-label": "Widget tรกvolsรกg", + "behavior-middle-click-command-description": "Kรถzรฉpsล‘ kattintรกsra vรฉgrehajtandรณ parancs.", + "behavior-middle-click-command-label": "Kรถzรฉpsล‘ kattintรกs parancsa", + "behavior-middle-click-command-placeholder": "niri msg action รกttekintรฉs vรกltรกsa", + "behavior-middle-click-description": "Mit csinรกljon a kรถzรฉpsล‘ kattintรกs a sรกv รผres terรผletein.", + "behavior-middle-click-follow-mouse-description": "Megnyitja a kivรกlasztott kรถzรฉpsล‘ kattintรกs panelt a kurzor pozรญciรณjรกnรกl.", + "behavior-middle-click-follow-mouse-label": "Kรถzรฉpsล‘ kattintรกs kรถveti az egeret", + "behavior-middle-click-label": "Sรกv kรถzรฉpsล‘ kattintรกs mลฑvelete", + "behavior-right-click-command-description": "Jobb kattintรกsra vรฉgrehajtandรณ parancs.", + "behavior-right-click-command-label": "Jobb Kattintรกsi Parancs", + "behavior-right-click-command-placeholder": "notify-send \"Jobb kattintรกs\"", + "behavior-right-click-description": "Mit csinรกljon a jobb kattintรกs a sรกv รผres terรผletein.", + "behavior-right-click-follow-mouse-description": "Megnyitja a kivรกlasztott jobb egรฉrgombos panelt a kurzor pozรญciรณjรกnรกl.", + "behavior-right-click-follow-mouse-label": "Jobb kattintรกs kรถveti az egeret", + "behavior-right-click-label": "Jobb kattintรกsi mลฑvelet a sรกvon", + "behavior-wheel-wrap-description": "Ha engedรฉlyezve van, a gรถrgetรฉs az utolsรณ elemtล‘l az elsล‘ig folytatรณdik.", + "behavior-wheel-wrap-label": "Kรถrbefutรกs", + "behavior-workspace-scroll-description": "Mit csinรกljon az egรฉrgรถrgล‘ a sรกv รผres terรผletein.", + "behavior-workspace-scroll-label": "Sรกv egรฉrgรถrgล‘ mลฑvelet", + "behavior-workspace-scroll-option-content": "Tartalom", + "behavior-workspace-scroll-option-workspace": "Munkaterรผlet", + "monitor-configure-widgets": "Widgetek konfigurรกlรกsa", + "monitor-override-settings": "Globรกlis beรกllรญtรกsok felรผlรญrรกsa", + "monitor-override-settings-description": "Egyรฉni beรกllรญtรกsok hasznรกlata ehhez a monitorhoz.", + "monitor-reset-all": "ร–sszes visszaรกllรญtรกsa", + "monitor-widgets-title": "Widget konfigurรกciรณ - {monitor}", + "monitors-desc": "Sรกv megjelenรญtรฉse meghatรกrozott monitorokon. Alapรฉrtelmezetten minden monitoron, ha nincs kivรกlasztva.", + "monitors-desc-new": "Mely monitorok jelenรญtsรฉk meg a sรกvot, รฉs a beรกllรญtรกsok testre szabรกsa monitoronkรฉnt.", + "monitors-title": "Monitorok megjelenรญtรฉse", + "title": "Sรกv", + "tray-blacklist-description": "Tรกlca kizรกrรกsi szabรกlyok, tรกmogatja a helyettesรญtล‘ karaktereket (*).", + "tray-blacklist-label": "Feketelista", + "tray-blacklist-placeholder": "pl. nm-applet, Fcitx*", + "tray-pin-application": "Alkalmazรกs rรถgzรญtรฉse", + "tray-unpin-application": "Alkalmazรกs feloldรกsa", + "use-global-widgets": "Globรกlis widgetek hasznรกlata", + "widgets-desc": "Hรบzd รกt a widgeteket a sorrendjรผk megvรกltoztatรกsรกhoz. Hasznรกld a jobb egรฉrgombos menรผt a widgetek szakaszok kรถzรถtti รกthelyezรฉsรฉhez vagy eltรกvolรญtรกsรกhoz.", + "widgets-title": "Widgetek elhelyezรฉse" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "Szรญnsรฉmรกk generรกlรกsa a hรกttรฉrkรฉpbล‘l. Automatikusan kinyeri a szรญneket, hogy egy egysรฉges tรฉmรกt hozzon lรฉtre.", + "color-source-use-wallpaper-colors-label": "Hรกttรฉrkรฉp szรญneinek hasznรกlata", + "dark-mode-mode-description": "Engedรฉlyezi az automatikus vรกltรกst a vilรกgos รฉs sรถtรฉt mรณd kรถzรถtt.", + "dark-mode-mode-label": "Sรถtรฉt mรณd รผtemezรฉse", + "dark-mode-mode-manual": "Kรฉzi", + "dark-mode-mode-off": "Ki", + "dark-mode-switch-description": "Sรถtรฉtebb tรฉmรกra vรกlt a kรถnnyebb รฉjszakai megtekintรฉs รฉrdekรฉben.", + "delete-error-description": "Nem sikerรผlt tรถrรถlni a(z) {scheme} sรฉmรกt", + "delete-error-title": "A tรถrlรฉs sikertelen", + "delete-success-description": "{scheme} sikeresen tรถrรถlve", + "delete-success-title": "Szรญnsรฉma tรถrรถlve", + "download-button": "Tรถbb letรถltรฉse", + "download-downloading": "Letรถltรฉsโ€ฆ", + "download-empty": "Nincsenek elรฉrhetล‘ szรญnsรฉmรกk", + "download-error-api-error": "API hiba: {status}", + "download-error-description": "Nem sikerรผlt letรถlteni a(z) {scheme} sรฉmรกt", + "download-error-download-failed": "A letรถltรฉs sikertelen, kilรฉpรฉsi kรณddal: {code}", + "download-error-invalid-response": "ร‰rvรฉnytelen API vรกlaszformรกtum", + "download-error-no-files": "Nem talรกlhatรณ fรกjl a sรฉmรกhoz", + "download-error-parse-failed": "Nem sikerรผlt รฉrtelmezni az API vรกlaszt: {error}", + "download-error-rate-limit": "A GitHub API sebessรฉgkorlรกtja tรบl lett lรฉpve", + "download-error-title": "A letรถltรฉs sikertelen", + "download-fetching": "Elรฉrhetล‘ szรญnsรฉmรกk lekรฉrรฉseโ€ฆ", + "download-success-description": "Sikeresen letรถltve: {scheme}", + "download-success-title": "Szรญnsรฉma letรถltve", + "download-title": "Szรญnsรฉmรกk letรถltรฉse", + "method-description": { + "content": "Material Design sรฉma nagy pontossรกgรบ szรญnkivonรกssal, amely szorosan illeszkedik a forrรกs tartalom tรฉnyleges szรญneihez.", + "dysfunctional": "Mint a Faithful, de a mรกsodik legdominรกnsabb szรญncsalรกdot vรกlasztja elsล‘dlegesnek.", + "faithful": "Igyekszik kรถzel maradni a forrรกsszรญnhez, mikรถzben harmonikus palettรกt generรกl.", + "fruit-salad": "Material Design sรฉma, amely jรกtรฉkos, รฉlรฉnk palettรกt hoz lรฉtre vรกltozatos รกrnyalatokkal.", + "monochrome": "Material Design sรฉma egyszรญnลฑ szรผrkeskรกlรกval, minimรกlis szรญntartalommal.", + "muted": "Lรกgyabb, telรญtetlen szรญneket generรกl a nyugodt รฉs visszafogott megjelenรฉs รฉrdekรฉben.", + "rainbow": "Material Design sรฉma, amely a teljes spektrumban szรญneket generรกl a maximรกlis szรญndiverzitรกs รฉrdekรฉben.", + "tonal-spot": "Alapรฉrtelmezett Material Design algoritmus. Kiegyensรบlyozott palettรกkat hoz lรฉtre a forrรกsszรญn tรณnusvรกltozataival.", + "vibrant": "Maximalizรกlja a szรญntelรญtettsรฉget a merรฉsz, szemet gyรถnyรถrkรถdtetล‘ palettรกkhoz รฉlรฉnk tรณnusokkal." + }, + "predefined-desc": "Vรกlassz az elล‘re definiรกlt szรญnsรฉmรกk gyลฑjtemรฉnyรฉbล‘l.", + "predefined-generate-templates-label": "Sablonok generรกlรกsa elล‘re definiรกlt sรฉmรกkhoz", + "predefined-title": "Elล‘re definiรกlt szรญnsรฉmรกk", + "sync-gsettings-description": "A rendszertรฉma igazรญtรกsa az aktรญv vilรกgos vagy sรถtรฉt mรณdhoz.", + "sync-gsettings-label": "Rendszertรฉma szinkronizรกlรกsa", + "templates-desc": "Szรญnek alkalmazรกsa kรผlsล‘ alkalmazรกsokra.", + "templates-filter-description": "Sablonok megjelenรญtรฉse egy adott kategรณriรกbรณl.", + "templates-filter-label": "Szลฑrรฉs kategรณria szerint", + "templates-misc-description": "Hozzon lรฉtre sajรกt sablonokat.", + "templates-misc-label": "Haladรณ", + "templates-misc-user-templates-description": "Csak akkor engedรฉlyezd, ha tudod, mit csinรกlsz โ€” lรกsd az online dokumentรกciรณt.", + "templates-misc-user-templates-label": "Felhasznรกlรณi sablonok engedรฉlyezรฉse", + "templates-none-detected": "Nem รฉszlelt semmit", + "templates-write-path": "รrja: {filepath}", + "title": "Szรญnsรฉma", + "wallpaper-method-description": "A kedvenc palettagenerรกlรกsi mรณdszerek kivรกlasztรกsa.", + "wallpaper-method-label": "Paletta generรกlรกsi mรณdszer", + "wallpaper-monitor-source-description": "Melyik monitor legyen hasznรกlva a hรกttรฉrkรฉp szรญneinek kinyerรฉsรฉhez.", + "wallpaper-monitor-source-label": "Szรญngenerรกlรกsi forrรกs" + }, + "connections": { + "authentication-required": "Hitelesรญtรฉs szรผksรฉges", + "bluetooth-auto-connect-description": "Automatikus csatlakozรกs megbรญzhatรณ pรกrosรญtott eszkรถzรถkhรถz, ha a Bluetooth engedรฉlyezve van.", + "bluetooth-auto-connect-label": "Pรกrosรญtott eszkรถzรถk automatikus csatlakoztatรกsa", + "bluetooth-devices-unnamed": "A nรฉvtelen eszkรถzรถk nem jelennek meg.", + "bluetooth-discoverable": "Ez az eszkรถz {hostName} nรฉven fedezhetล‘ fel, amรญg ez a beรกllรญtรกsi fรผl nyitva van.", + "bluetooth-rssi-polling-description": "Rendszeresen mintavรฉtelezi a csatlakoztatott eszkรถzรถk RSSI-jรฉt bluetoothctl-en keresztรผl. Lehet, hogy nem minden eszkรถzhรถz รฉrhetล‘ el; engedรฉlyezve minimรกlis erล‘forrรกsokat hasznรกl.", + "bluetooth-rssi-polling-interval-description": "Milyen gyakran frissรผljรถn a jelerล‘ssรฉg a csatlakoztatott eszkรถzรถk szรกmรกra.", + "bluetooth-rssi-polling-interval-label": "Lekรฉrdezรฉsi intervallum", + "bluetooth-rssi-polling-label": "Bluetooth jelszint lekรฉrdezรฉs", + "disable-discoverability-description": "Eszkรถz elrejtรฉse a kรถzeli Bluetooth eszkรถzรถk elล‘l.", + "disable-discoverability-label": "Eszkรถz lรกthatรณsรกgรกnak letiltรกsa", + "hide-unnamed-devices-description": "Elrejti azokat az eszkรถzรถket, amelyek csak Bluetooth-cรญmkรฉnt jelennek meg.", + "hide-unnamed-devices-label": "Nรฉvtelen eszkรถzรถk elrejtรฉse", + "pin-instructions": "รrja be az eszkรถzรฉn megjelenล‘ PIN-kรณdot.", + "title": "Kapcsolatok", + "wifi-header-text": "Hasznรกld a Wi-Fi-t az elรฉrhetล‘ hรกlรณzatok megtekintรฉsรฉhez รฉs az internethez valรณ csatlakozรกshoz." + }, + "control-center": { + "cards-desc": "Mely vezรฉrlล‘k jelenjenek meg a vezรฉrlล‘kรถzpontban รฉs milyen sorrendben.", + "desc": "A vezรฉrlล‘kรถzpont panel pozicionรกlรกsรกnak รฉs viselkedรฉsรฉnek konfigurรกlรกsa.", + "open-at-mouse-description": "Ha a sรกv รผres terรผletรฉre kattintasz jobb gombbal, megnyรญlik a kurzornรกl a vezรฉrlล‘kรถzpont. Ha ez le van tiltva, akkor a fenti pozรญciรณbeรกllรญtรกs kerรผl alkalmazรกsra.", + "open-at-mouse-label": "Megnyitรกs a kurzornรกl jobb kattintรกsra", + "position-description": "Hol jelenjen meg a vezรฉrlล‘kรถzpont panel megnyitรกskor.", + "shortcuts-custom-button-command-description": "A gombra kattintรกskor vรฉgrehajtandรณ parancs.", + "shortcuts-custom-button-enable-on-state-logic-description": "Mรกsodik ikon รฉs โ€žaktรญvโ€ รกllapot engedรฉlyezรฉse ellenล‘rzล‘ parancs alapjรกn.", + "shortcuts-custom-button-enable-on-state-logic-label": "Bekapcsolt รกllapotรบ logika engedรฉlyezรฉse", + "shortcuts-custom-button-icon-description": "Egy ikon kivรกlasztรกsa a kรถnyvtรกrbรณl.", + "shortcuts-custom-button-on-clicked-label": "Bal kattintรกs parancs", + "shortcuts-custom-button-on-middle-clicked-description": "Parancs, ami vรฉgrehajtรณdik, ha a gombot kรถzรฉpsล‘ kattintรกssal megnyomjรกk.", + "shortcuts-custom-button-on-state-command-description": "A gomb โ€žbekapcsoltโ€ รกllapotรกnak ellenล‘rzรฉsรฉhez vรฉgrehajtandรณ parancs. 0-t ad vissza a bekapcsolthoz, nem nullรกt a kikapcsolthoz.", + "shortcuts-custom-button-on-state-command-label": "Bekapcsolt รกllapot ellenล‘rzล‘ parancs", + "shortcuts-custom-button-on-state-icon-description": "A gomb ikonja โ€žbekapcsoltโ€ รกllapotban.", + "shortcuts-custom-button-on-state-icon-label": "Bekapcsolt รกllapotรบ ikon", + "shortcuts-custom-button-state-checks-add": "รllapotellenล‘rzรฉs hozzรกadรกsa", + "shortcuts-custom-button-state-checks-command": "Az รกllapotellenล‘rzรฉshez vรฉgrehajtandรณ parancs", + "shortcuts-custom-button-state-checks-label": "รllapotellenล‘rzรฉsek", + "shortcuts-custom-button-state-checks-remove": "Eltรกvolรญtรกs", + "shortcuts-custom-button-tooltip-description": "A gomb fรถlรฉ vitt egรฉrmutatรณnรกl megjelenล‘ eszkรถztipp.", + "shortcuts-custom-button-tooltip-label": "Eszkรถztipp", + "shortcuts-title": "Parancsikon widgetek", + "system-monitor-disk-path-description": "Melyik lemez csatolรกsi pontot kell figyelnie a rendszerfigyelล‘ kรกrtyรกnak a vezรฉrlล‘kรถzpontban.", + "system-monitor-disk-path-label": "Rendszerfigyelล‘ lemez elรฉrรฉsi รบtja", + "title": "Vezรฉrlล‘kรถzpont" + }, + "desktop-widgets": { + "clock-enabled-description": "ร“ra widget megjelenรญtรฉse az asztalon.", + "clock-enabled-label": "ร“ra widget engedรฉlyezรฉse", + "clock-format-label": "Formรกtum", + "clock-height-description": "Az รณra widget magassรกga pixelben.", + "clock-height-label": "Magassรกg", + "clock-minimal-mode-description": "Minimรกlis รณrakijelzรฉs idล‘vel รฉs dรกtummal.", + "clock-minimal-mode-label": "Minimรกlis mรณd", + "clock-rounded-corners-description": "A widget hรกttere legyen lekerekรญtett sarkรบ.", + "clock-rounded-corners-label": "Lekerekรญtett sarkok", + "clock-show-background-description": "Az รณra widget hรกttรฉrkontรฉnerรฉnek megjelenรญtรฉse.", + "clock-show-background-label": "Hรกttรฉr megjelenรญtรฉse", + "clock-show-date-description": "Az aktuรกlis dรกtum megjelenรญtรฉse az idล‘ alatt.", + "clock-show-date-label": "Dรกtum megjelenรญtรฉse", + "clock-show-seconds-description": "Mรกsodpercek megjelenรญtรฉse az idล‘ben.", + "clock-show-seconds-label": "Mรกsodpercek megjelenรญtรฉse", + "clock-style-analog": "Analรณg", + "clock-style-binary": "Binรกris", + "clock-style-description": "Az รณra kijelzรฉsi stรญlusa.", + "clock-style-digital": "Digitรกlis", + "clock-style-label": "ร“ra stรญlus", + "clock-style-minimal": "Minimรกlis", + "clock-width-description": "Az รณra widget szรฉlessรฉge kรฉppontokban.", + "cpu-intensive-note": "! jellel megjelรถlt widgetek tรถbb CPU-t hasznรกlnak, รฉs kรถrรผltekintล‘en kell ล‘ket engedรฉlyezni.", + "edit-mode-button-label": "Szerkesztรฉsi mรณdba lรฉpรฉs", + "edit-mode-controls-explanation": "Bal kattintรกs รฉs hรบzรกs: Widget mozgatรกsa vagy รกtmรฉretezรฉse. Jobb kattintรกs: Kontextus menรผ opciรณinak megnyitรกsa.", + "edit-mode-description": "Engedรฉlyezi a szerkesztรฉsi mรณdot az asztali widgetek mozgatรกsรกhoz รฉs รกthelyezรฉsรฉhez. Ha engedรฉlyezve van, a widgetek hรบzhatรณ kรถrvonalat mutatnak, รฉs รกthelyezhetล‘k.", + "edit-mode-exit-button": "Kilรฉpรฉs a szerkesztรฉsi mรณdbรณl", + "edit-mode-grid-snap-label": "Rรกcshoz igazรญtรกs", + "edit-mode-grid-snap-scale-label": "Rรกcsra illesztรฉs skรกlรกja", + "edit-mode-label": "Szerkesztรฉsi mรณd", + "enabled-description": "Asztali widgetek engedlyezรฉse vagy letiltรกsa.", + "enabled-label": "Asztali widgetek engedรฉlyezรฉse", + "general-desc": "Az asztalon megjelenล‘ widgetek konfigurรกlรกsa.", + "general-title": "Asztali widgetek", + "media-player-enabled-description": "Mรฉdiaplayer widget megjelenรญtรฉse az asztalon.", + "media-player-enabled-label": "Mรฉdiaplayer widget engedรฉlyezรฉse", + "media-player-rounded-corners-description": "Engedรฉlyezi a lekerekรญtett sarkokat a widget szรฉlein.", + "media-player-show-album-art-description": "Az album borรญtรณjรกnak รฉs a szรกmok adatainak (cรญm รฉs elล‘adรณ) megjelenรญtรฉse.", + "media-player-show-album-art-label": "Album borรญtรณ รฉs cรญm megjelenรญtรฉse", + "media-player-show-background-description": "A mรฉdialejรกtszรณ widget hรกttรฉrkontรฉnerรฉnek megjelenรญtรฉse.", + "media-player-show-buttons-description": "Mรฉdiavezรฉrlล‘ gombok (lejรกtszรกs/szรผnet, elล‘zล‘, kรถvetkezล‘) megjelenรญtรฉse a mรฉdia lejรกtszรณ widgeten.", + "media-player-show-buttons-label": "Gombok megjelenรญtรฉse", + "media-player-show-visualizer-description": "Audiovizuรกlis megjelenรญtล‘ fedvรฉny megjelenรญtรฉse.", + "media-player-visualizer-type-description": "Vizualizรกciรณs tรญpus az asztali mรฉdia lejรกtszรณ hรกtterรฉhez.", + "overview-enabled-description": "Asztali vezรฉrlล‘k megjelenรญtรฉse, amรญg a kompozitor รกttekintล‘je nyitva van.", + "overview-enabled-label": "Megjelenรญtรฉs รกttekintรฉsben", + "system-stat-layout-bottom": "Lent", + "system-stat-layout-description": "Hogyan jelenjen meg a jelmagyarรกzat a grafikonhoz kรฉpest.", + "system-stat-layout-label": "Elrendezรฉs", + "system-stat-layout-side": "Oldal", + "system-stat-rounded-corners-description": "Lekerekรญtett sarkok hasznรกlata a widget hรกtterรฉhez.", + "system-stat-rounded-corners-label": "Lekerekรญtett Sarkok", + "system-stat-show-background-description": "A hรกttรฉrkontรฉner megjelenรญtรฉse a rendszerstatisztika widgetben.", + "system-stat-show-background-label": "Hรกttรฉr megjelenรญtรฉse", + "system-stat-stat-type-description": "Mely rendszerstatisztikรกk jelenjenek meg.", + "system-stat-stat-type-label": "Statisztika Tรญpusa", + "title": "Asztali Widgetek", + "weather-enabled-description": "Idล‘jรกrรกs widget megjelenรญtรฉse az asztalon.", + "weather-enabled-label": "Idล‘jรกrรกs widget engedรฉlyezรฉse", + "weather-show-background-description": "Az idล‘jรกrรกs widget hรกttรฉrkontรฉnerรฉnek megjelenรญtรฉse." + }, + "display": { + "monitors-backlight-device-auto-option": "Alapรฉrtelmezett", + "monitors-backlight-device-description": "Hรกttรฉrvilรกgรญtรกsi eszkรถz ehhez a kimenethez.", + "monitors-backlight-device-label": "Hรกttรฉrvilรกgรญtรกsi Eszkรถz", + "monitors-brightness-step-description": "A lรฉpรฉsmรฉret a fรฉnyerล‘ vรกltozรกsokhoz (gรถrgล‘ รฉs gyorsbillentyลฑk).", + "monitors-brightness-step-label": "Fรฉnyerล‘ lรฉpรฉsmรฉret", + "monitors-brightness-unavailable-ddc-disabled": "Fรฉnyerล‘ vezรฉrlรฉs nem รฉrhetล‘ el. Engedรฉlyezd a โ€žKรผlsล‘ fรฉnyerล‘ tรกmogatรกs\" opciรณt a kijelzล‘ fรฉnyerejรฉnek szabรกlyozรกsรกhoz.", + "monitors-brightness-unavailable-generic": "Fรฉnyerล‘ vezรฉrlรฉs nem รฉrhetล‘ el ehhez a kijelzล‘hรถz.", + "monitors-desc": "Fรฉnyerล‘beรกllรญtรกsok minden kijelzล‘hรถz.", + "monitors-enforce-minimum-description": "Megoldja azt a problรฉmรกt, hogy egyes kijelzล‘k hรกttรฉrvilรกgรญtรกsa teljesen kikapcsol 0%-os fรฉnyerล‘nรฉl.", + "monitors-enforce-minimum-label": "Minimรกlis fรฉnyerล‘ (1%) kรฉnyszerรญtรฉse", + "monitors-external-brightness-description": "Engedรฉlyezi a ddcutil tรกmogatรกst a kรผlsล‘ kijelzล‘k fรฉnyerejรฉnek szabรกlyozรกsรกhoz DDC/CI protokollon keresztรผl.", + "monitors-external-brightness-label": "Kรผlsล‘ fรฉnyerล‘ tรกmogatรกs", + "monitors-global-brightness-description": "Az รถsszes monitor fรฉnyerejรฉnek egyidejลฑ beรกllรญtรกsa.", + "monitors-global-brightness-label": "ร–sszes monitor", + "monitors-title": "Monitorkรฉnti beรกllรญtรกsok", + "night-light-auto-schedule-description": "A napnyugta รฉs napkelte idล‘pontja alapjรกn itt: {location} โ€” ajรกnlott.", + "night-light-auto-schedule-label": "Automatikus รผtemezรฉs", + "night-light-desc": "Csรถkkenti a kรฉk fรฉny kibocsรกtรกsรกt, hogy jobban tudj aludni, รฉs csรถkkentse a szem megerล‘ltetรฉsรฉt.", + "night-light-enable-description": "Meleg szรญnszลฑrล‘ alkalmazรกsa a kรฉk fรฉny kibocsรกtรกs csรถkkentรฉse รฉrdekรฉben.", + "night-light-enable-label": "ร‰jszakai fรฉny engedรฉlyezรฉse", + "night-light-force-activation-description": "Figyelmen kรญvรผl hagyja az รผtemezรฉst, รฉs azonnal alkalmazza az รฉjszakai szลฑrล‘t.", + "night-light-force-activation-label": "Kรฉnyszerรญtett aktivรกlรกs", + "night-light-manual-schedule-description": "Egyรฉni idล‘pontok a napkeltรฉre รฉs napnyugtรกra.", + "night-light-manual-schedule-label": "Kรฉzi รผtemezรฉs", + "night-light-manual-schedule-select-start": "Kezdรฉsi idล‘ kivรกlasztรกsa", + "night-light-manual-schedule-select-stop": "Leรกllรญtรกsi idล‘ kivรกlasztรกsa", + "night-light-manual-schedule-sunrise": "Napkelte idล‘pontja", + "night-light-manual-schedule-sunset": "Napnyugta idล‘pontja", + "night-light-temperature-day": "Nap", + "night-light-temperature-day-description": "Szabรกlyozza a hล‘mรฉrsรฉkletet nappal.", + "night-light-temperature-description": "A szรญnhล‘mรฉrsรฉklet รฉjszakai รฉs nappali hasznรกlatra.", + "night-light-temperature-label": "Szรญnhล‘mรฉrsรฉklet", + "night-light-temperature-night": "ร‰jszaka", + "night-light-temperature-night-description": "Szabรกlyozza a hล‘mรฉrsรฉkletet รฉjszaka.", + "title": "Megjelenรฉs" + }, + "dock": { + "appearance-background-opacity-description": "A dokk hรกtterรฉnek รกtlรกtszรณsรกga.", + "appearance-border-radius-description": "A dokk szegรฉlyรฉnek lekerekรญtรฉse.", + "appearance-border-radius-label": "Szegรฉly lekerekรญtรฉs", + "appearance-colorize-icons-description": "A tรฉmakรถr szรญneinek alkalmazรกsa a dokk-alkalmazรกs ikonjaira (csak a nem fรณkuszรกlt alkalmazรกsokra).", + "appearance-colorize-icons-label": "Ikonok szรญnezรฉse", + "appearance-dead-opacity-description": "A nem futรณ alkalmazรกsikonok รกtlรกtszรณsรกga.", + "appearance-dead-opacity-label": "Inaktรญv รกtlรกtszรณsรกg", + "appearance-desc": "A dokk viselkedรฉsรฉnek รฉs megjelenรฉsรฉnek testreszabรกsa.", + "appearance-display-auto-hide": "Automatikus elrejtรฉs", + "appearance-display-description": "A dokk viselkedรฉse.", + "appearance-display-exclusive": "Exkluzรญv", + "appearance-dock-indicator-description": "Egy kis jelzล‘ megjelenรญtรฉse, amikor a dokk rejtve van.", + "appearance-dock-indicator-label": "Dokk jelzล‘", + "appearance-floating-distance-description": "A dokk รฉs a kรฉpernyล‘ szรฉle kรถzรถtti tรกvolsรกg.", + "appearance-floating-distance-label": "Dokk lebegรฉsi tรกvolsรกg", + "appearance-group-apps-description": "Ugyanazon alkalmazรกs tรถbb ablakรกnak csoportosรญtรกsa egy dokk bejegyzรฉsbe.", + "appearance-group-apps-label": "Azonos alkalmazรกsok csoportosรญtรกsa", + "appearance-group-click-action-cycle": "Ablakok vรกltรกsa", + "appearance-group-click-action-description": "Mit csinรกl a bal kattintรกs a csoportosรญtott alkalmazรกsoknรกl.", + "appearance-group-click-action-label": "Csoportosรญtott alkalmazรกs kattintรกsi mลฑvelete", + "appearance-group-click-action-list": "Ablaklista megnyitรกsa", + "appearance-group-context-menu-mode-description": "Hogyan viselkedik a helyi menรผ a csoportosรญtott alkalmazรกsokhoz.", + "appearance-group-context-menu-mode-extended": "Kiterjesztett", + "appearance-group-context-menu-mode-label": "Csoportosรญtott alkalmazรกsmenรผ mรณd", + "appearance-group-context-menu-mode-list": "Ablaklista", + "appearance-group-indicator-style-description": "Hogyan jelenรญtsรฉk meg a csoportosรญtott futรณ indikรกtorok a fรณkuszรกlt ablak รกllapotรกt.", + "appearance-group-indicator-style-dots": "Pontok", + "appearance-group-indicator-style-label": "Csoportosรญtott jelzล‘ stรญlus", + "appearance-group-indicator-style-number": "Szรกm", + "appearance-hide-show-speed-description": "A dokk elrejtรฉsi/megjelenรญtรฉsi animรกciรณjรกnak sebessรฉge.", + "appearance-hide-show-speed-label": "Elrejtรฉsi/megjelenรญtรฉsi sebessรฉg", + "appearance-icon-size-description": "A dokk teljes mรฉrete.", + "appearance-icon-size-label": "Dokk mรฉrete", + "appearance-inactive-indicators-description": "Jelenjenek meg jelzล‘pรถttyรถk minden alkalmazรกsnรกl, ne csak az aktuรกlisan aktรญvnรกl.", + "appearance-inactive-indicators-label": "Futรกsjelzล‘k", + "appearance-indicator-color-description": "A rejtett dokk jelzล‘jรฉnek szรญne.", + "appearance-indicator-color-label": "Jelzล‘ szรญne", + "appearance-indicator-opacity-description": "A rejtett dokk jelzล‘jรฉnek รกtlรกtszรณsรกga.", + "appearance-indicator-opacity-label": "Jelzล‘ รกtlรกtszรณsรกga", + "appearance-indicator-thickness-description": "Vastagabb jelzล‘ hasznรกlata a rejtett dokkhoz (3 px helyett 6 px).", + "appearance-indicator-thickness-label": "Vastagabb jelzล‘", + "appearance-launcher-icon-description": "A dokkindรญtรณhoz hasznรกlt ikon.", + "appearance-launcher-icon-label": "Indรญtรณ ikon", + "appearance-launcher-position-description": "Hol jelenjen meg az indรญtรณ ikonja a dokkban.", + "appearance-launcher-position-end": "Vรฉge", + "appearance-launcher-position-label": "Indรญtรณ pozรญciรณja", + "appearance-launcher-position-start": "Indรญtรกs", + "appearance-launcher-use-distro-logo-description": "A disztribรบciรณ logรณjรกnak hasznรกlata a dokkindรญtรณ ikonja helyett.", + "appearance-launcher-use-distro-logo-label": "Disztribรบciรณ logรณjรกnak hasznรกlata ikon helyett", + "appearance-pinned-static-description": "A rรถgzรญtett alkalmazรกsikonok mindig statikus sorrendben kerรผljenek balra.", + "appearance-pinned-static-label": "Statikus rรถgzรญtett alkalmazรกsok", + "appearance-position-description": "Hol jelenjen meg a dokk a kรฉpernyล‘n.", + "appearance-position-label": "Pozรญciรณ", + "appearance-show-launcher-icon-description": "Az alkalmazรกsindรญtรณ ikon megjelenรญtรฉse a dokkban.", + "appearance-show-launcher-icon-label": "Alkalmazรกsindรญtรณ megjelenรญtรฉse", + "appearance-sit-on-frame-description": "A dokk igazรญtรกsa a keret szegรฉlyรฉn belรผlre, ahelyett, hogy a tetejรฉn รผlne.", + "appearance-sit-on-frame-label": "A dokk a kereten รผl", + "appearance-type-attached": "Rรถgzรญtett", + "appearance-type-description": "Lebegล‘, kapszula alakรบ sรกv vagy statikus, a szรฉlhez rรถgzรญtett sรกv.", + "appearance-type-floating": "Lebegล‘", + "appearance-type-label": "Dokk stรญlus", + "enabled-description": "A dokk teljes megjelenรญtรฉse vagy elrejtรฉse.", + "enabled-label": "Dokk engedรฉlyezรฉse", + "monitors-desc": "Dokk megjelenรญtรฉse meghatรกrozott monitorokon. Alapรฉrtelmezรฉs szerint mindegyiken, ha nincs kivรกlasztva.", + "monitors-only-same-monitor-description": "Csak azok az alkalmazรกsok jelenjenek meg, amelyek azon a monitoron futnak, ahol a dokk talรกlhatรณ.", + "monitors-only-same-monitor-label": "Csak az azonos monitoron lรฉvล‘ alkalmazรกsok", + "monitors-title": "Monitor megjelenรญtรฉs", + "title": "Dokk" + }, + "general": { + "copy-settings": "Beรกllรญtรกsok mรกsolรกsa", + "fonts-default-description": "A felรผleten hasznรกlt fล‘ betลฑtรญpus.", + "fonts-default-label": "Alapรฉrtelmezett betลฑtรญpus", + "fonts-default-placeholder": "Alapรฉrtelmezett betลฑtรญpus kivรกlasztรกsaโ€ฆ", + "fonts-default-scale-description": "Szabvรกnyos szรถveg mรฉretรฉnek nรถvelรฉse vagy csรถkkentรฉse.", + "fonts-default-scale-label": "Alapรฉrtelmezett betลฑmรฉret", + "fonts-default-search-placeholder": "Betลฑtรญpus keresรฉseโ€ฆ", + "fonts-desc": "Az interfรฉszben hasznรกlt betลฑtรญpusok.", + "fonts-monospace-description": "Betลฑtรญpus szรกmok รฉs statisztikรกk megjelenรญtรฉsรฉhez.", + "fonts-monospace-label": "Monospace betลฑtรญpus", + "fonts-monospace-placeholder": "Monospace betลฑtรญpus kivรกlasztรกsaโ€ฆ", + "fonts-monospace-scale-description": "Monospace szรถveg mรฉretรฉnek nรถvelรฉse vagy csรถkkentรฉse.", + "fonts-monospace-scale-label": "Monospace betลฑmรฉret", + "fonts-monospace-search-placeholder": "Monospace betลฑtรญpus keresรฉseโ€ฆ", + "fonts-title": "Betลฑtรญpusok", + "keybinds-conflict-description": "A billentyลฑkombinรกciรณ mรกr hozzรก van rendelve ehhez: {action}.", + "keybinds-conflict-title": "Gyorsbillentyลฑ รผtkรถzรฉs", + "keybinds-description": "Globรกlis navigรกciรณs billentyลฑk beรกllรญtรกsa a panelekhez รฉs az indรญtรณhoz.", + "keybinds-down": "Lefelรฉ mozgatรกs", + "keybinds-enter": "Megerล‘sรญtรฉs / Mลฑvelet", + "keybinds-escape": "Bezรกrรกs / Vissza", + "keybinds-left": "Balra mozgatรกs", + "keybinds-modifier-description": "A gyorsbillentyลฑknek tartalmazniuk kell egy mรณdosรญtรณ billentyลฑt (Ctrl vagy Alt).", + "keybinds-modifier-title": "Mรณdosรญtรณ szรผksรฉges", + "keybinds-remove": "Tรถrlรฉs", + "keybinds-right": "Jobbra mozgatรกs", + "keybinds-title": "Navigรกciรณs Gyorsbillentyลฑk", + "keybinds-up": "Felfelรฉ mozgatรกs", + "language-desc": "Az alkalmazรกs elล‘nyben rรฉszesรญtett nyelvรฉt.", + "language-select-auto-detect": "Automatikus", + "language-select-description": "Az alkalmazรกs felรผletรฉn hasznรกlt nyelv.", + "language-select-label": "Alkalmazรกs nyelve", + "launch-setup-wizard": "Beรกllรญtรกsi varรกzslรณ", + "profile-desc": "Szerkessze felhasznรกlรณi adatait รฉs profilkรฉpรฉt.", + "profile-picture-description": "A profilkรฉp, amely megjelenik a felรผleten.", + "profile-picture-label": "Profilkรฉp", + "profile-select-avatar": "Profilkรฉp kivรกlasztรกsa", + "profile-title": "Profil", + "profile-tooltip": "Profilkรฉp", + "reverse-scrolling-description": "Az รฉrtelmezett gรถrgetรฉsi irรกny megfordรญtรกsa.", + "reverse-scrolling-label": "Gรถrgetรฉs megfordรญtรกsa", + "screen-corners-desc": "A kรฉpernyล‘sarkok lekerekรญtรฉsรฉnek รฉs vizuรกlis effektjeinek testreszabรกsa.", + "screen-corners-radius-description": "A kรฉpernyล‘ lekerekรญtett sarkai.", + "screen-corners-radius-label": "Kรฉpernyล‘sarkok lekerekรญtรฉsi sugara", + "screen-corners-radius-reset": "Kรฉpernyล‘sarkok lekerekรญtรฉsi sugarรกnak visszaรกllรญtรกsa", + "screen-corners-show-corners-description": "Lekerekรญtett sarkok megjelenรญtรฉse a kรฉpernyล‘ szรฉlรฉn.", + "screen-corners-show-corners-label": "Kรฉpernyล‘sarkok megjelenรญtรฉse", + "screen-corners-solid-black-description": "Egyszรญnลฑ fekete szรญn hasznรกlata a sรกv hรกttรฉrszรญne helyett.", + "screen-corners-solid-black-label": "Tรถmรถr fekete sarkok", + "screen-corners-title": "Kรฉpernyล‘sarkok", + "settings-copied": "Beรกllรญtรกsok a vรกgรณlapra mรกsolva", + "smooth-scrolling-description": "Listagรถrgetรฉs animรกlรกsa a gรถrdรผlรฉkenyebb gรถrgล‘รฉlmรฉnyรฉrt.", + "smooth-scrolling-label": "Simรกbb gรถrgetรฉs", + "tab-basics": "Alapok", + "tab-keybinds": "Gyorsbillentyลฑk" + }, + "hooks": { + "color-generation-description": "Parancs, amely akkor fut le, amikor a szรญnlรฉtrehozรกs befejezล‘dik (pl. hรกttรฉrkรฉp alapรบ tรฉma alkalmazรกsa utรกn).", + "color-generation-label": "Szรญnek generรกlva", + "color-generation-placeholder": "pl. notify-send \"Szรญnek\" \"Generรกlva\"", + "info-command-info-description": "โ€ข A parancsok shellen keresztรผl futnak (sh -lc)
โ€ข A parancsok a hรกttรฉrben futnak (levรกlasztva)
โ€ข A tesztgombok az aktuรกlis รฉrtรฉkekkel futnak le", + "info-parameters-description": "โ€ข Wallpaper horog: $1 = hรกttรฉrkรฉp รบtvonala, $2 = kรฉpernyล‘ neve, $3 = tรฉma (sรถtรฉt/vilรกgos)
โ€ข Tรฉmavรกltรณ horog: $1 = igaz/hamis (sรถtรฉt mรณd รกllapot)
โ€ข Kรฉpernyล‘ zรกrolรกsa/feloldรกsa horgok: $1 = zรกrolรกs/feloldรกs (kรฉpernyล‘zรกr รกllapot)
โ€ข Teljesรญtmรฉnymรณd horgok: Nincs paramรฉter
โ€ข Munkamenet horog: $1 = mลฑvelet (leรกllรญtรกs/รบjraindรญtรกs)", + "info-parameters-label": "Elรฉrhetล‘ paramรฉterek", + "noctalia-started-description": "Parancs, ami akkor fut le, amikor a Noctalia betรถltรฉse befejezล‘dรถtt.", + "noctalia-started-label": "Noctalia elindult", + "noctalia-started-placeholder": "pl. notify-send 'Noctalia betรถltve'", + "performance-mode-disabled-description": "Parancs, amely akkor fut le, ha a Noctalia teljesรญtmรฉny mรณd engedรฉlyezve van.", + "performance-mode-disabled-label": "Teljesรญtmรฉny mรณd letiltva", + "performance-mode-disabled-placeholder": "pl. notify-send \"Teljesรญtmรฉny\" \"Mรณd letiltva\"", + "performance-mode-enabled-description": "Parancs, amely akkor fut le, ha a Noctalia teljesรญtmรฉny mรณd engedรฉlyezve van.", + "performance-mode-enabled-label": "Teljesรญtmรฉny mรณd engedรฉlyezve", + "performance-mode-enabled-placeholder": "pl. notify-send \"Teljesรญtmรฉny\" \"Mรณd engedรฉlyezve\"", + "screen-lock-description": "Parancs, amely a kรฉpernyล‘ zรกrolรกsakor fut le.", + "screen-lock-label": "Kรฉpernyล‘ zรกrolva", + "screen-lock-placeholder": "pl. notify-send \"Kรฉpernyล‘\" \"Zรกrolva\"", + "screen-unlock-description": "Parancs, amely a zรกrkรฉpernyล‘ feloldรกsakor fut le.", + "screen-unlock-label": "Kรฉpernyล‘ feloldva", + "screen-unlock-placeholder": "pl. notify-send \"Kรฉpernyล‘\" \"Feloldva\"", + "session-description": "Parancs, amely a leรกllรญtรกs vagy รบjraindรญtรกs elล‘tt vรฉgrehajtรกsra kerรผl. A mลฑvelet tรญpusรกt $1-kรฉnt kapja meg (leรกllรญtรกs/รบjraindรญtรกs).", + "session-label": "Munkamenet vรฉge", + "session-placeholder": "pl., notify-send \"Munkamenet\" \"$1\"", + "system-hooks-desc": "Parancsok, amelyek a rendszeresemรฉnyek bekรถvetkezรฉsekor futnak le.", + "system-hooks-enable-description": "ร–sszes horog parancs engedรฉlyezรฉse vagy letiltรกsa.", + "system-hooks-enable-label": "Horgok engedรฉlyezรฉse", + "system-hooks-title": "Rendszer horgok", + "theme-changed-description": "Parancs, ami akkor fut le, amikor a tรฉma vรกlt a sรถtรฉt รฉs vilรกgos mรณd kรถzรถtt.", + "theme-changed-label": "Tรฉma megvรกltozott", + "theme-changed-placeholder": "pl. notify-send \"Tรฉma\" \"รtvรกltva\"", + "title": "Horgok", + "wallpaper-changed-description": "Parancs, amely a hรกttรฉrkรฉp megvรกltozรกsakor fut le.", + "wallpaper-changed-label": "Hรกttรฉrkรฉp megvรกltozott", + "wallpaper-changed-placeholder": "pl. notify-send \"Hรกttรฉrkรฉp\" \"Megvรกltozott\"" + }, + "idle": { + "custom-add": "Parancs hozzรกadรกsa", + "custom-description": "Futtass egy shell parancsot egy inaktivitรกsi idล‘szak utรกn.", + "custom-entry-command": "Parancs", + "custom-entry-delete": "Tรถrlรฉs", + "custom-entry-edit": "Egyรฉni parancs szerkesztรฉse", + "custom-entry-name": "Nรฉv", + "custom-entry-name-placeholder": "pl. Lรกmpรกk lekapcsolรกsa", + "custom-entry-new": "รšj egyรฉni parancs", + "custom-entry-timeout": "Inaktivitรกsi idล‘", + "custom-entry-timeout-format": "{count} mรกsodperc", + "custom-entry-timeout-format-plural": "{count} mรกsodperc", + "custom-entry-unnamed": "Nรฉvtelen parancs", + "custom-label": "Egyedi tรฉtlensรฉgi parancsok", + "enable-description": "Automatikusan kikapcsolja a kรฉpernyล‘t, zรกrolja vagy felfรผggeszti egy inaktivitรกsi idล‘szak utรกn.", + "enable-label": "Inaktivitรกs kezelรฉs engedรฉlyezรฉse", + "fade-duration-description": "Mรกsodpercek a feketรฉre halvรกnyulรณ animรกciรณhoz, miutรกn minden mลฑvelet elindul. Bรกrmilyen egรฉrmozgรกs megszakรญtja a halvรกnyรญtรกst.", + "fade-duration-label": "รttลฑnรฉs idล‘tartama", + "lock-description": "Inaktivitรกsi mรกsodpercek a zรกrolรกsi kรฉpernyล‘ aktivรกlรกsa elล‘tt.", + "lock-label": "Kรฉpernyล‘zรกr", + "resume-command-label": "Parancs folytatรกsa", + "screen-off-description": "Inaktivitรกs mรกsodpercei, miutรกn a monitorok kikapcsolnak.", + "screen-off-label": "Kรฉpernyล‘ kikapcsolรกsa", + "status-description": "A kompozitor รกltal jelentett inaktivitรกsi idล‘.", + "status-label": "Tรฉtlen idล‘", + "suspend-description": "Mรกsodpercnyi inaktivitรกs, miutรกn a rendszer felfรผggeszti magรกt.", + "tab-behavior": "Viselkedรฉs", + "tab-custom": "Egyรฉni", + "timeouts-description": "0 a szakasz letiltรกsรกhoz. Az idล‘tรบllรฉpรฉsek szรผnetelnek, amรญg az ร‰bren tartรกs aktรญv.", + "timeouts-label": "Idล‘tรบllรฉpรฉsek", + "title": "Tรฉtlensรฉg", + "unavailable": "Natรญv tรฉtlensรฉgfigyelรฉs nem รฉrhetล‘ el ezen a kompozitoron." + }, + "indicator": { + "default-value": "Alapรฉrtelmezett: {value}", + "system-default": "Rendszer alapรฉrtelmezรฉs" + }, + "launcher": { + "clipboard-desc": "Hozzรกfรฉrรฉs รฉs a vรกgรณlap elล‘zmรฉnyeinek kezelรฉse az indรญtรณbรณl.", + "execute-desc": "Alkalmazรกsok indรญtรกsรกnak konfigurรกlรกsa.", + "execute-title": "Vรฉgrehajtรกs", + "settings-annotation-tool-description": "Parancs, amely a jegyzetelรฉs gombra kattintva fut a vรกgรณlap elล‘zmรฉnyeiben. A kรฉp a parancsra lesz รกtirรกnyรญtva.", + "settings-annotation-tool-label": "Annotรกciรณs eszkรถz", + "settings-annotation-tool-placeholder": "pl. โ€žgradiaโ€, โ€žsatty -f -โ€", + "settings-auto-paste-description": "Automatikusan beilleszti a kivรกlasztott vรกgรณlap elemet. Wtype szรผksรฉges hozzรก.", + "settings-auto-paste-label": "Automatikus beillesztรฉs", + "settings-clip-chips-description": "Fรผlmenรผ megjelenรญtรฉse a vรกgรณlap elล‘zmรฉnyek tรญpus szerinti szลฑrรฉsรฉhez (Kรฉpek, Linkek, Fรกjlok, Kรณd stb.).", + "settings-clip-chips-label": "Kategรณria chipek engedรฉlyezรฉse", + "settings-clip-preview-description": "A vรกgรณlap tartalmรกnak elล‘nรฉzetรฉnek megjelenรญtรฉse a >clip parancs hasznรกlatakor.", + "settings-clip-preview-label": "Vรกgรณlap elล‘nรฉzet engedรฉlyezรฉse", + "settings-clip-smart-icons-description": "Mutasson pontos ikonokat linkekhez, fรกjlokhoz, szรญnekhez รฉs kรณdhoz egy รกltalรกnos vรกgรณlap ikon helyett.", + "settings-clip-smart-icons-label": "Intelligens ikonok engedรฉlyezรฉse", + "settings-clip-wrap-text-description": "A szรถveg tรถrdelรฉse a vรกgรณlap listรกban a csonkรญtรกs helyett.", + "settings-clip-wrap-text-label": "Vรกgรณlapszรถveg becsomagolรกsa", + "settings-clipboard-history-description": "Hozzรกfรฉrรฉs a korรกbban mรกsolt elemekhez az indรญtรณbรณl.", + "settings-clipboard-history-label": "Vรกgรณlapelล‘zmรฉnyek engedรฉlyezรฉse", + "settings-clipboard-watch-image-description": "A wl-paste-nek รกtadott teljes parancssor kรฉpmรณdosรญtรกsokhoz (รบjraindรญtรกs szรผksรฉges).", + "settings-clipboard-watch-image-label": "Kรฉpfigyelล‘ Parancs", + "settings-clipboard-watch-text-description": "A wl-paste-nek รกtadott teljes parancssor a szรถvegvรกltozรกsokhoz (รบjraindรญtรกst szรผksรฉges).", + "settings-clipboard-watch-text-label": "Szรถvegfigyelล‘ parancs", + "settings-custom-launch-prefix-description": "Elล‘tagolja a parancsokat egyรฉni indรญtรณval (pl. โ€žrunapp\" a systemd integrรกciรณhoz).", + "settings-custom-launch-prefix-enabled-description": "Egyรฉni elล‘tag hasznรกlata az alkalmazรกsok indรญtรกsรกhoz az alapรฉrtelmezett mรณdszer helyett.", + "settings-custom-launch-prefix-enabled-label": "Egyรฉni indรญtรกsi elล‘tag engedรฉlyezรฉse", + "settings-custom-launch-prefix-label": "Egyรฉni indรญtรกsi elล‘tag", + "settings-density-description": "Az alkalmazรกsikonok mรฉrete รฉs az indรญtรณ sลฑrลฑsรฉge.", + "settings-density-label": "Sลฑrลฑsรฉg", + "settings-desc": "Az indรญtรณ viselkedรฉsรฉnek รฉs megjelenรฉsรฉnek testreszabรกsa.", + "settings-enable-session-search-description": "Munkamenet-mลฑveletek (zรกrolรกs, leรกllรญtรกs, รบjraindรญtรกs stb.) megjelenรญtรฉse az indรญtรณban valรณ keresรฉskor.", + "settings-enable-session-search-label": "Munkamenet-keresรฉs engedรฉlyezรฉse", + "settings-enable-settings-search-description": "Beรกllรญtรกsok megjelenรญtรฉse a talรกlatok kรถzรถtt az indรญtรณn belรผl.", + "settings-enable-settings-search-label": "Beรกllรญtรกsok keresรฉsรฉnek engedรฉlyezรฉse", + "settings-enable-windows-search-description": "Aktรญv ablakok keresรฉse รฉs fรณkuszรกlรกsa.", + "settings-enable-windows-search-label": "Ablakkeresรฉs engedรฉlyezรฉse", + "settings-grid-view-description": "Elemek megjelenรญtรฉse rรกcs elrendezรฉsben lista helyett.", + "settings-icon-mode-description": "Natรญv rendszerikonok hasznรกlata a Tabler ikonok helyett.", + "settings-icon-mode-label": "Natรญv ikonok hasznรกlata", + "settings-ignore-mouse-input-description": "Az egรฉrmลฑveletek รฉs a gรถrgล‘ letiltรกsa az indรญtรณban.", + "settings-ignore-mouse-input-label": "Egรฉrbemenet figyelmen kรญvรผl hagyรกsa", + "settings-overlay-layer-description": "Az indรญtรณ megjelenรญtรฉse az รกtfedล‘ rรฉtegen, a teljes kรฉpernyล‘s ablakok felett. Ha engedรฉlyezve van, az indรญtรณ nem fog a sรกvhoz csatlakozni.", + "settings-overlay-layer-label": "Megjelenรญtรฉs teljes kรฉpernyล‘ felett", + "settings-position-description": "Hol jelenjen meg az indรญtรณ panel.", + "settings-show-categories-description": "Kategรณriafรผlek megjelenรญtรฉse az alkalmazรกsok szลฑrรฉsรฉhez.", + "settings-show-categories-label": "Kategรณriรกk megjelenรญtรฉse", + "settings-show-icon-background-description": "Ikonok mรถgรถtti lekerekรญtett tรฉglalap hรกttรฉr megjelenรญtรฉse.", + "settings-show-icon-background-label": "Ikon hรกttรฉr megjelenรญtรฉse", + "settings-sort-by-usage-description": "Ha engedรฉlyezve van, a gyakran indรญtott alkalmazรกsok jelennek meg elล‘szรถr a listรกn.", + "settings-sort-by-usage-label": "Rendezรฉs gyakorisรกg szerint", + "settings-terminal-command-description": "Parancs a terminรกl indรญtรกsรกhoz. Pl. โ€žkitty -eโ€ vagy โ€žgnome-terminal --โ€.", + "settings-terminal-command-label": "Terminรกl parancs", + "settings-use-app2unit-description": "Alternatรญv indรญtรกsi mรณdszert hasznรกl az alkalmazรกsfolyamatok jobb kezelรฉsรฉre รฉs a problรฉmรกk megelล‘zรฉsรฉre.", + "settings-use-app2unit-label": "App2Unit hasznรกlata alkalmazรกsok indรญtรกsรกhoz", + "settings-view-mode-description": "Az indรญtรณ bejegyzรฉsek elrendezรฉse.", + "settings-view-mode-label": "Nรฉzet mรณd", + "title": "Indรญtรณ" + }, + "location": { + "auto-locate-description": "Automata helymeghatรกrozรกs az IP-cรญm alapjรกn, rendszeres idล‘kรถzรถnkรฉnt.", + "auto-locate-label": "Automatikus helymeghatรกrozรกs", + "calendar-cards-desc": "A kรกrtyรกk rendezรฉsรฉnek engedรฉlyezรฉse/letiltรกsa a naptรกr panelen.", + "calendar-cards-title": "Naptรกrkรกrtyรกk", + "calendar-header-label": "Naptรกr fejlรฉc", + "calendar-month-label": "Naptรกr hรณnap", + "date-time-12hour-format-description": "Idล‘ megjelenรญtรฉse 12 รณrรกs formรกtumban a zรกrkรฉpernyล‘n รฉs a naptรกrban. A sรกvon lรฉvล‘ รณra sajรกt beรกllรญtรกsokkal rendelkezik.", + "date-time-12hour-format-label": "12 รณrรกs idล‘formรกtum hasznรกlata", + "date-time-desc": "Hogyan jelenjen meg a dรกtum รฉs az idล‘.", + "date-time-first-day-of-week-automatic": "Automatikus (rendszeri terรผleti beรกllรญtรกs hasznรกlata)", + "date-time-first-day-of-week-description": "Melyik nap kezdje a hetet a naptรกrban.", + "date-time-first-day-of-week-label": "A hรฉt elsล‘ napja", + "date-time-show-events-description": "Esemรฉnyek megjelenรญtรฉse a naptรกr panelen.", + "date-time-show-events-label": "Naptรกresemรฉnyek megjelenรญtรฉse", + "date-time-title": "Dรกtum รฉs idล‘", + "date-time-use-analog-description": "Analรณg stรญlusรบ รณra megjelenรญtรฉse a naptรกr ablakon.", + "date-time-use-analog-label": "Analรณg stรญlusรบ รณra hasznรกlata", + "date-time-week-numbers-description": "Az รฉv hetรฉnek megjelenรญtรฉse (pl. 38. hรฉt) a naptรกrban.", + "date-time-week-numbers-label": "Hรฉt szรกmok megjelenรญtรฉse", + "geolocate-now-button": "Helymeghatรกrozรกs most", + "location-desc": "Pontos idล‘jรกrรกs รฉs รฉjszakai fรฉny รผtemezรฉs elรฉrรฉse a hely beรกllรญtรกsรกval.", + "location-search-description": "pl. Toronto, ON", + "location-search-label": "Hely keresรฉse", + "location-search-placeholder": "Add meg a hely nevรฉt", + "location-title": "A tartรณzkodรกsi helye", + "weather-desc": "A kรญvรกnt hล‘mรฉrsรฉkleti egysรฉg kivรกlasztรกsa.", + "weather-enabled-description": "Idล‘jรกrรกsi informรกciรณk mutatรกsa a felรผleten, รฉs idล‘jรกrรกsi adatok bekรฉrรฉse. Ha le van tiltva, minden idล‘jรกrรกsi elem el lesz rejtve, รฉs nem tรถrtรฉnik hรกlรณzati kรฉrรฉs.", + "weather-enabled-label": "Idล‘jรกrรกs engedรฉlyezรฉse", + "weather-fahrenheit-description": "Hล‘mรฉrsรฉklet megjelenรญtรฉse Fahrenheitben Celsius helyett.", + "weather-fahrenheit-label": "Hล‘mรฉrsรฉklet megjelenรญtรฉse Fahrenheitben (ยฐF)", + "weather-hide-city-description": "Vรกrosnevek elrejtรฉse az idล‘jรกrรกs kijelzล‘krล‘l a teljes felรผleten.", + "weather-hide-city-label": "Vรกrosnรฉv elrejtรฉse", + "weather-hide-timezone-description": "Az idล‘zรณna rรถvidรญtรฉsรฉnek elrejtรฉse a felรผleten megjelenล‘ รถsszes idล‘jรกrรกs elล‘rejelzรฉsbล‘l.", + "weather-hide-timezone-label": "Idล‘zรณna elrejtรฉse", + "weather-show-effects-description": "Tovรกbbi vizuรกlis effektek (pรฉldรกul esล‘, hรณ vagy villรกmlรกs) megjelenรญtรฉse az idล‘jรกrรกs kรกrtyรกn.", + "weather-show-effects-label": "Idล‘jรกrรกsi effektek megjelenรญtรฉse", + "weather-show-in-calendar-description": "A napi idล‘jรกrรกs-elล‘rejelzรฉs megjelenรญtรฉse kรถzvetlenรผl a naptรกrnรฉzetben.", + "weather-show-in-calendar-label": "Idล‘jรกrรกs megjelenรญtรฉse a naptรกrban", + "weather-talia-mascot-always-description": "Talia hangulatjelek hasznรกlata a Tabler ikonok helyett.", + "weather-talia-mascot-always-label": "Mindig jelenรญtse meg a Talia idล‘jรกrรกs hangulatjeleket" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "Ha az fprintd (ujjlenyomat-hitelesรญtรฉs) aktรญv, ez az opciรณ lehetล‘vรฉ teszi, hogy tovรกbbra is jelszรณval jelentkezz be ujjlenyomat helyett.", + "allow-password-with-fprintd-label": "Jelszavas bejelentkezรฉs engedรฉlyezรฉse fprintd-vel", + "auto-start-auth-description": "Automatikusan elindรญtja az ujjlenyomat-hitelesรญtรฉst anรฉlkรผl, hogy billentyลฑt kellene megnyomni vagy gombra kellene kattintani.", + "auto-start-auth-label": "Automatikus hitelesรญtรฉs indรญtรกskor", + "clock-format-description": "Az รณra formรกtumรกnak testre szabรกsa dรกtum/idล‘ szintaxis tokenek hasznรกlatรกval.", + "clock-format-label": "ร“raformรกtum", + "clock-style-analog": "Analรณg", + "clock-style-custom": "Egyรฉni", + "clock-style-description": "Az รณra vizuรกlis stรญlusa a zรกrolรกsi kรฉpernyล‘n.", + "clock-style-digital": "Digitรกlis", + "clock-style-label": "ร“ra stรญlus", + "compact-lockscreen-description": "Csak a bejelentkezรฉsi beviteli mezล‘ รฉs a rendszervezรฉrlล‘k megjelenรญtรฉse, elrejtve az idล‘jรกrรกs รฉs mรฉdia widgeteket.", + "compact-lockscreen-label": "Kompakt zรกrolรกsi kรฉpernyล‘", + "enable-lockscreen-media-controls-description": "Interaktรญv mรฉdialejรกtszรกsi vezรฉrlล‘k megjelenรญtรฉse a zรกrolรกsi kรฉpernyล‘n.", + "enable-lockscreen-media-controls-label": "Zรกrolรกsi Kรฉpernyล‘ Mรฉdiavezรฉrlล‘i", + "lock-on-suspend-description": "A kรฉpernyล‘ automatikus zรกrolรกsa a rendszer felfรผggesztรฉsekor.", + "lock-on-suspend-label": "Zรกrolรกs felfรผggesztรฉskor", + "lock-screen-animations-description": "A zรกrolรกsi kรฉpernyล‘ animรกciรณinak engedรฉlyezรฉse vagy letiltรกsa.", + "lock-screen-animations-label": "Zรกrolรกsi Kรฉpernyล‘ Animรกciรณk", + "lock-screen-blur-strength-description": "Homรกlyosรญtรณ effektust alkalmaz a zรกrolรกsi kรฉpernyล‘ hรกttรฉrkรฉpรฉre.", + "lock-screen-blur-strength-label": "Zรกrolรกsi kรฉpernyล‘ elmosรกsรกnak erล‘ssรฉge", + "lock-screen-tint-strength-description": "Szรญnezett รกtfedรฉst alkalmaz a zรกrolรกsi kรฉpernyล‘ hรกttรฉrkรฉpรฉre.", + "lock-screen-tint-strength-label": "Zรกrolรกsi kรฉpernyล‘ รกrnyalatรกnak erล‘ssรฉge", + "monitors-desc": "Zรกrolรกsi kรฉpernyล‘ megjelenรญtรฉse meghatรกrozott monitorokon. Alapรฉrtelmezetten mindegyiken megjelenik, ha nincs kivรกlasztva.", + "password-chars-description": "Aranyos ikonok, amelyek elrejtik a jelszavadat.", + "password-chars-label": "Vรฉletlenszerลฑ jelszรณ ikonok", + "show-hibernate-description": "A โ€žhibernรกlรกsโ€ lehetล‘sรฉg megjelenรญtรฉse az energiakezelรฉsi mลฑveletek kรถzรถtt.", + "show-hibernate-label": "Hibernรกlรกs megjelenรญtรฉse", + "show-session-buttons-description": "Engedรฉlyezi a hozzรกfรฉrรฉst az energiabeรกllรญtรกsokhoz a zรกrolรกsi kรฉpernyล‘rล‘l.", + "show-session-buttons-label": "Energiakezelรฉs", + "title": "Zรกrolรกsi kรฉpernyล‘" + }, + "network": { + "bluetooth-description": "A Bluetooth kezelรฉs aktivรกlรกsa.", + "bluetooth-rssi-polling-description": "Idล‘nkรฉnt mintavรฉtelezi a csatlakoztatott eszkรถzรถk RSSI-รฉrtรฉkรฉt a bluetoothctl-en keresztรผl. Nem biztos, hogy minden eszkรถzhรถz elรฉrhetล‘; engedรฉlyezve minimรกlis erล‘forrรกsokat hasznรกl.", + "bluetooth-rssi-polling-label": "Bluetooth jel lekรฉrdezรฉs", + "desc": "Wi-Fi รฉs Bluetooth kapcsolatok kezelรฉse.", + "wifi-description": "Vezetรฉk nรฉlkรผli hรกlรณzatok kezelรฉse (NetworkManager szรผksรฉges)." + }, + "notifications": { + "duration-critical-urgency-description": "Meddig maradjanak lรกthatรณk a kritikus prioritรกsรบ รฉrtesรญtรฉsek.", + "duration-critical-urgency-label": "Kritikus sรผrgล‘ssรฉg", + "duration-desc": "Az รฉrtesรญtรฉsek mennyi ideig maradjanak lรกthatรณk a sรผrgล‘ssรฉgi szintjรผk alapjรกn.", + "duration-low-urgency-description": "Meddig maradjanak lรกthatรณk az alacsony prioritรกsรบ รฉrtesรญtรฉsek.", + "duration-low-urgency-label": "Alacsony sรผrgล‘ssรฉg", + "duration-normal-urgency-description": "Meddig maradjanak lรกthatรณk a normรกl prioritรกsรบ รฉrtesรญtรฉsek.", + "duration-normal-urgency-label": "Normรกl sรผrgล‘ssรฉg", + "duration-respect-expire-description": "Az รฉrtesรญtรฉsben beรกllรญtott lejรกrati idล‘tรบllรฉpรฉs hasznรกlata.", + "duration-respect-expire-label": "Lejรกrati idล‘tรบllรฉpรฉs figyelembe vรฉtele", + "duration-title": "ร‰rtesรญtรฉs idล‘tartama", + "history-clear-dismiss-description": "ร‰rtesรญtรฉs tรถrlรฉse az elล‘zmรฉnyekbล‘l elvetรฉskor.", + "history-clear-dismiss-label": "Elvetรฉskor tรถrlรฉs", + "history-critical-urgency-description": "Kritikus prioritรกsรบ รฉrtesรญtรฉsek mentรฉse az elล‘zmรฉnyekbe.", + "history-critical-urgency-label": "Kritikus sรผrgล‘ssรฉg mentรฉse az elล‘zmรฉnyekbe", + "history-desc": "Mely รฉrtesรญtรฉsek kerรผljenek mentรฉsre az elล‘zmรฉnyekbe a sรผrgล‘ssรฉgi szintjรผk alapjรกn.", + "history-low-urgency-description": "Alacsony prioritรกsรบ รฉrtesรญtรฉsek mentรฉse az elล‘zmรฉnyekbe.", + "history-low-urgency-label": "Alacsony sรผrgล‘ssรฉg mentรฉse az elล‘zmรฉnyekbe", + "history-normal-urgency-description": "Normรกl prioritรกsรบ รฉrtesรญtรฉsek mentรฉse az elล‘zmรฉnyekbe.", + "history-normal-urgency-label": "Normรกl sรผrgล‘ssรฉg mentรฉse az elล‘zmรฉnyekbe", + "monitors-desc": "ร‰rtesรญtรฉs megjelenรญtรฉse meghatรกrozott monitorokon. Alapรฉrtelmezetten mindegyiken, ha nincs kivรกlasztva.", + "rules-action-block": "Blokk", + "rules-action-block-desc": "Teljesen kihagyja.", + "rules-action-hide": "Elrejtรฉs", + "rules-action-hide-desc": "Nincs felugrรณ ablak, nincs hang, hozzรกadja az elล‘zmรฉnyekhez.", + "rules-action-label": "Mลฑvelet", + "rules-action-mute": "Nรฉmรญtรกs", + "rules-action-mute-desc": "Nincs hang, de megjelenรญti az elล‘ugrรณ ablakot รฉs az elล‘zmรฉnyekbe rakja.", + "rules-add": "Szabรกly hozzรกadรกsa", + "rules-delete": "Szabรกly tรถrlรฉse", + "rules-description": "Alkalmazรกsnรฉv vagy tartalom szerinti egyezรฉs. A szabรกlyok ellenล‘rzรฉse sorrendben tรถrtรฉnik, รฉs az elsล‘ talรกlat lรฉp รฉrvรฉnybe.", + "rules-edit": "Szabรกly szerkesztรฉse", + "rules-label": "Szลฑrล‘szabรกlyok", + "rules-pattern-label": "Minta", + "rules-pattern-placeholder": "firefox, discord, vagy /regex/", + "rules-tab": "Szabรกlyok", + "settings-always-on-top-description": "ร‰rtesรญtรฉsek megjelenรญtรฉse teljes kรฉpernyล‘s ablakok รฉs mรกs rรฉtegek felett.", + "settings-background-opacity-description": "Az รฉrtesรญtรฉsi hรกtterek รกtlรกtszรณsรกgรกnak beรกllรญtรกsa.", + "settings-density-description": "Az รฉrtesรญtรฉsi kรกrtya sลฑrลฑsรฉge.", + "settings-density-label": "Sลฑrลฑsรฉg", + "settings-desc": "Az รฉrtesรญtรฉsek megjelenรฉsรฉnek รฉs viselkedรฉsรฉnek konfigurรกlรกsa.", + "settings-do-not-disturb-description": "Letiltja az รถsszes รฉrtesรญtรฉsi felugrรณ ablakot, ha engedรฉlyezve van.", + "settings-enabled-description": "Az รฉrtesรญtรฉsi daemon engedรฉlyezรฉse vagy letiltรกsa a Noctalia shell รบjraindรญtรกsรกt igรฉnyli.", + "settings-enabled-label": "ร‰rtesรญtรฉsek engedรฉlyezรฉse", + "settings-location-description": "Ahol az รฉrtesรญtรฉsek megjelennek a kรฉpernyล‘n.", + "settings-markdown-description": "ร‰rtesรญtรฉs tartalmรกnak megjelenรญtรฉse Markdown formรกzรกssal.", + "settings-markdown-label": "Markdown engedรฉlyezรฉse", + "sounds-desc": "ร‰rtesรญtรฉsi hangeffektusok รฉs hangerล‘ beรกllรญtรกsa.", + "sounds-enabled-description": "Hanghatรกsok engedรฉlyezรฉse a bejรถvล‘ รฉrtesรญtรฉsekhez.", + "sounds-enabled-label": "ร‰rtesรญtรฉsi hangok engedรฉlyezรฉse", + "sounds-excluded-apps-description": "A beรกllรญtott รฉrtesรญtรฉsi hang lejรกtszรกsรกnak kihagyรกsa bizonyos alkalmazรกsoknรกl, amelyek sajรกt beรฉpรญtett hangokkal rendelkeznek.", + "sounds-excluded-apps-label": "Kizรกrt alkalmazรกsok", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "A kritikus prioritรกsรบ รฉrtesรญtรฉsekhez lejรกtszott hangfรกjl รบtvonala.", + "sounds-files-critical-label": "Kritikus sรผrgล‘ssรฉgi hang", + "sounds-files-critical-select-title": "Kritikus sรผrgล‘ssรฉgi hangfรกjl kivรกlasztรกsa", + "sounds-files-desc": "Egyรฉni hangfรกjlok konfigurรกlรกsa a kรผlรถnbรถzล‘ รฉrtesรญtรฉsi sรผrgล‘ssรฉgi szintekhez.", + "sounds-files-low-description": "Az alacsony prioritรกsรบ รฉrtesรญtรฉsekhez lejรกtszott hangfรกjl รบtvonala.", + "sounds-files-low-label": "Alacsony sรผrgล‘ssรฉgi hang", + "sounds-files-low-select-title": "Alacsony sรผrgล‘ssรฉgi hangfรกjl kivรกlasztรกsa", + "sounds-files-normal-description": "A normรกl prioritรกsรบ รฉrtesรญtรฉsekhez lejรกtszott hangfรกjl รบtvonala.", + "sounds-files-normal-label": "Normรกl sรผrgล‘ssรฉgi hang", + "sounds-files-normal-select-title": "Normรกl sรผrgล‘ssรฉgi hangfรกjl kivรกlasztรกsa", + "sounds-files-placeholder": "Add meg a hangfรกjl รบtvonalรกt", + "sounds-files-select-file": "Hangfรกjl kivรกlasztรกsa", + "sounds-files-title": "Hangfรกjlok", + "sounds-files-unified-description": "Az รฉrtesรญtรฉsekhez lejรกtszott hangfรกjl รบtvonala.", + "sounds-files-unified-label": "ร‰rtesรญtรฉsi hang", + "sounds-files-unified-select-title": "ร‰rtesรญtรฉsi hangfรกjl kivรกlasztรกsa", + "sounds-separate-description": "Kรผlรถnbรถzล‘ hangfรกjlok hasznรกlata az alacsony, normรกl รฉs kritikus prioritรกsรบ รฉrtesรญtรฉsekhez.", + "sounds-separate-label": "Kรผlรถnbรถzล‘ hangok hasznรกlata prioritรกs szerint", + "sounds-title": "Hangbeรกllรญtรกsok", + "sounds-unavailable-description": "Telepรญtsd a Qt6 Multimรฉdia csomagot az รฉrtesรญtรฉsi hangok engedรฉlyezรฉsรฉhez.", + "sounds-unavailable-label": "ร‰rtesรญtรฉsi hangok nem รฉrhetล‘k el", + "sounds-volume-description": "Az รฉrtesรญtรฉsi hangok hangerejรฉnek beรกllรญtรกsa.", + "sounds-volume-label": "Hangerล‘", + "toast-battery-description": "Figyelmeztetรฉs megjelenรญtรฉse, ha az akkumulรกtor tรถltรถttsรฉgi szintje ez alรก a szรกzalรฉk alรก esik.", + "toast-battery-label": "Akkumulรกtor figyelmeztetรฉs", + "toast-desc": "Felugrรณ รผzenet megjelenรฉsรฉnek รฉs viselkedรฉsรฉnek beรกllรญtรกsa.", + "toast-keyboard-description": "Felugrรณ รผzenet megjelenรญtรฉse a billentyลฑzetkiosztรกs vรกltozรกsakor.", + "toast-keyboard-label": "Billentyลฑzetkiosztรกs", + "toast-media-description": "Felugrรณ รผzenet megjelenรญtรฉse a mรฉdia lejรกtszรกsi รกllapotรกnak vรกltozรกsakor.", + "toast-media-label": "Mรฉdia" + }, + "osd": { + "always-on-top-description": "Kรฉpernyล‘kijelzล‘k megjelenรญtรฉse teljes kรฉpernyล‘s ablakok รฉs mรกs rรฉtegek felett.", + "always-on-top-label": "Mindig felรผl", + "background-opacity-description": "A kรฉpernyล‘kijelzล‘k hรกtterรฉnek รกtlรกtszรณsรกga.", + "background-opacity-label": "Hรกttรฉr รกtlรกtszatlansรกga", + "description": "Kรฉpernyล‘kijelzล‘k, pรฉldรกul hangerล‘- รฉs fรฉnyerล‘-รกtfedรฉsek beรกllรญtรกsa.", + "duration-auto-hide-description": "Az idล‘, miutรกn a kรฉpernyล‘kijelzล‘ eltลฑnik.", + "duration-auto-hide-label": "Automatikus elrejtรฉs ennyi idล‘ utรกn", + "duration-desc": "Mennyi ideig marad lรกthatรณ a kรฉpernyล‘kijelzล‘, mielล‘tt automatikusan elrejti magรกt.", + "duration-title": "Automatikus elrejtรฉsi idล‘tรบllรฉpรฉs", + "enabled-description": "A hangerล‘ รฉs a fรฉnyerล‘ vรกltozรกsainak megjelenรญtรฉse valรณs idล‘ben.", + "enabled-label": "Kรฉpernyล‘kijelzรฉs engedรฉlyezรฉse", + "events-desc": "Mely esemรฉnyek indรญtsรกk el a kรฉpernyล‘kijelzล‘t.", + "general-desc": "A kรฉpernyล‘kijelzล‘k lรกthatรณsรกgรกnak รฉs viselkedรฉsรฉnek konfigurรกlรกsa.", + "location-description": "A kรฉpernyล‘kijelzล‘k helye.", + "monitors-desc": "Kรฉpernyล‘kijelzล‘ megjelenรญtรฉse meghatรกrozott monitorokon. Alapรฉrtelmezรฉs szerint az รถsszes monitoron, ha nincs kivรกlasztva egy.", + "title": "Kรฉpernyล‘kijelzล‘k", + "types-brightness-description": "Kรฉpernyล‘kijelzล‘ megjelenรญtรฉse a kรฉpernyล‘ fรฉnyerejรฉnek vรกltozรกsakor.", + "types-brightness-label": "Fรฉnyerล‘", + "types-custom-text-description": "Kรฉpernyล‘kijelzล‘ megjelenรญtรฉse egyรฉni szรถveges รผzenetekhez IPC-bล‘l.", + "types-custom-text-label": "Egyรฉni szรถveg", + "types-desc": "A kรฉpernyล‘kijelzล‘t kivรกltรณ esemรฉnyeket.", + "types-input-volume-description": "Kรฉpernyล‘kijelzล‘ megjelenรญtรฉse a mikrofon hangerejรฉnek vรกltozรกsakor.", + "types-input-volume-label": "Bemeneti hangerล‘", + "types-lockkey-description": "Kรฉpernyล‘kijelzล‘ megjelenรญtรฉse a Caps Lock, Num Lock vagy Scroll Lock vรกltรกsakor.", + "types-lockkey-label": "Zรกrolรกsi gombok", + "types-media-description": "Kรฉpernyล‘kijelzล‘ megjelenรญtรฉse a mรฉdia lejรกtszรกsi รกllapotรกnak vรกltozรกsakor (lejรกtszรกs, szรผnet, ugrรกs).", + "types-media-label": "Mรฉdia lejรกtszรกs", + "types-title": "Kรฉpernyล‘kijelzล‘t kivรกltรณ esemรฉnyek", + "types-volume-description": "Kรฉpernyล‘kijelzล‘ megjelenรญtรฉse a hangkimenet hangerejรฉnek vรกltozรกsakor.", + "types-volume-label": "Kimeneti hangerล‘" + }, + "plugins": { + "auto-update": "Bล‘vรญtmรฉnyek automatikus frissรญtรฉse", + "auto-update-description": "Automatikusan frissรญtse az รถsszes plugint a shell indรญtรกsakor.", + "available-description": "Bล‘vรญtmรฉnyek bรถngรฉszรฉse รฉs telepรญtรฉse a beรกllรญtott forrรกsokbรณl.", + "available-label": "Elรฉrhetล‘ bล‘vรญtmรฉnyek", + "available-no-plugins-description": "Ellenล‘rizze a bล‘vรญtmรฉnyforrรกsokat, vagy frissรญtse a listรกt.", + "available-no-plugins-label": "Nincsenek elรฉrhetล‘ bล‘vรญtmรฉnyek", + "check-for-updates": "Frissรญtรฉsek keresรฉse", + "checking-for-updates": "Frissรญtรฉsek keresรฉseโ€ฆ", + "collision-already-installed": "Ez a bล‘vรญtmรฉny mรกr telepรญtve van", + "collision-custom-version-exists": "Egyรฉni verziรณ a kรถvetkezล‘ helyrล‘l: โ€ž{source}โ€ mรกr telepรญtve van", + "collision-official-version-exists": "A bล‘vรญtmรฉny hivatalos verziรณja mรกr telepรญtve van", + "development-disable": "Fejlesztล‘i mรณd letiltรกsa ennรฉl a bล‘vรญtmรฉnynรฉl", + "development-enable": "Fejlesztล‘i mรณd engedรฉlyezรฉse ehhez a beรฉpรผlล‘hรถz.", + "filter-downloaded": "Letรถltรถtt", + "filter-not-downloaded": "Nincs letรถltve", + "filter-tags-description": "Bล‘vรญtmรฉnyek szลฑrรฉse kategรณria vagy letรถltรฉsi รกllapot szerint.", + "filter-tags-label": "Cรญmkรฉk", + "hot-reload-description": "Automatikusan รบjratรถlti a bล‘vรญtmรฉnyeket, amikor a fรกjljaik megvรกltoznak. Hasznos a bล‘vรญtmรฉnyfejlesztรฉshez.", + "hot-reload-label": "Gyors รบjratรถltรฉs (fejlesztล‘i mรณd)", + "hot-reloaded": "รšjratรถltรถtt bล‘vรญtmรฉny: {name}", + "install-error": "Sikertelen telepรญtรฉs: {error}", + "install-incompatible": "A {plugin} bล‘vรญtmรฉnyhez Noctalia v{version} vagy รบjabb verziรณ szรผksรฉges", + "install-success": "Sikeresen telepรญtve: {plugin}", + "installed-description": "Az รถsszes helyileg telepรญtett bล‘vรญtmรฉny kezelรฉse รฉs beรกllรญtรกsa.", + "installed-label": "Telepรญtett bล‘vรญtmรฉnyek", + "installed-no-plugins-description": "Bล‘vรญtmรฉnyek telepรญtรฉse az โ€žElรฉrhetล‘โ€ szakaszban lehetsรฉges.", + "installed-no-plugins-label": "Nincs telepรญtett bล‘vรญtmรฉny", + "installing": "{plugin} telepรญtรฉseโ€ฆ", + "notify-updates": "Bล‘vรญtmรฉnyfrissรญtรฉsi รฉrtesรญtรฉsek", + "notify-updates-description": "ร‰rtesรญtรฉsi elล‘ugrรณ ablak megjelenรญtรฉse, ha รบj bล‘vรญtmรฉnyfrissรญtรฉsek รฉrhetล‘k el.", + "open-plugin-page": "Bล‘vรญtmรฉny oldal megnyitรกsa", + "open-plugins-tab": "Bล‘vรญtmรฉnyek beรกllรญtรกsainak megnyitรกsa", + "plugin-settings-title": "{plugin} beรกllรญtรกsai", + "refresh-refreshing": "Bล‘vรญtmรฉnyek listรกjรกnak frissรญtรฉseโ€ฆ", + "refresh-tooltip": "Elรฉrhetล‘ bล‘vรญtmรฉnyek frissรญtรฉse", + "settings-error-not-loaded": "Bล‘vรญtmรฉny nincs betรถltve", + "settings-saved": "Bล‘vรญtmรฉny beรกllรญtรกsai mentve", + "settings-tooltip": "Bล‘vรญtmรฉny beรกllรญtรกsai", + "source-custom": "Egyรฉni forrรกs", + "sources-add-custom": "Egyรฉni tรกrolรณ hozzรกadรกsa", + "sources-add-dialog-description": "GitHub tรกrolรณ hozzรกadรกsa bล‘vรญtmรฉnyforrรกskรฉnt.", + "sources-add-dialog-error": "Sikertelen bล‘vรญtmรฉnyforrรกs hozzรกadรกs", + "sources-add-dialog-name": "Tรกrolรณ neve", + "sources-add-dialog-name-placeholder": "Egyรฉni bล‘vรญtmรฉnyeim", + "sources-add-dialog-success": "Bล‘vรญtmรฉnyforrรกs sikeresen hozzรกadva", + "sources-add-dialog-title": "Bล‘vรญtmรฉnyforrรกs hozzรกadรกsa", + "sources-add-dialog-url": "Tรกrolรณ URL-cรญme", + "sources-description": "Bล‘vรญtmรฉnytรกrolรณk kezelรฉse.", + "sources-placeholder": "Sajรกt menล‘ tรกrolรณm", + "sources-remove-tooltip": "Bล‘vรญtmรฉnyforrรกs eltรกvolรญtรกsa", + "title": "Bล‘vรญtmรฉnyek", + "translations-reloaded": "Fordรญtรกsok รบjratรถltve: {name}", + "uninstall-dialog-description": "Biztosan el szeretnรฉd tรกvolรญtani a(z) {plugin} bล‘vรญtmรฉnyt? Ez eltรกvolรญtja az รถsszes bล‘vรญtmรฉnyadatot.", + "uninstall-dialog-title": "Bล‘vรญtmรฉny eltรกvolรญtรกsa", + "uninstall-error": "Sikertelen eltรกvolรญtรกs: {error}", + "uninstall-success": "{plugin} sikeresen eltรกvolรญtva", + "uninstalling": "{plugin} eltรกvolรญtรกsaโ€ฆ", + "update-all": "ร–sszes frissรญtรฉse ({count})", + "update-all-success": "Minden bล‘vรญtmรฉny sikeresen frissรญtve", + "update-available": "รšj bล‘vรญtmรฉnyfrissรญtรฉs elรฉrhetล‘", + "update-available-plural": "รšj bล‘vรญtmรฉnyfrissรญtรฉsek elรฉrhetล‘k ({count})", + "update-error": "Nem sikerรผlt frissรญteni a bล‘vรญtmรฉnyt: {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (szรผksรฉges Noctalia v{required})", + "update-success": "A(z) {plugin} frissรญtve lett a v{version} verziรณra", + "update-version": "v{current} โ†’ v{new}", + "updating": "Frissรญtรฉsโ€ฆ" + }, + "region": { + "title": "Rรฉgiรณ" + }, + "session-menu": { + "countdown-duration-description": "Mennyi ideig tart a visszaszรกmlรกlรกs az energiagazdรกlkodรกsi mลฑveletek vรฉgrehajtรกsa elล‘tt.", + "countdown-duration-label": "Visszaszรกmlรกlรกs idล‘tartama", + "enable-countdown-description": "Visszaszรกmlรกlรกs megjelenรญtรฉse az energiagazdรกlkodรกsi mลฑveletek vรฉgrehajtรกsa elล‘tt.", + "enable-countdown-label": "Visszaszรกmlรกlรณ engedรฉlyezรฉse", + "entries-desc": "Mely energiagazdรกlkodรกsi mลฑveletek jelenjenek meg a munkamenet menรผben, รฉs milyen sorrendben.", + "entries-title": "Energiagazdรกlkodรกsi mลฑveletek", + "entry-settings-command-description": "Egyรฉni parancs a mลฑvelet vรฉgrehajtรกsรกhoz. Hagyd รผresen az alapรฉrtelmezett rendszerparancs hasznรกlatรกhoz.", + "entry-settings-command-placeholder": "pl. systemctl poweroff", + "entry-settings-default-command-lock": "Belsล‘ zรกrolรกsi kรฉpernyล‘ (nincs parancs)", + "entry-settings-default-command-logout": "Belsล‘ kijelentkezรฉs (nincs parancs)", + "entry-settings-default-info-description": "Ha nincs megadva egyรฉni parancs, az alapรฉrtelmezett rendszerparancs kerรผl felhasznรกlรกsra.", + "entry-settings-default-info-label": "Alapรฉrtelmezett parancs", + "entry-settings-keybind-description": "Rendeljen hozzรก billentyลฑkombinรกciรณt ezen mลฑvelet elindรญtรกsรกhoz, amikor a munkamenet menรผ nyitva van.", + "entry-settings-keybind-placeholder": "Nyomd meg a Felvรฉtel gombot egy billentyลฑ hozzรกrendelรฉsรฉhezโ€ฆ", + "entry-settings-keybind-recording": "Felvรฉtelโ€ฆ Nyomj meg bรกrmilyen billentyลฑkombinรกciรณt", + "entry-settings-title": "{entry} konfigurรกlรกsa", + "entry-settings-tooltip": "Parancs konfigurรกlรกsa", + "general-desc": "A munkamenet menรผpanel viselkedรฉsรฉnek รฉs megjelenรฉsรฉnek konfigurรกlรกsa.", + "large-buttons-layout-description": "Hogyan jelenjenek meg a munkamenรผ menรผgombjai.", + "large-buttons-layout-label": "Nagy gombok elrendezรฉse", + "large-buttons-style-description": "A munkamenet menรผ megjelenรญtรฉse nagy gombokkal, rรกcsos elrendezรฉsben.", + "large-buttons-style-label": "Nagymรฉretลฑ gombok stรญlusa", + "position-description": "Hol jelenjen meg a munkamenet menรผpanel megnyitรกskor.", + "show-header-description": "A cรญm รฉs a bezรกrรกs gomb megjelenรญtรฉse a munkamenet menรผ tetejรฉn.", + "show-header-label": "Fejlรฉc megjelenรญtรฉse", + "show-keybinds-description": "Gyorsbillentyลฑ tippek megjelenรญtรฉse a munkamenet-opciรณknรกl.", + "show-keybinds-label": "Gyorsbillentyลฑk megjelenรญtรฉse" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Az asztali widgetek lรกthatรณak maradnak, amรญg a Noctalia teljesรญtmรฉny mรณd engedรฉlyezve van.", + "noctalia-performance-disable-desktop-widgets-label": "Engedรฉlyezi az asztali widgeteket teljesรญtmรฉny mรณdban", + "noctalia-performance-disable-wallpaper-description": "Az asztali, รกttekintล‘ รฉs zรกrolรกsi kรฉpernyล‘ hรกttรฉrkรฉpek lรกthatรณak maradnak, amรญg a Noctalia teljesรญtmรฉny mรณd engedรฉlyezve van.", + "noctalia-performance-disable-wallpaper-label": "Engedรฉlyezi a hรกttรฉrkรฉp renderelรฉsรฉt teljesรญtmรฉny mรณdban", + "title": "Rendszer" + }, + "system-monitor": { + "critical-color-label": "Kritikus szรญn", + "custom-highlight-colors-title-label": "Egyรฉni kiemelล‘szรญnek", + "disk-available-label": "Rendelkezรฉsre รกllรณ lemezterรผlet", + "disk-section-label": "Lemezhasznรกlat", + "enable-dgpu-monitoring-description": "Figyelem: Ez felรฉbreszti a dedikรกlt GPU-t (NVIDIA/AMD), ami jelentล‘sen befolyรกsolhatja az akkumulรกtor รฉlettartamรกt a hibrid grafikรกval rendelkezล‘ laptopokon.", + "enable-dgpu-monitoring-label": "Dedikรกlt GPU figyelรฉs engedรฉlyezรฉse", + "external-monitor-description": "Parancs vagy alkalmazรกs elรฉrรฉsi รบtja a kรผlsล‘ rendszermonitor alkalmazรกs indรญtรกsรกhoz.", + "external-monitor-label": "Kรผlsล‘ rendszermonitor parancs", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "A rendszerfigyelล‘ viselkedรฉsรฉnek รฉs megjelenรฉsรฉnek beรกllรญtรกsa.", + "gpu-section-label": "GPU-hล‘mรฉrsรฉklet", + "highlight-colors-section-label": "Kiemelล‘szรญnek", + "polling-interval-label": "Lekรฉrdezรฉsi intervallum", + "polling-section-description": "Milyen gyakran frissรผljenek az egyes rendszer metrikรกk.", + "polling-section-label": "Lekรฉrdezรฉsi idล‘kรถzรถk", + "threshold-critical": "Kritikus", + "threshold-warning": "Figyelmeztetรฉs", + "thresholds-section-description": "A figyelmeztetรฉsi/kritikus kรผszรถbรฉrtรฉkek รฉs a lekรฉrdezรฉsi intervallumok minden rendszermutatรณhoz.", + "use-custom-highlight-colors-description": "Ha le van tiltva, a tรฉma alapรฉrtelmezett kiemelล‘szรญnei kerรผlnek hasznรกlatra.", + "use-custom-highlight-colors-label": "Egyรฉni kiemelล‘szรญnek hasznรกlata", + "warning-color-label": "Figyelmeztetรฉsi szรญn" + }, + "user-interface": { + "allow-panels-without-bar-description": "Ha engedรฉlyezve van, a panelek bรกrmely kรฉpernyล‘n megnyithatรณk. Ha le van tiltva, a panelek csak olyan kรฉpernyล‘kรถn nyรญlnak meg, ahol van sรกv, ami csรถkkentheti a memรณriahasznรกlatot.", + "allow-panels-without-bar-label": "Panelek engedรฉlyezรฉse sรกv nรฉlkรผli kรฉpernyล‘kรถn", + "animation-disable-description": "Az รถsszes animรกciรณ letiltรกsa a gyorsabb, reszponzรญvabb รฉlmรฉny รฉrdekรฉben.", + "animation-disable-label": "A felhasznรกlรณi felรผlet animรกciรณinak letiltรกsa", + "animation-speed-description": "A globรกlis animรกciรณs sebessรฉg.", + "animation-speed-label": "Animรกciรณs sebessรฉg", + "animation-speed-reset": "Animรกciรณs sebessรฉg visszaรกllรญtรกsa", + "appearance-desc": "A vizuรกlis elemek (pรฉldรกul az eszkรถztippek), a szegรฉlyek รฉs az รกrnyรฉkok testreszabรกsa.", + "blur-behind-description": "Elhomรกlyosรญtja a sรกvok รฉs panelek mรถgรถtti terรผletet a kompozitor elmosรกsi protokolljรกnak hasznรกlatรกval.", + "blur-behind-label": "Hรกttรฉr elmosรกsa", + "box-border-description": "Tartalomterรผletek kรถrรฉ vรกzlatot jelenรญt meg.", + "box-border-label": "Kontรฉner kรถrvonal", + "box-border-radius-description": "A fล‘ elrendezรฉsi szakaszok (pรฉldรกul oldalsรกvok, kรกrtyรกk รฉs tartalompanelek) saroklekerekรญtรฉse.", + "box-border-radius-label": "Tรกrolรณ sugรกr", + "control-border-radius-description": "Az interaktรญv elemek (gombok, kapcsolรณk รฉs szรถvegmezล‘k) gรถrbรผlete.", + "control-border-radius-label": "Bemeneti sugรกr", + "desc": "Az interfรฉsz megjelenรฉsรฉnek, รฉrzetรฉnek รฉs viselkedรฉsรฉnek testreszabรกsa.", + "dim-desktop-description": "Asztal elsรถtรฉtรญtรฉse, ha a panelek vagy menรผk nyitva vannak.", + "dim-desktop-label": "Asztal elsรถtรฉtรญtรฉse", + "dimmer-opacity-description": "Az asztal elsรถtรฉtรญtรฉsรฉnek รกtlรกtszรณsรกgi szintje.", + "dimmer-opacity-label": "Elsรถtรฉtรญtett asztal รกtlรกtszรณsรกga", + "panel-background-opacity-description": "Az รถsszes panel (sรกv, indรญtรณ, beรกllรญtรกsok stb.) hรกttรฉrรฉnek รกtlรกtszรณsรกga.", + "panel-background-opacity-label": "Panel hรกttรฉr รกtlรกtszรณsรกg", + "panels-attached-to-bar-description": "A panelek a sรกvhoz รฉs a kรฉpernyล‘ szรฉleihez igazodnak, zรถkkenล‘mentes megjelenรฉst biztosรญtva stรญlusos fordรญtott sarkokkal.", + "panels-attached-to-bar-label": "Panelek szรฉlekhez illesztรฉse", + "panels-overlay-description": "Biztosรญtja, hogy a panelek รฉs a sรกv lรกthatรณak maradjanak, mรฉg teljes kรฉpernyล‘s alkalmazรกsok felett is.", + "panels-overlay-label": "Panelek รฉs sรกv felรผl tartรกsa", + "scaling-description": "Megvรกltoztatja az รกltalรกnos felhasznรกlรณi felรผlet mรฉretรฉt, kivรฉve a sรกvot.", + "scaling-label": "Felรผlet mรฉretezรฉs", + "scrollbar-always-visible-description": "Gรถrgetล‘sรกvok lรกthatรณan tartรกsa, amikor a tartalom gรถrgethetล‘, ahelyett, hogy csak rรกmutatรกskor jelennรฉnek meg.", + "scrollbar-always-visible-label": "Mindig jelenรญtse meg a gรถrgetล‘sรกvokat", + "settings-panel-header": "Beรกllรญtรกsok panel", + "settings-panel-mode-description": "A beรกllรญtรกsok elrendezรฉse (รบjraindรญtรกst igรฉnyelhet).", + "settings-panel-mode-label": "Beรกllรญtรกsi panel mรณd", + "settings-panel-sidebar-card-style-description": "Lekerekรญtett sarkokkal burkolja a beรกllรญtรกsok oldalsรกvjรกt egy kitรถltรถtt hรกttรฉrbe.", + "settings-panel-sidebar-card-style-label": "Oldalsรกv kรกrtyastรญlus", + "shadows-description": "Engedรฉlyezi az รกrnyรฉkokat a sรกvok รฉs panelek alatt.", + "shadows-direction-description": "Hovรก vetล‘djรถn az รกrnyรฉk.", + "shadows-direction-label": "รrnyรฉk irรกnya", + "shadows-label": "รrnyรฉkok", + "title": "Felhasznรกlรณi felรผlet", + "tooltips-description": "Engedรฉlyezi vagy letiltja az eszkรถztippeket a felรผleten.", + "tooltips-label": "Eszkรถztippek megjelenรญtรฉse", + "translucent-widgets-description": "Fรฉlig รกtlรกtszรณvรก teszi a gombokat, fรผleket รฉs egyรฉb widgeteket a paneleken belรผl.", + "translucent-widgets-label": "รttetszล‘ widgetek" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "รbรฉcรฉrend", + "automation-change-mode-description": "Hogyan vรกlasztรณdnak ki a hรกttรฉrkรฉpek automatikus vรกltรกskor.", + "automation-change-mode-label": "Vรกltรกsi mรณd", + "automation-custom-interval-description": "Add meg az idล‘t ร“ร“:PP formรกtumban (pl. 01:30).", + "automation-custom-interval-label": "Egyรฉni intervallum", + "automation-interval-description": "Milyen gyakran vรกltozzon automatikusan a hรกttรฉrkรฉp.", + "automation-interval-label": "Hรกttรฉrkรฉp intervallum", + "automation-random-wallpaper-description": "รœtemezze a vรฉletlenszerลฑ hรกttรฉrkรฉpvรกltรกsokat rendszeres idล‘kรถzรถnkรฉnt.", + "automation-scheduled-change-description": "Automatikusan vรกltoztassa a hรกttรฉrkรฉpeket rendszeres idล‘kรถzรถnkรฉnt.", + "automation-scheduled-change-label": "รœtemezett vรกltรกs", + "look-feel-edge-smoothness-description": "Lรกgy, tollas hatรกst alkalmaz az รกtmenetek szรฉlรฉn.", + "look-feel-edge-smoothness-label": "รtmeneti รฉl lรกgyรญtรกsa", + "look-feel-fill-color-description": "Kitรถltรฉsi szรญn, amely a hรกttรฉrkรฉp mรถgรถtt megjelenhet.", + "look-feel-fill-mode-description": "Hogyan mรฉretezze รกt a kรฉp magรกt a monitor felbontรกsรกhoz.", + "look-feel-fill-mode-label": "Kitรถltรฉsi mรณd", + "look-feel-skip-startup-transition-description": "Hagyja ki a hรกttรฉrkรฉp animรกciรณt a shell indรญtรกsakor.", + "look-feel-skip-startup-transition-label": "Indรญtรกsi รกtmenet kihagyรกsa", + "look-feel-title": "Megjelenรฉs รฉs รฉrzet", + "look-feel-transition-duration-description": "Az รกtmeneti animรกciรณk idล‘tartama mรกsodpercben.", + "look-feel-transition-duration-label": "รtmenet idล‘tartama", + "look-feel-transition-type-description": "Animรกciรณ tรญpusa hรกttรฉrkรฉpek kรถzรถtti vรกltรกskor.", + "look-feel-transition-type-label": "รtmenet tรญpusa", + "settings-clear-cache-button": "Gyorsรญtรณtรกr tรถrlรฉse", + "settings-clear-cache-description": "Gyorsรญtรณtรกrazott, รกtmรฉretezett hรกttรฉrkรฉpek tรถrlรฉse a lemezterรผlet felszabadรญtรกsรกรฉrt.", + "settings-clear-cache-label": "Hรกttรฉrkรฉp gyorsรญtรณtรกr", + "settings-clear-cache-toast": "Hรกttรฉrkรฉp gyorsรญtรณtรกr tรถrรถlve", + "settings-desc": "Hogyan legyenek kezelve รฉs megjelenรญtve a hรกttรฉrkรฉpek.", + "settings-enable-management-description": "Hรกttรฉrkรฉpek kezelรฉse a Noctalia segรญtsรฉgรฉvel. Vedd ki a pipรกt, ha inkรกbb mรกs alkalmazรกst hasznรกlnรกl.", + "settings-enable-management-label": "Hรกttรฉrkรฉp-kezelรฉs engedรฉlyezรฉse", + "settings-enable-overview-description": "Elmosรณdott รฉs elsรถtรฉtรญtett hรกttรฉrkรฉpet alkalmaz az รกttekintล‘ kรฉpernyล‘n.", + "settings-enable-overview-label": "รttekintล‘ hรกttรฉrkรฉp engedรฉlyezรฉse", + "settings-folder-description": "A fล‘ hรกttรฉrkรฉpek mappa elรฉrรฉsi รบtja.", + "settings-folder-label": "Hรกttรฉrkรฉp mappa", + "settings-hide-wallpaper-filenames-tooltip-hide": "Fรกjlnevek elrejtรฉse", + "settings-hide-wallpaper-filenames-tooltip-show": "Fรกjlnevek megjelenรญtรฉse", + "settings-monitor-specific-description": "Kรผlรถn hรกttรฉrkรฉp mappa beรกllรญtรกsa minden monitorhoz.", + "settings-monitor-specific-label": "Monitor-specifikus kรถnyvtรกrak", + "settings-monitor-specific-tooltip": "Monitor hรกttรฉrkรฉp mappa", + "settings-overview-blur-strength-description": "Az elmosรกs erล‘ssรฉge az รกttekintรฉsben.", + "settings-overview-blur-strength-label": "รttekintรฉs elmosรกsรกnak erล‘ssรฉge", + "settings-overview-tint-description": "Az รกrnyalat erล‘ssรฉge az รกttekintรฉsben.", + "settings-overview-tint-label": "รttekintรฉs รกrnyalatรกnak erล‘ssรฉge", + "settings-recursive-search-description": "Hรกttรฉrkรฉpek keresรฉse a hรกttรฉrkรฉp kรถnyvtรกr alkรถnyvtรกraiban is.", + "settings-recursive-search-label": "Alkรถnyvtรกrak keresรฉse", + "settings-select-monitor-folder": "Monitor hรกttรฉrkรฉp mappa kivรกlasztรกsa", + "settings-selector-description": "Hรกttรฉrkรฉp kivรกlasztรกsa.", + "settings-selector-position-description": "Hol jelenjen meg a hรกttรฉrkรฉp vรกlasztรณ panel.", + "settings-show-hidden-files-tooltip-hide": "Rejtett fรกjlok elrejtรฉse", + "settings-show-hidden-files-tooltip-show": "Rejtett fรกjlok megjelenรญtรฉse", + "settings-title": "Hรกttรฉrkรฉp beรกllรญtรกsok", + "settings-use-original-images-description": "Hรกttรฉrkรฉpek รกtmรฉretezรฉsรฉnek kihagyรกsa megjelenรญtรฉs elล‘tt. Helyet takarรญt meg a lemezen รฉs csรถkkenti a CPU-hasznรกlatot a hรกttรฉrkรฉpvรกltรกsok sorรกn, de nagyon nagy kรฉpek esetรฉn tรถbb memรณriรกt fogyaszthat.", + "settings-use-original-images-label": "Eredeti kรฉpek hasznรกlata", + "settings-view-mode-description": "Hogyan jelenjenek meg a hรกttรฉrkรฉpek a kรถnyvtรกrbรณl.", + "settings-view-mode-label": "Megtekintรฉsi mรณd", + "view-mode-browse": "Kรถnyvtรกrak bรถngรฉszรฉse", + "view-mode-cycle-tooltip": "Nรฉzet mรณd: {mode} (kattints a mรณdosรญtรกshoz)", + "view-mode-recursive": "Egybesimรญtott alkรถnyvtรกrak", + "view-mode-single": "Gyรถkรฉrkรถnyvtรกr" + } + }, + "placeholders": { + "add-new-keybind": "รšj gyorsbillentyลฑ hozzรกadรกsa", + "command-example": "echo \"Hellรณ, vilรกg!\"", + "enter-command": "Vรฉgrehajtandรณ parancs (alkalmazรกs vagy egyรฉni parancsfรกjl)", + "enter-ipc-identifier": "Az IPC parancsok egyedi azonosรญtรณja", + "enter-path": "รštvonal megadรกsaโ€ฆ", + "enter-text-to-collapse": "pl. โ€žsemmi sem jรกtszikโ€. /regex/ a mintรกkhoz.", + "enter-tooltip": "Add meg a buborรฉksรบgรณt", + "enter-width-pixels": "Add meg a szรฉlessรฉget kรฉppontban", + "keybind-recording": "Gyorsbillentyลฑ rรถgzรญtรฉseโ€ฆ", + "search": "Keresรฉsโ€ฆ", + "search-icons": "pl. noctalia, niri, battery, cloud", + "search-launcher": "Keress bejegyzรฉseket vagy รญrd be a โ€ž>โ€ jelet a parancsokhoz", + "search-wallhaven": "Wallhaven keresรฉseโ€ฆ", + "search-wallpapers": "รrja be a hรกttรฉrkรฉpek szลฑrรฉsรฉhezโ€ฆ" + }, + "positions": { + "bottom": "Alul", + "bottom-center": "Lent kรถzรฉpen", + "bottom-left": "Bal alsรณ", + "bottom-right": "Jobb alsรณ", + "center": "Kรถzรฉp", + "center-left": "Bal kรถzรฉpen", + "center-right": "Jobb kรถzรฉpen", + "close-to-bar": "Kรถzel a sรกv gombjรกhoz", + "follow-bar": "Kรถveti a sรกvot", + "left": "Bal", + "right": "Jobb", + "top": "Felรผl", + "top-center": "Fent kรถzรฉpen", + "top-left": "Bal felsล‘", + "top-right": "Jobb felsล‘" + }, + "session-menu": { + "action-in-seconds": "{action} {seconds} mรกsodperc mรบlvaโ€ฆ", + "cancel-timer": "Idล‘zรญtล‘ megszakรญtรกsa", + "title": "Munkamenet menรผ" + }, + "setup": { + "all-done": "Kรฉsz!", + "appearance": { + "subheader": "A sรถtรฉt mรณd รฉs a szรญnforrรกsok รกllรญtรกsa (hรกttรฉrkรฉp vagy elล‘re meghatรกrozott)." + }, + "customize": { + "header": "Tapasztalat testreszabรกsa", + "subheader": "A sรกv pozรญciรณja, sลฑrลฑsรฉge, mรฉretezรฉse รฉs egyebek." + }, + "skip-setup": "Beรกllรญtรกs kihagyรกsa", + "telemetry-wizard-done": "ร‰rtettem!", + "telemetry-wizard-note": "Te irรกnyรญtasz โ€” bรกrmikor engedรฉlyezheted vagy letilthatod a beรกllรญtรกsokban", + "telemetry-wizard-subtitle": "Nรฉvtelen analitikรกt adtunk hozzรก, hogy segรญtsรผk a Noctalia fejlesztรฉsรฉt", + "telemetry-wizard-title": "Adatvรฉdelmi frissรญtรฉs", + "wallpaper": { + "choose-dir": "A kรถnyvtรกr, amely tartalmazza a hรกttรฉrkรฉpeket", + "dir-browse": "Hรกttรฉrkรฉp mappa bรถngรฉszรฉse", + "dir-description": "A hรกttรฉrkรฉpeket tartalmazรณ mappa.", + "dir-label": "Hรกttรฉrkรฉp kรถnyvtรกr", + "dir-select-title": "Hรกttรฉrkรฉp mappa kivรกlasztรกsa", + "header": "Hรกttรฉrkรฉp kivรกlasztรกsa", + "no-dir": "Nincs kivรกlasztott hรกttรฉrkรฉp kรถnyvtรกr", + "no-valid": "Nincsenek รฉrvรฉnyes kรฉpfรกjlok a kรถvetkezล‘ben: {dir}", + "none-in-dir": "Nincs hรกttรฉrkรฉp a kรถnyvtรกrban", + "preview-error": "A kรฉp betรถltรฉse sikertelen", + "select-prompt": "Vรกlassz egy hรกttรฉrkรฉpet alรกbb", + "subheader": "Teremts hangulatot egy gyรถnyรถrลฑ hรกttรฉrrel." + }, + "welcome": "รœdvรถzlรผnk", + "welcome-note": "Csak nรฉhรกny alapvetล‘ dolog az indulรกshoz โ€” a teljes beรกllรญtรกsi lehetล‘sรฉg a beรกllรญtรกsokban talรกlhatรณ", + "welcome-subtitle": "Tegyรผk egyedivรฉ az asztalod", + "welcome-title": "รœdvรถzรถl a Noctalia!" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "Nincs mรฉdialejรกtszรณ รฉszlelve", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "Ismeretlen alkalmazรกs", + "uptime": "Mลฑkรถdรฉsi idล‘: {uptime}", + "user-requested": "Felhasznรกlรณ kรฉrte", + "welcome-back": "รœdvรถzlรผnk รบjra,", + "widget-settings-title": "{widget} beรกllรญtรกsok" + }, + "system-monitor": { + "core-usage": "Mag {id} hasznรกlat", + "cpu-temp": "CPU-hล‘mรฉrsรฉklet", + "cpu-usage": "CPU-hasznรกlat", + "disk": "Lemez", + "download-speed": "Letรถltรฉsi sebessรฉg", + "gpu-temp": "GPU-hล‘mรฉrsรฉklet", + "load-average": "รtlagos terhelรฉs", + "title": "Rendszerfigyelล‘", + "upload-speed": "Feltรถltรฉsi sebessรฉg" + }, + "toast": { + "airplane-mode": { + "description": "Az รถsszes vezetรฉk nรฉlkรผli kommunikรกciรณ letiltรกsa.", + "title": "Repรผlล‘gรฉp รผzemmรณd" + }, + "battery": { + "critical": "Kritikus akkumulรกtor", + "critical-desc": "Az akkumulรกtor {percent}%-on van โ€” azonnal csatlakoztasd a tรถltล‘t", + "low": "Alacsony akkumulรกtor", + "low-desc": "Az akkumulรกtor {percent}%-on van โ€” csatlakoztasd a tรถltล‘t" + }, + "bluetooth": { + "address-copied": "Cรญm a vรกgรณlapra mรกsolva", + "auto-connect-disabled": "Automatikus csatlakozรกs letiltva", + "auto-connect-enabled": "Automatikus csatlakozรกs engedรฉlyezve", + "auto-connecting": "Csatlakozรกs {count} eszkรถzhรถzโ€ฆ", + "confirm-code": "Erล‘sรญtsd meg a {value} kรณdot a mรกsik eszkรถzรถn.", + "connect-failed": "Nem sikerรผlt csatlakozni az eszkรถzhรถz", + "disconnect-failed": "Nem sikerรผlt levรกlasztani az eszkรถzrล‘l", + "discoverable-change-failed": "Nem sikerรผlt megvรกltoztatni a felfedezhetล‘sรกg รกllapotรกt", + "discoverable-disabled": "Felfedezhetล‘sรฉg letiltva", + "discoverable-enabled": "Felfedezhetล‘sรฉg engedรฉlyezve", + "display-code": "Add meg a {value} kรณdot a mรกsik eszkรถzรถn.", + "forget-failed": "Nem sikerรผlt elfelejteni az eszkรถzt", + "pair-failed": "Nem sikerรผlt pรกrosรญtani az eszkรถzt", + "passkey-required": "Az eszkรถz รกltal megkรถvetelt jelszรณ.", + "pincode-required": "Az eszkรถz รกltal megkรถvetelt PIN.", + "state-change-failed": "Nem sikerรผlt megvรกltoztatni a Bluetooth รกllapotรกt" + }, + "clipboard": { + "long-text": "Hosszรบ szรถveg", + "unavailable": "A vรกgรณlapelล‘zmรฉnyek nem elรฉrhetล‘k", + "unavailable-desc": "A โ€žcliphistโ€ alkalmazรกs nincs telepรญtve. Telepรญtsd a vรกgรณlapelล‘zmรฉnyek funkciรณinak hasznรกlatรกhoz" + }, + "custom-command-failed": { + "description": "A parancs sikertelen: {command}\\nKilรฉpรฉsi kรณd: {code}", + "title": "Egyรฉni parancs sikertelen" + }, + "do-not-disturb": { + "disabled": "Ne zavarjanak letiltva", + "disabled-desc": "Minden รฉrtesรญtรฉs megjelenรญtรฉse", + "enabled": "Ne zavarjanak engedรฉlyezve", + "enabled-desc": "Ezeket az รฉrtesรญtรฉseket az elล‘zmรฉnyekben talรกlja" + }, + "donation-opened": "Az adomรกnyozรกsi oldal megnyรญlt a bรถngรฉszล‘dben", + "internet-limited": "Csatlakoztatva internet nรฉlkรผl", + "keyboard-layout": { + "changed": "Billentyลฑzetkiosztรกs megvรกltozott: {layout}", + "title": "Billentyลฑzet" + }, + "missing-control-center": { + "description": "A vezรฉrlล‘kรถzpont widget eltรกvolรญtรกsra kerรผlt a sรกvrรณl. A sรกvrรณl valรณ รบjbรณli elรฉrรฉshez รบjra hozzรก kell adnia a widgetet. Jobb kattintรกssal is megnyithatja a sรกvon", + "label": "Utolsรณ vezรฉrlล‘kรถzpont widget eltรกvolรญtva" + }, + "night-light": { + "forced": "Kรฉnyszerรญtett aktivรกlรกs", + "normal": "Normรกl mรณd", + "not-installed": "wlsunset nincs telepรญtve" + }, + "noctalia-performance": { + "disabled": "Teljesรญtmรฉny mรณd letiltva", + "enabled": "Teljesรญtmรฉny mรณd engedรฉlyezve", + "label": "Noctalia Teljesรญtmรฉny" + }, + "power-profile": { + "changed": "Energia profil megvรกltozott", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "Hiba tรถrtรฉnt a sablonok feldolgozรกsa sorรกn", + "title": "A sablonfeldolgozรกs sikertelen" + }, + "vpn": { + "connected": "Csatlakozva a(z) โ€ž{name}โ€ hรกlรณzathoz", + "disconnected": "Kapcsolat megszakadt a(z) โ€ž{name}โ€ hรกlรณzattal" + }, + "wallpaper-colors": { + "disabled": "Hรกttรฉrkรฉpszรญnek letiltva", + "enabled": "Hรกttรฉrkรฉp szรญnek engedรฉlyezve", + "label": "Hรกttรฉrkรฉp szรญnek generรกlรกsa" + }, + "wifi": { + "connected": "Csatlakozva a(z) โ€ž{ssid}โ€ hรกlรณzathoz", + "connection-failed": "Csatlakozรกs sikertelen", + "connection-timeout": "Csatlakozรกsi idล‘tรบllรฉpรฉs", + "disconnected": "Kapcsolat megszakadt a(z) โ€ž{ssid}โ€ hรกlรณzattal", + "incorrect-password": "Helytelen jelszรณ.", + "network-not-found": "Hรกlรณzat nem talรกlhatรณ" + } + }, + "tooltips": { + "add-widget": "Widget hozzรกadรกsa", + "auto-connect": "Automatikus csatlakozรกs vรกltรกsa ehhez az eszkรถzhรถz", + "bluetooth-auto-connect-off": "Automatikus csatlakozรกs kikapcsolva", + "bluetooth-auto-connect-on": "Automatikus csatlakozรกs engedรฉlyezve", + "bluetooth-devices": "Bluetooth eszkรถzรถk", + "brightness-at": "Fรฉnyerล‘: {brightness}%", + "click-to-start-recording": "Kรฉpernyล‘felvevล‘ (felvรฉtel indรญtรกsa)", + "click-to-stop-recording": "Kรฉpernyล‘felvevล‘ (felvรฉtel leรกllรญtรกsa)", + "collapse": "Oldalsรกv รถsszecsukรกsa", + "copy-address": "Cรญm mรกsolรกsa", + "delete-notification": "ร‰rtesรญtรฉs tรถrlรฉse", + "dismiss-notification": "ร‰rtesรญtรฉs elvetรฉse", + "do-not-disturb-enabled": "Ne zavarjanak", + "expand": "Oldalsรกv kibontรกsa", + "forget-network": "Hรกlรณzat elfelejtรฉse", + "grid-view": "Rรกcs nรฉzet", + "hidden-files-hide": "Rejtett fรกjlok", + "hide-unnamed-devices": "Nรฉvtelen eszkรถzรถk elrejtรฉse", + "home": "Kezdล‘kรถnyvtรกr", + "input-muted": "Bemenet nรฉmรญtรกsรกnak vรกltรกsa", + "keep-awake": "ร‰bren tartรกs", + "keyboard-layout": "{layout} billentyลฑzetkiosztรกs", + "list-view": "Lista nรฉzet", + "manage-vpn": "VPN-kapcsolatok", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "Elรฉrte a maximรกlis widgetszรกmot", + "microphone-volume-at": "Mikrofon hangereje: {volume}%", + "move-to-section": "Ugrรกs a(z) {section} szakaszhoz", + "mute": "Nรฉmรญtรกs", + "next-media": "Kรถvetkezล‘ zene", + "next-month": "Kรถvetkezล‘ hรณnap", + "night-light-not-installed": "ร‰jszakai fรฉny (nem elรฉrhetล‘)", + "noctalia-performance-enabled": "Noctalia teljesรญtmรฉny mรณd", + "open-annotation-tool": "Megnyitรกs jegyzetelล‘ eszkรถzzel", + "open-control-center": "Vezรฉrlล‘kรถzpont", + "open-notification-history-enable-dnd": "ร‰rtesรญtรฉsi elล‘zmรฉnyek", + "open-settings": "Beรกllรญtรกsok", + "open-tray-dropdown": "Tรกlca", + "output-muted": "Kimenet nรฉmรญtรกsรกnak vรกltรกsa", + "power-profile": "{profile} energiaprofil", + "previous-media": "Elล‘zล‘ zene", + "previous-month": "Elล‘zล‘ hรณnap", + "refresh-devices": "Eszkรถzรถk frissรญtรฉse", + "refresh-wallhaven": "Wallhaven eredmรฉnyek frissรญtรฉse", + "refresh-wallpaper-list": "Hรกttรฉrkรฉp lista frissรญtรฉse", + "remove-widget": "Widget eltรกvolรญtรกsa", + "search-close": "Keresรฉs bezรกrรกsa", + "session-menu": "Munkamenet menรผ", + "show-all-devices": "ร–sszes eszkรถz megjelenรญtรฉse", + "switch-to-dark-mode": "Sรถtรฉt mรณd", + "switch-to-light-mode": "Vilรกgos mรณd", + "unmute": "Nรฉmรญtรกs feloldรกsa", + "up": "Szรผlล‘kรถnyvtรกr", + "volume-at": "Kimenet hangereje: {volume}%", + "wallpaper-selector": "Hรกttรฉrkรฉpvรกlasztรณ" + }, + "wallpaper": { + "browse": { + "empty-directory": "Ez a kรถnyvtรกr รผres.", + "go-root": "Ugrรกs a hรกttรฉrkรฉp gyรถkerรฉbe", + "go-up": "Ugrรกs a szรผlล‘ mappรกba", + "go-up-hint": "A visszagombbal navigรกlhatsz felfelรฉ." + }, + "configure-directory": "A hรกttรฉrkรฉpkรถnyvtรกr kรฉpekkel valรณ konfigurรกlรกsa.", + "fill-modes": { + "crop": "Vรกgรกs (Kitรถltรฉs)", + "fit": "Illesztรฉs (Tartalmazรกs)", + "repeat": "Ismรฉtlรฉs (Mozaik)", + "stretch": "Nyรบjtรกs" + }, + "no-match": "Nincs talรกlat.", + "no-wallpaper": "Nincs hรกttรฉrkรฉp talรกlat.", + "panel": { + "apikey-help": "NSFW tartalom elรฉrรฉsรฉhez API kulcs szรผksรฉges.", + "apikey-label": "API kulcs", + "apikey-managed-by-env": "A NOCTALIA_WALLHAVEN_API_KEY kรถrnyezeti vรกltozรณval kezelhetล‘.", + "apikey-placeholder": "Add meg a Wallhaven API kulcsod!", + "appearance-dark-tab": "Sรถtรฉt", + "appearance-light-tab": "Vilรกgos", + "categories-anime": "Anime", + "categories-label": "Kategรณriรกk", + "categories-people": "Emberek", + "color-extraction-disabled": "Hรกttรฉrkรฉp szรญnkivonรกs hasznรกlata", + "color-extraction-enabled": "Elล‘re meghatรกrozott szรญnsรฉmรกk hasznรกlata", + "header-devices-apply-all-tooltip": "Hรกttรฉrkรฉp vรกlasztรกsa monitoronkรฉnt", + "header-devices-per-monitor-tooltip": "Alkalmazรกs minden monitorra", + "header-link-light-dark-tooltip": "Hรกttรฉrkรฉp megtartรกsa tรฉma vรกltรกsakor", + "header-separate-light-dark-tooltip": "Hรกttรฉrkรฉp automatikus vรกltรกsa tรฉmรกval", + "header-sun-linked-tooltip": "Kรผlรถn vilรกgos รฉs sรถtรฉt hรกttรฉrkรฉpek", + "header-sun-separate-tooltip": "Ugyanaz a hรกttรฉrkรฉp vilรกgos รฉs sรถtรฉt mรณdhoz", + "order-asc": "Nรถvekvล‘", + "order-desc": "Csรถkkenล‘", + "order-label": "Sorrend", + "purity-label": "Tartalomszลฑrล‘", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "Sketchy", + "ratios-all-portrait": "ร–sszes รกllรณ", + "ratios-all-wide": "ร–sszes szรฉles", + "ratios-any": "Bรกrmelyik", + "ratios-label": "Kรฉparรกny", + "resolution-atleast": "Legalรกbb", + "resolution-exact": "Pontos", + "resolution-label": "Felbontรกs", + "resolution-mode-label": "Mรณd", + "solid-color-tooltip": "Egyszรญnลฑ hรกttรฉr", + "sort-date-asc": "Rendezรฉs: a legrรฉgebbiek elรถl", + "sort-date-desc": "Rendezรฉs: a legรบjabbak elรถl", + "sort-name-asc": "Rendezรฉs nรฉv szerint (A-Z)", + "sort-name-desc": "Rendezรฉs nรฉv szerint (Z-A)", + "sort-random": "Rendezรฉs vรฉletlenszerลฑen", + "sorting-date-added": "Hozzรกadรกs dรกtuma", + "sorting-favorites": "Kedvencek", + "sorting-label": "Rendezรฉs", + "sorting-relevance": "Relevancia", + "sorting-toplist": "Toplista", + "sorting-views": "Megtekintรฉsek", + "source-label": "Forrรกs", + "source-wallhaven": "Wallhaven", + "title": "Hรกttรฉrkรฉp-vรกlasztรณ", + "wallhaven-settings-title": "Wallhaven beรกllรญtรกsok" + }, + "transitions": { + "disc": "Korong", + "fade": "Halvรกnyรญtรกs", + "honeycomb": "Mรฉhsejt", + "pixelate": "Pixelezรฉs", + "stripes": "Csรญkok", + "wipe": "Tรถrlรฉs" + }, + "try-different-search": "Prรณbรกljon mรกsik keresรฉst.", + "wallhaven": { + "loading": "Hรกttรฉrkรฉpek betรถltรฉseโ€ฆ", + "no-results": "Nincs hรกttรฉrkรฉp. Prรณbรกljon mรกsik keresรฉst.", + "page": "{current} / {total}", + "page-prefix": "Oldal", + "page-suffix": "{total} kรถzรผl" + } + }, + "weather": { + "clear-sky": "Tiszta รฉgbolt", + "drizzle": "Szitรกlรกs", + "fog": "Kรถd", + "mainly-clear": "Tรถbbnyire derรผlt", + "overcast": "Borult", + "partly-cloudy": "Rรฉszben felhล‘s", + "rain-showers": "Esล‘zรกporok", + "snow": "Hรณ", + "thunderstorm": "Zivatar" + }, + "widgets": { + "color-picker": { + "palette-description": "Vรกlassz az elล‘re meghatรกrozott szรญnek szรฉles skรกlรกjรกbรณl.", + "palette-label": "Paletta", + "palette-theme-colors": "Gyors hozzรกfรฉrรฉs a tรฉma szรญnpalettรกjรกhoz.", + "title": "Szรญnvรกlasztรณ" + }, + "datetime-tokens": { + "ampm-lowercase": "am/pm kisbetลฑvel", + "ampm-uppercase": "AM/PM nagybetลฑvel", + "common-12hour-time-minutes": "12 รณrรกs formรกtum percekkel", + "common-24hour-time-minutes": "24 รณrรกs formรกtum percekkel", + "common-24hour-time-seconds": "24 รณrรกs formรกtum mรกsodpercekkel", + "common-european-date": "Eurรณpai dรกtumformรกtum", + "common-iso-date": "ISO dรกtumformรกtum", + "common-us-date": "Amerikai dรกtumformรกtum", + "common-weekday-date": "Hรฉt napja dรกtummal", + "common-weekday-month-day": "Hรฉt napja, hรณnap รฉs nap", + "day-abbreviated": "Rรถvidรญtett napnรฉv", + "day-full": "Teljes napnรฉv", + "day-leading-zero": "Nap nullรกval kezdve (01-31)", + "day-no-leading-zero": "Nap nem nullรกval kezdve (1-31)", + "hour-leading-zero": "ร“ra nullรกval kezdve (00-23) โ€” 24 รณrรกs formรกtum", + "hour-no-leading-zero": "ร“ra nem nullรกval kezdve (0-23) โ€” 24 รณrรกs formรกtum", + "minute-leading-zero": "Perc nullรกval kezdve (00-59)", + "minute-no-leading-zero": "Perc nem nullรกval kezdve (0-59)", + "month-abbreviated": "Rรถvidรญtett hรณnapnรฉv", + "month-full": "Teljes hรณnapnรฉv", + "month-number-leading-zero": "Hรณnap szรกmkรฉnt nullรกval kezdve (01-12)", + "month-number-no-zero": "Hรณnap szรกmkรฉnt nem nullรกval kezdve (1-12)", + "second-leading-zero": "Mรกsodperc nullรกval kezdve (00-59)", + "second-no-leading-zero": "Mรกsodperc nem nullรกval kezdve (0-59)", + "timezone-abbreviation": "Idล‘zรณna rรถvidรญtรฉse", + "year-four-digit": "ร‰v nรฉgy szรกmjeggyel", + "year-two-digit": "ร‰v kรฉt szรกmjeggyel (00-99)" + }, + "file-picker": { + "item": "elem", + "items": "elemek", + "search-placeholder": "Fรกjlok รฉs mappรกk keresรฉseโ€ฆ", + "select-current": "Aktuรกlis kijelรถlรฉse", + "select-file": "Fรกjl kijelรถlรฉse", + "select-folder": "Mappa kivรกlasztรกsa", + "selected": "Kivรกlasztva:", + "title": "Fรกjlvรกlasztรณ" + }, + "file-picker-title": "Fรกjl kivรกlasztรกsa", + "icon-picker": { + "title": "Ikonvรกlasztรณ" + }, + "tooltip-placeholder": "Helyล‘rzล‘" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "Anonim Identitรกs", + "ca-cert": "CA tanรบsรญtvรกny", + "eap-method": "EAP-mรณdszer", + "password": "Felhasznรกlรณi jelszรณ", + "phase2-auth": "2. fรกzisรบ hitelesรญtรฉs", + "username": "Identitรกs / Felhasznรกlรณnรฉv" + }, + "panel": { + "action-required": "Szรผkseges akciรณ", + "add-hidden-network": "Rejtett hรกlรณzat hozzรกadรกsa", + "add-network": "Hรกlรณzat hozzรกadรกsa", + "available-interfaces": "Elรฉrhetล‘ interfรฉszek", + "available-networks": "Elรฉrhetล‘ hรกlรณzatok", + "disabled": "A Wiโ€‘Fi le van tiltva", + "disconnecting": "Levรกlasztรกsโ€ฆ", + "dns": "DNS", + "enable-message": "Kapcsold be a Wiโ€‘Fi-t az elรฉrhetล‘ hรกlรณzatok megtekintรฉsรฉhez.", + "enter-password": "Add meg a Wiโ€‘Fi jelszรณtโ€ฆ", + "forget": "Elfelejtรฉs", + "forget-network": "Hรกlรณzat elfelejtรฉse", + "forgetting": "Elfelejtรฉsโ€ฆ", + "hidden-network": "Rejtett hรกlรณzat", + "interface": "Hรกlรณzati interfรฉsz", + "internet-connected": "Internet csatlakoztatva", + "internet-limited": "Nincs internet", + "internet-status": "Internet รกllapota", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "Ismert hรกlรณzatok", + "link-speed": "Kapcsolati sebessรฉg", + "network-name-ssid": "Hรกlรณzat neve (SSID)", + "no-ethernet-devices": "Nem talรกlhatรณ Ethernet eszkรถz", + "no-networks": "Nem talรกlhatรณ Wiโ€‘Fi-hรกlรณzat", + "saved": "Mentve", + "scan-again": "Keresรฉs รบjra", + "searching": "Hรกlรณzatok keresรฉseโ€ฆ", + "security-open": "Megnyitรกs", + "security-wep": "WEP", + "security-wpa": "WPA/WPA2/WPA3 Szemรฉlyes", + "security-wpa-ent": "WPA Enterprise", + "security-wpa2-ent": "WPA2 Enterprise", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 Enterprise", + "show-password": "Jelszรณ megjelenรญtรฉse", + "title": "Wiโ€‘Fi" + }, + "signal": { + "excellent": "Kivรกlรณsรกg", + "fair": "Kรถzepes", + "good": "Jรณ", + "poor": "Gyenge", + "weak": "Gyenge" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/it.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/it.json new file mode 100644 index 0000000..50cbaf8 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/it.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "Cancella cronologia", + "close-app": "Chiudi {app}", + "connect-vpn": "Connetti a {name}", + "cycle-visualizer": "Cambia visualizzatore", + "disable-bluetooth": "Disattiva Bluetooth", + "disable-dnd": "Disattiva 'Non disturbare'", + "disable-wifi": "Disattiva Wi-Fi", + "disconnect-vpn": "Disconnetti {name}", + "dock-settings": "Impostazioni dock", + "enable-bluetooth": "Attiva Bluetooth", + "enable-dnd": "Attiva 'Non disturbare'", + "enable-wifi": "Attiva Wi-Fi", + "launcher-settings": "Impostazioni launcher", + "lower-to-bottom": "Porta in basso", + "open-calendar": "Apri calendario", + "open-display-settings": "Impostazioni schermo", + "open-launcher": "Apri launcher", + "open-settings": "Apri impostazioni", + "raise-to-top": "Porta in primo piano", + "random-wallpaper": "Sfondo casuale", + "run-custom-command": "Esegui comando personalizzato", + "show-active-only": "Mostra solo attive", + "show-all": "Mostra tutto", + "toggle-mute": "Attiva/disattiva muto", + "widget-settings": "Impostazioni widget" + }, + "authentication": { + "error": "Errore di autenticazione", + "failed": "Autenticazione non riuscita" + }, + "bar": { + "active-window": { + "colorize-icons-description": "Applica i colori del tema allโ€™icona della finestra attiva.", + "hide-mode-description": "Controlla il comportamento del widget quando non cโ€™รจ alcuna finestra attiva.", + "scrolling-mode-description": "Controlla quando abilitare lo scorrimento del testo per titoli finestra lunghi.", + "show-app-icon-description": "Mostra lโ€™icona dellโ€™applicazione accanto al titolo della finestra.", + "show-app-icon-label": "Mostra icona app", + "show-app-text-description": "Mostra il titolo della finestra attiva.", + "show-app-text-label": "Mostra titolo finestra" + }, + "audio-visualizer": { + "color-name-description": "Seleziona il colore del visualizzatore.", + "color-name-label": "Colore riempimento", + "height-description": "Larghezza componente personalizzata.", + "hide-when-idle-description": "Se attivo, il visualizzatore viene nascosto quando nessun player รจ in riproduzione.", + "hide-when-idle-label": "Nascondi quando non รจ in riproduzione nulla", + "width-description": "Larghezza personalizzata del componente." + }, + "battery": { + "device-default": "Predefinito (dispositivo schermo)", + "device-description": "Seleziona quale dispositivo batteria visualizzare.", + "device-label": "Dispositivo batteria", + "display-mode-description": "Scegli come visualizzare la batteria nella barra.", + "display-mode-graphic": "Batteria grafica", + "display-mode-graphic-clean": "Batteria grafica (senza %)", + "display-mode-icon-always": "Icona - Mostra sempre %", + "display-mode-icon-hover": "Icona - Mostra al passaggio", + "display-mode-icon-only": "Solo icona", + "hide-if-idle-description": "Nasconde il widget quando la batteria non sta caricando o scaricando.", + "hide-if-idle-label": "Nascondi quando inattiva", + "hide-if-not-detected-description": "Nasconde il widget quando non viene rilevata alcuna batteria nel sistema.", + "hide-if-not-detected-label": "Nascondi se non rilevata", + "low-battery-threshold-label": "Soglia avviso batteria scarica", + "show-noctalia-performance-description": "Mostra lโ€™interruttore della modalitร  prestazioni Noctalia nel pannello batteria.
Disattiva ombre e animazioni in Noctalia per ridurre lโ€™uso di risorse.", + "show-noctalia-performance-label": "Mostra interruttore Noctalia Prestazioni", + "show-power-profile-description": "Mostra la selezione del profilo energetico nel pannello batteria.", + "show-power-profile-label": "Mostra controlli profilo energetico" + }, + "brightness": { + "apply-all-description": "Cambia la luminositร  su tutti i monitor usando la rotella del mouse.", + "apply-all-label": "Applica modifiche di scorrimento a tutti i monitor" + }, + "clock": { + "clock-display-description": "Personalizza la visualizzazione dellโ€™orologio aggiungendo token dallโ€™elenco qui sotto. Per usare il formato 12 ore devi includere il token 'AP'.", + "clock-display-label": "Visualizzazione orologio", + "custom-font-description": "Seleziona un font personalizzato per la visualizzazione dellโ€™orologio.", + "custom-font-label": "Font personalizzato", + "custom-font-placeholder": "Seleziona font personalizzato...", + "custom-font-search-placeholder": "Cerca font...", + "horizontal-bar-description": "Suggerimento: usa \\n per creare unโ€™interruzione di riga.", + "horizontal-bar-label": "Barra orizzontale", + "preview": "Anteprima", + "tooltip-format-description": "Stringa di formato del tooltip mostrato al passaggio sullโ€™orologio. Lascia vuoto per usare quello predefinito.", + "tooltip-format-label": "Formato tooltip", + "use-custom-font-description": "Sovrascrive la selezione del font predefinito con un font personalizzato per lโ€™orologio.", + "use-custom-font-label": "Usa font personalizzato", + "use-monospaced-font-description": "Se attivo, lโ€™orologio userร  un font monospaziato.", + "use-monospaced-font-label": "Usa font monospaziato", + "vertical-bar-description": "Usa uno spazio per separare ogni parte su una nuova riga.", + "vertical-bar-label": "Barra verticale" + }, + "control-center": { + "browse-file": "Sfoglia file", + "browse-library": "Sfoglia libreria", + "color-selection-description": "Applica i colori del tema alle icone.", + "enable-colorization-description": "Abilita la colorazione per l'icona, applicando i colori del tema.", + "icon-description": "Seleziona unโ€™icona dalla libreria o un file personalizzato.", + "select-custom-icon": "Seleziona unโ€™icona personalizzata", + "use-distro-logo-description": "Usa il logo della tua distribuzione invece di unโ€™icona personalizzata.", + "use-distro-logo-label": "Usa logo distro al posto dellโ€™icona" + }, + "custom-button": { + "collapse-condition-description": "Se il testo di output corrisponde a questo valore, il pulsante verrร  compresso.", + "collapse-condition-label": "Condizione di compressione", + "color-selection-description": "Applica i colori del tema a icona e testo.", + "default-tooltip": "Pulsante personalizzato, configurarlo nelle impostazioni", + "display-command-output-description": "Inserisci un comando da eseguire a intervalli regolari. La prima riga dellโ€™output verrร  mostrata come testo.", + "display-command-output-label": "Mostra output comando", + "display-command-output-stream-description": "Inserisci un comando da eseguire in modo continuo.", + "dynamic-text": "Testo dinamico", + "enable-colorization-description": "Abilita la colorazione di icona e testo del pulsante personalizzato applicando i colori del tema.", + "enable-colorization-label": "Abilita colorazione", + "general-tooltip-text-description": "Testo personalizzato da mostrare nel tooltip del pulsante.", + "general-tooltip-text-label": "Testo tooltip personalizzato", + "hide-mode-always-expanded": "Sempre espanso", + "hide-mode-description": "Controlla la visibilitร  del widget quando il comando non ha output.", + "hide-mode-expand-with-output": "Espandi quando cโ€™รจ output", + "hide-mode-label": "Modalitร  nascondi", + "hide-mode-max-transparent": "Massimo espanso ma trasparente", + "icon-description": "Seleziona unโ€™icona dalla libreria.", + "ipc-identifier-description": "Identificatore univoco per i comandi IPC. Usa questo identificatore con 'qs -c noctalia-shell ipc call cb [action] [identifier]' per controllare questo pulsante via IPC.", + "ipc-identifier-label": "Identificatore IPC", + "left-click-description": "Comando da eseguire al clic sinistro sul pulsante.", + "left-click-label": "Clic sinistro", + "left-click-update-text": "Aggiorna testo visualizzato al clic sinistro", + "max-text-length-horizontal-description": "Numero massimo di caratteri da mostrare in barra orizzontale (0 per nascondere il testo).", + "max-text-length-horizontal-label": "Lunghezza max testo (orizzontale)", + "max-text-length-vertical-description": "Numero massimo di caratteri da mostrare in barra verticale (0 per nascondere il testo).", + "max-text-length-vertical-label": "Lunghezza max testo (verticale)", + "middle-click-description": "Comando da eseguire al clic centrale sul pulsante.", + "middle-click-label": "Clic centrale", + "middle-click-update-text": "Aggiorna testo visualizzato al clic centrale", + "parse-json-description": "Interpreta lโ€™output del comando come oggetto JSON per impostare dinamicamente testo e icona.", + "parse-json-label": "Interpreta output come JSON", + "refresh-interval-description": "Intervallo in millisecondi.", + "refresh-interval-label": "Intervallo aggiornamento", + "right-click-description": "Comando da eseguire al clic destro sul pulsante.", + "right-click-label": "Clic destro", + "right-click-update-text": "Aggiorna testo visualizzato al clic destro", + "show-exec-tooltip-description": "Mostra tooltip con i dettagli dei comandi (clic sinistro/destro/centrale, rotella).", + "show-exec-tooltip-label": "Mostra tooltip comandi", + "show-icon-description": "Attiva/disattiva la visibilitร  dellโ€™icona del widget.", + "show-icon-label": "Mostra icona", + "show-text-tooltip-description": "Mostra tooltip con lโ€™output del comando di testo.", + "show-text-tooltip-label": "Mostra tooltip testo dinamico", + "text-stream-description": "Le righe in streaming del comando verranno mostrate come testo sul pulsante.", + "text-stream-label": "Flusso", + "wheel-description": "Comando da eseguire quando viene usata la rotella del mouse.
Usa $delta nel comando per il delta della rotella.", + "wheel-down": "Scorri giรน", + "wheel-down-description": "Comando da eseguire quando la rotella viene fatta scorrere verso il basso.", + "wheel-down-label": "Comando rotella giรน", + "wheel-label": "Rotella mouse", + "wheel-mode-separate-description": "Abilita comandi separati per rotella su e giรน.", + "wheel-mode-separate-label": "Comandi rotella separati", + "wheel-up": "Scorri su", + "wheel-up-description": "Comando da eseguire quando la rotella viene fatta scorrere verso lโ€™alto.", + "wheel-up-label": "Comando rotella su", + "wheel-update-text": "Aggiorna testo visualizzato allo scorrimento" + }, + "keyboard-layout": { + "show-icon-description": "Mostra lโ€™icona del layout tastiera." + }, + "lock-keys": { + "hide-when-off-description": "Nasconde lโ€™indicatore quando il tasto non รจ attivo.", + "hide-when-off-label": "Nascondi quando spento", + "show-caps-lock-description": "Mostra lo stato Caps Lock.", + "show-caps-lock-label": "Caps Lock", + "show-num-lock-description": "Mostra lo stato Num Lock.", + "show-num-lock-label": "Num Lock", + "show-scroll-lock-description": "Mostra lo stato Scroll Lock.", + "show-scroll-lock-label": "Scroll Lock" + }, + "media-mini": { + "compact-mode-description": "Abilita un layout salvaspazio per il pannello lettore multimediale.", + "compact-mode-label": "Modalitร  compatta", + "hide-mode-description": "Controlla il comportamento del widget quando non รจ in riproduzione alcun contenuto.", + "max-width-description": "Imposta la dimensione orizzontale massima del widget. Il widget si ridurrร  con contenuti piรน brevi.", + "no-active-player": "Nessun player attivo", + "panel-section-description": "Configura aspetto e comportamento del pannello lettore multimediale.", + "panel-section-label": "Pannello lettore multimediale", + "scrolling-mode-description": "Controlla quando abilitare lo scorrimento del testo per titoli brano lunghi.", + "scrolling-mode-label": "Modalitร  scorrimento", + "show-album-art-description": "Mostra la copertina dellโ€™album del brano attualmente in riproduzione.", + "show-album-art-label": "Mostra copertina album", + "show-artist-first-description": "Mostra artista - titolo invece di titolo - artista.", + "show-artist-first-label": "Mostra prima artista", + "show-progress-ring-description": "Mostra un indicatore circolare del progresso del brano.", + "show-progress-ring-label": "Mostra anello progresso", + "show-visualizer-description": "Mostra un visualizzatore audio durante la riproduzione musicale.", + "show-visualizer-label": "Mostra visualizzatore", + "use-fixed-width-description": "Se attivo, il widget userร  sempre la larghezza massima invece di adattarsi dinamicamente al contenuto.", + "use-fixed-width-label": "Usa larghezza fissa", + "visualizer-type-description": "Scegli lo stile del visualizzatore audio da mostrare.", + "visualizer-type-label": "Tipo visualizzatore" + }, + "notification-history": { + "hide-widget-when-zero-description": "Nasconde lโ€™icona notifiche quando non ci sono notifiche.", + "hide-widget-when-zero-label": "Nascondi icona quando non ci sono notifiche", + "hide-widget-when-zero-unread-description": "Nasconde lโ€™icona notifiche quando non ci sono notifiche non lette.", + "hide-widget-when-zero-unread-label": "Nascondi icona quando non ci sono non lette", + "show-unread-badge-description": "Mostra un badge con il numero di notifiche non lette.", + "show-unread-badge-label": "Mostra badge non lette", + "unread-badge-color-description": "Seleziona il colore del badge notifiche non lette.", + "unread-badge-color-label": "Colore badge non lette" + }, + "section-editor": { + "placeholder": "Seleziona un widget da aggiungere...", + "search-placeholder": "Cerca widget..." + }, + "spacer": { + "width-description": "Larghezza spaziatura in pixel." + }, + "system-monitor": { + "compact-mode-description": "Mostra statistiche come mini grafici a barre invece di valori testuali. Evita spostamenti del layout.", + "compact-mode-label": "Modalitร  compatta", + "cpu-cores-description": "Mostra l'utilizzo dei core della CPU individualmente.", + "cpu-cores-label": "Core CPU", + "cpu-frequency-description": "Mostra la frequenza corrente della CPU in GHz.", + "cpu-frequency-label": "Mostra frequenza CPU", + "cpu-temperature-description": "Mostra le letture della temperatura CPU se disponibili.", + "cpu-temperature-label": "Temperatura CPU", + "cpu-usage-description": "Mostra la percentuale di utilizzo CPU corrente.", + "cpu-usage-label": "Utilizzo CPU", + "disk-path-description": "Seleziona quale punto di mount disco monitorare.", + "disk-path-label": "Percorso disco", + "gpu-temperature-description": "Mostra le letture della temperatura GPU se disponibili.", + "load-average-description": "Mostra il carico medio del sistema.", + "load-average-label": "Carico medio", + "memory-percentage-description": "Mostra lโ€™uso memoria in percentuale invece che in valori assoluti.", + "memory-percentage-label": "Memoria in percentuale", + "memory-usage-description": "Mostra le informazioni sullโ€™uso RAM corrente.", + "memory-usage-label": "Uso memoria", + "network-traffic-description": "Mostra velocitร  di upload e download di rete.", + "network-traffic-label": "Traffico di rete", + "storage-as-percentage-description": "Mostra lo spazio disco in percentuale invece che in valori assoluti.", + "storage-as-percentage-label": "Disco in percentuale", + "storage-available-description": "Mostra quanto spazio disco รจ disponibile invece di quanto รจ usato.", + "storage-available-label": "Spazio disco disponibile", + "storage-usage-description": "Mostra informazioni sullโ€™uso dello spazio disco.", + "storage-usage-label": "Uso spazio disco", + "swap-usage-description": "Mostra utilizzo della memoria swap.", + "swap-usage-label": "Uso swap", + "use-monospace-font-description": "Usa font monospaziato per larghezza caratteri coerente.", + "use-monospace-font-label": "Font monospaziato", + "use-padding-description": "Aggiunge spazi iniziali ai valori testuali per evitare spostamenti del layout.", + "use-padding-description-disabled-monospace-font": "Per questa funzione รจ necessario un font monospaziato.", + "use-padding-description-disabled-vertical": "La barra verticale non supporta il padding del testo.", + "use-padding-label": "Padding testo" + }, + "taskbar": { + "colorize-icons-description": "Applica i colori del tema alle icone della taskbar.", + "hide-mode-description": "Controlla il comportamento del widget quando non ci sono finestre corrispondenti.", + "hide-mode-label": "Modalitร  nascondimento", + "icon-scale-description": "Imposta il fattore di scala per le icone della taskbar.", + "icon-scale-label": "Scala icone", + "max-width-description": "Larghezza massima della taskbar come percentuale della larghezza schermo.", + "max-width-label": "Larghezza massima", + "only-active-workspaces-description": "Mostra solo app delle workspace attive.", + "only-active-workspaces-label": "Solo dalle workspace attive", + "only-same-monitor-description": "Mostra solo app del monitor in cui si trova la barra.", + "only-same-monitor-label": "Solo dallo stesso monitor", + "show-pinned-apps-description": "Mostra nella taskbar le app fissate dal dock.", + "show-pinned-apps-label": "Mostra app fissate", + "show-title-description": "Mostra i titoli finestra nella taskbar.", + "show-title-description-disabled": "La taskbar verticale non supporta la visualizzazione dei titoli.", + "show-title-label": "Mostra titolo", + "smart-width-description": "Regola automaticamente la larghezza delle voci in base al loro numero.", + "smart-width-label": "Larghezza intelligente", + "title-width-description": "Imposta la larghezza dei titoli finestra nella taskbar (in pixel).", + "title-width-label": "Larghezza titolo", + "title-width-reset-tooltip": "Reimposta larghezza titolo" + }, + "tray": { + "chevron-color-description": "Applica i colori del tema allโ€™icona chevron del cassetto.", + "chevron-color-label": "Colore chevron", + "colorize-icons-description": "Applica i colori del tema alle icone tray.", + "colorize-icons-label": "Colora icone", + "drawer-enabled-description": "Se attivo, gli elementi tray non fissati vengono mostrati in un pannello cassetto.
Se disattivo, tutti gli elementi tray vengono mostrati in linea.", + "drawer-enabled-label": "Abilita cassetto", + "hide-passive-description": "Se attivo, gli elementi tray con stato 'Passive' verranno nascosti.", + "hide-passive-label": "Nascondi elementi passivi" + }, + "volume": { + "display-mode-description": "Scegli come vuoi che questo valore venga mostrato." + }, + "workspace": { + "character-count-description": "Numero di caratteri da mostrare nei nomi workspace (1-10).", + "character-count-label": "Numero caratteri", + "empty-color-description": "Imposta il colore di sfondo per workspace vuote.", + "empty-color-label": "Colore workspace vuota", + "enable-scrollwheel-description": "Passa tra workspace usando la rotella del mouse.", + "enable-scrollwheel-label": "Scorri per cambiare workspace", + "focused-color-description": "Imposta il colore di sfondo per la workspace focalizzata.", + "focused-color-label": "Colore workspace focalizzata", + "follow-focused-screen-description": "Mostra workspace dello schermo attualmente focalizzato invece dello schermo dove si trova la barra.", + "follow-focused-screen-label": "Segui schermo focalizzato", + "font-weight-description": "Imposta il peso visivo per il testo all'interno dello spazio di lavoro.", + "font-weight-label": "Peso del carattere", + "grouped-border-opacity-description": "Imposta il livello di opacitร  dei bordi contenitore workspace.", + "grouped-border-opacity-label": "Opacitร  bordi", + "hide-unoccupied-description": "Non mostrare workspace senza finestre.", + "hide-unoccupied-label": "Nascondi non occupate", + "label-mode-description": "Scegli come visualizzare le etichette workspace.", + "label-mode-label": "Modalitร  etichette", + "occupied-color-description": "Imposta il colore di sfondo per workspace occupate.", + "occupied-color-label": "Colore workspace occupata", + "pill-size-description": "Regola la dimensione delle pill workspace.", + "pill-size-label": "Dimensione pill", + "reverse-scrolling-description": "Inverti direzione del cambio workspace quando scorri.", + "reverse-scrolling-label": "Inverti scorrimento", + "show-applications-description": "Mostra icone applicazioni dentro ogni workspace.", + "show-applications-hover-description": "Mostra le icone delle applicazioni all'interno di ogni spazio di lavoro quando lo spazio di lavoro viene sorvolato.", + "show-applications-hover-label": "Mostra applicazioni al passaggio del mouse", + "show-applications-label": "Mostra applicazioni", + "show-badge-description": "Mostra badge numero workspace in modalitร  raggruppata.", + "show-badge-label": "Mostra badge workspace", + "show-labels-only-when-occupied-description": "Mostra etichette workspace solo quando contengono finestre.", + "show-labels-only-when-occupied-label": "Mostra etichette solo se occupate", + "unfocused-icons-opacity-description": "Imposta il livello di opacitร  delle icone app non focalizzate.", + "unfocused-icons-opacity-label": "Opacitร  icone non focalizzate" + } + }, + "battery": { + "all-batteries": "Tutte le batterie (combinate)", + "battery-health": "Stato batteria", + "battery-level": "Livello batteria", + "capacity-level": "Capacitร : {level}", + "charging-rate": "Velocitร  di carica: {rate} W", + "discharging-rate": "Velocitร  di scarica: {rate} W", + "health": "Salute: {percent}%", + "inhibit-idle-description": "Mantiene il sistema attivo.", + "no-battery-detected": "Nessuna batteria rilevata", + "plugged-in": "Collegato alla corrente", + "power-profile": "Profilo energetico", + "time-left": "Tempo rimanente: {time}", + "time-until-full": "Tempo alla carica completa: {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "Dispositivi disponibili", + "blocked": "Bloccato", + "connected-devices": "Dispositivi connessi", + "device-address": "Indirizzo dispositivo", + "disabled": "Bluetooth disattivato", + "discoverable": "Visibile", + "enable-message": "Attiva il Bluetooth per vedere i dispositivi disponibili.", + "known-devices": "Dispositivi conosciuti", + "no-devices": "Nessun dispositivo disponibile", + "paired-devices": "Dispositivi associati", + "pairing-mode": "Assicurati che il dispositivo sia in modalitร  associazione.", + "scanning": "Ricerca dispositivi in corso...", + "signal-text-excellent": "Segnale: Eccellente", + "signal-text-fair": "Segnale: Discreto", + "signal-text-good": "Segnale: Buono", + "signal-text-poor": "Segnale: Scarso", + "signal-text-unknown": "Segnale: Sconosciuto", + "signal-text-very-poor": "Segnale: Molto scarso" + } + }, + "changelog": { + "error": { + "fetch-failed": "Impossibile caricare il changelog. Riprova piรน tardi.", + "rate-limit": "Limite di richieste GitHub superato. Riprova tra qualche minuto." + }, + "panel": { + "buttons-discord": "Unisciti al nostro Discord", + "buttons-dismiss": "OK", + "buttons-feedback": "Invia feedback", + "empty": "Le note di rilascio non sono ancora disponibili.", + "highlight-title": "Novitร  principali", + "released": "Rilasciato il {date}", + "subtitle-fresh": "Grazie per aver installato Noctalia! Ecco cosa include questa build.", + "subtitle-updated": "Aggiornato da {previousVersion}", + "title": "Novitร  della versione {version}", + "version": "Versione {version}", + "version-new-user": "Nuova installazione" + } + }, + "common": { + "actions": "Azioni", + "active": "Attivo", + "add": "Aggiungi", + "appearance": "Aspetto", + "apply": "Applica", + "auto-connect": "Connessione automatica", + "automation": "Automazione", + "available": "Disponibile", + "back": "Indietro", + "battery": "Batteria", + "behavior": "Comportamento", + "bluetooth": "Bluetooth", + "brightness": "Luminositร ", + "browse": "Sfoglia", + "calendar": "Calendario", + "calendar-panel": "Pannello calendario", + "cancel": "Annulla", + "cards": "Schede", + "charging": "In carica", + "clear": "Cancella", + "clipboard": "Appunti", + "close": "Chiudi", + "color-muted": "Smorzato", + "colors": "Colori", + "command": "Comando", + "confirm": "Conferma", + "connect": "Connetti", + "connected": "Connesso", + "connecting": "Connessione...", + "continue": "Continua", + "contributors": "Contributori", + "copied-to-clipboard": "Copiato negli appunti", + "countdown": "Conto alla rovescia", + "customize": "Personalizza", + "date": "Data", + "default": "Predefinito", + "delete": "Elimina", + "devices": "Dispositivi", + "disabled": "Disattivato", + "discharging": "In scarica", + "disconnect": "Disconnetti", + "disconnected": "Disconnesso", + "disconnecting": "Disconnessione...", + "display-mode": "Modalitร  di visualizzazione", + "documentation": "Documentazione", + "download": "Scarica", + "duration": "Durata", + "dysfunctional": "Non funzionante", + "edit": "Modifica", + "enabled": "Attivato", + "error": "Errore", + "ethernet": "Ethernet", + "events": "Eventi", + "execute": "Esegui", + "faithful": "Fedele", + "focus": "Messa a fuoco", + "font-weight-bold": "Grassetto", + "font-weight-light": "Sottile", + "font-weight-medium": "Medio", + "font-weight-regular": "Normale", + "font-weight-semibold": "Semibold", + "frequency": "Frequenza", + "gateway": "Gateway", + "general": "Generale", + "height": "Altezza", + "hibernate": "Ibernazione", + "history": "Cronologia", + "icon": "Icona", + "idle": "Inattivo", + "info": "Info", + "input": "Ingresso", + "install": "Installa", + "installed": "Installato", + "interface": "Interfaccia", + "internet": "Internet", + "keybind": "Scorciatoia", + "language": "Lingua", + "loading": "Caricamento...", + "local": "Locale", + "location": "Posizione", + "lock": "Blocca", + "logout": "Disconnetti sessione", + "look": "Aspetto", + "margins": "Margini", + "media": "Media", + "media-player": "Lettore multimediale", + "memory": "Memoria", + "monitors": "Monitor", + "move-down": "Sposta in basso", + "move-up": "Sposta su", + "network": "Rete", + "next": "Successivo", + "night-light": "Luce notturna", + "no": "No", + "no-results": "Nessun risultato", + "no-summary": "Nessun riepilogo", + "none": "Nessuno", + "not-found": "Non trovato", + "notifications": "Notifiche", + "official": "Ufficiale", + "on-surface": "Su superficie", + "output": "Uscita", + "pair": "Associa", + "paired": "Associato", + "pairing": "Associazione...", + "panels": "Pannelli", + "password": "Password", + "pause": "Pausa", + "performance": "Prestazioni", + "pin": "Fissa", + "play": "Riproduci", + "polling": "Interrogazione", + "position": "Posizione", + "previous": "Precedente", + "primary": "Primario", + "random": "Casuale", + "reboot": "Riavvia", + "reboot-to-uefi": "Riavvia in UEFI", + "record": "Avvia registrazione", + "refresh": "Aggiorna", + "required": "(obbligatorio)", + "reset": "Reimposta", + "reset-to-default": "Ripristina predefiniti", + "result-count": "{count} risultato", + "result-count-plural": "{count} risultati", + "resume": "Riprendi", + "save": "Salva", + "scanning": "Scansione...", + "screen-corners": "Angoli schermo", + "search": "Cerca", + "second": "{count} secondo", + "second-plural": "{count} secondi", + "secondary": "Secondario", + "security": "Sicurezza", + "select": "Seleziona", + "select-color": "Seleziona colore", + "select-color-description": "Applica i colori del tema per evidenziare.", + "select-icon-color": "Seleziona colore icona", + "settings": "Impostazioni", + "shortcuts": "Scorciatoie", + "shutdown": "Spegni", + "signal": "Segnale", + "sound": "Suono", + "sources": "Sorgenti", + "start": "Avvia", + "stop": "Ferma", + "supporters": "Sostenitori", + "suspend": "Sospendi", + "templates": "Template", + "tertiary": "Terziario", + "test": "Prova", + "thresholds": "Soglie", + "title": "Titolo", + "toast": "Notifica popup", + "trusted": "Attendibile", + "uninstall": "Disinstalla", + "unknown": "Sconosciuto", + "unpair": "Rimuovi associazione", + "unpin": "Sblocca", + "update": "Aggiorna", + "upload": "Carica", + "username": "Nome utente", + "userspace-reboot": "Riavvia spazio utente", + "version": "Versione", + "vibrant": "Vivace", + "visualizer": "Visualizzatore", + "volume": "Volume", + "volumes": "Volumi", + "wallpaper": "Sfondo", + "weather": "Meteo", + "weather-loading": "Caricamento meteo...", + "weather-no-location": "Imposta una posizione nelle Impostazioni per vedere il meteo", + "week": "Settimana", + "widgets": "Widget", + "width": "Larghezza", + "wifi": "Wi-Fi", + "windows": "Finestre", + "yes": "Sรฌ" + }, + "control-center": { + "power-profile": { + "tooltip-action": "Profilo energetico", + "tooltip-disabled": "Installa power-profiles-daemon per usare i profili energetici" + }, + "wifi": { + "label-disconnected": "Wi-Fi disconnesso", + "label-ethernet": "Ethernet" + } + }, + "display-modes": { + "always-hide": "Nascondi sempre", + "always-show": "Mostra sempre", + "force-open": "Apri sempre", + "on-hover": "Al passaggio" + }, + "hide-modes": { + "auto-hide": "Nascondi automaticamente", + "hidden": "Nascondi se vuoto", + "idle": "Nascondi quando inattivo", + "non-exclusive": "Non esclusivo", + "transparent": "Trasparente se vuoto", + "visible": "Sempre visibile" + }, + "launcher": { + "categories": { + "all": "Tutte", + "audiovideo": "Audio e video", + "chat": "Chat", + "development": "Sviluppo", + "education": "Formazione", + "emoji-activity": "Attivitร ", + "emoji-animals": "Animali", + "emoji-flags": "Bandiere", + "emoji-food": "Cibo e bevande", + "emoji-nature": "Natura", + "emoji-objects": "Oggetti", + "emoji-people": "Persone e corpo", + "emoji-recent": "Recenti", + "emoji-symbols": "Simboli", + "emoji-travel": "Viaggi e luoghi", + "game": "Giochi", + "graphics": "Grafica", + "misc": "Varie", + "office": "Ufficio", + "pinned": "Fissate", + "system": "Sistema", + "webbrowser": "Browser web" + }, + "providers": { + "applications": "Applicazioni", + "calculator": "Calcolatrice", + "calculator-deprecated": "Il comando >calc รจ deprecato e verrร  rimosso presto. Digita direttamente le espressioni matematiche nella ricerca.", + "calculator-description": "Calcolatrice โ€” valuta espressioni matematiche", + "calculator-enter-expression": "Inserisci unโ€™espressione matematica", + "calculator-press-enter-to-copy": "Premi Invio per copiare il risultato", + "clipboard": "Cronologia appunti", + "clipboard-clear-description": "Cancella tutta la cronologia appunti", + "clipboard-clear-description-full": "Rimuovi tutti gli elementi dalla cronologia appunti", + "clipboard-clear-history": "Cancella cronologia appunti", + "clipboard-delete": "Elimina voce appunti", + "clipboard-history-disabled": "Cronologia appunti disattivata", + "clipboard-history-disabled-description": "Attiva la cronologia appunti nelle impostazioni o installa cliphist", + "clipboard-loading": "Caricamento cronologia appunti...", + "clipboard-search-description": "Cerca nella cronologia appunti", + "command-description": "Esegui comandi shell", + "emoji": "Selettore emoji", + "emoji-loading": "Caricamento emoji...", + "emoji-loading-description": "Attendi prego", + "emoji-no-recent": "Nessuna emoji recente", + "emoji-search-description": "Cerca e copia emoji", + "settings-search-description": "Cerca e vai alle impostazioni", + "windows-search-description": "Cerca e metti a fuoco finestre aperte" + } + }, + "lock-screen": { + "authenticating": "Autenticazione in corso...", + "password": "Inserisci la password...", + "restart": "Riavvia", + "shut-down": "Spegni" + }, + "notifications": { + "panel": { + "click-to-collapse": "Fai clic per comprimere", + "click-to-expand": "Fai clic per espandere", + "description": "Le notifiche appariranno qui quando arrivano.", + "no-notifications": "Nessuna notifica" + }, + "range": { + "earlier": "Precedenti", + "today": "Oggi", + "yesterday": "Ieri" + }, + "time": { + "diff-d": "1 giorno fa", + "diff-dd": "{diff} giorni fa", + "diff-h": "1 ora fa", + "diff-hh": "{diff} ore fa", + "diff-m": "1 minuto fa", + "diff-mm": "{diff} minuti fa", + "now": "ora" + } + }, + "options": { + "bar": { + "density-comfortable": "Comoda", + "density-compact": "Compatta", + "density-default": "Predefinita", + "density-mini": "Mini", + "density-spacious": "Ampia", + "display-mode-always-visible": "Sempre visibile", + "type-floating": "Flottante", + "type-framed": "Incorniciata", + "type-simple": "Semplice" + }, + "control-center": { + "quick-settings-style-classic": "Classico", + "quick-settings-style-modern": "Moderno" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "Comoda", + "compact": "Compatta", + "default": "Predefinita" + }, + "launcher-view-mode": { + "grid": "Griglia", + "list": "Elenco" + }, + "notification-density": { + "compact": "Compatta", + "default": "Predefinita" + }, + "scrolling-modes": { + "always": "Scorri sempre", + "hover": "Scorri al passaggio del mouse", + "never": "Non scorrere mai" + }, + "session-menu-grid-layout": { + "grid": "Griglia", + "single-row": "Singola riga" + }, + "settings-panel-mode": { + "attached": "Pannello agganciato alla barra", + "centered": "Pannello centrato", + "window": "Finestra separata" + }, + "visualizer-types": { + "linear": "Lineare", + "mirrored": "Specchiato", + "wave": "Onda" + }, + "visualizer-visibility": { + "with-background": "Solo con sfondo" + }, + "workspace-labels": { + "index": "Indice", + "index-and-name": "Indice e nome", + "name": "Nome" + } + }, + "panels": { + "about": { + "become-supporter": "Diventa sostenitore", + "changelog": "Visualizza changelog", + "changelog-on-startup": "Mostra il changelog all'aggiornamento", + "changelog-on-startup-desc": "Mostra automaticamente il changelog quando Noctalia viene aggiornato.", + "contributors-description": "Un ringraziamento al nostro {count} contributore fantastico!", + "contributors-description-plural": "Un ringraziamento ai nostri {count} contributori fantastici!", + "copy-info": "Copia info", + "debug-disabled": "Modalitร  debug disattivata", + "debug-enabled": "Modalitร  debug attivata", + "info-copied": "Info copiate negli appunti", + "noctalia-available": "Disponibile:", + "noctalia-desc": "Una shell desktop elegante e minimale, progettata con cura per Wayland, costruita con Quickshell.", + "noctalia-git-commit": "Commit Git:", + "noctalia-installed-version": "Versione installata:", + "noctalia-latest-version": "Ultima versione:", + "noctalia-qs-version": "Noctalia QS versione:", + "noctalia-title": "Shell Noctalia", + "privacy-policy": "Informativa privacy", + "support": "Supportaci", + "supporter-badge": "Sostenitore", + "supporters-desc": "Un enorme grazie al nostro fantastico sostenitore!", + "supporters-desc-plural": "Un enorme grazie ai nostri {count} fantastici sostenitori!", + "supporters-loading": "Caricamento sostenitori...", + "system-board": "Scheda madre:", + "system-cpu": "CPU:", + "system-disk": "Disco:", + "system-gpu": "GPU:", + "system-host": "Host:", + "system-install-hint": "Installa fastfetch per visualizzare le informazioni di sistema", + "system-kernel": "Kernel:", + "system-loading": "Caricamento informazioni sistema...", + "system-memory": "Memoria:", + "system-monitor": "Monitor:", + "system-not-installed": "fastfetch non รจ installato", + "system-os": "SO:", + "system-packages": "Pacchetti:", + "system-product": "Prodotto:", + "system-title": "Informazioni di sistema", + "system-uptime": "Uptime:", + "system-wm": "WM:", + "telemetry-data-copied": "Dati telemetria copiati negli appunti", + "telemetry-desc": "Aiuta a migliorare Noctalia condividendo info di sistema anonime (risoluzione schermo, compositor, distro). Inviate una sola volta allโ€™avvio, nessun tracciamento, dati eliminati automaticamente dopo 30 giorni.", + "telemetry-enabled": "Invia informazioni di sistema anonime", + "telemetry-show-data": "Visualizza dati", + "telemetry-title": "Privacy", + "title": "Informazioni", + "up-to-date": "Sei aggiornato!", + "update-available": "Aggiornamento disponibile", + "view-commit": "Visualizza commit su GitHub" + }, + "audio": { + "devices-desc": "Configura i dispositivi audio di input e output disponibili.", + "devices-input-device-description": "Seleziona il dispositivo audio di input desiderato.", + "devices-input-device-label": "Dispositivo input", + "devices-output-device-description": "Seleziona il dispositivo audio di output desiderato.", + "devices-output-device-label": "Dispositivo output", + "devices-title": "Dispositivi audio", + "external-mixer-description": "Inserisci il comando o il percorso dellโ€™applicazione da avviare quando attivi il mixer audio esterno.", + "external-mixer-label": "Comando mixer audio esterno", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "Imposta le applicazioni multimediali preferite e ignorate.", + "media-excluded-player-description": "Aggiungi parole chiave per i player che vuoi ignorare. Ogni parola chiave deve stare su una nuova riga.", + "media-excluded-player-label": "Player escluso", + "media-excluded-player-placeholder": "digita una sottostringa e premi +", + "media-frame-rate-description": "Valori piรน alti sono piรน fluidi ma usano piรน risorse.", + "media-frame-rate-label": "Frame rate", + "media-primary-player-description": "Inserisci una parola chiave per identificare il tuo player principale.", + "media-primary-player-label": "Player principale", + "media-primary-player-placeholder": "es. spotify, vlc, mpv", + "media-scrolling-speed-description": "Tempo in secondi affinchรฉ il titolo scorra dallโ€™inizio alla fine.", + "media-scrolling-speed-label": "Velocitร  scorrimento", + "media-scrolling-title-description": "Abilita lo scorrimento continuo per titoli media lunghi.", + "media-scrolling-title-label": "Titolo scorrevole", + "media-title": "Lettori multimediali", + "on-middle-clicked-description": "Comando da eseguire quando il pulsante riceve clic centrale.", + "panel-applications-empty": "Nessuna applicazione sta riproducendo audio", + "spectrum-mirrored-description": "Specchia lo spettro in modo che le basse frequenze siano al centro. Quando disattivato, le basse frequenze sono a sinistra e le alte a destra.", + "spectrum-mirrored-label": "Spettro speculare", + "title": "Audio", + "visualizer-type-description": "Scegli un tipo di visualizzazione per la riproduzione multimediale.", + "visualizer-type-label": "Tipo visualizzazione", + "volumes-desc": "Regola i controlli del volume e i livelli audio.", + "volumes-feedback-sound-file-description": "Percorso del file audio riprodotto quando si cambia il volume.", + "volumes-feedback-sound-file-label": "File audio di feedback del volume", + "volumes-feedback-sound-file-select-title": "Seleziona file audio di feedback volume", + "volumes-input-volume-description": "Livello volume di input del microfono.", + "volumes-mute-input-description": "Silenzia lโ€™input audio predefinito (microfono).", + "volumes-mute-input-label": "Silenzia input audio", + "volumes-mute-output-description": "Silenzia lโ€™output audio principale del sistema.", + "volumes-mute-output-label": "Silenzia output audio", + "volumes-output-volume-description": "Livello volume di sistema.", + "volumes-step-size-description": "Regola la dimensione del passo per le variazioni volume (rotella mouse, scorciatoie tastiera).", + "volumes-step-size-label": "Passo volume", + "volumes-volume-feedback-description": "Riproduci un suono di feedback quando regoli il volume.", + "volumes-volume-feedback-label": "Riproduci suono feedback volume", + "volumes-volume-overdrive-description": "Consenti di alzare il volume oltre il 100%. Potrebbe non essere supportato da tutto lโ€™hardware.", + "volumes-volume-overdrive-label": "Consenti overdrive volume" + }, + "bar": { + "appearance-auto-hide-delay-description": "Tempo prima che la barra si nasconda dopo lโ€™uscita del mouse", + "appearance-auto-hide-delay-label": "Ritardo nascondi", + "appearance-auto-hide-exclusive-note": "Nota: la zona esclusiva viene disabilitata automaticamente quando il nascondimento automatico รจ attivo", + "appearance-auto-show-delay-description": "Tempo prima che la barra appaia quando il mouse entra nel bordo", + "appearance-auto-show-delay-label": "Ritardo mostra", + "appearance-background-opacity-description": "Imposta lโ€™opacitร  sfondo specifica per la barra.", + "appearance-background-opacity-label": "Opacitร  sfondo barra", + "appearance-capsule-color-description": "Scegli un colore per le capsule barra oppure nessuno per usare il colore superficie predefinito.", + "appearance-capsule-color-label": "Colore capsula", + "appearance-capsule-opacity-description": "Imposta il livello opacitร  degli sfondi widget quando la capsula รจ mostrata.", + "appearance-capsule-opacity-label": "Opacitร  capsula", + "appearance-content-padding-description": "Regola la spaziatura tra i bordi della Bar e i Widget.", + "appearance-content-padding-label": "Spaziatura interna del contenuto", + "appearance-density-description": "Regola il padding della barra per un aspetto compatto o ampio.", + "appearance-density-label": "Densitร  barra", + "appearance-desc": "Personalizza aspetto e posizione della barra.", + "appearance-display-mode-description": "Scegli quando la barra รจ visibile.", + "appearance-enable-exclusion-zone-inset-description": "Riduci la zona di esclusione di 1 pixel fisico in modo che le finestre a filo si estendano perfettamente sotto il bordo della barra.", + "appearance-enable-exclusion-zone-inset-label": "Zona di esclusione interna", + "appearance-floating-description": "Mostra la barra come 'pill' flottante.", + "appearance-floating-label": "Barra flottante", + "appearance-font-scale-description": "Regola la scala dimensione font per il testo mostrato nella barra.", + "appearance-font-scale-label": "Scala font", + "appearance-frame-radius": "Raggio interno", + "appearance-frame-settings-description": "Regola spessore frame e raggio angolo interno", + "appearance-frame-settings-label": "Impostazioni frame", + "appearance-frame-thickness": "Spessore", + "appearance-hide-on-overview-description": "Nascondi barra e chiudi pannelli quando lโ€™overview compositor รจ attiva.", + "appearance-hide-on-overview-label": "Nascondi barra in overview", + "appearance-margins-description": "Regola i margini attorno alla barra flottante.", + "appearance-margins-horizontal": "Margine orizzontale", + "appearance-margins-vertical": "Margine verticale", + "appearance-outer-corners-description": "Mostra angoli curvati verso lโ€™esterno sulla barra.", + "appearance-outer-corners-label": "Angoli esterni", + "appearance-position-description": "Scegli dove posizionare la barra sullo schermo.", + "appearance-position-label": "Posizione barra", + "appearance-show-capsule-description": "Mostra sfondi widget.", + "appearance-show-capsule-label": "Mostra capsula", + "appearance-show-on-workspace-switch-description": "Mostra automaticamente la barra per poco quando cambia la workspace.", + "appearance-show-on-workspace-switch-label": "Mostra barra al cambio workspace", + "appearance-show-outline-description": "Mostra un bordo visibile attorno a ogni widget.", + "appearance-show-outline-label": "Mostra contorni widget", + "appearance-type-description": "Scegli lo stile barra: Semplice, Flottante o Incorniciata.", + "appearance-type-label": "Tipo barra", + "appearance-use-separate-opacity-description": "Abilita per usare un valore opacitร  separato per lo sfondo barra.", + "appearance-use-separate-opacity-label": "Usa opacitร  barra separata", + "appearance-widget-spacing-description": "Regola la spaziatura tra i widget nella barra.", + "appearance-widget-spacing-label": "Spaziatura widget", + "behavior-middle-click-command-description": "Comando da eseguire al clic centrale.", + "behavior-middle-click-command-label": "Comando Clic Centrale", + "behavior-middle-click-command-placeholder": "niri msg action commuta panoramica", + "behavior-middle-click-description": "Scegli cosa fa il clic centrale sulle aree vuote della Barra.", + "behavior-middle-click-follow-mouse-description": "Apri il pannello del clic centrale selezionato nella posizione del cursore.", + "behavior-middle-click-follow-mouse-label": "Clic centrale segue il mouse", + "behavior-middle-click-label": "Azione del clic centrale sulla Barra", + "behavior-right-click-command-description": "Comando da eseguire al clic destro.", + "behavior-right-click-command-label": "Comando Clic Destro", + "behavior-right-click-command-placeholder": "notify-send \"Clic destro\"", + "behavior-right-click-description": "Scegli cosa fa il clic destro sulle aree vuote della barra.", + "behavior-right-click-follow-mouse-description": "Apri il pannello del clic destro selezionato nella posizione del cursore.", + "behavior-right-click-follow-mouse-label": "Clic destro segue il mouse", + "behavior-right-click-label": "Azione al clic destro sulla Barra", + "behavior-wheel-wrap-description": "Quando abilitato, lo scorrimento continua dall'ultimo elemento al primo.", + "behavior-wheel-wrap-label": "Circolare", + "behavior-workspace-scroll-description": "Scegli cosa fa la rotellina del mouse sulle aree vuote della barra.", + "behavior-workspace-scroll-label": "Azione rotella mouse della Barra", + "behavior-workspace-scroll-option-content": "Contenuto", + "behavior-workspace-scroll-option-workspace": "Spazio di lavoro", + "monitor-configure-widgets": "Configura widget", + "monitor-override-settings": "Sovrascrivi impostazioni globali", + "monitor-override-settings-description": "Usa impostazioni personalizzate per questo monitor.", + "monitor-reset-all": "Reimposta tutto", + "monitor-widgets-title": "Configurazione Widget - {monitor}", + "monitors-desc": "Mostra barra su monitor specifici. Se non ne scegli nessuno, verranno usati tutti.", + "monitors-desc-new": "Configura quali monitor mostrano la barra e personalizza impostazioni per monitor.", + "monitors-title": "Visualizzazione monitor", + "title": "Barra", + "tray-blacklist-description": "Aggiungi regole esclusione tray, supporta wildcard (*).", + "tray-blacklist-label": "Lista nera", + "tray-blacklist-placeholder": "es. nm-applet, Fcitx*", + "tray-pin-application": "Fissa applicazione", + "tray-unpin-application": "Rimuovi applicazione fissata", + "use-global-widgets": "Usa widget globali", + "widgets-desc": "Trascina i widget per cambiarne lโ€™ordine. Usa il menu clic destro per spostare widget tra sezioni o rimuoverli.", + "widgets-title": "Posizionamento widget" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "Genera schemi colore dal tuo sfondo. Estrae automaticamente i colori per creare un tema coerente.", + "color-source-use-wallpaper-colors-label": "Usa colori dello sfondo", + "dark-mode-mode-description": "Abilita il passaggio automatico tra modalitร  chiara e scura.", + "dark-mode-mode-label": "Programmazione modalitร  scura", + "dark-mode-mode-manual": "Manuale", + "dark-mode-mode-off": "Disattivata", + "dark-mode-switch-description": "Passa a un tema piรน scuro per una visione piรน confortevole di notte.", + "delete-error-description": "Eliminazione di {scheme} non riuscita", + "delete-error-title": "Eliminazione non riuscita", + "delete-success-description": "{scheme} eliminato con successo", + "delete-success-title": "Schema colore eliminato", + "download-button": "Scarica altro", + "download-downloading": "Download in corso...", + "download-empty": "Nessuno schema colore disponibile", + "download-error-api-error": "Errore API: {status}", + "download-error-description": "Download di {scheme} non riuscito", + "download-error-download-failed": "Download fallito con codice uscita: {code}", + "download-error-invalid-response": "Formato risposta API non valido", + "download-error-no-files": "Nessun file trovato per lo schema", + "download-error-parse-failed": "Analisi risposta API non riuscita: {error}", + "download-error-rate-limit": "Limite richieste API GitHub superato", + "download-error-title": "Download non riuscito", + "download-fetching": "Recupero schemi colore disponibili...", + "download-success-description": "{scheme} scaricato con successo", + "download-success-title": "Schema colore scaricato", + "download-title": "Scarica Schemi Colore", + "method-description": { + "content": "Schema Material Design con estrazione colore ad alta fedeltร  che rispecchia da vicino i colori reali del contenuto sorgente.", + "dysfunctional": "Simile a Faithful ma sceglie come primario il secondo gruppo colore piรน dominante.", + "faithful": "Tenta di restare vicino al colore sorgente mantenendo una palette armoniosa.", + "fruit-salad": "Schema Material Design che produce una palette vivace e giocosa con tonalitร  varie e diversificate.", + "monochrome": "Schema Material Design in scala di grigi a tinta singola con contenuto cromatico minimo.", + "muted": "Genera tonalitร  piรน morbide e desaturate per un aspetto calmo e sobrio.", + "rainbow": "Schema Material Design che genera colori su tutto lo spettro per massima diversitร  cromatica.", + "tonal-spot": "Algoritmo predefinito Material Design. Crea palette bilanciate usando variazioni tonali del colore sorgente.", + "vibrant": "Massimizza la saturazione colore per palette audaci e dโ€™impatto con tonalitร  vivide." + }, + "predefined-desc": "Scegli da una raccolta di schemi colore predefiniti.", + "predefined-generate-templates-label": "Genera template per schemi predefiniti", + "predefined-title": "Schemi colore predefiniti", + "sync-gsettings-description": "Abbina il tema di sistema alla variante chiara o scura attiva.", + "sync-gsettings-label": "Sincronizza tema di sistema", + "templates-desc": "Applica colori ad applicazioni esterne.", + "templates-filter-description": "Mostra template di una categoria specifica.", + "templates-filter-label": "Filtra per categoria", + "templates-misc-description": "Crea i tuoi template.", + "templates-misc-label": "Avanzato", + "templates-misc-user-templates-description": "Abilita solo se sai cosa stai facendo โ€” consulta la documentazione online.", + "templates-misc-user-templates-label": "Abilita template utente", + "templates-none-detected": "Nessuno rilevato", + "templates-write-path": "Scrive: {filepath}", + "title": "Schema colore", + "wallpaper-method-description": "Scegli il tuo metodo preferito di generazione palette.", + "wallpaper-method-label": "Metodo generazione palette", + "wallpaper-monitor-source-description": "Seleziona quale monitor usare per estrarre i colori dallo sfondo.", + "wallpaper-monitor-source-label": "Sorgente generazione colore" + }, + "connections": { + "authentication-required": "Autenticazione richiesta", + "bluetooth-auto-connect-description": "Connettiti automaticamente ai dispositivi accoppiati fidati quando il Bluetooth รจ abilitato.", + "bluetooth-auto-connect-label": "Connetti automaticamente i dispositivi accoppiati", + "bluetooth-devices-unnamed": "I dispositivi senza nome non sono mostrati.", + "bluetooth-discoverable": "Questo dispositivo รจ visibile come {hostName} mentre questa scheda impostazioni รจ aperta.", + "bluetooth-rssi-polling-description": "Campiona periodicamente lโ€™RSSI dei dispositivi connessi tramite bluetoothctl. Potrebbe non essere disponibile per tutti i dispositivi; usa risorse minime quando attivo.", + "bluetooth-rssi-polling-interval-description": "Configura la frequenza di aggiornamento dellโ€™intensitร  segnale per i dispositivi connessi.", + "bluetooth-rssi-polling-interval-label": "Intervallo polling", + "bluetooth-rssi-polling-label": "Polling segnale Bluetooth", + "disable-discoverability-description": "Nascondi il tuo dispositivo ai dispositivi Bluetooth vicini.", + "disable-discoverability-label": "Disattiva visibilitร  dispositivo", + "hide-unnamed-devices-description": "Nascondi dispositivi che appaiono solo come indirizzi Bluetooth.", + "hide-unnamed-devices-label": "Nascondi dispositivi senza nome", + "pin-instructions": "Inserisci il codice PIN mostrato sul tuo dispositivo.", + "title": "Connessioni", + "wifi-header-text": "Usa il Wi-Fi per vedere le reti disponibili e connetterti a internet." + }, + "control-center": { + "cards-desc": "Personalizza quali controlli appaiono nel centro di controllo e in quale ordine.", + "desc": "Configura posizione e comportamento del pannello centro di controllo.", + "open-at-mouse-description": "Quando si fa clic destro su un'area vuota della Bar, apri il Centro di Controllo vicino al cursore. Se disabilitato, viene utilizzata l'impostazione di posizione sopra indicata.", + "open-at-mouse-label": "Apri al cursore con clic destro", + "position-description": "Scegli dove appare il pannello centro di controllo quando viene aperto.", + "shortcuts-custom-button-command-description": "Comando da eseguire quando il pulsante viene cliccato.", + "shortcuts-custom-button-enable-on-state-logic-description": "Abilita una seconda icona e uno stato 'attivo' basato su un comando di controllo.", + "shortcuts-custom-button-enable-on-state-logic-label": "Abilita logica stato attivo", + "shortcuts-custom-button-icon-description": "Seleziona unโ€™icona dalla libreria.", + "shortcuts-custom-button-on-clicked-label": "Comando clic sinistro", + "shortcuts-custom-button-on-middle-clicked-description": "Comando da eseguire al clic centrale del pulsante.", + "shortcuts-custom-button-on-state-command-description": "Comando da eseguire per verificare se il pulsante deve essere nello stato 'attivo'. Restituisce 0 per attivo, valore diverso da 0 per spento.", + "shortcuts-custom-button-on-state-command-label": "Comando controllo stato attivo", + "shortcuts-custom-button-on-state-icon-description": "Lโ€™icona del pulsante quando รจ nello stato 'attivo'.", + "shortcuts-custom-button-on-state-icon-label": "Icona stato attivo", + "shortcuts-custom-button-state-checks-add": "Aggiungi controllo stato", + "shortcuts-custom-button-state-checks-command": "Comando da eseguire per questo controllo stato", + "shortcuts-custom-button-state-checks-label": "Controlli stato", + "shortcuts-custom-button-state-checks-remove": "Rimuovi", + "shortcuts-custom-button-tooltip-description": "Tooltip da mostrare al passaggio del mouse sul pulsante.", + "shortcuts-custom-button-tooltip-label": "Suggerimento", + "shortcuts-title": "Widget scorciatoie", + "system-monitor-disk-path-description": "Seleziona quale punto di mount disco deve monitorare la scheda monitor di sistema nel centro di controllo.", + "system-monitor-disk-path-label": "Percorso disco monitor di sistema", + "title": "Centro di controllo" + }, + "desktop-widgets": { + "clock-enabled-description": "Mostra un widget orologio sul desktop.", + "clock-enabled-label": "Abilita widget orologio", + "clock-format-label": "Formato", + "clock-height-description": "Altezza del widget orologio in pixel.", + "clock-height-label": "Altezza", + "clock-minimal-mode-description": "Usa una visualizzazione orologio minimale con ora e data.", + "clock-minimal-mode-label": "Modalitร  minimale", + "clock-rounded-corners-description": "Usa angoli arrotondati per lo sfondo del widget.", + "clock-rounded-corners-label": "Angoli arrotondati", + "clock-show-background-description": "Mostra il contenitore sfondo per il widget orologio.", + "clock-show-background-label": "Mostra sfondo", + "clock-show-date-description": "Mostra la data corrente sotto lโ€™ora.", + "clock-show-date-label": "Mostra data", + "clock-show-seconds-description": "Mostra i secondi nellโ€™orario.", + "clock-show-seconds-label": "Mostra secondi", + "clock-style-analog": "Analogico", + "clock-style-binary": "Binario", + "clock-style-description": "Scegli lo stile di visualizzazione dellโ€™orologio.", + "clock-style-digital": "Digitale", + "clock-style-label": "Stile orologio", + "clock-style-minimal": "Minimale", + "clock-width-description": "Larghezza del widget orologio in pixel.", + "cpu-intensive-note": "I widget contrassegnati con ! usano piรน CPU e vanno abilitati con attenzione.", + "edit-mode-button-label": "Entra in modalitร  modifica", + "edit-mode-controls-explanation": "Clic sinistro e trascina: sposta o ridimensiona il widget.\nClic destro: apri opzioni menu contestuale.", + "edit-mode-description": "Abilita modalitร  modifica per spostare e riposizionare i widget desktop. Quando attiva, i widget mostrano un contorno di trascinamento e possono essere riposizionati.", + "edit-mode-exit-button": "Esci da modalitร  modifica", + "edit-mode-grid-snap-label": "Aggancio griglia", + "edit-mode-grid-snap-scale-label": "Scala di aggancio alla griglia", + "edit-mode-label": "Modalitร  modifica", + "enabled-description": "Abilita o disabilita completamente i widget desktop.", + "enabled-label": "Abilita widget desktop", + "general-desc": "Configura i widget che appaiono sul desktop.", + "general-title": "Widget desktop", + "media-player-enabled-description": "Mostra un widget lettore multimediale sul desktop.", + "media-player-enabled-label": "Abilita widget lettore multimediale", + "media-player-rounded-corners-description": "Abilita angoli arrotondati sui bordi del widget.", + "media-player-show-album-art-description": "Mostra la copertina album e le informazioni traccia (titolo e artista).", + "media-player-show-album-art-label": "Mostra copertina e titolo", + "media-player-show-background-description": "Mostra il contenitore sfondo.", + "media-player-show-buttons-description": "Mostra pulsanti controllo media (riproduci/pausa, precedente, successivo).", + "media-player-show-buttons-label": "Mostra pulsanti", + "media-player-show-visualizer-description": "Mostra la sovrapposizione visualizzatore audio.", + "media-player-visualizer-type-description": "Scegli un tipo di visualizzazione.", + "overview-enabled-description": "Mostra widget desktop quando lโ€™overview del compositor รจ aperta.", + "overview-enabled-label": "Mostra in overview", + "system-stat-layout-bottom": "In basso", + "system-stat-layout-description": "Scegli come mostrare la legenda rispetto al grafico.", + "system-stat-layout-label": "Disposizione", + "system-stat-layout-side": "Laterale", + "system-stat-rounded-corners-description": "Usa angoli arrotondati per lo sfondo del widget.", + "system-stat-rounded-corners-label": "Angoli arrotondati", + "system-stat-show-background-description": "Mostra il contenitore sfondo per il widget statistiche sistema.", + "system-stat-show-background-label": "Mostra sfondo", + "system-stat-stat-type-description": "Scegli quale statistica di sistema visualizzare.", + "system-stat-stat-type-label": "Tipo statistica", + "title": "Widget desktop", + "weather-enabled-description": "Mostra un widget meteo sul desktop.", + "weather-enabled-label": "Abilita widget meteo", + "weather-show-background-description": "Mostra il contenitore sfondo per il widget meteo." + }, + "display": { + "monitors-backlight-device-auto-option": "Predefinito", + "monitors-backlight-device-description": "Seleziona un dispositivo di retroilluminazione per questa uscita.", + "monitors-backlight-device-label": "Dispositivo retroilluminazione", + "monitors-brightness-step-description": "Regola la dimensione del passo per le variazioni luminositร  (rotella mouse e scorciatoie tastiera).", + "monitors-brightness-step-label": "Passo luminositร ", + "monitors-brightness-unavailable-ddc-disabled": "Controllo luminositร  non disponibile. Abilita \"Supporto luminositร  esterna\" per controllare la luminositร  di questo schermo.", + "monitors-brightness-unavailable-generic": "Il controllo luminositร  non รจ disponibile per questo schermo.", + "monitors-desc": "Regola le impostazioni di luminositร  per ciascun monitor.", + "monitors-enforce-minimum-description": "Risolve il problema della retroilluminazione che su alcuni schermi si spegne completamente a luminositร  0%.", + "monitors-enforce-minimum-label": "Forza luminositร  minima (1%)", + "monitors-external-brightness-description": "Abilita il supporto DDCUtil per controllare la luminositร  dei monitor esterni tramite protocollo DDC/CI.", + "monitors-external-brightness-label": "Supporto luminositร  esterna", + "monitors-global-brightness-description": "Regola la luminositร  di tutti i monitor contemporaneamente.", + "monitors-global-brightness-label": "Tutti i monitor", + "monitors-title": "Impostazioni per monitor", + "night-light-auto-schedule-description": "Basato su orario di alba e tramonto a {location} โ€” consigliato.", + "night-light-auto-schedule-label": "Programmazione automatica", + "night-light-desc": "Riduce lโ€™emissione di luce blu per aiutarti a dormire meglio e ridurre lโ€™affaticamento visivo.", + "night-light-enable-description": "Applica un filtro colore caldo per ridurre lโ€™emissione di luce blu.", + "night-light-enable-label": "Abilita Luce notturna", + "night-light-force-activation-description": "Ignora la programmazione e applica subito il filtro notturno.", + "night-light-force-activation-label": "Forza attivazione", + "night-light-manual-schedule-description": "Imposta orari personalizzati per alba e tramonto.", + "night-light-manual-schedule-label": "Programmazione manuale", + "night-light-manual-schedule-select-start": "Seleziona ora inizio", + "night-light-manual-schedule-select-stop": "Seleziona ora fine", + "night-light-manual-schedule-sunrise": "Ora alba", + "night-light-manual-schedule-sunset": "Ora tramonto", + "night-light-temperature-day": "Giorno", + "night-light-temperature-day-description": "Controlla la temperatura durante il giorno.", + "night-light-temperature-description": "Imposta il calore colore per notte e giorno.", + "night-light-temperature-label": "Temperatura colore", + "night-light-temperature-night": "Notte", + "night-light-temperature-night-description": "Controlla la temperatura durante la notte.", + "title": "Schermo" + }, + "dock": { + "appearance-background-opacity-description": "Regola lโ€™opacitร  dello sfondo del dock.", + "appearance-border-radius-description": "Regola il raggio bordo del dock.", + "appearance-border-radius-label": "Raggio bordo", + "appearance-colorize-icons-description": "Applica i colori del tema alle icone app del dock (solo app non focalizzate).", + "appearance-colorize-icons-label": "Colora icone", + "appearance-dead-opacity-description": "Regola lโ€™opacitร  delle icone app non in esecuzione.", + "appearance-dead-opacity-label": "Opacitร  app non attive", + "appearance-desc": "Personalizza comportamento e aspetto del dock.", + "appearance-display-auto-hide": "Nascondi automaticamente", + "appearance-display-description": "Scegli come si comporta il dock.", + "appearance-display-exclusive": "Esclusivo", + "appearance-dock-indicator-description": "Mostra un piccolo indicatore quando il dock รจ nascosto.", + "appearance-dock-indicator-label": "Indicatore dock", + "appearance-floating-distance-description": "Imposta la distanza tra il dock e il bordo dello schermo.", + "appearance-floating-distance-label": "Distanza flottante dock", + "appearance-group-apps-description": "Raggruppa piรน finestre della stessa app in una sola voce dock.", + "appearance-group-apps-label": "Raggruppa stesse app", + "appearance-group-click-action-cycle": "Scorri finestre", + "appearance-group-click-action-description": "Scegli cosa fa il clic sinistro sulle app raggruppate.", + "appearance-group-click-action-label": "Azione clic app raggruppata", + "appearance-group-click-action-list": "Apri elenco finestre", + "appearance-group-context-menu-mode-description": "Scegli come si comporta il menu contestuale per app raggruppate.", + "appearance-group-context-menu-mode-extended": "Esteso", + "appearance-group-context-menu-mode-label": "Modalitร  menu app raggruppata", + "appearance-group-context-menu-mode-list": "Elenco finestre", + "appearance-group-indicator-style-description": "Scegli come gli indicatori app raggruppate mostrano lo stato della finestra focalizzata.", + "appearance-group-indicator-style-dots": "Punti", + "appearance-group-indicator-style-label": "Stile indicatore raggruppato", + "appearance-group-indicator-style-number": "Numero", + "appearance-hide-show-speed-description": "Regola la velocitร  dellโ€™animazione mostra/nascondi del dock.", + "appearance-hide-show-speed-label": "Velocitร  mostra/nascondi", + "appearance-icon-size-description": "Regola la dimensione complessiva del dock.", + "appearance-icon-size-label": "Dimensione dock", + "appearance-inactive-indicators-description": "Mostra indicatori per tutte le app, non solo quella attualmente attiva.", + "appearance-inactive-indicators-label": "Indicatori in esecuzione", + "appearance-indicator-color-description": "Scegli il colore dell'indicatore del dock nascosto.", + "appearance-indicator-color-label": "Colore dell'indicatore", + "appearance-indicator-opacity-description": "Regola l'opacitร  dell'indicatore del dock nascosto.", + "appearance-indicator-opacity-label": "Opacitร  dell'indicatore", + "appearance-indicator-thickness-description": "Utilizza un indicatore del dock nascosto piรน spesso (6px invece di 3px).", + "appearance-indicator-thickness-label": "Indicatore piรน spesso", + "appearance-launcher-icon-description": "Scegli l'icona usata per il Dock Launcher.", + "appearance-launcher-icon-label": "Icona del Launcher", + "appearance-launcher-position-description": "Scegli dove appare lโ€™icona launcher nel dock.", + "appearance-launcher-position-end": "Fine", + "appearance-launcher-position-label": "Posizione launcher", + "appearance-launcher-position-start": "Inizio", + "appearance-launcher-use-distro-logo-description": "Usa il logo della tua distribuzione come icona del Dock Launcher.", + "appearance-launcher-use-distro-logo-label": "Usa il logo della distro invece dell'icona", + "appearance-pinned-static-description": "Spinge sempre le icone app fissate a sinistra in ordine statico.", + "appearance-pinned-static-label": "App fissate statiche", + "appearance-position-description": "Scegli dove appare il dock sullo schermo.", + "appearance-position-label": "Posizione", + "appearance-show-launcher-icon-description": "Mostra lโ€™icona launcher applicazioni nel dock.", + "appearance-show-launcher-icon-label": "Mostra launcher app", + "appearance-sit-on-frame-description": "Allinea il dock dentro il bordo frame invece di posizionarlo sopra.", + "appearance-sit-on-frame-label": "Dock sul frame", + "appearance-type-attached": "Ancorato", + "appearance-type-description": "Scegli tra pill flottante o barra statica agganciata al bordo.", + "appearance-type-floating": "Flottante", + "appearance-type-label": "Stile dock", + "enabled-description": "Mostra o nascondi completamente il dock.", + "enabled-label": "Abilita dock", + "monitors-desc": "Mostra dock su monitor specifici. Se non ne scegli nessuno, verranno usati tutti.", + "monitors-only-same-monitor-description": "Mostra solo app del monitor in cui si trova il dock.", + "monitors-only-same-monitor-label": "Solo app dello stesso monitor", + "monitors-title": "Visualizzazione monitor", + "title": "Dock" + }, + "general": { + "copy-settings": "Copia impostazioni", + "fonts-default-description": "Font principale usato in tutta lโ€™interfaccia.", + "fonts-default-label": "Font predefinito", + "fonts-default-placeholder": "Seleziona font predefinito...", + "fonts-default-scale-description": "Aumenta o riduci la dimensione del testo standard.", + "fonts-default-scale-label": "Dimensione font predefinito", + "fonts-default-search-placeholder": "Cerca font...", + "fonts-desc": "Scegli i font usati in tutta lโ€™interfaccia.", + "fonts-monospace-description": "Font monospaziato usato per numeri e visualizzazione statistiche.", + "fonts-monospace-label": "Font monospaziato", + "fonts-monospace-placeholder": "Seleziona font monospaziato...", + "fonts-monospace-scale-description": "Aumenta o riduci la dimensione del testo monospaziato.", + "fonts-monospace-scale-label": "Dimensione font monospaziato", + "fonts-monospace-search-placeholder": "Cerca font monospaziato...", + "fonts-title": "Font", + "keybinds-conflict-description": "La combinazione di tasti รจ giร  assegnata a {action}.", + "keybinds-conflict-title": "Conflitto scorciatoia", + "keybinds-description": "Configura i tasti di navigazione globali per pannelli e launcher.", + "keybinds-down": "Sposta in basso", + "keybinds-enter": "Conferma / Azione", + "keybinds-escape": "Chiudi / Indietro", + "keybinds-left": "Sposta a sinistra", + "keybinds-modifier-description": "Le scorciatoie devono includere un tasto modificatore (Ctrl o Alt).", + "keybinds-modifier-title": "Modificatore richiesto", + "keybinds-remove": "Rimuovi / Elimina", + "keybinds-right": "Sposta a destra", + "keybinds-title": "Scorciatoie navigazione", + "keybinds-up": "Sposta in alto", + "language-desc": "Scegli la lingua preferita per lโ€™applicazione.", + "language-select-auto-detect": "Automatico", + "language-select-description": "Seleziona la lingua usata nellโ€™interfaccia dellโ€™applicazione.", + "language-select-label": "Lingua applicazione", + "launch-setup-wizard": "Guidata di configurazione", + "profile-desc": "Modifica i dettagli utente e lโ€™avatar.", + "profile-picture-description": "La tua immagine profilo mostrata in tutta lโ€™interfaccia.", + "profile-picture-label": "Immagine profilo", + "profile-select-avatar": "Seleziona immagine avatar", + "profile-title": "Profilo", + "profile-tooltip": "Immagine profilo", + "reverse-scrolling-description": "Inverte la direzione di scorrimento interpretata", + "reverse-scrolling-label": "Inverti scorrimento", + "screen-corners-desc": "Personalizza arrotondamento angoli schermo ed effetti visivi.", + "screen-corners-radius-description": "Regola gli angoli arrotondati dello schermo.", + "screen-corners-radius-label": "Raggio angoli schermo", + "screen-corners-radius-reset": "Reimposta raggio angoli schermo", + "screen-corners-show-corners-description": "Mostra angoli arrotondati sul bordo dello schermo.", + "screen-corners-show-corners-label": "Mostra angoli schermo", + "screen-corners-solid-black-description": "Usa nero pieno invece del colore di sfondo della barra.", + "screen-corners-solid-black-label": "Angoli neri pieni", + "screen-corners-title": "Angoli schermo", + "settings-copied": "Impostazioni copiate negli appunti", + "smooth-scrolling-description": "Anima lo scorrimento delle liste per un'esperienza della rotella piรน fluida.", + "smooth-scrolling-label": "Scorrimento fluido", + "tab-basics": "Base", + "tab-keybinds": "Scorciatoie" + }, + "hooks": { + "color-generation-description": "Comando da eseguire al completamento della generazione del colore (ad es. dopo la tematizzazione basata sullo sfondo).", + "color-generation-label": "Colori generati", + "color-generation-placeholder": "ad es. notify-send \"Colori\" \"Generati\"", + "info-command-info-description": "โ€ข I comandi vengono eseguiti via shell (sh -lc)
โ€ข I comandi girano in background (staccati)
โ€ข I pulsanti test eseguono con i valori correnti", + "info-parameters-description": "โ€ข Wallpaper hook: $1 = percorso dello sfondo, $2 = nome dello schermo, $3 = tema (scuro/chiaro)
โ€ข Theme toggle hook: $1 = true/false (stato della modalitร  scura)
โ€ข Screen lock/unlock hooks: $1 = lock/unlock (stato di blocco schermo)
โ€ข Performance mode hooks: Nessun parametro
โ€ข Session hook: $1 = action (shutdown/reboot)", + "info-parameters-label": "Parametri disponibili", + "noctalia-started-description": "Comando da eseguire quando Noctalia ha finito il caricamento.", + "noctalia-started-label": "Noctalia avviata", + "noctalia-started-placeholder": "es. notify-send 'Noctalia caricata'", + "performance-mode-disabled-description": "Comando da eseguire quando la modalitร  prestazioni Noctalia รจ disabilitata.", + "performance-mode-disabled-label": "Modalitร  prestazioni disabilitata", + "performance-mode-disabled-placeholder": "es. notify-send \"Prestazioni\" \"Modalitร  disabilitata\"", + "performance-mode-enabled-description": "Comando da eseguire quando la modalitร  prestazioni Noctalia รจ abilitata.", + "performance-mode-enabled-label": "Modalitร  prestazioni abilitata", + "performance-mode-enabled-placeholder": "es. notify-send \"Prestazioni\" \"Modalitร  abilitata\"", + "screen-lock-description": "Comando da eseguire quando lo schermo viene bloccato.", + "screen-lock-label": "Schermo bloccato", + "screen-lock-placeholder": "es. notify-send \"Schermo\" \"Bloccato\"", + "screen-unlock-description": "Comando da eseguire quando la schermata blocco viene sbloccata.", + "screen-unlock-label": "Schermo sbloccato", + "screen-unlock-placeholder": "es. notify-send \"Schermo\" \"Sbloccato\"", + "session-description": "Comando da eseguire prima di spegnimento o riavvio. Riceve il tipo azione in $1 (shutdown/reboot).", + "session-label": "Fine sessione", + "session-placeholder": "es. notify-send \"Sessione\" \"$1\"", + "system-hooks-desc": "Configura comandi da eseguire quando avvengono eventi di sistema.", + "system-hooks-enable-description": "Abilita o disabilita tutti i comandi hook.", + "system-hooks-enable-label": "Abilita hook", + "system-hooks-title": "Hook di sistema", + "theme-changed-description": "Comando da eseguire quando il tema passa tra Modalitร  Scura e Chiara.", + "theme-changed-label": "Tema cambiato", + "theme-changed-placeholder": "es. notify-send \"Tema\" \"Cambiato\"", + "title": "Hook", + "wallpaper-changed-description": "Comando da eseguire quando cambia lo sfondo.", + "wallpaper-changed-label": "Sfondo cambiato", + "wallpaper-changed-placeholder": "es. notify-send \"Sfondo\" \"Cambiato\"" + }, + "idle": { + "custom-add": "Aggiungi comando", + "custom-description": "Esegui un comando shell dopo un periodo di inattivitร .", + "custom-entry-command": "Comando", + "custom-entry-delete": "Elimina", + "custom-entry-edit": "Modifica Comando Personalizzato", + "custom-entry-name": "Nome", + "custom-entry-name-placeholder": "es. Spegni le luci", + "custom-entry-new": "Nuovo Comando Personalizzato", + "custom-entry-timeout": "Tempo inattivitร ", + "custom-entry-timeout-format": "{count} secondo", + "custom-entry-timeout-format-plural": "{count} secondi", + "custom-entry-unnamed": "Comando senza nome", + "custom-label": "Comandi inattivitร  personalizzati", + "enable-description": "Spegni automaticamente schermo, blocca o sospendi dopo un periodo di inattivitร .", + "enable-label": "Abilita gestione inattivitร ", + "fade-duration-description": "Secondi per lโ€™animazione dissolvenza al nero prima che parta ogni azione. Qualsiasi movimento mouse annulla la dissolvenza.", + "fade-duration-label": "Durata dissolvenza", + "lock-description": "Secondi di inattivitร  prima che si attivi la schermata blocco.", + "lock-label": "Schermata blocco", + "resume-command-label": "Riprendi comando", + "screen-off-description": "Secondi di inattivitร  prima che i monitor vengano spenti.", + "screen-off-label": "Spegni schermo", + "status-description": "Tempo di inattivitร  come riportato dal compositor.", + "status-label": "Tempo inattivitร ", + "suspend-description": "Secondi di inattivitร  prima che il sistema venga sospeso.", + "tab-behavior": "Comportamento", + "tab-custom": "Personalizzato", + "timeouts-description": "Imposta a 0 per disabilitare una fase. I timeout vengono sospesi mentre 'Mantieni attivo' รจ attivo.", + "timeouts-label": "Timeout", + "title": "Inattivitร ", + "unavailable": "Il monitoraggio inattivitร  nativo non รจ disponibile su questo compositor." + }, + "indicator": { + "default-value": "Default: {value}", + "system-default": "System Default" + }, + "launcher": { + "clipboard-desc": "Accedi e gestisci la cronologia appunti dal launcher.", + "execute-desc": "Configura come vengono avviate le applicazioni.", + "execute-title": "Esecuzione", + "settings-annotation-tool-description": "Comando da eseguire quando fai clic sul pulsante annota nella cronologia appunti. Lโ€™immagine verrร  passata in pipe a questo comando.", + "settings-annotation-tool-label": "Strumento annotazioni", + "settings-annotation-tool-placeholder": "es. 'gradia', 'satty -f -'", + "settings-auto-paste-description": "Incolla automaticamente lโ€™elemento appunti selezionato. Richiede wtype.", + "settings-auto-paste-label": "Incolla automaticamente", + "settings-clip-chips-description": "Mostra una barra delle schede per filtrare la cronologia degli appunti per tipo (Immagini, Link, File, Codice, ecc.).", + "settings-clip-chips-label": "Abilita chip di categoria", + "settings-clip-preview-description": "Mostra unโ€™anteprima del contenuto appunti quando usi il comando >clip.", + "settings-clip-preview-label": "Abilita anteprima clip", + "settings-clip-smart-icons-description": "Mostra icone specifiche per link, file, colori e codice invece di un'icona generica per gli appunti.", + "settings-clip-smart-icons-label": "Abilita icone intelligenti", + "settings-clip-wrap-text-description": "Va a capo il testo nellโ€™elenco appunti invece di troncarlo.", + "settings-clip-wrap-text-label": "Testo appunti a capo", + "settings-clipboard-history-description": "Accedi agli elementi copiati in precedenza dal launcher.", + "settings-clipboard-history-label": "Abilita cronologia appunti", + "settings-clipboard-watch-image-description": "Stringa comando completa passata a wl-paste per cambi immagine. (richiede riavvio)", + "settings-clipboard-watch-image-label": "Comando monitoraggio immagini", + "settings-clipboard-watch-text-description": "Stringa comando completa passata a wl-paste per cambi testo. (richiede riavvio)", + "settings-clipboard-watch-text-label": "Comando monitoraggio testo", + "settings-custom-launch-prefix-description": "Prefissa i comandi con un launcher personalizzato (es. 'runapp' per integrazione systemd).", + "settings-custom-launch-prefix-enabled-description": "Usa un prefisso personalizzato per avviare applicazioni invece del metodo predefinito.", + "settings-custom-launch-prefix-enabled-label": "Abilita prefisso avvio personalizzato", + "settings-custom-launch-prefix-label": "Prefisso avvio personalizzato", + "settings-density-description": "Regola la dimensione delle icone applicazione e la densitร  del launcher.", + "settings-density-label": "Densitร ", + "settings-desc": "Personalizza comportamento e aspetto del launcher.", + "settings-enable-session-search-description": "Mostra azioni sessione (blocco, spegnimento, riavvio, ecc.) durante la ricerca nel launcher.", + "settings-enable-session-search-label": "Abilita ricerca sessione", + "settings-enable-settings-search-description": "Mostra risultati impostazioni durante la ricerca nel launcher.", + "settings-enable-settings-search-label": "Abilita ricerca impostazioni", + "settings-enable-windows-search-description": "Cerca e metti a fuoco finestre attive.", + "settings-enable-windows-search-label": "Abilita ricerca finestre", + "settings-grid-view-description": "Mostra elementi in layout a griglia invece che a elenco.", + "settings-icon-mode-description": "Usa icone di sistema native invece delle icone Tabler.", + "settings-icon-mode-label": "Usa icone native", + "settings-ignore-mouse-input-description": "Disabilita interazione mouse e rotella nel launcher.", + "settings-ignore-mouse-input-label": "Ignora input mouse", + "settings-overlay-layer-description": "Mostra il launcher sul layer overlay, sopra le finestre fullscreen. Se attivo, il launcher non verrร  agganciato alla barra.", + "settings-overlay-layer-label": "Mostra sopra fullscreen", + "settings-position-description": "Scegli dove appare il pannello launcher.", + "settings-show-categories-description": "Mostra schede categoria per filtrare le applicazioni.", + "settings-show-categories-label": "Mostra categorie", + "settings-show-icon-background-description": "Mostra uno sfondo a rettangolo arrotondato dietro le icone.", + "settings-show-icon-background-label": "Mostra sfondo icone", + "settings-sort-by-usage-description": "Se attivo, le app avviate piรน spesso appaiono per prime nellโ€™elenco.", + "settings-sort-by-usage-label": "Ordina per piรน usate", + "settings-terminal-command-description": "Comando per avviare un terminale. Es. \"kitty -e\" o \"gnome-terminal --\".", + "settings-terminal-command-label": "Comando terminale", + "settings-use-app2unit-description": "Usa un metodo di avvio alternativo per gestire meglio i processi app e prevenire problemi.", + "settings-use-app2unit-label": "Usa App2Unit per avviare applicazioni", + "settings-view-mode-description": "Scegli il layout delle voci launcher.", + "settings-view-mode-label": "Modalitร  vista", + "title": "Launcher" + }, + "location": { + "auto-locate-description": "Rileva automaticamente la tua posizione periodicamente usando il tuo indirizzo IP.", + "auto-locate-label": "Localizzazione Automatica", + "calendar-cards-desc": "Organizza e abilita/disabilita le schede nel pannello calendario.", + "calendar-cards-title": "Schede calendario", + "calendar-header-label": "Intestazione calendario", + "calendar-month-label": "Mese calendario", + "date-time-12hour-format-description": "Mostra lโ€™ora in formato 12 ore nella schermata blocco e nel calendario. Lโ€™orologio della barra ha impostazioni proprie.", + "date-time-12hour-format-label": "Usa formato ora 12 ore", + "date-time-desc": "Personalizza come vengono mostrati data e ora.", + "date-time-first-day-of-week-automatic": "Automatico (usa locale di sistema)", + "date-time-first-day-of-week-description": "Scegli quale giorno inizia la settimana nel calendario.", + "date-time-first-day-of-week-label": "Primo giorno della settimana", + "date-time-show-events-description": "Mostra gli eventi nel pannello calendario.", + "date-time-show-events-label": "Mostra eventi calendario", + "date-time-title": "Data e ora", + "date-time-use-analog-description": "Mostra un orologio in stile analogico nella finestra calendario.", + "date-time-use-analog-label": "Usa orologio stile analogico", + "date-time-week-numbers-description": "Mostra nel calendario la settimana dellโ€™anno (es. settimana 38).", + "date-time-week-numbers-label": "Mostra numeri settimana", + "geolocate-now-button": "Localizza ora", + "location-desc": "Ottieni meteo accurato e pianificazione Luce notturna impostando la tua posizione.", + "location-search-description": "es. Torino, TO", + "location-search-label": "Cerca una posizione", + "location-search-placeholder": "Inserisci il nome della posizione", + "location-title": "La tua posizione", + "weather-desc": "Scegli lโ€™unitร  di temperatura preferita.", + "weather-enabled-description": "Mostra informazioni meteo in tutta lโ€™interfaccia e recupera dati meteo. Se disattivato, tutti gli elementi meteo saranno nascosti e non verranno effettuate richieste di rete.", + "weather-enabled-label": "Abilita meteo", + "weather-fahrenheit-description": "Mostra la temperatura in Fahrenheit invece che in Celsius.", + "weather-fahrenheit-label": "Mostra temperatura in Fahrenheit (ยฐF)", + "weather-hide-city-description": "Nascondi il nome cittร  dalle visualizzazioni meteo in tutta lโ€™interfaccia.", + "weather-hide-city-label": "Nascondi nome cittร ", + "weather-hide-timezone-description": "Nascondi lโ€™abbreviazione del fuso orario dalle visualizzazioni meteo in tutta lโ€™interfaccia.", + "weather-hide-timezone-label": "Nascondi fuso orario", + "weather-show-effects-description": "Mostra effetti visivi aggiuntivi (come pioggia, neve o fulmini) sulla scheda meteo.", + "weather-show-effects-label": "Mostra effetti meteo", + "weather-show-in-calendar-description": "Mostra la previsione meteo giornaliera direttamente nella vista calendario.", + "weather-show-in-calendar-label": "Mostra meteo nel calendario", + "weather-talia-mascot-always-description": "Usa sempre le Talia emojis invece delle tabler icons.", + "weather-talia-mascot-always-label": "Mostra sempre le emoji meteo di Talia" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "Quando fprintd (autenticazione impronta digitale) รจ attivo, questa opzione ti consente comunque di accedere con la password invece che con lโ€™impronta.", + "allow-password-with-fprintd-label": "Consenti accesso con password con fprintd", + "auto-start-auth-description": "Avvia automaticamente lโ€™autenticazione con impronta senza richiedere pressione tasti o clic su pulsante.", + "auto-start-auth-label": "Avvio automatico autenticazione", + "clock-format-description": "Personalizza il formato orologio usando token di sintassi data/ora.", + "clock-format-label": "Formato orologio", + "clock-style-analog": "Analogico", + "clock-style-custom": "Personalizzato", + "clock-style-description": "Scegli lo stile visivo dellโ€™orologio nella schermata blocco.", + "clock-style-digital": "Digitale", + "clock-style-label": "Stile orologio", + "compact-lockscreen-description": "Mostra solo input di accesso e controlli di sistema, nascondendo i widget meteo e media.", + "compact-lockscreen-label": "Schermata blocco compatta", + "enable-lockscreen-media-controls-description": "Mostra i controlli interattivi di riproduzione multimediale sulla schermata di blocco.", + "enable-lockscreen-media-controls-label": "Controlli Multimediali della Schermata di Blocco", + "lock-on-suspend-description": "Blocca automaticamente lo schermo quando il sistema viene sospeso.", + "lock-on-suspend-label": "Blocca alla sospensione", + "lock-screen-animations-description": "Abilita o disabilita le animazioni della schermata blocco.", + "lock-screen-animations-label": "Animazioni schermata blocco", + "lock-screen-blur-strength-description": "Applica un effetto sfocatura allo sfondo della schermata blocco.", + "lock-screen-blur-strength-label": "Intensitร  sfocatura schermata blocco", + "lock-screen-tint-strength-description": "Applica una sovrapposizione colorata allo sfondo della schermata blocco.", + "lock-screen-tint-strength-label": "Intensitร  tinta schermata blocco", + "monitors-desc": "Mostra la schermata blocco su monitor specifici. Se non ne scegli nessuno, verranno usati tutti.", + "password-chars-description": "Icone carine usate per nascondere la password.", + "password-chars-label": "Icone password casuali", + "show-hibernate-description": "Mostra lโ€™opzione 'ibernazione' nei controlli alimentazione.", + "show-hibernate-label": "Mostra ibernazione", + "show-session-buttons-description": "Consenti accesso alle impostazioni di alimentazione dalla schermata blocco.", + "show-session-buttons-label": "Controlli alimentazione", + "title": "Schermata blocco" + }, + "network": { + "bluetooth-description": "Attiva la gestione Bluetooth.", + "bluetooth-rssi-polling-description": "Campiona periodicamente lโ€™RSSI dei dispositivi connessi tramite bluetoothctl. Potrebbe non essere disponibile per tutti i dispositivi; usa risorse minime quando attivo.", + "bluetooth-rssi-polling-label": "Polling segnale Bluetooth", + "desc": "Gestisci connessioni Wi-Fi e Bluetooth.", + "wifi-description": "Gestisci reti wireless (richiede NetworkManager)." + }, + "notifications": { + "duration-critical-urgency-description": "Per quanto tempo restano visibili le notifiche a prioritร  critica.", + "duration-critical-urgency-label": "Urgenza critica", + "duration-desc": "Configura per quanto tempo le notifiche restano visibili in base al livello di urgenza.", + "duration-low-urgency-description": "Per quanto tempo restano visibili le notifiche a prioritร  bassa.", + "duration-low-urgency-label": "Urgenza bassa", + "duration-normal-urgency-description": "Per quanto tempo restano visibili le notifiche a prioritร  normale.", + "duration-normal-urgency-label": "Urgenza normale", + "duration-respect-expire-description": "Usa il timeout di scadenza impostato nella notifica.", + "duration-respect-expire-label": "Rispetta timeout di scadenza", + "duration-title": "Durata notifiche", + "history-clear-dismiss-description": "Rimuove la notifica dalla cronologia quando viene ignorata.", + "history-clear-dismiss-label": "Cancella quando ignorata", + "history-critical-urgency-description": "Salva in cronologia le notifiche a prioritร  critica.", + "history-critical-urgency-label": "Salva in cronologia urgenza critica", + "history-desc": "Controlla quali notifiche vengono salvate in cronologia in base al livello di urgenza.", + "history-low-urgency-description": "Salva in cronologia le notifiche a prioritร  bassa.", + "history-low-urgency-label": "Salva in cronologia urgenza bassa", + "history-normal-urgency-description": "Salva in cronologia le notifiche a prioritร  normale.", + "history-normal-urgency-label": "Salva in cronologia urgenza normale", + "monitors-desc": "Mostra notifiche su monitor specifici. Se non ne scegli nessuno, verranno usati tutti.", + "rules-action-block": "Blocco", + "rules-action-block-desc": "Salta completamente.", + "rules-action-hide": "Nascondi", + "rules-action-hide-desc": "Nessun popup, nessun suono, aggiunge alla cronologia.", + "rules-action-label": "Azione", + "rules-action-mute": "Silenzia", + "rules-action-mute-desc": "Nessun suono, mostra comunque il pop-up e nella cronologia.", + "rules-add": "Aggiungi regola", + "rules-delete": "Elimina regola", + "rules-description": "Corrisponde al nome dell'app o al contenuto. Le regole vengono controllate in ordine e la prima corrispondenza viene applicata.", + "rules-edit": "Modifica regola", + "rules-label": "Regole di Filtro", + "rules-pattern-label": "Modello", + "rules-pattern-placeholder": "firefox, discord, o /regex/", + "rules-tab": "Regole", + "settings-always-on-top-description": "Mostra le notifiche sopra finestre fullscreen e altri layer.", + "settings-background-opacity-description": "Regola lโ€™opacitร  degli sfondi notifica.", + "settings-density-description": "Scegli la densitร  delle schede notifica.", + "settings-density-label": "Densitร ", + "settings-desc": "Configura aspetto e comportamento delle notifiche.", + "settings-do-not-disturb-description": "Disabilita tutti i popup notifica quando attivo.", + "settings-enabled-description": "Abilita o disabilita il demone notifiche; richiede riavvio della shell Noctalia.", + "settings-enabled-label": "Abilita notifiche", + "settings-location-description": "Dove appaiono le notifiche sullo schermo.", + "settings-markdown-description": "Renderizza il contenuto delle notifiche usando formattazione Markdown.", + "settings-markdown-label": "Abilita Markdown", + "sounds-desc": "Configura effetti sonori notifiche e volume.", + "sounds-enabled-description": "Abilita effetti sonori per le notifiche in arrivo.", + "sounds-enabled-label": "Abilita suoni notifiche", + "sounds-excluded-apps-description": "Salta la riproduzione del suono notifica configurato per applicazioni specifiche che hanno giร  suoni integrati.", + "sounds-excluded-apps-label": "Applicazioni escluse", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "Percorso del file audio riprodotto per notifiche a prioritร  critica.", + "sounds-files-critical-label": "Suono urgenza critica", + "sounds-files-critical-select-title": "Seleziona file suono urgenza critica", + "sounds-files-desc": "Configura file audio personalizzati per diversi livelli di urgenza notifica.", + "sounds-files-low-description": "Percorso del file audio riprodotto per notifiche a prioritร  bassa.", + "sounds-files-low-label": "Suono urgenza bassa", + "sounds-files-low-select-title": "Seleziona file suono urgenza bassa", + "sounds-files-normal-description": "Percorso del file audio riprodotto per notifiche a prioritร  normale.", + "sounds-files-normal-label": "Suono urgenza normale", + "sounds-files-normal-select-title": "Seleziona file suono urgenza normale", + "sounds-files-placeholder": "Inserisci percorso file audio", + "sounds-files-select-file": "Seleziona file audio", + "sounds-files-title": "File audio", + "sounds-files-unified-description": "Percorso del file audio riprodotto per le notifiche.", + "sounds-files-unified-label": "Suono notifica", + "sounds-files-unified-select-title": "Seleziona file suono notifica", + "sounds-separate-description": "Usa file audio diversi per notifiche a prioritร  bassa, normale e critica.", + "sounds-separate-label": "Usa suoni diversi per prioritร ", + "sounds-title": "Impostazioni suono", + "sounds-unavailable-description": "Installa Qt6 Multimedia per abilitare i suoni notifica.", + "sounds-unavailable-label": "Suoni notifiche non disponibili", + "sounds-volume-description": "Regola il livello volume per i suoni notifica.", + "sounds-volume-label": "Volume suono", + "toast-battery-description": "Mostra un avviso quando il livello batteria scende sotto questa percentuale.", + "toast-battery-label": "Avviso batteria", + "toast-desc": "Configura aspetto e comportamento dei toast.", + "toast-keyboard-description": "Mostra un toast quando cambia il layout tastiera.", + "toast-keyboard-label": "Layout tastiera", + "toast-media-description": "Mostra un toast quando cambia lo stato riproduzione media.", + "toast-media-label": "Media" + }, + "osd": { + "always-on-top-description": "Mostra OSD sopra finestre fullscreen e altri layer.", + "always-on-top-label": "Sempre in primo piano", + "background-opacity-description": "Controlla la trasparenza dello sfondo OSD.", + "background-opacity-label": "Opacitร  sfondo", + "description": "Configura indicatori a schermo come overlay volume e luminositร .", + "duration-auto-hide-description": "Regola il tempo prima che OSD scompaia.", + "duration-auto-hide-label": "Nascondi automaticamente dopo", + "duration-desc": "Per quanto tempo OSD resta visibile prima di nascondersi automaticamente.", + "duration-title": "Timeout nascondimento automatico", + "enabled-description": "Mostra in tempo reale le variazioni di volume e luminositร .", + "enabled-label": "Abilita on-screen display", + "events-desc": "Seleziona quali eventi attivano OSD.", + "general-desc": "Configura visibilitร  e comportamento di OSD.", + "location-description": "Dove appaiono gli indicatori a schermo.", + "monitors-desc": "Mostra OSD su monitor specifici. Se non ne scegli nessuno, verranno usati tutti.", + "title": "On-screen display", + "types-brightness-description": "Mostra OSD quando cambia la luminositร  schermo.", + "types-brightness-label": "Luminositร ", + "types-custom-text-description": "Mostra OSD per messaggi testuali personalizzati da IPC.", + "types-custom-text-label": "Testo personalizzato", + "types-desc": "Seleziona gli eventi che attivano OSD.", + "types-input-volume-description": "Mostra OSD quando cambia il volume microfono.", + "types-input-volume-label": "Volume input", + "types-lockkey-description": "Mostra OSD quando Caps Lock, Num Lock o Scroll Lock vengono attivati/disattivati.", + "types-lockkey-label": "Tasti lock", + "types-media-description": "Mostra OSD quando cambia lo stato riproduzione media (play, pausa, salta).", + "types-media-label": "Riproduzione media", + "types-title": "Eventi attivazione OSD", + "types-volume-description": "Mostra OSD quando cambia il volume output audio.", + "types-volume-label": "Volume output" + }, + "plugins": { + "auto-update": "Aggiorna automaticamente plugin", + "auto-update-description": "Aggiorna automaticamente tutti i plugin allโ€™avvio della shell.", + "available-description": "Sfoglia e installa plugin dalle sorgenti configurate.", + "available-label": "Plugin disponibili", + "available-no-plugins-description": "Controlla le sorgenti plugin o aggiorna lโ€™elenco.", + "available-no-plugins-label": "Nessun plugin disponibile", + "check-for-updates": "Controlla aggiornamenti", + "checking-for-updates": "Controllo aggiornamenti...", + "collision-already-installed": "Questo plugin รจ giร  installato", + "collision-custom-version-exists": "รˆ giร  installata una versione personalizzata da \"{source}\"", + "collision-official-version-exists": "La versione ufficiale di questo plugin รจ giร  installata", + "development-disable": "Disabilita modalitร  sviluppo per questo plugin", + "development-enable": "Abilita modalitร  sviluppo per questo plugin", + "filter-downloaded": "Scaricati", + "filter-not-downloaded": "Non scaricati", + "filter-tags-description": "Filtra plugin per categoria o stato download.", + "filter-tags-label": "Tag", + "hot-reload-description": "Ricarica automaticamente i plugin quando i loro file cambiano. Utile per sviluppo plugin.", + "hot-reload-label": "Hot reload (modalitร  dev)", + "hot-reloaded": "Plugin ricaricato: {name}", + "install-error": "Installazione non riuscita: {error}", + "install-incompatible": "{plugin} richiede Noctalia v{version} o superiore", + "install-success": "{plugin} installato con successo", + "installed-description": "Gestisci e configura tutti i plugin installati localmente.", + "installed-label": "Plugin installati", + "installed-no-plugins-description": "Installa plugin dalla sezione \"Disponibili\".", + "installed-no-plugins-label": "Nessun plugin installato", + "installing": "Installazione {plugin}...", + "notify-updates": "Notifiche di aggiornamento dei plugin", + "notify-updates-description": "Mostra un popup di notifica quando sono disponibili nuovi aggiornamenti per i plugin.", + "open-plugin-page": "Apri pagina plugin", + "open-plugins-tab": "Apri impostazioni plugin", + "plugin-settings-title": "Impostazioni {plugin}", + "refresh-refreshing": "Aggiornamento elenco plugin...", + "refresh-tooltip": "Aggiorna plugin disponibili", + "settings-error-not-loaded": "Plugin non caricato", + "settings-saved": "Impostazioni plugin salvate", + "settings-tooltip": "Impostazioni plugin", + "source-custom": "Sorgente personalizzata", + "sources-add-custom": "Aggiungi repository personalizzato", + "sources-add-dialog-description": "Aggiungi un repository GitHub come sorgente plugin.", + "sources-add-dialog-error": "Aggiunta sorgente plugin non riuscita", + "sources-add-dialog-name": "Nome repository", + "sources-add-dialog-name-placeholder": "I miei plugin personalizzati", + "sources-add-dialog-success": "Sorgente plugin aggiunta con successo", + "sources-add-dialog-title": "Aggiungi sorgente plugin", + "sources-add-dialog-url": "URL repository", + "sources-description": "Gestisci repository plugin.", + "sources-placeholder": "Il mio fantastico repository", + "sources-remove-tooltip": "Rimuovi sorgente plugin", + "title": "Plugin", + "translations-reloaded": "Traduzioni ricaricate: {name}", + "uninstall-dialog-description": "Sei sicuro di voler disinstallare {plugin}? Verranno rimossi tutti i dati del plugin.", + "uninstall-dialog-title": "Disinstalla plugin", + "uninstall-error": "Disinstallazione non riuscita: {error}", + "uninstall-success": "{plugin} disinstallato con successo", + "uninstalling": "Disinstallazione {plugin}...", + "update-all": "Aggiorna tutti ({count})", + "update-all-success": "Tutti i plugin aggiornati con successo", + "update-available": "Nuovo aggiornamento plugin disponibile", + "update-available-plural": "Aggiornamenti plugin disponibili ({count})", + "update-error": "Aggiornamento plugin non riuscito: {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (richiede Noctalia v{required})", + "update-success": "{plugin} aggiornato a v{version}", + "update-version": "v{current} โ†’ v{new}", + "updating": "Aggiornamento..." + }, + "region": { + "title": "Regione" + }, + "session-menu": { + "countdown-duration-description": "Imposta quanto dura il timer conto alla rovescia prima di eseguire azioni di alimentazione.", + "countdown-duration-label": "Durata conto alla rovescia", + "enable-countdown-description": "Mostra un timer conto alla rovescia prima di eseguire azioni di alimentazione.", + "enable-countdown-label": "Abilita timer conto alla rovescia", + "entries-desc": "Personalizza quali azioni di alimentazione appaiono nel menu sessione e in quale ordine.", + "entries-title": "Azioni alimentazione", + "entry-settings-command-description": "Comando personalizzato da eseguire per questa azione. Lascia vuoto per usare il comando di sistema predefinito.", + "entry-settings-command-placeholder": "es. systemctl poweroff", + "entry-settings-default-command-lock": "Schermata blocco interna (nessun comando)", + "entry-settings-default-command-logout": "Logout interno (nessun comando)", + "entry-settings-default-info-description": "Se non viene specificato un comando personalizzato, verrร  usato il comando di sistema predefinito.", + "entry-settings-default-info-label": "Comando predefinito", + "entry-settings-keybind-description": "Assegna una combinazione tasti per attivare questa azione quando il menu sessione รจ aperto.", + "entry-settings-keybind-placeholder": "Premi Registra per assegnare un tasto...", + "entry-settings-keybind-recording": "Registrazione... Premi qualsiasi combinazione tasti", + "entry-settings-title": "Configura {entry}", + "entry-settings-tooltip": "Configura comando", + "general-desc": "Configura comportamento e aspetto del pannello menu sessione.", + "large-buttons-layout-description": "Scegli come vengono mostrati i pulsanti grandi del menu sessione.", + "large-buttons-layout-label": "Layout pulsanti grandi", + "large-buttons-style-description": "Mostra il menu sessione con pulsanti grandi in layout a griglia.", + "large-buttons-style-label": "Stile pulsanti grandi", + "position-description": "Scegli dove appare il pannello menu sessione quando viene aperto.", + "show-header-description": "Mostra titolo e pulsante chiusura in alto nel menu sessione.", + "show-header-label": "Mostra intestazione", + "show-keybinds-description": "Mostra suggerimenti scorciatoie nelle opzioni sessione.", + "show-keybinds-label": "Mostra scorciatoie" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Mantenere visibili i widget del desktop mentre la modalitร  prestazioni Noctalia รจ abilitato.", + "noctalia-performance-disable-desktop-widgets-label": "Abilita i widget desktop in modalitร  prestazioni", + "noctalia-performance-disable-wallpaper-description": "Mantieni visibili gli sfondi del desktop, della panoramica e della schermata di blocco mentre la modalitร  prestazioni Noctalia รจ abilitato.", + "noctalia-performance-disable-wallpaper-label": "Abilita il rendering dello sfondo in modalitร  prestazioni", + "title": "Sistema" + }, + "system-monitor": { + "critical-color-label": "Colore critico", + "custom-highlight-colors-title-label": "Colori evidenziazione personalizzati", + "disk-available-label": "Disco disponibile", + "disk-section-label": "Utilizzo disco", + "enable-dgpu-monitoring-description": "Attenzione: questo riattiverร  la GPU discreta (NVIDIA/AMD), il che puรฒ influire significativamente sulla durata batteria nei portatili con grafica ibrida.", + "enable-dgpu-monitoring-label": "Abilita monitoraggio GPU discreta", + "external-monitor-description": "Inserisci il comando o il percorso dellโ€™applicazione da avviare quando attivi il monitor di sistema esterno.", + "external-monitor-label": "Comando monitor di sistema esterno", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "Configura comportamento e aspetto del monitor di sistema.", + "gpu-section-label": "Temperatura GPU", + "highlight-colors-section-label": "Colori evidenziazione", + "polling-interval-label": "Intervallo polling", + "polling-section-description": "Configura la frequenza di aggiornamento di ogni metrica di sistema.", + "polling-section-label": "Intervalli polling", + "threshold-critical": "Critico", + "threshold-warning": "Avviso", + "thresholds-section-description": "Regola soglie avviso/critiche per ogni metrica di sistema.", + "use-custom-highlight-colors-description": "Quando disattivato, vengono usati i colori evidenziazione predefiniti del tema.", + "use-custom-highlight-colors-label": "Usa colori evidenziazione personalizzati", + "warning-color-label": "Colore avviso" + }, + "user-interface": { + "allow-panels-without-bar-description": "Se attivo, i pannelli possono aprirsi su qualsiasi schermo. Se disattivo, i pannelli si aprono solo sugli schermi con barra, riducendo lโ€™uso memoria.", + "allow-panels-without-bar-label": "Consenti pannelli su schermi senza barra", + "animation-disable-description": "Disabilita tutte le animazioni per unโ€™esperienza piรน veloce e reattiva.", + "animation-disable-label": "Disabilita animazioni UI", + "animation-speed-description": "Regola la velocitร  globale delle animazioni.", + "animation-speed-label": "Velocitร  animazione", + "animation-speed-reset": "Reimposta velocitร  animazione", + "appearance-desc": "Personalizza elementi visivi come tooltip, bordi e ombre.", + "blur-behind-description": "Sfoca l'area dietro le barre e i pannelli utilizzando il protocollo di sfocatura del compositore.", + "blur-behind-label": "Sfocatura dello sfondo", + "box-border-description": "Mostra un contorno attorno alle aree contenuto.", + "box-border-label": "Contorno contenitore", + "box-border-radius-description": "Regola lโ€™arrotondamento degli angoli delle principali sezioni layout, come sidebar, schede e pannelli contenuto.", + "box-border-radius-label": "Raggio contenitore", + "control-border-radius-description": "Controlla la curvatura degli elementi interattivi, inclusi pulsanti, interruttori e campi testo.", + "control-border-radius-label": "Raggio input", + "desc": "Personalizza aspetto, stile e comportamento dellโ€™interfaccia.", + "dim-desktop-description": "Oscura il desktop quando pannelli o menu sono aperti.", + "dim-desktop-label": "Oscura desktop", + "dimmer-opacity-description": "Imposta il livello di opacitร  dellโ€™oscuramento desktop.", + "dimmer-opacity-label": "Opacitร  desktop oscurato", + "panel-background-opacity-description": "Imposta lโ€™opacitร  sfondo per tutti i pannelli (barra, launcher, impostazioni, ecc...).", + "panel-background-opacity-label": "Opacitร  sfondo pannelli", + "panels-attached-to-bar-description": "I pannelli si agganciano alla barra e ai bordi schermo, creando un aspetto continuo con eleganti angoli invertiti.", + "panels-attached-to-bar-label": "Aggancia pannelli ai bordi", + "panels-overlay-description": "Assicura che pannelli e barra restino visibili anche sopra applicazioni fullscreen.", + "panels-overlay-label": "Mantieni pannelli e barra in primo piano", + "scaling-description": "Cambia la dimensione dellโ€™interfaccia utente generale, esclusa la barra.", + "scaling-label": "Scala interfaccia", + "scrollbar-always-visible-description": "Mantieni le barre di scorrimento visibili ogni volta che il contenuto รจ scorrevole, invece di mostrarle solo al passaggio del mouse.", + "scrollbar-always-visible-label": "Mostra sempre le barre di scorrimento", + "settings-panel-header": "Pannello impostazioni", + "settings-panel-mode-description": "Scegli layout impostazioni (potrebbe richiedere riapertura).", + "settings-panel-mode-label": "Modalitร  pannello impostazioni", + "settings-panel-sidebar-card-style-description": "Avvolge la barra laterale delle impostazioni in uno sfondo pieno con angoli arrotondati.", + "settings-panel-sidebar-card-style-label": "Stile barra laterale", + "shadows-description": "Abilita ombre esterne sotto barre e pannelli.", + "shadows-direction-description": "Scegli la direzione in cui viene proiettata lโ€™ombra.", + "shadows-direction-label": "Direzione ombra", + "shadows-label": "Ombre esterne", + "title": "Interfaccia utente", + "tooltips-description": "Abilita o disabilita i tooltip in tutta lโ€™interfaccia.", + "tooltips-label": "Mostra tooltip", + "translucent-widgets-description": "Rendi semitrasparenti i pulsanti, le schede e gli altri widget all'interno dei pannelli.", + "translucent-widgets-label": "Widget traslucidi" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "Alfabetico", + "automation-change-mode-description": "Scegli come selezionare gli sfondi durante il cambio automatico.", + "automation-change-mode-label": "Modalitร  cambio", + "automation-custom-interval-description": "Inserisci il tempo come HH:MM (es. 01:30).", + "automation-custom-interval-label": "Intervallo personalizzato", + "automation-interval-description": "Quanto spesso cambiare automaticamente gli sfondi.", + "automation-interval-label": "Intervallo sfondo", + "automation-random-wallpaper-description": "Pianifica cambi sfondo casuali a intervalli regolari.", + "automation-scheduled-change-description": "Cambia automaticamente gli sfondi a intervalli regolari.", + "automation-scheduled-change-label": "Cambio pianificato", + "look-feel-edge-smoothness-description": "Applica un effetto morbido e sfumato al bordo delle transizioni.", + "look-feel-edge-smoothness-label": "Ammorbidisci bordo transizione", + "look-feel-fill-color-description": "Scegli un colore di riempimento che potrebbe apparire dietro lo sfondo.", + "look-feel-fill-mode-description": "Seleziona come lโ€™immagine deve scalare per adattarsi alla risoluzione del monitor.", + "look-feel-fill-mode-label": "Modalitร  riempimento", + "look-feel-skip-startup-transition-description": "Salta lโ€™animazione sfondo allโ€™avvio della shell.", + "look-feel-skip-startup-transition-label": "Salta transizione avvio", + "look-feel-title": "Aspetto e stile", + "look-feel-transition-duration-description": "Durata delle animazioni di transizione in secondi.", + "look-feel-transition-duration-label": "Durata transizione", + "look-feel-transition-type-description": "Tipo animazione durante il passaggio tra sfondi.", + "look-feel-transition-type-label": "Tipo transizione", + "settings-clear-cache-button": "Svuota cache", + "settings-clear-cache-description": "Cancella gli sfondi ridimensionati in cache per liberare spazio su disco.", + "settings-clear-cache-label": "Cache degli sfondi", + "settings-clear-cache-toast": "Cache dello sfondo cancellata", + "settings-desc": "Controlla come gli sfondi vengono gestiti e visualizzati.", + "settings-enable-management-description": "Gestisci gli sfondi con Noctalia. Disattiva se preferisci usare unโ€™altra applicazione.", + "settings-enable-management-label": "Abilita gestione sfondi", + "settings-enable-overview-description": "Applica uno sfondo sfocato e attenuato alla schermata overview.", + "settings-enable-overview-label": "Abilita sfondo overview", + "settings-folder-description": "Percorso della tua cartella sfondi principale.", + "settings-folder-label": "Cartella sfondi", + "settings-hide-wallpaper-filenames-tooltip-hide": "Nascondi nomi file", + "settings-hide-wallpaper-filenames-tooltip-show": "Mostra nomi file", + "settings-monitor-specific-description": "Imposta una cartella sfondi diversa per ogni monitor.", + "settings-monitor-specific-label": "Cartelle specifiche per monitor", + "settings-monitor-specific-tooltip": "Cartella sfondi monitor", + "settings-overview-blur-strength-description": "Applica intensitร  sfocatura allโ€™overview.", + "settings-overview-blur-strength-label": "Intensitร  sfocatura overview", + "settings-overview-tint-description": "Applica intensitร  tinta allโ€™overview.", + "settings-overview-tint-label": "Intensitร  tinta overview", + "settings-recursive-search-description": "Cerca sfondi anche nelle sottocartelle della cartella sfondi.", + "settings-recursive-search-label": "Cerca nelle sottocartelle", + "settings-select-monitor-folder": "Seleziona cartella sfondi monitor", + "settings-selector-description": "Scegli il tuo sfondo.", + "settings-selector-position-description": "Scegli dove appare il pannello selettore sfondo.", + "settings-show-hidden-files-tooltip-hide": "Nascondi file nascosti", + "settings-show-hidden-files-tooltip-show": "Mostra file nascosti", + "settings-title": "Impostazioni sfondo", + "settings-use-original-images-description": "Salta il ridimensionamento degli sfondi prima della visualizzazione. Risparmia spazio su disco e riduce l'utilizzo della CPU durante i cambi di sfondo, ma potrebbe usare piรน memoria per immagini molto grandi.", + "settings-use-original-images-label": "Usa le immagini originali", + "settings-view-mode-description": "Scegli come visualizzare gli sfondi dalla tua cartella.", + "settings-view-mode-label": "Modalitร  visualizzazione", + "view-mode-browse": "Sfoglia cartelle", + "view-mode-cycle-tooltip": "Modalitร  visualizzazione: {mode} (clic per cambiare)", + "view-mode-recursive": "Sottocartelle appiattite", + "view-mode-single": "Cartella radice" + } + }, + "placeholders": { + "add-new-keybind": "Aggiungi una nuova scorciatoia", + "command-example": "echo \"Ciao Mondo\"", + "enter-command": "Inserisci un comando da eseguire (app o script personalizzato)", + "enter-ipc-identifier": "Inserisci un identificatore univoco per i comandi IPC", + "enter-path": "Inserisci percorso...", + "enter-text-to-collapse": "es. 'non รจ in riproduzione'. Usa /regex/ per i pattern.", + "enter-tooltip": "Inserisci tooltip", + "enter-width-pixels": "Inserisci larghezza in pixel", + "keybind-recording": "Registrazione scorciatoia...", + "search": "Cerca...", + "search-icons": "es. noctalia, niri, batteria, nuvola", + "search-launcher": "Cerca voci... oppure usa > per i comandi", + "search-wallhaven": "Cerca su Wallhaven...", + "search-wallpapers": "Digita per filtrare gli sfondi..." + }, + "positions": { + "bottom": "In basso", + "bottom-center": "In basso al centro", + "bottom-left": "In basso a sinistra", + "bottom-right": "In basso a destra", + "center": "Centro", + "center-left": "Centro sinistra", + "center-right": "Centro destra", + "close-to-bar": "Vicino al pulsante della barra", + "follow-bar": "Segui la barra", + "left": "Sinistra", + "right": "Destra", + "top": "In alto", + "top-center": "In alto al centro", + "top-left": "In alto a sinistra", + "top-right": "In alto a destra" + }, + "session-menu": { + "action-in-seconds": "{action} tra {seconds} secondi...", + "cancel-timer": "Annulla timer", + "title": "Menu sessione" + }, + "setup": { + "all-done": "Tutto fatto!", + "appearance": { + "subheader": "Scegli la modalitร  scura e le sorgenti colore (sfondo o predefinite)." + }, + "customize": { + "header": "Personalizza la tua esperienza", + "subheader": "Regola posizione barra, densitร , scala e altro." + }, + "skip-setup": "Salta configurazione", + "telemetry-wizard-done": "Capito!", + "telemetry-wizard-note": "Hai il controllo โ€” abilita o disabilita questa opzione in qualsiasi momento nelle impostazioni", + "telemetry-wizard-subtitle": "Abbiamo aggiunto analisi anonime per migliorare Noctalia", + "telemetry-wizard-title": "Aggiornamento privacy", + "wallpaper": { + "choose-dir": "Scegli una cartella che contenga le immagini degli sfondi", + "dir-browse": "Sfoglia cartella sfondi", + "dir-description": "Scegli la cartella che contiene i tuoi sfondi.", + "dir-label": "Cartella sfondi", + "dir-select-title": "Seleziona cartella sfondi", + "header": "Scegli il tuo sfondo", + "no-dir": "Nessuna cartella sfondi selezionata", + "no-valid": "Nessun file immagine valido trovato in: {dir}", + "none-in-dir": "Nessuno sfondo trovato nella cartella", + "preview-error": "Impossibile caricare l'immagine", + "select-prompt": "Seleziona uno sfondo qui sotto", + "subheader": "Imposta lโ€™atmosfera con uno sfondo bellissimo." + }, + "welcome": "Benvenuto", + "welcome-note": "Solo poche basi per iniziare: tutte le opzioni sono nelle impostazioni", + "welcome-subtitle": "Rendiamo il tuo desktop davvero tuo", + "welcome-title": "Benvenuto in Noctalia!" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "Nessun lettore multimediale rilevato", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "App sconosciuta", + "uptime": "Attivo da: {uptime}", + "user-requested": "Richiesto dall'utente", + "welcome-back": "Bentornato,", + "widget-settings-title": "Impostazioni {widget}" + }, + "system-monitor": { + "core-usage": "Utilizzo del core {id}", + "cpu-temp": "Temperatura CPU", + "cpu-usage": "Uso CPU", + "disk": "Disco", + "download-speed": "Velocitร  download", + "gpu-temp": "Temperatura GPU", + "load-average": "Carico medio", + "title": "Monitor di sistema", + "upload-speed": "Velocitร  upload" + }, + "toast": { + "airplane-mode": { + "description": "Disabilita tutte le comunicazioni wireless.", + "title": "Modalitร  aereo" + }, + "battery": { + "critical": "Batteria critica", + "critical-desc": "Batteria al {percent}% โ€” collega subito il caricatore", + "low": "Batteria scarica", + "low-desc": "Batteria al {percent}% โ€” collega il caricatore" + }, + "bluetooth": { + "address-copied": "Address copied to clipboard", + "auto-connect-disabled": "Connessione automatica disabilitata", + "auto-connect-enabled": "Connessione automatica abilitata", + "auto-connecting": "Connessione a {count} dispositivo/i...", + "confirm-code": "Confirm code {value} on the other device.", + "connect-failed": "Failed to connect to device", + "disconnect-failed": "Failed to disconnect from device", + "discoverable-change-failed": "Failed to change discoverable state", + "discoverable-disabled": "Discoverable disabled", + "discoverable-enabled": "Discoverable enabled", + "display-code": "Enter code {value} on the other device.", + "forget-failed": "Failed to forget device", + "pair-failed": "Failed to pair device", + "passkey-required": "Passkey required by the device.", + "pincode-required": "PIN code required by the device.", + "state-change-failed": "Failed to change Bluetooth state" + }, + "clipboard": { + "long-text": "Long text", + "unavailable": "Clipboard history unavailable", + "unavailable-desc": "The 'cliphist' application is not installed. Please install it to use clipboard history features" + }, + "custom-command-failed": { + "description": "Comando fallito: {command}\\nCodice di uscita: {code}", + "title": "Comando personalizzato fallito" + }, + "do-not-disturb": { + "disabled": "'Non disturbare' disattivato", + "disabled-desc": "Showing all notifications", + "enabled": "'Non disturbare' attivato", + "enabled-desc": "You'll find these notifications in your history" + }, + "donation-opened": "Donation page opened in your browser", + "internet-limited": "Connected without internet", + "keyboard-layout": { + "changed": "Keyboard layout changed to {layout}", + "title": "Keyboard" + }, + "missing-control-center": { + "description": "The control center widget has been removed from the bar. To access it from the bar again, you will need to re-add the widget. You can open it with right clicking on the bar too", + "label": "Last control center widget removed" + }, + "night-light": { + "forced": "Forced activation", + "normal": "Normal mode", + "not-installed": "wlsunset not installed" + }, + "noctalia-performance": { + "disabled": "Modalitร  prestazioni disabilitata", + "enabled": "Modalitร  prestazioni abilitata", + "label": "Noctalia Prestazioni" + }, + "power-profile": { + "changed": "Power profile changed", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "Encountered an error while processing templates", + "title": "Templating processing failed" + }, + "vpn": { + "connected": "Connected to '{name}'", + "disconnected": "Disconnected from '{name}'" + }, + "wallpaper-colors": { + "disabled": "Wallpaper colors disabled", + "enabled": "Wallpaper colors enabled", + "label": "Wallpaper colors" + }, + "wifi": { + "connected": "Connected to '{ssid}'", + "connection-failed": "Connection failed", + "connection-timeout": "Connection timeout", + "disconnected": "Disconnected from '{ssid}'", + "incorrect-password": "Incorrect password", + "network-not-found": "Network not found" + } + }, + "tooltips": { + "add-widget": "Aggiungi widget", + "auto-connect": "Attiva/disattiva la connessione automatica per questo dispositivo", + "bluetooth-auto-connect-off": "Connessione automatica disattivata", + "bluetooth-auto-connect-on": "Connessione automatica attiva", + "bluetooth-devices": "Dispositivi Bluetooth", + "brightness-at": "Luminositร : {brightness}%", + "click-to-start-recording": "Registratore schermo (avvia registrazione)", + "click-to-stop-recording": "Registratore schermo (ferma registrazione)", + "collapse": "Comprimi barra laterale", + "copy-address": "Copia indirizzo", + "delete-notification": "Elimina notifica", + "dismiss-notification": "Ignora notifica", + "do-not-disturb-enabled": "Non disturbare", + "expand": "Espandi barra laterale", + "forget-network": "Dimentica rete", + "grid-view": "Vista a griglia", + "hidden-files-hide": "File nascosti", + "hide-unnamed-devices": "Nascondi dispositivi senza nome", + "home": "Cartella home", + "input-muted": "Attiva/disattiva muto input", + "keep-awake": "Mantieni attivo", + "keyboard-layout": "Layout tastiera {layout}", + "list-view": "Vista elenco", + "manage-vpn": "Connessioni VPN", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "Numero massimo di widget raggiunto", + "microphone-volume-at": "Volume microfono: {volume}%", + "move-to-section": "Sposta in {section}", + "mute": "Muto", + "next-media": "Brano successivo", + "next-month": "Mese successivo", + "night-light-not-installed": "Luce notturna (non disponibile)", + "noctalia-performance-enabled": "Modalitร  prestazioni Noctalia", + "open-annotation-tool": "Apri con strumento annotazioni", + "open-control-center": "Centro di controllo", + "open-notification-history-enable-dnd": "Cronologia notifiche", + "open-settings": "Impostazioni", + "open-tray-dropdown": "Area di notifica", + "output-muted": "Attiva/disattiva muto output", + "power-profile": "Profilo energetico {profile}", + "previous-media": "Brano precedente", + "previous-month": "Mese precedente", + "refresh-devices": "Aggiorna dispositivi", + "refresh-wallhaven": "Aggiorna risultati Wallhaven", + "refresh-wallpaper-list": "Aggiorna elenco sfondi", + "remove-widget": "Rimuovi widget", + "search-close": "Chiudi ricerca", + "session-menu": "Menu sessione", + "show-all-devices": "Mostra tutti i dispositivi", + "switch-to-dark-mode": "Modalitร  scura", + "switch-to-light-mode": "Modalitร  chiara", + "unmute": "Riattiva audio", + "up": "Cartella superiore", + "volume-at": "Volume uscita: {volume}%", + "wallpaper-selector": "Selettore sfondi" + }, + "wallpaper": { + "browse": { + "empty-directory": "Questa cartella รจ vuota.", + "go-root": "Vai alla cartella radice degli sfondi", + "go-up": "Vai alla cartella superiore", + "go-up-hint": "Usa il pulsante indietro per salire di livello." + }, + "configure-directory": "Configura la cartella degli sfondi con le immagini.", + "fill-modes": { + "crop": "Ritaglia (Riempi)", + "fit": "Adatta (Contieni)", + "repeat": "Ripeti (Affianca)", + "stretch": "Allunga" + }, + "no-match": "Nessuna corrispondenza trovata.", + "no-wallpaper": "Nessuno sfondo trovato.", + "panel": { + "apikey-help": "Una chiave API รจ richiesta per accedere ai contenuti NSFW.", + "apikey-label": "Chiave API", + "apikey-managed-by-env": "Gestita tramite la variabile dโ€™ambiente NOCTALIA_WALLHAVEN_API_KEY.", + "apikey-placeholder": "Inserisci la tua chiave API Wallhaven", + "appearance-dark-tab": "Scuro", + "appearance-light-tab": "Chiaro", + "categories-anime": "Anime", + "categories-label": "Categorie", + "categories-people": "Persone", + "color-extraction-disabled": "Usa estrazione colori dallo sfondo", + "color-extraction-enabled": "Usa schemi colore predefiniti", + "header-devices-apply-all-tooltip": "Scegli sfondo per monitor", + "header-devices-per-monitor-tooltip": "Applica a tutti i monitor", + "header-link-light-dark-tooltip": "Mantieni lo sfondo quando cambi tema", + "header-separate-light-dark-tooltip": "Cambia automaticamente lo sfondo con il tema", + "header-sun-linked-tooltip": "Separare sfondi chiari e scuri", + "header-sun-separate-tooltip": "Stesso sfondo per chiaro e scuro", + "order-asc": "Crescente", + "order-desc": "Decrescente", + "order-label": "Ordine", + "purity-label": "Filtro contenuti", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "Sketchy", + "ratios-all-portrait": "Tutti verticali", + "ratios-all-wide": "Tutti orizzontali", + "ratios-any": "Qualsiasi", + "ratios-label": "Rapporto dโ€™aspetto", + "resolution-atleast": "Almeno", + "resolution-exact": "Esatta", + "resolution-label": "Risoluzione", + "resolution-mode-label": "Modalitร ", + "solid-color-tooltip": "Sfondo a colore uniforme", + "sort-date-asc": "Ordina dal piรน vecchio al piรน recente", + "sort-date-desc": "Ordina dal piรน recente al piรน vecchio", + "sort-name-asc": "Ordina per nome (A-Z)", + "sort-name-desc": "Ordina per nome (Z-A)", + "sort-random": "Ordina casualmente", + "sorting-date-added": "Data aggiunta", + "sorting-favorites": "Preferiti", + "sorting-label": "Ordina per", + "sorting-relevance": "Rilevanza", + "sorting-toplist": "Top list", + "sorting-views": "Visualizzazioni", + "source-label": "Sorgente", + "source-wallhaven": "Wallhaven", + "title": "Selettore sfondi", + "wallhaven-settings-title": "Impostazioni Wallhaven" + }, + "transitions": { + "disc": "Disco", + "fade": "Dissolvenza", + "honeycomb": "Nido dโ€™ape", + "pixelate": "Pixelato", + "stripes": "Strisce", + "wipe": "Scorrimento" + }, + "try-different-search": "Prova una ricerca diversa.", + "wallhaven": { + "loading": "Caricamento sfondi...", + "no-results": "Nessuno sfondo trovato. Prova una ricerca diversa.", + "page": "{current} di {total}", + "page-prefix": "Pagina", + "page-suffix": "di {total}" + } + }, + "weather": { + "clear-sky": "Cielo sereno", + "drizzle": "Pioviggine", + "fog": "Nebbia", + "mainly-clear": "Prevalentemente sereno", + "overcast": "Coperto", + "partly-cloudy": "Parzialmente nuvoloso", + "rain-showers": "Rovesci di pioggia", + "snow": "Neve", + "thunderstorm": "Temporale" + }, + "widgets": { + "color-picker": { + "palette-description": "Scegli da unโ€™ampia gamma di colori predefiniti.", + "palette-label": "Tavolozza", + "palette-theme-colors": "Accesso rapido alla tavolozza colori del tema.", + "title": "Selettore colore" + }, + "datetime-tokens": { + "ampm-lowercase": "am/pm in minuscolo", + "ampm-uppercase": "AM/PM in maiuscolo", + "common-12hour-time-minutes": "Ora a 12 ore con minuti", + "common-24hour-time-minutes": "Ora a 24 ore con minuti", + "common-24hour-time-seconds": "Ora a 24 ore con secondi", + "common-european-date": "Formato data europeo", + "common-iso-date": "Formato data ISO", + "common-us-date": "Formato data USA", + "common-weekday-date": "Giorno della settimana con data", + "common-weekday-month-day": "Giorno settimana, mese e giorno", + "day-abbreviated": "Nome giorno abbreviato", + "day-full": "Nome giorno completo", + "day-leading-zero": "Giorno con zero iniziale (01-31)", + "day-no-leading-zero": "Giorno senza zero iniziale (1-31)", + "hour-leading-zero": "Ora con zero iniziale (00-23) โ€” formato 24 ore", + "hour-no-leading-zero": "Ora senza zero iniziale (0-23) โ€” formato 24 ore", + "minute-leading-zero": "Minuto con zero iniziale (00-59)", + "minute-no-leading-zero": "Minuto senza zero iniziale (0-59)", + "month-abbreviated": "Nome mese abbreviato", + "month-full": "Nome mese completo", + "month-number-leading-zero": "Mese come numero con zero iniziale (01-12)", + "month-number-no-zero": "Mese come numero senza zero iniziale (1-12)", + "second-leading-zero": "Secondo con zero iniziale (00-59)", + "second-no-leading-zero": "Secondo senza zero iniziale (0-59)", + "timezone-abbreviation": "Abbreviazione fuso orario", + "year-four-digit": "Anno a quattro cifre", + "year-two-digit": "Anno a due cifre (00-99)" + }, + "file-picker": { + "item": "elemento", + "items": "elementi", + "search-placeholder": "Cerca file e cartelle...", + "select-current": "Seleziona corrente", + "select-file": "Seleziona file", + "select-folder": "Seleziona cartella", + "selected": "Selezionato:", + "title": "Selettore file" + }, + "file-picker-title": "Seleziona un file", + "icon-picker": { + "title": "Selettore icone" + }, + "tooltip-placeholder": "Segnaposto" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "Identitร  Anonima", + "ca-cert": "Certificato CA", + "eap-method": "Metodo EAP", + "password": "Password Utente", + "phase2-auth": "Autenticazione Fase 2", + "username": "Identitร  / Nome Utente" + }, + "panel": { + "action-required": "Azione richiesta", + "add-hidden-network": "Aggiungi rete nascosta", + "add-network": "Aggiungi rete", + "available-interfaces": "Interfacce disponibili", + "available-networks": "Reti disponibili", + "disabled": "Wiโ€‘Fi disattivato", + "disconnecting": "Disconnessione in corso...", + "dns": "DNS", + "enable-message": "Attiva il Wiโ€‘Fi per vedere le reti disponibili.", + "enter-password": "Inserisci password Wiโ€‘Fi...", + "forget": "Dimentica", + "forget-network": "Dimentica questa rete", + "forgetting": "Dimenticando...", + "hidden-network": "Rete nascosta", + "interface": "Interfaccia di rete", + "internet-connected": "Internet connesso", + "internet-limited": "Nessun accesso a Internet", + "internet-status": "Stato Internet", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "Reti conosciute", + "link-speed": "Velocitร  collegamento", + "network-name-ssid": "Nome rete (SSID)", + "no-ethernet-devices": "Nessun dispositivo Ethernet rilevato", + "no-networks": "Nessuna rete Wiโ€‘Fi trovata", + "saved": "Salvata", + "scan-again": "Scansiona di nuovo", + "searching": "Ricerca reti in corso...", + "security-open": "Apri", + "security-wep": "WEP", + "security-wpa": "WPA/WPA2/WPA3 Personale", + "security-wpa-ent": "WPA Enterprise", + "security-wpa2-ent": "WPA2 Enterprise", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 Enterprise", + "show-password": "Mostra password", + "title": "Wiโ€‘Fi" + }, + "signal": { + "excellent": "Eccellente", + "fair": "Discreto", + "good": "Buono", + "poor": "Scarso", + "weak": "Debole" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/ja.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/ja.json new file mode 100644 index 0000000..1a23f99 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/ja.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "ๅฑฅๆญดใ‚’ๅ‰Š้™คใ™ใ‚‹", + "close-app": "{app} ใ‚’้–‰ใ˜ใ‚‹", + "connect-vpn": "{name} ใซๆŽฅ็ถš", + "cycle-visualizer": "ใƒ“ใ‚ธใƒฅใ‚ขใƒฉใ‚คใ‚ถใƒผใ‚’ๅˆ‡ใ‚Šๆ›ฟใˆใ‚‹", + "disable-bluetooth": "Bluetooth ใ‚’็„กๅŠนๅŒ–", + "disable-dnd": "ใŠใ‚„ใ™ใฟใƒขใƒผใƒ‰ใ‚’็„กๅŠนๅŒ–", + "disable-wifi": "Wi-Fi ใ‚’็„กๅŠนๅŒ–", + "disconnect-vpn": "{name} ใ‹ใ‚‰ๅˆ‡ๆ–ญ", + "dock-settings": "ใƒ‰ใƒƒใ‚ฏ่จญๅฎš", + "enable-bluetooth": "Bluetooth ใ‚’ๆœ‰ๅŠนๅŒ–", + "enable-dnd": "ใŠใ‚„ใ™ใฟใƒขใƒผใƒ‰ใ‚’ๆœ‰ๅŠนๅŒ–", + "enable-wifi": "Wi-Fi ใ‚’ๆœ‰ๅŠนๅŒ–", + "launcher-settings": "ใƒฉใƒณใƒใƒฃใƒผ่จญๅฎš", + "lower-to-bottom": "ไธ€็•ชไธ‹ใซ็งปๅ‹•", + "open-calendar": "ใ‚ซใƒฌใƒณใƒ€ใƒผใ‚’้–‹ใ", + "open-display-settings": "ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚ค่จญๅฎš", + "open-launcher": "ใƒฉใƒณใƒใƒฃใƒผใ‚’้–‹ใ", + "open-settings": "่จญๅฎšใ‚’้–‹ใ", + "raise-to-top": "ไธ€็•ชไธŠใซ็งปๅ‹•", + "random-wallpaper": "ๅฃ็ด™ใ‚’ใƒฉใƒณใƒ€ใƒ ใซๅค‰ๆ›ด", + "run-custom-command": "ใ‚ซใ‚นใ‚ฟใƒ ใ‚ณใƒžใƒณใƒ‰ใ‚’ๅฎŸ่กŒ", + "show-active-only": "ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใฎใฟ่กจ็คบ", + "show-all": "ใ™ในใฆ่กจ็คบ", + "toggle-mute": "ใƒŸใƒฅใƒผใƒˆๅˆ‡ใ‚Šๆ›ฟใˆ", + "widget-settings": "ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆ่จญๅฎš" + }, + "authentication": { + "error": "่ช่จผใ‚จใƒฉใƒผ", + "failed": "่ช่จผๅคฑๆ•—" + }, + "bar": { + "active-window": { + "colorize-icons-description": "ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใฎใ‚ขใ‚คใ‚ณใƒณใซใƒ†ใƒผใƒžใ‚ซใƒฉใƒผใ‚’้ฉ็”จใ—ใพใ™ใ€‚", + "hide-mode-description": "ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใŒใชใ„ๅ ดๅˆใฎใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎๅ‹•ไฝœใ‚’ๅˆถๅพกใ—ใพใ™ใ€‚", + "scrolling-mode-description": "้•ทใ„ใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใ‚ฟใ‚คใƒˆใƒซใฎใ‚นใ‚ฏใƒญใƒผใƒซๅ‹•ไฝœใ‚’ๅˆถๅพกใ—ใพใ™ใ€‚", + "show-app-icon-description": "ใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใ‚ฟใ‚คใƒˆใƒซใฎๆจชใซใ‚ขใƒ—ใƒชใ‚ขใ‚คใ‚ณใƒณใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "show-app-icon-label": "ใ‚ขใƒ—ใƒชใ‚ขใ‚คใ‚ณใƒณใ‚’่กจ็คบ", + "show-app-text-description": "ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใชใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใฎใ‚ฟใ‚คใƒˆใƒซใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "show-app-text-label": "ใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใ‚ฟใ‚คใƒˆใƒซใ‚’่กจ็คบ" + }, + "audio-visualizer": { + "color-name-description": "ใƒ“ใ‚ธใƒฅใ‚ขใƒฉใ‚คใ‚ถใƒผใฎ่‰ฒใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "color-name-label": "ๅก—ใ‚Šใคใถใ—่‰ฒ", + "height-description": "ใ‚ซใ‚นใ‚ฟใƒ ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใฎๅน…ใ€‚", + "hide-when-idle-description": "ๆœ‰ๅŠนใซใ™ใ‚‹ใจใ€ๅ†็”Ÿไธญใฎใƒ—ใƒฌใƒผใƒคใƒผใŒใชใ„ๅ ดๅˆใซใƒ“ใ‚ธใƒฅใ‚ขใƒฉใ‚คใ‚ถใƒผใ‚’้š ใ—ใพใ™ใ€‚", + "hide-when-idle-label": "ใƒกใƒ‡ใ‚ฃใ‚ขๅœๆญขๆ™‚ใฏ้š ใ™", + "width-description": "ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใฎๅน…ใ‚’ใ‚ซใ‚นใ‚ฟใƒ ่จญๅฎšใ—ใพใ™ใ€‚" + }, + "battery": { + "device-default": "ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆ๏ผˆ่กจ็คบใƒ‡ใƒใ‚คใ‚น๏ผ‰", + "device-description": "่กจ็คบใ™ใ‚‹ใƒใƒƒใƒ†ใƒชใƒผใƒ‡ใƒใ‚คใ‚นใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "device-label": "ใƒใƒƒใƒ†ใƒชใƒผใƒ‡ใƒใ‚คใ‚น", + "display-mode-description": "ใƒใƒƒใƒ†ใƒชใƒผใ‚’ใƒใƒผใซใฉใฎใ‚ˆใ†ใซ่กจ็คบใ™ใ‚‹ใ‹ใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "display-mode-graphic": "ใ‚ฐใƒฉใƒ•ใ‚ฃใƒƒใ‚ฏใƒใƒƒใƒ†ใƒชใƒผ", + "display-mode-graphic-clean": "ใ‚ฐใƒฉใƒ•ใ‚ฃใ‚ซใƒซใƒใƒƒใƒ†ใƒชใƒผ (ใƒ‘ใƒผใ‚ปใƒณใƒˆ่กจ็คบใชใ—)", + "display-mode-icon-always": "ใ‚ขใ‚คใ‚ณใƒณ - ๅธธใซ%ใ‚’่กจ็คบ", + "display-mode-icon-hover": "ใ‚ขใ‚คใ‚ณใƒณ - ใƒ›ใƒใƒผๆ™‚ใซ่กจ็คบ", + "display-mode-icon-only": "ใ‚ขใ‚คใ‚ณใƒณใฎใฟ", + "hide-if-idle-description": "ใƒใƒƒใƒ†ใƒชใƒผใŒๅ……้›ปใพใŸใฏๆ”พ้›ปใ—ใฆใ„ใชใ„ใจใใฏใ€ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’้ž่กจ็คบใซใ—ใพใ™ใ€‚", + "hide-if-idle-label": "ใ‚ขใ‚คใƒ‰ใƒซๆ™‚ใซ้š ใ™", + "hide-if-not-detected-description": "ใ‚ทใ‚นใƒ†ใƒ ใงใƒใƒƒใƒ†ใƒชใƒผใŒๆคœๅ‡บใ•ใ‚Œใชใ„ๅ ดๅˆใฏใ€ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’้ž่กจ็คบใซใ—ใพใ™ใ€‚", + "hide-if-not-detected-label": "ๆคœๅ‡บใ•ใ‚Œใชใ‘ใ‚Œใฐ้š ใ‚Œใ‚‹", + "low-battery-threshold-label": "ไฝŽใƒใƒƒใƒ†ใƒชใƒผ่ญฆๅ‘Šใฎ้–พๅ€ค", + "show-noctalia-performance-description": "ใƒใƒƒใƒ†ใƒชใƒผใƒ‘ใƒใƒซใซ Noctalia ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใƒขใƒผใƒ‰ใฎใƒˆใ‚ฐใƒซใ‚’่กจ็คบใ—ใพใ™ใ€‚
Noctalia ใฎใ‚ทใƒฃใƒ‰ใ‚ฆใจใ‚ขใƒ‹ใƒกใƒผใ‚ทใƒงใƒณใ‚’็„กๅŠนใซใ—ใฆใƒชใ‚ฝใƒผใ‚นไฝฟ็”จ้‡ใ‚’ๅ‰Šๆธ›ใ—ใพใ™ใ€‚", + "show-noctalia-performance-label": "Noctalia ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใƒˆใ‚ฐใƒซใ‚’่กจ็คบ", + "show-power-profile-description": "ใƒใƒƒใƒ†ใƒชใƒผใƒ‘ใƒใƒซใซ้›ปๆบใƒ—ใƒญใƒ•ใ‚กใ‚คใƒซใฎ้ธๆŠž้ …็›ฎใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "show-power-profile-label": "้›ปๆบใƒ—ใƒญใƒ•ใ‚กใ‚คใƒซใ‚ณใƒณใƒˆใƒญใƒผใƒซใ‚’่กจ็คบ" + }, + "brightness": { + "apply-all-description": "ใ‚นใ‚ฏใƒญใƒผใƒซใƒ›ใ‚คใƒผใƒซไฝฟ็”จๆ™‚ใซใ€ใ™ในใฆใฎใƒขใƒ‹ใ‚ฟใƒผใฎ่ผๅบฆใ‚’ๅค‰ๆ›ดใ—ใพใ™ใ€‚", + "apply-all-label": "ใ‚นใ‚ฏใƒญใƒผใƒซใฎๅค‰ๆ›ดใ‚’ใ™ในใฆใฎใƒขใƒ‹ใ‚ฟใƒผใซ้ฉ็”จ" + }, + "clock": { + "clock-display-description": "ไปฅไธ‹ใฎใƒˆใƒผใ‚ฏใƒณใ‚’ไฝฟ็”จใ—ใฆๆ™‚่จˆใฎ่กจ็คบใ‚’ใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บใ—ใพใ™ใ€‚12ๆ™‚้–“่กจ่จ˜ใซใ™ใ‚‹ใซใฏ AP ใƒˆใƒผใ‚ฏใƒณใ‚’ๅซใ‚ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚", + "clock-display-label": "ๆ™‚่จˆใฎ่กจ็คบๅฝขๅผ", + "custom-font-description": "ๆ™‚่จˆ่กจ็คบ็”จใฎใ‚ซใ‚นใ‚ฟใƒ ใƒ•ใ‚ฉใƒณใƒˆใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "custom-font-label": "ใ‚ซใ‚นใ‚ฟใƒ ใƒ•ใ‚ฉใƒณใƒˆ", + "custom-font-placeholder": "ใ‚ซใ‚นใ‚ฟใƒ ใƒ•ใ‚ฉใƒณใƒˆใ‚’้ธๆŠž...", + "custom-font-search-placeholder": "ใƒ•ใ‚ฉใƒณใƒˆใ‚’ๆคœ็ดข...", + "horizontal-bar-description": "ใƒ’ใƒณใƒˆ๏ผš\\n ใ‚’ไฝฟ็”จใ™ใ‚‹ใจๆ”น่กŒใงใใพใ™ใ€‚", + "horizontal-bar-label": "ๆฐดๅนณใƒใƒผ", + "preview": "ใƒ—ใƒฌใƒ“ใƒฅใƒผ", + "tooltip-format-description": "ๆ™‚่จˆใซใƒžใ‚ฆใ‚นใ‚’ๅˆใ‚ใ›ใŸใจใใซ่กจ็คบใ•ใ‚Œใ‚‹ใƒ„ใƒผใƒซใƒใƒƒใƒ—ใฎใƒ•ใ‚ฉใƒผใƒžใƒƒใƒˆๆ–‡ๅญ—ๅˆ—ใ€‚ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฎใƒ„ใƒผใƒซใƒใƒƒใƒ—ใ‚’ไฝฟ็”จใ™ใ‚‹ใซใฏ็ฉบใฎใพใพใซใ—ใพใ™ใ€‚", + "tooltip-format-label": "ใƒ„ใƒผใƒซใƒใƒƒใƒ—ๅฝขๅผ", + "use-custom-font-description": "ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฎใƒ•ใ‚ฉใƒณใƒˆ่จญๅฎšใ‚’ไธŠๆ›ธใใ—ใ€ๆ™‚่จˆใซใ‚ซใ‚นใ‚ฟใƒ ใƒ•ใ‚ฉใƒณใƒˆใ‚’ไฝฟ็”จใ—ใพใ™ใ€‚", + "use-custom-font-label": "ใ‚ซใ‚นใ‚ฟใƒ ใƒ•ใ‚ฉใƒณใƒˆใ‚’ไฝฟ็”จ", + "use-monospaced-font-description": "ๆœ‰ๅŠนใซใ™ใ‚‹ใจใ€ๆ™‚่จˆใซ็ญ‰ๅน…ใƒ•ใ‚ฉใƒณใƒˆใ‚’ไฝฟ็”จใ—ใพใ™ใ€‚", + "use-monospaced-font-label": "็ญ‰ๅน…ใƒ•ใ‚ฉใƒณใƒˆใ‚’ไฝฟ็”จ", + "vertical-bar-description": "ๅ„ใƒ‘ใƒผใƒ„ใ‚’ๆ–ฐใ—ใ„่กŒใซๅˆ†ใ‘ใ‚‹ใซใฏใ‚นใƒšใƒผใ‚นใ‚’ไฝฟ็”จใ—ใฆใใ ใ•ใ„ใ€‚", + "vertical-bar-label": "ๅž‚็›ดใƒใƒผ" + }, + "control-center": { + "browse-file": "ใƒ•ใ‚กใ‚คใƒซใ‚’ๅ‚็…ง", + "browse-library": "ใƒฉใ‚คใƒ–ใƒฉใƒชใ‚’ๅ‚็…ง", + "color-selection-description": "ใ‚ขใ‚คใ‚ณใƒณใซ้ฉ็”จใ™ใ‚‹ใƒ†ใƒผใƒžใฎ่‰ฒใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "enable-colorization-description": "ใ‚ขใ‚คใ‚ณใƒณใฎ่‰ฒไป˜ใ‘ใ‚’ๆœ‰ๅŠนใซใ—ใ€ใƒ†ใƒผใƒžใฎ่‰ฒใ‚’้ฉ็”จใ—ใพใ™ใ€‚", + "icon-description": "ใƒฉใ‚คใƒ–ใƒฉใƒชใพใŸใฏใ‚ซใ‚นใ‚ฟใƒ ใƒ•ใ‚กใ‚คใƒซใ‹ใ‚‰ใ‚ขใ‚คใ‚ณใƒณใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "select-custom-icon": "ใ‚ซใ‚นใ‚ฟใƒ ใ‚ขใ‚คใ‚ณใƒณใ‚’้ธๆŠž", + "use-distro-logo-description": "ใ‚ซใ‚นใ‚ฟใƒ ใ‚ขใ‚คใ‚ณใƒณใฎไปฃใ‚ใ‚Šใซใ€ใƒ‡ใ‚ฃใ‚นใƒˆใƒชใƒ“ใƒฅใƒผใ‚ทใƒงใƒณใฎใƒญใ‚ดใ‚’ไฝฟ็”จใ—ใพใ™ใ€‚", + "use-distro-logo-label": "ใƒ‡ใ‚ฃใ‚นใƒˆใƒชใƒ“ใƒฅใƒผใ‚ทใƒงใƒณใฎใƒญใ‚ดใฎไฝฟ็”จ" + }, + "custom-button": { + "collapse-condition-description": "ๅ‡บๅŠ›ใƒ†ใ‚ญใ‚นใƒˆใŒใ“ใฎๅ€คใจไธ€่‡ดใ™ใ‚‹ๅ ดๅˆใ€ใƒœใ‚ฟใƒณใ‚’ๆŠ˜ใ‚ŠใŸใŸใฟใพใ™ใ€‚", + "collapse-condition-label": "ๆŠ˜ใ‚ŠใŸใŸใฟๆกไปถ", + "color-selection-description": "ใƒ†ใƒผใƒžใ‚ซใƒฉใƒผใ‚’ใ‚ขใ‚คใ‚ณใƒณใจใƒ†ใ‚ญใ‚นใƒˆใซ้ฉ็”จใ—ใพใ™ใ€‚", + "default-tooltip": "ใ‚ซใ‚นใ‚ฟใƒ ใƒœใ‚ฟใƒณใ€่จญๅฎšใงๆง‹ๆˆ", + "display-command-output-description": "ๅฎšๆœŸ็š„ใซๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ‚’ๅ…ฅๅŠ›ใ—ใพใ™ใ€‚ๅ‡บๅŠ›ใฎๆœ€ๅˆใฎ่กŒใŒใƒ†ใ‚ญใ‚นใƒˆใจใ—ใฆ่กจ็คบใ•ใ‚Œใพใ™ใ€‚", + "display-command-output-label": "ใ‚ณใƒžใƒณใƒ‰ๅ‡บๅŠ›ใ‚’่กจ็คบ", + "display-command-output-stream-description": "็ถ™็ถš็š„ใซๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ‚’ๅ…ฅๅŠ›ใ—ใพใ™ใ€‚", + "dynamic-text": "ๅ‹•็š„ใƒ†ใ‚ญใ‚นใƒˆ", + "enable-colorization-description": "ใ‚ซใ‚นใ‚ฟใƒ ใƒœใ‚ฟใƒณใฎใ‚ขใ‚คใ‚ณใƒณใจใƒ†ใ‚ญใ‚นใƒˆใฎใ‚ซใƒฉใƒผใƒชใƒณใ‚ฐใ‚’ๆœ‰ๅŠนใซใ—ใ€ใƒ†ใƒผใƒžใ‚ซใƒฉใƒผใ‚’้ฉ็”จใ—ใพใ™ใ€‚", + "enable-colorization-label": "ใ‚ซใƒฉใƒผใƒชใƒณใ‚ฐใ‚’ๆœ‰ๅŠนๅŒ–", + "general-tooltip-text-description": "ใƒœใ‚ฟใƒณใฎใƒ„ใƒผใƒซใƒใƒƒใƒ—ใซ่กจ็คบใ™ใ‚‹ใ‚ซใ‚นใ‚ฟใƒ ใƒ†ใ‚ญใ‚นใƒˆใ€‚", + "general-tooltip-text-label": "ใ‚ซใ‚นใ‚ฟใƒ ใƒ„ใƒผใƒซใƒใƒƒใƒ—ใƒ†ใ‚ญใ‚นใƒˆ", + "hide-mode-always-expanded": "ๅธธใซๅฑ•้–‹", + "hide-mode-description": "ใ‚ณใƒžใƒณใƒ‰ใฎๅ‡บๅŠ›ใŒใชใ„ๅ ดๅˆใฎใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎ่กจ็คบใ‚’ๅˆถๅพกใ—ใพใ™ใ€‚", + "hide-mode-expand-with-output": "ๅ‡บๅŠ›ใŒใ‚ใ‚‹ๆ™‚ใฎใฟๅฑ•้–‹", + "hide-mode-label": "้ž่กจ็คบใƒขใƒผใƒ‰", + "hide-mode-max-transparent": "ๆœ€ๅคงๅฑ•้–‹๏ผˆ้€ๆ˜Ž๏ผ‰", + "icon-description": "ใƒฉใ‚คใƒ–ใƒฉใƒชใ‹ใ‚‰ใ‚ขใ‚คใ‚ณใƒณใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "ipc-identifier-description": "IPCใ‚ณใƒžใƒณใƒ‰ใฎไธ€ๆ„ใฎ่ญ˜ๅˆฅๅญใ€‚ใ“ใฎ่ญ˜ๅˆฅๅญใ‚’ใ€Œqs -c noctalia-shell ipc call cb [ใ‚ขใ‚ฏใ‚ทใƒงใƒณ] [่ญ˜ๅˆฅๅญ]ใ€ใงไฝฟ็”จใ—ใฆใ€IPC็ตŒ็”ฑใงใ“ใฎใƒœใ‚ฟใƒณใ‚’ๅˆถๅพกใ—ใพใ™ใ€‚", + "ipc-identifier-label": "IPC่ญ˜ๅˆฅๅญ", + "left-click-description": "ใƒœใ‚ฟใƒณใ‚’ๅทฆใ‚ฏใƒชใƒƒใ‚ฏใ—ใŸ้š›ใซๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚", + "left-click-label": "ๅทฆใ‚ฏใƒชใƒƒใ‚ฏ", + "left-click-update-text": "ๅทฆใ‚ฏใƒชใƒƒใ‚ฏๆ™‚ใซ่กจ็คบใƒ†ใ‚ญใ‚นใƒˆใ‚’ๆ›ดๆ–ฐ", + "max-text-length-horizontal-description": "ๆฐดๅนณใƒใƒผใซ่กจ็คบใ™ใ‚‹ๆœ€ๅคงๆ–‡ๅญ—ๆ•ฐ๏ผˆ0ใงใƒ†ใ‚ญใ‚นใƒˆใ‚’้ž่กจ็คบ๏ผ‰ใ€‚", + "max-text-length-horizontal-label": "ใƒ†ใ‚ญใ‚นใƒˆใฎๆœ€ๅคง้•ท๏ผˆๆฐดๅนณ๏ผ‰", + "max-text-length-vertical-description": "ๅž‚็›ดใƒใƒผใซ่กจ็คบใ™ใ‚‹ๆœ€ๅคงๆ–‡ๅญ—ๆ•ฐ๏ผˆ0ใงใƒ†ใ‚ญใ‚นใƒˆใ‚’้ž่กจ็คบ๏ผ‰ใ€‚", + "max-text-length-vertical-label": "ใƒ†ใ‚ญใ‚นใƒˆใฎๆœ€ๅคง้•ท๏ผˆๅž‚็›ด๏ผ‰", + "middle-click-description": "ใƒœใ‚ฟใƒณใ‚’ไธญใ‚ฏใƒชใƒƒใ‚ฏใ—ใŸ้š›ใซๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚", + "middle-click-label": "ไธญใ‚ฏใƒชใƒƒใ‚ฏ", + "middle-click-update-text": "ไธญใ‚ฏใƒชใƒƒใ‚ฏๆ™‚ใซ่กจ็คบใƒ†ใ‚ญใ‚นใƒˆใ‚’ๆ›ดๆ–ฐ", + "parse-json-description": "ใ‚ณใƒžใƒณใƒ‰ๅ‡บๅŠ›ใ‚’ JSON ใ‚ชใƒ–ใ‚ธใ‚งใ‚ฏใƒˆใจใ—ใฆ่งฃๆžใ—ใ€ใƒ†ใ‚ญใ‚นใƒˆใ‚„ใ‚ขใ‚คใ‚ณใƒณใ‚’ๅ‹•็š„ใซ่จญๅฎšใ—ใพใ™ใ€‚", + "parse-json-label": "ๅ‡บๅŠ›ใ‚’ JSON ใจใ—ใฆ่งฃๆž", + "refresh-interval-description": "้–“้š”๏ผˆใƒŸใƒช็ง’๏ผ‰ใ€‚", + "refresh-interval-label": "ๆ›ดๆ–ฐ้–“้š”", + "right-click-description": "ใƒœใ‚ฟใƒณใ‚’ๅณใ‚ฏใƒชใƒƒใ‚ฏใ—ใŸ้š›ใซๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚", + "right-click-label": "ๅณใ‚ฏใƒชใƒƒใ‚ฏ", + "right-click-update-text": "ๅณใ‚ฏใƒชใƒƒใ‚ฏๆ™‚ใซ่กจ็คบใƒ†ใ‚ญใ‚นใƒˆใ‚’ๆ›ดๆ–ฐ", + "show-exec-tooltip-description": "ใ‚ณใƒžใƒณใƒ‰ใฎ่ฉณ็ดฐใ‚’ๅซใ‚€ใƒ„ใƒผใƒซใƒใƒƒใƒ—ใ‚’่กจ็คบ (ๅทฆ/ๅณ/ไธญๅคฎใ‚ฏใƒชใƒƒใ‚ฏใ€ใƒ›ใ‚คใƒผใƒซ)ใ€‚", + "show-exec-tooltip-label": "ใ‚ณใƒžใƒณใƒ‰ใฎใƒ„ใƒผใƒซใƒใƒƒใƒ—ใ‚’่กจ็คบ", + "show-icon-description": "ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎใ‚ขใ‚คใ‚ณใƒณใฎ่กจ็คบใ‚’ๅˆ‡ใ‚Šๆ›ฟใˆใพใ™ใ€‚", + "show-icon-label": "ใ‚ขใ‚คใ‚ณใƒณใ‚’่กจ็คบ", + "show-text-tooltip-description": "ใƒ†ใ‚ญใ‚นใƒˆใ‚ณใƒžใƒณใƒ‰ใฎๅ‡บๅŠ›ใ‚’ๅซใ‚€ใƒ„ใƒผใƒซใƒใƒƒใƒ—ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "show-text-tooltip-label": "ๅ‹•็š„ใชใƒ†ใ‚ญใ‚นใƒˆใƒ„ใƒผใƒซใƒใƒƒใƒ—ใ‚’่กจ็คบ", + "text-stream-description": "ใ‚ณใƒžใƒณใƒ‰ใ‹ใ‚‰ใ‚นใƒˆใƒชใƒผใƒŸใƒณใ‚ฐใ•ใ‚ŒใŸ่กŒใ‚’ใ€ใƒœใ‚ฟใƒณไธŠใฎใƒ†ใ‚ญใ‚นใƒˆใจใ—ใฆ่กจ็คบใ—ใพใ™ใ€‚", + "text-stream-label": "ใ‚นใƒˆใƒชใƒผใƒ ", + "wheel-description": "ใ‚นใ‚ฏใƒญใƒผใƒซใƒ›ใ‚คใƒผใƒซๆ“ไฝœๆ™‚ใซๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚
ใ‚ณใƒžใƒณใƒ‰ๅ†…ใง $delta ใ‚’ไฝฟ็”จใ—ใฆๅ›ž่ปข้‡ใ‚’ๅ–ๅพ—ใงใใพใ™ใ€‚", + "wheel-down": "ไธ‹ใซใ‚นใ‚ฏใƒญใƒผใƒซ", + "wheel-down-description": "ใ‚นใ‚ฏใƒญใƒผใƒซใƒ›ใ‚คใƒผใƒซใ‚’ไธ‹ๆ–นๅ‘ใซๅ›ž่ปขใ•ใ›ใŸ้š›ใซๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚", + "wheel-down-label": "ใƒ›ใ‚คใƒผใƒซไธ‹ๅ›ž่ปขๆ™‚ใฎใ‚ณใƒžใƒณใƒ‰", + "wheel-label": "ใ‚นใ‚ฏใƒญใƒผใƒซใƒ›ใ‚คใƒผใƒซ", + "wheel-mode-separate-description": "ใƒ›ใ‚คใƒผใƒซใฎไธŠๅ›ž่ปขใจไธ‹ๅ›ž่ปขใงๅˆฅใ€…ใฎใ‚ณใƒžใƒณใƒ‰ใ‚’ๆœ‰ๅŠนใซใ—ใพใ™ใ€‚", + "wheel-mode-separate-label": "ใƒ›ใ‚คใƒผใƒซๆ“ไฝœใ‚’ๅˆ†้›ข", + "wheel-up": "ไธŠใซใ‚นใ‚ฏใƒญใƒผใƒซ", + "wheel-up-description": "ใ‚นใ‚ฏใƒญใƒผใƒซใƒ›ใ‚คใƒผใƒซใ‚’ไธŠๆ–นๅ‘ใซๅ›ž่ปขใ•ใ›ใŸ้š›ใซๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚", + "wheel-up-label": "ใƒ›ใ‚คใƒผใƒซไธŠๅ›ž่ปขๆ™‚ใฎใ‚ณใƒžใƒณใƒ‰", + "wheel-update-text": "ใ‚นใ‚ฏใƒญใƒผใƒซๆ™‚ใซ่กจ็คบใƒ†ใ‚ญใ‚นใƒˆใ‚’ๆ›ดๆ–ฐ" + }, + "keyboard-layout": { + "show-icon-description": "ใ‚ญใƒผใƒœใƒผใƒ‰ใƒฌใ‚คใ‚ขใ‚ฆใƒˆใฎใ‚ขใ‚คใ‚ณใƒณใ‚’่กจ็คบใ—ใพใ™ใ€‚" + }, + "lock-keys": { + "hide-when-off-description": "ใ‚ญใƒผใŒใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใงใชใ„ใจใใ‚คใƒณใ‚ธใ‚ฑใƒผใ‚ฟใƒผใ‚’้ž่กจ็คบใซใ—ใพใ™ใ€‚", + "hide-when-off-label": "ใ‚ชใƒ•ใฎๆ™‚ใซ้ž่กจ็คบ", + "show-caps-lock-description": "Caps Lock ใฎ็Šถๆ…‹ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "show-caps-lock-label": "Caps Lock", + "show-num-lock-description": "Num Lock ใฎ็Šถๆ…‹ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "show-num-lock-label": "Num Lock", + "show-scroll-lock-description": "Scroll Lock ใฎ็Šถๆ…‹ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "show-scroll-lock-label": "Scroll Lock" + }, + "media-mini": { + "compact-mode-description": "ใƒกใƒ‡ใ‚ฃใ‚ขใƒ—ใƒฌใƒผใƒคใƒผใƒ‘ใƒใƒซใฎ็œใ‚นใƒšใƒผใ‚นใƒฌใ‚คใ‚ขใ‚ฆใƒˆใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹ใ€‚", + "compact-mode-label": "ใ‚ณใƒณใƒ‘ใ‚ฏใƒˆใƒขใƒผใƒ‰", + "hide-mode-description": "ๅ†็”Ÿไธญใฎใƒกใƒ‡ใ‚ฃใ‚ขใŒใชใ„ๅ ดๅˆใฎใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎๅ‹•ไฝœใ‚’ๅˆถๅพกใ—ใพใ™ใ€‚", + "max-width-description": "ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎๆœ€ๅคงๅน…ใ‚’่จญๅฎšใ—ใพใ™ใ€‚ๅ†…ๅฎนใŒ็Ÿญใ„ๅ ดๅˆใฏ่‡ชๅ‹•็š„ใซ็ธฎๅฐใ—ใพใ™ใ€‚", + "no-active-player": "ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใชใƒ—ใƒฌใƒผใƒคใƒผใชใ—", + "panel-section-description": "ใƒกใƒ‡ใ‚ฃใ‚ขใƒ—ใƒฌใƒผใƒคใƒผใฎใƒ‘ใƒใƒซใฎ่กจ็คบใจๅ‹•ไฝœใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "panel-section-label": "ใƒกใƒ‡ใ‚ฃใ‚ขใƒ—ใƒฌใƒผใƒคใƒผใƒ‘ใƒใƒซ", + "scrolling-mode-description": "้•ทใ„ใƒˆใƒฉใƒƒใ‚ฏใ‚ฟใ‚คใƒˆใƒซใฎใ‚นใ‚ฏใƒญใƒผใƒซๅ‹•ไฝœใ‚’ๅˆถๅพกใ—ใพใ™ใ€‚", + "scrolling-mode-label": "ใ‚นใ‚ฏใƒญใƒผใƒซใƒขใƒผใƒ‰", + "show-album-art-description": "็พๅœจๅ†็”Ÿไธญใฎใƒˆใƒฉใƒƒใ‚ฏใฎใ‚ขใƒซใƒใƒ ใ‚ขใƒผใƒˆใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "show-album-art-label": "ใ‚ขใƒซใƒใƒ ใ‚ขใƒผใƒˆใ‚’่กจ็คบ", + "show-artist-first-description": "ใ€Œใ‚ขใƒผใƒ†ใ‚ฃใ‚นใƒˆๅ - ใ‚ฟใ‚คใƒˆใƒซใ€ใฎ้ †ใง่กจ็คบใ—ใพใ™ใ€‚", + "show-artist-first-label": "ใ‚ขใƒผใƒ†ใ‚ฃใ‚นใƒˆใ‚’ๅ…ˆใซ่กจ็คบ", + "show-progress-ring-description": "ใƒˆใƒฉใƒƒใ‚ฏใฎๅ†็”Ÿไฝ็ฝฎใ‚’็คบใ™ๅ††ๅฝขใฎ้€ฒๆ—ใ‚คใƒณใ‚ธใ‚ฑใƒผใ‚ฟใƒผใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "show-progress-ring-label": "ใƒ—ใƒญใ‚ฐใƒฌใ‚นใƒชใƒณใ‚ฐใ‚’่กจ็คบ", + "show-visualizer-description": "้Ÿณๆฅฝๅ†็”Ÿๆ™‚ใซใ‚ชใƒผใƒ‡ใ‚ฃใ‚ชใƒ“ใ‚ธใƒฅใ‚ขใƒฉใ‚คใ‚ถใƒผใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "show-visualizer-label": "ใƒ“ใ‚ธใƒฅใ‚ขใƒฉใ‚คใ‚ถใƒผใ‚’่กจ็คบ", + "use-fixed-width-description": "ๆœ‰ๅŠนใซใ™ใ‚‹ใจใ€ๅ†…ๅฎนใซๅˆใ‚ใ›ใฆ่‡ชๅ‹•่ชฟๆ•ดใ›ใšใ€ๅธธใซๆœ€ๅคงๅน…ใ‚’ไฝฟ็”จใ—ใพใ™ใ€‚", + "use-fixed-width-label": "ๅ›บๅฎšๅน…ใ‚’ไฝฟ็”จ", + "visualizer-type-description": "่กจ็คบใ™ใ‚‹ใ‚ชใƒผใƒ‡ใ‚ฃใ‚ชใƒ“ใ‚ธใƒฅใ‚ขใƒฉใ‚คใ‚ถใƒผใฎใ‚นใ‚ฟใ‚คใƒซใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "visualizer-type-label": "ใƒ“ใ‚ธใƒฅใ‚ขใƒฉใ‚คใ‚ถใƒผใฎ็จฎ้กž" + }, + "notification-history": { + "hide-widget-when-zero-description": "้€š็ŸฅใŒใชใ„ๅ ดๅˆใซ้€š็Ÿฅใ‚ขใ‚คใ‚ณใƒณใ‚’้ž่กจ็คบใซใ—ใพใ™ใ€‚", + "hide-widget-when-zero-label": "้€š็Ÿฅใชใ—ใงใ‚ขใ‚คใ‚ณใƒณใ‚’้ž่กจ็คบ", + "hide-widget-when-zero-unread-description": "ๆœช่ชญใฎ้€š็ŸฅใŒใชใ„ๅ ดๅˆใซ้€š็Ÿฅใ‚ขใ‚คใ‚ณใƒณใ‚’้ž่กจ็คบใซใ—ใพใ™ใ€‚", + "hide-widget-when-zero-unread-label": "ๆœช่ชญใชใ—ใงใ‚ขใ‚คใ‚ณใƒณใ‚’้ž่กจ็คบ", + "show-unread-badge-description": "ๆœช่ชญ้€š็Ÿฅใฎๆ•ฐใ‚’่กจ็คบใ™ใ‚‹ใƒใƒƒใ‚ธใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "show-unread-badge-label": "ๆœช่ชญใƒใƒƒใ‚ธใ‚’่กจ็คบ", + "unread-badge-color-description": "ๆœช่ชญใฎ้€š็Ÿฅใƒใƒƒใ‚ธใฎ่‰ฒใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "unread-badge-color-label": "ๆœช่ชญใƒใƒƒใ‚ธใฎ่‰ฒ" + }, + "section-editor": { + "placeholder": "่ฟฝๅŠ ใ™ใ‚‹ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’้ธๆŠž...", + "search-placeholder": "ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’ๆคœ็ดข..." + }, + "spacer": { + "width-description": "ใ‚นใƒšใƒผใ‚นใฎๅน…๏ผˆใƒ”ใ‚ฏใ‚ปใƒซ๏ผ‰ใ€‚" + }, + "system-monitor": { + "compact-mode-description": "ใƒ†ใ‚ญใ‚นใƒˆๅ€คใฎไปฃใ‚ใ‚Šใซใ€ใƒŸใƒ‹ๆฃ’ใ‚ฐใƒฉใƒ•ใจใ—ใฆ็ตฑ่จˆใ‚’่กจ็คบใ—ใพใ™ใ€‚ใƒฌใ‚คใ‚ขใ‚ฆใƒˆใฎใšใ‚Œใ‚’้˜ฒใŽใพใ™ใ€‚", + "compact-mode-label": "ใ‚ณใƒณใƒ‘ใ‚ฏใƒˆใƒขใƒผใƒ‰", + "cpu-cores-description": "CPUใ‚ณใ‚ขใฎไฝฟ็”จ็Šถๆณใ‚’ๅ€‹ๅˆฅใซ่กจ็คบใ—ใพใ™ใ€‚", + "cpu-cores-label": "CPUใ‚ณใ‚ข", + "cpu-frequency-description": "็พๅœจใฎCPUใ‚ฏใƒญใƒƒใ‚ฏ้€Ÿๅบฆใ‚’GHzใง่กจ็คบใ—ใพใ™ใ€‚", + "cpu-frequency-label": "CPUๅ‘จๆณขๆ•ฐใ‚’่กจ็คบ", + "cpu-temperature-description": "ๅˆฉ็”จๅฏ่ƒฝใชๅ ดๅˆใ€CPU ๆธฉๅบฆใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "cpu-temperature-label": "CPU ๆธฉๅบฆ", + "cpu-usage-description": "็พๅœจใฎ CPU ไฝฟ็”จ็އใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "cpu-usage-label": "CPU ไฝฟ็”จ็އ", + "disk-path-description": "็›ฃ่ฆ–ใ™ใ‚‹ใƒ‡ใ‚ฃใ‚นใ‚ฏใฎใƒžใ‚ฆใƒณใƒˆใƒใ‚คใƒณใƒˆใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "disk-path-label": "ใƒ‡ใ‚ฃใ‚นใ‚ฏใƒ‘ใ‚น", + "gpu-temperature-description": "ๅˆฉ็”จๅฏ่ƒฝใชๅ ดๅˆใ€GPU ๆธฉๅบฆใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "load-average-description": "ใ‚ทใ‚นใƒ†ใƒ ่ฒ ่ทๅนณๅ‡ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "load-average-label": "่ฒ ่ทๅนณๅ‡", + "memory-percentage-description": "ใƒกใƒขใƒชไฝฟ็”จ้‡ใ‚’็ตถๅฏพๅ€คใงใฏใชใใƒ‘ใƒผใ‚ปใƒณใƒ†ใƒผใ‚ธใง่กจ็คบใ—ใพใ™ใ€‚", + "memory-percentage-label": "ใƒกใƒขใƒชใ‚’ใƒ‘ใƒผใ‚ปใƒณใƒˆใง่กจ็คบ", + "memory-usage-description": "็พๅœจใฎ RAM ไฝฟ็”จ้‡ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "memory-usage-label": "ใƒกใƒขใƒชไฝฟ็”จ้‡", + "network-traffic-description": "ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใฎใ‚ขใƒƒใƒ—ใƒญใƒผใƒ‰ใƒปใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰้€Ÿๅบฆใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "network-traffic-label": "ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใƒˆใƒฉใƒ•ใ‚ฃใƒƒใ‚ฏ", + "storage-as-percentage-description": "ใƒ‡ใ‚ฃใ‚นใ‚ฏๅฎน้‡ใ‚’็ตถๅฏพๅ€คใงใฏใชใใƒ‘ใƒผใ‚ปใƒณใƒ†ใƒผใ‚ธใง่กจ็คบใ™ใ‚‹ใ€‚", + "storage-as-percentage-label": "ใƒ‡ใ‚ฃใ‚นใ‚ฏใ‚’ใƒ‘ใƒผใ‚ปใƒณใƒ†ใƒผใ‚ธใง", + "storage-available-description": "ไฝฟ็”จ้‡ใงใฏใชใใ€ๅˆฉ็”จๅฏ่ƒฝใชใƒ‡ใ‚ฃใ‚นใ‚ฏๅฎน้‡ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "storage-available-label": "ๅˆฉ็”จๅฏ่ƒฝใชใƒ‡ใ‚ฃใ‚นใ‚ฏๅฎน้‡", + "storage-usage-description": "ใ‚นใƒˆใƒฌใƒผใ‚ธใฎไฝฟ็”จ็Šถๆณใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "storage-usage-label": "ใ‚นใƒˆใƒฌใƒผใ‚ธไฝฟ็”จ้‡", + "swap-usage-description": "ใ‚นใƒฏใƒƒใƒ—ใƒกใƒขใƒชใฎไฝฟ็”จ็Šถๆณใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "swap-usage-label": "ใ‚นใƒฏใƒƒใƒ—ไฝฟ็”จ้‡", + "use-monospace-font-description": "ใ‚ฟใ‚คใƒ—ใƒฉใ‚คใ‚ฟใƒผไฝ“ใง็ฟป่จณใ—ใพใ™ใ€‚", + "use-monospace-font-label": "็ญ‰ๅน…ใƒ•ใ‚ฉใƒณใƒˆ", + "use-padding-description": "ใƒฌใ‚คใ‚ขใ‚ฆใƒˆใฎใšใ‚Œใ‚’้˜ฒใใŸใ‚ใ€ใƒ†ใ‚ญใ‚นใƒˆๅ€คใซๅ…ˆ้ ญใฎใ‚นใƒšใƒผใ‚นใ‚’ๅŸ‹ใ‚่พผใฟใพใ™ใ€‚", + "use-padding-description-disabled-monospace-font": "ใ“ใฎๆฉŸ่ƒฝใซใฏ็ญ‰ๅน…ใƒ•ใ‚ฉใƒณใƒˆใŒๅฟ…่ฆใงใ™ใ€‚", + "use-padding-description-disabled-vertical": "็ธฆใฎใ‚ฟใ‚นใ‚ฏใƒใƒผใฏใƒ‘ใƒ‡ใ‚ฃใƒณใ‚ฐใƒ†ใ‚ญใ‚นใƒˆใ‚’ใ‚ตใƒใƒผใƒˆใ—ใฆใ„ใพใ›ใ‚“ใ€‚", + "use-padding-label": "ใƒ†ใ‚ญใ‚นใƒˆใฎใƒ‘ใƒ‡ใ‚ฃใƒณใ‚ฐ" + }, + "taskbar": { + "colorize-icons-description": "ใ‚ฟใ‚นใ‚ฏใƒใƒผใฎใ‚ขใ‚คใ‚ณใƒณใซใƒ†ใƒผใƒžใ‚ซใƒฉใƒผใ‚’้ฉ็”จใ—ใพใ™ใ€‚", + "hide-mode-description": "ไธ€่‡ดใ™ใ‚‹ใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใŒใชใ„ๅ ดๅˆใฎใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎๅ‹•ไฝœใ‚’ๅˆถๅพกใ—ใพใ™ใ€‚", + "hide-mode-label": "้ž่กจ็คบใƒขใƒผใƒ‰", + "icon-scale-description": "ใ‚ฟใ‚นใ‚ฏใƒใƒผใฎใ‚ขใ‚คใ‚ณใƒณใฎๆ‹กๅคง็އใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "icon-scale-label": "ใ‚ขใ‚คใ‚ณใƒณใฎใ‚นใ‚ฑใƒผใƒชใƒณใ‚ฐ", + "max-width-description": "ใ‚ฟใ‚นใ‚ฏใƒใƒผใฎๆœ€ๅคงๅน…ใ‚’ใ€็”ป้ขๅน…ใซๅฏพใ™ใ‚‹ใƒ‘ใƒผใ‚ปใƒณใƒ†ใƒผใ‚ธใง่จญๅฎšใ—ใพใ™ใ€‚", + "max-width-label": "ๆœ€ๅคงๅน…", + "only-active-workspaces-description": "ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใชใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใฎใ‚ขใƒ—ใƒชใฎใฟใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "only-active-workspaces-label": "ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใชใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใฎใฟ", + "only-same-monitor-description": "ใƒใƒผใŒ้…็ฝฎใ•ใ‚Œใฆใ„ใ‚‹ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คไธŠใฎใ‚ขใƒ—ใƒชใฎใฟใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "only-same-monitor-label": "ๅŒใ˜ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คใฎใฟ", + "show-pinned-apps-description": "ใƒ‰ใƒƒใ‚ฏใซใƒ”ใƒณ็•™ใ‚ใ•ใ‚ŒใŸใ‚ขใƒ—ใƒชใ‚’ใ‚ฟใ‚นใ‚ฏใƒใƒผใซ่กจ็คบใ—ใพใ™ใ€‚", + "show-pinned-apps-label": "ใƒ”ใƒณ็•™ใ‚ใ‚ขใƒ—ใƒชใ‚’่กจ็คบ", + "show-title-description": "ใ‚ฟใ‚นใ‚ฏใƒใƒผใซใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใ‚ฟใ‚คใƒˆใƒซใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "show-title-description-disabled": "ๅž‚็›ดใ‚ฟใ‚นใ‚ฏใƒใƒผใงใฏใ‚ฟใ‚คใƒˆใƒซใ‚’่กจ็คบใงใใพใ›ใ‚“ใ€‚", + "show-title-label": "ใ‚ฟใ‚คใƒˆใƒซใ‚’่กจ็คบ", + "smart-width-description": "้ …็›ฎใฎๆ•ฐใซๅŸบใฅใ„ใฆใ€้ …็›ฎใฎๅน…ใ‚’่‡ชๅ‹•็š„ใซ่ชฟๆ•ดใ—ใพใ™ใ€‚", + "smart-width-label": "ๅน…ใฎ่‡ชๅ‹•่ชฟๆ•ด", + "title-width-description": "ใ‚ฟใ‚นใ‚ฏใƒใƒผใฎใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใ‚ฟใ‚คใƒˆใƒซใฎๅน…ใ‚’่จญๅฎšใ—ใพใ™๏ผˆใƒ”ใ‚ฏใ‚ปใƒซๅ˜ไฝ๏ผ‰ใ€‚", + "title-width-label": "ใ‚ฟใ‚คใƒˆใƒซใฎๅน…", + "title-width-reset-tooltip": "ใ‚ฟใ‚คใƒˆใƒซใฎๅน…ใ‚’ใƒชใ‚ปใƒƒใƒˆ" + }, + "tray": { + "chevron-color-description": "ใƒ‰ใƒญใƒฏใƒผใฎใ‚ทใ‚งใƒ–ใƒญใƒณใ‚ขใ‚คใ‚ณใƒณใซใƒ†ใƒผใƒžใฎ่‰ฒใ‚’้ฉ็”จใ—ใพใ™ใ€‚", + "chevron-color-label": "็Ÿขๅฐใฎ่‰ฒ", + "colorize-icons-description": "ใƒˆใƒฌใ‚คใฎใ‚ขใ‚คใ‚ณใƒณใซใƒ†ใƒผใƒžใ‚ซใƒฉใƒผใ‚’้ฉ็”จใ—ใพใ™ใ€‚", + "colorize-icons-label": "ใƒ†ใƒผใƒžใ‚ซใƒฉใƒผใฎ้ฉ็”จ", + "drawer-enabled-description": "ๆœ‰ๅŠนใซใ™ใ‚‹ใจใ€ใƒ”ใƒณ็•™ใ‚ใ•ใ‚Œใฆใ„ใชใ„ใƒˆใƒฌใ‚ค้ …็›ฎใ‚’ใƒ‰ใƒญใƒฏใƒผใƒ‘ใƒใƒซๅ†…ใซ่กจ็คบใ—ใพใ™ใ€‚
็„กๅŠนใซใ™ใ‚‹ใจใ€ใ™ในใฆใฎ้ …็›ฎใ‚’ใ‚คใƒณใƒฉใ‚คใƒณใง่กจ็คบใ—ใพใ™ใ€‚", + "drawer-enabled-label": "ใƒ‰ใƒญใƒฏใƒผใ‚’ๆœ‰ๅŠนๅŒ–", + "hide-passive-description": "ๆœ‰ๅŠนใซใ™ใ‚‹ใจใ€ใ‚นใƒ†ใƒผใ‚ฟใ‚นใŒใ€Œใƒ‘ใƒƒใ‚ทใƒ–ใ€ใฎใƒˆใƒฌใ‚ค้ …็›ฎใ‚’้ž่กจ็คบใซใ—ใพใ™ใ€‚", + "hide-passive-label": "ใƒ‘ใƒƒใ‚ทใƒ–้ …็›ฎใ‚’้ž่กจ็คบ" + }, + "volume": { + "display-mode-description": "ๅ€คใฎ่กจ็คบๆ–นๆณ•ใ‚’้ธๆŠžใ—ใพใ™ใ€‚" + }, + "workspace": { + "character-count-description": "ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นๅใ‹ใ‚‰่กจ็คบใ™ใ‚‹ๆ–‡ๅญ—ๆ•ฐ (1-10)ใ€‚", + "character-count-label": "ๆ–‡ๅญ—ๆ•ฐ", + "empty-color-description": "็ฉบใฎWorkspaceใฎ่ƒŒๆ™ฏ่‰ฒใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "empty-color-label": "็ฉบใฎใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใฎ่‰ฒ", + "enable-scrollwheel-description": "ใƒžใ‚ฆใ‚นใƒ›ใ‚คใƒผใƒซใ‚’ไฝฟ็”จใ—ใฆใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใ‚’ๅˆ‡ใ‚Šๆ›ฟใˆใพใ™ใ€‚", + "enable-scrollwheel-label": "ใ‚นใ‚ฏใƒญใƒผใƒซใงใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใ‚’ๅˆ‡ใ‚Šๆ›ฟใˆ", + "focused-color-description": "ใƒ•ใ‚ฉใƒผใ‚ซใ‚นใ•ใ‚ŒใŸWorkspaceใฎ่ƒŒๆ™ฏ่‰ฒใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "focused-color-label": "ใƒ•ใ‚ฉใƒผใ‚ซใ‚นใ•ใ‚ŒใŸใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใฎ่‰ฒ", + "follow-focused-screen-description": "ใƒใƒผใŒ้…็ฝฎใ•ใ‚Œใฆใ„ใ‚‹็”ป้ขใงใฏใชใใ€็พๅœจใƒ•ใ‚ฉใƒผใ‚ซใ‚นใ•ใ‚Œใฆใ„ใ‚‹็”ป้ขใฎใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "follow-focused-screen-label": "ใƒ•ใ‚ฉใƒผใ‚ซใ‚นไธญใฎ็”ป้ขใซ่ฟฝๅพ“", + "font-weight-description": "ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นๅ†…ใฎใƒ†ใ‚ญใ‚นใƒˆใฎ่ฆ–่ฆš็š„ใ‚ฆใ‚งใ‚คใƒˆใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "font-weight-label": "ใƒ•ใ‚ฉใƒณใƒˆใฎๅคชใ•", + "grouped-border-opacity-description": "ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใ‚ณใƒณใƒ†ใƒŠใฎใƒœใƒผใƒ€ใƒผใฎไธ้€ๆ˜Žๅบฆใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "grouped-border-opacity-label": "ๅขƒ็•Œ็ทšใฎไธ้€ๆ˜Žๅบฆ", + "hide-unoccupied-description": "ใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใŒใชใ„ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใ‚’่กจ็คบใ—ใพใ›ใ‚“ใ€‚", + "hide-unoccupied-label": "็ฉบใฎใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใ‚’้š ใ™", + "label-mode-description": "ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใƒฉใƒ™ใƒซใฎ่กจ็คบๆ–นๆณ•ใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "label-mode-label": "ใƒฉใƒ™ใƒซใƒขใƒผใƒ‰", + "occupied-color-description": "ไฝฟ็”จไธญใฎWorkspaceใฎ่ƒŒๆ™ฏ่‰ฒใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "occupied-color-label": "ไฝฟ็”จไธญใฎใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใฎ่‰ฒ", + "pill-size-description": "ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใƒ”ใƒซใฎใ‚ตใ‚คใ‚บใ‚’่ชฟๆ•ดใ—ใพใ™ (50%-100%)ใ€‚", + "pill-size-label": "ใƒ”ใƒซใ‚ตใ‚คใ‚บ", + "reverse-scrolling-description": "ใ‚นใ‚ฏใƒญใƒผใƒซๆ™‚ใฎใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นๅˆ‡ใ‚Šๆ›ฟใˆๆ–นๅ‘ใ‚’ๅ่ปขใ™ใ‚‹ใ€‚", + "reverse-scrolling-label": "ใ‚นใ‚ฏใƒญใƒผใƒซๆ–นๅ‘ใ‚’ๅ่ปข", + "show-applications-description": "ๅ„ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นๅ†…ใซใ‚ขใƒ—ใƒชใ‚ขใ‚คใ‚ณใƒณใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "show-applications-hover-description": "ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใซใ‚ซใƒผใ‚ฝใƒซใ‚’ๅˆใ‚ใ›ใŸใจใใซใ€ๅ„ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นๅ†…ใซใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใ‚ขใ‚คใ‚ณใƒณใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "show-applications-hover-label": "ใƒ›ใƒใƒผๆ™‚ใซใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใ‚’่กจ็คบ", + "show-applications-label": "ใ‚ขใƒ—ใƒชใ‚ขใ‚คใ‚ณใƒณใ‚’่กจ็คบ", + "show-badge-description": "ใ‚ฐใƒซใƒผใƒ—ๅŒ–ใƒขใƒผใƒ‰ใงใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚น็•ชๅทใƒใƒƒใ‚ธใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "show-badge-label": "ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใƒใƒƒใ‚ธใ‚’่กจ็คบ", + "show-labels-only-when-occupied-description": "ใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใŒ้–‹ใ„ใฆใ„ใ‚‹ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใซใฎใฟใƒฉใƒ™ใƒซใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "show-labels-only-when-occupied-label": "ใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใŒใ‚ใ‚‹ๆ™‚ใฎใฟใƒฉใƒ™ใƒซใ‚’่กจ็คบ", + "unfocused-icons-opacity-description": "ใƒ•ใ‚ฉใƒผใ‚ซใ‚นใ•ใ‚Œใฆใ„ใชใ„ใ‚ขใƒ—ใƒชใฎใ‚ขใ‚คใ‚ณใƒณใฎไธ้€ๆ˜Žๅบฆใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "unfocused-icons-opacity-label": "ใƒ•ใ‚ฉใƒผใ‚ซใ‚นใ•ใ‚Œใฆใ„ใชใ„ใ‚ขใ‚คใ‚ณใƒณใฎไธ้€ๆ˜Žๅบฆ" + } + }, + "battery": { + "all-batteries": "ใ™ในใฆใฎใƒใƒƒใƒ†ใƒชใƒผ๏ผˆๅˆ่จˆ๏ผ‰", + "battery-health": "ใƒใƒƒใƒ†ใƒชใƒผใฎ็Šถๆ…‹", + "battery-level": "ใƒใƒƒใƒ†ใƒชใƒผๆฎ‹้‡", + "capacity-level": "ๅฎน้‡๏ผš{level}", + "charging-rate": "ๅ……้›ป้€Ÿๅบฆ๏ผš{rate} W", + "discharging-rate": "ๆ”พ้›ป้€Ÿๅบฆ๏ผš{rate} W", + "health": "็Šถๆ…‹๏ผš{percent}%", + "inhibit-idle-description": "ใ‚ทใ‚นใƒ†ใƒ ใŒใ‚นใƒชใƒผใƒ—็Šถๆ…‹ใซใชใ‚‹ใฎใ‚’้˜ฒใŽใพใ™ใ€‚", + "no-battery-detected": "ใƒใƒƒใƒ†ใƒชใƒผใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“", + "plugged-in": "้›ปๆบๆŽฅ็ถšไธญ", + "power-profile": "้›ปๆบใƒ—ใƒญใƒ•ใ‚กใ‚คใƒซ", + "time-left": "ๆฎ‹ใ‚Šๆ™‚้–“๏ผš{time}", + "time-until-full": "ๅ……้›ปๅฎŒไบ†ใพใง๏ผš{time}" + }, + "bluetooth": { + "panel": { + "available-devices": "ๅˆฉ็”จๅฏ่ƒฝใชใƒ‡ใƒใ‚คใ‚น", + "blocked": "ใƒ–ใƒญใƒƒใ‚ฏไธญ", + "connected-devices": "ๆŽฅ็ถšๆธˆใฟใƒ‡ใƒใ‚คใ‚น", + "device-address": "ใƒ‡ใƒใ‚คใ‚นใ‚ขใƒ‰ใƒฌใ‚น", + "disabled": "Bluetooth ใฏ็„กๅŠนใงใ™", + "discoverable": "็™บ่ฆ‹ๅฏ่ƒฝ", + "enable-message": "ๅˆฉ็”จๅฏ่ƒฝใชใƒ‡ใƒใ‚คใ‚นใ‚’่กจ็คบใ™ใ‚‹ใซใฏ Bluetooth ใ‚’ๆœ‰ๅŠนใซใ—ใฆใใ ใ•ใ„ใ€‚", + "known-devices": "็™ป้Œฒๆธˆใฟใƒ‡ใƒใ‚คใ‚น", + "no-devices": "ๅˆฉ็”จๅฏ่ƒฝใชใƒ‡ใƒใ‚คใ‚นใฏใ‚ใ‚Šใพใ›ใ‚“", + "paired-devices": "ใƒšใ‚ขใƒชใƒณใ‚ฐใ•ใ‚ŒใŸใƒ‡ใƒใ‚คใ‚น", + "pairing-mode": "ใƒ‡ใƒใ‚คใ‚นใŒใƒšใ‚ขใƒชใƒณใ‚ฐใƒขใƒผใƒ‰ใซใชใฃใฆใ„ใ‚‹ใ“ใจใ‚’็ขบ่ชใ—ใฆใใ ใ•ใ„ใ€‚", + "scanning": "ใƒ‡ใƒใ‚คใ‚นใ‚’ใ‚นใ‚ญใƒฃใƒณไธญ...", + "signal-text-excellent": "ไฟกๅท๏ผš่‰ฏๅฅฝ", + "signal-text-fair": "ๅˆๅ›ณ๏ผšๅฏ", + "signal-text-good": "ไฟกๅท๏ผš่‰ฏใ—", + "signal-text-poor": "ไฟกๅท๏ผšๅผฑใ„", + "signal-text-unknown": "ไฟกๅท๏ผšไธๆ˜Ž", + "signal-text-very-poor": "ไฟกๅท๏ผš้žๅธธใซๆ‚ชใ„" + } + }, + "changelog": { + "error": { + "fetch-failed": "ๅค‰ๆ›ดๅฑฅๆญดใƒ‡ใƒผใ‚ฟใ‚’่ชญใฟ่พผใ‚ใพใ›ใ‚“ใงใ—ใŸใ€‚ๅพŒใปใฉๅ†่ฉฆ่กŒใ—ใฆใใ ใ•ใ„ใ€‚", + "rate-limit": "GitHub ใฎใƒฌใƒผใƒˆๅˆถ้™ใ‚’่ถ…ใˆใพใ—ใŸใ€‚ๆ•ฐๅˆ†ๅพ…ใฃใฆใ‹ใ‚‰ๅ†่ฉฆ่กŒใ—ใฆใใ ใ•ใ„ใ€‚" + }, + "panel": { + "buttons-discord": "Discord ใซๅ‚ๅŠ ", + "buttons-dismiss": "OK", + "buttons-feedback": "ใƒ•ใ‚ฃใƒผใƒ‰ใƒใƒƒใ‚ฏใใ ใ•ใ„", + "empty": "ใƒชใƒชใƒผใ‚นใƒŽใƒผใƒˆใฏใพใ ๅˆฉ็”จใงใใพใ›ใ‚“ใ€‚", + "highlight-title": "ใƒใ‚คใƒฉใ‚คใƒˆ", + "released": "{date} ใƒชใƒชใƒผใ‚น", + "subtitle-fresh": "Noctalia ใ‚’ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใฆใ„ใŸใ ใใ‚ใ‚ŠใŒใจใ†ใ”ใ–ใ„ใพใ™๏ผไปŠๅ›žใฎใƒ“ใƒซใƒ‰ๅ†…ๅฎนใฏไปฅไธ‹ใฎ้€šใ‚Šใงใ™ใ€‚", + "subtitle-updated": "{previousVersion} ใ‹ใ‚‰ใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆใ•ใ‚Œใพใ—ใŸ", + "title": "ใƒใƒผใ‚ธใƒงใƒณ {version} ใฎๆ–ฐๆฉŸ่ƒฝ", + "version": "ใƒใƒผใ‚ธใƒงใƒณ {version}", + "version-new-user": "ๆ–ฐ่ฆใ‚คใƒณใ‚นใƒˆใƒผใƒซ" + } + }, + "common": { + "actions": "ใ‚ขใ‚ฏใ‚ทใƒงใƒณ", + "active": "ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–", + "add": "่ฟฝๅŠ ", + "appearance": "ๅค–่ฆณ", + "apply": "้ฉ็”จ", + "auto-connect": "่‡ชๅ‹•ๆŽฅ็ถš", + "automation": "่‡ชๅ‹•ๅŒ–", + "available": "ๅˆฉ็”จๅฏ่ƒฝ", + "back": "ๆˆปใ‚‹", + "battery": "ใƒใƒƒใƒ†ใƒชใƒผ", + "behavior": "ๅ‹•ไฝœ", + "bluetooth": "Bluetooth", + "brightness": "ๆ˜Žใ‚‹ใ•", + "browse": "้–ฒ่ฆง", + "calendar": "ใ‚ซใƒฌใƒณใƒ€ใƒผ", + "calendar-panel": "ใ‚ซใƒฌใƒณใƒ€ใƒผใƒ‘ใƒใƒซ", + "cancel": "ใ‚ญใƒฃใƒณใ‚ปใƒซ", + "cards": "ใ‚ซใƒผใƒ‰", + "charging": "ๅ……้›ปไธญ", + "clear": "ใ‚ฏใƒชใ‚ข", + "clipboard": "ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰", + "close": "้–‰ใ˜ใ‚‹", + "color-muted": "ใƒŸใƒฅใƒผใƒˆ", + "colors": "่‰ฒ", + "command": "ใ‚ณใƒžใƒณใƒ‰", + "confirm": "็ขบ่ช", + "connect": "ๆŽฅ็ถš", + "connected": "ๆŽฅ็ถšๆธˆใฟ", + "connecting": "ๆŽฅ็ถšไธญ...", + "continue": "็ถšใ‘ใ‚‹", + "contributors": "่ฒข็Œฎ่€…", + "copied-to-clipboard": "ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ใซใ‚ณใƒ”ใƒผใ—ใพใ—ใŸ", + "countdown": "ใ‚ซใ‚ฆใƒณใƒˆใƒ€ใ‚ฆใƒณ", + "customize": "ใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บ", + "date": "ๆ—ฅไป˜", + "default": "ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆ", + "delete": "ๅ‰Š้™ค", + "devices": "ใƒ‡ใƒใ‚คใ‚น", + "disabled": "็„กๅŠน", + "discharging": "ๆ”พ้›ปไธญ", + "disconnect": "ๅˆ‡ๆ–ญ", + "disconnected": "ๅˆ‡ๆ–ญๆธˆใฟ", + "disconnecting": "ๅˆ‡ๆ–ญไธญ...", + "display-mode": "่กจ็คบใƒขใƒผใƒ‰", + "documentation": "ใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆ", + "download": "ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰", + "duration": "ๆœŸ้–“", + "dysfunctional": "ๆฉŸ่ƒฝไธๅ…จ", + "edit": "็ทจ้›†", + "enabled": "ๆœ‰ๅŠน", + "error": "ใ‚จใƒฉใƒผ", + "ethernet": "ใ‚คใƒผใ‚ตใƒใƒƒใƒˆ", + "events": "ใ‚คใƒ™ใƒณใƒˆ", + "execute": "ๅฎŸ่กŒ", + "faithful": "ๅฟ ๅฎŸ", + "focus": "้›†ไธญ", + "font-weight-bold": "ๅคชๅญ—", + "font-weight-light": "ใƒฉใ‚คใƒˆ", + "font-weight-medium": "ไธญ", + "font-weight-regular": "ๆจ™ๆบ–", + "font-weight-semibold": "ใ‚ปใƒŸใƒœใƒผใƒซใƒ‰", + "frequency": "้ ปๅบฆ", + "gateway": "ใ‚ฒใƒผใƒˆใ‚ฆใ‚งใ‚ค", + "general": "ไธ€่ˆฌ", + "height": "้ซ˜ใ•", + "hibernate": "ใƒใ‚คใƒใƒใƒผใƒˆ", + "history": "ๅฑฅๆญด", + "icon": "ใ‚ขใ‚คใ‚ณใƒณ", + "idle": "ใ‚ขใ‚คใƒ‰ใƒซ็Šถๆ…‹", + "info": "ๆƒ…ๅ ฑ", + "input": "ๅ…ฅๅŠ›", + "install": "ใ‚คใƒณใ‚นใƒˆใƒผใƒซ", + "installed": "ใ‚คใƒณใ‚นใƒˆใƒผใƒซๆธˆใฟ", + "interface": "ใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใƒผใ‚น", + "internet": "ใ‚คใƒณใ‚ฟใƒผใƒใƒƒใƒˆ", + "keybind": "ใ‚ญใƒผใƒใ‚คใƒณใƒ‰", + "language": "่จ€่ชž", + "loading": "่ชญใฟ่พผใฟไธญ...", + "local": "ใƒญใƒผใ‚ซใƒซ", + "location": "ๅ ดๆ‰€", + "lock": "ใƒญใƒƒใ‚ฏ", + "logout": "ใƒญใ‚ฐใ‚ขใ‚ฆใƒˆ", + "look": "ๅค–่ฆณ", + "margins": "ไฝ™็™ฝ", + "media": "ใƒกใƒ‡ใ‚ฃใ‚ข", + "media-player": "ใƒกใƒ‡ใ‚ฃใ‚ขใƒ—ใƒฌใƒผใƒคใƒผ", + "memory": "ใƒกใƒขใƒช", + "monitors": "ใƒขใƒ‹ใ‚ฟใƒผ", + "move-down": "ไธ‹ใธ็งปๅ‹•", + "move-up": "ไธŠใธ็งปๅ‹•", + "network": "ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏ", + "next": "ๆฌกใธ", + "night-light": "ๅคœ้–“ใƒขใƒผใƒ‰", + "no": "ใ„ใ„ใˆ", + "no-results": "็ตๆžœใชใ—", + "no-summary": "่ฆ็ด„ใชใ—", + "none": "ใชใ—", + "not-found": "่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใงใ—ใŸ", + "notifications": "้€š็Ÿฅ", + "official": "ๅ…ฌๅผ", + "on-surface": "่กจ้ขไธŠ", + "output": "ๅ‡บๅŠ›", + "pair": "ใƒšใ‚ขใƒชใƒณใ‚ฐ", + "paired": "ใƒšใ‚ขใƒชใƒณใ‚ฐๆธˆใฟ", + "pairing": "ใƒšใ‚ขใƒชใƒณใ‚ฐไธญ...", + "panels": "ใƒ‘ใƒใƒซ", + "password": "ใƒ‘ใ‚นใƒฏใƒผใƒ‰", + "pause": "ไธ€ๆ™‚ๅœๆญข", + "performance": "ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚น", + "pin": "ใƒ”ใƒณ็•™ใ‚", + "play": "ๅ†็”Ÿ", + "polling": "ใƒใƒผใƒชใƒณใ‚ฐ", + "position": "ไฝ็ฝฎ", + "previous": "ๅ‰ใธ", + "primary": "ใƒ—ใƒฉใ‚คใƒžใƒช", + "random": "ใƒฉใƒณใƒ€ใƒ ", + "reboot": "ๅ†่ตทๅ‹•", + "reboot-to-uefi": "UEFIใงๅ†่ตทๅ‹•", + "record": "้Œฒ็”ปใ‚’้–‹ๅง‹", + "refresh": "ๆ›ดๆ–ฐ", + "required": "(ๅฟ…้ ˆ)", + "reset": "ใƒชใ‚ปใƒƒใƒˆ", + "reset-to-default": "ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใซใƒชใ‚ปใƒƒใƒˆ", + "result-count": "{count} ็ตๆžœ", + "result-count-plural": "{count} ็ตๆžœ", + "resume": "ๅ†้–‹", + "save": "ไฟๅญ˜", + "scanning": "ใ‚นใ‚ญใƒฃใƒณไธญ...", + "screen-corners": "็”ป้ขใฎ้š…", + "search": "ๆคœ็ดข", + "second": "{count} ็ง’", + "second-plural": "{count} ็ง’", + "secondary": "ใ‚ปใ‚ซใƒณใƒ€ใƒชใƒผ", + "security": "ใ‚ปใ‚ญใƒฅใƒชใƒ†ใ‚ฃ", + "select": "้ธๆŠž", + "select-color": "่‰ฒใ‚’้ธๆŠž", + "select-color-description": "ๅผท่ชฟใฎใŸใ‚ใซใƒ†ใƒผใƒžใฎ่‰ฒใ‚’้ฉ็”จใ—ใพใ™ใ€‚", + "select-icon-color": "ใ‚ขใ‚คใ‚ณใƒณใฎ่‰ฒใ‚’้ธๆŠž", + "settings": "่จญๅฎš", + "shortcuts": "ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆ", + "shutdown": "ใ‚ทใƒฃใƒƒใƒˆใƒ€ใ‚ฆใƒณ", + "signal": "ไฟกๅท", + "sound": "ใ‚ตใ‚ฆใƒณใƒ‰", + "sources": "ใ‚ฝใƒผใ‚น", + "start": "้–‹ๅง‹", + "stop": "ๅœๆญข", + "supporters": "ใ‚ตใƒใƒผใ‚ฟใƒผ", + "suspend": "ใ‚ตใ‚นใƒšใƒณใƒ‰", + "templates": "ใƒ†ใƒณใƒ—ใƒฌใƒผใƒˆ", + "tertiary": "็ฌฌไธ‰", + "test": "ใƒ†ใ‚นใƒˆ", + "thresholds": "ใ—ใใ„ๅ€ค", + "title": "ใ‚ฟใ‚คใƒˆใƒซ", + "toast": "ใƒˆใƒผใ‚นใƒˆ", + "trusted": "ไฟก้ ผๆธˆใฟ", + "uninstall": "ใ‚ขใƒณใ‚คใƒณใ‚นใƒˆใƒผใƒซ", + "unknown": "ไธๆ˜Ž", + "unpair": "ใƒšใ‚ขใƒชใƒณใ‚ฐ่งฃ้™ค", + "unpin": "ใƒ”ใƒณ็•™ใ‚่งฃ้™ค", + "update": "ใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆ", + "upload": "ใ‚ขใƒƒใƒ—ใƒญใƒผใƒ‰", + "username": "ใƒฆใƒผใ‚ถใƒผๅ", + "userspace-reboot": "ใƒฆใƒผใ‚ถใƒผใ‚นใƒšใƒผใ‚นใ‚’ๅ†่ตทๅ‹•", + "version": "ใƒใƒผใ‚ธใƒงใƒณ", + "vibrant": "้ฎฎใ‚„ใ‹", + "visualizer": "ใƒ“ใ‚ธใƒฅใ‚ขใƒฉใ‚คใ‚ถใƒผ", + "volume": "ใƒœใƒชใƒฅใƒผใƒ ", + "volumes": "ใƒœใƒชใƒฅใƒผใƒ ", + "wallpaper": "ๅฃ็ด™", + "weather": "ๅคฉๆฐ—", + "weather-loading": "ๅคฉๆฐ—ใ‚’่ชญใฟ่พผใฟไธญ...", + "weather-no-location": "ๅคฉๆฐ—ใ‚’่กจ็คบใ™ใ‚‹ใซใฏใ€่จญๅฎšใงๅ ดๆ‰€ใ‚’่จญๅฎšใ—ใฆใใ ใ•ใ„", + "week": "้€ฑ", + "widgets": "ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆ", + "width": "ๅน…", + "wifi": "Wi-Fi", + "windows": "ใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆ", + "yes": "ใฏใ„" + }, + "control-center": { + "power-profile": { + "tooltip-action": "้›ปๆบใƒ—ใƒญใƒ•ใ‚กใ‚คใƒซ", + "tooltip-disabled": "้›ปๆบใƒ—ใƒญใƒ•ใ‚กใ‚คใƒซใ‚’ไฝฟ็”จใ™ใ‚‹ใซใฏ power-profiles-daemon ใ‚’ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใฆใใ ใ•ใ„" + }, + "wifi": { + "label-disconnected": "Wi-Fi ๆœชๆŽฅ็ถš", + "label-ethernet": "ใ‚คใƒผใ‚ตใƒใƒƒใƒˆ" + } + }, + "display-modes": { + "always-hide": "ๅธธใซ้š ใ‚Œใ‚‹", + "always-show": "ๅธธใซ่กจ็คบใ™ใ‚‹", + "force-open": "ใ“ใ˜้–‹ใ‘ใ‚‹", + "on-hover": "ใƒžใ‚ฆใ‚นใ‚ชใƒผใƒใƒผๆ™‚" + }, + "hide-modes": { + "auto-hide": "่‡ชๅ‹•้ž่กจ็คบ", + "hidden": "็ฉบใฎๅ ดๅˆใซ้ž่กจ็คบ", + "idle": "ใ‚ขใ‚คใƒ‰ใƒซๆ™‚ใซ้ž่กจ็คบ", + "non-exclusive": "้ž็‹ฌๅ ", + "transparent": "็ฉบใฎๆ™‚ใฏ้€ๆ˜Ž", + "visible": "ๅธธใซ่กจ็คบ" + }, + "launcher": { + "categories": { + "all": "ใ™ในใฆ", + "audiovideo": "้Ÿณๅฃฐใƒปใƒ“ใƒ‡ใ‚ช", + "chat": "ใƒใƒฃใƒƒใƒˆ", + "development": "้–‹็™บ", + "education": "ๆ•™่‚ฒ", + "emoji-activity": "ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ“ใƒ†ใ‚ฃ", + "emoji-animals": "ๅ‹•็‰ฉ", + "emoji-flags": "ๆ——", + "emoji-food": "้ฃŸใน็‰ฉใƒป้ฃฒใฟ็‰ฉ", + "emoji-nature": "่‡ช็„ถ", + "emoji-objects": "็‰ฉ", + "emoji-people": "ไบบใจไฝ“", + "emoji-recent": "ๆœ€่ฟ‘ไฝฟ็”จ", + "emoji-symbols": "่จ˜ๅท", + "emoji-travel": "ๆ—…่กŒใจๅ ดๆ‰€", + "game": "ใ‚ฒใƒผใƒ ", + "graphics": "ใ‚ฐใƒฉใƒ•ใ‚ฃใƒƒใ‚ฏใ‚น", + "misc": "ใใฎไป–", + "office": "ใ‚ชใƒ•ใ‚ฃใ‚น", + "pinned": "ใƒ”ใƒณ็•™ใ‚", + "system": "ใ‚ทใ‚นใƒ†ใƒ ", + "webbrowser": "ใ‚ฆใ‚งใƒ–ใƒ–ใƒฉใ‚ฆใ‚ถ" + }, + "providers": { + "applications": "ใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณ", + "calculator": "้›ปๅ“", + "calculator-deprecated": ">calc ใ‚ณใƒžใƒณใƒ‰ใฏ้žๆŽจๅฅจใจใชใ‚Šใ€ใพใ‚‚ใชใๅ‰Š้™คใ•ใ‚Œใพใ™ใ€‚ไปฃใ‚ใ‚Šใซใ€ๆคœ็ดขใงๆ•ฐๅผใ‚’็›ดๆŽฅๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚", + "calculator-description": "้›ปๅ“ โ€” ๆ•ฐๅผใ‚’่จˆ็ฎ—ใ—ใพใ™", + "calculator-enter-expression": "ๆ•ฐๅผใ‚’ๅ…ฅๅŠ›", + "calculator-press-enter-to-copy": "็ตๆžœใ‚’ใ‚ณใƒ”ใƒผใ™ใ‚‹ใซใฏEnterใ‚ญใƒผใ‚’ๆŠผใ—ใฆใใ ใ•ใ„", + "clipboard": "ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ๅฑฅๆญด", + "clipboard-clear-description": "ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ๅฑฅๆญดใ‚’ใ™ในใฆๆถˆๅŽป", + "clipboard-clear-description-full": "ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ๅฑฅๆญดใ‹ใ‚‰ใ™ในใฆใฎ้ …็›ฎใ‚’ๅ‰Š้™คใ—ใพใ™", + "clipboard-clear-history": "ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ๅฑฅๆญดใ‚’ๆถˆๅŽป", + "clipboard-delete": "ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ใฎ้ …็›ฎใ‚’ๅ‰Š้™ค", + "clipboard-history-disabled": "ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ๅฑฅๆญดใฏ็„กๅŠนใงใ™", + "clipboard-history-disabled-description": "่จญๅฎšใงๅฑฅๆญดใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹ใ‹ใ€cliphist ใ‚’ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใฆใใ ใ•ใ„", + "clipboard-loading": "ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ๅฑฅๆญดใ‚’่ชญใฟ่พผใฟไธญ...", + "clipboard-search-description": "ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ๅฑฅๆญดใ‚’ๆคœ็ดข", + "command-description": "ใ‚ทใ‚งใƒซใ‚ณใƒžใƒณใƒ‰ใ‚’ๅฎŸ่กŒใ™ใ‚‹", + "emoji": "็ตตๆ–‡ๅญ—้ธๆŠž", + "emoji-loading": "็ตตๆ–‡ๅญ—ใ‚’่ชญใฟ่พผใฟไธญ...", + "emoji-loading-description": "ใŠๅพ…ใกใใ ใ•ใ„", + "emoji-no-recent": "ใพใ ๆœ€่ฟ‘ไฝฟใฃใŸ็ตตๆ–‡ๅญ—ใฏใ‚ใ‚Šใพใ›ใ‚“", + "emoji-search-description": "็ตตๆ–‡ๅญ—ใ‚’ๆคœ็ดขใ—ใฆใ‚ณใƒ”ใƒผ", + "settings-search-description": "่จญๅฎšใ‚’ๆคœ็ดขใ—ใฆ็งปๅ‹•", + "windows-search-description": "้–‹ใ„ใฆใ„ใ‚‹ใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใ‚’ๆคœ็ดขใ—ใฆใƒ•ใ‚ฉใƒผใ‚ซใ‚นใ™ใ‚‹" + } + }, + "lock-screen": { + "authenticating": "่ช่จผไธญ...", + "password": "ใƒ‘ใ‚นใƒฏใƒผใƒ‰ใ‚’ๅ…ฅๅŠ›...", + "restart": "ๅ†่ตทๅ‹•", + "shut-down": "ใ‚ทใƒฃใƒƒใƒˆใƒ€ใ‚ฆใƒณ" + }, + "notifications": { + "panel": { + "click-to-collapse": "ใ‚ฏใƒชใƒƒใ‚ฏใงๆŠ˜ใ‚ŠใŸใŸใ‚€", + "click-to-expand": "ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆๅฑ•้–‹", + "description": "้€š็ŸฅใŒๅฑŠใใจใ“ใ“ใซ่กจ็คบใ•ใ‚Œใพใ™ใ€‚", + "no-notifications": "้€š็Ÿฅใชใ—" + }, + "range": { + "earlier": "ใใ‚Œไปฅๅ‰", + "today": "ไปŠๆ—ฅ", + "yesterday": "ๆ˜จๆ—ฅ" + }, + "time": { + "diff-d": "1ๆ—ฅๅ‰", + "diff-dd": "{diff}ๆ—ฅๅ‰", + "diff-h": "1ๆ™‚้–“ๅ‰", + "diff-hh": "{diff}ๆ™‚้–“ๅ‰", + "diff-m": "1ๅˆ†ๅ‰", + "diff-mm": "{diff}ๅˆ†ๅ‰", + "now": "ไปŠ" + } + }, + "options": { + "bar": { + "density-comfortable": "ใ‚†ใฃใŸใ‚Š", + "density-compact": "ใ‚ณใƒณใƒ‘ใ‚ฏใƒˆ", + "density-default": "ๆจ™ๆบ–", + "density-mini": "ใƒŸใƒ‹", + "density-spacious": "ๅบƒใ€…ใจใ—ใŸ", + "display-mode-always-visible": "ๅธธใซ่กจ็คบ", + "type-floating": "ใƒ•ใƒญใƒผใƒ†ใ‚ฃใƒณใ‚ฐ", + "type-framed": "ใƒ•ใƒฌใƒผใƒ ", + "type-simple": "ใ‚ทใƒณใƒ—ใƒซ" + }, + "control-center": { + "quick-settings-style-classic": "ใ‚ฏใƒฉใ‚ทใƒƒใ‚ฏ", + "quick-settings-style-modern": "ใƒขใƒ€ใƒณ" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "ๅฟซ้ฉ", + "compact": "ใ‚ณใƒณใƒ‘ใ‚ฏใƒˆ", + "default": "ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆ" + }, + "launcher-view-mode": { + "grid": "ใ‚ฐใƒชใƒƒใƒ‰", + "list": "ใƒชใ‚นใƒˆ" + }, + "notification-density": { + "compact": "ใ‚ณใƒณใƒ‘ใ‚ฏใƒˆ", + "default": "ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆ" + }, + "scrolling-modes": { + "always": "ๅธธใซใ‚นใ‚ฏใƒญใƒผใƒซ", + "hover": "ใƒ›ใƒใƒผๆ™‚ใซใ‚นใ‚ฏใƒญใƒผใƒซ", + "never": "ใ‚นใ‚ฏใƒญใƒผใƒซใ—ใชใ„" + }, + "session-menu-grid-layout": { + "grid": "ใ‚ฐใƒชใƒƒใƒ‰", + "single-row": "ๅ˜ไธ€่กŒ" + }, + "settings-panel-mode": { + "attached": "ใƒใƒผใซๅธ็€", + "centered": "ไธญๅคฎใซ้…็ฝฎ", + "window": "็‹ฌ็ซ‹ใ—ใŸใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆ" + }, + "visualizer-types": { + "linear": "ใƒชใƒ‹ใ‚ข", + "mirrored": "ใƒŸใƒฉใƒผ", + "wave": "ใ‚ฆใ‚งใƒผใƒ–" + }, + "visualizer-visibility": { + "with-background": "่ƒŒๆ™ฏใŒใ‚ใ‚‹ๅ ดๅˆใฎใฟ" + }, + "workspace-labels": { + "index": "็•ชๅท", + "index-and-name": "็•ชๅท + ๅๅ‰", + "name": "ๅๅ‰" + } + }, + "panels": { + "about": { + "become-supporter": "ใ‚ตใƒใƒผใ‚ฟใƒผใซใชใ‚‹", + "changelog": "ๅค‰ๆ›ดๅฑฅๆญดใ‚’่ฆ‹ใ‚‹", + "changelog-on-startup": "ใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆๆ™‚ใซๅค‰ๆ›ดๅฑฅๆญดใ‚’่กจ็คบ", + "changelog-on-startup-desc": "NoctaliaใŒๆ›ดๆ–ฐใ•ใ‚ŒใŸใจใใซใ€่‡ชๅ‹•็š„ใซๅค‰ๆ›ดๅฑฅๆญดใ‚’่กจ็คบใ™ใ‚‹ใ€‚", + "contributors-description": "{count}ไบบใฎ็ด ๆ™ดใ‚‰ใ—ใ„ใ‚ณใƒณใƒˆใƒชใƒ“ใƒฅใƒผใ‚ฟใƒผใซๆ„Ÿ่ฌ๏ผ", + "contributors-description-plural": "{count}ไบบใฎ็ด ๆ™ดใ‚‰ใ—ใ„ใ‚ณใƒณใƒˆใƒชใƒ“ใƒฅใƒผใ‚ฟใƒผใซๆ„Ÿ่ฌ๏ผ", + "copy-info": "ๆƒ…ๅ ฑใ‚’ใ‚ณใƒ”ใƒผ", + "debug-disabled": "ใƒ‡ใƒใƒƒใ‚ฐใƒขใƒผใƒ‰ใŒ็„กๅŠนใซใชใ‚Šใพใ—ใŸ", + "debug-enabled": "ใƒ‡ใƒใƒƒใ‚ฐใƒขใƒผใƒ‰ใŒๆœ‰ๅŠนใงใ™", + "info-copied": "ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ใซใ‚ณใƒ”ใƒผใ—ใพใ—ใŸ", + "noctalia-available": "ๅˆฉ็”จๅฏ่ƒฝ:", + "noctalia-desc": "Wayland ใฎใŸใ‚ใซไธๅฏงใซไฝœใ‚Š่พผใพใ‚ŒใŸใ€Quickshell ่ฃฝใฎๆด—็ทดใ•ใ‚ŒใŸใƒŸใƒ‹ใƒžใƒซใชใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใ‚ทใ‚งใƒซใ€‚", + "noctalia-git-commit": "Git ใ‚ณใƒŸใƒƒใƒˆ๏ผš", + "noctalia-installed-version": "็พๅœจใฎใƒใƒผใ‚ธใƒงใƒณ๏ผš", + "noctalia-latest-version": "ๆœ€ๆ–ฐใฎใƒใƒผใ‚ธใƒงใƒณ๏ผš", + "noctalia-qs-version": "Noctalia QS ใƒใƒผใ‚ธใƒงใƒณ:", + "noctalia-title": "Noctalia shell", + "privacy-policy": "ใƒ—ใƒฉใ‚คใƒใ‚ทใƒผใƒใƒชใ‚ทใƒผ", + "support": "ๆ”ฏๆดใ™ใ‚‹", + "supporter-badge": "ใ‚ตใƒใƒผใ‚ฟใƒผ", + "supporters-desc": "็ด ๆ™ดใ‚‰ใ—ใ„ใ‚ตใƒใƒผใ‚ฟใƒผใซๅฟƒใ‹ใ‚‰ใฎๆ„Ÿ่ฌใ‚’๏ผ", + "supporters-desc-plural": "{count}ๅใฎ็ด ๆ™ดใ‚‰ใ—ใ„ใ‚ตใƒใƒผใ‚ฟใƒผใฎ็š†ๆง˜ใซๅฟƒใ‚ˆใ‚Šๆ„Ÿ่ฌใ„ใŸใ—ใพใ™๏ผ", + "supporters-loading": "ใ‚ตใƒใƒผใ‚ฟใƒผใ‚’่ชญใฟ่พผใฟไธญ...", + "system-board": "ใƒžใ‚ถใƒผใƒœใƒผใƒ‰:", + "system-cpu": "CPU๏ผš", + "system-disk": "ใƒ‡ใ‚ฃใ‚นใ‚ฏ๏ผš", + "system-gpu": "GPU๏ผš", + "system-host": "ใƒ›ใ‚นใƒˆ๏ผš", + "system-install-hint": "fastfetch ใ‚’ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใฆใ‚ทใ‚นใƒ†ใƒ ๆƒ…ๅ ฑใ‚’่กจ็คบใ™ใ‚‹", + "system-kernel": "ใ‚ซใƒผใƒใƒซ๏ผš", + "system-loading": "ใ‚ทใ‚นใƒ†ใƒ ๆƒ…ๅ ฑใ‚’่ชญใฟ่พผใฟไธญ...", + "system-memory": "ใƒกใƒขใƒช๏ผš", + "system-monitor": "ใƒขใƒ‹ใ‚ฟใƒผ๏ผš", + "system-not-installed": "fastfetch ใŒใ‚คใƒณใ‚นใƒˆใƒผใƒซใ•ใ‚Œใฆใ„ใพใ›ใ‚“", + "system-os": "OS๏ผš", + "system-packages": "ใƒ‘ใƒƒใ‚ฑใƒผใ‚ธ๏ผš", + "system-product": "่ฃฝๅ“๏ผš", + "system-title": "ใ‚ทใ‚นใƒ†ใƒ ๆƒ…ๅ ฑ", + "system-uptime": "็จผๅƒๆ™‚้–“๏ผš", + "system-wm": "WM๏ผš", + "telemetry-data-copied": "ใƒ†ใƒฌใƒกใƒˆใƒชใƒผใƒ‡ใƒผใ‚ฟใ‚’ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ใซใ‚ณใƒ”ใƒผใ—ใพใ—ใŸใ€‚", + "telemetry-desc": "ๅŒฟๅใ‚ทใ‚นใƒ†ใƒ ๆƒ…ๅ ฑ๏ผˆ็”ป้ข่งฃๅƒๅบฆใ€ใ‚ณใƒณใƒใ‚ธใ‚ฟใƒผใ€ใƒ‡ใ‚ฃใ‚นใƒˆใƒชใƒ“ใƒฅใƒผใ‚ทใƒงใƒณ๏ผ‰ใ‚’ๅ…ฑๆœ‰ใ—ใฆใ€Noctaliaใฎๆ”นๅ–„ใซใ”ๅ”ๅŠ›ใใ ใ•ใ„ใ€‚่ตทๅ‹•ๆ™‚ใซไธ€ๅบฆ้€ไฟกใ•ใ‚Œใ€่ฟฝ่ทกใฏ่กŒใ‚ใ‚Œใšใ€ใƒ‡ใƒผใ‚ฟใฏ30ๆ—ฅๅพŒใซ่‡ชๅ‹•็š„ใซๅ‰Š้™คใ•ใ‚Œใพใ™ใ€‚", + "telemetry-enabled": "ๅŒฟๅใงใ‚ทใ‚นใƒ†ใƒ ๆƒ…ๅ ฑใ‚’้€ไฟกใ™ใ‚‹", + "telemetry-show-data": "ใƒ‡ใƒผใ‚ฟใฎ่กจ็คบ", + "telemetry-title": "ใƒ—ใƒฉใ‚คใƒใ‚ทใƒผ", + "title": "ใƒใƒผใ‚ธใƒงใƒณๆƒ…ๅ ฑ", + "up-to-date": "ๆœ€ๆ–ฐใฎ็Šถๆ…‹ใงใ™๏ผ", + "update-available": "ใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆใŒใ‚ใ‚Šใพใ™", + "view-commit": "GitHubใงใ‚ณใƒŸใƒƒใƒˆใ‚’่ฆ‹ใ‚‹" + }, + "audio": { + "devices-desc": "ๅˆฉ็”จๅฏ่ƒฝใชใ‚ชใƒผใƒ‡ใ‚ฃใ‚ชๅ…ฅๅ‡บๅŠ›ใƒ‡ใƒใ‚คใ‚นใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "devices-input-device-description": "ไฝฟ็”จใ™ใ‚‹ๅ…ฅๅŠ›ใƒ‡ใƒใ‚คใ‚นใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "devices-input-device-label": "ๅ…ฅๅŠ›ใƒ‡ใƒใ‚คใ‚น", + "devices-output-device-description": "ไฝฟ็”จใ™ใ‚‹ๅ‡บๅŠ›ใƒ‡ใƒใ‚คใ‚นใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "devices-output-device-label": "ๅ‡บๅŠ›ใƒ‡ใƒใ‚คใ‚น", + "devices-title": "ใ‚ชใƒผใƒ‡ใ‚ฃใ‚ชใƒ‡ใƒใ‚คใ‚น", + "external-mixer-description": "ๅค–้ƒจใ‚ชใƒผใƒ‡ใ‚ฃใ‚ชใƒŸใ‚ญใ‚ตใƒผๆฉŸ่ƒฝใ‚’ไฝฟ็”จใ™ใ‚‹้š›ใซ่ตทๅ‹•ใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€ใพใŸใฏใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใƒ‘ใ‚นใ‚’ๅ…ฅๅŠ›ใ—ใพใ™ใ€‚", + "external-mixer-label": "ๅค–้ƒจใƒŸใ‚ญใ‚ตใƒผใฎใ‚ณใƒžใƒณใƒ‰", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "ๅ„ชๅ…ˆใ™ใ‚‹ใƒกใƒ‡ใ‚ฃใ‚ขใ‚ขใƒ—ใƒชใ‚„ใ€้™คๅค–่จญๅฎšใ‚’่กŒใ„ใพใ™ใ€‚", + "media-excluded-player-description": "ใ‚ทใ‚นใƒ†ใƒ ใซ็„ก่ฆ–ใ•ใ›ใŸใ„ใƒ—ใƒฌใƒผใƒคใƒผใฎใ‚ญใƒผใƒฏใƒผใƒ‰ใ‚’่ฟฝๅŠ ใ—ใพใ™ใ€‚1่กŒใซใคใ1ใคใฎใ‚ญใƒผใƒฏใƒผใƒ‰ใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚", + "media-excluded-player-label": "้™คๅค–ใ™ใ‚‹ใƒ—ใƒฌใƒผใƒคใƒผ", + "media-excluded-player-placeholder": "ๆ–‡ๅญ—ๅˆ—ใ‚’ๅ…ฅๅŠ›ใ—ใฆ + ใ‚’ๆŠผใ™", + "media-frame-rate-description": "ๅ€คใ‚’้ซ˜ใใ™ใ‚‹ใจๆป‘ใ‚‰ใ‹ใซใชใ‚Šใพใ™ใŒใ€ใƒชใ‚ฝใƒผใ‚นๆถˆ่ฒปใŒๅข—ๅŠ ใ—ใพใ™ใ€‚", + "media-frame-rate-label": "ใƒ•ใƒฌใƒผใƒ ใƒฌใƒผใƒˆ", + "media-primary-player-description": "ใƒกใ‚คใƒณใฎใƒ—ใƒฌใƒผใƒคใƒผใ‚’่ญ˜ๅˆฅใ™ใ‚‹ใŸใ‚ใฎใ‚ญใƒผใƒฏใƒผใƒ‰ใ‚’ๅ…ฅๅŠ›ใ—ใพใ™ใ€‚", + "media-primary-player-label": "ๅ„ชๅ…ˆใƒ—ใƒฌใƒผใƒคใƒผ", + "media-primary-player-placeholder": "ไพ‹๏ผšspotify, vlc, mpv", + "media-scrolling-speed-description": "ใ‚ฟใ‚คใƒˆใƒซใŒ็ซฏใ‹ใ‚‰็ซฏใพใงใ‚นใ‚ฏใƒญใƒผใƒซใ™ใ‚‹ใฎใซใ‹ใ‹ใ‚‹ๆ™‚้–“๏ผˆ็ง’๏ผ‰ใ€‚", + "media-scrolling-speed-label": "ใ‚นใ‚ฏใƒญใƒผใƒซ้€Ÿๅบฆ", + "media-scrolling-title-description": "้•ทใ„ใƒกใƒ‡ใ‚ฃใ‚ขใ‚ฟใ‚คใƒˆใƒซใ‚’้€ฃ็ถšใ‚นใ‚ฏใƒญใƒผใƒซใ•ใ›ใพใ™ใ€‚", + "media-scrolling-title-label": "ใ‚ฟใ‚คใƒˆใƒซใฎใ‚นใ‚ฏใƒญใƒผใƒซ", + "media-title": "ใƒกใƒ‡ใ‚ฃใ‚ขใƒ—ใƒฌใƒผใƒคใƒผ", + "on-middle-clicked-description": "ใƒœใ‚ฟใƒณใ‚’ไธญใ‚ฏใƒชใƒƒใ‚ฏใ—ใŸใจใใซๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚", + "panel-applications-empty": "็พๅœจ้Ÿณๅฃฐใ‚’ๅ†็”Ÿไธญใฎใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใฏใ‚ใ‚Šใพใ›ใ‚“", + "spectrum-mirrored-description": "ใ‚นใƒšใ‚ฏใƒˆใƒซใ‚’ใƒŸใƒฉใƒผใƒชใƒณใ‚ฐใ—ใ€ไฝŽๅ‘จๆณขใ‚’ไธญๅคฎใซ้…็ฝฎใ—ใพใ™ใ€‚ใ‚ชใƒ•ใฎๅ ดๅˆใ€ไฝŽๅ‘จๆณขใฏๅทฆใ€้ซ˜ๅ‘จๆณขใฏๅณใซใชใ‚Šใพใ™ใ€‚", + "spectrum-mirrored-label": "ใƒŸใƒฉใƒผใ‚นใƒšใ‚ฏใƒˆใƒซ", + "title": "ใ‚ตใ‚ฆใƒณใƒ‰", + "visualizer-type-description": "ใƒกใƒ‡ใ‚ฃใ‚ขๅ†็”Ÿใฎๅฏ่ฆ–ๅŒ–ใ‚ฟใ‚คใƒ—ใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "visualizer-type-label": "่ฆ–่ฆšๅŒ–ใฎ็จฎ้กž", + "volumes-desc": "้Ÿณ้‡ใ‚ณใƒณใƒˆใƒญใƒผใƒซใจใƒฌใƒ™ใƒซใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "volumes-feedback-sound-file-description": "้Ÿณ้‡ๅค‰ๆ›ดๆ™‚ใซๅ†็”Ÿใ•ใ‚Œใ‚‹ใ‚ตใ‚ฆใƒณใƒ‰ใƒ•ใ‚กใ‚คใƒซใฎใƒ‘ใ‚นใ€‚", + "volumes-feedback-sound-file-label": "้Ÿณ้‡ใƒ•ใ‚ฃใƒผใƒ‰ใƒใƒƒใ‚ฏใฎใ‚ตใ‚ฆใƒณใƒ‰ใƒ•ใ‚กใ‚คใƒซ", + "volumes-feedback-sound-file-select-title": "้Ÿณ้‡ใƒ•ใ‚ฃใƒผใƒ‰ใƒใƒƒใ‚ฏใฎใ‚ตใ‚ฆใƒณใƒ‰ใƒ•ใ‚กใ‚คใƒซใ‚’้ธๆŠž", + "volumes-input-volume-description": "ใƒžใ‚คใ‚ฏใฎ้Ÿณ้‡ใƒฌใƒ™ใƒซใ€‚", + "volumes-mute-input-description": "ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฎ้Ÿณๅฃฐๅ…ฅๅŠ›๏ผˆใƒžใ‚คใ‚ฏ๏ผ‰ใ‚’ใƒŸใƒฅใƒผใƒˆใ—ใพใ™ใ€‚", + "volumes-mute-input-label": "ๅ…ฅๅŠ›๏ผˆใƒžใ‚คใ‚ฏ๏ผ‰ใ‚’ใƒŸใƒฅใƒผใƒˆ", + "volumes-mute-output-description": "ใ‚ทใ‚นใƒ†ใƒ ใฎใƒกใ‚คใƒณ้Ÿณๅฃฐๅ‡บๅŠ›ใ‚’ใƒŸใƒฅใƒผใƒˆใ—ใพใ™ใ€‚", + "volumes-mute-output-label": "ๅ‡บๅŠ›ใ‚’ใƒŸใƒฅใƒผใƒˆ", + "volumes-output-volume-description": "ใ‚ทใ‚นใƒ†ใƒ ๅ…จไฝ“ใฎ้Ÿณ้‡ใƒฌใƒ™ใƒซใ€‚", + "volumes-step-size-description": "้Ÿณ้‡ใฎๅค‰ๅŒ–้‡๏ผˆใ‚นใ‚ฏใƒญใƒผใƒซใƒ›ใ‚คใƒผใƒซใ‚„ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใ‚ญใƒผ๏ผ‰ใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "volumes-step-size-label": "ใƒœใƒชใƒฅใƒผใƒ ่ชฟๆ•ดใ‚นใƒ†ใƒƒใƒ—", + "volumes-volume-feedback-description": "้Ÿณ้‡ใ‚’่ชฟๆ•ดใ™ใ‚‹ใจใใซใƒ•ใ‚ฃใƒผใƒ‰ใƒใƒƒใ‚ฏ้Ÿณใ‚’ๅ†็”Ÿใ™ใ‚‹ใ€‚", + "volumes-volume-feedback-label": "้Ÿณ้‡ใƒ•ใ‚ฃใƒผใƒ‰ใƒใƒƒใ‚ฏใ‚ตใ‚ฆใƒณใƒ‰ใ‚’ๅ†็”Ÿ", + "volumes-volume-overdrive-description": "้Ÿณ้‡ใ‚’100%ไปฅไธŠใซไธŠใ’ใ‚‹ใ“ใจใ‚’่จฑๅฏใ—ใพใ™ใ€‚ใ™ในใฆใฎใƒใƒผใƒ‰ใ‚ฆใ‚งใ‚ขใงใ‚ตใƒใƒผใƒˆใ•ใ‚Œใฆใ„ใ‚‹ใจใฏ้™ใ‚Šใพใ›ใ‚“ใ€‚", + "volumes-volume-overdrive-label": "100%ไปฅไธŠใฎ้Ÿณ้‡ใ‚’่จฑๅฏ" + }, + "bar": { + "appearance-auto-hide-delay-description": "ใƒžใ‚ฆใ‚นใ‚ซใƒผใ‚ฝใƒซใŒBarใ‹ใ‚‰้›ขใ‚ŒใŸๅพŒใ€BarใŒ้ž่กจ็คบใซใชใ‚‹ใพใงใฎๆ™‚้–“", + "appearance-auto-hide-delay-label": "้ž่กจ็คบ้…ๅปถ", + "appearance-auto-hide-exclusive-note": "ๆณจ๏ผš่‡ชๅ‹•้ž่กจ็คบใŒๆœ‰ๅŠนใชๅ ดๅˆใ€ๆŽ’ไป–ใ‚พใƒผใƒณใฏ่‡ชๅ‹•็š„ใซ็„กๅŠนใซใชใ‚Šใพใ™ใ€‚", + "appearance-auto-show-delay-description": "ใƒžใ‚ฆใ‚นใŒ็ซฏใซๅˆฐ้”ใ—ใŸใจใใซใƒใƒผใŒ่กจ็คบใ•ใ‚Œใ‚‹ใพใงใฎๆ™‚้–“ใ€‚", + "appearance-auto-show-delay-label": "่กจ็คบ้…ๅปถ", + "appearance-background-opacity-description": "ใƒใƒผใฎ่ƒŒๆ™ฏใฎไธ้€ๆ˜Žๅบฆใ‚’็‰นใซ่จญๅฎšใ—ใพใ™ใ€‚", + "appearance-background-opacity-label": "ใƒใƒผใฎ่ƒŒๆ™ฏใฎไธ้€ๆ˜Žๅบฆ", + "appearance-capsule-color-description": "ใƒใƒผใฎใ‚ซใƒ—ใ‚ปใƒซใฎ่‰ฒใ‚’้ธๆŠžใ™ใ‚‹ใ‹ใ€ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฎ่กจ้ข่‰ฒใซใฏ่‰ฒใ‚’ไฝฟ็”จใ—ใชใ„ใ€‚", + "appearance-capsule-color-label": "ใ‚ซใƒ—ใ‚ปใƒซใฎ่‰ฒ", + "appearance-capsule-opacity-description": "ใ‚ซใƒ—ใ‚ปใƒซ่กจ็คบๆ™‚ใฎใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆ่ƒŒๆ™ฏใฎไธ้€ๆ˜Žๅบฆใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "appearance-capsule-opacity-label": "ใ‚ซใƒ—ใ‚ปใƒซใฎไธ้€ๆ˜Žๅบฆ", + "appearance-content-padding-description": "ใƒใƒผใฎ็ซฏใจใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆ้–“ใฎใƒ‘ใƒ‡ใ‚ฃใƒณใ‚ฐใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "appearance-content-padding-label": "ใ‚ณใƒณใƒ†ใƒณใƒ„ใƒ‘ใƒ‡ใ‚ฃใƒณใ‚ฐ", + "appearance-density-description": "ใƒใƒผใฎไฝ™็™ฝใ‚’่ชฟๆ•ดใ—ใ€ใ‚ณใƒณใƒ‘ใ‚ฏใƒˆใพใŸใฏใ‚†ใฃใŸใ‚Šใจใ—ใŸๅค–่ฆณใซใ—ใพใ™ใ€‚", + "appearance-density-label": "ใƒใƒผใฎๅฏ†ๅบฆ", + "appearance-desc": "ใƒใƒผใฎๅค–่ฆณใ‚„ไฝ็ฝฎใ‚’ใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บใ—ใพใ™ใ€‚", + "appearance-display-mode-description": "ใƒใƒผใฎ่กจ็คบใ‚ฟใ‚คใƒŸใƒณใ‚ฐใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "appearance-enable-exclusion-zone-inset-description": "ๆŽ’ไป–ใ‚พใƒผใƒณใ‚’1็‰ฉ็†ใƒ”ใ‚ฏใ‚ปใƒซ็ธฎๅฐใ—ใ€ใƒใƒผใฎ็ซฏใฎไธ‹ใซใดใฃใŸใ‚Šใจ้‡ใชใ‚‹ใ‚ˆใ†ใซใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใŒ่กจ็คบใ•ใ‚Œใ‚‹ใ‚ˆใ†ใซใ—ใพใ™ใ€‚", + "appearance-enable-exclusion-zone-inset-label": "ใ‚คใƒณใ‚ปใƒƒใƒˆ้™คๅค–้ ˜ๅŸŸ", + "appearance-floating-description": "ใƒใƒผใ‚’ๆตฎใ‹ใ›ใฆใ€ใ‚ซใƒ—ใ‚ปใƒซๅž‹ใง่กจ็คบใ—ใพใ™ใ€‚", + "appearance-floating-label": "ใƒ•ใƒญใƒผใƒ†ใ‚ฃใƒณใ‚ฐใƒใƒผ", + "appearance-font-scale-description": "ใƒใƒผใซ่กจ็คบใ•ใ‚Œใ‚‹ใƒ†ใ‚ญใ‚นใƒˆใฎใƒ•ใ‚ฉใƒณใƒˆใ‚ตใ‚คใ‚บใ‚นใ‚ฑใƒผใƒซใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "appearance-font-scale-label": "ใƒ•ใ‚ฉใƒณใƒˆใ‚นใ‚ฑใƒผใƒซ", + "appearance-frame-radius": "ๅ†…ๅดใฎๅŠๅพ„", + "appearance-frame-settings-description": "ใƒ•ใƒฌใƒผใƒ ใฎๅคชใ•ใจๅ†…ๅดใฎๅŠๅพ„ใ‚’่ชฟๆ•ดใ—ใพใ™", + "appearance-frame-settings-label": "ใƒ•ใƒฌใƒผใƒ ่จญๅฎš", + "appearance-frame-thickness": "ๅคชใ•", + "appearance-hide-on-overview-description": "ใ‚ณใƒณใƒใ‚ธใ‚ฟใฎๆฆ‚่ฆใŒใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใชใจใใฏใ€ใƒใƒผใ‚’้ž่กจ็คบใซใ—ใฆใƒ‘ใƒใƒซใ‚’้–‰ใ˜ใพใ™ใ€‚", + "appearance-hide-on-overview-label": "ๆฆ‚่ฆใฎใƒใƒผใ‚’้ž่กจ็คบใซใ™ใ‚‹", + "appearance-margins-description": "ใƒ•ใƒญใƒผใƒ†ใ‚ฃใƒณใ‚ฐใƒใƒผใฎๅ‘จๅ›ฒใฎไฝ™็™ฝใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "appearance-margins-horizontal": "ๆฐดๅนณใƒžใƒผใ‚ธใƒณ", + "appearance-margins-vertical": "ๅž‚็›ดใƒžใƒผใ‚ธใƒณ", + "appearance-outer-corners-description": "ใƒใƒผใฎ่ง’ใซใ€ๅค–ๅ‘ใใฎไธธใฟ๏ผˆ่ฃ…้ฃพ๏ผ‰ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "appearance-outer-corners-label": "ๅค–ๅดใฎใ‚ณใƒผใƒŠใƒผ", + "appearance-position-description": "็”ป้ขไธŠใฎใƒใƒผใฎ้…็ฝฎๅ ดๆ‰€ใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "appearance-position-label": "ใƒใƒผใฎไฝ็ฝฎ", + "appearance-show-capsule-description": "ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎ่ƒŒๆ™ฏใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "appearance-show-capsule-label": "ใ‚ซใƒ—ใ‚ปใƒซใ‚’่กจ็คบ", + "appearance-show-on-workspace-switch-description": "ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใŒๅค‰ๆ›ดใ•ใ‚ŒใŸใจใใซใ€Barใ‚’่‡ชๅ‹•็š„ใซ็Ÿญๆ™‚้–“่กจ็คบใ—ใพใ™ใ€‚", + "appearance-show-on-workspace-switch-label": "ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นๅˆ‡ใ‚Šๆ›ฟใˆๆ™‚ใซใƒใƒผใ‚’่กจ็คบ", + "appearance-show-outline-description": "ใ™ในใฆใฎใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎๅ‘จๅ›ฒใซๆž ็ทšใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "appearance-show-outline-label": "ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎๆž ็ทšใ‚’่กจ็คบ", + "appearance-type-description": "ใƒใƒผใฎใ‚นใ‚ฟใ‚คใƒซใ‚’้ธๆŠž๏ผšใ‚ทใƒณใƒ—ใƒซใ€ใƒ•ใƒญใƒผใƒ†ใ‚ฃใƒณใ‚ฐใ€ใพใŸใฏใƒ•ใƒฌใƒผใƒ ใ€‚", + "appearance-type-label": "ใƒใƒผใฎ็จฎ้กž", + "appearance-use-separate-opacity-description": "ใƒใƒผใฎ่ƒŒๆ™ฏใซๅˆฅใฎไธ้€ๆ˜Žๅบฆใฎๅ€คใ‚’ไฝฟ็”จใงใใ‚‹ใ‚ˆใ†ใซใ™ใ‚‹ใ€‚", + "appearance-use-separate-opacity-label": "ใƒใƒผใฎไธ้€ๆ˜Žๅบฆใ‚’ๅ€‹ๅˆฅใซ่จญๅฎš", + "appearance-widget-spacing-description": "ใƒใƒผๅ†…ใฎๅ„ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆ้–“ใฎ้–“้š”ใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "appearance-widget-spacing-label": "ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎ้–“้š”", + "behavior-middle-click-command-description": "ไธญๅคฎใ‚ฏใƒชใƒƒใ‚ฏใงๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚", + "behavior-middle-click-command-label": "ไธญๅคฎใ‚ฏใƒชใƒƒใ‚ฏใ‚ณใƒžใƒณใƒ‰", + "behavior-middle-click-command-placeholder": "niri msg action ๆฆ‚่ฆใฎๅˆ‡ใ‚Šๆ›ฟใˆ", + "behavior-middle-click-description": "ใƒใƒผใฎ็ฉบใ„ใฆใ„ใ‚‹้ ˜ๅŸŸใงไธญๅคฎใ‚ฏใƒชใƒƒใ‚ฏใŒไฝ•ใ‚’ใ™ใ‚‹ใ‹ใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "behavior-middle-click-follow-mouse-description": "้ธๆŠžใ—ใŸไธญๅคฎใ‚ฏใƒชใƒƒใ‚ฏใƒ‘ใƒใƒซใ‚’ใ‚ซใƒผใ‚ฝใƒซไฝ็ฝฎใง้–‹ใใ€‚", + "behavior-middle-click-follow-mouse-label": "ไธญๅคฎใ‚ฏใƒชใƒƒใ‚ฏใงใƒžใ‚ฆใ‚นใ‚’่ฟฝๅพ“", + "behavior-middle-click-label": "ใƒใƒผใฎไธญๅคฎใ‚ฏใƒชใƒƒใ‚ฏใ‚ขใ‚ฏใ‚ทใƒงใƒณ", + "behavior-right-click-command-description": "ๅณใ‚ฏใƒชใƒƒใ‚ฏๆ™‚ใซๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚", + "behavior-right-click-command-label": "ๅณใ‚ฏใƒชใƒƒใ‚ฏใ‚ณใƒžใƒณใƒ‰", + "behavior-right-click-command-placeholder": "notify-send \"ๅณใ‚ฏใƒชใƒƒใ‚ฏ\"", + "behavior-right-click-description": "ใƒใƒผใฎ็ฉบใ„ใฆใ„ใ‚‹้ ˜ๅŸŸใงๅณใ‚ฏใƒชใƒƒใ‚ฏใŒไฝ•ใ‚’ใ™ใ‚‹ใ‹ใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "behavior-right-click-follow-mouse-description": "้ธๆŠžใ—ใŸๅณใ‚ฏใƒชใƒƒใ‚ฏใƒ‘ใƒใƒซใ‚’ใ‚ซใƒผใ‚ฝใƒซไฝ็ฝฎใซ้–‹ใใ€‚", + "behavior-right-click-follow-mouse-label": "ๅณใ‚ฏใƒชใƒƒใ‚ฏใงใƒžใ‚ฆใ‚นใซ่ฟฝๅพ“", + "behavior-right-click-label": "ใƒใƒผใฎๅณใ‚ฏใƒชใƒƒใ‚ฏใ‚ขใ‚ฏใ‚ทใƒงใƒณ", + "behavior-wheel-wrap-description": "ๆœ‰ๅŠนใซใ™ใ‚‹ใจใ€ใ‚นใ‚ฏใƒญใƒผใƒซใฏๆœ€ๅพŒใฎ้ …็›ฎใ‹ใ‚‰ๆœ€ๅˆใฎ้ …็›ฎใธ็ถšใใพใ™ใ€‚", + "behavior-wheel-wrap-label": "ใƒซใƒผใƒ—", + "behavior-workspace-scroll-description": "ใƒใƒผใฎ็ฉบใ„ใฆใ„ใ‚‹้ ˜ๅŸŸใงใƒžใ‚ฆใ‚นใƒ›ใ‚คใƒผใƒซใŒไฝ•ใ‚’ใ™ใ‚‹ใ‹้ธๆŠžใ—ใพใ™ใ€‚", + "behavior-workspace-scroll-label": "ใƒใƒผใฎใƒžใ‚ฆใ‚นใƒ›ใ‚คใƒผใƒซใ‚ขใ‚ฏใ‚ทใƒงใƒณ", + "behavior-workspace-scroll-option-content": "ใ‚ณใƒณใƒ†ใƒณใƒ„", + "behavior-workspace-scroll-option-workspace": "ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚น", + "monitor-configure-widgets": "ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’่จญๅฎš", + "monitor-override-settings": "ใ‚ฐใƒญใƒผใƒใƒซ่จญๅฎšใ‚’ไธŠๆ›ธใ", + "monitor-override-settings-description": "ใ“ใฎใƒขใƒ‹ใ‚ฟใƒผใซใ‚ซใ‚นใ‚ฟใƒ ่จญๅฎšใ‚’ไฝฟ็”จใ™ใ‚‹ใ€‚", + "monitor-reset-all": "ใ™ในใฆใƒชใ‚ปใƒƒใƒˆ", + "monitor-widgets-title": "ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆ่จญๅฎš - {monitor}", + "monitors-desc": "ใƒใƒผใ‚’่กจ็คบใ™ใ‚‹ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คใ‚’้ธๆŠžใ—ใพใ™ใ€‚ๆœช้ธๆŠžใฎๅ ดๅˆใฏๅ…จใฆใซ่กจ็คบใ•ใ‚Œใพใ™ใ€‚", + "monitors-desc-new": "ใฉใฎใƒขใƒ‹ใ‚ฟใƒผใซใƒใƒผใ‚’่กจ็คบใ™ใ‚‹ใ‹ใ‚’่จญๅฎšใ—ใ€ใƒขใƒ‹ใ‚ฟใƒผใ”ใจใซ่จญๅฎšใ‚’ใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บใ—ใพใ™ใ€‚", + "monitors-title": "่กจ็คบใ™ใ‚‹ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚ค", + "title": "ใƒใƒผ", + "tray-blacklist-description": "ใƒˆใƒฌใ‚คใ‹ใ‚‰้™คๅค–ใ™ใ‚‹ใƒซใƒผใƒซใ‚’่ฟฝๅŠ ใ—ใพใ™ใ€‚ใƒฏใ‚คใƒซใƒ‰ใ‚ซใƒผใƒ‰ (*) ใซๅฏพๅฟœใ—ใฆใ„ใพใ™ใ€‚", + "tray-blacklist-label": "ใƒ–ใƒฉใƒƒใ‚ฏใƒชใ‚นใƒˆ", + "tray-blacklist-placeholder": "ไพ‹๏ผšnm-applet, Fcitx*", + "tray-pin-application": "ใ‚ขใƒ—ใƒชใ‚’ใƒ”ใƒณ็•™ใ‚", + "tray-unpin-application": "ใƒ”ใƒณ็•™ใ‚ใ‚’่งฃ้™ค", + "use-global-widgets": "ใ‚ฐใƒญใƒผใƒใƒซใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’ไฝฟ็”จใ™ใ‚‹", + "widgets-desc": "ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’ใƒ‰ใƒฉใƒƒใ‚ฐใ—ใฆไธฆในๆ›ฟใˆใพใ™ใ€‚ๅณใ‚ฏใƒชใƒƒใ‚ฏใƒกใƒ‹ใƒฅใƒผใ‹ใ‚‰ใ€ใ‚ปใ‚ฏใ‚ทใƒงใƒณ้–“ใฎ็งปๅ‹•ใ‚„ๅ‰Š้™คใŒ่กŒใˆใพใ™ใ€‚", + "widgets-title": "ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎ้…็ฝฎ" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "ๅฃ็ด™ใ‹ใ‚‰้…่‰ฒใ‚’็”Ÿๆˆใ—ใพใ™ใ€‚่‰ฒใ‚’่‡ชๅ‹•็š„ใซๆŠฝๅ‡บใ—ใ€ใพใจใพใ‚Šใฎใ‚ใ‚‹ใƒ†ใƒผใƒžใ‚’ไฝœๆˆใ—ใพใ™ใ€‚", + "color-source-use-wallpaper-colors-label": "ๅฃ็ด™ใฎ่‰ฒใ‚’ไฝฟ็”จ", + "dark-mode-mode-description": "ใƒฉใ‚คใƒˆใƒขใƒผใƒ‰ใจใƒ€ใƒผใ‚ฏใƒขใƒผใƒ‰ใฎ่‡ชๅ‹•ๅˆ‡ใ‚Šๆ›ฟใˆใ‚’ๆœ‰ๅŠนใซใ—ใพใ™ใ€‚", + "dark-mode-mode-label": "ใƒ€ใƒผใ‚ฏใƒขใƒผใƒ‰ใฎใ‚นใ‚ฑใ‚ธใƒฅใƒผใƒซ", + "dark-mode-mode-manual": "ๆ‰‹ๅ‹•", + "dark-mode-mode-off": "ใ‚ชใƒ•", + "dark-mode-switch-description": "ๅคœ้–“ใงใ‚‚่ฆ‹ใ‚„ใ™ใ„ใ‚ˆใ†ใซใ€ๆš—ใ„ใƒ†ใƒผใƒžใซๅˆ‡ใ‚Šๆ›ฟใˆใพใ™ใ€‚", + "delete-error-description": "{scheme} ใฎๅ‰Š้™คใซๅคฑๆ•—ใ—ใพใ—ใŸ", + "delete-error-title": "ๅ‰Š้™คๅคฑๆ•—", + "delete-success-description": "{scheme} ใ‚’ๅ‰Š้™คใ—ใพใ—ใŸ", + "delete-success-title": "้…่‰ฒใ‚’ๅ‰Š้™คใ—ใพใ—ใŸ", + "download-button": "ใ•ใ‚‰ใซใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰", + "download-downloading": "ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ไธญ...", + "download-empty": "ๅˆฉ็”จๅฏ่ƒฝใช้…่‰ฒใŒใ‚ใ‚Šใพใ›ใ‚“", + "download-error-api-error": "API ใ‚จใƒฉใƒผ๏ผš{status}", + "download-error-description": "{scheme} ใฎใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใซๅคฑๆ•—ใ—ใพใ—ใŸ", + "download-error-download-failed": "ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใซๅคฑๆ•—ใ—ใพใ—ใŸ๏ผˆ็ต‚ไบ†ใ‚ณใƒผใƒ‰๏ผš{code}๏ผ‰", + "download-error-invalid-response": "็„กๅŠนใชAPIใƒฌใ‚นใƒใƒณใ‚นๅฝขๅผ", + "download-error-no-files": "ใ‚นใ‚ญใƒผใƒ ใฎใƒ•ใ‚กใ‚คใƒซใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“", + "download-error-parse-failed": "APIใƒฌใ‚นใƒใƒณใ‚นใฎ่งฃๆžใซๅคฑๆ•—ใ—ใพใ—ใŸ๏ผš{error}", + "download-error-rate-limit": "GitHub APIใฎใƒฌใƒผใƒˆๅˆถ้™ใ‚’่ถ…ใˆใพใ—ใŸ", + "download-error-title": "ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ๅคฑๆ•—", + "download-fetching": "ๅˆฉ็”จๅฏ่ƒฝใช้…่‰ฒใ‚’ๅ–ๅพ—ไธญ...", + "download-success-description": "{scheme} ใ‚’ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใ—ใพใ—ใŸ", + "download-success-title": "้…่‰ฒใ‚’ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใ—ใพใ—ใŸ", + "download-title": "้…่‰ฒใฎใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰", + "method-description": { + "content": "ใ‚ฝใƒผใ‚นใ‚ณใƒณใƒ†ใƒณใƒ„ใฎๅฎŸ้š›ใฎ่‰ฒใจๅŽณๅฏ†ใซไธ€่‡ดใ™ใ‚‹ใ€้ซ˜ๅฟ ๅฎŸๅบฆใฎ่‰ฒๆŠฝๅ‡บใ‚’ๅ‚™ใˆใŸMaterial Designใ‚นใ‚ญใƒผใƒ ใ€‚", + "dysfunctional": "Faithfulใฎใ‚ˆใ†ใซใ€2็•ช็›ฎใซๆ”ฏ้…็š„ใช่‰ฒๆ—ใ‚’ใƒ—ใƒฉใ‚คใƒžใƒชใจใ—ใฆ้ธๆŠžใ—ใพใ™ใ€‚", + "faithful": "่ชฟๅ’Œใฎใจใ‚ŒใŸใƒ‘ใƒฌใƒƒใƒˆใ‚’็”Ÿๆˆใ—ใชใŒใ‚‰ใ€ใ‚ฝใƒผใ‚นใ‚ซใƒฉใƒผใซใงใใ‚‹ใ ใ‘่ฟ‘ใ„่‰ฒใ‚’็ถญๆŒใ—ใ‚ˆใ†ใจใ—ใพใ™ใ€‚", + "fruit-salad": "ๅคšๆง˜ใงๅคšๅฝฉใช่‰ฒๅˆใ„ใ‚’ๆŒใคใ€้Šใณๅฟƒใฎใ‚ใ‚‹้ฎฎใ‚„ใ‹ใชใƒ‘ใƒฌใƒƒใƒˆใ‚’็”Ÿใฟๅ‡บใ™Material Designใ‚นใ‚ญใƒผใƒ ใ€‚", + "monochrome": "ๅ˜ไธ€่‰ฒ็›ธใฎใ‚ฐใƒฌใƒผใ‚นใ‚ฑใƒผใƒซใจๆœ€ๅฐ้™ใฎๅฝฉๅบฆใ‚ณใƒณใƒ†ใƒณใƒ„ใ‚’ไฝฟ็”จใ—ใŸ Material Design ใ‚นใ‚ญใƒผใƒ ใ€‚", + "muted": "็ฉใ‚„ใ‹ใงๆŽงใˆใ‚ใชๅค–่ฆณใฎใŸใ‚ใซใ€ใ‚ˆใ‚ŠๆŸ”ใ‚‰ใ‹ใใ€ๅฝฉๅบฆใ‚’่ฝใจใ—ใŸ่‰ฒ่ชฟใ‚’็”Ÿๆˆใ—ใพใ™ใ€‚", + "rainbow": "ๆœ€ๅคงใฎ่‰ฒๅคšๆง˜ๆ€งใ‚’ๅฎŸ็พใ™ใ‚‹ใŸใ‚ใซใ€ๅ…จใ‚นใƒšใ‚ฏใƒˆใƒซใซใ‚ใŸใฃใฆ่‰ฒใ‚’็”Ÿๆˆใ™ใ‚‹ใƒžใƒ†ใƒชใ‚ขใƒซใƒ‡ใ‚ถใ‚คใƒณใ‚นใ‚ญใƒผใƒ ใ€‚", + "tonal-spot": "Material Designใฎใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใ‚ขใƒซใ‚ดใƒชใ‚บใƒ ใ€‚ใ‚ฝใƒผใ‚นใ‚ซใƒฉใƒผใฎ่‰ฒ่ชฟๅค‰ๅŒ–ใ‚’ไฝฟ็”จใ—ใฆใ€ใƒใƒฉใƒณใ‚นใฎๅ–ใ‚ŒใŸใƒ‘ใƒฌใƒƒใƒˆใ‚’ไฝœๆˆใ—ใพใ™ใ€‚", + "vibrant": "่‰ฒใฎๅฝฉๅบฆใ‚’ๆœ€ๅคงๅŒ–ใ—ใ€้ฎฎใ‚„ใ‹ใช่‰ฒ่ชฟใงๅคง่ƒ†ใง็›ฎใ‚’ๅผ•ใใƒ‘ใƒฌใƒƒใƒˆใ‚’ๅฎŸ็พใ—ใพใ™ใ€‚" + }, + "predefined-desc": "ใ‚ใ‚‰ใ‹ใ˜ใ‚็”จๆ„ใ•ใ‚ŒใŸ้…่‰ฒใ‹ใ‚‰้ธๆŠžใ—ใพใ™ใ€‚", + "predefined-generate-templates-label": "ใƒ—ใƒชใ‚ปใƒƒใƒˆ้…่‰ฒใฎใƒ†ใƒณใƒ—ใƒฌใƒผใƒˆใ‚’็”Ÿๆˆใ™ใ‚‹", + "predefined-title": "ใƒ—ใƒชใ‚ปใƒƒใƒˆ้…่‰ฒ", + "sync-gsettings-description": "ใ‚ทใ‚นใƒ†ใƒ ใƒ†ใƒผใƒžใ‚’ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใชใƒฉใ‚คใƒˆใพใŸใฏใƒ€ใƒผใ‚ฏใฎใƒใƒชใ‚ขใƒณใƒˆใซๅˆใ‚ใ›ใพใ™ใ€‚", + "sync-gsettings-label": "ใ‚ทใ‚นใƒ†ใƒ ใƒ†ใƒผใƒžใ‚’ๅŒๆœŸ", + "templates-desc": "ๅค–้ƒจใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใซ้…่‰ฒใ‚’้ฉ็”จใ—ใพใ™ใ€‚", + "templates-filter-description": "็‰นๅฎšใฎใ‚ซใƒ†ใ‚ดใƒชใฎใƒ†ใƒณใƒ—ใƒฌใƒผใƒˆใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "templates-filter-label": "ใ‚ซใƒ†ใ‚ดใƒชใง็ตžใ‚Š่พผใ‚€", + "templates-misc-description": "็‹ฌ่‡ชใฎใƒ†ใƒณใƒ—ใƒฌใƒผใƒˆใ‚’ไฝœๆˆใ€‚", + "templates-misc-label": "่ฉณ็ดฐ่จญๅฎš", + "templates-misc-user-templates-description": "ๆ“ไฝœๆ–นๆณ•ใ‚’็†่งฃใ—ใฆใ„ใ‚‹ๅ ดๅˆใฎใฟๆœ‰ๅŠนใซใ—ใฆใใ ใ•ใ„ใ€‚ใ‚ชใƒณใƒฉใ‚คใƒณใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„ใ€‚", + "templates-misc-user-templates-label": "ใƒฆใƒผใ‚ถใƒผใƒ†ใƒณใƒ—ใƒฌใƒผใƒˆใ‚’ๆœ‰ๅŠนๅŒ–", + "templates-none-detected": "ๆคœๅ‡บใ•ใ‚Œใพใ›ใ‚“ใงใ—ใŸ", + "templates-write-path": "ๆ›ธใ่พผใฟๅ…ˆ๏ผš{filepath}", + "title": "้…่‰ฒ", + "wallpaper-method-description": "ใŠๅฅฝใฟใฎใƒ‘ใƒฌใƒƒใƒˆ็”Ÿๆˆๆ–นๆณ•ใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "wallpaper-method-label": "ใƒ‘ใƒฌใƒƒใƒˆ็”Ÿๆˆๆ–นๆณ•", + "wallpaper-monitor-source-description": "ๅฃ็ด™ใฎ่‰ฒใ‚’ๆŠฝๅ‡บใ™ใ‚‹ใŸใ‚ใซไฝฟ็”จใ™ใ‚‹ใƒขใƒ‹ใ‚ฟใƒผใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "wallpaper-monitor-source-label": "่‰ฒใฎ็”Ÿๆˆๅ…ƒ" + }, + "connections": { + "authentication-required": "่ช่จผใŒๅฟ…่ฆใงใ™", + "bluetooth-auto-connect-description": "BluetoothใŒๆœ‰ๅŠนใชๅ ดๅˆใ€ไฟก้ ผใงใใ‚‹ใƒšใ‚ขใƒชใƒณใ‚ฐๆธˆใฟใƒ‡ใƒใ‚คใ‚นใซ่‡ชๅ‹•็š„ใซๆŽฅ็ถšใ—ใพใ™ใ€‚", + "bluetooth-auto-connect-label": "ใƒšใ‚ขใƒชใƒณใ‚ฐๆธˆใฟใƒ‡ใƒใ‚คใ‚นใ‚’่‡ชๅ‹•ๆŽฅ็ถš", + "bluetooth-devices-unnamed": "ๅๅ‰ใฎใชใ„ใƒ‡ใƒใ‚คใ‚นใฏ่กจ็คบใ•ใ‚Œใพใ›ใ‚“ใ€‚", + "bluetooth-discoverable": "ใ“ใฎใƒ‡ใƒใ‚คใ‚นใฏใ€ใ“ใฎ่จญๅฎšใ‚ฟใƒ–ใŒ้–‹ใ„ใฆใ„ใ‚‹้–“ใ€{hostName} ใจใ—ใฆๆคœๅ‡บๅฏ่ƒฝใงใ™ใ€‚", + "bluetooth-rssi-polling-description": "bluetoothctl ใ‚’ไป‹ใ—ใฆๆŽฅ็ถšใ•ใ‚ŒใŸใƒ‡ใƒใ‚คใ‚นใฎ RSSI ใ‚’ๅฎšๆœŸ็š„ใซใ‚ตใƒณใƒ—ใƒชใƒณใ‚ฐใ—ใพใ™ใ€‚ใ™ในใฆใฎใƒ‡ใƒใ‚คใ‚นใงๅˆฉ็”จใงใใ‚‹ใจใฏ้™ใ‚Šใพใ›ใ‚“ใ€‚ๆœ‰ๅŠนใซใ™ใ‚‹ใจๆœ€ๅฐ้™ใฎใƒชใ‚ฝใƒผใ‚นใ‚’ไฝฟ็”จใ—ใพใ™ใ€‚", + "bluetooth-rssi-polling-interval-description": "ๆŽฅ็ถšใ•ใ‚ŒใŸใƒ‡ใƒใ‚คใ‚นใฎไฟกๅทๅผทๅบฆใ‚’ๆ›ดๆ–ฐใ™ใ‚‹้ ปๅบฆใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "bluetooth-rssi-polling-interval-label": "ใƒใƒผใƒชใƒณใ‚ฐ้–“้š”", + "bluetooth-rssi-polling-label": "Bluetoothไฟกๅทใƒใƒผใƒชใƒณใ‚ฐ", + "disable-discoverability-description": "่ฟ‘ใใฎBluetoothใƒ‡ใƒใ‚คใ‚นใ‹ใ‚‰ใƒ‡ใƒใ‚คใ‚นใ‚’้ž่กจ็คบใซใ™ใ‚‹ใ€‚", + "disable-discoverability-label": "ใƒ‡ใƒใ‚คใ‚นใฎๅฏ่ฆ–ๆ€งใ‚’็„กๅŠนใซใ™ใ‚‹", + "hide-unnamed-devices-description": "Bluetoothใ‚ขใƒ‰ใƒฌใ‚นใจใ—ใฆใฎใฟ่กจ็คบใ•ใ‚Œใ‚‹ใƒ‡ใƒใ‚คใ‚นใ‚’้ž่กจ็คบใซใ™ใ‚‹ใ€‚", + "hide-unnamed-devices-label": "ๅๅ‰ใฎใชใ„ใƒ‡ใƒใ‚คใ‚นใ‚’้ž่กจ็คบใซใ™ใ‚‹", + "pin-instructions": "ใƒ‡ใƒใ‚คใ‚นใซ่กจ็คบใ•ใ‚Œใฆใ„ใ‚‹PINใ‚ณใƒผใƒ‰ใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚", + "title": "ๆŽฅ็ถš", + "wifi-header-text": "ๅˆฉ็”จๅฏ่ƒฝใชใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใ‚’่กจ็คบใ—ใ€ใ‚คใƒณใ‚ฟใƒผใƒใƒƒใƒˆใซๆŽฅ็ถšใ™ใ‚‹ใซใฏWi-Fiใ‚’ไฝฟ็”จใ—ใฆใใ ใ•ใ„ใ€‚" + }, + "control-center": { + "cards-desc": "ใ‚ณใƒณใƒˆใƒญใƒผใƒซใ‚ปใƒณใ‚ฟใƒผใซ่กจ็คบใ™ใ‚‹้ …็›ฎใจใ€ใใฎ้ †ๅบใ‚’ใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บใ—ใพใ™ใ€‚", + "desc": "ใ‚ณใƒณใƒˆใƒญใƒผใƒซใ‚ปใƒณใ‚ฟใƒผใฎไฝ็ฝฎใ‚„ๆŒ™ๅ‹•ใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "open-at-mouse-description": "ใƒใƒผใฎ็ฉบใ„ใฆใ„ใ‚‹้ ˜ๅŸŸใ‚’ๅณใ‚ฏใƒชใƒƒใ‚ฏใ™ใ‚‹ใจใ€ใ‚ซใƒผใ‚ฝใƒซไป˜่ฟ‘ใซใ‚ณใƒณใƒˆใƒญใƒผใƒซใ‚ปใƒณใ‚ฟใƒผใ‚’้–‹ใใพใ™ใ€‚็„กๅŠนใซใชใฃใฆใ„ใ‚‹ๅ ดๅˆใ€ไปฃใ‚ใ‚ŠใซไธŠ่จ˜ใฎ้…็ฝฎ่จญๅฎšใŒไฝฟ็”จใ•ใ‚Œใพใ™ใ€‚", + "open-at-mouse-label": "ๅณใ‚ฏใƒชใƒƒใ‚ฏใงใ‚ซใƒผใ‚ฝใƒซไฝ็ฝฎใซ้–‹ใ", + "position-description": "ใ‚ณใƒณใƒˆใƒญใƒผใƒซใ‚ปใƒณใ‚ฟใƒผใ‚’้–‹ใ„ใŸใจใใฎ่กจ็คบไฝ็ฝฎใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "shortcuts-custom-button-command-description": "ใƒœใ‚ฟใƒณใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใŸ้š›ใซๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚", + "shortcuts-custom-button-enable-on-state-logic-description": "ใƒใ‚งใƒƒใ‚ฏใ‚ณใƒžใƒณใƒ‰ใซๅŸบใฅใ„ใฆใ€ๅˆฅใฎใ‚ขใ‚คใ‚ณใƒณใ‚„ใ€ŒONใ€็Šถๆ…‹ใ‚’ๆœ‰ๅŠนใซใ—ใพใ™ใ€‚", + "shortcuts-custom-button-enable-on-state-logic-label": "ON ็Šถๆ…‹ใฎๅˆคๅฎšใƒญใ‚ธใƒƒใ‚ฏใ‚’ๆœ‰ๅŠนๅŒ–", + "shortcuts-custom-button-icon-description": "ใƒฉใ‚คใƒ–ใƒฉใƒชใ‹ใ‚‰ใ‚ขใ‚คใ‚ณใƒณใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "shortcuts-custom-button-on-clicked-label": "ๅทฆใ‚ฏใƒชใƒƒใ‚ฏๆ™‚ใฎใ‚ณใƒžใƒณใƒ‰", + "shortcuts-custom-button-on-middle-clicked-description": "ใƒœใ‚ฟใƒณใŒไธญๅคฎใ‚ฏใƒชใƒƒใ‚ฏใ•ใ‚ŒใŸใจใใซๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚", + "shortcuts-custom-button-on-state-command-description": "ใƒœใ‚ฟใƒณใ‚’ใ€ŒONใ€็Šถๆ…‹ใซใ™ใ‚‹ใ‹ๅˆคๅฎšใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚็ต‚ไบ†ใ‚ณใƒผใƒ‰ 0 ใงONใ€ใใ‚Œไปฅๅค–ใงOFFใจใชใ‚Šใพใ™ใ€‚", + "shortcuts-custom-button-on-state-command-label": "ON ็Šถๆ…‹ใฎๅˆคๅฎšใ‚ณใƒžใƒณใƒ‰", + "shortcuts-custom-button-on-state-icon-description": "ใ€ŒONใ€็Šถๆ…‹ใฎใจใใซ่กจ็คบใ™ใ‚‹ใ‚ขใ‚คใ‚ณใƒณใ€‚", + "shortcuts-custom-button-on-state-icon-label": "ON ็Šถๆ…‹ใฎใ‚ขใ‚คใ‚ณใƒณ", + "shortcuts-custom-button-state-checks-add": "็Šถๆ…‹ใƒใ‚งใƒƒใ‚ฏใ‚’่ฟฝๅŠ ", + "shortcuts-custom-button-state-checks-command": "ใ“ใฎ็Šถๆ…‹ใƒใ‚งใƒƒใ‚ฏใงๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰", + "shortcuts-custom-button-state-checks-label": "็Šถๆ…‹ใƒใ‚งใƒƒใ‚ฏ", + "shortcuts-custom-button-state-checks-remove": "ๅ‰Š้™ค", + "shortcuts-custom-button-tooltip-description": "ใƒœใ‚ฟใƒณใซใ‚ซใƒผใ‚ฝใƒซใ‚’ๅˆใ‚ใ›ใŸ้š›ใซ่กจ็คบใ™ใ‚‹ใƒ„ใƒผใƒซใƒใƒƒใƒ—ใ€‚", + "shortcuts-custom-button-tooltip-label": "ใƒ„ใƒผใƒซใƒใƒƒใƒ—", + "shortcuts-title": "ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆ", + "system-monitor-disk-path-description": "ใ‚ณใƒณใƒˆใƒญใƒผใƒซใ‚ปใƒณใ‚ฟใƒผใฎใ‚ทใ‚นใƒ†ใƒ ใƒขใƒ‹ใ‚ฟใƒผใ‚ซใƒผใƒ‰ใŒ็›ฃ่ฆ–ใ™ใ‚‹ใƒ‡ใ‚ฃใ‚นใ‚ฏใฎใƒžใ‚ฆใƒณใƒˆใƒใ‚คใƒณใƒˆใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "system-monitor-disk-path-label": "ใ‚ทใ‚นใƒ†ใƒ ใƒขใƒ‹ใ‚ฟใƒผใฎใƒ‡ใ‚ฃใ‚นใ‚ฏใƒ‘ใ‚น", + "title": "ใ‚ณใƒณใƒˆใƒญใƒผใƒซใ‚ปใƒณใ‚ฟใƒผ" + }, + "desktop-widgets": { + "clock-enabled-description": "ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใซๆ™‚่จˆใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "clock-enabled-label": "ๆ™‚่จˆใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’ๆœ‰ๅŠนๅŒ–", + "clock-format-label": "ใƒ•ใ‚ฉใƒผใƒžใƒƒใƒˆ", + "clock-height-description": "ๆ™‚่จˆใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎ้ซ˜ใ•ใ‚’ใƒ”ใ‚ฏใ‚ปใƒซๅ˜ไฝใง่จญๅฎšใ—ใพใ™ใ€‚", + "clock-height-label": "้ซ˜ใ•", + "clock-minimal-mode-description": "ๆ™‚ๅˆปใจๆ—ฅไป˜ใฎใฟใฎใƒŸใƒ‹ใƒžใƒซใชๆ™‚่จˆ่กจ็คบใ‚’ไฝฟ็”จใ—ใพใ™ใ€‚", + "clock-minimal-mode-label": "ใƒŸใƒ‹ใƒžใƒซใƒขใƒผใƒ‰", + "clock-rounded-corners-description": "ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎ่ƒŒๆ™ฏใ‚’่ง’ไธธใซใ—ใฆใใ ใ•ใ„ใ€‚", + "clock-rounded-corners-label": "่ง’ใ‚’ไธธใใ™ใ‚‹", + "clock-show-background-description": "ๆ™‚่จˆใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎ่ƒŒๆ™ฏใ‚ณใƒณใƒ†ใƒŠใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "clock-show-background-label": "่ƒŒๆ™ฏใ‚’่กจ็คบ", + "clock-show-date-description": "ๆ™‚ๅˆปใฎไธ‹ใซ็พๅœจใฎๆ—ฅไป˜ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "clock-show-date-label": "ๆ—ฅไป˜ใ‚’่กจ็คบ", + "clock-show-seconds-description": "ๆ™‚ๅˆปใซ็ง’ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "clock-show-seconds-label": "็ง’ใ‚’่กจ็คบ", + "clock-style-analog": "ใ‚ขใƒŠใƒญใ‚ฐ", + "clock-style-binary": "ใƒใ‚คใƒŠใƒช", + "clock-style-description": "ๆ™‚่จˆใฎ่กจ็คบใ‚นใ‚ฟใ‚คใƒซใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "clock-style-digital": "ใƒ‡ใ‚ธใ‚ฟใƒซ", + "clock-style-label": "ๆ™‚่จˆใฎใ‚นใ‚ฟใ‚คใƒซ", + "clock-style-minimal": "ใƒŸใƒ‹ใƒžใƒซ", + "clock-width-description": "ๆ™‚่จˆใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎๅน…ใ‚’ใƒ”ใ‚ฏใ‚ปใƒซๅ˜ไฝใง่จญๅฎšใ—ใพใ™ใ€‚", + "cpu-intensive-note": "! ใŒไป˜ใ„ใฆใ„ใ‚‹Widgetใฏใ‚ˆใ‚ŠๅคšใใฎCPUใ‚’ไฝฟ็”จใ™ใ‚‹ใŸใ‚ใ€ๆณจๆ„ใ—ใฆๆœ‰ๅŠนใซใ—ใฆใใ ใ•ใ„ใ€‚", + "edit-mode-button-label": "็ทจ้›†ใƒขใƒผใƒ‰ใซๅ…ฅใ‚‹", + "edit-mode-controls-explanation": "ๅทฆใ‚ฏใƒชใƒƒใ‚ฏใงใƒ‰ใƒฉใƒƒใ‚ฐ๏ผšใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎ็งปๅ‹•ใพใŸใฏใ‚ตใ‚คใ‚บๅค‰ๆ›ดใ€‚\nๅณใ‚ฏใƒชใƒƒใ‚ฏ๏ผšใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆใƒกใƒ‹ใƒฅใƒผใ‚ชใƒ—ใ‚ทใƒงใƒณใ‚’้–‹ใใ€‚", + "edit-mode-description": "็ทจ้›†ใƒขใƒผใƒ‰ใ‚’ๆœ‰ๅŠนใซใ—ใฆใ€ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎ็งปๅ‹•ใ‚„้…็ฝฎๅค‰ๆ›ดใ‚’่กŒใ„ใพใ™ใ€‚ๆœ‰ๅŠนใซใ™ใ‚‹ใจใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใซๆž ็ทšใŒ่กจ็คบใ•ใ‚Œใ€ไฝ็ฝฎใ‚’ๅค‰ๆ›ดใงใใ‚‹ใ‚ˆใ†ใซใชใ‚Šใพใ™ใ€‚", + "edit-mode-exit-button": "็ทจ้›†ใƒขใƒผใƒ‰ใ‚’็ต‚ไบ†", + "edit-mode-grid-snap-label": "ใ‚ฐใƒชใƒƒใƒ‰ใซใ‚นใƒŠใƒƒใƒ—", + "edit-mode-grid-snap-scale-label": "ใ‚ฐใƒชใƒƒใƒ‰ใ‚นใƒŠใƒƒใƒ—ใ‚นใ‚ฑใƒผใƒซ", + "edit-mode-label": "็ทจ้›†ใƒขใƒผใƒ‰", + "enabled-description": "ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆๅ…จไฝ“ใ‚’ๆœ‰ๅŠนใพใŸใฏ็„กๅŠนใซใ—ใพใ™ใ€‚", + "enabled-label": "ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’ๆœ‰ๅŠนๅŒ–", + "general-desc": "ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใซ่กจ็คบใ•ใ‚Œใ‚‹ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "general-title": "ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆ", + "media-player-enabled-description": "ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใซใƒกใƒ‡ใ‚ฃใ‚ขใƒ—ใƒฌใƒผใƒคใƒผใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "media-player-enabled-label": "ใƒกใƒ‡ใ‚ฃใ‚ขใƒ—ใƒฌใƒผใƒคใƒผใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’ๆœ‰ๅŠนๅŒ–", + "media-player-rounded-corners-description": "ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎ็ซฏใ‚’ไธธใใ™ใ‚‹ใ€‚", + "media-player-show-album-art-description": "ใ‚ขใƒซใƒใƒ ใฎใ‚ขใƒผใƒˆใƒฏใƒผใ‚ฏใจใƒˆใƒฉใƒƒใ‚ฏๆƒ…ๅ ฑ๏ผˆใ‚ฟใ‚คใƒˆใƒซใจใ‚ขใƒผใƒ†ใ‚ฃใ‚นใƒˆ๏ผ‰ใ‚’่กจ็คบใ—ใฆใใ ใ•ใ„ใ€‚", + "media-player-show-album-art-label": "ใ‚ขใƒซใƒใƒ ใ‚ขใƒผใƒˆใจใ‚ฟใ‚คใƒˆใƒซใ‚’่กจ็คบ", + "media-player-show-background-description": "ใƒกใƒ‡ใ‚ฃใ‚ขใƒ—ใƒฌใƒผใƒคใƒผใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎ่ƒŒๆ™ฏใ‚ณใƒณใƒ†ใƒŠใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "media-player-show-buttons-description": "ใƒกใƒ‡ใ‚ฃใ‚ขใƒ—ใƒฌใƒผใƒคใƒผใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใซใƒกใƒ‡ใ‚ฃใ‚ขใ‚ณใƒณใƒˆใƒญใƒผใƒซใƒœใ‚ฟใƒณ๏ผˆๅ†็”Ÿ/ไธ€ๆ™‚ๅœๆญขใ€ๅ‰ใธใ€ๆฌกใธ๏ผ‰ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "media-player-show-buttons-label": "ใƒœใ‚ฟใƒณใ‚’่กจ็คบ", + "media-player-show-visualizer-description": "ใ‚ชใƒผใƒ‡ใ‚ฃใ‚ชใƒ“ใ‚ธใƒฅใ‚ขใƒฉใ‚คใ‚ถใƒผใฎใ‚ชใƒผใƒใƒผใƒฌใ‚คใ‚’่กจ็คบใ™ใ‚‹ใ€‚", + "media-player-visualizer-type-description": "ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใƒกใƒ‡ใ‚ฃใ‚ขใƒ—ใƒฌใƒผใƒคใƒผใฎ่ƒŒๆ™ฏใซไฝฟ็”จใ™ใ‚‹ใƒ“ใ‚ธใƒฅใ‚ขใƒฉใ‚คใ‚ถใƒผใฎ็จฎ้กžใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "overview-enabled-description": "ใ‚ณใƒณใƒใ‚ธใ‚ฟใƒผใฎๆฆ‚่ฆใŒ้–‹ใ„ใฆใ„ใ‚‹้–“ใ€ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’่กจ็คบใ™ใ‚‹ใ€‚", + "overview-enabled-label": "ๆฆ‚่ฆใซ่กจ็คบ", + "system-stat-layout-bottom": "ไธ‹", + "system-stat-layout-description": "ๅ‡กไพ‹ใŒใ‚ฐใƒฉใƒ•ใซๅฏพใ—ใฆใฉใฎใ‚ˆใ†ใซ่กจ็คบใ•ใ‚Œใ‚‹ใ‹ใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "system-stat-layout-label": "ใƒฌใ‚คใ‚ขใ‚ฆใƒˆ", + "system-stat-layout-side": "ๅด", + "system-stat-rounded-corners-description": "ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎ่ƒŒๆ™ฏใซไธธใ„่ง’ใ‚’ไฝฟ็”จใ™ใ‚‹ใ€‚", + "system-stat-rounded-corners-label": "่ง’ไธธ", + "system-stat-show-background-description": "ใ‚ทใ‚นใƒ†ใƒ ็ตฑ่จˆใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎ่ƒŒๆ™ฏใ‚ณใƒณใƒ†ใƒŠใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "system-stat-show-background-label": "่ƒŒๆ™ฏใ‚’่กจ็คบ", + "system-stat-stat-type-description": "่กจ็คบใ™ใ‚‹ใ‚ทใ‚นใƒ†ใƒ ็ตฑ่จˆใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "system-stat-stat-type-label": "็ตฑ่จˆใฎ็จฎ้กž", + "title": "ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆ", + "weather-enabled-description": "ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใซๅคฉๆฐ—ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "weather-enabled-label": "ๅคฉๆฐ—ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’ๆœ‰ๅŠนๅŒ–", + "weather-show-background-description": "ๅคฉๆฐ—ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎ่ƒŒๆ™ฏใ‚ณใƒณใƒ†ใƒŠใ‚’่กจ็คบใ—ใพใ™ใ€‚" + }, + "display": { + "monitors-backlight-device-auto-option": "ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆ", + "monitors-backlight-device-description": "ใ“ใฎๅ‡บๅŠ›ใฎใƒใƒƒใ‚ฏใƒฉใ‚คใƒˆใƒ‡ใƒใ‚คใ‚นใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "monitors-backlight-device-label": "ใƒใƒƒใ‚ฏใƒฉใ‚คใƒˆใƒ‡ใƒใ‚คใ‚น", + "monitors-brightness-step-description": "ๆ˜Žใ‚‹ใ•ใฎๅค‰ๅŒ–้‡๏ผˆใ‚นใ‚ฏใƒญใƒผใƒซใƒ›ใ‚คใƒผใƒซใ‚„ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใ‚ญใƒผ๏ผ‰ใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "monitors-brightness-step-label": "ๆ˜Žใ‚‹ใ•ใฎ่ชฟๆ•ดใ‚นใƒ†ใƒƒใƒ—", + "monitors-brightness-unavailable-ddc-disabled": "ๆ˜Žใ‚‹ใ•่ชฟๆ•ดใ‚’ๅˆฉ็”จใงใใพใ›ใ‚“ใ€‚ใ“ใฎใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คใ‚’ๆ“ไฝœใ™ใ‚‹ใซใฏใ€Œๅค–้ƒจใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คใฎๆ˜Žใ‚‹ใ•ๅˆถๅพกใ€ใ‚’ๆœ‰ๅŠนใซใ—ใฆใใ ใ•ใ„ใ€‚", + "monitors-brightness-unavailable-generic": "ใ“ใฎใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คใงใฏๆ˜Žใ‚‹ใ•่ชฟๆ•ดใ‚’ๅˆฉ็”จใงใใพใ›ใ‚“ใ€‚", + "monitors-desc": "ๅ„ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คใฎๆ˜Žใ‚‹ใ•่จญๅฎšใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "monitors-enforce-minimum-description": "ๆ˜Žใ‚‹ใ•ใ‚’ 0% ใซใ—ใŸ้š›ใซใ€ไธ€้ƒจใฎใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คใงใƒใƒƒใ‚ฏใƒฉใ‚คใƒˆใŒๅฎŒๅ…จใซๆถˆใˆใฆใ—ใพใ†ๅ•้กŒใ‚’ๅ›ž้ฟใ—ใพใ™ใ€‚", + "monitors-enforce-minimum-label": "ๆœ€ไฝŽ่ผๅบฆใ‚’็ขบไฟ (1%)", + "monitors-external-brightness-description": "DDC/CI ใƒ—ใƒญใƒˆใ‚ณใƒซใ‚’ไฝฟ็”จใ—ใฆๅค–้ƒจใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คใฎๆ˜Žใ‚‹ใ•ใ‚’ๅˆถๅพกใ™ใ‚‹ใŸใ‚ใ€DDCUtil ใ‚’ๆœ‰ๅŠนใซใ—ใพใ™ใ€‚", + "monitors-external-brightness-label": "ๅค–้ƒจใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คใฎๆ˜Žใ‚‹ใ•ๅˆถๅพก", + "monitors-global-brightness-description": "ใ™ในใฆใฎใƒขใƒ‹ใ‚ฟใƒผใฎๆ˜Žใ‚‹ใ•ใ‚’ไธ€ๅบฆใซ่ชฟๆ•ดใ—ใพใ™ใ€‚", + "monitors-global-brightness-label": "ใ™ในใฆใฎใƒขใƒ‹ใ‚ฟใƒผ", + "monitors-title": "ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คใ”ใจใฎ่จญๅฎš", + "night-light-auto-schedule-description": "{location} ใฎๆ—ฅใฎๅ‡บใƒปๆ—ฅใฎๅ…ฅใ‚Šๆ™‚ๅˆปใซๅˆใ‚ใ›ใพใ™๏ผˆๆŽจๅฅจ๏ผ‰ใ€‚", + "night-light-auto-schedule-label": "่‡ชๅ‹•ใ‚นใ‚ฑใ‚ธใƒฅใƒผใƒซ", + "night-light-desc": "ใƒ–ใƒซใƒผใƒฉใ‚คใƒˆใ‚’ๆŠ‘ใˆใฆใ€็ก็œ ใฎๆ”นๅ–„ใจ็›ฎใฎ็–ฒใ‚Œใฎ่ปฝๆธ›ใ‚’ๅŠฉใ‘ใพใ™ใ€‚", + "night-light-enable-description": "ๆš–่‰ฒใฎใƒ•ใ‚ฃใƒซใ‚ฟใƒผใ‚’้ฉ็”จใ—ใ€ใƒ–ใƒซใƒผใƒฉใ‚คใƒˆใ‚’่ปฝๆธ›ใ—ใพใ™ใ€‚", + "night-light-enable-label": "ๅคœ้–“ใƒขใƒผใƒ‰ใฎๆœ‰ๅŠนๅŒ–", + "night-light-force-activation-description": "ใ‚นใ‚ฑใ‚ธใƒฅใƒผใƒซใ‚’็„ก่ฆ–ใ—ใฆใ€ใ™ใใซๅคœ้–“ใƒ•ใ‚ฃใƒซใ‚ฟใƒผใ‚’้ฉ็”จใ—ใพใ™ใ€‚", + "night-light-force-activation-label": "ๅธธใซๆœ‰ๅŠนใซใ™ใ‚‹", + "night-light-manual-schedule-description": "ๆ—ฅใฎๅ‡บใจๆ—ฅใฎๅ…ฅใ‚Šใฎๆ™‚้–“ใ‚’่‡ช็”ฑใซ่จญๅฎšใ—ใพใ™ใ€‚", + "night-light-manual-schedule-label": "ๆ‰‹ๅ‹•ใ‚นใ‚ฑใ‚ธใƒฅใƒผใƒซ่จญๅฎš", + "night-light-manual-schedule-select-start": "้–‹ๅง‹ๆ™‚้–“ใ‚’้ธๆŠž", + "night-light-manual-schedule-select-stop": "็ต‚ไบ†ๆ™‚้–“ใ‚’้ธๆŠž", + "night-light-manual-schedule-sunrise": "ๆ—ฅใฎๅ‡บใฎๆ™‚้–“", + "night-light-manual-schedule-sunset": "ๆ—ฅใฎๅ…ฅใ‚Šใฎๆ™‚้–“", + "night-light-temperature-day": "ๆ—ฅไธญ", + "night-light-temperature-day-description": "ๆ—ฅไธญใฎ่‰ฒๆธฉๅบฆใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "night-light-temperature-description": "ๆ—ฅไธญใจๅคœ้–“ใฎ่‰ฒๆธฉๅบฆ๏ผˆๆš–ใ‹ใ•๏ผ‰ใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "night-light-temperature-label": "่‰ฒๆธฉๅบฆ", + "night-light-temperature-night": "ๅคœ้–“", + "night-light-temperature-night-description": "ๅคœ้–“ใฎ่‰ฒๆธฉๅบฆใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "title": "ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚ค" + }, + "dock": { + "appearance-background-opacity-description": "ใƒ‰ใƒƒใ‚ฏใฎ่ƒŒๆ™ฏใฎไธ้€ๆ˜Žๅบฆใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "appearance-border-radius-description": "ใƒ‰ใƒƒใ‚ฏใฎ่ง’ใฎไธธใฟใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "appearance-border-radius-label": "่ง’ใฎไธธใฟ", + "appearance-colorize-icons-description": "ใƒ‰ใƒƒใ‚ฏใฎใ‚ขใƒ—ใƒชใ‚ขใ‚คใ‚ณใƒณใซใƒ†ใƒผใƒžใ‚ซใƒฉใƒผใ‚’้ฉ็”จใ—ใพใ™๏ผˆ้žใƒ•ใ‚ฉใƒผใ‚ซใ‚นๆ™‚ใฎใฟ๏ผ‰ใ€‚", + "appearance-colorize-icons-label": "ใƒ†ใƒผใƒžใ‚ซใƒฉใƒผใฎ้ฉ็”จ", + "appearance-dead-opacity-description": "่ตทๅ‹•ใ—ใฆใ„ใชใ„ใ‚ขใƒ—ใƒชใ‚ขใ‚คใ‚ณใƒณใฎไธ้€ๆ˜Žๅบฆใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "appearance-dead-opacity-label": "ๆœชๅฎŸ่กŒๆ™‚ใฎไธ้€ๆ˜Žๅบฆ", + "appearance-desc": "ใƒ‰ใƒƒใ‚ฏใฎๅ‹•ไฝœใจๅค–่ฆณใ‚’ใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บใ—ใพใ™ใ€‚", + "appearance-display-auto-hide": "่‡ชๅ‹•็š„ใซ้š ใ™", + "appearance-display-description": "ใƒ‰ใƒƒใ‚ฏใฎ่กจ็คบๆ–นๆณ•ใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "appearance-display-exclusive": "้ ˜ๅŸŸใ‚’็ขบไฟ", + "appearance-dock-indicator-description": "ใƒ‰ใƒƒใ‚ฏใŒ้ž่กจ็คบใฎใจใใซๅฐใ•ใชใ‚คใƒณใ‚ธใ‚ฑใƒผใ‚ฟใƒผใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "appearance-dock-indicator-label": "ใƒ‰ใƒƒใ‚ฏใ‚คใƒณใ‚ธใ‚ฑใƒผใ‚ฟใƒผ", + "appearance-floating-distance-description": "็”ป้ข็ซฏใ‹ใ‚‰ใƒ‰ใƒƒใ‚ฏใพใงใฎ่ท้›ขใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "appearance-floating-distance-label": "็”ป้ข็ซฏใ‹ใ‚‰ใฎ่ท้›ข", + "appearance-group-apps-description": "ๅŒใ˜ใ‚ขใƒ—ใƒชใฎ่ค‡ๆ•ฐใฎใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใ‚’1ใคใฎDockใ‚จใƒณใƒˆใƒชใซใ‚ฐใƒซใƒผใƒ—ๅŒ–ใ—ใพใ™ใ€‚", + "appearance-group-apps-label": "ๅŒใ˜ใ‚ขใƒ—ใƒชใ‚’ใ‚ฐใƒซใƒผใƒ—ๅŒ–", + "appearance-group-click-action-cycle": "ใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใ‚’ๅˆ‡ใ‚Šๆ›ฟใˆใ‚‹", + "appearance-group-click-action-description": "ใ‚ฐใƒซใƒผใƒ—ๅŒ–ใ•ใ‚ŒใŸใ‚ขใƒ—ใƒชใฎๅทฆใ‚ฏใƒชใƒƒใ‚ฏๅ‹•ไฝœใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "appearance-group-click-action-label": "ใ‚ฐใƒซใƒผใƒ—ๅŒ–ใ•ใ‚ŒใŸใ‚ขใƒ—ใƒชใฎใ‚ฏใƒชใƒƒใ‚ฏๆ™‚ใฎๅ‹•ไฝœ", + "appearance-group-click-action-list": "ใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใƒชใ‚นใƒˆใ‚’้–‹ใ", + "appearance-group-context-menu-mode-description": "ใ‚ฐใƒซใƒผใƒ—ๅŒ–ใ•ใ‚ŒใŸใ‚ขใƒ—ใƒชใฎใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆใƒกใƒ‹ใƒฅใƒผใฎๅ‹•ไฝœใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "appearance-group-context-menu-mode-extended": "ๆ‹กๅผต", + "appearance-group-context-menu-mode-label": "ใ‚ฐใƒซใƒผใƒ—ๅŒ–ใ•ใ‚ŒใŸใ‚ขใƒ—ใƒชใƒกใƒ‹ใƒฅใƒผใƒขใƒผใƒ‰", + "appearance-group-context-menu-mode-list": "ใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใƒชใ‚นใƒˆ", + "appearance-group-indicator-style-description": "ใ‚ฐใƒซใƒผใƒ—ๅŒ–ใ•ใ‚ŒใŸๅฎŸ่กŒไธญใฎใ‚คใƒณใ‚ธใ‚ฑใƒผใ‚ฟใƒผใŒใ€ใƒ•ใ‚ฉใƒผใ‚ซใ‚นใ•ใ‚ŒใŸใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใฎ็Šถๆ…‹ใ‚’ใฉใฎใ‚ˆใ†ใซ่กจ็คบใ™ใ‚‹ใ‹ใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "appearance-group-indicator-style-dots": "ใƒ‰ใƒƒใƒˆ", + "appearance-group-indicator-style-label": "ใ‚ฐใƒซใƒผใƒ—ๅŒ–ใ•ใ‚ŒใŸใ‚คใƒณใ‚ธใ‚ฑใƒผใ‚ฟใƒผใ‚นใ‚ฟใ‚คใƒซ", + "appearance-group-indicator-style-number": "ๆ•ฐๅ€ค", + "appearance-hide-show-speed-description": "ใƒ‰ใƒƒใ‚ฏใฎ่กจ็คบใƒป้ž่กจ็คบใ‚ขใƒ‹ใƒกใƒผใ‚ทใƒงใƒณใฎ้€Ÿๅบฆใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "appearance-hide-show-speed-label": "่กจ็คบใƒป้ž่กจ็คบใฎ้€Ÿๅบฆ", + "appearance-icon-size-description": "ใƒ‰ใƒƒใ‚ฏๅ…จไฝ“ใฎใ‚ตใ‚คใ‚บใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "appearance-icon-size-label": "ใƒ‰ใƒƒใ‚ฏใฎใ‚ตใ‚คใ‚บ", + "appearance-inactive-indicators-description": "็พๅœจใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใชใ‚ขใƒ—ใƒชใ ใ‘ใงใชใใ€ใ™ในใฆใฎใ‚ขใƒ—ใƒชใซใ‚คใƒณใ‚ธใ‚ฑใƒผใ‚ฟใƒผใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "appearance-inactive-indicators-label": "ๅฎŸ่กŒใ‚คใƒณใ‚ธใ‚ฑใƒผใ‚ฟใƒผ", + "appearance-indicator-color-description": "้š ใ‚ŒใŸใƒ‰ใƒƒใ‚ฏใฎใ‚คใƒณใ‚ธใ‚ฑใƒผใ‚ฟใƒผใฎ่‰ฒใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "appearance-indicator-color-label": "ใ‚คใƒณใ‚ธใ‚ฑใƒผใ‚ฟใƒผใฎ่‰ฒ", + "appearance-indicator-opacity-description": "้š ใ‚ŒใŸใƒ‰ใƒƒใ‚ฏใฎใ‚คใƒณใ‚ธใ‚ฑใƒผใ‚ฟใƒผใฎไธ้€ๆ˜Žๅบฆใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "appearance-indicator-opacity-label": "ใ‚คใƒณใ‚ธใ‚ฑใƒผใ‚ฟใƒผใฎไธ้€ๆ˜Žๅบฆ", + "appearance-indicator-thickness-description": "้š ใ‚ŒใŸใƒ‰ใƒƒใ‚ฏใฎใ‚คใƒณใ‚ธใ‚ฑใƒผใ‚ฟใƒผใ‚’ๅคชใ่กจ็คบใ—ใพใ™ (3pxใฎไปฃใ‚ใ‚Šใซ6px)ใ€‚", + "appearance-indicator-thickness-label": "ๅคชใ„ใ‚คใƒณใ‚ธใ‚ฑใƒผใ‚ฟใƒผ", + "appearance-launcher-icon-description": "Dock Launcher ใซไฝฟ็”จใ™ใ‚‹ใ‚ขใ‚คใ‚ณใƒณใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "appearance-launcher-icon-label": "ใƒฉใƒณใƒใƒฃใƒผใ‚ขใ‚คใ‚ณใƒณ", + "appearance-launcher-position-description": "ใƒฉใƒณใƒใƒฃใƒผใ‚ขใ‚คใ‚ณใƒณใ‚’ใƒ‰ใƒƒใ‚ฏใฎใฉใ“ใซ่กจ็คบใ™ใ‚‹ใ‹ใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "appearance-launcher-position-end": "็ต‚ไบ†", + "appearance-launcher-position-label": "ใƒฉใƒณใƒใƒฃใƒผใฎไฝ็ฝฎ", + "appearance-launcher-position-start": "้–‹ๅง‹", + "appearance-launcher-use-distro-logo-description": "ใƒ‡ใ‚ฃใ‚นใƒˆใƒชใƒ“ใƒฅใƒผใ‚ทใƒงใƒณใฎใƒญใ‚ดใ‚’Dock Launcherใ‚ขใ‚คใ‚ณใƒณใจใ—ใฆไฝฟ็”จใ—ใพใ™ใ€‚", + "appearance-launcher-use-distro-logo-label": "ใ‚ขใ‚คใ‚ณใƒณใฎไปฃใ‚ใ‚Šใซใƒ‡ใ‚ฃใ‚นใƒˆใƒญใฎใƒญใ‚ดใ‚’ไฝฟ็”จใ™ใ‚‹", + "appearance-pinned-static-description": "ใƒ”ใƒณ็•™ใ‚ใ•ใ‚ŒใŸใ‚ขใƒ—ใƒชใ‚ขใ‚คใ‚ณใƒณใ‚’ๅธธใซๅทฆๅดใซๅฏ„ใ›ใฆๅ›บๅฎšใ—ใพใ™ใ€‚", + "appearance-pinned-static-label": "ใƒ”ใƒณ็•™ใ‚ใ‚ขใƒ—ใƒชใฎๅ›บๅฎš้…็ฝฎ", + "appearance-position-description": "ใƒ‰ใƒƒใ‚ฏใ‚’็”ป้ขไธŠใฎใฉใ“ใซ่กจ็คบใ™ใ‚‹ใ‹ใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "appearance-position-label": "ไฝ็ฝฎ", + "appearance-show-launcher-icon-description": "ใƒ‰ใƒƒใ‚ฏใซใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใƒฉใƒณใƒใƒฃใƒผใ‚ขใ‚คใ‚ณใƒณใ‚’่กจ็คบใ™ใ‚‹ใ€‚", + "appearance-show-launcher-icon-label": "ใ‚ขใƒ—ใƒชใƒฉใƒณใƒใƒฃใƒผใ‚’่กจ็คบ", + "appearance-sit-on-frame-description": "ใƒ‰ใƒƒใ‚ฏใ‚’ใƒ•ใƒฌใƒผใƒ ใฎๅขƒ็•Œใฎๅ†…ๅดใซ้…็ฝฎใ—ใ€ไธŠ้ƒจใซ้‡ใชใ‚‰ใชใ„ใ‚ˆใ†ใซใ—ใพใ™ใ€‚", + "appearance-sit-on-frame-label": "ใƒ‰ใƒƒใ‚ฏใฏใƒ•ใƒฌใƒผใƒ ใซ้…็ฝฎ", + "appearance-type-attached": "ใƒ‰ใƒƒใ‚ญใƒณใ‚ฐ", + "appearance-type-description": "ๆตฎๅ‹•ใ™ใ‚‹ใƒ”ใƒซๅž‹ใ€ใพใŸใฏ็ซฏใซๅ›บๅฎšใ•ใ‚ŒใŸ้™็š„ใƒใƒผใฎใฉใกใ‚‰ใ‹ใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "appearance-type-floating": "ใƒ•ใƒญใƒผใƒ†ใ‚ฃใƒณใ‚ฐ", + "appearance-type-label": "ใƒ‰ใƒƒใ‚ฏใ‚นใ‚ฟใ‚คใƒซ", + "enabled-description": "ใƒ‰ใƒƒใ‚ฏใ‚’ๅฎŒๅ…จใซ่กจ็คบใพใŸใฏ้ž่กจ็คบใซใ—ใพใ™ใ€‚", + "enabled-label": "ใƒ‰ใƒƒใ‚ฏใฎๆœ‰ๅŠนๅŒ–", + "monitors-desc": "ใƒ‰ใƒƒใ‚ฏใ‚’่กจ็คบใ™ใ‚‹ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คใ‚’้ธๆŠžใ—ใพใ™ใ€‚ๆœช้ธๆŠžใฎๅ ดๅˆใฏๅ…จใฆใซ่กจ็คบใ•ใ‚Œใพใ™ใ€‚", + "monitors-only-same-monitor-description": "ใƒ‰ใƒƒใ‚ฏใŒ้…็ฝฎใ•ใ‚Œใฆใ„ใ‚‹ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คไธŠใฎใ‚ขใƒ—ใƒชใฎใฟใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "monitors-only-same-monitor-label": "ๅŒใ˜ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คใฎใ‚ขใƒ—ใƒชใฎใฟ", + "monitors-title": "่กจ็คบใ™ใ‚‹ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚ค", + "title": "ใƒ‰ใƒƒใ‚ฏ" + }, + "general": { + "copy-settings": "่จญๅฎšใ‚’ใ‚ณใƒ”ใƒผ", + "fonts-default-description": "ใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใƒผใ‚นๅ…จไฝ“ใงไฝฟ็”จใ•ใ‚Œใ‚‹ใƒกใ‚คใƒณใƒ•ใ‚ฉใƒณใƒˆใ€‚", + "fonts-default-label": "ใƒกใ‚คใƒณใƒ•ใ‚ฉใƒณใƒˆ", + "fonts-default-placeholder": "ใƒ•ใ‚ฉใƒณใƒˆใ‚’้ธๆŠž...", + "fonts-default-scale-description": "ๆจ™ๆบ–ใƒ†ใ‚ญใ‚นใƒˆใฎใ‚ตใ‚คใ‚บใ‚’ๆ‹กๅคงใƒป็ธฎๅฐใ—ใพใ™ใ€‚", + "fonts-default-scale-label": "ใƒกใ‚คใƒณใƒ•ใ‚ฉใƒณใƒˆใฎใ‚ตใ‚คใ‚บ", + "fonts-default-search-placeholder": "ใƒ•ใ‚ฉใƒณใƒˆใ‚’ๆคœ็ดข...", + "fonts-desc": "ใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใƒผใ‚นๅ…จไฝ“ใงไฝฟ็”จใ™ใ‚‹ใƒ•ใ‚ฉใƒณใƒˆใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "fonts-monospace-description": "ๆ•ฐๅญ—ใ‚„็ตฑ่จˆๆƒ…ๅ ฑใฎ่กจ็คบใซไฝฟ็”จใ•ใ‚Œใ‚‹็ญ‰ๅน…ใƒ•ใ‚ฉใƒณใƒˆใ€‚", + "fonts-monospace-label": "็ญ‰ๅน…ใƒ•ใ‚ฉใƒณใƒˆ", + "fonts-monospace-placeholder": "็ญ‰ๅน…ใƒ•ใ‚ฉใƒณใƒˆใ‚’้ธๆŠž...", + "fonts-monospace-scale-description": "็ญ‰ๅน…ใƒ†ใ‚ญใ‚นใƒˆใฎใ‚ตใ‚คใ‚บใ‚’ๆ‹กๅคงใƒป็ธฎๅฐใ—ใพใ™ใ€‚", + "fonts-monospace-scale-label": "็ญ‰ๅน…ใƒ•ใ‚ฉใƒณใƒˆใฎใ‚ตใ‚คใ‚บ", + "fonts-monospace-search-placeholder": "็ญ‰ๅน…ใƒ•ใ‚ฉใƒณใƒˆใ‚’ๆคœ็ดข...", + "fonts-title": "ใƒ•ใ‚ฉใƒณใƒˆ", + "keybinds-conflict-description": "ใ“ใฎใ‚ญใƒผใฎ็ต„ใฟๅˆใ‚ใ›ใฏใ™ใงใซ{action}ใซๅ‰ฒใ‚Šๅฝ“ใฆใ‚‰ใ‚Œใฆใ„ใพใ™ใ€‚", + "keybinds-conflict-title": "ใ‚ญใƒผใƒใ‚คใƒณใƒ‰ใฎ็ซถๅˆ", + "keybinds-description": "ใƒ‘ใƒใƒซใจใƒฉใƒณใƒใƒฃใƒผใฎใ‚ฐใƒญใƒผใƒใƒซใƒŠใƒ“ใ‚ฒใƒผใ‚ทใƒงใƒณใ‚ญใƒผใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "keybinds-down": "ไธ‹ใซ็งปๅ‹•", + "keybinds-enter": "็ขบ่ช / ใ‚ขใ‚ฏใ‚ทใƒงใƒณ", + "keybinds-escape": "้–‰ใ˜ใ‚‹ / ๆˆปใ‚‹", + "keybinds-left": "ๅทฆใธ็งปๅ‹•", + "keybinds-modifier-description": "ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใซใฏไฟฎ้ฃพใ‚ญใƒผ (Ctrl ใพใŸใฏ Alt) ใ‚’ๅซใ‚ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚", + "keybinds-modifier-title": "ไฟฎ้ฃพใ‚ญใƒผใŒๅฟ…่ฆใงใ™", + "keybinds-remove": "ๅ‰Š้™ค", + "keybinds-right": "ๅณใธ็งปๅ‹•", + "keybinds-title": "ใƒŠใƒ“ใ‚ฒใƒผใ‚ทใƒงใƒณใฎใ‚ญใƒผใƒใ‚คใƒณใƒ‰", + "keybinds-up": "ไธŠใธ็งปๅ‹•", + "language-desc": "ใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใงไฝฟ็”จใ™ใ‚‹่จ€่ชžใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "language-select-auto-detect": "่‡ชๅ‹•ๆคœๅ‡บ", + "language-select-description": "ใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใฎใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใƒผใ‚นใงไฝฟ็”จใ™ใ‚‹่จ€่ชžใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "language-select-label": "่กจ็คบ่จ€่ชž", + "launch-setup-wizard": "ใ‚ปใƒƒใƒˆใ‚ขใƒƒใƒ—ใ‚ฆใ‚ฃใ‚ถใƒผใƒ‰", + "profile-desc": "ใƒฆใƒผใ‚ถใƒผใฎ่ฉณ็ดฐใจใ‚ขใƒใ‚ฟใƒผใ‚’็ทจ้›†ใ—ใพใ™ใ€‚", + "profile-picture-description": "ใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใƒผใ‚นๅ…จไฝ“ใซ่กจ็คบใ•ใ‚Œใ‚‹ใƒ—ใƒญใƒ•ใ‚ฃใƒผใƒซ็”ปๅƒใ€‚", + "profile-picture-label": "ใƒ—ใƒญใƒ•ใ‚ฃใƒผใƒซ็”ปๅƒ", + "profile-select-avatar": "ใ‚ขใƒใ‚ฟใƒผ็”ปๅƒใ‚’้ธๆŠž", + "profile-title": "ใƒ—ใƒญใƒ•ใ‚ฃใƒผใƒซ", + "profile-tooltip": "ใƒ—ใƒญใƒ•ใ‚ฃใƒผใƒซๅ†™็œŸ", + "reverse-scrolling-description": "่งฃ้‡ˆใ•ใ‚ŒใŸใ‚นใ‚ฏใƒญใƒผใƒซๆ–นๅ‘ใ‚’ๅ่ปขใ€‚", + "reverse-scrolling-label": "ใ‚นใ‚ฏใƒญใƒผใƒซๆ–นๅ‘ใ‚’ๅ่ปข", + "screen-corners-desc": "็”ป้ขใฎ่ง’ใฎไธธใฟใ‚„่ฆ–่ฆšๅŠนๆžœใ‚’ใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บใ—ใพใ™ใ€‚", + "screen-corners-radius-description": "็”ป้ขใฎ่ง’ใฎไธธใฟใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "screen-corners-radius-label": "่ง’ใฎไธธใฟ", + "screen-corners-radius-reset": "่ง’ใฎไธธใฟใ‚’ใƒชใ‚ปใƒƒใƒˆ", + "screen-corners-show-corners-description": "็”ป้ขใฎ็ซฏใซไธธใ„่ง’๏ผˆ่ฃ…้ฃพ๏ผ‰ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "screen-corners-show-corners-label": "็”ป้ขใฎ่ง’ใ‚’่กจ็คบใ™ใ‚‹", + "screen-corners-solid-black-description": "ใƒใƒผใฎ่ƒŒๆ™ฏ่‰ฒใฎไปฃใ‚ใ‚Šใซใ€้ป’ไธ€่‰ฒใ‚’ไฝฟ็”จใ—ใพใ™ใ€‚", + "screen-corners-solid-black-label": "่ง’ใ‚’้ป’ใๅก—ใ‚Šใคใถใ™", + "screen-corners-title": "็”ป้ขใฎ่ง’", + "settings-copied": "่จญๅฎšใ‚’ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ใซใ‚ณใƒ”ใƒผใ—ใพใ—ใŸ", + "smooth-scrolling-description": "ใƒชใ‚นใƒˆใฎใ‚นใ‚ฏใƒญใƒผใƒซใ‚’ใ‚ขใƒ‹ใƒกใƒผใ‚ทใƒงใƒณๅŒ–ใ—ใฆใ€ใ‚ˆใ‚Šใ‚นใƒ ใƒผใ‚บใชใƒ›ใ‚คใƒผใƒซๆ“ไฝœใ‚’ๅฎŸ็พใ—ใพใ™ใ€‚", + "smooth-scrolling-label": "ใ‚นใƒ ใƒผใ‚บใ‚นใ‚ฏใƒญใƒผใƒซ", + "tab-basics": "ๅŸบๆœฌ", + "tab-keybinds": "ใ‚ญใƒผใƒใ‚คใƒณใƒ‰" + }, + "hooks": { + "color-generation-description": "่‰ฒใฎ็”ŸๆˆใŒๅฎŒไบ†ใ—ใŸใจใใซๅฎŸ่กŒใ•ใ‚Œใ‚‹ใ‚ณใƒžใƒณใƒ‰๏ผˆไพ‹๏ผšๅฃ็ด™ใƒ™ใƒผใ‚นใฎใƒ†ใƒผใƒž้ฉ็”จๅพŒใชใฉ๏ผ‰ใ€‚", + "color-generation-label": "่‰ฒใŒ็”Ÿๆˆใ•ใ‚Œใพใ—ใŸ", + "color-generation-placeholder": "ไพ‹: notify-send \"่‰ฒ\" \"็”Ÿๆˆๆธˆใฟ\"", + "info-command-info-description": "โ€ข ใ‚ณใƒžใƒณใƒ‰ใฏใ‚ทใ‚งใƒซ (sh -lc) ็ตŒ็”ฑใงๅฎŸ่กŒใ•ใ‚Œใพใ™
โ€ข ใ‚ณใƒžใƒณใƒ‰ใฏใƒใƒƒใ‚ฏใ‚ฐใƒฉใ‚ฆใƒณใƒ‰ใงๅฎŸ่กŒใ•ใ‚Œใพใ™
โ€ข ใƒ†ใ‚นใƒˆใƒœใ‚ฟใƒณใฏ็พๅœจใฎ่จญๅฎšๅ€คใ‚’ไฝฟใฃใฆๅฎŸ่กŒใ•ใ‚Œใพใ™", + "info-parameters-description": "โ€ข Wallpaper hook: $1 = ๅฃ็ด™ใฎใƒ‘ใ‚น, $2 = ็”ป้ขๅ, $3 = ใƒ†ใƒผใƒž (ใƒ€ใƒผใ‚ฏ/ใƒฉใ‚คใƒˆ)
โ€ข Theme toggle hook: $1 = true/false (ใƒ€ใƒผใ‚ฏใƒขใƒผใƒ‰ใฎ็Šถๆ…‹)
โ€ข Screen lock/unlock hooks: $1 = lock/unlock (็”ป้ขใƒญใƒƒใ‚ฏใฎ็Šถๆ…‹)
โ€ข Performance mode hooks: ใƒ‘ใƒฉใƒกใƒผใ‚ฟใชใ—
โ€ข Session hook: $1 = action (shutdown/reboot)", + "info-parameters-label": "ๅˆฉ็”จๅฏ่ƒฝใชใƒ‘ใƒฉใƒกใƒผใ‚ฟ๏ผˆๅผ•ๆ•ฐ๏ผ‰", + "noctalia-started-description": "Noctaliaใฎใƒญใƒผใƒ‰ใŒๅฎŒไบ†ใ—ใŸใจใใซๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚", + "noctalia-started-label": "Noctalia ใŒ่ตทๅ‹•ใ—ใพใ—ใŸ", + "noctalia-started-placeholder": "ไพ‹๏ผšnotify-send 'Noctalia ่ชญใฟ่พผใฟๅฎŒไบ†'", + "performance-mode-disabled-description": "Noctalia ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใƒขใƒผใƒ‰ใŒ็„กๅŠนใซใชใฃใŸ้š›ใซๅฎŸ่กŒใ•ใ‚Œใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚", + "performance-mode-disabled-label": "ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใƒขใƒผใƒ‰็„กๅŠนๆ™‚", + "performance-mode-disabled-placeholder": "ไพ‹๏ผšnotify-send \"Performance\" \"Mode disabled\"", + "performance-mode-enabled-description": "Noctalia ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใƒขใƒผใƒ‰ใŒๆœ‰ๅŠนใซใชใฃใŸ้š›ใซๅฎŸ่กŒใ•ใ‚Œใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚", + "performance-mode-enabled-label": "ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใƒขใƒผใƒ‰ๆœ‰ๅŠนๆ™‚", + "performance-mode-enabled-placeholder": "ไพ‹๏ผšnotify-send \"Performance\" \"Mode enabled\"", + "screen-lock-description": "็”ป้ขใŒใƒญใƒƒใ‚ฏใ•ใ‚ŒใŸ้š›ใซๅฎŸ่กŒใ•ใ‚Œใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚", + "screen-lock-label": "็”ป้ขใƒญใƒƒใ‚ฏๆ™‚", + "screen-lock-placeholder": "ไพ‹๏ผšnotify-send \"Screen\" \"Locked\"", + "screen-unlock-description": "็”ป้ขใƒญใƒƒใ‚ฏใŒ่งฃ้™คใ•ใ‚ŒใŸ้š›ใซๅฎŸ่กŒใ•ใ‚Œใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚", + "screen-unlock-label": "็”ป้ขใƒญใƒƒใ‚ฏ่งฃ้™คๆ™‚", + "screen-unlock-placeholder": "ไพ‹๏ผšnotify-send \"Screen\" \"Unlocked\"", + "session-description": "ใ‚ทใƒฃใƒƒใƒˆใƒ€ใ‚ฆใƒณใพใŸใฏๅ†่ตทๅ‹•ใฎๅ‰ใซๅฎŸ่กŒใ•ใ‚Œใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚ใ‚ขใ‚ฏใ‚ทใƒงใƒณใ‚ฟใ‚คใƒ—ใ‚’ $1 (ใ‚ทใƒฃใƒƒใƒˆใƒ€ใ‚ฆใƒณ/ๅ†่ตทๅ‹•) ใจใ—ใฆๅ—ใ‘ๅ–ใ‚Šใพใ™ใ€‚", + "session-label": "ใ‚ปใƒƒใ‚ทใƒงใƒณ็ต‚ไบ†", + "session-placeholder": "ไพ‹๏ผšnotify-send \"Session\" \"$1\"", + "system-hooks-desc": "ใ‚ทใ‚นใƒ†ใƒ ใ‚คใƒ™ใƒณใƒˆใŒ็™บ็”Ÿใ—ใŸ้š›ใซๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "system-hooks-enable-description": "ใ™ในใฆใฎใƒ•ใƒƒใ‚ฏใ‚ณใƒžใƒณใƒ‰ใฎๆœ‰ๅŠนใƒป็„กๅŠนใ‚’ๅˆ‡ใ‚Šๆ›ฟใˆใพใ™ใ€‚", + "system-hooks-enable-label": "ใƒ•ใƒƒใ‚ฏใฎๆœ‰ๅŠนๅŒ–", + "system-hooks-title": "ใ‚ทใ‚นใƒ†ใƒ ใƒ•ใƒƒใ‚ฏ", + "theme-changed-description": "ใƒ€ใƒผใ‚ฏใƒขใƒผใƒ‰ใจใƒฉใ‚คใƒˆใƒขใƒผใƒ‰ใŒๅˆ‡ใ‚Šๆ›ฟใ‚ใฃใŸ้š›ใซๅฎŸ่กŒใ•ใ‚Œใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚", + "theme-changed-label": "ใƒ†ใƒผใƒžๅค‰ๆ›ดๆ™‚", + "theme-changed-placeholder": "ไพ‹๏ผšnotify-send \"Theme\" \"Toggled\"", + "title": "ใƒ•ใƒƒใ‚ฏ", + "wallpaper-changed-description": "ๅฃ็ด™ใŒๅค‰ๆ›ดใ•ใ‚ŒใŸ้š›ใซๅฎŸ่กŒใ•ใ‚Œใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚", + "wallpaper-changed-label": "ๅฃ็ด™ๅค‰ๆ›ดๆ™‚", + "wallpaper-changed-placeholder": "ไพ‹๏ผšnotify-send \"Wallpaper\" \"Changed\"" + }, + "idle": { + "custom-add": "ใ‚ณใƒžใƒณใƒ‰ใ‚’่ฟฝๅŠ ", + "custom-description": "ไธ€ๅฎšๆœŸ้–“ใฎ้žใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–็Šถๆ…‹ใฎๅพŒใ€ใ‚ทใ‚งใƒซใ‚ณใƒžใƒณใƒ‰ใ‚’ๅฎŸ่กŒใ—ใพใ™ใ€‚", + "custom-entry-command": "ใ‚ณใƒžใƒณใƒ‰", + "custom-entry-delete": "ๅ‰Š้™ค", + "custom-entry-edit": "ใ‚ซใ‚นใ‚ฟใƒ ใ‚ณใƒžใƒณใƒ‰ใ‚’็ทจ้›†", + "custom-entry-name": "ๅๅ‰", + "custom-entry-name-placeholder": "ไพ‹๏ผšใƒฉใ‚คใƒˆใ‚’ๆถˆใ™", + "custom-entry-new": "ๆ–ฐใ—ใ„ใ‚ซใ‚นใ‚ฟใƒ ใ‚ณใƒžใƒณใƒ‰", + "custom-entry-timeout": "้žใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ๆ™‚้–“", + "custom-entry-timeout-format": "{count} ็ง’", + "custom-entry-timeout-format-plural": "{count} ็ง’", + "custom-entry-unnamed": "ๅๅ‰ใฎใชใ„ใ‚ณใƒžใƒณใƒ‰", + "custom-label": "ใ‚ซใ‚นใ‚ฟใƒ ใ‚ขใ‚คใƒ‰ใƒซใ‚ณใƒžใƒณใƒ‰", + "enable-description": "ไธ€ๅฎšๆ™‚้–“ๆ“ไฝœใŒใชใ„ๅ ดๅˆใ€่‡ชๅ‹•็š„ใซ็”ป้ขใ‚’ใ‚ชใƒ•ใซใ™ใ‚‹ใ€ใƒญใƒƒใ‚ฏใ™ใ‚‹ใ€ใพใŸใฏใ‚ตใ‚นใƒšใƒณใƒ‰ใ™ใ‚‹ใ€‚", + "enable-label": "ใ‚ขใ‚คใƒ‰ใƒซ็ฎก็†ใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹", + "fade-duration-description": "ๅ„ใ‚ขใ‚ฏใ‚ทใƒงใƒณใŒๅฎŸ่กŒใ•ใ‚Œใ‚‹ๅ‰ใฎใ€็”ป้ขใŒๆš—่ปขใ™ใ‚‹ใ‚ขใƒ‹ใƒกใƒผใ‚ทใƒงใƒณใฎ็ง’ๆ•ฐใ€‚ใƒžใ‚ฆใ‚นใ‚’ๅ‹•ใ‹ใ™ใจใƒ•ใ‚งใƒผใƒ‰ใฏใ‚ญใƒฃใƒณใ‚ปใƒซใ•ใ‚Œใพใ™ใ€‚", + "fade-duration-label": "ใƒ•ใ‚งใƒผใƒ‰ๆ™‚้–“", + "lock-description": "ใƒญใƒƒใ‚ฏ็”ป้ขใŒใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใซใชใ‚‹ใพใงใฎ้žใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–็ง’ๆ•ฐใ€‚", + "lock-label": "็”ป้ขใ‚’ใƒญใƒƒใ‚ฏ", + "resume-command-label": "ใ‚ณใƒžใƒณใƒ‰ใ‚’ๅ†้–‹", + "screen-off-description": "ใƒขใƒ‹ใ‚ฟใƒผใŒใ‚ชใƒ•ใซใชใ‚‹ใพใงใฎ้žใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใช็ง’ๆ•ฐใ€‚", + "screen-off-label": "็”ป้ขใ‚’ใ‚ชใƒ•ใซใ™ใ‚‹", + "status-description": "ใ‚ณใƒณใƒใ‚ธใ‚ฟใƒผใซใ‚ˆใฃใฆๅ ฑๅ‘Šใ•ใ‚ŒใŸใ‚ขใ‚คใƒ‰ใƒซๆ™‚้–“ใ€‚", + "status-label": "ใ‚ขใ‚คใƒ‰ใƒซๆ™‚้–“", + "suspend-description": "ใ‚ทใ‚นใƒ†ใƒ ใŒใ‚ตใ‚นใƒšใƒณใƒ‰ใ™ใ‚‹ใพใงใฎ้žใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใชๆ™‚้–“๏ผˆ็ง’๏ผ‰ใ€‚", + "tab-behavior": "ๅ‹•ไฝœ", + "tab-custom": "ใ‚ซใ‚นใ‚ฟใƒ ", + "timeouts-description": "ใ‚นใƒ†ใƒผใ‚ธใ‚’็„กๅŠนใซใ™ใ‚‹ใซใฏ0ใซ่จญๅฎšใ—ใพใ™ใ€‚ใ‚นใƒชใƒผใƒ—้˜ฒๆญขใŒใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใช้–“ใ€ใ‚ฟใ‚คใƒ ใ‚ขใ‚ฆใƒˆใฏไธ€ๆ™‚ๅœๆญขใ•ใ‚Œใพใ™ใ€‚", + "timeouts-label": "ใ‚ฟใ‚คใƒ ใ‚ขใ‚ฆใƒˆ", + "title": "ใ‚ขใ‚คใƒ‰ใƒซ", + "unavailable": "ใ“ใฎCompositorใงใฏใ€ใƒใ‚คใƒ†ใ‚ฃใƒ–ใฎใ‚ขใ‚คใƒ‰ใƒซ็›ฃ่ฆ–ใฏๅˆฉ็”จใงใใพใ›ใ‚“ใ€‚" + }, + "indicator": { + "default-value": "ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆ๏ผš{value}", + "system-default": "ใ‚ทใ‚นใƒ†ใƒ ใฎใƒ‡ใƒ•ใ‚ฉใƒซใƒˆ" + }, + "launcher": { + "clipboard-desc": "ใƒฉใƒณใƒใƒฃใƒผใ‹ใ‚‰ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ใฎๅฑฅๆญดใซใ‚ขใ‚ฏใ‚ปใ‚นใ—ใฆ็ฎก็†ใงใใพใ™ใ€‚", + "execute-desc": "ใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใฎ่ตทๅ‹•ๆ–นๆณ•ใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "execute-title": "ๅฎŸ่กŒ", + "settings-annotation-tool-description": "ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ๅฑฅๆญดใฎใ‚ขใƒŽใƒ†ใƒผใ‚ทใƒงใƒณใƒœใ‚ฟใƒณใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใŸใจใใซๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ€‚็”ปๅƒใฏใ“ใฎใ‚ณใƒžใƒณใƒ‰ใซใƒ‘ใ‚คใƒ—ใ•ใ‚Œใพใ™ใ€‚", + "settings-annotation-tool-label": "ๆณจ้‡ˆใƒ„ใƒผใƒซ", + "settings-annotation-tool-placeholder": "ไพ‹๏ผš'gradia', 'satty -f -'", + "settings-auto-paste-description": "้ธๆŠžใ•ใ‚ŒใŸใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ใฎ้ …็›ฎใ‚’่‡ชๅ‹•็š„ใซ่ฒผใ‚Šไป˜ใ‘ใพใ™ใ€‚wtypeใŒๅฟ…่ฆใงใ™ใ€‚", + "settings-auto-paste-label": "่‡ชๅ‹•่ฒผใ‚Šไป˜ใ‘", + "settings-clip-chips-description": "ใ‚ฟใƒ–ใƒใƒผใ‚’่กจ็คบใ—ใฆใ€ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ใฎๅฑฅๆญดใ‚’ใ‚ฟใ‚คใƒ—ๅˆฅ๏ผˆ็”ปๅƒใ€ใƒชใƒณใ‚ฏใ€ใƒ•ใ‚กใ‚คใƒซใ€ใ‚ณใƒผใƒ‰ใชใฉ๏ผ‰ใซใƒ•ใ‚ฃใƒซใ‚ฟใƒชใƒณใ‚ฐใ—ใพใ™ใ€‚", + "settings-clip-chips-label": "ใ‚ซใƒ†ใ‚ดใƒชใƒใƒƒใƒ—ใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹", + "settings-clip-preview-description": ">clip ใ‚ณใƒžใƒณใƒ‰ไฝฟ็”จๆ™‚ใซใ€ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ใฎๅ†…ๅฎนใ‚’ใƒ—ใƒฌใƒ“ใƒฅใƒผ่กจ็คบใ—ใพใ™ใ€‚", + "settings-clip-preview-label": "ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ใฎใƒ—ใƒฌใƒ“ใƒฅใƒผใ‚’ๆœ‰ๅŠนๅŒ–", + "settings-clip-smart-icons-description": "ไธ€่ˆฌ็š„ใชใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ใ‚ขใ‚คใ‚ณใƒณใฎไปฃใ‚ใ‚Šใซใ€ใƒชใƒณใ‚ฏใ€ใƒ•ใ‚กใ‚คใƒซใ€่‰ฒใ€ใ‚ณใƒผใƒ‰ใซๅ›บๆœ‰ใฎใ‚ขใ‚คใ‚ณใƒณใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "settings-clip-smart-icons-label": "ใ‚นใƒžใƒผใƒˆใ‚ขใ‚คใ‚ณใƒณใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹", + "settings-clip-wrap-text-description": "ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ใฎใƒชใ‚นใƒˆใงใƒ†ใ‚ญใ‚นใƒˆใ‚’ๅˆ‡ใ‚Šๆจใฆใ‚‹ไปฃใ‚ใ‚Šใซใ€ๆŠ˜ใ‚Š่ฟ”ใ—ใฆ่กจ็คบใ™ใ‚‹ใ€‚", + "settings-clip-wrap-text-label": "ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ใฎใƒ†ใ‚ญใ‚นใƒˆใ‚’ๆŠ˜ใ‚Š่ฟ”ใ™", + "settings-clipboard-history-description": "ไปฅๅ‰ใ‚ณใƒ”ใƒผใ—ใŸ้ …็›ฎใซใƒฉใƒณใƒใƒฃใƒผใ‹ใ‚‰ใ‚ขใ‚ฏใ‚ปใ‚นใงใใ‚‹ใ‚ˆใ†ใซใ—ใพใ™ใ€‚", + "settings-clipboard-history-label": "ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ๅฑฅๆญดใ‚’ๆœ‰ๅŠนๅŒ–", + "settings-clipboard-watch-image-description": "็”ปๅƒๅค‰ๆ›ดใฎใŸใ‚ใซwl-pasteใซๆธกใ•ใ‚Œใ‚‹ๅฎŒๅ…จใชใ‚ณใƒžใƒณใƒ‰ๆ–‡ๅญ—ๅˆ—๏ผˆๅ†่ตทๅ‹•ใŒๅฟ…่ฆ๏ผ‰ใ€‚", + "settings-clipboard-watch-image-label": "็”ปๅƒ็›ฃ่ฆ–ใ‚ณใƒžใƒณใƒ‰", + "settings-clipboard-watch-text-description": "ใƒ†ใ‚ญใ‚นใƒˆๅค‰ๆ›ดใฎใŸใ‚ใซ wl-paste ใซๆธกใ•ใ‚Œใ‚‹ๅฎŒๅ…จใชใ‚ณใƒžใƒณใƒ‰ๆ–‡ๅญ—ๅˆ—๏ผˆๅ†่ตทๅ‹•ใŒๅฟ…่ฆ๏ผ‰ใ€‚", + "settings-clipboard-watch-text-label": "ใƒ†ใ‚ญใ‚นใƒˆ็›ฃ่ฆ–ใ‚ณใƒžใƒณใƒ‰", + "settings-custom-launch-prefix-description": "ใ‚ณใƒžใƒณใƒ‰ใฎๅ…ˆ้ ญใซใ‚ซใ‚นใ‚ฟใƒ ใƒฉใƒณใƒใƒฃใƒผใ‚’ไป˜ไธŽใ—ใพใ™๏ผˆไพ‹๏ผšsystemd ้€ฃๆบ็”จใฎ 'runapp'๏ผ‰ใ€‚", + "settings-custom-launch-prefix-enabled-description": "ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฎๆ–นๆณ•ใฎไปฃใ‚ใ‚Šใซใ€ใ‚ซใ‚นใ‚ฟใƒ ใƒ—ใƒฌใƒ•ใ‚ฃใƒƒใ‚ฏใ‚นใ‚’ไฝฟ็”จใ—ใฆใ‚ขใƒ—ใƒชใ‚’่ตทๅ‹•ใ—ใพใ™ใ€‚", + "settings-custom-launch-prefix-enabled-label": "ใ‚ซใ‚นใ‚ฟใƒ ่ตทๅ‹•ใƒ—ใƒฌใƒ•ใ‚ฃใƒƒใ‚ฏใ‚นใ‚’ๆœ‰ๅŠนๅŒ–", + "settings-custom-launch-prefix-label": "ใ‚ซใ‚นใ‚ฟใƒ ่ตทๅ‹•ใƒ—ใƒฌใƒ•ใ‚ฃใƒƒใ‚ฏใ‚น", + "settings-density-description": "ใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใ‚ขใ‚คใ‚ณใƒณใฎใ‚ตใ‚คใ‚บใจLauncherใฎๅฏ†ๅบฆใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "settings-density-label": "ๅฏ†ๅบฆ", + "settings-desc": "ใƒฉใƒณใƒใƒฃใƒผใฎๆŒ™ๅ‹•ใจๅค–่ฆณใ‚’ใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บใ—ใพใ™ใ€‚", + "settings-enable-session-search-description": "Launcherใงๆคœ็ดขใ™ใ‚‹้š›ใซใ€ใ‚ปใƒƒใ‚ทใƒงใƒณใ‚ขใ‚ฏใ‚ทใƒงใƒณ๏ผˆใƒญใƒƒใ‚ฏใ€ใ‚ทใƒฃใƒƒใƒˆใƒ€ใ‚ฆใƒณใ€ๅ†่ตทๅ‹•ใชใฉ๏ผ‰ใ‚’่กจ็คบใ™ใ‚‹ใ€‚", + "settings-enable-session-search-label": "ใ‚ปใƒƒใ‚ทใƒงใƒณๆคœ็ดขใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹", + "settings-enable-settings-search-description": "ใƒฉใƒณใƒใƒฃใƒผใงๆคœ็ดขใ™ใ‚‹้š›ใซ่จญๅฎš็ตๆžœใ‚’่กจ็คบใ™ใ‚‹ใ€‚", + "settings-enable-settings-search-label": "่จญๅฎšๆคœ็ดขใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹", + "settings-enable-windows-search-description": "ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใชใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใ‚’ๆคœ็ดขใ—ใฆใƒ•ใ‚ฉใƒผใ‚ซใ‚นใ—ใพใ™ใ€‚", + "settings-enable-windows-search-label": "ใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆๆคœ็ดขใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹", + "settings-grid-view-description": "ใƒชใ‚นใƒˆๅฝขๅผใฎไปฃใ‚ใ‚Šใซใ€ใ‚ฐใƒชใƒƒใƒ‰๏ผˆๆ ผๅญ็Šถ๏ผ‰ใƒฌใ‚คใ‚ขใ‚ฆใƒˆใง้ …็›ฎใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "settings-icon-mode-description": "Tablerใ‚ขใ‚คใ‚ณใƒณใงใฏใชใใ€ใƒใ‚คใƒ†ใ‚ฃใƒ–ใฎใ‚ทใ‚นใƒ†ใƒ ใ‚ขใ‚คใ‚ณใƒณใ‚’ไฝฟ็”จใ—ใพใ™ใ€‚", + "settings-icon-mode-label": "ใƒใ‚คใƒ†ใ‚ฃใƒ–ใ‚ขใ‚คใ‚ณใƒณใ‚’ไฝฟ็”จ", + "settings-ignore-mouse-input-description": "ใƒฉใƒณใƒใƒฃใƒผใงใฎใƒžใ‚ฆใ‚นๆ“ไฝœใจใ‚นใ‚ฏใƒญใƒผใƒซใƒ›ใ‚คใƒผใƒซใ‚’็„กๅŠนใซใ™ใ‚‹ใ€‚", + "settings-ignore-mouse-input-label": "ใƒžใ‚ฆใ‚นๅ…ฅๅŠ›ใ‚’็„ก่ฆ–ใ™ใ‚‹", + "settings-overlay-layer-description": "ใƒฉใƒณใƒใƒฃใƒผใ‚’ใ‚ชใƒผใƒใƒผใƒฌใ‚คใƒฌใ‚คใƒคใƒผใซใ€ๅ…จ็”ป้ขใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใฎไธŠใซ่กจ็คบใ—ใพใ™ใ€‚ๆœ‰ๅŠนใซใ™ใ‚‹ใจใ€ใƒฉใƒณใƒใƒฃใƒผใฏใƒใƒผใซใ‚ขใ‚ฟใƒƒใƒใ•ใ‚Œใพใ›ใ‚“ใ€‚", + "settings-overlay-layer-label": "ใƒ•ใƒซใ‚นใ‚ฏใƒชใƒผใƒณใ‚ˆใ‚ŠไธŠใซ่กจ็คบ", + "settings-position-description": "ใƒฉใƒณใƒใƒฃใƒผใƒ‘ใƒใƒซใฎ่กจ็คบไฝ็ฝฎใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "settings-show-categories-description": "ใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใ‚’็ตžใ‚Š่พผใ‚€ใŸใ‚ใฎใ‚ซใƒ†ใ‚ดใƒชใ‚ฟใƒ–ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "settings-show-categories-label": "ใ‚ซใƒ†ใ‚ดใƒชใ‚’่กจ็คบ", + "settings-show-icon-background-description": "ใ‚ขใ‚คใ‚ณใƒณใฎ่ƒŒ้ขใซ่ง’ไธธใฎ้•ทๆ–นๅฝขใฎ่ƒŒๆ™ฏใ‚’่กจ็คบใ™ใ‚‹ใ€‚", + "settings-show-icon-background-label": "ใ‚ขใ‚คใ‚ณใƒณใฎ่ƒŒๆ™ฏใ‚’่กจ็คบ", + "settings-sort-by-usage-description": "ๆœ‰ๅŠนใซใ™ใ‚‹ใจใ€ใ‚ˆใไฝฟใ†ใ‚ขใƒ—ใƒชใŒใƒชใ‚นใƒˆใฎไธŠไฝใซ่กจ็คบใ•ใ‚Œใพใ™ใ€‚", + "settings-sort-by-usage-label": "ไฝฟ็”จ้ ปๅบฆ้ †ใซไธฆในๆ›ฟใˆ", + "settings-terminal-command-description": "ใ‚ฟใƒผใƒŸใƒŠใƒซใ‚’่ตทๅ‹•ใ™ใ‚‹ใŸใ‚ใฎใ‚ณใƒžใƒณใƒ‰๏ผˆไพ‹๏ผš'kitty -e' or 'gnome-terminal --'๏ผ‰ใ€‚", + "settings-terminal-command-label": "ใ‚ฟใƒผใƒŸใƒŠใƒซ่ตทๅ‹•ใ‚ณใƒžใƒณใƒ‰", + "settings-use-app2unit-description": "ใ‚ขใƒ—ใƒชใฎใƒ—ใƒญใ‚ปใ‚น็ฎก็†ใ‚’ๆ”นๅ–„ใ—ใ€ๅ•้กŒใ‚’ๅ›ž้ฟใ™ใ‚‹ใŸใ‚ใซไปฃๆ›ฟใฎ่ตทๅ‹•ๆ–นๆณ•ใ‚’ไฝฟ็”จใ—ใพใ™ใ€‚", + "settings-use-app2unit-label": "App2Unit ใ‚’ไฝฟ็”จใ—ใฆ่ตทๅ‹•", + "settings-view-mode-description": "ใƒฉใƒณใƒใƒฃใƒผใฎใ‚จใƒณใƒˆใƒชใฎใƒฌใ‚คใ‚ขใ‚ฆใƒˆใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "settings-view-mode-label": "่กจ็คบใƒขใƒผใƒ‰", + "title": "ใƒฉใƒณใƒใƒฃใƒผ" + }, + "location": { + "auto-locate-description": "IPใ‚ขใƒ‰ใƒฌใ‚นใ‚’ไฝฟ็”จใ—ใฆใ€ๅฎšๆœŸ็š„ใซ็พๅœจๅœฐใ‚’่‡ชๅ‹•ๆคœๅ‡บใ—ใพใ™ใ€‚", + "auto-locate-label": "่‡ชๅ‹•ๆคœๅ‡บ", + "calendar-cards-desc": "ใ‚ซใƒฌใƒณใƒ€ใƒผใƒ‘ใƒใƒซใฎใ‚ซใƒผใƒ‰ใ‚’ๆ•ด็†ใ—ใ€ๆœ‰ๅŠนใƒป็„กๅŠนใ‚’ๅˆ‡ใ‚Šๆ›ฟใˆใพใ™ใ€‚", + "calendar-cards-title": "ใ‚ซใƒฌใƒณใƒ€ใƒผใ‚ซใƒผใƒ‰", + "calendar-header-label": "ใ‚ซใƒฌใƒณใƒ€ใƒผใƒ˜ใƒƒใƒ€ใƒผ", + "calendar-month-label": "ใ‚ซใƒฌใƒณใƒ€ใƒผ๏ผˆๆœˆ่กจ็คบ๏ผ‰", + "date-time-12hour-format-description": "ใƒญใƒƒใ‚ฏ็”ป้ขใจใ‚ซใƒฌใƒณใƒ€ใƒผใงๆ™‚ๅˆปใ‚’12ๆ™‚้–“่กจ่จ˜ใง่กจ็คบใ—ใพใ™ใ€‚ใƒใƒผใฎๆ™‚่จˆใซใฏ็‹ฌ่‡ชใฎ่จญๅฎšใŒใ‚ใ‚Šใพใ™ใ€‚", + "date-time-12hour-format-label": "12ๆ™‚้–“่กจ่จ˜ใ‚’ไฝฟ็”จ", + "date-time-desc": "ๆ—ฅไป˜ใจๆ™‚ๅˆปใฎ่กจ็คบๅฝขๅผใ‚’ใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บใ—ใพใ™ใ€‚", + "date-time-first-day-of-week-automatic": "่‡ชๅ‹•๏ผˆใ‚ทใ‚นใƒ†ใƒ ใฎใƒญใ‚ฑใƒผใƒซใ‚’ไฝฟ็”จ๏ผ‰", + "date-time-first-day-of-week-description": "ใ‚ซใƒฌใƒณใƒ€ใƒผใฎ้€ฑใฎๅง‹ใพใ‚Šใฎๆ›œๆ—ฅใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "date-time-first-day-of-week-label": "้€ฑใฎๅง‹ใพใ‚Š", + "date-time-show-events-description": "ใ‚ซใƒฌใƒณใƒ€ใƒผใƒ‘ใƒใƒซใซใ‚คใƒ™ใƒณใƒˆ๏ผˆไบˆๅฎš๏ผ‰ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "date-time-show-events-label": "ใ‚ซใƒฌใƒณใƒ€ใƒผใฎใ‚คใƒ™ใƒณใƒˆใ‚’่กจ็คบ", + "date-time-title": "ๆ—ฅไป˜ใจๆ™‚ๅˆป", + "date-time-use-analog-description": "ใ‚ซใƒฌใƒณใƒ€ใƒผใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใซใ‚ขใƒŠใƒญใ‚ฐใ‚นใ‚ฟใ‚คใƒซใฎๆ™‚่จˆใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "date-time-use-analog-label": "ใ‚ขใƒŠใƒญใ‚ฐๆ™‚่จˆใ‚’ไฝฟ็”จ", + "date-time-week-numbers-description": "ใ‚ซใƒฌใƒณใƒ€ใƒผใซ้€ฑ็•ชๅท๏ผˆไพ‹๏ผš็ฌฌ38้€ฑ๏ผ‰ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "date-time-week-numbers-label": "้€ฑ็•ชๅทใ‚’่กจ็คบ", + "geolocate-now-button": "ไปŠใ™ใๆคœ็ดข", + "location-desc": "ๅ ดๆ‰€ใ‚’่จญๅฎšใ—ใฆใ€ๆญฃ็ขบใชๅคฉๆฐ—ๆƒ…ๅ ฑใ‚„ๅคœ้–“ใƒขใƒผใƒ‰ใฎใ‚นใ‚ฑใ‚ธใƒฅใƒผใƒซใ‚’ๅ–ๅพ—ใ—ใพใ™ใ€‚", + "location-search-description": "ไพ‹๏ผšๆฑไบฌใ€ๆ—ฅๆœฌ", + "location-search-label": "ๅ ดๆ‰€ใ‚’ๆคœ็ดข", + "location-search-placeholder": "ๅœฐๅใ‚’ๅ…ฅๅŠ›", + "location-title": "ๅ ดๆ‰€ใฎ่จญๅฎš", + "weather-desc": "ๆธฉๅบฆใฎๅ˜ไฝใชใฉใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "weather-enabled-description": "ใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใƒผใ‚นๅ…จไฝ“ใซๅคฉๆฐ—ๆƒ…ๅ ฑใ‚’่กจ็คบใ—ใ€ใƒ‡ใƒผใ‚ฟใ‚’ๅ–ๅพ—ใ—ใพใ™ใ€‚็„กๅŠนใซใ™ใ‚‹ใจใ€ใ™ในใฆใฎๅคฉๆฐ—่ฆ็ด ใŒ้ž่กจ็คบใซใชใ‚Šใ€ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏ้€šไฟกใ‚‚่กŒใ‚ใ‚Œใพใ›ใ‚“ใ€‚", + "weather-enabled-label": "ๅคฉๆฐ—ใ‚’ๆœ‰ๅŠนๅŒ–", + "weather-fahrenheit-description": "ๆฐ—ๆธฉใ‚’ๆ‘‚ๆฐ (ยฐC) ใงใฏใชใ่ฏๆฐ (ยฐF) ใง่กจ็คบใ—ใพใ™ใ€‚", + "weather-fahrenheit-label": "่ฏๆฐ (ยฐF) ใง่กจ็คบ", + "weather-hide-city-description": "ใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใƒผใ‚นๅ…จไฝ“ใฎๅคฉๆฐ—่กจ็คบใ‹ใ‚‰้ƒฝๅธ‚ๅใ‚’้š ใ—ใฆใใ ใ•ใ„ใ€‚", + "weather-hide-city-label": "้ƒฝๅธ‚ๅใ‚’้š ใ™", + "weather-hide-timezone-description": "ใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใƒผใ‚นๅ…จไฝ“ใฎๅคฉๆฐ—่กจ็คบใ‹ใ‚‰ใ‚ฟใ‚คใƒ ใ‚พใƒผใƒณใฎ็•ฅ็งฐใ‚’้ž่กจ็คบใซใ™ใ‚‹ใ€‚", + "weather-hide-timezone-label": "ใ‚ฟใ‚คใƒ ใ‚พใƒผใƒณใ‚’้š ใ™", + "weather-show-effects-description": "ๅคฉๆฐ—ใ‚ซใƒผใƒ‰ใซ่ฆ–่ฆšๅŠนๆžœ๏ผˆ้›จใ€้›ชใ€้›ทใชใฉ๏ผ‰ใ‚’่ฟฝๅŠ ่กจ็คบใ—ใพใ™ใ€‚", + "weather-show-effects-label": "ๅคฉๆฐ—ใ‚จใƒ•ใ‚งใ‚ฏใƒˆใ‚’่กจ็คบ", + "weather-show-in-calendar-description": "ใ‚ซใƒฌใƒณใƒ€ใƒผใƒ“ใƒฅใƒผใซๆฏŽๆ—ฅใฎๅคฉๆฐ—ไบˆๅ ฑใ‚’็›ดๆŽฅ่กจ็คบใ—ใพใ™ใ€‚", + "weather-show-in-calendar-label": "ใ‚ซใƒฌใƒณใƒ€ใƒผ", + "weather-talia-mascot-always-description": "tabler icons ใฎไปฃใ‚ใ‚Šใซๅธธใซ Talia emojis ใ‚’ไฝฟ็”จใ—ใฆใใ ใ•ใ„ใ€‚", + "weather-talia-mascot-always-label": "Taliaใฎๅคฉๆฐ—็ตตๆ–‡ๅญ—ใ‚’ๅธธใซ่กจ็คบใ™ใ‚‹" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "fprintd๏ผˆๆŒ‡็ด‹่ช่จผ๏ผ‰ใŒๆœ‰ๅŠนใชๅ ดๅˆใ€ใ“ใฎใ‚ชใƒ—ใ‚ทใƒงใƒณใ‚’ไฝฟ็”จใ™ใ‚‹ใจใ€ๆŒ‡็ด‹ใฎไปฃใ‚ใ‚Šใซใƒ‘ใ‚นใƒฏใƒผใƒ‰ใงใƒญใ‚ฐใ‚คใƒณใงใใพใ™ใ€‚", + "allow-password-with-fprintd-label": "fprintd ใงใƒ‘ใ‚นใƒฏใƒผใƒ‰ใƒญใ‚ฐใ‚คใƒณใ‚’่จฑๅฏใ™ใ‚‹", + "auto-start-auth-description": "ใŸใจใˆใฐใ€ใ‚ญใƒผใ‚’ๆŠผใ—ใŸใ‚Šใƒœใ‚ฟใƒณใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใŸใ‚Šใ—ใชใใฆใ‚‚ใ€ๆŒ‡็ด‹่ช่จผใŒ่‡ชๅ‹•็š„ใซ้–‹ๅง‹ใ•ใ‚Œใพใ™ใ€‚", + "auto-start-auth-label": "่‡ชๅ‹•่ช่จผใ‚’้–‹ๅง‹", + "clock-format-description": "ๆ—ฅไป˜/ๆ™‚ๅˆปใฎๆง‹ๆ–‡ใƒˆใƒผใ‚ฏใƒณใ‚’ไฝฟ็”จใ—ใฆๆ™‚่จˆใฎใƒ•ใ‚ฉใƒผใƒžใƒƒใƒˆใ‚’ใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บใ—ใพใ™ใ€‚", + "clock-format-label": "ๆ™‚่จˆใฎๅฝขๅผ", + "clock-style-analog": "ใ‚ขใƒŠใƒญใ‚ฐ", + "clock-style-custom": "ใ‚ซใ‚นใ‚ฟใƒ ", + "clock-style-description": "ใƒญใƒƒใ‚ฏ็”ป้ขใฎๆ™‚่จˆใฎ่กจ็คบใ‚นใ‚ฟใ‚คใƒซใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "clock-style-digital": "ใƒ‡ใ‚ธใ‚ฟใƒซ", + "clock-style-label": "ๆ™‚่จˆใฎใ‚นใ‚ฟใ‚คใƒซ", + "compact-lockscreen-description": "ๅคฉๆฐ—ใ‚„ใƒกใƒ‡ใ‚ฃใ‚ขใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’้š ใ—ใ€ใƒญใ‚ฐใ‚คใƒณๅ…ฅๅŠ›ใจใ‚ทใ‚นใƒ†ใƒ ใ‚ณใƒณใƒˆใƒญใƒผใƒซใฎใฟใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "compact-lockscreen-label": "ใ‚ณใƒณใƒ‘ใ‚ฏใƒˆใชใƒญใƒƒใ‚ฏ็”ป้ข", + "enable-lockscreen-media-controls-description": "ใƒญใƒƒใ‚ฏ็”ป้ขใซใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ–ใชใƒกใƒ‡ใ‚ฃใ‚ขๅ†็”Ÿใ‚ณใƒณใƒˆใƒญใƒผใƒซใ‚’่กจ็คบใ™ใ‚‹ใ€‚", + "enable-lockscreen-media-controls-label": "ใƒญใƒƒใ‚ฏ็”ป้ขใฎใƒกใƒ‡ใ‚ฃใ‚ขใ‚ณใƒณใƒˆใƒญใƒผใƒซ", + "lock-on-suspend-description": "ใ‚ทใ‚นใƒ†ใƒ ใฎใ‚ตใ‚นใƒšใƒณใƒ‰ๆ™‚ใซใ€่‡ชๅ‹•็š„ใซ็”ป้ขใ‚’ใƒญใƒƒใ‚ฏใ—ใพใ™ใ€‚", + "lock-on-suspend-label": "ใ‚ตใ‚นใƒšใƒณใƒ‰ๆ™‚ใซใƒญใƒƒใ‚ฏ", + "lock-screen-animations-description": "ใƒญใƒƒใ‚ฏ็”ป้ขใฎใ‚ขใƒ‹ใƒกใƒผใ‚ทใƒงใƒณใ‚’ๆœ‰ๅŠนใพใŸใฏ็„กๅŠนใซใ™ใ‚‹ใ€‚", + "lock-screen-animations-label": "ใƒญใƒƒใ‚ฏ็”ป้ขใฎใ‚ขใƒ‹ใƒกใƒผใ‚ทใƒงใƒณ", + "lock-screen-blur-strength-description": "ใƒญใƒƒใ‚ฏ็”ป้ขใฎๅฃ็ด™ใซใผใ‹ใ—ๅŠนๆžœใ‚’้ฉ็”จใ—ใพใ™ใ€‚", + "lock-screen-blur-strength-label": "ใƒญใƒƒใ‚ฏ็”ป้ขใฎใผใ‹ใ—ๅผทๅบฆ", + "lock-screen-tint-strength-description": "ใƒญใƒƒใ‚ฏ็”ป้ขใฎๅฃ็ด™ใซ่‰ฒๅˆใ„ใฎใ‚ชใƒผใƒใƒผใƒฌใ‚คใ‚’้ฉ็”จใ—ใพใ™ใ€‚", + "lock-screen-tint-strength-label": "ใƒญใƒƒใ‚ฏ็”ป้ขใฎ็€่‰ฒๅผทๅบฆ", + "monitors-desc": "ใƒญใƒƒใ‚ฏ็”ป้ขใ‚’็‰นๅฎšใฎใƒขใƒ‹ใ‚ฟใƒผใซ่กจ็คบใ—ใพใ™ใ€‚ไฝ•ใ‚‚้ธๆŠžใ•ใ‚Œใฆใ„ใชใ„ๅ ดๅˆใ€ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใงใ™ในใฆใซ่กจ็คบใ•ใ‚Œใพใ™ใ€‚", + "password-chars-description": "ใƒ‘ใ‚นใƒฏใƒผใƒ‰ใ‚’้š ใ™ใŸใ‚ใฎใ‹ใ‚ใ„ใ„ใ‚ขใ‚คใ‚ณใƒณใ€‚", + "password-chars-label": "ใƒฉใƒณใƒ€ใƒ ใชใƒ‘ใ‚นใƒฏใƒผใƒ‰ใ‚ขใ‚คใ‚ณใƒณ", + "show-hibernate-description": "้›ปๆบๆ“ไฝœใซใ€Œใƒใ‚คใƒใƒใƒผใƒˆ๏ผˆไผ‘ๆญข็Šถๆ…‹๏ผ‰ใ€ใฎใ‚ชใƒ—ใ‚ทใƒงใƒณใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "show-hibernate-label": "ใƒใ‚คใƒใƒใƒผใƒˆใ‚’่กจ็คบ", + "show-session-buttons-description": "ใƒญใƒƒใ‚ฏ็”ป้ขใ‹ใ‚‰้›ปๆบๆ“ไฝœใ‚’่กŒใˆใ‚‹ใ‚ˆใ†ใซใ—ใพใ™ใ€‚", + "show-session-buttons-label": "้›ปๆบๆ“ไฝœ", + "title": "ใƒญใƒƒใ‚ฏ็”ป้ข" + }, + "network": { + "bluetooth-description": "Bluetooth ใฎ็ฎก็†ใ‚’ๆœ‰ๅŠนใซใ—ใพใ™ใ€‚", + "bluetooth-rssi-polling-description": "bluetoothctl ็ตŒ็”ฑใงๆŽฅ็ถšใ•ใ‚ŒใŸใƒ‡ใƒใ‚คใ‚นใฎRSSIใ‚’ๅฎšๆœŸ็š„ใซใ‚ตใƒณใƒ—ใƒชใƒณใ‚ฐใ—ใพใ™ใ€‚ ใ™ในใฆใฎใƒ‡ใƒใ‚คใ‚นใงๅˆฉ็”จใงใใ‚‹ใจใฏ้™ใ‚Šใพใ›ใ‚“ใ€‚ ๆœ‰ๅŠนใซใ™ใ‚‹ใจใ€ๆœ€ๅฐ้™ใฎใƒชใ‚ฝใƒผใ‚นใ‚’ไฝฟ็”จใ—ใพใ™ใ€‚", + "bluetooth-rssi-polling-label": "Bluetoothไฟกๅทใƒใƒผใƒชใƒณใ‚ฐ", + "desc": "Wi-Fi ใจ Bluetooth ๆŽฅ็ถšใ‚’็ฎก็†ใ—ใพใ™ใ€‚", + "wifi-description": "ใƒฏใ‚คใƒคใƒฌใ‚นใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใ‚’็ฎก็†ใ—ใพใ™๏ผˆNetworkManager ใŒๅฟ…่ฆใงใ™๏ผ‰ใ€‚" + }, + "notifications": { + "duration-critical-urgency-description": "็ทŠๆ€ฅๅบฆใ€Œ้‡ๅคงใ€ใฎ้€š็ŸฅใŒ่กจ็คบใ•ใ‚Œ็ถšใ‘ใ‚‹ๆ™‚้–“ใ€‚", + "duration-critical-urgency-label": "็ทŠๆ€ฅๅบฆ๏ผš้‡ๅคง", + "duration-desc": "็ทŠๆ€ฅๅบฆใƒฌใƒ™ใƒซใซๅŸบใฅใ„ใฆใ€้€š็Ÿฅใฎ่กจ็คบๆ™‚้–“ใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "duration-low-urgency-description": "็ทŠๆ€ฅๅบฆใ€ŒไฝŽใ€ใฎ้€š็ŸฅใŒ่กจ็คบใ•ใ‚Œ็ถšใ‘ใ‚‹ๆ™‚้–“ใ€‚", + "duration-low-urgency-label": "็ทŠๆ€ฅๅบฆ๏ผšไฝŽ", + "duration-normal-urgency-description": "็ทŠๆ€ฅๅบฆใ€Œ้€šๅธธใ€ใฎ้€š็ŸฅใŒ่กจ็คบใ•ใ‚Œ็ถšใ‘ใ‚‹ๆ™‚้–“ใ€‚", + "duration-normal-urgency-label": "็ทŠๆ€ฅๅบฆ๏ผš้€šๅธธ", + "duration-respect-expire-description": "้€š็Ÿฅ่‡ชไฝ“ใซ่จญๅฎšใ•ใ‚ŒใŸๆœ‰ๅŠนๆœŸ้™๏ผˆใ‚ฟใ‚คใƒ ใ‚ขใ‚ฆใƒˆ๏ผ‰ใ‚’ไฝฟ็”จใ—ใพใ™ใ€‚", + "duration-respect-expire-label": "้€š็ŸฅๆŒ‡ๅฎšใฎๆœŸ้™ใซๅพ“ใ†", + "duration-title": "้€š็Ÿฅใฎ่กจ็คบๆ™‚้–“", + "history-clear-dismiss-description": "้€š็Ÿฅใ‚’้–‰ใ˜ใ‚‹ใจใใซๅฑฅๆญดใ‹ใ‚‰ๅ‰Š้™คใ™ใ‚‹ใ€‚", + "history-clear-dismiss-label": "ๅดไธ‹ๆ™‚ใซใ‚ฏใƒชใ‚ข", + "history-critical-urgency-description": "็ทŠๆ€ฅๅ„ชๅ…ˆๅบฆใฎ้€š็Ÿฅใ‚’ๅฑฅๆญดใซไฟๅญ˜ใ—ใพใ™ใ€‚", + "history-critical-urgency-label": "็ทŠๆ€ฅๅบฆ๏ผš้‡ๅคงใ‚’ๅฑฅๆญดใซไฟๅญ˜", + "history-desc": "็ทŠๆ€ฅๅบฆใƒฌใƒ™ใƒซใซๅŸบใฅใ„ใฆใ€ๅฑฅๆญดใซไฟๅญ˜ใ™ใ‚‹้€š็Ÿฅใ‚’ๅˆถๅพกใ—ใพใ™ใ€‚", + "history-low-urgency-description": "ไฝŽๅ„ชๅ…ˆๅบฆใฎ้€š็Ÿฅใ‚’ๅฑฅๆญดใซไฟๅญ˜ใ—ใพใ™ใ€‚", + "history-low-urgency-label": "ไฝŽ็ทŠๆ€ฅๅบฆใ‚’ๅฑฅๆญดใซไฟๅญ˜", + "history-normal-urgency-description": "้€šๅธธๅ„ชๅ…ˆๅบฆใฎ้€š็Ÿฅใ‚’ๅฑฅๆญดใซไฟๅญ˜ใ—ใพใ™ใ€‚", + "history-normal-urgency-label": "้€šๅธธ็ทŠๆ€ฅๅบฆใ‚’ๅฑฅๆญดใซไฟๅญ˜", + "monitors-desc": "้€š็Ÿฅใ‚’่กจ็คบใ™ใ‚‹ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คใ‚’้ธๆŠžใ—ใพใ™ใ€‚ๆœช้ธๆŠžใฎๅ ดๅˆใฏๅ…จใฆใซ่กจ็คบใ•ใ‚Œใพใ™ใ€‚", + "rules-action-block": "ใƒ–ใƒญใƒƒใ‚ฏ", + "rules-action-block-desc": "ๅฎŒๅ…จใซใ‚นใ‚ญใƒƒใƒ—ใ—ใพใ™ใ€‚", + "rules-action-hide": "้ž่กจ็คบ", + "rules-action-hide-desc": "ใƒใƒƒใƒ—ใ‚ขใƒƒใƒ—ใชใ—ใ€้Ÿณใชใ—ใ€ๅฑฅๆญดใซ่ฟฝๅŠ ใ•ใ‚Œใพใ™ใ€‚", + "rules-action-label": "ใ‚ขใ‚ฏใ‚ทใƒงใƒณ", + "rules-action-mute": "ใƒŸใƒฅใƒผใƒˆ", + "rules-action-mute-desc": "้Ÿณใฏๅ‡บใชใ„ใŒใ€ใƒใƒƒใƒ—ใ‚ขใƒƒใƒ—ใฏ่กจ็คบใ•ใ‚Œใ€ๅฑฅๆญดใซใ‚‚ๆฎ‹ใ‚‹ใ€‚", + "rules-add": "ใƒซใƒผใƒซใ‚’่ฟฝๅŠ ", + "rules-delete": "ใƒซใƒผใƒซใ‚’ๅ‰Š้™ค", + "rules-description": "ใ‚ขใƒ—ใƒชๅใพใŸใฏใ‚ณใƒณใƒ†ใƒณใƒ„ใซไธ€่‡ดใ€‚ใƒซใƒผใƒซใฏ้ †็•ชใซใƒใ‚งใƒƒใ‚ฏใ•ใ‚Œใ€ๆœ€ๅˆใฎไธ€่‡ดใŒ้ฉ็”จใ•ใ‚Œใพใ™ใ€‚", + "rules-edit": "ใƒซใƒผใƒซใ‚’็ทจ้›†", + "rules-label": "ใƒ•ใ‚ฃใƒซใ‚ฟใƒผ่ฆๅ‰‡", + "rules-pattern-label": "ใƒ‘ใ‚ฟใƒผใƒณ", + "rules-pattern-placeholder": "firefox, discord, ใพใŸใฏ /regex/", + "rules-tab": "ใƒซใƒผใƒซ", + "settings-always-on-top-description": "ๅ…จ็”ป้ขใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใ‚„ไป–ใฎใƒฌใ‚คใƒคใƒผใ‚ˆใ‚Šใ‚‚ๆ‰‹ๅ‰ใซ้€š็Ÿฅใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "settings-background-opacity-description": "้€š็Ÿฅใฎ่ƒŒๆ™ฏใฎไธ้€ๆ˜Žๅบฆใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "settings-density-description": "้€š็Ÿฅใ‚ซใƒผใƒ‰ใฎๅฏ†ๅบฆใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "settings-density-label": "ๅฏ†ๅบฆ", + "settings-desc": "้€š็Ÿฅใฎๅค–่ฆณใจๆŒ™ๅ‹•ใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "settings-do-not-disturb-description": "ๆœ‰ๅŠนใซใ™ใ‚‹ใจใ€ใ™ในใฆใฎ้€š็Ÿฅใƒใƒƒใƒ—ใ‚ขใƒƒใƒ—ใ‚’็„กๅŠนใซใ—ใพใ™ใ€‚", + "settings-enabled-description": "้€š็Ÿฅใƒ‡ใƒผใƒขใƒณใ‚’ๆœ‰ๅŠนๅŒ–ใƒป็„กๅŠนๅŒ–ใ—ใพใ™๏ผˆNoctalia shellใฎๅ†่ตทๅ‹•ใŒๅฟ…่ฆใงใ™๏ผ‰ใ€‚", + "settings-enabled-label": "้€š็Ÿฅใ‚’ๆœ‰ๅŠนๅŒ–", + "settings-location-description": "็”ป้ขไธŠใฎ้€š็Ÿฅใฎ่กจ็คบไฝ็ฝฎใ€‚", + "settings-markdown-description": "Markdownๅฝขๅผใ‚’ไฝฟ็”จใ—ใฆ้€š็Ÿฅใ‚ณใƒณใƒ†ใƒณใƒ„ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "settings-markdown-label": "Markdown ใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹", + "sounds-desc": "้€š็Ÿฅ้ŸณใฎๅŠนๆžœใจ้Ÿณ้‡ใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "sounds-enabled-description": "้€š็Ÿฅๅ—ไฟกๆ™‚ใฎใฎๅŠนๆžœ้Ÿณใ‚’ๆœ‰ๅŠนใซใ—ใพใ™ใ€‚", + "sounds-enabled-label": "้€š็Ÿฅ้Ÿณใ‚’ๆœ‰ๅŠนๅŒ–", + "sounds-excluded-apps-description": "็‹ฌ่‡ชใฎ้€š็Ÿฅ้Ÿณใ‚’ๆŒใค็‰นๅฎšใฎใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใซใคใ„ใฆใ€่จญๅฎšใ—ใŸ้€š็Ÿฅ้Ÿณใฎๅ†็”Ÿใ‚’ใ‚นใ‚ญใƒƒใƒ—ใ—ใพใ™ใ€‚", + "sounds-excluded-apps-label": "้™คๅค–ใ™ใ‚‹ใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณ", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "็ทŠๆ€ฅๅบฆใ€Œ้‡ๅคงใ€ใฎ้€š็Ÿฅใงๅ†็”Ÿใ™ใ‚‹้Ÿณๅฃฐใƒ•ใ‚กใ‚คใƒซใฎใƒ‘ใ‚นใ€‚", + "sounds-files-critical-label": "็ทŠๆ€ฅๅบฆใ€Œ้‡ๅคงใ€ใฎ้€š็Ÿฅ้Ÿณ", + "sounds-files-critical-select-title": "็ทŠๆ€ฅๅบฆใ€Œ้‡ๅคงใ€ใฎ้€š็Ÿฅ้Ÿณใƒ•ใ‚กใ‚คใƒซใ‚’้ธๆŠž", + "sounds-files-desc": "้€š็Ÿฅใฎ็ทŠๆ€ฅๅบฆใƒฌใƒ™ใƒซใ”ใจใซใ‚ซใ‚นใ‚ฟใƒ ้Ÿณๅฃฐใƒ•ใ‚กใ‚คใƒซใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "sounds-files-low-description": "็ทŠๆ€ฅๅบฆใ€ŒไฝŽใ€ใฎ้€š็Ÿฅใงๅ†็”Ÿใ™ใ‚‹้Ÿณๅฃฐใƒ•ใ‚กใ‚คใƒซใฎใƒ‘ใ‚นใ€‚", + "sounds-files-low-label": "็ทŠๆ€ฅๅบฆใ€ŒไฝŽใ€ใฎ้€š็Ÿฅ้Ÿณ", + "sounds-files-low-select-title": "็ทŠๆ€ฅๅบฆใ€ŒไฝŽใ€ใฎ้€š็Ÿฅ้Ÿณใƒ•ใ‚กใ‚คใƒซใ‚’้ธๆŠž", + "sounds-files-normal-description": "็ทŠๆ€ฅๅบฆใ€Œ้€šๅธธใ€ใฎ้€š็Ÿฅใงๅ†็”Ÿใ™ใ‚‹้Ÿณๅฃฐใƒ•ใ‚กใ‚คใƒซใฎใƒ‘ใ‚นใ€‚", + "sounds-files-normal-label": "็ทŠๆ€ฅๅบฆใ€Œ้€šๅธธใ€ใฎ้€š็Ÿฅ้Ÿณ", + "sounds-files-normal-select-title": "็ทŠๆ€ฅๅบฆใ€Œ้€šๅธธใ€ใฎ้€š็Ÿฅ้Ÿณใƒ•ใ‚กใ‚คใƒซใ‚’้ธๆŠž", + "sounds-files-placeholder": "้Ÿณๅฃฐใƒ•ใ‚กใ‚คใƒซใธใฎใƒ‘ใ‚นใ‚’ๅ…ฅๅŠ›", + "sounds-files-select-file": "้Ÿณๅฃฐใƒ•ใ‚กใ‚คใƒซใ‚’้ธๆŠž", + "sounds-files-title": "้Ÿณๅฃฐใƒ•ใ‚กใ‚คใƒซ", + "sounds-files-unified-description": "้€š็Ÿฅใงๅ†็”Ÿใ™ใ‚‹้Ÿณๅฃฐใƒ•ใ‚กใ‚คใƒซใธใฎใƒ‘ใ‚นใ€‚", + "sounds-files-unified-label": "้€š็Ÿฅ้Ÿณ", + "sounds-files-unified-select-title": "้€š็Ÿฅ้Ÿณใƒ•ใ‚กใ‚คใƒซใ‚’้ธๆŠž", + "sounds-separate-description": "็ทŠๆ€ฅๅบฆ๏ผˆไฝŽใ€้€šๅธธใ€้‡ๅคง๏ผ‰ใ”ใจใซ็•ฐใชใ‚‹้Ÿณๅฃฐใƒ•ใ‚กใ‚คใƒซใ‚’ไฝฟ็”จใ—ใพใ™ใ€‚", + "sounds-separate-label": "็ทŠๆ€ฅๅบฆใ”ใจใซ็•ฐใชใ‚‹้€š็Ÿฅ้Ÿณใ‚’ไฝฟ็”จ", + "sounds-title": "้€š็Ÿฅ้Ÿณ่จญๅฎš", + "sounds-unavailable-description": "้€š็Ÿฅ้Ÿณใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹ใซใฏใ€Qt6 Multimedia ใ‚’ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใฆใใ ใ•ใ„ใ€‚", + "sounds-unavailable-label": "้€š็Ÿฅ้Ÿณใ‚’ๅˆฉ็”จใงใใพใ›ใ‚“", + "sounds-volume-description": "้€š็Ÿฅ้Ÿณใฎ้Ÿณ้‡ใƒฌใƒ™ใƒซใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "sounds-volume-label": "้€š็Ÿฅ้Ÿณ้‡", + "toast-battery-description": "ใƒใƒƒใƒ†ใƒชใƒผๆฎ‹้‡ใŒใ“ใฎๅ‰ฒๅˆใ‚’ไธ‹ๅ›žใฃใŸๅ ดๅˆใซ่ญฆๅ‘Šใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "toast-battery-label": "ใƒใƒƒใƒ†ใƒชใƒผ่ญฆๅ‘Š", + "toast-desc": "ใƒˆใƒผใ‚นใƒˆ้€š็Ÿฅใฎๅค–่ฆณใจๆŒ™ๅ‹•ใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "toast-keyboard-description": "ใ‚ญใƒผใƒœใƒผใƒ‰ใƒฌใ‚คใ‚ขใ‚ฆใƒˆใฎๅค‰ๆ›ดๆ™‚ใซใƒˆใƒผใ‚นใƒˆใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "toast-keyboard-label": "ใ‚ญใƒผใƒœใƒผใƒ‰ใƒฌใ‚คใ‚ขใ‚ฆใƒˆ", + "toast-media-description": "ใƒกใƒ‡ใ‚ฃใ‚ขใฎๅ†็”Ÿ็Šถๆ…‹ใŒๅค‰ๅŒ–ใ—ใŸใจใใซใƒˆใƒผใ‚นใƒˆใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "toast-media-label": "ใƒกใƒ‡ใ‚ฃใ‚ข" + }, + "osd": { + "always-on-top-description": "ๅ…จ็”ป้ขใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใ‚„ไป–ใฎใƒฌใ‚คใƒคใƒผใ‚ˆใ‚Šใ‚‚ๆ‰‹ๅ‰ใซ OSD ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "always-on-top-label": "ๅธธใซๆœ€ๅ‰้ขใซ่กจ็คบ", + "background-opacity-description": "OSD ใฎ่ƒŒๆ™ฏใฎไธ้€ๆ˜Žๅบฆใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "background-opacity-label": "่ƒŒๆ™ฏใฎไธ้€ๆ˜Žๅบฆ", + "description": "้Ÿณ้‡ใ‚„่ผๅบฆใฎใ‚ชใƒผใƒใƒผใƒฌใ‚คใชใฉใ€ใ‚ชใƒณใ‚นใ‚ฏใƒชใƒผใƒณ่กจ็คบใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "duration-auto-hide-description": "OSD ใŒๆถˆใˆใ‚‹ใพใงใฎๆ™‚้–“ใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "duration-auto-hide-label": "่‡ชๅ‹•็š„ใซ้š ใ™ใพใงใฎๆ™‚้–“", + "duration-desc": "OSD ใŒ่‡ชๅ‹•็š„ใซ้š ใ‚Œใ‚‹ใพใงใฎ่กจ็คบ็ถ™็ถšๆ™‚้–“ใ€‚", + "duration-title": "่‡ชๅ‹•้ž่กจ็คบใฎใ‚ฟใ‚คใƒ ใ‚ขใ‚ฆใƒˆ", + "enabled-description": "้Ÿณ้‡ใ‚„่ผๅบฆใฎๅค‰ๆ›ดใ‚’ใƒชใ‚ขใƒซใ‚ฟใ‚คใƒ ใง่กจ็คบใ—ใพใ™ใ€‚", + "enabled-label": "ใ‚ชใƒณใ‚นใ‚ฏใƒชใƒผใƒณใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คใ‚’ๆœ‰ๅŠนใซ (OSD)", + "events-desc": "็”ป้ข่กจ็คบใ‚’ใƒˆใƒชใ‚ฌใƒผใ™ใ‚‹ใ‚คใƒ™ใƒณใƒˆใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "general-desc": "OSDใฎ่กจ็คบใจๅ‹•ไฝœใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "location-description": "OSD ใฎ่กจ็คบไฝ็ฝฎใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "monitors-desc": "OSD ใ‚’่กจ็คบใ™ใ‚‹ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คใ‚’้ธๆŠžใ—ใพใ™ใ€‚ๆœช้ธๆŠžใฎๅ ดๅˆใฏๅ…จใฆใซ่กจ็คบใ•ใ‚Œใพใ™ใ€‚", + "title": "ใ‚ชใƒณใ‚นใ‚ฏใƒชใƒผใƒณใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚ค", + "types-brightness-description": "็”ป้ขใฎ่ผๅบฆใŒๅค‰ๆ›ดใ•ใ‚ŒใŸๆ™‚ใซ OSD ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "types-brightness-label": "ๆ˜Žใ‚‹ใ•", + "types-custom-text-description": "IPC ็ตŒ็”ฑใฎใ‚ซใ‚นใ‚ฟใƒ ใƒ†ใ‚ญใ‚นใƒˆใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ OSD ใซ่กจ็คบใ—ใพใ™ใ€‚", + "types-custom-text-label": "ใ‚ซใ‚นใ‚ฟใƒ ใƒ†ใ‚ญใ‚นใƒˆ", + "types-desc": "OSD ใ‚’่กจ็คบใ™ใ‚‹ใƒˆใƒชใ‚ฌใƒผ๏ผˆใ‚คใƒ™ใƒณใƒˆ๏ผ‰ใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "types-input-volume-description": "ใƒžใ‚คใ‚ฏ้Ÿณ้‡ใŒๅค‰ๆ›ดใ•ใ‚ŒใŸๆ™‚ใซ OSD ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "types-input-volume-label": "ๅ…ฅๅŠ›้Ÿณ้‡", + "types-lockkey-description": "Caps Lockใ€Num Lockใ€Scroll Lock ใฎๅˆ‡ใ‚Šๆ›ฟใˆๆ™‚ใซ OSD ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "types-lockkey-label": "ใƒญใƒƒใ‚ฏใ‚ญใƒผ", + "types-media-description": "ใƒกใƒ‡ใ‚ฃใ‚ขๅ†็”Ÿ็Šถๆ…‹๏ผˆๅ†็”Ÿใ€ไธ€ๆ™‚ๅœๆญขใ€ใ‚นใ‚ญใƒƒใƒ—๏ผ‰ใŒๅค‰ๅŒ–ใ—ใŸใจใใซOSDใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "types-media-label": "ใƒกใƒ‡ใ‚ฃใ‚ขๅ†็”Ÿ", + "types-title": "OSD ใฎใƒˆใƒชใ‚ฌใƒผใ‚คใƒ™ใƒณใƒˆ", + "types-volume-description": "ๅ‡บๅŠ›้Ÿณ้‡ใŒๅค‰ๆ›ดใ•ใ‚ŒใŸๆ™‚ใซ OSD ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "types-volume-label": "ๅ‡บๅŠ›้Ÿณ้‡" + }, + "plugins": { + "auto-update": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’่‡ชๅ‹•ๆ›ดๆ–ฐ", + "auto-update-description": "ใ‚ทใ‚งใƒซ่ตทๅ‹•ๆ™‚ใซใ™ในใฆใฎใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’่‡ชๅ‹•็š„ใซๆ›ดๆ–ฐใ—ใพใ™ใ€‚", + "available-description": "่จญๅฎšใ•ใ‚ŒใŸใ‚ฝใƒผใ‚นใ‹ใ‚‰ใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’ๅ‚็…งใ—ใฆใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใพใ™ใ€‚", + "available-label": "ๅˆฉ็”จๅฏ่ƒฝใชใƒ—ใƒฉใ‚ฐใ‚คใƒณ", + "available-no-plugins-description": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณใฎใ‚ฝใƒผใ‚นใ‚’็ขบ่ชใ™ใ‚‹ใ‹ใ€ใƒชใ‚นใƒˆใ‚’ๆ›ดๆ–ฐใ—ใฆใใ ใ•ใ„ใ€‚", + "available-no-plugins-label": "ๅˆฉ็”จๅฏ่ƒฝใชใƒ—ใƒฉใ‚ฐใ‚คใƒณใฏใ‚ใ‚Šใพใ›ใ‚“", + "check-for-updates": "ใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆใ‚’็ขบ่ช", + "checking-for-updates": "ใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆใ‚’็ขบ่ชใ—ใฆใ„ใพใ™...", + "collision-already-installed": "ใ“ใฎใƒ—ใƒฉใ‚ฐใ‚คใƒณใฏใ™ใงใซใ‚คใƒณใ‚นใƒˆใƒผใƒซใ•ใ‚Œใฆใ„ใพใ™", + "collision-custom-version-exists": "ใ€Œ{source}ใ€ ็™บ ใฎ ใ‚ซใ‚นใ‚ฟใƒ ็‰ˆ ใฏ ๆ—ขใซ ใ‚คใƒณใ‚นใƒˆใƒผใƒซ ใ•ใ‚Œใฆใ„ใพใ™", + "collision-official-version-exists": "ใ“ใฎใƒ—ใƒฉใ‚ฐใ‚คใƒณใฎๅ…ฌๅผใƒใƒผใ‚ธใƒงใƒณใฏใ™ใงใซใ‚คใƒณใ‚นใƒˆใƒผใƒซใ•ใ‚Œใฆใ„ใพใ™", + "development-disable": "ใ“ใฎใƒ—ใƒฉใ‚ฐใ‚คใƒณใฎ้–‹็™บ่€…ใƒขใƒผใƒ‰ใ‚’็„กๅŠนใซใ™ใ‚‹", + "development-enable": "ใ“ใฎใƒ—ใƒฉใ‚ฐใ‚คใƒณใฎ้–‹็™บใƒขใƒผใƒ‰ใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹ใ€‚", + "filter-downloaded": "ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ๆธˆใฟ", + "filter-not-downloaded": "ๆœชใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰", + "filter-tags-description": "ใ‚ซใƒ†ใ‚ดใƒชใƒผใพใŸใฏใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰็Šถๆณใงใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’็ตžใ‚Š่พผใ‚€ใ€‚", + "filter-tags-label": "ใ‚ฟใ‚ฐ", + "hot-reload-description": "ใƒ•ใ‚กใ‚คใƒซใฎๅค‰ๆ›ดๆ™‚ใซใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’่‡ชๅ‹•็š„ใซใƒชใƒญใƒผใƒ‰ใ—ใพใ™ใ€‚ใƒ—ใƒฉใ‚ฐใ‚คใƒณ้–‹็™บใซไพฟๅˆฉใงใ™ใ€‚", + "hot-reload-label": "ใƒ›ใƒƒใƒˆใƒชใƒญใƒผใƒ‰๏ผˆ้–‹็™บใƒขใƒผใƒ‰๏ผ‰", + "hot-reloaded": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’ใƒชใƒญใƒผใƒ‰ใ—ใพใ—ใŸ๏ผš{name}", + "install-error": "ใ‚คใƒณใ‚นใƒˆใƒผใƒซใซๅคฑๆ•—ใ—ใพใ—ใŸ๏ผš{error}", + "install-incompatible": "{plugin} ใซใฏ Noctalia v{version} ไปฅ้™ใŒๅฟ…่ฆใงใ™", + "install-success": "{plugin} ใ‚’ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใพใ—ใŸ", + "installed-description": "ใƒญใƒผใ‚ซใƒซใซใ‚คใƒณใ‚นใƒˆใƒผใƒซใ•ใ‚ŒใŸใ™ในใฆใฎใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’็ฎก็†ใƒป่จญๅฎšใ—ใพใ™ใ€‚", + "installed-label": "ใ‚คใƒณใ‚นใƒˆใƒผใƒซๆธˆใฟใƒ—ใƒฉใ‚ฐใ‚คใƒณ", + "installed-no-plugins-description": "ใ€Œๅˆฉ็”จๅฏ่ƒฝใ€ใ‚ปใ‚ฏใ‚ทใƒงใƒณใ‹ใ‚‰ใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใฆใใ ใ•ใ„ใ€‚", + "installed-no-plugins-label": "ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ•ใ‚Œใฆใ„ใ‚‹ใƒ—ใƒฉใ‚ฐใ‚คใƒณใฏใ‚ใ‚Šใพใ›ใ‚“", + "installing": "{plugin} ใ‚’ใ‚คใƒณใ‚นใƒˆใƒผใƒซไธญ...", + "notify-updates": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณใฎๆ›ดๆ–ฐ้€š็Ÿฅ", + "notify-updates-description": "ๆ–ฐใ—ใ„ใƒ—ใƒฉใ‚ฐใ‚คใƒณใฎใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆใŒๅˆฉ็”จๅฏ่ƒฝใชๅ ดๅˆใซ้€š็Ÿฅใƒใƒƒใƒ—ใ‚ขใƒƒใƒ—ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "open-plugin-page": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณใƒšใƒผใ‚ธใ‚’้–‹ใ", + "open-plugins-tab": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณ่จญๅฎšใ‚’้–‹ใ", + "plugin-settings-title": "{plugin} ใฎ่จญๅฎš", + "refresh-refreshing": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณใƒชใ‚นใƒˆใ‚’ๆ›ดๆ–ฐไธญ...", + "refresh-tooltip": "ๅˆฉ็”จๅฏ่ƒฝใชใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’ๆ›ดๆ–ฐ", + "settings-error-not-loaded": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณใฏ่ชญใฟ่พผใพใ‚Œใฆใ„ใพใ›ใ‚“", + "settings-saved": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณ่จญๅฎšใ‚’ไฟๅญ˜ใ—ใพใ—ใŸ", + "settings-tooltip": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณ่จญๅฎš", + "source-custom": "ใ‚ซใ‚นใ‚ฟใƒ ใ‚ฝใƒผใ‚น", + "sources-add-custom": "ใ‚ซใ‚นใ‚ฟใƒ ใƒชใƒใ‚ธใƒˆใƒชใ‚’่ฟฝๅŠ ", + "sources-add-dialog-description": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚ฝใƒผใ‚นใจใ—ใฆ GitHub ใƒชใƒใ‚ธใƒˆใƒชใ‚’่ฟฝๅŠ ใ—ใพใ™ใ€‚", + "sources-add-dialog-error": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚ฝใƒผใ‚นใฎ่ฟฝๅŠ ใซๅคฑๆ•—ใ—ใพใ—ใŸ", + "sources-add-dialog-name": "ใƒชใƒใ‚ธใƒˆใƒชๅ", + "sources-add-dialog-name-placeholder": "ใƒžใ‚คใ‚ซใ‚นใ‚ฟใƒ ใƒ—ใƒฉใ‚ฐใ‚คใƒณ", + "sources-add-dialog-success": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚ฝใƒผใ‚นใ‚’่ฟฝๅŠ ใ—ใพใ—ใŸ", + "sources-add-dialog-title": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚ฝใƒผใ‚นใ‚’่ฟฝๅŠ ", + "sources-add-dialog-url": "ใƒชใƒใ‚ธใƒˆใƒชใฎ URL", + "sources-description": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณใƒชใƒใ‚ธใƒˆใƒชใ‚’็ฎก็†ใ—ใพใ™ใ€‚", + "sources-placeholder": "ใƒžใ‚คใƒชใƒใ‚ธใƒˆใƒช", + "sources-remove-tooltip": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚ฝใƒผใ‚นใ‚’ๅ‰Š้™ค", + "title": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณ", + "translations-reloaded": "็ฟป่จณใ‚’ใƒชใƒญใƒผใƒ‰ใ—ใพใ—ใŸ๏ผš{name}", + "uninstall-dialog-description": "ๆœฌๅฝ“ใซ {plugin} ใ‚’ใ‚ขใƒณใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใพใ™ใ‹๏ผŸใ™ในใฆใฎใƒ—ใƒฉใ‚ฐใ‚คใƒณใƒ‡ใƒผใ‚ฟใŒๅ‰Š้™คใ•ใ‚Œใพใ™ใ€‚", + "uninstall-dialog-title": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณใฎใ‚ขใƒณใ‚คใƒณใ‚นใƒˆใƒผใƒซ", + "uninstall-error": "ใ‚ขใƒณใ‚คใƒณใ‚นใƒˆใƒผใƒซใซๅคฑๆ•—ใ—ใพใ—ใŸ๏ผš{error}", + "uninstall-success": "{plugin} ใ‚’ใ‚ขใƒณใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใพใ—ใŸ", + "uninstalling": "{plugin} ใ‚’ใ‚ขใƒณใ‚คใƒณใ‚นใƒˆใƒผใƒซไธญ...", + "update-all": "ใ™ในใฆใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆ ({count})", + "update-all-success": "ใ™ในใฆใฎใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’ใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆใ—ใพใ—ใŸ", + "update-available": "ๆ–ฐใ—ใ„ใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆใŒใ‚ใ‚Šใพใ™", + "update-available-plural": "ๆ–ฐใ—ใ„ใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆใŒใ‚ใ‚Šใพใ™๏ผˆ{count}ๅ€‹๏ผ‰", + "update-error": "ใƒ—ใƒฉใ‚ฐใ‚คใƒณใฎใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆใซๅคฑๆ•—ใ—ใพใ—ใŸ๏ผš{plugin}๏ผš{error}", + "update-pending": "v{current} โ†’ v{new} (Noctalia v{required}ใŒๅฟ…่ฆใงใ™)", + "update-success": "{plugin} ใ‚’ v{version} ใซใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆใ—ใพใ—ใŸ", + "update-version": "v{current} โ†’ v{new}", + "updating": "ๆ›ดๆ–ฐไธญ..." + }, + "region": { + "title": "ๅœฐๅŸŸ" + }, + "session-menu": { + "countdown-duration-description": "ใ‚ซใ‚ฆใƒณใƒˆใƒ€ใ‚ฆใƒณใ‚ฟใ‚คใƒžใƒผใŒ้›ปๆบๆ“ไฝœใ‚’ๅฎŸ่กŒใ™ใ‚‹ใพใงใฎๆ™‚้–“ใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "countdown-duration-label": "ใ‚ซใ‚ฆใƒณใƒˆใƒ€ใ‚ฆใƒณใฎๆ™‚้–“", + "enable-countdown-description": "้›ปๆบๆ“ไฝœใ‚’ๅฎŸ่กŒใ™ใ‚‹ๅ‰ใซใ‚ซใ‚ฆใƒณใƒˆใƒ€ใ‚ฆใƒณใ‚ฟใ‚คใƒžใƒผใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "enable-countdown-label": "ใ‚ซใ‚ฆใƒณใƒˆใƒ€ใ‚ฆใƒณใ‚ฟใ‚คใƒžใƒผใฎๆœ‰ๅŠนๅŒ–", + "entries-desc": "ใ‚ปใƒƒใ‚ทใƒงใƒณใƒกใƒ‹ใƒฅใƒผใซ่กจ็คบใ•ใ‚Œใ‚‹้›ปๆบๆ“ไฝœใจใ€ใใฎ่กจ็คบ้ †ๅบใ‚’ใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บใ—ใพใ™ใ€‚", + "entries-title": "้›ปๆบๆ“ไฝœ", + "entry-settings-command-description": "ใ“ใฎใ‚ขใ‚ฏใ‚ทใƒงใƒณใฎใŸใ‚ใซๅฎŸ่กŒใ™ใ‚‹ใ‚ซใ‚นใ‚ฟใƒ ใ‚ณใƒžใƒณใƒ‰ใ€‚็ฉบๆฌ„ใซใ™ใ‚‹ใจใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฎใ‚ทใ‚นใƒ†ใƒ ใ‚ณใƒžใƒณใƒ‰ใ‚’ไฝฟ็”จใ—ใพใ™ใ€‚", + "entry-settings-command-placeholder": "ไพ‹๏ผšsystemctl poweroff", + "entry-settings-default-command-lock": "ๅ†…้ƒจใƒญใƒƒใ‚ฏ็”ป้ข๏ผˆใ‚ณใƒžใƒณใƒ‰ใชใ—๏ผ‰", + "entry-settings-default-command-logout": "ๅ†…้ƒจใƒญใ‚ฐใ‚ขใ‚ฆใƒˆ๏ผˆใ‚ณใƒžใƒณใƒ‰ใชใ—๏ผ‰", + "entry-settings-default-info-description": "ใ‚ซใ‚นใ‚ฟใƒ ใ‚ณใƒžใƒณใƒ‰ใŒๆŒ‡ๅฎšใ•ใ‚Œใฆใ„ใชใ„ๅ ดๅˆใ€ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฎใ‚ทใ‚นใƒ†ใƒ ใ‚ณใƒžใƒณใƒ‰ใŒไฝฟ็”จใ•ใ‚Œใพใ™ใ€‚", + "entry-settings-default-info-label": "ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฎใ‚ณใƒžใƒณใƒ‰", + "entry-settings-keybind-description": "ใ‚ปใƒƒใ‚ทใƒงใƒณใƒกใƒ‹ใƒฅใƒผใŒ้–‹ใ„ใฆใ„ใ‚‹ใจใใซใ€ใ“ใฎใ‚ขใ‚ฏใ‚ทใƒงใƒณใ‚’ใƒˆใƒชใ‚ฌใƒผใ™ใ‚‹ใ‚ญใƒผใฎ็ต„ใฟๅˆใ‚ใ›ใ‚’ๅ‰ฒใ‚Šๅฝ“ใฆใพใ™ใ€‚", + "entry-settings-keybind-placeholder": "ใ‚ญใƒผใ‚’ๅ‰ฒใ‚Šๅฝ“ใฆใ‚‹ใซใฏใ€่จ˜้Œฒใ‚’ๆŠผใ—ใฆใใ ใ•ใ„...", + "entry-settings-keybind-recording": "้Œฒ้Ÿณไธญ... ไปปๆ„ใฎใ‚ญใƒผใฎ็ต„ใฟๅˆใ‚ใ›ใ‚’ๆŠผใ—ใฆใใ ใ•ใ„", + "entry-settings-title": "ใ€Œ{entry}ใ€ใฎ่จญๅฎš", + "entry-settings-tooltip": "ใ‚ณใƒžใƒณใƒ‰ใ‚’่จญๅฎš", + "general-desc": "ใ‚ปใƒƒใ‚ทใƒงใƒณใƒกใƒ‹ใƒฅใƒผใƒ‘ใƒใƒซใฎๅ‹•ไฝœใจๅค–่ฆณใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "large-buttons-layout-description": "ใ‚ปใƒƒใ‚ทใƒงใƒณใƒกใƒ‹ใƒฅใƒผใฎใƒœใ‚ฟใƒณใฎ่กจ็คบๆ–นๆณ•ใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "large-buttons-layout-label": "ๅคงใใชใƒœใ‚ฟใƒณใฎใƒฌใ‚คใ‚ขใ‚ฆใƒˆ", + "large-buttons-style-description": "ใ‚ปใƒƒใ‚ทใƒงใƒณใƒกใƒ‹ใƒฅใƒผใ‚’ใ€ใ‚ฐใƒชใƒƒใƒ‰้…็ฝฎใฎๅคงใใชใƒœใ‚ฟใƒณใง่กจ็คบใ—ใพใ™ใ€‚", + "large-buttons-style-label": "ๅคงใใชใƒœใ‚ฟใƒณใฎใ‚นใ‚ฟใ‚คใƒซ", + "position-description": "ใ‚ปใƒƒใ‚ทใƒงใƒณใƒกใƒ‹ใƒฅใƒผใƒ‘ใƒใƒซใฎ่กจ็คบไฝ็ฝฎใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "show-header-description": "ใ‚ปใƒƒใ‚ทใƒงใƒณใƒกใƒ‹ใƒฅใƒผใฎไธŠ้ƒจใซใ‚ฟใ‚คใƒˆใƒซใจ้–‰ใ˜ใ‚‹ใƒœใ‚ฟใƒณใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "show-header-label": "ใƒ˜ใƒƒใƒ€ใƒผใ‚’่กจ็คบ", + "show-keybinds-description": "ใ‚ปใƒƒใ‚ทใƒงใƒณใ‚ชใƒ—ใ‚ทใƒงใƒณใซใ‚ญใƒผใƒใ‚คใƒณใƒ‰ใฎใƒ’ใƒณใƒˆใ‚’่กจ็คบใ™ใ‚‹ใ€‚", + "show-keybinds-label": "ใ‚ญใƒผใƒใ‚คใƒณใƒ‰ใ‚’่กจ็คบ" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Noctalia ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใƒขใƒผใƒ‰ใŒๆœ‰ๅŠนใช้–“ใ€ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’่กจ็คบใ—ใŸใพใพใซใ™ใ‚‹ใ€‚", + "noctalia-performance-disable-desktop-widgets-label": "ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใƒขใƒผใƒ‰ใงใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹", + "noctalia-performance-disable-wallpaper-description": "Noctalia ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใƒขใƒผใƒ‰ใŒๆœ‰ๅŠนใช้–“ใ€ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใ€ๆฆ‚่ฆใ€ใƒญใƒƒใ‚ฏ็”ป้ขใฎๅฃ็ด™ใ‚’่กจ็คบใ—ใŸใพใพใซใ—ใพใ™ใ€‚", + "noctalia-performance-disable-wallpaper-label": "ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใƒขใƒผใƒ‰ใงๅฃ็ด™ใฎใƒฌใƒณใƒ€ใƒชใƒณใ‚ฐใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹", + "title": "ใ‚ทใ‚นใƒ†ใƒ " + }, + "system-monitor": { + "critical-color-label": "ๅฑ้™บๆ™‚ใฎ่‰ฒ", + "custom-highlight-colors-title-label": "ใ‚ซใ‚นใ‚ฟใƒ ใƒใ‚คใƒฉใ‚คใƒˆ่‰ฒ", + "disk-available-label": "ๅˆฉ็”จๅฏ่ƒฝใชใƒ‡ใ‚ฃใ‚นใ‚ฏๅฎน้‡", + "disk-section-label": "ใ‚นใƒˆใƒฌใƒผใ‚ธไฝฟ็”จ้‡", + "enable-dgpu-monitoring-description": "่ญฆๅ‘Š: ใ“ใ‚Œใซใ‚ˆใ‚Šๅค–้ƒจGPU (NVIDIA/AMD) ใŒ่ตทๅ‹•็Šถๆ…‹ใซใชใ‚‹ใŸใ‚ใ€ใƒใ‚คใƒ–ใƒชใƒƒใƒ‰ใ‚ฐใƒฉใƒ•ใ‚ฃใƒƒใ‚ฏใ‚นๆญ่ผ‰ใฎใƒŽใƒผใƒˆ PC ใงใฏใƒใƒƒใƒ†ใƒชใƒผ้ง†ๅ‹•ๆ™‚้–“ใซๅคงใใชๅฝฑ้Ÿฟใ‚’ไธŽใˆใ‚‹ๅฏ่ƒฝๆ€งใŒใ‚ใ‚Šใพใ™ใ€‚", + "enable-dgpu-monitoring-label": "ๅค–้ƒจGPUใƒขใƒ‹ใ‚ฟใƒชใƒณใ‚ฐใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹", + "external-monitor-description": "ๅค–้ƒจใ‚ทใ‚นใƒ†ใƒ ใƒขใƒ‹ใ‚ฟใƒผใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใ‚’่ตทๅ‹•ใ™ใ‚‹้š›ใซๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใพใŸใฏใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใƒ‘ใ‚นใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚", + "external-monitor-label": "ๅค–้ƒจใ‚ทใ‚นใƒ†ใƒ ใƒขใƒ‹ใ‚ฟใƒผใ‚ณใƒžใƒณใƒ‰", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "ใ‚ทใ‚นใƒ†ใƒ ใƒขใƒ‹ใ‚ฟใƒผใฎๅ‹•ไฝœใจๅค–่ฆณใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "gpu-section-label": "GPU ๆธฉๅบฆ", + "highlight-colors-section-label": "ใƒใ‚คใƒฉใ‚คใƒˆ่‰ฒ", + "polling-interval-label": "ใƒใƒผใƒชใƒณใ‚ฐ้–“้š”", + "polling-section-description": "ๅ„ใ‚ทใ‚นใƒ†ใƒ ใƒกใƒˆใƒชใ‚ฏใ‚นใฎๆ›ดๆ–ฐ้ ปๅบฆใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "polling-section-label": "ใƒใƒผใƒชใƒณใ‚ฐ้–“้š”", + "threshold-critical": "้‡ๅคง", + "threshold-warning": "่ญฆๅ‘Š", + "thresholds-section-description": "ๅ„ใ‚ทใ‚นใƒ†ใƒ ใƒกใƒˆใƒชใƒƒใ‚ฏใฎ่ญฆๅ‘Š/ๅฑ้™บใ—ใใ„ๅ€คใจใƒใƒผใƒชใƒณใ‚ฐ้–“้š”ใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "use-custom-highlight-colors-description": "็„กๅŠนใซใ™ใ‚‹ใจใ€ใƒ†ใƒผใƒžใฎใƒ‡ใƒ•ใ‚ฉใƒซใƒˆๅผท่ชฟ่‰ฒใŒไฝฟ็”จใ•ใ‚Œใพใ™ใ€‚", + "use-custom-highlight-colors-label": "ใ‚ซใ‚นใ‚ฟใƒ ๅผท่ชฟ่‰ฒใ‚’ไฝฟ็”จใ™ใ‚‹", + "warning-color-label": "่ญฆๅ‘Šๆ™‚ใฎ่‰ฒ" + }, + "user-interface": { + "allow-panels-without-bar-description": "ๆœ‰ๅŠนใซใ™ใ‚‹ใจใ€ใฉใฎ็”ป้ขใงใ‚‚ใƒ‘ใƒใƒซใ‚’้–‹ใ‘ใ‚‹ใ‚ˆใ†ใซใชใ‚Šใพใ™ใ€‚็„กๅŠนใซใ™ใ‚‹ใจใ€ใƒใƒผใŒใ‚ใ‚‹็”ป้ขใงใฎใฟใƒ‘ใƒใƒซใŒ้–‹ใใ€ใƒกใƒขใƒชไฝฟ็”จ้‡ใ‚’ๆŠ‘ใˆใ‚‰ใ‚Œใพใ™ใ€‚", + "allow-panels-without-bar-label": "ใƒใƒผใฎใชใ„็”ป้ขใงใฎใƒ‘ใƒใƒซ่กจ็คบใ‚’่จฑๅฏ", + "animation-disable-description": "ใ™ในใฆใฎใ‚ขใƒ‹ใƒกใƒผใ‚ทใƒงใƒณใ‚’็„กๅŠนใซใ—ใ€ๅฟœ็ญ”ๆ€งใจ้€Ÿๅบฆใ‚’ๅ‘ไธŠใ•ใ›ใพใ™ใ€‚", + "animation-disable-label": "UI ใ‚ขใƒ‹ใƒกใƒผใ‚ทใƒงใƒณใฎ็„กๅŠนๅŒ–", + "animation-speed-description": "ๅ…จไฝ“ใฎใ‚ขใƒ‹ใƒกใƒผใ‚ทใƒงใƒณ้€Ÿๅบฆใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "animation-speed-label": "ใ‚ขใƒ‹ใƒกใƒผใ‚ทใƒงใƒณ้€Ÿๅบฆ", + "animation-speed-reset": "ใ‚ขใƒ‹ใƒกใƒผใ‚ทใƒงใƒณ้€Ÿๅบฆใ‚’ใƒชใ‚ปใƒƒใƒˆ", + "appearance-desc": "ใƒ„ใƒผใƒซใƒใƒƒใƒ—ใ€ๅขƒ็•Œ็ทšใ€ๅฝฑใชใฉใฎ่ฆ–่ฆš่ฆ็ด ใ‚’ใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บใ—ใพใ™ใ€‚", + "blur-behind-description": "ใƒใƒผใจใƒ‘ใƒใƒซใฎ่ƒŒๅพŒใฎ้ ˜ๅŸŸใ‚’ใ€ใ‚ณใƒณใƒใ‚ธใ‚ฟใƒผใฎใผใ‹ใ—ใƒ—ใƒญใƒˆใ‚ณใƒซใ‚’ไฝฟ็”จใ—ใฆใผใ‹ใ—ใพใ™ใ€‚", + "blur-behind-label": "่ƒŒๆ™ฏใผใ‹ใ—", + "box-border-description": "ใ‚ณใƒณใƒ†ใƒณใƒ„้ ˜ๅŸŸใฎๅ‘จใ‚Šใซใ‚ขใ‚ฆใƒˆใƒฉใ‚คใƒณใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "box-border-label": "ใ‚ณใƒณใƒ†ใƒŠใฎ่ผช้ƒญ", + "box-border-radius-description": "ใ‚ตใ‚คใƒ‰ใƒใƒผใ€ใ‚ซใƒผใƒ‰ใ€ใ‚ณใƒณใƒ†ใƒณใƒ„ใƒ‘ใƒใƒซใชใฉใ€ไธป่ฆใชใƒฌใ‚คใ‚ขใ‚ฆใƒˆ้ƒจๅˆ†ใฎ่ง’ใฎไธธใฟใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚", + "box-border-radius-label": "ใ‚ณใƒณใƒ†ใƒŠใฎ่ง’ใฎไธธใฟ", + "control-border-radius-description": "ใƒœใ‚ฟใƒณใ€ใƒˆใ‚ฐใƒซใ€ใƒ†ใ‚ญใ‚นใƒˆใƒ•ใ‚ฃใƒผใƒซใƒ‰ใชใฉใ€ๆ“ไฝœๅฏ่ƒฝใช่ฆ็ด ใฎ่ง’ใฎไธธใฟใ‚’ๅˆถๅพกใ—ใพใ™ใ€‚", + "control-border-radius-label": "ๅ…ฅๅŠ›่ฆ็ด ใฎ่ง’ใฎไธธใฟ", + "desc": "ใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใƒผใ‚นใฎๅค–่ฆณใ‚„ๆ“ไฝœๆ„Ÿใ€ๆŒ™ๅ‹•ใ‚’ใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บใ—ใพใ™ใ€‚", + "dim-desktop-description": "ใƒ‘ใƒใƒซใ‚„ใƒกใƒ‹ใƒฅใƒผใŒ้–‹ใ„ใฆใ„ใ‚‹้–“ใ€ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใ‚’ๆš—ใใ—ใพใ™ใ€‚", + "dim-desktop-label": "ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใ‚’ๆš—ใใ™ใ‚‹", + "dimmer-opacity-description": "ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใ‚’ๆš—ใใ—ใŸ้š›ใฎไธ้€ๆ˜Žๅบฆใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "dimmer-opacity-label": "ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใ‚’ๆš—ใใ—ใŸ้š›ใฎไธ้€ๆ˜Žๅบฆ", + "panel-background-opacity-description": "ใ™ในใฆใฎใƒ‘ใƒใƒซ๏ผˆใƒใƒผใ€ใƒฉใƒณใƒใƒฃใƒผใ€่จญๅฎšใชใฉ๏ผ‰ใฎ่ƒŒๆ™ฏใฎไธ้€ๆ˜Žๅบฆใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "panel-background-opacity-label": "ใƒ‘ใƒใƒซ่ƒŒๆ™ฏใฎไธ้€ๆ˜Žๅบฆ", + "panels-attached-to-bar-description": "ใƒ‘ใƒใƒซใ‚’ใƒใƒผใ‚„็”ป้ขใฎ็ซฏใซๅ›บๅฎšใ—ใพใ™ใ€‚ใ‚นใ‚ฟใ‚คใƒชใƒƒใ‚ทใƒฅใช้€†ใ‚ขใƒผใƒซ๏ผˆ่ง’ใฎ่ฃ…้ฃพ๏ผ‰ใซใ‚ˆใ‚Šใ€ใ‚ทใƒผใƒ ใƒฌใ‚นใชๅค–่ฆณใซใ—ใพใ™ใ€‚", + "panels-attached-to-bar-label": "ใƒ‘ใƒใƒซใ‚’็”ป้ขใฎ็ซฏใซๅธ็€", + "panels-overlay-description": "ๅ…จ็”ป้ขใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณไธŠใงใ‚‚ใ€ใƒ‘ใƒใƒซใจใƒใƒผใŒ่กจ็คบใ•ใ‚Œ็ถšใ‘ใ‚‹ใ‚ˆใ†ใซใ—ใพใ™ใ€‚", + "panels-overlay-label": "ใƒ‘ใƒใƒซใจใƒใƒผใ‚’ๆœ€ๅ‰้ขใซไฟๆŒ", + "scaling-description": "ใƒใƒผใ‚’้™คใใ€ใƒฆใƒผใ‚ถใƒผใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใƒผใ‚นๅ…จไฝ“ใฎใ‚ตใ‚คใ‚บใ‚’ๅค‰ๆ›ดใ—ใพใ™ใ€‚", + "scaling-label": "ใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใƒผใ‚นใฎใ‚นใ‚ฑใƒผใƒซ่ชฟๆ•ด", + "scrollbar-always-visible-description": "ใ‚ณใƒณใƒ†ใƒณใƒ„ใŒใ‚นใ‚ฏใƒญใƒผใƒซๅฏ่ƒฝใชๅ ดๅˆใ€ใ‚นใ‚ฏใƒญใƒผใƒซใƒใƒผใ‚’ๅธธใซ่กจ็คบใ—ใ€ใƒ›ใƒใƒผๆ™‚ใฎใฟ่กจ็คบใ™ใ‚‹ใฎใ‚’ใ‚„ใ‚ใ‚‹ใ€‚", + "scrollbar-always-visible-label": "ๅธธใซใ‚นใ‚ฏใƒญใƒผใƒซใƒใƒผใ‚’่กจ็คบ", + "settings-panel-header": "่จญๅฎšใƒ‘ใƒใƒซ", + "settings-panel-mode-description": "่จญๅฎš็”ป้ขใฎ่กจ็คบๅฝขๅผใ‚’้ธๆŠžใ—ใพใ™ (ๅ†่ตทๅ‹•ใŒๅฟ…่ฆใชๅ ดๅˆใŒใ‚ใ‚Šใพใ™)ใ€‚", + "settings-panel-mode-label": "่จญๅฎšใƒ‘ใƒใƒซใฎ่กจ็คบใƒขใƒผใƒ‰", + "settings-panel-sidebar-card-style-description": "่จญๅฎšใ‚ตใ‚คใƒ‰ใƒใƒผใ‚’ใ€่ง’ใŒไธธใ„ๅก—ใ‚Šใคใถใ•ใ‚ŒใŸ่ƒŒๆ™ฏใงๅ›ฒใฟใพใ™ใ€‚", + "settings-panel-sidebar-card-style-label": "ใ‚ตใ‚คใƒ‰ใƒใƒผใฎใ‚ซใƒผใƒ‰ใ‚นใ‚ฟใ‚คใƒซ", + "shadows-description": "ใƒใƒผใ‚„ใƒ‘ใƒใƒซใฎไธ‹ใซใƒ‰ใƒญใƒƒใƒ—ใ‚ทใƒฃใƒ‰ใ‚ฆ๏ผˆๅฝฑ๏ผ‰ใ‚’่กจ็คบใ—ใพใ™ใ€‚", + "shadows-direction-description": "ๅฝฑใ‚’่ฝใจใ™ๅ ดๆ‰€ใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "shadows-direction-label": "ๅฝฑใฎๆ–นๅ‘", + "shadows-label": "ใƒ‰ใƒญใƒƒใƒ—ใ‚ทใƒฃใƒ‰ใ‚ฆ", + "title": "ใƒฆใƒผใ‚ถใƒผใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใƒผใ‚น", + "tooltips-description": "ใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใƒผใ‚นๅ…จไฝ“ใฎใƒ„ใƒผใƒซใƒใƒƒใƒ—ใฎๆœ‰ๅŠนใƒป็„กๅŠนใ‚’ๅˆ‡ใ‚Šๆ›ฟใˆใพใ™ใ€‚", + "tooltips-label": "ใƒ„ใƒผใƒซใƒใƒƒใƒ—ใ‚’่กจ็คบ", + "translucent-widgets-description": "ใƒ‘ใƒใƒซๅ†…ใฎใƒœใ‚ฟใƒณใ€ใ‚ฟใƒ–ใ€ใใฎไป–ใฎใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’ๅŠ้€ๆ˜Žใซใ™ใ‚‹ใ€‚", + "translucent-widgets-label": "ๅŠ้€ๆ˜Žใฎใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆ" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "ใ‚ขใƒซใƒ•ใ‚กใƒ™ใƒƒใƒˆ้ †", + "automation-change-mode-description": "่‡ชๅ‹•ๅค‰ๆ›ดๆ™‚ใซๅฃ็ด™ใ‚’้ธๆŠžใ™ใ‚‹ๆ–นๆณ•ใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "automation-change-mode-label": "ๅค‰ๆ›ดใƒขใƒผใƒ‰", + "automation-custom-interval-description": "ๆ™‚้–“ใ‚’ HH:MM ๅฝขๅผใงๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„๏ผˆไพ‹๏ผš01:30๏ผ‰ใ€‚", + "automation-custom-interval-label": "ใ‚ซใ‚นใ‚ฟใƒ ้–“้š”", + "automation-interval-description": "ๅฃ็ด™ใ‚’่‡ชๅ‹•็š„ใซๅˆ‡ใ‚Šๆ›ฟใˆใ‚‹้ ปๅบฆใ€‚", + "automation-interval-label": "ๅค‰ๆ›ด้–“้š”", + "automation-random-wallpaper-description": "ไธ€ๅฎš้–“้š”ใงใƒฉใƒณใƒ€ใƒ ใซๅฃ็ด™ใ‚’ๅค‰ๆ›ดใ—ใพใ™ใ€‚", + "automation-scheduled-change-description": "ไธ€ๅฎš้–“้š”ใงๅฃ็ด™ใ‚’่‡ชๅ‹•็š„ใซๅค‰ๆ›ดใ—ใพใ™ใ€‚", + "automation-scheduled-change-label": "ใ‚นใ‚ฑใ‚ธใƒฅใƒผใƒซๅค‰ๆ›ด", + "look-feel-edge-smoothness-description": "ๅˆ‡ใ‚Šๆ›ฟใˆๆ™‚ใฎๅขƒ็•Œใซใ€ๆŸ”ใ‚‰ใ‹ใ„ใผใ‹ใ—ๅŠนๆžœ๏ผˆใƒ•ใ‚งใ‚ถใƒผ๏ผ‰ใ‚’้ฉ็”จใ—ใพใ™ใ€‚", + "look-feel-edge-smoothness-label": "ๅขƒ็•Œใฎใผใ‹ใ—", + "look-feel-fill-color-description": "ๅฃ็ด™ใฎ่ƒŒๆ™ฏใซ่กจ็คบใ•ใ‚Œใ‚‹ๅก—ใ‚Šใคใถใ—่‰ฒใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "look-feel-fill-mode-description": "็”ปๅƒใ‚’ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คใฎ่งฃๅƒๅบฆใซๅˆใ‚ใ›ใ‚‹ๆ–นๆณ•ใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "look-feel-fill-mode-label": "้…็ฝฎๆ–นๆณ•", + "look-feel-skip-startup-transition-description": "ใ‚ทใ‚งใƒซ่ตทๅ‹•ๆ™‚ใซๅฃ็ด™ใ‚ขใƒ‹ใƒกใƒผใ‚ทใƒงใƒณใ‚’ใ‚นใ‚ญใƒƒใƒ—ใ™ใ‚‹ใ€‚", + "look-feel-skip-startup-transition-label": "่ตทๅ‹•ๆ™‚ใฎใƒˆใƒฉใƒณใ‚ธใ‚ทใƒงใƒณใ‚’ใ‚นใ‚ญใƒƒใƒ—", + "look-feel-title": "่กจ็คบใจๆผ”ๅ‡บ", + "look-feel-transition-duration-description": "ๅˆ‡ใ‚Šๆ›ฟใˆใ‚ขใƒ‹ใƒกใƒผใ‚ทใƒงใƒณใซใ‹ใ‹ใ‚‹ๆ™‚้–“๏ผˆ็ง’๏ผ‰ใ€‚", + "look-feel-transition-duration-label": "ๅˆ‡ใ‚Šๆ›ฟใˆๆ™‚้–“", + "look-feel-transition-type-description": "ๅฃ็ด™ๅˆ‡ใ‚Šๆ›ฟใˆๆ™‚ใฎใ‚ขใƒ‹ใƒกใƒผใ‚ทใƒงใƒณใฎ็จฎ้กžใ€‚", + "look-feel-transition-type-label": "ๅˆ‡ใ‚Šๆ›ฟใˆๅŠนๆžœ", + "settings-clear-cache-button": "ใ‚ญใƒฃใƒƒใ‚ทใƒฅใ‚’ใ‚ฏใƒชใ‚ข", + "settings-clear-cache-description": "ใ‚ญใƒฃใƒƒใ‚ทใƒฅใ•ใ‚ŒใŸใƒชใ‚ตใ‚คใ‚บๆธˆใฟๅฃ็ด™ใ‚’ๅ‰Š้™คใ—ใฆใƒ‡ใ‚ฃใ‚นใ‚ฏๅฎน้‡ใ‚’่งฃๆ”พใ—ใพใ™ใ€‚", + "settings-clear-cache-label": "ๅฃ็ด™ใ‚ญใƒฃใƒƒใ‚ทใƒฅ", + "settings-clear-cache-toast": "ๅฃ็ด™ใ‚ญใƒฃใƒƒใ‚ทใƒฅใ‚’ใ‚ฏใƒชใ‚ขใ—ใพใ—ใŸ", + "settings-desc": "ๅฃ็ด™ใฎ็ฎก็†ๆ–นๆณ•ใ‚„่กจ็คบๆ–นๆณ•ใ‚’ๅˆถๅพกใ—ใพใ™ใ€‚", + "settings-enable-management-description": "Noctalia ใงๅฃ็ด™ใ‚’็ฎก็†ใ—ใพใ™ใ€‚ไป–ใฎใ‚ขใƒ—ใƒชใ‚’ไฝฟ็”จใ™ใ‚‹ๅ ดๅˆใฏใƒใ‚งใƒƒใ‚ฏใ‚’ๅค–ใ—ใฆใใ ใ•ใ„ใ€‚", + "settings-enable-management-label": "ๅฃ็ด™็ฎก็†ใ‚’ๆœ‰ๅŠนๅŒ–", + "settings-enable-overview-description": "ใ‚ชใƒผใƒใƒผใƒ“ใƒฅใƒผ็”ป้ขใฎๅฃ็ด™ใซใ€ใผใ‹ใ—ใจๆš—ใ•ใ—ใ‚’้ฉ็”จใ—ใพใ™ใ€‚", + "settings-enable-overview-label": "ใ‚ชใƒผใƒใƒผใƒ“ใƒฅใƒผใฎๅฃ็ด™ๅŠนๆžœ", + "settings-folder-description": "ใƒกใ‚คใƒณใฎๅฃ็ด™ใƒ•ใ‚ฉใƒซใƒ€ใฎใƒ‘ใ‚นใ€‚", + "settings-folder-label": "ๅฃ็ด™ใƒ•ใ‚ฉใƒซใƒ€", + "settings-hide-wallpaper-filenames-tooltip-hide": "ใƒ•ใ‚กใ‚คใƒซๅใ‚’้š ใ™", + "settings-hide-wallpaper-filenames-tooltip-show": "ใƒ•ใ‚กใ‚คใƒซๅใ‚’่กจ็คบ", + "settings-monitor-specific-description": "ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คใ”ใจใซ็•ฐใชใ‚‹ๅฃ็ด™ใƒ•ใ‚ฉใƒซใƒ€ใ‚’่จญๅฎšใ—ใพใ™ใ€‚", + "settings-monitor-specific-label": "ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คๅˆฅใฎๅฃ็ด™ใƒ•ใ‚ฉใƒซใƒ€", + "settings-monitor-specific-tooltip": "ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คๅˆฅใฎๅฃ็ด™ใƒ•ใ‚ฉใƒซใƒ€", + "settings-overview-blur-strength-description": "ๆฆ‚่ฆใซใผใ‹ใ—ใฎๅผทๅบฆใ‚’้ฉ็”จใ—ใพใ™ใ€‚", + "settings-overview-blur-strength-label": "ๆฆ‚่ฆใฎใผใ‹ใ—ๅผทๅบฆ", + "settings-overview-tint-description": "ๆฆ‚่ฆใซ่‰ฒๅˆใ„ใฎๅผทใ•ใ‚’้ฉ็”จใ—ใพใ™ใ€‚", + "settings-overview-tint-label": "ใ‚ชใƒผใƒใƒผใƒ“ใƒฅใƒผใฎ็€่‰ฒๅผทๅบฆ", + "settings-recursive-search-description": "ๅฃ็ด™ใƒ•ใ‚ฉใƒซใƒ€ใฎใ‚ตใƒ–ใƒ•ใ‚ฉใƒซใƒ€ๅ†…ใ‚‚ๆคœ็ดขๅฏพ่ฑกใซใ—ใพใ™ใ€‚", + "settings-recursive-search-label": "ใ‚ตใƒ–ใƒ•ใ‚ฉใƒซใƒ€ใ‚‚ๆคœ็ดข", + "settings-select-monitor-folder": "ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คๅˆฅใฎๅฃ็ด™ใƒ•ใ‚ฉใƒซใƒ€ใ‚’้ธๆŠž", + "settings-selector-description": "ๅฃ็ด™ใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "settings-selector-position-description": "ๅฃ็ด™้ธๆŠžใƒ‘ใƒใƒซใฎ่กจ็คบไฝ็ฝฎใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "settings-show-hidden-files-tooltip-hide": "้š ใ—ใƒ•ใ‚กใ‚คใƒซใ‚’้š ใ™", + "settings-show-hidden-files-tooltip-show": "้š ใ—ใƒ•ใ‚กใ‚คใƒซใ‚’่กจ็คบ", + "settings-title": "ๅฃ็ด™่จญๅฎš", + "settings-use-original-images-description": "ๅฃ็ด™ใ‚’่กจ็คบๅ‰ใซใƒชใ‚ตใ‚คใ‚บใ™ใ‚‹ใฎใ‚’ใ‚นใ‚ญใƒƒใƒ—ใ—ใพใ™ใ€‚ใƒ‡ใ‚ฃใ‚นใ‚ฏๅฎน้‡ใ‚’็ฏ€็ด„ใ—ใ€ๅฃ็ด™ๅค‰ๆ›ดๆ™‚ใฎCPUไฝฟ็”จ็އใ‚’ๅ‰Šๆธ›ใ—ใพใ™ใŒใ€้žๅธธใซๅคงใใช็”ปๅƒใงใฏใ‚ˆใ‚Šๅคšใใฎใƒกใƒขใƒชใ‚’ไฝฟ็”จใ™ใ‚‹ๅฏ่ƒฝๆ€งใŒใ‚ใ‚Šใพใ™ใ€‚", + "settings-use-original-images-label": "ๅ…ƒใฎ็”ปๅƒใ‚’ไฝฟ็”จ", + "settings-view-mode-description": "ใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒชใ‹ใ‚‰ๅฃ็ด™ใ‚’ใฉใฎใ‚ˆใ†ใซ่กจ็คบใ™ใ‚‹ใ‹ใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "settings-view-mode-label": "่กจ็คบใƒขใƒผใƒ‰", + "view-mode-browse": "ใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒชใ‚’ๅ‚็…ง", + "view-mode-cycle-tooltip": "่กจ็คบใƒขใƒผใƒ‰๏ผš{mode} (ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆๅค‰ๆ›ด)", + "view-mode-recursive": "ใƒ•ใƒฉใƒƒใƒˆๅŒ–ใ•ใ‚ŒใŸใ‚ตใƒ–ใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒช", + "view-mode-single": "ใƒซใƒผใƒˆใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒช" + } + }, + "placeholders": { + "add-new-keybind": "ๆ–ฐใ—ใ„ใ‚ญใƒผใƒใ‚คใƒณใƒ‰ใ‚’่ฟฝๅŠ ", + "command-example": "echo \"Hello World\"", + "enter-command": "ๅฎŸ่กŒใ™ใ‚‹ใ‚ณใƒžใƒณใƒ‰ใ‚’ๅ…ฅๅŠ›๏ผˆใ‚ขใƒ—ใƒชใพใŸใฏใ‚นใ‚ฏใƒชใƒ—ใƒˆ๏ผ‰", + "enter-ipc-identifier": "IPCใ‚ณใƒžใƒณใƒ‰ใฎไธ€ๆ„ใฎ่ญ˜ๅˆฅๅญใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„", + "enter-path": "ใƒ‘ใ‚นใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„...", + "enter-text-to-collapse": "ไพ‹๏ผš'nothing is playing'ใ€‚ๆญฃ่ฆ่กจ็พ (/.../) ใ‚‚ไฝฟ็”จใงใใพใ™ใ€‚", + "enter-tooltip": "ใƒ„ใƒผใƒซใƒใƒƒใƒ—ใ‚’ๅ…ฅๅŠ›", + "enter-width-pixels": "ๅน…ใ‚’ๅ…ฅๅŠ›๏ผˆใƒ”ใ‚ฏใ‚ปใƒซ๏ผ‰", + "keybind-recording": "ใ‚ญใƒผใƒใ‚คใƒณใƒ‰ใ‚’่จ˜้Œฒไธญ...", + "search": "ๆคœ็ดข...", + "search-icons": "ไพ‹๏ผšnoctalia, niri, battery, cloud", + "search-launcher": "้ …็›ฎใ‚’ๆคœ็ดข...๏ผˆใพใŸใฏ > ใงใ‚ณใƒžใƒณใƒ‰ๅฎŸ่กŒ๏ผ‰", + "search-wallhaven": "Wallhaven ใงๆคœ็ดข...", + "search-wallpapers": "ๅฃ็ด™ใ‚’ๆคœ็ดขใ—ใฆ็ตžใ‚Š่พผใ‚€..." + }, + "positions": { + "bottom": "ไธ‹", + "bottom-center": "ไธ‹้ƒจไธญๅคฎ", + "bottom-left": "ๅทฆไธ‹", + "bottom-right": "ๅณไธ‹", + "center": "ไธญๅคฎใซ้…็ฝฎใ™ใ‚‹", + "center-left": "ๅทฆๅดไธญๅคฎ", + "center-right": "ๅณๅดไธญๅคฎ", + "close-to-bar": "ใƒใƒผใฎใƒœใ‚ฟใƒณไป˜่ฟ‘", + "follow-bar": "ใƒใƒผใซ่ฟฝๅพ“", + "left": "ๅทฆ", + "right": "ๅณ", + "top": "ไธŠ", + "top-center": "ไธŠ้ƒจไธญๅคฎ", + "top-left": "ๅทฆไธŠ", + "top-right": "ๅณไธŠ" + }, + "session-menu": { + "action-in-seconds": "{seconds} ็ง’ๅพŒใซ {action}...", + "cancel-timer": "ใ‚ฟใ‚คใƒžใƒผใ‚’ใ‚ญใƒฃใƒณใ‚ปใƒซ", + "title": "ใ‚ปใƒƒใ‚ทใƒงใƒณใƒกใƒ‹ใƒฅใƒผ" + }, + "setup": { + "all-done": "ๅฎŒไบ†๏ผ", + "appearance": { + "subheader": "ใƒ€ใƒผใ‚ฏใƒขใƒผใƒ‰ใจ่‰ฒใฎใ‚ฝใƒผใ‚น๏ผˆๅฃ็ด™ใพใŸใฏไบ‹ๅ‰ๅฎš็พฉ๏ผ‰ใ‚’้ธๆŠžใ—ใพใ™ใ€‚" + }, + "customize": { + "header": "่‡ชๅˆ†ๅฅฝใฟใซใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บ", + "subheader": "ใƒใƒผใฎไฝ็ฝฎใ‚„ๅฏ†ๅบฆใ€ใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใƒผใ‚นใฎใ‚นใ‚ฑใƒผใƒซใชใฉใ‚’่ชฟๆ•ดใ—ใพใ™ใ€‚" + }, + "skip-setup": "ใ‚ปใƒƒใƒˆใ‚ขใƒƒใƒ—ใ‚’ใ‚นใ‚ญใƒƒใƒ—", + "telemetry-wizard-done": "ไบ†่งฃใ—ใพใ—ใŸ๏ผ", + "telemetry-wizard-note": "่จญๅฎšใงใ„ใคใงใ‚‚ๆœ‰ๅŠนใพใŸใฏ็„กๅŠนใซใงใใพใ™ใ€‚", + "telemetry-wizard-subtitle": "Noctaliaใฎๆ”นๅ–„ใซๅฝน็ซ‹ใคๅŒฟๅๅˆ†ๆžใ‚’่ฟฝๅŠ ใ—ใพใ—ใŸ", + "telemetry-wizard-title": "ใƒ—ใƒฉใ‚คใƒใ‚ทใƒผใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆ", + "wallpaper": { + "choose-dir": "ๅฃ็ด™็”ปๅƒใŒๅซใพใ‚Œใฆใ„ใ‚‹ใƒ•ใ‚ฉใƒซใƒ€ใ‚’้ธๆŠžใ—ใพใ™", + "dir-browse": "ๅฃ็ด™ใƒ•ใ‚ฉใƒซใƒ€ใ‚’ๅ‚็…ง", + "dir-description": "ๅฃ็ด™็”ปๅƒใŒๅซใพใ‚Œใฆใ„ใ‚‹ใƒ•ใ‚ฉใƒซใƒ€ใ‚’้ธๆŠžใ—ใพใ™ใ€‚", + "dir-label": "ๅฃ็ด™ใƒ•ใ‚ฉใƒซใƒ€", + "dir-select-title": "ๅฃ็ด™ใƒ•ใ‚ฉใƒซใƒ€ใ‚’้ธๆŠž", + "header": "ๅฃ็ด™ใ‚’้ธๆŠž", + "no-dir": "ๅฃ็ด™ใƒ•ใ‚ฉใƒซใƒ€ใŒ้ธๆŠžใ•ใ‚Œใฆใ„ใพใ›ใ‚“", + "no-valid": "ๆœ‰ๅŠนใช็”ปๅƒใƒ•ใ‚กใ‚คใƒซใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใงใ—ใŸ๏ผš{dir}", + "none-in-dir": "ใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒชใซๅฃ็ด™ใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใงใ—ใŸ", + "preview-error": "็”ปๅƒใ‚’่ชญใฟ่พผใ‚ใพใ›ใ‚“ใงใ—ใŸ", + "select-prompt": "ไปฅไธ‹ใ‹ใ‚‰ๅฃ็ด™ใ‚’้ธๆŠžใ—ใพใ™", + "subheader": "็พŽใ—ใ„่ƒŒๆ™ฏใง้›ฐๅ›ฒๆฐ—ใ‚’ๆผ”ๅ‡บใ—ใพใ—ใ‚‡ใ†ใ€‚" + }, + "welcome": "ใ‚ˆใ†ใ“ใ", + "welcome-note": "ใพใšใฏๅŸบๆœฌ่จญๅฎšใ‹ใ‚‰ๅง‹ใ‚ใพใ—ใ‚‡ใ†ใ€‚่ฉณ็ดฐใชใ‚ชใƒ—ใ‚ทใƒงใƒณใฏใ€Œ่จญๅฎšใ€ใซใ‚ใ‚Šใพใ™ใ€‚", + "welcome-subtitle": "ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใ‚’ใ‚ใชใŸใ ใ‘ใฎใ‚‚ใฎใซใ—ใพใ—ใ‚‡ใ†", + "welcome-title": "Noctaliaใธใ‚ˆใ†ใ“ใ๏ผ" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "ใƒกใƒ‡ใ‚ฃใ‚ขใƒ—ใƒฌใƒผใƒคใƒผใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "ไธๆ˜Žใชใ‚ขใƒ—ใƒช", + "uptime": "็จผๅƒๆ™‚้–“๏ผš{uptime}", + "user-requested": "ใƒฆใƒผใ‚ถใƒผใฎ่ฆๆฑ‚", + "welcome-back": "ใŠใ‹ใˆใ‚Šใชใ•ใ„ใ€", + "widget-settings-title": "{widget} ใฎ่จญๅฎš" + }, + "system-monitor": { + "core-usage": "ใ‚ณใ‚ข {id} ไฝฟ็”จ็އ", + "cpu-temp": "CPUๆธฉๅบฆ", + "cpu-usage": "CPUไฝฟ็”จ็އ", + "disk": "ใƒ‡ใ‚ฃใ‚นใ‚ฏ", + "download-speed": "ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰้€Ÿๅบฆ", + "gpu-temp": "GPUๆธฉๅบฆ", + "load-average": "่ฒ ่ทๅนณๅ‡", + "title": "ใ‚ทใ‚นใƒ†ใƒ ใƒขใƒ‹ใ‚ฟใƒผ", + "upload-speed": "ใ‚ขใƒƒใƒ—ใƒญใƒผใƒ‰้€Ÿๅบฆ" + }, + "toast": { + "airplane-mode": { + "description": "ใ™ในใฆใฎใƒฏใ‚คใƒคใƒฌใ‚น้€šไฟกใ‚’็„กๅŠนใซใ™ใ‚‹ใ€‚", + "title": "ๆฉŸๅ†…ใƒขใƒผใƒ‰" + }, + "battery": { + "critical": "ใƒใƒƒใƒ†ใƒชใƒผๆฎ‹้‡ไฝŽไธ‹", + "critical-desc": "ใƒใƒƒใƒ†ใƒชใƒผๆฎ‹้‡ {percent}% โ€” ใ™ใใซๅ……้›ปๅ™จใ‚’ๆŽฅ็ถšใ—ใฆใใ ใ•ใ„", + "low": "ใƒใƒƒใƒ†ใƒชใƒผๆฎ‹้‡ไฝŽไธ‹", + "low-desc": "ใƒใƒƒใƒ†ใƒชใƒผๆฎ‹้‡ {percent}% โ€” ๅ……้›ปๅ™จใ‚’ๆŽฅ็ถšใ—ใฆใใ ใ•ใ„" + }, + "bluetooth": { + "address-copied": "ใ‚ขใƒ‰ใƒฌใ‚นใ‚’ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ใซใ‚ณใƒ”ใƒผใ—ใพใ—ใŸ", + "auto-connect-disabled": "่‡ชๅ‹•ๆŽฅ็ถšใŒ็„กๅŠนใงใ™", + "auto-connect-enabled": "่‡ชๅ‹•ๆŽฅ็ถšใŒๆœ‰ๅŠนใงใ™", + "auto-connecting": "{count} ๅฐใฎใƒ‡ใƒใ‚คใ‚นใซๆŽฅ็ถšไธญ...", + "confirm-code": "ๅˆฅใฎใƒ‡ใƒใ‚คใ‚นใงใ‚ณใƒผใƒ‰{value}ใ‚’็ขบ่ชใ—ใฆใใ ใ•ใ„ใ€‚", + "connect-failed": "ใƒ‡ใƒใ‚คใ‚นใซๆŽฅ็ถšใงใใพใ›ใ‚“ใงใ—ใŸ", + "disconnect-failed": "ใƒ‡ใƒใ‚คใ‚นใ‹ใ‚‰ๅˆ‡ๆ–ญใงใใพใ›ใ‚“ใงใ—ใŸ", + "discoverable-change-failed": "ๆคœๅ‡บๅฏ่ƒฝใช็Šถๆ…‹ใฎๅค‰ๆ›ดใซๅคฑๆ•—ใ—ใพใ—ใŸ", + "discoverable-disabled": "ๆคœๅ‡บไธ่ƒฝใ‚’็„กๅŠนใซใ—ใพใ—ใŸ", + "discoverable-enabled": "ๆคœๅ‡บๅฏ่ƒฝใŒๆœ‰ๅŠน", + "display-code": "ๅˆฅใฎใƒ‡ใƒใ‚คใ‚นใซ{value}ใฎใ‚ณใƒผใƒ‰ใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚", + "forget-failed": "ใƒ‡ใƒใ‚คใ‚นใฎๅ‰Š้™คใซๅคฑๆ•—ใ—ใพใ—ใŸ", + "pair-failed": "ใƒ‡ใƒใ‚คใ‚นใฎใƒšใ‚ขใƒชใƒณใ‚ฐใซๅคฑๆ•—ใ—ใพใ—ใŸ", + "passkey-required": "ใƒ‡ใƒใ‚คใ‚นใงใƒ‘ใ‚นใ‚ญใƒผใŒๅฟ…่ฆใงใ™ใ€‚", + "pincode-required": "ใƒ‡ใƒใ‚คใ‚นใŒPINใ‚ณใƒผใƒ‰ใ‚’่ฆๆฑ‚ใ—ใฆใ„ใพใ™ใ€‚", + "state-change-failed": "Bluetoothใฎ็Šถๆ…‹ๅค‰ๆ›ดใซๅคฑๆ•—ใ—ใพใ—ใŸ" + }, + "clipboard": { + "long-text": "้•ทใ„ใƒ†ใ‚ญใ‚นใƒˆ", + "unavailable": "ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ๅฑฅๆญดใ‚’ๅˆฉ็”จใงใใพใ›ใ‚“", + "unavailable-desc": "cliphist ใŒใ‚คใƒณใ‚นใƒˆใƒผใƒซใ•ใ‚Œใฆใ„ใพใ›ใ‚“ใ€‚ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ๅฑฅๆญดๆฉŸ่ƒฝใ‚’ไฝฟ็”จใ™ใ‚‹ใซใฏใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใฆใใ ใ•ใ„" + }, + "custom-command-failed": { + "description": "ใ‚ณใƒžใƒณใƒ‰ใŒๅคฑๆ•—ใ—ใพใ—ใŸ: {command}\\n็ต‚ไบ†ใ‚ณใƒผใƒ‰: {code}", + "title": "ใ‚ซใ‚นใ‚ฟใƒ ใ‚ณใƒžใƒณใƒ‰ใŒๅคฑๆ•—ใ—ใพใ—ใŸ" + }, + "do-not-disturb": { + "disabled": "ใŠใ‚„ใ™ใฟใƒขใƒผใƒ‰ใ‚’็„กๅŠนๅŒ–", + "disabled-desc": "ใ™ในใฆใฎ้€š็Ÿฅใ‚’่กจ็คบใ—ใพใ™", + "enabled": "ใŠใ‚„ใ™ใฟใƒขใƒผใƒ‰ใ‚’ๆœ‰ๅŠนๅŒ–", + "enabled-desc": "้€š็Ÿฅใฏๅฑฅๆญดใซไฟๅญ˜ใ•ใ‚Œใพใ™" + }, + "donation-opened": "ๅฏ„ไป˜ใƒšใƒผใ‚ธใŒใƒ–ใƒฉใ‚ฆใ‚ถใง้–‹ใ‹ใ‚Œใพใ—ใŸ", + "internet-limited": "ใ‚คใƒณใ‚ฟใƒผใƒใƒƒใƒˆๆœชๆŽฅ็ถš", + "keyboard-layout": { + "changed": "ใ‚ญใƒผใƒœใƒผใƒ‰ใƒฌใ‚คใ‚ขใ‚ฆใƒˆใ‚’ {layout} ใซๅค‰ๆ›ดใ—ใพใ—ใŸ", + "title": "ใ‚ญใƒผใƒœใƒผใƒ‰" + }, + "missing-control-center": { + "description": "ใ‚ณใƒณใƒˆใƒญใƒผใƒซใ‚ปใƒณใ‚ฟใƒผใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใŒใƒใƒผใ‹ใ‚‰ๅ‰Š้™คใ•ใ‚Œใพใ—ใŸใ€‚ๅ†ๅบฆใ‚ขใ‚ฏใ‚ปใ‚นใ™ใ‚‹ใซใฏใ€ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’่ฟฝๅŠ ใ—็›ดใ™ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚ใƒใƒผใ‚’ๅณใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆ้–‹ใใ“ใจใ‚‚ๅฏ่ƒฝใงใ™", + "label": "ๆœ€ๅพŒใฎใ‚ณใƒณใƒˆใƒญใƒผใƒซใ‚ปใƒณใ‚ฟใƒผใŒๅ‰Š้™คใ•ใ‚Œใพใ—ใŸ" + }, + "night-light": { + "forced": "ๅผทๅˆถ็š„ใซๆœ‰ๅŠนๅŒ–", + "normal": "้€šๅธธใƒขใƒผใƒ‰", + "not-installed": "wlsunset ใŒใ‚คใƒณใ‚นใƒˆใƒผใƒซใ•ใ‚Œใฆใ„ใพใ›ใ‚“" + }, + "noctalia-performance": { + "disabled": "ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใƒขใƒผใƒ‰๏ผš็„กๅŠน", + "enabled": "ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใƒขใƒผใƒ‰๏ผšๆœ‰ๅŠน", + "label": "Noctalia ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚น" + }, + "power-profile": { + "changed": "้›ปๆบใƒ—ใƒญใƒ•ใ‚กใ‚คใƒซใ‚’ๅค‰ๆ›ดใ—ใพใ—ใŸ", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "ใƒ†ใƒณใƒ—ใƒฌใƒผใƒˆใฎๅ‡ฆ็†ไธญใซใ‚จใƒฉใƒผใŒ็™บ็”Ÿใ—ใพใ—ใŸ", + "title": "ใƒ†ใƒณใƒ—ใƒฌใƒผใƒˆๅ‡ฆ็†ใซๅคฑๆ•—ใ—ใพใ—ใŸ" + }, + "vpn": { + "connected": "{name} ใซๆŽฅ็ถšใ—ใพใ—ใŸ", + "disconnected": "{name} ใ‹ใ‚‰ๅˆ‡ๆ–ญใ—ใพใ—ใŸ" + }, + "wallpaper-colors": { + "disabled": "ๅฃ็ด™้…่‰ฒใฎ็”Ÿๆˆ๏ผš็„กๅŠน", + "enabled": "ๅฃ็ด™้…่‰ฒใฎ็”Ÿๆˆ๏ผšๆœ‰ๅŠน", + "label": "ๅฃ็ด™้…่‰ฒใฎ็”Ÿๆˆ" + }, + "wifi": { + "connected": "{ssid} ใซๆŽฅ็ถšใ—ใพใ—ใŸ", + "connection-failed": "ๆŽฅ็ถšใซๅคฑๆ•—ใ—ใพใ—ใŸ", + "connection-timeout": "ๆŽฅ็ถšใ‚ฟใ‚คใƒ ใ‚ขใ‚ฆใƒˆ", + "disconnected": "{ssid} ใ‹ใ‚‰ๅˆ‡ๆ–ญใ—ใพใ—ใŸ", + "incorrect-password": "ใƒ‘ใ‚นใƒฏใƒผใƒ‰ใŒ้•ใ„ใพใ™", + "network-not-found": "ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“" + } + }, + "tooltips": { + "add-widget": "ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’่ฟฝๅŠ ", + "auto-connect": "ใ“ใฎใƒ‡ใƒใ‚คใ‚นใฎ่‡ชๅ‹•ๆŽฅ็ถšใ‚’ๅˆ‡ใ‚Šๆ›ฟใˆใ‚‹", + "bluetooth-auto-connect-off": "่‡ชๅ‹•ๆŽฅ็ถšใŒ็„กๅŠนใงใ™", + "bluetooth-auto-connect-on": "่‡ชๅ‹•ๆŽฅ็ถšใŒๆœ‰ๅŠนใงใ™", + "bluetooth-devices": "Bluetooth ใƒ‡ใƒใ‚คใ‚น", + "brightness-at": "็”ป้ขใฎๆ˜Žใ‚‹ใ•๏ผš{brightness}%", + "click-to-start-recording": "็”ป้ข้Œฒ็”ป๏ผˆ้Œฒ็”ป้–‹ๅง‹๏ผ‰", + "click-to-stop-recording": "็”ป้ข้Œฒ็”ป๏ผˆ้Œฒ็”ปๅœๆญข๏ผ‰", + "collapse": "ใ‚ตใ‚คใƒ‰ใƒใƒผใ‚’ๆŠ˜ใ‚ŠใŸใŸใ‚€", + "copy-address": "ใ‚ขใƒ‰ใƒฌใ‚นใ‚’ใ‚ณใƒ”ใƒผ", + "delete-notification": "้€š็Ÿฅใ‚’ๅ‰Š้™ค", + "dismiss-notification": "้€š็Ÿฅใ‚’้–‰ใ˜ใ‚‹", + "do-not-disturb-enabled": "ใŠใ‚„ใ™ใฟใƒขใƒผใƒ‰", + "expand": "ใ‚ตใ‚คใƒ‰ใƒใƒผใ‚’ๅฑ•้–‹", + "forget-network": "ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏ่จญๅฎšใ‚’ๅ‰Š้™ค", + "grid-view": "ใ‚ฐใƒชใƒƒใƒ‰่กจ็คบ", + "hidden-files-hide": "้š ใ—ใƒ•ใ‚กใ‚คใƒซ", + "hide-unnamed-devices": "ๅๅ‰ใฎใชใ„ใƒ‡ใƒใ‚คใ‚นใ‚’้š ใ™", + "home": "ใƒ›ใƒผใƒ ใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒช", + "input-muted": "ๅ…ฅๅŠ›ใƒŸใƒฅใƒผใƒˆๅˆ‡ใ‚Šๆ›ฟใˆ", + "keep-awake": "ใ‚นใƒชใƒผใƒ—้˜ฒๆญข", + "keyboard-layout": "ใ‚ญใƒผใƒœใƒผใƒ‰ใƒฌใ‚คใ‚ขใ‚ฆใƒˆ๏ผš{layout}", + "list-view": "ใƒชใ‚นใƒˆ่กจ็คบ", + "manage-vpn": "VPN ๆŽฅ็ถš", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใฎๆœ€ๅคงๆ•ฐใซ้”ใ—ใพใ—ใŸ", + "microphone-volume-at": "ใƒžใ‚คใ‚ฏ้Ÿณ้‡๏ผš{volume}%", + "move-to-section": "{section} ใซ็งปๅ‹•", + "mute": "ใƒŸใƒฅใƒผใƒˆ", + "next-media": "ๆฌกใฎใƒˆใƒฉใƒƒใ‚ฏ", + "next-month": "็ฟŒๆœˆ", + "night-light-not-installed": "ๅคœ้–“ใƒขใƒผใƒ‰๏ผˆๅˆฉ็”จไธๅฏ๏ผ‰", + "noctalia-performance-enabled": "Noctalia ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใƒขใƒผใƒ‰", + "open-annotation-tool": "ๆณจ้‡ˆใƒ„ใƒผใƒซใง้–‹ใ", + "open-control-center": "ใ‚ณใƒณใƒˆใƒญใƒผใƒซใ‚ปใƒณใ‚ฟใƒผ", + "open-notification-history-enable-dnd": "้€š็Ÿฅๅฑฅๆญด", + "open-settings": "่จญๅฎš", + "open-tray-dropdown": "ใ‚ทใ‚นใƒ†ใƒ ใƒˆใƒฌใ‚ค", + "output-muted": "ๅ‡บๅŠ›ใƒŸใƒฅใƒผใƒˆๅˆ‡ใ‚Šๆ›ฟใˆ", + "power-profile": "้›ปๆบใƒ—ใƒญใƒ•ใ‚กใ‚คใƒซ๏ผš{profile}", + "previous-media": "ๅ‰ใฎใƒˆใƒฉใƒƒใ‚ฏ", + "previous-month": "ๅ‰ๆœˆ", + "refresh-devices": "ใƒ‡ใƒใ‚คใ‚นใ‚’ๆ›ดๆ–ฐ", + "refresh-wallhaven": "Wallhaven ใฎๆคœ็ดข็ตๆžœใ‚’ๆ›ดๆ–ฐ", + "refresh-wallpaper-list": "ๅฃ็ด™ใƒชใ‚นใƒˆใ‚’ๆ›ดๆ–ฐ", + "remove-widget": "ใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’ๅ‰Š้™ค", + "search-close": "ๆคœ็ดขใ‚’้–‰ใ˜ใ‚‹", + "session-menu": "ใ‚ปใƒƒใ‚ทใƒงใƒณใƒกใƒ‹ใƒฅใƒผ", + "show-all-devices": "ใ™ในใฆใฎใƒ‡ใƒใ‚คใ‚นใ‚’่กจ็คบ", + "switch-to-dark-mode": "ใƒ€ใƒผใ‚ฏใƒขใƒผใƒ‰", + "switch-to-light-mode": "ใƒฉใ‚คใƒˆใƒขใƒผใƒ‰", + "unmute": "ใƒŸใƒฅใƒผใƒˆ่งฃ้™ค", + "up": "่ฆชใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒช", + "volume-at": "ๅ‡บๅŠ›้Ÿณ้‡๏ผš{volume}%", + "wallpaper-selector": "ๅฃ็ด™้ธๆŠžใƒ‘ใƒใƒซ" + }, + "wallpaper": { + "browse": { + "empty-directory": "ใ“ใฎใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒชใฏ็ฉบใงใ™ใ€‚", + "go-root": "ๅฃ็ด™ใฎใƒซใƒผใƒˆใซ็งปๅ‹•", + "go-up": "่ฆชใƒ•ใ‚ฉใƒซใƒ€ใƒผใธ็งปๅ‹•", + "go-up-hint": "ๆˆปใ‚‹ใƒœใ‚ฟใƒณใ‚’ไฝฟใฃใฆไธŠใซ็งปๅ‹•ใ—ใพใ™ใ€‚" + }, + "configure-directory": "็”ปๅƒใƒ•ใ‚กใ‚คใƒซใŒๅซใพใ‚Œใ‚‹ๅฃ็ด™ใƒ•ใ‚ฉใƒซใƒ€ใ‚’่จญๅฎšใ—ใฆใใ ใ•ใ„ใ€‚", + "fill-modes": { + "crop": "ๆ‹กๅคงใ™ใ‚‹", + "fit": "็”ป้ขใซๅˆใ‚ใ›ใ‚‹", + "repeat": "็นฐใ‚Š่ฟ”ใ—๏ผˆใ‚ฟใ‚คใƒซ๏ผ‰", + "stretch": "ๅผ•ใไผธใฐใ—ใฆ่กจ็คบ" + }, + "no-match": "ไธ€่‡ดใ™ใ‚‹้ …็›ฎใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚", + "no-wallpaper": "ๅฃ็ด™ใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใ€‚", + "panel": { + "apikey-help": "ๆˆไบบๅ‘ใ‘ใ‚ณใƒณใƒ†ใƒณใƒ„ใซใ‚ขใ‚ฏใ‚ปใ‚นใ™ใ‚‹ใซใฏใ€APIใ‚ญใƒผใŒๅฟ…่ฆใงใ™ใ€‚", + "apikey-label": "APIใ‚ญใƒผ", + "apikey-managed-by-env": "NOCTALIA_WALLHAVEN_API_KEY ็’ฐๅขƒๅค‰ๆ•ฐ็ตŒ็”ฑใง็ฎก็†ใ•ใ‚Œใพใ™ใ€‚", + "apikey-placeholder": "Wallhaven APIใ‚ญใƒผใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„", + "appearance-dark-tab": "ใƒ€ใƒผใ‚ฏ", + "appearance-light-tab": "ใƒฉใ‚คใƒˆ", + "categories-anime": "ใ‚ขใƒ‹ใƒก", + "categories-label": "ใ‚ซใƒ†ใ‚ดใƒช", + "categories-people": "ไบบ็‰ฉ", + "color-extraction-disabled": "ๅฃ็ด™ใฎ่‰ฒๆŠฝๅ‡บใ‚’ไฝฟ็”จใ™ใ‚‹", + "color-extraction-enabled": "ไบ‹ๅ‰ๅฎš็พฉใ•ใ‚ŒใŸ้…่‰ฒใ‚นใ‚ญใƒผใƒ ใ‚’ไฝฟ็”จใ™ใ‚‹", + "header-devices-apply-all-tooltip": "ใƒขใƒ‹ใ‚ฟใƒผใ”ใจใซๅฃ็ด™ใ‚’้ธๆŠž", + "header-devices-per-monitor-tooltip": "ใ™ในใฆใฎใƒขใƒ‹ใ‚ฟใƒผใซ้ฉ็”จ", + "header-link-light-dark-tooltip": "ใƒ†ใƒผใƒžๅˆ‡ใ‚Šๆ›ฟใˆๆ™‚ใซๅฃ็ด™ใ‚’็ถญๆŒ", + "header-separate-light-dark-tooltip": "ใƒ†ใƒผใƒžใซๅˆใ‚ใ›ใฆๅฃ็ด™ใ‚’่‡ชๅ‹•ๅˆ‡ใ‚Šๆ›ฟใˆ", + "header-sun-linked-tooltip": "ใƒฉใ‚คใƒˆใจใƒ€ใƒผใ‚ฏใฎๅฃ็ด™ใ‚’ๅˆ†ใ‘ใ‚‹", + "header-sun-separate-tooltip": "ใƒฉใ‚คใƒˆใจใƒ€ใƒผใ‚ฏใงๅŒใ˜ๅฃ็ด™", + "order-asc": "ๆ˜‡้ †", + "order-desc": "้™้ †", + "order-label": "้ †ๅบ", + "purity-label": "ใ‚ณใƒณใƒ†ใƒณใƒ„ใƒ•ใ‚ฃใƒซใ‚ฟ", + "purity-nsfw": "R-18", + "purity-sfw": "ๅ…จๅนด้ฝข", + "purity-sketchy": "้š›ใฉใ„", + "ratios-all-portrait": "ใ™ในใฆใฎ็ธฆ้•ท", + "ratios-all-wide": "ใ™ในใฆใฎใƒฏใ‚คใƒ‰", + "ratios-any": "ใ™ในใฆ", + "ratios-label": "ใ‚ขใ‚นใƒšใ‚ฏใƒˆๆฏ”", + "resolution-atleast": "ไปฅไธŠ", + "resolution-exact": "ๅฎŒๅ…จไธ€่‡ด", + "resolution-label": "่งฃๅƒๅบฆ", + "resolution-mode-label": "ใƒขใƒผใƒ‰", + "solid-color-tooltip": "็„กๅœฐ", + "sort-date-asc": "ๅคใ„้ †ใซไธฆในๆ›ฟใˆ", + "sort-date-desc": "ๆ–ฐใ—ใ„้ †ใซไธฆในๆ›ฟใˆ", + "sort-name-asc": "ๅๅ‰ใงใ‚ฝใƒผใƒˆ (A-Z)", + "sort-name-desc": "ๅๅ‰้ †ใซไธฆในๆ›ฟใˆ (Z-A)", + "sort-random": "ใƒฉใƒณใƒ€ใƒ ใงไธฆในๆ›ฟใˆ", + "sorting-date-added": "่ฟฝๅŠ ๆ—ฅ", + "sorting-favorites": "ใŠๆฐ—ใซๅ…ฅใ‚Šๆ•ฐ", + "sorting-label": "ไธฆในๆ›ฟใˆ", + "sorting-relevance": "้–ข้€ฃๅบฆ", + "sorting-toplist": "ใƒˆใƒƒใƒ—ใƒชใ‚นใƒˆ", + "sorting-views": "้–ฒ่ฆงๆ•ฐ", + "source-label": "ๅ–ๅพ—ๅ…ƒ", + "source-wallhaven": "Wallhaven", + "title": "ๅฃ็ด™้ธๆŠžใƒ‘ใƒใƒซ", + "wallhaven-settings-title": "Wallhaven ่จญๅฎš" + }, + "transitions": { + "disc": "ใƒ‡ใ‚ฃใ‚นใ‚ฏ", + "fade": "ใƒ•ใ‚งใƒผใƒ‰", + "honeycomb": "ใƒใƒ‹ใ‚ซใƒ ", + "pixelate": "ใƒ”ใ‚ฏใ‚ปใƒซๅŒ–", + "stripes": "ใ‚นใƒˆใƒฉใ‚คใƒ—", + "wipe": "ใƒฏใ‚คใƒ—" + }, + "try-different-search": "ๅˆฅใฎๆคœ็ดขๆกไปถใ‚’่ฉฆใ—ใฆใใ ใ•ใ„ใ€‚", + "wallhaven": { + "loading": "ๅฃ็ด™ใ‚’่ชญใฟ่พผใฟไธญ...", + "no-results": "ๅฃ็ด™ใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใ€‚ๅˆฅใฎๆคœ็ดขๆกไปถใ‚’่ฉฆใ—ใฆใใ ใ•ใ„ใ€‚", + "page": "{current} / {total}", + "page-prefix": "ใƒšใƒผใ‚ธ", + "page-suffix": "{total}ไปถไธญ" + } + }, + "weather": { + "clear-sky": "ๅฟซๆ™ด", + "drizzle": "้œง้›จ", + "fog": "้œง", + "mainly-clear": "ๆ™ดใ‚Œ", + "overcast": "ๆ›‡ใ‚Š", + "partly-cloudy": "ๆ™ดใ‚Œๆ™‚ใ€…ๆ›‡ใ‚Š", + "rain-showers": "้›จ", + "snow": "้›ช", + "thunderstorm": "้›ท้›จ" + }, + "widgets": { + "color-picker": { + "palette-description": "่ฑŠๅฏŒใชใƒ—ใƒชใ‚ปใƒƒใƒˆใ‚ซใƒฉใƒผใ‹ใ‚‰้ธๆŠžใ—ใพใ™ใ€‚", + "palette-label": "ใƒ‘ใƒฌใƒƒใƒˆ", + "palette-theme-colors": "ใƒ†ใƒผใƒžใฎใ‚ซใƒฉใƒผใƒ‘ใƒฌใƒƒใƒˆใ€‚", + "title": "่‰ฒ้ธๆŠž" + }, + "datetime-tokens": { + "ampm-lowercase": "ๅˆๅ‰/ๅˆๅพŒ๏ผˆ่‹ฑ่ชž่กจ่จ˜ใงใฏๅฐๆ–‡ๅญ—๏ผ‰", + "ampm-uppercase": "ๅˆๅ‰/ๅˆๅพŒ๏ผˆ่‹ฑ่ชž่กจ่จ˜ใงใฏๅคงๆ–‡ๅญ—๏ผ‰", + "common-12hour-time-minutes": "12ๆ™‚้–“่กจ่จ˜๏ผˆๅˆ†ใ‚’ๅซใ‚€๏ผ‰", + "common-24hour-time-minutes": "24ๆ™‚้–“่กจ่จ˜๏ผˆๅˆ†ใ‚’ๅซใ‚€๏ผ‰", + "common-24hour-time-seconds": "24ๆ™‚้–“่กจ่จ˜๏ผˆ็ง’ใ‚’ๅซใ‚€๏ผ‰", + "common-european-date": "ใƒจใƒผใƒญใƒƒใƒ‘ๅฝขๅผใฎๆ—ฅไป˜๏ผˆๆ—ฅ.ๆœˆ.ๅนด๏ผ‰", + "common-iso-date": "ISO ๅฝขๅผใฎๆ—ฅไป˜๏ผˆๅนด-ๆœˆ-ๆ—ฅ๏ผ‰", + "common-us-date": "็ฑณๅ›ฝๅฝขๅผใฎๆ—ฅไป˜๏ผˆๆœˆ/ๆ—ฅ/ๅนด๏ผ‰", + "common-weekday-date": "ๆ›œๆ—ฅใจๆ—ฅไป˜", + "common-weekday-month-day": "ๆ›œๆ—ฅใ€ๆœˆใ€ๆ—ฅ", + "day-abbreviated": "ๆ›œๆ—ฅใฎ็Ÿญ็ธฎๅ๏ผˆไพ‹๏ผš็ซ๏ผ‰", + "day-full": "ๆ›œๆ—ฅใฎๅฎŒๅ…จๅ๏ผˆไพ‹๏ผšๆœˆๆ›œๆ—ฅ๏ผ‰", + "day-leading-zero": "ใ‚ผใƒญๅŸ‹ใ‚ใ‚ใ‚Šใฎๆ—ฅไป˜ (01-31)", + "day-no-leading-zero": "ใ‚ผใƒญๅŸ‹ใ‚ใชใ—ใฎๆ—ฅไป˜ (1-31)", + "hour-leading-zero": "ใ‚ผใƒญๅŸ‹ใ‚ใ‚ใ‚Šใฎๆ™‚้–“ (00-23) โ€” 24ๆ™‚้–“่กจ่จ˜", + "hour-no-leading-zero": "ใ‚ผใƒญๅŸ‹ใ‚ใชใ—ใฎๆ™‚้–“ (0-23) โ€” 24ๆ™‚้–“่กจ่จ˜", + "minute-leading-zero": "ใ‚ผใƒญๅŸ‹ใ‚ใ‚ใ‚Šใฎๅˆ† (00-59)", + "minute-no-leading-zero": "ใ‚ผใƒญๅŸ‹ใ‚ใชใ—ใฎๅˆ† (0-59)", + "month-abbreviated": "ๆœˆใฎ็Ÿญ็ธฎๅ๏ผˆไพ‹๏ผš1ๆœˆ/Jan๏ผ‰", + "month-full": "ๆœˆใฎๅฎŒๅ…จๅ๏ผˆไพ‹๏ผš1ๆœˆ/January๏ผ‰", + "month-number-leading-zero": "ใ‚ผใƒญๅŸ‹ใ‚ใ‚ใ‚Šใฎๆœˆ (01-12)", + "month-number-no-zero": "ใ‚ผใƒญๅŸ‹ใ‚ใชใ—ใฎๆœˆ (1-12)", + "second-leading-zero": "ใ‚ผใƒญๅŸ‹ใ‚ใ‚ใ‚Šใฎ็ง’ (00-59)", + "second-no-leading-zero": "ใ‚ผใƒญๅŸ‹ใ‚ใชใ—ใฎ็ง’ (0-59)", + "timezone-abbreviation": "ใ‚ฟใ‚คใƒ ใ‚พใƒผใƒณใฎ็•ฅ็งฐ", + "year-four-digit": "4ๆกใฎ่ฅฟๆšฆ", + "year-two-digit": "2ๆกใฎ่ฅฟๆšฆ (00-99)" + }, + "file-picker": { + "item": "้ …็›ฎ", + "items": "้ …็›ฎ", + "search-placeholder": "ใƒ•ใ‚กใ‚คใƒซใจใƒ•ใ‚ฉใƒซใƒ€ใ‚’ๆคœ็ดข...", + "select-current": "็พๅœจใฎใƒ•ใ‚ฉใƒซใƒ€ใ‚’้ธๆŠžใ™ใ‚‹", + "select-file": "ใƒ•ใ‚กใ‚คใƒซใ‚’้ธๆŠžใ™ใ‚‹", + "select-folder": "ใƒ•ใ‚ฉใƒซใƒ€ใ‚’้ธๆŠžใ™ใ‚‹", + "selected": "้ธๆŠžๆธˆใฟ๏ผš", + "title": "ใƒ•ใ‚กใ‚คใƒซ้ธๆŠž" + }, + "file-picker-title": "ใƒ•ใ‚กใ‚คใƒซใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„", + "icon-picker": { + "title": "ใ‚ขใ‚คใ‚ณใƒณ้ธๆŠž" + }, + "tooltip-placeholder": "ใƒ—ใƒฌใƒผใ‚นใƒ›ใƒซใƒ€ใƒผ" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "ๅŒฟๅID", + "ca-cert": "CA่จผๆ˜Žๆ›ธ", + "eap-method": "EAPๆ–นๅผ", + "password": "ใƒฆใƒผใ‚ถใƒผใƒ‘ใ‚นใƒฏใƒผใƒ‰", + "phase2-auth": "ใƒ•ใ‚งใƒผใ‚บ2่ช่จผ", + "username": "ID / ใƒฆใƒผใ‚ถใƒผๅ" + }, + "panel": { + "action-required": "ใ‚ขใ‚ฏใ‚ทใƒงใƒณใŒๅฟ…่ฆ", + "add-hidden-network": "้ž่กจ็คบใฎใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใ‚’่ฟฝๅŠ ", + "add-network": "ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใ‚’่ฟฝๅŠ ", + "available-interfaces": "ๅˆฉ็”จๅฏ่ƒฝใชใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใƒผใ‚น", + "available-networks": "ๅˆฉ็”จๅฏ่ƒฝใชใƒใƒƒใƒˆใƒฏใƒผใ‚ฏ", + "disabled": "Wi-Fi ใฏ็„กๅŠนใงใ™", + "disconnecting": "ๅˆ‡ๆ–ญไธญ...", + "dns": "DNS", + "enable-message": "ๅˆฉ็”จๅฏ่ƒฝใชใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใ‚’่กจ็คบใ™ใ‚‹ใซใฏ Wi-Fi ใ‚’ๆœ‰ๅŠนใซใ—ใฆใใ ใ•ใ„ใ€‚", + "enter-password": "ใƒ‘ใ‚นใƒฏใƒผใƒ‰ใ‚’ๅ…ฅๅŠ›...", + "forget": "ๅ‰Š้™ค", + "forget-network": "ใ“ใฎใƒใƒƒใƒˆใƒฏใƒผใ‚ฏ่จญๅฎšใ‚’ๅ‰Š้™คใ—ใพใ™ใ‹๏ผŸ", + "forgetting": "ๅ‰Š้™คไธญ...", + "hidden-network": "้š ใ—ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏ", + "interface": "ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใƒผใ‚น", + "internet-connected": "ใ‚คใƒณใ‚ฟใƒผใƒใƒƒใƒˆๆŽฅ็ถš", + "internet-limited": "ใ‚คใƒณใ‚ฟใƒผใƒใƒƒใƒˆใชใ—", + "internet-status": "ใ‚คใƒณใ‚ฟใƒผใƒใƒƒใƒˆๆŽฅ็ถš็Šถๆณ", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "ไฟๅญ˜ๆธˆใฟใƒใƒƒใƒˆใƒฏใƒผใ‚ฏ", + "link-speed": "ใƒชใƒณใ‚ฏ้€Ÿๅบฆ", + "network-name-ssid": "ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏๅ (SSID)", + "no-ethernet-devices": "ใ‚คใƒผใ‚ตใƒใƒƒใƒˆใƒ‡ใƒใ‚คใ‚นใŒๆคœๅ‡บใ•ใ‚Œใพใ›ใ‚“ใงใ—ใŸ", + "no-networks": "ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“", + "saved": "ไฟๅญ˜ๆธˆใฟ", + "scan-again": "ๅ†ใ‚นใ‚ญใƒฃใƒณ", + "searching": "่ฟ‘ใใฎใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใ‚’ๆคœ็ดขไธญ...", + "security-open": "้–‹ใ", + "security-wep": "WEP", + "security-wpa": "WPA/WPA2/WPA3 ใƒ‘ใƒผใ‚ฝใƒŠใƒซ", + "security-wpa-ent": "WPAใ‚จใƒณใ‚ฟใƒผใƒ—ใƒฉใ‚คใ‚บ", + "security-wpa2-ent": "WPA2ใ‚จใƒณใ‚ฟใƒผใƒ—ใƒฉใ‚คใ‚บ", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3ใ‚จใƒณใ‚ฟใƒผใƒ—ใƒฉใ‚คใ‚บ", + "show-password": "ใƒ‘ใ‚นใƒฏใƒผใƒ‰ใ‚’่กจ็คบ", + "title": "Wi-Fi" + }, + "signal": { + "excellent": "ๅ„ช็ง€", + "fair": "ๆ™ฎ้€š", + "good": "่‰ฏใ„", + "poor": "ไธ่‰ฏ", + "weak": "ๅผฑใ„" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/ko-KR.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/ko-KR.json new file mode 100644 index 0000000..31d81f3 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/ko-KR.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "๊ธฐ๋ก ์ง€์šฐ๊ธฐ", + "close-app": "{app} ๋‹ซ๊ธฐ", + "connect-vpn": "{name} ์—ฐ๊ฒฐ", + "cycle-visualizer": "๋น„์ฃผ์–ผ๋ผ์ด์ € ์ˆœํ™˜", + "disable-bluetooth": "๋ธ”๋ฃจํˆฌ์Šค ๋น„ํ™œ์„ฑํ™”", + "disable-dnd": "๋ฐฉํ•ด ๊ธˆ์ง€ ๋ชจ๋“œ ๋„๊ธฐ", + "disable-wifi": "Wi-Fi ๋น„ํ™œ์„ฑํ™”", + "disconnect-vpn": "{name} ์—ฐ๊ฒฐ ํ•ด์ œ", + "dock-settings": "๋… ์„ค์ •", + "enable-bluetooth": "๋ธ”๋ฃจํˆฌ์Šค ํ™œ์„ฑํ™”", + "enable-dnd": "๋ฐฉํ•ด ๊ธˆ์ง€ ๋ชจ๋“œ ์ผœ๊ธฐ", + "enable-wifi": "Wi-Fi ํ™œ์„ฑํ™”", + "launcher-settings": "๋Ÿฐ์ฒ˜ ์„ค์ •", + "lower-to-bottom": "๋งจ ์•„๋ž˜๋กœ ๋‚ด๋ฆฌ๊ธฐ", + "open-calendar": "๋‹ฌ๋ ฅ ์—ด๊ธฐ", + "open-display-settings": "๋””์Šคํ”Œ๋ ˆ์ด ์„ค์ •", + "open-launcher": "๋Ÿฐ์ฒ˜ ์—ด๊ธฐ", + "open-settings": "์„ค์ • ์—ด๊ธฐ", + "raise-to-top": "๋งจ ์œ„๋กœ ์˜ฌ๋ฆฌ๊ธฐ", + "random-wallpaper": "๋žœ๋ค ๋ฐฐ๊ฒฝํ™”๋ฉด", + "run-custom-command": "์‚ฌ์šฉ์ž ์ง€์ • ๋ช…๋ น ์‹คํ–‰", + "show-active-only": "ํ™œ์„ฑ ํ•ญ๋ชฉ๋งŒ ํ‘œ์‹œ", + "show-all": "๋ชจ๋‘ ํ‘œ์‹œ", + "toggle-mute": "์Œ์†Œ๊ฑฐ ์ „ํ™˜", + "widget-settings": "์œ„์ ฏ ์„ค์ •" + }, + "authentication": { + "error": "์ธ์ฆ ์˜ค๋ฅ˜", + "failed": "์ธ์ฆ ์‹คํŒจ" + }, + "bar": { + "active-window": { + "colorize-icons-description": "ํ™œ์„ฑ ์ฐฝ ์•„์ด์ฝ˜์— ํ…Œ๋งˆ ์ƒ‰์ƒ์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "hide-mode-description": "ํ™œ์„ฑ ์ฐฝ์ด ์—†์„ ๋•Œ ์œ„์ ฏ์˜ ๋™์ž‘ ๋ฐฉ์‹์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.", + "scrolling-mode-description": "์ฐฝ ์ œ๋ชฉ์ด ๊ธธ ๋•Œ ํ…์ŠคํŠธ ์Šคํฌ๋กค ํ™œ์„ฑํ™” ์‹œ์ ์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.", + "show-app-icon-description": "์ฐฝ ์ œ๋ชฉ ์˜†์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์•„์ด์ฝ˜์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-app-icon-label": "์•ฑ ์•„์ด์ฝ˜ ํ‘œ์‹œ", + "show-app-text-description": "ํ™œ์„ฑ ์ฐฝ์˜ ์ œ๋ชฉ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-app-text-label": "์ฐฝ ์ œ๋ชฉ ํ‘œ์‹œ" + }, + "audio-visualizer": { + "color-name-description": "๋น„์ฃผ์–ผ๋ผ์ด์ €์˜ ์ƒ‰์ƒ์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.", + "color-name-label": "์ƒ‰์œผ๋กœ ์ฑ„์šฐ๊ธฐ", + "height-description": "์‚ฌ์šฉ์ž ์ง€์ • ๊ตฌ์„ฑ ์š”์†Œ ๋„ˆ๋น„.", + "hide-when-idle-description": "ํ™œ์„ฑํ™”ํ•˜๋ฉด ์žฌ์ƒ๊ธฐ๊ฐ€ ์žฌ์ƒ ์ค‘์ด์ง€ ์•Š์„ ๋•Œ ๋น„์ฃผ์–ผ๋ผ์ด์ €๋ฅผ ์ˆจ๊น๋‹ˆ๋‹ค.", + "hide-when-idle-label": "๋ฏธ๋””์–ด ์žฌ์ƒ ์ค‘์ด ์•„๋‹ ๋•Œ ์ˆจ๊ธฐ๊ธฐ", + "width-description": "๊ตฌ์„ฑ ์š”์†Œ์˜ ๋„ˆ๋น„๋ฅผ ์‚ฌ์šฉ์ž ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค." + }, + "battery": { + "device-default": "๊ธฐ๋ณธ๊ฐ’ (๋””์Šคํ”Œ๋ ˆ์ด ์žฅ์น˜)", + "device-description": "ํ‘œ์‹œํ•  ๋ฐฐํ„ฐ๋ฆฌ ์žฅ์น˜๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.", + "device-label": "๋ฐฐํ„ฐ๋ฆฌ ์žฅ์น˜", + "display-mode-description": "๋ฐฐํ„ฐ๋ฆฌ๊ฐ€ ๋ฐ”์— ์–ด๋–ป๊ฒŒ ํ‘œ์‹œ๋ ์ง€ ์„ ํƒํ•˜์„ธ์š”.", + "display-mode-graphic": "๊ทธ๋ž˜ํ”ฝ ๋ฐฐํ„ฐ๋ฆฌ", + "display-mode-graphic-clean": "๊ทธ๋ž˜ํ”ฝ ๋ฐฐํ„ฐ๋ฆฌ (ํผ์„ผํŠธ ์—†์Œ)", + "display-mode-icon-always": "์•„์ด์ฝ˜ - ํ•ญ์ƒ % ํ‘œ์‹œ", + "display-mode-icon-hover": "์•„์ด์ฝ˜ - ํ˜ธ๋ฒ„ ์‹œ ํ‘œ์‹œ", + "display-mode-icon-only": "์•„์ด์ฝ˜๋งŒ", + "hide-if-idle-description": "๋ฐฐํ„ฐ๋ฆฌ๊ฐ€ ์ถฉ์ „ ์ค‘์ด๊ฑฐ๋‚˜ ๋ฐฉ์ „ ์ค‘์ด ์•„๋‹ ๋•Œ ์œ„์ ฏ์„ ์ˆจ๊น๋‹ˆ๋‹ค.", + "hide-if-idle-label": "์œ ํœด ์ƒํƒœ์ผ ๋•Œ ์ˆจ๊ธฐ๊ธฐ", + "hide-if-not-detected-description": "์‹œ์Šคํ…œ์—์„œ ๋ฐฐํ„ฐ๋ฆฌ๊ฐ€ ๊ฐ์ง€๋˜์ง€ ์•Š์œผ๋ฉด ์œ„์ ฏ์„ ์ˆจ๊น๋‹ˆ๋‹ค.", + "hide-if-not-detected-label": "๊ฐ์ง€๋˜์ง€ ์•Š์œผ๋ฉด ์ˆจ๊ธฐ๊ธฐ", + "low-battery-threshold-label": "๋ฐฐํ„ฐ๋ฆฌ ๋ถ€์กฑ ๊ฒฝ๊ณ  ์ž„๊ณ„๊ฐ’", + "show-noctalia-performance-description": "๋ฐฐํ„ฐ๋ฆฌ ํŒจ๋„ ๋‚ด๋ถ€์— Noctalia ์„ฑ๋Šฅ ๋ชจ๋“œ ํ† ๊ธ€์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
๋ฆฌ์†Œ์Šค ์‚ฌ์šฉ๋Ÿ‰์„ ์ค„์ด๊ธฐ ์œ„ํ•ด Noctalia์˜ ๊ทธ๋ฆผ์ž์™€ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "show-noctalia-performance-label": "Noctalia ์„ฑ๋Šฅ ํ† ๊ธ€ ํ‘œ์‹œ", + "show-power-profile-description": "๋ฐฐํ„ฐ๋ฆฌ ํŒจ๋„ ๋‚ด๋ถ€์— ์ „์› ํ”„๋กœํ•„ ์„ ํƒ ํ•ญ๋ชฉ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-power-profile-label": "์ „์› ํ”„๋กœํ•„ ์ œ์–ด ํ‘œ์‹œ" + }, + "brightness": { + "apply-all-description": "์Šคํฌ๋กค ํœ ์„ ์‚ฌ์šฉํ•  ๋•Œ ๋ชจ๋“  ๋ชจ๋‹ˆํ„ฐ์˜ ๋ฐ๊ธฐ๋ฅผ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.", + "apply-all-label": "์Šคํฌ๋กค ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ชจ๋“  ๋ชจ๋‹ˆํ„ฐ์— ์ ์šฉ" + }, + "clock": { + "clock-display-description": "์•„๋ž˜ ๋ชฉ๋ก์—์„œ ํ† ํฐ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์‹œ๊ณ„ ํ‘œ์‹œ๋ฅผ ์‚ฌ์šฉ์ž ์ง€์ •ํ•˜์„ธ์š”. 12์‹œ๊ฐ„ ํ˜•์‹์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด 'AP' ํ† ํฐ์„ ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "clock-display-label": "์‹œ๊ณ„ ํ‘œ์‹œ", + "custom-font-description": "์‹œ๊ณ„ ํ‘œ์‹œ์— ์‚ฌ์šฉํ•  ์‚ฌ์šฉ์ž ์ง€์ • ๊ธ€๊ผด์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.", + "custom-font-label": "์‚ฌ์šฉ์ž ์ง€์ • ๊ธ€๊ผด", + "custom-font-placeholder": "์‚ฌ์šฉ์ž ์ง€์ • ๊ธ€๊ผด ์„ ํƒ...", + "custom-font-search-placeholder": "๊ธ€๊ผด ๊ฒ€์ƒ‰...", + "horizontal-bar-description": "ํŒ: ์ค„๋ฐ”๊ฟˆ์„ ํ•˜๋ ค๋ฉด \\n์„ ์‚ฌ์šฉํ•˜์„ธ์š”.", + "horizontal-bar-label": "์ˆ˜ํ‰ ๋ฐ”", + "preview": "๋ฏธ๋ฆฌ๋ณด๊ธฐ", + "tooltip-format-description": "์‹œ๊ณ„ ์œ„์— ๋งˆ์šฐ์Šค๋ฅผ ์˜ฌ๋ ธ์„ ๋•Œ ํ‘œ์‹œ๋˜๋Š” ํˆดํŒ์˜ ํ˜•์‹ ๋ฌธ์ž์—ด์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ํˆดํŒ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋น„์›Œ ๋‘์„ธ์š”.", + "tooltip-format-label": "ํˆดํŒ ํ˜•์‹", + "use-custom-font-description": "์‹œ๊ณ„์˜ ๊ธฐ๋ณธ ๊ธ€๊ผด ๋Œ€์‹  ์‚ฌ์šฉ์ž ์ง€์ • ๊ธ€๊ผด์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "use-custom-font-label": "์‚ฌ์šฉ์ž ์ง€์ • ๊ธ€๊ผด ์‚ฌ์šฉ", + "use-monospaced-font-description": "ํ™œ์„ฑํ™”ํ•˜๋ฉด ์‹œ๊ณ„๊ฐ€ ๊ณ ์ •ํญ ๊ธ€๊ผด์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "use-monospaced-font-label": "๊ณ ์ •ํญ ๊ธ€๊ผด ์‚ฌ์šฉ", + "vertical-bar-description": "๊ฐ ๋ถ€๋ถ„์„ ์ƒˆ ์ค„๋กœ ๋‚˜๋ˆ„๋ ค๋ฉด ๊ณต๋ฐฑ์„ ์‚ฌ์šฉํ•˜์„ธ์š”.", + "vertical-bar-label": "์ˆ˜์ง ๋ฐ”" + }, + "control-center": { + "browse-file": "ํŒŒ์ผ ์ฐพ์•„๋ณด๊ธฐ", + "browse-library": "๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ฐพ์•„๋ณด๊ธฐ", + "color-selection-description": "์•„์ด์ฝ˜์— ํ…Œ๋งˆ ์ƒ‰์ƒ์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "enable-colorization-description": "์•„์ด์ฝ˜ ์ƒ‰์ƒํ™”๋ฅผ ํ™œ์„ฑํ™”ํ•˜๊ณ  ํ…Œ๋งˆ ์ƒ‰์ƒ์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "icon-description": "๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋˜๋Š” ์‚ฌ์šฉ์ž ์ง€์ • ํŒŒ์ผ์—์„œ ์•„์ด์ฝ˜์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.", + "select-custom-icon": "์‚ฌ์šฉ์ž ์ง€์ • ์•„์ด์ฝ˜ ์„ ํƒ", + "use-distro-logo-description": "์‚ฌ์šฉ์ž ์ง€์ • ์•„์ด์ฝ˜ ๋Œ€์‹  ๋ฐฐํฌํŒ ๋กœ๊ณ ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "use-distro-logo-label": "์•„์ด์ฝ˜ ๋Œ€์‹  ๋ฐฐํฌํŒ ๋กœ๊ณ  ์‚ฌ์šฉ" + }, + "custom-button": { + "collapse-condition-description": "์ถœ๋ ฅ ํ…์ŠคํŠธ๊ฐ€ ์ด ๊ฐ’๊ณผ ์ผ์น˜ํ•˜๋ฉด ๋ฒ„ํŠผ์ด ์ ‘ํž™๋‹ˆ๋‹ค.", + "collapse-condition-label": "์ ‘ํžˆ๋Š” ์กฐ๊ฑด", + "color-selection-description": "์•„์ด์ฝ˜๊ณผ ํ…์ŠคํŠธ์— ํ…Œ๋งˆ ์ƒ‰์ƒ์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "default-tooltip": "์‚ฌ์šฉ์ž ์ง€์ • ๋ฒ„ํŠผ, ์„ค์ •์—์„œ ๊ตฌ์„ฑ", + "display-command-output-description": "์ •๊ธฐ์ ์ธ ๊ฐ„๊ฒฉ์œผ๋กœ ์‹คํ–‰ํ•  ๋ช…๋ น์„ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค. ์ถœ๋ ฅ์˜ ์ฒซ ๋ฒˆ์งธ ์ค„์ด ํ…์ŠคํŠธ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.", + "display-command-output-label": "๋ช…๋ น ์ถœ๋ ฅ ํ‘œ์‹œ", + "display-command-output-stream-description": "์ง€์†์ ์œผ๋กœ ์‹คํ–‰ํ•  ๋ช…๋ น์„ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.", + "dynamic-text": "๋™์  ํ…์ŠคํŠธ", + "enable-colorization-description": "์‚ฌ์šฉ์ž ์ง€์ • ๋ฒ„ํŠผ ์•„์ด์ฝ˜๊ณผ ํ…์ŠคํŠธ์˜ ์ƒ‰์ƒํ™”๋ฅผ ํ™œ์„ฑํ™”ํ•˜์—ฌ ํ…Œ๋งˆ ์ƒ‰์ƒ์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "enable-colorization-label": "์ƒ‰์ƒํ™” ํ™œ์„ฑํ™”", + "general-tooltip-text-description": "๋ฒ„ํŠผ์˜ ํˆดํŒ์— ํ‘œ์‹œํ•  ์‚ฌ์šฉ์ž ์ง€์ • ํ…์ŠคํŠธ.", + "general-tooltip-text-label": "์‚ฌ์šฉ์ž ์ง€์ • ๋„๊ตฌ ์„ค๋ช… ํ…์ŠคํŠธ", + "hide-mode-always-expanded": "ํ•ญ์ƒ ํŽผ์นจ", + "hide-mode-description": "๋ช…๋ น์— ์ถœ๋ ฅ์ด ์—†์„ ๋•Œ ์œ„์ ฏ์˜ ๊ฐ€์‹œ์„ฑ์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.", + "hide-mode-expand-with-output": "์ถœ๋ ฅ์ด ์žˆ์„ ๋•Œ ํŽผ์นจ", + "hide-mode-label": "์ˆจ๊ธฐ๊ธฐ ๋ชจ๋“œ", + "hide-mode-max-transparent": "์ตœ๋Œ€๋กœ ํŽผ์น˜์ง€๋งŒ ํˆฌ๋ช…ํ•˜๊ฒŒ", + "icon-description": "๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์•„์ด์ฝ˜์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.", + "ipc-identifier-description": "IPC ๋ช…๋ น์„ ์œ„ํ•œ ๊ณ ์œ  ์‹๋ณ„์ž์ž…๋‹ˆ๋‹ค. IPC๋ฅผ ํ†ตํ•ด ์ด ๋ฒ„ํŠผ์„ ์ œ์–ดํ•˜๋ ค๋ฉด 'qs -c noctalia-shell ipc call cb [action] [identifier]'์™€ ํ•จ๊ป˜ ์ด ์‹๋ณ„์ž๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.", + "ipc-identifier-label": "IPC ์‹๋ณ„์ž", + "left-click-description": "๋ฒ„ํŠผ์„ ์™ผ์ชฝ ํด๋ฆญํ–ˆ์„ ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค.", + "left-click-label": "์™ผ์ชฝ ํด๋ฆญ", + "left-click-update-text": "์™ผ์ชฝ ํด๋ฆญ ์‹œ ํ‘œ์‹œ๋œ ํ…์ŠคํŠธ ์—…๋ฐ์ดํŠธ", + "max-text-length-horizontal-description": "์ˆ˜ํ‰ ๋ฐ”์— ํ‘œ์‹œํ•  ์ตœ๋Œ€ ๋ฌธ์ž ์ˆ˜ (ํ…์ŠคํŠธ๋ฅผ ์ˆจ๊ธฐ๋ ค๋ฉด 0).", + "max-text-length-horizontal-label": "์ตœ๋Œ€ ํ…์ŠคํŠธ ๊ธธ์ด (์ˆ˜ํ‰)", + "max-text-length-vertical-description": "์ˆ˜์ง ๋ฐ”์— ํ‘œ์‹œํ•  ์ตœ๋Œ€ ๋ฌธ์ž ์ˆ˜ (ํ…์ŠคํŠธ๋ฅผ ์ˆจ๊ธฐ๋ ค๋ฉด 0).", + "max-text-length-vertical-label": "์ตœ๋Œ€ ํ…์ŠคํŠธ ๊ธธ์ด (์ˆ˜์ง)", + "middle-click-description": "๋ฒ„ํŠผ์„ ๊ฐ€์šด๋ฐ ํด๋ฆญํ–ˆ์„ ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค.", + "middle-click-label": "๊ฐ€์šด๋ฐ ํด๋ฆญ", + "middle-click-update-text": "๊ฐ€์šด๋ฐ ํด๋ฆญ ์‹œ ํ‘œ์‹œ๋œ ํ…์ŠคํŠธ ์—…๋ฐ์ดํŠธ", + "parse-json-description": "๋ช…๋ น ์ถœ๋ ฅ์„ JSON ๊ฐ์ฒด๋กœ ํŒŒ์‹ฑํ•˜์—ฌ ํ…์ŠคํŠธ์™€ ์•„์ด์ฝ˜์„ ๋™์ ์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "parse-json-label": "์ถœ๋ ฅ์„ JSON์œผ๋กœ ํŒŒ์‹ฑ", + "refresh-interval-description": "๋ฐ€๋ฆฌ์ดˆ ๋‹จ์œ„ ๊ฐ„๊ฒฉ.", + "refresh-interval-label": "์ƒˆ๋กœ๊ณ ์นจ ๊ฐ„๊ฒฉ", + "right-click-description": "๋ฒ„ํŠผ์„ ์˜ค๋ฅธ์ชฝ ํด๋ฆญํ–ˆ์„ ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค.", + "right-click-label": "์˜ค๋ฅธ์ชฝ ํด๋ฆญ", + "right-click-update-text": "์˜ค๋ฅธ์ชฝ ํด๋ฆญ ์‹œ ํ‘œ์‹œ๋œ ํ…์ŠคํŠธ ์—…๋ฐ์ดํŠธ", + "show-exec-tooltip-description": "๋ช…๋ น ์„ธ๋ถ€ ์ •๋ณด๊ฐ€ ํฌํ•จ๋œ ํˆดํŒ ํ‘œ์‹œ (์™ผ์ชฝ/์˜ค๋ฅธ์ชฝ/๊ฐ€์šด๋ฐ ํด๋ฆญ, ํœ ).", + "show-exec-tooltip-label": "๋ช…๋ น์–ด ๋„๊ตฌ ์„ค๋ช… ํ‘œ์‹œ", + "show-icon-description": "์œ„์ ฏ ์•„์ด์ฝ˜์˜ ๊ฐ€์‹œ์„ฑ์„ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค.", + "show-icon-label": "์•„์ด์ฝ˜ ํ‘œ์‹œ", + "show-text-tooltip-description": "ํ…์ŠคํŠธ ๋ช…๋ น์–ด์˜ ์ถœ๋ ฅ์œผ๋กœ ํˆดํŒ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-text-tooltip-label": "๋™์  ํ…์ŠคํŠธ ํˆดํŒ ํ‘œ์‹œ", + "text-stream-description": "๋ช…๋ น์—์„œ ์ŠคํŠธ๋ฆฌ๋ฐ๋œ ์ค„์ด ๋ฒ„ํŠผ์— ํ…์ŠคํŠธ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.", + "text-stream-label": "์ŠคํŠธ๋ฆผ", + "wheel-description": "์Šคํฌ๋กค ํœ ์„ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค.
๋ช…๋ น์—์„œ ์Šคํฌ๋กค ํœ  ๋ธํƒ€ ๊ฐ’์œผ๋กœ $delta๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.", + "wheel-down": "์Šคํฌ๋กค ํœ  ์•„๋ž˜", + "wheel-down-description": "์Šคํฌ๋กค ํœ ์„ ์•„๋ž˜๋กœ ๋‚ด๋ ธ์„ ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค.", + "wheel-down-label": "ํœ  ๋‹ค์šด ๋ช…๋ น", + "wheel-label": "์Šคํฌ๋กค ํœ ", + "wheel-mode-separate-description": "ํœ  ์—…๊ณผ ๋‹ค์šด์— ๋Œ€ํ•ด ๋ณ„๋„์˜ ๋ช…๋ น์„ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "wheel-mode-separate-label": "๋ณ„๋„ ํœ  ๋ช…๋ น", + "wheel-up": "์Šคํฌ๋กค ํœ  ์œ„", + "wheel-up-description": "์Šคํฌ๋กค ํœ ์„ ์œ„๋กœ ์˜ฌ๋ ธ์„ ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค.", + "wheel-up-label": "ํœ  ์—… ๋ช…๋ น", + "wheel-update-text": "์Šคํฌ๋กค ์‹œ ํ‘œ์‹œ๋œ ํ…์ŠคํŠธ ์—…๋ฐ์ดํŠธ" + }, + "keyboard-layout": { + "show-icon-description": "ํ‚ค๋ณด๋“œ ๋ ˆ์ด์•„์›ƒ ์•„์ด์ฝ˜์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค." + }, + "lock-keys": { + "hide-when-off-description": "ํ‚ค๊ฐ€ ํ™œ์„ฑํ™”๋˜์ง€ ์•Š์•˜์„ ๋•Œ ํ‘œ์‹œ๊ธฐ๋ฅผ ์ˆจ๊น๋‹ˆ๋‹ค.", + "hide-when-off-label": "๊บผ์ ธ ์žˆ์„ ๋•Œ ์ˆจ๊ธฐ๊ธฐ", + "show-caps-lock-description": "Caps Lock ์ƒํƒœ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-caps-lock-label": "Caps Lock", + "show-num-lock-description": "Num Lock ์ƒํƒœ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-num-lock-label": "Num Lock", + "show-scroll-lock-description": "Scroll Lock ์ƒํƒœ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-scroll-lock-label": "Scroll Lock" + }, + "media-mini": { + "compact-mode-description": "๋ฏธ๋””์–ด ์žฌ์ƒ๊ธฐ ํŒจ๋„์„ ์œ„ํ•œ ๊ณต๊ฐ„ ์ ˆ์•ฝํ˜• ๋ ˆ์ด์•„์›ƒ์„ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "compact-mode-label": "์†Œํ˜• ๋ชจ๋“œ", + "hide-mode-description": "๋ฏธ๋””์–ด๊ฐ€ ์žฌ์ƒ ์ค‘์ด ์•„๋‹ ๋•Œ ์œ„์ ฏ์˜ ๋™์ž‘ ๋ฐฉ์‹์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.", + "max-width-description": "์œ„์ ฏ์˜ ์ตœ๋Œ€ ์ˆ˜ํ‰ ํฌ๊ธฐ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๋‚ด์šฉ์ด ์งง์œผ๋ฉด ์œ„์ ฏ ํฌ๊ธฐ๊ฐ€ ์ค„์–ด๋“ญ๋‹ˆ๋‹ค.", + "no-active-player": "ํ™œ์„ฑ ์žฌ์ƒ๊ธฐ ์—†์Œ", + "panel-section-description": "๋ฏธ๋””์–ด ์žฌ์ƒ๊ธฐ ํŒจ๋„์˜ ๋ชจ์–‘๊ณผ ๋™์ž‘์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "panel-section-label": "๋ฏธ๋””์–ด ์žฌ์ƒ๊ธฐ ํŒจ๋„", + "scrolling-mode-description": "ํŠธ๋ž™ ์ œ๋ชฉ์ด ๊ธธ ๋•Œ ํ…์ŠคํŠธ ์Šคํฌ๋กค ํ™œ์„ฑํ™” ์‹œ์ ์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.", + "scrolling-mode-label": "์Šคํฌ๋กค ๋ชจ๋“œ", + "show-album-art-description": "ํ˜„์žฌ ์žฌ์ƒ ์ค‘์ธ ํŠธ๋ž™์˜ ์•จ๋ฒ” ์•„ํŠธ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-album-art-label": "์•จ๋ฒ” ์•„ํŠธ ํ‘œ์‹œ", + "show-artist-first-description": "์ œ๋ชฉ - ์•„ํ‹ฐ์ŠคํŠธ ๋Œ€์‹  ์•„ํ‹ฐ์ŠคํŠธ - ์ œ๋ชฉ ์ˆœ์œผ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-artist-first-label": "์•„ํ‹ฐ์ŠคํŠธ ๋จผ์ € ํ‘œ์‹œ", + "show-progress-ring-description": "ํŠธ๋ž™ ์ง„ํ–‰ ์ƒํ™ฉ์„ ๋ณด์—ฌ์ฃผ๋Š” ์›ํ˜• ์ง„ํ–‰ ํ‘œ์‹œ๊ธฐ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-progress-ring-label": "์ง„ํ–‰ ๋ง ํ‘œ์‹œ", + "show-visualizer-description": "์Œ์•…์ด ์žฌ์ƒ ์ค‘์ผ ๋•Œ ์˜ค๋””์˜ค ๋น„์ฃผ์–ผ๋ผ์ด์ €๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-visualizer-label": "๋น„์ฃผ์–ผ๋ผ์ด์ € ํ‘œ์‹œ", + "use-fixed-width-description": "ํ™œ์„ฑํ™”ํ•˜๋ฉด ์œ„์ ฏ์ด ๋‚ด์šฉ์— ๋”ฐ๋ผ ๋™์ ์œผ๋กœ ์กฐ์ •๋˜์ง€ ์•Š๊ณ  ํ•ญ์ƒ ์ตœ๋Œ€ ๋„ˆ๋น„๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "use-fixed-width-label": "๊ณ ์ • ๋„ˆ๋น„ ์‚ฌ์šฉ", + "visualizer-type-description": "ํ‘œ์‹œํ•  ์˜ค๋””์˜ค ๋น„์ฃผ์–ผ๋ผ์ด์ € ์Šคํƒ€์ผ์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.", + "visualizer-type-label": "๋น„์ฃผ์–ผ๋ผ์ด์ € ์œ ํ˜•" + }, + "notification-history": { + "hide-widget-when-zero-description": "์•Œ๋ฆผ์ด ์—†์„ ๋•Œ ์•Œ๋ฆผ ์•„์ด์ฝ˜์„ ์ˆจ๊น๋‹ˆ๋‹ค.", + "hide-widget-when-zero-label": "์•Œ๋ฆผ์ด ์—†์„ ๋•Œ ์•„์ด์ฝ˜ ์ˆจ๊ธฐ๊ธฐ", + "hide-widget-when-zero-unread-description": "์ฝ์ง€ ์•Š์€ ์•Œ๋ฆผ์ด ์—†์„ ๋•Œ ์•Œ๋ฆผ ์•„์ด์ฝ˜์„ ์ˆจ๊น๋‹ˆ๋‹ค.", + "hide-widget-when-zero-unread-label": "์ฝ์ง€ ์•Š์€ ์•Œ๋ฆผ์ด ์—†์„ ๋•Œ ์•„์ด์ฝ˜ ์ˆจ๊ธฐ๊ธฐ", + "show-unread-badge-description": "์ฝ์ง€ ์•Š์€ ์•Œ๋ฆผ์˜ ์ˆ˜๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๋ฐฐ์ง€๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-unread-badge-label": "์ฝ์ง€ ์•Š์€ ๋ฐฐ์ง€ ํ‘œ์‹œ", + "unread-badge-color-description": "์ฝ์ง€ ์•Š์€ ์•Œ๋ฆผ ๋ฐฐ์ง€์˜ ์ƒ‰์ƒ์„ ์„ ํƒํ•˜์„ธ์š”.", + "unread-badge-color-label": "์ฝ์ง€ ์•Š์€ ๋ฑƒ์ง€ ์ƒ‰์ƒ" + }, + "section-editor": { + "placeholder": "์ถ”๊ฐ€ํ•  ์œ„์ ฏ์„ ์„ ํƒํ•˜์„ธ์š”...", + "search-placeholder": "์œ„์ ฏ ๊ฒ€์ƒ‰..." + }, + "spacer": { + "width-description": "ํ”ฝ์…€ ๋‹จ์œ„์˜ ๊ฐ„๊ฒฉ ๋„ˆ๋น„์ž…๋‹ˆ๋‹ค." + }, + "system-monitor": { + "compact-mode-description": "ํ…์ŠคํŠธ ๊ฐ’ ๋Œ€์‹  ๋ฏธ๋‹ˆ ๋ง‰๋Œ€ ์ฐจํŠธ๋กœ ํ†ต๊ณ„๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ๋ ˆ์ด์•„์›ƒ์ด ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.", + "compact-mode-label": "์†Œํ˜• ๋ชจ๋“œ", + "cpu-cores-description": "CPU ์ฝ”์–ด ์‚ฌ์šฉ๋Ÿ‰์„ ๊ฐœ๋ณ„์ ์œผ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "cpu-cores-label": "CPU ์ฝ”์–ด", + "cpu-frequency-description": "ํ˜„์žฌ CPU ํด๋Ÿญ ์†๋„๋ฅผ GHz ๋‹จ์œ„๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "cpu-frequency-label": "CPU ์ฃผํŒŒ์ˆ˜ ํ‘œ์‹œ", + "cpu-temperature-description": "๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ CPU ์˜จ๋„๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "cpu-temperature-label": "CPU ์˜จ๋„", + "cpu-usage-description": "ํ˜„์žฌ CPU ์‚ฌ์šฉ๋Ÿ‰์„ ๋ฐฑ๋ถ„์œจ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "cpu-usage-label": "CPU ์‚ฌ์šฉ๋Ÿ‰", + "disk-path-description": "๋ชจ๋‹ˆํ„ฐ๋งํ•  ๋””์Šคํฌ ๋งˆ์šดํŠธ ์ง€์ ์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.", + "disk-path-label": "๋””์Šคํฌ ๊ฒฝ๋กœ", + "gpu-temperature-description": "๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ GPU ์˜จ๋„๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "load-average-description": "์‹œ์Šคํ…œ ๋กœ๋“œ ํ‰๊ท ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "load-average-label": "๋กœ๋“œ ํ‰๊ท ", + "memory-percentage-description": "๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ์ ˆ๋Œ€๊ฐ’ ๋Œ€์‹  ๋ฐฑ๋ถ„์œจ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "memory-percentage-label": "๋ฉ”๋ชจ๋ฆฌ ๋ฐฑ๋ถ„์œจ ํ‘œ์‹œ", + "memory-usage-description": "ํ˜„์žฌ ๋žจ ์‚ฌ์šฉ๋Ÿ‰ ์ •๋ณด๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "memory-usage-label": "๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰", + "network-traffic-description": "๋„คํŠธ์›Œํฌ ์—…๋กœ๋“œ ๋ฐ ๋‹ค์šด๋กœ๋“œ ์†๋„๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "network-traffic-label": "๋„คํŠธ์›Œํฌ ํŠธ๋ž˜ํ”ฝ", + "storage-as-percentage-description": "๋””์Šคํฌ ๊ณต๊ฐ„์„ ์ ˆ๋Œ€๊ฐ’ ๋Œ€์‹  ๋ฐฑ๋ถ„์œจ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "storage-as-percentage-label": "๋””์Šคํฌ ๋ฐฑ๋ถ„์œจ", + "storage-available-description": "์‚ฌ์šฉ๋œ ๊ณต๊ฐ„ ๋Œ€์‹  ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋””์Šคํฌ ๊ณต๊ฐ„์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "storage-available-label": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋””์Šคํฌ ๊ณต๊ฐ„", + "storage-usage-description": "๋””์Šคํฌ ๊ณต๊ฐ„ ์‚ฌ์šฉ๋Ÿ‰ ์ •๋ณด๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "storage-usage-label": "์ €์žฅ์†Œ ์‚ฌ์šฉ๋Ÿ‰", + "swap-usage-description": "์Šค์™‘ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "swap-usage-label": "์Šค์™‘ ์‚ฌ์šฉ๋Ÿ‰", + "use-monospace-font-description": "์ผ๊ด€๋œ ๋ฌธ์ž ๋„ˆ๋น„๋ฅผ ์œ„ํ•ด ๊ณ ์ •ํญ ๊ธ€๊ผด์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "use-monospace-font-label": "๊ณ ์ •ํญ ๊ธ€๊ผด", + "use-padding-description": "๋ ˆ์ด์•„์›ƒ ์ด๋™์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ํ…์ŠคํŠธ ๊ฐ’์— ์„ ํ–‰ ๊ณต๋ฐฑ์„ ์ฑ„์›๋‹ˆ๋‹ค.", + "use-padding-description-disabled-monospace-font": "์ด ๊ธฐ๋Šฅ์—๋Š” ๊ณ ์ •ํญ ๊ธ€๊ผด์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.", + "use-padding-description-disabled-vertical": "์„ธ๋กœ ์ž‘์—… ํ‘œ์‹œ์ค„์€ ํŒจ๋”ฉ ํ…์ŠคํŠธ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + "use-padding-label": "ํ…์ŠคํŠธ ํŒจ๋”ฉ" + }, + "taskbar": { + "colorize-icons-description": "์ž‘์—… ํ‘œ์‹œ์ค„ ์•„์ด์ฝ˜์— ํ…Œ๋งˆ ์ƒ‰์ƒ์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "hide-mode-description": "์ผ์น˜ํ•˜๋Š” ์ฐฝ์ด ์—†์„ ๋•Œ ์œ„์ ฏ์˜ ๋™์ž‘ ๋ฐฉ์‹์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.", + "hide-mode-label": "์ˆจ๊ธฐ๊ธฐ ๋ชจ๋“œ", + "icon-scale-description": "์ž‘์—… ํ‘œ์‹œ์ค„ ์•„์ด์ฝ˜์˜ ๋ฐฐ์œจ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "icon-scale-label": "์•„์ด์ฝ˜ ๋ฐฐ์œจ", + "max-width-description": "์ž‘์—… ํ‘œ์‹œ์ค„์˜ ์ตœ๋Œ€ ๋„ˆ๋น„๋ฅผ ํ™”๋ฉด ๋„ˆ๋น„์˜ ๋ฐฑ๋ถ„์œจ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "max-width-label": "์ตœ๋Œ€ ๋„ˆ๋น„", + "only-active-workspaces-description": "ํ™œ์„ฑ ์ž‘์—… ๊ณต๊ฐ„์˜ ์•ฑ๋งŒ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "only-active-workspaces-label": "ํ™œ์„ฑ ์ž‘์—… ๊ณต๊ฐ„์˜ ์•ฑ๋งŒ", + "only-same-monitor-description": "๋ฐ”๊ฐ€ ์œ„์น˜ํ•œ ๋ชจ๋‹ˆํ„ฐ์˜ ์•ฑ๋งŒ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "only-same-monitor-label": "๋™์ผ ๋ชจ๋‹ˆํ„ฐ์˜ ์•ฑ๋งŒ", + "show-pinned-apps-description": "๋…์— ๊ณ ์ •๋œ ์•ฑ์„ ์ž‘์—… ํ‘œ์‹œ์ค„์— ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-pinned-apps-label": "๊ณ ์ •๋œ ์•ฑ ํ‘œ์‹œ", + "show-title-description": "์ž‘์—… ํ‘œ์‹œ์ค„์— ์ฐฝ ์ œ๋ชฉ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-title-description-disabled": "์ˆ˜์ง ์ž‘์—… ํ‘œ์‹œ์ค„์€ ์ œ๋ชฉ ํ‘œ์‹œ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + "show-title-label": "์ œ๋ชฉ ํ‘œ์‹œ", + "smart-width-description": "ํ•ญ๋ชฉ ์ˆ˜์— ๋”ฐ๋ผ ํ•ญ๋ชฉ ๋„ˆ๋น„๋ฅผ ์ž๋™์œผ๋กœ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "smart-width-label": "์Šค๋งˆํŠธ ๋„ˆ๋น„", + "title-width-description": "์ž‘์—… ํ‘œ์‹œ์ค„์˜ ์ฐฝ ์ œ๋ชฉ ๋„ˆ๋น„๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค (ํ”ฝ์…€ ๋‹จ์œ„).", + "title-width-label": "์ œ๋ชฉ ๋„ˆ๋น„", + "title-width-reset-tooltip": "์ œ๋ชฉ ๋„ˆ๋น„ ์ดˆ๊ธฐํ™”" + }, + "tray": { + "chevron-color-description": "๋“œ๋กœ์–ด ์…ฐ๋ธŒ๋ก  ์•„์ด์ฝ˜์— ํ…Œ๋งˆ ์ƒ‰์ƒ์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "chevron-color-label": "ํ™”์‚ดํ‘œ ์ƒ‰์ƒ", + "colorize-icons-description": "ํŠธ๋ ˆ์ด ์•„์ด์ฝ˜์— ํ…Œ๋งˆ ์ƒ‰์ƒ์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "colorize-icons-label": "์•„์ด์ฝ˜ ์ƒ‰์ƒํ™”", + "drawer-enabled-description": "ํ™œ์„ฑํ™”ํ•˜๋ฉด ๊ณ ์ •๋˜์ง€ ์•Š์€ ํŠธ๋ ˆ์ด ํ•ญ๋ชฉ์ด ์„œ๋ž ํŒจ๋„์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
๋น„ํ™œ์„ฑํ™”ํ•˜๋ฉด ๋ชจ๋“  ํŠธ๋ ˆ์ด ํ•ญ๋ชฉ์ด ์ธ๋ผ์ธ์œผ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.", + "drawer-enabled-label": "์„œ๋ž ํ™œ์„ฑํ™”", + "hide-passive-description": "ํ™œ์„ฑํ™”ํ•˜๋ฉด 'ํŒจ์‹œ๋ธŒ' ์ƒํƒœ์˜ ํŠธ๋ ˆ์ด ํ•ญ๋ชฉ์ด ์ˆจ๊ฒจ์ง‘๋‹ˆ๋‹ค.", + "hide-passive-label": "ํŒจ์‹œ๋ธŒ ํ•ญ๋ชฉ ์ˆจ๊ธฐ๊ธฐ" + }, + "volume": { + "display-mode-description": "์ด ๊ฐ’์ด ์–ด๋–ป๊ฒŒ ํ‘œ์‹œ๋ ์ง€ ์„ ํƒํ•˜์„ธ์š”." + }, + "workspace": { + "character-count-description": "์ž‘์—… ๊ณต๊ฐ„ ์ด๋ฆ„์—์„œ ํ‘œ์‹œํ•  ๋ฌธ์ž ์ˆ˜ (1-10).", + "character-count-label": "๋ฌธ์ž ์ˆ˜", + "empty-color-description": "๋นˆ ์ž‘์—… ๊ณต๊ฐ„์˜ ๋ฐฐ๊ฒฝ์ƒ‰์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "empty-color-label": "๋นˆ ์ž‘์—… ๊ณต๊ฐ„ ์ƒ‰์ƒ", + "enable-scrollwheel-description": "๋งˆ์šฐ์Šค ์Šคํฌ๋กค ํœ ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘์—… ๊ณต๊ฐ„์„ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค.", + "enable-scrollwheel-label": "์Šคํฌ๋กค๋กœ ์ž‘์—… ๊ณต๊ฐ„ ์ „ํ™˜", + "focused-color-description": "ํฌ์ปค์Šค๋œ ์ž‘์—… ๊ณต๊ฐ„์˜ ๋ฐฐ๊ฒฝ์ƒ‰์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "focused-color-label": "ํฌ์ปค์Šค๋œ ์ž‘์—… ๊ณต๊ฐ„ ์ƒ‰์ƒ", + "follow-focused-screen-description": "๋ฐ”๊ฐ€ ์œ„์น˜ํ•œ ํ™”๋ฉด ๋Œ€์‹  ํ˜„์žฌ ํฌ์ปค์Šค๋œ ํ™”๋ฉด์˜ ์ž‘์—… ๊ณต๊ฐ„์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "follow-focused-screen-label": "ํฌ์ปค์Šค๋œ ํ™”๋ฉด ๋”ฐ๋ฅด๊ธฐ", + "font-weight-description": "์ž‘์—… ๊ณต๊ฐ„ ๋‚ด ํ…์ŠคํŠธ์˜ ์‹œ๊ฐ์  ๊ฐ€์ค‘์น˜๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "font-weight-label": "๊ธ€๊ผด ๋‘๊ป˜", + "grouped-border-opacity-description": "์ž‘์—… ๊ณต๊ฐ„ ์ปจํ…Œ์ด๋„ˆ ํ…Œ๋‘๋ฆฌ์˜ ๋ถˆํˆฌ๋ช…๋„ ์ˆ˜์ค€์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "grouped-border-opacity-label": "ํ…Œ๋‘๋ฆฌ ๋ถˆํˆฌ๋ช…๋„", + "hide-unoccupied-description": "์ฐฝ์ด ์—†๋Š” ์ž‘์—… ๊ณต๊ฐ„์„ ํ‘œ์‹œํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + "hide-unoccupied-label": "๋น„์–ด ์žˆ์œผ๋ฉด ์ˆจ๊ธฐ๊ธฐ", + "label-mode-description": "์ž‘์—… ๊ณต๊ฐ„ ๋ ˆ์ด๋ธ” ํ‘œ์‹œ ๋ฐฉ์‹์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.", + "label-mode-label": "๋ ˆ์ด๋ธ” ๋ชจ๋“œ", + "occupied-color-description": "์‚ฌ์šฉ ์ค‘์ธ ์ž‘์—… ๊ณต๊ฐ„์˜ ๋ฐฐ๊ฒฝ์ƒ‰์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "occupied-color-label": "์‚ฌ์šฉ ์ค‘์ธ ์ž‘์—… ๊ณต๊ฐ„ ์ƒ‰์ƒ", + "pill-size-description": "์ž‘์—… ๊ณต๊ฐ„ ์•Œ์•ฝ์˜ ํฌ๊ธฐ๋ฅผ ์กฐ์ ˆํ•ฉ๋‹ˆ๋‹ค (50%-100%).", + "pill-size-label": "ํ•„ ํฌ๊ธฐ", + "reverse-scrolling-description": "์Šคํฌ๋กคํ•  ๋•Œ ์ž‘์—… ๊ณต๊ฐ„ ์ „ํ™˜ ๋ฐฉํ–ฅ์„ ๋ฐ˜๋Œ€๋กœ ํ•ฉ๋‹ˆ๋‹ค.", + "reverse-scrolling-label": "์Šคํฌ๋กค ๋ฐ˜์ „", + "show-applications-description": "๊ฐ ์ž‘์—… ๊ณต๊ฐ„ ๋‚ด๋ถ€์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์•„์ด์ฝ˜์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-applications-hover-description": "์ž‘์—… ๊ณต๊ฐ„์— ๋งˆ์šฐ์Šค๋ฅผ ์˜ฌ๋ ธ์„ ๋•Œ ๊ฐ ์ž‘์—… ๊ณต๊ฐ„ ๋‚ด์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์•„์ด์ฝ˜์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-applications-hover-label": "๋งˆ์šฐ์Šค ์˜ค๋ฒ„ ์‹œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ‘œ์‹œ", + "show-applications-label": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ‘œ์‹œ", + "show-badge-description": "๊ทธ๋ฃนํ™” ๋ชจ๋“œ์—์„œ ์ž‘์—… ๊ณต๊ฐ„ ๋ฒˆํ˜ธ ๋ฐฐ์ง€๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-badge-label": "์ž‘์—… ๊ณต๊ฐ„ ๋ฐฐ์ง€ ํ‘œ์‹œ", + "show-labels-only-when-occupied-description": "์ฐฝ์ด ์žˆ์„ ๋•Œ๋งŒ ์ž‘์—… ๊ณต๊ฐ„ ๋ ˆ์ด๋ธ”์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-labels-only-when-occupied-label": "์‚ฌ์šฉ ์ค‘์ผ ๋•Œ๋งŒ ๋ ˆ์ด๋ธ” ํ‘œ์‹œ", + "unfocused-icons-opacity-description": "ํฌ์ปค์Šค๋˜์ง€ ์•Š์€ ์•ฑ ์•„์ด์ฝ˜์˜ ๋ถˆํˆฌ๋ช…๋„ ์ˆ˜์ค€์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "unfocused-icons-opacity-label": "ํฌ์ปค์Šค๋˜์ง€ ์•Š์€ ์•„์ด์ฝ˜ ๋ถˆํˆฌ๋ช…๋„" + } + }, + "battery": { + "all-batteries": "๋ชจ๋“  ๋ฐฐํ„ฐ๋ฆฌ (๊ฒฐํ•ฉ๋จ)", + "battery-health": "๋ฐฐํ„ฐ๋ฆฌ ์ˆ˜๋ช…", + "battery-level": "๋ฐฐํ„ฐ๋ฆฌ ์ž”๋Ÿ‰", + "capacity-level": "์šฉ๋Ÿ‰: {level}", + "charging-rate": "์ถฉ์ „ ์†๋„: {rate} W", + "discharging-rate": "๋ฐฉ์ „ ์†๋„: {rate} W", + "health": "์ˆ˜๋ช…: {percent}%", + "inhibit-idle-description": "์‹œ์Šคํ…œ์„ ๊นจ์–ด ์žˆ๋Š” ์ƒํƒœ๋กœ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.", + "no-battery-detected": "๋ฐฐํ„ฐ๋ฆฌ๊ฐ€ ๊ฐ์ง€๋˜์ง€ ์•Š์Œ", + "plugged-in": "์ „์› ์—ฐ๊ฒฐ๋จ", + "power-profile": "์ „์› ํ”„๋กœํ•„", + "time-left": "๋‚จ์€ ์‹œ๊ฐ„: {time}", + "time-until-full": "์™„์ถฉ๊นŒ์ง€ ๋‚จ์€ ์‹œ๊ฐ„: {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์žฅ์น˜", + "blocked": "์ฐจ๋‹จ๋จ", + "connected-devices": "์—ฐ๊ฒฐ๋œ ์žฅ์น˜", + "device-address": "์žฅ์น˜ ์ฃผ์†Œ", + "disabled": "๋ธ”๋ฃจํˆฌ์Šค๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋จ", + "discoverable": "๊ฒ€์ƒ‰ ๊ฐ€๋Šฅ", + "enable-message": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์žฅ์น˜๋ฅผ ๋ณด๋ ค๋ฉด ๋ธ”๋ฃจํˆฌ์Šค๋ฅผ ํ™œ์„ฑํ™”ํ•˜์„ธ์š”.", + "known-devices": "์•Œ๋ ค์ง„ ์žฅ์น˜", + "no-devices": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์žฅ์น˜ ์—†์Œ", + "paired-devices": "ํŽ˜์–ด๋ง๋œ ์žฅ์น˜", + "pairing-mode": "์žฅ์น˜๊ฐ€ ํŽ˜์–ด๋ง ๋ชจ๋“œ์ธ์ง€ ํ™•์ธํ•˜์„ธ์š”.", + "scanning": "์žฅ์น˜ ๊ฒ€์ƒ‰ ์ค‘...", + "signal-text-excellent": "์‹ ํ˜ธ: ๋งค์šฐ ์ข‹์Œ", + "signal-text-fair": "์‹ ํ˜ธ: ๋ณดํ†ต", + "signal-text-good": "์‹ ํ˜ธ: ์ข‹์Œ", + "signal-text-poor": "์‹ ํ˜ธ: ๋‚˜์จ", + "signal-text-unknown": "์‹ ํ˜ธ: ์•Œ ์ˆ˜ ์—†์Œ", + "signal-text-very-poor": "์‹ ํ˜ธ: ๋งค์šฐ ๋‚˜์จ" + } + }, + "changelog": { + "error": { + "fetch-failed": "๋ณ€๊ฒฝ ๋กœ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋‚˜์ค‘์— ๋‹ค์‹œ ์‹œ๋„ํ•ด ์ฃผ์„ธ์š”.", + "rate-limit": "GitHub ์†๋„ ์ œํ•œ์„ ์ดˆ๊ณผํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ช‡ ๋ถ„ ํ›„์— ๋‹ค์‹œ ์‹œ๋„ํ•ด ์ฃผ์„ธ์š”." + }, + "panel": { + "buttons-discord": "Discord ์ฐธ์—ฌํ•˜๊ธฐ", + "buttons-dismiss": "ํ™•์ธ", + "buttons-feedback": "ํ”ผ๋“œ๋ฐฑ ๋ณด๋‚ด๊ธฐ", + "empty": "๋ฆด๋ฆฌ์Šค ๋…ธํŠธ๋Š” ์•„์ง ๋ณผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", + "highlight-title": "์ฃผ์š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ", + "released": "์ถœ์‹œ์ผ: {date}", + "subtitle-fresh": "Noctalia๋ฅผ ์„ค์น˜ํ•ด ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค! ์ด๋ฒˆ ๋นŒ๋“œ์— ํฌํ•จ๋œ ๋‚ด์šฉ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.", + "subtitle-updated": "{previousVersion}์—์„œ ์—…๋ฐ์ดํŠธ๋จ", + "title": "{version}์˜ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ", + "version": "๋ฒ„์ „ {version}", + "version-new-user": "์ƒˆ๋กœ ์„ค์น˜๋จ" + } + }, + "common": { + "actions": "์ž‘์—…", + "active": "ํ™œ์„ฑ", + "add": "์ถ”๊ฐ€", + "appearance": "๋ชจ์–‘", + "apply": "์ ์šฉ", + "auto-connect": "์ž๋™ ์—ฐ๊ฒฐ", + "automation": "์ž๋™ํ™”", + "available": "์‚ฌ์šฉ ๊ฐ€๋Šฅ", + "back": "๋’ค๋กœ", + "battery": "๋ฐฐํ„ฐ๋ฆฌ", + "behavior": "๋™์ž‘", + "bluetooth": "๋ธ”๋ฃจํˆฌ์Šค", + "brightness": "๋ฐ๊ธฐ", + "browse": "์ฐพ์•„๋ณด๊ธฐ", + "calendar": "๋‹ฌ๋ ฅ", + "calendar-panel": "๋‹ฌ๋ ฅ ํŒจ๋„", + "cancel": "์ทจ์†Œ", + "cards": "์นด๋“œ", + "charging": "์ถฉ์ „ ์ค‘", + "clear": "์ง€์šฐ๊ธฐ", + "clipboard": "ํด๋ฆฝ๋ณด๋“œ", + "close": "๋‹ซ๊ธฐ", + "color-muted": "์ฐจ๋ถ„ํ•˜๊ฒŒ", + "colors": "์ƒ‰์ƒ", + "command": "๋ช…๋ น", + "confirm": "ํ™•์ธ", + "connect": "์—ฐ๊ฒฐ", + "connected": "์—ฐ๊ฒฐ๋จ", + "connecting": "์—ฐ๊ฒฐ ์ค‘...", + "continue": "๊ณ„์†", + "contributors": "๊ธฐ์—ฌ์ž", + "copied-to-clipboard": "ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌ๋จ", + "countdown": "์นด์šดํŠธ๋‹ค์šด", + "customize": "์‚ฌ์šฉ์ž ์ง€์ •", + "date": "๋‚ ์งœ", + "default": "๊ธฐ๋ณธ๊ฐ’", + "delete": "์‚ญ์ œ", + "devices": "์žฅ์น˜", + "disabled": "๋น„ํ™œ์„ฑํ™”๋จ", + "discharging": "๋ฐฉ์ „ ์ค‘", + "disconnect": "์—ฐ๊ฒฐ ํ•ด์ œ", + "disconnected": "์—ฐ๊ฒฐ ๋Š๊น€", + "disconnecting": "์—ฐ๊ฒฐ ํ•ด์ œ ์ค‘...", + "display-mode": "๋””์Šคํ”Œ๋ ˆ์ด ๋ชจ๋“œ", + "documentation": "๋ฌธ์„œ", + "download": "๋‹ค์šด๋กœ๋“œ", + "duration": "์ง€์† ์‹œ๊ฐ„", + "dysfunctional": "๊ธฐ๋Šฅ ์žฅ์• ", + "edit": "ํŽธ์ง‘", + "enabled": "ํ™œ์„ฑํ™”๋จ", + "error": "์˜ค๋ฅ˜", + "ethernet": "์ด๋”๋„ท", + "events": "์ผ์ •", + "execute": "์‹คํ–‰", + "faithful": "์ถฉ์‹คํ•˜๊ฒŒ", + "focus": "ํฌ์ปค์Šค", + "font-weight-bold": "๊ตต๊ฒŒ", + "font-weight-light": "๋ผ์ดํŠธ", + "font-weight-medium": "์ค‘๊ฐ„", + "font-weight-regular": "๋ณดํ†ต", + "font-weight-semibold": "์„ธ๋ฏธ๋ณผ๋“œ", + "frequency": "๋นˆ๋„", + "gateway": "๊ฒŒ์ดํŠธ์›จ์ด", + "general": "์ผ๋ฐ˜", + "height": "๋†’์ด", + "hibernate": "์ตœ๋Œ€ ์ ˆ์ „", + "history": "๊ธฐ๋ก", + "icon": "์•„์ด์ฝ˜", + "idle": "์œ ํœด", + "info": "์ •๋ณด", + "input": "์ž…๋ ฅ", + "install": "์„ค์น˜", + "installed": "์„ค์น˜๋จ", + "interface": "์ธํ„ฐํŽ˜์ด์Šค", + "internet": "์ธํ„ฐ๋„ท", + "keybind": "ํ‚ค ๋ฐ”์ธ๋”ฉ", + "language": "์–ธ์–ด", + "loading": "๋กœ๋”ฉ ์ค‘...", + "local": "๋กœ์ปฌ", + "location": "์œ„์น˜", + "lock": "์ž ๊ธˆ", + "logout": "๋กœ๊ทธ์•„์›ƒ", + "look": "์™ธ๊ด€", + "margins": "์—ฌ๋ฐฑ", + "media": "๋ฏธ๋””์–ด", + "media-player": "๋ฏธ๋””์–ด ์žฌ์ƒ๊ธฐ", + "memory": "๋ฉ”๋ชจ๋ฆฌ", + "monitors": "๋ชจ๋‹ˆํ„ฐ", + "move-down": "์•„๋ž˜๋กœ ์ด๋™", + "move-up": "์œ„๋กœ ์ด๋™", + "network": "๋„คํŠธ์›Œํฌ", + "next": "๋‹ค์Œ", + "night-light": "์•ผ๊ฐ„ ์กฐ๋ช…", + "no": "์•„๋‹ˆ์š”", + "no-results": "๊ฒฐ๊ณผ ์—†์Œ", + "no-summary": "์š”์•ฝ ์—†์Œ", + "none": "์—†์Œ", + "not-found": "์ฐพ์„ ์ˆ˜ ์—†์Œ", + "notifications": "์•Œ๋ฆผ", + "official": "๊ณต์‹", + "on-surface": "ํ‘œ๋ฉด์ ", + "output": "์ถœ๋ ฅ", + "pair": "ํŽ˜์–ด๋ง", + "paired": "ํŽ˜์–ด๋ง๋จ", + "pairing": "ํŽ˜์–ด๋ง ์ค‘...", + "panels": "ํŒจ๋„", + "password": "๋น„๋ฐ€๋ฒˆํ˜ธ", + "pause": "์ผ์‹œ ์ •์ง€", + "performance": "์„ฑ๋Šฅ", + "pin": "๊ณ ์ •", + "play": "์žฌ์ƒ", + "polling": "ํด๋ง", + "position": "์œ„์น˜", + "previous": "์ด์ „", + "primary": "์ฒซ๋ฒˆ์งธ", + "random": "๋žœ๋ค", + "reboot": "์žฌ๋ถ€ํŒ…", + "reboot-to-uefi": "UEFI๋กœ ์žฌ๋ถ€ํŒ…", + "record": "๋…นํ™” ์‹œ์ž‘", + "refresh": "์ƒˆ๋กœ๊ณ ์นจ", + "required": "(ํ•„์ˆ˜)", + "reset": "์ดˆ๊ธฐํ™”", + "reset-to-default": "๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ดˆ๊ธฐํ™”", + "result-count": "{count}๊ฐœ ๊ฒฐ๊ณผ", + "result-count-plural": "{count}๊ฐœ ๊ฒฐ๊ณผ", + "resume": "์žฌ๊ฐœ", + "save": "์ €์žฅ", + "scanning": "๊ฒ€์ƒ‰ ์ค‘...", + "screen-corners": "ํ™”๋ฉด ๋ชจ์„œ๋ฆฌ", + "search": "๊ฒ€์ƒ‰", + "second": "{count} ์ดˆ", + "second-plural": "{count} ์ดˆ", + "secondary": "๋‘๋ฒˆ์งธ", + "security": "๋ณด์•ˆ", + "select": "์„ ํƒ", + "select-color": "์ƒ‰์ƒ ์„ ํƒ", + "select-color-description": "๊ฐ•์กฐ๋ฅผ ์œ„ํ•ด ํ…Œ๋งˆ ์ƒ‰์ƒ์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "select-icon-color": "์•„์ด์ฝ˜ ์ƒ‰์ƒ ์„ ํƒ", + "settings": "์„ค์ •", + "shortcuts": "๋‹จ์ถ•ํ‚ค", + "shutdown": "์‹œ์Šคํ…œ ์ข…๋ฃŒ", + "signal": "์‹ ํ˜ธ", + "sound": "์†Œ๋ฆฌ", + "sources": "์†Œ์Šค", + "start": "์‹œ์ž‘", + "stop": "์ค‘์ง€", + "supporters": "ํ›„์›์ž", + "suspend": "๋Œ€๊ธฐ ๋ชจ๋“œ", + "templates": "ํ…œํ”Œ๋ฆฟ", + "tertiary": "์„ธ๋ฒˆ์งธ", + "test": "ํ…Œ์ŠคํŠธ", + "thresholds": "์ž„๊ณ„๊ฐ’", + "title": "์ œ๋ชฉ", + "toast": "ํ† ์ŠคํŠธ", + "trusted": "์‹ ๋ขฐํ•จ", + "uninstall": "์ œ๊ฑฐ", + "unknown": "์•Œ ์ˆ˜ ์—†์Œ", + "unpair": "ํŽ˜์–ด๋ง ํ•ด์ œ", + "unpin": "๊ณ ์ • ํ•ด์ œ", + "update": "์—…๋ฐ์ดํŠธ", + "upload": "์—…๋กœ๋“œ", + "username": "์‚ฌ์šฉ์ž ์ด๋ฆ„", + "userspace-reboot": "์‚ฌ์šฉ์ž ๊ณต๊ฐ„ ์žฌ์‹œ์ž‘", + "version": "๋ฒ„์ „", + "vibrant": "์„ ๋ช…ํ•˜๊ฒŒ", + "visualizer": "๋น„์ฃผ์–ผ๋ผ์ด์ €", + "volume": "๋ณผ๋ฅจ", + "volumes": "๋ณผ๋ฅจ", + "wallpaper": "๋ฐฐ๊ฒฝํ™”๋ฉด", + "weather": "๋‚ ์”จ", + "weather-loading": "๋‚ ์”จ ๋กœ๋”ฉ ์ค‘...", + "weather-no-location": "๋‚ ์”จ๋ฅผ ๋ณด๋ ค๋ฉด ์„ค์ •์—์„œ ์œ„์น˜๋ฅผ ์„ค์ •ํ•˜์„ธ์š”", + "week": "์ฃผ", + "widgets": "์œ„์ ฏ", + "width": "๋„ˆ๋น„", + "wifi": "Wi-Fi", + "windows": "์ฐฝ", + "yes": "์˜ˆ" + }, + "control-center": { + "power-profile": { + "tooltip-action": "์ „์› ํ”„๋กœํ•„", + "tooltip-disabled": "์ „์› ํ”„๋กœํ•„์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด power-profiles-daemon์„ ์„ค์น˜ํ•˜์„ธ์š”" + }, + "wifi": { + "label-disconnected": "Wi-Fi ์—ฐ๊ฒฐ ๋Š๊น€", + "label-ethernet": "์ด๋”๋„ท" + } + }, + "display-modes": { + "always-hide": "ํ•ญ์ƒ ์ˆจ๊น€", + "always-show": "ํ•ญ์ƒ ํ‘œ์‹œ", + "force-open": "๊ฐ•์ œ ์—ด๊ธฐ", + "on-hover": "๋งˆ์šฐ์Šค ์˜ค๋ฒ„ ์‹œ" + }, + "hide-modes": { + "auto-hide": "์ž๋™ ์ˆจ๊ธฐ๊ธฐ", + "hidden": "๋น„์–ด ์žˆ์„ ๋•Œ ์ˆจ๊ธฐ๊ธฐ", + "idle": "์œ ํœด ์ƒํƒœ์ผ ๋•Œ ์ˆจ๊ธฐ๊ธฐ", + "non-exclusive": "๋น„๋…์ ", + "transparent": "๋น„์–ด ์žˆ์„ ๋•Œ ํˆฌ๋ช…ํ•˜๊ฒŒ", + "visible": "ํ•ญ์ƒ ํ‘œ์‹œ" + }, + "launcher": { + "categories": { + "all": "์ „์ฒด", + "audiovideo": "์˜ค๋””์˜ค ๋ฐ ๋น„๋””์˜ค", + "chat": "์ฑ„ํŒ…", + "development": "๊ฐœ๋ฐœ", + "education": "๊ต์œก", + "emoji-activity": "ํ™œ๋™", + "emoji-animals": "๋™๋ฌผ", + "emoji-flags": "๊ตญ๊ธฐ", + "emoji-food": "์Œ์‹ ๋ฐ ์Œ๋ฃŒ", + "emoji-nature": "์ž์—ฐ", + "emoji-objects": "์‚ฌ๋ฌผ", + "emoji-people": "์‚ฌ๋žŒ ๋ฐ ์‹ ์ฒด", + "emoji-recent": "์ตœ๊ทผ", + "emoji-symbols": "๊ธฐํ˜ธ", + "emoji-travel": "์—ฌํ–‰ ๋ฐ ์žฅ์†Œ", + "game": "๊ฒŒ์ž„", + "graphics": "๊ทธ๋ž˜ํ”ฝ", + "misc": "๊ธฐํƒ€", + "office": "์˜คํ”ผ์Šค", + "pinned": "๊ณ ์ •๋จ", + "system": "์‹œ์Šคํ…œ", + "webbrowser": "์›น ๋ธŒ๋ผ์šฐ์ €" + }, + "providers": { + "applications": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜", + "calculator": "๊ณ„์‚ฐ๊ธฐ", + "calculator-deprecated": ">calc ๋ช…๋ น์€ ๋” ์ด์ƒ ์‚ฌ์šฉ๋˜์ง€ ์•Š์œผ๋ฉฐ ๊ณง ์ œ๊ฑฐ๋  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค. ๋Œ€์‹  ๊ฒ€์ƒ‰์ฐฝ์— ์ˆ˜์‹์„ ์ง์ ‘ ์ž…๋ ฅํ•˜์„ธ์š”.", + "calculator-description": "๊ณ„์‚ฐ๊ธฐ โ€” ์ˆ˜์‹ ๊ณ„์‚ฐ", + "calculator-enter-expression": "์ˆ˜์‹ ์ž…๋ ฅ", + "calculator-press-enter-to-copy": "๊ฒฐ๊ณผ๋ฅผ ๋ณต์‚ฌํ•˜๋ ค๋ฉด ์—”ํ„ฐ ํ‚ค๋ฅผ ๋ˆ„๋ฅด์„ธ์š”", + "clipboard": "ํด๋ฆฝ๋ณด๋“œ ๊ธฐ๋ก", + "clipboard-clear-description": "๋ชจ๋“  ํด๋ฆฝ๋ณด๋“œ ๊ธฐ๋ก ์ง€์šฐ๊ธฐ", + "clipboard-clear-description-full": "ํด๋ฆฝ๋ณด๋“œ ๊ธฐ๋ก์—์„œ ๋ชจ๋“  ํ•ญ๋ชฉ ์ œ๊ฑฐ", + "clipboard-clear-history": "ํด๋ฆฝ๋ณด๋“œ ๊ธฐ๋ก ์ง€์šฐ๊ธฐ", + "clipboard-delete": "ํด๋ฆฝ๋ณด๋“œ ํ•ญ๋ชฉ ์‚ญ์ œ", + "clipboard-history-disabled": "ํด๋ฆฝ๋ณด๋“œ ๊ธฐ๋ก์ด ๋น„ํ™œ์„ฑํ™”๋จ", + "clipboard-history-disabled-description": "์„ค์ •์—์„œ ํด๋ฆฝ๋ณด๋“œ ๊ธฐ๋ก์„ ํ™œ์„ฑํ™”ํ•˜๊ฑฐ๋‚˜ cliphist๋ฅผ ์„ค์น˜ํ•˜์„ธ์š”", + "clipboard-loading": "ํด๋ฆฝ๋ณด๋“œ ๊ธฐ๋ก ๋กœ๋”ฉ ์ค‘...", + "clipboard-search-description": "ํด๋ฆฝ๋ณด๋“œ ๊ธฐ๋ก ๊ฒ€์ƒ‰", + "command-description": "์…ธ ๋ช…๋ น ์‹คํ–‰", + "emoji": "์ด๋ชจํ‹ฐ์ฝ˜ ์„ ํƒ๊ธฐ", + "emoji-loading": "์ด๋ชจํ‹ฐ์ฝ˜ ๋กœ๋”ฉ ์ค‘...", + "emoji-loading-description": "์ž ์‹œ๋งŒ ๊ธฐ๋‹ค๋ ค ์ฃผ์„ธ์š”", + "emoji-no-recent": "์ตœ๊ทผ ์ด๋ชจํ‹ฐ์ฝ˜์ด ์—†์Šต๋‹ˆ๋‹ค", + "emoji-search-description": "์ด๋ชจํ‹ฐ์ฝ˜ ๊ฒ€์ƒ‰ ๋ฐ ๋ณต์‚ฌ", + "settings-search-description": "์„ค์ • ๊ฒ€์ƒ‰ ๋ฐ ์ด๋™", + "windows-search-description": "์—ด๋ ค ์žˆ๋Š” ์ฐฝ ๊ฒ€์ƒ‰ ๋ฐ ํฌ์ปค์Šค" + } + }, + "lock-screen": { + "authenticating": "์ธ์ฆ ์ค‘...", + "password": "๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”...", + "restart": "๋‹ค์‹œ ์‹œ์ž‘", + "shut-down": "์‹œ์Šคํ…œ ์ข…๋ฃŒ" + }, + "notifications": { + "panel": { + "click-to-collapse": "ํด๋ฆญํ•˜์—ฌ ์ ‘๊ธฐ", + "click-to-expand": "ํด๋ฆญํ•˜์—ฌ ํŽผ์น˜๊ธฐ", + "description": "์•Œ๋ฆผ์ด ๋„์ฐฉํ•˜๋ฉด ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.", + "no-notifications": "์•Œ๋ฆผ ์—†์Œ" + }, + "range": { + "earlier": "์ด์ „", + "today": "์˜ค๋Š˜", + "yesterday": "์–ด์ œ" + }, + "time": { + "diff-d": "ํ•˜๋ฃจ ์ „", + "diff-dd": "{diff}์ผ ์ „", + "diff-h": "1์‹œ๊ฐ„ ์ „", + "diff-hh": "{diff}์‹œ๊ฐ„ ์ „", + "diff-m": "1๋ถ„ ์ „", + "diff-mm": "{diff}๋ถ„ ์ „", + "now": "๋ฐฉ๊ธˆ" + } + }, + "options": { + "bar": { + "density-comfortable": "ํŽธ์•ˆํ•˜๊ฒŒ", + "density-compact": "์ปดํŒฉํŠธ", + "density-default": "๊ธฐ๋ณธ", + "density-mini": "๋ฏธ๋‹ˆ", + "density-spacious": "๋„“๊ฒŒ", + "display-mode-always-visible": "ํ•ญ์ƒ ํ‘œ์‹œ", + "type-floating": "ํ”Œ๋กœํŒ…", + "type-framed": "ํ”„๋ ˆ์ž„", + "type-simple": "์‹ฌํ”Œ" + }, + "control-center": { + "quick-settings-style-classic": "ํด๋ž˜์‹", + "quick-settings-style-modern": "๋ชจ๋˜" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "ํŽธ์•ˆํ•œ", + "compact": "์ปดํŒฉํŠธ", + "default": "๊ธฐ๋ณธ๊ฐ’" + }, + "launcher-view-mode": { + "grid": "๊ทธ๋ฆฌ๋“œ", + "list": "๋ชฉ๋ก" + }, + "notification-density": { + "compact": "์ปดํŒฉํŠธ", + "default": "๊ธฐ๋ณธ๊ฐ’" + }, + "scrolling-modes": { + "always": "ํ•ญ์ƒ ์Šคํฌ๋กค", + "hover": "๋งˆ์šฐ์Šค ์˜ค๋ฒ„ ์‹œ ์Šคํฌ๋กค", + "never": "์Šคํฌ๋กค ์•ˆ ํ•จ" + }, + "session-menu-grid-layout": { + "grid": "๊ทธ๋ฆฌ๋“œ", + "single-row": "๋‹จ์ผ ํ–‰" + }, + "settings-panel-mode": { + "attached": "๋ฐ”์— ๋ถ€์ฐฉ๋œ ํŒจ๋„", + "centered": "์ค‘์•™ ํŒจ๋„", + "window": "๋ณ„๋„ ์ฐฝ" + }, + "visualizer-types": { + "linear": "์„ ํ˜•", + "mirrored": "๋ฏธ๋Ÿฌ๋ง", + "wave": "์›จ์ด๋ธŒ" + }, + "visualizer-visibility": { + "with-background": "๋ฐฐ๊ฒฝ๊ณผ ํ•จ๊ป˜ ํ‘œ์‹œ๋  ๋•Œ๋งŒ" + }, + "workspace-labels": { + "index": "์ธ๋ฑ์Šค", + "index-and-name": "์ธ๋ฑ์Šค ๋ฐ ์ด๋ฆ„", + "name": "์ด๋ฆ„" + } + }, + "panels": { + "about": { + "become-supporter": "ํ›„์›์ž ๋˜๊ธฐ", + "changelog": "๋ณ€๊ฒฝ ๋กœ๊ทธ ๋ณด๊ธฐ", + "changelog-on-startup": "์—…๋ฐ์ดํŠธ ์‹œ ๋ณ€๊ฒฝ ๋กœ๊ทธ ํ‘œ์‹œ", + "changelog-on-startup-desc": "Noctalia ์—…๋ฐ์ดํŠธ ์‹œ ๋ณ€๊ฒฝ ๋กœ๊ทธ๋ฅผ ์ž๋™์œผ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "contributors-description": "{count}๋ช…์˜ ๋ฉ‹์ง„ ๊ธฐ์—ฌ์ž์—๊ฒŒ ๊ฐ์‚ฌ๋ฅผ ์ „ํ•ฉ๋‹ˆ๋‹ค!", + "contributors-description-plural": "{count}๋ช…์˜ ๋ฉ‹์ง„ ๊ธฐ์—ฌ์ž๋“ค์—๊ฒŒ ๊ฐ์‚ฌ๋ฅผ ์ „ํ•ฉ๋‹ˆ๋‹ค!", + "copy-info": "์ •๋ณด ๋ณต์‚ฌ", + "debug-disabled": "๋””๋ฒ„๊ทธ ๋ชจ๋“œ ๋น„ํ™œ์„ฑํ™”๋จ", + "debug-enabled": "๋””๋ฒ„๊ทธ ๋ชจ๋“œ ํ™œ์„ฑํ™”๋จ", + "info-copied": "์ •๋ณด๊ฐ€ ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌ๋จ", + "noctalia-available": "์‚ฌ์šฉ ๊ฐ€๋Šฅ:", + "noctalia-desc": "Wayland๋ฅผ ์œ„ํ•ด ์„ธ์‹ฌํ•˜๊ฒŒ ์ œ์ž‘๋œ ๋งค๋„๋Ÿฝ๊ณ  ๋ฏธ๋‹ˆ๋ฉ€ํ•œ ๋ฐ์Šคํฌํ†ฑ ์…ธ๋กœ, Quickshell๋กœ ๋นŒ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "noctalia-git-commit": "Git ์ปค๋ฐ‹:", + "noctalia-installed-version": "์„ค์น˜๋œ ๋ฒ„์ „:", + "noctalia-latest-version": "์ตœ์‹  ๋ฒ„์ „:", + "noctalia-qs-version": "Noctalia QS ๋ฒ„์ „:", + "noctalia-title": "Noctalia ์…ธ", + "privacy-policy": "๊ฐœ์ธ์ •๋ณด ์ฒ˜๋ฆฌ๋ฐฉ์นจ", + "support": "ํ›„์›ํ•˜๊ธฐ", + "supporter-badge": "ํ›„์›์ž", + "supporters-desc": "๋ฉ‹์ง„ ํ›„์›์ž๋ถ„๊ป˜ ์ง„์‹ฌ์œผ๋กœ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค!", + "supporters-desc-plural": "์ €ํฌ์˜ {count}๋ช…์˜ ๋ฉ‹์ง„ ํ›„์›์ž๋ถ„๋“ค๊ป˜ ์ง„์‹ฌ์œผ๋กœ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค!", + "supporters-loading": "ํ›„์›์ž ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...", + "system-board": "๋ฉ”์ธ๋ณด๋“œ:", + "system-cpu": "CPU:", + "system-disk": "๋””์Šคํฌ:", + "system-gpu": "GPU:", + "system-host": "ํ˜ธ์ŠคํŠธ:", + "system-install-hint": "์‹œ์Šคํ…œ ์ •๋ณด๋ฅผ ๋ณด๋ ค๋ฉด fastfetch๋ฅผ ์„ค์น˜ํ•˜์„ธ์š”", + "system-kernel": "์ปค๋„:", + "system-loading": "์‹œ์Šคํ…œ ์ •๋ณด ๋กœ๋”ฉ ์ค‘...", + "system-memory": "๋ฉ”๋ชจ๋ฆฌ:", + "system-monitor": "๋ชจ๋‹ˆํ„ฐ:", + "system-not-installed": "fastfetch๊ฐ€ ์„ค์น˜๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค", + "system-os": "OS:", + "system-packages": "ํŒจํ‚ค์ง€:", + "system-product": "์ œํ’ˆ:", + "system-title": "์‹œ์Šคํ…œ ์ •๋ณด", + "system-uptime": "๊ฐ€๋™ ์‹œ๊ฐ„:", + "system-wm": "WM:", + "telemetry-data-copied": "์›๊ฒฉ ๋ถ„์„ ๋ฐ์ดํ„ฐ๊ฐ€ ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌ๋จ", + "telemetry-desc": "์ต๋ช… ์‹œ์Šคํ…œ ์ •๋ณด(ํ™”๋ฉด ํ•ด์ƒ๋„, ์ปดํฌ์ง€ํ„ฐ, ๋ฐฐํฌํŒ)๋ฅผ ๊ณต์œ ํ•˜์—ฌ Noctalia ๊ฐœ์„ ์„ ๋„์™€์ฃผ์„ธ์š”. ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ ์ „์†ก๋˜๋ฉฐ ์ถ”์ ํ•˜์ง€ ์•Š๊ณ  ๋ฐ์ดํ„ฐ๋Š” 30์ผ ํ›„ ์ž๋™ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค.", + "telemetry-enabled": "์ต๋ช… ์‹œ์Šคํ…œ ์ •๋ณด ์ „์†ก", + "telemetry-show-data": "๋ฐ์ดํ„ฐ ๋ณด๊ธฐ", + "telemetry-title": "๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ", + "title": "์ •๋ณด", + "up-to-date": "์ตœ์‹  ์ƒํƒœ์ž…๋‹ˆ๋‹ค!", + "update-available": "์—…๋ฐ์ดํŠธ ๊ฐ€๋Šฅ", + "view-commit": "GitHub์—์„œ ์ปค๋ฐ‹ ๋ณด๊ธฐ" + }, + "audio": { + "devices-desc": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์˜ค๋””์˜ค ์ž…๋ ฅ ๋ฐ ์ถœ๋ ฅ ์žฅ์น˜๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "devices-input-device-description": "์›ํ•˜๋Š” ์˜ค๋””์˜ค ์ž…๋ ฅ ์žฅ์น˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "devices-input-device-label": "์ž…๋ ฅ ์žฅ์น˜", + "devices-output-device-description": "์›ํ•˜๋Š” ์˜ค๋””์˜ค ์ถœ๋ ฅ ์žฅ์น˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "devices-output-device-label": "์ถœ๋ ฅ ์žฅ์น˜", + "devices-title": "์˜ค๋””์˜ค ์žฅ์น˜", + "external-mixer-description": "์™ธ๋ถ€ ์˜ค๋””์˜ค ๋ฏน์„œ ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ•  ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น ๋˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฒฝ๋กœ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", + "external-mixer-label": "์™ธ๋ถ€ ์˜ค๋””์˜ค ๋ฏน์„œ ๋ช…๋ น", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "์„ ํ˜ธํ•˜๋Š” ๋ฏธ๋””์–ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ๋ฌด์‹œํ•  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "media-excluded-player-description": "์‹œ์Šคํ…œ์—์„œ ๋ฌด์‹œํ•  ์žฌ์ƒ๊ธฐ์˜ ํ‚ค์›Œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”. ๊ฐ ํ‚ค์›Œ๋“œ๋Š” ์ƒˆ ์ค„์— ์ž…๋ ฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "media-excluded-player-label": "์ œ์™ธ๋œ ์žฌ์ƒ๊ธฐ", + "media-excluded-player-placeholder": "๋ถ€๋ถ„ ๋ฌธ์ž์—ด ์ž…๋ ฅ ํ›„ + ๋ˆ„๋ฅด๊ธฐ", + "media-frame-rate-description": "๋†’์€ ํ”„๋ ˆ์ž„ ๋ ˆ์ดํŠธ๋Š” ๋ถ€๋“œ๋Ÿฝ์ง€๋งŒ ๋ฆฌ์†Œ์Šค๋ฅผ ๋” ๋งŽ์ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "media-frame-rate-label": "ํ”„๋ ˆ์ž„ ๋ ˆ์ดํŠธ", + "media-primary-player-description": "๋ฉ”์ธ ์žฌ์ƒ๊ธฐ๋ฅผ ์‹๋ณ„ํ•  ํ‚ค์›Œ๋“œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", + "media-primary-player-label": "๋ฉ”์ธ ์žฌ์ƒ๊ธฐ", + "media-primary-player-placeholder": "์˜ˆ: spotify, vlc, mpv", + "media-scrolling-speed-description": "์ œ๋ชฉ์ด ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ์Šคํฌ๋กค๋˜๋Š” ์‹œ๊ฐ„(์ดˆ)์ž…๋‹ˆ๋‹ค.", + "media-scrolling-speed-label": "์Šคํฌ๋กค ์†๋„", + "media-scrolling-title-description": "๊ธด ๋ฏธ๋””์–ด ์ œ๋ชฉ์— ๋Œ€ํ•ด ์—ฐ์† ์Šคํฌ๋กค์„ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "media-scrolling-title-label": "์ œ๋ชฉ ์Šคํฌ๋กค", + "media-title": "๋ฏธ๋””์–ด ์žฌ์ƒ๊ธฐ", + "on-middle-clicked-description": "๋ฒ„ํŠผ์„ ๊ฐ€์šด๋ฐ ํด๋ฆญํ–ˆ์„ ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค.", + "panel-applications-empty": "ํ˜„์žฌ ์˜ค๋””์˜ค๋ฅผ ์žฌ์ƒ ์ค‘์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์—†์Šต๋‹ˆ๋‹ค.", + "spectrum-mirrored-description": "์ŠคํŽ™ํŠธ๋Ÿผ์„ ๋ฏธ๋Ÿฌ๋งํ•˜์—ฌ ์ €์ฃผํŒŒ์ˆ˜๊ฐ€ ์ค‘์•™์— ์˜ค๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๋น„ํ™œ์„ฑํ™” ์‹œ ์ €์ฃผํŒŒ์ˆ˜๋Š” ์™ผ์ชฝ์—, ๊ณ ์ฃผํŒŒ์ˆ˜๋Š” ์˜ค๋ฅธ์ชฝ์— ์žˆ์Šต๋‹ˆ๋‹ค.", + "spectrum-mirrored-label": "์ŠคํŽ™ํŠธ๋Ÿผ ๋ฏธ๋Ÿฌ", + "title": "์˜ค๋””์˜ค", + "visualizer-type-description": "๋ฏธ๋””์–ด ์žฌ์ƒ ์‹œ ์‚ฌ์šฉํ•  ๋น„์ฃผ์–ผ๋ผ์ด์ € ์œ ํ˜•์„ ์„ ํƒํ•˜์„ธ์š”.", + "visualizer-type-label": "๋น„์ฃผ์–ผ๋ผ์ด์ € ์œ ํ˜•", + "volumes-desc": "๋ณผ๋ฅจ ์ œ์–ด ๋ฐ ์˜ค๋””์˜ค ๋ ˆ๋ฒจ์„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "volumes-feedback-sound-file-description": "๋ณผ๋ฅจ ๋ณ€๊ฒฝ ์‹œ ์žฌ์ƒ๋˜๋Š” ์‚ฌ์šด๋“œ ํŒŒ์ผ ๊ฒฝ๋กœ.", + "volumes-feedback-sound-file-label": "๋ณผ๋ฅจ ํ”ผ๋“œ๋ฐฑ ์‚ฌ์šด๋“œ ํŒŒ์ผ", + "volumes-feedback-sound-file-select-title": "๋ณผ๋ฅจ ํ”ผ๋“œ๋ฐฑ ์‚ฌ์šด๋“œ ํŒŒ์ผ ์„ ํƒ", + "volumes-input-volume-description": "๋งˆ์ดํฌ ์ž…๋ ฅ ๋ณผ๋ฅจ ๋ ˆ๋ฒจ์ž…๋‹ˆ๋‹ค.", + "volumes-mute-input-description": "๊ธฐ๋ณธ ์˜ค๋””์˜ค ์ž…๋ ฅ(๋งˆ์ดํฌ)์„ ์Œ์†Œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.", + "volumes-mute-input-label": "์˜ค๋””์˜ค ์ž…๋ ฅ ์Œ์†Œ๊ฑฐ", + "volumes-mute-output-description": "์‹œ์Šคํ…œ์˜ ๋ฉ”์ธ ์˜ค๋””์˜ค ์ถœ๋ ฅ์„ ์Œ์†Œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.", + "volumes-mute-output-label": "์˜ค๋””์˜ค ์ถœ๋ ฅ ์Œ์†Œ๊ฑฐ", + "volumes-output-volume-description": "์‹œ์Šคํ…œ ์ „์ฒด ๋ณผ๋ฅจ ๋ ˆ๋ฒจ์ž…๋‹ˆ๋‹ค.", + "volumes-step-size-description": "๋ณผ๋ฅจ ๋ณ€๊ฒฝ(์Šคํฌ๋กค ํœ , ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค)์˜ ๋‹จ๊ณ„ ํฌ๊ธฐ๋ฅผ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "volumes-step-size-label": "๋ณผ๋ฅจ ๋‹จ๊ณ„ ํฌ๊ธฐ", + "volumes-volume-feedback-description": "๋ณผ๋ฅจ ์กฐ์ ˆ ์‹œ ํ”ผ๋“œ๋ฐฑ ์†Œ๋ฆฌ๋ฅผ ์žฌ์ƒํ•ฉ๋‹ˆ๋‹ค.", + "volumes-volume-feedback-label": "๋ณผ๋ฅจ ํ”ผ๋“œ๋ฐฑ ์†Œ๋ฆฌ ์žฌ์ƒ", + "volumes-volume-overdrive-description": "๋ณผ๋ฅจ์„ 100% ์ด์ƒ์œผ๋กœ ๋†’์ผ ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ํ•˜๋“œ์›จ์–ด์—์„œ ์ง€์›๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "volumes-volume-overdrive-label": "๋ณผ๋ฅจ ์˜ค๋ฒ„๋“œ๋ผ์ด๋ธŒ ํ—ˆ์šฉ" + }, + "bar": { + "appearance-auto-hide-delay-description": "๋งˆ์šฐ์Šค๊ฐ€ Bar๋ฅผ ๋ฒ—์–ด๋‚œ ํ›„ Bar๊ฐ€ ์ˆจ๊ฒจ์ง€๊ธฐ๊นŒ์ง€์˜ ์‹œ๊ฐ„", + "appearance-auto-hide-delay-label": "์ˆจ๊ธฐ๊ธฐ ์ง€์—ฐ", + "appearance-auto-hide-exclusive-note": "์ฐธ๊ณ : ์ž๋™ ์ˆจ๊ธฐ๊ธฐ๊ฐ€ ํ™œ์„ฑํ™”๋˜๋ฉด ์ „์šฉ ์˜์—ญ์ด ์ž๋™์œผ๋กœ ๋น„ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค.", + "appearance-auto-show-delay-description": "๋งˆ์šฐ์Šค๊ฐ€ ๊ฐ€์žฅ์ž๋ฆฌ์— ์ง„์ž…ํ•  ๋•Œ ๋ฐ”๊ฐ€ ํ‘œ์‹œ๋˜๊ธฐ๊นŒ์ง€์˜ ์‹œ๊ฐ„.", + "appearance-auto-show-delay-label": "ํ‘œ์‹œ ์ง€์—ฐ", + "appearance-background-opacity-description": "๋ฐ”์— ๋Œ€ํ•ด์„œ๋งŒ ๋ณ„๋„๋กœ ๋ฐฐ๊ฒฝ ๋ถˆํˆฌ๋ช…๋„๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-background-opacity-label": "๋ฐ” ๋ฐฐ๊ฒฝ ๋ถˆํˆฌ๋ช…๋„", + "appearance-capsule-color-description": "๋ฐ” ์บก์А์˜ ์ƒ‰์ƒ์„ ์„ ํƒํ•˜๊ฑฐ๋‚˜, ๊ธฐ๋ณธ ํ‘œ๋ฉด ์ƒ‰์ƒ์—๋Š” ์ƒ‰์ƒ์„ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”.", + "appearance-capsule-color-label": "์บก์А ์ƒ‰์ƒ", + "appearance-capsule-opacity-description": "์บก์А์ด ํ‘œ์‹œ๋  ๋•Œ ์œ„์ ฏ ๋ฐฐ๊ฒฝ์˜ ๋ถˆํˆฌ๋ช…๋„ ์ˆ˜์ค€์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-capsule-opacity-label": "์บก์А ๋ถˆํˆฌ๋ช…๋„", + "appearance-content-padding-description": "๋ฐ” ๊ฐ€์žฅ์ž๋ฆฌ์™€ ์œ„์ ฏ ์‚ฌ์ด์˜ ํŒจ๋”ฉ์„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-content-padding-label": "์ฝ˜ํ…์ธ  ํŒจ๋”ฉ", + "appearance-density-description": "์ปดํŒฉํŠธํ•˜๊ฑฐ๋‚˜ ๋„“์€ ๋ชจ์–‘์„ ์œ„ํ•ด ๋ฐ”์˜ ํŒจ๋”ฉ์„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-density-label": "๋ฐ” ๋ฐ€๋„", + "appearance-desc": "๋ฐ”์˜ ๋ชจ์–‘๊ณผ ์œ„์น˜๋ฅผ ์‚ฌ์šฉ์ž ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-display-mode-description": "๋ฐ”๊ฐ€ ์–ธ์ œ ํ‘œ์‹œ๋ ์ง€ ์„ ํƒํ•˜์„ธ์š”.", + "appearance-enable-exclusion-zone-inset-description": "์ œ์™ธ ์˜์—ญ์„ 1 ๋ฌผ๋ฆฌ ํ”ฝ์…€๋งŒํผ ์ค„์—ฌ ๋ฐ” ๊ฐ€์žฅ์ž๋ฆฌ ์•„๋ž˜๋กœ ์ฐฝ์ด ์™„๋ฒฝํ•˜๊ฒŒ ๊ฒน์น˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-enable-exclusion-zone-inset-label": "์ธ์…‹ ์ œ์™ธ ์˜์—ญ", + "appearance-floating-description": "๋ฐ”๋ฅผ ํ”Œ๋กœํŒ… '์•Œ์•ฝ' ํ˜•ํƒœ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "appearance-floating-label": "ํ”Œ๋กœํŒ… ๋ฐ”", + "appearance-font-scale-description": "๋ฐ”์— ํ‘œ์‹œ๋˜๋Š” ํ…์ŠคํŠธ์˜ ๊ธ€๊ผด ํฌ๊ธฐ ๋ฐฐ์œจ์„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-font-scale-label": "๊ธ€๊ผด ๋ฐฐ์œจ", + "appearance-frame-radius": "๋‚ด๋ถ€ ๋ฐ˜๊ฒฝ", + "appearance-frame-settings-description": "ํ”„๋ ˆ์ž„ ๋‘๊ป˜ ๋ฐ ๋‚ด๋ถ€ ๋ชจ์„œ๋ฆฌ ๋ฐ˜๊ฒฝ ์กฐ์ •", + "appearance-frame-settings-label": "ํ”„๋ ˆ์ž„ ์„ค์ •", + "appearance-frame-thickness": "๋‘๊ป˜", + "appearance-hide-on-overview-description": "์ปดํฌ์ง€ํ„ฐ ๊ฐœ์š”(Overview)๊ฐ€ ํ™œ์„ฑ ์ƒํƒœ์ผ ๋•Œ ๋ฐ”๋ฅผ ์ˆจ๊ธฐ๊ณ  ํŒจ๋„์„ ๋‹ซ์Šต๋‹ˆ๋‹ค.", + "appearance-hide-on-overview-label": "๊ฐœ์š”์—์„œ ๋ฐ” ์ˆจ๊ธฐ๊ธฐ", + "appearance-margins-description": "ํ”Œ๋กœํŒ… ๋ฐ” ์ฃผ์œ„์˜ ์—ฌ๋ฐฑ์„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-margins-horizontal": "์ˆ˜ํ‰ ์—ฌ๋ฐฑ", + "appearance-margins-vertical": "์ˆ˜์ง ์—ฌ๋ฐฑ", + "appearance-outer-corners-description": "๋ฐ”์— ๋ฐ”๊นฅ์ชฝ์œผ๋กœ ๊ตฝ์€ ๋ชจ์„œ๋ฆฌ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "appearance-outer-corners-label": "์™ธ๋ถ€ ๋ชจ์„œ๋ฆฌ", + "appearance-position-description": "ํ™”๋ฉด์—์„œ ๋ฐ”๋ฅผ ๋ฐฐ์น˜ํ•  ์œ„์น˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "appearance-position-label": "๋ฐ” ์œ„์น˜", + "appearance-show-capsule-description": "์œ„์ ฏ ๋ฐฐ๊ฒฝ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "appearance-show-capsule-label": "์บก์А ํ‘œ์‹œ", + "appearance-show-on-workspace-switch-description": "์›Œํฌ์ŠคํŽ˜์ด์Šค๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ Bar๋ฅผ ์ž๋™์œผ๋กœ ์ž ์‹œ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.", + "appearance-show-on-workspace-switch-label": "์ž‘์—… ๊ณต๊ฐ„ ์ „ํ™˜ ์‹œ ๋ฐ” ํ‘œ์‹œ", + "appearance-show-outline-description": "๋ชจ๋“  ์œ„์ ฏ ์ฃผ์œ„์— ๋ณด์ด๋Š” ํ…Œ๋‘๋ฆฌ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "appearance-show-outline-label": "์œ„์ ฏ ์œค๊ณฝ์„  ํ‘œ์‹œ", + "appearance-type-description": "๋ฐ”์˜ ์Šคํƒ€์ผ์„ ์„ ํƒํ•˜์„ธ์š”: ์‹ฌํ”Œ, ํ”Œ๋กœํŒ…, ํ”„๋ ˆ์ž„.", + "appearance-type-label": "๋ฐ” ์œ ํ˜•", + "appearance-use-separate-opacity-description": "๋ฐ” ๋ฐฐ๊ฒฝ์— ๋Œ€ํ•ด ๋ณ„๋„์˜ ๋ถˆํˆฌ๋ช…๋„ ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ํ™œ์„ฑํ™”ํ•˜์„ธ์š”.", + "appearance-use-separate-opacity-label": "๋ณ„๋„ ๋ฐ” ๋ถˆํˆฌ๋ช…๋„ ์‚ฌ์šฉ", + "appearance-widget-spacing-description": "๋ฐ”์˜ ๊ฐ ์œ„์ ฏ ์‚ฌ์ด ๊ฐ„๊ฒฉ์„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-widget-spacing-label": "์œ„์ ฏ ๊ฐ„๊ฒฉ", + "behavior-middle-click-command-description": "๊ฐ€์šด๋ฐ ํด๋ฆญ ์‹œ ์‹คํ–‰ํ•  ๋ช…๋ น.", + "behavior-middle-click-command-label": "๊ฐ€์šด๋ฐ ํด๋ฆญ ๋ช…๋ น", + "behavior-middle-click-command-placeholder": "niri msg action ๊ฐœ์š” ์ „ํ™˜", + "behavior-middle-click-description": "๋ฐ”์˜ ๋นˆ ์˜์—ญ์—์„œ ๊ฐ€์šด๋ฐ ํด๋ฆญ ์‹œ ์ˆ˜ํ–‰ํ•  ์ž‘์—…์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.", + "behavior-middle-click-follow-mouse-description": "์„ ํƒํ•œ ๊ฐ€์šด๋ฐ ํด๋ฆญ ํŒจ๋„์„ ์ปค์„œ ์œ„์น˜์— ์—ฝ๋‹ˆ๋‹ค.", + "behavior-middle-click-follow-mouse-label": "๊ฐ€์šด๋ฐ ํด๋ฆญ ์‹œ ๋งˆ์šฐ์Šค ๋”ฐ๋ผ๊ฐ€๊ธฐ", + "behavior-middle-click-label": "๋ฐ” ๊ฐ€์šด๋ฐ ํด๋ฆญ ๋™์ž‘", + "behavior-right-click-command-description": "๋งˆ์šฐ์Šค ์˜ค๋ฅธ์ชฝ ํด๋ฆญ ์‹œ ์‹คํ–‰ํ•  ๋ช…๋ น.", + "behavior-right-click-command-label": "์˜ค๋ฅธ์ชฝ ํด๋ฆญ ๋ช…๋ น", + "behavior-right-click-command-placeholder": "notify-send \"์˜ค๋ฅธ์ชฝ ํด๋ฆญ\"", + "behavior-right-click-description": "๋ฐ”์˜ ๋นˆ ์˜์—ญ์—์„œ ๋งˆ์šฐ์Šค ์˜ค๋ฅธ์ชฝ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์ˆ˜ํ–‰ํ•  ์ž‘์—…์„ ์„ ํƒํ•˜์„ธ์š”.", + "behavior-right-click-follow-mouse-description": "์„ ํƒํ•œ ์˜ค๋ฅธ์ชฝ ํด๋ฆญ ํŒจ๋„์„ ์ปค์„œ ์œ„์น˜์— ์—ฝ๋‹ˆ๋‹ค.", + "behavior-right-click-follow-mouse-label": "๋งˆ์šฐ์Šค ์˜ค๋ฅธ์ชฝ ํด๋ฆญ ์‹œ ๋งˆ์šฐ์Šค ๋”ฐ๋ผ๊ฐ€๊ธฐ", + "behavior-right-click-label": "๋ฐ” ์˜ค๋ฅธ์ชฝ ํด๋ฆญ ๋™์ž‘", + "behavior-wheel-wrap-description": "ํ™œ์„ฑํ™”๋˜๋ฉด ์Šคํฌ๋กค์ด ๋งˆ์ง€๋ง‰ ํ•ญ๋ชฉ์—์„œ ์ฒซ ๋ฒˆ์งธ ํ•ญ๋ชฉ์œผ๋กœ ๊ณ„์†๋ฉ๋‹ˆ๋‹ค.", + "behavior-wheel-wrap-label": "์ˆœํ™˜", + "behavior-workspace-scroll-description": "๋ฐ”์˜ ๋น„์–ด ์žˆ๋Š” ์˜์—ญ์—์„œ ๋งˆ์šฐ์Šค ํœ ์ด ๋ฌด์—‡์„ ํ• ์ง€ ์„ ํƒํ•˜์„ธ์š”.", + "behavior-workspace-scroll-label": "๋ฐ” ๋งˆ์šฐ์Šค ํœ  ๋™์ž‘", + "behavior-workspace-scroll-option-content": "์ฝ˜ํ…์ธ ", + "behavior-workspace-scroll-option-workspace": "์ž‘์—… ๊ณต๊ฐ„", + "monitor-configure-widgets": "์œ„์ ฏ ๊ตฌ์„ฑ", + "monitor-override-settings": "์ „์—ญ ์„ค์ • ์žฌ์ •์˜", + "monitor-override-settings-description": "์ด ๋ชจ๋‹ˆํ„ฐ์— ์‚ฌ์šฉ์ž ์ง€์ • ์„ค์ •์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "monitor-reset-all": "๋ชจ๋‘ ์ดˆ๊ธฐํ™”", + "monitor-widgets-title": "์œ„์ ฏ ๊ตฌ์„ฑ - {monitor}", + "monitors-desc": "ํŠน์ • ๋ชจ๋‹ˆํ„ฐ์— ๋ฐ”๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ์„ ํƒํ•˜์ง€ ์•Š์œผ๋ฉด ๋ชจ๋“  ๋ชจ๋‹ˆํ„ฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.", + "monitors-desc-new": "๋ฐ”๋ฅผ ํ‘œ์‹œํ•  ๋ชจ๋‹ˆํ„ฐ๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  ๋ชจ๋‹ˆํ„ฐ๋ณ„๋กœ ์„ค์ •์„ ์‚ฌ์šฉ์ž ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.", + "monitors-title": "๋ชจ๋‹ˆํ„ฐ ํ‘œ์‹œ", + "title": "๋ฐ”", + "tray-blacklist-description": "ํŠธ๋ ˆ์ด ์ œ์™ธ ๊ทœ์น™์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์™€์ผ๋“œ์นด๋“œ(*)๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.", + "tray-blacklist-label": "๋ธ”๋ž™๋ฆฌ์ŠคํŠธ", + "tray-blacklist-placeholder": "์˜ˆ: nm-applet, Fcitx*", + "tray-pin-application": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ณ ์ •", + "tray-unpin-application": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ณ ์ • ํ•ด์ œ", + "use-global-widgets": "์ „์—ญ ์œ„์ ฏ ์‚ฌ์šฉ", + "widgets-desc": "์œ„์ ฏ์„ ๋“œ๋ž˜๊ทธํ•˜์—ฌ ์ˆœ์„œ๋ฅผ ๋ณ€๊ฒฝํ•˜์„ธ์š”. ์˜ค๋ฅธ์ชฝ ํด๋ฆญ ๋ฉ”๋‰ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„น์…˜ ๊ฐ„์— ์œ„์ ฏ์„ ์ด๋™ํ•˜๊ฑฐ๋‚˜ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "widgets-title": "์œ„์ ฏ ๋ฐฐ์น˜" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "๋ฐฐ๊ฒฝํ™”๋ฉด์—์„œ ์ƒ‰์ƒ ๊ตฌ์„ฑ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ž๋™์œผ๋กœ ์ƒ‰์ƒ์„ ์ถ”์ถœํ•˜์—ฌ ์กฐํ™”๋กœ์šด ํ…Œ๋งˆ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.", + "color-source-use-wallpaper-colors-label": "๋ฐฐ๊ฒฝํ™”๋ฉด ์ƒ‰์ƒ ์‚ฌ์šฉ", + "dark-mode-mode-description": "๋ผ์ดํŠธ ๋ชจ๋“œ์™€ ๋‹คํฌ ๋ชจ๋“œ ๊ฐ„์˜ ์ž๋™ ์ „ํ™˜์„ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "dark-mode-mode-label": "๋‹คํฌ ๋ชจ๋“œ ์ผ์ •", + "dark-mode-mode-manual": "์ˆ˜๋™", + "dark-mode-mode-off": "๋„๊ธฐ", + "dark-mode-switch-description": "์•ผ๊ฐ„์— ๋” ํŽธ์•ˆํ•˜๊ฒŒ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก ์–ด๋‘์šด ํ…Œ๋งˆ๋กœ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค.", + "delete-error-description": "{scheme} ์‚ญ์ œ ์‹คํŒจ", + "delete-error-title": "์‚ญ์ œ ์‹คํŒจ", + "delete-success-description": "{scheme} ์‚ญ์ œ ์„ฑ๊ณต", + "delete-success-title": "์ƒ‰์ƒ ๊ตฌ์„ฑ ์‚ญ์ œ๋จ", + "download-button": "๋” ๋‹ค์šด๋กœ๋“œํ•˜๊ธฐ", + "download-downloading": "๋‹ค์šด๋กœ๋“œ ์ค‘...", + "download-empty": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ƒ‰์ƒ ๊ตฌ์„ฑ ์—†์Œ", + "download-error-api-error": "API ์˜ค๋ฅ˜: {status}", + "download-error-description": "{scheme} ๋‹ค์šด๋กœ๋“œ ์‹คํŒจ", + "download-error-download-failed": "์ข…๋ฃŒ ์ฝ”๋“œ {code}๋กœ ๋‹ค์šด๋กœ๋“œ ์‹คํŒจ", + "download-error-invalid-response": "์œ ํšจํ•˜์ง€ ์•Š์€ API ์‘๋‹ต ํ˜•์‹", + "download-error-no-files": "๊ตฌ์„ฑ์— ๋Œ€ํ•œ ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ", + "download-error-parse-failed": "API ์‘๋‹ต ํŒŒ์‹ฑ ์‹คํŒจ: {error}", + "download-error-rate-limit": "GitHub API ์†๋„ ์ œํ•œ ์ดˆ๊ณผ", + "download-error-title": "๋‹ค์šด๋กœ๋“œ ์‹คํŒจ", + "download-fetching": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ƒ‰์ƒ ๊ตฌ์„ฑ ๊ฐ€์ ธ์˜ค๋Š” ์ค‘...", + "download-success-description": "{scheme} ๋‹ค์šด๋กœ๋“œ ์„ฑ๊ณต", + "download-success-title": "์ƒ‰์ƒ ๊ตฌ์„ฑ ๋‹ค์šด๋กœ๋“œ๋จ", + "download-title": "์ƒ‰์ƒ ๊ตฌ์„ฑ ๋‹ค์šด๋กœ๋“œ", + "method-description": { + "content": "์†Œ์Šค ์ฝ˜ํ…์ธ ์˜ ์‹ค์ œ ์ƒ‰์ƒ์„ ๋งค์šฐ ์œ ์‚ฌํ•˜๊ฒŒ ์žฌํ˜„ํ•˜๋Š” ๊ณ ํ’ˆ์งˆ ์ƒ‰์ƒ ์ถ”์ถœ์„ ์‚ฌ์šฉํ•˜๋Š” ๋จธํ‹ฐ๋ฆฌ์–ผ ๋””์ž์ธ ๊ตฌ์„ฑ์ž…๋‹ˆ๋‹ค.", + "dysfunctional": "Faithful๊ณผ ๋น„์Šทํ•˜์ง€๋งŒ, ๋‘ ๋ฒˆ์งธ๋กœ ์ง€๋ฐฐ์ ์ธ ์ƒ‰์ƒ ๊ณ„์—ด์„ ๊ธฐ๋ณธ์œผ๋กœ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.", + "faithful": "์†Œ์Šค ์ƒ‰์ƒ์— ๊ฐ€๊น๊ฒŒ ์œ ์ง€ํ•˜๋ฉด์„œ ์กฐํ™”๋กœ์šด ํŒ”๋ ˆํŠธ ์ƒ์„ฑ์„ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.", + "fruit-salad": "๋‹ค์–‘ํ•˜๊ณ  ๋‹ค์ฑ„๋กœ์šด ์ƒ‰์กฐ๋กœ ์œ ์พŒํ•˜๊ณ  ์ƒ๋™๊ฐ ๋„˜์น˜๋Š” ํŒ”๋ ˆํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋จธํ‹ฐ๋ฆฌ์–ผ ๋””์ž์ธ ๊ตฌ์„ฑ์ž…๋‹ˆ๋‹ค.", + "monochrome": "์ฑ„๋„๊ฐ€ ์ตœ์†Œํ™”๋œ ๋‹จ์ผ ์ƒ‰์กฐ ๊ทธ๋ ˆ์ด์Šค์ผ€์ผ์„ ์‚ฌ์šฉํ•˜๋Š” ๋จธํ‹ฐ๋ฆฌ์–ผ ๋””์ž์ธ ๊ตฌ์„ฑ์ž…๋‹ˆ๋‹ค.", + "muted": "์ฐจ๋ถ„ํ•˜๊ณ  ์ ˆ์ œ๋œ ์™ธ๊ด€์„ ์œ„ํ•ด ๋ถ€๋“œ๋Ÿฝ๊ณ  ์ฑ„๋„๊ฐ€ ๋‚ฎ์€ ํ†ค์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "rainbow": "์ตœ๋Œ€ํ•œ์˜ ์ƒ‰์ƒ ๋‹ค์–‘์„ฑ์„ ์œ„ํ•ด ์ „์ฒด ์ŠคํŽ™ํŠธ๋Ÿผ์— ๊ฑธ์ณ ์ƒ‰์ƒ์„ ์ƒ์„ฑํ•˜๋Š” ๋จธํ‹ฐ๋ฆฌ์–ผ ๋””์ž์ธ ๊ตฌ์„ฑ์ž…๋‹ˆ๋‹ค.", + "tonal-spot": "๋จธํ‹ฐ๋ฆฌ์–ผ ๋””์ž์ธ ๊ธฐ๋ณธ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ž…๋‹ˆ๋‹ค. ์†Œ์Šค ์ƒ‰์ƒ์˜ ํ†ค ๋ณ€ํ™”๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ท ํ˜• ์žกํžŒ ํŒ”๋ ˆํŠธ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.", + "vibrant": "์„ ๋ช…ํ•œ ํ†ค์œผ๋กœ ๋Œ€๋‹ดํ•˜๊ณ  ์‹œ์„ ์„ ์‚ฌ๋กœ์žก๋Š” ํŒ”๋ ˆํŠธ๋ฅผ ์œ„ํ•ด ์ƒ‰์ƒ ์ฑ„๋„๋ฅผ ๊ทน๋Œ€ํ™”ํ•ฉ๋‹ˆ๋‹ค." + }, + "predefined-desc": "๋ฏธ๋ฆฌ ์ •์˜๋œ ์ƒ‰์ƒ ๊ตฌ์„ฑ ๋ชจ์Œ์—์„œ ์„ ํƒํ•˜์„ธ์š”.", + "predefined-generate-templates-label": "๋ฏธ๋ฆฌ ์ •์˜๋œ ๊ตฌ์„ฑ์— ๋Œ€ํ•œ ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑ", + "predefined-title": "๋ฏธ๋ฆฌ ์ •์˜๋œ ์ƒ‰์ƒ ๊ตฌ์„ฑ", + "sync-gsettings-description": "์‹œ์Šคํ…œ ํ…Œ๋งˆ๋ฅผ ํ™œ์„ฑ ๋ฐ๊ฑฐ๋‚˜ ์–ด๋‘์šด ๋ณ€ํ˜•์— ๋งž์ถฅ๋‹ˆ๋‹ค.", + "sync-gsettings-label": "์‹œ์Šคํ…œ ํ…Œ๋งˆ ๋™๊ธฐํ™”", + "templates-desc": "์™ธ๋ถ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ƒ‰์ƒ์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "templates-filter-description": "ํŠน์ • ์นดํ…Œ๊ณ ๋ฆฌ์˜ ํ…œํ”Œ๋ฆฟ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "templates-filter-label": "์นดํ…Œ๊ณ ๋ฆฌ๋กœ ํ•„ํ„ฐ๋ง", + "templates-misc-description": "๋‚˜๋งŒ์˜ ํ…œํ”Œ๋ฆฟ์„ ๋งŒ๋“œ์„ธ์š”.", + "templates-misc-label": "๊ณ ๊ธ‰", + "templates-misc-user-templates-description": "๋ฌด์—‡์„ ํ•˜๋Š”์ง€ ์•Œ๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ ํ™œ์„ฑํ™”ํ•˜์„ธ์š” โ€” ์˜จ๋ผ์ธ ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.", + "templates-misc-user-templates-label": "์‚ฌ์šฉ์ž ํ…œํ”Œ๋ฆฟ ํ™œ์„ฑํ™”", + "templates-none-detected": "๊ฐ์ง€๋œ ํ•ญ๋ชฉ ์—†์Œ", + "templates-write-path": "์“ฐ๊ธฐ: {filepath}", + "title": "์ƒ‰์ƒ ๊ตฌ์„ฑ", + "wallpaper-method-description": "์„ ํ˜ธํ•˜๋Š” ํŒ”๋ ˆํŠธ ์ƒ์„ฑ ๋ฐฉ๋ฒ•์„ ์„ ํƒํ•˜์„ธ์š”.", + "wallpaper-method-label": "ํŒ”๋ ˆํŠธ ์ƒ์„ฑ ๋ฐฉ๋ฒ•", + "wallpaper-monitor-source-description": "๋ฐฐ๊ฒฝํ™”๋ฉด ์ƒ‰์ƒ์„ ์ถ”์ถœํ•  ๋ชจ๋‹ˆํ„ฐ๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "wallpaper-monitor-source-label": "์ƒ‰์ƒ ์ƒ์„ฑ ์†Œ์Šค" + }, + "connections": { + "authentication-required": "์ธ์ฆ ํ•„์š”", + "bluetooth-auto-connect-description": "Bluetooth๊ฐ€ ํ™œ์„ฑํ™”๋˜๋ฉด ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ํŽ˜์–ด๋ง๋œ ์žฅ์น˜์— ์ž๋™์œผ๋กœ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค.", + "bluetooth-auto-connect-label": "ํŽ˜์–ด๋ง๋œ ๊ธฐ๊ธฐ ์ž๋™ ์—ฐ๊ฒฐ", + "bluetooth-devices-unnamed": "์ด๋ฆ„ ์—†๋Š” ์žฅ์น˜๋Š” ํ‘œ์‹œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + "bluetooth-discoverable": "์ด ์žฅ์น˜๋Š” ์ด ์„ค์ • ํƒญ์ด ์—ด๋ ค ์žˆ๋Š” ๋™์•ˆ {hostName}์œผ๋กœ ๊ฒ€์ƒ‰๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "bluetooth-rssi-polling-description": "bluetoothctl์„ ํ†ตํ•ด ์—ฐ๊ฒฐ๋œ ์žฅ์น˜์˜ RSSI๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ์ƒ˜ํ”Œ๋งํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ์žฅ์น˜์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ™œ์„ฑํ™” ์‹œ ์ตœ์†Œํ•œ์˜ ๋ฆฌ์†Œ์Šค๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "bluetooth-rssi-polling-interval-description": "์—ฐ๊ฒฐ๋œ ์žฅ์น˜์˜ ์‹ ํ˜ธ ๊ฐ•๋„๋ฅผ ์–ผ๋งˆ๋‚˜ ์ž์ฃผ ์—…๋ฐ์ดํŠธํ• ์ง€ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "bluetooth-rssi-polling-interval-label": "ํด๋ง ๊ฐ„๊ฒฉ", + "bluetooth-rssi-polling-label": "๋ธ”๋ฃจํˆฌ์Šค ์‹ ํ˜ธ ํด๋ง", + "disable-discoverability-description": "์ฃผ๋ณ€ ๋ธ”๋ฃจํˆฌ์Šค ์žฅ์น˜์—์„œ ์žฅ์น˜๋ฅผ ์ˆจ๊น๋‹ˆ๋‹ค.", + "disable-discoverability-label": "์žฅ์น˜ ๊ฐ€์‹œ์„ฑ ๋น„ํ™œ์„ฑํ™”", + "hide-unnamed-devices-description": "๋ธ”๋ฃจํˆฌ์Šค ์ฃผ์†Œ๋กœ๋งŒ ํ‘œ์‹œ๋˜๋Š” ์žฅ์น˜๋ฅผ ์ˆจ๊น๋‹ˆ๋‹ค.", + "hide-unnamed-devices-label": "์ด๋ฆ„ ์—†๋Š” ์žฅ์น˜ ์ˆจ๊ธฐ๊ธฐ", + "pin-instructions": "๊ธฐ๊ธฐ์— ํ‘œ์‹œ๋œ PIN ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", + "title": "์—ฐ๊ฒฐ", + "wifi-header-text": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋„คํŠธ์›Œํฌ๋ฅผ ํ™•์ธํ•˜๊ณ  ์ธํ„ฐ๋„ท์— ์—ฐ๊ฒฐํ•˜๋ ค๋ฉด Wi-Fi๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”." + }, + "control-center": { + "cards-desc": "์ œ์–ด ์„ผํ„ฐ์— ํ‘œ์‹œ๋  ์ปจํŠธ๋กค๊ณผ ์ˆœ์„œ๋ฅผ ์‚ฌ์šฉ์ž ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.", + "desc": "์ œ์–ด ์„ผํ„ฐ ํŒจ๋„์˜ ์œ„์น˜์™€ ๋™์ž‘์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "open-at-mouse-description": "๋ฐ”์˜ ๋นˆ ๊ณต๊ฐ„์„ ์˜ค๋ฅธ์ชฝ ํด๋ฆญํ•˜๋ฉด ์ปค์„œ ๊ทผ์ฒ˜์— ์ œ์–ด ์„ผํ„ฐ๋ฅผ ์—ฝ๋‹ˆ๋‹ค. ๋น„ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ, ๋Œ€์‹  ์œ„์˜ ์œ„์น˜ ์„ค์ •์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.", + "open-at-mouse-label": "๋งˆ์šฐ์Šค ์˜ค๋ฅธ์ชฝ ํด๋ฆญ ์‹œ ์ปค์„œ ์œ„์น˜์— ์—ด๊ธฐ", + "position-description": "์ œ์–ด ์„ผํ„ฐ ํŒจ๋„์ด ์—ด๋ฆด ๋•Œ ๋‚˜ํƒ€๋‚  ์œ„์น˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "shortcuts-custom-button-command-description": "๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค.", + "shortcuts-custom-button-enable-on-state-logic-description": "๊ฒ€์‚ฌ ๋ช…๋ น์— ๋”ฐ๋ผ ๋‘ ๋ฒˆ์งธ ์•„์ด์ฝ˜๊ณผ '์ผœ์ง(hot)' ์ƒํƒœ๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "shortcuts-custom-button-enable-on-state-logic-label": "์ผœ์ง ์ƒํƒœ ๋กœ์ง ํ™œ์„ฑํ™”", + "shortcuts-custom-button-icon-description": "๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์•„์ด์ฝ˜์„ ์„ ํƒํ•˜์„ธ์š”.", + "shortcuts-custom-button-on-clicked-label": "์™ผ์ชฝ ํด๋ฆญ ๋ช…๋ น", + "shortcuts-custom-button-on-middle-clicked-description": "๋ฒ„ํŠผ์„ ๊ฐ€์šด๋ฐ ํด๋ฆญํ–ˆ์„ ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น.", + "shortcuts-custom-button-on-state-command-description": "๋ฒ„ํŠผ์ด '์ผœ์ง' ์ƒํƒœ์—ฌ์•ผ ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค. ์ผœ์ง์ผ ๊ฒฝ์šฐ 0, ๊บผ์ง์ผ ๊ฒฝ์šฐ 0์ด ์•„๋‹Œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.", + "shortcuts-custom-button-on-state-command-label": "์ผœ์ง ์ƒํƒœ ํ™•์ธ ๋ช…๋ น", + "shortcuts-custom-button-on-state-icon-description": "๋ฒ„ํŠผ์ด '์ผœ์ง' ์ƒํƒœ์ผ ๋•Œ์˜ ์•„์ด์ฝ˜์ž…๋‹ˆ๋‹ค.", + "shortcuts-custom-button-on-state-icon-label": "์ผœ์ง ์ƒํƒœ ์•„์ด์ฝ˜", + "shortcuts-custom-button-state-checks-add": "์ƒํƒœ ํ™•์ธ ์ถ”๊ฐ€", + "shortcuts-custom-button-state-checks-command": "์ด ์ƒํƒœ ํ™•์ธ์„ ์œ„ํ•ด ์‹คํ–‰ํ•  ๋ช…๋ น", + "shortcuts-custom-button-state-checks-label": "์ƒํƒœ ํ™•์ธ", + "shortcuts-custom-button-state-checks-remove": "์ œ๊ฑฐ", + "shortcuts-custom-button-tooltip-description": "๋ฒ„ํŠผ ์œ„์— ๋งˆ์šฐ์Šค๋ฅผ ์˜ฌ๋ ธ์„ ๋•Œ ํ‘œ์‹œํ•  ํˆดํŒ์ž…๋‹ˆ๋‹ค.", + "shortcuts-custom-button-tooltip-label": "ํˆดํŒ", + "shortcuts-title": "๋‹จ์ถ•ํ‚ค ์œ„์ ฏ", + "system-monitor-disk-path-description": "์ œ์–ด ์„ผํ„ฐ์˜ ์‹œ์Šคํ…œ ๋ชจ๋‹ˆํ„ฐ ์นด๋“œ๊ฐ€ ๋ชจ๋‹ˆํ„ฐ๋งํ•  ๋””์Šคํฌ ๋งˆ์šดํŠธ ์ง€์ ์„ ์„ ํƒํ•˜์„ธ์š”.", + "system-monitor-disk-path-label": "์‹œ์Šคํ…œ ๋ชจ๋‹ˆํ„ฐ ๋””์Šคํฌ ๊ฒฝ๋กœ", + "title": "์ œ์–ด ์„ผํ„ฐ" + }, + "desktop-widgets": { + "clock-enabled-description": "๋ฐ”ํƒ• ํ™”๋ฉด์— ์‹œ๊ณ„ ์œ„์ ฏ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "clock-enabled-label": "์‹œ๊ณ„ ์œ„์ ฏ ํ™œ์„ฑํ™”", + "clock-format-label": "ํ˜•์‹", + "clock-height-description": "์‹œ๊ณ„ ์œ„์ ฏ์˜ ๋†’์ด(ํ”ฝ์…€ ๋‹จ์œ„).", + "clock-height-label": "๋†’์ด", + "clock-minimal-mode-description": "์‹œ๊ฐ„๊ณผ ๋‚ ์งœ๋งŒ ์žˆ๋Š” ๋ฏธ๋‹ˆ๋ฉ€ํ•œ ์‹œ๊ณ„ ํ‘œ์‹œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "clock-minimal-mode-label": "๋ฏธ๋‹ˆ๋ฉ€ ๋ชจ๋“œ", + "clock-rounded-corners-description": "์œ„์ ฏ ๋ฐฐ๊ฒฝ์— ๋‘ฅ๊ทผ ๋ชจ์„œ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "clock-rounded-corners-label": "๋‘ฅ๊ทผ ๋ชจ์„œ๋ฆฌ", + "clock-show-background-description": "์‹œ๊ณ„ ์œ„์ ฏ์˜ ๋ฐฐ๊ฒฝ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "clock-show-background-label": "๋ฐฐ๊ฒฝ ํ‘œ์‹œ", + "clock-show-date-description": "์‹œ๊ฐ„ ์•„๋ž˜์— ํ˜„์žฌ ๋‚ ์งœ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "clock-show-date-label": "๋‚ ์งœ ํ‘œ์‹œ", + "clock-show-seconds-description": "์‹œ๊ฐ„์— ์ดˆ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "clock-show-seconds-label": "์ดˆ ํ‘œ์‹œ", + "clock-style-analog": "์•„๋‚ ๋กœ๊ทธ", + "clock-style-binary": "๋ฐ”์ด๋„ˆ๋ฆฌ", + "clock-style-description": "์‹œ๊ณ„ ํ‘œ์‹œ ์Šคํƒ€์ผ์„ ์„ ํƒํ•˜์„ธ์š”.", + "clock-style-digital": "๋””์ง€ํ„ธ", + "clock-style-label": "์‹œ๊ณ„ ์Šคํƒ€์ผ", + "clock-style-minimal": "๋ฏธ๋‹ˆ๋ฉ€", + "clock-width-description": "์‹œ๊ณ„ ์œ„์ ฏ์˜ ๋„ˆ๋น„(ํ”ฝ์…€ ๋‹จ์œ„).", + "cpu-intensive-note": "! ํ‘œ์‹œ๋œ ์œ„์ ฏ์€ ๋” ๋งŽ์€ CPU๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์‹ ์ค‘ํ•˜๊ฒŒ ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "edit-mode-button-label": "ํŽธ์ง‘ ๋ชจ๋“œ ์ง„์ž…", + "edit-mode-controls-explanation": "์™ผ์ชฝ ํด๋ฆญ ๋ฐ ๋“œ๋ž˜๊ทธ: ์œ„์ ฏ ์ด๋™ ๋˜๋Š” ํฌ๊ธฐ ์กฐ์ •.\n์˜ค๋ฅธ์ชฝ ํด๋ฆญ: ์ปจํ…์ŠคํŠธ ๋ฉ”๋‰ด ์˜ต์…˜ ์—ด๊ธฐ.", + "edit-mode-description": "๋ฐ”ํƒ• ํ™”๋ฉด ์œ„์ ฏ์„ ์ด๋™ํ•˜๊ณ  ์žฌ๋ฐฐ์น˜ํ•˜๋ ค๋ฉด ํŽธ์ง‘ ๋ชจ๋“œ๋ฅผ ํ™œ์„ฑํ™”ํ•˜์„ธ์š”. ํ™œ์„ฑํ™”๋˜๋ฉด ์œ„์ ฏ์— ๋“œ๋ž˜๊ทธ ์œค๊ณฝ์„ ์ด ํ‘œ์‹œ๋˜๋ฉฐ ์žฌ๋ฐฐ์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "edit-mode-exit-button": "ํŽธ์ง‘ ๋ชจ๋“œ ์ข…๋ฃŒ", + "edit-mode-grid-snap-label": "๊ทธ๋ฆฌ๋“œ ์Šค๋ƒ…", + "edit-mode-grid-snap-scale-label": "๊ทธ๋ฆฌ๋“œ ์Šค๋ƒ… ๋ฐฐ์œจ", + "edit-mode-label": "ํŽธ์ง‘ ๋ชจ๋“œ", + "enabled-description": "๋ฐ”ํƒ• ํ™”๋ฉด ์œ„์ ฏ์„ ์™„์ „ํžˆ ํ™œ์„ฑํ™”ํ•˜๊ฑฐ๋‚˜ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "enabled-label": "๋ฐ”ํƒ• ํ™”๋ฉด ์œ„์ ฏ ํ™œ์„ฑํ™”", + "general-desc": "๋ฐ”ํƒ• ํ™”๋ฉด์— ๋‚˜ํƒ€๋‚  ์œ„์ ฏ์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "general-title": "๋ฐ”ํƒ• ํ™”๋ฉด ์œ„์ ฏ", + "media-player-enabled-description": "๋ฐ”ํƒ• ํ™”๋ฉด์— ๋ฏธ๋””์–ด ์žฌ์ƒ๊ธฐ ์œ„์ ฏ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "media-player-enabled-label": "๋ฏธ๋””์–ด ์žฌ์ƒ๊ธฐ ์œ„์ ฏ ํ™œ์„ฑํ™”", + "media-player-rounded-corners-description": "์œ„์ ฏ ๊ฐ€์žฅ์ž๋ฆฌ์— ๋‘ฅ๊ทผ ๋ชจ์„œ๋ฆฌ๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "media-player-show-album-art-description": "์•จ๋ฒ” ์•„ํŠธ์™€ ํŠธ๋ž™ ์ •๋ณด(์ œ๋ชฉ ๋ฐ ์•„ํ‹ฐ์ŠคํŠธ)๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "media-player-show-album-art-label": "์•จ๋ฒ” ์•„ํŠธ ๋ฐ ์ œ๋ชฉ ํ‘œ์‹œ", + "media-player-show-background-description": "๋ฐฐ๊ฒฝ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "media-player-show-buttons-description": "๋ฏธ๋””์–ด ์ œ์–ด ๋ฒ„ํŠผ(์žฌ์ƒ/์ผ์‹œ ์ •์ง€, ์ด์ „, ๋‹ค์Œ)์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "media-player-show-buttons-label": "๋ฒ„ํŠผ ํ‘œ์‹œ", + "media-player-show-visualizer-description": "์˜ค๋””์˜ค ๋น„์ฃผ์–ผ๋ผ์ด์ € ์˜ค๋ฒ„๋ ˆ์ด๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "media-player-visualizer-type-description": "๋น„์ฃผ์–ผ๋ผ์ด์ € ์œ ํ˜•์„ ์„ ํƒํ•˜์„ธ์š”.", + "overview-enabled-description": "์ปดํฌ์ง€ํ„ฐ ๊ฐœ์š”๊ฐ€ ์—ด๋ ค ์žˆ๋Š” ๋™์•ˆ ๋ฐ์Šคํฌํ†ฑ ์œ„์ ฏ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "overview-enabled-label": "๊ฐœ์š”์— ํ‘œ์‹œ", + "system-stat-layout-bottom": "์•„๋ž˜", + "system-stat-layout-description": "๋ฒ”๋ก€๊ฐ€ ๊ทธ๋ž˜ํ”„์— ์ƒ๋Œ€์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ํ‘œ์‹œ๋˜๋Š”์ง€ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.", + "system-stat-layout-label": "๋ ˆ์ด์•„์›ƒ", + "system-stat-layout-side": "์ธก๋ฉด", + "system-stat-rounded-corners-description": "์œ„์ ฏ ๋ฐฐ๊ฒฝ์— ๋‘ฅ๊ทผ ๋ชจ์„œ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "system-stat-rounded-corners-label": "๋‘ฅ๊ทผ ๋ชจ์„œ๋ฆฌ", + "system-stat-show-background-description": "์‹œ์Šคํ…œ ํ†ต๊ณ„ ์œ„์ ฏ์˜ ๋ฐฐ๊ฒฝ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "system-stat-show-background-label": "๋ฐฐ๊ฒฝ ํ‘œ์‹œ", + "system-stat-stat-type-description": "ํ‘œ์‹œํ•  ์‹œ์Šคํ…œ ํ†ต๊ณ„๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "system-stat-stat-type-label": "ํ†ต๊ณ„ ์œ ํ˜•", + "title": "๋ฐ”ํƒ• ํ™”๋ฉด ์œ„์ ฏ", + "weather-enabled-description": "๋ฐ”ํƒ• ํ™”๋ฉด์— ๋‚ ์”จ ์œ„์ ฏ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "weather-enabled-label": "๋‚ ์”จ ์œ„์ ฏ ํ™œ์„ฑํ™”", + "weather-show-background-description": "๋‚ ์”จ ์œ„์ ฏ์˜ ๋ฐฐ๊ฒฝ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค." + }, + "display": { + "monitors-backlight-device-auto-option": "๊ธฐ๋ณธ๊ฐ’", + "monitors-backlight-device-description": "์ด ์ถœ๋ ฅ์— ๋Œ€ํ•œ ๋ฐฑ๋ผ์ดํŠธ ์žฅ์น˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "monitors-backlight-device-label": "๋ฐฑ๋ผ์ดํŠธ ์žฅ์น˜", + "monitors-brightness-step-description": "๋ฐ๊ธฐ ๋ณ€๊ฒฝ(์Šคํฌ๋กค ํœ  ๋ฐ ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค)์˜ ๋‹จ๊ณ„ ํฌ๊ธฐ๋ฅผ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "monitors-brightness-step-label": "๋ฐ๊ธฐ ๋‹จ๊ณ„ ํฌ๊ธฐ", + "monitors-brightness-unavailable-ddc-disabled": "๋ฐ๊ธฐ ์ œ์–ด๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด ๋””์Šคํ”Œ๋ ˆ์ด์˜ ๋ฐ๊ธฐ๋ฅผ ์ œ์–ดํ•˜๋ ค๋ฉด \"์™ธ๋ถ€ ๋ฐ๊ธฐ ์ง€์›\"์„ ํ™œ์„ฑํ™”ํ•˜์„ธ์š”.", + "monitors-brightness-unavailable-generic": "์ด ๋””์Šคํ”Œ๋ ˆ์ด์—์„œ๋Š” ๋ฐ๊ธฐ ์ œ์–ด๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", + "monitors-desc": "๊ฐ ๋””์Šคํ”Œ๋ ˆ์ด์˜ ๋ฐ๊ธฐ๋ฅผ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "monitors-enforce-minimum-description": "์ผ๋ถ€ ๋””์Šคํ”Œ๋ ˆ์ด์—์„œ ๋ฐ๊ธฐ 0% ์‹œ ๋ฐฑ๋ผ์ดํŠธ๊ฐ€ ์™„์ „ํžˆ ๊บผ์ง€๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.", + "monitors-enforce-minimum-label": "์ตœ์†Œ ๋ฐ๊ธฐ ๊ฐ•์ œ (1%)", + "monitors-external-brightness-description": "DDC/CI ํ”„๋กœํ† ์ฝœ์„ ํ†ตํ•ด ์™ธ๋ถ€ ๋””์Šคํ”Œ๋ ˆ์ด์˜ ๋ฐ๊ธฐ๋ฅผ ์ œ์–ดํ•˜๊ธฐ ์œ„ํ•œ DDCUtil ์ง€์›์„ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "monitors-external-brightness-label": "์™ธ๋ถ€ ๋ฐ๊ธฐ ์ง€์›", + "monitors-global-brightness-description": "๋ชจ๋“  ๋ชจ๋‹ˆํ„ฐ์˜ ๋ฐ๊ธฐ๋ฅผ ํ•œ ๋ฒˆ์— ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "monitors-global-brightness-label": "๋ชจ๋“  ๋ชจ๋‹ˆํ„ฐ", + "monitors-title": "๋ชจ๋‹ˆํ„ฐ๋ณ„ ์„ค์ •", + "night-light-auto-schedule-description": "{location}์˜ ์ผ๋ชฐ ๋ฐ ์ผ์ถœ ์‹œ๊ฐ„์„ ๊ธฐ์ค€์œผ๋กœ ํ•จ โ€” ๊ถŒ์žฅ๋จ.", + "night-light-auto-schedule-label": "์ž๋™ ์ผ์ •", + "night-light-desc": "์ฒญ์ƒ‰๊ด‘(๋ธ”๋ฃจ ๋ผ์ดํŠธ) ๋ฐฉ์ถœ์„ ์ค„์—ฌ ์ˆ˜๋ฉด์„ ๋•๊ณ  ๋ˆˆ์˜ ํ”ผ๋กœ๋ฅผ ์ค„์ž…๋‹ˆ๋‹ค.", + "night-light-enable-description": "์ฒญ์ƒ‰๊ด‘(๋ธ”๋ฃจ ๋ผ์ดํŠธ) ๋ฐฉ์ถœ์„ ์ค„์ด๊ธฐ ์œ„ํ•ด ๋”ฐ๋œปํ•œ ์ƒ‰์ƒ ํ•„ํ„ฐ๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "night-light-enable-label": "์•ผ๊ฐ„ ์กฐ๋ช… ํ™œ์„ฑํ™”", + "night-light-force-activation-description": "์ผ์ •์„ ๋ฌด์‹œํ•˜๊ณ  ์•ผ๊ฐ„ ํ•„ํ„ฐ๋ฅผ ์ฆ‰์‹œ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "night-light-force-activation-label": "๊ฐ•์ œ ํ™œ์„ฑํ™”", + "night-light-manual-schedule-description": "์ผ์ถœ ๋ฐ ์ผ๋ชฐ ์‹œ๊ฐ„์„ ์‚ฌ์šฉ์ž ์ง€์ •์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "night-light-manual-schedule-label": "์ˆ˜๋™ ์ผ์ •", + "night-light-manual-schedule-select-start": "์‹œ์ž‘ ์‹œ๊ฐ„ ์„ ํƒ", + "night-light-manual-schedule-select-stop": "์ข…๋ฃŒ ์‹œ๊ฐ„ ์„ ํƒ", + "night-light-manual-schedule-sunrise": "์ผ์ถœ ์‹œ๊ฐ„", + "night-light-manual-schedule-sunset": "์ผ๋ชฐ ์‹œ๊ฐ„", + "night-light-temperature-day": "์ฃผ๊ฐ„", + "night-light-temperature-day-description": "์ฃผ๊ฐ„์˜ ์ƒ‰์˜จ๋„๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.", + "night-light-temperature-description": "์•ผ๊ฐ„ ๋ฐ ์ฃผ๊ฐ„์˜ ์ƒ‰์˜จ๋„(๋”ฐ๋œปํ•จ)๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "night-light-temperature-label": "์ƒ‰์˜จ๋„", + "night-light-temperature-night": "์•ผ๊ฐ„", + "night-light-temperature-night-description": "์•ผ๊ฐ„์˜ ์ƒ‰์˜จ๋„๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.", + "title": "๋””์Šคํ”Œ๋ ˆ์ด" + }, + "dock": { + "appearance-background-opacity-description": "๋…์˜ ๋ฐฐ๊ฒฝ ๋ถˆํˆฌ๋ช…๋„๋ฅผ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-border-radius-description": "๋…์˜ ํ…Œ๋‘๋ฆฌ ๋ฐ˜๊ฒฝ์„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-border-radius-label": "ํ…Œ๋‘๋ฆฌ ๋ฐ˜๊ฒฝ", + "appearance-colorize-icons-description": "๋… ์•ฑ ์•„์ด์ฝ˜์— ํ…Œ๋งˆ ์ƒ‰์ƒ์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค (ํฌ์ปค์Šค๋˜์ง€ ์•Š์€ ์•ฑ๋งŒ).", + "appearance-colorize-icons-label": "์•„์ด์ฝ˜ ์ƒ‰์ƒํ™”", + "appearance-dead-opacity-description": "์‹คํ–‰ ์ค‘์ด์ง€ ์•Š์€ ์•ฑ ์•„์ด์ฝ˜์˜ ๋ถˆํˆฌ๋ช…๋„๋ฅผ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-dead-opacity-label": "๋น„ํ™œ์„ฑ ๋ถˆํˆฌ๋ช…๋„", + "appearance-desc": "๋…์˜ ๋™์ž‘๊ณผ ๋ชจ์–‘์„ ์‚ฌ์šฉ์ž ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-display-auto-hide": "์ž๋™ ์ˆจ๊ธฐ๊ธฐ", + "appearance-display-description": "๋…์˜ ๋™์ž‘ ๋ฐฉ์‹์„ ์„ ํƒํ•˜์„ธ์š”.", + "appearance-display-exclusive": "๋…์ ", + "appearance-dock-indicator-description": "๋…์ด ์ˆจ๊ฒจ์ ธ ์žˆ์„ ๋•Œ ์ž‘์€ ํ‘œ์‹œ๊ธฐ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "appearance-dock-indicator-label": "๋… ํ‘œ์‹œ๊ธฐ", + "appearance-floating-distance-description": "๋…๊ณผ ํ™”๋ฉด ๊ฐ€์žฅ์ž๋ฆฌ ์‚ฌ์ด์˜ ๊ฑฐ๋ฆฌ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-floating-distance-label": "๋… ํ”Œ๋กœํŒ… ๊ฑฐ๋ฆฌ", + "appearance-group-apps-description": "๋™์ผํ•œ ์•ฑ์˜ ์—ฌ๋Ÿฌ ์ฐฝ์„ ํ•˜๋‚˜์˜ Dock ํ•ญ๋ชฉ์œผ๋กœ ๊ทธ๋ฃนํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-group-apps-label": "๋™์ผํ•œ ์•ฑ ๊ทธ๋ฃนํ™”", + "appearance-group-click-action-cycle": "์ฐฝ ์ „ํ™˜", + "appearance-group-click-action-description": "๊ทธ๋ฃนํ™”๋œ ์•ฑ์— ๋Œ€ํ•ด ์™ผ์ชฝ ํด๋ฆญ ์‹œ ์–ด๋–ค ๋™์ž‘์„ ํ• ์ง€ ์„ ํƒํ•˜์„ธ์š”.", + "appearance-group-click-action-label": "๊ทธ๋ฃนํ™”๋œ ์•ฑ ํด๋ฆญ ๋™์ž‘", + "appearance-group-click-action-list": "์ฐฝ ๋ชฉ๋ก ์—ด๊ธฐ", + "appearance-group-context-menu-mode-description": "๊ทธ๋ฃนํ™”๋œ ์•ฑ์˜ ์ƒํ™ฉ์— ๋งž๋Š” ๋ฉ”๋‰ด๊ฐ€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ• ์ง€ ์„ ํƒํ•˜์„ธ์š”.", + "appearance-group-context-menu-mode-extended": "ํ™•์žฅ๋จ", + "appearance-group-context-menu-mode-label": "๊ทธ๋ฃนํ™”๋œ ์•ฑ ๋ฉ”๋‰ด ๋ชจ๋“œ", + "appearance-group-context-menu-mode-list": "์ฐฝ ๋ชฉ๋ก", + "appearance-group-indicator-style-description": "๊ทธ๋ฃนํ™”๋œ ์‹คํ–‰ ์ค‘์ธ ํ‘œ์‹œ๊ธฐ๊ฐ€ ํฌ์ปค์Šค๋œ ์ฐฝ ์ƒํƒœ๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๋ฐฉ์‹์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.", + "appearance-group-indicator-style-dots": "์ ", + "appearance-group-indicator-style-label": "๊ทธ๋ฃนํ™”๋œ ํ‘œ์‹œ๊ธฐ ์Šคํƒ€์ผ", + "appearance-group-indicator-style-number": "์ˆซ์ž", + "appearance-hide-show-speed-description": "๋… ์ˆจ๊ธฐ๊ธฐ/๋ณด์ด๊ธฐ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์†๋„๋ฅผ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-hide-show-speed-label": "์ˆจ๊ธฐ๊ธฐ/๋ณด์ด๊ธฐ ์†๋„", + "appearance-icon-size-description": "๋…์˜ ์ „์ฒด ํฌ๊ธฐ๋ฅผ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-icon-size-label": "๋… ํฌ๊ธฐ", + "appearance-inactive-indicators-description": "ํ˜„์žฌ ํ™œ์„ฑ ์•ฑ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋ชจ๋“  ์•ฑ์— ๋Œ€ํ•ด ํ‘œ์‹œ๊ธฐ ์•Œ์•ฝ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "appearance-inactive-indicators-label": "์‹คํ–‰ ์ค‘ ํ‘œ์‹œ๊ธฐ", + "appearance-indicator-color-description": "์ˆจ๊ฒจ์ง„ ๋… ์ธ๋””์ผ€์ดํ„ฐ์˜ ์ƒ‰์ƒ์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.", + "appearance-indicator-color-label": "์ธ๋””์ผ€์ดํ„ฐ ์ƒ‰์ƒ", + "appearance-indicator-opacity-description": "์ˆจ๊ฒจ์ง„ ๋… ์ธ๋””์ผ€์ดํ„ฐ์˜ ๋ถˆํˆฌ๋ช…๋„๋ฅผ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-indicator-opacity-label": "์ธ๋””์ผ€์ดํ„ฐ ๋ถˆํˆฌ๋ช…๋„", + "appearance-indicator-thickness-description": "์ˆจ๊ฒจ์ง„ ๋… ์ธ๋””์ผ€์ดํ„ฐ๋ฅผ ๋” ๋‘๊ป๊ฒŒ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค (3px ๋Œ€์‹  6px).", + "appearance-indicator-thickness-label": "๋” ๋‘๊บผ์šด ์ธ๋””์ผ€์ดํ„ฐ", + "appearance-launcher-icon-description": "Dock Launcher์— ์‚ฌ์šฉ๋  ์•„์ด์ฝ˜์„ ์„ ํƒํ•˜์„ธ์š”.", + "appearance-launcher-icon-label": "๋Ÿฐ์ฒ˜ ์•„์ด์ฝ˜", + "appearance-launcher-position-description": "๋…์—์„œ ๋Ÿฐ์ฒ˜ ์•„์ด์ฝ˜์ด ๋‚˜ํƒ€๋‚  ์œ„์น˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "appearance-launcher-position-end": "๋", + "appearance-launcher-position-label": "๋Ÿฐ์ฒ˜ ์œ„์น˜", + "appearance-launcher-position-start": "์‹œ์ž‘", + "appearance-launcher-use-distro-logo-description": "๋ฐฐํฌํŒ ๋กœ๊ณ ๋ฅผ Dock Launcher ์•„์ด์ฝ˜์œผ๋กœ ์‚ฌ์šฉํ•˜์„ธ์š”.", + "appearance-launcher-use-distro-logo-label": "์•„์ด์ฝ˜ ๋Œ€์‹  ๋ฐฐํฌํŒ ๋กœ๊ณ  ์‚ฌ์šฉ", + "appearance-pinned-static-description": "๊ณ ์ •๋œ ์•ฑ ์•„์ด์ฝ˜์„ ํ•ญ์ƒ ์™ผ์ชฝ์— ์ •์ ์ธ ์ˆœ์„œ๋กœ ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-pinned-static-label": "์ •์  ๊ณ ์ • ์•ฑ", + "appearance-position-description": "ํ™”๋ฉด์—์„œ ๋…์ด ๋‚˜ํƒ€๋‚  ์œ„์น˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "appearance-position-label": "์œ„์น˜", + "appearance-show-launcher-icon-description": "๋…์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋Ÿฐ์ฒ˜ ์•„์ด์ฝ˜์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "appearance-show-launcher-icon-label": "์•ฑ ๋Ÿฐ์ฒ˜ ํ‘œ์‹œ", + "appearance-sit-on-frame-description": "๋…์„ ํ”„๋ ˆ์ž„ ํ…Œ๋‘๋ฆฌ ๋‚ด๋ถ€์— ์ •๋ ฌํ•˜๊ณ , ์œ„์— ๋†“์ด์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.", + "appearance-sit-on-frame-label": "๋…์€ ํ”„๋ ˆ์ž„์— ์œ„์น˜", + "appearance-type-attached": "๊ณ ์ •๋จ", + "appearance-type-description": "ํ”Œ๋กœํŒ… ํ•„ ํ˜•ํƒœ ๋˜๋Š” ๊ฐ€์žฅ์ž๋ฆฌ์— ๊ณ ์ •๋œ ๊ณ ์ • ๋ฐ” ์ค‘์—์„œ ์„ ํƒํ•˜์„ธ์š”.", + "appearance-type-floating": "ํ”Œ๋กœํŒ…", + "appearance-type-label": "๋… ์Šคํƒ€์ผ", + "enabled-description": "๋…์„ ์™„์ „ํžˆ ํ‘œ์‹œํ•˜๊ฑฐ๋‚˜ ์ˆจ๊น๋‹ˆ๋‹ค.", + "enabled-label": "๋… ํ™œ์„ฑํ™”", + "monitors-desc": "ํŠน์ • ๋ชจ๋‹ˆํ„ฐ์— ๋…์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ์„ ํƒํ•˜์ง€ ์•Š์œผ๋ฉด ๋ชจ๋“  ๋ชจ๋‹ˆํ„ฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.", + "monitors-only-same-monitor-description": "๋…์ด ์œ„์น˜ํ•œ ๋ชจ๋‹ˆํ„ฐ์˜ ์•ฑ๋งŒ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "monitors-only-same-monitor-label": "๋™์ผ ๋ชจ๋‹ˆํ„ฐ์˜ ์•ฑ๋งŒ", + "monitors-title": "๋ชจ๋‹ˆํ„ฐ ํ‘œ์‹œ", + "title": "๋…" + }, + "general": { + "copy-settings": "์„ค์ • ๋ณต์‚ฌ", + "fonts-default-description": "์ธํ„ฐํŽ˜์ด์Šค ์ „๋ฐ˜์— ์‚ฌ์šฉ๋˜๋Š” ๊ธฐ๋ณธ ๊ธ€๊ผด์ž…๋‹ˆ๋‹ค.", + "fonts-default-label": "๊ธฐ๋ณธ ๊ธ€๊ผด", + "fonts-default-placeholder": "๊ธฐ๋ณธ ๊ธ€๊ผด ์„ ํƒ...", + "fonts-default-scale-description": "ํ‘œ์ค€ ํ…์ŠคํŠธ์˜ ํฌ๊ธฐ๋ฅผ ๋Š˜๋ฆฌ๊ฑฐ๋‚˜ ์ค„์ž…๋‹ˆ๋‹ค.", + "fonts-default-scale-label": "๊ธฐ๋ณธ ๊ธ€๊ผด ํฌ๊ธฐ", + "fonts-default-search-placeholder": "๊ธ€๊ผด ๊ฒ€์ƒ‰...", + "fonts-desc": "์ธํ„ฐํŽ˜์ด์Šค ์ „๋ฐ˜์— ์‚ฌ์šฉ๋  ๊ธ€๊ผด์„ ์„ ํƒํ•˜์„ธ์š”.", + "fonts-monospace-description": "์ˆซ์ž ๋ฐ ํ†ต๊ณ„ ํ‘œ์‹œ์— ์‚ฌ์šฉ๋˜๋Š” ๊ณ ์ •ํญ ๊ธ€๊ผด์ž…๋‹ˆ๋‹ค.", + "fonts-monospace-label": "๊ณ ์ •ํญ ๊ธ€๊ผด", + "fonts-monospace-placeholder": "๊ณ ์ •ํญ ๊ธ€๊ผด ์„ ํƒ...", + "fonts-monospace-scale-description": "๊ณ ์ •ํญ ํ…์ŠคํŠธ์˜ ํฌ๊ธฐ๋ฅผ ๋Š˜๋ฆฌ๊ฑฐ๋‚˜ ์ค„์ž…๋‹ˆ๋‹ค.", + "fonts-monospace-scale-label": "๊ณ ์ •ํญ ๊ธ€๊ผด ํฌ๊ธฐ", + "fonts-monospace-search-placeholder": "๊ณ ์ •ํญ ๊ธ€๊ผด ๊ฒ€์ƒ‰...", + "fonts-title": "๊ธ€๊ผด", + "keybinds-conflict-description": "์ด ํ‚ค ์กฐํ•ฉ์€ ์ด๋ฏธ {action}์— ํ• ๋‹น๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.", + "keybinds-conflict-title": "ํ‚ค ๋ฐ”์ธ๋”ฉ ์ถฉ๋Œ", + "keybinds-description": "ํŒจ๋„ ๋ฐ ๋Ÿฐ์ฒ˜์— ๋Œ€ํ•œ ์ „์—ญ ํƒ์ƒ‰ ํ‚ค๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "keybinds-down": "์•„๋ž˜๋กœ ์ด๋™", + "keybinds-enter": "ํ™•์ธ / ๋™์ž‘", + "keybinds-escape": "๋‹ซ๊ธฐ / ๋’ค๋กœ", + "keybinds-left": "์™ผ์ชฝ์œผ๋กœ ์ด๋™", + "keybinds-modifier-description": "๋‹จ์ถ•ํ‚ค๋Š” ๋ณด์กฐํ‚ค(Ctrl ๋˜๋Š” Alt)๋ฅผ ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "keybinds-modifier-title": "์ˆ˜์ •์ž ํ‚ค ํ•„์š”", + "keybinds-remove": "์‚ญ์ œ", + "keybinds-right": "์˜ค๋ฅธ์ชฝ์œผ๋กœ ์ด๋™", + "keybinds-title": "ํƒ์ƒ‰ ํ‚ค ๋ฐ”์ธ๋”ฉ", + "keybinds-up": "์œ„๋กœ ์ด๋™", + "language-desc": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„ ํ˜ธ ์–ธ์–ด๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "language-select-auto-detect": "์ž๋™", + "language-select-description": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธํ„ฐํŽ˜์ด์Šค์— ์‚ฌ์šฉ๋  ์–ธ์–ด๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.", + "language-select-label": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์–ธ์–ด", + "launch-setup-wizard": "์„ค์ • ๋งˆ๋ฒ•์‚ฌ", + "profile-desc": "์‚ฌ์šฉ์ž ์„ธ๋ถ€ ์ •๋ณด์™€ ์•„๋ฐ”ํƒ€๋ฅผ ํŽธ์ง‘ํ•ฉ๋‹ˆ๋‹ค.", + "profile-picture-description": "์ธํ„ฐํŽ˜์ด์Šค ์ „๋ฐ˜์— ๋‚˜ํƒ€๋‚˜๋Š” ํ”„๋กœํ•„ ์‚ฌ์ง„์ž…๋‹ˆ๋‹ค.", + "profile-picture-label": "ํ”„๋กœํ•„ ์‚ฌ์ง„", + "profile-select-avatar": "์•„๋ฐ”ํƒ€ ์ด๋ฏธ์ง€ ์„ ํƒ", + "profile-title": "ํ”„๋กœํ•„", + "profile-tooltip": "ํ”„๋กœํ•„ ์‚ฌ์ง„", + "reverse-scrolling-description": "ํ•ด์„๋œ ์Šคํฌ๋กค ๋ฐฉํ–ฅ ๋ฐ˜์ „.", + "reverse-scrolling-label": "์Šคํฌ๋กค ๋ฐ˜์ „", + "screen-corners-desc": "ํ™”๋ฉด ๋ชจ์„œ๋ฆฌ ๋ผ์šด๋”ฉ ๋ฐ ์‹œ๊ฐ ํšจ๊ณผ๋ฅผ ์‚ฌ์šฉ์ž ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.", + "screen-corners-radius-description": "ํ™”๋ฉด์˜ ๋‘ฅ๊ทผ ๋ชจ์„œ๋ฆฌ๋ฅผ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "screen-corners-radius-label": "ํ™”๋ฉด ๋ชจ์„œ๋ฆฌ ๋ฐ˜๊ฒฝ", + "screen-corners-radius-reset": "ํ™”๋ฉด ๋ชจ์„œ๋ฆฌ ๋ฐ˜๊ฒฝ ์ดˆ๊ธฐํ™”", + "screen-corners-show-corners-description": "ํ™”๋ฉด ๊ฐ€์žฅ์ž๋ฆฌ์— ๋‘ฅ๊ทผ ๋ชจ์„œ๋ฆฌ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "screen-corners-show-corners-label": "ํ™”๋ฉด ๋ชจ์„œ๋ฆฌ ํ‘œ์‹œ", + "screen-corners-solid-black-description": "๋ฐ” ๋ฐฐ๊ฒฝ์ƒ‰ ๋Œ€์‹  ์™„์ „ํ•œ ๊ฒ€์€์ƒ‰์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "screen-corners-solid-black-label": "๊ฒ€์€์ƒ‰ ๋ชจ์„œ๋ฆฌ", + "screen-corners-title": "ํ™”๋ฉด ๋ชจ์„œ๋ฆฌ", + "settings-copied": "์„ค์ •์ด ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌ๋จ", + "smooth-scrolling-description": "๋ชฉ๋ก ์Šคํฌ๋กค ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ ์šฉํ•˜์—ฌ ๋” ๋ถ€๋“œ๋Ÿฌ์šด ํœ  ๊ฒฝํ—˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.", + "smooth-scrolling-label": "๋ถ€๋“œ๋Ÿฌ์šด ์Šคํฌ๋กค", + "tab-basics": "๊ธฐ๋ณธ", + "tab-keybinds": "ํ‚ค ๋ฐ”์ธ๋”ฉ" + }, + "hooks": { + "color-generation-description": "์ƒ‰์ƒ ์ƒ์„ฑ์ด ์™„๋ฃŒ๋  ๋•Œ ์‹คํ–‰๋  ๋ช…๋ น (์˜ˆ: ๋ฐฐ๊ฒฝํ™”๋ฉด ๊ธฐ๋ฐ˜ ํ…Œ๋งˆ ์ ์šฉ ํ›„).", + "color-generation-label": "์ƒ‰์ƒ ์ƒ์„ฑ๋จ", + "color-generation-placeholder": "์˜ˆ: notify-send \"์ƒ‰์ƒ\" \"์ƒ์„ฑ๋จ\"", + "info-command-info-description": "โ€ข ๋ช…๋ น์€ ์…ธ(sh -lc)์„ ํ†ตํ•ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
โ€ข ๋ช…๋ น์€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ(detached)์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
โ€ข ํ…Œ์ŠคํŠธ ๋ฒ„ํŠผ์€ ํ˜„์žฌ ๊ฐ’์œผ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.", + "info-parameters-description": "โ€ข Wallpaper hook: $1 = ๋ฐฐ๊ฒฝํ™”๋ฉด ๊ฒฝ๋กœ, $2 = ํ™”๋ฉด ์ด๋ฆ„, $3 = ํ…Œ๋งˆ (์–ด๋‘ก๊ฒŒ/๋ฐ๊ฒŒ)
โ€ข Theme toggle hook: $1 = true/false (๋‹คํฌ ๋ชจ๋“œ ์ƒํƒœ)
โ€ข Screen lock/unlock hooks: $1 = lock/unlock (ํ™”๋ฉด ์ž ๊ธˆ ์ƒํƒœ)
โ€ข Performance mode hooks: ๋งค๊ฐœ๋ณ€์ˆ˜ ์—†์Œ
โ€ข Session hook: $1 = action (shutdown/reboot)", + "info-parameters-label": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜", + "noctalia-started-description": "Noctalia ๋กœ๋”ฉ์ด ์™„๋ฃŒ๋˜์—ˆ์„ ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค.", + "noctalia-started-label": "Noctalia ์‹œ์ž‘๋จ", + "noctalia-started-placeholder": "์˜ˆ: notify-send 'Noctalia ๋กœ๋“œ๋จ'", + "performance-mode-disabled-description": "Noctalia ์„ฑ๋Šฅ ๋ชจ๋“œ๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋  ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค.", + "performance-mode-disabled-label": "์„ฑ๋Šฅ ๋ชจ๋“œ ๋น„ํ™œ์„ฑํ™”๋จ", + "performance-mode-disabled-placeholder": "์˜ˆ: notify-send \"์„ฑ๋Šฅ\" \"๋ชจ๋“œ ๋น„ํ™œ์„ฑํ™”๋จ\"", + "performance-mode-enabled-description": "Noctalia ์„ฑ๋Šฅ ๋ชจ๋“œ๊ฐ€ ํ™œ์„ฑํ™”๋  ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค.", + "performance-mode-enabled-label": "์„ฑ๋Šฅ ๋ชจ๋“œ ํ™œ์„ฑํ™”๋จ", + "performance-mode-enabled-placeholder": "์˜ˆ: notify-send \"์„ฑ๋Šฅ\" \"๋ชจ๋“œ ํ™œ์„ฑํ™”๋จ\"", + "screen-lock-description": "ํ™”๋ฉด์ด ์ž ๊ธธ ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค.", + "screen-lock-label": "ํ™”๋ฉด ์ž ๊น€", + "screen-lock-placeholder": "์˜ˆ: notify-send \"ํ™”๋ฉด\" \"์ž ๊น€\"", + "screen-unlock-description": "์ž ๊ธˆ ํ™”๋ฉด์ด ํ•ด์ œ๋  ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค.", + "screen-unlock-label": "ํ™”๋ฉด ์ž ๊ธˆ ํ•ด์ œ๋จ", + "screen-unlock-placeholder": "์˜ˆ: notify-send \"ํ™”๋ฉด\" \"์ž ๊ธˆ ํ•ด์ œ๋จ\"", + "session-description": "์‹œ์Šคํ…œ ์ข…๋ฃŒ ๋˜๋Š” ์žฌ๋ถ€ํŒ… ์ „์— ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค. ์ž‘์—… ์œ ํ˜•์„ $1๋กœ ๋ฐ›์Šต๋‹ˆ๋‹ค (shutdown/reboot).", + "session-label": "์„ธ์…˜ ์ข…๋ฃŒ", + "session-placeholder": "์˜ˆ: notify-send \"์„ธ์…˜\" \"$1\"", + "system-hooks-desc": "์‹œ์Šคํ…œ ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์‹œ ์‹คํ–‰ํ•  ๋ช…๋ น์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "system-hooks-enable-description": "๋ชจ๋“  ํ›… ๋ช…๋ น์„ ํ™œ์„ฑํ™”ํ•˜๊ฑฐ๋‚˜ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "system-hooks-enable-label": "ํ›… ํ™œ์„ฑํ™”", + "system-hooks-title": "์‹œ์Šคํ…œ ํ›…", + "theme-changed-description": "ํ…Œ๋งˆ๊ฐ€ ๋‹คํฌ ๋ชจ๋“œ์™€ ๋ผ์ดํŠธ ๋ชจ๋“œ ๊ฐ„์— ์ „ํ™˜๋  ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค.", + "theme-changed-label": "ํ…Œ๋งˆ ๋ณ€๊ฒฝ๋จ", + "theme-changed-placeholder": "์˜ˆ: notify-send \"ํ…Œ๋งˆ\" \"์ „ํ™˜๋จ\"", + "title": "ํ›…", + "wallpaper-changed-description": "๋ฐฐ๊ฒฝํ™”๋ฉด์ด ๋ณ€๊ฒฝ๋  ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค.", + "wallpaper-changed-label": "๋ฐฐ๊ฒฝํ™”๋ฉด ๋ณ€๊ฒฝ๋จ", + "wallpaper-changed-placeholder": "์˜ˆ: notify-send \"๋ฐฐ๊ฒฝํ™”๋ฉด\" \"๋ณ€๊ฒฝ๋จ\"" + }, + "idle": { + "custom-add": "๋ช…๋ น ์ถ”๊ฐ€", + "custom-description": "์ผ์ • ์‹œ๊ฐ„ ๋น„ํ™œ์„ฑ ์ƒํƒœ ํ›„ ์…ธ ๋ช…๋ น์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.", + "custom-entry-command": "๋ช…๋ น", + "custom-entry-delete": "์‚ญ์ œ", + "custom-entry-edit": "์‚ฌ์šฉ์ž ์ง€์ • ๋ช…๋ น ํŽธ์ง‘", + "custom-entry-name": "์ด๋ฆ„", + "custom-entry-name-placeholder": "์˜ˆ: ์กฐ๋ช… ๋„๊ธฐ", + "custom-entry-new": "์ƒˆ ์‚ฌ์šฉ์ž ์ง€์ • ๋ช…๋ น", + "custom-entry-timeout": "๋น„ํ™œ์„ฑ ์‹œ๊ฐ„", + "custom-entry-timeout-format": "{count} ์ดˆ", + "custom-entry-timeout-format-plural": "{count} ์ดˆ", + "custom-entry-unnamed": "์ด๋ฆ„ ์—†๋Š” ๋ช…๋ น์–ด", + "custom-label": "์‚ฌ์šฉ์ž ์ง€์ • ์œ ํœด ๋ช…๋ น", + "enable-description": "์ผ์ • ์‹œ๊ฐ„ ๋น„ํ™œ์„ฑ ์ƒํƒœ ํ›„ ์ž๋™์œผ๋กœ ํ™”๋ฉด์„ ๋„๊ฑฐ๋‚˜, ์ž ๊ทธ๊ฑฐ๋‚˜, ์ ˆ์ „ ๋ชจ๋“œ๋กœ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค.", + "enable-label": "์œ ํœด ๊ด€๋ฆฌ ํ™œ์„ฑํ™”", + "fade-duration-description": "๊ฐ ๋™์ž‘์ด ์‹คํ–‰๋˜๊ธฐ ์ „ ํ™”๋ฉด์ด ๊ฒ€๊ฒŒ ํŽ˜์ด๋“œ๋˜๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜ ์‹œ๊ฐ„(์ดˆ). ๋งˆ์šฐ์Šค ์›€์ง์ž„์ด ์žˆ์œผ๋ฉด ํŽ˜์ด๋“œ๊ฐ€ ์ทจ์†Œ๋ฉ๋‹ˆ๋‹ค.", + "fade-duration-label": "ํŽ˜์ด๋“œ ์ง€์† ์‹œ๊ฐ„", + "lock-description": "์ž ๊ธˆ ํ™”๋ฉด์ด ํ™œ์„ฑํ™”๋˜๊ธฐ ์ „ ๋น„ํ™œ์„ฑ ์‹œ๊ฐ„(์ดˆ).", + "lock-label": "ํ™”๋ฉด ์ž ๊ธˆ", + "resume-command-label": "๋ช…๋ น ์žฌ๊ฐœ", + "screen-off-description": "๋ชจ๋‹ˆํ„ฐ๊ฐ€ ๊บผ์ง€๊ธฐ ์ „ ๋น„ํ™œ์„ฑ ์‹œ๊ฐ„(์ดˆ).", + "screen-off-label": "ํ™”๋ฉด ๋„๊ธฐ", + "status-description": "์ปดํฌ์ง€ํ„ฐ๊ฐ€ ๋ณด๊ณ ํ•œ ์œ ํœด ์‹œ๊ฐ„.", + "status-label": "์œ ํœด ์‹œ๊ฐ„", + "suspend-description": "์‹œ์Šคํ…œ์ด ์ผ์‹œ ์ค‘๋‹จ๋˜๊ธฐ ์ „ ๋น„ํ™œ์„ฑ ์‹œ๊ฐ„(์ดˆ).", + "tab-behavior": "๋™์ž‘", + "tab-custom": "์‚ฌ์šฉ์ž ์ง€์ •", + "timeouts-description": "๋‹จ๊ณ„๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด 0์œผ๋กœ ์„ค์ •ํ•˜์„ธ์š”. ๊นจ์–ด ์žˆ๊ธฐ ๊ฐ€ ํ™œ์„ฑํ™”๋œ ๋™์•ˆ ์‹œ๊ฐ„ ์ดˆ๊ณผ๋Š” ์ผ์‹œ ์ค‘์ง€๋ฉ๋‹ˆ๋‹ค.", + "timeouts-label": "์‹œ๊ฐ„ ์ดˆ๊ณผ", + "title": "์œ ํœด", + "unavailable": "์ด Compositor์—์„œ๋Š” ๋„ค์ดํ‹ฐ๋ธŒ ์œ ํœด ๋ชจ๋‹ˆํ„ฐ๋ง์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." + }, + "indicator": { + "default-value": "๊ธฐ๋ณธ๊ฐ’: {value}", + "system-default": "์‹œ์Šคํ…œ ๊ธฐ๋ณธ๊ฐ’" + }, + "launcher": { + "clipboard-desc": "๋Ÿฐ์ฒ˜์—์„œ ํด๋ฆฝ๋ณด๋“œ ๊ธฐ๋ก์— ์•ก์„ธ์Šคํ•˜๊ณ  ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.", + "execute-desc": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ๋ฐฉ์‹์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "execute-title": "์‹คํ–‰", + "settings-annotation-tool-description": "ํด๋ฆฝ๋ณด๋“œ ๊ธฐ๋ก์—์„œ ์ฃผ์„ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•  ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€๊ฐ€ ์ด ๋ช…๋ น์œผ๋กœ ํŒŒ์ดํ”„๋ฉ๋‹ˆ๋‹ค.", + "settings-annotation-tool-label": "์ฃผ์„ ๋„๊ตฌ", + "settings-annotation-tool-placeholder": "์˜ˆ: 'gradia', 'satty -f -'", + "settings-auto-paste-description": "์„ ํƒํ•œ ํด๋ฆฝ๋ณด๋“œ ํ•ญ๋ชฉ์„ ์ž๋™์œผ๋กœ ๋ถ™์—ฌ๋„ฃ์Šต๋‹ˆ๋‹ค. wtype์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.", + "settings-auto-paste-label": "์ž๋™ ๋ถ™์—ฌ๋„ฃ๊ธฐ", + "settings-clip-chips-description": "ํƒญ ๋ฐ”๋ฅผ ํ‘œ์‹œํ•˜์—ฌ ํด๋ฆฝ๋ณด๋“œ ๊ธฐ๋ก์„ ์œ ํ˜•๋ณ„(์ด๋ฏธ์ง€, ๋งํฌ, ํŒŒ์ผ, ์ฝ”๋“œ ๋“ฑ)๋กœ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค.", + "settings-clip-chips-label": "์นดํ…Œ๊ณ ๋ฆฌ ์นฉ ํ™œ์„ฑํ™”", + "settings-clip-preview-description": ">clip ๋ช…๋ น ์‚ฌ์šฉ ์‹œ ํด๋ฆฝ๋ณด๋“œ ๋‚ด์šฉ์˜ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "settings-clip-preview-label": "ํด๋ฆฝ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ™œ์„ฑํ™”", + "settings-clip-smart-icons-description": "์ผ๋ฐ˜์ ์ธ ํด๋ฆฝ๋ณด๋“œ ์•„์ด์ฝ˜ ๋Œ€์‹  ๋งํฌ, ํŒŒ์ผ, ์ƒ‰์ƒ, ์ฝ”๋“œ์— ๋Œ€ํ•œ ํŠน์ • ์•„์ด์ฝ˜์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "settings-clip-smart-icons-label": "์Šค๋งˆํŠธ ์•„์ด์ฝ˜ ํ™œ์„ฑํ™”", + "settings-clip-wrap-text-description": "ํด๋ฆฝ๋ณด๋“œ ๋ชฉ๋ก์—์„œ ํ…์ŠคํŠธ๋ฅผ ์ž๋ฅด์ง€ ์•Š๊ณ  ์ค„๋ฐ”๊ฟˆํ•ฉ๋‹ˆ๋‹ค.", + "settings-clip-wrap-text-label": "ํด๋ฆฝ๋ณด๋“œ ํ…์ŠคํŠธ ์ค„๋ฐ”๊ฟˆ", + "settings-clipboard-history-description": "์ด์ „์— ๋ณต์‚ฌํ•œ ํ•ญ๋ชฉ์— ๋Ÿฐ์ฒ˜์—์„œ ์•ก์„ธ์Šคํ•ฉ๋‹ˆ๋‹ค.", + "settings-clipboard-history-label": "ํด๋ฆฝ๋ณด๋“œ ๊ธฐ๋ก ํ™œ์„ฑํ™”", + "settings-clipboard-watch-image-description": "์ด๋ฏธ์ง€ ๋ณ€๊ฒฝ์„ ์œ„ํ•ด wl-paste์— ์ „๋‹ฌ๋˜๋Š” ์ „์ฒด ๋ช…๋ น ๋ฌธ์ž์—ด (์žฌ์‹œ์ž‘ ํ•„์š”).", + "settings-clipboard-watch-image-label": "์ด๋ฏธ์ง€ ๊ฐ์‹œ ๋ช…๋ น", + "settings-clipboard-watch-text-description": "ํ…์ŠคํŠธ ๋ณ€๊ฒฝ์„ ์œ„ํ•ด wl-paste์— ์ „๋‹ฌ๋˜๋Š” ์ „์ฒด ๋ช…๋ น ๋ฌธ์ž์—ด (์žฌ์‹œ์ž‘ ํ•„์š”).", + "settings-clipboard-watch-text-label": "ํ…์ŠคํŠธ ๊ฐ์‹œ ๋ช…๋ น", + "settings-custom-launch-prefix-description": "๊ธฐ๋ณธ ๋Ÿฐ์ฒ˜ ๋Œ€์‹  ์‚ฌ์šฉ์ž ์ง€์ • ์ ‘๋‘์‚ฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ช…๋ น์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค (์˜ˆ: systemd ํ†ตํ•ฉ์„ ์œ„ํ•œ 'runapp').", + "settings-custom-launch-prefix-enabled-description": "๊ธฐ๋ณธ ๋ฐฉ์‹ ๋Œ€์‹  ์‚ฌ์šฉ์ž ์ง€์ • ์ ‘๋‘์‚ฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.", + "settings-custom-launch-prefix-enabled-label": "์‚ฌ์šฉ์ž ์ง€์ • ์‹คํ–‰ ์ ‘๋‘์‚ฌ ํ™œ์„ฑํ™”", + "settings-custom-launch-prefix-label": "์‚ฌ์šฉ์ž ์ง€์ • ์‹คํ–‰ ์ ‘๋‘์‚ฌ", + "settings-density-description": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์•„์ด์ฝ˜ ํฌ๊ธฐ์™€ Launcher ๋ฐ€๋„๋ฅผ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "settings-density-label": "๋ฐ€๋„", + "settings-desc": "๋Ÿฐ์ฒ˜์˜ ๋™์ž‘๊ณผ ๋ชจ์–‘์„ ์‚ฌ์šฉ์ž ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.", + "settings-enable-session-search-description": "๋Ÿฐ์ฒ˜์—์„œ ๊ฒ€์ƒ‰ํ•  ๋•Œ ์„ธ์…˜ ์ž‘์—…(์ž ๊ธˆ, ์ข…๋ฃŒ, ์žฌ์‹œ์ž‘ ๋“ฑ)์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "settings-enable-session-search-label": "์„ธ์…˜ ๊ฒ€์ƒ‰ ํ™œ์„ฑํ™”", + "settings-enable-settings-search-description": "๋Ÿฐ์ฒ˜์—์„œ ๊ฒ€์ƒ‰ํ•  ๋•Œ ์„ค์ • ๊ฒฐ๊ณผ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "settings-enable-settings-search-label": "์„ค์ • ๊ฒ€์ƒ‰ ํ™œ์„ฑํ™”", + "settings-enable-windows-search-description": "ํ™œ์„ฑ ์ฐฝ์„ ๊ฒ€์ƒ‰ํ•˜๊ณ  ํฌ์ปค์Šคํ•ฉ๋‹ˆ๋‹ค.", + "settings-enable-windows-search-label": "์ฐฝ ๊ฒ€์ƒ‰ ํ™œ์„ฑํ™”", + "settings-grid-view-description": "ํ•ญ๋ชฉ์„ ๋ชฉ๋ก ๋Œ€์‹  ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ์œผ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "settings-icon-mode-description": "Tabler ์•„์ด์ฝ˜ ๋Œ€์‹  ๊ธฐ๋ณธ ์‹œ์Šคํ…œ ์•„์ด์ฝ˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "settings-icon-mode-label": "๊ธฐ๋ณธ ์•„์ด์ฝ˜ ์‚ฌ์šฉ", + "settings-ignore-mouse-input-description": "๋Ÿฐ์ฒ˜์—์„œ ๋งˆ์šฐ์Šค ์ƒํ˜ธ์ž‘์šฉ ๋ฐ ์Šคํฌ๋กค ํœ ์„ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "settings-ignore-mouse-input-label": "๋งˆ์šฐ์Šค ์ž…๋ ฅ ๋ฌด์‹œ", + "settings-overlay-layer-description": "๋Ÿฐ์ฒ˜๋ฅผ ์ „์ฒด ํ™”๋ฉด ์ฐฝ ์œ„์— ์žˆ๋Š” ์˜ค๋ฒ„๋ ˆ์ด ๋ ˆ์ด์–ด์— ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ํ™œ์„ฑํ™”๋˜๋ฉด ๋Ÿฐ์ฒ˜๋Š” ๋ฐ”์— ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + "settings-overlay-layer-label": "์ „์ฒด ํ™”๋ฉด ์œ„์— ํ‘œ์‹œ", + "settings-position-description": "๋Ÿฐ์ฒ˜ ํŒจ๋„์ด ๋‚˜ํƒ€๋‚  ์œ„์น˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "settings-show-categories-description": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ•„ํ„ฐ๋ง์„ ์œ„ํ•œ ์นดํ…Œ๊ณ ๋ฆฌ ํƒญ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "settings-show-categories-label": "์นดํ…Œ๊ณ ๋ฆฌ ํ‘œ์‹œ", + "settings-show-icon-background-description": "์•„์ด์ฝ˜ ๋’ค์— ๋‘ฅ๊ทผ ์‚ฌ๊ฐํ˜• ๋ฐฐ๊ฒฝ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "settings-show-icon-background-label": "์•„์ด์ฝ˜ ๋ฐฐ๊ฒฝ ํ‘œ์‹œ", + "settings-sort-by-usage-description": "ํ™œ์„ฑํ™”ํ•˜๋ฉด ์ž์ฃผ ์‹คํ–‰ํ•˜๋Š” ์•ฑ์ด ๋ชฉ๋ก์˜ ์ฒ˜์Œ์— ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.", + "settings-sort-by-usage-label": "์‚ฌ์šฉ ๋นˆ๋„์ˆœ ์ •๋ ฌ", + "settings-terminal-command-description": "ํ„ฐ๋ฏธ๋„์„ ์‹คํ–‰ํ•  ๋ช…๋ น์ž…๋‹ˆ๋‹ค. ์˜ˆ: 'kitty -e' ๋˜๋Š” 'gnome-terminal --'.", + "settings-terminal-command-label": "ํ„ฐ๋ฏธ๋„ ๋ช…๋ น", + "settings-use-app2unit-description": "์•ฑ ํ”„๋กœ์„ธ์Šค๋ฅผ ๋” ์ž˜ ๊ด€๋ฆฌํ•˜๊ณ  ๋ฌธ์ œ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋Œ€์ฒด ์‹คํ–‰ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "settings-use-app2unit-label": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰์— App2Unit ์‚ฌ์šฉ", + "settings-view-mode-description": "๋Ÿฐ์ฒ˜ ํ•ญ๋ชฉ์˜ ๋ ˆ์ด์•„์›ƒ์„ ์„ ํƒํ•˜์„ธ์š”.", + "settings-view-mode-label": "๋ณด๊ธฐ ๋ชจ๋“œ", + "title": "๋Ÿฐ์ฒ˜" + }, + "location": { + "auto-locate-description": "IP ์ฃผ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฃผ๊ธฐ์ ์œผ๋กœ ์œ„์น˜๋ฅผ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•ฉ๋‹ˆ๋‹ค.", + "auto-locate-label": "์ž๋™ ์ฐพ๊ธฐ", + "calendar-cards-desc": "๋‹ฌ๋ ฅ ํŒจ๋„์—์„œ ์นด๋“œ๋ฅผ ์ •๋ฆฌํ•˜๊ณ  ํ™œ์„ฑํ™”/๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "calendar-cards-title": "๋‹ฌ๋ ฅ ์นด๋“œ", + "calendar-header-label": "๋‹ฌ๋ ฅ ํ—ค๋”", + "calendar-month-label": "๋‹ฌ๋ ฅ ์›”", + "date-time-12hour-format-description": "์ž ๊ธˆ ํ™”๋ฉด๊ณผ ๋‹ฌ๋ ฅ์—์„œ ์‹œ๊ฐ„์„ 12์‹œ๊ฐ„ ํ˜•์‹์œผ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ๋ฐ” ์‹œ๊ณ„์—๋Š” ์ž์ฒด ์„ค์ •์ด ์žˆ์Šต๋‹ˆ๋‹ค.", + "date-time-12hour-format-label": "12์‹œ๊ฐ„ ํ˜•์‹ ์‚ฌ์šฉ", + "date-time-desc": "๋‚ ์งœ์™€ ์‹œ๊ฐ„์ด ํ‘œ์‹œ๋˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉ์ž ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.", + "date-time-first-day-of-week-automatic": "์ž๋™ (์‹œ์Šคํ…œ ๋กœ์ผ€์ผ ์‚ฌ์šฉ)", + "date-time-first-day-of-week-description": "๋‹ฌ๋ ฅ์—์„œ ํ•œ ์ฃผ๊ฐ€ ์‹œ์ž‘๋˜๋Š” ์š”์ผ์„ ์„ ํƒํ•˜์„ธ์š”.", + "date-time-first-day-of-week-label": "์ฃผ์˜ ์‹œ์ž‘ ์š”์ผ", + "date-time-show-events-description": "๋‹ฌ๋ ฅ ํŒจ๋„์— ์ผ์ •์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "date-time-show-events-label": "๋‹ฌ๋ ฅ ์ผ์ • ํ‘œ์‹œ", + "date-time-title": "๋‚ ์งœ ๋ฐ ์‹œ๊ฐ„", + "date-time-use-analog-description": "๋‹ฌ๋ ฅ ์ฐฝ์— ์•„๋‚ ๋กœ๊ทธ ์Šคํƒ€์ผ ์‹œ๊ณ„๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "date-time-use-analog-label": "์•„๋‚ ๋กœ๊ทธ ์Šคํƒ€์ผ ์‹œ๊ณ„ ์‚ฌ์šฉ", + "date-time-week-numbers-description": "๋‹ฌ๋ ฅ์— ํ•ด๋‹น ์—ฐ๋„์˜ ์ฃผ์ฐจ(์˜ˆ: 38์ฃผ์ฐจ)๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "date-time-week-numbers-label": "์ฃผ ๋ฒˆํ˜ธ ํ‘œ์‹œ", + "geolocate-now-button": "์ง€๊ธˆ ์ฐพ๊ธฐ", + "location-desc": "์œ„์น˜๋ฅผ ์„ค์ •ํ•˜์—ฌ ์ •ํ™•ํ•œ ๋‚ ์”จ ๋ฐ ์•ผ๊ฐ„ ์กฐ๋ช… ์ผ์ •์„ ๊ฐ€์ ธ์˜ค์„ธ์š”.", + "location-search-description": "์˜ˆ: ์ข…๋กœ๊ตฌ, ํ•ด์šด๋Œ€, ๊ฐ•๋ฆ‰", + "location-search-label": "์œ„์น˜ ๊ฒ€์ƒ‰", + "location-search-placeholder": "์œ„์น˜ ์ด๋ฆ„ ์ž…๋ ฅ", + "location-title": "๋‚ด ์œ„์น˜", + "weather-desc": "์„ ํ˜ธํ•˜๋Š” ์˜จ๋„ ๋‹จ์œ„๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "weather-enabled-description": "์ธํ„ฐํŽ˜์ด์Šค ์ „๋ฐ˜์— ๋‚ ์”จ ์ •๋ณด๋ฅผ ํ‘œ์‹œํ•˜๊ณ  ๋‚ ์”จ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ๋น„ํ™œ์„ฑํ™”ํ•˜๋ฉด ๋ชจ๋“  ๋‚ ์”จ ์š”์†Œ๊ฐ€ ์ˆจ๊ฒจ์ง€๋ฉฐ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + "weather-enabled-label": "๋‚ ์”จ ํ™œ์„ฑํ™”", + "weather-fahrenheit-description": "์˜จ๋„๋ฅผ ์„ญ์”จ(ยฐC) ๋Œ€์‹  ํ™”์”จ(ยฐF)๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "weather-fahrenheit-label": "ํ™”์”จ(ยฐF)๋กœ ์˜จ๋„ ํ‘œ์‹œ", + "weather-hide-city-description": "์ธํ„ฐํŽ˜์ด์Šค ์ „๋ฐ˜์˜ ๋‚ ์”จ ํ‘œ์‹œ์—์„œ ๋„์‹œ ์ด๋ฆ„์„ ์ˆจ๊น๋‹ˆ๋‹ค.", + "weather-hide-city-label": "๋„์‹œ ์ด๋ฆ„ ์ˆจ๊ธฐ๊ธฐ", + "weather-hide-timezone-description": "์ธํ„ฐํŽ˜์ด์Šค ์ „๋ฐ˜์˜ ๋‚ ์”จ ํ‘œ์‹œ์—์„œ ์‹œ๊ฐ„๋Œ€ ์•ฝ์–ด๋ฅผ ์ˆจ๊น๋‹ˆ๋‹ค.", + "weather-hide-timezone-label": "์‹œ๊ฐ„๋Œ€ ์ˆจ๊ธฐ๊ธฐ", + "weather-show-effects-description": "๋‚ ์”จ ์นด๋“œ์— ์ถ”๊ฐ€ ์‹œ๊ฐ ํšจ๊ณผ(๋น„, ๋ˆˆ, ๋ฒˆ๊ฐœ ๋“ฑ)๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "weather-show-effects-label": "๋‚ ์”จ ํšจ๊ณผ ํ‘œ์‹œ", + "weather-show-in-calendar-description": "๋‹ฌ๋ ฅ ๋ทฐ์— ์ผ์ผ ์ผ๊ธฐ ์˜ˆ๋ณด๋ฅผ ์ง์ ‘ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "weather-show-in-calendar-label": "๋‹ฌ๋ ฅ์— ๋‚ ์”จ ํ‘œ์‹œ", + "weather-talia-mascot-always-description": "tabler icons ๋Œ€์‹  ํ•ญ์ƒ Talia emojis๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.", + "weather-talia-mascot-always-label": "ํ•ญ์ƒ Talia ๋‚ ์”จ ์ด๋ชจํ‹ฐ์ฝ˜ ํ‘œ์‹œ" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "fprintd(์ง€๋ฌธ ์ธ์ฆ)๊ฐ€ ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ, ์ด ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ์ง€๋ฌธ ๋Œ€์‹  ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "allow-password-with-fprintd-label": "fprintd๋กœ ์•”ํ˜ธ ๋กœ๊ทธ์ธ ํ—ˆ์šฉ", + "auto-start-auth-description": "ํ‚ค ์ž…๋ ฅ์ด๋‚˜ ๋ฒ„ํŠผ ํด๋ฆญ ์—†์ด ์ง€๋ฌธ ์ธ์ฆ์„ ์ž๋™์œผ๋กœ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.", + "auto-start-auth-label": "์ธ์ฆ ์ž๋™ ์‹œ์ž‘", + "clock-format-description": "๋‚ ์งœ/์‹œ๊ฐ„ ๊ตฌ๋ฌธ ํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ์‹œ๊ณ„ ํ˜•์‹์„ ์‚ฌ์šฉ์ž ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.", + "clock-format-label": "์‹œ๊ณ„ ํ˜•์‹", + "clock-style-analog": "์•„๋‚ ๋กœ๊ทธ", + "clock-style-custom": "์‚ฌ์šฉ์ž ์ง€์ •", + "clock-style-description": "์ž ๊ธˆ ํ™”๋ฉด ์‹œ๊ณ„์˜ ์‹œ๊ฐ์  ์Šคํƒ€์ผ์„ ์„ ํƒํ•˜์„ธ์š”.", + "clock-style-digital": "๋””์ง€ํ„ธ", + "clock-style-label": "์‹œ๊ณ„ ์Šคํƒ€์ผ", + "compact-lockscreen-description": "๋กœ๊ทธ์ธ ์ž…๋ ฅ๊ณผ ์‹œ์Šคํ…œ ์ œ์–ด๋งŒ ํ‘œ์‹œํ•˜๊ณ  ๋‚ ์”จ ๋ฐ ๋ฏธ๋””์–ด ์œ„์ ฏ์„ ์ˆจ๊น๋‹ˆ๋‹ค.", + "compact-lockscreen-label": "์ปดํŒฉํŠธ ์ž ๊ธˆ ํ™”๋ฉด", + "enable-lockscreen-media-controls-description": "์ž ๊ธˆ ํ™”๋ฉด์— ๋Œ€ํ™”ํ˜• ๋ฏธ๋””์–ด ์žฌ์ƒ ์ œ์–ด๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "enable-lockscreen-media-controls-label": "์ž ๊ธˆ ํ™”๋ฉด ๋ฏธ๋””์–ด ์ œ์–ด", + "lock-on-suspend-description": "์‹œ์Šคํ…œ ๋Œ€๊ธฐ ๋ชจ๋“œ ์ง„์ž… ์‹œ ์ž๋™์œผ๋กœ ํ™”๋ฉด์„ ์ž ๊ธ‰๋‹ˆ๋‹ค.", + "lock-on-suspend-label": "๋Œ€๊ธฐ ๋ชจ๋“œ ์‹œ ์ž ๊ธˆ", + "lock-screen-animations-description": "์ž ๊ธˆ ํ™”๋ฉด ์• ๋‹ˆ๋ฉ”์ด์…˜ ํ™œ์„ฑํ™” ๋˜๋Š” ๋น„ํ™œ์„ฑํ™”.", + "lock-screen-animations-label": "์ž ๊ธˆ ํ™”๋ฉด ์• ๋‹ˆ๋ฉ”์ด์…˜", + "lock-screen-blur-strength-description": "์ž ๊ธˆ ํ™”๋ฉด ๋ฐฐ๊ฒฝํ™”๋ฉด์— ํ๋ฆผ ํšจ๊ณผ๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "lock-screen-blur-strength-label": "์ž ๊ธˆ ํ™”๋ฉด ํ๋ฆผ ๊ฐ•๋„", + "lock-screen-tint-strength-description": "์ž ๊ธˆ ํ™”๋ฉด ๋ฐฐ๊ฒฝํ™”๋ฉด์— ์ƒ‰์กฐ ์˜ค๋ฒ„๋ ˆ์ด๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "lock-screen-tint-strength-label": "์ž ๊ธˆ ํ™”๋ฉด ํ‹ดํŠธ ๊ฐ•๋„", + "monitors-desc": "ํŠน์ • ๋ชจ๋‹ˆํ„ฐ์— ์ž ๊ธˆ ํ™”๋ฉด์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ์•„๋ฌด๊ฒƒ๋„ ์„ ํƒํ•˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋‘์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.", + "password-chars-description": "๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ˆจ๊ธฐ๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๊ท€์—ฌ์šด ์•„์ด์ฝ˜.", + "password-chars-label": "๋ฌด์ž‘์œ„ ๋น„๋ฐ€๋ฒˆํ˜ธ ์•„์ด์ฝ˜", + "show-hibernate-description": "์ „์› ์ œ์–ด์— '์ตœ๋Œ€ ์ ˆ์ „' ์˜ต์…˜์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-hibernate-label": "์ตœ๋Œ€ ์ ˆ์ „ ํ‘œ์‹œ", + "show-session-buttons-description": "์ž ๊ธˆ ํ™”๋ฉด์—์„œ ์ „์› ์„ค์ •์— ๋Œ€ํ•œ ์•ก์„ธ์Šค๋ฅผ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "show-session-buttons-label": "์ „์› ์ œ์–ด", + "title": "์ž ๊ธˆ ํ™”๋ฉด" + }, + "network": { + "bluetooth-description": "๋ธ”๋ฃจํˆฌ์Šค ๊ด€๋ฆฌ๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "bluetooth-rssi-polling-description": "bluetoothctl์„ ํ†ตํ•ด ์—ฐ๊ฒฐ๋œ ์žฅ์น˜์˜ RSSI๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ์ƒ˜ํ”Œ๋งํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ์žฅ์น˜์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์œผ๋ฉฐ ํ™œ์„ฑํ™” ์‹œ ์ตœ์†Œํ•œ์˜ ๋ฆฌ์†Œ์Šค๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "bluetooth-rssi-polling-label": "๋ธ”๋ฃจํˆฌ์Šค ์‹ ํ˜ธ ํด๋ง", + "desc": "Wi-Fi ๋ฐ ๋ธ”๋ฃจํˆฌ์Šค ์—ฐ๊ฒฐ์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.", + "wifi-description": "๋ฌด์„  ๋„คํŠธ์›Œํฌ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค (NetworkManager ํ•„์š”)." + }, + "notifications": { + "duration-critical-urgency-description": "๊ธด๊ธ‰ ๋ ˆ๋ฒจ์˜ ์•Œ๋ฆผ์ด ํ‘œ์‹œ๋˜๋Š” ์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค.", + "duration-critical-urgency-label": "์ค‘์š”๋„ ๊ธด๊ธ‰", + "duration-desc": "์ค‘์š”๋„์— ๋”ฐ๋ผ ์•Œ๋ฆผ์ด ํ‘œ์‹œ๋˜๋Š” ์‹œ๊ฐ„์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "duration-low-urgency-description": "๋‚ฎ์€ ๋ ˆ๋ฒจ์˜ ์•Œ๋ฆผ์ด ํ‘œ์‹œ๋˜๋Š” ์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค.", + "duration-low-urgency-label": "์ค‘์š”๋„ ๋‚ฎ์Œ", + "duration-normal-urgency-description": "๋ณดํ†ต ๋ ˆ๋ฒจ์˜ ์•Œ๋ฆผ์ด ํ‘œ์‹œ๋˜๋Š” ์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค.", + "duration-normal-urgency-label": "์ค‘์š”๋„ ๋ณดํ†ต", + "duration-respect-expire-description": "์•Œ๋ฆผ์— ์„ค์ •๋œ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "duration-respect-expire-label": "๋งŒ๋ฃŒ ์‹œ๊ฐ„ ์ค€์ˆ˜", + "duration-title": "์•Œ๋ฆผ ์ง€์† ์‹œ๊ฐ„", + "history-clear-dismiss-description": "์•Œ๋ฆผ์„ ๋‹ซ์„ ๋•Œ ๊ธฐ๋ก์—์„œ ์ง€์›๋‹ˆ๋‹ค.", + "history-clear-dismiss-label": "ํ•ด์ œ ์‹œ ์ง€์šฐ๊ธฐ", + "history-critical-urgency-description": "๊ธด๊ธ‰ ๋ ˆ๋ฒจ์˜ ์•Œ๋ฆผ์„ ๊ธฐ๋ก์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.", + "history-critical-urgency-label": "์ค‘์š”๋„ ๊ธด๊ธ‰ ๊ธฐ๋ก ์ €์žฅ", + "history-desc": "๋ ˆ๋ฒจ ์ˆ˜์ค€์— ๋”ฐ๋ผ ๊ธฐ๋ก์— ์ €์žฅํ•  ์•Œ๋ฆผ์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.", + "history-low-urgency-description": "๋‚ฎ์€ ๋ ˆ๋ฒจ์˜ ์•Œ๋ฆผ์„ ๊ธฐ๋ก์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.", + "history-low-urgency-label": "์ค‘์š”๋„ ๋‚ฎ์Œ ๊ธฐ๋ก ์ €์žฅ", + "history-normal-urgency-description": "๋ณดํ†ต ๋ ˆ๋ฒจ์˜ ์•Œ๋ฆผ์„ ๊ธฐ๋ก์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.", + "history-normal-urgency-label": "์ค‘์š”๋„ ๋ณดํ†ต ๊ธฐ๋ก ์ €์žฅ", + "monitors-desc": "ํŠน์ • ๋ชจ๋‹ˆํ„ฐ์— ์•Œ๋ฆผ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ์„ ํƒํ•˜์ง€ ์•Š์œผ๋ฉด ๋ชจ๋“  ๋ชจ๋‹ˆํ„ฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.", + "rules-action-block": "๋ธ”๋ก", + "rules-action-block-desc": "์™„์ „ํžˆ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.", + "rules-action-hide": "์ˆจ๊ธฐ๊ธฐ", + "rules-action-hide-desc": "ํŒ์—… ์—†์Œ, ์†Œ๋ฆฌ ์—†์Œ, ๊ธฐ๋ก์— ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค.", + "rules-action-label": "์•ก์…˜", + "rules-action-mute": "์Œ์†Œ๊ฑฐ", + "rules-action-mute-desc": "์†Œ๋ฆฌ ์—†์Œ, ํŒ์—…์€ ๊ณ„์† ํ‘œ์‹œ๋˜๋ฉฐ ๊ธฐ๋ก์— ๋‚จ์Œ.", + "rules-add": "๊ทœ์น™ ์ถ”๊ฐ€", + "rules-delete": "๊ทœ์น™ ์‚ญ์ œ", + "rules-description": "์•ฑ ์ด๋ฆ„ ๋˜๋Š” ๋‚ด์šฉ๊ณผ ์ผ์น˜์‹œํ‚ต๋‹ˆ๋‹ค. ๊ทœ์น™์€ ์ˆœ์„œ๋Œ€๋กœ ํ™•์ธ๋˜๋ฉฐ, ์ฒซ ๋ฒˆ์งธ ์ผ์น˜ํ•˜๋Š” ํ•ญ๋ชฉ์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.", + "rules-edit": "๊ทœ์น™ ํŽธ์ง‘", + "rules-label": "ํ•„ํ„ฐ ๊ทœ์น™", + "rules-pattern-label": "ํŒจํ„ด", + "rules-pattern-placeholder": "firefox, discord, ๋˜๋Š” /regex/", + "rules-tab": "๊ทœ์น™", + "settings-always-on-top-description": "์ „์ฒด ํ™”๋ฉด ์ฐฝ ๋ฐ ๋‹ค๋ฅธ ๋ ˆ์ด์–ด ์œ„์— ์•Œ๋ฆผ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "settings-background-opacity-description": "์•Œ๋ฆผ ๋ฐฐ๊ฒฝ์˜ ๋ถˆํˆฌ๋ช…๋„๋ฅผ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "settings-density-description": "์•Œ๋ฆผ ์นด๋“œ ๋ฐ€๋„๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "settings-density-label": "๋ฐ€๋„", + "settings-desc": "์•Œ๋ฆผ ๋ชจ์–‘๊ณผ ๋™์ž‘์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "settings-do-not-disturb-description": "ํ™œ์„ฑํ™”ํ•˜๋ฉด ๋ชจ๋“  ์•Œ๋ฆผ ํŒ์—…์„ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "settings-enabled-description": "์•Œ๋ฆผ ๋ฐ๋ชฌ์„ ํ™œ์„ฑํ™”ํ•˜๊ฑฐ๋‚˜ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค. Noctalia ์…ธ์„ ๋‹ค์‹œ ์‹œ์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "settings-enabled-label": "์•Œ๋ฆผ ํ™œ์„ฑํ™”", + "settings-location-description": "ํ™”๋ฉด์ƒ์— ์•Œ๋ฆผ์ด ๋‚˜ํƒ€๋‚˜๋Š” ์œ„์น˜์ž…๋‹ˆ๋‹ค.", + "settings-markdown-description": "Markdown ์„œ์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ์•Œ๋ฆผ ๋‚ด์šฉ์„ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.", + "settings-markdown-label": "Markdown ํ™œ์„ฑํ™”", + "sounds-desc": "์•Œ๋ฆผ ์†Œ๋ฆฌ ํšจ๊ณผ ๋ฐ ๋ณผ๋ฅจ์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "sounds-enabled-description": "์ˆ˜์‹  ์•Œ๋ฆผ์— ๋Œ€ํ•œ ์†Œ๋ฆฌ ํšจ๊ณผ๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "sounds-enabled-label": "์•Œ๋ฆผ ์†Œ๋ฆฌ ํ™œ์„ฑํ™”", + "sounds-excluded-apps-description": "์ž์ฒด ๋‚ด์žฅ ์†Œ๋ฆฌ๊ฐ€ ์žˆ๋Š” ํŠน์ • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋Œ€ํ•ด ๊ตฌ์„ฑ๋œ ์•Œ๋ฆผ ์†Œ๋ฆฌ ์žฌ์ƒ์„ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.", + "sounds-excluded-apps-label": "์ œ์™ธ๋œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "๊ธด๊ธ‰ ๋ ˆ๋ฒจ์˜ ์•Œ๋ฆผ์— ์žฌ์ƒํ•  ์†Œ๋ฆฌ ํŒŒ์ผ ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค.", + "sounds-files-critical-label": "๊ธด๊ธ‰ ๋ ˆ๋ฒจ์˜ ์•Œ๋ฆผ ์†Œ๋ฆฌ", + "sounds-files-critical-select-title": "๊ธด๊ธ‰ ๋ ˆ๋ฒจ์˜ ์•Œ๋ฆผ ์†Œ๋ฆฌ ํŒŒ์ผ ์„ ํƒ", + "sounds-files-desc": "๋‹ค์–‘ํ•œ ์•Œ๋ฆผ ์ˆ˜์ค€์— ๋Œ€ํ•ด ์‚ฌ์šฉ์ž ์ง€์ • ์†Œ๋ฆฌ ํŒŒ์ผ์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "sounds-files-low-description": "๋‚ฎ์€ ๋ ˆ๋ฒจ์˜ ์•Œ๋ฆผ์— ์žฌ์ƒํ•  ์†Œ๋ฆฌ ํŒŒ์ผ ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค.", + "sounds-files-low-label": "๋‚ฎ์€ ๋ ˆ๋ฒจ์˜ ์•Œ๋ฆผ ์†Œ๋ฆฌ", + "sounds-files-low-select-title": "๋‚ฎ์€ ๋ ˆ๋ฒจ์˜ ์•Œ๋ฆผ ์†Œ๋ฆฌ ํŒŒ์ผ ์„ ํƒ", + "sounds-files-normal-description": "๋ณดํ†ต ๋ ˆ๋ฒจ์˜ ์•Œ๋ฆผ์— ์žฌ์ƒํ•  ์†Œ๋ฆฌ ํŒŒ์ผ ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค.", + "sounds-files-normal-label": "๋ณดํ†ต ๋ ˆ๋ฒจ์˜ ์•Œ๋ฆผ ์†Œ๋ฆฌ", + "sounds-files-normal-select-title": "๋ณดํ†ต ๋ ˆ๋ฒจ์˜ ์•Œ๋ฆผ ์†Œ๋ฆฌ ํŒŒ์ผ ์„ ํƒ", + "sounds-files-placeholder": "์†Œ๋ฆฌ ํŒŒ์ผ ๊ฒฝ๋กœ ์ž…๋ ฅ", + "sounds-files-select-file": "์†Œ๋ฆฌ ํŒŒ์ผ ์„ ํƒ", + "sounds-files-title": "์†Œ๋ฆฌ ํŒŒ์ผ", + "sounds-files-unified-description": "์•Œ๋ฆผ์— ์žฌ์ƒํ•  ์†Œ๋ฆฌ ํŒŒ์ผ ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค.", + "sounds-files-unified-label": "์•Œ๋ฆผ ์†Œ๋ฆฌ", + "sounds-files-unified-select-title": "์•Œ๋ฆผ ์†Œ๋ฆฌ ํŒŒ์ผ ์„ ํƒ", + "sounds-separate-description": "์ค‘์š”๋„ ๋‚ฎ์Œ, ๋ณดํ†ต, ๊ธด๊ธ‰์˜ ์•Œ๋ฆผ์— ๋Œ€ํ•ด ์„œ๋กœ ๋‹ค๋ฅธ ์†Œ๋ฆฌ ํŒŒ์ผ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "sounds-separate-label": "์ค‘์š”๋„ ๋ณ„๋กœ ๋‹ค๋ฅธ ์†Œ๋ฆฌ๋ฅผ ์‚ฌ์šฉ", + "sounds-title": "์†Œ๋ฆฌ ์„ค์ •", + "sounds-unavailable-description": "์•Œ๋ฆผ ์†Œ๋ฆฌ๋ฅผ ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด Qt6 Multimedia๋ฅผ ์„ค์น˜ํ•˜์„ธ์š”.", + "sounds-unavailable-label": "์•Œ๋ฆผ ์†Œ๋ฆฌ ์‚ฌ์šฉ ๋ถˆ๊ฐ€", + "sounds-volume-description": "์•Œ๋ฆผ ์†Œ๋ฆฌ์˜ ๋ณผ๋ฅจ ๋ ˆ๋ฒจ์„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "sounds-volume-label": "์†Œ๋ฆฌ ๋ณผ๋ฅจ", + "toast-battery-description": "๋ฐฐํ„ฐ๋ฆฌ ์ž”๋Ÿ‰์ด ์ด ๋น„์œจ ์•„๋ž˜๋กœ ๋–จ์–ด์ง€๋ฉด ๊ฒฝ๊ณ ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "toast-battery-label": "๋ฐฐํ„ฐ๋ฆฌ ๊ฒฝ๊ณ ", + "toast-desc": "ํ† ์ŠคํŠธ ๋ชจ์–‘๊ณผ ๋™์ž‘์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "toast-keyboard-description": "ํ‚ค๋ณด๋“œ ๋ ˆ์ด์•„์›ƒ ๋ณ€๊ฒฝ ์‹œ ํ† ์ŠคํŠธ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "toast-keyboard-label": "ํ‚ค๋ณด๋“œ ๋ ˆ์ด์•„์›ƒ", + "toast-media-description": "๋ฏธ๋””์–ด ์žฌ์ƒ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ํ† ์ŠคํŠธ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "toast-media-label": "๋ฏธ๋””์–ด" + }, + "osd": { + "always-on-top-description": "์ „์ฒด ํ™”๋ฉด ์ฐฝ ๋ฐ ๋‹ค๋ฅธ ๋ ˆ์ด์–ด ์œ„์— OSD๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "always-on-top-label": "ํ•ญ์ƒ ์œ„์— ํ‘œ์‹œ", + "background-opacity-description": "OSD ๋ฐฐ๊ฒฝ์˜ ํˆฌ๋ช…๋„๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.", + "background-opacity-label": "๋ฐฐ๊ฒฝ ๋ถˆํˆฌ๋ช…๋„", + "description": "๋ณผ๋ฅจ ๋ฐ ๋ฐ๊ธฐ ์˜ค๋ฒ„๋ ˆ์ด์™€ ๊ฐ™์€ ํ™”๋ฉด ํ‘œ์‹œ๊ธฐ๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "duration-auto-hide-description": "OSD๊ฐ€ ์‚ฌ๋ผ์ง€๊ธฐ ์ „์˜ ์‹œ๊ฐ„์„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "duration-auto-hide-label": "์ž๋™ ์ˆจ๊ธฐ๊ธฐ ์‹œ๊ฐ„", + "duration-desc": "OSD๊ฐ€ ์ž๋™์œผ๋กœ ์ˆจ๊ฒจ์ง€๊ธฐ ์ „๊นŒ์ง€ ํ‘œ์‹œ๋˜๋Š” ์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค.", + "duration-title": "์ž๋™ ์ˆจ๊ธฐ๊ธฐ ์‹œ๊ฐ„ ์ œํ•œ", + "enabled-description": "๋ณผ๋ฅจ ๋ฐ ๋ฐ๊ธฐ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "enabled-label": "OSD (ํ™”๋ฉด ํ‘œ์‹œ) ํ™œ์„ฑํ™”", + "events-desc": "OSD๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•  ์ด๋ฒคํŠธ๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "general-desc": "OSD์˜ ๊ฐ€์‹œ์„ฑ๊ณผ ๋™์ž‘์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "location-description": "ํ™”๋ฉด ํ‘œ์‹œ๊ฐ€ ๋‚˜ํƒ€๋‚˜๋Š” ์œ„์น˜์ž…๋‹ˆ๋‹ค.", + "monitors-desc": "ํŠน์ • ๋ชจ๋‹ˆํ„ฐ์— OSD๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ์„ ํƒํ•˜์ง€ ์•Š์œผ๋ฉด ๋ชจ๋“  ๋ชจ๋‹ˆํ„ฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.", + "title": "OSD (ํ™”๋ฉด ํ‘œ์‹œ)", + "types-brightness-description": "ํ™”๋ฉด ๋ฐ๊ธฐ ๋ณ€๊ฒฝ ์‹œ OSD๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "types-brightness-label": "๋ฐ๊ธฐ", + "types-custom-text-description": "IPC์˜ ์‚ฌ์šฉ์ž ์ง€์ • ํ…์ŠคํŠธ ๋ฉ”์‹œ์ง€์— ๋Œ€ํ•ด OSD๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "types-custom-text-label": "์‚ฌ์šฉ์ž ์ง€์ • ํ…์ŠคํŠธ", + "types-desc": "OSD๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•  ์ด๋ฒคํŠธ๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "types-input-volume-description": "๋งˆ์ดํฌ ๋ณผ๋ฅจ ๋ณ€๊ฒฝ ์‹œ OSD๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "types-input-volume-label": "์ž…๋ ฅ ๋ณผ๋ฅจ", + "types-lockkey-description": "Caps Lock, Num Lock, Scroll Lock ์ „ํ™˜ ์‹œ OSD๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "types-lockkey-label": "์ž ๊ธˆ ํ‚ค", + "types-media-description": "๋ฏธ๋””์–ด ์žฌ์ƒ ์ƒํƒœ ๋ณ€๊ฒฝ(์žฌ์ƒ, ์ผ์‹œ ์ •์ง€, ๊ฑด๋„ˆ๋›ฐ๊ธฐ) ์‹œ OSD๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "types-media-label": "๋ฏธ๋””์–ด ์žฌ์ƒ", + "types-title": "OSD ํŠธ๋ฆฌ๊ฑฐ ์ด๋ฒคํŠธ", + "types-volume-description": "์˜ค๋””์˜ค ์ถœ๋ ฅ ๋ณผ๋ฅจ ๋ณ€๊ฒฝ ์‹œ OSD๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "types-volume-label": "์ถœ๋ ฅ ๋ณผ๋ฅจ" + }, + "plugins": { + "auto-update": "ํ”Œ๋Ÿฌ๊ทธ์ธ ์ž๋™ ์—…๋ฐ์ดํŠธ", + "auto-update-description": "์…ธ ์‹œ์ž‘ ์‹œ ๋ชจ๋“  ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.", + "available-description": "๊ตฌ์„ฑ๋œ ์†Œ์Šค์—์„œ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ฐพ์•„๋ณด๊ณ  ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.", + "available-label": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ”Œ๋Ÿฌ๊ทธ์ธ", + "available-no-plugins-description": "ํ”Œ๋Ÿฌ๊ทธ์ธ ์†Œ์Šค๋ฅผ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ๋ชฉ๋ก์„ ์ƒˆ๋กœ๊ณ ์นจํ•˜์„ธ์š”.", + "available-no-plugins-label": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ”Œ๋Ÿฌ๊ทธ์ธ ์—†์Œ", + "check-for-updates": "์—…๋ฐ์ดํŠธ ํ™•์ธ", + "checking-for-updates": "์—…๋ฐ์ดํŠธ ํ™•์ธ ์ค‘...", + "collision-already-installed": "์ด ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ์ด๋ฏธ ์„ค์น˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค", + "collision-custom-version-exists": "\"{source}\"์˜ ์‚ฌ์šฉ์ž ์ง€์ • ๋ฒ„์ „์ด ์ด๋ฏธ ์„ค์น˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค", + "collision-official-version-exists": "์ด ํ”Œ๋Ÿฌ๊ทธ์ธ์˜ ๊ณต์‹ ๋ฒ„์ „์ด ์ด๋ฏธ ์„ค์น˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค", + "development-disable": "์ด ํ”Œ๋Ÿฌ๊ทธ์ธ์˜ ๊ฐœ๋ฐœ ๋ชจ๋“œ๋ฅผ ๋น„ํ™œ์„ฑํ™”", + "development-enable": "์ด ํ”Œ๋Ÿฌ๊ทธ์ธ์— ๋Œ€ํ•œ ๊ฐœ๋ฐœ ๋ชจ๋“œ๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "filter-downloaded": "๋‹ค์šด๋กœ๋“œ๋จ", + "filter-not-downloaded": "๋‹ค์šด๋กœ๋“œ๋˜์ง€ ์•Š์Œ", + "filter-tags-description": "์นดํ…Œ๊ณ ๋ฆฌ ๋˜๋Š” ๋‹ค์šด๋กœ๋“œ ์ƒํƒœ๋ณ„๋กœ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค.", + "filter-tags-label": "ํƒœ๊ทธ", + "hot-reload-description": "ํŒŒ์ผ์ด ๋ณ€๊ฒฝ๋  ๋•Œ ์ž๋™์œผ๋กœ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋‹ค์‹œ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ฐœ๋ฐœ์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "hot-reload-label": "ํ•ซ ๋ฆฌ๋กœ๋“œ (๊ฐœ๋ฐœ ๋ชจ๋“œ)", + "hot-reloaded": "ํ”Œ๋Ÿฌ๊ทธ์ธ ๋‹ค์‹œ ๋กœ๋“œ๋จ: {name}", + "install-error": "์„ค์น˜ ์‹คํŒจ: {error}", + "install-incompatible": "{plugin}์€(๋Š”) Noctalia v{version} ์ด์ƒ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค", + "install-success": "{plugin} ์„ค์น˜ ์„ฑ๊ณต", + "installed-description": "๋กœ์ปฌ์— ์„ค์น˜๋œ ๋ชจ๋“  ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๊ด€๋ฆฌํ•˜๊ณ  ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "installed-label": "์„ค์น˜๋œ ํ”Œ๋Ÿฌ๊ทธ์ธ", + "installed-no-plugins-description": "\"์‚ฌ์šฉ ๊ฐ€๋Šฅ\" ์„น์…˜์—์„œ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์„ค์น˜ํ•˜์„ธ์š”.", + "installed-no-plugins-label": "์„ค์น˜๋œ ํ”Œ๋Ÿฌ๊ทธ์ธ ์—†์Œ", + "installing": "{plugin} ์„ค์น˜ ์ค‘...", + "notify-updates": "ํ”Œ๋Ÿฌ๊ทธ์ธ ์—…๋ฐ์ดํŠธ ์•Œ๋ฆผ", + "notify-updates-description": "์ƒˆ๋กœ์šด ํ”Œ๋Ÿฌ๊ทธ์ธ ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ์„ ๋•Œ ์•Œ๋ฆผ ํŒ์—…์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "open-plugin-page": "ํ”Œ๋Ÿฌ๊ทธ์ธ ํŽ˜์ด์ง€ ์—ด๊ธฐ", + "open-plugins-tab": "ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์ • ์—ด๊ธฐ", + "plugin-settings-title": "{plugin} ์„ค์ •", + "refresh-refreshing": "ํ”Œ๋Ÿฌ๊ทธ์ธ ๋ชฉ๋ก ์ƒˆ๋กœ๊ณ ์นจ ์ค‘...", + "refresh-tooltip": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ”Œ๋Ÿฌ๊ทธ์ธ ์ƒˆ๋กœ๊ณ ์นจ", + "settings-error-not-loaded": "ํ”Œ๋Ÿฌ๊ทธ์ธ ๋กœ๋“œ๋˜์ง€ ์•Š์Œ", + "settings-saved": "ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์ • ์ €์žฅ๋จ", + "settings-tooltip": "ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์ •", + "source-custom": "์‚ฌ์šฉ์ž ์ง€์ • ์†Œ์Šค", + "sources-add-custom": "์‚ฌ์šฉ์ž ์ง€์ • ์ €์žฅ์†Œ ์ถ”๊ฐ€", + "sources-add-dialog-description": "GitHub ์ €์žฅ์†Œ๋ฅผ ํ”Œ๋Ÿฌ๊ทธ์ธ ์†Œ์Šค๋กœ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.", + "sources-add-dialog-error": "ํ”Œ๋Ÿฌ๊ทธ์ธ ์†Œ์Šค ์ถ”๊ฐ€ ์‹คํŒจ", + "sources-add-dialog-name": "์ €์žฅ์†Œ ์ด๋ฆ„", + "sources-add-dialog-name-placeholder": "๋‚ด ์‚ฌ์šฉ์ž ์ง€์ • ํ”Œ๋Ÿฌ๊ทธ์ธ", + "sources-add-dialog-success": "ํ”Œ๋Ÿฌ๊ทธ์ธ ์†Œ์Šค ์ถ”๊ฐ€ ์„ฑ๊ณต", + "sources-add-dialog-title": "ํ”Œ๋Ÿฌ๊ทธ์ธ ์†Œ์Šค ์ถ”๊ฐ€", + "sources-add-dialog-url": "์ €์žฅ์†Œ URL", + "sources-description": "ํ”Œ๋Ÿฌ๊ทธ์ธ ์ €์žฅ์†Œ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.", + "sources-placeholder": "๋‚ด ๋ฉ‹์ง„ ์ €์žฅ์†Œ", + "sources-remove-tooltip": "ํ”Œ๋Ÿฌ๊ทธ์ธ ์†Œ์Šค ์ œ๊ฑฐ", + "title": "ํ”Œ๋Ÿฌ๊ทธ์ธ", + "translations-reloaded": "๋ฒˆ์—ญ ๋‹ค์‹œ ๋กœ๋“œ๋จ: {name}", + "uninstall-dialog-description": "์ •๋ง {plugin}์„(๋ฅผ) ์ œ๊ฑฐํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? ๋ชจ๋“  ํ”Œ๋Ÿฌ๊ทธ์ธ ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค.", + "uninstall-dialog-title": "ํ”Œ๋Ÿฌ๊ทธ์ธ ์ œ๊ฑฐ", + "uninstall-error": "์ œ๊ฑฐ ์‹คํŒจ: {error}", + "uninstall-success": "{plugin} ์ œ๊ฑฐ ์„ฑ๊ณต", + "uninstalling": "{plugin} ์ œ๊ฑฐ ์ค‘...", + "update-all": "๋ชจ๋‘ ์—…๋ฐ์ดํŠธ ({count})", + "update-all-success": "๋ชจ๋“  ํ”Œ๋Ÿฌ๊ทธ์ธ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต", + "update-available": "์ƒˆ ํ”Œ๋Ÿฌ๊ทธ์ธ ์—…๋ฐ์ดํŠธ ์‚ฌ์šฉ ๊ฐ€๋Šฅ", + "update-available-plural": "ํ”Œ๋Ÿฌ๊ทธ์ธ ์—…๋ฐ์ดํŠธ ์‚ฌ์šฉ ๊ฐ€๋Šฅ ({count}๊ฐœ)", + "update-error": "ํ”Œ๋Ÿฌ๊ทธ์ธ ์—…๋ฐ์ดํŠธ ์‹คํŒจ: {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (Noctalia v{required} ํ•„์š”)", + "update-success": "{plugin}์„(๋ฅผ) v{version}์œผ๋กœ ์—…๋ฐ์ดํŠธํ•จ", + "update-version": "v{current} โ†’ v{new}", + "updating": "์—…๋ฐ์ดํŠธ ์ค‘..." + }, + "region": { + "title": "์ง€์—ญ" + }, + "session-menu": { + "countdown-duration-description": "์ „์› ์ž‘์—…์„ ์‹คํ–‰ํ•˜๊ธฐ ์ „ ์นด์šดํŠธ๋‹ค์šด ํƒ€์ด๋จธ์˜ ์ง€์† ์‹œ๊ฐ„์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "countdown-duration-label": "์นด์šดํŠธ๋‹ค์šด ์ง€์† ์‹œ๊ฐ„", + "enable-countdown-description": "์ „์› ์ž‘์—…์„ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— ์นด์šดํŠธ๋‹ค์šด ํƒ€์ด๋จธ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "enable-countdown-label": "์นด์šดํŠธ๋‹ค์šด ํƒ€์ด๋จธ ํ™œ์„ฑํ™”", + "entries-desc": "์„ธ์…˜ ๋ฉ”๋‰ด์— ๋‚˜ํƒ€๋‚  ์ „์› ์ž‘์—…๊ณผ ์ˆœ์„œ๋ฅผ ์‚ฌ์šฉ์ž ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.", + "entries-title": "์ „์› ์ž‘์—…", + "entry-settings-command-description": "์ด ์ž‘์—…์— ๋Œ€ํ•ด ์‹คํ–‰ํ•  ์‚ฌ์šฉ์ž ์ง€์ • ๋ช…๋ น์ž…๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ๊ธฐ๋ณธ ๋ช…๋ น์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋น„์›Œ ๋‘์„ธ์š”.", + "entry-settings-command-placeholder": "์˜ˆ: systemctl poweroff", + "entry-settings-default-command-lock": "๋‚ด๋ถ€ ์ž ๊ธˆ ํ™”๋ฉด (๋ช…๋ น ์—†์Œ)", + "entry-settings-default-command-logout": "๋‚ด๋ถ€ ๋กœ๊ทธ์•„์›ƒ (๋ช…๋ น ์—†์Œ)", + "entry-settings-default-info-description": "์‚ฌ์šฉ์ž ์ง€์ • ๋ช…๋ น์ด ์ง€์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์‹œ์Šคํ…œ ๊ธฐ๋ณธ ๋ช…๋ น์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.", + "entry-settings-default-info-label": "๊ธฐ๋ณธ ๋ช…๋ น", + "entry-settings-keybind-description": "์„ธ์…˜ ๋ฉ”๋‰ด๊ฐ€ ์—ด๋ ค ์žˆ์„ ๋•Œ ์ด ์ž‘์—…์„ ํŠธ๋ฆฌ๊ฑฐํ•  ํ‚ค ์กฐํ•ฉ์„ ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค.", + "entry-settings-keybind-placeholder": "ํ‚ค๋ฅผ ํ• ๋‹นํ•˜๋ ค๋ฉด ๊ธฐ๋ก์„ ๋ˆ„๋ฅด์„ธ์š”...", + "entry-settings-keybind-recording": "๋…น์Œ ์ค‘... ์•„๋ฌด ํ‚ค ์กฐํ•ฉ์ด๋‚˜ ๋ˆ„๋ฅด์„ธ์š”", + "entry-settings-title": "{entry} ๊ตฌ์„ฑ", + "entry-settings-tooltip": "๋ช…๋ น ๊ตฌ์„ฑ", + "general-desc": "์„ธ์…˜ ๋ฉ”๋‰ด ํŒจ๋„์˜ ๋™์ž‘๊ณผ ๋ชจ์–‘์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "large-buttons-layout-description": "์„ธ์…˜ ๋ฉ”๋‰ด ๋ฒ„ํŠผ์˜ ํ‘œ์‹œ ๋ฐฉ์‹์„ ์„ ํƒํ•˜์„ธ์š”.", + "large-buttons-layout-label": "ํฐ ๋ฒ„ํŠผ ๋ ˆ์ด์•„์›ƒ", + "large-buttons-style-description": "์„ธ์…˜ ๋ฉ”๋‰ด๋ฅผ ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ์˜ ํฐ ๋ฒ„ํŠผ์œผ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "large-buttons-style-label": "ํฐ ๋ฒ„ํŠผ ์Šคํƒ€์ผ", + "position-description": "์„ธ์…˜ ๋ฉ”๋‰ด ํŒจ๋„์ด ์—ด๋ฆด ๋•Œ ๋‚˜ํƒ€๋‚  ์œ„์น˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "show-header-description": "์„ธ์…˜ ๋ฉ”๋‰ด ์ƒ๋‹จ์— ์ œ๋ชฉ๊ณผ ๋‹ซ๊ธฐ ๋ฒ„ํŠผ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-header-label": "ํ—ค๋” ํ‘œ์‹œ", + "show-keybinds-description": "์„ธ์…˜ ์˜ต์…˜์— ํ‚ค ๋ฐ”์ธ๋”ฉ ํžŒํŠธ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "show-keybinds-label": "๋‹จ์ถ•ํ‚ค ํ‘œ์‹œ" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Noctalia ์„ฑ๋Šฅ ๋ชจ๋“œ๊ฐ€ ํ™œ์„ฑํ™”๋œ ๋™์•ˆ ๋ฐ์Šคํฌํ†ฑ ์œ„์ ฏ์„ ๊ณ„์† ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "noctalia-performance-disable-desktop-widgets-label": "์„ฑ๋Šฅ ๋ชจ๋“œ์—์„œ ๋ฐ์Šคํฌํ†ฑ ์œ„์ ฏ ํ™œ์„ฑํ™”", + "noctalia-performance-disable-wallpaper-description": "Noctalia ์„ฑ๋Šฅ ๋ชจ๋“œ๊ฐ€ ํ™œ์„ฑํ™”๋œ ๋™์•ˆ ๋ฐ์Šคํฌํ†ฑ, ๊ฐœ์š”, ์ž ๊ธˆ ํ™”๋ฉด ๋ฐฐ๊ฒฝํ™”๋ฉด์„ ๊ณ„์† ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "noctalia-performance-disable-wallpaper-label": "์„ฑ๋Šฅ ๋ชจ๋“œ์—์„œ ๋ฐฐ๊ฒฝํ™”๋ฉด ๋ Œ๋”๋ง ํ™œ์„ฑํ™”", + "title": "์‹œ์Šคํ…œ" + }, + "system-monitor": { + "critical-color-label": "์œ„ํ—˜ ์ƒ‰์ƒ", + "custom-highlight-colors-title-label": "์‚ฌ์šฉ์ž ์ง€์ • ๊ฐ•์กฐ ์ƒ‰์ƒ", + "disk-available-label": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋””์Šคํฌ ๊ณต๊ฐ„", + "disk-section-label": "๋””์Šคํฌ ์‚ฌ์šฉ๋Ÿ‰", + "enable-dgpu-monitoring-description": "๊ฒฝ๊ณ : ์ด ๊ธฐ๋Šฅ์€ ์™ธ์žฅ GPU(NVIDIA/AMD)๋ฅผ ๊นจ์›Œ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๊ทธ๋ž˜ํ”ฝ ๋…ธํŠธ๋ถ์˜ ๋ฐฐํ„ฐ๋ฆฌ ์ˆ˜๋ช…์— ์ƒ๋‹นํ•œ ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "enable-dgpu-monitoring-label": "์™ธ์žฅ GPU ๋ชจ๋‹ˆํ„ฐ๋ง ํ™œ์„ฑํ™”", + "external-monitor-description": "์™ธ๋ถ€ ์‹œ์Šคํ…œ ๋ชจ๋‹ˆํ„ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ•  ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น ๋˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฒฝ๋กœ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", + "external-monitor-label": "์™ธ๋ถ€ ์‹œ์Šคํ…œ ๋ชจ๋‹ˆํ„ฐ ๋ช…๋ น", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "์‹œ์Šคํ…œ ๋ชจ๋‹ˆํ„ฐ์˜ ๋™์ž‘๊ณผ ๋ชจ์–‘์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.", + "gpu-section-label": "GPU ์˜จ๋„", + "highlight-colors-section-label": "๊ฐ•์กฐ ์ƒ‰์ƒ", + "polling-interval-label": "ํด๋ง ๊ฐ„๊ฒฉ", + "polling-section-description": "๊ฐ ์‹œ์Šคํ…œ ๊ฐ’์„ ๊ฐฑ์‹ ํ•˜๋Š” ๋นˆ๋„๋ฅผ ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "polling-section-label": "ํด๋ง ๊ฐ„๊ฒฉ", + "threshold-critical": "์‹ฌ๊ฐ", + "threshold-warning": "๊ฒฝ๊ณ ", + "thresholds-section-description": "๊ฐ ์‹œ์Šคํ…œ ๊ฐ’์— ๋Œ€ํ•œ ๊ฒฝ๊ณ /์œ„ํ—˜ ์ž„๊ณ„๊ฐ’์„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "use-custom-highlight-colors-description": "๋น„ํ™œ์„ฑํ™”ํ•˜๋ฉด ํ…Œ๋งˆ ๊ธฐ๋ณธ ๊ฐ•์กฐ ์ƒ‰์ƒ์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.", + "use-custom-highlight-colors-label": "์‚ฌ์šฉ์ž ์ง€์ • ๊ฐ•์กฐ ์ƒ‰์ƒ ์‚ฌ์šฉ", + "warning-color-label": "๊ฒฝ๊ณ  ์ƒ‰์ƒ" + }, + "user-interface": { + "allow-panels-without-bar-description": "ํ™œ์„ฑํ™”ํ•˜๋ฉด ๋ฐ”๊ฐ€ ์—†๋Š” ํ™”๋ฉด์—์„œ๋„ ํŒจ๋„์„ ์—ด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋น„ํ™œ์„ฑํ™”ํ•˜๋ฉด ๋ฐ”๊ฐ€ ์žˆ๋Š” ํ™”๋ฉด์—์„œ๋งŒ ํŒจ๋„์ด ์—ด๋ฆฌ๋ฏ€๋กœ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "allow-panels-without-bar-label": "๋ฐ”๊ฐ€ ์—†๋Š” ํ™”๋ฉด์—์„œ ํŒจ๋„ ํ—ˆ์šฉ", + "animation-disable-description": "๋” ๋น ๋ฅด๊ณ  ๋ฐ˜์‘์ด ์ข‹์€ ๊ฒฝํ—˜์„ ์œ„ํ•ด ๋ชจ๋“  ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "animation-disable-label": "UI ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋น„ํ™œ์„ฑํ™”", + "animation-speed-description": "์ „์—ญ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์†๋„๋ฅผ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "animation-speed-label": "์• ๋‹ˆ๋ฉ”์ด์…˜ ์†๋„", + "animation-speed-reset": "์• ๋‹ˆ๋ฉ”์ด์…˜ ์†๋„ ์ดˆ๊ธฐํ™”", + "appearance-desc": "ํˆดํŒ, ํ…Œ๋‘๋ฆฌ, ๊ทธ๋ฆผ์ž์™€ ๊ฐ™์€ ์‹œ๊ฐ์  ์š”์†Œ๋ฅผ ์‚ฌ์šฉ์ž ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.", + "blur-behind-description": "์ปดํฌ์ง€ํ„ฐ ๋ธ”๋Ÿฌ ํ”„๋กœํ† ์ฝœ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ”์™€ ํŒจ๋„ ๋’ค ์˜์—ญ์„ ํ๋ฆฌ๊ฒŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.", + "blur-behind-label": "๋ฐฐ๊ฒฝ ํ๋ฆผ", + "box-border-description": "์ฝ˜ํ…์ธ  ์˜์—ญ ์ฃผ์œ„์— ์œค๊ณฝ์„ ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.", + "box-border-label": "์ปจํ…Œ์ด๋„ˆ ์œค๊ณฝ์„ ", + "box-border-radius-description": "์‚ฌ์ด๋“œ๋ฐ”, ์นด๋“œ, ์ฝ˜ํ…์ธ  ํŒจ๋„๊ณผ ๊ฐ™์€ ์ฃผ์š” ๋ ˆ์ด์•„์›ƒ ์„น์…˜์˜ ๋ชจ์„œ๋ฆฌ ๋‘ฅ๊ธ€๊ธฐ๋ฅผ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "box-border-radius-label": "์ปจํ…Œ์ด๋„ˆ ๋ฐ˜๊ฒฝ", + "control-border-radius-description": "๋ฒ„ํŠผ, ํ† ๊ธ€, ํ…์ŠคํŠธ ํ•„๋“œ๋ฅผ ํฌํ•จํ•œ ๋Œ€ํ™”ํ˜• ์š”์†Œ์˜ ๊ตด๊ณก์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.", + "control-border-radius-label": "์ž…๋ ฅ ๋ฐ˜๊ฒฝ", + "desc": "์ธํ„ฐํŽ˜์ด์Šค์˜ ๋ชจ์–‘, ๋А๋‚Œ ๋ฐ ๋™์ž‘์„ ์‚ฌ์šฉ์ž ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.", + "dim-desktop-description": "ํŒจ๋„์ด๋‚˜ ๋ฉ”๋‰ด๊ฐ€ ์—ด๋ ค ์žˆ์„ ๋•Œ ๋ฐ”ํƒ• ํ™”๋ฉด์„ ์–ด๋‘ก๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.", + "dim-desktop-label": "๋ฐ”ํƒ• ํ™”๋ฉด ์–ด๋‘ก๊ฒŒ ํ•˜๊ธฐ", + "dimmer-opacity-description": "๋ฐ”ํƒ• ํ™”๋ฉด ์–ด๋‘ก๊ฒŒ ํ•˜๊ธฐ์˜ ๋ถˆํˆฌ๋ช…๋„ ์ˆ˜์ค€์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "dimmer-opacity-label": "์–ด๋‘์šด ๋ฐ”ํƒ• ํ™”๋ฉด ๋ถˆํˆฌ๋ช…๋„", + "panel-background-opacity-description": "๋ชจ๋“  ํŒจ๋„(๋ฐ”, ๋Ÿฐ์ฒ˜, ์„ค์ • ๋“ฑ...)์˜ ๋ฐฐ๊ฒฝ ๋ถˆํˆฌ๋ช…๋„๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "panel-background-opacity-label": "ํŒจ๋„ ๋ฐฐ๊ฒฝ ๋ถˆํˆฌ๋ช…๋„", + "panels-attached-to-bar-description": "ํŒจ๋„์ด ๋ฐ”์™€ ํ™”๋ฉด ๊ฐ€์žฅ์ž๋ฆฌ์— ๊ณ ์ •๋˜์–ด ์„ธ๋ จ๋œ ๋ฐ˜์ „ ๋ชจ์„œ๋ฆฌ๋กœ ๋งค๋„๋Ÿฌ์šด ๋ชจ์–‘์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.", + "panels-attached-to-bar-label": "ํŒจ๋„์„ ๊ฐ€์žฅ์ž๋ฆฌ์— ์Šค๋ƒ…", + "panels-overlay-description": "์ „์ฒด ํ™”๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์œ„์—์„œ๋„ ํŒจ๋„๊ณผ ๋ฐ”๊ฐ€ ๊ณ„์† ๋ณด์ด๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.", + "panels-overlay-label": "ํŒจ๋„๊ณผ ๋ฐ”๋ฅผ ํ•ญ์ƒ ์œ„์— ์œ ์ง€", + "scaling-description": "๋ฐ”๋ฅผ ์ œ์™ธํ•œ ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค์˜ ํฌ๊ธฐ๋ฅผ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.", + "scaling-label": "์ธํ„ฐํŽ˜์ด์Šค ๋ฐฐ์œจ", + "scrollbar-always-visible-description": "์ฝ˜ํ…์ธ ๋ฅผ ์Šคํฌ๋กคํ•  ์ˆ˜ ์žˆ์„ ๋•Œ ์Šคํฌ๋กค๋ฐ”๋ฅผ ํ•ญ์ƒ ํ‘œ์‹œํ•˜๊ณ , ๋งˆ์šฐ์Šค ์˜ค๋ฒ„ ์‹œ์—๋งŒ ํ‘œ์‹œํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + "scrollbar-always-visible-label": "ํ•ญ์ƒ ์Šคํฌ๋กค๋ฐ” ํ‘œ์‹œ", + "settings-panel-header": "์„ค์ • ํŒจ๋„", + "settings-panel-mode-description": "์„ค์ • ๋ ˆ์ด์•„์›ƒ์„ ์„ ํƒํ•˜์„ธ์š” (๋‹ค์‹œ ์—ด์–ด์•ผ ํ•  ์ˆ˜ ์žˆ์Œ).", + "settings-panel-mode-label": "์„ค์ • ํŒจ๋„ ๋ชจ๋“œ", + "settings-panel-sidebar-card-style-description": "์„ค์ • ์‚ฌ์ด๋“œ๋ฐ”๋ฅผ ๋‘ฅ๊ทผ ๋ชจ์„œ๋ฆฌ๊ฐ€ ์žˆ๋Š” ์ฑ„์›Œ์ง„ ๋ฐฐ๊ฒฝ์œผ๋กœ ๊ฐ์Œ‰๋‹ˆ๋‹ค.", + "settings-panel-sidebar-card-style-label": "์‚ฌ์ด๋“œ๋ฐ” ์นด๋“œ ์Šคํƒ€์ผ", + "shadows-description": "๋ฐ”์™€ ํŒจ๋„ ์•„๋ž˜์— ๊ทธ๋ฆผ์ž๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "shadows-direction-description": "๊ทธ๋ฆผ์ž๊ฐ€ ๋“œ๋ฆฌ์›Œ์งˆ ์œ„์น˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "shadows-direction-label": "๊ทธ๋ฆผ์ž ๋ฐฉํ–ฅ", + "shadows-label": "๊ทธ๋ฆผ์ž", + "title": "์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค", + "tooltips-description": "์ธํ„ฐํŽ˜์ด์Šค ์ „๋ฐ˜์— ํˆดํŒ์„ ํ™œ์„ฑํ™”ํ•˜๊ฑฐ๋‚˜ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "tooltips-label": "ํˆดํŒ ํ‘œ์‹œ", + "translucent-widgets-description": "ํŒจ๋„ ๋‚ด์˜ ๋ฒ„ํŠผ, ํƒญ ๋ฐ ๊ธฐํƒ€ ์œ„์ ฏ์„ ๋ฐ˜ํˆฌ๋ช…ํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.", + "translucent-widgets-label": "๋ฐ˜ํˆฌ๋ช… ์œ„์ ฏ" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "์•ŒํŒŒ๋ฒณ์ˆœ", + "automation-change-mode-description": "์ž๋™์œผ๋กœ ๋ณ€๊ฒฝํ•  ๋•Œ ๋ฐฐ๊ฒฝํ™”๋ฉด ์„ ํƒ ๋ฐฉ์‹์„ ์„ ํƒํ•˜์„ธ์š”.", + "automation-change-mode-label": "๋ณ€๊ฒฝ ๋ชจ๋“œ", + "automation-custom-interval-description": "์‹œ๊ฐ„์„ HH:MM์œผ๋กœ ์ž…๋ ฅํ•˜์„ธ์š” (์˜ˆ: 01:30).", + "automation-custom-interval-label": "์‚ฌ์šฉ์ž ์ง€์ • ๊ฐ„๊ฒฉ", + "automation-interval-description": "๋ฐฐ๊ฒฝํ™”๋ฉด์„ ์ž๋™์œผ๋กœ ๋ณ€๊ฒฝํ•  ๋นˆ๋„์ž…๋‹ˆ๋‹ค.", + "automation-interval-label": "๋ฐฐ๊ฒฝํ™”๋ฉด ๊ฐ„๊ฒฉ", + "automation-random-wallpaper-description": "์ •๊ธฐ์ ์ธ ๊ฐ„๊ฒฉ์œผ๋กœ ๋žœ๋ค ๋ฐฐ๊ฒฝํ™”๋ฉด ๋ณ€๊ฒฝ์„ ์˜ˆ์•ฝํ•ฉ๋‹ˆ๋‹ค.", + "automation-scheduled-change-description": "์ •๊ธฐ์ ์ธ ๊ฐ„๊ฒฉ์œผ๋กœ ๋ฐฐ๊ฒฝํ™”๋ฉด์„ ์ž๋™์œผ๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.", + "automation-scheduled-change-label": "์ž๋™ ๋ณ€๊ฒฝ", + "look-feel-edge-smoothness-description": "์ „ํ™˜ ๊ฐ€์žฅ์ž๋ฆฌ์— ๋ถ€๋“œ๋Ÿฌ์šด ํŽ˜๋”๋ง ํšจ๊ณผ๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "look-feel-edge-smoothness-label": "์ „ํ™˜ ๊ฐ€์žฅ์ž๋ฆฌ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ", + "look-feel-fill-color-description": "๋ฐฐ๊ฒฝํ™”๋ฉด ๋’ค์— ๋‚˜ํƒ€๋‚  ์ˆ˜ ์žˆ๋Š” ์ฑ„์šฐ๊ธฐ ์ƒ‰์ƒ์„ ์„ ํƒํ•˜์„ธ์š”.", + "look-feel-fill-mode-description": "์ด๋ฏธ์ง€๊ฐ€ ๋ชจ๋‹ˆํ„ฐ ํ•ด์ƒ๋„์— ๋งž์ถฐ ์Šค์ผ€์ผ๋ง๋˜๋Š” ๋ฐฉ์‹์„ ์„ ํƒํ•˜์„ธ์š”.", + "look-feel-fill-mode-label": "์ฑ„์šฐ๊ธฐ ๋ชจ๋“œ", + "look-feel-skip-startup-transition-description": "์‰˜ ์‹œ์ž‘ ์‹œ ๋ฐฐ๊ฒฝํ™”๋ฉด ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๊ฑด๋„ˆ๋›ฐ๊ธฐ.", + "look-feel-skip-startup-transition-label": "์‹œ์ž‘ ์ „ํ™˜ ๊ฑด๋„ˆ๋›ฐ๊ธฐ", + "look-feel-title": "๋ชจ์–‘ ๋ฐ ๋А๋‚Œ", + "look-feel-transition-duration-description": "์ „ํ™˜ ์• ๋‹ˆ๋ฉ”์ด์…˜์˜ ์ง€์† ์‹œ๊ฐ„(์ดˆ)์ž…๋‹ˆ๋‹ค.", + "look-feel-transition-duration-label": "์ „ํ™˜ ์ง€์† ์‹œ๊ฐ„", + "look-feel-transition-type-description": "๋ฐฐ๊ฒฝํ™”๋ฉด ์ „ํ™˜ ์‹œ์˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์œ ํ˜•์ž…๋‹ˆ๋‹ค.", + "look-feel-transition-type-label": "์ „ํ™˜ ์œ ํ˜•", + "settings-clear-cache-button": "์บ์‹œ ์ง€์šฐ๊ธฐ", + "settings-clear-cache-description": "์บ์‹œ๋œ ํฌ๊ธฐ ์กฐ์ •๋œ ๋ฐฐ๊ฒฝํ™”๋ฉด์„ ์ง€์›Œ ๋””์Šคํฌ ๊ณต๊ฐ„์„ ํ™•๋ณดํ•ฉ๋‹ˆ๋‹ค.", + "settings-clear-cache-label": "๋ฐฐ๊ฒฝํ™”๋ฉด ์บ์‹œ", + "settings-clear-cache-toast": "๋ฐฐ๊ฒฝํ™”๋ฉด ์บ์‹œ๊ฐ€ ์ง€์›Œ์กŒ์Šต๋‹ˆ๋‹ค", + "settings-desc": "๋ฐฐ๊ฒฝํ™”๋ฉด ๊ด€๋ฆฌ ๋ฐ ํ‘œ์‹œ ๋ฐฉ์‹์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.", + "settings-enable-management-description": "Noctalia๋กœ ๋ฐฐ๊ฒฝํ™”๋ฉด์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์ฒดํฌ ํ•ด์ œํ•˜์„ธ์š”.", + "settings-enable-management-label": "๋ฐฐ๊ฒฝํ™”๋ฉด ๊ด€๋ฆฌ ํ™œ์„ฑํ™”", + "settings-enable-overview-description": "๊ฐœ์š” ํ™”๋ฉด์— ํ๋ฆฌ๊ณ  ์–ด๋‘์šด ๋ฐฐ๊ฒฝํ™”๋ฉด์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "settings-enable-overview-label": "๊ฐœ์š” ๋ฐฐ๊ฒฝํ™”๋ฉด ํ™œ์„ฑํ™”", + "settings-folder-description": "๋ฉ”์ธ ๋ฐฐ๊ฒฝํ™”๋ฉด ํด๋”์˜ ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค.", + "settings-folder-label": "๋ฐฐ๊ฒฝํ™”๋ฉด ํด๋”", + "settings-hide-wallpaper-filenames-tooltip-hide": "ํŒŒ์ผ๋ช… ์ˆจ๊ธฐ๊ธฐ", + "settings-hide-wallpaper-filenames-tooltip-show": "ํŒŒ์ผ๋ช… ํ‘œ์‹œ", + "settings-monitor-specific-description": "๊ฐ ๋ชจ๋‹ˆํ„ฐ์— ๋Œ€ํ•ด ๋‹ค๋ฅธ ๋ฐฐ๊ฒฝํ™”๋ฉด ํด๋”๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "settings-monitor-specific-label": "๋ชจ๋‹ˆํ„ฐ๋ณ„ ๋””๋ ‰ํ† ๋ฆฌ", + "settings-monitor-specific-tooltip": "๋ชจ๋‹ˆํ„ฐ ๋ฐฐ๊ฒฝํ™”๋ฉด ํด๋”", + "settings-overview-blur-strength-description": "๊ฐœ์š”์— ํ๋ฆผ ๊ฐ•๋„๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "settings-overview-blur-strength-label": "๊ฐœ์š” ํ๋ฆผ ๊ฐ•๋„", + "settings-overview-tint-description": "๊ฐœ์š”์— ์ƒ‰์กฐ ๊ฐ•๋„๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "settings-overview-tint-label": "๊ฐœ์š” ์ƒ‰์กฐ ๊ฐ•๋„", + "settings-recursive-search-description": "๋ฐฐ๊ฒฝํ™”๋ฉด ๋””๋ ‰ํ† ๋ฆฌ์˜ ํ•˜์œ„ ํด๋”์—์„œ๋„ ๋ฐฐ๊ฒฝํ™”๋ฉด์„ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค.", + "settings-recursive-search-label": "ํ•˜์œ„ ํด๋” ๊ฒ€์ƒ‰", + "settings-select-monitor-folder": "๋ชจ๋‹ˆํ„ฐ ๋ฐฐ๊ฒฝํ™”๋ฉด ํด๋” ์„ ํƒ", + "settings-selector-description": "๋ฐฐ๊ฒฝํ™”๋ฉด์„ ์„ ํƒํ•˜์„ธ์š”.", + "settings-selector-position-description": "๋ฐฐ๊ฒฝํ™”๋ฉด ์„ ํƒ ํŒจ๋„์ด ๋‚˜ํƒ€๋‚  ์œ„์น˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "settings-show-hidden-files-tooltip-hide": "์ˆจ๊น€ ํŒŒ์ผ ์ˆจ๊ธฐ๊ธฐ", + "settings-show-hidden-files-tooltip-show": "์ˆจ๊น€ ํŒŒ์ผ ํ‘œ์‹œ", + "settings-title": "๋ฐฐ๊ฒฝํ™”๋ฉด ์„ค์ •", + "settings-use-original-images-description": "ํ‘œ์‹œ ์ „์— ๋ฐฐ๊ฒฝํ™”๋ฉด ํฌ๊ธฐ ์กฐ์ •์„ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. ๋””์Šคํฌ ๊ณต๊ฐ„์„ ์ ˆ์•ฝํ•˜๊ณ  ๋ฐฐ๊ฒฝํ™”๋ฉด ๋ณ€๊ฒฝ ์‹œ CPU ์‚ฌ์šฉ๋Ÿ‰์„ ์ค„์ด์ง€๋งŒ, ๋งค์šฐ ํฐ ์ด๋ฏธ์ง€์˜ ๊ฒฝ์šฐ ๋” ๋งŽ์€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "settings-use-original-images-label": "์›๋ณธ ์ด๋ฏธ์ง€ ์‚ฌ์šฉ", + "settings-view-mode-description": "๋””๋ ‰ํ† ๋ฆฌ์˜ ๋ฐฐ๊ฒฝํ™”๋ฉด ํ‘œ์‹œ ๋ฐฉ์‹์„ ์„ ํƒํ•˜์„ธ์š”.", + "settings-view-mode-label": "๋ณด๊ธฐ ๋ชจ๋“œ", + "view-mode-browse": "๋””๋ ‰ํ† ๋ฆฌ ์ฐพ์•„๋ณด๊ธฐ", + "view-mode-cycle-tooltip": "๋ณด๊ธฐ ๋ชจ๋“œ: {mode} (ํด๋ฆญํ•˜์—ฌ ๋ณ€๊ฒฝ)", + "view-mode-recursive": "ํ•˜์œ„ ๋””๋ ‰ํ† ๋ฆฌ ํ‰ํƒ„ํ™”", + "view-mode-single": "๋ฃจํŠธ ๋””๋ ‰ํ† ๋ฆฌ" + } + }, + "placeholders": { + "add-new-keybind": "์ƒˆ ํ‚ค ๋ฐ”์ธ๋”ฉ ์ถ”๊ฐ€", + "command-example": "echo \"Hello World\"", + "enter-command": "์‹คํ–‰ํ•  ๋ช…๋ น ์ž…๋ ฅ (์•ฑ ๋˜๋Š” ์‚ฌ์šฉ์ž ์ง€์ • ์Šคํฌ๋ฆฝํŠธ)", + "enter-ipc-identifier": "IPC ๋ช…๋ น์„ ์œ„ํ•œ ๊ณ ์œ  ์‹๋ณ„์ž ์ž…๋ ฅ", + "enter-path": "๊ฒฝ๋กœ ์ž…๋ ฅ...", + "enter-text-to-collapse": "์˜ˆ: '์žฌ์ƒ ์ค‘์ธ ํ•ญ๋ชฉ ์—†์Œ'. ํŒจํ„ด์—๋Š” /regex/๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.", + "enter-tooltip": "ํˆดํŒ ์ž…๋ ฅ", + "enter-width-pixels": "๋„ˆ๋น„ ์ž…๋ ฅ (ํ”ฝ์…€ ๋‹จ์œ„)", + "keybind-recording": "ํ‚ค ๋ฐ”์ธ๋”ฉ ๊ธฐ๋ก ์ค‘...", + "search": "๊ฒ€์ƒ‰...", + "search-icons": "์˜ˆ: noctalia, niri, battery, cloud", + "search-launcher": "ํ•ญ๋ชฉ ๊ฒ€์ƒ‰... ๋˜๋Š” ๋ช…๋ น์„ ์œ„ํ•ด > ์‚ฌ์šฉ", + "search-wallhaven": "Wallhaven์—์„œ ๊ฒ€์ƒ‰...", + "search-wallpapers": "๋ฐฐ๊ฒฝํ™”๋ฉด ํ•„ํ„ฐ๋ง์„ ์œ„ํ•ด ์ž…๋ ฅ..." + }, + "positions": { + "bottom": "ํ•˜๋‹จ", + "bottom-center": "ํ•˜๋‹จ ์ค‘์•™", + "bottom-left": "ํ•˜๋‹จ ์™ผ์ชฝ", + "bottom-right": "ํ•˜๋‹จ ์˜ค๋ฅธ์ชฝ", + "center": "์ค‘์•™", + "center-left": "์™ผ์ชฝ ์ค‘์•™", + "center-right": "์˜ค๋ฅธ์ชฝ ์ค‘์•™", + "close-to-bar": "๋ฐ” ๋ฒ„ํŠผ ๊ทผ์ฒ˜", + "follow-bar": "๋ฐ” ๋”ฐ๋ฅด๊ธฐ", + "left": "์™ผ์ชฝ", + "right": "์˜ค๋ฅธ์ชฝ", + "top": "์ƒ๋‹จ", + "top-center": "์ƒ๋‹จ ์ค‘์•™", + "top-left": "์ƒ๋‹จ ์™ผ์ชฝ", + "top-right": "์ƒ๋‹จ ์˜ค๋ฅธ์ชฝ" + }, + "session-menu": { + "action-in-seconds": "{seconds}์ดˆ ํ›„ {action}...", + "cancel-timer": "ํƒ€์ด๋จธ ์ทจ์†Œ", + "title": "์„ธ์…˜ ๋ฉ”๋‰ด" + }, + "setup": { + "all-done": "์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "appearance": { + "subheader": "๋‹คํฌ ๋ชจ๋“œ์™€ ์ƒ‰์ƒ ์†Œ์Šค(๋ฐฐ๊ฒฝํ™”๋ฉด ๋˜๋Š” ์‚ฌ์ „ ์ •์˜)๋ฅผ ์„ ํƒํ•˜์„ธ์š”." + }, + "customize": { + "header": "๊ฒฝํ—˜ ์‚ฌ์šฉ์ž ์ง€์ •", + "subheader": "๋ฐ” ์œ„์น˜, ๋ฐ€๋„, ๋ฐฐ์œจ ๋“ฑ์„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค." + }, + "skip-setup": "์„ค์ • ๊ฑด๋„ˆ๋›ฐ๊ธฐ", + "telemetry-wizard-done": "์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค!", + "telemetry-wizard-note": "์–ธ์ œ๋“ ์ง€ ์„ค์ •์—์„œ ํ™œ์„ฑํ™”ํ•˜๊ฑฐ๋‚˜ ๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค", + "telemetry-wizard-subtitle": "Noctalia ๊ฐœ์„ ์„ ๋•๊ธฐ ์œ„ํ•ด ์ต๋ช… ๋ถ„์„์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค", + "telemetry-wizard-title": "๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ ์—…๋ฐ์ดํŠธ", + "wallpaper": { + "choose-dir": "๋ฐฐ๊ฒฝํ™”๋ฉด ์ด๋ฏธ์ง€๊ฐ€ ํฌํ•จ๋œ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์„ ํƒํ•˜์„ธ์š”", + "dir-browse": "๋ฐฐ๊ฒฝํ™”๋ฉด ํด๋” ์ฐพ์•„๋ณด๊ธฐ", + "dir-description": "๋ฐฐ๊ฒฝํ™”๋ฉด์ด ํฌํ•จ๋œ ํด๋”๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "dir-label": "๋ฐฐ๊ฒฝํ™”๋ฉด ๋””๋ ‰ํ† ๋ฆฌ", + "dir-select-title": "๋ฐฐ๊ฒฝํ™”๋ฉด ํด๋” ์„ ํƒ", + "header": "๋ฐฐ๊ฒฝํ™”๋ฉด ์„ ํƒ", + "no-dir": "์„ ํƒ๋œ ๋ฐฐ๊ฒฝํ™”๋ฉด ๋””๋ ‰ํ† ๋ฆฌ ์—†์Œ", + "no-valid": "๋‹ค์Œ ์œ„์น˜์—์„œ ์œ ํšจํ•œ ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ: {dir}", + "none-in-dir": "๋””๋ ‰ํ† ๋ฆฌ์—์„œ ๋ฐฐ๊ฒฝํ™”๋ฉด์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ", + "preview-error": "์ด๋ฏธ์ง€ ๋กœ๋“œ ์‹คํŒจ", + "select-prompt": "์•„๋ž˜์—์„œ ๋ฐฐ๊ฒฝํ™”๋ฉด์„ ์„ ํƒํ•˜์„ธ์š”", + "subheader": "์•„๋ฆ„๋‹ค์šด ๋ฐฐ๊ฒฝ์œผ๋กœ ๋ถ„์œ„๊ธฐ๋ฅผ ์„ค์ •ํ•˜์„ธ์š”." + }, + "welcome": "ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค", + "welcome-note": "์‹œ์ž‘ํ•˜๊ธฐ ์œ„ํ•œ ๋ช‡ ๊ฐ€์ง€ ๊ธฐ๋ณธ ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค โ€” ์ „์ฒด ์˜ต์…˜์€ ์„ค์ •์— ์žˆ์Šต๋‹ˆ๋‹ค", + "welcome-subtitle": "๋‹น์‹ ๋งŒ์˜ ๋…ํŠนํ•œ ๋ฐ์Šคํฌํ†ฑ์„ ๋งŒ๋“ค์–ด ๋ณด์„ธ์š”", + "welcome-title": "Noctalia์— ์˜ค์‹  ๊ฒƒ์„ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค!" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "๊ฐ์ง€๋œ ๋ฏธ๋””์–ด ์žฌ์ƒ๊ธฐ ์—†์Œ", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "์•Œ ์ˆ˜ ์—†๋Š” ์•ฑ", + "uptime": "๊ฐ€๋™ ์‹œ๊ฐ„: {uptime}", + "user-requested": "์‚ฌ์šฉ์ž ์š”์ฒญ๋จ", + "welcome-back": "ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค,", + "widget-settings-title": "{widget} ์„ค์ •" + }, + "system-monitor": { + "core-usage": "์ฝ”์–ด {id} ์‚ฌ์šฉ๋Ÿ‰", + "cpu-temp": "CPU ์˜จ๋„", + "cpu-usage": "CPU ์‚ฌ์šฉ๋Ÿ‰", + "disk": "๋””์Šคํฌ", + "download-speed": "๋‹ค์šด๋กœ๋“œ ์†๋„", + "gpu-temp": "GPU ์˜จ๋„", + "load-average": "๋กœ๋“œ ํ‰๊ท ", + "title": "์‹œ์Šคํ…œ ๋ชจ๋‹ˆํ„ฐ", + "upload-speed": "์—…๋กœ๋“œ ์†๋„" + }, + "toast": { + "airplane-mode": { + "description": "๋ชจ๋“  ๋ฌด์„  ํ†ต์‹ ์„ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.", + "title": "๋น„ํ–‰๊ธฐ ๋ชจ๋“œ" + }, + "battery": { + "critical": "๋ฐฐํ„ฐ๋ฆฌ ๋ถ€์กฑ", + "critical-desc": "๋ฐฐํ„ฐ๋ฆฌ {percent}% โ€” ์ฆ‰์‹œ ์ถฉ์ „๊ธฐ๋ฅผ ์—ฐ๊ฒฐํ•ด์ฃผ์„ธ์š”", + "low": "๋ฐฐํ„ฐ๋ฆฌ ๋ถ€์กฑ", + "low-desc": "๋ฐฐํ„ฐ๋ฆฌ๊ฐ€ {percent}%์ž…๋‹ˆ๋‹ค โ€” ์ถฉ์ „๊ธฐ๋ฅผ ์—ฐ๊ฒฐํ•ด์ฃผ์„ธ์š”" + }, + "bluetooth": { + "address-copied": "์ฃผ์†Œ๊ฐ€ ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌ๋จ", + "auto-connect-disabled": "์ž๋™ ์—ฐ๊ฒฐ ๋น„ํ™œ์„ฑํ™”๋จ", + "auto-connect-enabled": "์ž๋™ ์—ฐ๊ฒฐ ํ™œ์„ฑํ™”๋จ", + "auto-connecting": "{count}๊ฐœ์˜ ์žฅ์น˜์— ์—ฐ๊ฒฐ ์ค‘...", + "confirm-code": "๋‹ค๋ฅธ ์žฅ์น˜์—์„œ ์ฝ”๋“œ {value}๋ฅผ ํ™•์ธํ•˜์„ธ์š”.", + "connect-failed": "์žฅ์น˜ ์—ฐ๊ฒฐ ์‹คํŒจ", + "disconnect-failed": "์žฅ์น˜ ์—ฐ๊ฒฐ ํ•ด์ œ ์‹คํŒจ", + "discoverable-change-failed": "๊ฒ€์ƒ‰ ๊ฐ€๋Šฅ ์ƒํƒœ ๋ณ€๊ฒฝ ์‹คํŒจ", + "discoverable-disabled": "๊ฒ€์ƒ‰ ๊ฐ€๋Šฅ ๋น„ํ™œ์„ฑํ™”๋จ", + "discoverable-enabled": "๊ฒ€์ƒ‰ ๊ฐ€๋Šฅ ํ™œ์„ฑํ™”๋จ", + "display-code": "๋‹ค๋ฅธ ์žฅ์น˜์— ์ฝ”๋“œ {value}๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", + "forget-failed": "์žฅ์น˜ ์ง€์šฐ๊ธฐ ์‹คํŒจ", + "pair-failed": "์žฅ์น˜ ํŽ˜์–ด๋ง ์‹คํŒจ", + "passkey-required": "์žฅ์น˜์—์„œ ํŒจ์Šคํ‚ค๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.", + "pincode-required": "์žฅ์น˜์—์„œ PIN ์ฝ”๋“œ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.", + "state-change-failed": "๋ธ”๋ฃจํˆฌ์Šค ์ƒํƒœ ๋ณ€๊ฒฝ ์‹คํŒจ" + }, + "clipboard": { + "long-text": "๊ธด ํ…์ŠคํŠธ", + "unavailable": "ํด๋ฆฝ๋ณด๋“œ ๊ธฐ๋ก ์‚ฌ์šฉ ๋ถˆ๊ฐ€", + "unavailable-desc": "'cliphist' ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์„ค์น˜๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํด๋ฆฝ๋ณด๋“œ ๊ธฐ๋ก ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์„ค์น˜ํ•˜์„ธ์š”" + }, + "custom-command-failed": { + "description": "๋ช…๋ น ์‹คํŒจ: {command}\\n์ข…๋ฃŒ ์ฝ”๋“œ: {code}", + "title": "์‚ฌ์šฉ์ž ์ง€์ • ๋ช…๋ น ์‹คํŒจ" + }, + "do-not-disturb": { + "disabled": "๋ฐฉํ•ด ๊ธˆ์ง€ ๋ชจ๋“œ ๋น„ํ™œ์„ฑํ™”๋จ", + "disabled-desc": "๋ชจ๋“  ์•Œ๋ฆผ ํ‘œ์‹œ ์ค‘", + "enabled": "๋ฐฉํ•ด ๊ธˆ์ง€ ๋ชจ๋“œ ํ™œ์„ฑํ™”๋จ", + "enabled-desc": "์•Œ๋ฆผ์€ ๊ธฐ๋ก์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค" + }, + "donation-opened": "๋ธŒ๋ผ์šฐ์ €์—์„œ ํ›„์› ํŽ˜์ด์ง€๊ฐ€ ์—ด๋ฆผ", + "internet-limited": "์ธํ„ฐ๋„ท ์—†์ด ์—ฐ๊ฒฐ๋จ", + "keyboard-layout": { + "changed": "ํ‚ค๋ณด๋“œ ๋ ˆ์ด์•„์›ƒ์ด {layout}(์œผ)๋กœ ๋ณ€๊ฒฝ๋จ", + "title": "ํ‚ค๋ณด๋“œ" + }, + "missing-control-center": { + "description": "์ œ์–ด ์„ผํ„ฐ ์œ„์ ฏ์ด ๋ฐ”์—์„œ ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋ฐ”์—์„œ ๋‹ค์‹œ ์•ก์„ธ์Šคํ•˜๋ ค๋ฉด ์œ„์ ฏ์„ ๋‹ค์‹œ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ”๋ฅผ ์˜ค๋ฅธ์ชฝ ํด๋ฆญํ•˜์—ฌ ์—ด ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.", + "label": "๋งˆ์ง€๋ง‰ ์ œ์–ด ์„ผํ„ฐ ์œ„์ ฏ ์ œ๊ฑฐ๋จ" + }, + "night-light": { + "forced": "๊ฐ•์ œ ํ™œ์„ฑํ™”", + "normal": "์ผ๋ฐ˜ ๋ชจ๋“œ", + "not-installed": "wlsunset์ด ์„ค์น˜๋˜์ง€ ์•Š์Œ" + }, + "noctalia-performance": { + "disabled": "์„ฑ๋Šฅ ๋ชจ๋“œ ๋น„ํ™œ์„ฑํ™”๋จ", + "enabled": "์„ฑ๋Šฅ ๋ชจ๋“œ ํ™œ์„ฑํ™”๋จ", + "label": "Noctalia ์„ฑ๋Šฅ" + }, + "power-profile": { + "changed": "์ „์› ํ”„๋กœํ•„ ๋ณ€๊ฒฝ๋จ", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "ํ…œํ”Œ๋ฆฟ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค", + "title": "ํ…œํ”Œ๋ฆฟ ์ฒ˜๋ฆฌ ์‹คํŒจ" + }, + "vpn": { + "connected": "'{name}'์— ์—ฐ๊ฒฐ๋จ", + "disconnected": "'{name}' ์—ฐ๊ฒฐ ๋Š๊น€" + }, + "wallpaper-colors": { + "disabled": "๋ฐฐ๊ฒฝํ™”๋ฉด ์ƒ‰์ƒ ๋น„ํ™œ์„ฑํ™”๋จ", + "enabled": "๋ฐฐ๊ฒฝํ™”๋ฉด ์ƒ‰์ƒ ํ™œ์„ฑํ™”๋จ", + "label": "๋ฐฐ๊ฒฝํ™”๋ฉด ์ƒ‰์ƒ" + }, + "wifi": { + "connected": "'{ssid}'์— ์—ฐ๊ฒฐ๋จ", + "connection-failed": "์—ฐ๊ฒฐ ์‹คํŒจ", + "connection-timeout": "์—ฐ๊ฒฐ ์‹œ๊ฐ„ ์ดˆ๊ณผ", + "disconnected": "'{ssid}' ์—ฐ๊ฒฐ ๋Š๊น€", + "incorrect-password": "์ž˜๋ชป๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ", + "network-not-found": "๋„คํŠธ์›Œํฌ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ" + } + }, + "tooltips": { + "add-widget": "์œ„์ ฏ ์ถ”๊ฐ€", + "auto-connect": "์ด ์žฅ์น˜์˜ ์ž๋™ ์—ฐ๊ฒฐ ํ† ๊ธ€", + "bluetooth-auto-connect-off": "์ž๋™ ์—ฐ๊ฒฐ ๋น„ํ™œ์„ฑํ™”๋จ", + "bluetooth-auto-connect-on": "์ž๋™ ์—ฐ๊ฒฐ ํ™œ์„ฑํ™”๋จ", + "bluetooth-devices": "๋ธ”๋ฃจํˆฌ์Šค ์žฅ์น˜", + "brightness-at": "๋ฐ๊ธฐ: {brightness}%", + "click-to-start-recording": "ํ™”๋ฉด ๋…นํ™” (๋…นํ™” ์‹œ์ž‘)", + "click-to-stop-recording": "ํ™”๋ฉด ๋…นํ™” (๋…นํ™” ์ค‘์ง€)", + "collapse": "์‚ฌ์ด๋“œ๋ฐ” ์ ‘๊ธฐ", + "copy-address": "์ฃผ์†Œ ๋ณต์‚ฌ", + "delete-notification": "์•Œ๋ฆผ ์‚ญ์ œ", + "dismiss-notification": "์•Œ๋ฆผ ๋‹ซ๊ธฐ", + "do-not-disturb-enabled": "๋ฐฉํ•ด ๊ธˆ์ง€ ๋ชจ๋“œ", + "expand": "์‚ฌ์ด๋“œ๋ฐ” ํŽผ์น˜๊ธฐ", + "forget-network": "๋„คํŠธ์›Œํฌ ์ง€์šฐ๊ธฐ", + "grid-view": "๊ทธ๋ฆฌ๋“œ ๋ณด๊ธฐ", + "hidden-files-hide": "์ˆจ๊น€ ํŒŒ์ผ", + "hide-unnamed-devices": "์ด๋ฆ„ ์—†๋Š” ์žฅ์น˜ ์ˆจ๊ธฐ๊ธฐ", + "home": "ํ™ˆ ๋””๋ ‰ํ† ๋ฆฌ", + "input-muted": "์ž…๋ ฅ ์Œ์†Œ๊ฑฐ ์ „ํ™˜", + "keep-awake": "๊นจ์–ด ์žˆ๊ธฐ", + "keyboard-layout": "{layout} ํ‚ค๋ณด๋“œ ๋ ˆ์ด์•„์›ƒ", + "list-view": "๋ชฉ๋ก ๋ณด๊ธฐ", + "manage-vpn": "VPN ์—ฐ๊ฒฐ", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "์ตœ๋Œ€ ์œ„์ ฏ ๋„๋‹ฌ", + "microphone-volume-at": "๋งˆ์ดํฌ ๋ณผ๋ฅจ: {volume}%", + "move-to-section": "{section}(์œผ)๋กœ ์ด๋™", + "mute": "์Œ์†Œ๊ฑฐ", + "next-media": "๋‹ค์Œ ํŠธ๋ž™", + "next-month": "๋‹ค์Œ ๋‹ฌ", + "night-light-not-installed": "์•ผ๊ฐ„ ์กฐ๋ช… (์‚ฌ์šฉ ๋ถˆ๊ฐ€)", + "noctalia-performance-enabled": "Noctalia ์„ฑ๋Šฅ ๋ชจ๋“œ", + "open-annotation-tool": "์ฃผ์„ ๋„๊ตฌ๋กœ ์—ด๊ธฐ", + "open-control-center": "์ œ์–ด ์„ผํ„ฐ", + "open-notification-history-enable-dnd": "์•Œ๋ฆผ ๊ธฐ๋ก", + "open-settings": "์„ค์ •", + "open-tray-dropdown": "์‹œ์Šคํ…œ ํŠธ๋ ˆ์ด", + "output-muted": "์ถœ๋ ฅ ์Œ์†Œ๊ฑฐ ์ „ํ™˜", + "power-profile": "{profile} ์ „์› ํ”„๋กœํ•„", + "previous-media": "์ด์ „ ํŠธ๋ž™", + "previous-month": "์ด์ „ ๋‹ฌ", + "refresh-devices": "์žฅ์น˜ ์ƒˆ๋กœ๊ณ ์นจ", + "refresh-wallhaven": "Wallhaven ๊ฒฐ๊ณผ ์ƒˆ๋กœ๊ณ ์นจ", + "refresh-wallpaper-list": "๋ฐฐ๊ฒฝํ™”๋ฉด ๋ชฉ๋ก ์ƒˆ๋กœ๊ณ ์นจ", + "remove-widget": "์œ„์ ฏ ์ œ๊ฑฐ", + "search-close": "๊ฒ€์ƒ‰ ๋‹ซ๊ธฐ", + "session-menu": "์„ธ์…˜ ๋ฉ”๋‰ด", + "show-all-devices": "๋ชจ๋“  ์žฅ์น˜ ํ‘œ์‹œ", + "switch-to-dark-mode": "๋‹คํฌ ๋ชจ๋“œ", + "switch-to-light-mode": "๋ผ์ดํŠธ ๋ชจ๋“œ", + "unmute": "์Œ์†Œ๊ฑฐ ํ•ด์ œ", + "up": "์ƒ์œ„ ๋””๋ ‰ํ† ๋ฆฌ", + "volume-at": "์ถœ๋ ฅ ๋ณผ๋ฅจ: {volume}%", + "wallpaper-selector": "๋ฐฐ๊ฒฝํ™”๋ฉด ์„ ํƒ๊ธฐ" + }, + "wallpaper": { + "browse": { + "empty-directory": "์ด ๋””๋ ‰ํ† ๋ฆฌ๋Š” ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.", + "go-root": "๋ฐฐ๊ฒฝํ™”๋ฉด ๋ฃจํŠธ๋กœ ์ด๋™", + "go-up": "์ƒ์œ„ ํด๋”๋กœ ์ด๋™", + "go-up-hint": "๋’ค๋กœ ๋ฒ„ํŠผ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒ์œ„๋กœ ์ด๋™ํ•˜์„ธ์š”." + }, + "configure-directory": "์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋Š” ๋ฐฐ๊ฒฝํ™”๋ฉด ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๊ตฌ์„ฑํ•˜์„ธ์š”.", + "fill-modes": { + "crop": "์ž๋ฅด๊ธฐ (์ฑ„์›€)", + "fit": "๋งž์ถค (ํฌํ•จ)", + "repeat": "๋ฐ˜๋ณต (ํƒ€์ผ)", + "stretch": "๋Š˜๋ฆฌ๊ธฐ" + }, + "no-match": "์ผ์น˜ํ•˜๋Š” ํ•ญ๋ชฉ์ด ์—†์Šต๋‹ˆ๋‹ค.", + "no-wallpaper": "๋ฐฐ๊ฒฝํ™”๋ฉด์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", + "panel": { + "apikey-help": "NSFW ์ฝ˜ํ…์ธ ์— ์•ก์„ธ์Šคํ•˜๋ ค๋ฉด API ํ‚ค๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.", + "apikey-label": "API ํ‚ค", + "apikey-managed-by-env": "NOCTALIA_WALLHAVEN_API_KEY ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ํ†ตํ•ด ๊ด€๋ฆฌ๋ฉ๋‹ˆ๋‹ค.", + "apikey-placeholder": "Wallhaven API ํ‚ค ์ž…๋ ฅ", + "appearance-dark-tab": "์–ด๋‘ก๊ฒŒ", + "appearance-light-tab": "๋ผ์ดํŠธ", + "categories-anime": "์• ๋‹ˆ๋ฉ”์ด์…˜", + "categories-label": "์นดํ…Œ๊ณ ๋ฆฌ", + "categories-people": "์ธ๋ฌผ", + "color-extraction-disabled": "๋ฐฐ๊ฒฝํ™”๋ฉด ์ƒ‰์ƒ ์ถ”์ถœ ์‚ฌ์šฉ", + "color-extraction-enabled": "์‚ฌ์ „ ์ •์˜๋œ ์ƒ‰์ƒ ๊ตฌ์„ฑํ‘œ ์‚ฌ์šฉ", + "header-devices-apply-all-tooltip": "๋ชจ๋‹ˆํ„ฐ๋ณ„ ๋ฐฐ๊ฒฝํ™”๋ฉด ์„ ํƒ", + "header-devices-per-monitor-tooltip": "๋ชจ๋“  ๋ชจ๋‹ˆํ„ฐ์— ์ ์šฉ", + "header-link-light-dark-tooltip": "ํ…Œ๋งˆ ๋ณ€๊ฒฝ ์‹œ ๋ฐฐ๊ฒฝํ™”๋ฉด ์œ ์ง€", + "header-separate-light-dark-tooltip": "ํ…Œ๋งˆ์— ๋”ฐ๋ผ ๋ฐฐ๊ฒฝํ™”๋ฉด ์ž๋™ ์ „ํ™˜", + "header-sun-linked-tooltip": "๋ฐ๊ณ  ์–ด๋‘์šด ๋ฐฐ๊ฒฝํ™”๋ฉด ๋ถ„๋ฆฌ", + "header-sun-separate-tooltip": "๋ฐ๊ณ  ์–ด๋‘์šด ๋ชจ๋“œ์— ๋™์ผํ•œ ๋ฐฐ๊ฒฝํ™”๋ฉด", + "order-asc": "์˜ค๋ฆ„์ฐจ์ˆœ", + "order-desc": "๋‚ด๋ฆผ์ฐจ์ˆœ", + "order-label": "์ˆœ์„œ", + "purity-label": "์ฝ˜ํ…์ธ  ํ•„ํ„ฐ", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "Sketchy", + "ratios-all-portrait": "๋ชจ๋“  ์„ธ๋กœ", + "ratios-all-wide": "๋ชจ๋“  ๊ฐ€๋กœ", + "ratios-any": "๋ชจ๋‘", + "ratios-label": "ํ™”๋ฉด๋น„", + "resolution-atleast": "์ตœ์†Œ", + "resolution-exact": "์ •ํ™•ํžˆ", + "resolution-label": "ํ•ด์ƒ๋„", + "resolution-mode-label": "๋ชจ๋“œ", + "solid-color-tooltip": "๋‹จ์ƒ‰ ๋ฐฐ๊ฒฝ", + "sort-date-asc": "์˜ค๋ž˜๋œ ์ˆœ์œผ๋กœ ์ •๋ ฌ", + "sort-date-desc": "์ตœ์‹  ์ˆœ์œผ๋กœ ์ •๋ ฌ", + "sort-name-asc": "์ด๋ฆ„์œผ๋กœ ์ •๋ ฌ (A-Z)", + "sort-name-desc": "์ด๋ฆ„์œผ๋กœ ์ •๋ ฌ (Z-A)", + "sort-random": "๋ฌด์ž‘์œ„๋กœ ์ •๋ ฌ", + "sorting-date-added": "์ถ”๊ฐ€๋œ ๋‚ ์งœ", + "sorting-favorites": "์ฆ๊ฒจ์ฐพ๊ธฐ", + "sorting-label": "์ •๋ ฌ ๊ธฐ์ค€", + "sorting-relevance": "๊ด€๋ จ์„ฑ", + "sorting-toplist": "์ธ๊ธฐ ๋ชฉ๋ก", + "sorting-views": "์กฐํšŒ์ˆ˜", + "source-label": "์†Œ์Šค", + "source-wallhaven": "Wallhaven", + "title": "๋ฐฐ๊ฒฝํ™”๋ฉด ์„ ํƒ๊ธฐ", + "wallhaven-settings-title": "Wallhaven ์„ค์ •" + }, + "transitions": { + "disc": "๋””์Šคํฌ", + "fade": "ํŽ˜์ด๋“œ", + "honeycomb": "๋ฒŒ์ง‘", + "pixelate": "ํ”ฝ์…€ํ™”", + "stripes": "์ŠคํŠธ๋ผ์ดํ”„", + "wipe": "์™€์ดํ”„" + }, + "try-different-search": "๋‹ค๋ฅธ ๊ฒ€์ƒ‰์–ด๋ฅผ ์‹œ๋„ํ•ด ๋ณด์„ธ์š”.", + "wallhaven": { + "loading": "๋ฐฐ๊ฒฝํ™”๋ฉด ๋กœ๋”ฉ ์ค‘...", + "no-results": "๋ฐฐ๊ฒฝํ™”๋ฉด์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๊ฒ€์ƒ‰์–ด๋ฅผ ์‹œ๋„ํ•ด ๋ณด์„ธ์š”.", + "page": "{total} ์ค‘ {current}", + "page-prefix": "ํŽ˜์ด์ง€", + "page-suffix": "{total}๊ฐœ ์ค‘" + } + }, + "weather": { + "clear-sky": "๋ง‘์Œ", + "drizzle": "์ด์Šฌ๋น„", + "fog": "์•ˆ๊ฐœ", + "mainly-clear": "๋Œ€์ฒด๋กœ ๋ง‘์Œ", + "overcast": "ํ๋ฆผ", + "partly-cloudy": "๊ตฌ๋ฆ„ ์กฐ๊ธˆ", + "rain-showers": "์†Œ๋‚˜๊ธฐ", + "snow": "๋ˆˆ", + "thunderstorm": "๋‡Œ์šฐ" + }, + "widgets": { + "color-picker": { + "palette-description": "๋‹ค์–‘ํ•œ ๋ฏธ๋ฆฌ ์ •์˜๋œ ์ƒ‰์ƒ ์ค‘์—์„œ ์„ ํƒํ•˜์„ธ์š”.", + "palette-label": "ํŒ”๋ ˆํŠธ", + "palette-theme-colors": "ํ…Œ๋งˆ ์ƒ‰์ƒ ํŒ”๋ ˆํŠธ์— ๋น ๋ฅด๊ฒŒ ์•ก์„ธ์Šคํ•ฉ๋‹ˆ๋‹ค.", + "title": "์ƒ‰์ƒ ์„ ํƒ๊ธฐ" + }, + "datetime-tokens": { + "ampm-lowercase": "์†Œ๋ฌธ์ž ์˜ค์ „/์˜คํ›„ (am/pm)", + "ampm-uppercase": "๋Œ€๋ฌธ์ž ์˜ค์ „/์˜คํ›„ (AM/PM)", + "common-12hour-time-minutes": "12์‹œ๊ฐ„์ œ ์‹œ๊ฐ„ ๋ฐ ๋ถ„", + "common-24hour-time-minutes": "24์‹œ๊ฐ„์ œ ์‹œ๊ฐ„ ๋ฐ ๋ถ„", + "common-24hour-time-seconds": "24์‹œ๊ฐ„์ œ ์‹œ๊ฐ„ ๋ฐ ์ดˆ", + "common-european-date": "์œ ๋Ÿฝ์‹ ๋‚ ์งœ ํ˜•์‹", + "common-iso-date": "ISO ๋‚ ์งœ ํ˜•์‹", + "common-us-date": "๋ฏธ๊ตญ์‹ ๋‚ ์งœ ํ˜•์‹", + "common-weekday-date": "์š”์ผ ๋ฐ ๋‚ ์งœ", + "common-weekday-month-day": "์š”์ผ, ์›” ๋ฐ ์ผ", + "day-abbreviated": "์ถ•์•ฝ๋œ ์š”์ผ ์ด๋ฆ„", + "day-full": "์ „์ฒด ์š”์ผ ์ด๋ฆ„", + "day-leading-zero": "์•ž์— 0์ด ์žˆ๋Š” ์ผ (01-31)", + "day-no-leading-zero": "์•ž์— 0์ด ์—†๋Š” ์ผ (1-31)", + "hour-leading-zero": "์•ž์— 0์ด ์žˆ๋Š” ์‹œ (00-23) โ€” 24์‹œ๊ฐ„ ํ˜•์‹", + "hour-no-leading-zero": "์•ž์— 0์ด ์—†๋Š” ์‹œ (0-23) โ€” 24์‹œ๊ฐ„ ํ˜•์‹", + "minute-leading-zero": "์•ž์— 0์ด ์žˆ๋Š” ๋ถ„ (00-59)", + "minute-no-leading-zero": "์•ž์— 0์ด ์—†๋Š” ๋ถ„ (0-59)", + "month-abbreviated": "์ถ•์•ฝ๋œ ์›” ์ด๋ฆ„", + "month-full": "์ „์ฒด ์›” ์ด๋ฆ„", + "month-number-leading-zero": "์•ž์— 0์ด ์žˆ๋Š” ์›” ๋ฒˆํ˜ธ (01-12)", + "month-number-no-zero": "์•ž์— 0์ด ์—†๋Š” ์›” ๋ฒˆํ˜ธ (1-12)", + "second-leading-zero": "์•ž์— 0์ด ์žˆ๋Š” ์ดˆ (00-59)", + "second-no-leading-zero": "์•ž์— 0์ด ์—†๋Š” ์ดˆ (0-59)", + "timezone-abbreviation": "์‹œ๊ฐ„๋Œ€ ์•ฝ์–ด", + "year-four-digit": "4์ž๋ฆฌ ์—ฐ๋„", + "year-two-digit": "2์ž๋ฆฌ ์—ฐ๋„ (00-99)" + }, + "file-picker": { + "item": "๊ฐœ ํ•ญ๋ชฉ", + "items": "๊ฐœ ํ•ญ๋ชฉ", + "search-placeholder": "ํŒŒ์ผ ๋ฐ ํด๋” ๊ฒ€์ƒ‰...", + "select-current": "ํ˜„์žฌ ํ•ญ๋ชฉ ์„ ํƒ", + "select-file": "ํŒŒ์ผ ์„ ํƒ", + "select-folder": "ํด๋” ์„ ํƒ", + "selected": "์„ ํƒ๋จ:", + "title": "ํŒŒ์ผ ์„ ํƒ๊ธฐ" + }, + "file-picker-title": "ํŒŒ์ผ ์„ ํƒ", + "icon-picker": { + "title": "์•„์ด์ฝ˜ ์„ ํƒ๊ธฐ" + }, + "tooltip-placeholder": "ํ”Œ๋ ˆ์ด์Šคํ™€๋”" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "์ต๋ช… ID", + "ca-cert": "CA ์ธ์ฆ์„œ", + "eap-method": "EAP ๋ฐฉ์‹", + "password": "์‚ฌ์šฉ์ž ๋น„๋ฐ€๋ฒˆํ˜ธ", + "phase2-auth": "2๋‹จ๊ณ„ ์ธ์ฆ", + "username": "ID / ์‚ฌ์šฉ์ž ์ด๋ฆ„" + }, + "panel": { + "action-required": "์กฐ์น˜ ํ•„์š”", + "add-hidden-network": "์ˆจ๊ฒจ์ง„ ๋„คํŠธ์›Œํฌ ์ถ”๊ฐ€", + "add-network": "๋„คํŠธ์›Œํฌ ์ถ”๊ฐ€", + "available-interfaces": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ธํ„ฐํŽ˜์ด์Šค", + "available-networks": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋„คํŠธ์›Œํฌ", + "disabled": "Wiโ€‘Fi๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋จ", + "disconnecting": "์—ฐ๊ฒฐ ํ•ด์ œ ์ค‘...", + "dns": "DNS", + "enable-message": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋„คํŠธ์›Œํฌ๋ฅผ ๋ณด๋ ค๋ฉด Wiโ€‘Fi๋ฅผ ํ™œ์„ฑํ™”ํ•˜์„ธ์š”.", + "enter-password": "Wiโ€‘Fi ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ...", + "forget": "์ง€์šฐ๊ธฐ", + "forget-network": "์ด ๋„คํŠธ์›Œํฌ ์ง€์šฐ๊ธฐ", + "forgetting": "์ง€์šฐ๋Š” ์ค‘...", + "hidden-network": "์ˆจ๊ฒจ์ง„ ๋„คํŠธ์›Œํฌ", + "interface": "๋„คํŠธ์›Œํฌ ์ธํ„ฐํŽ˜์ด์Šค", + "internet-connected": "์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ๋จ", + "internet-limited": "์ธํ„ฐ๋„ท ์—†์Œ", + "internet-status": "์ธํ„ฐ๋„ท ์ƒํƒœ", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "์•Œ๋ ค์ง„ ๋„คํŠธ์›Œํฌ", + "link-speed": "๋งํฌ ์†๋„", + "network-name-ssid": "๋„คํŠธ์›Œํฌ ์ด๋ฆ„ (SSID)", + "no-ethernet-devices": "์ด๋”๋„ท ์žฅ์น˜ ๊ฐ์ง€๋˜์ง€ ์•Š์Œ", + "no-networks": "Wiโ€‘Fi ๋„คํŠธ์›Œํฌ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ", + "saved": "์ €์žฅ๋จ", + "scan-again": "๋‹ค์‹œ ๊ฒ€์ƒ‰", + "searching": "๋„คํŠธ์›Œํฌ ๊ฒ€์ƒ‰ ์ค‘...", + "security-open": "์—ด๊ธฐ", + "security-wep": "WEP", + "security-wpa": "WPA/WPA2/WPA3 ๊ฐœ์ธ", + "security-wpa-ent": "WPA ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ", + "security-wpa2-ent": "WPA2 ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ", + "show-password": "๋น„๋ฐ€๋ฒˆํ˜ธ ํ‘œ์‹œ", + "title": "Wiโ€‘Fi" + }, + "signal": { + "excellent": "๋งค์šฐ ์ข‹์Œ", + "fair": "๋ณดํ†ต", + "good": "์ข‹์Œ", + "poor": "๋ถˆ๋Ÿ‰", + "weak": "์•ฝํ•จ" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/ku.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/ku.json new file mode 100644 index 0000000..b0c4901 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/ku.json @@ -0,0 +1,1861 @@ +{ + "actions": { + "clear-history": "Dรฎrokรช pak bike", + "close-app": "{app} bigire", + "connect-vpn": "Bi {name} ve girรช bide", + "cycle-visualizer": "Dรฎmenkerรช รงerxรช", + "disable-bluetooth": "Bluetooth รงalak neke", + "disable-dnd": "'Dengรช dernexรฎne' รงalak neke", + "disable-wifi": "Wi-Fi รงalak neke", + "disconnect-vpn": "Girรชdanรช bi {name} re qut bike", + "enable-bluetooth": "Bluetooth รงalak bike", + "enable-dnd": "'Dengรช dernexรฎne' รงalak bike", + "enable-wifi": "Wi-Fi รงalak bike", + "launcher-settings": "Sazkariyรชn destpรชkarรช", + "lower-to-bottom": "Bo jรชr daxรฎne", + "open-calendar": "Salnameyรช veke", + "open-display-settings": "Sazkariyรชn dรฎmenderรช", + "open-launcher": "Destpรชkarรช veke", + "open-settings": "Sazkariyan veke", + "raise-to-top": "Bo jor bilind bike", + "random-wallpaper": "Wรชneyรช dรฎwรชrรช rastehatรฎ", + "run-custom-command": "Fermana kesane bixebtรฎne", + "show-active-only": "Tenรช yรชn รงalak nรฎลŸan bide", + "show-all": "Hemรปyan nรฎลŸan bide", + "toggle-mute": "Guhertรฎna bรชdengkirinรช", + "widget-settings": "Sazkariyรชn sepanokรช" + }, + "authentication": { + "error": "ลžaลŸetiya rastkirinรช", + "failed": "Rastkirin tรชk รงรป" + }, + "bar": { + "active-window": { + "colorize-icons-description": "Rengรชn rรปkarรช li ser รฎkona รงarรงoveya รงalak bisepรฎne.", + "hide-mode-description": "Dema ku รงarรงove neรงalak be, kontrol dike ka sepanok รงawa tev digere.", + "scrolling-mode-description": "Dema ku ลŸemitandina nivรฎsรช ji bo sernavรชn รงarรงoveyรชn รชn dirรชj รงalak e, kontrol bike.", + "show-app-icon-description": "รŽkona sepanรช li kรชleka sernavรช รงarรงoveyรช nรฎลŸan bide.", + "show-app-icon-label": "รŽkona sepanรช nรฎลŸan bide" + }, + "audio-visualizer": { + "color-name-description": "Rengรช ji bo dรฎtbarรฎner hilbijรชre.", + "color-name-label": "Rengรช tijรฎkirinรช", + "hide-when-idle-description": "Dema ku รงalak be, dรฎtbarรฎner veลŸartรฎ ye heya ku lรฎstikvanek bi awayekรฎ รงalak were lรฎstin.", + "hide-when-idle-label": "Dema ku medyaya lรช nalรฎze veลŸรชre", + "width-description": "Ferehiya pรชkhateya kesane." + }, + "battery": { + "device-default": "Berdest (Amรปra dรฎmenderรช)", + "device-description": "Ka kรฎjan amรปrรช betarรฎ hilbijรชre ku were nรฎลŸandan.", + "device-label": "Amรปrรช betarรฎ", + "display-mode-description": "Hilbijรชre ka bila betarรฎ di darikรช de were xuyakirin", + "display-mode-graphic": "Betariyรช grafรฎkรฎ", + "display-mode-graphic-clean": "Betariyรช grafรฎkรฎ (bรช %)", + "display-mode-icon-always": "รŽkon - Her dem nรฎลŸan bide %", + "display-mode-icon-hover": "รŽkon - Li ser ลŸemitandinรช nรฎลŸan bide", + "display-mode-icon-only": "Tenรช รฎkon", + "hide-if-idle-description": "Dema ku betarรฎ neyรช tijรฎkirin an valakirin, sepanokรช veลŸรชre.", + "hide-if-idle-label": "Dema de bรชkar veลŸรชre", + "hide-if-not-detected-description": "Dema betarรฎ li ser pergalรช neyรช dรฎtin, sepanokรช veลŸรชre.", + "hide-if-not-detected-label": "Dema ku neyรช dรฎtin veลŸรชre", + "low-battery-threshold-label": "Asta hiลŸyariya betarรฎ ya kรชm", + "show-noctalia-performance-description": "Guhรชrbarรช awaya performansรช ya Noctalia di hundirรช destgeha betariyรช de nรฎลŸan bide.
Sรฎ รป anรฎmasyonan di Noctalia de neรงalak dike da ku bikaranรฎna รงavkaniyan kรชm bike.", + "show-noctalia-performance-label": "Guhรชrbarรช Performansa Noctalia nรฎลŸan bide", + "show-power-profile-description": "Hilbijartina profรฎla hรชzรช di hundirรช destgeha betariyรช de nรฎลŸan bide.", + "show-power-profile-label": "Kontrolรชn profรฎla hรชzรช nรฎลŸan bide" + }, + "clock": { + "clock-display-description": "NรฎลŸandana demjimรชra xwe bi riya tevlรฎkirina nรฎลŸanekan ji lรฎsteya jรชrรฎn kesane bike. Ji bo ku tu formata 12 demjimรชran bi kar bรฎnรฎ, divรช tu nรฎลŸana 'AP' tรชxรฎnรฎ nav.", + "clock-display-label": "NรฎลŸandana demjimรชrรช", + "custom-font-description": "Ji bo nรฎลŸanadana demjimรชrรช cรปrenivรฎseke kesane hilbijรชre.", + "custom-font-label": "Cรปrenivรฎsa kesane", + "custom-font-placeholder": "Cรปrenivรฎsa kesane hilbijรชre...", + "custom-font-search-placeholder": "Cรปrenivรฎsan bigere......", + "horizontal-bar-description": "SeriลŸte: Ji bo afirandina ลŸikandina rรชzรช, \\n bi kar bรฎne.", + "horizontal-bar-label": "Darika asoyรฎ", + "preview": "PรชลŸdรฎtin", + "tooltip-format-description": "Derdรช rรชzika ji bo sernavรช ku dema mirov li ser demjimรชrรช disekine tรช nรฎลŸandan. Ji bo bikaranรฎna sernavรช xwerรป vala bihรชle.", + "tooltip-format-label": "Formata sernavรช", + "use-custom-font-description": "Hilbijartina cรปrenivรฎsa berdest bi tรฎpeke kesane ji bo demjimรชrรช derbas bike.", + "use-custom-font-label": "Cรปrenivรฎsa kesane bi kar bรฎne", + "use-monospaced-font-description": "Dema ku รงalak be, demjimรชr wรช cรปrenivรฎsa monospace bi kar bรฎne.", + "use-monospaced-font-label": "Cรปrenivรฎsa monospace bi kar bรฎne", + "vertical-bar-description": "Bi kar tรฎnin valahiyekรช ji hev veqetรฎnin her beลŸek li ser rรชzeke nรป.", + "vertical-bar-label": "Darรช vertical" + }, + "control-center": { + "browse-file": "Pelรช bigere", + "browse-library": "Geroka pirtรปkxaneyรช", + "color-selection-description": "Rengรชn rรปkarkรช bo รฎkonan bisepรฎne.", + "enable-colorization-description": "ร‡alak bike rengkirina รฎkona navenda kontrolรช, rengรชn temayรช bi kar bรฎne.", + "icon-description": "รŽkonekรช ji pirtรปkxaneyรช an jรฎ dosyeyeke xwerรป hilbijรชre.", + "select-custom-icon": "รŽkoneke kesane hilbijรชre", + "use-distro-logo-description": "Li ลŸรปna รฎkoneke kesane, logoya belavkirina xwe bi kar bรฎne.", + "use-distro-logo-label": "Li ลŸรปna รฎkonรช logoya belavkirina bi kar bรฎne" + }, + "custom-button": { + "collapse-condition-description": "Ger nivรฎsa derketรฎ bi vรช nirxรช re tรชkildar be, dรช biลŸkok hilweลŸe.", + "collapse-condition-label": "RewลŸa hilweลŸรฎnรช", + "color-selection-description": "Rengรชn mรปqeyรช li รฎkon รป nivรฎsรช bi kar bรฎne.", + "display-command-output-description": "Fermanekรช binivรฎse ku di navberรชn rรชkรปpรชk de bixebite. Rรชza yekem a derketina wรช dรช wekรฎ nivรฎs were xuyang kirin.", + "display-command-output-label": "NรฎลŸandana derketina fermanรช", + "display-command-output-stream-description": "Fermanekรช binivรฎse ku bi berdewamรฎ bixebite.", + "dynamic-text": "Nivรฎsa dรฎnamรฎkรฎ", + "enable-colorization-description": "Rengรฎnkirinรช ji bo รฎkona biลŸkoka xwerรป รป nivรฎsรช รงalak bike, rengรชn temayรช bi kar bรฎne.", + "enable-colorization-label": "Rengรฎnkirinรช รงalak bike", + "hide-mode-always-expanded": "Her dem berferehkirรฎ", + "hide-mode-description": "Dema ku derana fermanรช tune be, dรฎtina sepanokรช kontrol dike.", + "hide-mode-expand-with-output": "Berfireh bike dema ku deran hebe", + "hide-mode-label": "Awaya veลŸartinรช", + "hide-mode-max-transparent": "Pir berfirehkirรฎ lรช zelal", + "icon-description": "รŽkonekรช ji pirtรปkxaneyรช hilbijรชre.", + "ipc-identifier-description": "Nasnameya yekta ji bo fermanรชn IPC. Vรช nasnameyรช bi 'qs -c noctalia-shell ipc call cb [รงalakรฎ] [nasname]' bikar bรฎnin da ku vรช biลŸkokรช bi rรชya IPC kontrol bikin.", + "ipc-identifier-label": "Nasnameya IPC", + "left-click-description": "Fermana ku dema biลŸkok li ser รงepรช tรช klรฎkkirin tรช meลŸandin.", + "left-click-label": "Klรฎka รงepรช", + "left-click-update-text": "Nรปvekirina nivรฎsa ku li ser klรฎka รงepรช tรช nรฎลŸandan", + "max-text-length-horizontal-description": "Jimara herรฎ pir ya tรฎpan ku di darika asoyรฎ de tรชn nรฎลŸandan (0 ji bo veลŸartina nivรฎsรช).", + "max-text-length-horizontal-label": "Dirรชjahiya herรฎ zรชde ya nivรฎsรช (horรฎzontal)", + "max-text-length-vertical-description": "Jimara herรฎ pir ya tรฎpan ku di darika stรปnรฎ de were nรฎลŸandan (0 ji bo veลŸartina nivรฎsรช).", + "max-text-length-vertical-label": "Dirรชjahiya herรฎ pir ya nivรฎsรช (stรปnรฎ)", + "middle-click-description": "Fermana ku dema biลŸkok bi miลŸkรช navรฎn tรช tikandin bรช meลŸandin.", + "middle-click-label": "Klรฎka navรฎn", + "middle-click-update-text": "Nรปvekirina nivรฎsa ku bi klรฎka navรฎn tรช nรฎลŸandan", + "parse-json-description": "Parskirina derketina fermanรช wekรฎ objeyek JSON da ku nivรฎs รป รฎkonรช bi awayekรฎ dรฎnamรฎkรฎ were danรฎn.", + "parse-json-label": "Deranรช wekรฎ JSON analรฎz bike", + "refresh-interval-description": "Navbera bi milรฎsaniyeyan.", + "refresh-interval-label": "Navbera nรปkirinรช", + "right-click-description": "Fermana ku dema biลŸkok bi aliyรช rastรช ve hat tikandin, tรช bicihkirin.", + "right-click-label": "Rast bikirtรฎnin", + "right-click-update-text": "Nรปkirina nivรฎsa ku li ser rast-klรฎkรช tรช nรฎลŸandan", + "show-icon-description": "Guherรฎna xuya รป ne xuya ya รฎkona widgetรช.", + "show-icon-label": "NรฎลŸanรช nรฎลŸan bide", + "text-stream-description": "Rรชzikรชn ji fermanรช hatine weลŸandin dรช wekรฎ nivรฎs li ser biลŸkokรช werin xuyandin.", + "text-stream-label": "Rรชz", + "wheel-description": "Fermana ku dema รงerxa miลŸkรช tรช bikaranรฎn were meลŸandin.
Ji bo delta รงerxa miลŸkรช di fermanรช de $delta bikar bรฎne.", + "wheel-down-description": "Fermana ku dema รงerxa miลŸkรช ber bi jรชr ve tรช gerandin, were meลŸandin.", + "wheel-down-label": "Fermana daketina รงerxรช", + "wheel-label": "Tekerรช gerandinรช", + "wheel-mode-separate-description": "Fermana cuda ji bo teker ber bi jor รป xwarรช รงalak bike.", + "wheel-mode-separate-label": "Fermanรชn รงerxรช yรชn cuda", + "wheel-up-description": "Fermana ku dema รงerxa miลŸkรช ber bi jor ve tรช gerandin, tรช meลŸandin.", + "wheel-up-label": "Fermana hilkiลŸรฎna รงerxรช", + "wheel-update-text": "Nรปkirina nivรฎsa ku li ser gerandinรช tรช nรฎลŸandan" + }, + "keyboard-layout": { + "show-icon-description": "NรฎลŸana ลŸรชweya klavyeyรช nรฎลŸan bide." + }, + "lock-keys": { + "hide-when-off-description": "Eger nexwan รงalak nabe nรฎลŸanek veลŸรชre.", + "hide-when-off-label": "VeลŸรชre gava ku ne รงalak e", + "show-caps-lock-description": "RewลŸa Caps Lock nรฎลŸan bide.", + "show-caps-lock-label": "Girtina hรปrfรชn mezรฎn", + "show-num-lock-description": "RewลŸa Num Lock nรฎลŸan bide.", + "show-num-lock-label": "Girtina jimareyan", + "show-scroll-lock-description": "RewลŸa Scroll Lock nรฎลŸan bide.", + "show-scroll-lock-label": "Girtina rรชรงรชkirinรช" + }, + "media-mini": { + "compact-mode-description": "ร‡alak bike sรชwirandinek ku cih digire ji bo panelรช lรฎstikvanรช medyayรช.", + "compact-mode-label": "Awaya kompakt", + "hide-mode-description": "Kontrola ku widget รงawa tevdigere dema ku tu medya nayรช lรฎstin.", + "max-width-description": "Mezinahiya herรฎ mezin a asoyรฎ ya sepanokรช saz dike. Sepanok wรช bi naveroka kurt re were biรงรปkkirin.", + "no-active-player": "Bรช lรฎstikvanรช รงalak", + "panel-section-description": "Xuyakirina xuyabรปn รป reftarรชn panelรช lรฎstikvanรช medyayรช.", + "panel-section-label": "Panelรช lรฎstikvanรช medyayรช", + "scrolling-mode-description": "Kontrola dema ku geroka nivรฎsรช ji bo sernavรชn rรชyรชn dirรชj tรช รงalak kirin.", + "scrolling-mode-label": "Moda Gerandinรช", + "show-album-art-description": "Wรชneyรช albรปmรช ya strana ku niha lรช tรช guhdarรฎkirin nรฎลŸan bide.", + "show-album-art-label": "Wรชneyรช albรปmรช nรฎลŸan bide", + "show-artist-first-description": "Hunermend - sernav li ลŸรปna sernav - hunermend nรฎลŸan bide.", + "show-artist-first-label": "PรชลŸรฎ hunermend nรฎลŸan bide", + "show-progress-ring-description": "PรชลŸandanek pรชลŸketina dorhรชlรฎ ya ku pรชลŸketina ลŸopรช nรฎลŸan dide.", + "show-progress-ring-label": "NรฎลŸanek xeleka pรชลŸketinรช bide", + "show-visualizer-description": "Dรฎmenek dรฎtbarรฎ ya dengรฎ dema muzรฎk lรชdixe nรฎลŸan bide.", + "show-visualizer-label": "NรฎลŸana dรฎtbarรฎkerรช bide", + "use-fixed-width-description": "Dema ku were รงalak kirin, widget dรช her dem firehiya herรฎ zรชde bikar bรฎne li ลŸรปna ku bi dรฎnamรฎkรฎ li gorรฎ naverokรช were sererast kirin.", + "use-fixed-width-label": "Bi berfirehรฎ bikar bรฎne", + "visualizer-type-description": "ลžรชwaza dรฎtbarรฎzera deng hilbijรชre ku were nรฎลŸandan.", + "visualizer-type-label": "Cureyรช dรฎtbarรฎker" + }, + "notification-history": { + "hide-widget-when-zero-description": "Dema ku ti agahdarรฎ tune be, รฎkona agahdarรฎyรช veลŸรชre.", + "hide-widget-when-zero-label": "รŽkonรช bรช agahdarรฎ veลŸรชre", + "hide-widget-when-zero-unread-description": "Dema ku ti agahdarรฎya nexwendรฎ tune be, รฎkona agahdarรฎyรช veลŸรชre.", + "hide-widget-when-zero-unread-label": "รŽkonรช bรช nexwendรฎ veลŸรชre", + "show-unread-badge-description": "NรฎลŸaneyekรช nรฎลŸan bide ku hejmara agahiyรชn nehatine xwendin nรฎลŸan dide.", + "show-unread-badge-label": "NรฎลŸana ne xwendรฎ nรฎลŸan bide", + "unread-badge-color-description": "Rengรช nรฎลŸana agahdariya nexwendรฎ hilbijรชre.", + "unread-badge-color-label": "Rengรช nรฎลŸana nexwendรฎ" + }, + "section-editor": { + "placeholder": "Sepanokekรช hilbijรชre ku lรช tevlรฎ bikรฎ...", + "search-placeholder": "Sepanokรช bigere..." + }, + "spacer": { + "width-description": "Firehiya firehiyรช bi pรฎkselan." + }, + "system-monitor": { + "compact-mode-description": "Statรฎstรฎkan wekรฎ ลŸรฎroveya barรชn piรงรปk nรฎลŸan bide li ลŸรปna nirxรชn nivรฎskรฎ. PรชลŸรฎ li guhertina layoutรช digire.", + "compact-mode-label": "Awaya kompakt", + "cpu-frequency-description": "Leza demjimรชra CPU ya heyรฎ di GHz de nรฎลŸan bide.", + "cpu-temperature-description": "Germahiya YKN ku hebe nรฎลŸan bide.", + "cpu-temperature-label": "Germahiya YKN", + "cpu-usage-description": "Rรชjeya bikaranรฎna CPU ya niha nรฎลŸan bide.", + "cpu-usage-label": "Bikaranรฎna YKN", + "disk-path-description": "Hilbijรชre kรฎjan xala รงiyayรช dรฎskรช were ลŸopandin.", + "disk-path-label": "Rรชya dรฎskรช", + "gpu-temperature-description": "Germahiya YKG ku hebe, nรฎลŸan bide.", + "load-average-description": "Barkirina navรฎn a pergalรช nรฎลŸan bide.", + "load-average-label": "Barkirina navรฎn", + "memory-percentage-description": "Bรฎranรฎnรช wekรฎ rรชjeyekรช nรฎลŸan bide, ne wekรฎ nirxรชn mutleq.", + "memory-percentage-label": "Bรฎr wekรฎ sedรฎ", + "memory-usage-description": "Agahiyรชn bikaranรฎna RAM'a niha nรฎลŸan bide.", + "memory-usage-label": "Bikaranรฎna bรฎrรช", + "network-traffic-description": "Leza leza barkirina torรช รป dakรชลŸana torรช nรฎลŸan bide.", + "network-traffic-label": "Trafรฎka torรช", + "storage-as-percentage-label": "Dรฎsk wekรฎ rรชje", + "storage-usage-description": "Agahiyรชn bikaranรฎna cรฎhรช dรฎskรช nรฎลŸan bide.", + "storage-usage-label": "Bikaranรฎna hilanรฎnรช", + "swap-usage-description": "Bikaranรฎna bรฎra swapรช nรฎลŸan bide.", + "swap-usage-label": "Bikaranรฎna Swapรช", + "use-monospace-font-description": "Bi kar tรฎne fonta monospace ji bo firehiya karakterรช ya domdar.", + "use-monospace-font-label": "Tรฎpa yek-firehรฎ" + }, + "taskbar": { + "colorize-icons-description": "Rengรชn temayรช li รฎkonรชn taskbarรช bicรฎh bike.", + "hide-mode-description": "Dema ku รงarรงoveyรชn lihevhatรฎ tune bin, sepanok รงawa tev digere kontrol dike", + "hide-mode-label": "Moda veลŸartinรช", + "icon-scale-description": "Pรฎvana รฎkonรชn taskbarรช destnรฎลŸan dike.", + "icon-scale-label": "Pรฎvandina รฎkonan", + "max-width-description": "Herรฎ zรชde firehiya taskbarรช wekรฎ rรชjeya firehiya ekranรช.", + "max-width-label": "Ferehiya herรฎ pir", + "only-active-workspaces-description": "Tenรช sepanรชn ji cihรชn kar รชn รงalak nรฎลŸan bide.", + "only-active-workspaces-label": "Tenรช ji cihรชn kar รชn รงalak", + "only-same-monitor-description": "Tenรช sepanรชn ji รงavdรชriya ku bar lรช ye nรฎลŸan bide.", + "only-same-monitor-label": "Tenรช ji heman monitorรช", + "show-pinned-apps-description": "NรฎลŸandana sepanรชn pรชรงandรฎ yรชn ji dockรช di taskbarรช de.", + "show-pinned-apps-label": "NรฎลŸandan sepanรชn pรชรงandรฎ", + "show-title-description": "Sernavรชn pencereyรช di taskbarรช de nรฎลŸan bide.", + "show-title-description-disabled": "Darika erkรช ya stรปnรฎ nรฎลŸandana sernavan piลŸtgirรฎ nake.", + "show-title-label": "Navรช pรชลŸandanรช", + "smart-width-description": "Bi awayekรฎ otomatรฎkรฎ firehiya tรชketinรช li gorรฎ hejmara tรชketinan eyar bike.", + "smart-width-label": "Firehiya berz", + "title-width-description": "Firehiya firehiya sernavรชn paceyan di taskbarรช de (bi pรฎksel).", + "title-width-label": "Firehiya sernavรช", + "title-width-reset-tooltip": "Firehiya firehiya sernavรช" + }, + "tray": { + "chevron-color-label": "Rengรช chevron", + "colorize-icons-description": "Rengรชn rรปkarรช li ser รฎkonรชn devera agahiyรช bisepรฎne.", + "colorize-icons-label": "รŽkonan rengรฎn bike", + "drawer-enabled-description": "Dema ku รงalak be, hรชmanรชn trayรช yรชn nehatine pin kirin di panelek dolabรช de tรชne xuyang kirin.
Dema ku neรงalak be, hemรฎ hรชmanรชn trayรช inline tรชne xuyang kirin.", + "drawer-enabled-label": "ร‡alak bike dolabรช", + "hide-passive-description": "Dema ku hat aktรฎfkirin, tiลŸtรชn trayรช yรชn bi statรปya 'Pasรฎf' dรช werin veลŸartin.", + "hide-passive-label": "VeลŸartina tiลŸtรชn pasรฎf" + }, + "volume": { + "display-mode-description": "Tu dixwazรฎ ev nirx รงawa xuya bike hilbijรชre." + }, + "workspace": { + "character-count-description": "Hejmara tรฎpรชn ku ji navรชn qada xebatรช bรชn nรฎลŸandan (1-10).", + "character-count-label": "Hejmara tรฎpan", + "empty-color-description": "Rengรช paลŸxaneya ji bo Workspaceyรชn val destnรฎลŸan bike.", + "empty-color-label": "Rengรช cรฎhรช kar รช vala", + "enable-scrollwheel-description": "Bi tekerรช miลŸkรช di navbera cรฎhรชn xebatรช de biguhere.", + "enable-scrollwheel-label": "BiลŸkoka gerokรช bizivirรฎne da ku cรฎhรชn kar biguherรฎ", + "focused-color-description": "Rengรช paลŸperdeyรช ji bo cรฎhรช karรช ku hatiye fokus kirin destnรฎลŸan bike.", + "focused-color-label": "Rengรช cรฎhรช kar รช fokusdayรฎ", + "follow-focused-screen-description": "Dรฎmรชn xebatรช ji dรฎmendera niha ya ku balรช dikiลŸรฎne, ne ji dรฎmendera ku bar lรช ye, nรฎลŸan bide.", + "follow-focused-screen-label": "ลžopandina ekrana fokusรช", + "grouped-border-opacity-description": "Ayarรช asta zelalbรปna sรฎnorรชn konteynerรช cรฎhรช kar.", + "grouped-border-opacity-label": "Rรปdana sรฎnor", + "hide-unoccupied-description": "Cihรชn xebatรช yรชn bรช pencere nรฎลŸan nede.", + "hide-unoccupied-label": "VeลŸartรฎ ne dagirkirรฎ", + "label-mode-description": "ร‡awa nรฎลŸaneyรชn cรฎhรช kar tรชne nรฎลŸandan hilbijรชre.", + "label-mode-label": "Moda labelรช", + "occupied-color-description": "Rengรช paลŸxaneyรช ji bo Workspaceรชn dagirkirรฎ destnรฎลŸan bike.", + "occupied-color-label": "Rengรช qada xebatรช ya dagirkirรฎ", + "reverse-scrolling-description": "Dema ku hรปn digerรฎnin, rรชgeha guhertina cรฎhรช kar berevajรฎ bike.", + "reverse-scrolling-label": "ลžemitandinรช berevajรฎ bike", + "show-applications-description": "NรฎลŸaneyรชn sepanan di hundirรช her cรฎhรช xebatรช de nรฎลŸan bide.", + "show-applications-label": "NรฎลŸan sepanan bide", + "show-badge-description": "Di moda komkirรฎ de nรฎลŸana jimareya cรฎhรช kar nรฎลŸan bide.", + "show-badge-label": "NรฎลŸana qada xebatรช nรฎลŸan bide", + "show-labels-only-when-occupied-description": "Tenรช etรฎketรชn cรฎhรช xebatรช nรฎลŸan bide dema ku ew pencereyan dihewรฎnin.", + "show-labels-only-when-occupied-label": "Tenรช dema dagirkirรฎ etรฎketan nรฎลŸan bide", + "unfocused-icons-opacity-description": "Ayarรช asta zelalbรปna รฎkonรชn sepanรชn ne di fokusรช de bike.", + "unfocused-icons-opacity-label": "ลžefafiya รฎkonรชn ne baldar" + } + }, + "battery": { + "battery-health": "RewลŸa betariyรช", + "battery-level": "Asta bataryayรช", + "capacity-level": "Kapasรฎte: {level}", + "charging-rate": "Rรชjeya barkirinรช: {rate} W", + "discharging-rate": "Rรชjeya valabรปna betariyรช: {rate} W", + "health": "Tendรปrรปstรฎ: {percent}%", + "inhibit-idle-description": "Pergalรช ลŸiyar dihรชle.", + "no-battery-detected": "nehat dรฎtin", + "plugged-in": "Hat girรชdan", + "power-profile": "Profรฎla hรชzรช", + "time-left": "Demรช mayรฎ: {time}", + "time-until-full": "Dem mayรฎ bo tijรฎbรปnรช: {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "Amรปrรชn heyรฎ", + "blocked": "Astengkirรฎ", + "connected-devices": "Amรปrรชn girรชdayรฎ", + "device-address": "NavnรฎลŸana amรปrรช", + "disabled": "Bluetooth neรงalak e", + "discoverable": "Dรฎtbar", + "enable-message": "Bo dรฎtina amรปrรชn heyรฎ Bluetooth รงalak bike.", + "known-devices": "Amรปrรชn naskirรฎ", + "no-devices": "Amรปr tune ne", + "paired-devices": "Amรปrรชn hevgirtรฎ", + "pairing-mode": "PiลŸtrast bike ku cรฎhaza te di moda cotbรปnรช de ye.", + "scanning": "Li amรปran digere...", + "signal-text-excellent": "NรฎลŸan: BaลŸ e gelek", + "signal-text-fair": "NรฎลŸan: Bihรชz e", + "signal-text-good": "NรฎลŸan: BaลŸ e", + "signal-text-poor": "NรฎลŸan: Lawaz e", + "signal-text-unknown": "NรฎลŸan: Nenas", + "signal-text-very-poor": "NรฎลŸan: Gelekรฎ lawaz e" + } + }, + "changelog": { + "error": { + "fetch-failed": "Nikare daneyรชn tรชketinรช guhertinรช bar bike. PaลŸรช dรฎsa biceribรฎne.", + "rate-limit": "Rรชjeya GitHub derbas bรป. Di nav รงend xulekanan de dรฎsa biceribรฎne." + }, + "panel": { + "buttons-discord": "Tรชkeve Discorda me", + "buttons-dismiss": "BaลŸ e", + "buttons-feedback": "Nerรฎnan bide", + "empty": "NรฎลŸeyรชn weลŸanรช hรฎn ne pรชkan in.", + "highlight-title": "Berbiroj", + "released": "Di {date} de hat weลŸandin", + "subtitle-fresh": "Spas ji bo sazkirina Noctalia! Ev tiลŸtรชn ku di vรช รงรชkirinรช de hene.", + "subtitle-updated": "Nรปkirin ji {previousVersion}", + "title": "ร‡i nรป di {versiyon} de heye", + "version": "Goherto {version}", + "version-new-user": "Sazkirina nรป" + } + }, + "common": { + "actions": "ร‡alakรฎ", + "add": "Tevlรฎ bike", + "appearance": "Xuyang", + "apply": "Bisepรฎne", + "automation": "Xweberรฎ", + "available": "Heyรฎ", + "back": "PaลŸ", + "battery": "Betarรฎ", + "bluetooth": "Bluetooth", + "brightness": "Ronahรฎ", + "browse": "Bigere", + "calendar": "Salname", + "calendar-panel": "Destgeha salnameyรช", + "cancel": "Tรชk bibe", + "cards": "Kert", + "charging": "Barkirin", + "clear": "Pak bike", + "clipboard": "Klรฎpbir", + "close": "Bigire", + "color-muted": "Bรชdengkirรฎ", + "colors": "Rengan", + "command": "Ferman", + "connect": "Girรชdan", + "connected": "Girรชdayรฎ", + "connecting": "Tรช girรชdan...", + "continue": "Berdewam", + "contributors": "BeลŸdar", + "copied-to-clipboard": "Hate bรฎra miqeyd kirin", + "countdown": "Jimartin", + "date": "Dรฎrok", + "default": "Xwerรป", + "delete": "Jรช bibe", + "devices": "Amรปr", + "disabled": "Nabe kar", + "discharging": "Deravkirin", + "disconnect": "Qutbรปn", + "disconnected": "Berdรฎtin qut bรปye", + "disconnecting": "Derdibirรฎne...", + "display-mode": "Awaya nรฎลŸandanรช", + "download": "Daxistin", + "duration": "Dirรชjahรฎ", + "edit": "Biguherรฎne", + "enabled": "ร‡alak", + "error": "ลžaลŸetรฎ", + "events": "Bรปyer", + "execute": "Bicรฎh bรฎne", + "faithful": "Dilsoz", + "focus": "Balรช bide", + "frequency": "Frekans", + "gateway": "Dergeh", + "general": "GiลŸtรฎ", + "height": "Bilindรฎ", + "hibernate": "Bicemdรฎne", + "history": "Dรฎrok", + "icon": "รŽkon", + "idle": "Betal", + "info": "Zanyarรฎ", + "input": "Ketan", + "install": "Sazkirin", + "installed": "Sazkirรฎ", + "interface": "Navrรป", + "internet": "รŽnternet", + "language": "Ziman", + "loading": "Tรช barkirin...", + "local": "Herรชmรฎ", + "location": "Cih", + "lock": "Kilรฎt bike", + "logout": "Derkeve", + "look": "Xuyang", + "margins": "Qirax", + "media": "Medya", + "media-player": "Lรฎstikvanรช medyayรช", + "memory": "Bรฎr", + "monitors": "Dรฎmender", + "network": "Tor", + "next": "PรชลŸ", + "night-light": "Ronahiya ลŸevรช", + "no": "Na", + "no-results": "Ti encam tune", + "no-summary": "Kurte tune ye", + "none": "Tune", + "not-found": "Nehat dรฎtin", + "notifications": "Agahdarรฎ", + "official": "Fermรฎ", + "on-surface": "Li ser rรปyรช erdรช", + "output": "Deran", + "pair": "Hevpar", + "paired": "Hevpar bรปyรฎ", + "pairing": "Hevberkirin...", + "panels": "Panelan", + "password": "ลžรฎfre", + "pause": "Rawestรฎne", + "pin": "BikรชลŸรฎne", + "play": "Bilรฎzรฎne", + "polling": "Lรชpirsรฎn", + "position": "Cih", + "previous": "Berรช", + "primary": "Serekรฎ", + "random": "Bรชserรปber", + "reboot": "Ji nรป dest pรช bike", + "record": "tomarkirinรช dest pรช bike", + "refresh": "Nรปkirin", + "required": "(pรชwรฎst)", + "reset": "Vegerandin", + "reset-to-default": "Ji bo rewลŸa berdest ji nรป ve saz bike", + "result-count": "{count} encam", + "result-count-plural": "{count} encaman", + "resume": "Berdewam kirin", + "save": "Tomar bike", + "scanning": "Digere...", + "screen-corners": "KiลŸekanรชn dรฎmenderรช", + "search": "Bigere", + "secondary": "Duyemรฎn", + "security": "Ewlehรฎ", + "select": "Hilbijรชre", + "select-color": "Rengรช hilbijรชre", + "select-icon-color": "Rengรช รฎkonรช hilbijรชre", + "shortcuts": "Kurterรช", + "shutdown": "Vemrรฎne", + "signal": "Sรฎnyal", + "sound": "Deng", + "sources": "ร‡avkanรฎ", + "start": "Destpรชkirin", + "stop": "Raweste", + "supporters": "PiลŸtgir", + "suspend": "Rawestรฎne", + "templates": "ลžablonan", + "tertiary": "Sรชyemรฎn", + "test": "Test", + "thresholds": "Asteyan", + "title": "Sernav", + "toast": "Toast", + "trusted": "Bawerรฎpรชkirรฎ", + "uninstall": "Rakirin", + "unknown": "Nenas", + "unpair": "Hevparekรช berdan", + "unpin": "Bi dar nexe", + "update": "Nรปkirin", + "upload": "Barkirin", + "version": "Guherto", + "vibrant": "GeลŸ", + "visualizer": "Dรฎmender", + "volume": "Hรชjmar", + "volumes": "Deng", + "wallpaper": "Wรชneyรช dรฎwรชr", + "weather": "Hewa", + "weather-loading": "RewลŸa bayรช tรช barkirin...", + "week": "Hefte", + "widgets": "Sepanok", + "width": "Firehรฎ", + "windows": "ร‡arรงove", + "yes": "Erรช" + }, + "control-center": { + "power-profile": { + "tooltip-action": "Power profile", + "tooltip-disabled": "Daemonรช power-profiles-daemon saz bike da ku profรฎlรชn hรชzรช bikar bรฎne" + }, + "wifi": { + "label-disconnected": "Wi-Fi qut bรป", + "label-ethernet": "Ethernet" + } + }, + "display-modes": { + "always-hide": "Her tim veลŸรชre", + "always-show": "Her tim nรฎลŸan bide", + "force-open": "BiลŸkรฎne veke", + "on-hover": "Li ser miลŸkรช" + }, + "hide-modes": { + "auto-hide": "Xweber veลŸรชre", + "hidden": "VeลŸรชre dema vala be", + "idle": "Dema bรชkar veลŸรชre", + "transparent": "Dema vala zelal e", + "visible": "Her tim xuya dike" + }, + "launcher": { + "categories": { + "all": "Hemรป", + "audiovideo": "Deng & vรฎdyo", + "chat": "Axaftin", + "development": "PรชลŸveรงรปn", + "education": "Perwerde", + "emoji-activity": "ร‡alakรฎ", + "emoji-animals": "Ajal", + "emoji-flags": "Ala", + "emoji-food": "Xwarin & vexwarin", + "emoji-nature": "Xweza", + "emoji-objects": "TiลŸt", + "emoji-people": "Mirov & beden", + "emoji-recent": "Dawรฎ", + "emoji-symbols": "Sembolan", + "emoji-travel": "Gerr รป cih", + "game": "Lรฎstik", + "graphics": "Grafรฎk", + "misc": "รŠn din", + "office": "Ofรฎs", + "pinned": "Bidarxistรฎ", + "system": "Pergal", + "webbrowser": "Geroka tevnรช" + }, + "providers": { + "applications": "Sepan", + "calculator": "Jimarkar", + "calculator-deprecated": "Fermana >calc hatiye betalkirin รป dรช di demeke nรชz de were rakirin. Ji kerema xwe rasterast di lรชgerรฎnรช de รฎfadeyรชn matematรฎkรช binivรฎse.", + "calculator-description": "Hesabker โ€” nirxandina รฎfadeyรชn matematรฎkรฎ", + "calculator-enter-expression": "Tรชketineke matematรฎkรฎ binivรฎse", + "calculator-press-enter-to-copy": "Enter bitikรฎne kopรฎ bike", + "clipboard": "Dรฎroka clipboardรช", + "clipboard-clear-description": "Hemรป dรฎroka clipboardรช paqij bike", + "clipboard-clear-description-full": "Hemรป tiลŸtan ji dรฎroka clipboardรช rake", + "clipboard-clear-history": "Dรฎroka clipboardรช paqij bike", + "clipboard-delete": "Jรชbirina navnรฎลŸa clipboardรช", + "clipboard-history-disabled": "Dรฎroka clipboardรช neรงalak e", + "clipboard-history-disabled-description": "Dรฎroka clipboardรช di mรฎhengan de รงalak bike an cliphist saz bike", + "clipboard-loading": "Dรฎroka clipboardรช tรช barkirin...", + "clipboard-search-description": "Dรฎroka jรชgirtindankรช bigere", + "command-description": "Fermanรชn shell bixebitรฎne", + "emoji": "Hilbijรชrรช รฎmojรฎ", + "emoji-loading": "รŽmojรฎ tรชn barkriin...", + "emoji-loading-description": "Tika raweste", + "emoji-no-recent": "Hรฎna emojiyรชn dawรฎ tune ne", + "emoji-search-description": "Emojiyan bigere รป jรช bigire", + "settings-search-description": "Li sazkariyan bigere", + "windows-search-description": "Pencereyรชn vekirรฎ bigere รป bรฎne pรชลŸ" + } + }, + "lock-screen": { + "authenticating": "Rastkirin...", + "password": "Borรฎnpeyva xwe tรชxรฎne..", + "restart": "Ji nรป ve dest pรช bike", + "shut-down": "Vemrรฎne" + }, + "notifications": { + "panel": { + "click-to-expand": "Bo dirรชjkirinรช bitikรฎne", + "description": "Agahdariyรชn te dema ku werin ew รช li vir xuya bibin.", + "no-notifications": "Agahdarรฎ tune ne" + }, + "range": { + "earlier": "Berรช", + "today": "รŽro", + "yesterday": "Duh" + }, + "time": { + "diff-d": "1 roj berรช", + "diff-dd": "{diff} roj berรช", + "diff-h": "1 demjimรชr berรช", + "diff-hh": "{diff} demjimรชr berรช", + "diff-m": "1 xulek berรช", + "diff-mm": "{diff} xulek berรช", + "now": "niha" + } + }, + "options": { + "bar": { + "density-comfortable": "Rรชhet", + "density-compact": "Kompakt", + "density-default": "Berdest", + "density-mini": "Piรงรปk", + "density-spacious": "Berfireh", + "display-mode-always-visible": "Her dem dรฎtbar", + "type-floating": "Herikbar", + "type-framed": "ร‡arรงovekirรฎ", + "type-simple": "Sade" + }, + "control-center": { + "quick-settings-style-classic": "Klasรฎk", + "quick-settings-style-modern": "Nรปjen" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "default": "Berdest" + }, + "launcher-view-mode": { + "grid": "Tor", + "list": "Lรฎste" + }, + "scrolling-modes": { + "always": "Her tim bigere", + "hover": "Li ser gerandinรช bigere", + "never": "Qet negere" + }, + "session-menu-grid-layout": { + "grid": "Tor", + "single-row": "Yek rรชz" + }, + "settings-panel-mode": { + "attached": "Panel bi bar ve girรชdayรฎ ye", + "centered": "Panela navendรฎkirรฎ", + "window": "Pencereya cuda" + }, + "visualizer-types": { + "linear": "Xรชzikรฎ", + "mirrored": "Neynikรฎkirรฎ", + "wave": "Pรชl" + }, + "visualizer-visibility": { + "with-background": "Tenรช bi paลŸxaneyรช" + }, + "workspace-labels": { + "index": "Naverok", + "index-and-name": "Naverok รป nav", + "name": "Nav" + } + }, + "panels": { + "about": { + "changelog": "Dรฎroka guhertinan bibรฎne", + "contributors-description": "Silav ji {count} beลŸkdarรชn me yรชn gewre re!", + "contributors-description-plural": "Spas ji {count} beลŸdarรชn me yรชn ecรชb!", + "copy-info": "Zanyariyan jรช bigire", + "info-copied": "Agahรฎ li clipboardรช hat kopรฎkirin", + "noctalia-desc": "ลžรชlekek sermaseyรช ya spehรฎ รป mรฎnรฎmal ku bi baldarรฎ ji bo Waylandรช hatiye รงรชkirin, bi Quickshellรช hatiye avakirin.", + "noctalia-git-commit": "PรชลŸkรชลŸkirina Git:", + "noctalia-installed-version": "Guhertoya sazkirรฎ:", + "noctalia-latest-version": "Guhertoya herรฎ dawรฎ:", + "noctalia-title": "Noctalia qalik", + "privacy-policy": "Siyaseta nepenรฎtiyรช", + "support": "PiลŸtgiriya me bike", + "supporter-badge": "PiลŸtgir", + "system-cpu": "YKN:", + "system-disk": "Dรฎsk:", + "system-gpu": "YKG:", + "system-host": "Host:", + "system-install-hint": "Fastfetch saz bike da ku agahiyรชn pergalรช bibรฎne", + "system-kernel": "Kernel:", + "system-loading": "Agahiyรชn pergalรช bar dikin...", + "system-memory": "Bรฎr:", + "system-monitor": "Dรฎmender:", + "system-not-installed": "fastfetch nehatiye sazkirin", + "system-os": "PX:", + "system-packages": "Pakรชt:", + "system-product": "Berhem:", + "system-title": "Zanyariyรชn pergalรช", + "system-uptime": "Dema xebitandinรช:", + "system-wm": "Rร‡:", + "telemetry-data-copied": "Daneyรชn Telemetry hatin jรช girtin bo jรชgirtindankรช", + "telemetry-desc": "Bi parvekirina agahdariya sรฎstema anonรฎm (รงareseriya ekranรช, berhevkar, belavkirin) alรฎkariya pรชลŸxistina Noctalia bikin. Carekรช di destpรชkรช de tรช ลŸandin, ลŸopandin tune, dane piลŸtรฎ 30 rojan bixweber tรชne jรชbirin.", + "telemetry-enabled": "Agahiyรชn pergalรช yรชn nenas biลŸรฎne", + "telemetry-show-data": "Dรฎtina daneyan", + "telemetry-title": "Taybetรฎ", + "title": "Derbar", + "up-to-date": "Tu rojane ye!", + "update-available": "Rojanekirin heye", + "view-commit": "Li ser GitHubรช guhertinรช bibรฎne" + }, + "audio": { + "devices-desc": "Amรปrรชn tรชketin รป derketina deng รชn berdest mรฎheng bike.", + "devices-input-device-description": "Amรปra tรชketina deng a xwestรฎ hilbijรชre.", + "devices-input-device-label": "Amรปra ketanรช", + "devices-output-device-description": "Amรปra derketina deng a xwestรฎ hilbijรชre.", + "devices-output-device-label": "Amรปra deranรช", + "devices-title": "Amรปrรชn deng", + "external-mixer-description": "NavnรฎลŸana ferman an rรชya sepanรช binivรฎse ku dema รงalakkirina taybetmendiya mรฎksera dengรฎ ya derveyรฎ were destpรชkirin.", + "external-mixer-label": "Fermana mรฎksera dengรฎ ya derveyรฎ", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "Serlรชdanรชn xwe yรชn medyayรช yรชn bijartรฎ รป yรชn ku te paลŸguh kirine destnรฎลŸan bike.", + "media-excluded-player-description": "Peyvรชn bingehรฎn รชn lรฎstikvanรชn ku hรปn dixwazin sรฎstem wan paลŸguh bike zรชde bikin. Divรช her peyvek bingehรฎn li ser rรชzeke nรป be.", + "media-excluded-player-label": "Lรฎstikvanรช derxistรฎ", + "media-excluded-player-placeholder": "binavรช substring binivรฎse รป + bitikรฎne", + "media-frame-rate-description": "Rรชjeyรชn bilindtir nermtir in lรช bรชtir รงavkaniyan bikar tรฎnin.", + "media-frame-rate-label": "Rรชjeya รงarรงoveyรช", + "media-primary-player-description": "Tรชgehek binivรฎse da ku lรฎstikvanรช xwe yรช sereke nas bike.", + "media-primary-player-label": "Lรฎstikvanรช sereke", + "media-primary-player-placeholder": "mรฎnakรฎ spotify, vlc, mpv", + "media-scrolling-speed-description": "Demjimรชra bi saniyeyan ku sernav ji destpรชkรช heta dawiyรช bigere.", + "media-scrolling-speed-label": "Leza gerandinรช", + "media-scrolling-title-description": "ร‡alak bike geroka domdar ji bo sernavรชn medyayรช yรชn dirรชj.", + "media-scrolling-title-label": "NavnรฎลŸana ku digere", + "media-title": "Lรฎstikvanรชn medyayรช", + "on-middle-clicked-description": "Fermana ku dema biลŸkok bi miลŸkรช navรฎn tรช tikandin, bรช rรชvebirin.", + "panel-applications-empty": "Ni sepanรชn ku niha deng lรชdixin tune ne", + "title": "Deng", + "visualizer-type-description": "Cureyeke cureyek dรฎtbarรฎkirinรช ji bo lรชdana medyayรช.", + "visualizer-type-label": "Curey vรฎzyonรช", + "volumes-desc": "Kontrolรชn deng รป astรชn bihรฎstwerรช sererast bikin.", + "volumes-input-volume-description": "Asta asta dengรช mรฎkrofonรช.", + "volumes-mute-input-description": "Dengรช tรชketina dengรฎ ya xwerรป (mรฎkrofon) bรชdeng bike.", + "volumes-mute-input-label": "Dengรช tรชketinรช bรชdeng bike", + "volumes-mute-output-description": "Dengรช sereke yรช pergalรช bรชdeng bike.", + "volumes-mute-output-label": "Dengรช derketinรช bรชdeng bike", + "volumes-output-volume-description": "Asta rรชjeya dengรช ya tevahiya pergalรช.", + "volumes-step-size-description": "Guhertina pรฎvana gavรช ji bo guhertinรชn deng (tekerรช miลŸkรช, kurteriyรชn klavyeyรช).", + "volumes-step-size-label": "Mezinahiya gava dengdanรช", + "volumes-volume-feedback-description": "Dema ku deng tรช sererastkirin dengekรฎ bersivdanรช lรชxe.", + "volumes-volume-feedback-label": "Dengรช berteka qebareyรช lรชxe", + "volumes-volume-overdrive-description": "Destรปr dide bilindkirina deng ji sedรฎ 100รฎ zรชdetir. Dibe ku ji hรชla hemรป hardware ve neyรช piลŸtgirรฎkirin.", + "volumes-volume-overdrive-label": "Destรปra zรชdekirina deng" + }, + "bar": { + "appearance-auto-hide-delay-description": "Demรช ku berรฎ darik were veลŸartin piลŸtรฎ miลŸรฎk diรงe", + "appearance-auto-hide-delay-label": "Derengbรปnรช veลŸรชre", + "appearance-auto-hide-exclusive-note": "NรฎลŸe: Dema ku VeลŸartina xweber รงalak be qada taybet tรช neรงalakkirin", + "appearance-auto-show-delay-description": "Demรช berรฎ ku darik tรช nรฎลŸandan dema mรฎลŸk dikeve qiraxรช", + "appearance-auto-show-delay-label": "Derengbรปnรช nรฎลŸan bide", + "appearance-background-opacity-description": "ลžeffafiya paลŸxanรช bi taybetรฎ ji bo barรฎ destnรฎลŸan bike.", + "appearance-background-opacity-label": "Zelaliya paลŸrรปyรช darikรช", + "appearance-capsule-opacity-description": "Dema kapsul xuya dibe, asta zelalbรปna paลŸrรปyรชn sepanokรช saz dike.", + "appearance-capsule-opacity-label": "Zelaliya kapsulรช", + "appearance-density-description": "Pรชdivรฎ ye ku pรชlava barรช were sererast kirin ji bo xuyangek berbiรงav an fireh.", + "appearance-density-label": "ร‡alyรฎ bar", + "appearance-desc": "Xuyakirina xuyabรปn รป pozรฎsyona bar.", + "appearance-display-mode-description": "Hilbijรชre ka kengรฎ bila darik were xuyakirin.", + "appearance-floating-description": "BariลŸ wekรฎ 'heb'eke herikbar nรฎลŸan bide.", + "appearance-floating-label": "Darika herรฎkbar", + "appearance-frame-radius": "Nรฎvรงapa Hundirรฎn", + "appearance-frame-settings-description": "Stรปrahiya รงarรงoveyรช รป nรฎvรงapa goลŸeyรช hundirรฎn sererast bike", + "appearance-frame-settings-label": "Sazkariyรชn ร‡arรงoveyรช", + "appearance-frame-thickness": "Qalindรฎ", + "appearance-hide-on-overview-description": "Dema ku pรชลŸdรฎtina berhevker รงalak be, bar veลŸรชre รป panelan bigire.", + "appearance-hide-on-overview-label": "Li ser dรฎtinรช bar veลŸรชre", + "appearance-margins-description": "Marginรชn dora bara herikbar eyar bike.", + "appearance-margins-horizontal": "Asoyรฎ", + "appearance-margins-vertical": "Stรปnรฎ", + "appearance-outer-corners-description": "GoลŸeyรชn derveyรฎ yรชn รงemandรฎ li ser barรฎ nรฎลŸan bide.", + "appearance-outer-corners-label": "GoลŸeyรชn derve", + "appearance-position-description": "Cihรช ku bar li ser ekranรช deyne hilbijรชre.", + "appearance-position-label": "Cihรช darikรช", + "appearance-show-capsule-description": "paลŸrรปyรชn sepanokรช nรฎลŸan bide.", + "appearance-show-capsule-label": "PรชลŸkรชลŸkirina kapsulรช", + "appearance-show-outline-description": "Dรฎwarรช xuya li dora her widgetรช nรฎลŸan dide.", + "appearance-show-outline-label": "Xรชzรชn sepanokรช nรฎลŸan bide", + "appearance-type-description": "Stรฎla darikรช hilbijรชre: Sade, Herikbar an ร‡arรงovekirรฎ.", + "appearance-type-label": "Cรปreya darikรช", + "appearance-use-separate-opacity-description": "Destรปrรช bide ku ji bo paลŸxaneya bar nirxek opakbรปna cuda were bikaranรฎn.", + "appearance-use-separate-opacity-label": "ลžefafiya bara cuda bikar bรฎne", + "monitor-configure-widgets": "Sepanokan saz bike", + "monitor-override-settings": "Mรฎhengรชn gerdรปnรฎ betal bike", + "monitor-override-settings-description": "Ji bo vรฎ monitorรช mรฎhengรชn xwerรป bikar bรฎne.", + "monitor-reset-all": "Hemรปyan vegerรฎne", + "monitor-widgets-title": "Rรชkxistina sepanokรช- {monitor}", + "monitors-desc": "NรฎลŸan bide bar li ser monitorรชn taybet. Eger tu kes neyรช hilbijartin, ew รช bi awayekรฎ xwerรป hemรปyan nรฎลŸan bide.", + "monitors-desc-new": "Mรฎheng bike ka kรฎjan รงavdรชr Barรช nรฎลŸan didin รป mรฎhengรชn ji bo her รงavdรชrekรช xwerรป bike.", + "monitors-title": "Dรฎmender", + "title": "Darik", + "tray-blacklist-description": "Zรชdekirina rรชgezรชn derxistina trayรช, piลŸtgiriya wildcardan (*) dike.", + "tray-blacklist-label": "ReลŸkirin", + "tray-blacklist-placeholder": "mรฎnakรฎ nm-applet, Fcitx*", + "tray-pin-application": "Sepanรช bi dar bixe", + "tray-unpin-application": "Sepanรช bi dar nexe", + "use-global-widgets": "Sepanokรชn gerdรปnรฎ bi kar bรฎne", + "widgets-desc": "sepanokan kaลŸ bike da ku rรชza wan biguherรฎnรฎ. Ji bo livandinรช sepanokan di navbera beลŸan de yan rakirina wan, menuya rast-tikandin bi kar bรฎne.", + "widgets-title": "Bicihkirina sepanokan" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "Ji dรฎwarรช xwe ลŸemayรชn rengan รงรชbike. Rengan bixweber derdixe da ku temayek hevgirtรฎ biafirรฎne.", + "color-source-use-wallpaper-colors-label": "Rengรชn dรฎwรชr bikar bรฎne", + "dark-mode-mode-description": "Veguhestina otomatรฎkรฎ di navbera moda ronahรฎ รป tarรฎ de รงalak dike.", + "dark-mode-mode-label": "Bernameya moda tarรฎ", + "dark-mode-mode-manual": "Bi destan", + "dark-mode-mode-off": "Vemirรฎ", + "dark-mode-switch-description": "Veguherรฎne bo temayek tarรฎtir ji bo dรฎtina hรชsantir bi ลŸev.", + "delete-error-description": "Xwe ji birina {scheme} bi ser neket", + "delete-error-title": "Serbirin nemรป ye", + "delete-success-description": "Bi serkeftรฎ {scheme} hat jรชbirin", + "delete-success-title": "ลžรชweya rengan hate jรชbirin", + "download-button": "Bรชtir daxรฎne", + "download-downloading": "Tรช daxistin...", + "download-empty": "Rengรชn rengan tune ne", + "download-error-api-error": "ร‡ewtiya API: {status}", + "download-error-description": "Daxistina {scheme} bi ser neket", + "download-error-download-failed": "Daxistin bi koda derketinรช ya: {code} tรชk รงรป", + "download-error-invalid-response": "Formata bersiva API ne derbasdar e", + "download-error-no-files": "Tu dosye ji bo ลŸemayรช nehatin dรฎtin", + "download-error-parse-failed": "ลžirovekirina bersiva API-yรช bi ser neket: {error}", + "download-error-rate-limit": "Rรชjeya daxwazรชn API ya GitHubรช derbas bรป", + "download-error-title": "Daxistin bi ser neket", + "download-fetching": "Digerandina ลŸemayรชn rengan รชn berdest...", + "download-success-description": "{scheme} bi serkeftรฎ hate daxistin", + "download-success-title": "ลžรชweya rengรช hate daxistin", + "download-title": "DakรชลŸkirina rengรชn rengรฎn", + "method-description": { + "content": "ลžemaya Sรชwirana Materyalรช bi derxistina rengรช ya bi kalรฎte ku bi rengรชn rastรฎ yรชn naveroka รงavkaniyรช re li hev tรช.", + "faithful": "Dema hewl dide ku nรชzรฎ rengรช bingehรฎn bimรฎne, di heman demรช de paletek hevaheng รงรชbike.", + "fruit-salad": "ลžemaya Materyal Dรฎzaynรช ku paletek dilรฎzรฎk, geลŸ bi rengรชn cihรชreng รป pirrengรฎ รงรชdike.", + "monochrome": "ลžemaya Materyal Dรฎzaynรช ya ku bi rengรช yekane ya gewr รป naveroka kromatรฎk a hindik bikar tรฎne.", + "muted": "Ji bo xuyangek aram รป neberรงav, rengรชn nermtir รป bรชrengtir รงรชdike.", + "rainbow": "ลžemaya Materyal Dรฎzaynรช ku ji bo pirrengiya herรฎ zรชde ya rengan, rengan li seranserรช spektrumรช รงรชdike.", + "tonal-spot": "Algorรฎtmaya default a Material Design. Bi bikaranรฎna guhertoyรชn tonal รชn rengรช รงavkaniyรช, paletรชn hevseng diafirรฎne.", + "vibrant": "Ji bo paletรชn berbiรงav รป balkรชลŸ รชn bi rengรชn geลŸ, tรชrbรปna rengan zรชde dike." + }, + "predefined-desc": "Ji berhevokek ลŸemayรชn rengan รชn pรชลŸdiyarkirรฎ hilbijรชrin.", + "predefined-generate-templates-label": "ลžablonan ji bo ลŸemayรชn pรชลŸdiyarkirรฎ รงรชbike", + "predefined-title": "ลžemayรชn rengan รชn pรชลŸdiyarkirรฎ", + "templates-desc": "Rengan li ser sepanรชn derveyรฎ bisepรฎne.", + "templates-filter-description": "ลžablonรชn ji kategoriyek taybet nรฎลŸan bide.", + "templates-filter-label": "Li gorรฎ kategoriyรช parzรปn bike", + "templates-misc-description": "ลžablonรชn xwe biafirรฎne.", + "templates-misc-label": "PรชลŸketรฎ", + "templates-misc-user-templates-description": "Tenรช eger tu dizanรฎ รงi dikรฎ รงalak bike โ€” serรฎ li belgekirina me ya serhรชl bide.", + "templates-misc-user-templates-label": "ลžablonรชn bikarhรชner รงalak bike", + "templates-none-detected": "TiลŸtek nehat dรฎtin", + "templates-write-path": "Dinivรฎse: {filepath}", + "title": "ลžรชweya rengan", + "wallpaper-method-description": "Rรชbaza xweya bijarte ya hilberรฎna paletรช hilbijรชre.", + "wallpaper-method-label": "Rรชbaza รงรชkirina paletรช", + "wallpaper-monitor-source-description": "Ji bo derxistina rengรชn dรฎwรชrรช, kรฎjan monitorรช bikar bรฎne hilbijรชre.", + "wallpaper-monitor-source-label": "ร‡avkaniya hilberรฎna rengan" + }, + "control-center": { + "cards-desc": "Xwerรป bike ka kรฎjan kontrol di navenda kontrolรช de รป bi รงi rรชzรช de xuya dibin.", + "desc": "Vesazkirina cih รป reftarรชn panelรช navenda kontrolรช.", + "position-description": "Dema ku destgeha navenda kontrolรช vebe, hilbijรชre ka li ku derรช bila xuya bibe.", + "shortcuts-custom-button-command-description": "Fermana ku dema ku biลŸkok tรช tikandin were meลŸandin.", + "shortcuts-custom-button-enable-on-state-logic-description": "ร‡alak bike รฎkonek duyem รป rewลŸa 'germ' li ser bingeha fermana kontrolรช.", + "shortcuts-custom-button-enable-on-state-logic-label": "Mantiqa rewลŸa-li-ser รงalak bike", + "shortcuts-custom-button-on-clicked-label": "Fermana klรฎka รงepรช", + "shortcuts-custom-button-on-state-command-description": "Fermana ku bรช meลŸandin ji bo kontrolkirina ka divรช biลŸkok di rewลŸa 'vekirรฎ' de be. Ji bo vekirรฎ 0, ji bo girtรฎ ne-sifir vedigerรฎne.", + "shortcuts-custom-button-on-state-command-label": "Fermana kontrola rewลŸa li ser", + "shortcuts-custom-button-on-state-icon-description": "รŽkona ji bo biลŸkokรช dema ku di rewลŸa 'vekirรฎ' de ye.", + "shortcuts-custom-button-on-state-icon-label": "รŽkona rewลŸa รงalak", + "shortcuts-custom-button-state-checks-add": "RewลŸa dewletรช zรชde bike", + "shortcuts-custom-button-state-checks-command": "Fermana ji bo vรช kontrola rewลŸรช were meลŸandin", + "shortcuts-custom-button-state-checks-label": "Kontrolรชn dewletรช", + "shortcuts-custom-button-state-checks-remove": "Rake", + "shortcuts-custom-button-tooltip-description": "SeriลŸteya ku dema miลŸk li ser biลŸkokรช be tรช nรฎลŸandan.", + "shortcuts-custom-button-tooltip-label": "Serpรชhatรฎ", + "shortcuts-title": "Kurteriyรชn sepanokan", + "system-monitor-disk-path-description": "Hilbijรชre kรฎjan xala รงiyayรช dรฎskรช karta รงavdรชriya pergalรช ya di navenda kontrolรช de divรช รงavdรชriyรช bike.", + "system-monitor-disk-path-label": "Rรชya dรฎska รงavdรชriya pergalรช", + "title": "Navenda kontrolรช" + }, + "desktop-widgets": { + "clock-enabled-description": "Sepanokeke demjimรชrรช li ser sermaseyรช nรฎลŸan bide.", + "clock-enabled-label": "Sepanoka demjimรชrรช รงalak bike", + "clock-format-label": "Format", + "clock-height-description": "Bilindahiya sepanokรช demjimรชrรช bi pixelan.", + "clock-height-label": "Bilindรฎ", + "clock-minimal-mode-description": "Dรฎmendereke demjimรชrรช ya herรฎ kรชm bi dem รป dรฎrokรช bi kar bรฎne.", + "clock-minimal-mode-label": "Moda herรฎ kรชm", + "clock-rounded-corners-description": "Quncikรชn gilover ji bo paลŸrรปyรช sepanokรช bi kar bรฎne.", + "clock-rounded-corners-label": "Quncikรชn gilover", + "clock-show-background-description": "PaลŸrรปya konteynera sepanoka demjimรชrรช nรฎลŸan bide.", + "clock-show-background-label": "PaลŸrรปyรช nรฎลŸan bide", + "clock-show-date-description": "Dรฎroka รฎro li jรชr demjimรชrรช nรฎลŸan bide.", + "clock-show-date-label": "Dรฎrokรช nรฎลŸan bide", + "clock-show-seconds-description": "ร‡irkeyan di demรช de nรฎลŸan bide.", + "clock-show-seconds-label": "ร‡irkeyan nรฎลŸan bide", + "clock-style-analog": "Analog", + "clock-style-binary": "Dรปalรฎ", + "clock-style-description": "ลžรชwaza nรฎลŸandana demjimรชrรช hilbijรชre.", + "clock-style-digital": "Jimarรฎ", + "clock-style-label": "ลžรชwaza demjimรชrรช", + "clock-style-minimal": "Kรชmtirรฎn", + "clock-width-description": "Ferehiya sepanoka demjimรชrรช bi pรฎkselan.", + "edit-mode-button-label": "Tรชkeve awaya guhertinรช", + "edit-mode-controls-explanation": "ร‡ep-bitikรฎne & kaลŸ bike: Sepanokรช bilivรฎne yan mezinahiya wรช biguherรฎne. Rast-bitikรฎne: Vebijรชrkรชn menuya naverokรช veke.", + "edit-mode-description": "Awaya guhertinรช รงalak bike da ku sepanokรชn sermaseyรช bilivรฎnรฎ รป ji nรป ve bicih bikรฎ . Dema ku รงalak be, sepanok xรชzek kaลŸkirinรช nรฎลŸan dide รป dikare ji nรป ve were bicihkirin.", + "edit-mode-exit-button": "Ji awaya guhertinรช derkeve", + "edit-mode-grid-snap-label": "Girรชdana torรช", + "edit-mode-label": "Awaya guhertinรช", + "enabled-description": "Sepanokรชn sermaseyรช bi tevahรฎ รงalak an neรงalak bike.", + "enabled-label": "Sepanokรชn sermaseyรช รงalak bike", + "general-desc": "Sepanokรชn ku li ser sermaseya te xuya dibin birรชkxistin bike.", + "general-title": "Sepanokรชn sermaseyรช", + "media-player-enabled-description": "Sepanokรช lรฎstikvanรช medyayรช li ser sermaseyรช nรฎลŸan bide.", + "media-player-enabled-label": "Sepanokรช lรฎstikvanรช medyayรช รงalak bike", + "media-player-rounded-corners-description": "Quncikรช giolver li ser qiraxรชn sepanokรช รงalak bike.", + "media-player-show-album-art-description": "Wรชneyรช bergรช elbumรช รป zanyariyรชn stranรช (nav รป hunermend) nรฎลŸan bide.", + "media-player-show-album-art-label": "Wรชneyรช bergรช รป sernavรช nรฎลŸan bide", + "media-player-show-background-description": "Konteynerรช paลŸrรปyรช nรฎลŸan bide.", + "media-player-show-buttons-description": "BiลŸkokรชn kontrola medyayรช (lรฎstin/rawestandin, pรชลŸ, paลŸ) nรฎลŸan bide.", + "media-player-show-buttons-label": "BiลŸkokan nรฎลŸan bide", + "media-player-show-visualizer-description": "overlayรช dรฎtbarรฎkarรช dengรฎ nรฎลŸan bide.", + "media-player-visualizer-type-description": "Cรปreyeke dรฎtbariyรช hilbijรชre.", + "system-stat-layout-bottom": "BiลŸkok", + "system-stat-layout-description": "Choose how the legend is displayed relative to the graph.", + "system-stat-layout-label": "ลžรชwaz", + "system-stat-layout-side": "Kรชlek", + "system-stat-rounded-corners-description": "Quncikรชn girover ji bo paลŸrรปya sepanokรช bi kar bรฎne.", + "system-stat-rounded-corners-label": "Quncikรชn gilover", + "system-stat-show-background-description": "Ji bo sepanokรช amara pergalรช konteynerรช paลŸrรปyรช nรฎลŸan bide.", + "system-stat-show-background-label": "PaลŸrรปyรช nรฎลŸan bide", + "system-stat-stat-type-description": "Hilbijรชre kรฎjan amara pergalรช bila were nรฎลŸandan.", + "system-stat-stat-type-label": "Cรปreya amarรฎ", + "title": "Sepanokรชn sermaseyรช", + "weather-enabled-description": "Sepanokรช rewลŸa bayรช li ser sermaseyรช nรฎลŸan bide.", + "weather-enabled-label": "Sepanokรช rewลŸa bayรช รงalak bike", + "weather-show-background-description": "Konteynera paลŸrรปyรช ya sepanokรช rewลŸa bayรช nรฎลŸan bide." + }, + "display": { + "monitors-brightness-step-description": "Pรฎvana gava guhertinรชn ronahiyรช (tekerรช miลŸkรช รป kurteriyรชn klavyeyรช) eyar bike.", + "monitors-brightness-step-label": "Mezinahiya gava ronahiyรช", + "monitors-brightness-unavailable-ddc-disabled": "Kontrola ronahiyรช peyda nabe. Ji bo kontrolkirina ronahiya vรช dรฎmenderรช, \"PiลŸtgiriya ronahiya derveyรฎ\" รงalak bike.", + "monitors-brightness-unavailable-generic": "Kontrola ronahiyรช ji bo vรช dรฎmรชrรช berdest nรฎne.", + "monitors-desc": "Guherandinรชn ronahiya mรฎhengรชn ji bo her dรฎmenderรช.", + "monitors-enforce-minimum-description": "ร‡areseriya pirsgirรชka ku ronahiya paลŸ di hin dรฎmendรชran de di ronahiya %0 de bi temamรฎ dimire.", + "monitors-enforce-minimum-label": "Bi zorรช ronahiyรช (1%) bide sepandin", + "monitors-external-brightness-description": "PiลŸtgiriya DDCUtil ji bo kontrolkirina ronรฎbรปnรช li ser dรฎmenderรชn derveyรฎ bi riya protokola DDC/CI รงalak bike.", + "monitors-external-brightness-label": "PiลŸtgiriya ronahiya derve", + "monitors-title": "Sazkariyรช bo her dรฎmender", + "night-light-auto-schedule-description": "Li gorรฎ dema rojavabรปn รป rohilatbรปna rojรช li {cih} โ€” pรชลŸniyarkirรฎ.", + "night-light-auto-schedule-label": "Bernamekirina otomatรฎk", + "night-light-desc": "Kรชmkirina belavbรปna ronahiya ลŸรฎn ji bo ku hรปn รงรชtir razรชn รป zexta รงavan kรชm bikin.", + "night-light-enable-description": "Fรฎltreyeke rengรช germ bikar bรฎne da ku belavbรปna ronahiya ลŸรฎn kรชm bike.", + "night-light-enable-label": "Ronahiya ลŸevรช รงalak bike", + "night-light-force-activation-description": "Bernameyรช paลŸguh dike รป fรฎltra ลŸevรช yekser tetbรฎq dike.", + "night-light-force-activation-label": "ร‡alakkirina bide zorkirin", + "night-light-manual-schedule-description": "Demjimรชrรชn kesane ji bo rojhilatbรปn รป rojavabรปnรช Saz bike.", + "night-light-manual-schedule-label": "Bernamekirina destรฎ", + "night-light-manual-schedule-select-start": "Dema destpรชkirinรช hilbijรชre", + "night-light-manual-schedule-select-stop": "Dema dawรฎbรปnรช hilbijรชre", + "night-light-manual-schedule-sunrise": "Dema rojhilatbรปnรช", + "night-light-manual-schedule-sunset": "Dema rojavabรปnรช", + "night-light-temperature-day": "Roj", + "night-light-temperature-day-description": "Kontrola germa di dema rojรช de dike.", + "night-light-temperature-description": "Germahiya rengรช ji bo ลŸev รป rojรช destnรฎลŸan bike.", + "night-light-temperature-label": "Germa rengan", + "night-light-temperature-night": "ลžev", + "night-light-temperature-night-description": "Germahiya di dema ลŸevรช de kontrol dike.", + "title": "Dรฎmender" + }, + "dock": { + "appearance-background-opacity-description": "Zelalbรปna paลŸrรปyรช benderรช biguherรฎne.", + "appearance-border-radius-description": "Radyusa sรฎnorรช benderรช biguherรฎne.", + "appearance-border-radius-label": "Radyusa sรฎnorรช", + "appearance-colorize-icons-description": "Rengรชn rรปkarรช li ser รฎkonรชn sepanรช bisepรฎne (tenรช sepanรชn bal-nekiลŸandรฎ).", + "appearance-colorize-icons-label": "รŽkonan bi reng bike", + "appearance-dead-opacity-description": "Zelalbรปna รฎkonรชn sepanรชn ku naxebitin biguherรฎne.", + "appearance-dead-opacity-label": "Zelalbรปna mirรฎ", + "appearance-desc": "XweลŸikahรฎ รป xuyabรปna dockรช xweลŸ bike.", + "appearance-display-auto-hide": "Xweber veลŸรชre", + "appearance-display-description": "Hilbijรชre ka bender รงawa tev digere.", + "appearance-display-exclusive": "Taybet", + "appearance-floating-distance-description": "Dรปrahiya di navbera benderรช รป qiraxa dรฎmenderรช de saz bike.", + "appearance-floating-distance-label": "Dรปrahiya herikรฎnรช ya benderรช", + "appearance-hide-show-speed-description": "Lezbรปna anรฎmasyona veลŸartin/nรฎลŸandan a benderรช biguherรฎne.", + "appearance-hide-show-speed-label": "Lezรช nรฎลŸan bide/veลŸรชre", + "appearance-icon-size-description": "Mezinahiya giลŸtรฎ ya dockรช sererast bike.", + "appearance-icon-size-label": "Mezinahiya benderรช", + "appearance-inactive-indicators-description": "Pรฎlรปlรชn nรฎลŸanderรช ji bo hemรป sepanan, ne tenรช ya niha รงalak, nรฎลŸan bide.", + "appearance-inactive-indicators-label": "NรฎลŸaneyรชn xebatรช", + "appearance-pinned-static-description": "Her gav รฎkonรชn sepanรชn pรชvekirรฎ bi rรชza statรฎk ber bi รงepรช ve bikiลŸรฎne.", + "appearance-pinned-static-label": "Serรฎlรชdanรชn sabรฎtkirรฎ yรชn statรฎk", + "appearance-position-description": "Cihรช ku bender li ser dรฎmenderรชรช xuya dibe hilbijรชre.", + "appearance-position-label": "Cih", + "enabled-description": "NรฎลŸan bide an jรฎ bi tevahรฎ dokรช veลŸรชre.", + "enabled-label": "Dock รงalak bike", + "monitors-desc": "NรฎลŸan bide dock li ser รงavdรชrรชn taybet. Eger tu neyรช hilbijartin, ew bi awayekรฎ xwerรป li ser hemรปyan e.", + "monitors-only-same-monitor-description": "Tenรช sepanรชn ji รงavdรชriya ku dock lรช ye nรฎลŸan bide.", + "monitors-only-same-monitor-label": "Tenรช sepanรชn ji heman monitorรช", + "monitors-title": "PรชลŸangeha รงavdรชriyรช", + "title": "Bender" + }, + "general": { + "copy-settings": "Sazkariyan jรช bigire", + "fonts-default-description": "Cรปrenivรฎsa sereke ku li seranserรช navrรปyรช tรช bikaranรฎn.", + "fonts-default-label": "Cรปrenivรฎsa berdest", + "fonts-default-placeholder": "Cรปrenivรฎsa berdest hilbijรชre...", + "fonts-default-scale-description": "Mezinahiya nivรฎsa standard mezin an biรงรปk bike.", + "fonts-default-scale-label": "Mezinahiya cรปrenivรฎsรช", + "fonts-default-search-placeholder": "Cรปrenivรฎsรช bigere...", + "fonts-desc": "Cรปrenivรฎsa ku di seranserรช navrรปyรช de tรชn bikaranรฎn hilbijรชre.", + "fonts-monospace-description": "Cรปrenivรฎsa monospace ji bo nรฎลŸandana jimar รป amaran tรช bikaranรฎn.", + "fonts-monospace-label": "Cรปrenivรฎsa monospace", + "fonts-monospace-placeholder": "Cรปrenivรฎsa monospace hilbijรชre...", + "fonts-monospace-scale-description": "Mezinahiya nivรฎsa monospace mezin an biรงรปk bike.", + "fonts-monospace-scale-label": "Mezinahiya cรปernivรฎsa monospace", + "fonts-monospace-search-placeholder": "Lรชgerรฎn li fonta monospace...", + "fonts-title": "Cรปrenivรฎs", + "keybinds-down": "Bilivรฎne jรชr", + "keybinds-left": "Bilivรฎne รงep", + "keybinds-right": "Bilivรฎne rast", + "language-desc": "Zimanรช xwe yรช daxwazkirรฎ ji bo sepanรช hilbijรชre.", + "language-select-auto-detect": "Xweber", + "language-select-description": "Zimanรช ku di navrรปya sepanรช de tรช bikaranรฎn hilbijรชre.", + "language-select-label": "Zimanรช sepanรช", + "launch-setup-wizard": "Sรชrbazรช sazkirinรช", + "profile-desc": "Hรปrgilyรชn bikarhรชner รป avatarรช xwe biguherรฎne.", + "profile-picture-description": "Wรชneyรช profรฎla we ku li seranserรช navrรปyรช xuya dibe.", + "profile-picture-label": "Wรชneyรช profรฎlรช", + "profile-select-avatar": "Wรชneyรช avatarรช hilbijรชre", + "profile-title": "Profรฎl", + "profile-tooltip": "Wรชneyรช profรฎlรช", + "screen-corners-desc": "Xรชzkirina goลŸeyรชn ekranรช รป bandorรชn dรฎtbarรฎ xweลŸ bike.", + "screen-corners-radius-description": "GoลŸeyรชn giloverkirรฎ yรชn ekranรช eyar bike.", + "screen-corners-radius-label": "Radyusa quncikรชn dรฎmenderรช", + "screen-corners-radius-reset": "Radiusรช goลŸeyรชn ekranรช ji nรป ve saz bike", + "screen-corners-show-corners-description": "GoลŸeyรชn giloverkirรฎ li qiraxa ekranรช nรฎลŸan bide.", + "screen-corners-show-corners-label": "NรฎลŸandan quncikรชn ekranรช", + "screen-corners-solid-black-description": "ReลŸekรฎ zexm bikar bรฎne li ลŸรปna rengรช paลŸnavรช bar.", + "screen-corners-solid-black-label": "GoลŸeyรชn reลŸ รชn zexm", + "screen-corners-title": "Quncikรชn dรฎmenderรช", + "settings-copied": "Sazkarรฎ hatin jรช girtin bo jรชgirtindankรช", + "tab-basics": "Bingehรฎn" + }, + "hooks": { + "info-command-info-description": "โ€ข Ferman bi rรชya shellรช (sh -lc) tรชn meลŸandin
โ€ข Ferman di paลŸperdeyรช de dimeลŸin (veqetandรฎ)
โ€ข BiลŸkokรชn ceribandinรช bi nirxรชn heyรฎ dimeลŸin", + "info-parameters-description": "โ€ข Hooka dรฎwรชr: $1 = rรชya dรฎwรชr, $2 = navรช ekranรช
โ€ข Hooka guherรฎna mijarรช: $1 = rast/derew (rewลŸa moda tarรฎ)
โ€ข Hookรชn kilรฎtkirin/vekirina ekranรช: Parametre tune
โ€ข Hookรชn moda performansรช: Parametre tune
โ€ข Hooka daniลŸรฎnรช: $1 = รงalakรฎ (girtin/ji nรป ve destpรชkirin)", + "info-parameters-label": "Parametreyรชn berdest", + "noctalia-started-description": "Fermana ku dema Noctalia barkirina xwe qedand pรชk were.", + "noctalia-started-label": "Noctalia hรชla destpรชkirin", + "noctalia-started-placeholder": "mรฎnakรฎ notify-send 'Noctalia hate barkirin'", + "performance-mode-disabled-description": "Fermana ku dema moda performansรช ya Noctalia bรชbandor dibe, tรช meลŸandin.", + "performance-mode-disabled-label": "Rewaya kar rawestayรฎ ye", + "performance-mode-disabled-placeholder": "mรฎnakรฎ notify-send \"Performans\" \"Moda neรงalak\"", + "performance-mode-enabled-description": "Fermana ku dema moda performansรช ya Noctalia were รงalak kirin, were meลŸandin.", + "performance-mode-enabled-label": "Hรชza xebatรช aktรฎf bรป", + "performance-mode-enabled-placeholder": "mรฎnakรฎ notify-send \"Performans\" \"Moda รงalak\"", + "screen-lock-description": "Fermana ku dema ekran tรช kilรฎtkirin dรช were meลŸandin.", + "screen-lock-label": "Ekrana kilรฎtkirรฎ ye", + "screen-lock-placeholder": "mรฎnakรฎ notify-send \"Dรฎmender\" \"Kilรฎtkirรฎ\"", + "screen-unlock-description": "Fermana ku dema ekrana kilรฎtkirรฎ vebe, bรช tetbรฎqkirin.", + "screen-unlock-label": "Ekrana vekirรฎ ye", + "screen-unlock-placeholder": "mรฎnakรฎ notify-send \"Screen\" \"Unlocked\"", + "session-description": "Fermana ku berรฎ girtin an ji nรป ve destpรชkirinรช were darve kirin. Cureya รงalakiyรช wekรฎ $1 (girtin/ji nรป ve destpรชkirin) distรฎne.", + "session-label": "Dawiya rรปniลŸtinรช", + "session-placeholder": "mรฎnakรฎ notify-send \"Session\" \"$1\"", + "system-hooks-desc": "Fermanan saz bike ku dema bรปyerรชn pergalรช รงรชdibin werin darvekirin.", + "system-hooks-enable-description": "Hemรป fermanรชn hook รงalak bike an jรฎ neรงalak bike.", + "system-hooks-enable-label": "Hooks รงalak bike", + "system-hooks-title": "Sรฎstem hooks", + "theme-changed-description": "Fermana ku dema mijar di navbera Moda Tarรฎ รป Ronahรฎ de diguhere, were meลŸandin.", + "theme-changed-label": "ลžรชwe hate guhertin", + "theme-changed-placeholder": "mรฎnakรฎ notify-send \"Tema\" \"Guherandin\"", + "title": "ร‡engel", + "wallpaper-changed-description": "Fermana ku dema dรฎwรชr diguhere bรช cรฎbicรฎkirin.", + "wallpaper-changed-label": "Dรฎwar hate guhertin", + "wallpaper-changed-placeholder": "mรฎnakรฎ notify-send \"Dรฎwarรช dรฎwarรช\" \"Guherandin\"" + }, + "indicator": { + "default-value": "Xwerรป: {value}", + "system-default": "Sรฎstema xwerรป" + }, + "launcher": { + "clipboard-desc": "GihรฎลŸtin รป dรฎroka clipboardรช xwe ji destpรชkerรช birรชve bibin.", + "execute-desc": "Vรช vesaz bike ka sepan รงawa tรชn destpรชkirin.", + "execute-title": "Cรฎbicรฎkirin", + "settings-annotation-tool-description": "Fermana ku dema tikandina biลŸkoka ลŸรฎrovekirinรช de di dรฎroka clipboardรช de were xebitandin. Wรชne dรช bi rรชya pipeโ€™รช were ลŸandin nav vรช fermanรช.", + "settings-annotation-tool-label": "Amรปra ลŸรฎrovekirinรช", + "settings-annotation-tool-placeholder": "mรฎnakรฎ 'gradia', 'satty -f -'", + "settings-auto-paste-description": "Bi otomatรฎkรฎ tiลŸta clipboardรช ya hilbijartรฎ biลŸรฎne. Wtype hewce dike.", + "settings-auto-paste-label": "Otomatรฎkรฎ bixwe ve zeliqandin", + "settings-clip-preview-description": "Dรฎmenek pรชลŸdรฎtinรช ya naveroka clipboardรช nรฎลŸan bide dema ku emrรช >clip tรช bikaranรฎn.", + "settings-clip-preview-label": "PรชลŸdรฎtina klรฎpรช รงalak bike", + "settings-clip-wrap-text-description": "Tekstรช di lรฎsteya clipboardรช de bipรชรงe, li ลŸรปna ku were kurtkirin.", + "settings-clip-wrap-text-label": "Pรชรงa nivรฎsa clipboardรช", + "settings-clipboard-history-description": "GihรฎลŸtina tiลŸtรชn ku berรช hatine kopรฎkirin ji destpรชkerรช.", + "settings-clipboard-history-label": "Dรฎroka clipboardรช รงalak bike", + "settings-clipboard-watch-image-description": "Ji bo guhertinรชn wรชneyรช, rรชza fermanรช ya tevahรฎ ji wl-paste re hat ลŸandin (ji nรป ve destpรชkirinรช pรชwist dike).", + "settings-clipboard-watch-image-label": "Fermana temaลŸekirina wรชneyรช", + "settings-clipboard-watch-text-description": "Ji bo guhertinรชn wรชneyรช, rรชza fermanรช ya tevahรฎ ji wl-paste re hat ลŸandin (ji nรป ve destpรชkirinรช pรชwist dike).", + "settings-clipboard-watch-text-label": "Fermana รงavdรชriya nivรฎsรช", + "settings-custom-launch-prefix-description": "Fermanรชn pรชลŸgiran bi destpรชkerek xwerรป pรชลŸda bikin (mรฎnakรฎ 'runapp' ji bo entegrasyona systemd).", + "settings-custom-launch-prefix-enabled-description": "Bi awayekรฎ xwerรป destpรชkรช ji bo destpรชkirina sepanan bikar bรฎne li ลŸรปna rรชbaza standard.", + "settings-custom-launch-prefix-enabled-label": "PรชลŸgira destpรชkirinรช ya xwerรป รงalak bike", + "settings-custom-launch-prefix-label": "PรชลŸgira destpรชkirinรช ya kesane", + "settings-desc": "XweลŸikbรปn รป xuyabรปna destpรชkerรช xweลŸ bike.", + "settings-enable-settings-search-description": "Dema ku di nav destpรชker de digerรฎ, encamรชn mรฎhengan nรฎลŸan bide.", + "settings-enable-settings-search-label": "Lรชgerรฎna mรฎhengan รงalak bike", + "settings-enable-windows-search-description": "Li รงarรงoveyรชn รงalak bigere รป balรช bide ser.", + "settings-enable-windows-search-label": "Lรชgerรฎnรช รงarรงoveyรช รงalak bike", + "settings-grid-view-description": "TiลŸtan di ลŸรชweya grรฎdรช de nรฎลŸan bide, ne di lรฎsteyรช de.", + "settings-icon-mode-description": "รŽkonรชn pergalรช yรชn xwemalรฎ li ลŸรปna รฎkonรชn Tabler bikar bรฎne.", + "settings-icon-mode-label": "รŽkonรชn xwecihรฎ bikar bรฎne", + "settings-ignore-mouse-input-description": "Di nav destpรชkerรช de, danรปstandina miลŸkรช รป รงerxa gerokรช neรงalak bike.", + "settings-ignore-mouse-input-label": "Guherandina miลŸkรช paลŸguh bike", + "settings-position-description": "Cihรช ku panelรช destpรชkerรช lรช xuya dibe hilbijรชre.", + "settings-show-categories-description": "Tabรชn kategoriyรช nรฎลŸan bide ji bo parzรปnkirina sepanan.", + "settings-show-categories-label": "Kategoriyan nรฎลŸan bide", + "settings-show-icon-background-description": "Li paลŸ รฎkonan paลŸxaneyeke รงargoลŸe ya gilover nรฎลŸan bide.", + "settings-show-icon-background-label": "PaลŸxaneya รฎkonรช nรฎลŸan bide", + "settings-sort-by-usage-description": "Dema ku รงalak be, sepanรชn ku pir caran tรชne destpรชkirin pรชลŸรฎ di lรฎsteyรช de xuya dibin.", + "settings-sort-by-usage-label": "Li gorรฎ ya herรฎ pir tรช bikaranรฎn rรชz bike", + "settings-terminal-command-description": "Fermana destpรชkirina termรฎnalรช. Mรฎnakรฎ 'kitty -e' an 'gnome-terminal --'.", + "settings-terminal-command-label": "Fermana termรฎnalรช", + "settings-use-app2unit-description": "Rรชbazeke destpรชkirinรช ya alternatรฎf bikar tรฎne da ku pรชvajoyรชn sepanรช baลŸtir birรชve bibe รป pรชลŸรฎ li pirsgirรชkan bigire.", + "settings-use-app2unit-label": "App2Unit bi kar bรฎne da ku sepanan bide destpรชkirin", + "settings-view-mode-label": "Awaya nรฎลŸandanรช", + "title": "Destpรชkar" + }, + "location": { + "calendar-cards-desc": "kertan rรชkxistin bike di destgeha salnameyรช de รงalak bike/neke.", + "calendar-cards-title": "Kartรชn salnameyรช", + "calendar-header-label": "Sernavรช salnameyรช", + "calendar-month-label": "Meha salnameyรช", + "date-time-12hour-format-description": "Demรช bi formata 12-demjimรชr li ser dรฎmendera kilรฎtkirinรช รป salnameyรช nรฎลŸan bide. Darika demjimรชrรช sazkariyรชn xwe yรชn taybet hene.", + "date-time-12hour-format-label": "Formata demรช ya 12-demjimรชr bi kar bรฎne", + "date-time-desc": "ร‡awa dรฎrok รป dem xuya dibin kesane bike.", + "date-time-first-day-of-week-automatic": "Xweber (a pergalรช bi kar bรฎne)", + "date-time-first-day-of-week-description": "Roja kรฎjanรช di salnameyรช de dest bi hefteyรช dike hilbijรชre.", + "date-time-first-day-of-week-label": "Roja yekem a hefteyรช", + "date-time-show-events-description": "Bรปyeran di destgeha salnameyรช de nรฎลŸan bide.", + "date-time-show-events-label": "Bรปyerรชn salnameyรช nรฎลŸan bide", + "date-time-title": "Dรฎrok & Dem", + "date-time-use-analog-description": "Demjimรชreke bi ลŸรชwaza analog li ser รงarรงoveya salnameyรช รป dรฎmendera kilรฎtkirinรช nรฎลŸan bide.", + "date-time-use-analog-label": "Demjimรชrรช bi ลŸรชwaza analog bi kar bรฎne", + "date-time-week-numbers-description": "Hefteya salรช (mรฎnakรฎ hefteya 38) di salnameyรช de nรฎลŸan bide.", + "date-time-week-numbers-label": "Jimarรชn hefteyรช nรฎลŸan bide", + "location-desc": "Ji bo wergirtina rewลŸa bayรช รป bernamekirina ronahiya ลŸevรช ya rast, cihรช xwe saz bike.", + "location-search-description": "mรฎnakรฎ Toronto, ON", + "location-search-label": "Ji bo cihekรฎ bigere", + "location-search-placeholder": "Navรช cihรช tรชxรฎne", + "location-title": "Cihรช te", + "weather-desc": "Yekรฎneya germahiya xwe ya bijarte hilbijรชre.", + "weather-enabled-description": "Agahiyรชn hewayรช li seranserรช navbeynรช nรฎลŸan bide รป daneyรชn hewayรช bรฎne. Dema ku neรงalak be, dรช hemรป hรชmanรชn hewayรช werin veลŸartin รป dรช tu daxwazรชn torรช neyรชn kirin.", + "weather-enabled-label": "ร‡alak bike hewayรช", + "weather-fahrenheit-description": "Germahiya bi Fahrenheit nรฎลŸan bide, ne bi Celsius.", + "weather-fahrenheit-label": "Pรฎvana germahiyรช bi Fahrenheit (ยฐF) nรฎลŸan bide", + "weather-hide-city-description": "Navรช bajarรช ji dรฎmenรชn hewayรช yรชn li seranserรช navbeynรช veลŸรชre.", + "weather-hide-city-label": "VeลŸรชre nameyรช sรปke", + "weather-hide-timezone-description": "Demjim kurtkirina herรชma demjimรชrรช ji pรชลŸandanรชn hewayรช li seranserรช navbeynรช.", + "weather-hide-timezone-label": "Demjimรชrรช veลŸรชre", + "weather-show-effects-description": "Bandora bandorรชn dรฎtbarรฎ yรชn din (wek baran, berf, an birรปsk) li ser karta hewayรช nรฎลŸan bide.", + "weather-show-effects-label": "NรฎลŸandan bandorรชn hewayรช", + "weather-show-in-calendar-description": "PรชลŸbรฎniya hewayรช ya rojane rasterast di dรฎmena salnameya xwe de nรฎลŸan bide.", + "weather-show-in-calendar-label": "NรฎลŸandanรฎna hewayรช di salnameyรช de" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "Dema ku fprintd (nasnameya tiliyan) รงalak e, ev vebijรชrk dihรชle hรปn bi ลŸรฎfreya xwe tรชkevin, ne bi tiliya xwe.", + "allow-password-with-fprintd-label": "Rรชdan bi tรชketina ลŸรฎfreyรช bi fprintd", + "auto-start-auth-description": "Otomatรฎkรฎ dest bi rastkirina tiliyรช dike bรชyรฎ ku pรชdivรฎ bi pรชlkeyek an klรฎkek hebe.", + "auto-start-auth-label": "Destpรชkirina otomatรฎkรฎ ya rastkirinรช", + "clock-format-label": "Formata demjimรชrรช", + "clock-style-analog": "Analog", + "clock-style-custom": "Kesane bike", + "clock-style-digital": "Jimarรฎ", + "compact-lockscreen-description": "Tenรช tรชketina ketanรช รป kontrolรชn pergalรช nรฎลŸan bide, sepanokรชn rewลŸa bayรช รป medyayรช veลŸรชre.", + "compact-lockscreen-label": "Kilรฎta ekrana berhevkirรฎ", + "lock-on-suspend-description": "Dema ku pergal tรช rawestandin, ekranรช bixweber kilรฎt bike.", + "lock-on-suspend-label": "Li ser rawestandinรช kilรฎt bike", + "show-hibernate-description": "Vebijรชrka 'Xewnรช' di kontrolรชn hรชzรช de nรฎลŸan bide.", + "show-hibernate-label": "NรฎลŸan bide xewnรช", + "show-session-buttons-description": "Destรปra gihรฎลŸtina mรฎhengรชn hรชzรช ji ekrana kilรฎtkirรฎ re bide.", + "show-session-buttons-label": "Kontrolรชn hรชzรช", + "title": "Dรฎmendera kilรฎtkirinรช" + }, + "network": { + "bluetooth-description": "Rรชveberiya Bluetooth รงalak bike.", + "bluetooth-rssi-polling-description": "Demjimรชr RSSI ji bo cรฎhazรชn girรชdayรฎ bi rรชya bluetoothctl nimรปne bike. Dibe ku ji bo hemรป cรฎhazรชn berdest nebe; dema ku รงalak be รงavkaniyรชn hindik bikar tรฎne.", + "bluetooth-rssi-polling-label": "Pirsgirรชka sรฎnyala Bluetoothรช", + "desc": "Girรชdanรชn Wi-Fi รป Bluetooth bi rรช ve bibe.", + "wifi-description": "Torรชn bรชtรชl bi rรช ve bibe (pรชdivรฎ bi NetworkManager heye)." + }, + "notifications": { + "duration-critical-urgency-description": "ร‡end demjimรชr notรชn girรฎng รชn pรชลŸรฎn xuya dimรฎnin.", + "duration-critical-urgency-label": "Lezgรฎnรฎya krรฎtรฎk", + "duration-desc": "Dem bi awayรช lezgรฎniya wan notรฎs รงiqas dirรชj xuya dimรฎnin mรฎheng bike.", + "duration-low-urgency-description": "ร‡iqas notรฎfรฎkasyonรชn pรชลŸรฎniya kรชm xuya dimรฎnin.", + "duration-low-urgency-label": "Lezgรฎnรฎ kรชm", + "duration-normal-urgency-description": "Notรฎfรฎkasyonรชn pรชลŸรฎniya normal รงiqas dirรชj xuya dimรฎnin.", + "duration-normal-urgency-label": "Lezgรฎnahiya normal", + "duration-respect-expire-description": "Dem wextรช qedandinรช yรช ku di agahdariyรช de hatiye danรฎn bikar bรฎne.", + "duration-respect-expire-label": "Dem bihurmet demdirรชj qedandin", + "duration-title": "Dirรชjahiya agahdariyรช", + "history-critical-urgency-description": "Agahiyรชn girรฎng รชn pรชลŸรฎn li dรฎrokรช tomar bike.", + "history-critical-urgency-label": "Dema awarte ya krรฎtรฎk tomar bike", + "history-desc": "Kontrol bike kaรฎdรฎ notรฎfรฎkasyonรชn ku li gorรฎ asta lezgรฎniya wan di dรฎrokรช de tรชne hilanรฎn.", + "history-low-urgency-description": "Agahdariyรชn bi girรฎngiya nizm di dรฎrokรช de tomar bike.", + "history-low-urgency-label": "Dรฎroka lezgรฎniya kรชm tomar bike", + "history-normal-urgency-description": "Agahdariyรชn pรชลŸรฎniya normal di dรฎrokรช de tomar bike.", + "history-normal-urgency-label": "Dรฎroka lezgรฎniya normal tomar bike", + "monitors-desc": "NรฎลŸandana agahdarkirinรช li ser รงavdรชrรชn taybet. Heke neyรชn hilbijartin, bi awayekรฎ xwerรป hemรปyan nรฎลŸan dide.", + "settings-always-on-top-description": "NรฎลŸandanรชn li ser pencereyรชn tev-ekran รป qatรชn din nรฎลŸan bide.", + "settings-background-opacity-description": "Guhertina zelaliya paลŸxaneyรชn agahdariyan.", + "settings-desc": "Xuyakir รป reftarรช agahdariyan saz bike.", + "settings-do-not-disturb-description": "Dema ku hat รงalak kirin, hemรป pop-upรชn agahdarkirinรช neรงalak bike.", + "settings-enabled-description": "Aktรฎv bike an jรฎ deaktรฎv bike daemona ragihandinรช, ji bo vรช yekรช ji nรป ve destpรชkirina ลŸรชla Noctalia hewce dike.", + "settings-enabled-label": "Agahdariyan รงalak bike", + "settings-location-description": "Agahรฎ li ser ekranรช li ku derรช xuya dibin.", + "sounds-desc": "Dengรช bandorรชn hiลŸyariyรช รป qebareyรช saz bike.", + "sounds-enabled-description": "ร‡alak bike bandorรชn dengรฎ ji bo agahiyรชn hatin.", + "sounds-enabled-label": "Dengรชn agahdariyรช รงalak bike", + "sounds-excluded-apps-description": "Berbรฎna lรชdana dengรช agahdarkirinรช ya ku hatรฎ vesazkirin ji bo sepanรชn taybet รชn ku dengรชn wan รชn รงรชkirรฎ hene.", + "sounds-excluded-apps-label": "Serlรชdanรชn hatine derxistin", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "Rรชya pelรช dengรฎ ya ku ji bo agahiyรชn pรชลŸรฎniya krรฎtรฎk tรช lรฎstin.", + "sounds-files-critical-label": "Dengek lezgรฎniya krรฎtรฎk", + "sounds-files-critical-select-title": "Pelรช dengรฎ yรช lezgรฎniya krรฎtรฎk hilbijรชre", + "sounds-files-desc": "Pelรชn dengรฎ yรชn xwerรป ji bo astรชn lezgรฎniya agahdarkirinรช yรชn cuda mรฎheng bike.", + "sounds-files-low-description": "Rรชya pelรช dengรฎ ya ku ji bo agahiyรชn pรชลŸรฎniya kรชm tรช lรฎstin.", + "sounds-files-low-label": "Dengekรฎ lezรฎ kรชm", + "sounds-files-low-select-title": "Pelรช dengรฎ yรช bilezรฎnek kรชm hilbijรชre", + "sounds-files-normal-description": "Rรชya pelรช dengรฎ ya ku ji bo agahiyรชn pรชลŸรฎniya normal tรช lรฎstin.", + "sounds-files-normal-label": "Dengรช lezgรฎniya normal", + "sounds-files-normal-select-title": "Pelรช dengรฎ yรช bileziya asayรฎ hilbijรชre", + "sounds-files-placeholder": "Rรชya pelรช dengรฎ binivรฎse", + "sounds-files-select-file": "Pelรช deng hilbijรชre", + "sounds-files-title": "Pelรชn deng", + "sounds-files-unified-description": "Rรชya pelรช dengรฎ ya ku ji bo agahdariyan tรช lรฎstin.", + "sounds-files-unified-label": "Dengรช agahdarkirinรช", + "sounds-files-unified-select-title": "Pelรช dengรช agahdarkirinรช hilbijรชre", + "sounds-separate-description": "Ji bo agahiyรชn girรฎngiyรชn kรชm, normal รป krรฎtรฎk pelรชn dengรฎ yรชn cuda bikar bรฎne.", + "sounds-separate-label": "Dengรช cuda li gorรฎ pรชลŸรฎniyรช bikar bรฎne", + "sounds-title": "Mรฎhengรชn deng", + "sounds-unavailable-description": "Ji bo ku dengรชn agahdarkirinรช รงalak bibin, Qt6 Multimedia saz bikin.", + "sounds-unavailable-label": "Dengรชn agahdarkirinรช peyda nabin", + "sounds-volume-description": "Ayarรช asta deng ji bo dengรชn agahdarkirinรช rast bike.", + "sounds-volume-label": "Jimara dengรช", + "toast-battery-label": "HiลŸyariya betariyรช", + "toast-desc": "Xuyabรปn รป reftarรชn toastรช saz bike.", + "toast-keyboard-description": "Dema ku ลŸewaza kilรฎtdankรช were guhertin, hiลŸyarรฎ nรฎลŸan bide.", + "toast-keyboard-label": "ลžรชwaza kilรฎtdankรช", + "toast-media-description": "Dema rewลŸa lรชdana medyayรช biguhere, toastekรช nรฎลŸan bide.", + "toast-media-label": "Medya" + }, + "osd": { + "always-on-top-description": "OSD li ser pencereyรชn tev-ekran รป qatรชn din nรฎลŸan bide.", + "always-on-top-label": "Her tim li ser", + "background-opacity-description": "Kontrola zelalbรปna paลŸxaneya OSD.", + "background-opacity-label": "Zelaliya paลŸrรปya destgehรช", + "description": "Mรฎhengรชn nรฎลŸanderรชn ser-ekranรช yรชn wekรฎ pรชลŸandanรชn deng รป ronahiyรช saz bike.", + "duration-auto-hide-description": "Dema berรฎ windabรปna OSD biguherรฎne.", + "duration-auto-hide-label": "PiลŸtรฎ otomatรฎk veลŸรชre", + "duration-desc": "ร‡iqas dirรชj OSD berรฎ ku bixweber veลŸรชre, xuya dimรฎne.", + "duration-title": "Demjimรชra veลŸartina otomatรฎk", + "enabled-description": "Guhertinรชn deng รป ronahiyรช di dema rast de nรฎลŸan bide.", + "enabled-label": "PรชลŸandana ser ekranรช (OSD) รงalak bike", + "events-desc": "Bรปyerรชn ku pรชลŸandana ser ekranรช รงalak dikin hilbijรชre.", + "general-desc": "Vebijรชrkรชn dรฎtin รป tevgera OSD'รช saz bike.", + "location-description": "Li ku derรช pรชลŸandanรชn ser ekranรช xuya dibin.", + "monitors-desc": "OSD li ser รงavdรชrรชn taybet nรฎลŸan bide. Ger tu neyรชn hilbijartin, bi awayekรฎ xwerรป li ser hemรปyan nรฎลŸan dide.", + "title": "PรชลŸandana ser ekranรช", + "types-brightness-description": "Dรฎmendera OSD nรฎลŸan bide dema ronahiya ekranรช diguhere.", + "types-brightness-label": "Ronahรฎ", + "types-custom-text-description": "OSD ya nivรฎsandeyรชn xwerรป yรชn ji IPC'yรช nรฎลŸan bide.", + "types-custom-text-label": "Teksta xweser", + "types-desc": "Bรปyerรชn ku OSD'รช รงalak dikin hilbijรชre.", + "types-input-volume-description": "Dรฎmenรช OSD nรฎลŸan bide dema ku qebareya mรฎkrofonรช diguhere.", + "types-input-volume-label": "Dengรช ketanรช", + "types-lockkey-description": "Dema Caps Lock, Num Lock, an Scroll Lock hatin guhertin, OSD nรฎลŸan bide.", + "types-lockkey-label": "Kilรฎtรชn qeflรช", + "types-media-description": "Dema rewลŸa lรชdana medyayรช biguhere OSD nรฎลŸan bide (lรชdan, rawestandin, derbasbรปn).", + "types-media-label": "Lรฎstina medyayรช", + "types-title": "Bรปyerรชn destpรชker รชn OSD", + "types-volume-description": "Dema ku asta dengรช derketina bihรฎstwerรช diguhere, OSD nรฎลŸan bide.", + "types-volume-label": "Jimara deranรช" + }, + "plugins": { + "available-description": "Pรชvekรชn ji รงavkaniyรชn rรชkxistรฎ bigere รป saz bike", + "available-label": "Pรชvekรชn berdest", + "available-no-plugins-description": "ร‡avkaniyรชn pรชvekรช xwe kontrol bikin an lรฎsteyรช nรป bikin.", + "available-no-plugins-label": "TiลŸtรชn pรชvek tune ne", + "check-for-updates": "Ji bo rojanekirinรช kontrol bike", + "checking-for-updates": "Ji bo rojanekirinรช kontrol dike...", + "collision-already-installed": "Ev pรชvek berรช hatiye sazkirin", + "collision-custom-version-exists": "Guhertoyeke taybet a ji \"{source}\" berรช hatiye sazkirin", + "collision-official-version-exists": "Guhertoya fermรฎ ya vรช pรชvekรช berรช hatiye sazkirin", + "filter-downloaded": "Daxistรฎ", + "filter-not-downloaded": "Nehatiye daxistin", + "filter-tags-description": "Parzรปna pรชvekรชn bi kategoriya an rewลŸa daxistinรช.", + "filter-tags-label": "Tagan", + "hot-reload-description": "Bi guhertina pelรชn wan, bixweber pรชvekรชn xwe ji nรป ve bar bike. Ji bo pรชลŸxistina pรชvekan bikรชr e.", + "hot-reload-label": "Nรปbarkirina germ (moda pรชลŸvebirinรช)", + "hot-reloaded": "Plugin ji nรป ve hate barkirin: {name}", + "install-error": "Sazkirin bi ser neket: {error}", + "install-incompatible": "{plugin} pรชdivรฎ bi Noctalia v{version} an bilindtir heye", + "install-success": "Bi serkeftรฎ {plugin} hate sazkirin", + "installed-description": "Birรชvebir รป hemรป pรชvekรชn ku herรชmรฎ hatine sazkirin mรฎheng bike.", + "installed-label": "Pรชvekรชn sazkirรฎ", + "installed-no-plugins-description": "Pรชvekรชn ji beลŸa \"Berdest\" a saz bike.", + "installed-no-plugins-label": "Tu pรชvek nehatine sazkirin", + "installing": "Sazkirina {plugin}...", + "open-plugin-page": "Rรปpela pรชvekรช veke", + "open-plugins-tab": "Vekirina mรฎhengรชn pรชvekรช", + "plugin-settings-title": "Sazkairyรชn {plugin}", + "refresh-refreshing": "Lรฎsteya pรชvekรชn nรปvekirinรช...", + "refresh-tooltip": "Nรปkirina pรชvekรชn berdest", + "settings-error-not-loaded": "Pรชvek nehatiye barkirin", + "settings-saved": "Ayarlarรช pluginรช hatin tomarkirin", + "settings-tooltip": "Mรฎhengรชn pรชvekรช", + "source-custom": "ร‡avkaniya xwerรป", + "sources-add-custom": "Lรช zรชde bike embarรช xwerรป", + "sources-add-dialog-description": "Depoyek GitHub wekรฎ รงavkaniyek plugin zรชde bike.", + "sources-add-dialog-error": "ร‡avkaniya pรชvekรช nehat zรชdekirin", + "sources-add-dialog-name": "Navรช embarรช", + "sources-add-dialog-name-placeholder": "Pรชvekรชn min รชn xweser", + "sources-add-dialog-success": "ร‡avkaniya pรชvekรช bi serkeftรฎ hate zรชdekirin", + "sources-add-dialog-title": "ร‡avkaniya pรชvekรช zรชde bike", + "sources-add-dialog-url": "Girรชdanรช depoyรช", + "sources-description": "Depoyรชn pรชvekan bi rรช ve bibe.", + "sources-placeholder": "Depoya min a bedew", + "sources-remove-tooltip": "ร‡avkaniya pรชvekรช jรช bibe", + "title": "Pรชvek", + "uninstall-dialog-description": "Tu dixwazรฎ {plugin} jรช bibรฎ? Ev รช hemรป daneyรชn pรชvekรช jรช bibe.", + "uninstall-dialog-title": "RakรชลŸana pรชvekรช", + "uninstall-error": "Nekarรฎ were rakirin: {error}", + "uninstall-success": "Bi serkeftรฎ {plugin} hate rakirin", + "uninstalling": "Rakirina {plugin}...", + "update-all": "Hemรปyan nรป bike ({count})", + "update-all-success": "Hemรป pรชvek bi serkeftรฎ hatin nรปkirin", + "update-available": "Rojanekirina pรชveka nรป heye", + "update-available-plural": "Rojanekirinรชn pรชvekรชn nรป hene ({count})", + "update-error": "Nekarรฎ plugรฎn nรปve bikin: {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (hewce dike Noctalia v{required})", + "update-success": "{plugin} hate nรปkirin bo v{version}", + "update-version": "v{current} โ†’ v{new}", + "updating": "Tรช rojanekirin..." + }, + "region": { + "title": "Herรชm" + }, + "session-menu": { + "countdown-duration-description": "Demjimรชra heta pรชkanรฎna kiryarรชn hรชzรช destnรฎลŸan bike.", + "countdown-duration-label": "Dirรชjahiya jimartinรช berjรชr", + "enable-countdown-description": "Demjimรชrek paลŸvekirinรช nรฎลŸan bide berรฎ pรชkanรฎna kiryarรชn hรชzรช.", + "enable-countdown-label": "Demjimรชra paลŸvejimรชr รงalak bike", + "entries-desc": "ร‡alakiyรชn hรชzรช yรชn ku di menuya daniลŸรฎnรช de รป bi kรฎjan rรชzรช de xuya dibin, xweลŸ bikin.", + "entries-title": "Kiryarรชn hรชzรช", + "entry-settings-command-description": "Fermana xwerรป ya ku ji bo vรช รงalakiyรช were meลŸandin. Ji bo bikaranรฎna fermana pergalรช ya xwerรป vala bihรชle.", + "entry-settings-command-placeholder": "mรฎnakรฎ systemctl poweroff", + "entry-settings-default-command-lock": "Kilรฎta hundirรฎn a ekranรช (bรช ferman)", + "entry-settings-default-command-logout": "Derketina navxweyรฎ (bรช ferman)", + "entry-settings-default-info-description": "Heke fermana xwerรป neyรช diyarkirin, fermana pergalรช ya xwerรป dรช were bikar anรฎn.", + "entry-settings-default-info-label": "Fermana berdest", + "entry-settings-title": "Vexรฎne {entry}", + "entry-settings-tooltip": "Fermanรช vesazkirinรช", + "general-desc": "Maneja reftar รป xuyabรปna panelรช menรปya rรปniลŸtinรช.", + "large-buttons-layout-description": "ร‡awa biลŸkokรชn menuya daniลŸรฎnรช bรชn nรฎลŸandan hilbijรชre.", + "large-buttons-layout-label": "Rรชzika biลŸkokรชn mezin", + "large-buttons-style-description": "PรชลŸeka daniลŸรฎnรช bi biลŸkokรชn mezin di ลŸรชweya grรฎd de nรฎลŸan bide.", + "large-buttons-style-label": "ลžรชwaza biลŸkokรชn mezin", + "position-description": "Dema ku panelรช menuya daniลŸรฎnรช vebe, hilbijรชre ku li ku derรช xuya bibe.", + "show-header-description": "Sernavรช รป biลŸkoka girtinรช li jorรช menuyรช rรปniลŸtinรช nรฎลŸan bide.", + "show-header-label": "Serpรช nรฎลŸan bide" + }, + "system-monitor": { + "critical-color-label": "Rengรช krรฎtรฎk", + "custom-highlight-colors-title-label": "Rengรชn ronรฎkirina xwerรป", + "disk-section-label": "Bikaranรฎna dรฎskรช", + "enable-dgpu-monitoring-description": "HiลŸyarรฎ: Ev dรช YKG a te ya veqetandรฎ (NVIDIA/AMD) hiลŸyar bike, ku dibe ku bandorek girรฎng li ser jiyana pรฎlรช li ser laptopรชn bi grafรฎkรชn hรฎbrรฎd bike.", + "enable-dgpu-monitoring-label": "ร‡avdรชriya YKG veqetandรฎ รงalak bike", + "external-monitor-description": "Fermanรฎ an rรชya sepanรช binivรฎse ku dema sepana monitora pergala derveyรฎ รงalak dike were xebitandin.", + "external-monitor-label": "Fermana monitora pergala derveyรฎ", + "external-monitor-placeholder": "รงavkanรฎ || navendamisyonรช || jdsรฎstemonรฎtor || statรฎstรฎkรชnbingehรฎn || navenda-รงavdรชriya-sรฎstemรช || gnome-sรฎstem-monitor || plasma-sรฎstemmonitor || mate-sรฎstem-monitor || ukui-sรฎstem-monitor || deepin-sรฎstem-monitor || pantheon-sรฎstem-monitor", + "general-desc": "Sazkirina reftar รป xuyabรปna รงavdรชriya pergalรช.", + "gpu-section-label": "Germahiya YKG", + "highlight-colors-section-label": "Rengรชn ronรฎkirinรช", + "polling-interval-label": "Navbera anketรช", + "polling-section-description": "Mรฎheng bike ku her pรฎvana pergalรช รงiqas caran were nรปkirin.", + "polling-section-label": "Navberรชn anketรช", + "threshold-critical": "Astana krรฎtรฎk", + "threshold-warning": "Asteya hiลŸyariyรช", + "thresholds-section-description": "Guherรฎn sรฎnorรชn hiลŸyariyรช/krรฎtรฎk รป navberรชn anketรช ji bo her metrรฎkรชn pergalรช.", + "use-custom-highlight-colors-description": "Dema ku neรงalak be, rengรชn ronรฎkirina xwerรป yรชn temayรช tรชne bikar anรฎn.", + "use-custom-highlight-colors-label": "Rengรชn ronรฎkirina xwerรป bikar bรฎne", + "warning-color-label": "Rengรช hiลŸyariyรช" + }, + "user-interface": { + "allow-panels-without-bar-description": "Dema ku รงalak be, panel dikarin li ser her ekranรช vebin. Dema ku neรงalak be, panel tenรช dรช li ser ekranรชn ku barekรฎ wan heye vebin, ku dikare bikaranรฎna bรฎrรช kรชm bike.", + "allow-panels-without-bar-label": "Destรปrรช bide panelan li ser ekranan bรชyรฎ barekรฎ", + "animation-disable-description": "Hemรป anรฎmasyonan neรงalak bike ji bo ezmรปneke bileztir รป bersivdartir.", + "animation-disable-label": "Anรฎmasyonรชn NB รงalak neke", + "animation-speed-description": "Lezaaniya lezgรฎniya gerdรปnรฎ sererast bike.", + "animation-speed-label": "Leza anรฎmasyonรช", + "animation-speed-reset": "Lezaaniya bilezรฎneka nรป bike", + "appearance-desc": "Xwerรปkirina hรชmanรชn dรฎtbarรฎ yรชn wekรฎ seriลŸteyรชn amรปran, sรฎnor รป siya.", + "box-border-description": "Xรชzekรช li dora deverรชn naverokรช nรฎลŸan dide.", + "box-border-label": "ร‡arรงoweya konteynerรช", + "box-border-radius-description": "GoลŸeya dorfirehiya beลŸรชn sereke yรชn layoutรช, wekรฎ barรชn kรชlekรช, kart, รป panelรชn naverokรช, eyar dike.", + "box-border-radius-label": "Radiusa konteynirรช", + "control-border-radius-description": "Kontrola รงemandina hรชmanรชn รฎnteraktรฎv, di nav de biลŸkok, guhรชrbarok รป qadรชn nivรฎsรช.", + "control-border-radius-label": "Tรฎrรชjรช tรชketinรช", + "desc": "Xuyakirina xuyabรปn, hest, รป reftarรชn navrรปyรช.", + "dim-desktop-description": "Dema panel an menรผ vebin, sermaseyรช tarรฎ bike.", + "dim-desktop-label": "Dim sermaseyรช", + "dimmer-opacity-description": "Ayarรช asta zelalbรปna ji bo tarรฎkirina sermaseyรช bike.", + "dimmer-opacity-label": "ลžefafiya sermaseyรช sermaseyรช kรชmkirรฎ", + "panel-background-opacity-description": "Zelalbรปna paลŸrรปyรช ji bo hemรป destgehan (darik, destpรชker, sazkarรฎ, hwd...) saz bike.", + "panel-background-opacity-label": "Zelaliya paลŸrรปyรช destgehรช", + "panels-attached-to-bar-description": "Panelรชn bi qiraxรชn bar รป ekranรช ve tรชne kilรฎt kirin, bi goลŸeyรชn berevajรฎkirรฎ yรชn ลŸรชwekar xuyangek bรชkรชmasรฎ diafirรฎnin.", + "panels-attached-to-bar-label": "Panรชlan bi kรชlekรช ve biลŸkรฎnin", + "panels-overlay-description": "PiลŸkin dike ku panel รป bar, heta ser sepanรชn tev-ekran jรฎ, xuya bimรฎnin.", + "panels-overlay-label": "Destgeh & darikรช li jor bihรชle", + "scaling-description": "Guhertina mezinahiya navrรปya giลŸtรฎ ya bikarhรชner, ji bilรฎ bar.", + "scaling-label": "Pรฎvana navrรปyรช", + "settings-panel-mode-description": "Hilbijรชre ลŸรชweyรช mรฎhengan (dibe ku ji nรป ve vekirinรช hewce bike).", + "settings-panel-mode-label": "Awaya destgeha sazkariyan", + "shadows-description": "Dรฎsa siya di bin bar รป panelan de รงalak dike.", + "shadows-direction-description": "Hilbijรชre ku siya ji ku tรช avรชtin.", + "shadows-direction-label": "Arasteya siya", + "shadows-label": "Siyeyรชn ketรฎ", + "title": "Navrรปya bikarhรชner", + "tooltips-description": "Aktรฎf bike an neรงalak bike seriลŸteyรชn amรปrรช li seranserรช navbeynรช.", + "tooltips-label": "NรฎลŸandanรช sernavรชn amรปran" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "Alfabetรฎk", + "automation-change-mode-description": "Dema ku dรฎwar otomatรฎk diguhere, hilbijรชre ka dรฎwar รงawa tรชne hilbijartin.", + "automation-change-mode-label": "Guherandin moda", + "automation-custom-interval-description": "Demรช wekรฎ HH:MM (mรฎnakรฎ 01:30) tรชxรฎne.", + "automation-custom-interval-label": "Navbera xwerรป", + "automation-interval-description": "ร‡end caran divรช dรฎwarรชn dรฎwรชr bixweber werin guhertin.", + "automation-interval-label": "Navbera dรฎwรชr", + "automation-random-wallpaper-description": "Bername guhertina dรฎwarรช rasthatรฎ di navberรชn rรชkรปpรชk de.", + "automation-scheduled-change-description": "Bi awayekรฎ otomatรฎkรฎ di navberรชn rรชkรปpรชk de dรฎwรชran biguherรฎne.", + "automation-scheduled-change-label": "Guherรฎna plansazkirรฎ", + "look-feel-edge-smoothness-description": "Bandora bo bandorรช perรฎลŸan รป nerm li keviya veguherรฎnan dike.", + "look-feel-edge-smoothness-label": "Nermkirina keviya derbasbรปnรช", + "look-feel-fill-color-description": "Rengรช dagirtinรช hilbijรชre ku dibe ku li piลŸt wรชneyรช dรฎwรชr xuya bibe", + "look-feel-fill-mode-description": "Hilbijรชre ka divรช wรชne รงawa were pรฎvandin da ku bi รงareseriya รงavdรชriya te re tรชkildar be.", + "look-feel-fill-mode-label": "Moda dagirtinรช", + "look-feel-title": "Binรชre รป hรฎs bike", + "look-feel-transition-duration-description": "Dirรชjahiya anรฎmasyonรชn derbasbรปnรช bi รงirkeyan.", + "look-feel-transition-duration-label": "Dirรชjahiya derbasbรปnรช", + "look-feel-transition-type-description": "Cรปreyรช anรฎmasyonรช dema ku di navbera wรชneyรชn dรฎwรชr de diguhere.", + "look-feel-transition-type-label": "Curey guherรฎnรช", + "settings-desc": "Kontrolkirina รงawaniya birรชvebirin รป nรฎลŸandana dรฎwรชrรชn dรฎmenderรช.", + "settings-enable-management-description": "Bi Noctalia dรฎwaran birรชve bibin. Ger hรปn dixwazin serlรชdanek din bikar bรฎnin, nรฎลŸana wรช rake.", + "settings-enable-management-label": "Aktรฎvkirina birรชvebirina dรฎwarรช", + "settings-enable-overview-description": "Dรฎwarekรฎ dรฎm รป nezelal li ser dรฎmendera giลŸtรฎ ferz dike.", + "settings-enable-overview-label": "Aktรฎfkirina dรฎwarรช giลŸtรฎ", + "settings-folder-description": "Rรชya peldanka te ya sereke ya dรฎwรชr.", + "settings-folder-label": "Peldanka wรชneyรช dรฎwรชr", + "settings-hide-wallpaper-filenames-tooltip-hide": "Navรชn pelan veลŸรชre", + "settings-hide-wallpaper-filenames-tooltip-show": "Navรชn pelan nรฎลŸan bide", + "settings-monitor-specific-description": "Peldankekรฎ dรฎwarรช cuda ji bo her monitorekรฎ destnรฎลŸan bike.", + "settings-monitor-specific-label": "NavnรฎลŸanรชn taybet bi รงavdรชran", + "settings-monitor-specific-tooltip": "Peldanka dรฎwarรช รงavdรชriyรช", + "settings-recursive-search-description": "Herwiha li peldankรชn binรช peldanka dรฎwรชr jรฎ li dรฎwรชran bigere.", + "settings-recursive-search-label": "Lรชgerรฎna binyadan", + "settings-select-monitor-folder": "Hilbijรชre peldanka dรฎwarรช dรฎmenderรช", + "settings-selector-description": "Wรชneyรช dรฎwarรช xwe hilbijรชre.", + "settings-selector-position-description": "Cihรช ku panelรช hilbijรชra dรฎwรชr lรช xuya dike hilbijรชre.", + "settings-show-hidden-files-tooltip-hide": "Pelรชn veลŸartรฎ veลŸรชre", + "settings-show-hidden-files-tooltip-show": "Pelรชn veลŸartรฎ nรฎลŸan bide", + "settings-title": "Mรฎhengรชn dรฎwรชr", + "settings-view-mode-description": "Ji bo dรฎmendana dรฎwรชrรชn ji pelrรชรงa xwe rรชbazek hilbijรชre.", + "settings-view-mode-label": "Awayรช dรฎtinรช", + "view-mode-browse": "Navรฎgasyon di peldankan de", + "view-mode-cycle-tooltip": "Awayรช dรฎtinรช: {mode} (bitikรฎne da biguherรฎ)", + "view-mode-recursive": "BinkรชลŸkรชn jรชrรฎn รชn rรปxandรฎ", + "view-mode-single": "Peldaรฎna bingehรฎn" + } + }, + "placeholders": { + "command-example": "echo \"Silav cihan\"", + "enter-command": "Fermana ku bรช tetbรฎqkirin binivรฎse (sepan an nivรฎsara taybet)", + "enter-ipc-identifier": "Ji bo fermanรชn IPC nasnameyek yekta binivรฎse", + "enter-path": "Rรชya binivรฎse...", + "enter-text-to-collapse": "mรฎnakรฎ 'tiลŸtek nayรช lรฎstin'. Use /regex/ for patterns.", + "enter-tooltip": "Tรชketina sernavรช amรปrรช", + "enter-width-pixels": "Firehiya bi pรฎxel binivรฎse", + "search": "Bigere...", + "search-icons": "mรฎnakรฎ noctalia, niri, betarรฎ, ewr", + "search-launcher": "Li navan bigere... an ji bo fermanan > bi kar bรฎne", + "search-wallhaven": "Lรชgerรฎn li Wallhaven...", + "search-wallpapers": "Binivรฎse da ku dรฎwรชran parzรปn bikรฎ..." + }, + "positions": { + "bottom": "Jรชr", + "bottom-center": "Jรชr navend", + "bottom-left": "Jรชr รงepรช", + "bottom-right": "Jรชr rastรช", + "center": "Navend", + "center-left": "Navend รงep", + "center-right": "Navend rast", + "close-to-bar": "Nรชzรฎkรฎ biลŸkoka darikรช", + "follow-bar": "Darikรช biลŸopรฎne", + "left": "ร‡ep", + "right": "Rast", + "top": "Jor", + "top-center": "Jor navend", + "top-left": "Jorรฎ รงep", + "top-right": "Rastรช jor" + }, + "session-menu": { + "action-in-seconds": "di {seconds} รงirke de {action} ...", + "cancel-timer": "Demjimรชr betal bike", + "title": "Menuya daniลŸรฎnรช" + }, + "setup": { + "all-done": "Qediya!", + "appearance": { + "subheader": "Moda tarรฎ รป รงavkaniyรชn rengan hilbijรชre (wallpaper an pรชลŸdiyarkirรฎ)." + }, + "customize": { + "header": "Xweserkirina ezmรปna xwe bike", + "subheader": "RewลŸa bar, dendikbรปn, pรฎvan รป hwd. eyar bike." + }, + "skip-setup": "Sazkirinรช Derbas Bike", + "telemetry-wizard-done": "GihรฎลŸt!", + "telemetry-wizard-note": "Hรปn di kontrolรช de ne โ€” hรปn dikarin vรช yekรช di mรฎhengan de her dem รงalak an neรงalak bikin", + "telemetry-wizard-subtitle": "Me analรฎtรฎkรชn anonรฎm zรชde kirin da ku alรฎkariya baลŸkirina Noctalia bikin", + "telemetry-wizard-title": "Nรปkirina Nepenรฎtiyรช", + "wallpaper": { + "choose-dir": "Dรฎrekteriyek ku wรชneyรชn dรฎwarรช te tรช de hilbijรชre", + "dir-browse": "Geroka peldanka dรฎwรชr", + "dir-description": "Peldรชra ku dรฎwarรชn te dihewรฎne hilbijรชre.", + "dir-label": "NavnรฎลŸana dรฎwarรช", + "dir-select-title": "Pelรช dรฎwarรช hilbijรชre", + "header": "Wรชneyรช dรฎwarรช xwe hilbijรชre", + "no-dir": "Tuwalxaneyek dรฎwรชr nehatiye hilbijartin", + "no-valid": "Di {dir} de pelรชn wรชneyan รชn derbasdar nehatin dรฎtin", + "none-in-dir": "Di wรช peldankรช de tu dรฎwarรชn dรฎwaran nehatin dรฎtin", + "preview-error": "Wรชne barkirin tรชk รงรป", + "select-prompt": "Wรชneyekรฎ dรฎwarรช jรชrรฎn hilbijรชre", + "subheader": "Bi dรฎmenek xweลŸik hestรช saz bike." + }, + "welcome-note": "Tenรช รงend bingehรฎn ji bo ku hรปn dest pรช bikin โ€” vebijarkรชn tevahรฎ di mรฎhengan de ne", + "welcome-subtitle": "Werin em sermaseya we bรชhempa bikin", + "welcome-title": "Bi xรชr hatรฎ Noctalia!" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "%{percent}", + "location-display": "{nav} ({koordinat})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "Tu lรฎstikvanen medyayรช nehatin dรฎtin", + "scaling-percentage": "%{percentage}", + "signal-strength": "%{signal}", + "unknown-app": "Sepanek nenas", + "uptime": "Dema xebitandinรช: {uptime}", + "user-requested": "Daxwaza bikarhรชner", + "welcome-back": "Silav,", + "widget-settings-title": "Sazkariyรชn {widget}" + }, + "system-monitor": { + "cpu-temp": "Germahiya YKN", + "cpu-usage": "Bikaranรฎna YKN", + "disk": "Dรฎsk", + "download-speed": "Leza daxistinรช", + "gpu-temp": "Germahiya YKG", + "load-average": "Barkirina navรฎn", + "title": "ร‡avdรชriya pergalรช", + "upload-speed": "Leza barkirinรช" + }, + "toast": { + "airplane-mode": { + "title": "Awaya balafirรช" + }, + "battery": { + "low": "Pรฎl kรชm e", + "low-desc": "Betarรฎ li ser %{percent} e. Tijรฎkarรช girรช bide" + }, + "bluetooth": { + "address-copied": "NavnรฎลŸan li clipboardรช hate kopรฎkirin", + "confirm-code": "Koda {value} li ser amรปra din piลŸtrast bike.", + "connect-failed": "Tรชk รงรป girรชdan bi cรฎhazรช re", + "disconnect-failed": "Tรชkรงรปn di qutkirina ji cรฎhazรช de", + "discoverable-change-failed": "Guhertina rewลŸa dรฎtลŸbar tรชk รงรป", + "discoverable-disabled": "Dรฎtbarรฎ hat neรงalakkirin", + "discoverable-enabled": "Dรฎtbarรฎ hat neรงalakkirin", + "display-code": "Koda {value} li ser amรปra din binivรฎse.", + "forget-failed": "Tรชk รงรป ku amรปr were jibรฎrkirin", + "pair-failed": "Cรฎhaz bi hev ve nehat girรชdan", + "passkey-required": "Pรชdivรฎ bi borรฎnpeyvรช amรปrรช heye.", + "pincode-required": "Koda PIN ji hรชla cรฎhazรช ve tรช xwestin.", + "state-change-failed": "Guhertina rewลŸa Bluetooth tรชk รงรป" + }, + "clipboard": { + "long-text": "Teksta dirรชj", + "unavailable": "Dรฎroka clipboardรช tune ye", + "unavailable-desc": "Serlรชdana 'cliphist' nehatiye sazkirin. Ji bo bikaranรฎna taybetmendiyรชn dรฎroka clipboardรช, ji kerema xwe wรช saz bikin" + }, + "do-not-disturb": { + "disabled": "Dengรช dernexรฎne neรงalak e", + "disabled-desc": "Hemรป agahdarรฎ tรชn nรฎลŸandan", + "enabled": "Dengรช dernexรฎne รงalak e", + "enabled-desc": "Tu yรช van agahdariyan di dรฎroka xwe de bibรฎnรฎ" + }, + "donation-opened": "Rรปpela bexลŸรฎnรช di geroka we de vebรป", + "internet-limited": "Pรชwendรฎdar bรช รฎnternetรช", + "keyboard-layout": { + "changed": "ลžรชwaza kilรฎtdankรช bo {layout} hat guhertin", + "title": "Kilรฎtdank" + }, + "missing-control-center": { + "description": "Kurteya kontrolรช ji barรช hatiye rakirin. Ji bo ku hรปn dรฎsa ji barรช bigihรฎjinรช, hรปn รช hewce ne ku kurteyรช ji nรป ve zรชde bikin. Her weha hรปn dikarin bi rast-klรฎkkirina li ser barรช jรฎ vekin", + "label": "Amรปra dawรฎ ya navenda kontrolรช hate rakirin" + }, + "night-light": { + "forced": "ร‡alakdarkirina bi zorรช", + "normal": "Moda normal", + "not-installed": "wlsunset nehatiye sazkirin" + }, + "noctalia-performance": { + "disabled": "RewลŸa performansรช hat betalkirin", + "enabled": "RewลŸa performansรช รงalak bรป", + "label": "Performansa Noctalia" + }, + "power-profile": { + "changed": "Profรฎla hรชzรช hate guhertin", + "profile-name": "{profรฎl}" + }, + "theming-processor-failed": { + "desc-generic": "ลžaลŸetiyek di dema pรชvajoykirina ลŸablonan de rรป da", + "title": "Pรชvajoya ลŸablonรช tรชk รงรป" + }, + "vpn": { + "connected": "Hat girรชdan bi '{name}' re", + "disconnected": "Ji '{name}' qut bรป" + }, + "wallpaper-colors": { + "disabled": "Rengรชn dรฎwรชr hatine bรชbandorkirin", + "enabled": "Rengรชn dรฎwรชr hatine รงalak kirin", + "label": "Afirandina rengรชn dรฎwรชr" + }, + "wifi": { + "connected": "Bi '{ssid}' ve hat girรชdan", + "connection-failed": "Girรชdan tรชk รงรป", + "connection-timeout": "Dema girรชdanรช qediya", + "disconnected": "Ji '{ssid}' qut bรป", + "incorrect-password": "Borรฎnpeyva ลŸaลŸ e", + "network-not-found": "Tor nehat dรฎtin" + } + }, + "tooltips": { + "add-widget": "Sepanokรช tevlรฎ bike", + "bluetooth-devices": "Amรปrรชn Bluetooth", + "brightness-at": "Ronahรฎ: {brightness}%", + "click-to-start-recording": "Tomarkarรช dรฎmenderรช (dest bi tomarkirinรช bike)", + "click-to-stop-recording": "Tomarkarรช dรฎmenderรช (tomarkirinรช rawestรฎne)", + "collapse": "Darika kรชlelรช veลŸรชre", + "copy-address": "NavnรฎลŸana kopรฎ bike", + "delete-notification": "Agahdariyรช jรช bibe", + "do-not-disturb-enabled": "Dengรช dernexรฎne", + "expand": "Darika kรชlekรช fereh bike", + "forget-network": "Torรช ji bรฎr bike", + "grid-view": "Dรฎtina torรฎ", + "hidden-files-hide": "Pelรชn veลŸartรฎ", + "hide-unnamed-devices": "VeลŸรชre cรฎhazรชn bรช nav", + "home": "Dรฎrektoriya malรช", + "input-muted": "Guherandina bรชdengkirina tรชketinรช", + "keep-awake": "HiลŸyar bimรฎne", + "keyboard-layout": "ลžรชwaza kilรฎtdankรช {layout}", + "list-view": "Dรฎtina lรฎsteyรช", + "manage-vpn": "Girรชdanรชn VPN", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "Jimara herรฎ pir a sepanokan hat gihรฎลŸtin", + "microphone-volume-at": "Dengรช mรฎkrofonรช: {volume}%", + "move-to-section": "Here'ke {section} here", + "mute": "Bรชdeng bike", + "next-media": "Strana pรชลŸ", + "next-month": "Meha bรช", + "night-light-not-installed": "Ronahiya ลŸevรช (tune)", + "noctalia-performance-enabled": "Noctalia moda performansรช", + "open-annotation-tool": "Bi amรปra ลŸรฎrovekirinรช veke", + "open-control-center": "Navenda kontrolรช", + "open-notification-history-enable-dnd": "Dรฎroka agahdariyรช", + "open-settings": "Sazkarรฎ", + "open-tray-dropdown": "Trayรช sรฎstemรช", + "output-muted": "Deng vedengรช vemirรฎne/veke", + "power-profile": "Profรฎla hรชzรช ya {profile}", + "previous-media": "PรชลŸiya ลŸopรช", + "previous-month": "Meha borรฎ", + "refresh-devices": "Amรปran rojane bike", + "refresh-wallhaven": "Nรปkirina encamรชn Wallhavenรช", + "refresh-wallpaper-list": "Nรปkirina lรฎsteya dรฎwรชr", + "remove-widget": "Rakire amรปrekรช", + "search-close": "Lรชgerรฎnรช bigire", + "session-menu": "Menuya daniลŸรฎnรช", + "show-all-devices": "Hemรป amรปran nรฎลŸan bide", + "switch-to-dark-mode": "Moda tarรฎ", + "switch-to-light-mode": "Awaya ronahรฎ", + "unmute": "Bรชdeng neke", + "up": "Rรชgeha resen", + "volume-at": "Dengรช deranรช: {volume}%", + "wallpaper-selector": "Hilbijรชrรช wรชneyรช dรฎwรชr" + }, + "wallpaper": { + "browse": { + "empty-directory": "Ev peldank vala ye.", + "go-root": "Biรงe peldanka sereke ya dรฎwรชr", + "go-up": "Biรงe peldanka dรช รป bav", + "go-up-hint": "Ji bo ku hรปn ber bi jor ve biรงin, biลŸkoka paลŸ bikar bรฎnin." + }, + "configure-directory": "Rรชgeha wรชneyรช dรฎwรชrรช xwe bi wรชneyan re bi rรชkxistin bike.", + "fill-modes": { + "crop": "Birรฎn (Dagirtin)", + "fit": "Guncav (Tรช de)", + "repeat": "Dubarekirin (Tile)", + "stretch": "Dirรชjkirin" + }, + "no-match": "Lihevhatin nehat dรฎtin.", + "no-wallpaper": "Wรชneyรช dรฎwรชr nehat dรฎtin.", + "panel": { + "apikey-help": "Pรชdivรฎ bi kilรฎta API heye ji bo gihรฎลŸtina naveroka NSFW.", + "apikey-label": "Kilรฎta API", + "apikey-managed-by-env": "Bi rรชya guherbara hawรฎrdorรช ya NOCTALIA_WALLHAVEN_API_KEY tรช birรชvebirin.", + "apikey-placeholder": "API Key-a Wallhavenรช xwe binivรฎse", + "categories-anime": "Anรฎme", + "categories-label": "BeลŸ", + "categories-people": "Mirov", + "order-asc": "Berjor", + "order-desc": "Berjรชr", + "order-label": "Ferman", + "purity-label": "Parzรปna naverokรช", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "Sketchy", + "ratios-all-portrait": "Hemรป portre", + "ratios-all-wide": "Hemรป fereh", + "ratios-any": "Her", + "ratios-label": "Rรชjeya aliyรช", + "resolution-atleast": "Herรฎ kรชm", + "resolution-exact": "Rast", + "resolution-label": "ร‡areserรฎ", + "resolution-mode-label": "Awa", + "solid-color-tooltip": "Zemรฎnek bi rengรช zexm", + "sorting-date-added": "Dรฎroka tevlรฎkirinรช", + "sorting-favorites": "Bijarte", + "sorting-label": "Rรชz bike bi ya", + "sorting-relevance": "Girรฎngรฎ", + "sorting-toplist": "Lรฎsteya sereke", + "sorting-views": "Dรฎtin", + "source-label": "ร‡avkanรฎ", + "source-wallhaven": "Wallhaven", + "title": "Hilbijรชrรช wรชneya dรฎwรชr", + "wallhaven-settings-title": "Sazkariyรชn Wallhaven" + }, + "transitions": { + "disc": "Dรฎsk", + "fade": "Wendabรปn", + "stripes": "xรชz", + "wipe": "Rake" + }, + "try-different-search": "Lรชgerรฎneke cรปda bi kar bรฎne.", + "wallhaven": { + "loading": "Wรชneyรชn dรฎwaran tรชn barkirin...", + "no-results": "Wรชneyรชn dรฎwaran nehatin dรฎtin. Lรชgerรฎneke cรปda bi kar bรฎne.", + "page": "{current} ji {total}", + "page-prefix": "Rรปpel", + "page-suffix": "ji {total}" + } + }, + "weather": { + "clear-sky": "Ezmanรช zelal", + "drizzle": "\nBaran", + "fog": "Mij", + "mainly-clear": "Bi piranรฎ zelal", + "overcast": "Ewrรฎn", + "partly-cloudy": "Nรฎv ewrรฎn", + "rain-showers": "Barรฎna baranรช", + "snow": "Berf", + "thunderstorm": "Bahoza bruskรฎ" + }, + "widgets": { + "color-picker": { + "palette-description": "Ji rรชzek fereh ji rengรชn pรชลŸdanasรฎnkirรฎ hilbijรชre.", + "palette-label": "Destgeh", + "palette-theme-colors": "GihรฎลŸtina bilez bo destgeha rengรชn rรปkara te.", + "title": "Hilbijรชrรช rengรช" + }, + "datetime-tokens": { + "ampm-lowercase": "am/pm bi tรฎpรชn biรงรปk", + "ampm-uppercase": "AM/PM bi tรฎpรชn mezin", + "common-12hour-time-minutes": "12-demjimรชr bi xulekan", + "common-24hour-time-minutes": "24-demjimรชr bi xulekan", + "common-24hour-time-seconds": "24-demjimรชr bi รงirkeyan", + "common-european-date": "Awaya demรช ya Ewropรฎ", + "common-iso-date": "Awaya demรช ya ISO", + "common-us-date": "Awaya demรช ya DYE", + "common-weekday-date": "Roja hefteyรช bi demรช", + "common-weekday-month-day": "Rojรชn hefteyรช, meh รป roj", + "day-abbreviated": "Navรช rojรช ya kurtkirรฎ", + "day-full": "Navรช rojรช ya tevahรฎ", + "day-leading-zero": "Roj bi sifira destpรชkรช (01-31)", + "day-no-leading-zero": "Roj bรช sifira destpรชkรช (1-31)", + "hour-leading-zero": "Demjimรชr bi sifira destpรชkรช (00-23) โ€” 24-awaya demjimรชrรช", + "hour-no-leading-zero": "Demjimรชr bรช sifira destpรชkรช (0-23) โ€” 24-awaya demjimรชrรช", + "minute-leading-zero": "Xulek bi sifira destpรชkรช (00-59)", + "minute-no-leading-zero": "Xulek bรช sifira destpรชkรช (0-59)", + "month-abbreviated": "Navรช meha kurtkirรฎ", + "month-full": "Navรช mehรช yรช tevahรฎ", + "month-number-leading-zero": "Meh wekรฎ jimarek bi sifira destpรชkรช (01-12)", + "month-number-no-zero": "Meh wekรฎ jimarek bรช sifira destpรชkรช (1-12)", + "second-leading-zero": "ร‡irke bi sifira destpรชkรช (00-59)", + "second-no-leading-zero": "ร‡irke bรช sifira destpรชkรช (0-59)", + "timezone-abbreviation": "Kurteriya herรชma demรช", + "year-four-digit": "Sal wekรฎ รงar-jimar", + "year-two-digit": "Sal wekรฎ du-jimar (00-99)" + }, + "file-picker": { + "item": "hรชman", + "items": "hรชman", + "search-placeholder": "Li pel รป peldankan bigere...", + "select-current": "A heyรฎ hilbijรชre", + "select-file": "Pelรช hilbijรชre", + "select-folder": "Peldankรช hilbijรชre", + "selected": "Hilbijartรฎ:", + "title": "Hilbijรชrรช pelรช" + }, + "file-picker-title": "Pelekรช hilbijรชre", + "icon-picker": { + "title": "Hilbijรชrรช รฎkonรช" + }, + "tooltip-placeholder": "Cihgir" + }, + "wifi": { + "panel": { + "action-required": "ร‡alakรฎ pรชwรฎst e", + "available-interfaces": "Navrรปyรชn heyรฎ", + "available-networks": "Torรชn heyรฎ", + "disabled": "Wiโ€‘Fi neรงalak e", + "disconnecting": "Tรช qutkirin...", + "dns": "DNS", + "enable-message": "Wiโ€‘Fi รงalak bike da ku torรชn heyรฎ bibรฎnรฎ.", + "enter-password": "Borรฎnpeyva Wiโ€‘Fi tรชxรฎne...", + "forget": "Ji bรฎr bike", + "forget-network": "Vรช torรช ji bรฎr bike", + "forgetting": "Tรช jibรฎrkirin...", + "interface": "Navrรปya torรช", + "internet-connected": "รŽnternet hat girรชdan", + "internet-limited": "รŽnternet tune ye", + "internet-status": "RewลŸa รฎnternetรช", + "ipv4": "IPv4", + "known-networks": "Torรชn nas", + "link-speed": "Lรชza girรชdanรช", + "no-ethernet-devices": "Amรปrรชn Ethernetรช nehatin dรฎtin", + "no-networks": "Torรชn Wiโ€‘Fi nehatin dรฎtin", + "saved": "Hat tomarkirin", + "scan-again": "Dรฎsa bigere", + "searching": "Ji bo toran bigere...", + "title": "Wi-Fi" + }, + "signal": { + "excellent": "Bihรชz e", + "fair": "Navรฎn e", + "good": "BaลŸ e", + "poor": "Lawaz e" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/nl.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/nl.json new file mode 100644 index 0000000..8cb2029 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/nl.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "Geschiedenis wissen", + "close-app": "Sluit {app}", + "connect-vpn": "Verbinding maken met {name}", + "cycle-visualizer": "Cyclusvisualisatie", + "disable-bluetooth": "Bluetooth uitschakelen", + "disable-dnd": "'Niet storen' uitschakelen", + "disable-wifi": "Wi-Fi uitschakelen", + "disconnect-vpn": "Verbinding met {name} verbreken", + "dock-settings": "Dock-instellingen", + "enable-bluetooth": "Bluetooth inschakelen", + "enable-dnd": "'Niet storen' inschakelen", + "enable-wifi": "Wi-Fi inschakelen", + "launcher-settings": "Launcher instellingen", + "lower-to-bottom": "Laagste naar beneden", + "open-calendar": "Open agenda", + "open-display-settings": "Beeldscherminstellingen", + "open-launcher": "Launcher openen", + "open-settings": "Instellingen openen", + "raise-to-top": "Naar boven halen", + "random-wallpaper": "Willekeurige achtergrond", + "run-custom-command": "Aangepast commando uitvoeren", + "show-active-only": "Alleen actieve weergeven", + "show-all": "Alles tonen", + "toggle-mute": "Dempen aan/uit", + "widget-settings": "Widgetinstellingen" + }, + "authentication": { + "error": "Authenticatiefout", + "failed": "Authenticatie mislukt" + }, + "bar": { + "active-window": { + "colorize-icons-description": "Pas themakleuren toe op het pictogram van het actieve venster.", + "hide-mode-description": "Bepaalt hoe de widget zich gedraagt wanneer er geen actief venster is.", + "scrolling-mode-description": "Bepaalt wanneer tekstscrollen wordt ingeschakeld voor lange venstertitels.", + "show-app-icon-description": "Toon het applicatiepictogram naast de venstertitel.", + "show-app-icon-label": "App-pictogram tonen", + "show-app-text-description": "Toon de titel van het actieve venster.", + "show-app-text-label": "Venstertitel weergeven" + }, + "audio-visualizer": { + "color-name-description": "Selecteer de kleur voor de visualizer.", + "color-name-label": "Vulkleur", + "height-description": "Aangepaste componentbreedte.", + "hide-when-idle-description": "Indien ingeschakeld wordt de visualizer verborgen, tenzij een speler actief afspeelt.", + "hide-when-idle-label": "Verbergen wanneer geen media speelt", + "width-description": "Aangepaste componentbreedte." + }, + "battery": { + "device-default": "Standaard (weergaveapparaat)", + "device-description": "Selecteer welk accuapparaat u wilt weergeven.", + "device-label": "Accuapparaat", + "display-mode-description": "Kies hoe de batterij wordt weergegeven in de balk.", + "display-mode-graphic": "Grafische accu", + "display-mode-graphic-clean": "Grafische accu (geen %)", + "display-mode-icon-always": "Pictogram - Altijd % tonen", + "display-mode-icon-hover": "Icoon - Tonen bij hover", + "display-mode-icon-only": "Alleen pictogram", + "hide-if-idle-description": "Verberg de widget wanneer de accu niet wordt opgeladen of ontladen.", + "hide-if-idle-label": "Verbergen indien inactief", + "hide-if-not-detected-description": "Verberg de widget als er geen accu op het systeem wordt gedetecteerd.", + "hide-if-not-detected-label": "Verbergen indien niet gedetecteerd", + "low-battery-threshold-label": "Waarschuwingsdrempel voor lage accu", + "show-noctalia-performance-description": "Toon de Noctalia-prestatiemodus-schakelaar in het accupaneel.
Schakelt schaduwen en animaties in Noctalia uit om het resourcegebruik te verminderen.", + "show-noctalia-performance-label": "Noctalia-Performance-schakelaar tonen", + "show-power-profile-description": "Toon de keuze van het stroomprofiel in het accupaneel.", + "show-power-profile-label": "Stroomprofielbediening tonen" + }, + "brightness": { + "apply-all-description": "Helderheid voor alle monitoren wijzigen bij gebruik van het scrollwiel.", + "apply-all-label": "Schuifwijzigingen toepassen op alle monitoren" + }, + "clock": { + "clock-display-description": "Pas de weergave van je klok aan door tokens uit onderstaande lijst toe te voegen. Om de 12-uursnotatie te gebruiken, moet je de 'AP'-token opnemen.", + "clock-display-label": "Klokweergave", + "custom-font-description": "Selecteer een aangepast lettertype voor de klokweergave.", + "custom-font-label": "Aangepast lettertype", + "custom-font-placeholder": "Selecteer aangepast lettertype...", + "custom-font-search-placeholder": "Lettertypen zoeken...", + "horizontal-bar-description": "Tip: gebruik \\n om een regeleinde te maken.", + "horizontal-bar-label": "Horizontale balk", + "preview": "Voorbeeld", + "tooltip-format-description": "Opmaakreeks voor de tooltip die wordt weergegeven bij het zweven over de klok. Laat leeg om de standaard tooltip te gebruiken.", + "tooltip-format-label": "Tooltip-opmaak", + "use-custom-font-description": "Overschrijf het standaardlettertype met een aangepast lettertype voor de klok.", + "use-custom-font-label": "Aangepast lettertype gebruiken", + "use-monospaced-font-description": "Indien ingeschakeld gebruikt de klok het monospace-lettertype.", + "use-monospaced-font-label": "Monospace-lettertype gebruiken", + "vertical-bar-description": "Gebruik een spatie om elk deel op een nieuwe regel te plaatsen.", + "vertical-bar-label": "Verticale balk" + }, + "control-center": { + "browse-file": "Bestand kiezen", + "browse-library": "Bibliotheek doorbladeren", + "color-selection-description": "Themakleuren toepassen op pictogrammen.", + "enable-colorization-description": "Schakel kleurtoepassing voor pictogram in, themakleuren toepassend.", + "icon-description": "Selecteer een pictogram uit de bibliotheek of een aangepast bestand.", + "select-custom-icon": "Aangepast pictogram selecteren", + "use-distro-logo-description": "Gebruik het logo van je distributie in plaats van een aangepast pictogram.", + "use-distro-logo-label": "Distributielogo gebruiken in plaats van pictogram" + }, + "custom-button": { + "collapse-condition-description": "Als de uitvoer overeenkomt met deze waarde, wordt de knop samengevouwen.", + "collapse-condition-label": "Inklapvoorwaarde", + "color-selection-description": "Pas themakleuren toe op pictogram en tekst.", + "default-tooltip": "Aangepaste knop, configureren in instellingen", + "display-command-output-description": "Voer een commando in dat regelmatig wordt uitgevoerd. De eerste regel van de uitvoer wordt als tekst weergegeven.", + "display-command-output-label": "Commando-uitvoer weergeven", + "display-command-output-stream-description": "Voer een commando in dat continu wordt uitgevoerd.", + "dynamic-text": "Dynamische tekst", + "enable-colorization-description": "Kleurtoepassing inschakelen voor het aangepaste knoppictogram en de tekst, waarbij themakleuren worden toegepast.", + "enable-colorization-label": "Kleurtoepassing inschakelen", + "general-tooltip-text-description": "Aangepaste tekst om weer te geven in de tooltip van de knop.", + "general-tooltip-text-label": "Aangepaste tooltiptekst", + "hide-mode-always-expanded": "Altijd uitgeklapt", + "hide-mode-description": "Bepaalt de zichtbaarheid van de widget wanneer het commando geen uitvoer heeft.", + "hide-mode-expand-with-output": "Uitklappen wanneer er uitvoer is", + "hide-mode-label": "Verbergmodus", + "hide-mode-max-transparent": "Maximaal uitgeklapt maar transparant", + "icon-description": "Selecteer een pictogram uit de bibliotheek.", + "ipc-identifier-description": "Unieke identificatie voor IPC-opdrachten. Gebruik deze identificatie met 'qs -c noctalia-shell ipc call cb [actie] [identificatie]' om deze knop via IPC te bedienen.", + "ipc-identifier-label": "IPC-identificatie", + "left-click-description": "Commando dat wordt uitgevoerd wanneer met de linkermuisknop op de knop wordt geklikt.", + "left-click-label": "Linkermuisklik", + "left-click-update-text": "Tekst bijwerken bij linksklik", + "max-text-length-horizontal-description": "Maximaal aantal tekens dat moet worden weergegeven in horizontale balk (0 om tekst te verbergen).", + "max-text-length-horizontal-label": "Max. tekstlengte (horizontaal)", + "max-text-length-vertical-description": "Maximaal aantal tekens dat moet worden weergegeven in verticale balk (0 om tekst te verbergen).", + "max-text-length-vertical-label": "Max. tekstlengte (verticaal)", + "middle-click-description": "Commando dat wordt uitgevoerd wanneer met de middelste muisknop op de knop wordt geklikt.", + "middle-click-label": "Middelste muisklik", + "middle-click-update-text": "Tekst bijwerken bij middelste muisklik", + "parse-json-description": "Interpreteer de commando-uitvoer als JSON-object om tekst en pictogram dynamisch in te stellen.", + "parse-json-label": "Uitvoer als JSON parseren", + "refresh-interval-description": "Interval in milliseconden.", + "refresh-interval-label": "Verversingsinterval", + "right-click-description": "Commando dat wordt uitgevoerd wanneer met de rechtermuisknop op de knop wordt geklikt.", + "right-click-label": "Rechtermuisklik", + "right-click-update-text": "Tekst bijwerken die wordt weergegeven bij rechtsklikken", + "show-exec-tooltip-description": "Tooltips met commando details weergeven (linker-/rechter-/middenklik, scrollwiel).", + "show-exec-tooltip-label": "Opdracht-tooltips weergeven", + "show-icon-description": "Schakelt de zichtbaarheid van het widgetpictogram.", + "show-icon-label": "Pictogram weergeven", + "show-text-tooltip-description": "Toon tooltips met de uitvoer van het tekstcommando.", + "show-text-tooltip-label": "Dynamische teksttooltips weergeven", + "text-stream-description": "Gestreamde regels uit het commando worden als tekst op de knop weergegeven.", + "text-stream-label": "Stream", + "wheel-description": "Commando om uit te voeren wanneer het scrollwiel wordt gebruikt.
Gebruik $delta voor de scrollwiel-delta in het commando.", + "wheel-down": "Scrollwiel omlaag", + "wheel-down-description": "Commando om uit te voeren wanneer het scrollwiel omlaag wordt bewogen.", + "wheel-down-label": "Scrollwiel omlaag commando", + "wheel-label": "Scrollwiel", + "wheel-mode-separate-description": "Afzonderlijke commando's inschakelen voor scrollwiel omhoog en omlaag.", + "wheel-mode-separate-label": "Afzonderlijke scrollwielcommando's", + "wheel-up": "Scrollwiel omhoog", + "wheel-up-description": "Commando om uit te voeren wanneer het scrollwiel omhoog wordt bewogen.", + "wheel-up-label": "Scrollwiel omhoog commando", + "wheel-update-text": "Weergegeven tekst bij scrollen bijwerken" + }, + "keyboard-layout": { + "show-icon-description": "Toon het toetsenbordindeling-icoon." + }, + "lock-keys": { + "hide-when-off-description": "Verberg de indicator wanneer de toets niet actief is.", + "hide-when-off-label": "Verbergen wanneer uitgeschakeld", + "show-caps-lock-description": "Toon de status van caps lock.", + "show-caps-lock-label": "Caps Lock", + "show-num-lock-description": "Toon de status van num lock.", + "show-num-lock-label": "Num Lock", + "show-scroll-lock-description": "Toon de status van scroll lock.", + "show-scroll-lock-label": "Scroll Lock" + }, + "media-mini": { + "compact-mode-description": "Schakel een ruimtebesparende lay-out in voor het mediaplayerpaneel.", + "compact-mode-label": "Compacte modus", + "hide-mode-description": "Bepaalt hoe de widget zich gedraagt wanneer er geen media wordt afgespeeld.", + "max-width-description": "Stelt de maximale horizontale grootte van de widget in. De widget krimpt mee met kortere inhoud.", + "no-active-player": "Geen actieve speler", + "panel-section-description": "Configureer het uiterlijk en gedrag van het mediaplayerpaneel.", + "panel-section-label": "Mediaplayerpaneel", + "scrolling-mode-description": "Bepaalt wanneer tekstscrollen wordt ingeschakeld voor lange tracktitels.", + "scrolling-mode-label": "Scrollmodus", + "show-album-art-description": "Toon de albumhoes van het huidige nummer.", + "show-album-art-label": "Albumhoes tonen", + "show-artist-first-description": "Toon artiest - titel in plaats van titel - artiest.", + "show-artist-first-label": "Toon eerst de artiest", + "show-progress-ring-description": "Toon een circulaire voortgangsindicator die het bestandsspoor voortgang toont.", + "show-progress-ring-label": "Voortgangscirkel tonen", + "show-visualizer-description": "Toon een audiovisualizer wanneer muziek wordt afgespeeld.", + "show-visualizer-label": "Visualizer tonen", + "use-fixed-width-description": "Indien ingeschakeld gebruikt de widget altijd de maximale breedte in plaats van zich aan te passen aan de inhoud.", + "use-fixed-width-label": "Vaste breedte gebruiken", + "visualizer-type-description": "Kies de stijl van de audiovisualizer.", + "visualizer-type-label": "Type visualizer" + }, + "notification-history": { + "hide-widget-when-zero-description": "Het meldingspictogram verbergen wanneer er geen meldingen zijn.", + "hide-widget-when-zero-label": "Pictogram verbergen zonder meldingen", + "hide-widget-when-zero-unread-description": "Het meldingspictogram verbergen wanneer er geen ongelezen meldingen zijn.", + "hide-widget-when-zero-unread-label": "Pictogram verbergen zonder ongelezen meldingen", + "show-unread-badge-description": "Toon een badge met het aantal ongelezen meldingen.", + "show-unread-badge-label": "Ongelezen-badge tonen", + "unread-badge-color-description": "Selecteer de kleur voor de badge van ongelezen meldingen.", + "unread-badge-color-label": "Kleur van de ongelezen badge" + }, + "section-editor": { + "placeholder": "Selecteer een widget om toe te voegen...", + "search-placeholder": "Widget zoeken..." + }, + "spacer": { + "width-description": "Breedte van de ruimte in pixels." + }, + "system-monitor": { + "compact-mode-description": "Statistieken weergeven als mini-staafdiagrammen in plaats van tekstwaarden. Voorkomt lay-outverschuiving.", + "compact-mode-label": "Compacte modus", + "cpu-cores-description": "Het gebruik van CPU-kernen individueel weergeven.", + "cpu-cores-label": "CPU-kernen", + "cpu-frequency-description": "De huidige CPU-kloksnelheid in GHz weergeven.", + "cpu-frequency-label": "CPU-frequentie weergeven", + "cpu-temperature-description": "Toon CPU-temperatuur indien beschikbaar.", + "cpu-temperature-label": "CPU-temperatuur", + "cpu-usage-description": "Toon het huidige CPU-gebruik in procent.", + "cpu-usage-label": "CPU-gebruik", + "disk-path-description": "Selecteer welk schijfkoppelpunt u wilt monitoren.", + "disk-path-label": "Schijfpad", + "gpu-temperature-description": "Toon GPU-temperatuurmetingen indien beschikbaar.", + "load-average-description": "Systeembelasting gemiddelde weergeven.", + "load-average-label": "Gemiddelde belasting", + "memory-percentage-description": "Toon geheugengebruik als percentage in plaats van absolute waarden.", + "memory-percentage-label": "Geheugen als percentage", + "memory-usage-description": "Toon informatie over huidig RAM-gebruik.", + "memory-usage-label": "Geheugengebruik", + "network-traffic-description": "Toon uploadsnelheid en downloadsnelheid van het netwerk.", + "network-traffic-label": "Netwerkverkeer", + "storage-as-percentage-description": "Toon schijfruimte als percentage in plaats van absolute waarden.", + "storage-as-percentage-label": "Schijf als percentage", + "storage-available-description": "Toont hoeveel schijfruimte beschikbaar is in plaats van hoeveel er wordt gebruikt.", + "storage-available-label": "Beschikbare schijfruimte", + "storage-usage-description": "Toon informatie over schijfruimtegebruik.", + "storage-usage-label": "Opslaggebruik", + "swap-usage-description": "Toon swap geheugengebruik.", + "swap-usage-label": "Swapgebruik", + "use-monospace-font-description": "Gebruik een monospace lettertype voor een consistente tekenbreedte.", + "use-monospace-font-label": "Lettertype met vaste breedte", + "use-padding-description": "Vult de tekstwaarden aan met voorloopspaties om lay-outverschuiving te voorkomen.", + "use-padding-description-disabled-monospace-font": "Een monospace lettertype is nodig voor deze functie.", + "use-padding-description-disabled-vertical": "De verticale taakbalk ondersteunt geen tekstvulling.", + "use-padding-label": "Tekstopvulling" + }, + "taskbar": { + "colorize-icons-description": "Pas themakleuren toe op taakbalkpictogrammen.", + "hide-mode-description": "Bepaalt hoe de widget zich gedraagt wanneer er geen overeenkomende vensters zijn.", + "hide-mode-label": "Verbergmodus", + "icon-scale-description": "Stelt de schaalfactor in voor taakbalkpictogrammen.", + "icon-scale-label": "Icoon schaling", + "max-width-description": "Maximale breedte van de taakbalk als percentage van de schermbreedte.", + "max-width-label": "Maximale breedte", + "only-active-workspaces-description": "Toon alleen apps van actieve werkruimten.", + "only-active-workspaces-label": "Alleen van actieve werkruimten", + "only-same-monitor-description": "Toon alleen apps van de monitor waar de balk zich bevindt.", + "only-same-monitor-label": "Alleen vanaf dezelfde monitor", + "show-pinned-apps-description": "Toon vastgemaakte apps van het dock in de taakbalk.", + "show-pinned-apps-label": "Toon vastgemaakte apps", + "show-title-description": "Venster titels weergeven in de taakbalk.", + "show-title-description-disabled": "Verticale taakbalk ondersteunt geen titelopties.", + "show-title-label": "Titel weergeven", + "smart-width-description": "Pas automatisch de breedte van items aan op basis van het aantal items.", + "smart-width-label": "Slimme breedte", + "title-width-description": "Stel de breedte van venstertitels in de taakbalk in (in pixels).", + "title-width-label": "Titelbreedte", + "title-width-reset-tooltip": "Titelbreedte resetten" + }, + "tray": { + "chevron-color-description": "Themakleuren toepassen op het pijl-icoon van de lade.", + "chevron-color-label": "Pijlkleur", + "colorize-icons-description": "Pas themakleuren toe op systeemvakpictogrammen.", + "colorize-icons-label": "Pictogrammen inkleuren", + "drawer-enabled-description": "Indien ingeschakeld worden niet-vastgemaakte systeemvakitems in een lade weergegeven.
Indien uitgeschakeld worden alle items inline getoond.", + "drawer-enabled-label": "Lade inschakelen", + "hide-passive-description": "Indien ingeschakeld, worden tray-items met de status 'Passief' verborgen.", + "hide-passive-label": "Verberg passieve items" + }, + "volume": { + "display-mode-description": "Kies hoe je wilt dat deze waarde wordt weergegeven." + }, + "workspace": { + "character-count-description": "Aantal tekens dat wordt weergegeven van werkruimtenamen (1-10).", + "character-count-label": "Aantal tekens", + "empty-color-description": "Stel de achtergrondkleur in voor lege Workspaces.", + "empty-color-label": "Kleur van lege Workspace", + "enable-scrollwheel-description": "Schakel tussen werkruimtes met het muiswiel.", + "enable-scrollwheel-label": "Scroll om tussen werkruimtes te schakelen", + "focused-color-description": "Stel de achtergrondkleur in voor de gefocuste Workspace.", + "focused-color-label": "Kleur van de gefocuste Workspace", + "follow-focused-screen-description": "Werkruimten weergeven van het momenteel gefocuste scherm, in plaats van het scherm waar de balk zich bevindt.", + "follow-focused-screen-label": "Gefocust scherm volgen", + "font-weight-description": "Stel het visuele gewicht in voor tekst binnen de werkruimte.", + "font-weight-label": "Lettertypegewicht", + "grouped-border-opacity-description": "Stel het dekkingsniveau in voor de randen van de werkruimtecontainer.", + "grouped-border-opacity-label": "Randdekking", + "hide-unoccupied-description": "Werkruimten zonder vensters niet weergeven.", + "hide-unoccupied-label": "Ongebruikte verbergen", + "label-mode-description": "Kies hoe labels van werkruimten worden weergegeven.", + "label-mode-label": "Labelmodus", + "occupied-color-description": "Stel de achtergrondkleur in voor bezette Workspaces.", + "occupied-color-label": "Kleur van bezette workspace", + "pill-size-description": "Pas de grootte van de werkruimte-pillen aan (50%-100%).", + "pill-size-label": "Pilgrootte", + "reverse-scrolling-description": "Keer de richting van het wisselen van werkruimtes om bij het scrollen.", + "reverse-scrolling-label": "Omgekeerd scrollen", + "show-applications-description": "Toon applicatiepictogrammen in elke werkruimte.", + "show-applications-hover-description": "Toon applicatiepictogrammen in elke werkruimte wanneer de werkruimte wordt aangewezen.", + "show-applications-hover-label": "Toon applicaties bij hoveren", + "show-applications-label": "Toon applicaties", + "show-badge-description": "Toon de werkomgevingnummerbadge in gegroepeerde modus.", + "show-badge-label": "Werkruimtebadge weergeven", + "show-labels-only-when-occupied-description": "Toon alleen werkruimtelabels wanneer ze vensters bevatten.", + "show-labels-only-when-occupied-label": "Toon labels alleen wanneer bezet", + "unfocused-icons-opacity-description": "Stel het dekkingsniveau in voor app-pictogrammen die niet in focus zijn.", + "unfocused-icons-opacity-label": "Ondoorzichtige, niet-focusseerbare pictogrammen" + } + }, + "battery": { + "all-batteries": "Alle accu's (gecombineerd)", + "battery-health": "Accuconditie", + "battery-level": "Accuniveau", + "capacity-level": "Capaciteit: {level}", + "charging-rate": "Laadsnelheid: {rate} W", + "discharging-rate": "Ontlaadsnelheid: {rate} W", + "health": "Gezondheid: {percent}%", + "inhibit-idle-description": "Houdt het systeem wakker.", + "no-battery-detected": "Geen accu gedetecteerd", + "plugged-in": "Op netstroom", + "power-profile": "Energieprofiel", + "time-left": "Resterende tijd: {time}", + "time-until-full": "Tijd tot vol: {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "Beschikbare apparaten", + "blocked": "Geblokkeerd", + "connected-devices": "Verbonden apparaten", + "device-address": "Apparaatadres", + "disabled": "Bluetooth is uitgeschakeld", + "discoverable": "Zichtbaar", + "enable-message": "Schakel Bluetooth in om beschikbare apparaten te zien.", + "known-devices": "Bekende apparaten", + "no-devices": "Geen apparaten beschikbaar", + "paired-devices": "Gekoppelde apparaten", + "pairing-mode": "Zorg ervoor dat je apparaat in koppelmodus staat.", + "scanning": "Zoeken naar apparaten...", + "signal-text-excellent": "Signaal: Uitstekend", + "signal-text-fair": "Signaal: Redelijk", + "signal-text-good": "Signaal: Goed", + "signal-text-poor": "Signaal: Zwak", + "signal-text-unknown": "Signaal: Onbekend", + "signal-text-very-poor": "Signaal: Zeer slecht" + } + }, + "changelog": { + "error": { + "fetch-failed": "Kan changeloggegevens niet laden. Probeer het later opnieuw.", + "rate-limit": "GitHub-limiet bereikt. Probeer het over enkele minuten opnieuw." + }, + "panel": { + "buttons-discord": "Word lid van onze Discord", + "buttons-dismiss": "OK", + "buttons-feedback": "Geef feedback", + "empty": "Er zijn nog geen release-opmerkingen beschikbaar.", + "highlight-title": "Hoogtepunten", + "released": "Uitgebracht op {date}", + "subtitle-fresh": "Bedankt voor het installeren van Noctalia! Dit zit er in deze build.", + "subtitle-updated": "Bijgewerkt vanaf {previousVersion}", + "title": "Wat is er nieuw in {version}", + "version": "Versie {version}", + "version-new-user": "Nieuwe installatie" + } + }, + "common": { + "actions": "Acties", + "active": "Actief", + "add": "Toevoegen", + "appearance": "Uiterlijk", + "apply": "Toepassen", + "auto-connect": "Automatisch verbinden", + "automation": "Automatisering", + "available": "Beschikbaar", + "back": "Terug", + "battery": "Accu", + "behavior": "Gedrag", + "bluetooth": "Bluetooth", + "brightness": "Helderheid", + "browse": "Bladeren", + "calendar": "Kalender", + "calendar-panel": "Kalenderpaneel", + "cancel": "Annuleren", + "cards": "Kaarten", + "charging": "Opladen", + "clear": "Wissen", + "clipboard": "Klembord", + "close": "Sluiten", + "color-muted": "Gedempt", + "colors": "Kleuren", + "command": "Commando", + "confirm": "Bevestigen", + "connect": "Verbinden", + "connected": "Verbonden", + "connecting": "Verbinden...", + "continue": "Doorgaan", + "contributors": "Medewerkers", + "copied-to-clipboard": "Gekopieerd naar klembord", + "countdown": "Aftellen", + "customize": "Aanpassen", + "date": "Datum", + "default": "Standaard", + "delete": "Verwijderen", + "devices": "Apparaten", + "disabled": "Uitgeschakeld", + "discharging": "Afvoeren", + "disconnect": "Verbreek de verbinding", + "disconnected": "Verbinding verbroken", + "disconnecting": "Verbinding verbroken...", + "display-mode": "Weergavemodus", + "documentation": "Documentatie", + "download": "Downloaden", + "duration": "Duur", + "dysfunctional": "Dysfunctioneel", + "edit": "Bewerken", + "enabled": "Ingeschakeld", + "error": "Fout", + "ethernet": "Ethernet", + "events": "Evenementen", + "execute": "Uitvoeren", + "faithful": "Getrouw", + "focus": "Focus", + "font-weight-bold": "Vet", + "font-weight-light": "Licht", + "font-weight-medium": "Medium", + "font-weight-regular": "Normaal", + "font-weight-semibold": "Semi-vet", + "frequency": "Frequentie", + "gateway": "Poort", + "general": "Algemeen", + "height": "Hoogte", + "hibernate": "Sluimeren", + "history": "Geschiedenis", + "icon": "Icoon", + "idle": "Inactief", + "info": "Info", + "input": "Invoer", + "install": "Installeren", + "installed": "Geรฏnstalleerd", + "interface": "Interface", + "internet": "Internet", + "keybind": "Toetsencombinatie", + "language": "Taal", + "loading": "Laden...", + "local": "Lokaal", + "location": "Locatie", + "lock": "Vergrendelen", + "logout": "Uitloggen", + "look": "Uiterlijk", + "margins": "Marges", + "media": "Media", + "media-player": "Mediaplayer", + "memory": "Geheugen", + "monitors": "Monitoren", + "move-down": "Naar beneden verplaatsen", + "move-up": "Omhoog verplaatsen", + "network": "Netwerk", + "next": "Volgende", + "night-light": "Nachtlicht", + "no": "Nee", + "no-results": "Geen resultaten", + "no-summary": "Geen samenvatting", + "none": "Geen", + "not-found": "Niet gevonden", + "notifications": "Meldingen", + "official": "Officieel", + "on-surface": "Op oppervlak", + "output": "Uitvoer", + "pair": "Koppelen", + "paired": "Gekoppeld", + "pairing": "Koppelen...", + "panels": "Panelen", + "password": "Wachtwoord", + "pause": "Pauze", + "performance": "Prestaties", + "pin": "Vastmaken", + "play": "Afspelen", + "polling": "Polling", + "position": "Positie", + "previous": "Vorige", + "primary": "Primair", + "random": "Willekeurig", + "reboot": "Herstarten", + "reboot-to-uefi": "Herstarten naar UEFI", + "record": "Opname starten", + "refresh": "Vernieuwen", + "required": "(vereist)", + "reset": "Resetten", + "reset-to-default": "Herstellen naar Standaard", + "result-count": "{count} resultaat", + "result-count-plural": "{count} resultaten", + "resume": "Hervatten", + "save": "Opslaan", + "scanning": "Scannen...", + "screen-corners": "Schermhoeken", + "search": "Zoeken", + "second": "{count} seconde", + "second-plural": "{count} seconden", + "secondary": "Secundair", + "security": "Beveiliging", + "select": "Selecteer", + "select-color": "Kleur selecteren", + "select-color-description": "Themakleuren toepassen voor nadruk.", + "select-icon-color": "Icoonkleur selecteren", + "settings": "Instellingen", + "shortcuts": "Snelkoppelingen", + "shutdown": "Afsluiten", + "signal": "Signaal", + "sound": "Geluid", + "sources": "Bronnen", + "start": "Begin", + "stop": "Stop", + "supporters": "Ondersteuners", + "suspend": "Onderbreken", + "templates": "Sjablonen", + "tertiary": "Tertiair", + "test": "Test", + "thresholds": "Drempels", + "title": "Titel", + "toast": "Toast", + "trusted": "Vertrouwd", + "uninstall": "Verwijderen", + "unknown": "Onbekend", + "unpair": "Ontkoppelen", + "unpin": "Losmaken", + "update": "Update", + "upload": "Uploaden", + "username": "Gebruikersnaam", + "userspace-reboot": "Gebruikersruimte herstarten", + "version": "Versie", + "vibrant": "Levendig", + "visualizer": "Visualiseerder", + "volume": "Volume", + "volumes": "Volumes", + "wallpaper": "Achtergrond", + "weather": "Weer", + "weather-loading": "Weer laden...", + "weather-no-location": "Stel een locatie in de Instellingen in om het weer te zien", + "week": "Week", + "widgets": "Widgets", + "width": "Breedte", + "wifi": "Wi-Fi", + "windows": "Vensters", + "yes": "Ja" + }, + "control-center": { + "power-profile": { + "tooltip-action": "Energieprofiel", + "tooltip-disabled": "Installeer power-profiles-daemon om energieprofielen te gebruiken" + }, + "wifi": { + "label-disconnected": "Wi-Fi niet verbonden", + "label-ethernet": "Ethernet" + } + }, + "display-modes": { + "always-hide": "Altijd verbergen", + "always-show": "Altijd weergeven", + "force-open": "Openbreken", + "on-hover": "Bij hover" + }, + "hide-modes": { + "auto-hide": "Automatisch verbergen", + "hidden": "Verbergen indien leeg", + "idle": "Verbergen indien inactief", + "non-exclusive": "Niet-exclusief", + "transparent": "Transparant wanneer leeg", + "visible": "Altijd zichtbaar" + }, + "launcher": { + "categories": { + "all": "Alle", + "audiovideo": "Audio & video", + "chat": "Chat", + "development": "Ontwikkeling", + "education": "Onderwijs", + "emoji-activity": "Activiteit", + "emoji-animals": "Dieren", + "emoji-flags": "Vlaggen", + "emoji-food": "Eten en drinken", + "emoji-nature": "Natuur", + "emoji-objects": "Objecten", + "emoji-people": "Mensen en lichaam", + "emoji-recent": "Recent", + "emoji-symbols": "Symbolen", + "emoji-travel": "Reizen en plaatsen", + "game": "Spellen", + "graphics": "Grafische weergave", + "misc": "Diversen", + "office": "Kantoor", + "pinned": "Vastgemaakt", + "system": "Systeem", + "webbrowser": "Webbrowser" + }, + "providers": { + "applications": "Applicaties", + "calculator": "Rekenmachine", + "calculator-deprecated": "De >calc opdracht is verouderd en zal binnenkort verwijderd worden. Typ wiskundige uitdrukkingen in plaats daarvan direct in de zoekopdracht.", + "calculator-description": "Rekenmachine โ€” bereken wiskundige expressies", + "calculator-enter-expression": "Voer een wiskundige expressie in", + "calculator-press-enter-to-copy": "Druk op Enter om het resultaat te kopiรซren", + "clipboard": "Klembordgeschiedenis", + "clipboard-clear-description": "Klembordgeschiedenis wissen", + "clipboard-clear-description-full": "Alle items uit de klembordgeschiedenis verwijderen", + "clipboard-clear-history": "Klembordgeschiedenis wissen", + "clipboard-delete": "Klemborditem verwijderen", + "clipboard-history-disabled": "Klembordgeschiedenis uitgeschakeld", + "clipboard-history-disabled-description": "Schakel klembordgeschiedenis in de instellingen in of installeer cliphist", + "clipboard-loading": "Klembordgeschiedenis laden...", + "clipboard-search-description": "Zoek in klembordgeschiedenis", + "command-description": "Shell-opdrachten uitvoeren", + "emoji": "Emoji-kiezer", + "emoji-loading": "Emoji's laden...", + "emoji-loading-description": "Even geduld", + "emoji-no-recent": "Nog geen recente emoji's", + "emoji-search-description": "Zoek en kopieer emoji's", + "settings-search-description": "Zoeken en navigeren naar instellingen", + "windows-search-description": "Zoek en focus open vensters" + } + }, + "lock-screen": { + "authenticating": "Bezig met verifiรซren...", + "password": "Voer je wachtwoord in...", + "restart": "Opnieuw opstarten", + "shut-down": "Uitschakelen" + }, + "notifications": { + "panel": { + "click-to-collapse": "Klikken om in te klappen", + "click-to-expand": "Klik om uit te vouwen", + "description": "Je meldingen verschijnen hier zodra ze binnenkomen.", + "no-notifications": "Geen meldingen" + }, + "range": { + "earlier": "Eerder", + "today": "Vandaag", + "yesterday": "Gisteren" + }, + "time": { + "diff-d": "1 dag geleden", + "diff-dd": "{diff} dagen geleden", + "diff-h": "1 uur geleden", + "diff-hh": "{diff} uur geleden", + "diff-m": "1 minuut geleden", + "diff-mm": "{diff} minuten geleden", + "now": "nu" + } + }, + "options": { + "bar": { + "density-comfortable": "Comfortabel", + "density-compact": "Compact", + "density-default": "Standaard", + "density-mini": "Mini", + "density-spacious": "Ruim", + "display-mode-always-visible": "Altijd zichtbaar", + "type-floating": "Zwevend", + "type-framed": "Omkaderd", + "type-simple": "Eenvoudig" + }, + "control-center": { + "quick-settings-style-classic": "Klassiek", + "quick-settings-style-modern": "Modern" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "Comfortabel", + "compact": "Compact", + "default": "Standaard" + }, + "launcher-view-mode": { + "grid": "Raster", + "list": "Lijst" + }, + "notification-density": { + "compact": "Compact", + "default": "Standaard" + }, + "scrolling-modes": { + "always": "Altijd scrollen", + "hover": "Scrollen bij hover", + "never": "Nooit scrollen" + }, + "session-menu-grid-layout": { + "grid": "Raster", + "single-row": "Enkele rij" + }, + "settings-panel-mode": { + "attached": "Paneel bevestigd aan de balk", + "centered": "Gecentreerd paneel", + "window": "Apart venster" + }, + "visualizer-types": { + "linear": "Lineair", + "mirrored": "Gespiegeld", + "wave": "Golf" + }, + "visualizer-visibility": { + "with-background": "Alleen met achtergrond" + }, + "workspace-labels": { + "index": "Index", + "index-and-name": "Index en naam", + "name": "Naam" + } + }, + "panels": { + "about": { + "become-supporter": "Word supporter", + "changelog": "Bekijk wijzigingslogboek", + "changelog-on-startup": "Toon wijzigingslogboek bij update", + "changelog-on-startup-desc": "Toon automatisch de changelog wanneer Noctalia wordt bijgewerkt.", + "contributors-description": "Een shout-out naar onze {count} geweldige bijdrager!", + "contributors-description-plural": "Een shout-out naar onze {count} geweldige bijdragers!", + "copy-info": "Kopieer info", + "debug-disabled": "Foutopsporingsmodus uitgeschakeld", + "debug-enabled": "Foutopsporingsmodus ingeschakeld", + "info-copied": "Info gekopieerd naar klembord", + "noctalia-available": "Beschikbaar:", + "noctalia-desc": "Een strakke en minimale desktopshell, met zorg gemaakt voor Wayland en gebouwd met Quickshell.", + "noctalia-git-commit": "Git commit:", + "noctalia-installed-version": "Geรฏnstalleerde versie:", + "noctalia-latest-version": "Nieuwste versie:", + "noctalia-qs-version": "Noctalia QS versie:", + "noctalia-title": "Noctalia-shell", + "privacy-policy": "Privacybeleid", + "support": "Steun ons", + "supporter-badge": "Supporter", + "supporters-desc": "Een enorme dank aan onze geweldige supporter!", + "supporters-desc-plural": "Enorm bedankt aan onze {count} geweldige supporters!", + "supporters-loading": "Donateurs laden...", + "system-board": "Moederbord:", + "system-cpu": "CPU:", + "system-disk": "Schijf:", + "system-gpu": "GPU:", + "system-host": "Host:", + "system-install-hint": "Installeer fastfetch om systeeminformatie te bekijken", + "system-kernel": "Kernel:", + "system-loading": "Systeeminformatie laden...", + "system-memory": "Geheugen:", + "system-monitor": "Monitor:", + "system-not-installed": "fastfetch is niet geรฏnstalleerd", + "system-os": "Besturingssysteem:", + "system-packages": "Pakketten:", + "system-product": "Product:", + "system-title": "Systeeminformatie", + "system-uptime": "Uptime:", + "system-wm": "WM:", + "telemetry-data-copied": "Telemetriegegevens gekopieerd naar klembord", + "telemetry-desc": "Help Noctalia te verbeteren door anonieme systeeminformatie (schermresolutie, compositor, distributie) te delen. Wordt eenmalig bij het opstarten verzonden, geen tracking, data wordt na 30 dagen automatisch verwijderd.", + "telemetry-enabled": "Anonieme systeeminformatie verzenden", + "telemetry-show-data": "Gegevens bekijken", + "telemetry-title": "Privacy", + "title": "Over", + "up-to-date": "Je bent up-to-date!", + "update-available": "Update beschikbaar", + "view-commit": "Bekijk commit op GitHub" + }, + "audio": { + "devices-desc": "Configureer beschikbare audio-invoer- en -uitvoerapparaten.", + "devices-input-device-description": "Selecteer het gewenste audio-invoerapparaat.", + "devices-input-device-label": "Invoerapparaat", + "devices-output-device-description": "Selecteer het gewenste audio-uitvoerapparaat.", + "devices-output-device-label": "Uitvoerapparaat", + "devices-title": "Audio-apparaten", + "external-mixer-description": "Voer het commando of het applicatiepad in om te starten bij het activeren van de externe audiomixerfunctie.", + "external-mixer-label": "Extern audio mixer commando", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "Stel je favoriete en genegeerde media-applicaties in.", + "media-excluded-player-description": "Voeg trefwoorden toe voor spelers die het systeem moet negeren. Elk trefwoord moet op een nieuwe regel staan.", + "media-excluded-player-label": "Uitgesloten spelers", + "media-excluded-player-placeholder": "typ een substring en druk op +", + "media-frame-rate-description": "Hogere waarden zijn vloeiender maar gebruiken meer resources.", + "media-frame-rate-label": "Framesnelheid", + "media-primary-player-description": "Voer een trefwoord in om je hoofdspeler te herkennen.", + "media-primary-player-label": "Primaire speler", + "media-primary-player-placeholder": "bijv. spotify, vlc, mpv", + "media-scrolling-speed-description": "Tijd in seconden waarin de titel van begin tot eind scrolt.", + "media-scrolling-speed-label": "Scrollsnelheid", + "media-scrolling-title-description": "Schakel continu scrollen in voor lange mediatitels.", + "media-scrolling-title-label": "Scrollende titel", + "media-title": "Mediaplayers", + "on-middle-clicked-description": "Commando dat moet worden uitgevoerd wanneer er met de middelste muisknop op de knop wordt geklikt.", + "panel-applications-empty": "Er spelen momenteel geen applicaties audio af", + "spectrum-mirrored-description": "Spiegel het spectrum zodat lage frequenties in het midden zijn. Indien uitgeschakeld, zijn lage frequenties links en hoge rechts.", + "spectrum-mirrored-label": "Spiegelspectrum", + "title": "Audio", + "visualizer-type-description": "Kies een visualisatietype voor mediaweergave.", + "visualizer-type-label": "Visualisatietype", + "volumes-desc": "Pas volumeregelaars en audioniveaus aan.", + "volumes-feedback-sound-file-description": "Pad naar het geluidsbestand dat wordt afgespeeld bij het wijzigen van het volume.", + "volumes-feedback-sound-file-label": "Geluidsbestand voor volumefeedback", + "volumes-feedback-sound-file-select-title": "Selecteer volumefeedback-geluidsbestand", + "volumes-input-volume-description": "Volumeniveau van microfooninvoer.", + "volumes-mute-input-description": "Dempt de standaard audio-invoer (microfoon).", + "volumes-mute-input-label": "Audio-invoer dempen", + "volumes-mute-output-description": "Dempt de hoofd audio-uitvoer van het systeem.", + "volumes-mute-output-label": "Audio-uitvoer dempen", + "volumes-output-volume-description": "Systeembreed volumeniveau.", + "volumes-step-size-description": "Pas de stapgrootte voor volumewijzigingen aan (scrollwiel, sneltoetsen).", + "volumes-step-size-label": "Volumestapgrootte", + "volumes-volume-feedback-description": "Een feedbackgeluid afspelen bij het aanpassen van het volume.", + "volumes-volume-feedback-label": "Volume feedback geluid afspelen", + "volumes-volume-overdrive-description": "Sta toe dat het volume boven 100% wordt verhoogd. Mogelijk niet door alle hardware ondersteund.", + "volumes-volume-overdrive-label": "Volume-overdrive toestaan" + }, + "bar": { + "appearance-auto-hide-delay-description": "Tijd voordat de Bar verdwijnt nadat de muis deze verlaat.", + "appearance-auto-hide-delay-label": "Verbergvertraging", + "appearance-auto-hide-exclusive-note": "Opmerking: De exclusieve zone wordt automatisch uitgeschakeld wanneer automatisch verbergen actief is.", + "appearance-auto-show-delay-description": "Tijd voordat de balk verschijnt wanneer de muis de rand bereikt.", + "appearance-auto-show-delay-label": "Toonvertraging", + "appearance-background-opacity-description": "Stel de achtergronddoorzichtigheid specifiek in voor de balk.", + "appearance-background-opacity-label": "Balk achtergronddoorzichtigheid", + "appearance-capsule-color-description": "Kies een kleur voor balkcapsules, of gebruik geen kleur voor de standaard oppervlaktekleur.", + "appearance-capsule-color-label": "Capsulekleur", + "appearance-capsule-opacity-description": "Stel het doorzichtigheidsniveau in voor widget-achtergronden wanneer capsule wordt getoond.", + "appearance-capsule-opacity-label": "Capsule-doorzichtigheid", + "appearance-content-padding-description": "Pas de opvulling tussen de baranden en widgets aan.", + "appearance-content-padding-label": "Inhoudopvulling", + "appearance-density-description": "Pas de opvulling van de balk aan voor een compacte of ruime uitstraling.", + "appearance-density-label": "Balkdichtheid", + "appearance-desc": "Pas de uitstraling en positie van de balk aan.", + "appearance-display-mode-description": "Kies wanneer de balk zichtbaar is.", + "appearance-enable-exclusion-zone-inset-description": "Verklein de uitsluitingszone met 1 fysieke pixel, zodat gelijkgelegen vensters perfect onder de rand van de balk overlappen.", + "appearance-enable-exclusion-zone-inset-label": "Binnenmarge van de uitsluitingszone", + "appearance-floating-description": "Toon de balk weer als een zwevende 'pil'.", + "appearance-floating-label": "Zwevende balk", + "appearance-font-scale-description": "De schaal van de lettergrootte aanpassen voor tekst die in de balk wordt weergegeven.", + "appearance-font-scale-label": "Lettertypeschaal", + "appearance-frame-radius": "Binnenradius", + "appearance-frame-settings-description": "Pas de kaderdikte en binnenste hoekradius aan", + "appearance-frame-settings-label": "Kaderinstellingen", + "appearance-frame-thickness": "Dikte", + "appearance-hide-on-overview-description": "Verberg de balk en sluit panelen wanneer het compositoroverzicht actief is.", + "appearance-hide-on-overview-label": "Balk verbergen in overzicht", + "appearance-margins-description": "Pas de marges rond de zwevende balk aan.", + "appearance-margins-horizontal": "Horizontale marge", + "appearance-margins-vertical": "Verticale marge", + "appearance-outer-corners-description": "Toon naar buiten afgeronde hoeken op de balk weer.", + "appearance-outer-corners-label": "Buitenhoeken", + "appearance-position-description": "Kies waar je de balk op het scherm wilt plaatsen.", + "appearance-position-label": "Positie van de balk", + "appearance-show-capsule-description": "Widget-achtergronden tonen.", + "appearance-show-capsule-label": "Capsules tonen", + "appearance-show-on-workspace-switch-description": "De Bar automatisch kort tonen wanneer de Workspace verandert.", + "appearance-show-on-workspace-switch-label": "Balk tonen bij werkruimtewissel", + "appearance-show-outline-description": "Toon een zichtbare rand rondom elke widget.", + "appearance-show-outline-label": "Toon widget-omtrekken", + "appearance-type-description": "Kies de stijl van de balk: Eenvoudig, Zwevend of Omkaderd.", + "appearance-type-label": "Balktype", + "appearance-use-separate-opacity-description": "Maak het mogelijk om een aparte dekkingswaarde te gebruiken voor de achtergrond van de balk.", + "appearance-use-separate-opacity-label": "Gebruik afzonderlijke balkdekking", + "appearance-widget-spacing-description": "Pas de afstand tussen elke widget in de balk aan.", + "appearance-widget-spacing-label": "Widgetafstand", + "behavior-middle-click-command-description": "Commando om uit te voeren bij middelklik.", + "behavior-middle-click-command-label": "Middelklik Commando", + "behavior-middle-click-command-placeholder": "niri msg action overzicht schakelen", + "behavior-middle-click-description": "Kies wat een middenklik doet op lege gebieden van de Balk.", + "behavior-middle-click-follow-mouse-description": "Open het geselecteerde middenklik-paneel op de cursorpositie.", + "behavior-middle-click-follow-mouse-label": "Middenklik volgt muis", + "behavior-middle-click-label": "Balk middenklik actie", + "behavior-right-click-command-description": "Commando om uit te voeren bij rechtermuisklik.", + "behavior-right-click-command-label": "Rechtermuisklikopdracht", + "behavior-right-click-command-placeholder": "notify-send \"Rechtermuisklik\"", + "behavior-right-click-description": "Kies wat een rechtermuisklik doet op lege gebieden van de balk.", + "behavior-right-click-follow-mouse-description": "Open het geselecteerde rechtermuisknop-paneel op de cursorpositie.", + "behavior-right-click-follow-mouse-label": "Rechtermuisklik volgt muis", + "behavior-right-click-label": "Actie bij rechtermuisklik op de Balk", + "behavior-wheel-wrap-description": "Indien ingeschakeld, gaat het scrollen door van het laatste naar het eerste item.", + "behavior-wheel-wrap-label": "Doorlopen", + "behavior-workspace-scroll-description": "Kies wat het muiswiel doet op lege gebieden van de balk.", + "behavior-workspace-scroll-label": "Muiswielactie van de Balk", + "behavior-workspace-scroll-option-content": "Inhoud", + "behavior-workspace-scroll-option-workspace": "Werkruimte", + "monitor-configure-widgets": "Widgets configureren", + "monitor-override-settings": "Globale instellingen overschrijven", + "monitor-override-settings-description": "Gebruik aangepaste instellingen voor deze monitor.", + "monitor-reset-all": "Alles resetten", + "monitor-widgets-title": "Widgetconfiguratie - {monitor}", + "monitors-desc": "Balk op specifieke monitoren weergeven. Standaard op alle als er geen zijn gekozen.", + "monitors-desc-new": "Configureer welke monitoren de Bar weergeven en pas de instellingen per monitor aan.", + "monitors-title": "Monitorweergave", + "title": "Balk", + "tray-blacklist-description": "Voeg uitsluitingsregels voor het systeemvak toe, jokertekens (*) worden ondersteund.", + "tray-blacklist-label": "Zwarte lijst", + "tray-blacklist-placeholder": "bijv. nm-applet, Fcitx*", + "tray-pin-application": "Toepassing vastmaken", + "tray-unpin-application": "Toepassing losmaken", + "use-global-widgets": "Globale widgets gebruiken", + "widgets-desc": "Sleep widgets om hun volgorde te wijzigen. Gebruik het rechtermuisknopmenu om widgets tussen secties te verplaatsen of te verwijderen.", + "widgets-title": "Widgetpositionering" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "Genereer kleurenschema's van je achtergrond. Extraheert automatisch kleuren om een samenhangend thema te creรซren.", + "color-source-use-wallpaper-colors-label": "Achtergrondkleuren gebruiken", + "dark-mode-mode-description": "Schakelt automatisch tussen lichte en donkere modus.", + "dark-mode-mode-label": "Schema voor donkere modus", + "dark-mode-mode-manual": "Handmatig", + "dark-mode-mode-off": "Uit", + "dark-mode-switch-description": "Schakelt over naar een donkerder thema voor comfortabeler kijken 's nachts.", + "delete-error-description": "Verwijderen van {scheme} mislukt", + "delete-error-title": "Verwijderen mislukt", + "delete-success-description": "{scheme} succesvol verwijderd", + "delete-success-title": "Kleurenschema verwijderd", + "download-button": "Meer downloaden", + "download-downloading": "Downloaden...", + "download-empty": "Geen kleurenschema's beschikbaar", + "download-error-api-error": "API-fout: {status}", + "download-error-description": "Download van {scheme} mislukt", + "download-error-download-failed": "Download mislukt met afsluitcode: {code}", + "download-error-invalid-response": "Ongeldig API-antwoordformaat", + "download-error-no-files": "Geen bestanden gevonden voor schema", + "download-error-parse-failed": "Fout bij parseren van API-antwoord: {error}", + "download-error-rate-limit": "GitHub API-snelheidslimiet overschreden", + "download-error-title": "Download mislukt", + "download-fetching": "Beschikbare kleurenschema's ophalen...", + "download-success-description": "{scheme} succesvol gedownload", + "download-success-title": "Kleurenschema gedownload", + "download-title": "Kleurenschema's downloaden", + "method-description": { + "content": "Material Design-schema met high-fidelity kleurextractie die nauw aansluit bij de werkelijke kleuren van de broninhoud.", + "dysfunctional": "Zoals Faithful, maar kiest de op รฉรฉn na meest dominante kleurenfamilie als primair.", + "faithful": "Probeert dicht bij de bronkleur te blijven en tegelijkertijd een harmonieus palet te genereren.", + "fruit-salad": "Material Design-schema dat een speels, levendig palet produceert met diverse en gevarieerde tinten.", + "monochrome": "Material Design-schema met een grijswaarde met รฉรฉn tint en minimale chromatische inhoud.", + "muted": "Genereert zachtere, minder verzadigde tinten voor een kalme en ingetogen uitstraling.", + "rainbow": "Material Design-schema dat kleuren over het volledige spectrum genereert voor maximale kleurdiversiteit.", + "tonal-spot": "Standaard Material Design algoritme. Creรซert gebalanceerde paletten met behulp van tonale variaties van de bronkleur.", + "vibrant": "Maximaliseert de kleurverzadiging voor gedurfde, opvallende paletten met levendige tinten." + }, + "predefined-desc": "Kies uit een verzameling vooraf gedefinieerde kleurenschema's.", + "predefined-generate-templates-label": "Sjablonen genereren voor vooraf gedefinieerde schema's", + "predefined-title": "Vooraf gedefinieerde kleurenschema's", + "sync-gsettings-description": "Pas het systeemthema aan de actieve lichte of donkere variant aan.", + "sync-gsettings-label": "Systeemthema synchroniseren", + "templates-desc": "Pas kleuren toe op externe applicaties.", + "templates-filter-description": "Sjablonen uit een specifieke categorie weergeven.", + "templates-filter-label": "Filteren op categorie", + "templates-misc-description": "Maak uw eigen sjablonen.", + "templates-misc-label": "Geavanceerd", + "templates-misc-user-templates-description": "Alleen inschakelen als u weet wat u doet โ€” raadpleeg onze online documentatie.", + "templates-misc-user-templates-label": "Gebruikerssjablonen inschakelen", + "templates-none-detected": "Geen gedetecteerd", + "templates-write-path": "Schrijft: {filepath}", + "title": "Kleurschema", + "wallpaper-method-description": "Kies je favoriete methode voor het genereren van paletten.", + "wallpaper-method-label": "Paletgeneratiemethode", + "wallpaper-monitor-source-description": "Selecteer welke monitor te gebruiken voor het extraheren van wallpaperkleuren.", + "wallpaper-monitor-source-label": "Bron van kleurgeneratie" + }, + "connections": { + "authentication-required": "Authenticatie vereist", + "bluetooth-auto-connect-description": "Automatisch verbinding maken met vertrouwde gekoppelde apparaten wanneer Bluetooth is ingeschakeld.", + "bluetooth-auto-connect-label": "Gekoppelde apparaten automatisch verbinden", + "bluetooth-devices-unnamed": "Naamloze apparaten worden niet getoond.", + "bluetooth-discoverable": "Dit apparaat is vindbaar als {hostName} zolang dit instellingen-tabblad geopend is.", + "bluetooth-rssi-polling-description": "Periodiek RSSI van verbonden apparaten via bluetoothctl samplen. Mogelijk niet beschikbaar voor alle apparaten; gebruikt minimale middelen wanneer ingeschakeld.", + "bluetooth-rssi-polling-interval-description": "Configureer hoe vaak de signaalsterkte voor verbonden apparaten moet worden bijgewerkt.", + "bluetooth-rssi-polling-interval-label": "Polling-interval", + "bluetooth-rssi-polling-label": "Bluetooth-signaalpeiling", + "disable-discoverability-description": "Verberg je apparaat voor Bluetooth-apparaten in de buurt.", + "disable-discoverability-label": "Zichtbaarheid van apparaat uitschakelen", + "hide-unnamed-devices-description": "Verberg apparaten die alleen als Bluetooth-adressen verschijnen.", + "hide-unnamed-devices-label": "Onbenoemde apparaten verbergen", + "pin-instructions": "Voer de PIN-code in die op uw apparaat wordt weergegeven.", + "title": "Verbindingen", + "wifi-header-text": "Gebruik Wi-Fi om beschikbare netwerken te zien en verbinding te maken met het internet." + }, + "control-center": { + "cards-desc": "Pas aan welke bedieningselementen in het bedieningscentrum verschijnen en in welke volgorde.", + "desc": "Configureer de positie en het gedrag van het bedieningscentrumpaneel.", + "open-at-mouse-description": "Bij een rechtermuisklik op een leeg gedeelte van de Bar, open het Controlecentrum dicht bij de cursor. Indien uitgeschakeld, wordt de bovenstaande positie-instelling gebruikt.", + "open-at-mouse-label": "Openen bij cursor met rechtermuisklik", + "position-description": "Kies waar het bedieningscentrumpaneel verschijnt wanneer het wordt geopend.", + "shortcuts-custom-button-command-description": "Het commando dat wordt uitgevoerd wanneer op de knop wordt geklikt.", + "shortcuts-custom-button-enable-on-state-logic-description": "Schakel een tweede pictogram en aan-status in op basis van een controlecommando.", + "shortcuts-custom-button-enable-on-state-logic-label": "Logica voor aan-status inschakelen", + "shortcuts-custom-button-icon-description": "Selecteer een pictogram uit de bibliotheek.", + "shortcuts-custom-button-on-clicked-label": "Commando bij linkermuisklik", + "shortcuts-custom-button-on-middle-clicked-description": "Commando dat wordt uitgevoerd wanneer de knop met de middelste muisknop wordt geklikt.", + "shortcuts-custom-button-on-state-command-description": "Commando dat wordt uitgevoerd om te controleren of de knop in de aan-status moet staan. Geeft 0 terug voor aan, een andere waarde voor uit.", + "shortcuts-custom-button-on-state-command-label": "Controlecommando voor aan-status", + "shortcuts-custom-button-on-state-icon-description": "Het pictogram voor de knop wanneer deze in de aan-status staat.", + "shortcuts-custom-button-on-state-icon-label": "Pictogram voor aan-status", + "shortcuts-custom-button-state-checks-add": "Statuscontrole toevoegen", + "shortcuts-custom-button-state-checks-command": "Commando dat wordt uitgevoerd voor deze statuscontrole", + "shortcuts-custom-button-state-checks-label": "Statuscontroles", + "shortcuts-custom-button-state-checks-remove": "Verwijderen", + "shortcuts-custom-button-tooltip-description": "De tooltip die wordt getoond wanneer je over de knop zweeft.", + "shortcuts-custom-button-tooltip-label": "Tooltip", + "shortcuts-title": "Snelkoppelingwidgets", + "system-monitor-disk-path-description": "Selecteer welk schijf-mountpunt de systeemmonitor-kaart in het bedieningscentrum moet bewaken.", + "system-monitor-disk-path-label": "Systeemmonitor schijfpad", + "title": "Bedieningscentrum" + }, + "desktop-widgets": { + "clock-enabled-description": "Toon een klokwidget op het bureaublad.", + "clock-enabled-label": "Klokwidget inschakelen", + "clock-format-label": "Formaat", + "clock-height-description": "Hoogte van de klokwidget in pixels.", + "clock-height-label": "Hoogte", + "clock-minimal-mode-description": "Gebruik een minimale klokweergave met tijd en datum.", + "clock-minimal-mode-label": "Minimale modus", + "clock-rounded-corners-description": "Gebruik afgeronde hoeken voor de widgetachtergrond.", + "clock-rounded-corners-label": "Afgeronde hoeken", + "clock-show-background-description": "Toon de achtergrondcontainer voor de klokwidget.", + "clock-show-background-label": "Achtergrond weergeven", + "clock-show-date-description": "Toon de huidige datum onder de tijd.", + "clock-show-date-label": "Toon datum", + "clock-show-seconds-description": "Toon seconden in de tijd.", + "clock-show-seconds-label": "Toon seconden", + "clock-style-analog": "Analoog", + "clock-style-binary": "Binair", + "clock-style-description": "Kies de weergavestijl van de klok.", + "clock-style-digital": "Digitaal", + "clock-style-label": "Klokstijl", + "clock-style-minimal": "Minimaal", + "clock-width-description": "Breedte van de klokwidget in pixels.", + "cpu-intensive-note": "Widgets gemarkeerd met ! gebruiken meer CPU en moeten bewust ingeschakeld worden.", + "edit-mode-button-label": "Ga naar de bewerkingsmodus", + "edit-mode-controls-explanation": "Linksklikken en slepen: Widget verplaatsen of het formaat wijzigen.\nRechtermuisknop: Het contextmenu openen.", + "edit-mode-description": "Schakel de bewerkingsmodus in om desktopwidgets te verplaatsen en te herpositioneren. In de ingeschakelde modus tonen widgets een sleepomtrek en kunnen ze worden verplaatst.", + "edit-mode-exit-button": "Bewerkingsmodus verlaten", + "edit-mode-grid-snap-label": "Raster uitlijnen", + "edit-mode-grid-snap-scale-label": "Rasteruitlijningsschaal", + "edit-mode-label": "Bewerkingsmodus", + "enabled-description": "Desktopwidgets volledig in- of uitschakelen.", + "enabled-label": "Desktopwidgets inschakelen", + "general-desc": "Configureer widgets die op je bureaublad verschijnen.", + "general-title": "Desktopwidgets", + "media-player-enabled-description": "Toon een mediaplayerwidget op het bureaublad.", + "media-player-enabled-label": "Mediaplayerwidget inschakelen", + "media-player-rounded-corners-description": "Ronde hoeken inschakelen op de widgetranden.", + "media-player-show-album-art-description": "Toon de albumhoes en trackinformatie (titel en artiest).", + "media-player-show-album-art-label": "Toon albumhoes & titel", + "media-player-show-background-description": "Toon de achtergrondcontainer voor de mediaplayerwidget.", + "media-player-show-buttons-description": "Toon mediabesturingsknoppen (afspelen/pauzeren, vorige, volgende) op de mediaplayerwidget.", + "media-player-show-buttons-label": "Knoppen weergeven", + "media-player-show-visualizer-description": "Toon de audio-visualisatie overlay.", + "media-player-visualizer-type-description": "Kies een visualisatietype voor de achtergrond van de desktop mediaplayer.", + "overview-enabled-description": "Toon desktopwidgets terwijl het compositor-overzicht geopend is.", + "overview-enabled-label": "Tonen in overzicht", + "system-stat-layout-bottom": "Onder", + "system-stat-layout-description": "Kies hoe de legenda wordt weergegeven ten opzichte van de grafiek.", + "system-stat-layout-label": "Indeling", + "system-stat-layout-side": "Zijde", + "system-stat-rounded-corners-description": "Afgeronde hoeken gebruiken voor de widget-achtergrond.", + "system-stat-rounded-corners-label": "Afgeronde Hoeken", + "system-stat-show-background-description": "Toon de achtergrondcontainer voor de systeemstatistieken-widget.", + "system-stat-show-background-label": "Achtergrond weergeven", + "system-stat-stat-type-description": "Kies welke systeemstatistiek weer te geven.", + "system-stat-stat-type-label": "Statistiektype", + "title": "Desktopwidgets", + "weather-enabled-description": "Toon een weerwidget op het bureaublad.", + "weather-enabled-label": "Weerwidget inschakelen", + "weather-show-background-description": "Toon de achtergrondcontainer voor de weerwidget." + }, + "display": { + "monitors-backlight-device-auto-option": "Standaard", + "monitors-backlight-device-description": "Selecteer een achtergrondverlichtingsapparaat voor deze uitvoer.", + "monitors-backlight-device-label": "Achtergrondverlichting Apparaat", + "monitors-brightness-step-description": "Pas de stapgrootte voor helderheidswijzigingen aan (scrollwiel en sneltoetsen).", + "monitors-brightness-step-label": "Stapgrootte helderheid", + "monitors-brightness-unavailable-ddc-disabled": "Helderheidsregeling niet beschikbaar. Schakel \"Ondersteuning voor externe helderheid\" in om de helderheid van dit scherm te regelen.", + "monitors-brightness-unavailable-generic": "Helderheidsregeling is niet beschikbaar voor dit scherm.", + "monitors-desc": "Pas helderheidsinstellingen per scherm aan.", + "monitors-enforce-minimum-description": "Lost het probleem op dat de achtergrondverlichting op sommige schermen volledig uitgaat bij 0% helderheid.", + "monitors-enforce-minimum-label": "Minimale helderheid afdwingen (1%)", + "monitors-external-brightness-description": "Schakel DDCUtil-ondersteuning in om de helderheid op externe schermen te regelen via het DDC/CI-protocol.", + "monitors-external-brightness-label": "Ondersteuning voor externe helderheid", + "monitors-global-brightness-description": "Pas de helderheid van alle monitoren tegelijk aan.", + "monitors-global-brightness-label": "Alle monitoren", + "monitors-title": "Instellingen per monitor", + "night-light-auto-schedule-description": "Gebaseerd op de zonsopgang- en zonsondergangtijd in {location} โ€” aanbevolen.", + "night-light-auto-schedule-label": "Automatische planning", + "night-light-desc": "Verminder blauw licht om beter te slapen en vermoeidheid van de ogen te beperken.", + "night-light-enable-description": "Past een warme kleurfilter toe om de afgifte van blauw licht te verminderen.", + "night-light-enable-label": "Nachtlicht inschakelen", + "night-light-force-activation-description": "Negeert het schema en past het nachtfilter direct toe.", + "night-light-force-activation-label": "Geforceerde activatie", + "night-light-manual-schedule-description": "Stel aangepaste tijden voor zonsopgang en zonsondergang in.", + "night-light-manual-schedule-label": "Handmatige planning", + "night-light-manual-schedule-select-start": "Starttijd selecteren", + "night-light-manual-schedule-select-stop": "Stoptijd selecteren", + "night-light-manual-schedule-sunrise": "Tijd van zonsopgang", + "night-light-manual-schedule-sunset": "Tijd van zonsondergang", + "night-light-temperature-day": "Dag", + "night-light-temperature-day-description": "Regelt de kleurtemperatuur overdag.", + "night-light-temperature-description": "Stel de kleurwarmte voor nacht en dag in.", + "night-light-temperature-label": "Kleurtemperatuur", + "night-light-temperature-night": "Nacht", + "night-light-temperature-night-description": "Regelt de kleurtemperatuur 's nachts.", + "title": "Beeldscherm" + }, + "dock": { + "appearance-background-opacity-description": "Pas de achtergronddekking van de dock aan.", + "appearance-border-radius-description": "Pas de randradius van het dock aan.", + "appearance-border-radius-label": "Randradius", + "appearance-colorize-icons-description": "Pas themakleuren toe op dock-pictogrammen (alleen niet-focuste apps).", + "appearance-colorize-icons-label": "Pictogrammen inkleuren", + "appearance-dead-opacity-description": "Pas de transparantie aan van app-iconen die niet actief zijn.", + "appearance-dead-opacity-label": "Dode opaciteit", + "appearance-desc": "Pas het gedrag en uiterlijk van de dock aan.", + "appearance-display-auto-hide": "Automatisch verbergen", + "appearance-display-description": "Kies hoe de dock zich gedraagt.", + "appearance-display-exclusive": "Exclusief", + "appearance-dock-indicator-description": "Toon een kleine indicator wanneer het dock verborgen is.", + "appearance-dock-indicator-label": "Dock-indicator", + "appearance-floating-distance-description": "Stel de afstand in tussen de dock en de rand van het scherm.", + "appearance-floating-distance-label": "Afstand zwevende dock", + "appearance-group-apps-description": "Groepeer meerdere vensters van dezelfde app in รฉรฉn Dock-item.", + "appearance-group-apps-label": "Dezelfde apps groeperen", + "appearance-group-click-action-cycle": "Vensters wisselen", + "appearance-group-click-action-description": "Kies wat de linkermuisklik doet voor gegroepeerde apps.", + "appearance-group-click-action-label": "Actie bij klik op gegroepeerde app", + "appearance-group-click-action-list": "Vensterlijst openen", + "appearance-group-context-menu-mode-description": "Kies hoe het contextmenu zich gedraagt voor gegroepeerde apps.", + "appearance-group-context-menu-mode-extended": "Uitgebreid", + "appearance-group-context-menu-mode-label": "Gegroepeerde app-menumodus", + "appearance-group-context-menu-mode-list": "Vensterlijst", + "appearance-group-indicator-style-description": "Kies hoe gegroepeerde actieve indicatoren de status van het gefocuste venster weergeven.", + "appearance-group-indicator-style-dots": "Punten", + "appearance-group-indicator-style-label": "Gegroepeerde indicatorstijl", + "appearance-group-indicator-style-number": "Nummer", + "appearance-hide-show-speed-description": "Pas de snelheid van de verberg/weergeef animatie van de dock aan.", + "appearance-hide-show-speed-label": "Verberg/weergeef snelheid", + "appearance-icon-size-description": "Pas de algehele grootte van de dock aan.", + "appearance-icon-size-label": "Grootte van de dock", + "appearance-inactive-indicators-description": "Toon indicatorpillen voor alle apps, niet alleen de actieve.", + "appearance-inactive-indicators-label": "Lopende indicatoren", + "appearance-indicator-color-description": "Kies de kleur van de indicator voor het verborgen dock.", + "appearance-indicator-color-label": "Indicatorkleur", + "appearance-indicator-opacity-description": "Pas de ondoorzichtigheid van de indicator voor het verborgen dock aan.", + "appearance-indicator-opacity-label": "Ondoorzichtigheid van de indicator", + "appearance-indicator-thickness-description": "Schakel een dikkere indicator in voor het verborgen dock (6px in plaats van 3px).", + "appearance-indicator-thickness-label": "Dikkere indicator", + "appearance-launcher-icon-description": "Kies het pictogram dat wordt gebruikt voor de Dock Launcher.", + "appearance-launcher-icon-label": "Launcher-pictogram", + "appearance-launcher-position-description": "Kies waar het starter-icoon in het dock verschijnt.", + "appearance-launcher-position-end": "Einde", + "appearance-launcher-position-label": "Positie van de starter", + "appearance-launcher-position-start": "Start", + "appearance-launcher-use-distro-logo-description": "Gebruik het logo van je distributie als het Dock Launcher-pictogram.", + "appearance-launcher-use-distro-logo-label": "Gebruik distro-logo in plaats van pictogram", + "appearance-pinned-static-description": "Zet vastgemaakte app-pictogrammen altijd in statische volgorde aan de linkerkant.", + "appearance-pinned-static-label": "Statische vastgezette apps", + "appearance-position-description": "Kies waar het dock op het scherm verschijnt.", + "appearance-position-label": "Positie", + "appearance-show-launcher-icon-description": "Toon het pictogram van de applicatiestarter in het dock.", + "appearance-show-launcher-icon-label": "Toon app-starter", + "appearance-sit-on-frame-description": "Lijn het dock uit binnen de kadergrens, in plaats van erbovenop te liggen.", + "appearance-sit-on-frame-label": "Dock zit op het frame", + "appearance-type-attached": "Gedockt", + "appearance-type-description": "Kies tussen een zwevende pilvorm of een statische balk die aan de rand is bevestigd.", + "appearance-type-floating": "Zwevend", + "appearance-type-label": "Dockstijl", + "enabled-description": "Dock geheel tonen of verbergen.", + "enabled-label": "Dock inschakelen", + "monitors-desc": "Dock op specifieke monitoren weergeven. Standaard op alle als er geen zijn gekozen.", + "monitors-only-same-monitor-description": "Toon alleen apps van de monitor waar het dock zich bevindt.", + "monitors-only-same-monitor-label": "Alleen apps van dezelfde monitor", + "monitors-title": "Monitorweergave", + "title": "Dock" + }, + "general": { + "copy-settings": "Instellingen kopiรซren", + "fonts-default-description": "Hoofdlettertype dat in de hele interface wordt gebruikt.", + "fonts-default-label": "Standaardlettertype", + "fonts-default-placeholder": "Selecteer standaardlettertype...", + "fonts-default-scale-description": "Vergroot of verklein de grootte van standaardtekst.", + "fonts-default-scale-label": "Grootte standaardlettertype", + "fonts-default-search-placeholder": "Lettertype zoeken...", + "fonts-desc": "Kies de lettertypes die in de interface worden gebruikt.", + "fonts-monospace-description": "Monospace-lettertype dat wordt gebruikt voor cijfers en statische weergaven.", + "fonts-monospace-label": "Monospace-lettertype", + "fonts-monospace-placeholder": "Selecteer monospace-lettertype...", + "fonts-monospace-scale-description": "Vergroot of verklein de grootte van monospace-tekst.", + "fonts-monospace-scale-label": "Grootte monospace-lettertype", + "fonts-monospace-search-placeholder": "Monospace-lettertype zoeken...", + "fonts-title": "Lettertypes", + "keybinds-conflict-description": "De toetscombinatie is al toegewezen aan {action}.", + "keybinds-conflict-title": "Toetsencombinatieconflict", + "keybinds-description": "Globale navigatietoetsen voor panelen en de starter configureren.", + "keybinds-down": "Omlaag", + "keybinds-enter": "Bevestigen / Actie", + "keybinds-escape": "Sluiten / Terug", + "keybinds-left": "Naar links", + "keybinds-modifier-description": "Sneltoetsen moeten een modificatietoets (Ctrl of Alt) bevatten.", + "keybinds-modifier-title": "Modificator vereist", + "keybinds-remove": "Verwijderen", + "keybinds-right": "Naar rechts", + "keybinds-title": "Navigatietoetsencombinaties", + "keybinds-up": "Omhoog", + "language-desc": "Kies je voorkeurstaal voor de applicatie.", + "language-select-auto-detect": "Automatisch", + "language-select-description": "Selecteer de taal die in de interface van de applicatie wordt gebruikt.", + "language-select-label": "Applicatietaal", + "launch-setup-wizard": "Installatiewizard", + "profile-desc": "Bewerk je gebruikersgegevens en avatar.", + "profile-picture-description": "Je profielfoto die overal in de interface verschijnt.", + "profile-picture-label": "Profielfoto", + "profile-select-avatar": "Avatarafbeelding selecteren", + "profile-title": "Profiel", + "profile-tooltip": "Profielfoto", + "reverse-scrolling-description": "Geรฏnterpreteerde schuifrichting omkeren.", + "reverse-scrolling-label": "Scrollen omkeren", + "screen-corners-desc": "Pas afronding en visuele effecten van schermhoeken aan.", + "screen-corners-radius-description": "Pas de afronding van de schermhoeken aan.", + "screen-corners-radius-label": "Straal schermhoeken", + "screen-corners-radius-reset": "Straal schermhoeken resetten", + "screen-corners-show-corners-description": "Toon afgeronde hoeken aan de rand van het scherm.", + "screen-corners-show-corners-label": "Schermhoeken tonen", + "screen-corners-solid-black-description": "Gebruik effen zwart in plaats van de achtergrondkleur van de balk.", + "screen-corners-solid-black-label": "Effen zwarte hoeken", + "screen-corners-title": "Schermhoeken", + "settings-copied": "Instellingen gekopieerd naar klembord", + "smooth-scrolling-description": "Animeer lijstscrollen voor een vloeiendere scrollervaring.", + "smooth-scrolling-label": "Vloeiend scrollen", + "tab-basics": "Basis", + "tab-keybinds": "Toetsencombinaties" + }, + "hooks": { + "color-generation-description": "Commando dat wordt uitgevoerd wanneer de kleurgeneratie is voltooid (bijv. na thematisering op basis van de achtergrond).", + "color-generation-label": "Kleuren gegenereerd", + "color-generation-placeholder": "bijv. notify-send \"Kleuren\" \"Gegenereerd\"", + "info-command-info-description": "โ€ข Commando's worden uitgevoerd via shell (sh -lc)
โ€ข Commando's draaien op de achtergrond (losgekoppeld)
โ€ข Testknoppen worden uitgevoerd met de huidige waarden", + "info-parameters-description": "โ€ข Wallpaper hook: $1 = achtergrondpad, $2 = schermnaam, $3 = thema (donker/licht)
โ€ข Theme toggle hook: $1 = true/false (donkere modus-status)
โ€ข Screen lock/unlock hooks: $1 = lock/unlock (schermvergrendelingsstatus)
โ€ข Performance mode hooks: Geen parameters
โ€ข Session hook: $1 = action (shutdown/reboot)", + "info-parameters-label": "Beschikbare parameters", + "noctalia-started-description": "Opdracht die moet worden uitgevoerd wanneer Noctalia volledig is geladen.", + "noctalia-started-label": "Noctalia gestart", + "noctalia-started-placeholder": "bijv. notify-send 'Noctalia geladen'", + "performance-mode-disabled-description": "Commando dat wordt uitgevoerd wanneer de Noctalia prestatiemodus wordt uitgeschakeld.", + "performance-mode-disabled-label": "Prestatiemodus uitgeschakeld", + "performance-mode-disabled-placeholder": "bijv. notify-send \"Prestatie\" \"Modus uitgeschakeld\"", + "performance-mode-enabled-description": "Commando dat wordt uitgevoerd wanneer de Noctalia prestatiemodus wordt ingeschakeld.", + "performance-mode-enabled-label": "Prestatiemodus ingeschakeld", + "performance-mode-enabled-placeholder": "bijv. notify-send \"Prestatie\" \"Modus ingeschakeld\"", + "screen-lock-description": "Commando dat wordt uitgevoerd wanneer het scherm wordt vergrendeld.", + "screen-lock-label": "Scherm vergrendeld", + "screen-lock-placeholder": "bijv. notify-send \"Scherm\" \"Vergrendeld\"", + "screen-unlock-description": "Commando dat wordt uitgevoerd wanneer het scherm wordt ontgrendeld.", + "screen-unlock-label": "Scherm ontgrendeld", + "screen-unlock-placeholder": "bijv. notify-send \"Scherm\" \"Ontgrendeld\"", + "session-description": "Commando dat moet worden uitgevoerd voor het afsluiten of opnieuw opstarten. Ontvangt actietype als $1 (afsluiten/opnieuw opstarten).", + "session-label": "Sessie beรซindigen", + "session-placeholder": "bijv. notify-send \"Session\" \"$1\"", + "system-hooks-desc": "Configureer commando's die worden uitgevoerd wanneer systeemgebeurtenissen plaatsvinden.", + "system-hooks-enable-description": "Schakel alle hook-commando's in of uit.", + "system-hooks-enable-label": "Hooks inschakelen", + "system-hooks-title": "Systeemhooks", + "theme-changed-description": "Commando dat wordt uitgevoerd wanneer het thema tussen donkere en lichte modus schakelt.", + "theme-changed-label": "Thema gewijzigd", + "theme-changed-placeholder": "bijv. notify-send \"Thema\" \"Gewisseld\"", + "title": "Hooks", + "wallpaper-changed-description": "Commando dat wordt uitgevoerd wanneer de achtergrond verandert.", + "wallpaper-changed-label": "Achtergrond gewijzigd", + "wallpaper-changed-placeholder": "bijv. notify-send \"Achtergrond\" \"Gewijzigd\"" + }, + "idle": { + "custom-add": "Commando toevoegen", + "custom-description": "Voer een shell-commando uit na een periode van inactiviteit.", + "custom-entry-command": "Commando", + "custom-entry-delete": "Verwijderen", + "custom-entry-edit": "Aangepaste Opdracht Bewerken", + "custom-entry-name": "Naam", + "custom-entry-name-placeholder": "bijv. Lichten uitdoen", + "custom-entry-new": "Nieuwe Aangepaste Opdracht", + "custom-entry-timeout": "Inactiviteitstijd", + "custom-entry-timeout-format": "{count} seconde", + "custom-entry-timeout-format-plural": "{count} seconden", + "custom-entry-unnamed": "Naamloos commando", + "custom-label": "Aangepaste inactiviteitscommando's", + "enable-description": "Schakel het scherm automatisch uit, vergrendel of onderbreek na een periode van inactiviteit.", + "enable-label": "Inactiviteitsbeheer inschakelen", + "fade-duration-description": "Seconden voor de fade-to-black animatie voordat elke actie wordt geactiveerd. Elke muisbeweging annuleert de fade.", + "fade-duration-label": "Vervagingsduur", + "lock-description": "Seconden van inactiviteit voordat het vergrendelscherm wordt geactiveerd.", + "lock-label": "Scherm vergrendelen", + "resume-command-label": "Commando hervatten", + "screen-off-description": "Seconden van inactiviteit voordat monitoren worden uitgeschakeld.", + "screen-off-label": "Scherm uitschakelen", + "status-description": "Inactiviteitstijd zoals gerapporteerd door de compositor.", + "status-label": "Inactiviteitstijd", + "suspend-description": "Seconden van inactiviteit voordat het systeem onderbreekt.", + "tab-behavior": "Gedrag", + "tab-custom": "Aangepast", + "timeouts-description": "Stel in op 0 om een fase uit te schakelen. Time-outs worden gepauzeerd zolang 'Wakker houden' actief is.", + "timeouts-label": "Time-outs", + "title": "Inactiviteit", + "unavailable": "Native inactiviteitsmonitoring is niet beschikbaar op deze Compositor." + }, + "indicator": { + "default-value": "Standaard: {value}", + "system-default": "Systeemstandaard" + }, + "launcher": { + "clipboard-desc": "Toegang tot en beheer uw klembordgeschiedenis vanuit de launcher.", + "execute-desc": "Configureer hoe applicaties worden gestart.", + "execute-title": "Uitvoering", + "settings-annotation-tool-description": "Commando dat wordt uitgevoerd bij het klikken op de annotatieknop in de klembordgeschiedenis. De afbeelding wordt naar dit commando gepiped.", + "settings-annotation-tool-label": "Annotatietool", + "settings-annotation-tool-placeholder": "bijv. 'gradia', 'satty -f -'", + "settings-auto-paste-description": "Plak automatisch het geselecteerde klemborditem. Vereist wtype.", + "settings-auto-paste-label": "Automatisch plakken", + "settings-clip-chips-description": "Toon een tabbladbalk om de klembordgeschiedenis op type te filteren (Afbeeldingen, Links, Bestanden, Code, enz.).", + "settings-clip-chips-label": "Categoriechips inschakelen", + "settings-clip-preview-description": "Toon een voorbeeld van de inhoud van het klembord bij gebruik van het >clip-commando.", + "settings-clip-preview-label": "Klembordvoorbeeld inschakelen", + "settings-clip-smart-icons-description": "Toon specifieke pictogrammen voor links, bestanden, kleuren en code in plaats van een generiek klembordpictogram.", + "settings-clip-smart-icons-label": "Slimme pictogrammen inschakelen", + "settings-clip-wrap-text-description": "Tekst in de klembordlijst wrappen in plaats van af te breken.", + "settings-clip-wrap-text-label": "Klembordtekst omwikkelen", + "settings-clipboard-history-description": "Toegang tot eerder gekopieerde items vanuit de launcher.", + "settings-clipboard-history-label": "Klembordgeschiedenis inschakelen", + "settings-clipboard-watch-image-description": "Volledige commandoreeks die aan wl-paste wordt doorgegeven voor afbeeldingswijzigingen (herstart vereist).", + "settings-clipboard-watch-image-label": "Afbeelding Bewakingscommando", + "settings-clipboard-watch-text-description": "Volledige commandoreeks die aan wl-paste wordt doorgegeven voor tekstwijzigingen (vereist herstart).", + "settings-clipboard-watch-text-label": "Tekst volgcommando", + "settings-custom-launch-prefix-description": "Voorzie commando's van een aangepaste launcher-prefix (bijv. 'runapp' voor systemd-integratie).", + "settings-custom-launch-prefix-enabled-description": "Gebruik een aangepaste prefix om applicaties te starten in plaats van de standaardmethode.", + "settings-custom-launch-prefix-enabled-label": "Aangepaste startprefix inschakelen", + "settings-custom-launch-prefix-label": "Aangepaste startprefix", + "settings-density-description": "Pas de grootte van applicatiepictogrammen en de dichtheid van de Launcher aan.", + "settings-density-label": "Dichtheid", + "settings-desc": "Pas het gedrag en uiterlijk van de launcher aan.", + "settings-enable-session-search-description": "Toon sessieacties (vergrendelen, afsluiten, herstarten, etc.) bij het zoeken in de Launcher.", + "settings-enable-session-search-label": "Sessie zoeken inschakelen", + "settings-enable-settings-search-description": "Toon instellingen in de zoekresultaten van de Launcher.", + "settings-enable-settings-search-label": "Instellingen zoeken inschakelen", + "settings-enable-windows-search-description": "Actieve vensters zoeken en focussen.", + "settings-enable-windows-search-label": "Vensterzoekfunctie inschakelen", + "settings-grid-view-description": "Items in een raster weergeven in plaats van een lijst.", + "settings-icon-mode-description": "Gebruik native systeemiconen in plaats van Tabler-iconen.", + "settings-icon-mode-label": "Gebruik native iconen", + "settings-ignore-mouse-input-description": "Schakel muisinteractie en scrollwiel uit in de launcher.", + "settings-ignore-mouse-input-label": "Negeer muisinvoer", + "settings-overlay-layer-description": "Toon de starter op de overlaylaag, boven vensters op volledig scherm. Indien ingeschakeld, wordt de starter niet aan de balk bevestigd.", + "settings-overlay-layer-label": "Boven volledig scherm weergeven", + "settings-position-description": "Kies waar het launcherpaneel verschijnt.", + "settings-show-categories-description": "Categorieรซn-tabbladen weergeven voor het filteren van applicaties.", + "settings-show-categories-label": "Categorieรซn weergeven", + "settings-show-icon-background-description": "Toon een afgeronde rechthoek achtergrond achter pictogrammen.", + "settings-show-icon-background-label": "Toon pictogramachtergrond", + "settings-sort-by-usage-description": "Indien ingeschakeld, verschijnen vaak gestarte apps bovenaan in de lijst.", + "settings-sort-by-usage-label": "Sorteren op meest gebruikt", + "settings-terminal-command-description": "Commando om een terminal te starten. Bijv. 'kitty -e' of 'gnome-terminal --'.", + "settings-terminal-command-label": "Terminalcommando", + "settings-use-app2unit-description": "Gebruikt een alternatieve startmethode om app-processen beter te beheren en problemen te voorkomen.", + "settings-use-app2unit-label": "App2Unit gebruiken om applicaties te starten", + "settings-view-mode-description": "Kies de indeling voor de starter-items.", + "settings-view-mode-label": "Weergavemodus", + "title": "Launcher" + }, + "location": { + "auto-locate-description": "Detecteert automatisch uw locatie periodiek met behulp van uw IP-adres.", + "auto-locate-label": "Automatische Lokalisatie", + "calendar-cards-desc": "Kaarten organiseren en in-/uitschakelen in het kalenderpaneel.", + "calendar-cards-title": "Kalenderkaarten", + "calendar-header-label": "Kalenderkop", + "calendar-month-label": "Kalendermaand", + "date-time-12hour-format-description": "Toon tijd in 12-uurs formaat op het vergrendelscherm en in de kalender. De klok op de balk heeft zijn eigen instellingen.", + "date-time-12hour-format-label": "12-uurs tijdnotatie gebruiken", + "date-time-desc": "Pas aan hoe datum en tijd worden weergegeven.", + "date-time-first-day-of-week-automatic": "Automatisch (systeemlocale gebruiken)", + "date-time-first-day-of-week-description": "Kies welke dag de week in de kalender start.", + "date-time-first-day-of-week-label": "Eerste dag van de week", + "date-time-show-events-description": "Toon gebeurtenissen in het kalenderpaneel.", + "date-time-show-events-label": "Kalendergebeurtenissen tonen", + "date-time-title": "Datum & tijd", + "date-time-use-analog-description": "Toon een analoge klok op het kalendervenster.", + "date-time-use-analog-label": "Analoge klok gebruiken", + "date-time-week-numbers-description": "Toon het weeknummer van het jaar (bijv. week 38) in de kalender.", + "date-time-week-numbers-label": "Weeknummers tonen", + "geolocate-now-button": "Nu lokaliseren", + "location-desc": "Krijg nauwkeurige weersinformatie en nachtlichtplanning door je locatie in te stellen.", + "location-search-description": "bijv. Toronto, ON", + "location-search-label": "Zoek een locatie", + "location-search-placeholder": "Voer de locatienaam in", + "location-title": "Je locatie", + "weather-desc": "Kies je voorkeurseenheid voor temperatuur.", + "weather-enabled-description": "Toon weerinformatie in de interface en haal weergegevens op. Wanneer uitgeschakeld, worden alle weerelementen verborgen en worden er geen netwerkverzoeken gedaan.", + "weather-enabled-label": "Weer inschakelen", + "weather-fahrenheit-description": "Temperatuur in Fahrenheit weergeven in plaats van Celsius.", + "weather-fahrenheit-label": "Temperatuur in Fahrenheit (ยฐF) weergeven", + "weather-hide-city-description": "Verberg de stadsnaam in weerweergaven in de hele interface.", + "weather-hide-city-label": "Verberg stadsnaam", + "weather-hide-timezone-description": "Verberg de tijdzone-afkorting in alle weerweergaves in de interface.", + "weather-hide-timezone-label": "Verberg tijdzone", + "weather-show-effects-description": "Toon extra visuele effecten (zoals regen, sneeuw of bliksem) op de weerkaart.", + "weather-show-effects-label": "Weerseffecten weergeven", + "weather-show-in-calendar-description": "Toon de dagelijkse weersverwachting direct in je kalenderview.", + "weather-show-in-calendar-label": "Weer in kalender weergeven", + "weather-talia-mascot-always-description": "Gebruik altijd Talia emojis in plaats van tabler icons.", + "weather-talia-mascot-always-label": "Altijd Talia weer-emoji's tonen" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "Wanneer fprintd (vingerafdrukauthenticatie) actief is, kunt u met deze optie nog steeds inloggen met uw wachtwoord in plaats van een vingerafdruk.", + "allow-password-with-fprintd-label": "Wachtwoordlogin toestaan met fprintd", + "auto-start-auth-description": "Start bijvoorbeeld automatisch de vingerafdrukauthenticatie zonder dat er een toetsaanslag of klik nodig is.", + "auto-start-auth-label": "Automatische authenticatie bij opstarten", + "clock-format-description": "Pas het klokformaat aan met datum-/tijd-syntaxtokens.", + "clock-format-label": "Klokformaat", + "clock-style-analog": "Analoog", + "clock-style-custom": "Aangepast", + "clock-style-description": "Kies de visuele stijl van de klok op het vergrendelscherm.", + "clock-style-digital": "Digitaal", + "clock-style-label": "Klokstijl", + "compact-lockscreen-description": "Toon alleen de logininvoer en systeemknoppen, verberg weer- en mediawidgets.", + "compact-lockscreen-label": "Compact vergrendelscherm", + "enable-lockscreen-media-controls-description": "Toon interactieve mediabediening op het vergrendelscherm.", + "enable-lockscreen-media-controls-label": "Mediabediening op het Vergrendelscherm", + "lock-on-suspend-description": "Vergrendel het scherm automatisch wanneer het systeem wordt onderbroken.", + "lock-on-suspend-label": "Vergrendelen bij onderbreken", + "lock-screen-animations-description": "Vergrendelschermanimaties in- of uitschakelen.", + "lock-screen-animations-label": "Vergrendelschermanimaties", + "lock-screen-blur-strength-description": "Past een vervagingseffect toe op de achtergrond van het vergrendelscherm.", + "lock-screen-blur-strength-label": "Sterkte van de vervaging van het vergrendelscherm", + "lock-screen-tint-strength-description": "Past een getinte overlay toe op de achtergrond van het vergrendelscherm.", + "lock-screen-tint-strength-label": "Sterkte van de vergrendelschermtint", + "monitors-desc": "Vergrendelscherm tonen op specifieke monitoren. Standaard op alle, indien er geen zijn gekozen.", + "password-chars-description": "Leuke pictogrammen om je wachtwoord te verbergen.", + "password-chars-label": "Willekeurige wachtwoordpictogrammen", + "show-hibernate-description": "De optie 'sluimerstand' tonen in de energieopties.", + "show-hibernate-label": "Sluimerstand tonen", + "show-session-buttons-description": "Toegang tot energie-instellingen toestaan vanaf het vergrendelscherm.", + "show-session-buttons-label": "Energieopties", + "title": "Vergrendelscherm" + }, + "network": { + "bluetooth-description": "Bluetooth-beheer activeren.", + "bluetooth-rssi-polling-description": "Periodiek de RSSI bemonsteren voor verbonden apparaten via bluetoothctl. Mogelijk niet beschikbaar voor alle apparaten; gebruikt minimale resources wanneer ingeschakeld.", + "bluetooth-rssi-polling-label": "Bluetooth-signaalpolling", + "desc": "Beheer Wi-Fi- en Bluetooth-verbindingen.", + "wifi-description": "Draadloze netwerken beheren (vereist NetworkManager)." + }, + "notifications": { + "duration-critical-urgency-description": "Hoe lang meldingen met kritieke prioriteit zichtbaar blijven.", + "duration-critical-urgency-label": "Kritieke urgentie", + "duration-desc": "Configureer hoe lang meldingen zichtbaar blijven op basis van hun urgentieniveau.", + "duration-low-urgency-description": "Hoe lang meldingen met lage prioriteit zichtbaar blijven.", + "duration-low-urgency-label": "Lage urgentie", + "duration-normal-urgency-description": "Hoe lang meldingen met normale prioriteit zichtbaar blijven.", + "duration-normal-urgency-label": "Normale urgentie", + "duration-respect-expire-description": "Gebruik de vervaltijd die in de melding is ingesteld.", + "duration-respect-expire-label": "Vervaltijd respecteren", + "duration-title": "Duur van meldingen", + "history-clear-dismiss-description": "Melding uit de geschiedenis wissen wanneer deze wordt gesloten.", + "history-clear-dismiss-label": "Wissen bij sluiten", + "history-critical-urgency-description": "Meldingen met kritieke prioriteit opslaan in de geschiedenis.", + "history-critical-urgency-label": "Kritieke urgentie opslaan in geschiedenis", + "history-desc": "Bepalen welke meldingen worden opgeslagen in de geschiedenis op basis van hun urgentieniveau.", + "history-low-urgency-description": "Meldingen met lage prioriteit opslaan in de geschiedenis.", + "history-low-urgency-label": "Lage urgentie opslaan in geschiedenis", + "history-normal-urgency-description": "Meldingen met normale prioriteit opslaan in de geschiedenis.", + "history-normal-urgency-label": "Normale urgentie opslaan in geschiedenis", + "monitors-desc": "Meldingen op specifieke monitoren tonen. Standaard op alle als er geen zijn gekozen.", + "rules-action-block": "Blok", + "rules-action-block-desc": "Slaat volledig over.", + "rules-action-hide": "Verbergen", + "rules-action-hide-desc": "Geen pop-up, geen geluid, wordt toegevoegd aan de geschiedenis.", + "rules-action-label": "Actie", + "rules-action-mute": "Dempen", + "rules-action-mute-desc": "Geen geluid, toont nog steeds pop-up en in geschiedenis.", + "rules-add": "Regel toevoegen", + "rules-delete": "Regel verwijderen", + "rules-description": "Komt overeen met app-naam of inhoud. Regels worden in volgorde gecontroleerd, en de eerste overeenkomst wordt toegepast.", + "rules-edit": "Regel bewerken", + "rules-label": "Filterregels", + "rules-pattern-label": "Patroon", + "rules-pattern-placeholder": "firefox, discord, of /regex/", + "rules-tab": "Regels", + "settings-always-on-top-description": "Toon meldingen boven fullscreen-vensters en andere lagen.", + "settings-background-opacity-description": "Pas de dekking van meldingsachtergronden aan.", + "settings-density-description": "Kies de dichtheid van de meldingenkaart.", + "settings-density-label": "Dichtheid", + "settings-desc": "Configureer uiterlijk en gedrag van meldingen.", + "settings-do-not-disturb-description": "Schakel alle meldingspop-ups uit wanneer ingeschakeld.", + "settings-enabled-description": "Schakel de meldingsdaemon in of uit, vereist een herstart van Noctalia shell.", + "settings-enabled-label": "Meldingen inschakelen", + "settings-location-description": "Waar meldingen op het scherm verschijnen.", + "settings-markdown-description": "Meldingsinhoud weergeven met Markdown-opmaak.", + "settings-markdown-label": "Markdown inschakelen", + "sounds-desc": "Configureer meldingsgeluidseffecten en volume.", + "sounds-enabled-description": "Schakel geluidseffecten in voor inkomende meldingen.", + "sounds-enabled-label": "Meldingsgeluiden inschakelen", + "sounds-excluded-apps-description": "Sla het afspelen van het geconfigureerde notificatiegeluid over voor specifieke applicaties die hun eigen ingebouwde geluiden hebben.", + "sounds-excluded-apps-label": "Uitgesloten applicaties", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "Pad naar het geluidsbestand dat wordt afgespeeld voor meldingen met kritieke prioriteit.", + "sounds-files-critical-label": "Kritieke urgentie geluid", + "sounds-files-critical-select-title": "Selecteer kritieke urgentie geluidsbestand", + "sounds-files-desc": "Configureer aangepaste geluidsbestanden voor verschillende meldingsurgentieniveaus.", + "sounds-files-low-description": "Pad naar het geluidsbestand dat wordt afgespeeld voor meldingen met lage prioriteit.", + "sounds-files-low-label": "Lage urgentie geluid", + "sounds-files-low-select-title": "Selecteer lage urgentie geluidsbestand", + "sounds-files-normal-description": "Pad naar het geluidsbestand dat wordt afgespeeld voor meldingen met normale prioriteit.", + "sounds-files-normal-label": "Normale urgentie geluid", + "sounds-files-normal-select-title": "Selecteer normale urgentie geluidsbestand", + "sounds-files-placeholder": "Voer pad naar geluidsbestand in", + "sounds-files-select-file": "Selecteer geluidsbestand", + "sounds-files-title": "Geluidsbestanden", + "sounds-files-unified-description": "Pad naar het geluidsbestand dat wordt afgespeeld voor meldingen.", + "sounds-files-unified-label": "Meldingsgeluid", + "sounds-files-unified-select-title": "Selecteer meldingsgeluidsbestand", + "sounds-separate-description": "Gebruik verschillende geluidsbestanden voor lage, normale en kritieke prioriteitsmeldingen.", + "sounds-separate-label": "Gebruik verschillende geluiden per prioriteit", + "sounds-title": "Geluidsinstellingen", + "sounds-unavailable-description": "Installeer Qt6 Multimedia om meldingsgeluiden in te schakelen.", + "sounds-unavailable-label": "Meldingsgeluiden niet beschikbaar", + "sounds-volume-description": "Pas het volumeniveau aan voor meldingsgeluiden.", + "sounds-volume-label": "Geluidsvolume", + "toast-battery-description": "Toon een waarschuwing wanneer het accuniveau onder dit percentage zakt.", + "toast-battery-label": "Accuwaarschuwing", + "toast-desc": "Configureer het uiterlijk en gedrag van toasts.", + "toast-keyboard-description": "Toon een toastmelding wanneer de toetsenbordindeling verandert.", + "toast-keyboard-label": "Toetsenbordindeling", + "toast-media-description": "Een toast weergeven wanneer de afspeelstatus van media verandert.", + "toast-media-label": "Media" + }, + "osd": { + "always-on-top-description": "Toon OSD boven fullscreen-vensters en andere lagen.", + "always-on-top-label": "Altijd bovenop", + "background-opacity-description": "Regelt de transparantie van de OSD-achtergrond.", + "background-opacity-label": "Achtergrond dekkendheid", + "description": "Configureer on-screenindicatoren zoals overlays voor volume en helderheid.", + "duration-auto-hide-description": "Stel de tijd in voordat OSD verdwijnt.", + "duration-auto-hide-label": "Automatisch verbergen na", + "duration-desc": "Hoe lang OSD zichtbaar blijft voordat het automatisch verdwijnt.", + "duration-title": "Timeout automatisch verbergen", + "enabled-description": "Toon volumeveranderingen en helderheid in realtime.", + "enabled-label": "On-screenweergave inschakelen", + "events-desc": "Selecteer welke gebeurtenissen de weergave op het scherm activeren.", + "general-desc": "Configureer de zichtbaarheid en het gedrag van OSD.", + "location-description": "Waar on-screenweergaven verschijnen.", + "monitors-desc": "Toon OSD op specifieke monitoren. Standaard op alle als er geen zijn gekozen.", + "title": "On-screenweergave", + "types-brightness-description": "Toon het OSD wanneer de schermhelderheid verandert.", + "types-brightness-label": "Helderheid", + "types-custom-text-description": "OSD weergeven voor aangepaste tekstberichten via IPC.", + "types-custom-text-label": "Aangepaste tekst", + "types-desc": "Selecteer de gebeurtenissen die de OSD activeren.", + "types-input-volume-description": "Toon het OSD wanneer het microfoonvolume verandert.", + "types-input-volume-label": "Invoervolume", + "types-lockkey-description": "Toon het OSD wanneer Caps Lock, Num Lock of Scroll Lock wordt omgeschakeld.", + "types-lockkey-label": "Vergrendeltoetsen", + "types-media-description": "OSD weergeven wanneer de afspeelstatus van media verandert (afspelen, pauzeren, overslaan).", + "types-media-label": "Media afspelen", + "types-title": "OSD triggergebeurtenissen", + "types-volume-description": "Toon het OSD wanneer het uitvoervolume verandert.", + "types-volume-label": "Uitvoervolume" + }, + "plugins": { + "auto-update": "Plugins automatisch bijwerken", + "auto-update-description": "Update automatisch alle plugins wanneer de shell start.", + "available-description": "Blader door en installeer plugins vanuit geconfigureerde bronnen.", + "available-label": "Beschikbare plug-ins", + "available-no-plugins-description": "Controleer je plugin-bronnen of vernieuw de lijst.", + "available-no-plugins-label": "Geen plug-ins beschikbaar", + "check-for-updates": "Controleren op updates", + "checking-for-updates": "Controleren op updates...", + "collision-already-installed": "Deze plugin is al geรฏnstalleerd", + "collision-custom-version-exists": "Er is al een aangepaste versie van \"{source}\" geรฏnstalleerd", + "collision-official-version-exists": "De officiรซle versie van deze plugin is al geรฏnstalleerd", + "development-disable": "Ontwikkelingsmodus voor deze plug-in uitschakelen", + "development-enable": "Ontwikkelingsmodus inschakelen voor deze plug-in.", + "filter-downloaded": "Gedownload", + "filter-not-downloaded": "Niet gedownload", + "filter-tags-description": "Filter plug-ins op categorie of downloadstatus.", + "filter-tags-label": "Tags", + "hot-reload-description": "Laad plugins automatisch opnieuw wanneer hun bestanden veranderen. Handig voor plugin-ontwikkeling.", + "hot-reload-label": "Hot reload (ontwikkelmodus)", + "hot-reloaded": "Plugin opnieuw geladen: {name}", + "install-error": "Installatie mislukt: {error}", + "install-incompatible": "{plugin} vereist Noctalia v{version} of hoger", + "install-success": "{plugin} succesvol geรฏnstalleerd", + "installed-description": "Beheer en configureer alle lokaal geรฏnstalleerde plugins.", + "installed-label": "Geรฏnstalleerde plug-ins", + "installed-no-plugins-description": "Installeer plugins uit de sectie \"Beschikbaar\".", + "installed-no-plugins-label": "Geen plugins geรฏnstalleerd", + "installing": "{plugin} installeren...", + "notify-updates": "Plugin-updatemeldingen", + "notify-updates-description": "Toon een melding wanneer nieuwe plug-in-updates beschikbaar zijn.", + "open-plugin-page": "Open plug-in pagina", + "open-plugins-tab": "Open plugininstellingen", + "plugin-settings-title": "{plugin} Instellingen", + "refresh-refreshing": "Pluginlijst vernieuwen...", + "refresh-tooltip": "Beschikbare plugins vernieuwen", + "settings-error-not-loaded": "Plugin niet geladen", + "settings-saved": "Plugininstellingen opgeslagen", + "settings-tooltip": "Plugin instellingen", + "source-custom": "Aangepaste bron", + "sources-add-custom": "Aangepaste repository toevoegen", + "sources-add-dialog-description": "Voeg een GitHub repository toe als een pluginbron.", + "sources-add-dialog-error": "Pluginbron toevoegen mislukt", + "sources-add-dialog-name": "Repositorynaam", + "sources-add-dialog-name-placeholder": "Mijn aangepaste plugins", + "sources-add-dialog-success": "Pluginbron succesvol toegevoegd", + "sources-add-dialog-title": "Pluginbron toevoegen", + "sources-add-dialog-url": "Repository URL", + "sources-description": "Beheer plugin-repositories.", + "sources-placeholder": "Mijn coole repository", + "sources-remove-tooltip": "Pluginbron verwijderen", + "title": "Plugins", + "translations-reloaded": "Vertalingen opnieuw geladen: {name}", + "uninstall-dialog-description": "Weet je zeker dat je {plugin} wilt verwijderen? Hiermee worden alle plugin-gegevens verwijderd.", + "uninstall-dialog-title": "Plugin verwijderen", + "uninstall-error": "Verwijderen mislukt: {error}", + "uninstall-success": "{plugin} is succesvol verwijderd", + "uninstalling": "{plugin} verwijderen...", + "update-all": "Alles bijwerken ({count})", + "update-all-success": "Alle plugins succesvol bijgewerkt", + "update-available": "Nieuwe plugin update beschikbaar", + "update-available-plural": "Nieuwe plugin updates beschikbaar ({count})", + "update-error": "Plugin bijwerken mislukt: {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (vereist Noctalia v{required})", + "update-success": "{plugin} is bijgewerkt naar v{version}", + "update-version": "v{current} โ†’ v{new}", + "updating": "Bezig met updaten..." + }, + "region": { + "title": "Regio" + }, + "session-menu": { + "countdown-duration-description": "Stel in hoe lang de afteltimer duurt voordat energieacties worden uitgevoerd.", + "countdown-duration-label": "Afteltimer duur", + "enable-countdown-description": "Toon een afteltimer voordat energieacties worden uitgevoerd.", + "enable-countdown-label": "Afteltimer inschakelen", + "entries-desc": "Aanpassen welke energieacties in het sessiemenu verschijnen en in welke volgorde.", + "entries-title": "Energieacties", + "entry-settings-command-description": "Aangepast commando om uit te voeren voor deze actie. Laat leeg om het standaard systeemcommando te gebruiken.", + "entry-settings-command-placeholder": "bijv. systemctl poweroff", + "entry-settings-default-command-lock": "Intern vergrendelscherm (geen commando)", + "entry-settings-default-command-logout": "Interne afmelding (geen commando)", + "entry-settings-default-info-description": "Als er geen aangepast commando is opgegeven, wordt het standaard systeemcommando gebruikt.", + "entry-settings-default-info-label": "Standaardopdracht", + "entry-settings-keybind-description": "Wijs een toetscombinatie toe om deze actie te activeren wanneer het sessiemenu geopend is.", + "entry-settings-keybind-placeholder": "Druk op Opnemen om een toets toe te wijzen...", + "entry-settings-keybind-recording": "Opname... Druk op een willekeurige toetscombinatie", + "entry-settings-title": "Configureer {entry}", + "entry-settings-tooltip": "Configureer opdracht", + "general-desc": "Configureer het gedrag en uiterlijk van het sessiemenupaneel.", + "large-buttons-layout-description": "Kies hoe sessiemenuknoppen worden weergegeven.", + "large-buttons-layout-label": "Lay-out met grote knoppen", + "large-buttons-style-description": "Toon het sessiemenu met grote knoppen in een rasterindeling.", + "large-buttons-style-label": "Stijl met grote knoppen", + "position-description": "Kies waar het sessiemenupaneel verschijnt wanneer het wordt geopend.", + "show-header-description": "Toon de titel en sluitknop bovenaan het sessiemenu.", + "show-header-label": "Koptekst tonen", + "show-keybinds-description": "Toon toetscombinatiehints bij sessie-opties.", + "show-keybinds-label": "Toetsencombinaties weergeven" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Houd desktopwidgets zichtbaar terwijl de Noctalia prestatiemodus is ingeschakeld.", + "noctalia-performance-disable-desktop-widgets-label": "Desktopwidgets inschakelen in prestatiemodus", + "noctalia-performance-disable-wallpaper-description": "Houd bureaublad-, overzicht- en vergrendelschermachtergronden zichtbaar terwijl de Noctalia prestatiemodus is ingeschakeld.", + "noctalia-performance-disable-wallpaper-label": "Achtergrondweergave inschakelen in prestatiemodus", + "title": "Systeem" + }, + "system-monitor": { + "critical-color-label": "Kritische kleur", + "custom-highlight-colors-title-label": "Aangepaste markeerkleuren", + "disk-available-label": "Beschikbare schijfruimte", + "disk-section-label": "Schijfgebruik", + "enable-dgpu-monitoring-description": "Waarschuwing: Dit zal uw dedicated GPU (NVIDIA/AMD) activeren, wat een aanzienlijke impact kan hebben op de accuduur van laptops met hybride grafische kaarten.", + "enable-dgpu-monitoring-label": "Dedicated GPU-monitoring inschakelen", + "external-monitor-description": "Voer de opdracht of applicatiepad in om te starten bij het activeren van de externe systeemmonitor applicatie.", + "external-monitor-label": "Externe systeemmonitor opdracht", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "Configureer het gedrag en de weergave van de systeemmonitor.", + "gpu-section-label": "GPU-temperatuur", + "highlight-colors-section-label": "Markeerkleuren", + "polling-interval-label": "Peilingsinterval", + "polling-section-description": "Configureer hoe vaak elke systeemmetriek wordt bijgewerkt.", + "polling-section-label": "Peilingsintervallen", + "threshold-critical": "Kritiek", + "threshold-warning": "Waarschuwing", + "thresholds-section-description": "Pas de waarschuwing/kritieke drempels en de pollingsintervallen voor elke systeemmetriek aan.", + "use-custom-highlight-colors-description": "Indien uitgeschakeld, worden de standaard markeerkleuren van het thema gebruikt.", + "use-custom-highlight-colors-label": "Gebruik aangepaste markeerkleuren", + "warning-color-label": "Waarschuwingskleur" + }, + "user-interface": { + "allow-panels-without-bar-description": "Indien ingeschakeld, kunnen panelen op elk scherm worden geopend. Indien uitgeschakeld, worden panelen alleen geopend op schermen met een balk, wat het geheugengebruik kan verminderen.", + "allow-panels-without-bar-label": "Sta panelen toe op schermen zonder balk", + "animation-disable-description": "Schakel alle animaties uit voor een snellere, responsievere ervaring.", + "animation-disable-label": "UI-animaties uitschakelen", + "animation-speed-description": "Pas de globale animatiesnelheid aan.", + "animation-speed-label": "Animatiesnelheid", + "animation-speed-reset": "Animatiesnelheid resetten", + "appearance-desc": "Pas visuele elementen aan, zoals tooltips, randen en schaduwen.", + "blur-behind-description": "Vervaagt het gebied achter balken en panelen met behulp van het compositor-blurprotocol.", + "blur-behind-label": "Achtergrond vervagen", + "box-border-description": "Toon een omlijning rond inhoudsgebieden.", + "box-border-label": "Containeromtrek", + "box-border-radius-description": "Past de hoekronding aan van belangrijke lay-outsecties, zoals zijbalken, kaarten en inhoudspanelen.", + "box-border-radius-label": "Hoekradius", + "control-border-radius-description": "Regelt de kromming van interactieve elementen, waaronder knoppen, schakelaars en tekstvelden.", + "control-border-radius-label": "Voer straal in", + "desc": "Pas de look, feel en het gedrag van de interface aan.", + "dim-desktop-description": "Dim de desktop wanneer panelen of menu's geopend zijn.", + "dim-desktop-label": "Desktop dimmen", + "dimmer-opacity-description": "Stel het opaciteitsniveau in voor het dimmen van het bureaublad.", + "dimmer-opacity-label": "Doorzichtigheid van gedimde desktop", + "panel-background-opacity-description": "Stel de achtergronddoorzichtigheid in voor alle panelen (balk, launcher, instellingen, enz...).", + "panel-background-opacity-label": "Paneel achtergronddoorzichtigheid", + "panels-attached-to-bar-description": "Panelen vergrendelen aan de balk en schermranden, voor een naadloze look met stijlvolle omgekeerde hoeken.", + "panels-attached-to-bar-label": "Panelen aan randen vastklikken", + "panels-overlay-description": "Zorgt ervoor dat panelen en de balk zichtbaar blijven, zelfs boven fullscreen-applicaties.", + "panels-overlay-label": "Panelen & balk bovenop houden", + "scaling-description": "Wijzigt de grootte van de algemene gebruikersinterface, exclusief de balk.", + "scaling-label": "Interfaceschaling", + "scrollbar-always-visible-description": "Houd schuifbalken altijd zichtbaar wanneer inhoud scrollbaar is, in plaats van ze alleen bij hover te tonen.", + "scrollbar-always-visible-label": "Schuifbalken altijd weergeven", + "settings-panel-header": "Instellingenpaneel", + "settings-panel-mode-description": "Kies lay-out voor instellingen (mogelijk opnieuw openen vereist).", + "settings-panel-mode-label": "Instellingenpaneelmodus", + "settings-panel-sidebar-card-style-description": "Omhult de instellingen zijbalk met een gevulde achtergrond en afgeronde hoeken.", + "settings-panel-sidebar-card-style-label": "Zijbalk kaartstijl", + "shadows-description": "Schakelt slagschaduwen onder balken en panelen in.", + "shadows-direction-description": "Kies waar de schaduw wordt geworpen.", + "shadows-direction-label": "Richting van schaduw", + "shadows-label": "Slagschaduwen", + "title": "Gebruikersinterface", + "tooltips-description": "Schakel tooltips in of uit in de hele interface.", + "tooltips-label": "Tooltips tonen", + "translucent-widgets-description": "Maak knoppen, tabbladen en andere widgets in panelen semi-transparant.", + "translucent-widgets-label": "Doorzichtige widgets" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "Alfabetisch", + "automation-change-mode-description": "Kies hoe achtergronden worden geselecteerd bij automatisch wisselen.", + "automation-change-mode-label": "Wisselmodus", + "automation-custom-interval-description": "Voer tijd in als UU:MM (bijv. 01:30).", + "automation-custom-interval-label": "Aangepast interval", + "automation-interval-description": "Hoe vaak achtergronden automatisch worden gewisseld.", + "automation-interval-label": "Achtergrondinterval", + "automation-random-wallpaper-description": "Plan willekeurige achtergrondwissels op vaste intervallen.", + "automation-scheduled-change-description": "Wissel automatisch achtergronden op vaste intervallen.", + "automation-scheduled-change-label": "Geplande wissel", + "look-feel-edge-smoothness-description": "Past een zacht, vervaagd effect toe op de rand van overgangen.", + "look-feel-edge-smoothness-label": "Overgangsranden verzachten", + "look-feel-fill-color-description": "Kies een vulkleur die achter de achtergrond kan verschijnen.", + "look-feel-fill-mode-description": "Selecteer hoe de afbeelding moet schalen om overeen te komen met de resolutie van je monitor.", + "look-feel-fill-mode-label": "Vulmodus", + "look-feel-skip-startup-transition-description": "Sla de achtergrondanimatie over wanneer de shell start.", + "look-feel-skip-startup-transition-label": "Opstartovergang overslaan", + "look-feel-title": "Uiterlijk & gevoel", + "look-feel-transition-duration-description": "Duur van overgangsanimaties in seconden.", + "look-feel-transition-duration-label": "Overgangsduur", + "look-feel-transition-type-description": "Animatietype bij het wisselen tussen achtergronden.", + "look-feel-transition-type-label": "Overgangstype", + "settings-clear-cache-button": "Cache wissen", + "settings-clear-cache-description": "Wis in de cache opgeslagen, verkleinde achtergronden om schijfruimte vrij te maken.", + "settings-clear-cache-label": "Achtergrondcache", + "settings-clear-cache-toast": "Achtergrondcache geleegd", + "settings-desc": "Bepaal hoe achtergronden worden beheerd en weergegeven.", + "settings-enable-management-description": "Beheer achtergronden met Noctalia. Schakel uit als je liever een andere applicatie gebruikt.", + "settings-enable-management-label": "Achtergrondbeheer inschakelen", + "settings-enable-overview-description": "Past een vervaagde en gedimde achtergrond toe op het overzichtsscherm.", + "settings-enable-overview-label": "Achtergrond voor overzicht inschakelen", + "settings-folder-description": "Pad naar je hoofdachtergrondmap.", + "settings-folder-label": "Achtergrondmap", + "settings-hide-wallpaper-filenames-tooltip-hide": "Bestandsnamen verbergen", + "settings-hide-wallpaper-filenames-tooltip-show": "Bestandsnamen weergeven", + "settings-monitor-specific-description": "Stel een andere achtergrondmap in voor elke monitor.", + "settings-monitor-specific-label": "Monitor-specifieke mappen", + "settings-monitor-specific-tooltip": "Monitor-achtergrondmap", + "settings-overview-blur-strength-description": "Past de vervagingssterkte toe op het overzicht.", + "settings-overview-blur-strength-label": "Vervagingssterkte van het overzicht", + "settings-overview-tint-description": "Past de tintsterkte toe op het overzicht.", + "settings-overview-tint-label": "Overzichtstintsterkte", + "settings-recursive-search-description": "Zoek ook naar achtergronden in submappen van de achtergrondmap.", + "settings-recursive-search-label": "Submappen doorzoeken", + "settings-select-monitor-folder": "Achtergrondmap voor monitor selecteren", + "settings-selector-description": "Kies je achtergrond.", + "settings-selector-position-description": "Kies waar het achtergrondselectiepaneel verschijnt.", + "settings-show-hidden-files-tooltip-hide": "Verborgen bestanden verbergen", + "settings-show-hidden-files-tooltip-show": "Verborgen bestanden weergeven", + "settings-title": "Achtergrondinstellingen", + "settings-use-original-images-description": "Sla het formaat wijzigen van achtergronden over voordat ze worden weergegeven. Bespaart schijfruimte en vermindert CPU-gebruik tijdens achtergrondwisselingen, maar kan meer geheugen gebruiken voor zeer grote afbeeldingen.", + "settings-use-original-images-label": "Gebruik originele afbeeldingen", + "settings-view-mode-description": "Kies hoe achtergronden uit uw map worden weergegeven.", + "settings-view-mode-label": "Weergavemodus", + "view-mode-browse": "Blader door mappen", + "view-mode-cycle-tooltip": "Weergavemodus: {mode} (klik om te wijzigen)", + "view-mode-recursive": "Afgevlakte submappen", + "view-mode-single": "Rootdirectory" + } + }, + "placeholders": { + "add-new-keybind": "Nieuwe toetsencombinatie toevoegen", + "command-example": "echo \"Hello World\"", + "enter-command": "Voer het uit te voeren commando in (app of aangepast script)", + "enter-ipc-identifier": "Voer een unieke identificatie in voor IPC-opdrachten", + "enter-path": "Pad invoeren...", + "enter-text-to-collapse": "bijv. 'er wordt niets afgespeeld'. Gebruik /regex/ voor patronen.", + "enter-tooltip": "Voer tooltip in", + "enter-width-pixels": "Voer breedte in pixels in", + "keybind-recording": "Toetsencombinatie opnemen...", + "search": "Zoeken...", + "search-icons": "bijv. noctalia, niri, battery, cloud", + "search-launcher": "Items zoeken... of gebruik > voor commando's", + "search-wallhaven": "Zoek in Wallhaven...", + "search-wallpapers": "Typ om achtergronden te filteren..." + }, + "positions": { + "bottom": "Onder", + "bottom-center": "Middenonder", + "bottom-left": "Linksonder", + "bottom-right": "Rechtsonder", + "center": "Centreren", + "center-left": "Midden links", + "center-right": "Midden rechts", + "close-to-bar": "Dicht bij balkknop", + "follow-bar": "Balk volgen", + "left": "Links", + "right": "Rechts", + "top": "Boven", + "top-center": "Middenboven", + "top-left": "Linksboven", + "top-right": "Rechtsboven" + }, + "session-menu": { + "action-in-seconds": "{action} over {seconds} seconden...", + "cancel-timer": "Timer annuleren", + "title": "Sessie-menu" + }, + "setup": { + "all-done": "Klaar!", + "appearance": { + "subheader": "Kies donkere modus en kleurbronnen (achtergrond of voorgedefinieerd)." + }, + "customize": { + "header": "Pas je ervaring aan", + "subheader": "Pas balkpositie, dichtheid, schaal en meer aan." + }, + "skip-setup": "Setup overslaan", + "telemetry-wizard-done": "Begrepen!", + "telemetry-wizard-note": "Je hebt de controle โ€” schakel dit op elk moment in of uit in de instellingen", + "telemetry-wizard-subtitle": "We hebben anonieme analyses toegevoegd om Noctalia te helpen verbeteren", + "telemetry-wizard-title": "Privacy-update", + "wallpaper": { + "choose-dir": "Kies een map met je achtergrondafbeeldingen", + "dir-browse": "Bladeren naar achtergrondmap", + "dir-description": "Kies de map met je achtergronden.", + "dir-label": "Achtergrondmap", + "dir-select-title": "Achtergrondmap selecteren", + "header": "Kies je achtergrond", + "no-dir": "Geen achtergrondmap geselecteerd", + "no-valid": "Geen geldige afbeeldingsbestanden gevonden in: {dir}", + "none-in-dir": "Geen achtergronden gevonden in map", + "preview-error": "Afbeelding laden mislukt", + "select-prompt": "Selecteer hieronder een achtergrond", + "subheader": "Zet de toon met een mooie achtergrond." + }, + "welcome": "Welkom", + "welcome-note": "Een paar basisinstellingen om je op weg te helpen โ€” alle opties vind je in instellingen", + "welcome-subtitle": "Laten we uw bureaublad uniek maken", + "welcome-title": "Welkom bij Noctalia!" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "Geen mediaplayer gedetecteerd", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "Onbekende app", + "uptime": "Actieve tijd: {uptime}", + "user-requested": "Door gebruiker aangevraagd", + "welcome-back": "Welkom terug,", + "widget-settings-title": "Instellingen voor {widget}" + }, + "system-monitor": { + "core-usage": "Kern {id} gebruik", + "cpu-temp": "CPU-temperatuur", + "cpu-usage": "CPU-gebruik", + "disk": "Schijf", + "download-speed": "Downloadsnelheid", + "gpu-temp": "GPU-temperatuur", + "load-average": "Gem. belasting", + "title": "Systeemmonitor", + "upload-speed": "Uploadsnelheid" + }, + "toast": { + "airplane-mode": { + "description": "Alle draadloze communicatie uitschakelen.", + "title": "Vliegtuigmodus" + }, + "battery": { + "critical": "Kritiek accuniveau", + "critical-desc": "Accu is {percent}% โ€” sluit de oplader onmiddellijk aan", + "low": "Accu bijna leeg", + "low-desc": "Accu is op {percent}% โ€” sluit de oplader aan" + }, + "bluetooth": { + "address-copied": "Adres gekopieerd naar klembord", + "auto-connect-disabled": "Automatisch verbinden uitgeschakeld", + "auto-connect-enabled": "Automatisch verbinden ingeschakeld", + "auto-connecting": "Verbinding maken met {count} appara(a)t(en)...", + "confirm-code": "Bevestig code {value} op het andere apparaat.", + "connect-failed": "Verbinding met apparaat mislukt", + "disconnect-failed": "Verbinding verbreken met apparaat mislukt", + "discoverable-change-failed": "Zichtbaarheidsstatus wijzigen mislukt", + "discoverable-disabled": "Zichtbaar uitgeschakeld", + "discoverable-enabled": "Zichtbaar ingeschakeld", + "display-code": "Voer code {value} in op het andere apparaat.", + "forget-failed": "Apparaat vergeten mislukt", + "pair-failed": "Apparaat koppelen mislukt", + "passkey-required": "Passkey vereist door het apparaat.", + "pincode-required": "PIN-code vereist door het apparaat.", + "state-change-failed": "Bluetoothโ€‘status wijzigen mislukt" + }, + "clipboard": { + "long-text": "Lange tekst", + "unavailable": "Klembordgeschiedenis niet beschikbaar", + "unavailable-desc": "De toepassing 'cliphist' is niet geรฏnstalleerd. Installeer deze om functies voor klembordgeschiedenis te gebruiken" + }, + "custom-command-failed": { + "description": "Commando mislukt: {command}\\nExitcode: {code}", + "title": "Aangepaste opdracht mislukt" + }, + "do-not-disturb": { + "disabled": "'Niet storen' uitgeschakeld", + "disabled-desc": "Alle meldingen worden weergegeven", + "enabled": "'Niet storen' ingeschakeld", + "enabled-desc": "Je vindt deze meldingen terug in je geschiedenis" + }, + "donation-opened": "Donatiepagina geopend in uw browser", + "internet-limited": "Verbonden zonder internet", + "keyboard-layout": { + "changed": "Toetsenbordindeling gewijzigd naar {layout}", + "title": "Toetsenbord" + }, + "missing-control-center": { + "description": "De bedieningscentrumwidget is uit de balk verwijderd. Om er vanuit de balk weer toegang toe te krijgen, moet je de widget opnieuw toevoegen. Je kunt het ook openen door met de rechtermuisknop op de balk te klikken", + "label": "Laatste bedieningscentrumwidget verwijderd" + }, + "night-light": { + "forced": "Geforceerde activatie", + "normal": "Normale modus", + "not-installed": "wlsunset niet geรฏnstalleerd" + }, + "noctalia-performance": { + "disabled": "Prestatiemodus uitgeschakeld", + "enabled": "Prestatiemodus ingeschakeld", + "label": "Noctalia Performance" + }, + "power-profile": { + "changed": "Energieprofiel gewijzigd", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "Er is een fout opgetreden tijdens het verwerken van sjablonen", + "title": "Sjabloonverwerking mislukt" + }, + "vpn": { + "connected": "Verbonden met '{name}'", + "disconnected": "Verbinding met '{name}' verbroken" + }, + "wallpaper-colors": { + "disabled": "Achtergrondkleuren uitgeschakeld", + "enabled": "Achtergrondkleuren ingeschakeld", + "label": "Achtergrondkleuren genereren" + }, + "wifi": { + "connected": "Verbonden met '{ssid}'", + "connection-failed": "Verbinding mislukt", + "connection-timeout": "Time-out bij verbinding", + "disconnected": "Verbinding met '{ssid}' verbroken", + "incorrect-password": "Onjuist wachtwoord", + "network-not-found": "Netwerk niet gevonden" + } + }, + "tooltips": { + "add-widget": "Widget toevoegen", + "auto-connect": "Automatisch verbinden voor dit apparaat in-/uitschakelen", + "bluetooth-auto-connect-off": "Automatisch verbinden uitgeschakeld", + "bluetooth-auto-connect-on": "Automatisch verbinden ingeschakeld", + "bluetooth-devices": "Bluetooth-apparaten", + "brightness-at": "Helderheid: {brightness}%", + "click-to-start-recording": "Schermrecorder (opname starten)", + "click-to-stop-recording": "Schermrecorder (opname stoppen)", + "collapse": "Zijbalk inklappen", + "copy-address": "Adres kopiรซren", + "delete-notification": "Melding verwijderen", + "dismiss-notification": "Melding sluiten", + "do-not-disturb-enabled": "Niet storen", + "expand": "Zijbalk uitklappen", + "forget-network": "Netwerk vergeten", + "grid-view": "Rasterweergave", + "hidden-files-hide": "Verborgen bestanden", + "hide-unnamed-devices": "Verberg naamloze apparaten", + "home": "Home-map", + "input-muted": "Ingang dempen/dempen opheffen", + "keep-awake": "Wakker houden", + "keyboard-layout": "{layout}-toetsenbordindeling", + "list-view": "Lijstweergave", + "manage-vpn": "VPN-verbindingen", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "Maximum aantal widgets bereikt", + "microphone-volume-at": "Microfoonvolume: {volume}%", + "move-to-section": "Ga naar {section}", + "mute": "Dempen", + "next-media": "Volgende track", + "next-month": "Volgende maand", + "night-light-not-installed": "Nachtlicht (niet beschikbaar)", + "noctalia-performance-enabled": "Noctalia-prestatiemodus", + "open-annotation-tool": "Openen met annotatietool", + "open-control-center": "Bedieningscentrum", + "open-notification-history-enable-dnd": "Meldingsgeschiedenis", + "open-settings": "Instellingen", + "open-tray-dropdown": "Systeemvak", + "output-muted": "Uitgang dempen/dempen opheffen", + "power-profile": "{profile} energieprofiel", + "previous-media": "Vorige track", + "previous-month": "Vorige maand", + "refresh-devices": "Apparaten verversen", + "refresh-wallhaven": "Wallhaven-resultaten verversen", + "refresh-wallpaper-list": "Achtergrondlijst verversen", + "remove-widget": "Widget verwijderen", + "search-close": "Zoeken sluiten", + "session-menu": "Sessiemenu", + "show-all-devices": "Toon alle apparaten", + "switch-to-dark-mode": "Donkere modus", + "switch-to-light-mode": "Lichte modus", + "unmute": "Dempen opheffen", + "up": "Bovenliggende map", + "volume-at": "Uitvoervolume: {volume}%", + "wallpaper-selector": "Achtergrondkiezer" + }, + "wallpaper": { + "browse": { + "empty-directory": "Deze map is leeg.", + "go-root": "Ga naar de root van de achtergrond", + "go-up": "Ga naar de bovenliggende map", + "go-up-hint": "Gebruik de terugknop om omhoog te navigeren." + }, + "configure-directory": "Configureer je achtergrondmap met afbeeldingen.", + "fill-modes": { + "crop": "Bijsnijden (vullend)", + "fit": "Passend (volledig zichtbaar)", + "repeat": "Herhalen (Tegel)", + "stretch": "Uitrekken" + }, + "no-match": "Geen resultaten gevonden.", + "no-wallpaper": "Geen achtergrond gevonden.", + "panel": { + "apikey-help": "Een API-sleutel is vereist om toegang te krijgen tot NSFW-content.", + "apikey-label": "API-sleutel", + "apikey-managed-by-env": "Beheerd via de NOCTALIA_WALLHAVEN_API_KEY omgevingsvariabele.", + "apikey-placeholder": "Voer uw Wallhaven API-sleutel in", + "appearance-dark-tab": "Donker", + "appearance-light-tab": "Licht", + "categories-anime": "Anime", + "categories-label": "Categorieรซn", + "categories-people": "Mensen", + "color-extraction-disabled": "Achtergrondkleurextractie gebruiken", + "color-extraction-enabled": "Voorgedefinieerde kleurenschema's gebruiken", + "header-devices-apply-all-tooltip": "Kies achtergrond per monitor", + "header-devices-per-monitor-tooltip": "Op alle monitoren toepassen", + "header-link-light-dark-tooltip": "Achtergrond behouden bij het wisselen van thema", + "header-separate-light-dark-tooltip": "Achtergrond automatisch wisselen met thema", + "header-sun-linked-tooltip": "Aparte lichte en donkere achtergronden", + "header-sun-separate-tooltip": "Zelfde achtergrond voor licht en donker", + "order-asc": "Oplopend", + "order-desc": "Aflopend", + "order-label": "Volgorde", + "purity-label": "Inhoudsfilter", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "Sketchy", + "ratios-all-portrait": "Alle portret", + "ratios-all-wide": "Alle brede", + "ratios-any": "Alle", + "ratios-label": "Beeldverhouding", + "resolution-atleast": "Ten minste", + "resolution-exact": "Exact", + "resolution-label": "Resolutie", + "resolution-mode-label": "Modus", + "solid-color-tooltip": "Effen kleur achtergrond", + "sort-date-asc": "Sorteren op oudste eerst", + "sort-date-desc": "Sorteren op nieuwste eerst", + "sort-name-asc": "Sorteren op naam (A-Z)", + "sort-name-desc": "Sorteren op naam (Z-A)", + "sort-random": "Sorteer willekeurig", + "sorting-date-added": "Datum toegevoegd", + "sorting-favorites": "Favorieten", + "sorting-label": "Sorteren op", + "sorting-relevance": "Relevantie", + "sorting-toplist": "Toplijst", + "sorting-views": "Weergaven", + "source-label": "Bron", + "source-wallhaven": "Wallhaven", + "title": "Achtergrondkiezer", + "wallhaven-settings-title": "Wallhaven-instellingen" + }, + "transitions": { + "disc": "Schijf", + "fade": "Vervagen", + "honeycomb": "Honingraat", + "pixelate": "Pixeliseren", + "stripes": "Strepen", + "wipe": "Vegen" + }, + "try-different-search": "Probeer een andere zoekopdracht.", + "wallhaven": { + "loading": "Achtergronden laden...", + "no-results": "Geen achtergronden gevonden. Probeer een andere zoekopdracht.", + "page": "{current} van {total}", + "page-prefix": "Pagina", + "page-suffix": "van {total}" + } + }, + "weather": { + "clear-sky": "Onbewolkt", + "drizzle": "Motregen", + "fog": "Mist", + "mainly-clear": "Overwegend helder", + "overcast": "Zwaar bewolkt", + "partly-cloudy": "Gedeeltelijk bewolkt", + "rain-showers": "Regenbuien", + "snow": "Sneeuw", + "thunderstorm": "Onweer" + }, + "widgets": { + "color-picker": { + "palette-description": "Kies uit een breed scala aan vooraf gedefinieerde kleuren.", + "palette-label": "Palet", + "palette-theme-colors": "Snelle toegang tot het kleurenpalet van je thema.", + "title": "Kleurkiezer" + }, + "datetime-tokens": { + "ampm-lowercase": "am/pm in kleine letters", + "ampm-uppercase": "AM/PM in hoofdletters", + "common-12hour-time-minutes": "12-uurs tijdnotatie met minuten", + "common-24hour-time-minutes": "24-uurs tijdnotatie met minuten", + "common-24hour-time-seconds": "24-uurs tijdnotatie met seconden", + "common-european-date": "Europese datumnotatie", + "common-iso-date": "ISO-datumnotatie", + "common-us-date": "Amerikaanse datumnotatie", + "common-weekday-date": "Weekdag met datum", + "common-weekday-month-day": "Weekdag, maand en dag", + "day-abbreviated": "Afgekorte naam van de dag", + "day-full": "Volledige naam van de dag", + "day-leading-zero": "Dag met voorloopnul (01-31)", + "day-no-leading-zero": "Dag zonder voorloopnul (1-31)", + "hour-leading-zero": "Uur met voorloopnul (00-23) โ€” 24-uurs formaat", + "hour-no-leading-zero": "Uur zonder voorloopnul (0-23) โ€” 24-uurs formaat", + "minute-leading-zero": "Minuut met voorloopnul (00-59)", + "minute-no-leading-zero": "Minuut zonder voorloopnul (0-59)", + "month-abbreviated": "Afgekorte maandnaam", + "month-full": "Volledige maandnaam", + "month-number-leading-zero": "Maand als getal met voorloopnul (01-12)", + "month-number-no-zero": "Maand als getal zonder voorloopnul (1-12)", + "second-leading-zero": "Seconde met voorloopnul (00-59)", + "second-no-leading-zero": "Seconde zonder voorloopnul (0-59)", + "timezone-abbreviation": "Afkorting van de tijdzone", + "year-four-digit": "Jaar als viercijferig getal", + "year-two-digit": "Jaar als tweecijferig getal (00-99)" + }, + "file-picker": { + "item": "item", + "items": "items", + "search-placeholder": "Zoek bestanden en mappen...", + "select-current": "Huidige selecteren", + "select-file": "Bestand selecteren", + "select-folder": "Map selecteren", + "selected": "Geselecteerd:", + "title": "Bestandskiezer" + }, + "file-picker-title": "Selecteer een bestand", + "icon-picker": { + "title": "Pictogramkiezer" + }, + "tooltip-placeholder": "Plaatshouder" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "Anonieme Identiteit", + "ca-cert": "CA-certificaat", + "eap-method": "EAP-methode", + "password": "Gebruikerswachtwoord", + "phase2-auth": "Fase 2 Authenticatie", + "username": "Identiteit / Gebruikersnaam" + }, + "panel": { + "action-required": "Actie vereist", + "add-hidden-network": "Verborgen netwerk toevoegen", + "add-network": "Netwerk toevoegen", + "available-interfaces": "Beschikbare interfaces", + "available-networks": "Beschikbare netwerken", + "disabled": "Wi-Fi is uitgeschakeld", + "disconnecting": "Verbreken...", + "dns": "DNS", + "enable-message": "Schakel Wi-Fi in om beschikbare netwerken te zien.", + "enter-password": "Voer wachtwoord in...", + "forget": "Vergeten", + "forget-network": "Dit netwerk vergeten?", + "forgetting": "Vergeten...", + "hidden-network": "Verborgen netwerk", + "interface": "Netwerkinterface", + "internet-connected": "Verbonden met internet", + "internet-limited": "Geen internet", + "internet-status": "Internetstatus", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "Bekende netwerken", + "link-speed": "Link snelheid", + "network-name-ssid": "Netwerknaam (SSID)", + "no-ethernet-devices": "Geen Ethernet-apparaten gedetecteerd", + "no-networks": "Geen netwerken gevonden", + "saved": "Opgeslagen", + "scan-again": "Opnieuw scannen", + "searching": "Zoeken naar netwerken in de buurt...", + "security-open": "Openen", + "security-wep": "WEP", + "security-wpa": "WPA/WPA2/WPA3 Persoonlijk", + "security-wpa-ent": "WPA Enterprise", + "security-wpa2-ent": "WPA2 Enterprise", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 Enterprise", + "show-password": "Wachtwoord tonen", + "title": "Wi-Fi" + }, + "signal": { + "excellent": "Uitstekend", + "fair": "Redelijk", + "good": "Goed", + "poor": "Slecht", + "weak": "Zwak" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/nn-HN.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/nn-HN.json new file mode 100644 index 0000000..de1f34d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/nn-HN.json @@ -0,0 +1,241 @@ +{ + "actions": { + "clear-history": "Tรธm tidefar", + "close-app": "Steng {app}", + "connect-vpn": "Fรฅ samband med {name}", + "cycle-visualizer": "Skift framsyning", + "disable-bluetooth": "Slรฅ av Blรฅtann", + "disable-dnd": "Slรฅ av \"Ikkje skjepla\"", + "disable-wifi": "Slรฅ av trรฅdlaust net", + "disconnect-vpn": "Enda samband med {name}", + "dock-settings": "Festeinnstillingar", + "enable-bluetooth": "Slรฅ pรฅ Blรฅtann", + "enable-dnd": "Slรฅ pรฅ \"Ikkje skjepla\"", + "enable-wifi": "Slรฅ pรฅ trรฅdlaust net", + "launcher-settings": "Innstillingar for nรธytendeveljar", + "lower-to-bottom": "Send til botnar", + "open-calendar": "Opna almanakka", + "open-display-settings": "Syn innstillingar", + "open-launcher": "Opna nรธytendeveljar", + "open-settings": "Opna innstillingar", + "raise-to-top": "Send til toppen", + "random-wallpaper": "Slumpebilรฆte", + "run-custom-command": "Kรธyr sjรธlvgjort styrebod", + "show-active-only": "Syn berre verksam", + "show-all": "Syn alt", + "toggle-mute": "Ljodslรธkkjing pรฅ/av", + "widget-settings": "Nรธytelinnstillingar" + }, + "authentication": { + "error": "Sanningsmistak", + "failed": "Sanningsmistak" + }, + "bar": { + "active-window": { + "colorize-icons-description": "Lita den verksame ruta med hamlitene.", + "hide-mode-description": "Stjorna kor nรธytelen ter seg nรฅr det ikkje er ei verksam ruta.", + "scrolling-mode-description": "Stjorna nรฅr lange rutenomn kann bledjast.", + "show-app-icon-description": "Syn nรธytendefrรฅmerke attmed rutenamn.", + "show-app-icon-label": "Syn nรธytendefrรฅmerke" + }, + "audio-visualizer": { + "color-name-description": "Kjos lรฌten pรฅ framsyningi.", + "color-name-label": "Fyllelรฌt", + "hide-when-idle-description": "Framsyningi lรธynest nรฅr dette er pรฅ, minder noko spelar av.", + "hide-when-idle-label": "Lรธyn nรฅr ingen ting spelar av", + "width-description": "Sjรธlvvald lutbreidd." + }, + "battery": { + "device-default": "Fyrevald syningsambod", + "device-description": "Kjos batteriambod du vil syna fram.", + "device-label": "Batteriambod", + "display-mode-description": "Vel korleรฎs batteriet er synt fram pรฅ radi.", + "display-mode-graphic": "Upplysande batteriteikn", + "display-mode-graphic-clean": "Upplysande batteriteikn (ingen %)", + "display-mode-icon-always": "Frรฅmerke - syn % stรธdt", + "display-mode-icon-hover": "Frรฅmerke - syn ved sviv", + "display-mode-icon-only": "Einast teikn", + "hide-if-idle-description": "Lรธyn nรธytelen nรฅr batteriet ikkje ladar og ikkje gjev straum.", + "hide-if-idle-label": "Lรธyn nรฅr uverksam", + "hide-if-not-detected-description": "Lรธyn nรธytelen nรฅr kjernen ikkje finn eit batteri.", + "hide-if-not-detected-label": "Lรธyn nรฅr ikkje funne", + "low-battery-threshold-label": "ร…tvaringstreskald for lรฅgt batteri", + "show-noctalia-performance-description": "Syn brjotaren for Ytestoda frรฅ Noctalia i batterifjรธli.
Slรฆr av skuggar og bilรฆtrรธrslor i Noctalia for รฅ minka emnebruk.", + "show-noctalia-performance-label": "Syn brjotaren for Ytestoda frรฅ Noctalia", + "show-power-profile-description": "Syn veljaren for straumhรฅtt i batterifjรธli.", + "show-power-profile-label": "Syn straumhรฅttstoli" + }, + "brightness": { + "apply-all-description": "Brigd pรฅ ljosstyrke pรฅ kvar skjerm nรฅr ein nyttar bladhjulet.", + "apply-all-label": "Gjev bladbrigde รฅt kvar skjerm" + }, + "clock": { + "clock-display-description": "Legg til merke frรฅ skrรฅi her for รฅ mรฅta til klokka. 'AP'-merket lyt vera med um klokka berre skal hava 12 timar.", + "clock-display-label": "Klokkesyning", + "custom-font-description": "Kjos skriftkynd til klokka.", + "custom-font-label": "Skriftkynd", + "custom-font-placeholder": "Vel skriftkynd ...", + "custom-font-search-placeholder": "Leita i skriftkynder ...", + "horizontal-bar-description": "Rรฅd: Du kann nytta \\n til รฅ skifta til ny lina.", + "horizontal-bar-label": "Vassbein rad", + "preview": "Vis fyreรฅt", + "tooltip-format-description": "Snidstreng for vitringi som kjem upp nรฅr du sviv yver klokka. Haldt tom for fyrevald vitring.", + "tooltip-format-label": "Vitringssnid", + "use-custom-font-description": "Sjรฅ burt frรฅ skriftkyndi elles og nytta ei onnor skriftkynd til klokka.", + "use-custom-font-label": "Nytta onnor skriftkynd", + "use-monospaced-font-description": "Klokka nyttar fastbreiddeskrift nรฅr dette er pรฅ.", + "use-monospaced-font-label": "Nytta fastbreiddeskrift", + "vertical-bar-description": "Skriv inn eit millomrom for รฅ skifta kvar lut til ei ny lina.", + "vertical-bar-label": "Loddbein rad" + }, + "control-center": { + "browse-file": "Leita etter fil", + "browse-library": "Leita i samning", + "color-selection-description": "Lita nรธytelfrรฅmerke med hamlitene.", + "enable-colorization-description": "Lita merket til styreromet med hamlitene.", + "icon-description": "Kjos frรฅmerke frรฅ samningi eller ei onnor fil.", + "select-custom-icon": "Kjos anna frรฅmerke", + "use-distro-logo-description": "Nytta frรฅmerket til Linuxavbrigdet ditt helder en eit anna teikn.", + "use-distro-logo-label": "Nytta avbrigdesfrรฅmerke" + }, + "custom-button": { + "collapse-condition-description": "Knappen lรธynest um utskrifti er lik dette.", + "collapse-condition-label": "Lรธyningsvilkor", + "color-selection-description": "Lita frรฅmerket og teksti med hamlitene.", + "default-tooltip": "Sjรธlvgjord knapp, stjorna i innstillingane", + "display-command-output-description": "Skriv inn eit styrebod som skal kรธyra jamlege. Syner fyrste utskriftsradi som tekst.", + "display-command-output-label": "Syn utskrift frรฅ styrebod", + "display-command-output-stream-description": "Skriv inn styrebod som skal kรธyra heile tidi.", + "dynamic-text": "Kvikskrift", + "enable-colorization-description": "Lita frรฅmerket og teksti pรฅ den sjรธlvgjorde knappen med hamlitene.", + "enable-colorization-label": "Lita", + "general-tooltip-text-description": "Sjรธlvvald tekst รฅ syna i hjelpeteksti รฅt knappen.", + "general-tooltip-text-label": "Sjรธlvvald hjelpetekst", + "hide-mode-always-expanded": "Stรธdt vidka", + "hide-mode-description": "Stjornar um nรธytelen er til รฅ sjรฅ nรฅr styrebodet ikkje kjem med utskrift.", + "hide-mode-expand-with-output": "Vidka ved utskrift", + "hide-mode-label": "Lรธyndarluna", + "hide-mode-max-transparent": "Vidka heilt men klรฅr", + "icon-description": "Kjos frรฅmerke frรฅ samningi.", + "ipc-identifier-description": "Sermerke for IPC-styrebod. Nytta dette merket med 'qs -c noctalia-shell ipc call cb [gjerning] [merke]' for รฅ styra knappen med IPC.", + "ipc-identifier-label": "IPC-sermerke", + "left-click-description": "Styrebod som kรธyrer ved vinstretrykk.", + "left-click-label": "Vinstretrykk", + "left-click-update-text": "Fรธr upp ny tekst ved vinstretrykk", + "max-text-length-horizontal-description": "ร˜vre grensa pรฅ teikn som kann synast pรฅ vassbein rad (0 for รฅ lรธyna).", + "max-text-length-horizontal-label": "ร˜vre grensa pรฅ tekstlengd (vassbein)", + "max-text-length-vertical-description": "ร˜vre grensa pรฅ teikn som kann synast pรฅ loddbein rad (0 for รฅ lรธyna).", + "max-text-length-vertical-label": "ร˜vre grensa pรฅ tekstlengd (loddbein)", + "middle-click-description": "Styrebod som kรธyrer ved midtrykk.", + "middle-click-label": "Midtrykk", + "middle-click-update-text": "Fรธr upp ny tekst ved midtrykk", + "parse-json-description": "Tyd utfall frรฅ styrebodet som JSON-likam for รฅ stjorna tekst og frรฅmerke kvikt.", + "parse-json-label": "Tyd utfall som JSON", + "refresh-interval-description": "Bil i millisekundar.", + "refresh-interval-label": "Nykveikjingsbil", + "right-click-description": "Styrebod som kรธyrer ved hรธgretrykk.", + "right-click-label": "Hรธgretrykk", + "right-click-update-text": "Fรธr upp ny tekst ved midtrykk", + "show-exec-tooltip-description": "Syn hjelpetekst med styrebodsupplysningar (vinstre/hรธgre/midtrykk, hjul).", + "show-exec-tooltip-label": "Syn hjelpetekst med styrebod", + "show-icon-description": "Skifter um nรธytelfrรฅmerket er synlegt eller ikkje.", + "show-icon-label": "Syn frรฅmerke", + "show-text-tooltip-description": "Syn hjelpetekst med utskrifti or styrebodet.", + "show-text-tooltip-label": "Syn kvikskrift hjelpeskrift", + "text-stream-description": "Strรธymde utfallssegner frรฅ styrebodet syner seg som tekst pรฅ knappen.", + "text-stream-label": "Strรธym", + "wheel-description": "Styrebod som kรธyrer nรฅr ein bled hjulet pรฅ musi.
Nytta $delta i styrebodet for hjulsbrigdi.", + "wheel-down": "Bled nedetter", + "wheel-down-description": "Styrebod som kรธyrer nรฅr hjulet er bladt nedetter.", + "wheel-down-label": "Styrebod - nedetter", + "wheel-label": "Bladhjul", + "wheel-mode-separate-description": "Hav ulike styrebod nรฅr ein bled uppetter og nedetter.", + "wheel-mode-separate-label": "Skilde hjulstyrebod", + "wheel-up": "Bled uppetter", + "wheel-up-description": "Styrebod som kรธyrer nรฅr hjulet er bladt uppetter.", + "wheel-up-label": "Styrebod - uppetter", + "wheel-update-text": "Fรธr upp ny tekst med bledjing" + }, + "keyboard-layout": { + "show-icon-description": "Syn merket for knappefjรธluppsett." + }, + "lock-keys": { + "hide-when-off-description": "Lรธyn sermerket nรฅr knappen ikkje er verksam.", + "hide-when-off-label": "Lรธyn nรฅr av", + "show-caps-lock-description": "Syn storlรฅshรฅtten", + "show-caps-lock-label": "Storlรฅs", + "show-num-lock-description": "Syn tallรฅshรฅtten", + "show-num-lock-label": "Tallรฅs", + "show-scroll-lock-description": "Syn bladlรฅshรฅtten", + "show-scroll-lock-label": "Bladlรฅs" + }, + "media-mini": { + "compact-mode-description": "Fรฅ spelnรธytelsfjรธli til รฅ roma mindre.", + "compact-mode-label": "Klemb spelnรธytelen", + "hide-mode-description": "Stjorna framferdi รฅt nรธytelen nรฅr ingen ting spelar av.", + "max-width-description": "Fastset kor mykje nรธytelen kan lengjast vassbeint. Nรธytelen dreg seg i hop for รฅ rรธma stuttare innhald.", + "no-active-player": "Ingi verksame spelarar", + "panel-section-description": "Stjorna skapnaden og framferdi รฅt spelnรธytelsfjรธli.", + "panel-section-label": "Spelnรธytelsfjรธl", + "scrolling-mode-description": "Stjorna nรฅr lange spelnomn kann bledjast.", + "scrolling-mode-label": "Bladluna", + "show-album-art-description": "Syn platekunsten til spelet som spelar av no.", + "show-album-art-label": "Syn platekunst", + "show-artist-first-description": "Syn spelmann - spel helder en spel - spelmann", + "show-artist-first-label": "Syn spelmann fyrst", + "show-progress-ring-description": "Syn ein ringvorden framgangsvisar.", + "show-progress-ring-label": "Syn framgangsvisar", + "show-visualizer-description": "Syn ljodframsyning nรฅr eitkvart spelar av.", + "show-visualizer-label": "Syn framsyning", + "use-fixed-width-description": "Nรธytelen ruvar so mykje rรฅd er helder en รฅ hรธva seg til innhald, nรฅr dette er pรฅ.", + "use-fixed-width-label": "Nytta fast breidd", + "visualizer-type-description": "Vel eit slag for ljodframsyningi.", + "visualizer-type-label": "Framsyningsslag" + }, + "notification-history": { + "hide-widget-when-zero-description": "Lรธyn varselteikn nรฅr det ikkje er varsel.", + "hide-widget-when-zero-label": "Lรธyn teikn nรฅr inkje varsel", + "hide-widget-when-zero-unread-description": "Lรธyn varselteikn nรฅr det ikkje er ulesne varsel.", + "hide-widget-when-zero-unread-label": "Lรธyn teikn nรฅr inkje ulesne varsel", + "show-unread-badge-description": "Syn eit merke med kor mange ulesne varsel det er.", + "show-unread-badge-label": "Syn \"ulesne\"-merke", + "unread-badge-color-description": "Kjos lit for merket som syner kor mange ulesne varsel det er.", + "unread-badge-color-label": "\"Ulesne\"-merkelit" + }, + "section-editor": { + "placeholder": "Kjos ein nรธytel รฅ leggja til ...", + "search-placeholder": "Leita etter nรธytel ..." + }, + "spacer": { + "width-description": "Rombreidd i molar." + }, + "system-monitor": { + "compact-mode-description": "Syn upplysningar som suleteikningar helder en skrift. Stoggar brigde i uppsettet.", + "compact-mode-label": "Klemb nรธytelen", + "cpu-frequency-description": "Syn midverksnarleik i GHz.", + "cpu-frequency-label": "Syn midverksรธkning", + "cpu-temperature-description": "Syn hitemรฆlingar for midverket um rรฅd.", + "cpu-temperature-label": "Midverkhite", + "cpu-usage-description": "Syn midverkbruk som hundradpart.", + "cpu-usage-label": "Midverkbruk" + } + }, + "common": { + "brightness": "Ljosstyrke", + "select-color-description": "Lita med hamlitene for tyngd." + }, + "options": { + "bar": { + "density-compact": "Klembd" + }, + "launcher-density": { + "compact": "Klembd" + }, + "notification-density": { + "compact": "Klembd" + } + }, + "system": { + "welcome-back": "Velkomen attende," + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/nn-NO.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/nn-NO.json new file mode 100644 index 0000000..6738243 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/nn-NO.json @@ -0,0 +1,2041 @@ +{ + "actions": { + "clear-history": "Tรธm historikk", + "close-app": "Steng {app}", + "connect-vpn": "Kopla til {app}", + "cycle-visualizer": "Skift visualiserar", + "disable-bluetooth": "Slรฅ av Bluetooth", + "disable-dnd": "Slรฅ av Ikkje forstyrr", + "disable-wifi": "Slรฅ av Wi-Fi", + "disconnect-vpn": "Kopla frรฅ {name}", + "dock-settings": "Festepunktinstillingar", + "enable-bluetooth": "Slรฅ pรฅ Bluetooth", + "enable-dnd": "Slรฅ pรฅ Ikkje forstyrr", + "enable-wifi": "Slรฅ pรฅ Wi-Fi", + "launcher-settings": "Innstillingar for programveljar", + "lower-to-bottom": "Flytt til botnen", + "open-calendar": "Opna kalender", + "open-display-settings": "Innstillingar for skjerm", + "open-launcher": "Opna programveljar", + "open-settings": "Opna innstillingar", + "raise-to-top": "Flytt til toppen", + "random-wallpaper": "Tilfelleleg bakgrunnsbilete", + "run-custom-command": "Kรธyr eigendefinert kommando", + "show-active-only": "Vis berre aktive", + "show-all": "Vis alle", + "toggle-mute": "Slรฅ ljod av/pรฅ", + "widget-settings": "Innstillingar for miniprogram" + }, + "authentication": { + "error": "Autentiseringsfeil", + "failed": "Autentisering feila" + }, + "bar": { + "active-window": { + "colorize-icons-description": "Fargar ikonet til det aktive vindauga med temafargane.", + "hide-mode-description": "Styrer kva miniprogrammet gjer nรฅr det ikkje er eit aktivt vindauga.", + "scrolling-mode-description": "Styrer nรฅr rullande tekst slรฅr pรฅ for lange vindaugenamn.", + "show-app-icon-description": "Viser programikonet attmed vindaugenamnet.", + "show-app-icon-label": "Vis programikon" + }, + "audio-visualizer": { + "color-name-description": "Vel farge for visualiseraren.", + "color-name-label": "Fyll farge", + "height-description": "Eigendefinert komponentbreidd", + "hide-when-idle-description": "Nรฅr dette er pรฅ, gรธymer visualiseraren seg med mindre noko aktivt spelar.", + "hide-when-idle-label": "Gรธym nรฅr ingenting spelar av", + "width-description": "Eigendefinert komponentbreidd." + }, + "battery": { + "device-default": "Standard (Skjermeining)", + "device-description": "Vel kva for batterieining รฅ visa.", + "device-label": "Batterieining", + "display-mode-description": "Vel korleis batteriet vert vist pรฅ oppgรฅvelina.", + "display-mode-graphic": "Grafisk batteri", + "display-mode-graphic-clean": "Grafisk batteri (ingen %)", + "display-mode-icon-always": "Ikon - Vis alltid %", + "display-mode-icon-hover": "Ikon - Vis ved sviv", + "display-mode-icon-only": "Einast ikon", + "hide-if-idle-description": "Gรธymer miniprogrammet nรฅr batteriet ikkje ladar opp eller ut.", + "hide-if-idle-label": "Gรธym nรฅr i dvale", + "hide-if-not-detected-description": "Gรธymer miniprogrammet nรฅr inga batterieining er funnen.", + "hide-if-not-detected-label": "Gรธym nรฅr ikkje funnen", + "low-battery-threshold-label": "Varslingsterskel for lรฅgt batterinivรฅ", + "show-noctalia-performance-description": "Viser ytingsmodusbrytaren for Noctalia i batterifanen.
Slรฅr av skuggar og animasjonar i Noctalia for รฅ minka ressursbruk.", + "show-noctalia-performance-label": "Vis Noctalia Yting-brytaren", + "show-power-profile-description": "Viser veljaren for energiprofil i batterifanen.", + "show-power-profile-label": "Vis kontrollane for energiprofil" + }, + "brightness": { + "apply-all-description": "Endra pรฅ ljosstyrken for alle skjermar nรฅr ein brukar rullehjulet. ", + "apply-all-label": "Gjev rulleendringar til alle skjermar" + }, + "clock": { + "clock-display-description": "Mรฅtar klokkevisninga ved รฅ leggja til merke frรฅ lista. For รฅ bruka 12-timeformat mรฅ du ha med 'AP'-merket.", + "clock-display-label": "Klokkevisar", + "custom-font-description": "Vel eigendefinert skrifttype til klokkevisaren.", + "custom-font-label": "Eigendefinert skrifttype", + "custom-font-placeholder": "Vel eigendefinert skrifttype...", + "custom-font-search-placeholder": "Sรธk gjennom skrifttypar...", + "horizontal-bar-description": "Tips: Skriv \\n for รฅ byrja ei ny linje", + "horizontal-bar-label": "Vassrett panel", + "preview": "Fรธrevisning", + "tooltip-format-description": "Formater streng til hjelpeteksten som kjem opp nรฅr peikaren er over klokka. Haldt dette tomt for รฅ bruka standardteksten.", + "tooltip-format-label": "Hjelpetekstformat", + "use-custom-font-description": "Overstyrer standardskrifttypen med eigendefinert skrifttype til klokka.", + "use-custom-font-label": "Bruk eigendefinert skrifttype", + "use-monospaced-font-description": "Nรฅr dette er pรฅ, brukar klokka fastbreiddeskrift.", + "use-monospaced-font-label": "Bruk fastbreiddeskrift", + "vertical-bar-description": "Brukar mellomrom for รฅ skilja kvar del pรฅ ei ny linje.", + "vertical-bar-label": "Loddrett panel" + }, + "control-center": { + "browse-file": "Leita i filene", + "browse-library": "Leita i biblioteket", + "color-selection-description": "Gjev ikona temafargane.", + "enable-colorization-description": "Fargar kontrollsenterikonet med temafargane.", + "icon-description": "Vel ikon frรฅ biblioteket eller eigendefinert fil.", + "select-custom-icon": "Vel eigendefinert ikon", + "use-distro-logo-description": "Brukar kjennemerket til distribusjonen din heller enn eigendefinert ikon.", + "use-distro-logo-label": "Bruk distrokjennemerke heller enn ikon" + }, + "custom-button": { + "collapse-condition-description": "Dersom utskrifta er lik dette, skyler knappen seg.", + "collapse-condition-label": "Skylevilkรฅr", + "color-selection-description": "Gjev ikon og tekst temafargane.", + "default-tooltip": "Eigendefinert knapp, styr i innstillingane", + "display-command-output-description": "Gjev kommando for รฅ kรธyra regelrett. Fyrste utskriftslinja vert vist som tekst.", + "display-command-output-label": "Vis kommando utskrift", + "display-command-output-stream-description": "Gjev kommando for รฅ kรธyra heile tida.", + "dynamic-text": "Dynamisk tekst", + "enable-colorization-description": "Gjev temafargane til ikonet og teksten pรฅ den eigendefinerte knappen.", + "enable-colorization-label": "Slรฅ pรฅ fargelegging", + "general-tooltip-text-description": "Eigendefinert tekst som skal visast i hjelpeteksta for knappen.", + "general-tooltip-text-label": "Eigendefinert hjelpetekst", + "hide-mode-always-expanded": "Alltid utvida", + "hide-mode-description": "Styrer om miniprogrammet skal visast nรฅr kommandoen ikkje har utskrift.", + "hide-mode-expand-with-output": "Utvida med utskrift", + "hide-mode-label": "Skjulemodus", + "hide-mode-max-transparent": "Heilt utvida men gjennomsynleg", + "icon-description": "Vel ikon frรฅ biblioteket.", + "ipc-identifier-description": "Sรฆrskild identifikator for IPC kommandoar. Bruk denne identifikatoren med 'qs -c noctalia-shell ipc call cb [handling] [identifikator]' for รฅ styra denne knappen med IPC.", + "ipc-identifier-label": "IPC-identifikator", + "left-click-description": "Kommando som kรธyrer nรฅr knappen er venstreklikka.", + "left-click-label": "Venstreklikk", + "left-click-update-text": "Oppdater tekst som viser seg med venstreklikk", + "max-text-length-horizontal-description": "ร˜vre grense pรฅ kor mange teikn som kan visast i vassrett panel (0 for รฅ skyla tekst).", + "max-text-length-horizontal-label": "ร˜vre grense pรฅ tekstlengd (vassrett)", + "max-text-length-vertical-description": "ร˜vre grense pรฅ kor mange teikn som kan visast i loddrett panel (0 for รฅ skyla tekst).", + "max-text-length-vertical-label": "ร˜vre grense pรฅ tekstlengd (loddrett)", + "middle-click-description": "Kommando som kรธyrer nรฅr knappen er midtklikka.", + "middle-click-label": "Midtklikk", + "middle-click-update-text": "Oppdater tekst som viser seg med midtklikk", + "parse-json-description": "Tolkar kommandoutskrift som JSON-objekt for รฅ setja tekst og ikon dynamisk.", + "parse-json-label": "Tolk utskrift som JSON", + "refresh-interval-description": "Tidsrom i millisekund", + "refresh-interval-label": "Tidsrom mellom oppdateringar", + "right-click-description": "Kommando som kรธyrer nรฅr knappen er hรธgreklikka.", + "right-click-label": "Hรธgreklikk", + "right-click-update-text": "Oppdater tekst som viser seg med hรธgreklikk", + "show-exec-tooltip-description": "Vis hjelpetekst med kommandodetaljar (venstre/hรธgre/midtklikk, hjul).", + "show-exec-tooltip-label": "Syn kommandohjelpetekst", + "show-icon-description": "Gjer miniprogramsikonet synleg eller ikkje.", + "show-icon-label": "Vis ikon", + "show-text-tooltip-description": "Vis hjelpetekst med utskrifta frรฅ tekstkommandoen.", + "show-text-tooltip-label": "Vis dynamisk hjelpetekst", + "text-stream-description": "Strรธymde linjer frรฅ kommandoen viser seg som tekst pรฅ knappen.", + "text-stream-label": "Strรธym", + "wheel-description": "Kommando som kรธyrer nรฅr ein brukar rullehjulet.
Bruk $delta for rullehjulsdeltaen i kommandoen.", + "wheel-down": "Rull nedetter", + "wheel-down-description": "Kommando som kรธyrer nรฅr ein rullar ned pรฅ rullehjulet.", + "wheel-down-label": "Hjul ned-kommando", + "wheel-label": "Rullehjul", + "wheel-mode-separate-description": "Slรฅr pรฅ รฅtskilde kommandoar for nรฅr ein rullar opp eller ned.", + "wheel-mode-separate-label": "Skilde hjulekommandoar", + "wheel-up": "Rull oppetter", + "wheel-up-description": "Kommando som kรธyrer nรฅr ein rullar opp pรฅ rullehjulet.", + "wheel-up-label": "Hjul opp-kommando", + "wheel-update-text": "Oppdater vist tekst pรฅ rulling" + }, + "keyboard-layout": { + "show-icon-description": "Viser ikonet for tastaturoppsett." + }, + "lock-keys": { + "hide-when-off-description": "Skyler teiknet nรฅr knappen ikkje er aktiv.", + "hide-when-off-label": "Skyl nรฅr avslegen", + "show-caps-lock-description": "Viser status pรฅ Caps Lock.", + "show-caps-lock-label": "Caps Lock", + "show-num-lock-description": "Viser status pรฅ Num Lock.", + "show-num-lock-label": "Num Lock", + "show-scroll-lock-description": "Viser status pรฅ Scroll Lock.", + "show-scroll-lock-label": "Scroll Lock" + }, + "media-mini": { + "compact-mode-description": "Slรฅr pรฅ eit oppsett som sparar rom i mediaavspelarpanelet.", + "compact-mode-label": "Tettmodus", + "hide-mode-description": "Styrer kva miniprogrammet gjer nรฅr ingenting spelar av.", + "max-width-description": "Set ei รธvre grense for kor breid miniprogrammet kan vera. Miniprogrammet minkar for รฅ romma kortare innhald.", + "no-active-player": "Ingen aktiv avspelar", + "panel-section-description": "Styrer kva mediaavspelarpanelet gjer og kor det ser ut.", + "panel-section-label": "Mediaavspelarpanel", + "scrolling-mode-description": "Styrer nรฅr rullande tekst slรฅr pรฅ med lange songtitlar.", + "scrolling-mode-label": "Rullemodus", + "show-album-art-description": "Viser albumkunst for songen som spelar av no.", + "show-album-art-label": "Vis albumkunst", + "show-artist-first-description": "Viser artist - tittel heller enn tittel - artist.", + "show-artist-first-label": "Vis artist fyrst", + "show-progress-ring-description": "Viser rund sporgangsvisar.", + "show-progress-ring-label": "Vis rund sporgangsvisar", + "show-visualizer-description": "Viser ljodvisualiserar nรฅr musikk spelar av.", + "show-visualizer-label": "Vis visualiserar", + "use-fixed-width-description": "Nรฅr dette er pรฅ, brukar miniprogrammet maksimum breidde heile tida, heller enn รฅ mรฅta seg til innhald dynamisk.", + "use-fixed-width-label": "Bruk fast breidde", + "visualizer-type-description": "Vel kva slags ljodvisualiserer du vil sjรฅ.", + "visualizer-type-label": "Visualiserartype" + }, + "notification-history": { + "hide-widget-when-zero-description": "Skyler varslingsikonet nรฅr det ikkje er varsel.", + "hide-widget-when-zero-label": "Skyl ikon nรฅr inga varsel", + "hide-widget-when-zero-unread-description": "Skyler varslingsikonet nรฅr det ikkje er ulesne varsel.", + "hide-widget-when-zero-unread-label": "Skyl ikon nรฅr inga ulesne varsel.", + "show-unread-badge-description": "Viser merke med kor mange ulesne varsel det er.", + "show-unread-badge-label": "Vis \"ulesne\"-merke", + "unread-badge-color-description": "Vel fargen for merket for uleste varslingar.", + "unread-badge-color-label": "Farge pรฅ ulest merke" + }, + "section-editor": { + "placeholder": "Vel miniprogram รฅ leggja til...", + "search-placeholder": "Leita i miniprogram..." + }, + "spacer": { + "width-description": "Mellomrom i piksel." + }, + "system-monitor": { + "compact-mode-description": "Viser statistikk som smรฅe stolpediagram heller enn tekst. Stoggar oppsettet frรฅ รฅ skifta.", + "compact-mode-label": "Tettmodus", + "cpu-cores-description": "Syn prosessorkjernebruk รฅtskilde.", + "cpu-cores-label": "Prosessorkjerner", + "cpu-frequency-description": "Vis prosessorklokkefrekvens no i GHz", + "cpu-frequency-label": "Vis prosessorfrekvens", + "cpu-temperature-description": "Viser temperaturmรฅlingar frรฅ prosessoren om mogeleg.", + "cpu-temperature-label": "Prosessortemperatur", + "cpu-usage-description": "Viser prosessorbruk no i prosenttal.", + "cpu-usage-label": "Prosessorbruk", + "disk-path-description": "Vel kva for monteringspunkt for disk รฅ overvaka.", + "disk-path-label": "Filbane til disk", + "gpu-temperature-description": "Viser temperaturmรฅlingar frรฅ skjermkortet om mogeleg.", + "load-average-description": "Viser snittleg systemlast.", + "load-average-label": "Snittleg last", + "memory-percentage-description": "Viser minnebruk som prosenttal heller enn absolutte verdiar.", + "memory-percentage-label": "Minne som prosent", + "memory-usage-description": "Viser minnebruk no.", + "memory-usage-label": "Minnebruk", + "network-traffic-description": "Viser snรธggleik pรฅ opplasting og nedlasting pรฅ nettverket.", + "network-traffic-label": "Nettverkstraffik", + "storage-as-percentage-description": "Viser diskplass som prosenttal heller enn absolutte tal.", + "storage-as-percentage-label": "Disk som prosenttal", + "storage-available-description": "Viser kor mykje diskplass stรฅr att heller enn kor mykje er brukt.", + "storage-available-label": "Tilgjengeleg diskplass", + "storage-usage-description": "Viser diskplassbruk.", + "storage-usage-label": "Diskplassbruk", + "swap-usage-description": "Viser swap-minnebruk.", + "swap-usage-label": "Swap-minnebruk", + "use-monospace-font-description": "Bruk skrift med fast breidde.", + "use-monospace-font-label": "Fastbreiddeskrift", + "use-padding-description": "Legg til utfylling fรธre teksta for รฅ hindra endring i oppsett.", + "use-padding-description-disabled-monospace-font": "Du mรฅ ha fastsett ei fastbreiddeskrift for รฅ bruka dette.", + "use-padding-description-disabled-vertical": "Loddrett oppgรฅvelina stรธtter ikkje utfylling.", + "use-padding-label": "Utfyll tekst" + }, + "taskbar": { + "colorize-icons-description": "Gjev ikon pรฅ oppgรฅvelina temafargane.", + "hide-mode-description": "Styrer kva miniprogrammet gjer nรฅr det ikkje er samsvarande vindaugo.", + "hide-mode-label": "Gรธymemodus", + "icon-scale-description": "Fastset skaleringsfaktor for ikon pรฅ oppgรฅvelina.", + "icon-scale-label": "Ikonskalering", + "max-width-description": "ร˜vre grensa pรฅ kor breid oppgรฅvelina kan vera i hรธve til skjermen, i prosent.", + "max-width-label": "ร˜vre grense pรฅ breidde", + "only-active-workspaces-description": "Viser berre appar frรฅ aktive arbeidsrom.", + "only-active-workspaces-label": "Berre frรฅ aktive arbeidsrom", + "only-same-monitor-description": "Viser berre appar frรฅ skjermen med panelet pรฅ.", + "only-same-monitor-label": "Berre frรฅ same skjerm", + "show-pinned-apps-description": "Viser feste appar frรฅ festepunktet i oppgรฅvelina.", + "show-pinned-apps-label": "Vis feste appar", + "show-title-description": "Viser vindaugenamn i oppgรฅvelina.", + "show-title-description-disabled": "Loddrett panel kan ikkje visa vindaugenamn.", + "show-title-label": "Vis namn", + "smart-width-description": "Mรฅtar til breidde automatisk med talet pรฅ oppfรธringar.", + "smart-width-label": "Smart breidde", + "title-width-description": "Fastset breidde pรฅ vindaugenamn i oppgรฅvelina (i piksel).", + "title-width-label": "Namnebreidde", + "title-width-reset-tooltip": "Tilbakestill namnebreidde" + }, + "tray": { + "chevron-color-description": "Gjev temafargane til skuffepila.", + "chevron-color-label": "Pilfarge", + "colorize-icons-description": "Gjev ikona i systempanelet temafargane.", + "colorize-icons-label": "Farga ikon", + "drawer-enabled-description": "Nรฅr dette er pรฅ, viser ufeste systempanelobjekt seg i eit skuffepanel.
Nรฅr dette er avslege, viser alle systempanelobjekt seg i rad.", + "drawer-enabled-label": "Aktiver skuff", + "hide-passive-description": "Nรฅr dette er pรฅ, gรธymer systempanelobjekt med 'passiv' status seg.", + "hide-passive-label": "Gรธym passive objekt" + }, + "volume": { + "display-mode-description": "Vel korleis dette skal visast." + }, + "workspace": { + "character-count-description": "Kor mange teikn som skal visast frรฅ arbeidsromnamn (1 - 10).", + "character-count-label": "Teikntal", + "empty-color-description": "Set bakgrunnsfargen for tomme Workspaces.", + "empty-color-label": "Farge for tomt arbeidsomrรฅde", + "enable-scrollwheel-description": "Byter mellom arbeidsrom med rullehjulet pรฅ musa.", + "enable-scrollwheel-label": "Rull for รฅ byta arbeidsrom", + "focused-color-description": "Set bakgrunnsfargen for det fokuserte Workspace.", + "focused-color-label": "Fokusert arbeidsomrรฅde-farge", + "follow-focused-screen-description": "Viser arbeidsrom frรฅ den aktive skjermen, heller enn skjermen der panelet finn seg.", + "follow-focused-screen-label": "Fylg aktiv skjerm", + "font-weight-description": "Fastset synleg tyngd for tekst innan arbeidsrom.", + "font-weight-label": "Skrifttyngd", + "grouped-border-opacity-description": "Fastset kor ugjennomsynlege grensene pรฅ arbeidsromrammene er.", + "grouped-border-opacity-label": "Ugjennomsynlege grenser", + "hide-unoccupied-description": "Viser ikkje arbeidsrom utan vindaugo.", + "hide-unoccupied-label": "Gรธym utan vindaugo", + "label-mode-description": "Vel korleis etikettar for arbeidsrom skal visast.", + "label-mode-label": "Etikettmodus", + "occupied-color-description": "Set bakgrunnsfargen for opptekne Workspaces.", + "occupied-color-label": "Farge for oppteke arbeidsomrรฅde", + "pill-size-description": "Endrar pรฅ kor stor pillene er for kvart arbeidsrom.", + "pill-size-label": "Pillestorleik", + "reverse-scrolling-description": "Reverser retninga for arbeidsomrรฅdebytte nรฅr du skrollar.", + "reverse-scrolling-label": "Omvendt rulling", + "show-applications-description": "Viser applikasjonsikon innanfor kvart arbeidsrom.", + "show-applications-label": "Vis applikasjonar", + "show-badge-description": "Vis arbeidsomrรฅdenummer-merket i gruppert modus.", + "show-badge-label": "Vis arbeidsomrรฅde-merke", + "show-labels-only-when-occupied-description": "Viser etiketter for arbeidsrom berre nรฅr dei har vindaugo.", + "show-labels-only-when-occupied-label": "Vis etikettar berre med vindaugo", + "unfocused-icons-opacity-description": "Fastset kor ugjennomsynlege ufokuserte applikasjonsikon er.", + "unfocused-icons-opacity-label": "Ugjennomsynlege ufokuserte ikon" + } + }, + "battery": { + "all-batteries": "Alle batteria (i sum)", + "battery-health": "Batterihelse", + "battery-level": "Batterinivรฅ", + "capacity-level": "Kapasitet: {level}", + "charging-rate": "Ladesnรธggleik: {rate} W", + "discharging-rate": "Utladingssnรธggleik: {rate} W", + "health": "Helse: {percent}%", + "inhibit-idle-description": "Held systemet vake.", + "no-battery-detected": "Inga batteri funnen", + "plugged-in": "Tilkopla", + "power-profile": "Energiprofil", + "time-left": "Tid att: {tid}", + "time-until-full": "Tid til full: {tid}" + }, + "bluetooth": { + "panel": { + "available-devices": "Tilgjengelege einingar", + "blocked": "Blokkert", + "connected-devices": "Kopla einingar", + "device-address": "Einingadresse", + "disabled": "Bluetooth er frรฅkopla", + "discoverable": "Finnande", + "enable-message": "Slรฅ pรฅ Bluetooth for รฅ sjรฅ tilgjengelege einingar.", + "known-devices": "Kjende einingar", + "no-devices": "Ingen einingar funne", + "paired-devices": "Kopla einingar", + "pairing-mode": "Sjรฅ at eininga di er i koplingsmodus.", + "scanning": "Leitar etter einingar...", + "signal-text-excellent": "Tilkopling: Framifrรฅ", + "signal-text-fair": "Tilkopling: Grei", + "signal-text-good": "Tilkopling: God", + "signal-text-poor": "Tilkopling: Dรฅleg", + "signal-text-unknown": "Tilkopling: Ukjend", + "signal-text-very-poor": "Tilkopling: Svรฆrt dรฅleg" + } + }, + "changelog": { + "error": { + "fetch-failed": "Kan ikkje lasta inn historikk. Prรธv att seinare.", + "rate-limit": "Du har nรฅtt lastgrensa pรฅ GitHub. Prรธv att om nokre minutt." + }, + "panel": { + "buttons-discord": "Vert med pรฅ Discorden vรฅr", + "buttons-dismiss": "Greitt", + "buttons-feedback": "Gjev attermelding", + "empty": "Versjonsnotatar er ikkje tilgjengelege enno.", + "highlight-title": "Hรธgdepunkt", + "released": "Gjeven ut pรฅ {date}", + "subtitle-fresh": "Takk for at du installerte Noctalia! Her er det som er med i denne utgรฅva.", + "subtitle-updated": "Oppdatert frรฅ {previousVersion}", + "title": "Nytt i {version}", + "version": "Versjon {version}", + "version-new-user": "Fersk installasjon" + } + }, + "common": { + "actions": "Handlingar", + "active": "Aktiv", + "add": "Legg til", + "appearance": "Utsjรฅnad", + "apply": "Bruk", + "auto-connect": "Kopla til automatisk", + "automation": "Automatisering", + "available": "Tilgjengelege", + "back": "Attende", + "battery": "Batteri", + "behavior": "ร…tferd", + "bluetooth": "Bluetooth", + "brightness": "Ljosstyrke", + "browse": "Blad", + "calendar": "Kalender", + "calendar-panel": "Kalenderpanel", + "cancel": "Bryt av", + "cards": "Kort", + "charging": "Ladar", + "clear": "Tรธm", + "clipboard": "Utklippstavle", + "close": "Steng", + "color-muted": "Dempa", + "colors": "Fargar", + "command": "Kommando", + "confirm": "Stadfest", + "connect": "Kopla til", + "connected": "Kopla til", + "connecting": "Koplar til...", + "continue": "Hald fram", + "contributors": "Medverkarar", + "copied-to-clipboard": "Kopiert til utklippstavla", + "countdown": "Nedteljing", + "customize": "Mรฅta til", + "date": "Dato", + "default": "Standard", + "delete": "Slett", + "devices": "Einingar", + "disabled": "Avslegen", + "discharging": "Ladar ut", + "disconnect": "Kopla frรฅ", + "disconnected": "Kopla frรฅ", + "disconnecting": "Koplar frรฅ...", + "display-mode": "Visingsmodus", + "documentation": "Dokumentasjon", + "download": "Last ned", + "duration": "Varing", + "dysfunctional": "I uorden", + "edit": "Endra", + "enabled": "Pรฅslegen", + "error": "Feil", + "ethernet": "Ethernet", + "events": "Hende", + "execute": "Utfรธr", + "faithful": "Tru", + "focus": "Fokus", + "font-weight-bold": "Feit", + "font-weight-light": "Tynn", + "font-weight-medium": "Mellomstor", + "font-weight-regular": "Vanleg", + "font-weight-semibold": "Halvfeit", + "frequency": "Frekvens", + "gateway": "Port", + "general": "Generell", + "height": "Hรธgd", + "hibernate": "Set i full dvale", + "history": "Historikk", + "icon": "Ikon", + "idle": "Inaktiv", + "info": "Info", + "input": "Inndata", + "install": "Installer", + "installed": "Installerte", + "interface": "Grensesnitt", + "internet": "Internett", + "keybind": "Beinveg", + "language": "Mรฅl", + "loading": "Lastar inn...", + "local": "Lokal", + "location": "Stad", + "lock": "Lรฅs", + "logout": "Skriv ut", + "look": "Sjรฅ", + "margins": "Margar", + "media": "Media", + "media-player": "Mediaavspelar", + "memory": "Minne", + "monitors": "Skjermar", + "network": "Nettverk", + "next": "Neste", + "night-light": "Natteljos", + "no": "Nei", + "no-results": "Ikkje noko utfall", + "no-summary": "Ikkje noko samandrag", + "none": "Ingen", + "not-found": "Ikkje funne", + "notifications": "Varsel", + "official": "Offisiell", + "on-surface": "Pรฅ overflata", + "output": "Utdata", + "pair": "Par", + "paired": "Para", + "pairing": "Parar...", + "panels": "Panel", + "password": "Passord", + "pause": "Pause", + "performance": "Yting", + "pin": "Fest", + "play": "Spel", + "polling": "Spรธrjing", + "position": "Posisjon", + "previous": "Fรธrre", + "primary": "Primรฆr", + "random": "Tilfelleleg", + "reboot": "Start รฅ nyo", + "reboot-to-uefi": "Start รฅ nyo til UEFI Fastvare", + "record": "Start opptak", + "refresh": "Lasta inn รฅ nyo", + "required": "(krevst)", + "reset": "Attendestill", + "reset-to-default": "Set att til Standard", + "result-count": "{count} utfall", + "result-count-plural": "{count} utfall", + "resume": "Tak opp att", + "save": "Lagra", + "scanning": "Leitar...", + "screen-corners": "Skjermhyrne", + "search": "Sรธk", + "second": "{count} sekund", + "second-plural": "{count} sekund", + "secondary": "Sekundรฆr", + "security": "Trygging", + "select": "Vel", + "select-color": "Vel farge", + "select-color-description": "Gjev temafargar for รฅ heva ut.", + "select-icon-color": "Vel ikonfarge", + "settings": "Innstillingar", + "shortcuts": "Beinvegar", + "shutdown": "Slรฅ av", + "signal": "Dekning", + "sound": "Ljod", + "sources": "Kjelder", + "start": "Start", + "stop": "Stogg", + "supporters": "Stรธttarar", + "suspend": "Set i lett dvale", + "templates": "Malar", + "tertiary": "Tertiรฆr", + "test": "Test", + "thresholds": "Tersklar", + "title": "Tittel", + "toast": "Skjermvarsel", + "trusted": "Lita pรฅ", + "uninstall": "Desinstaller", + "unknown": "Ukjend", + "unpair": "Par frรฅ", + "unpin": "Fest av", + "update": "Oppdater", + "upload": "Lasta opp", + "userspace-reboot": "Start brukarromet รฅ nyo", + "version": "Versjon", + "vibrant": "Livleg", + "visualizer": "Visualiserar", + "volume": "Ljodstyrke", + "volumes": "Ljodstyrke", + "wallpaper": "Bakgrunnsbilete", + "weather": "Vรชr", + "weather-loading": "Lastar inn vรชr", + "week": "Veke", + "widgets": "Miniprogram", + "width": "Breidde", + "wifi": "Wi-Fi", + "windows": "Vindauge", + "yes": "Ja" + }, + "control-center": { + "power-profile": { + "tooltip-action": "Energiprofil", + "tooltip-disabled": "Installer power-profiles-daemon for รฅ bruka energiprofil" + }, + "wifi": { + "label-disconnected": "Kopla frรฅ Wi-Fi", + "label-ethernet": "Ethernet" + } + }, + "display-modes": { + "always-hide": "Alltid gรธym", + "always-show": "Alltid vis", + "force-open": "Tving open", + "on-hover": "Ved sviv" + }, + "hide-modes": { + "auto-hide": "Automatisk gรธyming", + "hidden": "Gรธym nรฅr tom", + "idle": "Gรธym nรฅr inaktiv", + "non-exclusive": "Ikkje uthaldande", + "transparent": "Gjennomsynleg nรฅr tom", + "visible": "Alltid synleg" + }, + "launcher": { + "categories": { + "all": "Alt", + "audiovideo": "Ljod & video", + "chat": "Samtale", + "development": "Utvikling", + "education": "Opplรฆring", + "emoji-activity": "Aktivitetar", + "emoji-animals": "Dyr", + "emoji-flags": "Flagg", + "emoji-food": "Mat og drikke", + "emoji-nature": "Natur", + "emoji-objects": "Ting", + "emoji-people": "Folk & kropp", + "emoji-recent": "Nyleg", + "emoji-symbols": "Symbol", + "emoji-travel": "Reiser & stader", + "game": "Spel", + "graphics": "Grafikk", + "misc": "Ymis", + "office": "Kontor", + "pinned": "Feste", + "system": "System", + "webbrowser": "Nettlesar" + }, + "providers": { + "applications": "Applikasjonar", + "calculator": "Kalkulator", + "calculator-deprecated": "\">calc\"-kommandoen er deprekert og skal snart slettast. Skriv heller matteuttrykk beinveges i sรธk.", + "calculator-description": "Kalkulator โ€” reknar ut matematiske uttrykk", + "calculator-enter-expression": "Skriv inn eit matematisk uttrykk", + "calculator-press-enter-to-copy": "Trykk Enter for รฅ kopiera utfallet", + "clipboard": "Utklippsbok", + "clipboard-clear-description": "Tรธm heile utklippsboka", + "clipboard-clear-description-full": "Slett alt frรฅ utklippsboka", + "clipboard-clear-history": "Tรธm utklippsboka", + "clipboard-delete": "Slett oppfรธring pรฅ utklippstavla", + "clipboard-history-disabled": "Utklippsboka avslegen", + "clipboard-history-disabled-description": "Slรฅ pรฅ utklippsboka i innstillingane eller installer cliphist", + "clipboard-loading": "Lastar inn utklippsbok...", + "clipboard-search-description": "Leita i utklippsboka", + "command-description": "Kรธyr skalkommandoar", + "emoji": "Emojiveljar", + "emoji-loading": "Lastar inn emojiar...", + "emoji-loading-description": "Vent litt", + "emoji-no-recent": "Ingen nylege emojiar enno", + "emoji-search-description": "Leita i og kopiera emojiar", + "settings-search-description": "Sรธk og naviger til innstillingar", + "windows-search-description": "Sรธk og fokuser opne vindauge" + } + }, + "lock-screen": { + "authenticating": "Autentiserer...", + "password": "Skriv passordet ditt...", + "restart": "Start รฅ nyo", + "shut-down": "Slรฅ av" + }, + "notifications": { + "panel": { + "click-to-collapse": "Klikk for รฅ gรธyma", + "click-to-expand": "Klikk for รฅ utvida", + "description": "Varsla dine kjem opp her nรฅr dei kjem inn.", + "no-notifications": "Ingen varsel" + }, + "range": { + "earlier": "Tidlegare", + "today": "I dag", + "yesterday": "I gรฅr" + }, + "time": { + "diff-d": "For 1 dag sidan", + "diff-dd": "For {diff} dagar sidan", + "diff-h": "For 1 time sidan", + "diff-hh": "For {diff} timar sidan", + "diff-m": "For 1 minutt sidan", + "diff-mm": "For {diff} minutt sidan", + "now": "no" + } + }, + "options": { + "bar": { + "density-comfortable": "Hรธveleg", + "density-compact": "Tett", + "density-default": "Standard", + "density-mini": "ร˜rliten", + "density-spacious": "Romsleg", + "display-mode-always-visible": "Alltid synleg", + "type-floating": "Flytande", + "type-framed": "I ramme", + "type-simple": "Endefram" + }, + "control-center": { + "quick-settings-style-classic": "Klassisk", + "quick-settings-style-modern": "Moderne" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "Hรธveleg", + "compact": "Tett", + "default": "Standard" + }, + "launcher-view-mode": { + "grid": "Rutenett", + "list": "Liste" + }, + "notification-density": { + "compact": "Tett", + "default": "Standard" + }, + "scrolling-modes": { + "always": "Alltid rull", + "hover": "Rull ved sviv", + "never": "Aldri rull" + }, + "session-menu-grid-layout": { + "grid": "Rutenett", + "single-row": "Einskild rad" + }, + "settings-panel-mode": { + "attached": "Panel knytt til lina", + "centered": "Panel i midten", + "window": "Utskilt vindauga" + }, + "visualizer-types": { + "linear": "Lineรฆr", + "mirrored": "Spegla", + "wave": "Bylgja" + }, + "visualizer-visibility": { + "with-background": "Einast med bakgrunn" + }, + "workspace-labels": { + "index": "Register", + "index-and-name": "Register og namn", + "name": "Namn" + } + }, + "panels": { + "about": { + "become-supporter": "Vert med og stรธtt", + "changelog": "Sjรฅ endringar", + "changelog-on-startup": "Syn endringshistorikk etter oppdatering", + "changelog-on-startup-desc": "Syn endringshistorikken automatisk nรฅr Noctalia er oppdatert", + "contributors-description": "Takk og รฆre til den {count} framifrรฅ medverkaren vรฅr!", + "contributors-description-plural": "Takk og รฆre til alle dei {count} framifrรฅ medverkarane vรฅre!", + "copy-info": "Kopier info", + "debug-disabled": "Feilsรธking av", + "debug-enabled": "Feilsรธking pรฅ", + "info-copied": "Info kopiert til utklippstavla", + "noctalia-available": "Tilgjengeleg:", + "noctalia-desc": "Eit lettfram og minimalistisk skrivebordsskal skapa med omhug for Wayland, bygt med Quickshell.", + "noctalia-git-commit": "Git commit:", + "noctalia-installed-version": "Installert versjon:", + "noctalia-latest-version": "Nyaste versjon:", + "noctalia-qs-version": "Noctalia QS versjon:", + "noctalia-title": "Noctaliaskal", + "privacy-policy": "Personvernfrรฅsegn", + "support": "Stรธtt oss", + "supporter-badge": "Stรธttar", + "supporters-desc": "Ein stor takk til alle framifrรฅ stรธttarar!", + "supporters-desc-plural": "Ein stor takk til alle {count} framifrรฅ stรธttarar!", + "supporters-loading": "Lastar inn stรธttarar...", + "system-board": "Brett:", + "system-cpu": "Prosessor:", + "system-disk": "Disk:", + "system-gpu": "Skjermkort:", + "system-host": "Vert:", + "system-install-hint": "Installer fastfetch for รฅ sjรฅ systemopplysningar", + "system-kernel": "Kjerne:", + "system-loading": "Lastar inn systemopplysningar...", + "system-memory": "Minne:", + "system-monitor": "Skjerm:", + "system-not-installed": "fasfetch er ikkje installert", + "system-os": "OS:", + "system-packages": "Pakkar:", + "system-product": "Vare:", + "system-title": "Systemopplysningar", + "system-uptime": "Driftstid:", + "system-wm": "WM:", + "telemetry-data-copied": "Telemetri kopiert til utklippstavla", + "telemetry-desc": "Hjelp oss gjera Noctalia betre ved รฅ dela anonyme systemopplysningar (skjermopplรธysing, kompositor, Linux-utgรฅve). Sende ein gong med maskinoppstart, inga sporing, data sletta automatisk etter 30 dagar.", + "telemetry-enabled": "Send anonyme systemopplysningar", + "telemetry-show-data": "Sjรฅ data", + "telemetry-title": "Personvern", + "title": "Om", + "up-to-date": "Du er oppdatert!", + "update-available": "Oppdatering tilgjengeleg", + "view-commit": "Sjรฅ commit pรฅ GitHub" + }, + "audio": { + "devices-desc": "Set opp tilgjengeleg ljodinn- og ljoduteiningar.", + "devices-input-device-description": "Vel ljodinneininga du vil setja opp.", + "devices-input-device-label": "Inneining", + "devices-output-device-description": "Vel ljoduteininga du vil setja opp.", + "devices-output-device-label": "Uteining", + "devices-title": "Ljodeiningar", + "external-mixer-description": "Skriv inn kommandoen eller applikasjonsbana som kรธyrer nรฅr du slรฅr pรฅ funksjonen for den eksterne ljodmiksaren.", + "external-mixer-label": "Kommando for ekstern ljodmiksar", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "Set mediaapplikasjonane du likar best og dei du vil ignorera.", + "media-excluded-player-description": "Skriv inn lykelord for avspelarar som systemet skal ignorera. Kvart lykel ord skal vera pรฅ ei ny line.", + "media-excluded-player-label": "Borttekne avspelar", + "media-excluded-player-placeholder": "skriv substring og klikk pรฅ +", + "media-frame-rate-description": "Hรธgre frekvens er jamnare men krev fleire ressursar.", + "media-frame-rate-label": "Biletfrekvens", + "media-primary-player-description": "Skriv lykelord for รฅ kjenna att avspelaren du brukar mest.", + "media-primary-player-label": "Hovudspelar", + "media-primary-player-placeholder": "t.d. spotify, vlc, mpv", + "media-scrolling-speed-description": "Tida det tek for tittelen รฅ blada frรฅ start til ende, i sekund.", + "media-scrolling-speed-label": "Bladefart", + "media-scrolling-title-description": "Slรฅ pรฅ varig blading for lange mediatitlar.", + "media-scrolling-title-label": "Bladande tittel", + "media-title": "Mediaavspelarar", + "on-middle-clicked-description": "Kommando som kรธyrer nรฅr knappen er midtklikka.", + "panel-applications-empty": "Ingen applikasjonar spelar av ljod no", + "title": "Ljod", + "visualizer-type-description": "Vel visualiseringstypen nรฅr ein spelar av media.", + "visualizer-type-label": "Visualiseringstype", + "volumes-desc": "Endrar kontrollane for ljodstyrke og ljodnivรฅ", + "volumes-feedback-sound-file-description": "Bane til ljoden som spelar av nรฅr du endrar ljodstyrke.", + "volumes-feedback-sound-file-label": "Ljodstyrkeendring ljodfil", + "volumes-feedback-sound-file-select-title": "Vel ljodfila som spelar av med ljodstyrkeendring", + "volumes-input-volume-description": "Mikrofon inn-nivรฅ", + "volumes-mute-input-description": "Demp standard ljodinneining (mikrofon).", + "volumes-mute-input-label": "Demp ljodinndata", + "volumes-mute-output-description": "Demp hovudljoduteininga pรฅ systemet", + "volumes-mute-output-label": "Demp ljodutdata", + "volumes-output-volume-description": "Ljodstyrke pรฅ heile systemet", + "volumes-step-size-description": "Endra kor mykje ljodstyrke endrar seg for kvart steg (rullehjul, beinknappar).", + "volumes-step-size-label": "Steg for ljodstyrke", + "volumes-volume-feedback-description": "Spel av svarljod nรฅr ein endrar pรฅ ljodstyrke.", + "volumes-volume-feedback-label": "Spel av svarljod med ljodstyrke", + "volumes-volume-overdrive-description": "Tillรจt at ljodstyrke kan gรฅ over 100%. Ikkje alltid stรธtta av maskinvare.", + "volumes-volume-overdrive-label": "Tillat overdrift pรฅ ljodstyrke" + }, + "bar": { + "appearance-auto-hide-delay-description": "Tid fรธr Bar-en skjuler seg etter at musa forlรจt den.", + "appearance-auto-hide-delay-label": "Skjulingsforsinking", + "appearance-auto-hide-exclusive-note": "Merk: Eksklusiv sone blir automatisk deaktivert nรฅr auto-skjul er aktivt.", + "appearance-auto-show-delay-description": "Tid fรธr baren viser seg nรฅr musa kjem til kanten.", + "appearance-auto-show-delay-label": "Visingsforsinking", + "appearance-background-opacity-description": "Fastset kor ugjennomsynleg oppgรฅvelina er.", + "appearance-background-opacity-label": "Ugjennomsynleg oppgรฅvelina", + "appearance-capsule-color-description": "Vel farge for ilรฅt pรฅ oppgรฅvelina. Dersom du ikkje vel noko, fรฅr det standardfargen.", + "appearance-capsule-color-label": "Ilรฅtsfarge", + "appearance-capsule-opacity-description": "Set kor ugjennomsynlege bakgrunnen er for miniprogram nรฅr ilรฅtet er pรฅ skjermen.", + "appearance-capsule-opacity-label": "Ugjennomsynleg ilรฅt", + "appearance-content-padding-description": "Mรฅta til utfyllinga mellom panelrender og miniprogram.", + "appearance-content-padding-label": "Innhaldsutfylling", + "appearance-density-description": "Fastset utfylling pรฅ lina for รฅ gjera henne tett eller romsleg.", + "appearance-density-label": "Tettleik pรฅ oppgรฅvelina", + "appearance-desc": "Mรฅtar til kor oppgรฅvelina ser ut og kvar ho er.", + "appearance-display-mode-description": "Vel nรฅr baren er synleg.", + "appearance-enable-exclusion-zone-inset-description": "Minska ekskluderingsona med 1 fysisk piksel slik at vindaugo som er kant-i-kant blรธder saman.", + "appearance-enable-exclusion-zone-inset-label": "Innrykt ekskluderingssone", + "appearance-floating-description": "Viser oppgรฅvelina som flytande 'pille'.", + "appearance-floating-label": "Flytande oppgรฅvelina", + "appearance-font-scale-description": "Mรฅta til skala for skriftstorleik for skrift pรฅ panelen.", + "appearance-font-scale-label": "Skriftskala", + "appearance-frame-radius": "Innradius", + "appearance-frame-settings-description": "Endrar rammetjukn og innradius pรฅ hyrnet", + "appearance-frame-settings-label": "Innstillingar for ramma", + "appearance-frame-thickness": "Tjukn", + "appearance-hide-on-overview-description": "Gรธymer oppgรฅvelina og stengjer panel nรฅr oversyna for samansetjaren er aktiv.", + "appearance-hide-on-overview-label": "Gรธym opggรฅvelina i oversyn", + "appearance-margins-description": "Endrar pรฅ margane kring den flytande oppgรฅvelina.", + "appearance-margins-horizontal": "Vassrett", + "appearance-margins-vertical": "Loddrett", + "appearance-outer-corners-description": "Viser hyrner som bรธygjer ut pรฅ oppgรฅvelina.", + "appearance-outer-corners-label": "Ytterhyrne", + "appearance-position-description": "Vel kvar oppgรฅvelina skal vera pรฅ skjermen.", + "appearance-position-label": "Plassering pรฅ oppgรฅvelina", + "appearance-show-capsule-description": "Vis bakgrunn for miniprogram.", + "appearance-show-capsule-label": "Vis ilรฅt", + "appearance-show-outline-description": "Viser ei synleg grense kring kvart miniprogram.", + "appearance-show-outline-label": "Vis grenser pรฅ miniprogram", + "appearance-type-description": "Vel stil for oppgรฅvelina: Endefram, Flytande eller Innramma.", + "appearance-type-label": "Oppgรฅvelinestil", + "appearance-use-separate-opacity-description": "Slรฅ dette pรฅ for รฅ bruka eit anna nivรฅ for kor ugjennomsynleg bakgrunnen for oppgรฅvelina er.", + "appearance-use-separate-opacity-label": "Bruk anna nivรฅ for ugjennomsynleg oppgรฅvelina", + "monitor-configure-widgets": "Set opp miniprogram", + "monitor-override-settings": "Overstyr globale innstillingar", + "monitor-override-settings-description": "Bruk eigendefinerte innstillingar for denne skjermen.", + "monitor-reset-all": "Nullstill alt", + "monitor-widgets-title": "Miniprogramoppsett - {monitor}", + "monitors-desc": "Viser oppgรฅvelina pรฅ visse skjermar. Standarden er alle, dersom du ikkje vel noko.", + "monitors-desc-new": "Vel kva for skjermar viser oppgรฅvelina, og mรฅta til innstillingar for kvar skjerm.", + "monitors-title": "Vising pรฅ skjermar", + "title": "Oppgรฅvelina", + "tray-blacklist-description": "Legg til unntaksreglar for systempanelet. Stรธttar varateikn (*).", + "tray-blacklist-label": "Svarteliste", + "tray-blacklist-placeholder": "t.d. nm-applet, Fcitx*", + "tray-pin-application": "Fest applikasjon", + "tray-unpin-application": "Lรธys applikasjon", + "use-global-widgets": "Bruk globale miniprogram", + "widgets-desc": "Drag miniprogram for รฅ endra pรฅ rekkjefylgjd. Bruk hรธgreklikksmenyen for รฅ senda miniprogram andre stader eller sletta dei.", + "widgets-title": "Miniprogramplassering" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "Lagar fargeskala frรฅ bakgrunnsbiletet ditt. Vel fargar automatisk for รฅ skapa ein samanhengande skala.", + "color-source-use-wallpaper-colors-label": "Bruk bakgrunnsfargar", + "dark-mode-mode-description": "Slรฅr pรฅ automatisk byte mellom ljos- og mรธrkemodus.", + "dark-mode-mode-label": "Tidsplan for mรธrkemodus", + "dark-mode-mode-manual": "For hand", + "dark-mode-mode-off": "Av", + "dark-mode-switch-description": "Byter til mรธrkare tema for รฅ lettare sjรฅ om natta.", + "delete-error-description": "Mislukka med รฅ sletta {scheme}", + "delete-error-title": "Sletting mislukka", + "delete-success-description": "Lukkast med รฅ sletta {scheme}", + "delete-success-title": "Fargeskala sletta", + "download-button": "Last ned fleire", + "download-downloading": "Lastar ned...", + "download-empty": "Ingen tilgjengelege fargeskalaer", + "download-error-api-error": "API-feil: {status}", + "download-error-description": "Kunne ikkje lasta inn {scheme}", + "download-error-download-failed": "Nedlasting mislukka med feilkode: {code}", + "download-error-invalid-response": "Ugyldig svarformat frรฅ API", + "download-error-no-files": "Ingen filer funne for skala", + "download-error-parse-failed": "Kunne ikkje tolka API-svar: {error}", + "download-error-rate-limit": "Lastgrensa pรฅ GitHub-API nรฅdd", + "download-error-title": "Nedlasting mislukka", + "download-fetching": "Hentar tilgjengelege fargeskalaer...", + "download-success-description": "Lasta inn {scheme} utan feil", + "download-success-title": "Fargeskala lasta ned", + "download-title": "Last ned fargeskalaer", + "method-description": { + "content": "Material Design-skala som tek fargane ut nรธye slik at fargane samstavast med kjeldefargane.", + "dysfunctional": "Som Faithful, men vel den nest vanlegaste fargefamilien som primรฆr.", + "faithful": "Prรธver รฅ halda seg nรฆr til kjeldefargen men samstundes halda paletten harmonisk.", + "fruit-salad": "Material Design-skala som gjev ein livleg og leiksam palett med ymse ulike grunnfargar.", + "monochrome": "Material Design-skala som brukar รฉin grunnfarge i grรฅtt med minimal fargelegging.", + "muted": "Gjev mjukare, umetta fargar som er rolege og avdempa. ", + "rainbow": "Material Design-skala som gjev fargar frรฅ heile spekteret for รฅ auka fargemangfald.", + "tonal-spot": "Standard Material Design-algoritme. Gjev jamne palettar med toneymsing frรฅ grunnfargen.", + "vibrant": "Maksimera metting for djerve, audsynlege palettar med sterke tonar." + }, + "predefined-desc": "Vel frรฅ ei samling fรธrevalde fargeskalaer.", + "predefined-generate-templates-label": "Laga malar for fรธrevalde skalaer", + "predefined-title": "Fรธrevalde fargeskalaer", + "templates-desc": "Gjev fargane til eksterne applikasjonar.", + "templates-filter-description": "Vis malar frรฅ ein viss kategori.", + "templates-filter-label": "Filtrer med kategori", + "templates-misc-description": "Lag dine eigne malar.", + "templates-misc-label": "Avansert", + "templates-misc-user-templates-description": "Slรฅ pรฅ berre om du veit kva du gjer โ€” sjรฅ pรฅ dokumentasjonen pรฅ nettet.", + "templates-misc-user-templates-label": "Slรฅ pรฅ brukarmalar", + "templates-none-detected": "Ingen funne", + "templates-write-path": "Skriv: {filepath}", + "title": "Fargeskala", + "wallpaper-method-description": "Vel mรฅten Noctalia lagar fargeskala pรฅ.", + "wallpaper-method-label": "Fargeskala lagingsmรฅte", + "wallpaper-monitor-source-description": "Vel kva for skjerm Noctalia ser pรฅ for รฅ velja bakgrunnsfargar.", + "wallpaper-monitor-source-label": "Kjelde for fargeval" + }, + "connections": { + "authentication-required": "Krev autentisering", + "bluetooth-devices-unnamed": "Einingar utan namn er ikkje viste.", + "bluetooth-discoverable": "Ein kan finna eininga som {hostName} medan denne innstillingsfana er open.", + "bluetooth-rssi-polling-description": "Prรธv RSSI jamt for รฅ finna kopla einingar med bluetoothctl. Kanskje ikkje tilgjengeleg for alle einingar; brukar minimale ressursar nรฅr pรฅ.", + "bluetooth-rssi-polling-interval-description": "Styr kor ofte dekningsstyrke skal oppdaterast for tilkopla einingar.", + "bluetooth-rssi-polling-interval-label": "Spรธrjingsintervall", + "bluetooth-rssi-polling-label": "Bluetooth-dekningsspรธrjing", + "disable-discoverability-description": "Gรธym eininga di frรฅ nรฆre Bluetooth-eininga.", + "disable-discoverability-label": "Gjer eininga usynleg", + "hide-unnamed-devices-description": "Gรธym einingar som syner seg berre som Bluetooth-adresser.", + "hide-unnamed-devices-label": "Gรธym einingar utan namn", + "pin-instructions": "Skriv inn PIN-koden som eininga gjev deg.", + "title": "Koplingar" + }, + "control-center": { + "cards-desc": "Vel kva for kontrollar skal visast i kontrollsenteret og rekkjefylgda.", + "desc": "Styrer stad og รฅtferd for kontrollsenteret.", + "position-description": "Vel kvar pรฅ skjermen kontrollsenteret opnar seg.", + "shortcuts-custom-button-command-description": "Kommandoen som kรธyrer nรฅr du klikkar pรฅ knappen.", + "shortcuts-custom-button-enable-on-state-logic-description": "Slรฅ pรฅ eit anna ikon og 'varmt' status frรฅ ein sjekkekommando.", + "shortcuts-custom-button-enable-on-state-logic-label": "Slรฅ pรฅ \"pรฅ-stode\"-logikk", + "shortcuts-custom-button-icon-description": "Vel eit ikon frรฅ biblioteket.", + "shortcuts-custom-button-on-clicked-label": "Kommando pรฅ venstreklikk", + "shortcuts-custom-button-on-middle-clicked-description": "Kommando som kรธyrer nรฅr knappen er midtklikka.", + "shortcuts-custom-button-on-state-command-description": "Kommando som kรธyrer for รฅ sjรฅ om knappen er i 'pรฅ'-stoda.", + "shortcuts-custom-button-on-state-command-label": "Pรฅ-stode sjekkekommando", + "shortcuts-custom-button-on-state-icon-description": "Ikonet for knappen nรฅr han er i 'pรฅ'-stoda.", + "shortcuts-custom-button-on-state-icon-label": "Pรฅ-stode ikon", + "shortcuts-custom-button-state-checks-add": "Legg til stodesjekk", + "shortcuts-custom-button-state-checks-command": "Kommando som kรธyrer for denne stodesjekken.", + "shortcuts-custom-button-state-checks-label": "Stodesjekkar", + "shortcuts-custom-button-state-checks-remove": "Slett", + "shortcuts-custom-button-tooltip-description": "Hjelpeteksten som viser seg ved musesviv.", + "shortcuts-custom-button-tooltip-label": "Hjelpetekst", + "shortcuts-title": "Miniprogram for beinvegar", + "system-monitor-disk-path-description": "Vel kva for diskmonteringspunkt systemvaktskortet i kontrollsenteret skal vakta.", + "system-monitor-disk-path-label": "Systemvakt filbane", + "title": "Kontrollsenter" + }, + "desktop-widgets": { + "clock-enabled-description": "Vis ei klokkeminiprogram pรฅ skrivebordet.", + "clock-enabled-label": "Slรฅ pรฅ klokka", + "clock-format-label": "Format", + "clock-height-description": "Hรธgd pรฅ klokkeprogrammet i piksel", + "clock-height-label": "Hรธgd", + "clock-minimal-mode-description": "Bruk endefram klokkevisning med tid og dato.", + "clock-minimal-mode-label": "Endefram-modus", + "clock-rounded-corners-description": "Bruk avrunda hyrne for bakgrunnen til miniprogrammet.", + "clock-rounded-corners-label": "Avrunda hyrne", + "clock-show-background-description": "Vis bakgrunnsilรฅta for klokkeprogrammet.", + "clock-show-background-label": "Vis bakgrunn", + "clock-show-date-description": "Vis datoen i dag under tida.", + "clock-show-date-label": "Vis dato", + "clock-show-seconds-description": "Vis sekund i tida.", + "clock-show-seconds-label": "Vis sekund", + "clock-style-analog": "Analog", + "clock-style-binary": "Binรฆr", + "clock-style-description": "Vel klokkevisning.", + "clock-style-digital": "Digital", + "clock-style-label": "Klokkestil", + "clock-style-minimal": "Endefram", + "clock-width-description": "Breidde pรฅ klokkeprogrammet i piksel", + "edit-mode-button-label": "Endringsmodus", + "edit-mode-controls-explanation": "Venstreklikk & drag: Flytt eller endra storleik. Hรธgreklikk: opna val for samanhengsmenyen.", + "edit-mode-description": "Slรฅ pรฅ endringsmodus for รฅ flytta pรฅ skrivebordsminiprogram. Nรฅr dette er pรฅ, viser miniprogram ei grense og dei kan flyttast pรฅ.", + "edit-mode-exit-button": "Kom ut av endringsmodus", + "edit-mode-grid-snap-label": "Snap til rutenett", + "edit-mode-label": "Endringsmodus", + "enabled-description": "Slรฅ av/pรฅ skrivebordsminiprogram heilt.", + "enabled-label": "Slรฅ pรฅ skrivebordsminiprogram", + "general-desc": "Styr miniprogram som stรฅr pรฅ skrivebordet ditt.", + "general-title": "Skrivebordsminiprogram", + "media-player-enabled-description": "Vis mediaavspelar pรฅ skrivebordet.", + "media-player-enabled-label": "Slรฅ pรฅ mediaminiprogram", + "media-player-rounded-corners-description": "Slรฅ pรฅ avrunda hyrne pรฅ miniprogramsrendene.", + "media-player-show-album-art-description": "Vis albumkunst og spordata (tittel og artist).", + "media-player-show-album-art-label": "Vis albumkunst og tittel", + "media-player-show-background-description": "Vis bakgrunnsilรฅta.", + "media-player-show-buttons-description": "Vis mediakontrollane (spel/pause, fรธrre, neste).", + "media-player-show-buttons-label": "Vis knappar", + "media-player-show-visualizer-description": "Vis overlegg for ljodvisualiseraren.", + "media-player-visualizer-type-description": "Vel visualiseringstype.", + "system-stat-layout-bottom": "Nedst", + "system-stat-layout-description": "Vel korleis legenden viser seg i hรธve til grafen.", + "system-stat-layout-label": "Oppsett", + "system-stat-layout-side": "Side", + "system-stat-rounded-corners-description": "Bruk avrunda hyrne for bakgrunnsbilete til miniprogrammet.", + "system-stat-rounded-corners-label": "Avrunda Hjรธrne", + "system-stat-show-background-description": "Viser bakgrunnsilรฅt til miniprogrammet for systemstatistikk.", + "system-stat-show-background-label": "Vis bakgrunn", + "system-stat-stat-type-description": "Vel kva for systemstatistikk som skal visast.", + "system-stat-stat-type-label": "Statistikktype", + "title": "Skrivebordsminiprogram", + "weather-enabled-description": "Viser miniprogram for vรชr pรฅ skrivebordet.", + "weather-enabled-label": "Slรฅ pรฅ miniprogram for vรชr", + "weather-show-background-description": "Viser bakgrunnsilรฅt for miniprogrammet for vรชr." + }, + "display": { + "monitors-brightness-step-description": "Endra pรฅ kor store endringane i ljosstyrke er med beinvegar og rullehjul.", + "monitors-brightness-step-label": "Ljosstyrkesteg", + "monitors-brightness-unavailable-ddc-disabled": "Kan ikkje endra pรฅ ljosstyrke. Slรฅ pรฅ \"Ekstern ljosstyrkestรธtte\" for รฅ endra pรฅ ljosstyrken pรฅ denne skjermen.", + "monitors-brightness-unavailable-generic": "Kan ikkje endra pรฅ ljosstyrke pรฅ denne skjermen.", + "monitors-desc": "Endra pรฅ ljosstyrke pรฅ kvar skjerm for seg.", + "monitors-enforce-minimum-description": "Pรฅ somme skjermar slรฅr bakljoset seg av med 0% ljosstyrke. Dette lรธyser problemet.", + "monitors-enforce-minimum-label": "Set minimum ljosstyrke til 1%", + "monitors-external-brightness-description": "Slรฅr pรฅ stรธtte for DDCUtil for รฅ styra ljosstyrke pรฅ eksterne skjermar med DDC/CI-protokol.", + "monitors-external-brightness-label": "Ekstern ljosstyrkestรธtte", + "monitors-global-brightness-description": "Endra pรฅ ljosstyrke for alle skjermar samstundes. ", + "monitors-global-brightness-label": "Alle skjermar", + "monitors-title": "Innstillingar for kvar skjerm for seg", + "night-light-auto-schedule-description": "Utifrรฅ solglad og solrenning der du er ({location}) โ€” rรฅd.", + "night-light-auto-schedule-label": "Automatisk timeplan", + "night-light-desc": "Minkar blรฅljos for รฅ hjelpa med svevn og mot trรธytte augo.", + "night-light-enable-description": "Gjev eit varmt fargefilter for รฅ minka blรฅljos", + "night-light-enable-label": "Slรฅ pรฅ natteljos", + "night-light-force-activation-description": "Ser bort ifrรฅ timeplanen og set nattefilteret pรฅ med det same.", + "night-light-force-activation-label": "Tving filter", + "night-light-manual-schedule-description": "Set eigendefinerte tider for solrenning og solglad.", + "night-light-manual-schedule-label": "Timeplan for hand", + "night-light-manual-schedule-select-start": "Vel starttid", + "night-light-manual-schedule-select-stop": "Vel stoggtid", + "night-light-manual-schedule-sunrise": "Solgladetid", + "night-light-manual-schedule-sunset": "Solrenningstid", + "night-light-temperature-day": "Dag", + "night-light-temperature-day-description": "Styrer temperatur om dagen.", + "night-light-temperature-description": "Set fargevarmen om natta og om dagen.", + "night-light-temperature-label": "Fargetemperatur", + "night-light-temperature-night": "Natt", + "night-light-temperature-night-description": "Styrer temperaturen om natta.", + "title": "Vising" + }, + "dock": { + "appearance-background-opacity-description": "Endrar pรฅ kor ugjennomsynleg festepunktbakgrunnen er.", + "appearance-border-radius-description": "Endra pรฅ grenseradiusen pรฅ festepunktet.", + "appearance-border-radius-label": "Grenseradius", + "appearance-colorize-icons-description": "Gjev temafargane til appikon pรฅ festepunktet (berre uaktive appar).", + "appearance-colorize-icons-label": "Farga ikon", + "appearance-dead-opacity-description": "Endrar pรฅ kor ugjennomsynlege appikon er nรฅr appen ikkje kรธyrer.", + "appearance-dead-opacity-label": "Ugjennomsynlege daude appar", + "appearance-desc": "Mรฅta til korleis festepunktet ser ut og ter seg.", + "appearance-display-auto-hide": "Gรธym automatisk", + "appearance-display-description": "Vel korleis festepunktet ter seg", + "appearance-display-exclusive": "Uthaldande", + "appearance-dock-indicator-description": "Syn ein framgangsvisar nรฅr festepunktet er gรธymt.", + "appearance-dock-indicator-label": "Festepunktvisar", + "appearance-floating-distance-description": "Set avstanden mellom festepunktet og skjermranda.", + "appearance-floating-distance-label": "Festepunkt flyteavstand", + "appearance-hide-show-speed-description": "Endrar pรฅ kor fort festepunktet viser og gรธymer seg.", + "appearance-hide-show-speed-label": "Vis/gรธym fart", + "appearance-icon-size-description": "Endra pรฅ kor stor festepunktet er.", + "appearance-icon-size-label": "Storleik", + "appearance-inactive-indicators-description": "Viser ei pille for kvar app, ikkje berre den opne.", + "appearance-inactive-indicators-label": "Kรธyreteikn", + "appearance-indicator-color-description": "Vel fargen pรฅ indikatoren for den skjulte dokken.", + "appearance-indicator-color-label": "Indikatorfarge", + "appearance-indicator-opacity-description": "Juster gjennomsiktigheita for indikatoren for den skjulte dokken.", + "appearance-indicator-opacity-label": "Gjennomsiktigheit for indikator", + "appearance-indicator-thickness-description": "Bruk ein tjukkare indikator for den skjulte dokken (6 px i staden for 3 px).", + "appearance-indicator-thickness-label": "Tjukkare indikator", + "appearance-pinned-static-description": "Alltid sender feste appikon til venstre i statisk rekkjefylgd.", + "appearance-pinned-static-label": "Statiske feste appar", + "appearance-position-description": "Vel kvar festepunktet stรฅr pรฅ skjermen.", + "appearance-position-label": "Stad", + "appearance-sit-on-frame-description": "Set festepunktet i ramma heller enn รฅ ha det pรฅ toppen.", + "appearance-sit-on-frame-label": "Festepunktet sit pรฅ ramma", + "appearance-type-attached": "Statisk", + "appearance-type-description": "Vel mellom flytande pille eller statisk oppgรฅveline fest til randa.", + "appearance-type-floating": "Flytande", + "appearance-type-label": "Festepunktstil", + "enabled-description": "Viser eller gรธymer festepunktet heilt.", + "enabled-label": "Slรฅ pรฅ festepunkt", + "monitors-desc": "Viser festepunktet pรฅ visse skjermar. Dersom du ikkje vel noko, viser det seg pรฅ alle.", + "monitors-only-same-monitor-description": "Viser berre appar pรฅ skjermen som festepunktet stรฅr pรฅ.", + "monitors-only-same-monitor-label": "Berre appar frรฅ same skjermen", + "monitors-title": "Skjermvising", + "title": "Festepunkt" + }, + "general": { + "copy-settings": "Kopier innstillingar", + "fonts-default-description": "Skrifttypen brukt mest gjennom heile grensesnittet", + "fonts-default-label": "Standardskrifttype", + "fonts-default-placeholder": "Vel standardskrifttype...", + "fonts-default-scale-description": "Aukar eller minkar kor stor standardskrifta er.", + "fonts-default-scale-label": "Standard skriftstorleik", + "fonts-default-search-placeholder": "Leit etter skrifttype...", + "fonts-desc": "Vel skrifttypane brukt gjennom heile grensesnittet.", + "fonts-monospace-description": "Fastbreiddeskrift brukt for tal og statistikkvising.", + "fonts-monospace-label": "Fastbreiddeskrift", + "fonts-monospace-placeholder": "Vel fastbreiddeskrift...", + "fonts-monospace-scale-description": "Aukar eller minkar kor stor fastbreiddeskrifta er.", + "fonts-monospace-scale-label": "Fastbreiddeskriftstorleik", + "fonts-monospace-search-placeholder": "Leit etter fastbreiddeskrift...", + "fonts-title": "Skrifttypar", + "keybinds-conflict-description": "Tastkombinasjonen er alt gjeven til {action}.", + "keybinds-conflict-title": "Beinknapp-konflikt", + "keybinds-description": "Styr navigasjonsknappar for panel og programveljaren.", + "keybinds-down": "Flytt nedover", + "keybinds-enter": "Stadfest / Hende", + "keybinds-escape": "Steng / Attende", + "keybinds-left": "Flytt til venstre", + "keybinds-modifier-description": "Beinknappar mรฅ ha styringstast (Ctrl eller Alt).", + "keybinds-modifier-title": "Krev styringstast", + "keybinds-remove": "Fjern", + "keybinds-right": "Flytt til hรธgre", + "keybinds-title": "Navigasjonsknappar", + "keybinds-up": "Flytt oppover", + "language-desc": "Vel sprรฅket du vil sjรฅ i applikasjonen.", + "language-select-auto-detect": "Automatisk", + "language-select-description": "Vel sprรฅket bruk gjennom applikasjonsgrensesnittet.", + "language-select-label": "Applikasjonssprรฅk", + "launch-setup-wizard": "Oppsetthjelpar", + "profile-desc": "Endra pรฅ brukardetaljane dine og brukarbiletet ditt.", + "profile-picture-description": "Brukarbiletet ditt som vert vist gjennom grensesnittet.", + "profile-picture-label": "Brukarbilete", + "profile-select-avatar": "Vel brukarbilete", + "profile-title": "Profil", + "profile-tooltip": "Brukarbilete", + "reverse-scrolling-description": "Snu om den tolka rulleretninga.", + "reverse-scrolling-label": "Omsnudd rulling", + "screen-corners-desc": "Mรฅta til avrunding pรฅ skjermhyrna og synlege effektar.", + "screen-corners-radius-description": "Endra pรฅ dei avrunda hyrna pรฅ skjermen.", + "screen-corners-radius-label": "Skjermhyrneradius", + "screen-corners-radius-reset": "Nullstill skjermhyrneradius", + "screen-corners-show-corners-description": "Viser avrunda hyrne pรฅ skjermranda.", + "screen-corners-show-corners-label": "Vis skjermhyrne", + "screen-corners-solid-black-description": "Bruk heilsvart heller enn bakgrunnsfargen til oppgรฅvelina.", + "screen-corners-solid-black-label": "Heilsvarte hyrne", + "screen-corners-title": "Skjermhyrne", + "settings-copied": "Innstillingar kopierte til utklippstavla", + "tab-basics": "Grunnleggjande", + "tab-keybinds": "Beinknappar" + }, + "hooks": { + "info-command-info-description": "โ€ข Kommandoar kรธyrer i skal (sh -lc)
โ€ข Kommandoar kรธyrer i bakgrunnen (ufeste)
โ€ข Prรธveknappar kรธyrer med noverande verdiar", + "info-parameters-description": "Bakgrunnskrok: $1 = bakgrunnsbane, $2 = skjermnamn
โ€ข Tema brytarkrok: $1 = sann/usann (mรธrkemodus-status)
โ€ข Skjermopplรฅsings-/ulรฅsingskrokar: Ingen parametrar
โ€ข Ytingsmoduskrokar: Ingen parametrar
โ€ข ร˜ktskrok: $1 = handling (Slรฅ av/starta รฅ nyo)", + "info-parameters-label": "Tilgjengelege parametrar", + "noctalia-started-description": "Kommandoen som kรธyrer nรฅr Noctalia er ferdig med รฅ lasta inn.", + "noctalia-started-label": "Noctalia byrja", + "noctalia-started-placeholder": "t.d. notify-send 'Noctalia lasta inn'", + "performance-mode-disabled-description": "Kommandoen som kรธyrer nรฅr Noctalia ytingsmodus er avslege.", + "performance-mode-disabled-label": "Ytingsmodus av", + "performance-mode-disabled-placeholder": "t.d. notify-send \"Yting\" \"Modus avslege\"", + "performance-mode-enabled-description": "Kommandoen som kรธyrer nรฅr Noctalia ytingsmodus er pรฅ.", + "performance-mode-enabled-label": "Ytingsmodus pรฅ", + "performance-mode-enabled-placeholder": "t.d. notify-send \"Yting\" \"Modus pรฅslege\"", + "screen-lock-description": "Kommandoen som kรธyrer nรฅr skjermen er lรฅst.", + "screen-lock-label": "Skjerm lรฅst", + "screen-lock-placeholder": "t.d. notify-send \"Skjerm\" \"Lรฅst\"", + "screen-unlock-description": "Kommandoen som kรธyrer nรฅr lรฅseskjermen vert lรฅst opp.", + "screen-unlock-label": "Skjerm lรฅst opp", + "screen-unlock-placeholder": "t.d. notify-send \"Skjerm\" \"Lรฅst opp\"", + "session-description": "Kommandoen som kรธyrer fรธr maskina er avslegen eller starta รฅ nyo. Fรฅr handlingtype som $1 (slรฅ av/start รฅ nyo).", + "session-label": "ร˜ktslutt", + "session-placeholder": "t.d. notify-send \"ร˜kt\" \"$1\"", + "system-hooks-desc": "Set opp kommandoar som kรธyrer nรฅr systemhende skjer.", + "system-hooks-enable-description": "Slรฅr av eller pรฅ krokkommandoar.", + "system-hooks-enable-label": "Slรฅ pรฅ krokar", + "system-hooks-title": "Systemkrokar", + "theme-changed-description": "Kommandoen som kรธyrer nรฅr temaet byter mellom Mรธrke- og Ljosmodus.", + "theme-changed-label": "Tema endra", + "theme-changed-placeholder": "e.g. notify-send \"Tema\" \"Bytte\"", + "title": "Krokar", + "wallpaper-changed-description": "Kommandoen som kรธyrer nรฅr bakgrunnsbiletet endrar.", + "wallpaper-changed-label": "Bakgrunnsbiletet endra", + "wallpaper-changed-placeholder": "t.d. notify-send \"Bakgrunnsbilete\" \"Endra\"" + }, + "indicator": { + "default-value": "Standard: {value}", + "system-default": "Systemstandard" + }, + "launcher": { + "clipboard-desc": "Sjรฅ og styra utklippsboka di frรฅ programveljaren.", + "execute-desc": "Styrer korleis applikasjonar er kรธyrde", + "execute-title": "Kรธyring", + "settings-annotation-tool-description": "Kommando som kรธyrer nรฅr ein klikkar pรฅ merknadsknappen i utklippsboka. Biletet vert sendt med rรธyr til denne kommandoen.", + "settings-annotation-tool-label": "Merknadsverktรธy", + "settings-annotation-tool-placeholder": "t.d. 'gradia', 'satty -f -'", + "settings-auto-paste-description": "Limar inn det valde utklippet automatisk. Krev wtype.", + "settings-auto-paste-label": "Automatisk liming", + "settings-clip-preview-description": "Viser fรธrebilete av utklippsinnhaldet nรฅr ein nyttar >clip kommandoen.", + "settings-clip-preview-label": "Slรฅ pรฅ klippfรธrebilete", + "settings-clip-wrap-text-description": "Bryt tekst i utklippsboka heller enn รฅ korta han av.", + "settings-clip-wrap-text-label": "Tekstbryting i utklipp", + "settings-clipboard-history-description": "Sjรฅ ting som var kopiert tidlegare i programveljaren.", + "settings-clipboard-history-label": "Slรฅ pรฅ utklippsboka", + "settings-clipboard-watch-image-description": "Heile kommandostreng send til wl-paste for bileteendringar (krev omstart).", + "settings-clipboard-watch-image-label": "Biletvaktskommando", + "settings-clipboard-watch-text-description": "Heile kommandostreng send til wl-paste for tekstendringar (krev omstart).", + "settings-clipboard-watch-text-label": "Tekstvaktskommando", + "settings-custom-launch-prefix-description": "Fรธrefester kommandoar med eigendefinert programveljar (t.d. 'runapp' for systemd-integrering).", + "settings-custom-launch-prefix-enabled-description": "Brukar eigendefinert fรธrefeste for รฅ starta applikasjonar heller enn standardmรฅten.", + "settings-custom-launch-prefix-enabled-label": "Slรฅ pรฅ eigendefinert startfรธrefeste", + "settings-custom-launch-prefix-label": "Eigendefinert startfรธrefeste", + "settings-density-description": "Endra pรฅ kor store applikasjonsikon er, og kor tette dei er i veljaren.", + "settings-density-label": "Tettleik", + "settings-desc": "Vel korleis programveljaren ser ut og kva det gjer.", + "settings-enable-session-search-description": "Vis รธktshandlingar (lรฅs, slรฅ av, start รฅ nyo, osb.) ved sรธk i veljaren.", + "settings-enable-session-search-label": "Slรฅ pรฅ รธktssรธk", + "settings-enable-settings-search-description": "Viser innstillingar nรฅr ein leitar i programveljaren.", + "settings-enable-settings-search-label": "Sรธk i innstillingar", + "settings-enable-windows-search-description": "Sรธk og fokuser aktive vindauge.", + "settings-enable-windows-search-label": "Aktiver vindaugssรธk", + "settings-grid-view-description": "Viser utfall i rutenett heller enn som liste.", + "settings-icon-mode-description": "Brukar heimlege systemikon heller enn Tabler-ikon.", + "settings-icon-mode-label": "Bruk heimlege ikon", + "settings-ignore-mouse-input-description": "Slรฅ av musekontakt og rullehjul i programveljaren.", + "settings-ignore-mouse-input-label": "Ikkje vyrd musekontakt", + "settings-overlay-layer-description": "Syn programveljaren pรฅ overlaget, over fullskjermsvindaugo. Nรฅr dette er pรฅ, koplar ikkje veljaren seg til oppgรฅvelina.", + "settings-overlay-layer-label": "Syn over fullskjerm", + "settings-position-description": "Vel kvar programveljarpanelet opnar seg.", + "settings-show-categories-description": "Viser kategorifanar for รฅ granska applikasjonar", + "settings-show-categories-label": "Vis kategori", + "settings-show-icon-background-description": "Viser ein avrunda firkant som bakgrunn bak ikon.", + "settings-show-icon-background-label": "Vis ikonbakgrunn", + "settings-sort-by-usage-description": "Nรฅr dette er pรฅ, stรฅr dei appane du brukar mest fyrst pรฅ lista.", + "settings-sort-by-usage-label": "Sortera ved bruk", + "settings-terminal-command-description": "Kommando som kรธyrer frรฅ terminal. T.d. 'kitty -e' eller 'gnome-terminal --'.", + "settings-terminal-command-label": "Terminalkommando", + "settings-use-app2unit-description": "Brukar annan oppstartsmรฅte for รฅ betre kunna ordna app-prosessar og for รฅ hindra feil.", + "settings-use-app2unit-label": "Bruk App2Unit for รฅ kรธyra applikasjonar", + "settings-view-mode-description": "Vel oppsettet for veljarobjekt.", + "settings-view-mode-label": "Synsmodus", + "title": "Programveljar" + }, + "location": { + "calendar-cards-desc": "Ordna og slรฅ av/pรฅ kort i kalenderpanelet.", + "calendar-cards-title": "Kalenderkort", + "calendar-header-label": "Kalendertopptekst", + "calendar-month-label": "Kalendermรฅnad", + "date-time-12hour-format-description": "Viser tid i 12-timeformat pรฅ lรฅseskjermen og kalenderen. Klokka pรฅ oppgรฅvelina har eigne innstillingar.", + "date-time-12hour-format-label": "Bruk 12-timeformat", + "date-time-desc": "Mรฅta til korleis dato og tid ser ut.", + "date-time-first-day-of-week-automatic": "Automatisk (bruk systemlokal)", + "date-time-first-day-of-week-description": "Vel kva for dag byrjar veka pรฅ kalenderen.", + "date-time-first-day-of-week-label": "Fyrste vekedag", + "date-time-show-events-description": "Vis arrangement pรฅ kalenderpanelet.", + "date-time-show-events-label": "Vis kalenderarrangement", + "date-time-title": "Dato og tid", + "date-time-use-analog-description": "Vis analogklokke pรฅ kalendervindauga og lรฅseskjermen.", + "date-time-use-analog-label": "Bruk analogklokke", + "date-time-week-numbers-description": "Vis veketal (t.d. veke 38) pรฅ kalenderen.", + "date-time-week-numbers-label": "Vis veketal", + "location-desc": "Fรฅ vรชr og natteljosplanleggjing ved รฅ segja kvar du er.", + "location-search-description": "t.d. Mosjรธen, Nordland", + "location-search-label": "Leit etter stad", + "location-search-placeholder": "Skriv inn stadnamn", + "location-title": "Der du er", + "weather-desc": "Vel temperatureininga du vil sjรฅ.", + "weather-enabled-description": "Vis vรชrdata gjennom grensesnittet og henta vรชrdata. Nรฅr dette er avslege, gรธymer alle vรชrobjekt seg og Noctalia skal ikkje spรธrja etter dei pรฅ nettverket.", + "weather-enabled-label": "Slรฅ pรฅ vรชr", + "weather-fahrenheit-description": "Vis temperatur i Fahrenheit heller enn Celsius.", + "weather-fahrenheit-label": "Vis temperatur i Fahrenheit (ยฐF)", + "weather-hide-city-description": "Gรธym bynamnet pรฅ vรชrvisningar gjennom grensesnittet.", + "weather-hide-city-label": "Gรธym bynamn", + "weather-hide-timezone-description": "Gรธym tidssone pรฅ vรชrvisningar gjennom grensesnittet.", + "weather-hide-timezone-label": "Gรธym tidssone", + "weather-show-effects-description": "Vis fleire synlege effektar (som regn, snรธ, lyn) pรฅ vรชrkortet.", + "weather-show-effects-label": "Vis vรชreffektar", + "weather-show-in-calendar-description": "Vis det daglege vรชrvarselet rett i kalenderen.", + "weather-show-in-calendar-label": "Vis vรชr i kalenderen" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "Nรฅr fprintd (fingeravtrykkautentisering) er aktiv, lar dette alternativet deg framleis logge inn med passordet ditt i staden for eit fingeravtrykk.", + "allow-password-with-fprintd-label": "Tillat passordpรฅlogging med fprintd", + "auto-start-auth-description": "Startar automatisk fingeravtrykksautentisering utan รฅ krevje eit tastetrykk eller eit knappetrykk.", + "auto-start-auth-label": "Autostart autentisering", + "clock-format-description": "Mรฅta til klokkeformatet med dato/tidssyntaksteikn.", + "clock-format-label": "Klokkeformat", + "clock-style-analog": "Analog", + "clock-style-custom": "Eigendefinert", + "clock-style-description": "Vel korleis klokka ser ut pรฅ lรฅseskjermen.", + "clock-style-digital": "Digital", + "clock-style-label": "Klokkestil", + "compact-lockscreen-description": "Viser berre inndata for รฅ logga inn og systemkontrollar, gรธymer miniprogramma for vรชr og media.", + "compact-lockscreen-label": "Endefram lรฅseskjerm", + "lock-on-suspend-description": "Lรฅser skjermen automatisk nรฅr ein suspenderer systemet.", + "lock-on-suspend-label": "Lรฅs pรฅ suspendering", + "lock-screen-animations-description": "Slรฅ pรฅ/av animasjonar pรฅ lรฅseskjermen.", + "lock-screen-animations-label": "Animasjonar pรฅ lรฅseskjermen", + "lock-screen-blur-strength-description": "Gjev ein uklรฅr effekt til bilรฆtet pรฅ lรฅseskjermen.", + "lock-screen-blur-strength-label": "Uklรฅre pรฅ lรฅseskjermen", + "lock-screen-tint-strength-description": "Gjev ein fargesnev i overlag til biletet pรฅ lรฅseskjermen.", + "lock-screen-tint-strength-label": "Fargestyrke pรฅ lรฅseskjermen", + "monitors-desc": "Syn lรฅseskjerm pรฅ visse skjermar. Dersom du ikkje vel noko, syner han seg pรฅ alle.", + "show-hibernate-description": "Viser 'Set i full dvale' i straumkontrollane.", + "show-hibernate-label": "Vis full dvale", + "show-session-buttons-description": "Tillรจt at ein kann bruka straumkontrollane frรฅ lรฅseskjermen.", + "show-session-buttons-label": "Straumkontrollar", + "title": "Lรฅseskjerm" + }, + "network": { + "bluetooth-description": "Slรฅ pรฅ Bluetooth-handsaming.", + "bluetooth-rssi-polling-description": "Prรธvetak RSSI jamt for tilkopla einingar med bluetoothctl. Det kan henda dette ikkje er tilgjengeleg for alle einingar; brukar minimale ressursar nรฅr pรฅslege.", + "bluetooth-rssi-polling-label": "Bluetooth-dekningsspรธrjing", + "desc": "Styr Wi-Fi- og Bluetooth-koplingar", + "wifi-description": "Styr trรฅdlause nettverk (krev NetworkManager)." + }, + "notifications": { + "duration-critical-urgency-description": "Kor lenge kritiske varsel er synlege.", + "duration-critical-urgency-label": "Kritisk", + "duration-desc": "Styr kor lenge varsel er synlege utifrรฅ kor viktige dei er.", + "duration-low-urgency-description": "Kor lenge mindre viktige varsel er synlege.", + "duration-low-urgency-label": "Mindre viktig", + "duration-normal-urgency-description": "Kor lenge vanlege varsel er synlege.", + "duration-normal-urgency-label": "Vanleg", + "duration-respect-expire-description": "Bruk tidsavbrotet fastsett i varselet.", + "duration-respect-expire-label": "Vyrd tidsavbrot", + "duration-title": "Varsellengd", + "history-clear-dismiss-description": "Slett varsel frรฅ historikk nรฅr avvist.", + "history-clear-dismiss-label": "Slett nรฅr avvist", + "history-critical-urgency-description": "Lagrar kritiske varsel i historikken.", + "history-critical-urgency-label": "Lagra kritiske varsel i historikken", + "history-desc": "Styrer kva for varsel vert lagra i historikken utifrรฅ kor viktige dei er.", + "history-low-urgency-description": "Lagrar mindre viktige varsel i historikken.", + "history-low-urgency-label": "Lagra mindre viktige varsel i historikken.", + "history-normal-urgency-description": "Lagrar vanlege varsel i historikken.", + "history-normal-urgency-label": "Lagra vanlege varsel i historikken.", + "monitors-desc": "Viser varsel pรฅ visse skjermar. Dersom du ikkje vel noko, viser varsel seg pรฅ alle.", + "settings-always-on-top-description": "Viser varsel over fullskjermsvindaugo og andre lag.", + "settings-background-opacity-description": "Endrar pรฅ kor ugjennomsynlege varselbakgrunnar er.", + "settings-density-description": "Vel varselkorttettleik", + "settings-density-label": "Tettleik", + "settings-desc": "Styrer korleis varsel ser ut og ter seg.", + "settings-do-not-disturb-description": "Slรฅr av varselpoppopp nรฅr dette er pรฅ.", + "settings-enabled-description": "Slรฅr pรฅ eller av varseldaemonen, krev at du startar Noctaliaskal รฅ nyo.", + "settings-enabled-label": "Slรฅ pรฅ varsel", + "settings-location-description": "Der varsel stรฅr opp pรฅ skjermen.", + "settings-markdown-description": "Gjev att innhaldet i varselet med Markdown-formattering.", + "settings-markdown-label": "Slรฅ pรฅ Markdown", + "sounds-desc": "Styrer varselljod og ljodstyrke.", + "sounds-enabled-description": "Slรฅr pรฅ ljodeffektar for innkomande varsel.", + "sounds-enabled-label": "Slรฅ pรฅ varselljod", + "sounds-excluded-apps-description": "Ikkje spel av varselljoden for visse applikasjonar som har eigne ljodar.", + "sounds-excluded-apps-label": "Borttekne applikasjonar", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "Filbana til ljodfila som spelar av med kritiske varsel.", + "sounds-files-critical-label": "Kritisk varselljod", + "sounds-files-critical-select-title": "Vel fil for kritisk varselljod", + "sounds-files-desc": "Styrer eigendefinerte ljodfiler for varsel med grunnlag i kor viktige dei er.", + "sounds-files-low-description": "Filbana til ljodfila som spelar av for mindre viktige varsel.", + "sounds-files-low-label": "Mindre viktig varselljod", + "sounds-files-low-select-title": "Vel fil for mindre viktig varselljod", + "sounds-files-normal-description": "Filbana til ljodfila som spelar av for vanlege varsel.", + "sounds-files-normal-label": "Vanleg varselljod", + "sounds-files-normal-select-title": "Vel fil for vanleg varselljod", + "sounds-files-placeholder": "Vel filbana til ljodfila", + "sounds-files-select-file": "Vel ljodfil", + "sounds-files-title": "Ljodfiler", + "sounds-files-unified-description": "Filbana til ljoden som spelar av for varsel.", + "sounds-files-unified-label": "Varselljod", + "sounds-files-unified-select-title": "Vel fil for varselljod", + "sounds-separate-description": "Bruk ulike ljodfiler for mindre viktige, vanlege og kritiske varsel.", + "sounds-separate-label": "Bruk ulike ljoder for kvart prioritetsnivรฅ", + "sounds-title": "Ljodinnstillingar", + "sounds-unavailable-description": "Installer Qt6 Multimedia for รฅ slรฅ pรฅ varselljodar.", + "sounds-unavailable-label": "Varselljodar ikkje tilgjengelege", + "sounds-volume-description": "Styre ljodstyrke for varselljodar.", + "sounds-volume-label": "Ljodstyrke", + "toast-battery-description": "Viser รฅtvaring nรฅr batterinivรฅet gรฅr under dette prosenttalet.", + "toast-battery-label": "Batteriรฅtvaring", + "toast-desc": "Styrer korleis varsel ser ut og ter seg.", + "toast-keyboard-description": "Viser skjermvarsel nรฅr knappefjรธloppsett er endra.", + "toast-keyboard-label": "Knappefjรธloppsett", + "toast-media-description": "Viser skjermvarsel nรฅr det er ei endring i mediaavspelingsstatus.", + "toast-media-label": "Media" + }, + "osd": { + "always-on-top-description": "Vis Skjermvising over fullskjermsvindaugo og andre lag.", + "always-on-top-label": "Alltid รธvst", + "background-opacity-description": "Styrer kor gjennomsynleg skjermvisingsbakgrunnen er.", + "background-opacity-label": "Ugjennomsynleg bakgrunn", + "description": "Styrer varsel pรฅ skjermen som overlaga for ljodstyrke og ljosstyrke.", + "duration-auto-hide-description": "Endra pรฅ tida for skjermvisinga kverv.", + "duration-auto-hide-label": "Gรธym etter", + "duration-desc": "Kor lenge skjermvisinga er synleg fรธr ho gรธymer seg.", + "duration-title": "Gรธymetidsavbrot", + "enabled-description": "Vis endringar i ljodstyrke og ljosstyrke i sanntid.", + "enabled-label": "Slรฅ pรฅ skjermvisinga", + "events-desc": "Vel kva for hende som slรฅr skjermvisinga pรฅ.", + "general-desc": "Styrer om skjermvisinga er synleg og korleis ho ter seg.", + "location-description": "Der skjermvisingar stรฅr opp.", + "monitors-desc": "Vis skjermvisingar pรฅ visse skjermar. Dersom du ikkje vel noko, stรฅr dei opp pรฅ alle.", + "title": "Skjermvising", + "types-brightness-description": "Vis skjermvising med endring i ljosstyrke.", + "types-brightness-label": "Ljosstyrke", + "types-custom-text-description": "Vis skjermvising med eigendefinerte tekstmeldingar frรฅ IPC.", + "types-custom-text-label": "Eigendefinert tekst", + "types-desc": "Vel kva for hende som slรฅr skjermvisinga pรฅ.", + "types-input-volume-description": "Vis skjermvising med endring i mikrofonljodstyrke.", + "types-input-volume-label": "Inndata-ljodstyrke", + "types-lockkey-description": "Vis skjermvising med byte pรฅ Caps Lock, Num Lock eller Scroll Lock.", + "types-lockkey-label": "Lรฅseknappar", + "types-media-description": "Vis skjermvising med endring i mediaavspeling (spel, pause, hopp over).", + "types-media-label": "Mediaavspeling", + "types-title": "Skjermvisingshende", + "types-volume-description": "Vis skjermvisinga med endring i utdataljodstyrke.", + "types-volume-label": "Utdata-ljodstyrke" + }, + "plugins": { + "auto-update": "Oppdater tillegg automatisk", + "auto-update-description": "Oppdaterer alle tillegga automatisk nรฅr skalet byrjar.", + "available-description": "Leit etter og installer tillegg frรฅ tilmรฅta kjelder.", + "available-label": "Tilgjengelege tillegg", + "available-no-plugins-description": "Sjekk tilleggkjeldene dine eller lasta inn lista รฅ nyo.", + "available-no-plugins-label": "Ingen tilgjengelege tillegg", + "check-for-updates": "Leit etter oppdateringar", + "checking-for-updates": "Leitar etter oppdateringar...", + "collision-already-installed": "Dette tillegget er alt installert", + "collision-custom-version-exists": "Ein annan versjon frรฅ \"{source}\" er alt installert", + "collision-official-version-exists": "Den ofisielle versjonen av dette tillegget er alt installert", + "development-disable": "Slรฅ av utviklingsmodus for tillegget", + "development-enable": "Slรฅ pรฅ utviklingsmodus for tillegget", + "filter-downloaded": "Nedlasta", + "filter-not-downloaded": "Ikkje nedlasta", + "filter-tags-description": "Filtrer tillegg med kategori eller status pรฅ nedlasting.", + "filter-tags-label": "Taggar", + "hot-reload-description": "Last inn tillegg รฅ nyo automatisk nรฅr filene er endra. Nyttig for tilleggutvikling.", + "hot-reload-label": "Innlasting pรฅ farta (utviklingsmodus)", + "hot-reloaded": "Lasta inn tillegg รฅ nyo: {name}", + "install-error": "Kunne ikkje installera: {error}", + "install-incompatible": "{plugin} krev Noctalia v{version} eller nyare", + "install-success": "Installerte {plugin}", + "installed-description": "Handsama og styr alle lokalinstallerte tillegg.", + "installed-label": "Installerte tillegg", + "installed-no-plugins-description": "Installer tillegg frรฅ \"Tilgjengelege\"-fana.", + "installed-no-plugins-label": "Ingen installerte tillegg", + "installing": "Installerer {plugin}...", + "open-plugin-page": "Opna tilleggsida", + "open-plugins-tab": "Opna tillegginnstillingar", + "plugin-settings-title": "{plugin}-innstillingar", + "refresh-refreshing": "Lastar inn tilleggslista รฅ nyo...", + "refresh-tooltip": "Last inn tilgjengelege tillegg รฅ nyo", + "settings-error-not-loaded": "Tillegg ikkje lasta inn", + "settings-saved": "Tillegginnstillingar lagra", + "settings-tooltip": "Tillegginnstillingar", + "source-custom": "Eigendefinert kjelde", + "sources-add-custom": "Legg til eigendefinert lager", + "sources-add-dialog-description": "Legg til eit GitHub-lager som tilleggkjelde.", + "sources-add-dialog-error": "Kunne ikkje leggja til tilleggkjelde", + "sources-add-dialog-name": "Lagernamn", + "sources-add-dialog-name-placeholder": "Mine eigne tillegg", + "sources-add-dialog-success": "La til tilleggkjelde", + "sources-add-dialog-title": "Legg til tilleggkjelde", + "sources-add-dialog-url": "Lageradresse", + "sources-description": "Handsama tillegglager", + "sources-placeholder": "Det tรธffe lageret mitt", + "sources-remove-tooltip": "Slett tilleggkjelde", + "title": "Tillegg", + "translations-reloaded": "Lasta inn omsetjingar รฅ nyo for {name}", + "uninstall-dialog-description": "Er du viss pรฅ at du vil desinstallera {plugin}? Dette slettar alt av tilleggdata.", + "uninstall-dialog-title": "Desinstaller tillegg", + "uninstall-error": "Kunne ikkje desinstallera: {error}", + "uninstall-success": "Desinstallerte {plugin}", + "uninstalling": "Desinstallerer {plugin}...", + "update-all": "Oppdater alle ({count})", + "update-all-success": "Klarte รฅ oppdatere alle tillegga", + "update-available": "Ny tilleggoppdatering tilgjengeleg", + "update-available-plural": "Tilleggoppdateringar tilgjengelege ({count})", + "update-error": "Kunne ikkje oppdatera tillegg: {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (krev Noctalia v{required})", + "update-success": "Oppdaterte {plugin} til v{version}", + "update-version": "v{current} โ†’ v{new}", + "updating": "Oppdaterer..." + }, + "region": { + "title": "Region" + }, + "session-menu": { + "countdown-duration-description": "Fastset kor lenge nedteljinga varar fรธr straumhandlingar kรธyrer.", + "countdown-duration-label": "Nedteljing", + "enable-countdown-description": "Viser nedteljing fรธr straumhandlingar kรธyrer.", + "enable-countdown-label": "Slรฅ pรฅ nedteljing", + "entries-desc": "Vel kva for straumhandlingar stรฅr i รธktsmenyen og rekkjefylgja dei stรฅr i.", + "entries-title": "Straumhandlingar", + "entry-settings-command-description": "Eigendefinert kommando som kรธyrer med denne handlinga. Haldt tom for รฅ bruka systemstandarden.", + "entry-settings-command-placeholder": "t.d. systemctl poweroff", + "entry-settings-default-command-lock": "Intern lรฅseskjerm (ingen kommando)", + "entry-settings-default-command-logout": "Intern utlogging (ingen kommando)", + "entry-settings-default-info-description": "Dersom du ikkje ha definert ein kommando, kรธyrer systemstandarden.", + "entry-settings-default-info-label": "Standardkommando", + "entry-settings-keybind-description": "Vel knappane som utlรธyser handlinga nรฅr รธktsmenyen er open.", + "entry-settings-keybind-placeholder": "Trykk \"Tak opp\" for รฅ setja opp knapp...", + "entry-settings-keybind-recording": "Tek opp... Trykk knappar", + "entry-settings-title": "Set opp {entry}", + "entry-settings-tooltip": "Set opp kommando", + "general-desc": "Set opp korleis รธktsmenyen ser ut og kva det gjer.", + "large-buttons-layout-description": "Vel korleis knappar viser seg pรฅ รธktsmenyen.", + "large-buttons-layout-label": "Oppsett med store knappar", + "large-buttons-style-description": "Viser รธktsmenyen med store knappar i rutenett.", + "large-buttons-style-label": "Stil med store knappar", + "position-description": "Vel kvar รธktsmenyen opnar seg pรฅ skjermen nรฅr du opnar han.", + "show-header-description": "Viser tittel og stengeknapp รธvst pรฅ รธktsmenyen.", + "show-header-label": "Vis topptekst", + "show-keybinds-description": "Syn knappefjรธltips pรฅ รธktval", + "show-keybinds-label": "Syn beinknappar" + }, + "system-monitor": { + "critical-color-label": "Kritisk farge", + "custom-highlight-colors-title-label": "Eigendefinerte uthevingsfargar", + "disk-available-label": "Disk tilgjengeleg", + "disk-section-label": "Diskbruk", + "enable-dgpu-monitoring-description": "ร…tvaring: Dette vekkjer det diskrete skjermkortet ditt (NVIDIA/AMD), som kan ha stor pรฅverknad pรฅ batterilivet pรฅ berande datamaskiner med hypridgrafikk.", + "enable-dgpu-monitoring-label": "Slรฅ pรฅ vakt for diskret skjermkort", + "external-monitor-description": "Skriv inn kommandoen eller applikasjonbanen som kรธyrer nรฅr du slรฅr pรฅ eksterne systemvaktapplikasjonar", + "external-monitor-label": "Ekstern systemvaktkommando", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "Set opp korleis systemvakta ser ut og ter seg.", + "gpu-section-label": "Skjermkorttemperatur", + "highlight-colors-section-label": "Uthevingsfargar", + "polling-interval-label": "Spรธrjingsintervall", + "polling-section-description": "Set opp kor ofte kvart systemmรฅl vert oppdatert", + "polling-section-label": "Spรธrjingsintervall", + "threshold-critical": "Kritisk terskel", + "threshold-warning": "ร…tvaringsterskel", + "thresholds-section-description": "Endra รฅtvarings-/kritiske tersklar for kvart systemmรฅl.", + "use-custom-highlight-colors-description": "Nรฅr dette er avslege, brukar systemvakta uthevingsfargen frรฅ temaet.", + "use-custom-highlight-colors-label": "Bruk eigendefinerte uthevingsfargar", + "warning-color-label": "ร…tvaringsfarge" + }, + "user-interface": { + "allow-panels-without-bar-description": "Nรฅr dette er pรฅ kan panel opna pรฅ kvar som helst skjerm. Nรฅr dette er av, opnar dei berre pรฅ skjermar med oppgรฅvelina. Dette kan minka minnebruk.", + "allow-panels-without-bar-label": "Tillat panel pรฅ skjermar utan oppgรฅvelina", + "animation-disable-description": "Slรฅr av alle animasjonar for ei kjappar oppleving med betre svartid.", + "animation-disable-label": "Slรฅ av UI-animasjonar", + "animation-speed-description": "Styr global animasjonsfart", + "animation-speed-label": "Animasjonsfart", + "animation-speed-reset": "Nullstill animasjonsfart", + "appearance-desc": "Mรฅta til synlege objekt som hjelpetekst, grenser og skuggar.", + "box-border-description": "Vis ei grense kring omrรฅde med innhald.", + "box-border-label": "Ilรฅtgrense", + "box-border-radius-description": "Styrer kor avrunda hyrna er pรฅ viktige oppsettsomrรฅde, til dรธmes pรฅ stolpar, kort og innhaldspanel.", + "box-border-radius-label": "Ilรฅtradius", + "control-border-radius-description": "Styrer kor bรธygde interaktiv delar er, til dรธmes knappar, brytarar og tekstfelt.", + "control-border-radius-label": "Inndataradius", + "desc": "Mรฅta til korleis grensesnittet ser ut og ter seg.", + "dim-desktop-description": "Mรธrknar skrivebordet nรฅr panel eller menyar er opne.", + "dim-desktop-label": "Mรธrkna skrivebord", + "dimmer-opacity-description": "Set kor mรธrkt det vert nรฅr skrivebordet mรธrknar.", + "dimmer-opacity-label": "Skrivebordsmรธrker", + "panel-background-opacity-description": "Set kor ugjennomsynleg bakgrunnen er for alle panel (oppgรฅvelina, programveljar, innstillingar osb.).", + "panel-background-opacity-label": "Ugjennomsynlege panelbakgrunnar", + "panels-attached-to-bar-description": "Panel lรฅser seg til oppgรฅvelina og skjermrendene, som gjev ein stilig utsjรฅnad med innbรธygde hyrne.", + "panels-attached-to-bar-label": "Fest panel til render", + "panels-overlay-description": "Tryggjer at panel og oppgรฅvelina held seg synlege, jamvel over fullskjermsappar.", + "panels-overlay-label": "Haldt panel og oppgรฅvelina รธvst", + "scaling-description": "Endrar kor stor brukargrensesnittet er, sett bort ifrรฅ oppgรฅvelina.", + "scaling-label": "Grensesnittskalering", + "settings-panel-mode-description": "Vel oppsett for innstillingar (kan krevja at du mรฅ opna dei pรฅ nytt).", + "settings-panel-mode-label": "Oppsett for innstillingar", + "shadows-description": "Slรฅr pรฅ skuggar under liner og panel.", + "shadows-direction-description": "Vel kvar skuggen kjem frรฅ.", + "shadows-direction-label": "Skuggeretning", + "shadows-label": "Skuggar", + "title": "Brukargrensesnitt", + "tooltips-description": "Slรฅ pรฅ eller av hjelpetekst gjennom brukargrensesnittet", + "tooltips-label": "Vis hjelpetekst" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "Alfabetisk", + "automation-change-mode-description": "Vel korleis Noctalia vel bakgrunnsbilete nรฅr dei endrar automatisk.", + "automation-change-mode-label": "Endringsmodus", + "automation-custom-interval-description": "Skriv inn tid som TT:MM (t.d. 01:30).", + "automation-custom-interval-label": "Eigendefinert tidsrom", + "automation-interval-description": "Kor ofte bakgrunnsbiletet endrar seg automatisk.", + "automation-interval-label": "Endringstidsrom", + "automation-random-wallpaper-description": "Planlegg tilfellelege endringar pรฅ bakgrunnsbilete jamleg.", + "automation-scheduled-change-description": "Endrar bakgrunnsbilete automatisk og jamleg.", + "automation-scheduled-change-label": "Planlagd endring", + "look-feel-edge-smoothness-description": "Nรฅr biletet endrar seg fรฅr rendene ein mjuk, fjรธrvoren effekt.", + "look-feel-edge-smoothness-label": "Mykna endringsrand", + "look-feel-fill-color-description": "Vel fyllfarge som kan visast bak bakgrunnsbiletet.", + "look-feel-fill-mode-description": "Vel korleis biletet skal skalerast for รฅ hรธva skjermopplรธysinga di.", + "look-feel-fill-mode-label": "Fyllmodus", + "look-feel-skip-startup-transition-description": "Hopp over bakgrunnsanimasjonen nรฅr skalet byrjar.", + "look-feel-skip-startup-transition-label": "Hopp over startanimasjon", + "look-feel-title": "Utsjรฅnad", + "look-feel-transition-duration-description": "Kor lenge endringsanimasjonane tek i sekund.", + "look-feel-transition-duration-label": "Endringstid", + "look-feel-transition-type-description": "Animasjonstype nรฅr bakgrunnsbiletet endrar seg.", + "look-feel-transition-type-label": "Endringstype", + "settings-desc": "Styr korleis bakgrunnsbiletet vert handsama og viste.", + "settings-enable-management-description": "Styr bakgrunnsbilete med Noctalia. Slรฅ av dersom du vil heller bruka eit anna program.", + "settings-enable-management-label": "Slรฅ pรฅ bakgrunnshandsaming", + "settings-enable-overview-description": "Gjev oversynsskjermen eit uskarpt og dempa bakgrunnsbilete.", + "settings-enable-overview-label": "Slรฅ pรฅ oversynsbilete", + "settings-folder-description": "Filbane til mappa der du har bakgrunnsbileta dine.", + "settings-folder-label": "Bakgrunnsbiletemappe", + "settings-hide-wallpaper-filenames-tooltip-hide": "Gรธym filnamn", + "settings-hide-wallpaper-filenames-tooltip-show": "Vis filnamn", + "settings-monitor-specific-description": "Fastset ulike biletemapper for kvar skjerm.", + "settings-monitor-specific-label": "Skjermvise mapper", + "settings-monitor-specific-tooltip": "Vakta bakgrunnsbiletemappa", + "settings-overview-blur-strength-description": "Gjev uklรฅre til overlaget.", + "settings-overview-blur-strength-label": "Overlagsuklรฅre", + "settings-overview-tint-description": "Gjev fargesnev til oversyna.", + "settings-overview-tint-label": "Oversynsfargestyrke", + "settings-recursive-search-description": "Leit etter bakgrunnsbilete i underordna mapper i biletemappa รฒg.", + "settings-recursive-search-label": "Leit i underordna mapper", + "settings-select-monitor-folder": "Vel bakgrunnsbiletemappe for skjerm", + "settings-selector-description": "Vel bakgrunnsbilete.", + "settings-selector-position-description": "Vel kvar bileteveljarpanelet skal visast.", + "settings-show-hidden-files-tooltip-hide": "Gรธym gรธymde filer", + "settings-show-hidden-files-tooltip-show": "Vis gรธymde filer", + "settings-title": "Innstillingar for bakgrunnsbilete", + "settings-view-mode-description": "Vel korleis bakgrunnsbilete skal visast frรฅ mappa di.", + "settings-view-mode-label": "Visningsmodus", + "view-mode-browse": "Sรธk i mapper", + "view-mode-cycle-tooltip": "Visningsmodus: {mode} (klikk for รฅ endra)", + "view-mode-recursive": "Flate undermapper", + "view-mode-single": "Rotmappe" + } + }, + "placeholders": { + "add-new-keybind": "Legg til ny beinknapp", + "command-example": "echo \"Hei, verd\"", + "enter-command": "Skriv inn kommando som skal kรธyra (app eller eigendefinert skript)", + "enter-ipc-identifier": "Skriv inn unik identifikator for IPC kommandoar", + "enter-path": "Skriv inn filbane...", + "enter-text-to-collapse": "t.d. 'ingenting spelar av'. Bruk /regex/ til mรธnster.", + "enter-tooltip": "Skriv inn hjelpetekst", + "enter-width-pixels": "Skriv inn breidde i piksel", + "keybind-recording": "Tek opp beinknapp...", + "search": "Sรธk...", + "search-icons": "t.d. noctalia, niri, battery, cloud", + "search-launcher": "Sรธk i treff... eller bruk > for kommandoar", + "search-wallhaven": "Leit pรฅ Wallhaven...", + "search-wallpapers": "Skriv for รฅ filtrera bakgrunnsbilete..." + }, + "positions": { + "bottom": "Nedst", + "bottom-center": "Nede i midten", + "bottom-left": "Nede til venstre", + "bottom-right": "Nede til hรธgre", + "center": "I midten", + "center-left": "I midten til venstre", + "center-right": "I midten til hรธgre", + "close-to-bar": "Attmed lineknappen", + "follow-bar": "Fylg oppgรฅvelina", + "left": "Venstre", + "right": "Hรธgre", + "top": "ร˜vst", + "top-center": "ร˜vst i midten", + "top-left": "ร˜vst til venstre", + "top-right": "ร˜vst til hรธgre" + }, + "session-menu": { + "action-in-seconds": "{action} i {seconds} sekund...", + "cancel-timer": "Avbryt nedteljing", + "title": "ร˜ktsmeny" + }, + "setup": { + "all-done": "Ferdig!", + "appearance": { + "subheader": "Vel mรธrkemodus og fargekjelder (bakgrunnsbilete eller eigendefinert)." + }, + "customize": { + "header": "Skreddersy opplevinga di", + "subheader": "Endra kvar oppgรฅvelina er, tettleik, skalering og meir." + }, + "skip-setup": "Hopp over oppsett", + "telemetry-wizard-done": "Skjรธnar!", + "telemetry-wizard-note": "Du har kontroll โ€” slรฅ dette av eller pรฅ nรฅr som helst i innstillingar", + "telemetry-wizard-subtitle": "Vi har lagt til anonyme analytikk for รฅ hjelpa gjera Noctalia betre.", + "telemetry-wizard-title": "Oppdatering om personvern", + "wallpaper": { + "choose-dir": "Vel ei mappe som har bakgrunnsbileta dine i seg", + "dir-browse": "Sรธk etter bakgrunnsmappe", + "dir-description": "Vel mappa som har bakgrunnsbileta dine i seg.", + "dir-label": "Bakgrunnsmappe", + "dir-select-title": "Vel bakgrunnsmappe", + "header": "Vel bakgrunnsbiletet ditt", + "no-dir": "Inga bakgrunnsmappe vald", + "no-valid": "Fann ikkje gilde biletefiler i: {dir}", + "none-in-dir": "Fann ikkje bakgrunnsbilete i mappa", + "preview-error": "Kunne ikkje lasta inn biletet", + "select-prompt": "Vel bakgrunnsbilete under", + "subheader": "Mรฅta til stemninga med eit fagert bakgrunnsbilete." + }, + "welcome-note": "Berre nokre lette ting for รฅ fรฅ deg opp og gรฅande โ€” alle vala finn du i innstillingane.", + "welcome-subtitle": "Lat oss gjera skrivebordet ditt", + "welcome-title": "Velkomen รฅt Noctalia!" + }, + "system": { + "cpu-temperature": "{temp} ยฐC", + "disk-usage": "{percent} %", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "Ingen mediaavspelar funnen", + "scaling-percentage": "{percentage} %", + "signal-strength": "{signal} %", + "unknown-app": "Ukjend app", + "uptime": "Oppetid: {uptime}", + "user-requested": "Brukaretterspurd", + "welcome-back": "Velkomen attende,", + "widget-settings-title": "{widget}innstillingar" + }, + "system-monitor": { + "cpu-temp": "Prosessortemperatur", + "cpu-usage": "Prosessorbruk", + "disk": "Disk", + "download-speed": "Nedlastingsfart", + "gpu-temp": "Skjermkorttemperatur", + "load-average": "Snittleg last", + "title": "Systemvakt", + "upload-speed": "Opplastingsfart" + }, + "toast": { + "airplane-mode": { + "title": "Flymodus" + }, + "battery": { + "critical": "Kritisk batterinivรฅ", + "critical-desc": "Batterinivรฅet er pรฅ {percent} % - kopla til ladaren no!", + "low": "Lรฅgt batteri", + "low-desc": "Batterinivรฅet er {percent} %. Du bรธr kopla til ladaren." + }, + "bluetooth": { + "address-copied": "Adresse kopiert til utklippstavla", + "confirm-code": "Stadfest koden {value} pรฅ hi eininga.", + "connect-failed": "Mislukka tilkopling", + "disconnect-failed": "Mislukka frรฅkopling", + "discoverable-change-failed": "Kunne ikkje endra oppdagingstilstand", + "discoverable-disabled": "Ikkje finnande", + "discoverable-enabled": "Finnande", + "display-code": "Skriv inn koden {value} pรฅ hi eininga.", + "forget-failed": "Kunne ikkje glรธyma eininga", + "pair-failed": "Kunne ikkje kopla til eininga", + "passkey-required": "Eininga krev passord.", + "pincode-required": "Eininga krev PIN-kode.", + "state-change-failed": "Kunne ikkje endra Bluetoothstatus" + }, + "clipboard": { + "long-text": "Lang tekst", + "unavailable": "Utklippsboka ikkje tilgjengeleg", + "unavailable-desc": "'cliphist' er ikkje installert. Du mรฅ installera han for รฅ kunne bruka utklippsboka." + }, + "do-not-disturb": { + "disabled": "\"Ikkje forstyrr\" av", + "disabled-desc": "Viser alle varsel", + "enabled": "\"Ikkje forstyrr\" pรฅ", + "enabled-desc": "Du finn desse varsla i historikken din" + }, + "donation-opened": "Donasjonsside opna i nettlesaren din", + "internet-limited": "Kopla til utan internett", + "keyboard-layout": { + "changed": "Oppsett endra til {layout}", + "title": "Knappefjรธl" + }, + "missing-control-center": { + "description": "Miniprogrammet for kontrollsenteret er ikkje lenger pรฅ oppgรฅvelina. For รฅ fรฅ tilgang frรฅ oppgรฅvelina, mรฅ du leggja det til att. Du kan opna det ved รฅ hรธgreklikka pรฅ oppgรฅvelina รฒg.", + "label": "Siste miniprogram for kontrollsenter sletta" + }, + "night-light": { + "forced": "Tvinga aktivering", + "normal": "Vanleg modus", + "not-installed": "wlsunset ikkje installert" + }, + "noctalia-performance": { + "disabled": "Ytingsmodus av", + "enabled": "Ytingsmodus pรฅ", + "label": "Noctalia Yting" + }, + "power-profile": { + "changed": "Straumprofil endra", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "Noctalia stรธytte pรฅ ein feil medan det handsama malar", + "title": "Malhandsaming mislukka" + }, + "vpn": { + "connected": "Kopla til '{name}'", + "disconnected": "Kopla frรฅ '{name}'" + }, + "wallpaper-colors": { + "disabled": "Bakgrunnsfargar av", + "enabled": "Bakgrunnsfargar pรฅ", + "label": "Bakgrunnsfargar" + }, + "wifi": { + "connected": "Kopla til '{ssid}'", + "connection-failed": "Kopling mislukka", + "connection-timeout": "Tidsavbrot med kopling", + "disconnected": "Kopla frรฅ '{ssid}'", + "incorrect-password": "Gale passord", + "network-not-found": "Nettverk ikkje funne" + } + }, + "tooltips": { + "add-widget": "Legg til miniprogram", + "bluetooth-devices": "Bluetooth-einingar", + "brightness-at": "Ljosstyrke: {brightness} %", + "click-to-start-recording": "Skjermopptak (start opptak)", + "click-to-stop-recording": "Skjermopptak (stogg opptak)", + "collapse": "Gรธym sidestolpen", + "copy-address": "Kopier adresse", + "delete-notification": "Slett varsel", + "dismiss-notification": "Avvis varsel", + "do-not-disturb-enabled": "Ikkje forstyrr", + "expand": "Brett ut sidestolpen", + "forget-network": "Glรธym nettverk", + "grid-view": "Rutenett", + "hidden-files-hide": "Gรธymde filer", + "hide-unnamed-devices": "Gรธym einingar utan namn", + "home": "Heimemappe", + "input-muted": "Slรฅ av/pรฅ ljod i inngang", + "keep-awake": "Haldt vaken", + "keyboard-layout": "{layout} knappefjรธloppsett", + "list-view": "Liste", + "manage-vpn": "VPN-tilkoplingar", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "ร˜vre grense pรฅ miniprogramm nรฅdd", + "microphone-volume-at": "Ljodstyrke pรฅ mikrofon: {volume} %", + "move-to-section": "Flytt til {section}", + "mute": "Slรฅ av ljod", + "next-media": "Neste song", + "next-month": "Neste mรฅnad", + "night-light-not-installed": "Natteljos (ikkje tilgjengeleg)", + "noctalia-performance-enabled": "Noctalia ytingsmodus", + "open-annotation-tool": "Opna med merknadsverktรธyet", + "open-control-center": "Kontrollsenter", + "open-notification-history-enable-dnd": "Varselhistorikk", + "open-settings": "Innstillingar", + "open-tray-dropdown": "Systemkorg", + "output-muted": "Slรฅ av/pรฅ ljod i utgang", + "power-profile": "{profile} straumprofil", + "previous-media": "Fรธrre song", + "previous-month": "Fรธrre mรฅnad", + "refresh-devices": "Last inn einingar รฅ nyo", + "refresh-wallhaven": "Last inn Wallhaven-utfall รฅ nyo", + "refresh-wallpaper-list": "Last inn bakgrunnslista รฅ nyo", + "remove-widget": "Slett miniprogram", + "search-close": "Steng sรธk", + "session-menu": "ร˜ktsmeny", + "show-all-devices": "Vis alle einingar", + "switch-to-dark-mode": "Mรธrkemodus", + "switch-to-light-mode": "Ljosmodus", + "unmute": "Slรฅ pรฅ ljod", + "up": "Overordna mappe", + "volume-at": "Ljod i utgang: {volume} %", + "wallpaper-selector": "Bakgrunnsveljar" + }, + "wallpaper": { + "browse": { + "empty-directory": "Denne mappa er tom.", + "go-root": "Gรฅ til bakgrunnsbileterota", + "go-up": "Gรฅ til overordna mappe", + "go-up-hint": "Bruk \"attende\"-knappen for รฅ gรฅ opp eitt nivรฅ." + }, + "configure-directory": "Styr bakgrunnsbiletemappa med bilete.", + "fill-modes": { + "crop": "Skjer av (Fyll)", + "fit": "Mรฅta til (Haldt)", + "repeat": "Tak opp att (Flislegg)", + "stretch": "Tรธyg" + }, + "no-match": "Ingen treff.", + "no-wallpaper": "Ingen bakgrunnsbilete funne.", + "panel": { + "apikey-help": "Ein API-lykel krevst for รฅ fรฅ tilgang til NSFW-innhald", + "apikey-label": "API-lykel", + "apikey-managed-by-env": "Styrt med NOCTALIA_WALLHAVEN_API_KEY miljรธvariabel.", + "apikey-placeholder": "Skriv inn API-lyklen din for Wallhaven", + "categories-anime": "Anime", + "categories-label": "Kategoriar", + "categories-people": "Folk", + "color-extraction-disabled": "Tak fargane frรฅ bakgrunnsbiletet", + "color-extraction-enabled": "Bruk fรธredefinerte fargeskalaar", + "order-asc": "Oppover", + "order-desc": "Nedover", + "order-label": "Rekkjefylgd", + "purity-label": "Innhaldsfilter", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "Pรฅ randa", + "ratios-all-portrait": "Alle stรฅande", + "ratios-all-wide": "Alle breide", + "ratios-any": "Kva som helst", + "ratios-label": "Sidetilhรธve", + "resolution-atleast": "I minsto", + "resolution-exact": "Nรธye", + "resolution-label": "Opplรธysing", + "resolution-mode-label": "Modus", + "solid-color-tooltip": "Einsfarga bakgrunn", + "sorting-date-added": "Dato lagt til", + "sorting-favorites": "Favorittar", + "sorting-label": "Sorter ved", + "sorting-relevance": "Relevans", + "sorting-toplist": "Toppliste", + "sorting-views": "Visningar", + "source-label": "Kjelde", + "source-wallhaven": "Wallhaven", + "title": "Wallhaven-veljar", + "wallhaven-settings-title": "Wallhaven-innstillingar" + }, + "transitions": { + "disc": "Disk", + "fade": "Bleikna", + "honeycomb": "Vokstavle", + "pixelate": "Pikseler", + "stripes": "Striper", + "wipe": "Viska" + }, + "try-different-search": "Prรธv รฅ sรธkja noko anna.", + "wallhaven": { + "loading": "Lastar inn bakgrunnsbilete...", + "no-results": "Fann ingen bakgrunnsbilete. Prรธv รฅ sรธkja noko anna.", + "page": "{current} av {total}", + "page-prefix": "Side", + "page-suffix": "av {total}" + } + }, + "weather": { + "clear-sky": "Klรฅr himmel", + "drizzle": "Yr", + "fog": "Skodde", + "mainly-clear": "For det meste klรฅr", + "overcast": "Skyete", + "partly-cloudy": "Nokre fรฅe skyer", + "rain-showers": "Regnskurer", + "snow": "Snรธ", + "thunderstorm": "Storm" + }, + "widgets": { + "color-picker": { + "palette-description": "Vel frรฅ mange ulike fargar.", + "palette-label": "Palett", + "palette-theme-colors": "Kjapp tilgang til fargeskalaen for temaet ditt.", + "title": "Fargeveljar" + }, + "datetime-tokens": { + "ampm-lowercase": "am/pm med smรฅe bokstavar", + "ampm-uppercase": "AM/PM med store bokstavar", + "common-12hour-time-minutes": "Tid i 12-timar med minutt", + "common-24hour-time-minutes": "Tid i 24-timar med minutt", + "common-24hour-time-seconds": "Tid i 24-timar med sekund", + "common-european-date": "Europeisk datoformat", + "common-iso-date": "ISO datoformat", + "common-us-date": "Amerikansk datoformat", + "common-weekday-date": "Vekedag med dato", + "common-weekday-month-day": "Vekedag, mรฅnad og dag", + "day-abbreviated": "Forkorta dagsnamn", + "day-full": "Fullt dagsnamn", + "day-leading-zero": "Dag med null fรธre (01-31)", + "day-no-leading-zero": "Dag utan null fรธre (1-31)", + "hour-leading-zero": "Time med null fรธre (00-23) โ€” 24t-format", + "hour-no-leading-zero": "Time utan null fรธre (0-23) โ€” 24t-format", + "minute-leading-zero": "Minutt med null fรธre (00-59)", + "minute-no-leading-zero": "Minutt utan null fรธre (0-59)", + "month-abbreviated": "Forkorta mรฅnadsnamn", + "month-full": "Fullt mรฅnadsnamn", + "month-number-leading-zero": "Mรฅnad som tal med null fรธre (01-12)", + "month-number-no-zero": "Mรฅnad som tal utan null fรธre (1-12)", + "second-leading-zero": "Sekund med null fรธre (00-59)", + "second-no-leading-zero": "Sekund utan null fรธre (0-59)", + "timezone-abbreviation": "Forkorta tidssone", + "year-four-digit": "ร…r som tal med fire siffer", + "year-two-digit": "ร…r som tal med to siffer (00-99)" + }, + "file-picker": { + "item": "objekt", + "items": "objekt", + "search-placeholder": "Leita i filer og mapper...", + "select-current": "Vel noverande", + "select-file": "Vel fil", + "select-folder": "Vel mappe", + "selected": "Valt:", + "title": "Filveljar" + }, + "file-picker-title": "Vel ei fil", + "icon-picker": { + "title": "Ikonveljar" + }, + "tooltip-placeholder": "Plasshaldar" + }, + "wifi": { + "panel": { + "action-required": "Krev handling", + "available-interfaces": "Tilgjengelege grensesnitt", + "available-networks": "Tilgjengelege nettverk", + "disabled": "Wi-Fi er slege av", + "disconnecting": "Koplar frรฅ...", + "dns": "DNS", + "enable-message": "Slรฅ pรฅ Wi-Fi for รฅ sjรฅ tilgjengelege nettverk.", + "enter-password": "Skriv inn Wi-Fi-passord...", + "forget": "Glรธym", + "forget-network": "Glรธym dette nettverket", + "forgetting": "Glรธymer...", + "interface": "Nettverkgrensesnitt", + "internet-connected": "Kopla til internettet", + "internet-limited": "Ikkje kopla til internettet", + "internet-status": "Internettstatus", + "ipv4": "IPv4", + "known-networks": "Kjende nettverk", + "link-speed": "Lenkjefart", + "no-ethernet-devices": "Ingen etherneteiningar funne", + "no-networks": "Ingen Wi-Fi-nettverk funne", + "saved": "Lagra", + "scan-again": "Leit atter", + "searching": "Sรธkjer etter nettverk...", + "title": "Wi-Fi" + }, + "signal": { + "excellent": "Framifrรฅ", + "fair": "Grei", + "good": "God", + "poor": "Dรฅleg" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/pl.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/pl.json new file mode 100644 index 0000000..115049c --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/pl.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "Wyczyล›ฤ‡ historiฤ™", + "close-app": "Zamknij {app}", + "connect-vpn": "Poล‚ฤ…cz z {name}", + "cycle-visualizer": "Przeล‚ฤ…cz wizualizator", + "disable-bluetooth": "Wyล‚ฤ…cz Bluetooth", + "disable-dnd": "Wyล‚ฤ…cz 'Nie przeszkadzaฤ‡'", + "disable-wifi": "Wyล‚ฤ…cz Wi-Fi", + "disconnect-vpn": "Odล‚ฤ…cz {name}", + "dock-settings": "Ustawienia docka", + "enable-bluetooth": "Wล‚ฤ…cz Bluetooth", + "enable-dnd": "Wล‚ฤ…cz 'Nie przeszkadzaฤ‡'", + "enable-wifi": "Wล‚ฤ…cz Wi-Fi", + "launcher-settings": "Ustawienia programu uruchamiajฤ…cego", + "lower-to-bottom": "Opuล›ฤ‡ na dรณล‚", + "open-calendar": "Otwรณrz kalendarz", + "open-display-settings": "Ustawienia ekranu", + "open-launcher": "Otwรณrz launcher", + "open-settings": "Otwรณrz ustawienia", + "raise-to-top": "Przenieล› na gรณrฤ™", + "random-wallpaper": "Losowa tapeta", + "run-custom-command": "Uruchom wล‚asne polecenie", + "show-active-only": "Pokaลผ tylko aktywne", + "show-all": "Pokaลผ wszystko", + "toggle-mute": "Przeล‚ฤ…cz wyciszenie", + "widget-settings": "Ustawienia widลผetu" + }, + "authentication": { + "error": "Bล‚ฤ…d uwierzytelniania", + "failed": "Uwierzytelnianie nieudane" + }, + "bar": { + "active-window": { + "colorize-icons-description": "Zastosuj kolory motywu do ikony aktywnego okna.", + "hide-mode-description": "Kontroluje zachowanie widลผetu, gdy ลผadne okno nie jest aktywne.", + "scrolling-mode-description": "Kontroluje, kiedy wล‚ฤ…czone jest przewijanie tekstu dla dล‚ugich tytuล‚รณw okien.", + "show-app-icon-description": "Wyล›wietl ikonฤ™ aplikacji obok tytuล‚u okna.", + "show-app-icon-label": "Pokaลผ ikonฤ™ aplikacji", + "show-app-text-description": "Wyล›wietl tytuล‚ aktywnego okna.", + "show-app-text-label": "Pokaลผ tytuล‚ okna" + }, + "audio-visualizer": { + "color-name-description": "Wybierz kolor dla wizualizatora.", + "color-name-label": "Kolor wypeล‚nienia", + "height-description": "Niestandardowa szerokoล›ฤ‡ komponentu.", + "hide-when-idle-description": "Gdy wล‚ฤ…czone, wizualizator jest ukryty, chyba ลผe odtwarzacz jest aktywny.", + "hide-when-idle-label": "Ukryj, gdy nic nie jest odtwarzane", + "width-description": "Niestandardowa szerokoล›ฤ‡ komponentu." + }, + "battery": { + "device-default": "Domyล›lne (urzฤ…dzenie wyล›wietlajฤ…ce)", + "device-description": "Wybierz, ktรณre urzฤ…dzenie baterii ma byฤ‡ wyล›wietlane.", + "device-label": "Urzฤ…dzenie baterii", + "display-mode-description": "Wybierz, jak bateria jest wyล›wietlana na pasku.", + "display-mode-graphic": "Graficzna bateria", + "display-mode-graphic-clean": "Graficzna bateria (bez %)", + "display-mode-icon-always": "Ikona - Zawsze pokazuj %", + "display-mode-icon-hover": "Ikona - Pokaลผ po najechaniu", + "display-mode-icon-only": "Tylko ikona", + "hide-if-idle-description": "Ukryj widลผet, gdy bateria nie jest ล‚adowana ani rozล‚adowywana.", + "hide-if-idle-label": "Ukryj, gdy bezczynny", + "hide-if-not-detected-description": "Ukryj widลผet, gdy w systemie nie zostanie wykryta bateria.", + "hide-if-not-detected-label": "Ukryj siฤ™, jeล›li nie wykryto", + "low-battery-threshold-label": "Prรณg ostrzegania o niskim poziomie baterii", + "show-noctalia-performance-description": "Wyล›wietl przeล‚ฤ…cznik trybu wydajnoล›ci Noctalia w panelu baterii.
Wyล‚ฤ…cza cienie i animacje w Noctalia, aby zmniejszyฤ‡ zuลผycie zasobรณw.", + "show-noctalia-performance-label": "Pokaลผ przeล‚ฤ…cznik Wydajnoล›ci Noctalia", + "show-power-profile-description": "Wyล›wietl wybรณr profilu zasilania w panelu baterii.", + "show-power-profile-label": "Pokaลผ kontrolki profilu zasilania" + }, + "brightness": { + "apply-all-description": "Zmieล„ jasnoล›ฤ‡ dla wszystkich monitorรณw podczas uลผywania kรณล‚ka przewijania.", + "apply-all-label": "Zastosuj zmiany przewijania do wszystkich monitorรณw" + }, + "clock": { + "clock-display-description": "Dostosuj wyล›wietlanie zegara, dodajฤ…c tokeny z poniลผszej listy. Aby uลผyฤ‡ formatu 12-godzinnego, musisz doล‚ฤ…czyฤ‡ token 'AP'.", + "clock-display-label": "Wyล›wietlanie zegara", + "custom-font-description": "Wybierz niestandardowฤ… czcionkฤ™ dla zegara.", + "custom-font-label": "Niestandardowa czcionka", + "custom-font-placeholder": "Wybierz czcionkฤ™...", + "custom-font-search-placeholder": "Szukaj czcionek...", + "horizontal-bar-description": "Wskazรณwka: Uลผyj \\n, aby utworzyฤ‡ podziaล‚ linii.", + "horizontal-bar-label": "Pasek poziomy", + "preview": "Podglฤ…d", + "tooltip-format-description": "Ciฤ…g formatujฤ…cy dla etykietki wyล›wietlanej po najechaniu na zegar. Pozostaw puste, aby uลผyฤ‡ domyล›lnej etykietki.", + "tooltip-format-label": "Format etykietki", + "use-custom-font-description": "Zastฤ…p domyล›lnฤ… czcionkฤ™ zegara czcionkฤ… niestandardowฤ….", + "use-custom-font-label": "Uลผyj niestandardowej czcionki", + "use-monospaced-font-description": "Gdy wล‚ฤ…czone, zegar bฤ™dzie uลผywaล‚ czcionki o staล‚ej szerokoล›ci.", + "use-monospaced-font-label": "Uลผyj czcionki monospaced", + "vertical-bar-description": "Uลผyj spacji, aby oddzieliฤ‡ kaลผdฤ… czฤ™ล›ฤ‡ w nowej linii.", + "vertical-bar-label": "Pasek pionowy" + }, + "control-center": { + "browse-file": "Przeglฤ…daj pliki", + "browse-library": "Przeglฤ…daj bibliotekฤ™", + "color-selection-description": "Zastosuj kolory motywu do ikon.", + "enable-colorization-description": "Wล‚ฤ…cz koloryzacjฤ™ ikony, stosujฤ…c kolory motywu.", + "icon-description": "Wybierz ikonฤ™ z biblioteki lub niestandardowy plik.", + "select-custom-icon": "Wybierz wล‚asnฤ… ikonฤ™", + "use-distro-logo-description": "Uลผyj logo twojej dystrybucji zamiast niestandardowej ikony.", + "use-distro-logo-label": "Uลผyj logo dystrybucji zamiast ikony" + }, + "custom-button": { + "collapse-condition-description": "Jeล›li tekst wyjล›ciowy pasuje do tej wartoล›ci, przycisk zostanie zwiniฤ™ty.", + "collapse-condition-label": "Warunek zwiniฤ™cia", + "color-selection-description": "Zastosuj kolory motywu do ikony i tekstu.", + "default-tooltip": "Przycisk niestandardowy, konfiguruj w ustawieniach", + "display-command-output-description": "Wpisz polecenie do uruchamiania w regularnych odstฤ™pach czasu. Pierwsza linia wyniku zostanie wyล›wietlona jako tekst.", + "display-command-output-label": "Wyล›wietl wynik polecenia", + "display-command-output-stream-description": "Wpisz polecenie do uruchamiania w trybie ciฤ…gล‚ym.", + "dynamic-text": "Dynamiczny tekst", + "enable-colorization-description": "Wล‚ฤ…cz kolorowanie ikony i tekstu niestandardowego przycisku, stosujฤ…c kolory motywu.", + "enable-colorization-label": "Wล‚ฤ…cz kolorowanie", + "general-tooltip-text-description": "Niestandardowy tekst do wyล›wietlenia w dymku przycisku.", + "general-tooltip-text-label": "Niestandardowy tekst podpowiedzi", + "hide-mode-always-expanded": "Zawsze rozwiniฤ™ty", + "hide-mode-description": "Kontroluje widocznoล›ฤ‡ widลผetu, gdy polecenie nie zwraca wyniku.", + "hide-mode-expand-with-output": "Rozwiล„, gdy jest wynik", + "hide-mode-label": "Tryb ukrywania", + "hide-mode-max-transparent": "Rozwiniฤ™ty, ale przezroczysty", + "icon-description": "Wybierz ikonฤ™ z biblioteki.", + "ipc-identifier-description": "Unikalny identyfikator dla poleceล„ IPC. Uลผyj tego identyfikatora z 'qs -c noctalia-shell ipc call cb [akcja] [identyfikator]', aby sterowaฤ‡ tym przyciskiem za pomocฤ… IPC.", + "ipc-identifier-label": "Identyfikator IPC", + "left-click-description": "Polecenie do wykonania po klikniฤ™ciu lewym przyciskiem myszy.", + "left-click-label": "Lewy przycisk", + "left-click-update-text": "Aktualizuj tekst po klikniฤ™ciu lewym przyciskiem", + "max-text-length-horizontal-description": "Maksymalna liczba znakรณw na pasku poziomym (0, aby ukryฤ‡ tekst).", + "max-text-length-horizontal-label": "Max dล‚ugoล›ฤ‡ tekstu (poziomo)", + "max-text-length-vertical-description": "Maksymalna liczba znakรณw na pasku pionowym (0, aby ukryฤ‡ tekst).", + "max-text-length-vertical-label": "Max dล‚ugoล›ฤ‡ tekstu (pionowo)", + "middle-click-description": "Polecenie do wykonania po klikniฤ™ciu ล›rodkowym przyciskiem myszy.", + "middle-click-label": "ลšrodkowy przycisk", + "middle-click-update-text": "Aktualizuj tekst po klikniฤ™ciu ล›rodkowym przyciskiem", + "parse-json-description": "Przetwรณrz wynik polecenia jako obiekt JSON, aby dynamicznie ustawiฤ‡ tekst i ikonฤ™.", + "parse-json-label": "Parsuj wynik jako JSON", + "refresh-interval-description": "Interwaล‚ w milisekundach.", + "refresh-interval-label": "Interwaล‚ odล›wieลผania", + "right-click-description": "Polecenie do wykonania po klikniฤ™ciu prawym przyciskiem myszy.", + "right-click-label": "Prawy przycisk", + "right-click-update-text": "Aktualizuj tekst po klikniฤ™ciu prawym przyciskiem", + "show-exec-tooltip-description": "Pokaลผ podpowiedzi ze szczegรณล‚ami poleceล„ (lewy/prawy/ล›rodkowy klik, kรณล‚ko).", + "show-exec-tooltip-label": "Pokaลผ podpowiedzi poleceล„", + "show-icon-description": "Przeล‚ฤ…cza widocznoล›ฤ‡ ikony widลผetu.", + "show-icon-label": "Pokaลผ ikonฤ™", + "show-text-tooltip-description": "Pokaลผ dymki z podpowiedziฤ… z wyjล›ciem polecenia tekstowego.", + "show-text-tooltip-label": "Pokaลผ dynamiczne etykietki tekstowe", + "text-stream-description": "Linie przesyล‚ane strumieniowo z polecenia bฤ™dฤ… wyล›wietlane jako tekst na przycisku.", + "text-stream-label": "Strumieล„", + "wheel-description": "Polecenie do wykonania przy uลผyciu kรณล‚ka myszy.
Uลผyj $delta dla wartoล›ci przesuniฤ™cia kรณล‚ka w poleceniu.", + "wheel-down": "Rolka w dรณล‚", + "wheel-down-description": "Polecenie do wykonania przy przewijaniu kรณล‚kiem w dรณล‚.", + "wheel-down-label": "Polecenie kรณล‚ka w dรณล‚", + "wheel-label": "Kรณล‚ko myszy", + "wheel-mode-separate-description": "Wล‚ฤ…cz oddzielne polecenia dla przewijania w gรณrฤ™ i w dรณล‚.", + "wheel-mode-separate-label": "Oddzielne polecenia kรณล‚ka", + "wheel-up": "Rolka w gรณrฤ™", + "wheel-up-description": "Polecenie do wykonania przy przewijaniu kรณล‚kiem w gรณrฤ™.", + "wheel-up-label": "Polecenie kรณล‚ka w gรณrฤ™", + "wheel-update-text": "Aktualizuj tekst po uลผyciu kรณล‚ka" + }, + "keyboard-layout": { + "show-icon-description": "Wyล›wietl ikonฤ™ ukล‚adu klawiatury." + }, + "lock-keys": { + "hide-when-off-description": "Ukryj wskaลบnik, gdy klawisz jest nieaktywny.", + "hide-when-off-label": "Ukryj gdy wyล‚ฤ…czony", + "show-caps-lock-description": "Wyล›wietl status Caps Lock.", + "show-caps-lock-label": "Caps Lock", + "show-num-lock-description": "Wyล›wietl status Num Lock.", + "show-num-lock-label": "Num Lock", + "show-scroll-lock-description": "Wyล›wietl status Scroll Lock.", + "show-scroll-lock-label": "Scroll Lock" + }, + "media-mini": { + "compact-mode-description": "Wล‚ฤ…cz ukล‚ad oszczฤ™dzajฤ…cy miejsce dla panelu odtwarzacza multimedialnego.", + "compact-mode-label": "Tryb kompaktowy", + "hide-mode-description": "Kontroluje zachowanie widลผetu, gdy ลผadne media nie sฤ… odtwarzane.", + "max-width-description": "Ustawia maksymalnฤ… szerokoล›ฤ‡ poziomฤ… widลผetu. Widลผet skurczy siฤ™, aby dopasowaฤ‡ siฤ™ do krรณtszej zawartoล›ci.", + "no-active-player": "Brak aktywnego odtwarzacza", + "panel-section-description": "Skonfiguruj wyglฤ…d i zachowanie panelu odtwarzacza multimediรณw.", + "panel-section-label": "Panel odtwarzacza multimedialnego", + "scrolling-mode-description": "Kontroluje, kiedy wล‚ฤ…czone jest przewijanie tekstu dla dล‚ugich tytuล‚รณw utworรณw.", + "scrolling-mode-label": "Tryb przewijania", + "show-album-art-description": "Wyล›wietl okล‚adkฤ™ albumu aktualnie odtwarzanego utworu.", + "show-album-art-label": "Pokaลผ okล‚adkฤ™", + "show-artist-first-description": "Wyล›wietl artystฤ™ - tytuล‚ zamiast tytuล‚ - artysta.", + "show-artist-first-label": "Pokaลผ najpierw artystฤ™", + "show-progress-ring-description": "Wyล›wietl koล‚owy wskaลบnik postฤ™pu utworu.", + "show-progress-ring-label": "Pokaลผ pierล›cieล„ postฤ™pu", + "show-visualizer-description": "Wyล›wietl wizualizator dลบwiฤ™ku podczas odtwarzania muzyki.", + "show-visualizer-label": "Pokaลผ wizualizator", + "use-fixed-width-description": "Gdy wล‚ฤ…czone, widลผet zawsze bฤ™dzie uลผywaล‚ maksymalnej szerokoล›ci zamiast dynamicznie dostosowywaฤ‡ siฤ™ do treล›ci.", + "use-fixed-width-label": "Uลผyj staล‚ej szerokoล›ci", + "visualizer-type-description": "Wybierz styl wyล›wietlanego wizualizatora dลบwiฤ™ku.", + "visualizer-type-label": "Typ wizualizatora" + }, + "notification-history": { + "hide-widget-when-zero-description": "Ukryj ikonฤ™ powiadomieล„, gdy nie ma ลผadnych powiadomieล„.", + "hide-widget-when-zero-label": "Ukryj ikonฤ™ bez powiadomieล„", + "hide-widget-when-zero-unread-description": "Ukryj ikonฤ™ powiadomieล„, gdy nie ma nieprzeczytanych powiadomieล„.", + "hide-widget-when-zero-unread-label": "Ukryj ikonฤ™ bez nieprzeczytanych", + "show-unread-badge-description": "Wyล›wietl plakietkฤ™ z liczbฤ… nieprzeczytanych powiadomieล„.", + "show-unread-badge-label": "Pokaลผ plakietkฤ™ nieprzeczytanych", + "unread-badge-color-description": "Wybierz kolor dla plakietki nieprzeczytanych powiadomieล„.", + "unread-badge-color-label": "Kolor plakietki nieprzeczytanych" + }, + "section-editor": { + "placeholder": "Wybierz widลผet do dodania...", + "search-placeholder": "Szukaj widลผetu..." + }, + "spacer": { + "width-description": "Szerokoล›ฤ‡ odstฤ™pu w pikselach." + }, + "system-monitor": { + "compact-mode-description": "Wyล›wietlaj statystyki jako mini wykresy sล‚upkowe zamiast wartoล›ci tekstowych. Zapobiega przesuniฤ™ciom ukล‚adu.", + "compact-mode-label": "Tryb kompaktowy", + "cpu-cores-description": "Wyล›wietlaj uลผycie rdzeni CPU indywidualnie.", + "cpu-cores-label": "Rdzenie CPU", + "cpu-frequency-description": "Wyล›wietl aktualnฤ… prฤ™dkoล›ฤ‡ zegara CPU w GHz.", + "cpu-frequency-label": "Pokaลผ czฤ™stotliwoล›ฤ‡ CPU", + "cpu-temperature-description": "Pokaลผ odczyty temperatury procesora (jeล›li dostฤ™pne).", + "cpu-temperature-label": "Temperatura CPU", + "cpu-usage-description": "Wyล›wietl aktualne uลผycie procesora w procentach.", + "cpu-usage-label": "Uลผycie CPU", + "disk-path-description": "Wybierz punkt montowania dysku do monitorowania.", + "disk-path-label": "ลšcieลผka dysku", + "gpu-temperature-description": "Pokaลผ odczyty temperatury GPU (jeล›li dostฤ™pne).", + "load-average-description": "Wyล›wietl ล›rednie obciฤ…ลผenie systemu.", + "load-average-label": "ลšrednie obciฤ…ลผenie", + "memory-percentage-description": "Pokaลผ uลผycie pamiฤ™ci w procentach zamiast wartoล›ci bezwzglฤ™dnych.", + "memory-percentage-label": "Pamiฤ™ฤ‡ w procentach", + "memory-usage-description": "Wyล›wietl aktualne uลผycie pamiฤ™ci RAM.", + "memory-usage-label": "Uลผycie pamiฤ™ci", + "network-traffic-description": "Wyล›wietl prฤ™dkoล›ฤ‡ wysyล‚ania i pobierania danych w sieci.", + "network-traffic-label": "Ruch sieciowy", + "storage-as-percentage-description": "Wyล›wietlaj miejsce na dysku jako procent zamiast wartoล›ci bezwzglฤ™dnych.", + "storage-as-percentage-label": "Dysk jako procent", + "storage-available-description": "Pokazuje, ile miejsca na dysku jest dostฤ™pne, zamiast ile jest uลผywane.", + "storage-available-label": "Dostฤ™pne miejsce na dysku", + "storage-usage-description": "Pokaลผ informacje o uลผyciu miejsca na dysku.", + "storage-usage-label": "Uลผycie dysku", + "swap-usage-description": "Pokaลผ uลผycie pamiฤ™ci wymiany.", + "swap-usage-label": "Uลผycie pamiฤ™ci wymiany", + "use-monospace-font-description": "Uลผyj czcionki o staล‚ej szerokoล›ci znakรณw dla zachowania spรณjnej szerokoล›ci znakรณw.", + "use-monospace-font-label": "Czcionka o staล‚ej szerokoล›ci znakรณw", + "use-padding-description": "Wypeล‚nia wartoล›ci tekstowe spacjami wiodฤ…cymi, aby zapobiec przesuniฤ™ciom ukล‚adu.", + "use-padding-description-disabled-monospace-font": "Do tej funkcji potrzebna jest czcionka monospace.", + "use-padding-description-disabled-vertical": "Pionowy pasek zadaล„ nie obsล‚uguje wypeล‚nienia tekstowego.", + "use-padding-label": "Wypeล‚nienie Tekstu" + }, + "taskbar": { + "colorize-icons-description": "Zastosuj kolory motywu do ikon paska zadaล„.", + "hide-mode-description": "Kontroluje zachowanie widลผetu, gdy nie ma pasujฤ…cych okien.", + "hide-mode-label": "Tryb ukrywania", + "icon-scale-description": "Ustawia wspรณล‚czynnik skalowania ikon na pasku zadaล„.", + "icon-scale-label": "Skalowanie ikon", + "max-width-description": "Maksymalna szerokoล›ฤ‡ paska zadaล„ jako procent szerokoล›ci ekranu.", + "max-width-label": "Maksymalna szerokoล›ฤ‡", + "only-active-workspaces-description": "Pokaลผ tylko aplikacje z aktywnych obszarรณw roboczych.", + "only-active-workspaces-label": "Tylko z aktywnych obszarรณw", + "only-same-monitor-description": "Pokaลผ tylko aplikacje z monitora, na ktรณrym znajduje siฤ™ pasek.", + "only-same-monitor-label": "Tylko z tego samego monitora", + "show-pinned-apps-description": "Pokaลผ przypiฤ™te aplikacje z doku na pasku zadaล„.", + "show-pinned-apps-label": "Pokaลผ przypiฤ™te aplikacje", + "show-title-description": "Wyล›wietl tytuล‚y okien na pasku zadaล„.", + "show-title-description-disabled": "Pionowy pasek zadaล„ nie obsล‚uguje wyล›wietlania tytuล‚รณw.", + "show-title-label": "Pokaลผ tytuล‚", + "smart-width-description": "Automatycznie dostosuj szerokoล›ฤ‡ wpisรณw na podstawie ich liczby.", + "smart-width-label": "Inteligentna szerokoล›ฤ‡", + "title-width-description": "Ustaw szerokoล›ฤ‡ tytuล‚รณw okien na pasku zadaล„ (w pikselach).", + "title-width-label": "Szerokoล›ฤ‡ tytuล‚u", + "title-width-reset-tooltip": "Resetuj szerokoล›ฤ‡ tytuล‚u" + }, + "tray": { + "chevron-color-description": "Zastosuj kolory motywu do ikony strzaล‚ki szuflady.", + "chevron-color-label": "Kolor strzaล‚ki", + "colorize-icons-description": "Zastosuj kolory motywu do ikon zasobnika.", + "colorize-icons-label": "Koloryzuj ikony", + "drawer-enabled-description": "Gdy wล‚ฤ…czone, nieprzypiฤ™te elementy zasobnika sฤ… pokazywane w panelu szuflady.
Gdy wyล‚ฤ…czone, wszystkie elementy sฤ… pokazywane w linii.", + "drawer-enabled-label": "Wล‚ฤ…cz szufladฤ™", + "hide-passive-description": "Gdy wล‚ฤ…czone, elementy zasobnika o statusie 'Passive' bฤ™dฤ… ukryte.", + "hide-passive-label": "Ukryj pasywne elementy" + }, + "volume": { + "display-mode-description": "Wybierz sposรณb wyล›wietlania tej wartoล›ci." + }, + "workspace": { + "character-count-description": "Liczba znakรณw do wyล›wietlenia z nazw obszarรณw roboczych (1-10).", + "character-count-label": "Liczba znakรณw", + "empty-color-description": "Ustaw kolor tล‚a dla pustych Workspaceรณw.", + "empty-color-label": "Kolor pustego obszaru roboczego", + "enable-scrollwheel-description": "Przeล‚ฤ…czaj miฤ™dzy obszarami roboczymi za pomocฤ… kรณล‚ka myszy.", + "enable-scrollwheel-label": "Przewiล„, aby przeล‚ฤ…czyฤ‡ obszary", + "focused-color-description": "Ustaw kolor tล‚a dla aktywnego Workspace.", + "focused-color-label": "Kolor aktywnego obszaru roboczego", + "follow-focused-screen-description": "Wyล›wietl obszary robocze z aktualnie aktywnego ekranu, zamiast ekranu, na ktรณrym znajduje siฤ™ pasek.", + "follow-focused-screen-label": "Podฤ…ลผaj za skupionym ekranem", + "font-weight-description": "Ustaw wizualnฤ… wagฤ™ tekstu w obszarze roboczym.", + "font-weight-label": "Gruboล›ฤ‡ czcionki", + "grouped-border-opacity-description": "Ustaw poziom krycia dla obramowaล„ kontenerรณw obszaru roboczego.", + "grouped-border-opacity-label": "Krycie obramowania", + "hide-unoccupied-description": "Nie wyล›wietlaj obszarรณw roboczych bez okien.", + "hide-unoccupied-label": "Ukryj nieobsadzone", + "label-mode-description": "Wybierz sposรณb wyล›wietlania etykiet obszarรณw roboczych.", + "label-mode-label": "Tryb etykiet", + "occupied-color-description": "Ustaw kolor tล‚a dla zajฤ™tych Workspace'รณw.", + "occupied-color-label": "Kolor zajฤ™tego obszaru roboczego", + "pill-size-description": "Dostosuj rozmiar piguล‚ek obszaru roboczego (50%-100%).", + "pill-size-label": "Rozmiar piguล‚ki", + "reverse-scrolling-description": "Odwrรณฤ‡ kierunek przeล‚ฤ…czania obszarรณw roboczych podczas przewijania.", + "reverse-scrolling-label": "Odwrรณฤ‡ przewijanie", + "show-applications-description": "Wyล›wietl ikony aplikacji wewnฤ…trz kaลผdego obszaru roboczego.", + "show-applications-hover-description": "Wyล›wietl ikony aplikacji w kaลผdym obszarze roboczym po najechaniu na obszar roboczy.", + "show-applications-hover-label": "Pokaลผ aplikacje po najechaniu kursorem", + "show-applications-label": "Pokaลผ aplikacje", + "show-badge-description": "Pokaลผ plakietkฤ™ z numerem obszaru roboczego w trybie grupowym.", + "show-badge-label": "Pokaลผ odznakฤ™ obszaru roboczego", + "show-labels-only-when-occupied-description": "Pokazuj etykiety obszarรณw roboczych tylko wtedy, gdy zawierajฤ… okna.", + "show-labels-only-when-occupied-label": "Pokaลผ etykiety tylko gdy obsadzone", + "unfocused-icons-opacity-description": "Ustaw poziom przezroczystoล›ci dla ikon nieaktywnych aplikacji.", + "unfocused-icons-opacity-label": "Krycie ikon bez ostroล›ci" + } + }, + "battery": { + "all-batteries": "Wszystkie baterie (ล‚ฤ…cznie)", + "battery-health": "Stan baterii", + "battery-level": "Poziom baterii", + "capacity-level": "Pojemnoล›ฤ‡: {level}", + "charging-rate": "Prฤ™dkoล›ฤ‡ ล‚adowania: {rate} W", + "discharging-rate": "Prฤ™dkoล›ฤ‡ rozล‚adowywania: {rate} W", + "health": "Kondycja: {percent}%", + "inhibit-idle-description": "Zapobiega zasypianiu systemu.", + "no-battery-detected": "Nie wykryto baterii", + "plugged-in": "Podล‚ฤ…czony", + "power-profile": "Profil zasilania", + "time-left": "Pozostaล‚y czas: {time}", + "time-until-full": "Czas do peล‚nego naล‚adowania: {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "Dostฤ™pne urzฤ…dzenia", + "blocked": "Zablokowane", + "connected-devices": "Poล‚ฤ…czone urzฤ…dzenia", + "device-address": "Adres urzฤ…dzenia", + "disabled": "Bluetooth jest wyล‚ฤ…czony", + "discoverable": "Wykrywalny", + "enable-message": "Wล‚ฤ…cz Bluetooth, aby zobaczyฤ‡ dostฤ™pne urzฤ…dzenia.", + "known-devices": "Znane urzฤ…dzenia", + "no-devices": "Brak dostฤ™pnych urzฤ…dzeล„", + "paired-devices": "Sparowane urzฤ…dzenia", + "pairing-mode": "Upewnij siฤ™, ลผe urzฤ…dzenie jest w trybie parowania.", + "scanning": "Skanowanie urzฤ…dzeล„...", + "signal-text-excellent": "Sygnaล‚: Doskonaล‚y", + "signal-text-fair": "Sygnaล‚: Dobry", + "signal-text-good": "Sygnaล‚: Dobry", + "signal-text-poor": "Sygnaล‚: Sล‚aby", + "signal-text-unknown": "Sygnaล‚: Nieznany", + "signal-text-very-poor": "Sygnaล‚: Bardzo sล‚aby" + } + }, + "changelog": { + "error": { + "fetch-failed": "Nie moลผna zaล‚adowaฤ‡ dziennika zmian. Sprรณbuj ponownie pรณลบniej.", + "rate-limit": "Przekroczono limit zapytaล„ GitHub. Sprรณbuj ponownie za kilka minut." + }, + "panel": { + "buttons-discord": "Doล‚ฤ…cz do Discorda", + "buttons-dismiss": "OK", + "buttons-feedback": "Przekaลผ opiniฤ™", + "empty": "Informacje o wydaniu nie sฤ… jeszcze dostฤ™pne.", + "highlight-title": "Najwaลผniejsze zmiany", + "released": "Wydano: {date}", + "subtitle-fresh": "Dziฤ™kujemy za zainstalowanie Noctalia! Oto co zawiera ta kompilacja.", + "subtitle-updated": "Zaktualizowano z {previousVersion}", + "title": "Co nowego w {version}", + "version": "Wersja {version}", + "version-new-user": "Nowa instalacja" + } + }, + "common": { + "actions": "Akcje", + "active": "Aktywny", + "add": "Dodaj", + "appearance": "Wyglฤ…d", + "apply": "Zastosuj", + "auto-connect": "Automatyczne ล‚ฤ…czenie", + "automation": "Automatyzacja", + "available": "Dostฤ™pne", + "back": "Powrรณt", + "battery": "Bateria", + "behavior": "Zachowanie", + "bluetooth": "Bluetooth", + "brightness": "Jasnoล›ฤ‡", + "browse": "Przeglฤ…daj", + "calendar": "Kalendarz", + "calendar-panel": "Panel kalendarza", + "cancel": "Anuluj", + "cards": "Karty", + "charging": "ลadowanie", + "clear": "Wyczyล›ฤ‡", + "clipboard": "Schowek", + "close": "Zamknij", + "color-muted": "Wyciszony", + "colors": "Kolory", + "command": "Komenda", + "confirm": "Potwierdลบ", + "connect": "Poล‚ฤ…cz", + "connected": "Poล‚ฤ…czone", + "connecting": "ลฤ…czenie...", + "continue": "Kontynuuj", + "contributors": "Wspรณล‚twรณrcy", + "copied-to-clipboard": "Skopiowano do schowka", + "countdown": "Odliczanie", + "customize": "Dostosuj", + "date": "Data", + "default": "Domyล›lny", + "delete": "Usuล„", + "devices": "Urzฤ…dzenia", + "disabled": "Wyล‚ฤ…czone", + "discharging": "Wypisywanie", + "disconnect": "Rozล‚ฤ…cz", + "disconnected": "Rozล‚ฤ…czone", + "disconnecting": "Rozล‚ฤ…czanie...", + "display-mode": "Tryb wyล›wietlania", + "documentation": "Dokumentacja", + "download": "Pobierz", + "duration": "Czas trwania", + "dysfunctional": "Dysfunkcyjny", + "edit": "Edytuj", + "enabled": "Wล‚ฤ…czone", + "error": "Bล‚ฤ…d", + "ethernet": "Ethernet", + "events": "Wydarzenia", + "execute": "Wykonaj", + "faithful": "Wierny", + "focus": "Skupienie", + "font-weight-bold": "Pogrubienie", + "font-weight-light": "Cienka", + "font-weight-medium": "ลšredni", + "font-weight-regular": "Zwykล‚y", + "font-weight-semibold": "Pรณล‚gruby", + "frequency": "Czฤ™stotliwoล›ฤ‡", + "gateway": "Brama", + "general": "Ogรณlne", + "height": "Wysokoล›ฤ‡", + "hibernate": "Hibernacja", + "history": "Historia", + "icon": "Ikona", + "idle": "Bezczynne", + "info": "Informacje", + "input": "Wprowadลบ", + "install": "Zainstaluj", + "installed": "Zainstalowano", + "interface": "Interfejs", + "internet": "Internet", + "keybind": "Skrรณt klawiszowy", + "language": "Jฤ™zyk", + "loading": "ลadowanie...", + "local": "Lokalny", + "location": "Lokalizacja", + "lock": "Zablokuj", + "logout": "Wyloguj", + "look": "Wyglฤ…d", + "margins": "Marginesy", + "media": "Media", + "media-player": "Odtwarzacz multimedialny", + "memory": "Pamiฤ™ฤ‡", + "monitors": "Monitory", + "move-down": "Przenieล› w dรณล‚", + "move-up": "Przenieล› w gรณrฤ™", + "network": "Sieฤ‡", + "next": "Nastฤ™pny", + "night-light": "Podล›wietlenie nocne", + "no": "Nie", + "no-results": "Brak wynikรณw", + "no-summary": "Brak podsumowania", + "none": "Brak", + "not-found": "Nie znaleziono", + "notifications": "Powiadomienia", + "official": "Oficjalne", + "on-surface": "Na powierzchni", + "output": "Wyjล›cie", + "pair": "Paruj", + "paired": "Sparowane", + "pairing": "Parowanie...", + "panels": "Panele", + "password": "Hasล‚o", + "pause": "Pauza", + "performance": "Wydajnoล›ฤ‡", + "pin": "Przypnij", + "play": "Odtwรณrz", + "polling": "Odpytywanie", + "position": "Pozycja", + "previous": "Poprzedni", + "primary": "Podstawowy", + "random": "Losowy", + "reboot": "Uruchom ponownie", + "reboot-to-uefi": "Uruchom ponownie do UEFI", + "record": "Rozpocznij nagrywanie", + "refresh": "Odล›wieลผ", + "required": "(wymagane)", + "reset": "Zresetuj", + "reset-to-default": "Przywrรณฤ‡ do Domyล›lnych", + "result-count": "{count} wynik", + "result-count-plural": "{count} wyniki", + "resume": "Wznรณw", + "save": "Zapisz", + "scanning": "Skanowanie...", + "screen-corners": "Naroลผniki ekranu", + "search": "Szukaj", + "second": "{count} sekunda", + "second-plural": "{count} sekund", + "secondary": "Dodatkowy", + "security": "Bezpieczeล„stwo", + "select": "Wybierz", + "select-color": "Wybierz kolor", + "select-color-description": "Zastosuj kolory motywu dla podkreล›lenia.", + "select-icon-color": "Wybierz kolor ikony", + "settings": "Ustawienia", + "shortcuts": "Skrรณty", + "shutdown": "Wyล‚ฤ…cz", + "signal": "Sygnaล‚", + "sound": "Dลบwiฤ™k", + "sources": "ลนrรณdล‚a", + "start": "Start", + "stop": "Stop", + "supporters": "Wspierajฤ…cy", + "suspend": "Wstrzymaj", + "templates": "Szablony", + "tertiary": "Trzeciorzฤ™dowy", + "test": "Test", + "thresholds": "Progi", + "title": "Tytuล‚", + "toast": "Toast", + "trusted": "Zaufany", + "uninstall": "Odinstaluj", + "unknown": "Nieznany", + "unpair": "Rozล‚ฤ…cz parฤ™", + "unpin": "Odepnij", + "update": "Aktualizacja", + "upload": "Wyล›lij", + "username": "Nazwa uลผytkownika", + "userspace-reboot": "Uruchom ponownie przestrzeล„ uลผytkownika", + "version": "Wersja", + "vibrant": "ลปywy", + "visualizer": "Wizualizator", + "volume": "Objฤ™toล›ฤ‡", + "volumes": "Tomy", + "wallpaper": "Tapeta", + "weather": "Pogoda", + "weather-loading": "ลadowanie pogody...", + "weather-no-location": "Ustaw lokalizacjฤ™ w Ustawieniach, aby zobaczyฤ‡ pogodฤ™", + "week": "Tydzieล„", + "widgets": "Widลผety", + "width": "Szerokoล›ฤ‡", + "wifi": "Wi-Fi", + "windows": "Okna", + "yes": "Tak" + }, + "control-center": { + "power-profile": { + "tooltip-action": "Profil zasilania", + "tooltip-disabled": "Zainstaluj power-profiles-daemon, aby korzystaฤ‡ z profili zasilania" + }, + "wifi": { + "label-disconnected": "Wi-Fi rozล‚ฤ…czone", + "label-ethernet": "Ethernet" + } + }, + "display-modes": { + "always-hide": "Zawsze siฤ™ ukrywaj", + "always-show": "Zawsze pokazuj", + "force-open": "Wywaลผyฤ‡", + "on-hover": "Po najechaniu" + }, + "hide-modes": { + "auto-hide": "Automatyczne ukrywanie", + "hidden": "Ukryj, gdy puste", + "idle": "Ukryj, gdy bezczynny", + "non-exclusive": "Niewyล‚ฤ…czny", + "transparent": "Przezroczysty, gdy pusty", + "visible": "Zawsze widoczny" + }, + "launcher": { + "categories": { + "all": "Wszystkie", + "audiovideo": "Audio i wideo", + "chat": "Czat", + "development": "Programowanie", + "education": "Edukacja", + "emoji-activity": "Aktywnoล›ฤ‡", + "emoji-animals": "Zwierzฤ™ta", + "emoji-flags": "Flagi", + "emoji-food": "Jedzenie i napoje", + "emoji-nature": "Natura", + "emoji-objects": "Obiekty", + "emoji-people": "Ludzie i ciaล‚o", + "emoji-recent": "Ostatni", + "emoji-symbols": "Symbole", + "emoji-travel": "Podrรณลผe i miejsca", + "game": "Gry", + "graphics": "Grafika", + "misc": "Rรณลผne", + "office": "Biuro", + "pinned": "Przypiฤ™te", + "system": "System", + "webbrowser": "Przeglฤ…darka www" + }, + "providers": { + "applications": "Aplikacje", + "calculator": "Kalkulator", + "calculator-deprecated": "Polecenie >calc jest przestarzaล‚e i wkrรณtce zostanie usuniฤ™te. Zamiast tego wpisuj wyraลผenia matematyczne bezpoล›rednio w wyszukiwarce.", + "calculator-description": "Kalkulator โ€” obliczaj wyraลผenia matematyczne", + "calculator-enter-expression": "Wpisz wyraลผenie matematyczne", + "calculator-press-enter-to-copy": "Naciล›nij Enter, aby skopiowaฤ‡ wynik", + "clipboard": "Historia schowka", + "clipboard-clear-description": "Wyczyล›ฤ‡ caล‚ฤ… historiฤ™ schowka", + "clipboard-clear-description-full": "Usuล„ wszystkie elementy z historii schowka", + "clipboard-clear-history": "Wyczyล›ฤ‡ historiฤ™ schowka", + "clipboard-delete": "Usuล„ wpis ze schowka", + "clipboard-history-disabled": "Historia schowka wyล‚ฤ…czona", + "clipboard-history-disabled-description": "Wล‚ฤ…cz historiฤ™ schowka w ustawieniach lub zainstaluj cliphist", + "clipboard-loading": "ลadowanie historii schowka...", + "clipboard-search-description": "Szukaj w historii schowka", + "command-description": "Uruchamiaj polecenia powล‚oki", + "emoji": "Wybรณr Emoji", + "emoji-loading": "ลadowanie emoji...", + "emoji-loading-description": "Proszฤ™ czekaฤ‡", + "emoji-no-recent": "Brak ostatnich emoji", + "emoji-search-description": "Szukaj i kopiuj emoji", + "settings-search-description": "Szukaj i przejdลบ do ustawieล„", + "windows-search-description": "Wyszukaj i aktywuj otwarte okna" + } + }, + "lock-screen": { + "authenticating": "Uwierzytelnianie...", + "password": "Wprowadลบ hasล‚o...", + "restart": "Restart", + "shut-down": "Wyล‚ฤ…cz" + }, + "notifications": { + "panel": { + "click-to-collapse": "Kliknij, aby zwinฤ…ฤ‡", + "click-to-expand": "Kliknij, aby rozwinฤ…ฤ‡", + "description": "Twoje powiadomienia pojawiฤ… siฤ™ tutaj, gdy nadejdฤ….", + "no-notifications": "Brak powiadomieล„" + }, + "range": { + "earlier": "Wczeล›niej", + "today": "Dzisiaj", + "yesterday": "Wczoraj" + }, + "time": { + "diff-d": "1 dzieล„ temu", + "diff-dd": "{diff} dni temu", + "diff-h": "1 godzinฤ™ temu", + "diff-hh": "{diff} godzin temu", + "diff-m": "1 minutฤ™ temu", + "diff-mm": "{diff} minut temu", + "now": "teraz" + } + }, + "options": { + "bar": { + "density-comfortable": "Komfortowy", + "density-compact": "Kompaktowy", + "density-default": "Domyล›lny", + "density-mini": "Mini", + "density-spacious": "Przestronny", + "display-mode-always-visible": "Zawsze widoczne", + "type-floating": "Pล‚ywajฤ…cy", + "type-framed": "Obramowany", + "type-simple": "Prosty" + }, + "control-center": { + "quick-settings-style-classic": "Klasyczny", + "quick-settings-style-modern": "Nowoczesny" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "Komfortowy", + "compact": "Kompaktowy", + "default": "Domyล›lny" + }, + "launcher-view-mode": { + "grid": "Siatka", + "list": "Lista" + }, + "notification-density": { + "compact": "Kompaktowy", + "default": "Domyล›lny" + }, + "scrolling-modes": { + "always": "Zawsze przewijaj", + "hover": "Przewijaj po najechaniu", + "never": "Nigdy nie przewijaj" + }, + "session-menu-grid-layout": { + "grid": "Siatka", + "single-row": "Pojedynczy wiersz" + }, + "settings-panel-mode": { + "attached": "Panel przypiฤ™ty do paska", + "centered": "Panel wyล›rodkowany", + "window": "Oddzielne okno" + }, + "visualizer-types": { + "linear": "Liniowy", + "mirrored": "Lustrzany", + "wave": "Fala" + }, + "visualizer-visibility": { + "with-background": "Tylko z tล‚em" + }, + "workspace-labels": { + "index": "Indeks", + "index-and-name": "Indeks i nazwa", + "name": "Nazwa" + } + }, + "panels": { + "about": { + "become-supporter": "Zostaล„ wspierajฤ…cym", + "changelog": "Zobacz dziennik zmian", + "changelog-on-startup": "Pokaลผ dziennik zmian po aktualizacji", + "changelog-on-startup-desc": "Automatycznie pokazuj dziennik zmian po aktualizacji Noctalia.", + "contributors-description": "Podziฤ™kowania dla naszego {count} niesamowitego wspรณล‚twรณrcy!", + "contributors-description-plural": "Podziฤ™kowania dla naszych {count} niesamowitych wspรณล‚twรณrcรณw!", + "copy-info": "Kopiuj informacje", + "debug-disabled": "Tryb debugowania wyล‚ฤ…czony", + "debug-enabled": "Tryb debugowania wล‚ฤ…czony", + "info-copied": "Informacje skopiowane do schowka", + "noctalia-available": "Dostฤ™pne:", + "noctalia-desc": "Elegancka i minimalistyczna powล‚oka pulpitu stworzona dla Wayland, zbudowana w Quickshell.", + "noctalia-git-commit": "Git commit:", + "noctalia-installed-version": "Zainstalowana wersja:", + "noctalia-latest-version": "Najnowsza wersja:", + "noctalia-qs-version": "Noctalia QS wersja:", + "noctalia-title": "Noctalia shell", + "privacy-policy": "Polityka prywatnoล›ci", + "support": "Wesprzyj nas", + "supporter-badge": "Wspierajฤ…cy", + "supporters-desc": "Ogromne podziฤ™kowania dla naszego wspaniaล‚ego wspierajฤ…cego!", + "supporters-desc-plural": "Ogromne podziฤ™kowania dla naszych {count} wspaniaล‚ych wspierajฤ…cych!", + "supporters-loading": "ลadowanie wspierajฤ…cych...", + "system-board": "Pล‚yta gล‚รณwna:", + "system-cpu": "Procesor:", + "system-disk": "Dysk:", + "system-gpu": "GPU:", + "system-host": "Gospodarz:", + "system-install-hint": "Zainstaluj fastfetch, aby wyล›wietliฤ‡ informacje o systemie", + "system-kernel": "Jฤ…dro:", + "system-loading": "ลadowanie informacji o systemie...", + "system-memory": "Pamiฤ™ฤ‡:", + "system-monitor": "Monitor:", + "system-not-installed": "fastfetch nie jest zainstalowany", + "system-os": "System operacyjny:", + "system-packages": "Paczki:", + "system-product": "Produkt:", + "system-title": "Informacje o systemie", + "system-uptime": "Czas pracy:", + "system-wm": "WM:", + "telemetry-data-copied": "Dane telemetryczne skopiowane do schowka.", + "telemetry-desc": "Pomรณลผ ulepszyฤ‡ Noctaliฤ™, udostฤ™pniajฤ…c anonimowe informacje o systemie (rozdzielczoล›ฤ‡ ekranu, kompozytor, dystrybucja). Wysyล‚ane raz przy uruchomieniu, bez ล›ledzenia, dane usuwane automatycznie po 30 dniach.", + "telemetry-enabled": "Wyล›lij anonimowe informacje o systemie.", + "telemetry-show-data": "Wyล›wietl dane", + "telemetry-title": "Prywatnoล›ฤ‡", + "title": "O programie", + "up-to-date": "Jesteล› na bieลผฤ…co!", + "update-available": "Dostฤ™pna aktualizacja", + "view-commit": "Wyล›wietl zatwierdzenie na GitHubie" + }, + "audio": { + "devices-desc": "Konfiguruj dostฤ™pne urzฤ…dzenia wejล›ciowe i wyjล›ciowe audio.", + "devices-input-device-description": "Wybierz ลผฤ…dane urzฤ…dzenie wejล›ciowe dลบwiฤ™ku.", + "devices-input-device-label": "Urzฤ…dzenie wejล›ciowe", + "devices-output-device-description": "Wybierz ลผฤ…dane urzฤ…dzenie wyjล›ciowe dลบwiฤ™ku.", + "devices-output-device-label": "Urzฤ…dzenie wyjล›ciowe", + "devices-title": "Urzฤ…dzenia audio", + "external-mixer-description": "Wpisz polecenie lub ล›cieลผkฤ™ do aplikacji, aby uruchomiฤ‡ zewnฤ™trzny mikser dลบwiฤ™ku.", + "external-mixer-label": "Polecenie zewnฤ™trznego miksera", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "Ustaw preferowane i ignorowane aplikacje multimedialne.", + "media-excluded-player-description": "Dodaj sล‚owa kluczowe dla odtwarzaczy, ktรณre system ma ignorowaฤ‡. Kaลผde sล‚owo w nowej linii.", + "media-excluded-player-label": "Wykluczony odtwarzacz", + "media-excluded-player-placeholder": "wpisz fragment i naciล›nij +", + "media-frame-rate-description": "Wyลผsze wartoล›ci sฤ… pล‚ynniejsze, ale zuลผywajฤ… wiฤ™cej zasobรณw.", + "media-frame-rate-label": "Liczba klatek (FPS)", + "media-primary-player-description": "Wpisz sล‚owo kluczowe, aby zidentyfikowaฤ‡ swรณj gล‚รณwny odtwarzacz.", + "media-primary-player-label": "Gล‚รณwny odtwarzacz", + "media-primary-player-placeholder": "np. spotify, vlc, mpv", + "media-scrolling-speed-description": "Czas w sekundach na przewiniฤ™cie tytuล‚u od poczฤ…tku do koล„ca.", + "media-scrolling-speed-label": "Prฤ™dkoล›ฤ‡ przewijania", + "media-scrolling-title-description": "Wล‚ฤ…cz ciฤ…gล‚e przewijanie dล‚ugich tytuล‚รณw mediรณw.", + "media-scrolling-title-label": "Przewijany tytuล‚", + "media-title": "Odtwarzacze mediรณw", + "on-middle-clicked-description": "Komenda do wykonania po klikniฤ™ciu ล›rodkowym przyciskiem myszy.", + "panel-applications-empty": "ลปadna aplikacja nie odtwarza obecnie dลบwiฤ™ku", + "spectrum-mirrored-description": "Odbij spektrum tak, aby niskie czฤ™stotliwoล›ci byล‚y w centrum. Gdy wyล‚ฤ…czone, niskie czฤ™stotliwoล›ci sฤ… po lewej, a wysokie po prawej.", + "spectrum-mirrored-label": "Lustrzane spektrum", + "title": "Audio", + "visualizer-type-description": "Wybierz typ wizualizacji odtwarzania multimediรณw.", + "visualizer-type-label": "Typ wizualizacji", + "volumes-desc": "Dostosuj gล‚oล›noล›ฤ‡ i poziomy dลบwiฤ™ku.", + "volumes-feedback-sound-file-description": "ลšcieลผka do pliku dลบwiฤ™kowego odtwarzanego podczas zmiany gล‚oล›noล›ci.", + "volumes-feedback-sound-file-label": "Plik dลบwiฤ™kowy informacji zwrotnej gล‚oล›noล›ci", + "volumes-feedback-sound-file-select-title": "Wybierz plik dลบwiฤ™kowy informacji zwrotnej o gล‚oล›noล›ci", + "volumes-input-volume-description": "Poziom gล‚oล›noล›ci wejล›cia mikrofonu.", + "volumes-mute-input-description": "Wycisz domyล›lne wejล›cie audio (mikrofon).", + "volumes-mute-input-label": "Wycisz wejล›cie audio", + "volumes-mute-output-description": "Wycisz gล‚รณwne wyjล›cie audio systemu.", + "volumes-mute-output-label": "Wycisz wyjล›cie audio", + "volumes-output-volume-description": "Poziom gล‚oล›noล›ci caล‚ego systemu.", + "volumes-step-size-description": "Dostosuj skok zmiany gล‚oล›noล›ci (kรณล‚ko myszy, skrรณty klawiszowe).", + "volumes-step-size-label": "Skok gล‚oล›noล›ci", + "volumes-volume-feedback-description": "Odtwarzaj dลบwiฤ™k zwrotny podczas regulacji gล‚oล›noล›ci.", + "volumes-volume-feedback-label": "Odtwรณrz dลบwiฤ™k sprzฤ™ลผenia zwrotnego gล‚oล›noล›ci", + "volumes-volume-overdrive-description": "Pozwรณl na zwiฤ™kszenie gล‚oล›noล›ci powyลผej 100%. Moลผe nie byฤ‡ obsล‚ugiwane przez kaลผdy sprzฤ™t.", + "volumes-volume-overdrive-label": "Zezwalaj na wzmocnienie gล‚oล›noล›ci (overdrive)" + }, + "bar": { + "appearance-auto-hide-delay-description": "Czas, po ktรณrym Bar zniknie po opuszczeniu go przez mysz.", + "appearance-auto-hide-delay-label": "Opรณลบnienie ukrywania", + "appearance-auto-hide-exclusive-note": "Uwaga: Strefa wyล‚ฤ…czna jest automatycznie wyล‚ฤ…czana, gdy autoukrywanie jest aktywne.", + "appearance-auto-show-delay-description": "Czas, zanim pasek pojawi siฤ™, gdy mysz wejdzie na krawฤ™dลบ.", + "appearance-auto-show-delay-label": "Opรณลบnienie Wyล›wietlania", + "appearance-background-opacity-description": "Ustaw przezroczystoล›ฤ‡ tล‚a konkretnie dla paska.", + "appearance-background-opacity-label": "Krycie tล‚a paska", + "appearance-capsule-color-description": "Wybierz kolor dla kapsuล‚ paska, lub nie uลผywaj ลผadnego dla domyล›lnego koloru powierzchni.", + "appearance-capsule-color-label": "Kolor kapsuล‚y", + "appearance-capsule-opacity-description": "Ustaw poziom przezroczystoล›ci tล‚a widลผetรณw, gdy kapsuล‚a jest widoczna.", + "appearance-capsule-opacity-label": "Przezroczystoล›ฤ‡ kapsuล‚y", + "appearance-content-padding-description": "Dostosuj odstฤ™p miฤ™dzy krawฤ™dziami paska a widลผetami.", + "appearance-content-padding-label": "Odstฤ™p treล›ci", + "appearance-density-description": "Dostosuj odstฤ™py paska dla kompaktowego lub przestronnego wyglฤ…du.", + "appearance-density-label": "Zagฤ™szczenie paska", + "appearance-desc": "Dostosuj wyglฤ…d i pozycjฤ™ paska.", + "appearance-display-mode-description": "Wybierz, kiedy pasek jest widoczny.", + "appearance-enable-exclusion-zone-inset-description": "Zmniejsz strefฤ™ wykluczenia o 1 fizyczny piksel, aby okna przylegajฤ…ce idealnie zachodziล‚y pod krawฤ™dลบ paska.", + "appearance-enable-exclusion-zone-inset-label": "Wewnฤ™trzna strefa wykluczenia", + "appearance-floating-description": "Wyล›wietl pasek jako pล‚ywajฤ…cฤ… 'piguล‚kฤ™'.", + "appearance-floating-label": "Pล‚ywajฤ…cy pasek", + "appearance-font-scale-description": "Dostosuj skalฤ™ rozmiaru czcionki dla tekstu wyล›wietlanego na pasku.", + "appearance-font-scale-label": "Skala czcionki", + "appearance-frame-radius": "Promieล„ wewnฤ™trzny", + "appearance-frame-settings-description": "Dostosuj gruboล›ฤ‡ ramki i wewnฤ™trzny promieล„ naroลผnikรณw", + "appearance-frame-settings-label": "Ustawienia ramki", + "appearance-frame-thickness": "Gruboล›ฤ‡", + "appearance-hide-on-overview-description": "Ukryj pasek i zamknij panele, gdy aktywny jest przeglฤ…d kompozytora.", + "appearance-hide-on-overview-label": "Ukryj pasek w przeglฤ…dzie", + "appearance-margins-description": "Dostosuj marginesy wokรณล‚ pล‚ywajฤ…cego paska.", + "appearance-margins-horizontal": "Margines poziomy", + "appearance-margins-vertical": "Margines pionowy", + "appearance-outer-corners-description": "Wyล›wietl zaokrฤ…glone naroลผniki na zewnฤ…trz paska.", + "appearance-outer-corners-label": "Naroลผniki zewnฤ™trzne", + "appearance-position-description": "Wybierz miejsce umieszczenia paska na ekranie.", + "appearance-position-label": "Pozycja paska", + "appearance-show-capsule-description": "Pokaลผ tล‚a widลผetรณw.", + "appearance-show-capsule-label": "Pokaลผ kapsuล‚ฤ™", + "appearance-show-on-workspace-switch-description": "Automatycznie krรณtko wyล›wietlaj Bar, gdy zmieni siฤ™ obszar roboczy.", + "appearance-show-on-workspace-switch-label": "Pokaลผ pasek przy zmianie obszaru roboczego", + "appearance-show-outline-description": "Wyล›wietla widoczne obramowanie wokรณล‚ kaลผdego widลผetu.", + "appearance-show-outline-label": "Pokaลผ obramowanie widลผetรณw", + "appearance-type-description": "Wybierz styl paska: Prosty, Pล‚ywajฤ…cy lub Obramowany.", + "appearance-type-label": "Typ paska", + "appearance-use-separate-opacity-description": "Umoลผliwia uลผycie oddzielnej wartoล›ci krycia dla tล‚a paska.", + "appearance-use-separate-opacity-label": "Uลผyj osobnej przezroczystoล›ci sล‚upkรณw", + "appearance-widget-spacing-description": "Dostosuj odstฤ™py miฤ™dzy kaลผdym widลผetem na pasku.", + "appearance-widget-spacing-label": "Odstฤ™py miฤ™dzy widลผetami", + "behavior-middle-click-command-description": "Polecenie do wykonania po ล›rodkowym klikniฤ™ciu.", + "behavior-middle-click-command-label": "Polecenie ลšrodkowego Klikniฤ™cia", + "behavior-middle-click-command-placeholder": "niri msg action przeล‚ฤ…cz przeglฤ…d", + "behavior-middle-click-description": "Wybierz, co robi ล›rodkowe klikniฤ™cie na pustych obszarach Paska.", + "behavior-middle-click-follow-mouse-description": "Otwรณrz wybrany panel ล›rodkowego klikniฤ™cia w pozycji kursora.", + "behavior-middle-click-follow-mouse-label": "ลšrodkowe klikniฤ™cie podฤ…ลผa za kursorem", + "behavior-middle-click-label": "Akcja ล›rodkowego klikniฤ™cia Paska", + "behavior-right-click-command-description": "Polecenie do wykonania po klikniฤ™ciu prawym przyciskiem.", + "behavior-right-click-command-label": "Polecenie Prawego Przycisku Myszy", + "behavior-right-click-command-placeholder": "notify-send \"Prawy przycisk myszy\"", + "behavior-right-click-description": "Wybierz, co robi prawy przycisk myszy na pustych obszarach paska.", + "behavior-right-click-follow-mouse-description": "Otwรณrz wybrany panel prawego przycisku myszy w pozycji kursora.", + "behavior-right-click-follow-mouse-label": "Prawy przycisk myszy podฤ…ลผa za kursorem", + "behavior-right-click-label": "Akcja prawego przycisku myszy na Pasku", + "behavior-wheel-wrap-description": "Po wล‚ฤ…czeniu przewijanie kontynuuje siฤ™ od ostatniego elementu do pierwszego.", + "behavior-wheel-wrap-label": "Cyklicznie", + "behavior-workspace-scroll-description": "Wybierz, co robi kรณล‚ko myszy na pustych obszarach paska.", + "behavior-workspace-scroll-label": "Akcja kรณล‚ka myszy Paska", + "behavior-workspace-scroll-option-content": "Treล›ฤ‡", + "behavior-workspace-scroll-option-workspace": "Obszar roboczy", + "monitor-configure-widgets": "Skonfiguruj widลผety", + "monitor-override-settings": "Zastฤ…p ustawienia globalne", + "monitor-override-settings-description": "Uลผyj niestandardowych ustawieล„ dla tego monitora.", + "monitor-reset-all": "Zresetuj wszystko", + "monitor-widgets-title": "Konfiguracja widgetu - {monitor}", + "monitors-desc": "Pokaลผ pasek na konkretnych monitorach. Domyล›lnie na wszystkich, jeล›li ลผaden nie zostanie wybrany.", + "monitors-desc-new": "Skonfiguruj, ktรณre monitory wyล›wietlajฤ… Bar i dostosuj ustawienia dla kaลผdego monitora.", + "monitors-title": "Wyล›wietlanie na monitorach", + "title": "Pasek", + "tray-blacklist-description": "Dodaj reguล‚y wykluczenia zasobnika, obsล‚uguje maski (*).", + "tray-blacklist-label": "Czarna lista", + "tray-blacklist-placeholder": "np. nm-applet, Fcitx*", + "tray-pin-application": "Przypnij aplikacjฤ™", + "tray-unpin-application": "Odepnij aplikacjฤ™", + "use-global-widgets": "Uลผywaj globalnych widลผetรณw", + "widgets-desc": "Przeciฤ…gnij widลผety, aby zmieniฤ‡ ich kolejnoล›ฤ‡. Uลผyj menu pod prawym przyciskiem, aby przenosiฤ‡ lub usuwaฤ‡ widลผety.", + "widgets-title": "Pozycjonowanie widลผetรณw" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "Generuj schematy kolorรณw z tapety. Automatycznie wyodrฤ™bnia kolory, aby stworzyฤ‡ spรณjny motyw.", + "color-source-use-wallpaper-colors-label": "Uลผyj kolorรณw tapety", + "dark-mode-mode-description": "Wล‚ฤ…cza automatyczne przeล‚ฤ…czanie miฤ™dzy trybem jasnym a ciemnym.", + "dark-mode-mode-label": "Harmonogram trybu ciemnego", + "dark-mode-mode-manual": "Rฤ™cznie", + "dark-mode-mode-off": "Wyล‚ฤ…czony", + "dark-mode-switch-description": "Przeล‚ฤ…cza na ciemniejszy motyw dla ล‚atwiejszego oglฤ…dania w nocy.", + "delete-error-description": "Nie udaล‚o siฤ™ usunฤ…ฤ‡ {scheme}", + "delete-error-title": "Usuwanie nieudane", + "delete-success-description": "Pomyล›lnie usuniฤ™to {scheme}", + "delete-success-title": "Schemat kolorรณw usuniฤ™ty", + "download-button": "Pobierz wiฤ™cej", + "download-downloading": "Pobieranie...", + "download-empty": "Brak dostฤ™pnych schematรณw kolorรณw", + "download-error-api-error": "Bล‚ฤ…d API: {status}", + "download-error-description": "Nie udaล‚o siฤ™ pobraฤ‡ {scheme}", + "download-error-download-failed": "Pobieranie nieudane z kodem: {code}", + "download-error-invalid-response": "Nieprawidล‚owy format odpowiedzi API", + "download-error-no-files": "Nie znaleziono plikรณw dla schematu", + "download-error-parse-failed": "Bล‚ฤ…d przetwarzania odpowiedzi API: {error}", + "download-error-rate-limit": "Przekroczono limit zapytaล„ GitHub API", + "download-error-title": "Pobieranie nieudane", + "download-fetching": "Pobieranie dostฤ™pnych schematรณw kolorรณw...", + "download-success-description": "Pomyล›lnie pobrano {scheme}", + "download-success-title": "Schemat kolorรณw pobrany", + "download-title": "Pobierz schematy kolorรณw", + "method-description": { + "content": "Schemat Material Design z ekstrakcjฤ… kolorรณw o wysokiej wiernoล›ci, ktรณra ล›ciล›le odpowiada rzeczywistym kolorom treล›ci ลบrรณdล‚owej.", + "dysfunctional": "Jak Faithful, ale wybiera drugฤ… najbardziej dominujฤ…cฤ… rodzinฤ™ kolorรณw jako gล‚รณwnฤ….", + "faithful": "Prรณbuje pozostaฤ‡ blisko koloru ลบrรณdล‚owego, jednoczeล›nie generujฤ…c harmonijnฤ… paletฤ™.", + "fruit-salad": "Schemat Material Design, ktรณry tworzy radosnฤ…, ลผywฤ… paletฤ™ z rรณลผnorodnymi odcieniami.", + "monochrome": "Schemat Material Design wykorzystujฤ…cy jednobarwnฤ… skalฤ™ szaroล›ci z minimalnฤ… zawartoล›ciฤ… chromatycznฤ….", + "muted": "Generuje bardziej miฤ™kkie, odbarwione tony dla spokojnego i stonowanego wyglฤ…du.", + "rainbow": "Schemat Material Design, ktรณre generuje kolory w caล‚ym spektrum, aby uzyskaฤ‡ maksymalnฤ… rรณลผnorodnoล›ฤ‡ kolorรณw.", + "tonal-spot": "Domyล›lny algorytm Material Design. Tworzy zbalansowane palety, wykorzystujฤ…c tonalne wariacje koloru ลบrรณdล‚owego.", + "vibrant": "Maksymalizuje nasycenie kolorรณw dla odwaลผnych, przyciฤ…gajฤ…cych wzrok palet o ลผywych tonach." + }, + "predefined-desc": "Wybierz z kolekcji predefiniowanych schematรณw kolorรณw.", + "predefined-generate-templates-label": "Generuj szablony dla predefiniowanych schematรณw", + "predefined-title": "Predefiniowane schematy kolorรณw", + "sync-gsettings-description": "Dopasuj motyw systemowy do aktywnego wariantu jasnego lub ciemnego.", + "sync-gsettings-label": "Synchronizuj motyw systemowy", + "templates-desc": "Zastosuj kolory w aplikacjach zewnฤ™trznych.", + "templates-filter-description": "Pokaลผ szablony z okreล›lonej kategorii.", + "templates-filter-label": "Filtruj wedล‚ug kategorii", + "templates-misc-description": "Twรณrz wล‚asne szablony.", + "templates-misc-label": "Zaawansowane", + "templates-misc-user-templates-description": "Wล‚ฤ…cz tylko jeล›li wiesz co robisz โ€” zapoznaj siฤ™ z dokumentacjฤ… online.", + "templates-misc-user-templates-label": "Wล‚ฤ…cz szablony uลผytkownika", + "templates-none-detected": "Nie wykryto", + "templates-write-path": "Zapisuje: {filepath}", + "title": "Schemat kolorรณw", + "wallpaper-method-description": "Wybierz swojฤ… ulubionฤ… metodฤ™ generowania palety.", + "wallpaper-method-label": "Metoda generowania palety", + "wallpaper-monitor-source-description": "Wybierz monitor, ktรณry ma byฤ‡ uลผywany do wydobywania kolorรณw tapety.", + "wallpaper-monitor-source-label": "ลนrรณdล‚o generowania kolorรณw" + }, + "connections": { + "authentication-required": "Wymagana autentykacja", + "bluetooth-auto-connect-description": "Automatycznie ล‚ฤ…cz siฤ™ z zaufanymi sparowanymi urzฤ…dzeniami, gdy Bluetooth jest wล‚ฤ…czony.", + "bluetooth-auto-connect-label": "Automatyczne ล‚ฤ…czenie sparowanych urzฤ…dzeล„", + "bluetooth-devices-unnamed": "Nienazwane urzฤ…dzenia nie sฤ… wyล›wietlane.", + "bluetooth-discoverable": "To urzฤ…dzenie jest wykrywalne jako {hostName}, dopรณki ta zakล‚adka ustawieล„ jest otwarta.", + "bluetooth-rssi-polling-description": "Okresowo prรณbkuje RSSI dla podล‚ฤ…czonych urzฤ…dzeล„ za poล›rednictwem bluetoothctl. Moลผe nie byฤ‡ dostฤ™pne dla wszystkich urzฤ…dzeล„; zuลผywa minimalne zasoby po wล‚ฤ…czeniu.", + "bluetooth-rssi-polling-interval-description": "Skonfiguruj, jak czฤ™sto aktualizowaฤ‡ siล‚ฤ™ sygnaล‚u dla podล‚ฤ…czonych urzฤ…dzeล„.", + "bluetooth-rssi-polling-interval-label": "Interwaล‚ odpytywania", + "bluetooth-rssi-polling-label": "Odpytywanie sygnaล‚u Bluetooth", + "disable-discoverability-description": "Ukryj swoje urzฤ…dzenie przed pobliskimi urzฤ…dzeniami Bluetooth.", + "disable-discoverability-label": "Wyล‚ฤ…cz widocznoล›ฤ‡ urzฤ…dzenia", + "hide-unnamed-devices-description": "Ukryj urzฤ…dzenia, ktรณre pojawiajฤ… siฤ™ tylko jako adresy Bluetooth.", + "hide-unnamed-devices-label": "Ukryj nienazwane urzฤ…dzenia", + "pin-instructions": "Wprowadลบ kod PIN wyล›wietlony na Twoim urzฤ…dzeniu.", + "title": "Poล‚ฤ…czenia", + "wifi-header-text": "Uลผyj Wi-Fi, aby zobaczyฤ‡ dostฤ™pne sieci i poล‚ฤ…czyฤ‡ siฤ™ z internetem." + }, + "control-center": { + "cards-desc": "Dostosuj, ktรณre kontrolki pojawiajฤ… siฤ™ w centrum sterowania i w jakiej kolejnoล›ci.", + "desc": "Konfiguruj pozycjonowanie i zachowanie panelu centrum sterowania.", + "open-at-mouse-description": "Po klikniฤ™ciu prawym przyciskiem myszy na pusty obszar Bar, otwรณrz Centrum Sterowania blisko kursora. Gdy wyล‚ฤ…czone, zamiast tego uลผywane jest powyลผsze ustawienie pozycji.", + "open-at-mouse-label": "Otwรณrz w miejscu kursora po prawym klikniฤ™ciu", + "position-description": "Wybierz, gdzie pojawia siฤ™ panel centrum sterowania po otwarciu.", + "shortcuts-custom-button-command-description": "Polecenie do wykonania po klikniฤ™ciu przycisku.", + "shortcuts-custom-button-enable-on-state-logic-description": "Wล‚ฤ…cz drugฤ… ikonฤ™ i stan 'aktywny' na podstawie polecenia sprawdzajฤ…cego.", + "shortcuts-custom-button-enable-on-state-logic-label": "Wล‚ฤ…cz logikฤ™ stanu 'on'", + "shortcuts-custom-button-icon-description": "Wybierz ikonฤ™ z biblioteki.", + "shortcuts-custom-button-on-clicked-label": "Polecenie lewego klikniฤ™cia", + "shortcuts-custom-button-on-middle-clicked-description": "Polecenie do wykonania po klikniฤ™ciu ล›rodkowym przyciskiem myszy.", + "shortcuts-custom-button-on-state-command-description": "Polecenie sprawdzajฤ…ce, czy przycisk powinien byฤ‡ w stanie 'on'. Zwraca 0 dla on, inne dla off.", + "shortcuts-custom-button-on-state-command-label": "Polecenie sprawdzajฤ…ce stan 'on'", + "shortcuts-custom-button-on-state-icon-description": "Ikona przycisku, gdy jest on w stanie 'on'.", + "shortcuts-custom-button-on-state-icon-label": "Ikona stanu 'on'", + "shortcuts-custom-button-state-checks-add": "Dodaj sprawdzanie stanu", + "shortcuts-custom-button-state-checks-command": "Polecenie do wykonania dla tego sprawdzenia stanu", + "shortcuts-custom-button-state-checks-label": "Sprawdzanie stanu", + "shortcuts-custom-button-state-checks-remove": "Usuล„", + "shortcuts-custom-button-tooltip-description": "Podpowiedลบ wyล›wietlana po najechaniu na przycisk.", + "shortcuts-custom-button-tooltip-label": "Podpowiedลบ", + "shortcuts-title": "Widลผety skrรณtรณw", + "system-monitor-disk-path-description": "Wybierz, ktรณry punkt montowania dysku ma byฤ‡ monitorowany przez kartฤ™ monitora systemu w centrum sterowania.", + "system-monitor-disk-path-label": "ลšcieลผka dysku monitora systemu", + "title": "Centrum sterowania" + }, + "desktop-widgets": { + "clock-enabled-description": "Pokaลผ widลผet zegara na pulpicie.", + "clock-enabled-label": "Wล‚ฤ…cz widลผet zegara", + "clock-format-label": "Format", + "clock-height-description": "Wysokoล›ฤ‡ widลผetu zegara w pikselach.", + "clock-height-label": "Wysokoล›ฤ‡", + "clock-minimal-mode-description": "Uลผyj minimalistycznego zegara z czasem i datฤ….", + "clock-minimal-mode-label": "Tryb minimalistyczny", + "clock-rounded-corners-description": "Uลผyj zaokrฤ…glonych rogรณw dla tล‚a widลผetu.", + "clock-rounded-corners-label": "Zaokrฤ…glone rogi", + "clock-show-background-description": "Pokaลผ tล‚o dla widลผetu zegara.", + "clock-show-background-label": "Pokaลผ tล‚o", + "clock-show-date-description": "Wyล›wietl aktualnฤ… datฤ™ poniลผej godziny.", + "clock-show-date-label": "Pokaลผ datฤ™", + "clock-show-seconds-description": "Wyล›wietl sekundy.", + "clock-show-seconds-label": "Pokaลผ sekundy", + "clock-style-analog": "Analogowy", + "clock-style-binary": "Binarny", + "clock-style-description": "Wybierz styl wyล›wietlania zegara.", + "clock-style-digital": "Cyfrowy", + "clock-style-label": "Styl zegara", + "clock-style-minimal": "Minimalistyczny", + "clock-width-description": "Szerokoล›ฤ‡ widลผetu zegara w pikselach.", + "cpu-intensive-note": "Widgety oznaczone ! zuลผywajฤ… wiฤ™cej CPU i powinny byฤ‡ wล‚ฤ…czane z rozwagฤ….", + "edit-mode-button-label": "Wejdลบ w tryb edycji", + "edit-mode-controls-explanation": "Lewy przycisk myszy: Przesuล„ widลผet.\nPrawy przycisk myszy: Zmieล„ rozmiar widลผetu.", + "edit-mode-description": "Wล‚ฤ…cz tryb edycji, aby przesuwaฤ‡ i zmieniaฤ‡ pozycjฤ™ widลผetรณw pulpitu. Gdy wล‚ฤ…czone, widลผety pokazujฤ… ramkฤ™ przeciฤ…gania.", + "edit-mode-exit-button": "Wyjdลบ z trybu edycji", + "edit-mode-grid-snap-label": "Przyciฤ…gaj do siatki", + "edit-mode-grid-snap-scale-label": "Skala przyciฤ…gania do siatki", + "edit-mode-label": "Tryb edycji", + "enabled-description": "Wล‚ฤ…cz lub caล‚kowicie wyล‚ฤ…cz widลผety pulpitu.", + "enabled-label": "Wล‚ฤ…cz widลผety pulpitu", + "general-desc": "Konfiguruj widลผety pojawiajฤ…ce siฤ™ na pulpicie.", + "general-title": "Widลผety pulpitu", + "media-player-enabled-description": "Pokaลผ widลผet odtwarzacza mediรณw na pulpicie.", + "media-player-enabled-label": "Wล‚ฤ…cz widลผet odtwarzacza", + "media-player-rounded-corners-description": "Wล‚ฤ…cz zaokrฤ…glone rogi na krawฤ™dziach widลผetu.", + "media-player-show-album-art-description": "Pokaลผ okล‚adkฤ™ albumu i informacje o utworach (tytuล‚ i wykonawca).", + "media-player-show-album-art-label": "Pokaลผ okล‚adkฤ™ albumu i tytuล‚", + "media-player-show-background-description": "Pokaลผ tล‚o dla widลผetu odtwarzacza.", + "media-player-show-buttons-description": "Pokaลผ przyciski sterowania multimediami (odtwarzanie/pauza, poprzedni, nastฤ™pny) na widลผecie odtwarzacza multimedialnego.", + "media-player-show-buttons-label": "Pokaลผ przyciski", + "media-player-show-visualizer-description": "Pokaลผ nakล‚adkฤ™ wizualizatora audio.", + "media-player-visualizer-type-description": "Wybierz typ wizualizacji dla tล‚a odtwarzacza pulpitu.", + "overview-enabled-description": "Pokaลผ widลผety pulpitu, gdy przeglฤ…d kompozytora jest otwarty.", + "overview-enabled-label": "Pokaลผ w przeglฤ…dzie", + "system-stat-layout-bottom": "Dรณล‚", + "system-stat-layout-description": "Wybierz, jak legenda jest wyล›wietlana wzglฤ™dem wykresu.", + "system-stat-layout-label": "Ukล‚ad", + "system-stat-layout-side": "Strona", + "system-stat-rounded-corners-description": "Uลผyj zaokrฤ…glonych rogรณw dla tล‚a widลผetu.", + "system-stat-rounded-corners-label": "Zaokrฤ…glone Naroลผniki", + "system-stat-show-background-description": "Pokaลผ kontener tล‚a dla widลผetu statystyk systemowych.", + "system-stat-show-background-label": "Pokaลผ tล‚o", + "system-stat-stat-type-description": "Wybierz, ktรณrฤ… statystykฤ™ systemowฤ… wyล›wietliฤ‡.", + "system-stat-stat-type-label": "Typ Statystyki", + "title": "Widลผety pulpitu", + "weather-enabled-description": "Pokaลผ widลผet pogody na pulpicie.", + "weather-enabled-label": "Wล‚ฤ…cz widลผet pogody", + "weather-show-background-description": "Pokaลผ tล‚o dla widลผetu pogody." + }, + "display": { + "monitors-backlight-device-auto-option": "Domyล›lny", + "monitors-backlight-device-description": "Wybierz urzฤ…dzenie podล›wietlenia dla tego wyjล›cia.", + "monitors-backlight-device-label": "Urzฤ…dzenie Podล›wietlenia", + "monitors-brightness-step-description": "Dostosuj skok zmiany jasnoล›ci (kรณล‚ko myszy i skrรณty klawiszowe).", + "monitors-brightness-step-label": "Skok jasnoล›ci", + "monitors-brightness-unavailable-ddc-disabled": "Sterowanie jasnoล›ciฤ… niedostฤ™pne. Wล‚ฤ…cz \"Obsล‚uga zewnฤ™trznej jasnoล›ci\", aby sterowaฤ‡ jasnoล›ciฤ… tego ekranu.", + "monitors-brightness-unavailable-generic": "Sterowanie jasnoล›ciฤ… nie jest dostฤ™pne dla tego ekranu.", + "monitors-desc": "Dostosuj jasnoล›ฤ‡ dla kaลผdego ekranu.", + "monitors-enforce-minimum-description": "Rozwiฤ…zuje problem caล‚kowitego wyล‚ฤ…czania podล›wietlenia na niektรณrych ekranach przy 0% jasnoล›ci.", + "monitors-enforce-minimum-label": "Wymuล› minimalnฤ… jasnoล›ฤ‡ (1%)", + "monitors-external-brightness-description": "Wล‚ฤ…cz obsล‚ugฤ™ DDCUtil dla sterowania jasnoล›ciฤ… zewnฤ™trznych monitorรณw przez protokรณล‚ DDC/CI.", + "monitors-external-brightness-label": "Obsล‚uga zewnฤ™trznej jasnoล›ci", + "monitors-global-brightness-description": "Dostosuj jasnoล›ฤ‡ wszystkich monitorรณw jednoczeล›nie.", + "monitors-global-brightness-label": "Wszystkie monitory", + "monitors-title": "Ustawienia per-monitor", + "night-light-auto-schedule-description": "Oparte na czasie zachodu i wschodu sล‚oล„ca w {location} โ€” zalecane.", + "night-light-auto-schedule-label": "Automatyczny harmonogram", + "night-light-desc": "Zredukuj emisjฤ™ niebieskiego ล›wiatล‚a, aby lepiej spaฤ‡ i mniej mฤ™czyฤ‡ oczy.", + "night-light-enable-description": "Zastosuj ciepล‚y filtr kolorรณw, aby zredukowaฤ‡ emisjฤ™ niebieskiego ล›wiatล‚a.", + "night-light-enable-label": "Wล‚ฤ…cz podล›wietlenie nocne", + "night-light-force-activation-description": "Ignoruje harmonogram i natychmiast stosuje filtr nocny.", + "night-light-force-activation-label": "Wymuล› aktywacjฤ™", + "night-light-manual-schedule-description": "Ustaw wล‚asne godziny wschodu i zachodu sล‚oล„ca.", + "night-light-manual-schedule-label": "Rฤ™czny harmonogram", + "night-light-manual-schedule-select-start": "Wybierz czas rozpoczฤ™cia", + "night-light-manual-schedule-select-stop": "Wybierz czas zakoล„czenia", + "night-light-manual-schedule-sunrise": "Godzina wschodu", + "night-light-manual-schedule-sunset": "Godzina zachodu", + "night-light-temperature-day": "Dzieล„", + "night-light-temperature-day-description": "Kontroluje temperaturฤ™ barwowฤ… w ciฤ…gu dnia.", + "night-light-temperature-description": "Ustaw ciepล‚o barw dla nocy i dnia.", + "night-light-temperature-label": "Temperatura barwowa", + "night-light-temperature-night": "Noc", + "night-light-temperature-night-description": "Kontroluje temperaturฤ™ barwowฤ… w nocy.", + "title": "Ekran" + }, + "dock": { + "appearance-background-opacity-description": "Dostosuj przezroczystoล›ฤ‡ tล‚a doku.", + "appearance-border-radius-description": "Dostosuj zaokrฤ…glenie krawฤ™dzi doku.", + "appearance-border-radius-label": "Promieล„ naroลผnikรณw", + "appearance-colorize-icons-description": "Zastosuj kolory motywu do ikon aplikacji w doku (tylko dla nieaktywnych aplikacji).", + "appearance-colorize-icons-label": "Koloryzuj ikony", + "appearance-dead-opacity-description": "Dostosuj przezroczystoล›ฤ‡ ikon aplikacji, ktรณre nie sฤ… uruchomione.", + "appearance-dead-opacity-label": "Przezroczystoล›ฤ‡ nieaktywnych", + "appearance-desc": "Dostosuj zachowanie i wyglฤ…d doku.", + "appearance-display-auto-hide": "Auto-ukrywanie", + "appearance-display-description": "Wybierz, jak ma zachowywaฤ‡ siฤ™ dok.", + "appearance-display-exclusive": "Ekskluzywny", + "appearance-dock-indicator-description": "Pokaลผ maล‚y wskaลบnik, gdy dock jest ukryty.", + "appearance-dock-indicator-label": "Wskaลบnik docka", + "appearance-floating-distance-description": "Ustaw odlegล‚oล›ฤ‡ miฤ™dzy dokiem a krawฤ™dziฤ… ekranu.", + "appearance-floating-distance-label": "Odlegล‚oล›ฤ‡ pล‚ywania doku", + "appearance-group-apps-description": "Grupuj wiele okien z tej samej aplikacji w jeden wpis Docka.", + "appearance-group-apps-label": "Grupuj te same aplikacje", + "appearance-group-click-action-cycle": "Przeล‚ฤ…czaj okna", + "appearance-group-click-action-description": "Wybierz, co robi lewe klikniฤ™cie dla zgrupowanych aplikacji.", + "appearance-group-click-action-label": "Akcja klikniฤ™cia zgrupowanej aplikacji", + "appearance-group-click-action-list": "Otwรณrz listฤ™ okien", + "appearance-group-context-menu-mode-description": "Wybierz, jak zachowuje siฤ™ menu kontekstowe dla zgrupowanych aplikacji.", + "appearance-group-context-menu-mode-extended": "Rozszerzony", + "appearance-group-context-menu-mode-label": "Tryb pogrupowanego menu aplikacji", + "appearance-group-context-menu-mode-list": "Lista okien", + "appearance-group-indicator-style-description": "Wybierz, jak zgrupowane wskaลบniki uruchomionych aplikacji wyล›wietlajฤ… stan aktywnego okna.", + "appearance-group-indicator-style-dots": "Kropki", + "appearance-group-indicator-style-label": "Styl zgrupowanego wskaลบnika", + "appearance-group-indicator-style-number": "Numer", + "appearance-hide-show-speed-description": "Dostosuj prฤ™dkoล›ฤ‡ animacji ukrywania/pokazywania doku.", + "appearance-hide-show-speed-label": "Prฤ™dkoล›ฤ‡ ukrywania/pokazywania", + "appearance-icon-size-description": "Dostosuj ogรณlny rozmiar doku.", + "appearance-icon-size-label": "Rozmiar doku", + "appearance-inactive-indicators-description": "Wyล›wietlaj wskaลบniki dla wszystkich uruchomionych aplikacji, nie tylko dla aktywnej.", + "appearance-inactive-indicators-label": "Wskaลบniki uruchomionych", + "appearance-indicator-color-description": "Wybierz kolor indykatora ukrytego doku.", + "appearance-indicator-color-label": "Kolor indykatora", + "appearance-indicator-opacity-description": "Dostosuj przezroczystoล›ฤ‡ indykatora ukrytego doku.", + "appearance-indicator-opacity-label": "Przezroczystoล›ฤ‡ indykatora", + "appearance-indicator-thickness-description": "Wล‚ฤ…cz grubszy indykator ukrytego doku (6 px zamiast 3 px).", + "appearance-indicator-thickness-label": "Grubszy indykator", + "appearance-launcher-icon-description": "Wybierz ikonฤ™ uลผywanฤ… dla Dock Launchera.", + "appearance-launcher-icon-label": "Ikona Launchera", + "appearance-launcher-position-description": "Wybierz, gdzie ikona programu uruchamiajฤ…cego pojawi siฤ™ w docku.", + "appearance-launcher-position-end": "Koniec", + "appearance-launcher-position-label": "Pozycja uruchamiacza", + "appearance-launcher-position-start": "Start", + "appearance-launcher-use-distro-logo-description": "Uลผyj logo swojej dystrybucji jako ikony Dock Launcher.", + "appearance-launcher-use-distro-logo-label": "Uลผyj logo dystrybucji zamiast ikony", + "appearance-pinned-static-description": "Zawsze spychaj przypiฤ™te aplikacje na lewo w staล‚ej kolejnoล›ci.", + "appearance-pinned-static-label": "Statyczne przypiฤ™te aplikacje", + "appearance-position-description": "Wybierz, gdzie dok ma siฤ™ pojawiaฤ‡ na ekranie.", + "appearance-position-label": "Pozycja", + "appearance-show-launcher-icon-description": "Pokaลผ ikonฤ™ programu uruchamiajฤ…cego aplikacje w docku.", + "appearance-show-launcher-icon-label": "Pokaลผ launcher aplikacji", + "appearance-sit-on-frame-description": "Wyrรณwnaj dock wewnฤ…trz ramki, zamiast umieszczaฤ‡ go na wierzchu.", + "appearance-sit-on-frame-label": "Dock znajduje siฤ™ na ramce", + "appearance-type-attached": "Zadokowany", + "appearance-type-description": "Wybierz pomiฤ™dzy pล‚ywajฤ…cฤ… piguล‚kฤ… a statycznym paskiem przymocowanym do krawฤ™dzi.", + "appearance-type-floating": "Pล‚ywajฤ…cy", + "appearance-type-label": "Styl docka", + "enabled-description": "Pokaลผ lub caล‚kowicie ukryj dok.", + "enabled-label": "Wล‚ฤ…cz dok", + "monitors-desc": "Pokaลผ dok na konkretnych monitorach. Domyล›lnie na wszystkich, jeล›li ลผaden nie zostanie wybrany.", + "monitors-only-same-monitor-description": "Pokaลผ tylko aplikacje z monitora, na ktรณrym znajduje siฤ™ dok.", + "monitors-only-same-monitor-label": "Tylko aplikacje z tego samego monitora", + "monitors-title": "Wyล›wietlanie na monitorach", + "title": "Dok" + }, + "general": { + "copy-settings": "Kopiuj ustawienia", + "fonts-default-description": "Gล‚รณwna czcionka uลผywana w caล‚ym interfejsie.", + "fonts-default-label": "Czcionka domyล›lna", + "fonts-default-placeholder": "Wybierz czcionkฤ™ domyล›lnฤ…...", + "fonts-default-scale-description": "Zwiฤ™ksz lub zmniejsz rozmiar standardowego tekstu.", + "fonts-default-scale-label": "Rozmiar czcionki domyล›lnej", + "fonts-default-search-placeholder": "Szukaj czcionki...", + "fonts-desc": "Wybierz czcionki uลผywane w interfejsie.", + "fonts-monospace-description": "Czcionka o staล‚ej szerokoล›ci uลผywana do liczb i statystyk.", + "fonts-monospace-label": "Czcionka monospace", + "fonts-monospace-placeholder": "Wybierz czcionkฤ™ monospace...", + "fonts-monospace-scale-description": "Zwiฤ™ksz lub zmniejsz rozmiar tekstu o staล‚ej szerokoล›ci.", + "fonts-monospace-scale-label": "Rozmiar czcionki monospace", + "fonts-monospace-search-placeholder": "Szukaj czcionki monospace...", + "fonts-title": "Czcionki", + "keybinds-conflict-description": "Kombinacja klawiszy jest juลผ przypisana do {action}.", + "keybinds-conflict-title": "Konflikt skrรณtรณw klawiszowych", + "keybinds-description": "Skonfiguruj globalne klawisze nawigacyjne dla paneli i launchera.", + "keybinds-down": "Przenieล› w dรณล‚", + "keybinds-enter": "Potwierdลบ / Akcja", + "keybinds-escape": "Zamknij / Wstecz", + "keybinds-left": "Przesuล„ w lewo", + "keybinds-modifier-description": "Skrรณty muszฤ… zawieraฤ‡ klawisz modyfikujฤ…cy (Ctrl lub Alt).", + "keybinds-modifier-title": "Wymagany modyfikator", + "keybinds-remove": "Usuล„ / Skasuj", + "keybinds-right": "Przesuล„ w prawo", + "keybinds-title": "Skrรณty klawiszowe nawigacji", + "keybinds-up": "Przenieล› w gรณrฤ™", + "language-desc": "Wybierz preferowany jฤ™zyk aplikacji.", + "language-select-auto-detect": "Automatyczny", + "language-select-description": "Wybierz jฤ™zyk uลผywany w interfejsie aplikacji.", + "language-select-label": "Jฤ™zyk aplikacji", + "launch-setup-wizard": "Kreator konfiguracji", + "profile-desc": "Edytuj dane uลผytkownika i awatar.", + "profile-picture-description": "Twoje zdjฤ™cie profilowe pojawiajฤ…ce siฤ™ w interfejsie.", + "profile-picture-label": "Zdjฤ™cie profilowe", + "profile-select-avatar": "Wybierz obraz awatara", + "profile-title": "Profil", + "profile-tooltip": "Zdjฤ™cie profilowe", + "reverse-scrolling-description": "Odwrรณฤ‡ zinterpretowany kierunek przewijania.", + "reverse-scrolling-label": "Odwrรณฤ‡ przewijanie", + "screen-corners-desc": "Dostosuj zaokrฤ…glenia krawฤ™dzi ekranu i efekty wizualne.", + "screen-corners-radius-description": "Dostosuj zaokrฤ…glenie naroลผnikรณw ekranu.", + "screen-corners-radius-label": "Promieล„ naroลผnikรณw ekranu", + "screen-corners-radius-reset": "Resetuj promieล„ naroลผnikรณw", + "screen-corners-show-corners-description": "Wyล›wietlaj zaokrฤ…glone naroลผniki na krawฤ™dzi ekranu.", + "screen-corners-show-corners-label": "Pokaลผ naroลผniki ekranu", + "screen-corners-solid-black-description": "Uลผyj jednolitej czerni zamiast koloru tล‚a paska.", + "screen-corners-solid-black-label": "Jednolicie czarne naroลผniki", + "screen-corners-title": "Naroลผniki ekranu", + "settings-copied": "Ustawienia skopiowane do schowka", + "smooth-scrolling-description": "Animuj przewijanie list, aby uzyskaฤ‡ pล‚ynniejsze wraลผenia z kรณล‚ka.", + "smooth-scrolling-label": "Pล‚ynne przewijanie", + "tab-basics": "Podstawy", + "tab-keybinds": "Skrรณty klawiszowe" + }, + "hooks": { + "color-generation-description": "Polecenie do wykonania po zakoล„czeniu generowania kolorรณw (np. po motywowaniu na podstawie tapety).", + "color-generation-label": "Kolory wygenerowane", + "color-generation-placeholder": "np. notify-send \"Kolory\" \"Wygenerowano\"", + "info-command-info-description": "โ€ข Polecenia sฤ… wykonywane przez powล‚okฤ™ (sh -lc)
โ€ข Polecenia dziaล‚ajฤ… w tle (detached)
โ€ข Przyciski testowe wykonujฤ… siฤ™ z aktualnymi wartoล›ciami", + "info-parameters-description": "โ€ข Wallpaper hook: $1 = ล›cieลผka tapety, $2 = nazwa ekranu, $3 = motyw (ciemny/jasny)
โ€ข Theme toggle hook: $1 = true/false (stan trybu ciemnego)
โ€ข Screen lock/unlock hooks: $1 = lock/unlock (stan blokady ekranu)
โ€ข Performance mode hooks: Brak parametrรณw
โ€ข Session hook: $1 = action (shutdown/reboot)", + "info-parameters-label": "Dostฤ™pne parametry", + "noctalia-started-description": "Polecenie do wykonania po zakoล„czeniu ล‚adowania Noctalii.", + "noctalia-started-label": "Noctalia uruchomiona", + "noctalia-started-placeholder": "np. notify-send 'Noctalia zaล‚adowany'", + "performance-mode-disabled-description": "Komenda do wykonania, gdy tryb wydajnoล›ci Noctalia zostanie wyล‚ฤ…czony.", + "performance-mode-disabled-label": "Tryb wydajnoล›ci wyล‚ฤ…czony", + "performance-mode-disabled-placeholder": "np. notify-send \"Wydajnoล›ฤ‡\" \"Tryb wyล‚ฤ…czony\"", + "performance-mode-enabled-description": "Komenda do wykonania, gdy tryb wydajnoล›ci Noctalia jest wล‚ฤ…czony.", + "performance-mode-enabled-label": "Tryb wydajnoล›ci wล‚ฤ…czony", + "performance-mode-enabled-placeholder": "np. notify-send \"Wydajnoล›ฤ‡\" \"Tryb wล‚ฤ…czony\"", + "screen-lock-description": "Polecenie do wykonania, gdy ekran zostanie zablokowany.", + "screen-lock-label": "Ekran zablokowany", + "screen-lock-placeholder": "np. notify-send \"Ekran\" \"Zablokowany\"", + "screen-unlock-description": "Polecenie do wykonania, gdy ekran zostanie odblokowany.", + "screen-unlock-label": "Ekran odblokowany", + "screen-unlock-placeholder": "np. notify-send \"Ekran\" \"Odblokowany\"", + "session-description": "Polecenie do wykonania przed zamkniฤ™ciem lub ponownym uruchomieniem. Otrzymuje typ akcji jako $1 (zamkniฤ™cie/ponowne uruchomienie).", + "session-label": "Koniec sesji", + "session-placeholder": "np. notify-send \"Session\" \"$1\"", + "system-hooks-desc": "Konfiguruj polecenia do wykonania podczas zdarzeล„ systemowych.", + "system-hooks-enable-description": "Wล‚ฤ…cz lub wyล‚ฤ…cz wszystkie polecenia hook.", + "system-hooks-enable-label": "Wล‚ฤ…cz hooks", + "system-hooks-title": "Hooks systemowe", + "theme-changed-description": "Polecenie do wykonania podczas przeล‚ฤ…czania miฤ™dzy trybem ciemnym a jasnym.", + "theme-changed-label": "Zmiana motywu", + "theme-changed-placeholder": "np. notify-send \"Motyw\" \"Przeล‚ฤ…czony\"", + "title": "Hooks", + "wallpaper-changed-description": "Polecenie do wykonania podczas zmiany tapety.", + "wallpaper-changed-label": "Zmiana tapety", + "wallpaper-changed-placeholder": "np. notify-send \"Tapeta\" \"Zmieniona\"" + }, + "idle": { + "custom-add": "Dodaj polecenie", + "custom-description": "Uruchom polecenie shella po okresie bezczynnoล›ci.", + "custom-entry-command": "Polecenie", + "custom-entry-delete": "Usuล„", + "custom-entry-edit": "Edytuj Niestandardowe Polecenie", + "custom-entry-name": "Nazwa", + "custom-entry-name-placeholder": "np. Wyล‚ฤ…cz ล›wiatล‚a", + "custom-entry-new": "Nowe Niestandardowe Polecenie", + "custom-entry-timeout": "Czas bezczynnoล›ci", + "custom-entry-timeout-format": "{count} sekunda", + "custom-entry-timeout-format-plural": "{count} sekund", + "custom-entry-unnamed": "Nienazwana komenda", + "custom-label": "Niestandardowe polecenia bezczynnoล›ci", + "enable-description": "Automatycznie wyล‚ฤ…cz ekran, zablokuj lub uล›pij po okresie bezczynnoล›ci.", + "enable-label": "Wล‚ฤ…cz zarzฤ…dzanie bezczynnoล›ciฤ…", + "fade-duration-description": "Sekundy animacji wygaszania do czerni przed uruchomieniem kaลผdej akcji. Kaลผdy ruch myszy anuluje wygaszanie.", + "fade-duration-label": "Czas trwania zanikania", + "lock-description": "Sekundy bezczynnoล›ci przed aktywacjฤ… ekranu blokady.", + "lock-label": "Zablokuj ekran", + "resume-command-label": "Wznรณw polecenie", + "screen-off-description": "Sekundy bezczynnoล›ci, zanim monitory zostanฤ… wyล‚ฤ…czone.", + "screen-off-label": "Wyล‚ฤ…cz ekran", + "status-description": "Czas bezczynnoล›ci zgล‚oszony przez compositora.", + "status-label": "Czas bezczynnoล›ci", + "suspend-description": "Sekundy bezczynnoล›ci przed wstrzymaniem systemu.", + "tab-behavior": "Zachowanie", + "tab-custom": "Niestandardowy", + "timeouts-description": "Ustaw na 0, aby wyล‚ฤ…czyฤ‡ etap. Limity czasu sฤ… wstrzymane, gdy 'Nie usypiaj' jest aktywny.", + "timeouts-label": "Limity czasu", + "title": "Bezczynnoล›ฤ‡", + "unavailable": "Natywne monitorowanie bezczynnoล›ci nie jest dostฤ™pne na tym Compositorze." + }, + "indicator": { + "default-value": "Domyล›lnie: {value}", + "system-default": "Domyล›lne ustawienia systemowe" + }, + "launcher": { + "clipboard-desc": "Dostฤ™p i zarzฤ…dzanie historiฤ… schowka z poziomu launchera.", + "execute-desc": "Skonfiguruj sposรณb uruchamiania aplikacji.", + "execute-title": "Wykonanie", + "settings-annotation-tool-description": "Komenda do uruchomienia po klikniฤ™ciu przycisku adnotacji w historii schowka. Obraz zostanie przekazany do tej komendy.", + "settings-annotation-tool-label": "Narzฤ™dzie do adnotacji", + "settings-annotation-tool-placeholder": "np. 'gradia', 'satty -f -'", + "settings-auto-paste-description": "Automatyczne wklejanie wybranego elementu ze schowka. Wymaga wtype.", + "settings-auto-paste-label": "Automatyczne wklejanie", + "settings-clip-chips-description": "Pokaลผ pasek zakล‚adek, aby filtrowaฤ‡ historiฤ™ schowka wedล‚ug typu (Obrazy, Linki, Pliki, Kod itp.).", + "settings-clip-chips-label": "Wล‚ฤ…cz chipy kategorii", + "settings-clip-preview-description": "Pokaลผ podglฤ…d zawartoล›ci schowka przy uลผyciu polecenia >clip.", + "settings-clip-preview-label": "Wล‚ฤ…cz podglฤ…d schowka", + "settings-clip-smart-icons-description": "Pokazuj specyficzne ikony dla linkรณw, plikรณw, kolorรณw i kodu zamiast ogรณlnej ikony schowka.", + "settings-clip-smart-icons-label": "Wล‚ฤ…cz inteligentne ikony", + "settings-clip-wrap-text-description": "Zawijaj tekst na liล›cie schowka zamiast go obcinaฤ‡.", + "settings-clip-wrap-text-label": "Zawijaj tekst ze schowka", + "settings-clipboard-history-description": "Dostฤ™p do wczeล›niej skopiowanych elementรณw z poziomu launchera.", + "settings-clipboard-history-label": "Wล‚ฤ…cz historiฤ™ schowka", + "settings-clipboard-watch-image-description": "Peล‚ny ciฤ…g poleceล„ przekazywany do wl-paste dla zmian obrazu (wymaga ponownego uruchomienia).", + "settings-clipboard-watch-image-label": "Polecenie Monitorowania Obrazรณw", + "settings-clipboard-watch-text-description": "Peล‚ny ciฤ…g poleceล„ przekazywany do wl-paste dla zmian tekstu (wymaga ponownego uruchomienia).", + "settings-clipboard-watch-text-label": "Polecenie monitorowania tekstu", + "settings-custom-launch-prefix-description": "Poprzedzaj polecenia wล‚asnym prefiksem (np. 'runapp' dla integracji z systemd).", + "settings-custom-launch-prefix-enabled-description": "Uลผywaj wล‚asnego prefiksu do uruchamiania aplikacji zamiast metody domyล›lnej.", + "settings-custom-launch-prefix-enabled-label": "Wล‚ฤ…cz wล‚asny prefiks", + "settings-custom-launch-prefix-label": "Wล‚asny prefiks uruchamiania", + "settings-density-description": "Dostosuj rozmiar ikon aplikacji i gฤ™stoล›ฤ‡ Launchera.", + "settings-density-label": "Gฤ™stoล›ฤ‡", + "settings-desc": "Dostosuj zachowanie i wyglฤ…d launchera.", + "settings-enable-session-search-description": "Pokaลผ akcje sesji (blokowanie, wyล‚ฤ…czanie, ponowne uruchamianie itp.) podczas wyszukiwania w Launcherze.", + "settings-enable-session-search-label": "Wล‚ฤ…cz wyszukiwanie sesji", + "settings-enable-settings-search-description": "Wyล›wietlaj wyniki ustawieล„ podczas wyszukiwania w Launcherze.", + "settings-enable-settings-search-label": "Wล‚ฤ…cz wyszukiwanie w ustawieniach", + "settings-enable-windows-search-description": "Wyszukaj i skup aktywne okna.", + "settings-enable-windows-search-label": "Wล‚ฤ…cz wyszukiwanie okien", + "settings-grid-view-description": "Wyล›wietlaj elementy w ukล‚adzie siatki zamiast listy.", + "settings-icon-mode-description": "Uลผywaj natywnych ikon systemowych zamiast ikon Tabler.", + "settings-icon-mode-label": "Uลผywaj natywnych ikon", + "settings-ignore-mouse-input-description": "Wyล‚ฤ…cz interakcjฤ™ myszy i kรณล‚ko przewijania w launcherze.", + "settings-ignore-mouse-input-label": "Ignoruj dane wejล›ciowe myszy", + "settings-overlay-layer-description": "Wyล›wietl program uruchamiajฤ…cy na warstwie nakล‚adki, nad oknami peล‚noekranowymi. Po wล‚ฤ…czeniu program uruchamiajฤ…cy nie zostanie przypiฤ™ty do paska.", + "settings-overlay-layer-label": "Pokaลผ nad peล‚nym ekranem", + "settings-position-description": "Wybierz, gdzie pojawia siฤ™ panel launchera.", + "settings-show-categories-description": "Pokaลผ karty kategorii do filtrowania aplikacji.", + "settings-show-categories-label": "Pokaลผ kategorie", + "settings-show-icon-background-description": "Pokaลผ zaokrฤ…glone tล‚o prostokฤ…tne za ikonami.", + "settings-show-icon-background-label": "Pokaลผ tล‚o ikony", + "settings-sort-by-usage-description": "Gdy wล‚ฤ…czone, najczฤ™ล›ciej uruchamiane aplikacje pojawiajฤ… siฤ™ na poczฤ…tku listy.", + "settings-sort-by-usage-label": "Sortuj wedล‚ug uลผycia", + "settings-terminal-command-description": "Polecenie uruchamiajฤ…ce terminal. Np. 'kitty -e' lub 'gnome-terminal --'.", + "settings-terminal-command-label": "Polecenie terminala", + "settings-use-app2unit-description": "Uลผywa alternatywnej metody uruchamiania, aby lepiej zarzฤ…dzaฤ‡ procesami i zapobiegaฤ‡ problemom.", + "settings-use-app2unit-label": "Uลผyj App2Unit do uruchamiania aplikacji", + "settings-view-mode-description": "Wybierz ukล‚ad dla wpisรณw programu uruchamiajฤ…cego.", + "settings-view-mode-label": "Tryb widoku", + "title": "Launcher" + }, + "location": { + "auto-locate-description": "Automatycznie wykrywaj swojฤ… lokalizacjฤ™ okresowo, uลผywajฤ…c adresu IP.", + "auto-locate-label": "Automatyczne Wykrywanie", + "calendar-cards-desc": "Organizuj i wล‚ฤ…czaj/wyล‚ฤ…czaj karty w panelu kalendarza.", + "calendar-cards-title": "Karty kalendarza", + "calendar-header-label": "Nagล‚รณwek kalendarza", + "calendar-month-label": "Miesiฤ…c kalendarza", + "date-time-12hour-format-description": "Wyล›wietlaj czas w formacie 12-godzinnym na ekranie blokady i w kalendarzu. Zegar paska ma wล‚asne ustawienia.", + "date-time-12hour-format-label": "Uลผyj formatu 12-godzinnego", + "date-time-desc": "Dostosuj sposรณb wyล›wietlania daty i czasu.", + "date-time-first-day-of-week-automatic": "Automatycznie (wedล‚ug systemu)", + "date-time-first-day-of-week-description": "Wybierz, ktรณry dzieล„ zaczyna tydzieล„ w kalendarzu.", + "date-time-first-day-of-week-label": "Pierwszy dzieล„ tygodnia", + "date-time-show-events-description": "Wyล›wietlaj wydarzenia w panelu kalendarza.", + "date-time-show-events-label": "Pokaลผ wydarzenia kalendarza", + "date-time-title": "Data i czas", + "date-time-use-analog-description": "Pokaลผ zegar w stylu analogowym w oknie kalendarza.", + "date-time-use-analog-label": "Uลผyj zegara analogowego", + "date-time-week-numbers-description": "Wyล›wietlaj numer tygodnia w roku (np. tydzieล„ 38) w kalendarzu.", + "date-time-week-numbers-label": "Pokaลผ numery tygodni", + "geolocate-now-button": "Zlokalizuj teraz", + "location-desc": "Ustaw lokalizacjฤ™, aby otrzymywaฤ‡ dokล‚adnฤ… pogodฤ™ i harmonogram podล›wietlenia nocnego.", + "location-search-description": "np. Warszawa, PL", + "location-search-label": "Szukaj lokalizacji", + "location-search-placeholder": "Wpisz nazwฤ™ lokalizacji", + "location-title": "Twoja lokalizacja", + "weather-desc": "Wybierz preferowanฤ… jednostkฤ™ temperatury.", + "weather-enabled-description": "Wล‚ฤ…cz pobieranie danych pogodowych i ich wyล›wietlanie. Gdy wyล‚ฤ…czone, wszystkie elementy pogody zostanฤ… ukryte i nie bฤ™dฤ… wysyล‚ane zapytania sieciowe.", + "weather-enabled-label": "Wล‚ฤ…cz pogodฤ™", + "weather-fahrenheit-description": "Wyล›wietlaj temperaturฤ™ w stopniach Fahrenheita zamiast Celsjusza.", + "weather-fahrenheit-label": "Pokaลผ temperaturฤ™ w Fahrenheitach (ยฐF)", + "weather-hide-city-description": "Ukryj nazwฤ™ miasta na wyล›wietlaczach pogody w caล‚ym interfejsie.", + "weather-hide-city-label": "Ukryj nazwฤ™ miasta", + "weather-hide-timezone-description": "Ukryj skrรณt strefy czasowej na wyล›wietlaczach pogody w caล‚ym interfejsie.", + "weather-hide-timezone-label": "Ukryj strefฤ™ czasowฤ…", + "weather-show-effects-description": "Pokaลผ dodatkowe efekty wizualne (deszcz, ล›nieg, bล‚yskawice) na karcie pogody.", + "weather-show-effects-label": "Wyล›wietlaj efekty pogodowe", + "weather-show-in-calendar-description": "Pokaลผ dziennฤ… prognozฤ™ pogody bezpoล›rednio w widoku kalendarza.", + "weather-show-in-calendar-label": "Wyล›wietlaj pogodฤ™ w kalendarzu", + "weather-talia-mascot-always-description": "Zawsze uลผywaj Talia emojis zamiast tabler icons.", + "weather-talia-mascot-always-label": "Zawsze pokazuj emotikony pogody Talia" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "Gdy fprintd (uwierzytelnianie odciskiem palca) jest aktywne, ta opcja pozwala nadal logowaฤ‡ siฤ™ za pomocฤ… hasล‚a zamiast odcisku palca.", + "allow-password-with-fprintd-label": "Zezwรณl na logowanie hasล‚em za pomocฤ… fprintd", + "auto-start-auth-description": "Automatycznie uruchamia uwierzytelnianie odciskiem palca bez koniecznoล›ci naciskania klawisza lub klikania przycisku.", + "auto-start-auth-label": "Automatyczne uruchamianie uwierzytelniania", + "clock-format-description": "Dostosuj format zegara, uลผywajฤ…c tokenรณw skล‚adni daty/czasu.", + "clock-format-label": "Format zegara", + "clock-style-analog": "Analogowy", + "clock-style-custom": "Niestandardowy", + "clock-style-description": "Wybierz styl wizualny zegara na ekranie blokady.", + "clock-style-digital": "Cyfrowy", + "clock-style-label": "Styl Zegara", + "compact-lockscreen-description": "Pokaลผ tylko logowanie i kontrolki systemowe, ukrywajฤ…c widลผety pogody i mediรณw.", + "compact-lockscreen-label": "Kompaktowy ekran blokady", + "enable-lockscreen-media-controls-description": "Pokaลผ interaktywne elementy sterujฤ…ce odtwarzaniem multimediรณw na ekranie blokady.", + "enable-lockscreen-media-controls-label": "Sterowanie Multimediami na Ekranie Blokady", + "lock-on-suspend-description": "Automatycznie blokuj ekran podczas uล›pienia systemu.", + "lock-on-suspend-label": "Blokuj przy uล›pieniu", + "lock-screen-animations-description": "Wล‚ฤ…cz lub wyล‚ฤ…cz animacje ekranu blokady.", + "lock-screen-animations-label": "Animacje Ekranu Blokady", + "lock-screen-blur-strength-description": "Stosuje efekt rozmycia do tapety ekranu blokady.", + "lock-screen-blur-strength-label": "Siล‚a rozmycia ekranu blokady", + "lock-screen-tint-strength-description": "Stosuje nakล‚adkฤ™ z odcieniem do tapety ekranu blokady.", + "lock-screen-tint-strength-label": "Siล‚a zabarwienia ekranu blokady", + "monitors-desc": "Pokaลผ ekran blokady na okreล›lonych monitorach. Domyล›lnie na wszystkich, jeล›li ลผadne nie zostanฤ… wybrane.", + "password-chars-description": "Urocze ikony uลผywane do ukrywania hasล‚a.", + "password-chars-label": "Losowe ikony haseล‚", + "show-hibernate-description": "Pokaลผ opcjฤ™ 'hibernacja' w kontrolkach zasilania.", + "show-hibernate-label": "Pokaลผ hibernacjฤ™", + "show-session-buttons-description": "Zezwalaj na dostฤ™p do ustawieล„ zasilania z poziomu ekranu blokady.", + "show-session-buttons-label": "Kontrolki zasilania", + "title": "Ekran blokady" + }, + "network": { + "bluetooth-description": "Wล‚ฤ…cz zarzฤ…dzanie Bluetooth.", + "bluetooth-rssi-polling-description": "Okresowo prรณbkuje RSSI dla podล‚ฤ…czonych urzฤ…dzeล„ przez bluetoothctl. Moลผe nie byฤ‡ dostฤ™pne dla wszystkich urzฤ…dzeล„; zuลผywa minimalne zasoby, gdy jest wล‚ฤ…czone.", + "bluetooth-rssi-polling-label": "Odpytywanie sygnaล‚u Bluetooth", + "desc": "Zarzฤ…dzaj poล‚ฤ…czeniami Wi-Fi i Bluetooth.", + "wifi-description": "Zarzฤ…dzanie sieciami bezprzewodowymi (wymaga NetworkManager)." + }, + "notifications": { + "duration-critical-urgency-description": "Jak dล‚ugo widoczne sฤ… powiadomienia o priorytecie krytycznym.", + "duration-critical-urgency-label": "Pilnoล›ฤ‡ krytyczna", + "duration-desc": "Konfiguruj, jak dล‚ugo powiadomienia pozostajฤ… widoczne w zaleลผnoล›ci od poziomu pilnoล›ci.", + "duration-low-urgency-description": "Jak dล‚ugo widoczne sฤ… powiadomienia o priorytecie niskim.", + "duration-low-urgency-label": "Pilnoล›ฤ‡ niska", + "duration-normal-urgency-description": "Jak dล‚ugo widoczne sฤ… powiadomienia o priorytecie normalnym.", + "duration-normal-urgency-label": "Pilnoล›ฤ‡ normalna", + "duration-respect-expire-description": "Uลผywaj czasu wygasania ustawionego w samym powiadomieniu.", + "duration-respect-expire-label": "Respektuj czas wygasania (expire timeout)", + "duration-title": "Czas trwania powiadomienia", + "history-clear-dismiss-description": "Wyczyล›ฤ‡ powiadomienie z historii po odrzuceniu.", + "history-clear-dismiss-label": "Wyczyล›ฤ‡ po odrzuceniu", + "history-critical-urgency-description": "Zapisuj powiadomienia o krytycznym priorytecie w historii.", + "history-critical-urgency-label": "Zachowaj krytycznฤ… pilnoล›ฤ‡ dla historii", + "history-desc": "Kontroluj, ktรณre powiadomienia sฤ… zapisywane w historii na podstawie ich poziomu waลผnoล›ci.", + "history-low-urgency-description": "Zapisuj powiadomienia o niskim priorytecie w historii.", + "history-low-urgency-label": "Odeล›lij niskฤ… pilnoล›ฤ‡ do historii", + "history-normal-urgency-description": "Zapisuj powiadomienia o normalnym priorytecie w historii.", + "history-normal-urgency-label": "Zapisz normalny priorytet do historii", + "monitors-desc": "Pokazuj powiadomienia na konkretnych monitorach. Domyล›lnie na wszystkich.", + "rules-action-block": "Blok", + "rules-action-block-desc": "Caล‚kowicie pomija.", + "rules-action-hide": "Ukryj", + "rules-action-hide-desc": "Bez wyskakujฤ…cego okienka, bez dลบwiฤ™ku, dodaje do historii.", + "rules-action-label": "Akcja", + "rules-action-mute": "Wycisz", + "rules-action-mute-desc": "Brak dลบwiฤ™ku, nadal wyล›wietla wyskakujฤ…ce okienko i w historii.", + "rules-add": "Dodaj reguล‚ฤ™", + "rules-delete": "Usuล„ reguล‚ฤ™", + "rules-description": "Dopasuj nazwฤ™ aplikacji lub zawartoล›ฤ‡. Reguล‚y sฤ… sprawdzane po kolei, a pierwsze dopasowanie jest stosowane.", + "rules-edit": "Edytuj reguล‚ฤ™", + "rules-label": "Reguล‚y Filtrowania", + "rules-pattern-label": "Wzรณr", + "rules-pattern-placeholder": "firefox, discord, lub /regex/", + "rules-tab": "Zasady", + "settings-always-on-top-description": "Wyล›wietlaj powiadomienia nad oknami peล‚noekranowymi i innymi warstwami.", + "settings-background-opacity-description": "Dostosuj przezroczystoล›ฤ‡ tล‚a powiadomieล„.", + "settings-density-description": "Wybierz gฤ™stoล›ฤ‡ karty powiadomieล„.", + "settings-density-label": "Gฤ™stoล›ฤ‡", + "settings-desc": "Konfiguruj wyglฤ…d i zachowanie powiadomieล„.", + "settings-do-not-disturb-description": "Wyล‚ฤ…cz wszystkie wyskakujฤ…ce powiadomienia.", + "settings-enabled-description": "Wล‚ฤ…cz lub wyล‚ฤ…cz demona powiadomieล„ (wymaga restartu Noctalia).", + "settings-enabled-label": "Wล‚ฤ…cz powiadomienia", + "settings-location-description": "Miejsce na ekranie, w ktรณrym pojawiajฤ… siฤ™ powiadomienia.", + "settings-markdown-description": "Renderuj treล›ฤ‡ powiadomieล„, uลผywajฤ…c formatowania Markdown.", + "settings-markdown-label": "Wล‚ฤ…cz Markdown", + "sounds-desc": "Konfiguruj efekty dลบwiฤ™kowe i gล‚oล›noล›ฤ‡ powiadomieล„.", + "sounds-enabled-description": "Wล‚ฤ…cz efekty dลบwiฤ™kowe dla przychodzฤ…cych powiadomieล„.", + "sounds-enabled-label": "Wล‚ฤ…cz dลบwiฤ™ki powiadomieล„", + "sounds-excluded-apps-description": "Pomiล„ odtwarzanie dลบwiฤ™ku systemowego dla aplikacji, ktรณre majฤ… wล‚asne wbudowane powiadomienia dลบwiฤ™kowe.", + "sounds-excluded-apps-label": "Wykluczone aplikacje", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "ลšcieลผka do pliku dลบwiฤ™kowego dla powiadomieล„ krytycznych.", + "sounds-files-critical-label": "Dลบwiฤ™k pilnoล›ci krytycznej", + "sounds-files-critical-select-title": "Wybierz dลบwiฤ™k pilnoล›ci krytycznej", + "sounds-files-desc": "Konfiguruj wล‚asne pliki dลบwiฤ™kowe dla rรณลผnych poziomรณw pilnoล›ci.", + "sounds-files-low-description": "ลšcieลผka do pliku dลบwiฤ™kowego dla powiadomieล„ o niskim priorytecie.", + "sounds-files-low-label": "Dลบwiฤ™k pilnoล›ci niskiej", + "sounds-files-low-select-title": "Wybierz dลบwiฤ™k pilnoล›ci niskiej", + "sounds-files-normal-description": "ลšcieลผka do pliku dลบwiฤ™kowego dla powiadomieล„ normalnych.", + "sounds-files-normal-label": "Dลบwiฤ™k pilnoล›ci normalnej", + "sounds-files-normal-select-title": "Wybierz dลบwiฤ™k pilnoล›ci normalnej", + "sounds-files-placeholder": "Wprowadลบ ล›cieลผkฤ™ do pliku dลบwiฤ™kowego", + "sounds-files-select-file": "Wybierz plik dลบwiฤ™kowy", + "sounds-files-title": "Pliki dลบwiฤ™kowe", + "sounds-files-unified-description": "ลšcieลผka do pliku dลบwiฤ™kowego dla wszystkich powiadomieล„.", + "sounds-files-unified-label": "Dลบwiฤ™k powiadomienia", + "sounds-files-unified-select-title": "Wybierz plik dลบwiฤ™ku powiadomienia", + "sounds-separate-description": "Uลผywaj rรณลผnych dลบwiฤ™kรณw dla niskiej, normalnej i krytycznej pilnoล›ci.", + "sounds-separate-label": "Rรณลผne dลบwiฤ™ki zaleลผnie od priorytetu", + "sounds-title": "Ustawienia dลบwiฤ™ku", + "sounds-unavailable-description": "Zainstaluj Qt6 Multimedia, aby wล‚ฤ…czyฤ‡ dลบwiฤ™ki powiadomieล„.", + "sounds-unavailable-label": "Dลบwiฤ™ki powiadomieล„ niedostฤ™pne", + "sounds-volume-description": "Dostosuj poziom gล‚oล›noล›ci dลบwiฤ™kรณw powiadomieล„.", + "sounds-volume-label": "Gล‚oล›noล›ฤ‡ dลบwiฤ™ku", + "toast-battery-description": "Wyล›wietl ostrzeลผenie, gdy poziom baterii spadnie poniลผej tego procentu.", + "toast-battery-label": "Ostrzeลผenie o baterii", + "toast-desc": "Konfiguruj wyglฤ…d i zachowanie dymkรณw powiadomieล„.", + "toast-keyboard-description": "Pokaลผ dymek (toast), gdy zmieni siฤ™ ukล‚ad klawiatury.", + "toast-keyboard-label": "Ukล‚ad klawiatury", + "toast-media-description": "Wyล›wietl toast, gdy zmieni siฤ™ stan odtwarzania multimediรณw.", + "toast-media-label": "Media" + }, + "osd": { + "always-on-top-description": "Wyล›wietlaj OSD nad oknami peล‚noekranowymi i innymi warstwami.", + "always-on-top-label": "Zawsze na wierzchu", + "background-opacity-description": "Kontroluje przezroczystoล›ฤ‡ tล‚a OSD.", + "background-opacity-label": "Przezroczystoล›ฤ‡ tล‚a", + "description": "Konfiguruj wskaลบniki ekranowe, takie jak nakล‚adki gล‚oล›noล›ci i jasnoล›ci.", + "duration-auto-hide-description": "Dostosuj czas, po ktรณrym OSD zniknie.", + "duration-auto-hide-label": "Ukryj automatycznie po", + "duration-desc": "Jak dล‚ugo OSD pozostaje widoczne przed automatycznym ukryciem.", + "duration-title": "Czas wygasania OSD", + "enabled-description": "Pokazuj zmiany gล‚oล›noล›ci i jasnoล›ci w czasie rzeczywistym.", + "enabled-label": "Wล‚ฤ…cz wyล›wietlanie ekranowe (OSD)", + "events-desc": "Wybierz, ktรณre zdarzenia majฤ… wywoล‚ywaฤ‡ wyล›wietlanie ekranowe.", + "general-desc": "Skonfiguruj widocznoล›ฤ‡ i zachowanie OSD.", + "location-description": "Miejsce na ekranie, w ktรณrym pojawia siฤ™ OSD.", + "monitors-desc": "Pokazuj OSD na konkretnych monitorach. Domyล›lnie na wszystkich.", + "title": "Wyล›wietlanie ekranowe", + "types-brightness-description": "Pokazuj OSD podczas zmiany jasnoล›ci ekranu.", + "types-brightness-label": "Jasnoล›ฤ‡", + "types-custom-text-description": "Pokazuj OSD dla wล‚asnych wiadomoล›ci tekstowych z IPC.", + "types-custom-text-label": "Wล‚asny tekst", + "types-desc": "Wybierz zdarzenia, ktรณre wyzwalajฤ… OSD.", + "types-input-volume-description": "Pokazuj OSD podczas zmiany gล‚oล›noล›ci mikrofonu.", + "types-input-volume-label": "Gล‚oล›noล›ฤ‡ wejล›ciowa", + "types-lockkey-description": "Pokazuj OSD podczas przeล‚ฤ…czania Caps Lock, Num Lock lub Scroll Lock.", + "types-lockkey-label": "Klawisze blokady", + "types-media-description": "Wyล›wietlaj OSD, gdy zmieni siฤ™ stan odtwarzania multimediรณw (odtwarzanie, pauza, pomijanie).", + "types-media-label": "Odtwarzanie multimediรณw", + "types-title": "Zdarzenia wyzwalajฤ…ce OSD", + "types-volume-description": "Pokazuj OSD podczas zmiany gล‚oล›noล›ci wyjล›cia audio.", + "types-volume-label": "Gล‚oล›noล›ฤ‡ wyjล›ciowa" + }, + "plugins": { + "auto-update": "Automatyczna aktualizacja wtyczek", + "auto-update-description": "Automatycznie aktualizuj wszystkie wtyczki po uruchomieniu shella.", + "available-description": "Przeglฤ…daj i instaluj wtyczki ze skonfigurowanych ลบrรณdeล‚.", + "available-label": "Dostฤ™pne wtyczki", + "available-no-plugins-description": "Sprawdลบ ลบrรณdล‚a wtyczek lub odล›wieลผ listฤ™.", + "available-no-plugins-label": "Brak dostฤ™pnych wtyczek", + "check-for-updates": "Sprawdลบ aktualizacje", + "checking-for-updates": "Sprawdzanie aktualizacji...", + "collision-already-installed": "Ta wtyczka jest juลผ zainstalowana", + "collision-custom-version-exists": "Niestandardowa wersja z \"{source}\" jest juลผ zainstalowana", + "collision-official-version-exists": "Oficjalna wersja tej wtyczki jest juลผ zainstalowana", + "development-disable": "Wyล‚ฤ…cz tryb deweloperski dla tej wtyczki", + "development-enable": "Wล‚ฤ…cz tryb deweloperski dla tej wtyczki.", + "filter-downloaded": "Pobrane", + "filter-not-downloaded": "Niepobrane", + "filter-tags-description": "Filtruj wtyczki wedล‚ug kategorii lub statusu pobierania.", + "filter-tags-label": "Tagi", + "hot-reload-description": "Automatycznie przeล‚aduj wtyczki przy zmianie ich plikรณw. Przydatne przy tworzeniu wtyczek.", + "hot-reload-label": "Gorฤ…ce przeล‚adowanie (tryb deweloperski)", + "hot-reloaded": "Przeล‚adowano wtyczkฤ™: {name}", + "install-error": "Bล‚ฤ…d instalacji: {error}", + "install-incompatible": "{plugin} wymaga Noctalii w wersji v{version} lub wyลผszej", + "install-success": "Pomyล›lnie zainstalowano {plugin}", + "installed-description": "Zarzฤ…dzaj i konfiguruj lokalnie zainstalowane wtyczki.", + "installed-label": "Zainstalowane wtyczki", + "installed-no-plugins-description": "Zainstaluj wtyczki z sekcji \"Dostฤ™pne\".", + "installed-no-plugins-label": "Brak zainstalowanych wtyczek", + "installing": "Instalowanie {plugin}...", + "notify-updates": "Powiadomienia o aktualizacjach wtyczek", + "notify-updates-description": "Wyล›wietl wyskakujฤ…ce powiadomienie, gdy dostฤ™pne sฤ… nowe aktualizacje wtyczek.", + "open-plugin-page": "Otwรณrz stronฤ™ wtyczki", + "open-plugins-tab": "Otwรณrz ustawienia wtyczek", + "plugin-settings-title": "Ustawienia {plugin}", + "refresh-refreshing": "Odล›wieลผanie listy wtyczek...", + "refresh-tooltip": "Odล›wieลผ dostฤ™pne wtyczki", + "settings-error-not-loaded": "Wtyczka nie zostaล‚a zaล‚adowana", + "settings-saved": "Ustawienia wtyczki zapisane", + "settings-tooltip": "Ustawienia wtyczki", + "source-custom": "ลนrรณdล‚o niestandardowe", + "sources-add-custom": "Dodaj wล‚asne repozytorium", + "sources-add-dialog-description": "Dodaj repozytorium GitHub jako ลบrรณdล‚o wtyczek.", + "sources-add-dialog-error": "Nie udaล‚o siฤ™ dodaฤ‡ ลบrรณdล‚a wtyczek", + "sources-add-dialog-name": "Nazwa repozytorium", + "sources-add-dialog-name-placeholder": "Moje wล‚asne wtyczki", + "sources-add-dialog-success": "ลนrรณdล‚o wtyczek dodane pomyล›lnie", + "sources-add-dialog-title": "Dodaj ลบrรณdล‚o wtyczek", + "sources-add-dialog-url": "Adres URL repozytorium", + "sources-description": "Zarzฤ…dzaj repozytoriami wtyczek.", + "sources-placeholder": "Moje fajne repozytorium", + "sources-remove-tooltip": "Usuล„ ลบrรณdล‚o wtyczek", + "title": "Wtyczki", + "translations-reloaded": "Przeล‚adowano tล‚umaczenia: {name}", + "uninstall-dialog-description": "Czy na pewno chcesz odinstalowaฤ‡ {plugin}? Spowoduje to usuniฤ™cie wszystkich danych wtyczki.", + "uninstall-dialog-title": "Odinstaluj wtyczkฤ™", + "uninstall-error": "Bล‚ฤ…d odinstalowywania: {error}", + "uninstall-success": "Pomyล›lnie odinstalowano {plugin}", + "uninstalling": "Odinstalowywanie {plugin}...", + "update-all": "Aktualizuj wszystkie ({count})", + "update-all-success": "Wszystkie wtyczki zaktualizowane pomyล›lnie", + "update-available": "Dostฤ™pna nowa aktualizacja wtyczki", + "update-available-plural": "Dostฤ™pne nowe aktualizacje wtyczek ({count})", + "update-error": "Bล‚ฤ…d aktualizacji wtyczki {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (wymaga Noctalii v{required})", + "update-success": "Zaktualizowano {plugin} do v{version}", + "update-version": "v{current} โ†’ v{new}", + "updating": "Aktualizowanie {plugin}..." + }, + "region": { + "title": "Region" + }, + "session-menu": { + "countdown-duration-description": "Ustaw czas odliczania przed wykonaniem akcji zasilania.", + "countdown-duration-label": "Czas trwania odliczania", + "enable-countdown-description": "Pokaลผ minutnik odliczajฤ…cy przed wykonaniem akcji zasilania.", + "enable-countdown-label": "Wล‚ฤ…cz minutnik odliczania", + "entries-desc": "Dostosuj, ktรณre akcje zasilania pojawiajฤ… siฤ™ w menu sesji i w jakiej kolejnoล›ci.", + "entries-title": "Akcje zasilania", + "entry-settings-command-description": "Wล‚asne polecenie dla tej akcji. Zostaw puste, aby uลผyฤ‡ domyล›lnego polecenia systemowego.", + "entry-settings-command-placeholder": "np. systemctl poweroff", + "entry-settings-default-command-lock": "Wewnฤ™trzna blokada (brak polecenia)", + "entry-settings-default-command-logout": "Wewnฤ™trzne wylogowanie (brak polecenia)", + "entry-settings-default-info-description": "Jeล›li nie podano wล‚asnego polecenia, zostanie uลผyte domyล›lne polecenie systemowe.", + "entry-settings-default-info-label": "Domyล›lne polecenie", + "entry-settings-keybind-description": "Przypisz kombinacjฤ™ klawiszy, aby wywoล‚aฤ‡ tฤ™ akcjฤ™, gdy menu sesji jest otwarte.", + "entry-settings-keybind-placeholder": "Naciล›nij Nagraj, aby przypisaฤ‡ klawisz...", + "entry-settings-keybind-recording": "Nagrywanie... Naciล›nij dowolnฤ… kombinacjฤ™ klawiszy", + "entry-settings-title": "Konfiguruj {entry}", + "entry-settings-tooltip": "Konfiguruj polecenie", + "general-desc": "Konfiguruj zachowanie i wyglฤ…d panelu menu sesji.", + "large-buttons-layout-description": "Wybierz sposรณb wyล›wietlania przyciskรณw menu sesji.", + "large-buttons-layout-label": "Ukล‚ad z duลผymi przyciskami", + "large-buttons-style-description": "Wyล›wietlaj menu sesji z duลผymi przyciskami w ukล‚adzie siatki.", + "large-buttons-style-label": "Styl duลผych przyciskรณw", + "position-description": "Wybierz, gdzie pojawia siฤ™ panel menu sesji po otwarciu.", + "show-header-description": "Wyล›wietlaj tytuล‚ i przycisk zamkniฤ™cia na gรณrze menu sesji.", + "show-header-label": "Pokaลผ nagล‚รณwek", + "show-keybinds-description": "Wyล›wietl podpowiedzi skrรณtรณw klawiszowych w opcjach sesji.", + "show-keybinds-label": "Pokaลผ skrรณty klawiszowe" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Zachowaj widocznoล›ฤ‡ widลผetรณw pulpitu, gdy tryb wydajnoล›ci Noctalia jest wล‚ฤ…czony.", + "noctalia-performance-disable-desktop-widgets-label": "Wล‚ฤ…cz widลผety pulpitu w trybie wydajnoล›ci", + "noctalia-performance-disable-wallpaper-description": "Zachowaj widoczne tapety pulpitu, przeglฤ…du i ekranu blokady, gdy tryb wydajnoล›ci Noctalia jest wล‚ฤ…czony.", + "noctalia-performance-disable-wallpaper-label": "Wล‚ฤ…cz renderowanie tapety w trybie wydajnoล›ci", + "title": "System" + }, + "system-monitor": { + "critical-color-label": "Kolor krytyczny", + "custom-highlight-colors-title-label": "Wล‚asne kolory podล›wietlenia", + "disk-available-label": "Dostฤ™pne miejsce na dysku", + "disk-section-label": "Uลผycie dysku", + "enable-dgpu-monitoring-description": "Ostrzeลผenie: To obudzi twojฤ… dedykowanฤ… kartฤ™ graficznฤ… (NVIDIA/AMD), co moลผe znaczฤ…co wpล‚ynฤ…ฤ‡ na ลผywotnoล›ฤ‡ baterii w laptopach z hybrydowฤ… grafikฤ….", + "enable-dgpu-monitoring-label": "Wล‚ฤ…cz monitorowanie dedykowanej karty graficznej", + "external-monitor-description": "Wprowadลบ polecenie lub ล›cieลผkฤ™ aplikacji do uruchomienia przy aktywacji zewnฤ™trznej aplikacji monitora systemu.", + "external-monitor-label": "Polecenie zewnฤ™trznego monitora systemu", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "Konfiguruj zachowanie i wyglฤ…d monitora systemu.", + "gpu-section-label": "Temperatura GPU", + "highlight-colors-section-label": "Kolory podล›wietlenia", + "polling-interval-label": "Interwaล‚ odล›wieลผania", + "polling-section-description": "Skonfiguruj, jak czฤ™sto kaลผda metryka systemowa jest aktualizowana.", + "polling-section-label": "Interwaล‚y odpytywania", + "threshold-critical": "Krytyczny", + "threshold-warning": "Ostrzeลผenie", + "thresholds-section-description": "Dostosuj progi ostrzegawcze/krytyczne oraz interwaล‚y odล›wieลผania dla kaลผdej metryki systemu.", + "use-custom-highlight-colors-description": "Gdy wyล‚ฤ…czone, uลผywane sฤ… domyล›lne kolory podล›wietlenia motywu.", + "use-custom-highlight-colors-label": "Uลผyj wล‚asnych kolorรณw podล›wietlenia", + "warning-color-label": "Kolor ostrzegawczy" + }, + "user-interface": { + "allow-panels-without-bar-description": "Gdy wล‚ฤ…czone, panele mogฤ… otwieraฤ‡ siฤ™ na dowolnym ekranie. Gdy wyล‚ฤ…czone, panele otworzฤ… siฤ™ tylko na ekranach z paskiem, co moลผe zmniejszyฤ‡ zuลผycie pamiฤ™ci.", + "allow-panels-without-bar-label": "Zezwalaj na panele na ekranach bez paska", + "animation-disable-description": "Wyล‚ฤ…cz wszystkie animacje dla szybszego i bardziej responsywnego dziaล‚ania.", + "animation-disable-label": "Wyล‚ฤ…cz animacje UI", + "animation-speed-description": "Dostosuj globalnฤ… prฤ™dkoล›ฤ‡ animacji.", + "animation-speed-label": "Prฤ™dkoล›ฤ‡ animacji", + "animation-speed-reset": "Resetuj prฤ™dkoล›ฤ‡ animacji", + "appearance-desc": "Dostosuj elementy wizualne, takie jak podpowiedzi, obramowania i cienie.", + "blur-behind-description": "Rozmywa obszar za paskami i panelami, uลผywajฤ…c protokoล‚u rozmycia kompozytora.", + "blur-behind-label": "Rozmycie tล‚a", + "box-border-description": "Wyล›wietla obrys wokรณล‚ obszarรณw zawartoล›ci.", + "box-border-label": "Obrys kontenera", + "box-border-radius-description": "Dostosowuje zaokrฤ…glenie naroลผnikรณw gล‚รณwnych sekcji ukล‚adu, takich jak paski boczne, karty i panele treล›ci.", + "box-border-radius-label": "Promieล„ kontenerรณw", + "control-border-radius-description": "Kontroluje zaokrฤ…glenie elementรณw interaktywnych, w tym przyciskรณw, przeล‚ฤ…cznikรณw i pรณl tekstowych.", + "control-border-radius-label": "Promieล„ wejล›ฤ‡", + "desc": "Dostosuj wyglฤ…d i zachowanie interfejsu.", + "dim-desktop-description": "Przyciemniaj pulpit, gdy panele lub menu sฤ… otwarte.", + "dim-desktop-label": "Przyciemniaj pulpit", + "dimmer-opacity-description": "Ustaw poziom przezroczystoล›ci przyciemnienia pulpitu.", + "dimmer-opacity-label": "Przezroczystoล›ฤ‡ przyciemnienia", + "panel-background-opacity-description": "Ustaw przezroczystoล›ฤ‡ tล‚a dla wszystkich paneli (pasek, launcher, ustawienia itp.).", + "panel-background-opacity-label": "Przezroczystoล›ฤ‡ tล‚a paneli", + "panels-attached-to-bar-description": "Panele przypinajฤ… siฤ™ do paska i krawฤ™dzi ekranu, tworzฤ…c spรณjny wyglฤ…d ze stylowymi odwrรณconymi naroลผnikami.", + "panels-attached-to-bar-label": "Przyciฤ…gaj panele do krawฤ™dzi", + "panels-overlay-description": "Zapewnia, ลผe panele i pasek pozostajฤ… widoczne nawet nad aplikacjami peล‚noekranowymi.", + "panels-overlay-label": "Trzymaj panele i pasek na wierzchu", + "scaling-description": "Zmienia rozmiar ogรณlnego interfejsu uลผytkownika, z wyล‚ฤ…czeniem paska.", + "scaling-label": "Skalowanie interfejsu", + "scrollbar-always-visible-description": "Paski przewijania zawsze widoczne, gdy zawartoล›ฤ‡ jest przewijalna, zamiast pokazywaฤ‡ je tylko po najechaniu myszฤ….", + "scrollbar-always-visible-label": "Zawsze pokazuj paski przewijania", + "settings-panel-header": "Panel Ustawieล„", + "settings-panel-mode-description": "Wybierz ukล‚ad ustawieล„ (moลผe wymagaฤ‡ ponownego otwarcia).", + "settings-panel-mode-label": "Tryb panelu ustawieล„", + "settings-panel-sidebar-card-style-description": "Otacza pasek boczny ustawieล„ wypeล‚nionym tล‚em z zaokrฤ…glonymi rogami.", + "settings-panel-sidebar-card-style-label": "Styl kart paska bocznego", + "shadows-description": "Wล‚ฤ…cza cienie pod paskami i panelami.", + "shadows-direction-description": "Wybierz, gdzie rzucany jest cieล„.", + "shadows-direction-label": "Kierunek cienia", + "shadows-label": "Cienie", + "title": "Interfejs uลผytkownika", + "tooltips-description": "Wล‚ฤ…cz lub wyล‚ฤ…cz podpowiedzi w caล‚ym interfejsie.", + "tooltips-label": "Pokaลผ podpowiedzi", + "translucent-widgets-description": "Spraw, aby przyciski, zakล‚adki i inne widลผety w panelach byล‚y pรณล‚przezroczyste.", + "translucent-widgets-label": "Pรณล‚przezroczyste widลผety" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "Alfabetyczny", + "automation-change-mode-description": "Wybierz, jak wybierane sฤ… tapety podczas automatycznej zmiany.", + "automation-change-mode-label": "Tryb zmiany", + "automation-custom-interval-description": "Wprowadลบ czas jako GG:MM (np. 01:30).", + "automation-custom-interval-label": "Niestandardowy interwaล‚", + "automation-interval-description": "Jak czฤ™sto automatycznie zmieniaฤ‡ tapety.", + "automation-interval-label": "Interwaล‚ tapet", + "automation-random-wallpaper-description": "Harmonogram losowych zmian tapet w regularnych odstฤ™pach czasu.", + "automation-scheduled-change-description": "Automatycznie zmieniaj tapety w regularnych odstฤ™pach czasu.", + "automation-scheduled-change-label": "Zaplanowana zmiana", + "look-feel-edge-smoothness-description": "Stosuje efekt miฤ™kkiego wtapiania na krawฤ™dziach przejล›ฤ‡.", + "look-feel-edge-smoothness-label": "Zmiฤ™kcz krawฤ™dzie przejล›cia", + "look-feel-fill-color-description": "Wybierz kolor wypeล‚nienia, ktรณry moลผe pojawiฤ‡ siฤ™ pod tapetฤ….", + "look-feel-fill-mode-description": "Wybierz, jak obraz ma siฤ™ skalowaฤ‡, aby pasowaฤ‡ do rozdzielczoล›ci monitora.", + "look-feel-fill-mode-label": "Tryb wypeล‚nienia", + "look-feel-skip-startup-transition-description": "Pomiล„ animacjฤ™ tapety przy starcie powล‚oki.", + "look-feel-skip-startup-transition-label": "Pomiล„ animacjฤ™ startowฤ…", + "look-feel-title": "Wyglฤ…d i styl", + "look-feel-transition-duration-description": "Czas trwania animacji przejล›cia w sekundach.", + "look-feel-transition-duration-label": "Czas trwania przejล›cia", + "look-feel-transition-type-description": "Typ animacji podczas przeล‚ฤ…czania tapet.", + "look-feel-transition-type-label": "Typ przejล›cia", + "settings-clear-cache-button": "Wyczyล›ฤ‡ pamiฤ™ฤ‡ podrฤ™cznฤ…", + "settings-clear-cache-description": "Wyczyล›ฤ‡ buforowane, przeskalowane tapety, aby zwolniฤ‡ miejsce na dysku.", + "settings-clear-cache-label": "Pamiฤ™ฤ‡ podrฤ™czna tapet", + "settings-clear-cache-toast": "Pamiฤ™ฤ‡ podrฤ™czna tapet wyczyszczona", + "settings-desc": "Kontroluj sposรณb zarzฤ…dzania i wyล›wietlania tapet.", + "settings-enable-management-description": "Zarzฤ…dzaj tapetami za pomocฤ… Noctalia. Odznacz, jeล›li wolisz uลผywaฤ‡ innej aplikacji.", + "settings-enable-management-label": "Wล‚ฤ…cz zarzฤ…dzanie tapetami", + "settings-enable-overview-description": "Stosuje rozmytฤ… i przyciemnionฤ… tapetฤ™ na ekranie podglฤ…du (overview).", + "settings-enable-overview-label": "Wล‚ฤ…cz tapetฤ™ podglฤ…du", + "settings-folder-description": "ลšcieลผka do gล‚รณwnego folderu z tapetami.", + "settings-folder-label": "Folder tapet", + "settings-hide-wallpaper-filenames-tooltip-hide": "Ukryj nazwy plikรณw", + "settings-hide-wallpaper-filenames-tooltip-show": "Pokaลผ nazwy plikรณw", + "settings-monitor-specific-description": "Ustaw inny folder tapet dla kaลผdego monitora.", + "settings-monitor-specific-label": "Katalogi per-monitor", + "settings-monitor-specific-tooltip": "Folder tapet monitora", + "settings-overview-blur-strength-description": "Stosuje siล‚ฤ™ rozmycia do przeglฤ…du.", + "settings-overview-blur-strength-label": "Siล‚a rozmycia przeglฤ…du", + "settings-overview-tint-description": "Stosuje siล‚ฤ™ odcienia do przeglฤ…du.", + "settings-overview-tint-label": "Siล‚a zabarwienia przeglฤ…du", + "settings-recursive-search-description": "Szukaj tapet rรณwnieลผ w podfolderach katalogu gล‚รณwnego.", + "settings-recursive-search-label": "Przeszukuj podfoldery", + "settings-select-monitor-folder": "Wybierz folder tapet monitora", + "settings-selector-description": "Wybierz swojฤ… tapetฤ™.", + "settings-selector-position-description": "Wybierz, gdzie pojawia siฤ™ panel wyboru tapety.", + "settings-show-hidden-files-tooltip-hide": "Ukryj ukryte pliki", + "settings-show-hidden-files-tooltip-show": "Pokaลผ ukryte pliki", + "settings-title": "Ustawienia tapet", + "settings-use-original-images-description": "Pomiล„ zmianฤ™ rozmiaru tapet przed wyล›wietleniem. Oszczฤ™dza miejsce na dysku i zmniejsza zuลผycie CPU podczas zmiany tapet, ale moลผe zuลผywaฤ‡ wiฤ™cej pamiฤ™ci dla bardzo duลผych obrazรณw.", + "settings-use-original-images-label": "Uลผyj oryginalnych obrazรณw", + "settings-view-mode-description": "Wybierz sposรณb wyล›wietlania tapet z Twojego katalogu.", + "settings-view-mode-label": "Tryb wyล›wietlania", + "view-mode-browse": "Przeglฤ…daj katalogi", + "view-mode-cycle-tooltip": "Tryb widoku: {mode} (kliknij, aby zmieniฤ‡)", + "view-mode-recursive": "Spล‚aszczone podkatalogi", + "view-mode-single": "Katalog gล‚รณwny" + } + }, + "placeholders": { + "add-new-keybind": "Dodaj nowy skrรณt klawiszowy", + "command-example": "echo \"Witaj ล›wiecie\"", + "enter-command": "Wpisz polecenie do wykonania (aplikacja lub skrypt)", + "enter-ipc-identifier": "Wprowadลบ unikalny identyfikator dla poleceล„ IPC", + "enter-path": "Wprowadลบ ล›cieลผkฤ™...", + "enter-text-to-collapse": "np. 'nic nie jest odtwarzane'. Uลผyj /regex/ dla wzorcรณw.", + "enter-tooltip": "Wpisz tekst podpowiedzi", + "enter-width-pixels": "Wpisz szerokoล›ฤ‡ w pikselach", + "keybind-recording": "Nagrywanie skrรณtu klawiszowego...", + "search": "Szukaj...", + "search-icons": "np. noctalia, niri, battery, cloud", + "search-launcher": "Szukaj wpisรณw... lub uลผyj > dla poleceล„", + "search-wallhaven": "Szukaj w Wallhaven...", + "search-wallpapers": "Wpisz, aby filtrowaฤ‡ tapety..." + }, + "positions": { + "bottom": "Dรณล‚", + "bottom-center": "Dรณล‚ ล›rodek", + "bottom-left": "Dรณล‚ lewo", + "bottom-right": "Dรณล‚ prawo", + "center": "ลšrodek", + "center-left": "ลšrodek lewo", + "center-right": "ลšrodek prawo", + "close-to-bar": "Blisko przycisku paska", + "follow-bar": "Podฤ…ลผaj za paskiem", + "left": "Lewo", + "right": "Prawo", + "top": "Gรณra", + "top-center": "Gรณra ล›rodek", + "top-left": "Gรณra lewo", + "top-right": "Gรณra prawo" + }, + "session-menu": { + "action-in-seconds": "{action} za {seconds} sekund...", + "cancel-timer": "Anuluj timer", + "title": "Menu sesji" + }, + "setup": { + "all-done": "Gotowe!", + "appearance": { + "subheader": "Wybierz tryb ciemny i ลบrรณdล‚a kolorรณw (tapeta lub predefiniowane)." + }, + "customize": { + "header": "Dostosuj swoje doล›wiadczenie", + "subheader": "Dostosuj pozycjฤ™ paska, zagฤ™szczenie, skalowanie i inne." + }, + "skip-setup": "Pomiล„ konfiguracjฤ™", + "telemetry-wizard-done": "Rozumiem!", + "telemetry-wizard-note": "Masz kontrolฤ™ โ€” moลผesz to wล‚ฤ…czyฤ‡ lub wyล‚ฤ…czyฤ‡ w dowolnym momencie w ustawieniach", + "telemetry-wizard-subtitle": "Dodaliล›my anonimowe analizy, aby pomรณc w ulepszaniu Noctalia", + "telemetry-wizard-title": "Aktualizacja prywatnoล›ci", + "wallpaper": { + "choose-dir": "Wybierz katalog zawierajฤ…cy obrazy tapet", + "dir-browse": "Przeglฤ…daj folder tapet", + "dir-description": "Wybierz folder zawierajฤ…cy twoje tapety.", + "dir-label": "Katalog tapet", + "dir-select-title": "Wybierz folder tapet", + "header": "Wybierz tapetฤ™", + "no-dir": "Nie wybrano katalogu tapet", + "no-valid": "Nie znaleziono prawidล‚owych plikรณw obrazรณw w: {dir}", + "none-in-dir": "Nie znaleziono tapet w katalogu", + "preview-error": "Bล‚ฤ…d ล‚adowania obrazu", + "select-prompt": "Wybierz tapetฤ™ poniลผej", + "subheader": "Ustaw nastrรณj dziฤ™ki piฤ™knemu tล‚u." + }, + "welcome": "Witaj", + "welcome-note": "Tylko kilka podstaw na poczฤ…tek โ€” peล‚ne opcje znajdujฤ… siฤ™ w ustawieniach", + "welcome-subtitle": "Sprawmy, aby Twรณj pulpit byล‚ wyjฤ…tkowy", + "welcome-title": "Witamy w Noctalia!" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "Nie wykryto odtwarzacza mediรณw", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "Nieznana aplikacja", + "uptime": "Czas pracy: {uptime}", + "user-requested": "Na proล›bฤ™ uลผytkownika", + "welcome-back": "Witaj z powrotem,", + "widget-settings-title": "Ustawienia {widget}" + }, + "system-monitor": { + "core-usage": "Uลผycie rdzenia {id}", + "cpu-temp": "Temperatura CPU", + "cpu-usage": "Uลผycie CPU", + "disk": "Dysk", + "download-speed": "Prฤ™dkoล›ฤ‡ pobierania", + "gpu-temp": "Temperatura GPU", + "load-average": "ลšr. obciฤ…ลผenie", + "title": "Monitor systemu", + "upload-speed": "Prฤ™dkoล›ฤ‡ wysyล‚ania" + }, + "toast": { + "airplane-mode": { + "description": "Wyล‚ฤ…cz wszystkie komunikacje bezprzewodowe.", + "title": "Tryb samolotowy" + }, + "battery": { + "critical": "Krytyczny poziom baterii", + "critical-desc": "Bateria naล‚adowana w {percent}% โ€” proszฤ™ natychmiast podล‚ฤ…czyฤ‡ ล‚adowarkฤ™", + "low": "Sล‚aba bateria", + "low-desc": "Bateria naล‚adowana w {percent}% โ€” proszฤ™ podล‚ฤ…czyฤ‡ ล‚adowarkฤ™" + }, + "bluetooth": { + "address-copied": "Adres skopiowany do schowka", + "auto-connect-disabled": "Automatyczne ล‚ฤ…czenie wyล‚ฤ…czone", + "auto-connect-enabled": "Automatyczne ล‚ฤ…czenie wล‚ฤ…czone", + "auto-connecting": "ลฤ…czenie z {count} urzฤ…dzeniem/urzฤ…dzeniami...", + "confirm-code": "Potwierdลบ kod {value} na drugim urzฤ…dzeniu.", + "connect-failed": "Nie udaล‚o siฤ™ poล‚ฤ…czyฤ‡ z urzฤ…dzeniem", + "disconnect-failed": "Nie udaล‚o siฤ™ rozล‚ฤ…czyฤ‡ z urzฤ…dzeniem", + "discoverable-change-failed": "Nie udaล‚o siฤ™ zmieniฤ‡ stanu wykrywalnoล›ci", + "discoverable-disabled": "Wykrywalnoล›ฤ‡ wyล‚ฤ…czona", + "discoverable-enabled": "Wykrywalnoล›ฤ‡ wล‚ฤ…czona", + "display-code": "Wprowadลบ kod {value} na drugim urzฤ…dzeniu.", + "forget-failed": "Nie udaล‚o siฤ™ zapomnieฤ‡ urzฤ…dzenia", + "pair-failed": "Nie udaล‚o siฤ™ sparowaฤ‡ urzฤ…dzenia", + "passkey-required": "Wymagany klucz dostฤ™pu przez urzฤ…dzenie.", + "pincode-required": "Kod PIN wymagany przez urzฤ…dzenie.", + "state-change-failed": "Nie udaล‚o siฤ™ zmieniฤ‡ stanu Bluetooth" + }, + "clipboard": { + "long-text": "Dล‚ugi tekst", + "unavailable": "Historia schowka niedostฤ™pna", + "unavailable-desc": "Aplikacja 'cliphist' nie jest zainstalowana. Zainstaluj jฤ…, aby korzystaฤ‡ z historii schowka" + }, + "custom-command-failed": { + "description": "Polecenie nie powiodล‚o siฤ™: {command}\\nKod wyjล›cia: {code}", + "title": "Niestandardowe polecenie nie powiodล‚o siฤ™" + }, + "do-not-disturb": { + "disabled": "Nie przeszkadzaฤ‡ wyล‚ฤ…czone", + "disabled-desc": "Pokazywanie wszystkich powiadomieล„", + "enabled": "Nie przeszkadzaฤ‡ wล‚ฤ…czone", + "enabled-desc": "Powiadomienia znajdziesz w swojej historii" + }, + "donation-opened": "Strona darowizny otwarta w Twojej przeglฤ…darce", + "internet-limited": "Poล‚ฤ…czono bez dostฤ™pu do internetu", + "keyboard-layout": { + "changed": "Ukล‚ad klawiatury zmieniony na {layout}", + "title": "Klawiatura" + }, + "missing-control-center": { + "description": "Widลผet centrum sterowania zostaล‚ usuniฤ™ty z paska. Aby mieฤ‡ do niego dostฤ™p, musisz go dodaฤ‡ ponownie. Moลผesz go rรณwnieลผ otworzyฤ‡, klikajฤ…c prawym przyciskiem na pasek", + "label": "Ostatni widลผet centrum sterowania usuniฤ™ty" + }, + "night-light": { + "forced": "Wymuszona aktywacja", + "normal": "Tryb normalny", + "not-installed": "wlsunset nie jest zainstalowany" + }, + "noctalia-performance": { + "disabled": "Tryb wydajnoล›ci wyล‚ฤ…czony", + "enabled": "Tryb wydajnoล›ci wล‚ฤ…czony", + "label": "Wydajnoล›ฤ‡ Noctalia" + }, + "power-profile": { + "changed": "Profil zasilania zmieniony", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "Napotkano bล‚ฤ…d podczas przetwarzania szablonรณw", + "title": "Przetwarzanie szablonu nie powiodล‚o siฤ™" + }, + "vpn": { + "connected": "Poล‚ฤ…czono z '{name}'", + "disconnected": "Rozล‚ฤ…czono z '{name}'" + }, + "wallpaper-colors": { + "disabled": "Kolory tapety wyล‚ฤ…czone", + "enabled": "Kolory tapety wล‚ฤ…czone", + "label": "Generowanie kolorรณw tapety" + }, + "wifi": { + "connected": "Poล‚ฤ…czono z '{ssid}'", + "connection-failed": "Poล‚ฤ…czenie nieudane", + "connection-timeout": "Przekroczenie czasu poล‚ฤ…czenia", + "disconnected": "Rozล‚ฤ…czono z '{ssid}'", + "incorrect-password": "Nieprawidล‚owe hasล‚o", + "network-not-found": "Nie znaleziono sieci" + } + }, + "tooltips": { + "add-widget": "Dodaj widลผet", + "auto-connect": "Przeล‚ฤ…cz automatyczne ล‚ฤ…czenie dla tego urzฤ…dzenia", + "bluetooth-auto-connect-off": "Automatyczne ล‚ฤ…czenie wyล‚ฤ…czone", + "bluetooth-auto-connect-on": "Automatyczne ล‚ฤ…czenie wล‚ฤ…czone", + "bluetooth-devices": "Urzฤ…dzenia Bluetooth", + "brightness-at": "Jasnoล›ฤ‡: {brightness}%", + "click-to-start-recording": "Nagrywanie ekranu (start)", + "click-to-stop-recording": "Nagrywanie ekranu (stop)", + "collapse": "Zwiล„ pasek boczny", + "copy-address": "Kopiuj adres", + "delete-notification": "Usuล„ powiadomienie", + "dismiss-notification": "Odrzuฤ‡ powiadomienie", + "do-not-disturb-enabled": "Nie przeszkadzaฤ‡", + "expand": "Rozwiล„ pasek boczny", + "forget-network": "Zapomnij sieฤ‡", + "grid-view": "Widok siatki", + "hidden-files-hide": "Ukryte pliki", + "hide-unnamed-devices": "Ukryj urzฤ…dzenia bez nazwy", + "home": "Katalog domowy", + "input-muted": "Przeล‚ฤ…cz wyciszenie wejล›cia", + "keep-awake": "Nie usypiaj", + "keyboard-layout": "Ukล‚ad klawiatury {layout}", + "list-view": "Widok listy", + "manage-vpn": "Poล‚ฤ…czenia VPN", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "Osiฤ…gniฤ™to maksymalnฤ… liczbฤ™ widลผetรณw", + "microphone-volume-at": "Gล‚oล›noล›ฤ‡ mikrofonu: {volume}%", + "move-to-section": "Przejdลบ do {sekcja}", + "mute": "Wycisz", + "next-media": "Nastฤ™pny utwรณr", + "next-month": "Nastฤ™pny miesiฤ…c", + "night-light-not-installed": "Podล›wietlenie nocne (niedostฤ™pne)", + "noctalia-performance-enabled": "Tryb wydajnoล›ci Noctalia", + "open-annotation-tool": "Otwรณrz za pomocฤ… narzฤ™dzia do adnotacji", + "open-control-center": "Centrum sterowania", + "open-notification-history-enable-dnd": "Historia powiadomieล„", + "open-settings": "Ustawienia", + "open-tray-dropdown": "Zasobnik systemowy", + "output-muted": "Przeล‚ฤ…cz wyciszenie wyjล›cia", + "power-profile": "Profil zasilania {profile}", + "previous-media": "Poprzedni utwรณr", + "previous-month": "Poprzedni miesiฤ…c", + "refresh-devices": "Odล›wieลผ urzฤ…dzenia", + "refresh-wallhaven": "Odล›wieลผ wyniki Wallhaven", + "refresh-wallpaper-list": "Odล›wieลผ listฤ™ tapet", + "remove-widget": "Usuล„ widลผet", + "search-close": "Zamknij wyszukiwanie", + "session-menu": "Menu sesji", + "show-all-devices": "Pokaลผ wszystkie urzฤ…dzenia", + "switch-to-dark-mode": "Tryb ciemny", + "switch-to-light-mode": "Tryb jasny", + "unmute": "Wyล‚ฤ…cz wyciszenie", + "up": "Katalog nadrzฤ™dny", + "volume-at": "Gล‚oล›noล›ฤ‡ wyjล›ciowa: {volume}%", + "wallpaper-selector": "Wybรณr tapety" + }, + "wallpaper": { + "browse": { + "empty-directory": "Ten katalog jest pusty.", + "go-root": "Przejdลบ do katalogu gล‚รณwnego tapety", + "go-up": "Przejdลบ do folderu nadrzฤ™dnego", + "go-up-hint": "Uลผyj przycisku wstecz, aby przejล›ฤ‡ w gรณrฤ™." + }, + "configure-directory": "Skonfiguruj swรณj katalog z obrazami tapet.", + "fill-modes": { + "crop": "Przytnij (Wypeล‚nij)", + "fit": "Dopasuj (Zmieล›ฤ‡)", + "repeat": "Powtรณrz (Kafel)", + "stretch": "Rozciฤ…gnij" + }, + "no-match": "Nie znaleziono dopasowania.", + "no-wallpaper": "Nie znaleziono tapety.", + "panel": { + "apikey-help": "Do dostฤ™pu do treล›ci NSFW wymagany jest klucz API.", + "apikey-label": "Klucz API", + "apikey-managed-by-env": "Zarzฤ…dzane za pomocฤ… zmiennej ล›rodowiskowej NOCTALIA_WALLHAVEN_API_KEY.", + "apikey-placeholder": "Wprowadลบ swรณj klucz API Wallhaven", + "appearance-dark-tab": "Ciemny", + "appearance-light-tab": "Jasny", + "categories-anime": "Anime", + "categories-label": "Kategorie", + "categories-people": "Ludzie", + "color-extraction-disabled": "Uลผyj ekstrakcji kolorรณw tapety", + "color-extraction-enabled": "Uลผyj predefiniowanych schematรณw kolorรณw", + "header-devices-apply-all-tooltip": "Wybierz tapetฤ™ na monitor", + "header-devices-per-monitor-tooltip": "Zastosuj do wszystkich monitorรณw", + "header-link-light-dark-tooltip": "Zachowaj tapetฤ™ przy zmianie motywu", + "header-separate-light-dark-tooltip": "Automatyczna zmiana tapety z motywem", + "header-sun-linked-tooltip": "Oddzielne jasne i ciemne tapety", + "header-sun-separate-tooltip": "Ta sama tapeta dla trybu jasnego i ciemnego", + "order-asc": "Rosnฤ…co", + "order-desc": "Malejฤ…co", + "order-label": "Kolejnoล›ฤ‡", + "purity-label": "Filtr treล›ci", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "Sketchy", + "ratios-all-portrait": "Wszystkie pionowe", + "ratios-all-wide": "Wszystkie szerokie", + "ratios-any": "Dowolny", + "ratios-label": "Wspรณล‚czynnik proporcji", + "resolution-atleast": "Przynajmniej", + "resolution-exact": "Dokล‚adnie", + "resolution-label": "Rozdzielczoล›ฤ‡", + "resolution-mode-label": "Tryb", + "solid-color-tooltip": "Jednolite tล‚o kolorystyczne", + "sort-date-asc": "Sortuj od najstarszych", + "sort-date-desc": "Sortuj od najnowszych", + "sort-name-asc": "Sortuj wedล‚ug nazwy (A-Z)", + "sort-name-desc": "Sortuj wedล‚ug nazwy (Z-A)", + "sort-random": "Sortuj losowo", + "sorting-date-added": "Data dodania", + "sorting-favorites": "Ulubione", + "sorting-label": "Sortuj wedล‚ug", + "sorting-relevance": "Trafnoล›ฤ‡", + "sorting-toplist": "Najlepsze", + "sorting-views": "Wyล›wietlenia", + "source-label": "ลนrรณdล‚o", + "source-wallhaven": "Wallhaven", + "title": "Selektor tapety", + "wallhaven-settings-title": "Ustawienia Wallhaven" + }, + "transitions": { + "disc": "Dysk", + "fade": "Zanikanie", + "honeycomb": "Plaster miodu", + "pixelate": "Pikselizuj", + "stripes": "Paski", + "wipe": "Wycieranie" + }, + "try-different-search": "Sprรณbuj innego zapytania.", + "wallhaven": { + "loading": "ลadowanie tapet...", + "no-results": "Nie znaleziono tapet. Sprรณbuj innego zapytania.", + "page": "{current} z {total}", + "page-prefix": "Strona", + "page-suffix": "z {total}" + } + }, + "weather": { + "clear-sky": "Czyste niebo", + "drizzle": "Mลผawka", + "fog": "Mgล‚a", + "mainly-clear": "Gล‚รณwnie bezchmurnie", + "overcast": "Zachmurzenie caล‚kowite", + "partly-cloudy": "Czฤ™ล›ciowe zachmurzenie", + "rain-showers": "Przelotne opady deszczu", + "snow": "ลšnieg", + "thunderstorm": "Burza" + }, + "widgets": { + "color-picker": { + "palette-description": "Wybierz z szerokiej gamy predefiniowanych kolorรณw.", + "palette-label": "Paleta", + "palette-theme-colors": "Szybki dostฤ™p do palety kolorรณw twojego motywu.", + "title": "Prรณbnik kolorรณw" + }, + "datetime-tokens": { + "ampm-lowercase": "am/pm maล‚ymi literami", + "ampm-uppercase": "AM/PM wielkimi literami", + "common-12hour-time-minutes": "Czas 12-godzinny z minutami", + "common-24hour-time-minutes": "Czas 24-godzinny z minutami", + "common-24hour-time-seconds": "Czas 24-godzinny z sekundami", + "common-european-date": "Europejski format daty", + "common-iso-date": "Format daty ISO", + "common-us-date": "Amerykaล„ski format daty", + "common-weekday-date": "Dzieล„ tygodnia z datฤ…", + "common-weekday-month-day": "Dzieล„ tygodnia, miesiฤ…c i dzieล„", + "day-abbreviated": "Skrรณcona nazwa dnia", + "day-full": "Peล‚na nazwa dnia", + "day-leading-zero": "Dzieล„ z zerem wiodฤ…cym (01-31)", + "day-no-leading-zero": "Dzieล„ bez zera wiodฤ…cego (1-31)", + "hour-leading-zero": "Godzina z zerem wiodฤ…cym (00-23) โ€” format 24h", + "hour-no-leading-zero": "Godzina bez zera wiodฤ…cego (0-23) โ€” format 24h", + "minute-leading-zero": "Minuta z zerem wiodฤ…cym (00-59)", + "minute-no-leading-zero": "Minuta bez zera wiodฤ…cego (0-59)", + "month-abbreviated": "Skrรณcona nazwa miesiฤ…ca", + "month-full": "Peล‚na nazwa miesiฤ…ca", + "month-number-leading-zero": "Miesiฤ…c jako liczba z zerem wiodฤ…cym (01-12)", + "month-number-no-zero": "Miesiฤ…c jako liczba bez zera wiodฤ…cego (1-12)", + "second-leading-zero": "Sekunda z zerem wiodฤ…cym (00-59)", + "second-no-leading-zero": "Sekunda bez zera wiodฤ…cego (0-59)", + "timezone-abbreviation": "Skrรณt strefy czasowej", + "year-four-digit": "Rok jako liczba czterocyfrowa", + "year-two-digit": "Rok jako liczba dwucyfrowa (00-99)" + }, + "file-picker": { + "item": "element", + "items": "elementy", + "search-placeholder": "Szukaj plikรณw i folderรณw...", + "select-current": "Wybierz bieลผฤ…cy", + "select-file": "Wybierz plik", + "select-folder": "Wybierz folder", + "selected": "Wybrano:", + "title": "Wybรณr plikรณw" + }, + "file-picker-title": "Wybierz plik", + "icon-picker": { + "title": "Wybรณr ikon" + }, + "tooltip-placeholder": "Miejsce na tekst" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "Anonimowa Toลผsamoล›ฤ‡", + "ca-cert": "Certyfikat CA", + "eap-method": "Metoda EAP", + "password": "Hasล‚o uลผytkownika", + "phase2-auth": "Uwierzytelnianie Fazy 2", + "username": "Toลผsamoล›ฤ‡ / Nazwa Uลผytkownika" + }, + "panel": { + "action-required": "Wymagane dziaล‚anie", + "add-hidden-network": "Dodaj ukrytฤ… sieฤ‡", + "add-network": "Dodaj sieฤ‡", + "available-interfaces": "Dostฤ™pne interfejsy", + "available-networks": "Dostฤ™pne sieci", + "disabled": "Wi-Fi jest wyล‚ฤ…czone", + "disconnecting": "Rozล‚ฤ…czanie...", + "dns": "DNS", + "enable-message": "Wล‚ฤ…cz Wi-Fi, aby zobaczyฤ‡ dostฤ™pne sieci.", + "enter-password": "Wpisz hasล‚o...", + "forget": "Zapomnij", + "forget-network": "Zapomnieฤ‡ tฤ™ sieฤ‡?", + "forgetting": "Zapominanie...", + "hidden-network": "Ukryta sieฤ‡", + "interface": "Interfejs sieciowy", + "internet-connected": "Poล‚ฤ…czony z internetem", + "internet-limited": "Brak internetu", + "internet-status": "Status internetu", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "Znane sieci", + "link-speed": "Prฤ™dkoล›ฤ‡ ล‚ฤ…cza", + "network-name-ssid": "Nazwa sieci (SSID)", + "no-ethernet-devices": "Nie wykryto ลผadnych urzฤ…dzeล„ Ethernet", + "no-networks": "Nie znaleziono ลผadnych sieci", + "saved": "Zapisano", + "scan-again": "Skanuj ponownie", + "searching": "Szukanie pobliskich sieci...", + "security-open": "Otwรณrz", + "security-wep": "WEP", + "security-wpa": "WPA/WPA2/WPA3 Osobiste", + "security-wpa-ent": "WPA Enterprise", + "security-wpa2-ent": "WPA2 Enterprise", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 Enterprise", + "show-password": "Pokaลผ hasล‚o", + "title": "Wi-Fi" + }, + "signal": { + "excellent": "Doskonaล‚y", + "fair": "Przeciฤ™tny", + "good": "Dobry", + "poor": "Zล‚y", + "weak": "Sล‚aby" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/pt.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/pt.json new file mode 100644 index 0000000..2080d9d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/pt.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "Limpar histรณrico", + "close-app": "Fechar {app}", + "connect-vpn": "Conectar-se a {name}", + "cycle-visualizer": "Visualizador de ciclo", + "disable-bluetooth": "Desativar Bluetooth", + "disable-dnd": "Desativar 'Nรฃo perturbe'", + "disable-wifi": "Desativar Wi-Fi", + "disconnect-vpn": "Desconectar {name}", + "dock-settings": "Definiรงรตes do dock", + "enable-bluetooth": "Ativar Bluetooth", + "enable-dnd": "Ativar 'Nรฃo perturbe''", + "enable-wifi": "Ativar Wi-Fi", + "launcher-settings": "Configuraรงรตes do iniciador", + "lower-to-bottom": "Abaixar para o fundo", + "open-calendar": "Abrir calendรกrio", + "open-display-settings": "Configuraรงรตes de exibiรงรฃo", + "open-launcher": "Abrir iniciador", + "open-settings": "Abrir configuraรงรตes", + "raise-to-top": "Elevar ao topo", + "random-wallpaper": "Papel de parede aleatรณrio", + "run-custom-command": "Executar comando personalizado", + "show-active-only": "Mostrar apenas ativos", + "show-all": "Mostrar tudo", + "toggle-mute": "Alternar mudo", + "widget-settings": "Configuraรงรตes do widget" + }, + "authentication": { + "error": "Erro de autenticaรงรฃo", + "failed": "Falha na autenticaรงรฃo" + }, + "bar": { + "active-window": { + "colorize-icons-description": "Aplicar cores do tema ao รญcone da janela ativa.", + "hide-mode-description": "Controla o comportamento do widget quando nenhuma janela estรก ativa.", + "scrolling-mode-description": "Controlar quando a rolagem de texto estรก habilitada para tรญtulos de janela longos.", + "show-app-icon-description": "Exibir o รญcone do aplicativo ao lado do tรญtulo da janela.", + "show-app-icon-label": "Mostrar รญcone do aplicativo", + "show-app-text-description": "Exibir o tรญtulo da janela ativa.", + "show-app-text-label": "Mostrar tรญtulo da janela" + }, + "audio-visualizer": { + "color-name-description": "Selecione a cor para o visualizador.", + "color-name-label": "Cor de preenchimento", + "height-description": "Largura de componente personalizada.", + "hide-when-idle-description": "Quando ativado, o visualizador fica oculto a menos que haja reproduรงรฃo ativa.", + "hide-when-idle-label": "Ocultar quando nรฃo houver reproduรงรฃo", + "width-description": "Largura do componente personalizado." + }, + "battery": { + "device-default": "Padrรฃo (Dispositivo de exibiรงรฃo)", + "device-description": "Selecione qual dispositivo de bateria exibir.", + "device-label": "Dispositivo de bateria", + "display-mode-description": "Escolha como a bateria รฉ exibida na barra.", + "display-mode-graphic": "Bateria grรกfica", + "display-mode-graphic-clean": "Bateria grรกfica (sem %)", + "display-mode-icon-always": "รcone - Sempre mostrar %", + "display-mode-icon-hover": "รcone - Mostrar ao passar", + "display-mode-icon-only": "Apenas รญcone", + "hide-if-idle-description": "Ocultar o widget quando a bateria nรฃo estiver carregando ou descarregando.", + "hide-if-idle-label": "Ocultar quando inativo", + "hide-if-not-detected-description": "Oculte o widget quando nenhuma bateria for detectada no sistema.", + "hide-if-not-detected-label": "Ocultar se nรฃo detectado", + "low-battery-threshold-label": "Limite de aviso de bateria fraca", + "show-noctalia-performance-description": "Exibir o interruptor do modo de desempenho Noctalia no painel da bateria.
Desativa sombras e animaรงรตes no Noctalia para reduzir o uso de recursos.", + "show-noctalia-performance-label": "Mostrar alternรขncia Noctalia Desempenho", + "show-power-profile-description": "Exibir a seleรงรฃo de perfil de energia no painel da bateria.", + "show-power-profile-label": "Mostrar controles de perfil de energia" + }, + "brightness": { + "apply-all-description": "Alterar o brilho de todos os monitores ao usar a roda de rolagem.", + "apply-all-label": "Aplicar alteraรงรตes de rolagem a todos os monitores" + }, + "clock": { + "clock-display-description": "Personalize a exibiรงรฃo do seu relรณgio adicionando tokens da lista abaixo. Para usar o formato de 12 horas, vocรช deve incluir o token 'AP'.", + "clock-display-label": "Exibiรงรฃo do relรณgio", + "custom-font-description": "Selecione uma fonte personalizada para a exibiรงรฃo do relรณgio.", + "custom-font-label": "Fonte personalizada", + "custom-font-placeholder": "Selecionar fonte personalizada...", + "custom-font-search-placeholder": "Procurar fontes...", + "horizontal-bar-description": "Dica: Use \\n para criar uma quebra de linha.", + "horizontal-bar-label": "Barra horizontal", + "preview": "Prรฉ-visualizaรงรฃo", + "tooltip-format-description": "String de formato para a dica de ferramenta exibida ao passar o mouse sobre o relรณgio. Deixe vazio para usar a dica de ferramenta padrรฃo.", + "tooltip-format-label": "Formato da dica de ferramenta", + "use-custom-font-description": "Substitua a fonte padrรฃo por uma fonte personalizada para o relรณgio.", + "use-custom-font-label": "Usar fonte personalizada", + "use-monospaced-font-description": "Quando ativado, o relรณgio usarรก a fonte monoespaรงada.", + "use-monospaced-font-label": "Usar fonte monoespaรงada", + "vertical-bar-description": "Use um espaรงo para separar cada parte em uma nova linha.", + "vertical-bar-label": "Barra vertical" + }, + "control-center": { + "browse-file": "Navegar por arquivo", + "browse-library": "Navegar na biblioteca", + "color-selection-description": "Aplica as cores do tema aos รญcones.", + "enable-colorization-description": "Ativar colorizaรงรฃo para o รญcone, aplicando as cores do tema.", + "icon-description": "Selecione um รญcone da biblioteca ou um arquivo personalizado.", + "select-custom-icon": "Selecione um รญcone personalizado", + "use-distro-logo-description": "Usar o logo da sua distribuiรงรฃo em vez de um รญcone personalizado.", + "use-distro-logo-label": "Usar logo da distribuiรงรฃo em vez de รญcone" + }, + "custom-button": { + "collapse-condition-description": "Se o texto de saรญda corresponder a este valor, o botรฃo serรก recolhido.", + "collapse-condition-label": "Condiรงรฃo de recolhimento", + "color-selection-description": "Aplicar cores do tema ao รญcone e ao texto.", + "default-tooltip": "Botรฃo personalizado, configurar nas configuraรงรตes", + "display-command-output-description": "Digite um comando para ser executado em um intervalo regular. A primeira linha da sua saรญda serรก exibida como texto.", + "display-command-output-label": "Exibir saรญda de comando", + "display-command-output-stream-description": "Insira um comando para executar continuamente.", + "dynamic-text": "Texto dinรขmico", + "enable-colorization-description": "Ativar colorizaรงรฃo para o รญcone e texto do botรฃo personalizado, aplicando as cores do tema.", + "enable-colorization-label": "Ativar colorizaรงรฃo", + "general-tooltip-text-description": "Texto personalizado para exibir na dica de ferramenta do botรฃo.", + "general-tooltip-text-label": "Texto de dica de ferramenta personalizado", + "hide-mode-always-expanded": "Sempre expandido", + "hide-mode-description": "Controla a visibilidade do widget quando o comando nรฃo tem saรญda.", + "hide-mode-expand-with-output": "Expandir quando tem saรญda", + "hide-mode-label": "Modo de ocultaรงรฃo", + "hide-mode-max-transparent": "Totalmente expandido mas transparente", + "icon-description": "Selecione um รญcone da biblioteca.", + "ipc-identifier-description": "Identificador exclusivo para comandos IPC. Use este identificador com 'qs -c noctalia-shell ipc call cb [aรงรฃo] [identificador]' para controlar este botรฃo via IPC.", + "ipc-identifier-label": "Identificador IPC", + "left-click-description": "Comando a executar quando o botรฃo รฉ clicado com o botรฃo esquerdo.", + "left-click-label": "Clique esquerdo", + "left-click-update-text": "Atualizar texto exibido ao clicar com o botรฃo esquerdo", + "max-text-length-horizontal-description": "Nรบmero mรกximo de caracteres a serem exibidos na barra horizontal (0 para ocultar o texto).", + "max-text-length-horizontal-label": "Comprimento mรกximo do texto (horizontal)", + "max-text-length-vertical-description": "Nรบmero mรกximo de caracteres a serem exibidos na barra vertical (0 para ocultar o texto).", + "max-text-length-vertical-label": "Comprimento mรกximo do texto (vertical)", + "middle-click-description": "Comando a executar quando o botรฃo รฉ clicado com o botรฃo do meio.", + "middle-click-label": "Clique do meio", + "middle-click-update-text": "Atualizar texto exibido com clique do meio", + "parse-json-description": "Analisa a saรญda do comando como um objeto JSON para definir dinamicamente o texto e o รญcone.", + "parse-json-label": "Analisar saรญda como JSON", + "refresh-interval-description": "Intervalo em milissegundos.", + "refresh-interval-label": "Intervalo de atualizaรงรฃo", + "right-click-description": "Comando a executar quando o botรฃo รฉ clicado com o botรฃo direito.", + "right-click-label": "Clique direito", + "right-click-update-text": "Atualizar o texto exibido ao clicar com o botรฃo direito", + "show-exec-tooltip-description": "Mostrar tooltips com detalhes do comando (clique esquerdo/direito/meio, roda).", + "show-exec-tooltip-label": "Mostrar dicas de ferramentas de comando", + "show-icon-description": "Alterna a visibilidade do รญcone do widget.", + "show-icon-label": "Mostrar รญcone", + "show-text-tooltip-description": "Mostrar dicas de ferramentas com a saรญda do comando de texto.", + "show-text-tooltip-label": "Mostrar dicas de texto dinรขmicas", + "text-stream-description": "As linhas transmitidas do comando serรฃo exibidas como texto no botรฃo.", + "text-stream-label": "Transmissรฃo", + "wheel-description": "Comando a executar quando a roda de rolagem รฉ usada.
Use $delta para o delta da roda de rolagem no comando.", + "wheel-down": "Roda baixo", + "wheel-down-description": "Comando a executar quando a roda de rolagem รฉ rolada para baixo.", + "wheel-down-label": "Comando de roda para baixo", + "wheel-label": "Roda de rolagem", + "wheel-mode-separate-description": "Ativar comandos separados para roda para cima e para baixo.", + "wheel-mode-separate-label": "Comandos de roda separados", + "wheel-up": "Roda cima", + "wheel-up-description": "Comando a executar quando a roda de rolagem รฉ rolada para cima.", + "wheel-up-label": "Comando de roda para cima", + "wheel-update-text": "Atualizar texto exibido ao rolar" + }, + "keyboard-layout": { + "show-icon-description": "Exibir o รญcone do layout do teclado." + }, + "lock-keys": { + "hide-when-off-description": "Ocultar o indicador quando a tecla nรฃo estรก ativa.", + "hide-when-off-label": "Ocultar quando desativado", + "show-caps-lock-description": "Exibir o status do Bloq Caps.", + "show-caps-lock-label": "Bloq Caps", + "show-num-lock-description": "Exibir o status do Bloq Num.", + "show-num-lock-label": "Bloq Num", + "show-scroll-lock-description": "Exibir o status do Bloq Rol.", + "show-scroll-lock-label": "Bloq Rol" + }, + "media-mini": { + "compact-mode-description": "Ativar um layout que economize espaรงo para o painel do reprodutor de mรญdia.", + "compact-mode-label": "Modo compacto", + "hide-mode-description": "Controla o comportamento do widget quando nenhuma mรญdia estรก sendo reproduzida.", + "max-width-description": "Define o tamanho horizontal mรกximo do widget. O widget serรก reduzido para se adequar a conteรบdo mais curto.", + "no-active-player": "Nenhum player ativo", + "panel-section-description": "Configure a aparรชncia e o comportamento do painel do reprodutor de mรญdia.", + "panel-section-label": "Painel do reprodutor de mรญdia", + "scrolling-mode-description": "Controlar quando a rolagem de texto estรก habilitada para tรญtulos de faixa longos.", + "scrolling-mode-label": "Modo de rolagem", + "show-album-art-description": "Exibir a arte do รกlbum da faixa atualmente sendo reproduzida.", + "show-album-art-label": "Mostrar arte do รกlbum", + "show-artist-first-description": "Exibir artista - tรญtulo em vez de tรญtulo - artista.", + "show-artist-first-label": "Mostrar o artista primeiro", + "show-progress-ring-description": "Exibir um indicador de progresso circular mostrando o progresso da faixa.", + "show-progress-ring-label": "Mostrar anel de progresso", + "show-visualizer-description": "Exibir um visualizador de รกudio quando mรบsica estรก sendo reproduzida.", + "show-visualizer-label": "Mostrar visualizador", + "use-fixed-width-description": "Quando ativado, o widget sempre usarรก a largura mรกxima em vez de ajustar dinamicamente ao conteรบdo.", + "use-fixed-width-label": "Usar largura fixa", + "visualizer-type-description": "Escolher o estilo de visualizador de รกudio a exibir.", + "visualizer-type-label": "Tipo de visualizador" + }, + "notification-history": { + "hide-widget-when-zero-description": "Ocultar o รญcone de notificaรงรตes quando nรฃo houver notificaรงรตes.", + "hide-widget-when-zero-label": "Ocultar รญcone sem notificaรงรตes", + "hide-widget-when-zero-unread-description": "Ocultar o รญcone de notificaรงรตes quando nรฃo houver notificaรงรตes nรฃo lidas.", + "hide-widget-when-zero-unread-label": "Ocultar รญcone sem notificaรงรตes nรฃo lidas", + "show-unread-badge-description": "Exibir um distintivo mostrando o nรบmero de notificaรงรตes nรฃo lidas.", + "show-unread-badge-label": "Mostrar distintivo de nรฃo lidos", + "unread-badge-color-description": "Selecione a cor para o emblema de notificaรงรฃo nรฃo lida.", + "unread-badge-color-label": "Cor do distintivo de nรฃo lido" + }, + "section-editor": { + "placeholder": "Selecione um widget para adicionar...", + "search-placeholder": "Pesquisar widgets..." + }, + "spacer": { + "width-description": "Largura do espaรงamento em pixels." + }, + "system-monitor": { + "compact-mode-description": "Exibir as estatรญsticas como mini grรกficos de barras em vez de valores de texto. Evita mudanรงas no layout.", + "compact-mode-label": "Modo compacto", + "cpu-cores-description": "Exibir o uso dos nรบcleos da CPU individualmente.", + "cpu-cores-label": "Nรบcleos de CPU", + "cpu-frequency-description": "Exibir a velocidade de clock atual da CPU em GHz.", + "cpu-frequency-label": "Mostrar frequรชncia da CPU", + "cpu-temperature-description": "Mostrar as leituras de temperatura da CPU se disponรญveis.", + "cpu-temperature-label": "Temperatura da CPU", + "cpu-usage-description": "Exibir o percentual atual de uso da CPU.", + "cpu-usage-label": "Uso da CPU", + "disk-path-description": "Selecione qual ponto de montagem de disco monitorar.", + "disk-path-label": "Caminho do disco", + "gpu-temperature-description": "Mostrar as leituras de temperatura da GPU se disponรญveis.", + "load-average-description": "Exibir a carga mรฉdia do sistema.", + "load-average-label": "Carga mรฉdia", + "memory-percentage-description": "Mostrar o uso de memรณria como percentual em vez de valores absolutos.", + "memory-percentage-label": "Memรณria como percentual", + "memory-usage-description": "Exibir as informaรงรตes atuais de uso da RAM.", + "memory-usage-label": "Uso de memรณria", + "network-traffic-description": "Exibir as velocidades de upload e download da rede.", + "network-traffic-label": "Trรกfego de rede", + "storage-as-percentage-description": "Mostrar o espaรงo em disco como percentagem em vez de valores absolutos.", + "storage-as-percentage-label": "Disco como percentagem", + "storage-available-description": "Mostra quanto espaรงo em disco estรก disponรญvel em vez de quanto รฉ usado.", + "storage-available-label": "Espaรงo em disco disponรญvel", + "storage-usage-description": "Mostrar as informaรงรตes de uso do espaรงo em disco.", + "storage-usage-label": "Uso de armazenamento", + "swap-usage-description": "Mostrar o uso da memรณria swap.", + "swap-usage-label": "Utilizaรงรฃo de swap", + "use-monospace-font-description": "Use fonte monoespaรงada para largura de caractere consistente.", + "use-monospace-font-label": "Fonte monoespaรงada", + "use-padding-description": "Preenche os valores de texto com espaรงos iniciais para evitar o deslocamento do layout.", + "use-padding-description-disabled-monospace-font": "ร‰ necessรกria uma fonte monoespaรงada para esta funcionalidade.", + "use-padding-description-disabled-vertical": "A barra de tarefas vertical nรฃo suporta preenchimento de texto.", + "use-padding-label": "Preenchimento do Texto" + }, + "taskbar": { + "colorize-icons-description": "Aplicar cores do tema aos รญcones da barra de tarefas.", + "hide-mode-description": "Controla como o widget se comporta quando nรฃo hรก janelas correspondentes.", + "hide-mode-label": "Modo de ocultaรงรฃo", + "icon-scale-description": "Define o fator de escala para รญcones da barra de tarefas.", + "icon-scale-label": "Escalonamento de รญcones", + "max-width-description": "Largura mรกxima da barra de tarefas como porcentagem da largura da tela.", + "max-width-label": "Largura mรกxima", + "only-active-workspaces-description": "Mostrar apenas aplicativos de รกreas de trabalho ativas.", + "only-active-workspaces-label": "Apenas de espaรงos de trabalho ativos", + "only-same-monitor-description": "Mostrar apenas aplicativos do monitor onde a barra estรก localizada.", + "only-same-monitor-label": "Apenas do mesmo monitor", + "show-pinned-apps-description": "Mostrar aplicativos fixados do Dock na barra de tarefas.", + "show-pinned-apps-label": "Mostrar aplicativos fixados", + "show-title-description": "Exibir tรญtulos de janelas na barra de tarefas.", + "show-title-description-disabled": "A barra de tarefas vertical nรฃo suporta exibir tรญtulos.", + "show-title-label": "Mostrar tรญtulo", + "smart-width-description": "Ajustar automaticamente a largura das entradas com base no nรบmero de entradas.", + "smart-width-label": "Largura inteligente", + "title-width-description": "Definir a largura dos tรญtulos de janelas na barra de tarefas (em pixels).", + "title-width-label": "Largura do tรญtulo", + "title-width-reset-tooltip": "Redefinir largura do tรญtulo" + }, + "tray": { + "chevron-color-description": "Aplicar as cores do tema ao รญcone de seta da gaveta.", + "chevron-color-label": "Cor da seta", + "colorize-icons-description": "Aplicar cores do tema aos รญcones da bandeja do sistema.", + "colorize-icons-label": "Colorir รญcones", + "drawer-enabled-description": "Quando habilitado, os itens da bandeja nรฃo fixados sรฃo exibidos em um painel gaveta.
Quando desabilitado, todos os itens da bandeja sรฃo exibidos inline.", + "drawer-enabled-label": "Habilitar gaveta", + "hide-passive-description": "Quando ativado, os itens da bandeja com status 'Passivo' serรฃo ocultados.", + "hide-passive-label": "Ocultar itens passivos" + }, + "volume": { + "display-mode-description": "Escolha como vocรช gostaria que este valor aparecesse." + }, + "workspace": { + "character-count-description": "Nรบmero de caracteres a exibir dos nomes de espaรงos de trabalho (1-10).", + "character-count-label": "Nรบmero de caracteres", + "empty-color-description": "Definir a cor de fundo para Workspaces vazios.", + "empty-color-label": "Cor do espaรงo de trabalho vazio", + "enable-scrollwheel-description": "Alternar entre รกreas de trabalho usando a roda do mouse.", + "enable-scrollwheel-label": "Role para alternar entre รกreas de trabalho", + "focused-color-description": "Definir a cor de fundo para o Workspace focado.", + "focused-color-label": "Cor do workspace focado", + "follow-focused-screen-description": "Exibir รกreas de trabalho da tela atualmente em foco, em vez da tela onde a barra estรก localizada.", + "follow-focused-screen-label": "Seguir tela em foco", + "font-weight-description": "Definir o peso visual para o texto na รกrea de trabalho.", + "font-weight-label": "Peso da fonte", + "grouped-border-opacity-description": "Definir o nรญvel de opacidade para as bordas do contรชiner do espaรงo de trabalho.", + "grouped-border-opacity-label": "Opacidade da borda", + "hide-unoccupied-description": "Nรฃo exibir รกreas de trabalho sem janelas.", + "hide-unoccupied-label": "Ocultar desocupados", + "label-mode-description": "Escolher como os rรณtulos de espaรงo de trabalho sรฃo exibidos.", + "label-mode-label": "Modo de rรณtulo", + "occupied-color-description": "Definir a cor de fundo para Workspaces ocupados.", + "occupied-color-label": "Cor do espaรงo de trabalho ocupado", + "pill-size-description": "Ajuste o tamanho das pรญlulas da รกrea de trabalho (50%-100%).", + "pill-size-label": "Tamanho da pรญlula", + "reverse-scrolling-description": "Inverter a direรงรฃo da troca de รกreas de trabalho ao rolar.", + "reverse-scrolling-label": "Rolagem invertida", + "show-applications-description": "Exibir รญcones de aplicativos dentro de cada espaรงo de trabalho.", + "show-applications-hover-description": "Exibir รญcones de aplicativos dentro de cada รกrea de trabalho quando a รกrea de trabalho รฉ sobreposta.", + "show-applications-hover-label": "Mostrar aplicativos ao passar o mouse", + "show-applications-label": "Mostrar aplicativos", + "show-badge-description": "Mostrar o selo do nรบmero da รกrea de trabalho no modo agrupado.", + "show-badge-label": "Mostrar selo da รกrea de trabalho", + "show-labels-only-when-occupied-description": "Mostrar rรณtulos de รกrea de trabalho apenas quando contiverem janelas.", + "show-labels-only-when-occupied-label": "Mostrar rรณtulos apenas quando ocupado", + "unfocused-icons-opacity-description": "Definir o nรญvel de opacidade para รญcones de aplicativos nรฃo focados.", + "unfocused-icons-opacity-label": "Opacidade dos รญcones desfocados" + } + }, + "battery": { + "all-batteries": "Todas as baterias (combinadas)", + "battery-health": "Estado da bateria", + "battery-level": "Nรญvel da bateria", + "capacity-level": "Capacidade: {level}", + "charging-rate": "Taxa de carregamento: {rate} W", + "discharging-rate": "Taxa de descarregamento: {rate} W", + "health": "Saรบde: {percent}%", + "inhibit-idle-description": "Mantรฉm o sistema acordado.", + "no-battery-detected": "Nenhuma bateria detectada", + "plugged-in": "Conectado", + "power-profile": "Perfil de energia", + "time-left": "Tempo restante: {time}", + "time-until-full": "Tempo atรฉ carga completa: {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "Dispositivos disponรญveis", + "blocked": "Bloqueado", + "connected-devices": "Dispositivos conectados", + "device-address": "Endereรงo do dispositivo", + "disabled": "O Bluetooth estรก desativado", + "discoverable": "Visรญvel", + "enable-message": "Ative o Bluetooth para ver os dispositivos disponรญveis.", + "known-devices": "Dispositivos conhecidos", + "no-devices": "Nenhum dispositivo disponรญvel", + "paired-devices": "Dispositivos emparelhados", + "pairing-mode": "Certifique-se de que seu dispositivo estรก em modo de pareamento.", + "scanning": "Procurando por dispositivos...", + "signal-text-excellent": "Sinal: Excelente", + "signal-text-fair": "Sinal: Justo", + "signal-text-good": "Sinal: Bom", + "signal-text-poor": "Sinal: Fraco", + "signal-text-unknown": "Sinal: Desconhecido", + "signal-text-very-poor": "Sinal: Muito fraco" + } + }, + "changelog": { + "error": { + "fetch-failed": "Nรฃo foi possรญvel carregar os dados do changelog. Tente novamente mais tarde.", + "rate-limit": "Limite do GitHub atingido. Tente novamente em alguns minutos." + }, + "panel": { + "buttons-discord": "Entre no nosso Discord", + "buttons-dismiss": "OK", + "buttons-feedback": "Dar feedback", + "empty": "As notas da versรฃo ainda nรฃo estรฃo disponรญveis.", + "highlight-title": "Destaques", + "released": "Lanรงado em {date}", + "subtitle-fresh": "Obrigado por instalar o Noctalia! Veja o que estรก incluรญdo nesta compilaรงรฃo.", + "subtitle-updated": "Atualizado a partir da {previousVersion}", + "title": "Novidades na {version}", + "version": "Versรฃo {version}", + "version-new-user": "Nova instalaรงรฃo" + } + }, + "common": { + "actions": "Aรงรตes", + "active": "Ativo", + "add": "Adicionar", + "appearance": "Aparรชncia", + "apply": "Aplicar", + "auto-connect": "Conexรฃo automรกtica", + "automation": "Automaรงรฃo", + "available": "Disponรญvel", + "back": "Voltar", + "battery": "Bateria", + "behavior": "Comportamento", + "bluetooth": "Bluetooth", + "brightness": "Brilho", + "browse": "Navegar", + "calendar": "Calendรกrio", + "calendar-panel": "Painel do calendรกrio", + "cancel": "Cancelar", + "cards": "Cartรตes", + "charging": "Carregando", + "clear": "Limpar", + "clipboard": "รrea de transferรชncia", + "close": "Fechar", + "color-muted": "Silenciado", + "colors": "Cores", + "command": "Comando", + "confirm": "Confirmar", + "connect": "Conectar", + "connected": "Conectado(a)", + "connecting": "Conectando...", + "continue": "Continuar", + "contributors": "Colaboradores", + "copied-to-clipboard": "Copiado para a รกrea de transferรชncia", + "countdown": "Contagem regressiva", + "customize": "Personalizar", + "date": "Data", + "default": "Padrรฃo", + "delete": "Apagar", + "devices": "Dispositivos", + "disabled": "Desativado", + "discharging": "Descarregando", + "disconnect": "Desconectar", + "disconnected": "Desconectado(a)", + "disconnecting": "Desconectando...", + "display-mode": "Modo de exibiรงรฃo", + "documentation": "Documentaรงรฃo", + "download": "Baixar", + "duration": "Duraรงรฃo", + "dysfunctional": "Disfuncional", + "edit": "Editar", + "enabled": "Ativado", + "error": "Erro", + "ethernet": "Ethernet", + "events": "Eventos", + "execute": "Executar", + "faithful": "Fiel", + "focus": "Foco", + "font-weight-bold": "Negrito", + "font-weight-light": "Leve", + "font-weight-medium": "Mรฉdio", + "font-weight-regular": "Regular", + "font-weight-semibold": "Seminegrito", + "frequency": "Frequรชncia", + "gateway": "Porta de entrada", + "general": "Geral", + "height": "Altura", + "hibernate": "Hibernar", + "history": "Histรณria", + "icon": "รcone", + "idle": "Ocioso", + "info": "Informaรงรฃo", + "input": "Entrada", + "install": "Instalar", + "installed": "Instalado", + "interface": "Interface", + "internet": "Internet", + "keybind": "Atalho de teclado", + "language": "Linguagem", + "loading": "Carregando...", + "local": "Local", + "location": "Localizaรงรฃo", + "lock": "Bloquear", + "logout": "Sair", + "look": "Aparรชncia", + "margins": "Margens", + "media": "Mรญdia", + "media-player": "Reprodutor de mรญdia", + "memory": "Memรณria", + "monitors": "Monitores", + "move-down": "Mover para baixo", + "move-up": "Mover para cima", + "network": "Rede", + "next": "Prรณximo", + "night-light": "Luz noturna", + "no": "Nรฃo", + "no-results": "Nenhum resultado", + "no-summary": "Sem resumo", + "none": "Nenhum", + "not-found": "Nรฃo encontrado", + "notifications": "Notificaรงรตes", + "official": "Oficial", + "on-surface": "Na Superfรญcie", + "output": "Saรญda", + "pair": "Emparelhar", + "paired": "Emparelhado", + "pairing": "Emparelhando...", + "panels": "Painรฉis", + "password": "Senha", + "pause": "Pausar", + "performance": "Desempenho", + "pin": "Fixar", + "play": "Reproduzir", + "polling": "Sondagem", + "position": "Posiรงรฃo", + "previous": "Anterior", + "primary": "Primรกrio", + "random": "Aleatรณrio", + "reboot": "Reiniciar", + "reboot-to-uefi": "Reiniciar para UEFI", + "record": "Iniciar gravaรงรฃo", + "refresh": "Atualizar", + "required": "(obrigatรณrio)", + "reset": "Reiniciar", + "reset-to-default": "Redefinir para o Padrรฃo", + "result-count": "{count} resultado", + "result-count-plural": "{count} resultados", + "resume": "Retomar", + "save": "Salvar", + "scanning": "A digitalizaรงรฃo estรก em andamento...", + "screen-corners": "Cantos da tela", + "search": "Pesquisar", + "second": "{count} segundo", + "second-plural": "{count} segundos", + "secondary": "Secundรกrio", + "security": "Seguranรงa", + "select": "Selecionar", + "select-color": "Selecionar cor", + "select-color-description": "Aplicar cores do tema para enfatizar.", + "select-icon-color": "Selecionar cor do รญcone", + "settings": "Definiรงรตes", + "shortcuts": "Atalhos", + "shutdown": "Desligar", + "signal": "Sinal", + "sound": "Som", + "sources": "Fontes", + "start": "Comeรงar", + "stop": "Pare", + "supporters": "Apoiadores", + "suspend": "Suspender", + "templates": "Modelos", + "tertiary": "Terciรกrio", + "test": "Teste", + "thresholds": "Limiares", + "title": "Tรญtulo", + "toast": "Toast", + "trusted": "Confiรกvel", + "uninstall": "Desinstalar", + "unknown": "Desconhecido", + "unpair": "Desemparelhar", + "unpin": "Desafixar", + "update": "Atualizaรงรฃo", + "upload": "Enviar", + "username": "Nome de usuรกrio", + "userspace-reboot": "Reiniciar espaรงo de usuรกrio", + "version": "Versรฃo", + "vibrant": "Vibrante", + "visualizer": "Visualizador", + "volume": "Volume", + "volumes": "Volumes", + "wallpaper": "Papel de parede", + "weather": "Tempo", + "weather-loading": "Carregando o clima...", + "weather-no-location": "Defina um local nas Definiรงรตes para ver o tempo", + "week": "Semana", + "widgets": "Widgets", + "width": "Largura", + "wifi": "Wi-Fi", + "windows": "Janelas", + "yes": "Sim" + }, + "control-center": { + "power-profile": { + "tooltip-action": "Perfil de energia", + "tooltip-disabled": "Instale power-profiles-daemon para usar perfis de energia" + }, + "wifi": { + "label-disconnected": "Wi-Fi desconectado", + "label-ethernet": "Ethernet" + } + }, + "display-modes": { + "always-hide": "Sempre se esconda", + "always-show": "Sempre mostrar", + "force-open": "Forรงar a abertura", + "on-hover": "Ao passar" + }, + "hide-modes": { + "auto-hide": "Ocultar automaticamente", + "hidden": "Ocultar quando vazio", + "idle": "Ocultar quando inativo", + "non-exclusive": "Nรฃo exclusivo", + "transparent": "Transparente quando vazio", + "visible": "Sempre visรญvel" + }, + "launcher": { + "categories": { + "all": "Tudo", + "audiovideo": "รudio e vรญdeo", + "chat": "Bate-papo", + "development": "Desenvolvimento", + "education": "Educaรงรฃo", + "emoji-activity": "Atividade", + "emoji-animals": "Animais", + "emoji-flags": "Bandeiras", + "emoji-food": "Comida e bebida", + "emoji-nature": "Natureza", + "emoji-objects": "Objetos", + "emoji-people": "Pessoas e corpo", + "emoji-recent": "Recentes", + "emoji-symbols": "Sรญmbolos", + "emoji-travel": "Viagens e lugares", + "game": "Jogos", + "graphics": "Grรกficos", + "misc": "Diversos", + "office": "Escritรณrio", + "pinned": "Fixado", + "system": "Sistema", + "webbrowser": "Navegador web" + }, + "providers": { + "applications": "Aplicativos", + "calculator": "Calculadora", + "calculator-deprecated": "O comando >calc estรก obsoleto e serรก removido em breve. Por favor, digite expressรตes matemรกticas diretamente na pesquisa.", + "calculator-description": "Calculadora โ€” avalie expressรตes matemรกticas", + "calculator-enter-expression": "Digite uma expressรฃo matemรกtica", + "calculator-press-enter-to-copy": "Pressione Enter para copiar o resultado", + "clipboard": "Histรณrico da รกrea de transferรชncia", + "clipboard-clear-description": "Limpar todo o histรณrico da รกrea de transferรชncia", + "clipboard-clear-description-full": "Remover todos os itens do histรณrico da รกrea de transferรชncia", + "clipboard-clear-history": "Limpar histรณrico da รกrea de transferรชncia", + "clipboard-delete": "Apagar entrada da รกrea de transferรชncia", + "clipboard-history-disabled": "Histรณrico da รกrea de transferรชncia desativado", + "clipboard-history-disabled-description": "Ative o histรณrico da รกrea de transferรชncia nas configuraรงรตes ou instale o cliphist", + "clipboard-loading": "Carregando histรณrico da รกrea de transferรชncia...", + "clipboard-search-description": "Pesquisar no histรณrico da รกrea de transferรชncia", + "command-description": "Executar comandos shell", + "emoji": "Seletor de emojis", + "emoji-loading": "Carregando emojis...", + "emoji-loading-description": "Por favor, aguarde", + "emoji-no-recent": "Ainda nรฃo hรก emojis recentes", + "emoji-search-description": "Buscar e copiar emojis", + "settings-search-description": "Pesquisar e navegar para as configuraรงรตes", + "windows-search-description": "Pesquisar e focar janelas abertas" + } + }, + "lock-screen": { + "authenticating": "Autenticando...", + "password": "Digite sua senha...", + "restart": "Reiniciar", + "shut-down": "Desligar" + }, + "notifications": { + "panel": { + "click-to-collapse": "Clique para recolher", + "click-to-expand": "Clique para expandir", + "description": "Suas notificaรงรตes aparecerรฃo aqui assim que chegarem.", + "no-notifications": "Nenhuma notificaรงรฃo" + }, + "range": { + "earlier": "Antigas", + "today": "Hoje", + "yesterday": "Ontem" + }, + "time": { + "diff-d": "hรก 1 dia", + "diff-dd": "hรก {diff} dias", + "diff-h": "hรก 1 hora", + "diff-hh": "hรก {diff} horas", + "diff-m": "hรก 1 minuto", + "diff-mm": "hรก {diff} minutos", + "now": "agora" + } + }, + "options": { + "bar": { + "density-comfortable": "Confortรกvel", + "density-compact": "Compacta", + "density-default": "Padrรฃo", + "density-mini": "Mini", + "density-spacious": "Espaรงoso(a)", + "display-mode-always-visible": "Sempre Visรญvel", + "type-floating": "Flutuante", + "type-framed": "Emoldurada", + "type-simple": "Simples" + }, + "control-center": { + "quick-settings-style-classic": "Clรกssico", + "quick-settings-style-modern": "Moderno" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "Confortรกvel", + "compact": "Compacto", + "default": "Padrรฃo" + }, + "launcher-view-mode": { + "grid": "Grade", + "list": "Lista" + }, + "notification-density": { + "compact": "Compacto", + "default": "Padrรฃo" + }, + "scrolling-modes": { + "always": "Rolar sempre", + "hover": "Rolar ao passar", + "never": "Nunca rolar" + }, + "session-menu-grid-layout": { + "grid": "Grade", + "single-row": "Linha รบnica" + }, + "settings-panel-mode": { + "attached": "Painel anexado ร  barra", + "centered": "Painel centralizado", + "window": "Janela separada" + }, + "visualizer-types": { + "linear": "Linear", + "mirrored": "Espelhado", + "wave": "Onda" + }, + "visualizer-visibility": { + "with-background": "Apenas com fundo" + }, + "workspace-labels": { + "index": "รndice", + "index-and-name": "รndice e Nome", + "name": "Nome" + } + }, + "panels": { + "about": { + "become-supporter": "Torne-se um apoiador", + "changelog": "Ver histรณrico de alteraรงรตes", + "changelog-on-startup": "Mostrar changelog na atualizaรงรฃo", + "changelog-on-startup-desc": "Mostrar automaticamente o registo de alteraรงรตes quando o Noctalia for atualizado.", + "contributors-description": "Agradecimentos ao nosso incrรญvel colaborador!", + "contributors-description-plural": "Agradecimentos aos nossos {count} incrรญveis colaboradores!", + "copy-info": "Copiar informaรงรตes", + "debug-disabled": "Modo de depuraรงรฃo desativado", + "debug-enabled": "Modo de depuraรงรฃo ativado", + "info-copied": "Informaรงรฃo copiada para a รกrea de transferรชncia", + "noctalia-available": "Disponรญvel:", + "noctalia-desc": "Um shell de desktop elegante e minimalista, cuidadosamente criado para Wayland, construรญdo com Quickshell.", + "noctalia-git-commit": "Commit Git:", + "noctalia-installed-version": "Versรฃo instalada:", + "noctalia-latest-version": "รšltima versรฃo:", + "noctalia-qs-version": "Noctalia QS versรฃo:", + "noctalia-title": "Shell Noctalia", + "privacy-policy": "Polรญtica de privacidade", + "support": "Apoie-nos", + "supporter-badge": "Apoiador", + "supporters-desc": "Um enorme obrigado ao nosso incrรญvel apoiador!", + "supporters-desc-plural": "Um enorme obrigado aos nossos {count} incrรญveis apoiadores!", + "supporters-loading": "Carregando apoiadores...", + "system-board": "Placa-mรฃe:", + "system-cpu": "CPU:", + "system-disk": "Disco:", + "system-gpu": "GPU:", + "system-host": "Host:", + "system-install-hint": "Instale o fastfetch para visualizar informaรงรตes do sistema", + "system-kernel": "Kernel:", + "system-loading": "Carregando informaรงรตes do sistema...", + "system-memory": "Memรณria:", + "system-monitor": "Monitor:", + "system-not-installed": "fastfetch nรฃo estรก instalado", + "system-os": "SO:", + "system-packages": "Pacotes:", + "system-product": "Produto:", + "system-title": "Informaรงรตes do Sistema", + "system-uptime": "Tempo de atividade:", + "system-wm": "WM:", + "telemetry-data-copied": "Dados de telemetria copiados para a รกrea de transferรชncia.", + "telemetry-desc": "Ajude a melhorar o Noctalia compartilhando informaรงรตes anรดnimas do sistema (resoluรงรฃo da tela, compositor, distribuiรงรฃo). Enviado uma vez na inicializaรงรฃo, sem rastreamento, os dados sรฃo excluรญdos automaticamente apรณs 30 dias.", + "telemetry-enabled": "Enviar informaรงรตes anรดnimas do sistema", + "telemetry-show-data": "Visualizar dados", + "telemetry-title": "Privacidade", + "title": "Sobre", + "up-to-date": "Vocรช estรก atualizado!", + "update-available": "Atualizaรงรฃo disponรญvel", + "view-commit": "Ver commit no GitHub" + }, + "audio": { + "devices-desc": "Configure os dispositivos de entrada e saรญda de รกudio disponรญveis.", + "devices-input-device-description": "Selecione o dispositivo de entrada de รกudio desejado.", + "devices-input-device-label": "Dispositivo de entrada", + "devices-output-device-description": "Selecione o dispositivo de saรญda de รกudio desejado.", + "devices-output-device-label": "Dispositivo de saรญda", + "devices-title": "Dispositivos de รกudio", + "external-mixer-description": "Insira o comando ou o caminho do aplicativo para iniciar ao ativar o recurso de mixer de รกudio externo.", + "external-mixer-label": "Comando de mixer de รกudio externo", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "Defina seus aplicativos de mรญdia preferidos e ignorados.", + "media-excluded-player-description": "Adicione palavras-chave para os reprodutores que vocรช deseja que o sistema ignore. Cada palavra-chave deve estar em uma nova linha.", + "media-excluded-player-label": "Reprodutor excluรญdo", + "media-excluded-player-placeholder": "digite a substring e pressione +", + "media-frame-rate-description": "Taxas mais altas sรฃo mais suaves, mas consomem mais recursos.", + "media-frame-rate-label": "Taxa de quadros", + "media-primary-player-description": "Digite uma palavra-chave para identificar seu reprodutor principal.", + "media-primary-player-label": "Reprodutor principal", + "media-primary-player-placeholder": "p. ex. spotify, vlc, mpv", + "media-scrolling-speed-description": "Tempo em segundos para o tรญtulo rolar do inรญcio ao fim.", + "media-scrolling-speed-label": "Velocidade de rolagem", + "media-scrolling-title-description": "Ativar rolagem contรญnua para tรญtulos de mรญdia longos.", + "media-scrolling-title-label": "Tรญtulo com rolagem", + "media-title": "Reprodutores de mรญdia", + "on-middle-clicked-description": "Comando a ser executado quando o botรฃo do meio รฉ clicado.", + "panel-applications-empty": "Nenhum aplicativo estรก reproduzindo รกudio no momento", + "spectrum-mirrored-description": "Espelha o espectro para que as frequรชncias baixas fiquem no centro. Quando desativado, as frequรชncias baixas ficam ร  esquerda e as altas ร  direita.", + "spectrum-mirrored-label": "Espectro espelhado", + "title": "รudio", + "visualizer-type-description": "Escolha um tipo de visualizaรงรฃo para reproduรงรฃo de mรญdia.", + "visualizer-type-label": "Tipo de visualizaรงรฃo", + "volumes-desc": "Ajuste os controles de volume e os nรญveis de รกudio.", + "volumes-feedback-sound-file-description": "Caminho para o arquivo de som reproduzido ao mudar o volume.", + "volumes-feedback-sound-file-label": "Arquivo de som de feedback de volume", + "volumes-feedback-sound-file-select-title": "Selecionar arquivo de som de feedback de volume", + "volumes-input-volume-description": "Nรญvel de volume de entrada do microfone.", + "volumes-mute-input-description": "Silencia a entrada de รกudio padrรฃo (microfone).", + "volumes-mute-input-label": "Silenciar entrada de รกudio", + "volumes-mute-output-description": "Silencia a saรญda de รกudio principal do sistema.", + "volumes-mute-output-label": "Silenciar saรญda de รกudio", + "volumes-output-volume-description": "Nรญvel de volume de todo o sistema.", + "volumes-step-size-description": "Ajuste o tamanho do passo para alteraรงรตes de volume (roda do mouse, atalhos de teclado).", + "volumes-step-size-label": "Tamanho do passo do volume", + "volumes-volume-feedback-description": "Reproduzir um som de feedback ao ajustar o volume.", + "volumes-volume-feedback-label": "Reproduzir som de feedback de volume", + "volumes-volume-overdrive-description": "Permite aumentar o volume para alรฉm dos 100%. Poderรก nรฃo ser suportado por todo o hardware.", + "volumes-volume-overdrive-label": "Permitir volume acima de 100%" + }, + "bar": { + "appearance-auto-hide-delay-description": "Tempo atรฉ a Bar se ocultar depois que o rato a deixa.", + "appearance-auto-hide-delay-label": "Atraso para Ocultar", + "appearance-auto-hide-exclusive-note": "Nota: A zona exclusiva รฉ automaticamente desativada quando o auto-ocultar estรก ativo.", + "appearance-auto-show-delay-description": "Tempo atรฉ a barra aparecer quando o mouse entra na borda.", + "appearance-auto-show-delay-label": "Atraso de Exibiรงรฃo", + "appearance-background-opacity-description": "Defina a opacidade do fundo especificamente para a barra.", + "appearance-background-opacity-label": "Opacidade do fundo da barra", + "appearance-capsule-color-description": "Escolha uma cor para as cรกpsulas da barra, ou nรฃo use nenhuma para a cor de superfรญcie padrรฃo.", + "appearance-capsule-color-label": "Cor da cรกpsula", + "appearance-capsule-opacity-description": "Define o nรญvel de opacidade para fundos dos widgets quando a cรกpsula รฉ mostrada.", + "appearance-capsule-opacity-label": "Opacidade da cรกpsula", + "appearance-content-padding-description": "Ajusta o espaรงamento entre as bordas da barra e os widgets.", + "appearance-content-padding-label": "Espaรงamento do conteรบdo", + "appearance-density-description": "Ajuste o preenchimento da barra para um visual compacto ou espaรงoso.", + "appearance-density-label": "Densidade da barra", + "appearance-desc": "Personalize a aparรชncia e a posiรงรฃo da barra.", + "appearance-display-mode-description": "Escolha quando a barra estรก visรญvel.", + "appearance-enable-exclusion-zone-inset-description": "Reduza a zona de exclusรฃo em 1 pixel fรญsico para que as janelas alinhadas se estendam perfeitamente sob a borda da barra.", + "appearance-enable-exclusion-zone-inset-label": "Zona de exclusรฃo interna", + "appearance-floating-description": "Exibe a barra como uma 'pรญlula' flutuante.", + "appearance-floating-label": "Barra flutuante", + "appearance-font-scale-description": "Ajustar a escala do tamanho da fonte para o texto exibido na barra.", + "appearance-font-scale-label": "Escala da fonte", + "appearance-frame-radius": "Raio Interno", + "appearance-frame-settings-description": "Ajuste a espessura da moldura e o raio interno", + "appearance-frame-settings-label": "Configuraรงรตes da Moldura", + "appearance-frame-thickness": "Espessura", + "appearance-hide-on-overview-description": "Ocultar a barra e fechar os painรฉis quando a visรฃo geral do compositor estiver ativa.", + "appearance-hide-on-overview-label": "Ocultar barra na visรฃo geral", + "appearance-margins-description": "Ajuste as margens ao redor da barra flutuante.", + "appearance-margins-horizontal": "Margem horizontal", + "appearance-margins-vertical": "Margem vertical", + "appearance-outer-corners-description": "Exibe cantos curvados para fora na barra.", + "appearance-outer-corners-label": "Cantos externos", + "appearance-position-description": "Escolha onde posicionar a barra na tela.", + "appearance-position-label": "Posiรงรฃo da barra", + "appearance-show-capsule-description": "Mostra o fundo dos widgets.", + "appearance-show-capsule-label": "Mostrar cรกpsula", + "appearance-show-on-workspace-switch-description": "Mostrar a Bar automaticamente por um breve perรญodo quando o espaรงo de trabalho mudar.", + "appearance-show-on-workspace-switch-label": "Mostrar barra ao mudar de รกrea de trabalho", + "appearance-show-outline-description": "Exibe uma borda visรญvel ao redor de cada widget.", + "appearance-show-outline-label": "Mostrar contornos do widget", + "appearance-type-description": "Escolha o estilo da barra: Simples, Flutuante ou Emoldurada.", + "appearance-type-label": "Tipo de barra", + "appearance-use-separate-opacity-description": "Permitir usar um valor de opacidade separado para o fundo da barra.", + "appearance-use-separate-opacity-label": "Usar opacidade separada para as barras", + "appearance-widget-spacing-description": "Ajusta o espaรงamento entre cada widget na barra.", + "appearance-widget-spacing-label": "Espaรงamento de widgets", + "behavior-middle-click-command-description": "Comando a executar no clique do meio.", + "behavior-middle-click-command-label": "Comando do Clique do Meio", + "behavior-middle-click-command-placeholder": "niri msg action alternar visรฃo geral", + "behavior-middle-click-description": "Escolha o que o clique do meio faz nas รกreas vazias da Barra.", + "behavior-middle-click-follow-mouse-description": "Abrir o painel de clique do meio selecionado na posiรงรฃo do cursor.", + "behavior-middle-click-follow-mouse-label": "Clique do meio segue o mouse", + "behavior-middle-click-label": "Aรงรฃo de clique do meio na Barra", + "behavior-right-click-command-description": "Comando a executar ao clicar com o botรฃo direito.", + "behavior-right-click-command-label": "Comando do Botรฃo Direito", + "behavior-right-click-command-placeholder": "notify-send \"Clicar com o botรฃo direito\"", + "behavior-right-click-description": "Escolha o que o clique direito faz nas รกreas vazias da barra.", + "behavior-right-click-follow-mouse-description": "Abrir o painel de clique direito selecionado na posiรงรฃo do cursor.", + "behavior-right-click-follow-mouse-label": "Clique direito segue o rato", + "behavior-right-click-label": "Aรงรฃo de clique direito na Barra", + "behavior-wheel-wrap-description": "Quando ativado, a rolagem continua do รบltimo item para o primeiro.", + "behavior-wheel-wrap-label": "Circular", + "behavior-workspace-scroll-description": "Escolha o que a roda do mouse faz em รกreas vazias da barra.", + "behavior-workspace-scroll-label": "Aรงรฃo da roda do rato da Barra", + "behavior-workspace-scroll-option-content": "Conteรบdo", + "behavior-workspace-scroll-option-workspace": "รrea de trabalho", + "monitor-configure-widgets": "Configurar widgets", + "monitor-override-settings": "Substituir configuraรงรตes globais", + "monitor-override-settings-description": "Usar definiรงรตes personalizadas para este monitor.", + "monitor-reset-all": "Redefinir tudo", + "monitor-widgets-title": "Configuraรงรฃo do Widget - {monitor}", + "monitors-desc": "Mostra a barra em monitores especรญficos. O padrรฃo รฉ todos, se nenhum for escolhido.", + "monitors-desc-new": "Configure quais monitores exibem a Bar e personalize as configuraรงรตes por monitor.", + "monitors-title": "Exibiรงรฃo nos monitores", + "title": "Barra", + "tray-blacklist-description": "Adicione regras de exclusรฃo para a bandeja do sistema, suporta curingas (*).", + "tray-blacklist-label": "Lista Negra", + "tray-blacklist-placeholder": "p. ex. nm-applet, Fcitx*", + "tray-pin-application": "Fixar aplicativo", + "tray-unpin-application": "Desfixar aplicativo", + "use-global-widgets": "Usar widgets globais", + "widgets-desc": "Arraste os widgets para alterar a ordem. Use o menu do botรฃo direito para transferir widgets entre seรงรตes ou removรช-los.", + "widgets-title": "Posicionamento dos widgets" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "Gere esquemas de cores a partir do seu papel de parede. Extrai automaticamente as cores para criar um tema coeso.", + "color-source-use-wallpaper-colors-label": "Usar cores do papel de parede", + "dark-mode-mode-description": "Ativa a mudanรงa automรกtica entre o modo claro e o modo escuro.", + "dark-mode-mode-label": "Agendamento do modo escuro", + "dark-mode-mode-manual": "Manual", + "dark-mode-mode-off": "Desligado", + "dark-mode-switch-description": "Muda para um tema mais escuro para facilitar a visualizaรงรฃo ร  noite.", + "delete-error-description": "Falha ao excluir {scheme}", + "delete-error-title": "Falha ao excluir", + "delete-success-description": "{scheme} excluรญdo com sucesso", + "delete-success-title": "Esquema de cores excluรญdo", + "download-button": "Baixar mais", + "download-downloading": "Baixando...", + "download-empty": "Nenhum esquema de cores disponรญvel", + "download-error-api-error": "Erro da API: {status}", + "download-error-description": "Falha ao baixar {scheme}", + "download-error-download-failed": "Falha no download com cรณdigo de saรญda: {code}", + "download-error-invalid-response": "Formato de resposta da API invรกlido", + "download-error-no-files": "Nenhum arquivo encontrado para o esquema", + "download-error-parse-failed": "Falha ao analisar a resposta da API: {error}", + "download-error-rate-limit": "Limite de taxa da API do GitHub excedido", + "download-error-title": "Falha no download", + "download-fetching": "Buscando esquemas de cores disponรญveis...", + "download-success-description": "{scheme} baixado com sucesso", + "download-success-title": "Esquema de cores baixado", + "download-title": "Baixar esquemas de cores", + "method-description": { + "content": "Esquema de Material Design com extraรงรฃo de cores de alta fidelidade que corresponde de perto ร s cores reais do conteรบdo de origem.", + "dysfunctional": "Como Faithful, mas escolhe a segunda famรญlia de cores mais dominante como primรกria.", + "faithful": "Tenta manter-se prรณximo da cor de origem, enquanto gera uma paleta harmoniosa.", + "fruit-salad": "Esquema de Material Design que produz uma paleta divertida e vibrante com tons diversos e variados.", + "monochrome": "Esquema de Material Design usando uma escala de cinza de tom รบnico com conteรบdo cromรกtico mรญnimo.", + "muted": "Gera tons mais suaves e dessaturados para uma aparรชncia calma e discreta.", + "rainbow": "Esquema de Material Design que gera cores em todo o espectro para mรกxima diversidade de cores.", + "tonal-spot": "Algoritmo padrรฃo do Material Design. Cria paletas equilibradas usando variaรงรตes tonais da cor de origem.", + "vibrant": "Maximiza a saturaรงรฃo da cor para paletas ousadas e atraentes com tons vรญvidos." + }, + "predefined-desc": "Escolha entre uma coleรงรฃo de esquemas de cores predefinidos.", + "predefined-generate-templates-label": "Gerar modelos para esquemas predefinidos", + "predefined-title": "Esquemas de cores predefinidos", + "sync-gsettings-description": "Corresponder o tema do sistema ร  variante clara ou escura ativa.", + "sync-gsettings-label": "Sincronizar tema do sistema", + "templates-desc": "Aplicar cores a aplicaรงรตes externas.", + "templates-filter-description": "Mostrar templates de uma categoria especรญfica.", + "templates-filter-label": "Filtrar por categoria", + "templates-misc-description": "Crie seus prรณprios modelos.", + "templates-misc-label": "Avanรงado", + "templates-misc-user-templates-description": "Ative apenas se souber o que estรก fazendo โ€” consulte nossa documentaรงรฃo online.", + "templates-misc-user-templates-label": "Ativar modelos do usuรกrio", + "templates-none-detected": "Nenhum detectado", + "templates-write-path": "Escreve: {filepath}", + "title": "Esquema de cores", + "wallpaper-method-description": "Escolha o seu mรฉtodo de geraรงรฃo de paleta favorito.", + "wallpaper-method-label": "Mรฉtodo de geraรงรฃo de paleta", + "wallpaper-monitor-source-description": "Selecione qual monitor usar para extrair as cores do papel de parede.", + "wallpaper-monitor-source-label": "Fonte de geraรงรฃo de cores" + }, + "connections": { + "authentication-required": "Autenticaรงรฃo necessรกria", + "bluetooth-auto-connect-description": "Conectar automaticamente a dispositivos emparelhados confiรกveis quando o Bluetooth estiver ativado.", + "bluetooth-auto-connect-label": "Conectar automaticamente dispositivos emparelhados", + "bluetooth-devices-unnamed": "Dispositivos sem nome nรฃo sรฃo exibidos.", + "bluetooth-discoverable": "Este dispositivo รฉ detectรกvel como {hostName} enquanto esta aba de configuraรงรตes estiver aberta.", + "bluetooth-rssi-polling-description": "Amostra periodicamente o RSSI de dispositivos conectados via bluetoothctl. Pode nรฃo estar disponรญvel para todos os dispositivos; usa recursos mรญnimos quando ativado.", + "bluetooth-rssi-polling-interval-description": "Configurar com que frequรชncia atualizar a intensidade do sinal para dispositivos conectados.", + "bluetooth-rssi-polling-interval-label": "Intervalo de sondagem", + "bluetooth-rssi-polling-label": "Sondagem de sinal Bluetooth", + "disable-discoverability-description": "Ocultar o seu dispositivo de dispositivos Bluetooth prรณximos.", + "disable-discoverability-label": "Desativar visibilidade do dispositivo", + "hide-unnamed-devices-description": "Ocultar dispositivos que aparecem apenas como endereรงos Bluetooth.", + "hide-unnamed-devices-label": "Ocultar dispositivos sem nome", + "pin-instructions": "Por favor, insira o cรณdigo PIN exibido no seu dispositivo.", + "title": "Conexรตes", + "wifi-header-text": "Use o Wi-Fi para ver as redes disponรญveis e conectar-se ร  internet." + }, + "control-center": { + "cards-desc": "Personalize quais controles aparecem na central de controle e em que ordem.", + "desc": "Configurar o posicionamento e o comportamento do painel da central de controle.", + "open-at-mouse-description": "Ao clicar com o botรฃo direito numa รกrea vazia da Bar, abre o Centro de Controle perto do cursor. Quando desativado, a definiรงรฃo de posiรงรฃo acima รฉ usada em vez disso.", + "open-at-mouse-label": "Abrir no cursor ao clicar com o botรฃo direito", + "position-description": "Escolha onde o painel da central de controle aparece quando aberto.", + "shortcuts-custom-button-command-description": "O comando a ser executado quando o botรฃo รฉ clicado.", + "shortcuts-custom-button-enable-on-state-logic-description": "Ativa um segundo รญcone e um estado 'quente' com base em um comando de verificaรงรฃo.", + "shortcuts-custom-button-enable-on-state-logic-label": "Ativar lรณgica de estado ativado", + "shortcuts-custom-button-icon-description": "Selecione um รญcone da biblioteca.", + "shortcuts-custom-button-on-clicked-label": "Comando de clique esquerdo", + "shortcuts-custom-button-on-middle-clicked-description": "Comando a executar quando o botรฃo for clicado com o botรฃo do meio.", + "shortcuts-custom-button-on-state-command-description": "Comando a ser executado para verificar se o botรฃo deve estar no estado 'ligado'. Retorna 0 para ligado, diferente de zero para desligado.", + "shortcuts-custom-button-on-state-command-label": "Comando de verificaรงรฃo de estado ativado", + "shortcuts-custom-button-on-state-icon-description": "O รญcone para o botรฃo quando ele estรก no estado 'ligado'.", + "shortcuts-custom-button-on-state-icon-label": "รcone de estado ativado", + "shortcuts-custom-button-state-checks-add": "Adicionar verificaรงรฃo de estado", + "shortcuts-custom-button-state-checks-command": "Comando a ser executado para esta verificaรงรฃo de estado", + "shortcuts-custom-button-state-checks-label": "Verificaรงรตes de estado", + "shortcuts-custom-button-state-checks-remove": "Remover", + "shortcuts-custom-button-tooltip-description": "A dica de ferramenta a ser exibida ao passar o mouse sobre o botรฃo.", + "shortcuts-custom-button-tooltip-label": "Dica de ferramenta", + "shortcuts-title": "Widgets de atalhos", + "system-monitor-disk-path-description": "Selecione qual ponto de montagem do disco o cartรฃo do monitor do sistema no centro de controle deve monitorar.", + "system-monitor-disk-path-label": "Caminho do disco do monitor do sistema", + "title": "Central de controle" + }, + "desktop-widgets": { + "clock-enabled-description": "Mostrar um widget de relรณgio na รกrea de trabalho.", + "clock-enabled-label": "Ativar widget de relรณgio", + "clock-format-label": "Formato", + "clock-height-description": "Altura do widget do relรณgio em pixels.", + "clock-height-label": "Altura", + "clock-minimal-mode-description": "Usar uma exibiรงรฃo de relรณgio minimalista com hora e data.", + "clock-minimal-mode-label": "Modo minimalista", + "clock-rounded-corners-description": "Use cantos arredondados para o fundo do widget.", + "clock-rounded-corners-label": "Cantos arredondados", + "clock-show-background-description": "Mostrar o contรชiner de fundo para o widget de relรณgio.", + "clock-show-background-label": "Mostrar plano de fundo", + "clock-show-date-description": "Exiba a data atual abaixo da hora.", + "clock-show-date-label": "Mostrar data", + "clock-show-seconds-description": "Exibir segundos na hora.", + "clock-show-seconds-label": "Mostrar segundos", + "clock-style-analog": "Analรณgico", + "clock-style-binary": "Binรกrio", + "clock-style-description": "Escolha o estilo de exibiรงรฃo do relรณgio.", + "clock-style-digital": "Digital", + "clock-style-label": "Estilo do relรณgio", + "clock-style-minimal": "Minimal", + "clock-width-description": "Largura do widget do relรณgio em pixels.", + "cpu-intensive-note": "Os widgets marcados com ! usam mais CPU e devem ser ativados com atenรงรฃo.", + "edit-mode-button-label": "Entrar no modo de ediรงรฃo", + "edit-mode-controls-explanation": "Clique esquerdo e arraste: Mover ou redimensionar o widget.\nClique direito: Abrir as opรงรตes do menu de contexto.", + "edit-mode-description": "Ative o modo de ediรงรฃo para mover e reposicionar widgets do desktop. Quando ativado, os widgets exibem um contorno de arrastar e podem ser reposicionados.", + "edit-mode-exit-button": "Sair do modo de ediรงรฃo", + "edit-mode-grid-snap-label": "Alinhar ร  grade", + "edit-mode-grid-snap-scale-label": "Escala de ajuste ร  grelha", + "edit-mode-label": "Modo de ediรงรฃo", + "enabled-description": "Ativar ou desativar widgets do desktop completamente.", + "enabled-label": "Ativar widgets do desktop", + "general-desc": "Configure widgets que aparecem na sua รกrea de trabalho.", + "general-title": "Widgets para desktop", + "media-player-enabled-description": "Mostrar um widget de reprodutor de mรญdia na รกrea de trabalho.", + "media-player-enabled-label": "Ativar widget do reprodutor de mรญdia", + "media-player-rounded-corners-description": "Ativar cantos arredondados nas bordas do widget.", + "media-player-show-album-art-description": "Mostrar a capa do รกlbum e as informaรงรตes da faixa (tรญtulo e artista).", + "media-player-show-album-art-label": "Mostrar a capa do รกlbum e o tรญtulo", + "media-player-show-background-description": "Mostrar o contรชiner de fundo para o widget do reprodutor de mรญdia.", + "media-player-show-buttons-description": "Mostrar botรตes de controle de mรญdia (reproduzir/pausar, anterior, prรณximo) no widget do reprodutor de mรญdia.", + "media-player-show-buttons-label": "Mostrar botรตes", + "media-player-show-visualizer-description": "Mostrar a sobreposiรงรฃo do visualizador de รกudio.", + "media-player-visualizer-type-description": "Escolha um tipo de visualizaรงรฃo para o fundo do reprodutor de mรญdia na รกrea de trabalho.", + "overview-enabled-description": "Mostrar widgets do desktop enquanto a visรฃo geral do compositor estiver aberta.", + "overview-enabled-label": "Mostrar na visรฃo geral", + "system-stat-layout-bottom": "Inferior", + "system-stat-layout-description": "Escolha como a legenda รฉ exibida em relaรงรฃo ao grรกfico.", + "system-stat-layout-label": "Layout", + "system-stat-layout-side": "Lado", + "system-stat-rounded-corners-description": "Usar cantos arredondados para o fundo do widget.", + "system-stat-rounded-corners-label": "Cantos Arredondados", + "system-stat-show-background-description": "Mostrar o contรชiner de fundo para o widget de estatรญsticas do sistema.", + "system-stat-show-background-label": "Mostrar plano de fundo", + "system-stat-stat-type-description": "Escolhe qual estatรญstica do sistema exibir.", + "system-stat-stat-type-label": "Tipo de Estatรญstica", + "title": "Widgets do desktop", + "weather-enabled-description": "Mostrar um widget de clima na รกrea de trabalho.", + "weather-enabled-label": "Ativar widget de clima", + "weather-show-background-description": "Mostrar o contรชiner de fundo para o widget de clima." + }, + "display": { + "monitors-backlight-device-auto-option": "Padrรฃo", + "monitors-backlight-device-description": "Selecione um dispositivo de retroiluminaรงรฃo para esta saรญda.", + "monitors-backlight-device-label": "Dispositivo de Retroiluminaรงรฃo", + "monitors-brightness-step-description": "Ajuste o tamanho do passo para alteraรงรตes de brilho (roda do mouse e atalhos de teclado).", + "monitors-brightness-step-label": "Tamanho do passo do brilho", + "monitors-brightness-unavailable-ddc-disabled": "Controle de brilho indisponรญvel. Ative \"Suporte de brilho externo\" para controlar o brilho desta tela.", + "monitors-brightness-unavailable-generic": "O controle de brilho nรฃo estรก disponรญvel para esta tela.", + "monitors-desc": "Ajustar o brilho das configuraรงรตes para cada tela.", + "monitors-enforce-minimum-description": "Resolve o problema da retroiluminaรงรฃo apagar completamente em alguns monitores com brilho em 0%.", + "monitors-enforce-minimum-label": "Forรงar brilho mรญnimo (1%)", + "monitors-external-brightness-description": "Ativar suporte DDCUtil para controlar o brilho em monitores externos atravรฉs do protocolo DDC/CI.", + "monitors-external-brightness-label": "Suporte de brilho externo", + "monitors-global-brightness-description": "Ajustar o brilho de todos os monitores de uma vez.", + "monitors-global-brightness-label": "Todos os monitores", + "monitors-title": "Configuraรงรตes por monitor", + "night-light-auto-schedule-description": "Baseado no horรกrio do pรดr do sol e do nascer do sol em {location} โ€” recomendado.", + "night-light-auto-schedule-label": "Agendamento automรกtico", + "night-light-desc": "Reduz a emissรฃo de luz azul para ajudar vocรช a dormir melhor e diminuir o cansaรงo visual.", + "night-light-enable-description": "Aplica um filtro de cor quente para reduzir a emissรฃo de luz azul.", + "night-light-enable-label": "Ativar luz noturna", + "night-light-force-activation-description": "Ignora o agendamento e aplica o filtro noturno imediatamente.", + "night-light-force-activation-label": "Forรงar ativaรงรฃo", + "night-light-manual-schedule-description": "Defina horรกrios personalizados para o nascer e o pรดr do sol.", + "night-light-manual-schedule-label": "Agendamento manual", + "night-light-manual-schedule-select-start": "Selecionar hora de inรญcio", + "night-light-manual-schedule-select-stop": "Selecionar hora de tรฉrmino", + "night-light-manual-schedule-sunrise": "Horรกrio do nascer do sol", + "night-light-manual-schedule-sunset": "Horรกrio do pรดr do sol", + "night-light-temperature-day": "Dia", + "night-light-temperature-day-description": "Controla a temperatura durante o dia.", + "night-light-temperature-description": "Defina o quรฃo quente a cor serรก durante a noite e o dia.", + "night-light-temperature-label": "Temperatura da cor", + "night-light-temperature-night": "Noite", + "night-light-temperature-night-description": "Controla a temperatura durante a noite.", + "title": "Tela" + }, + "dock": { + "appearance-background-opacity-description": "Ajuste a opacidade do fundo da dock.", + "appearance-border-radius-description": "Ajustar o raio da borda da dock.", + "appearance-border-radius-label": "Raio da borda", + "appearance-colorize-icons-description": "Aplicar cores do tema aos รญcones de aplicativos da dock (apenas aplicativos nรฃo focados).", + "appearance-colorize-icons-label": "Colorir รญcones", + "appearance-dead-opacity-description": "Ajustar a opacidade dos รญcones de aplicativos que nรฃo estรฃo em execuรงรฃo.", + "appearance-dead-opacity-label": "Opacidade morta", + "appearance-desc": "Personalize o comportamento e a aparรชncia da dock.", + "appearance-display-auto-hide": "Ocultar automaticamente", + "appearance-display-description": "Escolha como a dock se comporta.", + "appearance-display-exclusive": "Exclusivo", + "appearance-dock-indicator-description": "Mostrar um pequeno indicador quando o dock estiver oculto.", + "appearance-dock-indicator-label": "Indicador do dock", + "appearance-floating-distance-description": "Ajuste a distรขncia de flutuaรงรฃo da borda da tela.", + "appearance-floating-distance-label": "Distรขncia de flutuaรงรฃo da dock", + "appearance-group-apps-description": "Agrupar vรกrias janelas da mesma app em uma รบnica entrada do Dock.", + "appearance-group-apps-label": "Agrupar aplicativos semelhantes", + "appearance-group-click-action-cycle": "Alternar janelas", + "appearance-group-click-action-description": "Escolha o que o clique esquerdo faz para aplicaรงรตes agrupadas.", + "appearance-group-click-action-label": "Aรงรฃo ao clicar em aplicaรงรฃo agrupada", + "appearance-group-click-action-list": "Abrir lista de janelas", + "appearance-group-context-menu-mode-description": "Escolha como o menu de contexto se comporta para aplicativos agrupados.", + "appearance-group-context-menu-mode-extended": "Estendido", + "appearance-group-context-menu-mode-label": "Modo de menu de aplicativos agrupado", + "appearance-group-context-menu-mode-list": "Lista de janelas", + "appearance-group-indicator-style-description": "Escolha como os indicadores de execuรงรฃo agrupados exibem o estado da janela focada.", + "appearance-group-indicator-style-dots": "Pontos", + "appearance-group-indicator-style-label": "Estilo de indicador agrupado", + "appearance-group-indicator-style-number": "Nรบmero", + "appearance-hide-show-speed-description": "Ajuste a velocidade da animaรงรฃo de ocultar/mostrar da dock.", + "appearance-hide-show-speed-label": "Velocidade de ocultar/mostrar", + "appearance-icon-size-description": "Ajustar o tamanho geral do dock.", + "appearance-icon-size-label": "Tamanho do dock", + "appearance-inactive-indicators-description": "Exibir indicadores de pรญlula para todos os aplicativos, nรฃo apenas para o aplicativo ativo no momento.", + "appearance-inactive-indicators-label": "Indicadores de execuรงรฃo", + "appearance-indicator-color-description": "Escolha a cor do indicador da dock oculta.", + "appearance-indicator-color-label": "Cor do indicador", + "appearance-indicator-opacity-description": "Ajuste a opacidade do indicador da dock oculta.", + "appearance-indicator-opacity-label": "Opacidade do indicador", + "appearance-indicator-thickness-description": "Ativar um indicador da dock oculta mais espesso (6 px em vez de 3 px).", + "appearance-indicator-thickness-label": "Indicador mais espesso", + "appearance-launcher-icon-description": "Escolha o รญcone usado para o Dock Launcher.", + "appearance-launcher-icon-label": "รcone do Lanรงador", + "appearance-launcher-position-description": "Escolha onde o รญcone do lanรงador aparece no dock.", + "appearance-launcher-position-end": "Fim", + "appearance-launcher-position-label": "Posiรงรฃo do lanรงador", + "appearance-launcher-position-start": "Iniciar", + "appearance-launcher-use-distro-logo-description": "Use o logo da sua distribuiรงรฃo como o รญcone do Dock Launcher.", + "appearance-launcher-use-distro-logo-label": "Usar o logรณtipo da distro em vez do รญcone", + "appearance-pinned-static-description": "Sempre posicione os รญcones de aplicativos fixados ร  esquerda em ordem estรกtica.", + "appearance-pinned-static-label": "Aplicativos fixados estaticamente", + "appearance-position-description": "Escolha onde o dock aparece na tela.", + "appearance-position-label": "Posiรงรฃo", + "appearance-show-launcher-icon-description": "Mostrar o รญcone do lanรงador de aplicativos na dock.", + "appearance-show-launcher-icon-label": "Mostrar lanรงador de aplicaรงรตes", + "appearance-sit-on-frame-description": "Alinhe o dock dentro da borda do quadro em vez de ficar por cima.", + "appearance-sit-on-frame-label": "O dock fica no quadro", + "appearance-type-attached": "Ancorado", + "appearance-type-description": "Escolha entre uma pรญlula flutuante ou uma barra estรกtica anexada ร  borda.", + "appearance-type-floating": "Flutuante", + "appearance-type-label": "Estilo do dock", + "enabled-description": "Mostrar ou ocultar o dock completamente.", + "enabled-label": "Ativar dock", + "monitors-desc": "Mostrar dock em monitores especรญficos. O padrรฃo รฉ todos se nenhum for escolhido.", + "monitors-only-same-monitor-description": "Mostrar apenas aplicativos do monitor onde a dock estรก localizada.", + "monitors-only-same-monitor-label": "Apenas aplicativos do mesmo monitor", + "monitors-title": "Exibiรงรฃo no monitor", + "title": "Dock" + }, + "general": { + "copy-settings": "Copiar configuraรงรตes", + "fonts-default-description": "Fonte principal usada em toda a interface.", + "fonts-default-label": "Fonte padrรฃo", + "fonts-default-placeholder": "Selecione a fonte padrรฃo...", + "fonts-default-scale-description": "Aumentar ou diminuir o tamanho do texto padrรฃo.", + "fonts-default-scale-label": "Tamanho de fonte padrรฃo", + "fonts-default-search-placeholder": "Pesquisar fontes...", + "fonts-desc": "Escolha as fontes usadas em toda a interface.", + "fonts-monospace-description": "Fonte monoespaรงada usada para exibiรงรฃo de nรบmeros e estatรญsticas.", + "fonts-monospace-label": "Fonte monoespaรงada", + "fonts-monospace-placeholder": "Selecione a fonte monoespaรงada...", + "fonts-monospace-scale-description": "Aumentar ou diminuir o tamanho do texto monoespaรงado.", + "fonts-monospace-scale-label": "Tamanho da fonte monoespaรงada", + "fonts-monospace-search-placeholder": "Pesquisar fontes monoespaรงadas...", + "fonts-title": "Fontes", + "keybinds-conflict-description": "A combinaรงรฃo de teclas jรก estรก atribuรญda a {action}.", + "keybinds-conflict-title": "Conflito de atalhos de teclado", + "keybinds-description": "Configure as teclas de navegaรงรฃo globais para painรฉis e o lanรงador.", + "keybinds-down": "Mover para baixo", + "keybinds-enter": "Confirmar / Aรงรฃo", + "keybinds-escape": "Fechar / Voltar", + "keybinds-left": "Mover para a esquerda", + "keybinds-modifier-description": "Atalhos devem incluir uma tecla modificadora (Ctrl ou Alt).", + "keybinds-modifier-title": "Modificador necessรกrio", + "keybinds-remove": "Remover", + "keybinds-right": "Mover para a direita", + "keybinds-title": "Atalhos de teclado de navegaรงรฃo", + "keybinds-up": "Mover para cima", + "language-desc": "Escolha o seu idioma preferido para a aplicaรงรฃo.", + "language-select-auto-detect": "Automรกtico", + "language-select-description": "Selecione o idioma usado na interface da aplicaรงรฃo.", + "language-select-label": "Idioma da aplicaรงรฃo", + "launch-setup-wizard": "Assistente de configuraรงรฃo", + "profile-desc": "Edite os detalhes do seu usuรกrio e avatar.", + "profile-picture-description": "Sua foto de perfil que aparece em toda a interface.", + "profile-picture-label": "Foto de perfil", + "profile-select-avatar": "Selecionar imagem de avatar", + "profile-title": "Perfil", + "profile-tooltip": "Foto de perfil", + "reverse-scrolling-description": "Inverter a direรงรฃo de rolagem interpretada.", + "reverse-scrolling-label": "Rolagem invertida", + "screen-corners-desc": "Personalize o arredondamento dos cantos da tela e os efeitos visuais.", + "screen-corners-radius-description": "Ajuste os cantos arredondados da tela.", + "screen-corners-radius-label": "Raio dos cantos da tela", + "screen-corners-radius-reset": "Redefinir raio dos cantos da tela", + "screen-corners-show-corners-description": "Exibe cantos arredondados na borda da tela.", + "screen-corners-show-corners-label": "Mostrar cantos da tela", + "screen-corners-solid-black-description": "Use preto sรณlido em vez da cor de fundo da barra.", + "screen-corners-solid-black-label": "Cantos pretos sรณlidos", + "screen-corners-title": "Cantos da tela", + "settings-copied": "Configuraรงรตes copiadas para a รกrea de transferรชncia", + "smooth-scrolling-description": "Animar a rolagem de listas para uma experiรชncia de roda mais suave.", + "smooth-scrolling-label": "Rolagem suave", + "tab-basics": "Bรกsico", + "tab-keybinds": "Atalhos de teclado" + }, + "hooks": { + "color-generation-description": "Comando a ser executado quando a geraรงรฃo de cores for concluรญda (p. ex., apรณs a tematizaรงรฃo baseada no papel de parede).", + "color-generation-label": "Cores geradas", + "color-generation-placeholder": "ex. notify-send \"Cores\" \"Gerados\"", + "info-command-info-description": "โ€ข Comandos sรฃo executados via shell (sh -lc)
โ€ข Comandos rodam em segundo plano (desanexados)
โ€ข Botรตes de teste executam com os valores atuais", + "info-parameters-description": "โ€ข Wallpaper hook: $1 = caminho do papel de parede, $2 = nome da tela, $3 = tema (escuro/claro)
โ€ข Theme toggle hook: $1 = true/false (estado do modo escuro)
โ€ข Screen lock/unlock hooks: $1 = lock/unlock (estado de bloqueio de tela)
โ€ข Performance mode hooks: Sem parรขmetros
โ€ข Session hook: $1 = action (shutdown/reboot)", + "info-parameters-label": "Parรขmetros disponรญveis", + "noctalia-started-description": "Comando para executar quando o Noctalia terminar de carregar.", + "noctalia-started-label": "Noctalia iniciado", + "noctalia-started-placeholder": "p. ex. notify-send 'Noctalia Carregado'", + "performance-mode-disabled-description": "Comando a ser executado quando o modo de desempenho Noctalia รฉ desabilitado.", + "performance-mode-disabled-label": "Modo de desempenho desabilitado", + "performance-mode-disabled-placeholder": "p. ex. notify-send \"Desempenho\" \"Modo desabilitado\"", + "performance-mode-enabled-description": "Comando a ser executado quando o modo de desempenho Noctalia รฉ habilitado.", + "performance-mode-enabled-label": "Modo de desempenho habilitado", + "performance-mode-enabled-placeholder": "p. ex. notify-send \"Desempenho\" \"Modo habilitado\"", + "screen-lock-description": "Comando a ser executado quando a tela รฉ bloqueada.", + "screen-lock-label": "Tela bloqueada", + "screen-lock-placeholder": "p. ex. notify-send \"Tela\" \"Bloqueada\"", + "screen-unlock-description": "Comando a ser executado quando a tela รฉ desbloqueada.", + "screen-unlock-label": "Tela desbloqueada", + "screen-unlock-placeholder": "p. ex. notify-send \"Tela\" \"Desbloqueada\"", + "session-description": "Comando a ser executado antes do desligamento ou reinicializaรงรฃo. Recebe o tipo de aรงรฃo como $1 (desligamento/reinicializaรงรฃo).", + "session-label": "Fim da sessรฃo", + "session-placeholder": "p. ex. notify-send \"Session\" \"$1\"", + "system-hooks-desc": "Configure comandos para serem executados quando eventos do sistema ocorrerem.", + "system-hooks-enable-description": "Ativa ou desativa todos os comandos de hook.", + "system-hooks-enable-label": "Ativar hooks", + "system-hooks-title": "Ganchos do sistema", + "theme-changed-description": "Comando a ser executado quando o tema alterna entre modo escuro e claro.", + "theme-changed-label": "Tema alterado", + "theme-changed-placeholder": "p. ex. notify-send \"Tema\" \"Alternado\"", + "title": "Hooks", + "wallpaper-changed-description": "Comando a ser executado quando o papel de parede muda.", + "wallpaper-changed-label": "Papel de parede alterado", + "wallpaper-changed-placeholder": "p. ex. notify-send \"Papel de parede\" \"Alterado\"" + }, + "idle": { + "custom-add": "Adicionar comando", + "custom-description": "Executa um comando de shell apรณs um perรญodo de inatividade.", + "custom-entry-command": "Comando", + "custom-entry-delete": "Excluir", + "custom-entry-edit": "Editar Comando Personalizado", + "custom-entry-name": "Nome", + "custom-entry-name-placeholder": "p. ex. Desligar luzes", + "custom-entry-new": "Novo Comando Personalizado", + "custom-entry-timeout": "Tempo de inatividade", + "custom-entry-timeout-format": "{count} segundo", + "custom-entry-timeout-format-plural": "{count} segundos", + "custom-entry-unnamed": "Comando sem nome", + "custom-label": "Comandos de inatividade personalizados", + "enable-description": "Desligar a tela, bloquear ou suspender automaticamente apรณs um perรญodo de inatividade.", + "enable-label": "Ativar gerenciamento de inatividade", + "fade-duration-description": "Segundos para a animaรงรฃo de esmaecimento para preto antes de cada aรงรฃo ser acionada. Qualquer movimento do rato cancela o esmaecimento.", + "fade-duration-label": "Duraรงรฃo do esmaecimento", + "lock-description": "Segundos de inatividade antes da ativaรงรฃo da tela de bloqueio.", + "lock-label": "Bloquear tela", + "resume-command-label": "Retomar comando", + "screen-off-description": "Segundos de inatividade antes que os monitores sejam desligados.", + "screen-off-label": "Desligar tela", + "status-description": "Tempo ocioso conforme relatado pelo compositor.", + "status-label": "Tempo ocioso", + "suspend-description": "Segundos de inatividade antes que o sistema suspenda.", + "tab-behavior": "Comportamento", + "tab-custom": "Personalizado", + "timeouts-description": "Defina como 0 para desativar uma etapa. Os tempos limite sรฃo pausados enquanto o 'Manter acordado' estiver ativo.", + "timeouts-label": "Tempos limite", + "title": "Inatividade", + "unavailable": "A monitorizaรงรฃo nativa de inatividade nรฃo estรก disponรญvel neste Compositor." + }, + "indicator": { + "default-value": "Padrรฃo: {value}", + "system-default": "Padrรฃo do sistema" + }, + "launcher": { + "clipboard-desc": "Acesse e gerencie seu histรณrico da รกrea de transferรชncia a partir do iniciador.", + "execute-desc": "Configurar como os aplicativos sรฃo iniciados.", + "execute-title": "Execuรงรฃo", + "settings-annotation-tool-description": "Comando para executar ao clicar no botรฃo de anotaรงรฃo no histรณrico da รกrea de transferรชncia. A imagem serรก enviada para este comando.", + "settings-annotation-tool-label": "Ferramenta de anotaรงรฃo", + "settings-annotation-tool-placeholder": "p. ex. 'gradia', 'satty -f -'", + "settings-auto-paste-description": "Colar automaticamente o item selecionado da รกrea de transferรชncia. Requer wtype.", + "settings-auto-paste-label": "Colar automaticamente", + "settings-clip-chips-description": "Mostrar uma barra de abas para filtrar o histรณrico da รกrea de transferรชncia por tipo (Imagens, Links, Ficheiros, Cรณdigo, etc.).", + "settings-clip-chips-label": "Ativar chips de categoria", + "settings-clip-preview-description": "Mostra uma prรฉ-visualizaรงรฃo do conteรบdo da รกrea de transferรชncia ao usar o comando >clip.", + "settings-clip-preview-label": "Ativar prรฉ-visualizaรงรฃo da รกrea de transferรชncia", + "settings-clip-smart-icons-description": "Mostrar รญcones especรญficos para links, arquivos, cores e cรณdigo em vez de um รญcone de รกrea de transferรชncia genรฉrico.", + "settings-clip-smart-icons-label": "Ativar รญcones inteligentes", + "settings-clip-wrap-text-description": "Quebrar o texto na lista da รกrea de transferรชncia em vez de truncรก-lo.", + "settings-clip-wrap-text-label": "Quebrar texto da รกrea de transferรชncia", + "settings-clipboard-history-description": "Acesse itens copiados anteriormente a partir do lanรงador.", + "settings-clipboard-history-label": "Ativar histรณrico da รกrea de transferรชncia", + "settings-clipboard-watch-image-description": "Cadeia de comando completa passada para wl-paste para alteraรงรตes de imagem (requer reinรญcio).", + "settings-clipboard-watch-image-label": "Comando de Vigilรขncia de Imagens", + "settings-clipboard-watch-text-description": "Cadeia de comando completa passada para wl-paste para alteraรงรตes de texto (requer reinรญcio).", + "settings-clipboard-watch-text-label": "Comando de observaรงรฃo de texto", + "settings-custom-launch-prefix-description": "Prefixar comandos com um inicializador personalizado (p. ex. 'runapp' para integraรงรฃo systemd).", + "settings-custom-launch-prefix-enabled-description": "Usar um prefixo personalizado para inicializar aplicativos em vez do mรฉtodo padrรฃo.", + "settings-custom-launch-prefix-enabled-label": "Habilitar prefixo de inicializaรงรฃo personalizado", + "settings-custom-launch-prefix-label": "Prefixo de inicializaรงรฃo personalizado", + "settings-density-description": "Ajuste o tamanho dos รญcones de aplicativos e a densidade do Launcher.", + "settings-density-label": "Densidade", + "settings-desc": "Personalize o comportamento e a aparรชncia do lanรงador.", + "settings-enable-session-search-description": "Mostrar aรงรตes de sessรฃo (bloquear, desligar, reiniciar, etc.) ao pesquisar no Launcher.", + "settings-enable-session-search-label": "Ativar pesquisa de sessรฃo", + "settings-enable-settings-search-description": "Mostrar os resultados das configuraรงรตes ao pesquisar no Launcher.", + "settings-enable-settings-search-label": "Ativar pesquisa nas configuraรงรตes", + "settings-enable-windows-search-description": "Pesquisar e focar janelas ativas.", + "settings-enable-windows-search-label": "Ativar pesquisa de janelas", + "settings-grid-view-description": "Exibir itens em uma grade em vez de uma lista.", + "settings-icon-mode-description": "Use รญcones nativos do sistema em vez de รญcones Tabler.", + "settings-icon-mode-label": "Usar รญcones nativos", + "settings-ignore-mouse-input-description": "Desativar a interaรงรฃo do mouse e a roda de rolagem no iniciador.", + "settings-ignore-mouse-input-label": "Ignorar entrada do mouse", + "settings-overlay-layer-description": "Exibe o lanรงador na camada de sobreposiรงรฃo, acima de janelas em tela cheia. Quando ativado, o lanรงador nรฃo serรก anexado ร  barra.", + "settings-overlay-layer-label": "Mostrar acima da tela cheia", + "settings-position-description": "Escolha onde o painel do lanรงador aparece.", + "settings-show-categories-description": "Mostrar abas de categorias para filtrar aplicativos.", + "settings-show-categories-label": "Mostrar categorias", + "settings-show-icon-background-description": "Mostrar um fundo retangular arredondado atrรกs dos รญcones.", + "settings-show-icon-background-label": "Mostrar fundo do รญcone", + "settings-sort-by-usage-description": "Quando ativado, os aplicativos mais usados aparecem primeiro na lista.", + "settings-sort-by-usage-label": "Ordenar por mais usados", + "settings-terminal-command-description": "Comando para iniciar um terminal. P. ex. 'kitty -e' ou 'gnome-terminal --'.", + "settings-terminal-command-label": "Comando do terminal", + "settings-use-app2unit-description": "Usa um mรฉtodo de inicializaรงรฃo alternativo para gerenciar melhor os processos de aplicativos e evitar problemas.", + "settings-use-app2unit-label": "Usar App2Unit para iniciar aplicativos", + "settings-view-mode-description": "Escolha o layout para as entradas do Lanรงador.", + "settings-view-mode-label": "Modo de visualizaรงรฃo", + "title": "Lanรงador" + }, + "location": { + "auto-locate-description": "Deteta automaticamente a sua localizaรงรฃo periodicamente usando o seu endereรงo IP.", + "auto-locate-label": "Auto-Localizaรงรฃo", + "calendar-cards-desc": "Organize e ative/desative cartรตes no painel do calendรกrio.", + "calendar-cards-title": "Cartรตes de calendรกrio", + "calendar-header-label": "Cabeรงalho do calendรกrio", + "calendar-month-label": "Mรชs do calendรกrio", + "date-time-12hour-format-description": "Exibe a hora no formato de 12 horas na tela de bloqueio e no calendรกrio. O relรณgio da barra tem suas prรณprias configuraรงรตes.", + "date-time-12hour-format-label": "Use o formato de hora de 12 horas", + "date-time-desc": "Personalize como a data e a hora aparecem.", + "date-time-first-day-of-week-automatic": "Automรกtico (usar localizaรงรฃo do sistema)", + "date-time-first-day-of-week-description": "Escolha qual dia comeรงa a semana no calendรกrio.", + "date-time-first-day-of-week-label": "Primeiro dia da semana", + "date-time-show-events-description": "Exibir eventos no painel do calendรกrio.", + "date-time-show-events-label": "Mostrar eventos do calendรกrio", + "date-time-title": "Data e hora", + "date-time-use-analog-description": "Mostrar um relรณgio de estilo analรณgico na janela do calendรกrio.", + "date-time-use-analog-label": "Use um relรณgio de estilo analรณgico", + "date-time-week-numbers-description": "Exibe a semana do ano (p. ex. Semana 38) no calendรกrio.", + "date-time-week-numbers-label": "Mostrar nรบmeros da semana", + "geolocate-now-button": "Localizar agora", + "location-desc": "Obtenha previsรฃo do tempo e agendamento de luz noturna precisos definindo sua localizaรงรฃo.", + "location-search-description": "p. ex. Sรฃo Paulo, SP", + "location-search-label": "Procurar por uma localizaรงรฃo", + "location-search-placeholder": "Digite o nome da localizaรงรฃo", + "location-title": "Sua localizaรงรฃo", + "weather-desc": "Escolha sua unidade de temperatura preferida.", + "weather-enabled-description": "Mostrar informaรงรตes meteorolรณgicas em toda a interface e buscar dados do clima. Quando desativado, todos os elementos do clima serรฃo ocultados e nenhuma solicitaรงรฃo de rede serรก feita.", + "weather-enabled-label": "Ativar clima", + "weather-fahrenheit-description": "Exibe a temperatura em Fahrenheit em vez de Celsius.", + "weather-fahrenheit-label": "Exibir temperatura em Fahrenheit (ยฐF)", + "weather-hide-city-description": "Oculte o nome da cidade das exibiรงรตes de clima em toda a interface.", + "weather-hide-city-label": "Ocultar nome da cidade", + "weather-hide-timezone-description": "Oculte a abreviaรงรฃo do fuso horรกrio das exibiรงรตes de clima em toda a interface.", + "weather-hide-timezone-label": "Ocultar fuso horรกrio", + "weather-show-effects-description": "Mostra efeitos visuais adicionais (como chuva, neve ou relรขmpagos) no cartรฃo de clima.", + "weather-show-effects-label": "Exibir efeitos climรกticos", + "weather-show-in-calendar-description": "Mostre a previsรฃo do tempo diรกria diretamente na sua visualizaรงรฃo de calendรกrio.", + "weather-show-in-calendar-label": "Exibir clima no calendรกrio", + "weather-talia-mascot-always-description": "Sempre use Talia emojis em vez de tabler icons.", + "weather-talia-mascot-always-label": "Sempre mostrar os emojis de clima da Talia" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "Quando o fprintd (autenticaรงรฃo por impressรฃo digital) estรก ativo, esta opรงรฃo permite-lhe iniciar sessรฃo com a sua palavra-passe em vez de uma impressรฃo digital.", + "allow-password-with-fprintd-label": "Permitir login por senha com fprintd", + "auto-start-auth-description": "Inicia automaticamente a autenticaรงรฃo de impressรฃo digital sem exigir uma pressรฃo de tecla ou um clique de botรฃo.", + "auto-start-auth-label": "Autenticaรงรฃo de inรญcio automรกtico", + "clock-format-description": "Personalize o formato do relรณgio usando tokens de sintaxe de data/hora.", + "clock-format-label": "Formato do relรณgio", + "clock-style-analog": "Analรณgico", + "clock-style-custom": "Personalizado", + "clock-style-description": "Escolha o estilo visual do relรณgio na Tela de Bloqueio.", + "clock-style-digital": "Digital", + "clock-style-label": "Estilo do Relรณgio", + "compact-lockscreen-description": "Mostrar apenas a entrada de login e os controles do sistema, ocultando os widgets de clima e mรญdia.", + "compact-lockscreen-label": "Tela de bloqueio compacta", + "enable-lockscreen-media-controls-description": "Mostrar controles interativos de reproduรงรฃo de mรญdia na tela de bloqueio.", + "enable-lockscreen-media-controls-label": "Controles de Mรญdia do Ecrรฃ de Bloqueio", + "lock-on-suspend-description": "Bloquear a tela automaticamente ao suspender o sistema.", + "lock-on-suspend-label": "Bloquear ao suspender", + "lock-screen-animations-description": "Ativar ou desativar as animaรงรตes do ecrรฃ de bloqueio.", + "lock-screen-animations-label": "Animaรงรตes da Tela de Bloqueio", + "lock-screen-blur-strength-description": "Aplica um efeito de desfoque ao papel de parede da tela de bloqueio.", + "lock-screen-blur-strength-label": "Intensidade do desfoque da tela de bloqueio", + "lock-screen-tint-strength-description": "Aplica uma sobreposiรงรฃo de tonalidade ao papel de parede da tela de bloqueio.", + "lock-screen-tint-strength-label": "Intensidade do matiz da tela de bloqueio", + "monitors-desc": "Mostrar tela de bloqueio em monitores especรญficos. O padrรฃo รฉ em todos, se nenhum for escolhido.", + "password-chars-description": "รcones fofos usados para ocultar sua senha.", + "password-chars-label": "รcones de senha aleatรณrios", + "show-hibernate-description": "Mostrar a opรงรฃo 'hibernar' nos controles de energia.", + "show-hibernate-label": "Mostrar hibernar", + "show-session-buttons-description": "Permitir acesso ร s configuraรงรตes de energia a partir da tela de bloqueio.", + "show-session-buttons-label": "Controles de energia", + "title": "Tela de bloqueio" + }, + "network": { + "bluetooth-description": "Ativar gerenciamento de Bluetooth.", + "bluetooth-rssi-polling-description": "Amostre periodicamente o RSSI para dispositivos conectados via bluetoothctl. Pode nรฃo estar disponรญvel para todos os dispositivos; usa recursos mรญnimos quando ativado.", + "bluetooth-rssi-polling-label": "Sondagem de sinal Bluetooth", + "desc": "Gerencie conexรตes Wi-Fi e Bluetooth.", + "wifi-description": "Gerenciar redes sem fio (requer NetworkManager)." + }, + "notifications": { + "duration-critical-urgency-description": "Por quanto tempo as notificaรงรตes de prioridade crรญtica permanecem visรญveis.", + "duration-critical-urgency-label": "Urgรชncia crรญtica", + "duration-desc": "Configure por quanto tempo as notificaรงรตes permanecem visรญveis com base no nรญvel de urgรชncia.", + "duration-low-urgency-description": "Por quanto tempo as notificaรงรตes de baixa prioridade permanecem visรญveis.", + "duration-low-urgency-label": "Urgรชncia baixa", + "duration-normal-urgency-description": "Por quanto tempo as notificaรงรตes de prioridade normal permanecem visรญveis.", + "duration-normal-urgency-label": "Urgรชncia normal", + "duration-respect-expire-description": "Usa o tempo de expiraรงรฃo definido na notificaรงรฃo.", + "duration-respect-expire-label": "Respeitar tempo de expiraรงรฃo", + "duration-title": "Duraรงรฃo da notificaรงรฃo", + "history-clear-dismiss-description": "Limpar notificaรงรฃo do histรณrico ao descartar.", + "history-clear-dismiss-label": "Limpar ao dispensar", + "history-critical-urgency-description": "Salvar notificaรงรตes de prioridade crรญtica no histรณrico.", + "history-critical-urgency-label": "Salvar urgรชncia crรญtica no histรณrico", + "history-desc": "Controlar quais notificaรงรตes sรฃo salvas no histรณrico com base no nรญvel de urgรชncia.", + "history-low-urgency-description": "Salvar notificaรงรตes de prioridade baixa no histรณrico.", + "history-low-urgency-label": "Salvar urgรชncia baixa no histรณrico", + "history-normal-urgency-description": "Salvar notificaรงรตes de prioridade normal no histรณrico.", + "history-normal-urgency-label": "Salvar urgรชncia normal no histรณrico", + "monitors-desc": "Mostra a notificaรงรฃo em monitores especรญficos. O padrรฃo รฉ todos, se nenhum for escolhido.", + "rules-action-block": "Bloco", + "rules-action-block-desc": "Ignora completamente.", + "rules-action-hide": "Ocultar", + "rules-action-hide-desc": "Sem pop-up, sem som, adiciona ao histรณrico.", + "rules-action-label": "Aรงรฃo", + "rules-action-mute": "Silenciar", + "rules-action-mute-desc": "Sem som, ainda mostra o pop-up e no histรณrico.", + "rules-add": "Adicionar regra", + "rules-delete": "Excluir regra", + "rules-description": "Corresponder nome da aplicaรงรฃo ou conteรบdo. As regras sรฃo verificadas por ordem, e a primeira correspondรชncia รฉ aplicada.", + "rules-edit": "Editar regra", + "rules-label": "Regras de Filtro", + "rules-pattern-label": "Padrรฃo", + "rules-pattern-placeholder": "firefox, discord, ou /regex/", + "rules-tab": "Regras", + "settings-always-on-top-description": "Exibe notificaรงรตes acima de janelas em tela cheia e outras camadas.", + "settings-background-opacity-description": "Ajustar a opacidade dos fundos das notificaรงรตes.", + "settings-density-description": "Escolha a densidade do cartรฃo de notificaรงรฃo.", + "settings-density-label": "Densidade", + "settings-desc": "Configure a aparรชncia e o comportamento das notificaรงรตes.", + "settings-do-not-disturb-description": "Desativa todos os pop-ups de notificaรงรฃo quando ativado.", + "settings-enabled-description": "Ativar ou desativar o daemon de notificaรงรตes, requer reiniciar o Noctalia shell.", + "settings-enabled-label": "Ativar notificaรงรตes", + "settings-location-description": "Onde as notificaรงรตes aparecem na tela.", + "settings-markdown-description": "Renderizar o conteรบdo da notificaรงรฃo usando formataรงรฃo Markdown.", + "settings-markdown-label": "Ativar Markdown", + "sounds-desc": "Configurar efeitos sonoros e volume de notificaรงรตes.", + "sounds-enabled-description": "Ativar efeitos sonoros para notificaรงรตes recebidas.", + "sounds-enabled-label": "Ativar sons de notificaรงรฃo", + "sounds-excluded-apps-description": "Pular a reproduรงรฃo do som de notificaรงรฃo configurado para aplicativos especรญficos que tรชm seus prรณprios sons integrados.", + "sounds-excluded-apps-label": "Aplicativos excluรญdos", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "Caminho para o arquivo de som tocado para notificaรงรตes de prioridade crรญtica.", + "sounds-files-critical-label": "Som de urgรชncia crรญtica", + "sounds-files-critical-select-title": "Selecionar arquivo de som de urgรชncia crรญtica", + "sounds-files-desc": "Configurar arquivos de som personalizados para diferentes nรญveis de urgรชncia de notificaรงรฃo.", + "sounds-files-low-description": "Caminho para o arquivo de som tocado para notificaรงรตes de prioridade baixa.", + "sounds-files-low-label": "Som de urgรชncia baixa", + "sounds-files-low-select-title": "Selecionar arquivo de som de urgรชncia baixa", + "sounds-files-normal-description": "Caminho para o arquivo de som tocado para notificaรงรตes de prioridade normal.", + "sounds-files-normal-label": "Som de urgรชncia normal", + "sounds-files-normal-select-title": "Selecionar arquivo de som de urgรชncia normal", + "sounds-files-placeholder": "Digitar o caminho para o arquivo de som", + "sounds-files-select-file": "Selecionar arquivo de som", + "sounds-files-title": "Arquivos de som", + "sounds-files-unified-description": "Caminho para o arquivo de som tocado para notificaรงรตes.", + "sounds-files-unified-label": "Som de notificaรงรฃo", + "sounds-files-unified-select-title": "Selecionar arquivo de som de notificaรงรฃo", + "sounds-separate-description": "Usar arquivos de som diferentes para notificaรงรตes de prioridade baixa, normal e crรญtica.", + "sounds-separate-label": "Usar sons diferentes por prioridade", + "sounds-title": "Configuraรงรตes de som", + "sounds-unavailable-description": "Instale o Qt6 Multimedia para habilitar sons de notificaรงรฃo.", + "sounds-unavailable-label": "Sons de notificaรงรฃo indisponรญveis", + "sounds-volume-description": "Ajustar o nรญvel de volume para sons de notificaรงรฃo.", + "sounds-volume-label": "Volume do som", + "toast-battery-description": "Mostrar um aviso quando o nรญvel da bateria cair abaixo desta percentagem.", + "toast-battery-label": "Aviso de bateria", + "toast-desc": "Configurar a aparรชncia e o comportamento do toast.", + "toast-keyboard-description": "Mostrar uma notificaรงรฃo quando o layout do teclado mudar.", + "toast-keyboard-label": "Layout do teclado", + "toast-media-description": "Mostrar um toast quando o estado de reproduรงรฃo de mรญdia mudar.", + "toast-media-label": "Multimรฉdia" + }, + "osd": { + "always-on-top-description": "Exibir OSD acima de janelas em tela cheia e outras camadas.", + "always-on-top-label": "Sempre no topo", + "background-opacity-description": "Controla a transparรชncia do fundo do OSD.", + "background-opacity-label": "Opacidade do fundo", + "description": "Configure indicadores na tela, como volume e brilho.", + "duration-auto-hide-description": "Ajusta o tempo antes da OSD desaparecer.", + "duration-auto-hide-label": "Ocultar apรณs", + "duration-desc": "Quanto tempo a OSD permanece visรญvel antes de ocultar automaticamente.", + "duration-title": "Tempo para ocultar", + "enabled-description": "Mostrar alteraรงรตes de volume e brilho em tempo real.", + "enabled-label": "Ativar exibiรงรฃo na tela (OSD)", + "events-desc": "Selecione quais eventos acionam a exibiรงรฃo na tela.", + "general-desc": "Configurar a visibilidade e o comportamento do OSD.", + "location-description": "Onde a exibiรงรฃo na tela aparece.", + "monitors-desc": "Mostrar a OSD em monitores especรญficos. Padrรฃo รฉ todos se nenhum for escolhido.", + "title": "Exibiรงรฃo na tela", + "types-brightness-description": "Mostrar o OSD quando o brilho da tela mudar.", + "types-brightness-label": "Brilho", + "types-custom-text-description": "Mostrar OSD para mensagens de texto personalizadas via IPC.", + "types-custom-text-label": "Texto personalizado", + "types-desc": "Selecione os eventos que acionam o OSD.", + "types-input-volume-description": "Mostrar o OSD quando o volume do microfone mudar.", + "types-input-volume-label": "Volume de entrada", + "types-lockkey-description": "Mostrar o OSD quando Bloq Caps, Bloq Num ou Bloq Rol forem alternadas.", + "types-lockkey-label": "Teclas de bloqueio", + "types-media-description": "Mostrar OSD quando o estado de reproduรงรฃo de mรญdia mudar (reproduzir, pausar, pular).", + "types-media-label": "Reproduรงรฃo de mรญdia", + "types-title": "Eventos de disparo OSD", + "types-volume-description": "Mostrar o OSD quando o volume de saรญda de รกudio mudar.", + "types-volume-label": "Volume de saรญda" + }, + "plugins": { + "auto-update": "Atualizar plugins automaticamente", + "auto-update-description": "Atualizar automaticamente todos os plugins quando a shell iniciar.", + "available-description": "Navegue e instale plugins a partir de fontes configuradas.", + "available-label": "Plugins disponรญveis", + "available-no-plugins-description": "Verifique as fontes do seu plugin ou atualize a lista.", + "available-no-plugins-label": "Nenhum plugin disponรญvel", + "check-for-updates": "Verificar atualizaรงรตes", + "checking-for-updates": "Verificando atualizaรงรตes...", + "collision-already-installed": "Este plugin jรก estรก instalado", + "collision-custom-version-exists": "Uma versรฃo personalizada de \"{source}\" jรก estรก instalada", + "collision-official-version-exists": "A versรฃo oficial deste plugin jรก estรก instalada", + "development-disable": "Desativar o modo de desenvolvimento para este plugin", + "development-enable": "Ativar o modo de desenvolvimento para este plugin.", + "filter-downloaded": "Baixado", + "filter-not-downloaded": "Nรฃo baixado", + "filter-tags-description": "Filtrar plugins por categoria ou estado de download.", + "filter-tags-label": "Etiquetas", + "hot-reload-description": "Recarrega automaticamente os plugins quando seus arquivos sรฃo alterados. รštil para o desenvolvimento de plugins.", + "hot-reload-label": "Recarga a quente (modo de desenvolvimento)", + "hot-reloaded": "Plugin recarregado: {name}", + "install-error": "Falha ao instalar: {error}", + "install-incompatible": "{plugin} requer Noctalia v{version} ou superior", + "install-success": "{plugin} instalado com sucesso", + "installed-description": "Gerenciar e configurar todos os plugins instalados localmente.", + "installed-label": "Plugins instalados", + "installed-no-plugins-description": "Instale os plugins da seรงรฃo \"Disponรญvel\".", + "installed-no-plugins-label": "Nenhum plugin instalado", + "installing": "Instalando {plugin}...", + "notify-updates": "Notificaรงรตes de atualizaรงรฃo de plugins", + "notify-updates-description": "Mostrar um popup de notificaรงรฃo quando novas atualizaรงรตes de plugins estiverem disponรญveis.", + "open-plugin-page": "Abrir pรกgina do plugin", + "open-plugins-tab": "Abrir configuraรงรตes de plugins", + "plugin-settings-title": "Configuraรงรตes do {plugin}", + "refresh-refreshing": "Atualizando a lista de plugins...", + "refresh-tooltip": "Atualizar plugins disponรญveis", + "settings-error-not-loaded": "Plugin nรฃo carregado", + "settings-saved": "Configuraรงรตes do plugin salvas", + "settings-tooltip": "Configuraรงรตes do plugin", + "source-custom": "Fonte personalizada", + "sources-add-custom": "Adicionar repositรณrio personalizado", + "sources-add-dialog-description": "Adicionar um repositรณrio GitHub como fonte de plugin.", + "sources-add-dialog-error": "Falha ao adicionar a fonte do plugin", + "sources-add-dialog-name": "Nome do repositรณrio", + "sources-add-dialog-name-placeholder": "Meus plugins personalizados", + "sources-add-dialog-success": "Fonte do plugin adicionada com sucesso", + "sources-add-dialog-title": "Adicionar fonte do plugin", + "sources-add-dialog-url": "URL do repositรณrio", + "sources-description": "Gerenciar repositรณrios de plugins.", + "sources-placeholder": "Meu repositรณrio legal", + "sources-remove-tooltip": "Remover cรณdigo fonte do plugin", + "title": "Plugins", + "translations-reloaded": "Traduรงรตes recarregadas: {name}", + "uninstall-dialog-description": "Tem certeza de que deseja desinstalar {plugin}? Isso removerรก todos os dados do plugin.", + "uninstall-dialog-title": "Desinstalar plugin", + "uninstall-error": "Falha ao desinstalar: {error}", + "uninstall-success": "{plugin} desinstalado com sucesso", + "uninstalling": "Desinstalando {plugin}...", + "update-all": "Atualizar tudo ({count})", + "update-all-success": "Todos os plugins foram atualizados com sucesso", + "update-available": "Nova atualizaรงรฃo de plugin disponรญvel", + "update-available-plural": "Novas atualizaรงรตes de plugin disponรญveis ({count})", + "update-error": "Falha ao atualizar o plugin: {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (requer Noctalia v{required})", + "update-success": "{plugin} atualizado para a versรฃo {version}", + "update-version": "v{current} โ†’ v{new}", + "updating": "Atualizando..." + }, + "region": { + "title": "Regiรฃo" + }, + "session-menu": { + "countdown-duration-description": "Definir por quanto tempo o temporizador de contagem regressiva dura antes de executar aรงรตes de energia.", + "countdown-duration-label": "Duraรงรฃo da contagem regressiva", + "enable-countdown-description": "Mostrar um temporizador de contagem regressiva antes de executar aรงรตes de energia.", + "enable-countdown-label": "Ativar temporizador de contagem regressiva", + "entries-desc": "Personalizar quais aรงรตes de energia aparecem no menu de sessรฃo e em que ordem.", + "entries-title": "Aรงรตes de energia", + "entry-settings-command-description": "Comando personalizado para executar para esta aรงรฃo. Deixe vazio para usar o comando padrรฃo do sistema.", + "entry-settings-command-placeholder": "p. ex. systemctl poweroff", + "entry-settings-default-command-lock": "Tela de bloqueio interna (sem comando)", + "entry-settings-default-command-logout": "Logout interno (sem comando)", + "entry-settings-default-info-description": "Se nenhum comando personalizado for especificado, o comando padrรฃo do sistema serรก usado.", + "entry-settings-default-info-label": "Comando padrรฃo", + "entry-settings-keybind-description": "Atribua uma combinaรงรฃo de teclas para acionar esta aรงรฃo quando o menu de sessรฃo estiver aberto.", + "entry-settings-keybind-placeholder": "Pressione Gravar para atribuir uma tecla...", + "entry-settings-keybind-recording": "Gravando... Pressione qualquer combinaรงรฃo de teclas", + "entry-settings-title": "Configurar {entry}", + "entry-settings-tooltip": "Configurar comando", + "general-desc": "Configurar o comportamento e a aparรชncia do painel do menu de sessรฃo.", + "large-buttons-layout-description": "Escolha como os botรตes do menu de sessรฃo sรฃo exibidos.", + "large-buttons-layout-label": "Layout de botรตes grandes", + "large-buttons-style-description": "Exibir o menu de sessรฃo com botรตes grandes em um layout de grade.", + "large-buttons-style-label": "Estilo de botรตes grandes", + "position-description": "Escolha onde o painel do Menu de sessรฃo aparece quando aberto.", + "show-header-description": "Exibir o tรญtulo e o botรฃo de fechar na parte superior do menu de sessรฃo.", + "show-header-label": "Mostrar cabeรงalho", + "show-keybinds-description": "Exibir dicas de atalhos de teclado nas opรงรตes de sessรฃo.", + "show-keybinds-label": "Mostrar atalhos de teclado" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Manter os widgets da รกrea de trabalho visรญveis enquanto o modo de desempenho Noctalia estiver ativado.", + "noctalia-performance-disable-desktop-widgets-label": "Ativar widgets de ambiente de trabalho no modo de desempenho", + "noctalia-performance-disable-wallpaper-description": "Mantenha os papรฉis de parede da รกrea de trabalho, da visรฃo geral e da tela de bloqueio visรญveis enquanto o modo de desempenho Noctalia estiver ativado.", + "noctalia-performance-disable-wallpaper-label": "Ativar renderizaรงรฃo de papel de parede no modo de desempenho", + "title": "Sistema" + }, + "system-monitor": { + "critical-color-label": "Cor crรญtica", + "custom-highlight-colors-title-label": "Cores de destaque personalizadas", + "disk-available-label": "Espaรงo em disco disponรญvel", + "disk-section-label": "Uso do disco", + "enable-dgpu-monitoring-description": "Atenรงรฃo: Isto irรก ativar sua GPU dedicada (NVIDIA/AMD), o que pode impactar significativamente a duraรงรฃo da bateria em laptops com grรกficos hรญbridos.", + "enable-dgpu-monitoring-label": "Ativar o monitoramento da GPU dedicada", + "external-monitor-description": "Digite o comando ou caminho da aplicaรงรฃo para executar ao ativar a aplicaรงรฃo externa de monitoramento do sistema.", + "external-monitor-label": "Comando do monitor do sistema externo", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "Configure o comportamento e a aparรชncia do monitor do sistema.", + "gpu-section-label": "Temperatura da GPU", + "highlight-colors-section-label": "Cores de destaque", + "polling-interval-label": "Intervalo de pesquisa", + "polling-section-description": "Configure a frequรชncia com que cada mรฉtrica do sistema รฉ atualizada.", + "polling-section-label": "Intervalos de pesquisa", + "threshold-critical": "Crรญtico", + "threshold-warning": "Aviso", + "thresholds-section-description": "Ajuste os limites de aviso/crรญtico e os intervalos de consulta para cada mรฉtrica do sistema.", + "use-custom-highlight-colors-description": "Quando desativado, sรฃo usadas as cores de destaque padrรฃo do tema.", + "use-custom-highlight-colors-label": "Usar cores de destaque personalizadas", + "warning-color-label": "Cor de aviso" + }, + "user-interface": { + "allow-panels-without-bar-description": "Quando ativado, os painรฉis podem abrir em qualquer tela. Quando desativado, os painรฉis sรณ abrirรฃo em telas que tenham uma barra, o que pode reduzir o uso de memรณria.", + "allow-panels-without-bar-label": "Permitir painรฉis em telas sem uma barra", + "animation-disable-description": "Desative todas as animaรงรตes para uma experiรชncia mais rรกpida e responsiva.", + "animation-disable-label": "Desativar animaรงรตes da interface do usuรกrio", + "animation-speed-description": "Ajustar a velocidade global da animaรงรฃo.", + "animation-speed-label": "Velocidade da animaรงรฃo", + "animation-speed-reset": "Redefinir velocidade da animaรงรฃo", + "appearance-desc": "Personalize elementos visuais como dicas de ferramentas, bordas e sombras.", + "blur-behind-description": "Desfoca a รกrea atrรกs das barras e painรฉis usando o protocolo de desfoque do compositor.", + "blur-behind-label": "Desfoque de fundo", + "box-border-description": "Exibe um contorno ao redor das รกreas de conteรบdo.", + "box-border-label": "Contorno do recipiente", + "box-border-radius-description": "Ajusta o arredondamento dos cantos das principais seรงรตes do layout, como barras laterais, cards e painรฉis de conteรบdo.", + "box-border-radius-label": "Raio do contรชiner", + "control-border-radius-description": "Controla a curvatura de elementos interativos, incluindo botรตes, alternรขncias e campos de texto.", + "control-border-radius-label": "Raio de entrada", + "desc": "Personalize a aparรชncia, a sensaรงรฃo e o comportamento da interface.", + "dim-desktop-description": "Escurecer a รกrea de trabalho quando painรฉis ou menus estiverem abertos.", + "dim-desktop-label": "Dim รกrea de trabalho", + "dimmer-opacity-description": "Definir o nรญvel de opacidade para o escurecimento da รกrea de trabalho.", + "dimmer-opacity-label": "Opacidade da รกrea de trabalho escurecida", + "panel-background-opacity-description": "Definir a opacidade do fundo para todos os painรฉis (barra, iniciador, configuraรงรตes, etc...).", + "panel-background-opacity-label": "Opacidade de fundo do painel", + "panels-attached-to-bar-description": "Os painรฉis travam na barra e nas bordas da tela, criando uma aparรชncia perfeita com cantos invertidos elegantes.", + "panels-attached-to-bar-label": "Ajustar painรฉis ร s bordas", + "panels-overlay-description": "Garante que os painรฉis e a barra permaneรงam visรญveis, mesmo sobre aplicativos em tela cheia.", + "panels-overlay-label": "Manter painรฉis e barra no topo", + "scaling-description": "Altera o tamanho da interface geral do usuรกrio, excluindo a barra.", + "scaling-label": "Escalonamento da interface", + "scrollbar-always-visible-description": "Manter as barras de rolagem visรญveis sempre que o conteรบdo for rolรกvel, em vez de mostrรก-las apenas ao passar o mouse.", + "scrollbar-always-visible-label": "Sempre mostrar barras de rolagem", + "settings-panel-header": "Painel de Definiรงรตes", + "settings-panel-mode-description": "Escolha o layout das configuraรงรตes (pode ser necessรกrio reabrir).", + "settings-panel-mode-label": "Modo do painel de configuraรงรตes", + "settings-panel-sidebar-card-style-description": "Envolve a barra lateral de configuraรงรตes num fundo preenchido com cantos arredondados.", + "settings-panel-sidebar-card-style-label": "Estilo de Cartรฃo da Barra Lateral", + "shadows-description": "Ativa sombras projetadas sob barras e painรฉis.", + "shadows-direction-description": "Escolha onde a sombra รฉ projetada.", + "shadows-direction-label": "Direรงรฃo da sombra", + "shadows-label": "Sombras projetadas", + "title": "Interface do usuรกrio", + "tooltips-description": "Ativar ou desativar dicas de ferramentas em toda a interface.", + "tooltips-label": "Mostrar dicas de ferramenta", + "translucent-widgets-description": "Torne botรตes, abas e outros widgets dentro dos painรฉis semitransparentes.", + "translucent-widgets-label": "Widgets translรบcidos" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "Alfabรฉtico", + "automation-change-mode-description": "Escolha como os papรฉis de parede sรฃo selecionados ao alterar automaticamente.", + "automation-change-mode-label": "Modo de alteraรงรฃo", + "automation-custom-interval-description": "Digite o tempo como HH:MM (p. ex. 01:30).", + "automation-custom-interval-label": "Intervalo personalizado", + "automation-interval-description": "Com que frequรชncia alterar os papรฉis de parede automaticamente.", + "automation-interval-label": "Intervalo do papel de parede", + "automation-random-wallpaper-description": "Agende mudanรงas aleatรณrias de papel de parede em intervalos regulares.", + "automation-scheduled-change-description": "Alterar automaticamente os papรฉis de parede em intervalos regulares.", + "automation-scheduled-change-label": "Alteraรงรฃo agendada", + "look-feel-edge-smoothness-description": "Aplica um efeito suave e esfumaรงado ร  borda das transiรงรตes.", + "look-feel-edge-smoothness-label": "Suavizar borda da transiรงรฃo", + "look-feel-fill-color-description": "Escolha uma cor de preenchimento que pode aparecer atrรกs do papel de parede.", + "look-feel-fill-mode-description": "Selecione como a imagem deve ser dimensionada para corresponder ร  resoluรงรฃo do seu monitor.", + "look-feel-fill-mode-label": "Modo de preenchimento", + "look-feel-skip-startup-transition-description": "Ignorar a animaรงรฃo do papel de parede ao iniciar a shell.", + "look-feel-skip-startup-transition-label": "Pular transiรงรฃo de inicializaรงรฃo", + "look-feel-title": "Aparรชncia e estilo", + "look-feel-transition-duration-description": "Duraรงรฃo das animaรงรตes de transiรงรฃo em segundos.", + "look-feel-transition-duration-label": "Duraรงรฃo da transiรงรฃo", + "look-feel-transition-type-description": "Tipo de animaรงรฃo ao alternar entre papรฉis de parede.", + "look-feel-transition-type-label": "Tipo de transiรงรฃo", + "settings-clear-cache-button": "Limpar cache", + "settings-clear-cache-description": "Limpar papรฉis de parede redimensionados em cache para liberar espaรงo em disco.", + "settings-clear-cache-label": "Cache de papรฉis de parede", + "settings-clear-cache-toast": "Cache de papel de parede limpa", + "settings-desc": "Controle como os papรฉis de parede sรฃo gerenciados e exibidos.", + "settings-enable-management-description": "Gerencie papรฉis de parede com o Noctalia. Desmarque se preferir usar outro aplicativo.", + "settings-enable-management-label": "Ativar gerenciamento de papel de parede", + "settings-enable-overview-description": "Aplica um papel de parede desfocado e escurecido ร  tela de visรฃo geral.", + "settings-enable-overview-label": "Ativar papel de parede de visรฃo geral", + "settings-folder-description": "Caminho para a sua pasta principal de papรฉis de parede.", + "settings-folder-label": "Pasta de papรฉis de parede", + "settings-hide-wallpaper-filenames-tooltip-hide": "Ocultar nomes de arquivo", + "settings-hide-wallpaper-filenames-tooltip-show": "Mostrar nomes de arquivo", + "settings-monitor-specific-description": "Defina uma pasta de papel de parede diferente para cada monitor.", + "settings-monitor-specific-label": "Diretรณrios especรญficos por monitor", + "settings-monitor-specific-tooltip": "Pasta de papรฉis de parede do monitor", + "settings-overview-blur-strength-description": "Aplica a intensidade do desfoque ร  Visรฃo Geral.", + "settings-overview-blur-strength-label": "Intensidade do desfoque da visรฃo geral", + "settings-overview-tint-description": "Aplica a intensidade da tonalidade ร  visรฃo geral.", + "settings-overview-tint-label": "Intensidade da tonalidade da visรฃo geral", + "settings-recursive-search-description": "Tambรฉm buscar papรฉis de parede em subpastas do diretรณrio de papรฉis de parede.", + "settings-recursive-search-label": "Buscar em subpastas", + "settings-select-monitor-folder": "Selecionar pasta de papรฉis de parede do monitor", + "settings-selector-description": "Escolha o seu papel de parede.", + "settings-selector-position-description": "Escolha onde o painel do seletor de papel de parede aparece.", + "settings-show-hidden-files-tooltip-hide": "Ocultar arquivos ocultos", + "settings-show-hidden-files-tooltip-show": "Mostrar ficheiros ocultos", + "settings-title": "Configuraรงรตes de papel de parede", + "settings-use-original-images-description": "Ignorar o redimensionamento de papรฉis de parede antes da exibiรงรฃo. Economiza espaรงo em disco e reduz o uso da CPU durante as mudanรงas de papel de parede, mas pode usar mais memรณria para imagens muito grandes.", + "settings-use-original-images-label": "Usar imagens originais", + "settings-view-mode-description": "Escolha como os papรฉis de parede sรฃo exibidos a partir do seu diretรณrio.", + "settings-view-mode-label": "Modo de visualizaรงรฃo", + "view-mode-browse": "Navegar em diretรณrios", + "view-mode-cycle-tooltip": "Modo de visualizaรงรฃo: {mode} (clique para alterar)", + "view-mode-recursive": "Subdiretรณrios achatados", + "view-mode-single": "Diretรณrio raiz" + } + }, + "placeholders": { + "add-new-keybind": "Adicionar nova combinaรงรฃo de teclas", + "command-example": "echo \"Olรก, mundo\"", + "enter-command": "Digite o comando para executar (app ou script personalizado)", + "enter-ipc-identifier": "Introduza um identificador exclusivo para comandos IPC", + "enter-path": "Digite o caminho...", + "enter-text-to-collapse": "p. ex. 'nada estรก sendo reproduzido'. Use /regex/ para padrรตes.", + "enter-tooltip": "Inserir dica de ferramenta", + "enter-width-pixels": "Digite a largura em pixels", + "keybind-recording": "Gravando atalho de teclado...", + "search": "Pesquisar...", + "search-icons": "p. ex. noctalia, niri, battery, cloud", + "search-launcher": "Pesquisar entradas... ou use > para comandos", + "search-wallhaven": "Pesquisar no Wallhaven...", + "search-wallpapers": "Digite para filtrar papรฉis de parede..." + }, + "positions": { + "bottom": "Inferior", + "bottom-center": "Centro inferior", + "bottom-left": "Inferior esquerda", + "bottom-right": "Inferior direita", + "center": "Centralizar", + "center-left": "Esquerda central", + "center-right": "Direita central", + "close-to-bar": "Perto do botรฃo da barra", + "follow-bar": "Seguir barra", + "left": "Esquerda", + "right": "Direito/Certo/ร€ direita", + "top": "Topo", + "top-center": "Centro superior", + "top-left": "Superior esquerda", + "top-right": "Superior direita" + }, + "session-menu": { + "action-in-seconds": "{action} em {seconds} segundos...", + "cancel-timer": "Cancelar temporizador", + "title": "Menu da sessรฃo" + }, + "setup": { + "all-done": "Concluรญdo!", + "appearance": { + "subheader": "Escolha o modo escuro e as fontes de cor (papel de parede ou predefinidas)." + }, + "customize": { + "header": "Personalizar a sua experiรชncia", + "subheader": "Ajuste a posiรงรฃo da barra, densidade, escala e mais." + }, + "skip-setup": "Ignorar configuraรงรฃo", + "telemetry-wizard-done": "Entendi!", + "telemetry-wizard-note": "Vocรช estรก no controle โ€” ative ou desative isso a qualquer momento nas configuraรงรตes", + "telemetry-wizard-subtitle": "Adicionรกmos anรกlises anรณnimas para ajudar a melhorar o Noctalia", + "telemetry-wizard-title": "Atualizaรงรฃo de privacidade", + "wallpaper": { + "choose-dir": "Escolha um diretรณrio contendo seus papรฉis de parede", + "dir-browse": "Procurar pasta", + "dir-description": "Escolha a pasta que contรฉm seus papรฉis de parede.", + "dir-label": "Diretรณrio de papรฉis de parede", + "dir-select-title": "Selecionar pasta de papรฉis de parede", + "header": "Escolha o seu papel de parede", + "no-dir": "Nenhum diretรณrio de papรฉis de parede selecionado", + "no-valid": "Nenhuma imagem vรกlida encontrada em: {dir}", + "none-in-dir": "Nenhum papel de parede encontrado no diretรณrio", + "preview-error": "Falha ao carregar a imagem", + "select-prompt": "Selecione um papel de parede abaixo", + "subheader": "Defina o ambiente com um belo fundo." + }, + "welcome": "Bem-vindo", + "welcome-note": "Apenas alguns ajustes bรกsicos para comeรงar โ€” o restante estรก em configuraรงรตes", + "welcome-subtitle": "Vamos tornar o seu ambiente de trabalho รบnico", + "welcome-title": "Bem-vindo ao Noctalia!" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "Nenhum reprodutor de mรญdia detectado", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "Aplicativo desconhecido", + "uptime": "Atividade: {uptime}", + "user-requested": "Solicitado pelo usuรกrio", + "welcome-back": "Bem-vindo(a) de volta,", + "widget-settings-title": "Configuraรงรตes de {widget}" + }, + "system-monitor": { + "core-usage": "Uso do nรบcleo {id}", + "cpu-temp": "Temperatura da CPU", + "cpu-usage": "Uso da CPU", + "disk": "Disco", + "download-speed": "Velocidade de download", + "gpu-temp": "Temperatura da GPU", + "load-average": "Carga mรฉdia", + "title": "Monitor do sistema", + "upload-speed": "Velocidade de upload" + }, + "toast": { + "airplane-mode": { + "description": "Desativar todas as comunicaรงรตes sem fio.", + "title": "Modo aviรฃo" + }, + "battery": { + "critical": "Bateria crรญtica", + "critical-desc": "Bateria a {percent}% โ€” por favor, conecte o carregador imediatamente", + "low": "Bateria fraca", + "low-desc": "Bateria estรก em {percent}% โ€” por favor, conecte o carregador" + }, + "bluetooth": { + "address-copied": "Endereรงo copiado para a รกrea de transferรชncia", + "auto-connect-disabled": "Conexรฃo automรกtica desativada", + "auto-connect-enabled": "Conexรฃo automรกtica ativada", + "auto-connecting": "A conectar a {count} dispositivo(s)...", + "confirm-code": "Confirme o cรณdigo {value} no outro dispositivo.", + "connect-failed": "Falha ao conectar ao dispositivo", + "disconnect-failed": "Falha ao desconectar do dispositivo", + "discoverable-change-failed": "Falha ao alterar o estado de visibilidade", + "discoverable-disabled": "Visรญvel desativado", + "discoverable-enabled": "Visรญvel ativado", + "display-code": "Insira o cรณdigo {value} no outro dispositivo.", + "forget-failed": "Falha ao esquecer o dispositivo", + "pair-failed": "Falha ao emparelhar o dispositivo", + "passkey-required": "Senha necessรกria pelo dispositivo.", + "pincode-required": "Cรณdigo PIN exigido pelo dispositivo.", + "state-change-failed": "Falha ao alterar o estado do Bluetooth" + }, + "clipboard": { + "long-text": "Texto longo", + "unavailable": "Histรณrico da รกrea de transferรชncia indisponรญvel", + "unavailable-desc": "O aplicativo 'cliphist' nรฃo estรก instalado. Por favor, instale-o para usar os recursos do histรณrico da รกrea de transferรชncia" + }, + "custom-command-failed": { + "description": "Comando falhou: {command}\\nCรณdigo de saรญda: {code}", + "title": "Comando personalizado falhou" + }, + "do-not-disturb": { + "disabled": "'Nรฃo perturbe' desativado", + "disabled-desc": "Mostrando todas as notificaรงรตes", + "enabled": "'Nรฃo perturbe' ativado", + "enabled-desc": "Vocรช encontrarรก essas notificaรงรตes no seu histรณrico" + }, + "donation-opened": "Pรกgina de doaรงรฃo aberta no seu navegador", + "internet-limited": "Conectado sem Internet", + "keyboard-layout": { + "changed": "Layout de teclado alterado para {layout}", + "title": "Teclado" + }, + "missing-control-center": { + "description": "O widget da central de controle foi removido da barra. Para acessรก-lo novamente pela barra, adicione o widget novamente. Vocรช tambรฉm pode abri-lo clicando com o botรฃo direito na barra", + "label": "รšltimo widget da central de controle removido" + }, + "night-light": { + "forced": "Ativaรงรฃo forรงada", + "normal": "Modo normal", + "not-installed": "wlsunset nรฃo estรก instalado" + }, + "noctalia-performance": { + "disabled": "Modo de desempenho desativado", + "enabled": "Modo de desempenho ativado", + "label": "Noctalia Desempenho" + }, + "power-profile": { + "changed": "Perfil de energia alterado", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "Ocorreu um erro ao processar os templates", + "title": "Falha ao processar o modelo" + }, + "vpn": { + "connected": "Conectado a '{name}'", + "disconnected": "Desconectado de '{name}'" + }, + "wallpaper-colors": { + "disabled": "Cores do papel de parede desativadas", + "enabled": "Cores do papel de parede ativadas", + "label": "Geraรงรฃo de cores para papel de parede" + }, + "wifi": { + "connected": "Conectado a '{ssid}'", + "connection-failed": "Falha na conexรฃo", + "connection-timeout": "Tempo de conexรฃo esgotado", + "disconnected": "Desconectado de '{ssid}'", + "incorrect-password": "Senha incorreta", + "network-not-found": "Rede nรฃo encontrada" + } + }, + "tooltips": { + "add-widget": "Adicionar widget", + "auto-connect": "Alternar conexรฃo automรกtica para este dispositivo", + "bluetooth-auto-connect-off": "Conexรฃo automรกtica desativada", + "bluetooth-auto-connect-on": "Conexรฃo automรกtica ativada", + "bluetooth-devices": "Dispositivos Bluetooth", + "brightness-at": "Brilho: {brightness}%", + "click-to-start-recording": "Gravador de tela (iniciar gravaรงรฃo)", + "click-to-stop-recording": "Gravador de tela (parar gravaรงรฃo)", + "collapse": "Recolher barra lateral", + "copy-address": "Copiar endereรงo", + "delete-notification": "Excluir notificaรงรฃo", + "dismiss-notification": "Descartar notificaรงรฃo", + "do-not-disturb-enabled": "Nรฃo perturbe", + "expand": "Expandir barra lateral", + "forget-network": "Esquecer rede", + "grid-view": "Visualizaรงรฃo em grade", + "hidden-files-hide": "Arquivos ocultos", + "hide-unnamed-devices": "Ocultar dispositivos sem nome", + "home": "Diretรณrio home", + "input-muted": "Alternar mudo de entrada", + "keep-awake": "Manter acordado", + "keyboard-layout": "Layout de teclado {layout}", + "list-view": "Visualizaรงรฃo em lista", + "manage-vpn": "Conexรตes VPN", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "Nรบmero mรกximo de widgets atingido", + "microphone-volume-at": "Volume do microfone: {volume}%", + "move-to-section": "Mover para {section}", + "mute": "Silenciar", + "next-media": "Prรณxima faixa", + "next-month": "Prรณximo mรชs", + "night-light-not-installed": "Luz noturna (nรฃo disponรญvel)", + "noctalia-performance-enabled": "Modo de desempenho Noctalia", + "open-annotation-tool": "Abrir com ferramenta de anotaรงรฃo", + "open-control-center": "Central de controle", + "open-notification-history-enable-dnd": "Histรณrico de notificaรงรตes", + "open-settings": "Configuraรงรตes", + "open-tray-dropdown": "Bandeja do sistema", + "output-muted": "Alternar mudo da saรญda", + "power-profile": "{profile} perfil de energia", + "previous-media": "Faixa anterior", + "previous-month": "Mรชs anterior", + "refresh-devices": "Atualizar dispositivos", + "refresh-wallhaven": "Atualizar resultados do Wallhaven", + "refresh-wallpaper-list": "Atualizar lista de papรฉis de parede", + "remove-widget": "Remover widget", + "search-close": "Fechar pesquisa", + "session-menu": "Menu da Sessรฃo", + "show-all-devices": "Mostrar todos os dispositivos", + "switch-to-dark-mode": "Modo escuro", + "switch-to-light-mode": "Modo claro", + "unmute": "Desativar o mudo", + "up": "Diretรณrio superior", + "volume-at": "Volume de saรญda: {volume}%", + "wallpaper-selector": "Seletor de papel de parede" + }, + "wallpaper": { + "browse": { + "empty-directory": "Este diretรณrio estรก vazio.", + "go-root": "Ir para a raiz do papel de parede", + "go-up": "Ir para a pasta pai", + "go-up-hint": "Use o botรฃo voltar para navegar para cima." + }, + "configure-directory": "Configure seu diretรณrio de papรฉis de parede com imagens.", + "fill-modes": { + "crop": "Cortar (Preencher)", + "fit": "Ajustar (Conter)", + "repeat": "Repetir (Mosaico)", + "stretch": "Esticar" + }, + "no-match": "Nenhuma correspondรชncia encontrada.", + "no-wallpaper": "Nenhum papel de parede encontrado.", + "panel": { + "apikey-help": "ร‰ necessรกria uma chave de API para acessar conteรบdo NSFW.", + "apikey-label": "Chave de API", + "apikey-managed-by-env": "Gerenciado atravรฉs da variรกvel de ambiente NOCTALIA_WALLHAVEN_API_KEY.", + "apikey-placeholder": "Insira sua chave da API Wallhaven", + "appearance-dark-tab": "Escuro", + "appearance-light-tab": "Claro", + "categories-anime": "Anime", + "categories-label": "Categorias", + "categories-people": "Pessoas", + "color-extraction-disabled": "Usar extraรงรฃo de cores do papel de parede", + "color-extraction-enabled": "Usar esquemas de cores predefinidos", + "header-devices-apply-all-tooltip": "Escolher papel de parede por monitor", + "header-devices-per-monitor-tooltip": "Aplicar a todos os monitores", + "header-link-light-dark-tooltip": "Manter papel de parede ao mudar de tema", + "header-separate-light-dark-tooltip": "Trocar papel de parede automaticamente com o tema", + "header-sun-linked-tooltip": "Separar papรฉis de parede claros e escuros", + "header-sun-separate-tooltip": "Mesmo papel de parede para claro e escuro", + "order-asc": "Crescente", + "order-desc": "Decrescente", + "order-label": "Ordem", + "purity-label": "Filtro de conteรบdo", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "Sketchy", + "ratios-all-portrait": "Todos os retratos", + "ratios-all-wide": "Todas as paisagens", + "ratios-any": "Qualquer", + "ratios-label": "Proporรงรฃo da tela", + "resolution-atleast": "Pelo menos", + "resolution-exact": "Exato", + "resolution-label": "Resoluรงรฃo", + "resolution-mode-label": "Modo", + "solid-color-tooltip": "Fundo de cor sรณlida", + "sort-date-asc": "Ordenar por mais antigo primeiro", + "sort-date-desc": "Ordenar por mais recente primeiro", + "sort-name-asc": "Ordenar por nome (A-Z)", + "sort-name-desc": "Ordenar por nome (Z-A)", + "sort-random": "Ordenar aleatoriamente", + "sorting-date-added": "Data de adiรงรฃo", + "sorting-favorites": "Favoritos", + "sorting-label": "Ordenar por", + "sorting-relevance": "Relevรขncia", + "sorting-toplist": "Lista superior", + "sorting-views": "Visualizaรงรตes", + "source-label": "Fonte", + "source-wallhaven": "Wallhaven", + "title": "Seletor de papel de parede", + "wallhaven-settings-title": "Configuraรงรตes do Wallhaven" + }, + "transitions": { + "disc": "Disco", + "fade": "Esmaecer", + "honeycomb": "Favo de mel", + "pixelate": "Pixelizar", + "stripes": "Listras", + "wipe": "Limpar" + }, + "try-different-search": "Tente uma busca diferente.", + "wallhaven": { + "loading": "Carregando papรฉis de parede...", + "no-results": "Nenhum papel de parede encontrado. Tente uma busca diferente.", + "page": "{current} de {total}", + "page-prefix": "Pรกgina", + "page-suffix": "de {total}" + } + }, + "weather": { + "clear-sky": "Cรฉu limpo", + "drizzle": "Garoa", + "fog": "Nรฉvoa", + "mainly-clear": "Predominantemente limpo", + "overcast": "Nublado", + "partly-cloudy": "Parcialmente nublado", + "rain-showers": "Pancadas de chuva", + "snow": "Neve", + "thunderstorm": "Tempestade" + }, + "widgets": { + "color-picker": { + "palette-description": "Escolha entre uma vasta gama de cores predefinidas.", + "palette-label": "Paleta", + "palette-theme-colors": "Acesso rรกpido ร  paleta de cores do seu tema.", + "title": "Seletor de cores" + }, + "datetime-tokens": { + "ampm-lowercase": "am/pm em minรบsculas", + "ampm-uppercase": "AM/PM em maiรบsculas", + "common-12hour-time-minutes": "Horรกrio de 12 horas com minutos", + "common-24hour-time-minutes": "Horรกrio de 24 horas com minutos", + "common-24hour-time-seconds": "Horรกrio de 24 horas com segundos", + "common-european-date": "Formato de data europeu", + "common-iso-date": "Formato de data ISO", + "common-us-date": "Formato de data dos EUA", + "common-weekday-date": "Dia da semana com data", + "common-weekday-month-day": "Dia da semana, mรชs e dia", + "day-abbreviated": "Nome do dia abreviado", + "day-full": "Nome completo do dia", + "day-leading-zero": "Dia com zero ร  esquerda (01-31)", + "day-no-leading-zero": "Dia sem zero ร  esquerda (1-31)", + "hour-leading-zero": "Hora com zero ร  esquerda (00-23) โ€” formato de 24 horas", + "hour-no-leading-zero": "Hora sem zero ร  esquerda (0-23) โ€” formato de 24 horas", + "minute-leading-zero": "Minuto com zero ร  esquerda (00-59)", + "minute-no-leading-zero": "Minuto sem zero ร  esquerda (0-59)", + "month-abbreviated": "Nome do mรชs abreviado", + "month-full": "Nome completo do mรชs", + "month-number-leading-zero": "Mรชs como nรบmero com zero ร  esquerda (01-12)", + "month-number-no-zero": "Mรชs como nรบmero sem zero ร  esquerda (1-12)", + "second-leading-zero": "Segundo com zero ร  esquerda (00-59)", + "second-no-leading-zero": "Segundo sem zero ร  esquerda (0-59)", + "timezone-abbreviation": "Abreviaรงรฃo do fuso horรกrio", + "year-four-digit": "Ano com quatro dรญgitos", + "year-two-digit": "Ano com dois dรญgitos (00-99)" + }, + "file-picker": { + "item": "item", + "items": "itens", + "search-placeholder": "Pesquisar arquivos e pastas...", + "select-current": "Selecionar atual", + "select-file": "Selecionar arquivo", + "select-folder": "Selecionar pasta", + "selected": "Selecionado:", + "title": "Seletor de arquivos" + }, + "file-picker-title": "Selecione um arquivo", + "icon-picker": { + "title": "Seletor de รญcones" + }, + "tooltip-placeholder": "Espaรงo reservado" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "Identidade Anรดnima", + "ca-cert": "Certificado CA", + "eap-method": "Mรฉtodo EAP", + "password": "Senha do Usuรกrio", + "phase2-auth": "Autenticaรงรฃo de Fase 2", + "username": "Identidade / Nome de Usuรกrio" + }, + "panel": { + "action-required": "Aรงรฃo requerida", + "add-hidden-network": "Adicionar rede oculta", + "add-network": "Adicionar rede", + "available-interfaces": "Interfaces disponรญveis", + "available-networks": "Redes disponรญveis", + "disabled": "O Wi-Fi estรก desativado", + "disconnecting": "Desconectando...", + "dns": "DNS", + "enable-message": "Ative o Wi-Fi para ver as redes disponรญveis.", + "enter-password": "Digite a senha...", + "forget": "Esquecer", + "forget-network": "Esquecer esta rede?", + "forgetting": "Esquecendo...", + "hidden-network": "Rede oculta", + "interface": "Interface de rede", + "internet-connected": "Conectado ร  internet", + "internet-limited": "Sem internet", + "internet-status": "Status da internet", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "Redes conhecidas", + "link-speed": "Velocidade de ligaรงรฃo", + "network-name-ssid": "Nome da rede (SSID)", + "no-ethernet-devices": "Nenhum dispositivo Ethernet detectado", + "no-networks": "Nenhuma rede encontrada", + "saved": "Salva", + "scan-again": "Escanear novamente", + "searching": "Procurando redes prรณximas...", + "security-open": "Abrir", + "security-wep": "WEP", + "security-wpa": "WPA/WPA2/WPA3 Pessoal", + "security-wpa-ent": "WPA Enterprise", + "security-wpa2-ent": "WPA2 Enterprise", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 Enterprise", + "show-password": "Mostrar senha", + "title": "Wi-Fi" + }, + "signal": { + "excellent": "Excelente", + "fair": "Razoรกvel", + "good": "Bom", + "poor": "Ruim", + "weak": "Fraco" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/ru.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/ru.json new file mode 100644 index 0000000..2267606 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/ru.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "ะžั‡ะธัั‚ะธั‚ัŒ ะธัั‚ะพั€ะธัŽ", + "close-app": "ะ—ะฐะบั€ั‹ั‚ัŒ {app}", + "connect-vpn": "ะŸะพะดะบะปัŽั‡ะธั‚ัŒัั ะบ {name}", + "cycle-visualizer": "ะ’ะธะทัƒะฐะปะธะทะฐั‚ะพั€ ั†ะธะบะปะพะฒ", + "disable-bluetooth": "ะžั‚ะบะปัŽั‡ะธั‚ัŒ Bluetooth", + "disable-dnd": "ะžั‚ะบะปัŽั‡ะธั‚ัŒ ั€ะตะถะธะผ 'ะะต ะฑะตัะฟะพะบะพะธั‚ัŒ'", + "disable-wifi": "ะžั‚ะบะปัŽั‡ะธั‚ัŒ Wi-Fi", + "disconnect-vpn": "ะžั‚ะบะปัŽั‡ะธั‚ัŒ {name}", + "dock-settings": "ะะฐัั‚ั€ะพะนะบะธ ะดะพะบะฐ", + "enable-bluetooth": "ะ’ะบะปัŽั‡ะธั‚ัŒ Bluetooth", + "enable-dnd": "ะะต ะฑะตัะฟะพะบะพะธั‚ัŒ", + "enable-wifi": "ะ’ะบะปัŽั‡ะธั‚ัŒ Wi-Fi", + "launcher-settings": "ะะฐัั‚ั€ะพะนะบะธ ะปะฐัƒะฝั‡ะตั€ะฐ", + "lower-to-bottom": "ะžะฟัƒัั‚ะธั‚ัŒ ะดะพ ะดะฝะฐ", + "open-calendar": "ะžั‚ะบั€ั‹ั‚ัŒ ะบะฐะปะตะฝะดะฐั€ัŒ", + "open-display-settings": "ะะฐัั‚ั€ะพะนะบะธ ัะบั€ะฐะฝะฐ", + "open-launcher": "ะžั‚ะบั€ั‹ั‚ัŒ ะปะฐัƒะฝั‡ะตั€", + "open-settings": "ะžั‚ะบั€ั‹ั‚ัŒ ะฝะฐัั‚ั€ะพะนะบะธ", + "raise-to-top": "ะŸะพะดะฝัั‚ัŒ ะฝะฐะฒะตั€ั…", + "random-wallpaper": "ะกะปัƒั‡ะฐะนะฝั‹ะต ะพะฑะพะธ", + "run-custom-command": "ะ’ั‹ะฟะพะปะฝะธั‚ัŒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบัƒัŽ ะบะพะผะฐะฝะดัƒ", + "show-active-only": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ั‚ะพะปัŒะบะพ ะฐะบั‚ะธะฒะฝั‹ะต", + "show-all": "ะŸะพะบะฐะทะฐั‚ัŒ ะฒัะต", + "toggle-mute": "ะ’ะบะปัŽั‡ะธั‚ัŒ/ะฒั‹ะบะปัŽั‡ะธั‚ัŒ ะทะฒัƒะบ", + "widget-settings": "ะะฐัั‚ั€ะพะนะบะธ ะฒะธะดะถะตั‚ะฐ" + }, + "authentication": { + "error": "ะžัˆะธะฑะบะฐ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ", + "failed": "ะกะฑะพะน ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ" + }, + "bar": { + "active-window": { + "colorize-icons-description": "ะŸั€ะธะผะตะฝะธั‚ัŒ ั†ะฒะตั‚ะฐ ั‚ะตะผั‹ ะบ ะธะบะพะฝะบะต ะฐะบั‚ะธะฒะฝะพะณะพ ะพะบะฝะฐ.", + "hide-mode-description": "ะฃะฟั€ะฐะฒะปัะตั‚ ะฟะพะฒะตะดะตะฝะธะตะผ ะฒะธะดะถะตั‚ะฐ, ะบะพะณะดะฐ ะฝะธ ะพะดะฝะพ ะพะบะฝะพ ะฝะต ะฐะบั‚ะธะฒะฝะพ.", + "scrolling-mode-description": "ะฃะฟั€ะฐะฒะปะตะฝะธะต ะฟั€ะพะบั€ัƒั‚ะบะพะน ั‚ะตะบัั‚ะฐ ะดะปั ะดะปะธะฝะฝั‹ั… ะทะฐะณะพะปะพะฒะบะพะฒ ะพะบะพะฝ.", + "show-app-icon-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะธะบะพะฝะบัƒ ะฟั€ะธะปะพะถะตะฝะธั ั€ัะดะพะผ ั ะทะฐะณะพะปะพะฒะบะพะผ ะพะบะฝะฐ.", + "show-app-icon-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะธะบะพะฝะบัƒ ะฟั€ะธะปะพะถะตะฝะธั", + "show-app-text-description": "ะžั‚ะพะฑั€ะฐะทะธั‚ัŒ ะทะฐะณะพะปะพะฒะพะบ ะฐะบั‚ะธะฒะฝะพะณะพ ะพะบะฝะฐ.", + "show-app-text-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะทะฐะณะพะปะพะฒะพะบ ะพะบะฝะฐ" + }, + "audio-visualizer": { + "color-name-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ั†ะฒะตั‚ ะดะปั ะฒะธะทัƒะฐะปะธะทะฐั‚ะพั€ะฐ.", + "color-name-label": "ะฆะฒะตั‚ ะทะฐะปะธะฒะบะธ", + "height-description": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะฐั ะฒั‹ัะพั‚ะฐ ะบะพะผะฟะพะฝะตะฝั‚ะฐ.", + "hide-when-idle-description": "ะ•ัะปะธ ะฒะบะปัŽั‡ะตะฝะพ, ะฒะธะทัƒะฐะปะธะทะฐั‚ะพั€ ัะบั€ั‹ั‚, ะฟะพะบะฐ ะฟะปะตะตั€ ะฝะต ะฝะฐั‡ะฝั‘ั‚ ะฒะพัะฟั€ะพะธะทะฒะตะดะตะฝะธะต.", + "hide-when-idle-label": "ะกะบั€ั‹ะฒะฐั‚ัŒ, ะบะพะณะดะฐ ะผะตะดะธะฐ ะฝะต ะฒะพัะฟั€ะพะธะทะฒะพะดะธั‚ัั", + "width-description": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะฐั ัˆะธั€ะธะฝะฐ ะบะพะผะฟะพะฝะตะฝั‚ะฐ." + }, + "battery": { + "device-default": "ะฃัั‚ั€ะพะนัั‚ะฒะพ ะพั‚ะพะฑั€ะฐะถะตะฝะธั ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ", + "device-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ัƒัั‚ั€ะพะนัั‚ะฒะพ ะดะปั ะพั‚ะพะฑั€ะฐะถะตะฝะธั ัะพัั‚ะพัะฝะธั ะฑะฐั‚ะฐั€ะตะธ.", + "device-label": "ะฃัั‚ั€ะพะนัั‚ะฒะพ ั ะฑะฐั‚ะฐั€ะตะตะน", + "display-mode-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะบะฐะบ ะพั‚ะพะฑั€ะฐะถะฐะตั‚ัั ะฑะฐั‚ะฐั€ะตั ะฝะฐ ะฟะฐะฝะตะปะธ.", + "display-mode-graphic": "ะ“ั€ะฐั„ะธั‡ะตัะบะฐั ะฑะฐั‚ะฐั€ะตั", + "display-mode-graphic-clean": "ะ“ั€ะฐั„ะธั‡ะตัะบะฐั ะฑะฐั‚ะฐั€ะตั (ะฑะตะท %)", + "display-mode-icon-always": "ะ—ะฝะฐั‡ะพะบ - ะ’ัะตะณะดะฐ ะฟะพะบะฐะทั‹ะฒะฐั‚ัŒ %", + "display-mode-icon-hover": "ะ˜ะบะพะฝะบะฐ - ะŸะพะบะฐะทะฐั‚ัŒ ะฟั€ะธ ะฝะฐะฒะตะดะตะฝะธะธ", + "display-mode-icon-only": "ะขะพะปัŒะบะพ ะทะฝะฐั‡ะพะบ", + "hide-if-idle-description": "ะกะบั€ั‹ั‚ัŒ ะฒะธะดะถะตั‚, ะบะพะณะดะฐ ะฑะฐั‚ะฐั€ะตั ะฝะต ะทะฐั€ัะถะฐะตั‚ัั ะธ ะฝะต ั€ะฐะทั€ัะถะฐะตั‚ัั.", + "hide-if-idle-label": "ะกะบั€ั‹ะฒะฐั‚ัŒ ะฒ ั€ะตะถะธะผะต ะฟั€ะพัั‚ะพั", + "hide-if-not-detected-description": "ะกะบั€ั‹ั‚ัŒ ะฒะธะดะถะตั‚, ะตัะปะธ ะฒ ัะธัั‚ะตะผะต ะฝะต ะพะฑะฝะฐั€ัƒะถะตะฝะฐ ะฑะฐั‚ะฐั€ะตั.", + "hide-if-not-detected-label": "ะกะบั€ั‹ะฒะฐั‚ัŒ ะฟั€ะธ ะพั‚ััƒั‚ัั‚ะฒะธะธ ะฑะฐั‚ะฐั€ะตะธ", + "low-battery-threshold-label": "ะŸะพั€ะพะณ ะฟั€ะตะดัƒะฟั€ะตะถะดะตะฝะธั ะพ ะฝะธะทะบะพะผ ะทะฐั€ัะดะต ะฑะฐั‚ะฐั€ะตะธ", + "show-noctalia-performance-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะฟะตั€ะตะบะปัŽั‡ะฐั‚ะตะปัŒ ั€ะตะถะธะผะฐ ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ะธ Noctalia ะฒ ะฟะฐะฝะตะปะธ ะฑะฐั‚ะฐั€ะตะธ.
ะžั‚ะบะปัŽั‡ะฐะตั‚ ั‚ะตะฝะธ ะธ ะฐะฝะธะผะฐั†ะธะธ ะฒ Noctalia ะดะปั ัะฝะธะถะตะฝะธั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั ั€ะตััƒั€ัะพะฒ.", + "show-noctalia-performance-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฟะตั€ะตะบะปัŽั‡ะฐั‚ะตะปัŒ ะŸั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ัŒ Noctalia", + "show-power-profile-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะฒั‹ะฑะพั€ ะฟั€ะพั„ะธะปั ะฟะธั‚ะฐะฝะธั ะฒ ะฟะฐะฝะตะปะธ ะฑะฐั‚ะฐั€ะตะธ.", + "show-power-profile-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ัะปะตะผะตะฝั‚ั‹ ัƒะฟั€ะฐะฒะปะตะฝะธั ะฟั€ะพั„ะธะปะตะผ ะฟะธั‚ะฐะฝะธั" + }, + "brightness": { + "apply-all-description": "ะ˜ะทะผะตะฝัั‚ัŒ ัั€ะบะพัั‚ัŒ ะดะปั ะฒัะตั… ะผะพะฝะธั‚ะพั€ะพะฒ ะฟั€ะธ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะธ ะบะพะปะตัะฐ ะฟั€ะพะบั€ัƒั‚ะบะธ.", + "apply-all-label": "ะŸั€ะธะผะตะฝะธั‚ัŒ ะธะทะผะตะฝะตะฝะธั ะฟั€ะพะบั€ัƒั‚ะบะธ ะบะพ ะฒัะตะผ ะผะพะฝะธั‚ะพั€ะฐะผ" + }, + "clock": { + "clock-display-description": "ะะฐัั‚ั€ะพะนั‚ะต ะพั‚ะพะฑั€ะฐะถะตะฝะธะต ั‡ะฐัะพะฒ, ะดะพะฑะฐะฒะธะฒ ั‚ะพะบะตะฝั‹ ะธะท ัะฟะธัะบะฐ ะฝะธะถะต. ะงั‚ะพะฑั‹ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ 12-ั‡ะฐัะพะฒะพะน ั„ะพั€ะผะฐั‚, ะฝะตะพะฑั…ะพะดะธะผะพ ะฒะบะปัŽั‡ะธั‚ัŒ ั‚ะพะบะตะฝ 'AP'.", + "clock-display-label": "ะžั‚ะพะฑั€ะฐะถะตะฝะธะต ั‡ะฐัะพะฒ", + "custom-font-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ัˆั€ะธั„ั‚ ะดะปั ะพั‚ะพะฑั€ะฐะถะตะฝะธั ั‡ะฐัะพะฒ.", + "custom-font-label": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ัˆั€ะธั„ั‚", + "custom-font-placeholder": "ะ’ั‹ะฑั€ะฐั‚ัŒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ัˆั€ะธั„ั‚...", + "custom-font-search-placeholder": "ะŸะพะธัะบ ัˆั€ะธั„ั‚ะพะฒ...", + "horizontal-bar-description": "ะกะพะฒะตั‚: ะธัะฟะพะปัŒะทัƒะนั‚ะต \\n ะดะปั ัะพะทะดะฐะฝะธั ั€ะฐะทั€ั‹ะฒะฐ ัั‚ั€ะพะบะธ.", + "horizontal-bar-label": "ะ“ะพั€ะธะทะพะฝั‚ะฐะปัŒะฝะฐั ะฟะฐะฝะตะปัŒ", + "preview": "ะŸั€ะตะดะฒะฐั€ะธั‚ะตะปัŒะฝั‹ะน ะฟั€ะพัะผะพั‚ั€", + "tooltip-format-description": "ะกั‚ั€ะพะบะฐ ั„ะพั€ะผะฐั‚ะฐ ะดะปั ะฟะพะดัะบะฐะทะบะธ, ะพั‚ะพะฑั€ะฐะถะฐะตะผะพะน ะฟั€ะธ ะฝะฐะฒะตะดะตะฝะธะธ ะฝะฐ ั‡ะฐัั‹. ะžัั‚ะฐะฒัŒั‚ะต ะฟัƒัั‚ั‹ะผ, ั‡ั‚ะพะฑั‹ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฟะพะดัะบะฐะทะบัƒ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ.", + "tooltip-format-label": "ะคะพั€ะผะฐั‚ ะฟะพะดัะบะฐะทะบะธ", + "use-custom-font-description": "ะŸะตั€ะตะพะฟั€ะตะดะตะปะธั‚ัŒ ะฒั‹ะฑะพั€ ัˆั€ะธั„ั‚ะฐ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะผ ัˆั€ะธั„ั‚ะพะผ ะดะปั ั‡ะฐัะพะฒ.", + "use-custom-font-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ัˆั€ะธั„ั‚", + "use-monospaced-font-description": "ะ•ัะปะธ ะฒะบะปัŽั‡ะตะฝะพ, ั‡ะฐัั‹ ะฑัƒะดัƒั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะผะพะฝะพัˆะธั€ะธะฝะฝั‹ะน ัˆั€ะธั„ั‚.", + "use-monospaced-font-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะผะพะฝะพัˆะธั€ะธะฝะฝั‹ะน ัˆั€ะธั„ั‚", + "vertical-bar-description": "ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต ะฟั€ะพะฑะตะป, ั‡ั‚ะพะฑั‹ ั€ะฐะทะดะตะปะธั‚ัŒ ะบะฐะถะดัƒัŽ ั‡ะฐัั‚ัŒ ะฝะฐ ะฝะพะฒัƒัŽ ัั‚ั€ะพะบัƒ.", + "vertical-bar-label": "ะ’ะตั€ั‚ะธะบะฐะปัŒะฝะฐั ะฟะฐะฝะตะปัŒ" + }, + "control-center": { + "browse-file": "ะ’ั‹ะฑะพั€ ั„ะฐะนะปะฐ", + "browse-library": "ะžะฑะทะพั€ ะฑะธะฑะปะธะพั‚ะตะบะธ", + "color-selection-description": "ะŸั€ะธะผะตะฝัะตั‚ ั†ะฒะตั‚ะฐ ั‚ะตะผั‹ ะบ ะทะฝะฐั‡ะบะฐะผ.", + "enable-colorization-description": "ะ’ะบะปัŽั‡ะธั‚ัŒ ั€ะฐัะบั€ะฐัˆะธะฒะฐะฝะธะต ะทะฝะฐั‡ะบะฐ ั ะฟั€ะธะผะตะฝะตะฝะธะตะผ ั†ะฒะตั‚ะพะฒ ั‚ะตะผั‹.", + "icon-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ะธะบะพะฝะบัƒ ะธะท ะฑะธะฑะปะธะพั‚ะตะบะธ ะธะปะธ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ั„ะฐะนะป.", + "select-custom-icon": "ะ’ั‹ะฑั€ะฐั‚ัŒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบัƒัŽ ะธะบะพะฝะบัƒ", + "use-distro-logo-description": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะปะพะณะพั‚ะธะฟ ะฒะฐัˆะตะณะพ ะดะธัั‚ั€ะธะฑัƒั‚ะธะฒะฐ ะฒะผะตัั‚ะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะพะน ะธะบะพะฝะบะธ.", + "use-distro-logo-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะปะพะณะพั‚ะธะฟ ะดะธัั‚ั€ะธะฑัƒั‚ะธะฒะฐ ะฒะผะตัั‚ะพ ะธะบะพะฝะบะธ" + }, + "custom-button": { + "collapse-condition-description": "ะ•ัะปะธ ะฒั‹ั…ะพะดะฝะพะน ั‚ะตะบัั‚ ัะพะพั‚ะฒะตั‚ัั‚ะฒัƒะตั‚ ัั‚ะพะผัƒ ะทะฝะฐั‡ะตะฝะธัŽ, ะบะฝะพะฟะบะฐ ะฑัƒะดะตั‚ ัะบั€ั‹ั‚ะฐ.", + "collapse-condition-label": "ะฃัะปะพะฒะธะต ัะบั€ั‹ั‚ะธั", + "color-selection-description": "ะŸั€ะธะผะตะฝะธั‚ัŒ ั†ะฒะตั‚ะฐ ั‚ะตะผั‹ ะบ ะทะฝะฐั‡ะบัƒ ะธ ั‚ะตะบัั‚ัƒ.", + "default-tooltip": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะฐั ะบะฝะพะฟะบะฐ, ะฝะฐัั‚ั€ะพะธั‚ัŒ ะฒ ะฝะฐัั‚ั€ะพะนะบะฐั…", + "display-command-output-description": "ะ’ะฒะตะดะธั‚ะต ะบะพะผะฐะฝะดัƒ ะดะปั ั€ะตะณัƒะปัั€ะฝะพะณะพ ะฒั‹ะฟะพะปะฝะตะฝะธั. ะŸะตั€ะฒะฐั ัั‚ั€ะพะบะฐ ะตั‘ ะฒั‹ะฒะพะดะฐ ะฑัƒะดะตั‚ ะพั‚ะพะฑั€ะฐะถะฐั‚ัŒัั ะบะฐะบ ั‚ะตะบัั‚.", + "display-command-output-label": "ะ’ั‹ะฒะพะด ะบะพะผะฐะฝะดั‹", + "display-command-output-stream-description": "ะ’ะฒะตะดะธั‚ะต ะบะพะผะฐะฝะดัƒ ะดะปั ะฝะตะฟั€ะตั€ั‹ะฒะฝะพะณะพ ะฒั‹ะฟะพะปะฝะตะฝะธั.", + "dynamic-text": "ะ”ะธะฝะฐะผะธั‡ะตัะบะธะน ั‚ะตะบัั‚", + "enable-colorization-description": "ะ’ะบะปัŽั‡ะธั‚ัŒ ั€ะฐัะบั€ะฐัะบัƒ ะทะฝะฐั‡ะบะฐ ะธ ั‚ะตะบัั‚ะฐ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะพะน ะบะฝะพะฟะบะธ, ะฟั€ะธะผะตะฝัั ั†ะฒะตั‚ะฐ ั‚ะตะผั‹.", + "enable-colorization-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ั€ะฐัะบั€ะฐัะบัƒ", + "general-tooltip-text-description": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ั‚ะตะบัั‚ ะดะปั ะพั‚ะพะฑั€ะฐะถะตะฝะธั ะฒะพ ะฒัะฟะปั‹ะฒะฐัŽั‰ะตะน ะฟะพะดัะบะฐะทะบะต ะบะฝะพะฟะบะธ.", + "general-tooltip-text-label": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ั‚ะตะบัั‚ ะฒัะฟะปั‹ะฒะฐัŽั‰ะตะน ะฟะพะดัะบะฐะทะบะธ", + "hide-mode-always-expanded": "ะ’ัะตะณะดะฐ ั€ะฐัะบั€ั‹ั‚", + "hide-mode-description": "ะฃะฟั€ะฐะฒะปัะตั‚ ะฒะธะดะธะผะพัั‚ัŒัŽ ะฒะธะดะถะตั‚ะฐ, ะบะพะณะดะฐ ะบะพะผะฐะฝะดะฐ ะฝะต ะธะผะตะตั‚ ะฒั‹ะฒะพะดะฐ.", + "hide-mode-expand-with-output": "ะ ะฐัะบั€ั‹ะฒะฐั‚ัŒ ะฟั€ะธ ะฝะฐะปะธั‡ะธะธ ะฒั‹ะฒะพะดะฐ", + "hide-mode-label": "ะ ะตะถะธะผ ัะบั€ั‹ั‚ะธั", + "hide-mode-max-transparent": "ะœะฐะบัะธะผะฐะปัŒะฝะพ ั€ะฐัะบั€ั‹ั‚, ะฝะพ ะฟั€ะพะทั€ะฐั‡ะฝั‹ะน", + "icon-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ะธะบะพะฝะบัƒ ะธะท ะฑะธะฑะปะธะพั‚ะตะบะธ.", + "ipc-identifier-description": "ะฃะฝะธะบะฐะปัŒะฝั‹ะน ะธะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ ะดะปั ะบะพะผะฐะฝะด IPC. ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต ัั‚ะพั‚ ะธะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ ั 'qs -c noctalia-shell ipc call cb [ะดะตะนัั‚ะฒะธะต] [ะธะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€]', ั‡ั‚ะพะฑั‹ ัƒะฟั€ะฐะฒะปัั‚ัŒ ัั‚ะพะน ะบะฝะพะฟะบะพะน ั‡ะตั€ะตะท IPC.", + "ipc-identifier-label": "ะ˜ะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ IPC", + "left-click-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟั€ะธ ะฝะฐะถะฐั‚ะธะธ ะปะตะฒะพะน ะบะฝะพะฟะบะพะน ะผั‹ัˆะธ.", + "left-click-label": "ะšะปะธะบ ะปะตะฒะพะน ะบะฝะพะฟะบะพะน", + "left-click-update-text": "ะžะฑะฝะพะฒะธั‚ัŒ ะพั‚ะพะฑั€ะฐะถะฐะตะผั‹ะน ั‚ะตะบัั‚ ะฟะพ ะปะตะฒะพะผัƒ ะบะปะธะบัƒ", + "max-text-length-horizontal-description": "ะœะฐะบัะธะผะฐะปัŒะฝะพะต ะบะพะปะธั‡ะตัั‚ะฒะพ ัะธะผะฒะพะปะพะฒ ะดะปั ะพั‚ะพะฑั€ะฐะถะตะฝะธั ะฒ ะณะพั€ะธะทะพะฝั‚ะฐะปัŒะฝะพะน ะฟะฐะฝะตะปะธ (0 ะดะปั ัะบั€ั‹ั‚ะธั ั‚ะตะบัั‚ะฐ).", + "max-text-length-horizontal-label": "ะœะฐะบั. ะดะปะธะฝะฐ ั‚ะตะบัั‚ะฐ (ะณะพั€ะธะทะพะฝั‚ะฐะปัŒะฝะพ)", + "max-text-length-vertical-description": "ะœะฐะบัะธะผะฐะปัŒะฝะพะต ะบะพะปะธั‡ะตัั‚ะฒะพ ัะธะผะฒะพะปะพะฒ ะดะปั ะพั‚ะพะฑั€ะฐะถะตะฝะธั ะฒ ะฒะตั€ั‚ะธะบะฐะปัŒะฝะพะน ะฟะฐะฝะตะปะธ (0 ะดะปั ัะบั€ั‹ั‚ะธั ั‚ะตะบัั‚ะฐ).", + "max-text-length-vertical-label": "ะœะฐะบั. ะดะปะธะฝะฐ ั‚ะตะบัั‚ะฐ (ะฒะตั€ั‚ะธะบะฐะปัŒะฝะพ)", + "middle-click-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟั€ะธ ะฝะฐะถะฐั‚ะธะธ ัั€ะตะดะฝะตะน ะบะฝะพะฟะบะพะน ะผั‹ัˆะธ.", + "middle-click-label": "ะšะปะธะบ ัั€ะตะดะฝะตะน ะบะฝะพะฟะบะพะน", + "middle-click-update-text": "ะžะฑะฝะพะฒะธั‚ัŒ ะพั‚ะพะฑั€ะฐะถะฐะตะผั‹ะน ั‚ะตะบัั‚ ะฟะพ ัั€ะตะดะฝะตะผัƒ ะบะปะธะบัƒ", + "parse-json-description": "ะ ะฐะทะพะฑั€ะฐั‚ัŒ ะฒั‹ะฒะพะด ะบะพะผะฐะฝะดั‹ ะบะฐะบ ะพะฑัŠะตะบั‚ JSON ะดะปั ะดะธะฝะฐะผะธั‡ะตัะบะพะน ัƒัั‚ะฐะฝะพะฒะบะธ ั‚ะตะบัั‚ะฐ ะธ ะธะบะพะฝะบะธ.", + "parse-json-label": "ะ ะฐะทะพะฑั€ะฐั‚ัŒ ะฒั‹ะฒะพะด ะบะฐะบ JSON", + "refresh-interval-description": "ะ˜ะฝั‚ะตั€ะฒะฐะป ะฒ ะผะธะปะปะธัะตะบัƒะฝะดะฐั….", + "refresh-interval-label": "ะ˜ะฝั‚ะตั€ะฒะฐะป ะพะฑะฝะพะฒะปะตะฝะธั", + "right-click-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟั€ะธ ะฝะฐะถะฐั‚ะธะธ ะฟั€ะฐะฒะพะน ะบะฝะพะฟะบะพะน ะผั‹ัˆะธ.", + "right-click-label": "ะšะปะธะบ ะฟั€ะฐะฒะพะน ะบะฝะพะฟะบะพะน", + "right-click-update-text": "ะžะฑะฝะพะฒะธั‚ัŒ ะพั‚ะพะฑั€ะฐะถะฐะตะผั‹ะน ั‚ะตะบัั‚ ะฟะพ ะฟั€ะฐะฒะพะผัƒ ะบะปะธะบัƒ", + "show-exec-tooltip-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฒัะฟะปั‹ะฒะฐัŽั‰ะธะต ะฟะพะดัะบะฐะทะบะธ ั ะดะตั‚ะฐะปัะผะธ ะบะพะผะฐะฝะด (ะปะตะฒั‹ะน/ะฟั€ะฐะฒั‹ะน/ัั€ะตะดะฝะธะน ะบะปะธะบ, ะบะพะปะตัะพ).", + "show-exec-tooltip-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฒัะฟะปั‹ะฒะฐัŽั‰ะธะต ะฟะพะดัะบะฐะทะบะธ ะบะพะผะฐะฝะด", + "show-icon-description": "ะŸะตั€ะตะบะปัŽั‡ะฐะตั‚ ะฒะธะดะธะผะพัั‚ัŒ ะธะบะพะฝะบะธ ะฒะธะดะถะตั‚ะฐ.", + "show-icon-label": "ะŸะพะบะฐะทะฐั‚ัŒ ะธะบะพะฝะบัƒ", + "show-text-tooltip-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฒัะฟะปั‹ะฒะฐัŽั‰ะธะต ะฟะพะดัะบะฐะทะบะธ ั ะฒั‹ะฒะพะดะพะผ ั‚ะตะบัั‚ะพะฒะพะน ะบะพะผะฐะฝะดั‹.", + "show-text-tooltip-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะดะธะฝะฐะผะธั‡ะตัะบะธะต ั‚ะตะบัั‚ะพะฒั‹ะต ะฟะพะดัะบะฐะทะบะธ", + "text-stream-description": "ะŸะพั‚ะพะบะพะฒั‹ะต ัั‚ั€ะพะบะธ ะธะท ะบะพะผะฐะฝะดั‹ ะฑัƒะดัƒั‚ ะพั‚ะพะฑั€ะฐะถะฐั‚ัŒัั ะบะฐะบ ั‚ะตะบัั‚ ะฝะฐ ะบะฝะพะฟะบะต.", + "text-stream-label": "ะŸะพั‚ะพะบ", + "wheel-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟั€ะธ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะธ ะบะพะปะตัะฐ ะฟั€ะพะบั€ัƒั‚ะบะธ.
ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต $delta ะดะปั ะดะตะปัŒั‚ั‹ ะบะพะปะตัะฐ ะฟั€ะพะบั€ัƒั‚ะบะธ ะฒ ะบะพะผะฐะฝะดะต.", + "wheel-down": "ะšะพะปะตัะพ ะฒะฝะธะท", + "wheel-down-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟั€ะธ ะฟั€ะพะบั€ัƒั‚ะบะต ะบะพะปะตัะฐ ะฒะฝะธะท.", + "wheel-down-label": "ะšะพะผะฐะฝะดะฐ ะฟั€ะพะบั€ัƒั‚ะบะธ ะบะพะปะตัะฐ ะฒะฝะธะท", + "wheel-label": "ะšะพะปะตัะพ ะผั‹ัˆะธ", + "wheel-mode-separate-description": "ะ’ะบะปัŽั‡ะธั‚ัŒ ั€ะฐะทะดะตะปัŒะฝั‹ะต ะบะพะผะฐะฝะดั‹ ะดะปั ะบะพะปะตัะฐ ะฟั€ะพะบั€ัƒั‚ะบะธ ะฒะฒะตั€ั… ะธ ะฒะฝะธะท.", + "wheel-mode-separate-label": "ะ ะฐะทะดะตะปัŒะฝั‹ะต ะบะพะผะฐะฝะดั‹ ะบะพะปะตัะฐ ะฟั€ะพะบั€ัƒั‚ะบะธ", + "wheel-up": "ะšะพะปะตัะพ ะฒะฒะตั€ั…", + "wheel-up-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟั€ะธ ะฟั€ะพะบั€ัƒั‚ะบะต ะบะพะปะตัะฐ ะฒะฒะตั€ั….", + "wheel-up-label": "ะšะพะผะฐะฝะดะฐ ะฟั€ะพะบั€ัƒั‚ะบะธ ะบะพะปะตัะฐ ะฒะฒะตั€ั…", + "wheel-update-text": "ะžะฑะฝะพะฒะธั‚ัŒ ะพั‚ะพะฑั€ะฐะถะฐะตะผั‹ะน ั‚ะตะบัั‚ ะฟั€ะธ ะฟั€ะพะบั€ัƒั‚ะบะต" + }, + "keyboard-layout": { + "show-icon-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะทะฝะฐั‡ะพะบ ั€ะฐัะบะปะฐะดะบะธ ะบะปะฐะฒะธะฐั‚ัƒั€ั‹." + }, + "lock-keys": { + "hide-when-off-description": "ะกะบั€ั‹ั‚ัŒ ะธะฝะดะธะบะฐั‚ะพั€, ะบะพะณะดะฐ ะบะปะฐะฒะธัˆะฐ ะฝะตะฐะบั‚ะธะฒะฝะฐ.", + "hide-when-off-label": "ะกะบั€ั‹ะฒะฐั‚ัŒ, ะบะพะณะดะฐ ะพั‚ะบะปัŽั‡ะตะฝะพ", + "show-caps-lock-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ัั‚ะฐั‚ัƒั Caps Lock.", + "show-caps-lock-label": "Caps Lock", + "show-num-lock-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ัั‚ะฐั‚ัƒั Num Lock.", + "show-num-lock-label": "Num Lock", + "show-scroll-lock-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ัั‚ะฐั‚ัƒั Scroll Lock.", + "show-scroll-lock-label": "Scroll Lock" + }, + "media-mini": { + "compact-mode-description": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะบะพะผะฟะฐะบั‚ะฝั‹ะน ั€ะตะถะธะผ ะดะปั ะฟะฐะฝะตะปะธ ะผะตะดะธะฐะฟะปะตะตั€ะฐ.", + "compact-mode-label": "ะšะพะผะฟะฐะบั‚ะฝั‹ะน ั€ะตะถะธะผ", + "hide-mode-description": "ะฃะฟั€ะฐะฒะปัะตั‚ ะฟะพะฒะตะดะตะฝะธะตะผ ะฒะธะดะถะตั‚ะฐ, ะบะพะณะดะฐ ะผะตะดะธะฐ ะฝะต ะฒะพัะฟั€ะพะธะทะฒะพะดะธั‚ัั.", + "max-width-description": "ะฃัั‚ะฐะฝะฐะฒะปะธะฒะฐะตั‚ ะผะฐะบัะธะผะฐะปัŒะฝั‹ะน ะณะพั€ะธะทะพะฝั‚ะฐะปัŒะฝั‹ะน ั€ะฐะทะผะตั€ ะฒะธะดะถะตั‚ะฐ. ะ’ะธะดะถะตั‚ ะฑัƒะดะตั‚ ัะถะธะผะฐั‚ัŒัั, ั‡ั‚ะพะฑั‹ ัะพะพั‚ะฒะตั‚ัั‚ะฒะพะฒะฐั‚ัŒ ะฑะพะปะตะต ะบะพั€ะพั‚ะบะพะผัƒ ัะพะดะตั€ะถะธะผะพะผัƒ.", + "no-active-player": "ะะตั‚ ะฐะบั‚ะธะฒะฝะพะณะพ ะฟะปะตะตั€ะฐ", + "panel-section-description": "ะะฐัั‚ั€ะพะธั‚ัŒ ะฒะฝะตัˆะฝะธะน ะฒะธะด ะธ ะฟะพะฒะตะดะตะฝะธะต ะฟะฐะฝะตะปะธ ะผะตะดะธะฐะฟะปะตะตั€ะฐ.", + "panel-section-label": "ะŸะฐะฝะตะปัŒ ะผะตะดะธะฐะฟะปะตะตั€ะฐ", + "scrolling-mode-description": "ะฃะฟั€ะฐะฒะปะตะฝะธะต ะฒะบะปัŽั‡ะตะฝะธะตะผ ะฟั€ะพะบั€ัƒั‚ะบะธ ั‚ะตะบัั‚ะฐ ะดะปั ะดะปะธะฝะฝั‹ั… ะฝะฐะทะฒะฐะฝะธะน ั‚ั€ะตะบะพะฒ.", + "scrolling-mode-label": "ะ ะตะถะธะผ ะฟั€ะพะบั€ัƒั‚ะบะธ", + "show-album-art-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะพะฑะปะพะถะบัƒ ะฐะปัŒะฑะพะผะฐ ะดะปั ั‚ะตะบัƒั‰ะตะณะพ ะฒะพัะฟั€ะพะธะทะฒะพะดะธะผะพะณะพ ั‚ั€ะตะบะฐ.", + "show-album-art-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะพะฑะปะพะถะบัƒ ะฐะปัŒะฑะพะผะฐ", + "show-artist-first-description": "ะ˜ัะฟะพะปะฝะธั‚ะตะปัŒ - ะฝะฐะทะฒะฐะฝะธะต ะฒะผะตัั‚ะพ ะฝะฐะทะฒะฐะฝะธะต - ะธัะฟะพะปะฝะธั‚ะตะปัŒ.", + "show-artist-first-label": "ะกะฝะฐั‡ะฐะปะฐ ะธัะฟะพะปะฝะธั‚ะตะปัŒ", + "show-progress-ring-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะบั€ัƒะณะพะฒะพะน ะธะฝะดะธะบะฐั‚ะพั€ ะฟั€ะพะณั€ะตััะฐ ะฒะพัะฟั€ะพะธะทะฒะตะดะตะฝะธั ั‚ั€ะตะบะฐ.", + "show-progress-ring-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะบะพะปัŒั†ะพ ะฟั€ะพะณั€ะตััะฐ", + "show-visualizer-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะฐัƒะดะธะพะฒะธะทัƒะฐะปะธะทะฐั‚ะพั€ ะฟั€ะธ ะฒะพัะฟั€ะพะธะทะฒะตะดะตะฝะธะธ ะผัƒะทั‹ะบะธ.", + "show-visualizer-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฒะธะทัƒะฐะปะธะทะฐั‚ะพั€", + "use-fixed-width-description": "ะ•ัะปะธ ะฒะบะปัŽั‡ะตะฝะพ, ะฒะธะดะถะตั‚ ะฒัะตะณะดะฐ ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะผะฐะบัะธะผะฐะปัŒะฝัƒัŽ ัˆะธั€ะธะฝัƒ ะฒะผะตัั‚ะพ ะดะธะฝะฐะผะธั‡ะตัะบะพะน ะฟะพะดัั‚ั€ะพะนะบะธ ะฟะพะด ัะพะดะตั€ะถะธะผะพะต.", + "use-fixed-width-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ั„ะธะบัะธั€ะพะฒะฐะฝะฝัƒัŽ ัˆะธั€ะธะฝัƒ", + "visualizer-type-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ัั‚ะธะปัŒ ะฐัƒะดะธะพะฒะธะทัƒะฐะปะธะทะฐั‚ะพั€ะฐ ะดะปั ะพั‚ะพะฑั€ะฐะถะตะฝะธั.", + "visualizer-type-label": "ะขะธะฟ ะฒะธะทัƒะฐะปะธะทะฐั‚ะพั€ะฐ" + }, + "notification-history": { + "hide-widget-when-zero-description": "ะกะบั€ั‹ะฒะฐั‚ัŒ ะทะฝะฐั‡ะพะบ ัƒะฒะตะดะพะผะปะตะฝะธะน, ะตัะปะธ ัƒะฒะตะดะพะผะปะตะฝะธะน ะฝะตั‚.", + "hide-widget-when-zero-label": "ะกะบั€ั‹ะฒะฐั‚ัŒ ะทะฝะฐั‡ะพะบ ะฑะตะท ัƒะฒะตะดะพะผะปะตะฝะธะน", + "hide-widget-when-zero-unread-description": "ะกะบั€ั‹ะฒะฐั‚ัŒ ะทะฝะฐั‡ะพะบ ัƒะฒะตะดะพะผะปะตะฝะธะน, ะตัะปะธ ะฝะตั‚ ะฝะตะฟั€ะพั‡ะธั‚ะฐะฝะฝั‹ั….", + "hide-widget-when-zero-unread-label": "ะกะบั€ั‹ะฒะฐั‚ัŒ ะทะฝะฐั‡ะพะบ ะฑะตะท ะฝะตะฟั€ะพั‡ะธั‚ะฐะฝะฝั‹ั…", + "show-unread-badge-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะทะฝะฐั‡ะพะบ, ะฟะพะบะฐะทั‹ะฒะฐัŽั‰ะธะน ะบะพะปะธั‡ะตัั‚ะฒะพ ะฝะตะฟั€ะพั‡ะธั‚ะฐะฝะฝั‹ั… ัƒะฒะตะดะพะผะปะตะฝะธะน.", + "show-unread-badge-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะทะฝะฐั‡ะพะบ ะฝะตะฟั€ะพั‡ะธั‚ะฐะฝะฝั‹ั…", + "unread-badge-color-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ั†ะฒะตั‚ ะดะปั ะทะฝะฐั‡ะบะฐ ะฝะตะฟั€ะพั‡ะธั‚ะฐะฝะฝั‹ั… ัƒะฒะตะดะพะผะปะตะฝะธะน.", + "unread-badge-color-label": "ะฆะฒะตั‚ ะทะฝะฐั‡ะบะฐ ะฝะตะฟั€ะพั‡ะธั‚ะฐะฝะฝั‹ั…" + }, + "section-editor": { + "placeholder": "ะ’ั‹ะฑะตั€ะธั‚ะต ะฒะธะดะถะตั‚ ะดะปั ะดะพะฑะฐะฒะปะตะฝะธั...", + "search-placeholder": "ะŸะพะธัะบ ะฒะธะดะถะตั‚ะพะฒ..." + }, + "spacer": { + "width-description": "ะจะธั€ะธะฝะฐ ะธะฝั‚ะตั€ะฒะฐะปะฐ ะฒ ะฟะธะบัะตะปัั…." + }, + "system-monitor": { + "compact-mode-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ัั‚ะฐั‚ะธัั‚ะธะบัƒ ะฒ ะฒะธะดะต ะผะธะฝะธ-ะดะธะฐะณั€ะฐะผะผ ะฒะผะตัั‚ะพ ั‚ะตะบัั‚ะพะฒั‹ั… ะทะฝะฐั‡ะตะฝะธะน. ะŸั€ะตะดะพั‚ะฒั€ะฐั‰ะฐะตั‚ ัะดะฒะธะณ ะผะฐะบะตั‚ะฐ.", + "compact-mode-label": "ะšะพะผะฟะฐะบั‚ะฝั‹ะน ั€ะตะถะธะผ", + "cpu-cores-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะต ัะดะตั€ CPU ะฟะพ ะพั‚ะดะตะปัŒะฝะพัั‚ะธ.", + "cpu-cores-label": "ะฏะดั€ะฐ CPU", + "cpu-frequency-description": "ะžั‚ะพะฑั€ะฐะทะธั‚ัŒ ั‚ะตะบัƒั‰ัƒัŽ ั‚ะฐะบั‚ะพะฒัƒัŽ ั‡ะฐัั‚ะพั‚ัƒ CPU ะฒ ะ“ะ“ั†.", + "cpu-frequency-label": "ะŸะพะบะฐะทะฐั‚ัŒ ั‡ะฐัั‚ะพั‚ัƒ CPU", + "cpu-temperature-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฟะพะบะฐะทะฐะฝะธั ั‚ะตะผะฟะตั€ะฐั‚ัƒั€ั‹ ะฟั€ะพั†ะตััะพั€ะฐ, ะตัะปะธ ะดะพัั‚ัƒะฟะฝั‹.", + "cpu-temperature-label": "ะขะตะผะฟะตั€ะฐั‚ัƒั€ะฐ CPU", + "cpu-usage-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ั‚ะตะบัƒั‰ะธะน ะฟั€ะพั†ะตะฝั‚ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั CPU.", + "cpu-usage-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต CPU", + "disk-path-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ั‚ะพั‡ะบัƒ ะผะพะฝั‚ะธั€ะพะฒะฐะฝะธั ะดะธัะบะฐ ะดะปั ะผะพะฝะธั‚ะพั€ะธะฝะณะฐ.", + "disk-path-label": "ะŸัƒั‚ัŒ ะบ ะดะธัะบัƒ", + "gpu-temperature-description": "ะŸะพะบะฐะทะฐั‚ัŒ ะฟะพะบะฐะทะฐะฝะธั ั‚ะตะผะฟะตั€ะฐั‚ัƒั€ั‹ GPU, ะตัะปะธ ะดะพัั‚ัƒะฟะฝั‹.", + "load-average-description": "ะŸะพะบะฐะทะฐั‚ัŒ ัั€ะตะดะฝัŽัŽ ะฝะฐะณั€ัƒะทะบัƒ ัะธัั‚ะตะผั‹.", + "load-average-label": "ะกั€ะตะดะฝัั ะฝะฐะณั€ัƒะทะบะฐ", + "memory-percentage-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะต ะฟะฐะผัั‚ะธ ะฒ ะฟั€ะพั†ะตะฝั‚ะฐั… ะฒะผะตัั‚ะพ ะฐะฑัะพะปัŽั‚ะฝั‹ั… ะทะฝะฐั‡ะตะฝะธะน.", + "memory-percentage-label": "ะŸะฐะผัั‚ัŒ ะฒ ะฟั€ะพั†ะตะฝั‚ะฐั…", + "memory-usage-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะธะฝั„ะพั€ะผะฐั†ะธัŽ ะพ ั‚ะตะบัƒั‰ะตะผ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะธ RAM.", + "memory-usage-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต ะฟะฐะผัั‚ะธ", + "network-traffic-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ัะบะพั€ะพัั‚ะธ ะทะฐะณั€ัƒะทะบะธ ะธ ัะบะฐั‡ะธะฒะฐะฝะธั ะฟะพ ัะตั‚ะธ.", + "network-traffic-label": "ะกะตั‚ะตะฒะพะน ั‚ั€ะฐั„ะธะบ", + "storage-as-percentage-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะผะตัั‚ะพ ะฝะฐ ะดะธัะบะต ะฒ ะฟั€ะพั†ะตะฝั‚ะฐั… ะฒะผะตัั‚ะพ ะฐะฑัะพะปัŽั‚ะฝั‹ั… ะทะฝะฐั‡ะตะฝะธะน.", + "storage-as-percentage-label": "ะ”ะธัะบ ะฒ ะฟั€ะพั†ะตะฝั‚ะฐั…", + "storage-available-description": "ะŸะพะบะฐะทั‹ะฒะฐะตั‚, ัะบะพะปัŒะบะพ ะผะตัั‚ะฐ ะฝะฐ ะดะธัะบะต ะดะพัั‚ัƒะฟะฝะพ, ะฒะผะตัั‚ะพ ั‚ะพะณะพ, ัะบะพะปัŒะบะพ ะธัะฟะพะปัŒะทัƒะตั‚ัั.", + "storage-available-label": "ะ”ะพัั‚ัƒะฟะฝะพะต ะผะตัั‚ะพ ะฝะฐ ะดะธัะบะต", + "storage-usage-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะธะฝั„ะพั€ะผะฐั†ะธัŽ ะพะฑ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะธ ะดะธัะบะพะฒะพะณะพ ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะฐ.", + "storage-usage-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต ั…ั€ะฐะฝะธะปะธั‰ะฐ", + "swap-usage-description": "ะŸะพะบะฐะทะฐั‚ัŒ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะต swap-ะฟะฐะผัั‚ะธ.", + "swap-usage-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต swap", + "use-monospace-font-description": "ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต ะผะพะฝะพัˆะธั€ะธะฝะฝั‹ะน ัˆั€ะธั„ั‚ ะดะปั ะตะดะธะฝะพะพะฑั€ะฐะทะฝะพะน ัˆะธั€ะธะฝั‹ ัะธะผะฒะพะปะพะฒ.", + "use-monospace-font-label": "ะœะพะฝะพัˆะธั€ะธะฝะฝั‹ะน ัˆั€ะธั„ั‚", + "use-padding-description": "ะ”ะพะฑะฐะฒะปัะตั‚ ะบ ั‚ะตะบัั‚ะพะฒั‹ะผ ะทะฝะฐั‡ะตะฝะธัะผ ะฝะฐั‡ะฐะปัŒะฝั‹ะต ะฟั€ะพะฑะตะปั‹, ั‡ั‚ะพะฑั‹ ะฟั€ะตะดะพั‚ะฒั€ะฐั‚ะธั‚ัŒ ัะผะตั‰ะตะฝะธะต ะผะฐะบะตั‚ะฐ.", + "use-padding-description-disabled-monospace-font": "ะ”ะปั ัั‚ะพะน ั„ัƒะฝะบั†ะธะธ ะฝะตะพะฑั…ะพะดะธะผ ะผะพะฝะพัˆะธั€ะธะฝะฝั‹ะน ัˆั€ะธั„ั‚.", + "use-padding-description-disabled-vertical": "ะ’ะตั€ั‚ะธะบะฐะปัŒะฝะฐั ะฟะฐะฝะตะปัŒ ะทะฐะดะฐั‡ ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ ั‚ะตะบัั‚ะพะฒั‹ะต ะพั‚ัั‚ัƒะฟั‹.", + "use-padding-label": "ะžั‚ัั‚ัƒะฟ ะขะตะบัั‚ะฐ" + }, + "taskbar": { + "colorize-icons-description": "ะŸั€ะธะผะตะฝะธั‚ัŒ ั†ะฒะตั‚ะฐ ั‚ะตะผั‹ ะบ ะธะบะพะฝะบะฐะผ ะฟะฐะฝะตะปะธ ะทะฐะดะฐั‡.", + "hide-mode-description": "ะฃะฟั€ะฐะฒะปัะตั‚ ะฟะพะฒะตะดะตะฝะธะตะผ ะฒะธะดะถะตั‚ะฐ, ะบะพะณะดะฐ ะฝะตั‚ ัะพะพั‚ะฒะตั‚ัั‚ะฒัƒัŽั‰ะธั… ะพะบะพะฝ.", + "hide-mode-label": "ะ ะตะถะธะผ ัะบั€ั‹ั‚ะธั", + "icon-scale-description": "ะ—ะฐะดะฐั‘ั‚ ะบะพัั„ั„ะธั†ะธะตะฝั‚ ะผะฐััˆั‚ะฐะฑะธั€ะพะฒะฐะฝะธั ะดะปั ะทะฝะฐั‡ะบะพะฒ ะฟะฐะฝะตะปะธ ะทะฐะดะฐั‡.", + "icon-scale-label": "ะœะฐััˆั‚ะฐะฑะธั€ะพะฒะฐะฝะธะต ะทะฝะฐั‡ะบะพะฒ", + "max-width-description": "ะœะฐะบัะธะผะฐะปัŒะฝะฐั ัˆะธั€ะธะฝะฐ ะฟะฐะฝะตะปะธ ะทะฐะดะฐั‡ ะฒ ะฟั€ะพั†ะตะฝั‚ะฐั… ะพั‚ ัˆะธั€ะธะฝั‹ ัะบั€ะฐะฝะฐ.", + "max-width-label": "ะœะฐะบัะธะผะฐะปัŒะฝะฐั ัˆะธั€ะธะฝะฐ", + "only-active-workspaces-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ั‚ะพะปัŒะบะพ ะฟั€ะธะปะพะถะตะฝะธั ะธะท ะฐะบั‚ะธะฒะฝั‹ั… ั€ะฐะฑะพั‡ะธั… ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒ.", + "only-active-workspaces-label": "ะขะพะปัŒะบะพ ะธะท ะฐะบั‚ะธะฒะฝั‹ั… ั€ะฐะฑะพั‡ะธั… ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒ", + "only-same-monitor-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ั‚ะพะปัŒะบะพ ะฟั€ะธะปะพะถะตะฝะธั ั ะผะพะฝะธั‚ะพั€ะฐ, ะฝะฐ ะบะพั‚ะพั€ะพะผ ะฝะฐั…ะพะดะธั‚ัั ะฟะฐะฝะตะปัŒ.", + "only-same-monitor-label": "ะขะพะปัŒะบะพ ั ั‚ะพะณะพ ะถะต ะผะพะฝะธั‚ะพั€ะฐ", + "show-pinned-apps-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะทะฐะบั€ะตะฟะปั‘ะฝะฝั‹ะต ะฟั€ะธะปะพะถะตะฝะธั ะธะท ะฟะฐะฝะตะปะธ ะฟั€ะธะปะพะถะตะฝะธะน ะฝะฐ ะฟะฐะฝะตะปะธ ะทะฐะดะฐั‡.", + "show-pinned-apps-label": "ะŸะพะบะฐะทะฐั‚ัŒ ะทะฐะบั€ะตะฟะปั‘ะฝะฝั‹ะต ะฟั€ะธะปะพะถะตะฝะธั", + "show-title-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะทะฐะณะพะปะพะฒะบะธ ะพะบะพะฝ ะฝะฐ ะฟะฐะฝะตะปะธ ะทะฐะดะฐั‡.", + "show-title-description-disabled": "ะ’ะตั€ั‚ะธะบะฐะปัŒะฝะฐั ะฟะฐะฝะตะปัŒ ะทะฐะดะฐั‡ ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ ะพั‚ะพะฑั€ะฐะถะตะฝะธะต ะทะฐะณะพะปะพะฒะบะพะฒ.", + "show-title-label": "ะŸะพะบะฐะทะฐั‚ัŒ ะทะฐะณะพะปะพะฒะพะบ", + "smart-width-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ั€ะตะณัƒะปะธั€ะพะฒะฐั‚ัŒ ัˆะธั€ะธะฝัƒ ะทะฐะฟะธัะตะน ะฒ ะทะฐะฒะธัะธะผะพัั‚ะธ ะพั‚ ะธั… ะบะพะปะธั‡ะตัั‚ะฒะฐ.", + "smart-width-label": "ะฃะผะฝะฐั ัˆะธั€ะธะฝะฐ", + "title-width-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ัˆะธั€ะธะฝัƒ ะทะฐะณะพะปะพะฒะบะพะฒ ะพะบะพะฝ ะฝะฐ ะฟะฐะฝะตะปะธ ะทะฐะดะฐั‡ (ะฒ ะฟะธะบัะตะปัั…).", + "title-width-label": "ะจะธั€ะธะฝะฐ ะทะฐะณะพะปะพะฒะบะฐ", + "title-width-reset-tooltip": "ะกะฑั€ะพัะธั‚ัŒ ัˆะธั€ะธะฝัƒ ะทะฐะณะพะปะพะฒะบะฐ" + }, + "tray": { + "chevron-color-description": "ะŸั€ะธะผะตะฝะธั‚ัŒ ั†ะฒะตั‚ะฐ ั‚ะตะผั‹ ะบ ะธะบะพะฝะบะต ัˆะตะฒั€ะพะฝะฐ ะฒั‹ะดะฒะธะถะฝะพะน ะฟะฐะฝะตะปะธ.", + "chevron-color-label": "ะฆะฒะตั‚ ัั‚ั€ะตะปะบะธ", + "colorize-icons-description": "ะŸั€ะธะผะตะฝะธั‚ัŒ ั†ะฒะตั‚ะฐ ั‚ะตะผั‹ ะบ ะธะบะพะฝะบะฐะผ ั‚ั€ะตั.", + "colorize-icons-label": "ะ ะฐัะบั€ะฐัะธั‚ัŒ ะธะบะพะฝะบะธ", + "drawer-enabled-description": "ะ•ัะปะธ ะฒะบะปัŽั‡ะตะฝะพ, ะฝะตะทะฐะบั€ะตะฟะปะตะฝะฝั‹ะต ัะปะตะผะตะฝั‚ั‹ ั‚ั€ะตั ะพั‚ะพะฑั€ะฐะถะฐัŽั‚ัั ะฒะพ ะฒัะฟะปั‹ะฒะฐัŽั‰ะตะน ะฟะฐะฝะตะปะธ.
ะ•ัะปะธ ะพั‚ะบะปัŽั‡ะตะฝะพ, ะฒัะต ัะปะตะผะตะฝั‚ั‹ ั‚ั€ะตั ะพั‚ะพะฑั€ะฐะถะฐัŽั‚ัั ะฒ ัั‚ั€ะพะบัƒ.", + "drawer-enabled-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะฒัะฟะปั‹ะฒะฐัŽั‰ัƒัŽ ะฟะฐะฝะตะปัŒ", + "hide-passive-description": "ะ•ัะปะธ ะฒะบะปัŽั‡ะตะฝะพ, ัะปะตะผะตะฝั‚ั‹ ะฒ ั‚ั€ะตะต ัะพ ัั‚ะฐั‚ัƒัะพะผ 'ะŸะฐััะธะฒะฝั‹ะน' ะฑัƒะดัƒั‚ ัะบั€ั‹ั‚ั‹.", + "hide-passive-label": "ะกะบั€ั‹ั‚ัŒ ะฟะฐััะธะฒะฝั‹ะต ะฟั€ะตะดะผะตั‚ั‹" + }, + "volume": { + "display-mode-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะบะฐะบ ัั‚ะพ ะทะฝะฐั‡ะตะฝะธะต ะดะพะปะถะฝะพ ะพั‚ะพะฑั€ะฐะถะฐั‚ัŒัั." + }, + "workspace": { + "character-count-description": "ะšะพะปะธั‡ะตัั‚ะฒะพ ัะธะผะฒะพะปะพะฒ ะดะปั ะพั‚ะพะฑั€ะฐะถะตะฝะธั ะธะท ะธะผั‘ะฝ ั€ะฐะฑะพั‡ะธั… ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒ (1-10).", + "character-count-label": "ะšะพะปะธั‡ะตัั‚ะฒะพ ัะธะผะฒะพะปะพะฒ", + "empty-color-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ั†ะฒะตั‚ ั„ะพะฝะฐ ะดะปั ะฟัƒัั‚ั‹ั… ั€ะฐะฑะพั‡ะธั… ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒ.", + "empty-color-label": "ะฆะฒะตั‚ ะฟัƒัั‚ะพะณะพ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะฐ", + "enable-scrollwheel-description": "ะŸะตั€ะตะบะปัŽั‡ะฐะนั‚ะตััŒ ะผะตะถะดัƒ ั€ะฐะฑะพั‡ะธะผะธ ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะฐะผะธ ั ะฟะพะผะพั‰ัŒัŽ ะบะพะปะตัะฐ ะฟั€ะพะบั€ัƒั‚ะบะธ ะผั‹ัˆะธ.", + "enable-scrollwheel-label": "ะŸั€ะพะบั€ัƒั‚ะธั‚ะต, ั‡ั‚ะพะฑั‹ ะฟะตั€ะตะบะปัŽั‡ะธั‚ัŒ ั€ะฐะฑะพั‡ะธะต ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะฐ", + "focused-color-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ั†ะฒะตั‚ ั„ะพะฝะฐ ะดะปั ะฐะบั‚ะธะฒะฝะพะณะพ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะฐ.", + "focused-color-label": "ะฆะฒะตั‚ ะฐะบั‚ะธะฒะฝะพะณะพ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะฐ", + "follow-focused-screen-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ั€ะฐะฑะพั‡ะธะต ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะฐ ั ั‚ะตะบัƒั‰ะตะณะพ ะฐะบั‚ะธะฒะฝะพะณะพ ัะบั€ะฐะฝะฐ, ะฐ ะฝะต ั ัะบั€ะฐะฝะฐ, ะฝะฐ ะบะพั‚ะพั€ะพะผ ั€ะฐัะฟะพะปะพะถะตะฝะฐ ะฟะฐะฝะตะปัŒ.", + "follow-focused-screen-label": "ะกะปะตะดะพะฒะฐั‚ัŒ ะทะฐ ะฐะบั‚ะธะฒะฝั‹ะผ ัะบั€ะฐะฝะพะผ", + "font-weight-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ะฒะธะทัƒะฐะปัŒะฝั‹ะน ะฒะตั ั‚ะตะบัั‚ะฐ ะฒ ั€ะฐะฑะพั‡ะตะผ ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะต.", + "font-weight-label": "ะขะพะปั‰ะธะฝะฐ ัˆั€ะธั„ั‚ะฐ", + "grouped-border-opacity-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ัƒั€ะพะฒะตะฝัŒ ะฟั€ะพะทั€ะฐั‡ะฝะพัั‚ะธ ะดะปั ะณั€ะฐะฝะธั† ะบะพะฝั‚ะตะนะฝะตั€ะฐ ั€ะฐะฑะพั‡ะตะน ะพะฑะปะฐัั‚ะธ.", + "grouped-border-opacity-label": "ะŸั€ะพะทั€ะฐั‡ะฝะพัั‚ัŒ ะณั€ะฐะฝะธั†ั‹", + "hide-unoccupied-description": "ะะต ะพั‚ะพะฑั€ะฐะถะฐั‚ัŒ ั€ะฐะฑะพั‡ะธะต ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะฐ ะฑะตะท ะพะบะพะฝ.", + "hide-unoccupied-label": "ะกะบั€ั‹ั‚ัŒ ะฝะตะทะฐะฝัั‚ั‹ะต", + "label-mode-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะบะฐะบ ะพั‚ะพะฑั€ะฐะถะฐัŽั‚ัั ะผะตั‚ะบะธ ั€ะฐะฑะพั‡ะธั… ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒ.", + "label-mode-label": "ะ ะตะถะธะผ ะผะตั‚ะบะธ", + "occupied-color-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ั†ะฒะตั‚ ั„ะพะฝะฐ ะดะปั ะทะฐะฝัั‚ั‹ั… ั€ะฐะฑะพั‡ะธั… ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒ.", + "occupied-color-label": "ะฆะฒะตั‚ ะทะฐะฝัั‚ะพะณะพ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะฐ", + "pill-size-description": "ะžั‚ั€ะตะณัƒะปะธั€ัƒะนั‚ะต ั€ะฐะทะผะตั€ ะธะฝะดะธะบะฐั‚ะพั€ะพะฒ ั€ะฐะฑะพั‡ะธั… ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒ (50%-100%).", + "pill-size-label": "ะ ะฐะทะผะตั€ ะบะฐะฟััƒะปั‹", + "reverse-scrolling-description": "ะ˜ะทะผะตะฝะธั‚ัŒ ะฝะฐะฟั€ะฐะฒะปะตะฝะธะต ะฟะตั€ะตะบะปัŽั‡ะตะฝะธั ั€ะฐะฑะพั‡ะธั… ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒ ะฟั€ะธ ะฟั€ะพะบั€ัƒั‚ะบะต.", + "reverse-scrolling-label": "ะžะฑั€ะฐั‚ะฝะฐั ะฟั€ะพะบั€ัƒั‚ะบะฐ", + "show-applications-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะทะฝะฐั‡ะบะธ ะฟั€ะธะปะพะถะตะฝะธะน ะฒะฝัƒั‚ั€ะธ ะบะฐะถะดะพะณะพ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะฐ.", + "show-applications-hover-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะทะฝะฐั‡ะบะธ ะฟั€ะธะปะพะถะตะฝะธะน ะฒะฝัƒั‚ั€ะธ ะบะฐะถะดะพะณะพ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะฐ ะฟั€ะธ ะฝะฐะฒะตะดะตะฝะธะธ ะฝะฐ ะฝะตะณะพ.", + "show-applications-hover-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฟั€ะธะปะพะถะตะฝะธั ะฟั€ะธ ะฝะฐะฒะตะดะตะฝะธะธ", + "show-applications-label": "ะŸะพะบะฐะทะฐั‚ัŒ ะฟั€ะธะปะพะถะตะฝะธั", + "show-badge-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะทะฝะฐั‡ะพะบ ะฝะพะผะตั€ะฐ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะฐ ะฒ ัะณั€ัƒะฟะฟะธั€ะพะฒะฐะฝะฝะพะผ ั€ะตะถะธะผะต.", + "show-badge-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะทะฝะฐั‡ะพะบ ั€ะฐะฑะพั‡ะตะณะพ ัั‚ะพะปะฐ", + "show-labels-only-when-occupied-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะผะตั‚ะบะธ ั€ะฐะฑะพั‡ะธั… ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒ ั‚ะพะปัŒะบะพ ั‚ะพะณะดะฐ, ะบะพะณะดะฐ ะพะฝะธ ัะพะดะตั€ะถะฐั‚ ะพะบะฝะฐ.", + "show-labels-only-when-occupied-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะผะตั‚ะบะธ ั‚ะพะปัŒะบะพ ะฟั€ะธ ะฝะฐะปะธั‡ะธะธ ัะพะดะตั€ะถะธะผะพะณะพ", + "unfocused-icons-opacity-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ัƒั€ะพะฒะตะฝัŒ ะฟั€ะพะทั€ะฐั‡ะฝะพัั‚ะธ ะดะปั ะฝะตะฐะบั‚ะธะฒะฝั‹ั… ะทะฝะฐั‡ะบะพะฒ ะฟั€ะธะปะพะถะตะฝะธะน.", + "unfocused-icons-opacity-label": "ะะตะฐะบั‚ะธะฒะฝั‹ะต ะทะฝะฐั‡ะบะธ, ะฟั€ะพะทั€ะฐั‡ะฝะพัั‚ัŒ" + } + }, + "battery": { + "all-batteries": "ะ’ัะต ะฑะฐั‚ะฐั€ะตะธ (ะพะฑัŠะตะดะธะฝะตะฝะฝั‹ะต)", + "battery-health": "ะกะพัั‚ะพัะฝะธะต ะฑะฐั‚ะฐั€ะตะธ", + "battery-level": "ะฃั€ะพะฒะตะฝัŒ ะทะฐั€ัะดะฐ ะฑะฐั‚ะฐั€ะตะธ", + "capacity-level": "ะะผะบะพัั‚ัŒ: {level}", + "charging-rate": "ะกะบะพั€ะพัั‚ัŒ ะทะฐั€ัะดะบะธ: {rate} ะ’ั‚", + "discharging-rate": "ะกะบะพั€ะพัั‚ัŒ ั€ะฐะทั€ัะดะบะธ: {rate} ะ’ั‚", + "health": "ะ—ะดะพั€ะพะฒัŒะต: {percent}%", + "inhibit-idle-description": "ะะต ะดะฐั‘ั‚ ัะธัั‚ะตะผะต ัƒัะฝัƒั‚ัŒ.", + "no-battery-detected": "ะ‘ะฐั‚ะฐั€ะตั ะฝะต ะพะฑะฝะฐั€ัƒะถะตะฝะฐ", + "plugged-in": "ะŸะพะดะบะปัŽั‡ะตะฝะพ", + "power-profile": "ะŸั€ะพั„ะธะปัŒ ะฟะธั‚ะฐะฝะธั", + "time-left": "ะžัั‚ะฐะปะพััŒ ะฒั€ะตะผะตะฝะธ: {time}", + "time-until-full": "ะ’ั€ะตะผั ะดะพ ะฟะพะปะฝะพะน ะทะฐั€ัะดะบะธ: {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "ะ”ะพัั‚ัƒะฟะฝั‹ะต ัƒัั‚ั€ะพะนัั‚ะฒะฐ", + "blocked": "ะ—ะฐะฑะปะพะบะธั€ะพะฒะฐะฝะพ", + "connected-devices": "ะŸะพะดะบะปัŽั‡ะตะฝะฝั‹ะต ัƒัั‚ั€ะพะนัั‚ะฒะฐ", + "device-address": "ะะดั€ะตั ัƒัั‚ั€ะพะนัั‚ะฒะฐ", + "disabled": "Bluetooth ะพั‚ะบะปัŽั‡ะตะฝ", + "discoverable": "ะžะฑะฝะฐั€ัƒะถะธะฒะฐะตะผั‹ะน", + "enable-message": "ะ’ะบะปัŽั‡ะธั‚ะต Bluetooth, ั‡ั‚ะพะฑั‹ ัƒะฒะธะดะตั‚ัŒ ะดะพัั‚ัƒะฟะฝั‹ะต ัƒัั‚ั€ะพะนัั‚ะฒะฐ.", + "known-devices": "ะ˜ะทะฒะตัั‚ะฝั‹ะต ัƒัั‚ั€ะพะนัั‚ะฒะฐ", + "no-devices": "ะะตั‚ ะดะพัั‚ัƒะฟะฝั‹ั… ัƒัั‚ั€ะพะนัั‚ะฒ", + "paired-devices": "ะกะพะฟั€ัะถั‘ะฝะฝั‹ะต ัƒัั‚ั€ะพะนัั‚ะฒะฐ", + "pairing-mode": "ะฃะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ะฒะฐัˆะต ัƒัั‚ั€ะพะนัั‚ะฒะพ ะฝะฐั…ะพะดะธั‚ัั ะฒ ั€ะตะถะธะผะต ัะพะฟั€ัะถะตะฝะธั.", + "scanning": "ะŸะพะธัะบ ัƒัั‚ั€ะพะนัั‚ะฒ...", + "signal-text-excellent": "ะกะธะณะฝะฐะป: ะžั‚ะปะธั‡ะฝั‹ะน", + "signal-text-fair": "ะกะธะณะฝะฐะป: ะกั€ะตะดะฝะธะน", + "signal-text-good": "ะกะธะณะฝะฐะป: ะฅะพั€ะพัˆะธะน", + "signal-text-poor": "ะกะธะณะฝะฐะป: ะŸะปะพั…ะพะน", + "signal-text-unknown": "ะกะธะณะฝะฐะป: ะะตะธะทะฒะตัั‚ะตะฝ", + "signal-text-very-poor": "ะกะธะณะฝะฐะป: ะžั‡ะตะฝัŒ ัะปะฐะฑั‹ะน" + } + }, + "changelog": { + "error": { + "fetch-failed": "ะะต ัƒะดะฐะปะพััŒ ะทะฐะณั€ัƒะทะธั‚ัŒ ะดะฐะฝะฝั‹ะต ะถัƒั€ะฝะฐะปะฐ ะธะทะผะตะฝะตะฝะธะน. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฟะพะฟั€ะพะฑัƒะนั‚ะต ะฟะพะทะถะต.", + "rate-limit": "ะŸั€ะตะฒั‹ัˆะตะฝ ะปะธะผะธั‚ GitHub. ะŸะพะฟั€ะพะฑัƒะนั‚ะต ัะฝะพะฒะฐ ั‡ะตั€ะตะท ะฝะตัะบะพะปัŒะบะพ ะผะธะฝัƒั‚." + }, + "panel": { + "buttons-discord": "ะŸั€ะธัะพะตะดะธะฝะธั‚ัŒัั ะบ ะฝะฐัˆะตะผัƒ Discord", + "buttons-dismiss": "ะžะš", + "buttons-feedback": "ะžัั‚ะฐะฒัŒั‚ะต ะพั‚ะทั‹ะฒ", + "empty": "ะŸั€ะธะผะตั‡ะฐะฝะธั ะบ ะฒั‹ะฟัƒัะบัƒ ะฟะพะบะฐ ะฝะตะดะพัั‚ัƒะฟะฝั‹.", + "highlight-title": "ะžัะฝะพะฒะฝั‹ะต ะธะทะผะตะฝะตะฝะธั", + "released": "ะ’ั‹ะฟัƒั‰ะตะฝะพ {date}", + "subtitle-fresh": "ะกะฟะฐัะธะฑะพ ะทะฐ ัƒัั‚ะฐะฝะพะฒะบัƒ Noctalia! ะ’ะพั‚ ั‡ั‚ะพ ะฒั…ะพะดะธั‚ ะฒ ัั‚ัƒ ัะฑะพั€ะบัƒ.", + "subtitle-updated": "ะžะฑะฝะพะฒะปะตะฝะพ ั {previousVersion}", + "title": "ะงั‚ะพ ะฝะพะฒะพะณะพ ะฒ {version}", + "version": "ะ’ะตั€ัะธั {version}", + "version-new-user": "ะะพะฒะฐั ัƒัั‚ะฐะฝะพะฒะบะฐ" + } + }, + "common": { + "actions": "ะ”ะตะนัั‚ะฒะธั", + "active": "ะะบั‚ะธะฒะฝั‹ะน", + "add": "ะ”ะพะฑะฐะฒะธั‚ัŒ", + "appearance": "ะ’ะฝะตัˆะฝะธะน ะฒะธะด", + "apply": "ะŸั€ะธะผะตะฝะธั‚ัŒ", + "auto-connect": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะพะต ะฟะพะดะบะปัŽั‡ะตะฝะธะต", + "automation": "ะะฒั‚ะพะผะฐั‚ะธะทะฐั†ะธั", + "available": "ะ”ะพัั‚ัƒะฟะฝะพ", + "back": "ะะฐะทะฐะด", + "battery": "ะ‘ะฐั‚ะฐั€ะตั", + "behavior": "ะŸะพะฒะตะดะตะฝะธะต", + "bluetooth": "Bluetooth", + "brightness": "ะฏั€ะบะพัั‚ัŒ", + "browse": "ะžะฑะทะพั€", + "calendar": "ะšะฐะปะตะฝะดะฐั€ัŒ", + "calendar-panel": "ะŸะฐะฝะตะปัŒ ะบะฐะปะตะฝะดะฐั€ั", + "cancel": "ะžั‚ะผะตะฝะธั‚ัŒ", + "cards": "ะšะฐั€ั‚ะพั‡ะบะธ", + "charging": "ะ—ะฐั€ัะดะบะฐ", + "clear": "ะžั‡ะธัั‚ะธั‚ัŒ", + "clipboard": "ะ‘ัƒั„ะตั€ ะพะฑะผะตะฝะฐ", + "close": "ะ—ะฐะบั€ั‹ั‚ัŒ", + "color-muted": "ะ’ั‹ะบะปัŽั‡ะตะฝะพ", + "colors": "ะฆะฒะตั‚ะฐ", + "command": "ะšะพะผะฐะฝะดะฐ", + "confirm": "ะŸะพะดั‚ะฒะตั€ะดะธั‚ัŒ", + "connect": "ะกะพะตะดะธะฝะธั‚ัŒ", + "connected": "ะŸะพะดะบะปัŽั‡ะตะฝะพ", + "connecting": "ะกะพะตะดะธะฝะตะฝะธะต...", + "continue": "ะŸั€ะพะดะพะปะถะธั‚ัŒ", + "contributors": "ะฃั‡ะฐัั‚ะฝะธะบะธ", + "copied-to-clipboard": "ะกะบะพะฟะธั€ะพะฒะฐะฝะพ ะฒ ะฑัƒั„ะตั€ ะพะฑะผะตะฝะฐ", + "countdown": "ะžะฑั€ะฐั‚ะฝั‹ะน ะพั‚ัั‡ั‘ั‚", + "customize": "ะะฐัั‚ั€ะพะธั‚ัŒ", + "date": "ะ”ะฐั‚ะฐ", + "default": "ะŸะพ ัƒะผะพะปั‡ะฐะฝะธัŽ", + "delete": "ะฃะดะฐะปะธั‚ัŒ", + "devices": "ะฃัั‚ั€ะพะนัั‚ะฒะฐ", + "disabled": "ะžั‚ะบะปัŽั‡ะตะฝะพ", + "discharging": "ะ ะฐะทั€ัะดะบะฐ", + "disconnect": "ะžั‚ะบะปัŽั‡ะธั‚ัŒ", + "disconnected": "ะžั‚ะบะปัŽั‡ะตะฝะพ", + "disconnecting": "ะžั‚ะบะปัŽั‡ะตะฝะธะต...", + "display-mode": "ะ ะตะถะธะผ ะพั‚ะพะฑั€ะฐะถะตะฝะธั", + "documentation": "ะ”ะพะบัƒะผะตะฝั‚ะฐั†ะธั", + "download": "ะกะบะฐั‡ะฐั‚ัŒ", + "duration": "ะŸั€ะพะดะพะปะถะธั‚ะตะปัŒะฝะพัั‚ัŒ", + "dysfunctional": "ะ”ะธัั„ัƒะฝะบั†ะธะพะฝะฐะปัŒะฝั‹ะน", + "edit": "ะ ะตะดะฐะบั‚ะธั€ะพะฒะฐั‚ัŒ", + "enabled": "ะ’ะบะปัŽั‡ะตะฝะพ", + "error": "ะžัˆะธะฑะบะฐ", + "ethernet": "Ethernet", + "events": "ะกะพะฑั‹ั‚ะธั", + "execute": "ะ’ั‹ะฟะพะปะฝะธั‚ัŒ", + "faithful": "ะ’ะตั€ะฝั‹ะน", + "focus": "ะคะพะบัƒั", + "font-weight-bold": "ะ–ะธั€ะฝั‹ะน", + "font-weight-light": "ะขะพะฝะบะธะน", + "font-weight-medium": "ะกั€ะตะดะฝะธะน", + "font-weight-regular": "ะžะฑั‹ั‡ะฝั‹ะน", + "font-weight-semibold": "ะŸะพะปัƒะถะธั€ะฝั‹ะน", + "frequency": "ะงะฐัั‚ะพั‚ะฐ", + "gateway": "ะจะปัŽะท", + "general": "ะžะฑั‰ะตะต", + "height": "ะ’ั‹ัะพั‚ะฐ", + "hibernate": "ะ“ะธะฑะตั€ะฝะฐั†ะธั", + "history": "ะ˜ัั‚ะพั€ะธั", + "icon": "ะ˜ะบะพะฝะบะฐ", + "idle": "ะŸั€ะพัั‚ะพะน", + "info": "ะ˜ะฝั„ะพั€ะผะฐั†ะธั", + "input": "ะ’ะฒะพะด", + "install": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ", + "installed": "ะฃัั‚ะฐะฝะพะฒะปะตะฝะพ", + "interface": "ะ˜ะฝั‚ะตั€ั„ะตะนั", + "internet": "ะ˜ะฝั‚ะตั€ะฝะตั‚", + "keybind": "ะŸั€ะธะฒัะทะบะฐ ะบะปะฐะฒะธัˆ", + "language": "ะฏะทั‹ะบ", + "loading": "ะ—ะฐะณั€ัƒะทะบะฐ...", + "local": "ะœะตัั‚ะฝั‹ะน", + "location": "ะœะตัั‚ะพะฟะพะปะพะถะตะฝะธะต", + "lock": "ะ—ะฐะฑะปะพะบะธั€ะพะฒะฐั‚ัŒ", + "logout": "ะ’ั‹ะนั‚ะธ", + "look": "ะ’ะฝะตัˆะฝะธะน ะฒะธะด", + "margins": "ะžั‚ัั‚ัƒะฟั‹", + "media": "ะŸะปะตะตั€", + "media-player": "ะœะตะดะธะฐะฟะปะตะตั€", + "memory": "ะŸะฐะผัั‚ัŒ", + "monitors": "ะœะพะฝะธั‚ะพั€ั‹", + "move-down": "ะŸะตั€ะตะผะตัั‚ะธั‚ัŒ ะฒะฝะธะท", + "move-up": "ะŸะตั€ะตะผะตัั‚ะธั‚ัŒ ะฒะฒะตั€ั…", + "network": "ะกะตั‚ัŒ", + "next": "ะกะปะตะดัƒัŽั‰ะธะน", + "night-light": "ะะพั‡ะฝะพะน ัะฒะตั‚", + "no": "ะะตั‚", + "no-results": "ะะตั‚ ั€ะตะทัƒะปัŒั‚ะฐั‚ะพะฒ", + "no-summary": "ะะตั‚ ัะฒะพะดะบะธ", + "none": "ะะธั‡ะตะณะพ", + "not-found": "ะะต ะฝะฐะนะดะตะฝะพ", + "notifications": "ะฃะฒะตะดะพะผะปะตะฝะธั", + "official": "ะžั„ะธั†ะธะฐะปัŒะฝั‹ะน", + "on-surface": "ะะฐ ะฟะพะฒะตั€ั…ะฝะพัั‚ะธ", + "output": "ะ’ั‹ะฒะพะด", + "pair": "ะกะฟะฐั€ะธั‚ัŒ", + "paired": "ะกะฟะฐั€ะตะฝะพ", + "pairing": "ะกะพะฟั€ัะถะตะฝะธะต...", + "panels": "ะŸะฐะฝะตะปะธ", + "password": "ะŸะฐั€ะพะปัŒ", + "pause": "ะŸะฐัƒะทะฐ", + "performance": "ะŸั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ัŒ", + "pin": "ะ—ะฐะบั€ะตะฟะธั‚ัŒ", + "play": "ะ’ะพัะฟั€ะพะธะทะฒะตัั‚ะธ", + "polling": "ะžะฟั€ะฐัˆะธะฒะฐะฝะธะต", + "position": "ะŸะพะทะธั†ะธั", + "previous": "ะŸั€ะตะดั‹ะดัƒั‰ะธะน", + "primary": "ะžัะฝะพะฒะฝะพะน", + "random": "ะกะปัƒั‡ะฐะนะฝั‹ะน", + "reboot": "ะŸะตั€ะตะทะฐะณั€ัƒะทะธั‚ัŒ", + "reboot-to-uefi": "ะŸะตั€ะตะทะฐะณั€ัƒะทะธั‚ัŒ ะฒ UEFI", + "record": "ะะฐั‡ะฐั‚ัŒ ะทะฐะฟะธััŒ", + "refresh": "ะžะฑะฝะพะฒะธั‚ัŒ", + "required": "(ะพะฑัะทะฐั‚ะตะปัŒะฝะพ)", + "reset": "ะกะฑั€ะพั", + "reset-to-default": "ะกะฑั€ะพัะธั‚ัŒ ะฟะพ ะฃะผะพะปั‡ะฐะฝะธัŽ", + "result-count": "{count} ั€ะตะทัƒะปัŒั‚ะฐั‚", + "result-count-plural": "{count} ั€ะตะทัƒะปัŒั‚ะฐั‚ั‹", + "resume": "ะŸั€ะพะดะพะปะถะธั‚ัŒ", + "save": "ะกะพั…ั€ะฐะฝะธั‚ัŒ", + "scanning": "ะกะบะฐะฝะธั€ะพะฒะฐะฝะธะต...", + "screen-corners": "ะฃะณะปั‹ ัะบั€ะฐะฝะฐ", + "search": "ะŸะพะธัะบ", + "second": "{count} ัะตะบัƒะฝะดะฐ", + "second-plural": "{count} ัะตะบัƒะฝะด", + "secondary": "ะ’ั‚ะพั€ะธั‡ะฝั‹ะน", + "security": "ะ‘ะตะทะพะฟะฐัะฝะพัั‚ัŒ", + "select": "ะ’ั‹ะฑั€ะฐั‚ัŒ", + "select-color": "ะ’ั‹ะฑั€ะฐั‚ัŒ ั†ะฒะตั‚", + "select-color-description": "ะŸั€ะธะผะตะฝะธั‚ัŒ ั†ะฒะตั‚ะฐ ั‚ะตะผั‹ ะดะปั ะฒั‹ะดะตะปะตะฝะธั.", + "select-icon-color": "ะ’ั‹ะฑั€ะฐั‚ัŒ ั†ะฒะตั‚ ะทะฝะฐั‡ะบะฐ", + "settings": "ะะฐัั‚ั€ะพะนะบะธ", + "shortcuts": "ะฏั€ะปั‹ะบะธ", + "shutdown": "ะ’ั‹ะบะปัŽั‡ะธั‚ัŒ", + "signal": "ะกะธะณะฝะฐะป", + "sound": "ะ—ะฒัƒะบ", + "sources": "ะ˜ัั‚ะพั‡ะฝะธะบะธ", + "start": "ะะฐั‡ะฐั‚ัŒ", + "stop": "ะกั‚ะพะฟ", + "supporters": "ะŸะพะดะดะตั€ะถะธะฒะฐัŽั‰ะธะต", + "suspend": "ะŸั€ะธะพัั‚ะฐะฝะพะฒะธั‚ัŒ", + "templates": "ะจะฐะฑะปะพะฝั‹", + "tertiary": "ะขั€ะตั‚ะธั‡ะฝั‹ะน", + "test": "ะขะตัั‚", + "thresholds": "ะŸะพั€ะพะณะธ", + "title": "ะะฐะทะฒะฐะฝะธะต", + "toast": "Toast", + "trusted": "ะะฐะดั‘ะถะฝั‹ะน", + "uninstall": "ะฃะดะฐะปะธั‚ัŒ", + "unknown": "ะะตะธะทะฒะตัั‚ะฝะพ", + "unpair": "ะ ะฐะทะพั€ะฒะฐั‚ัŒ ะฟะฐั€ัƒ", + "unpin": "ะžั‚ะบั€ะตะฟะธั‚ัŒ", + "update": "ะžะฑะฝะพะฒะปะตะฝะธะต", + "upload": "ะ—ะฐะณั€ัƒะทะธั‚ัŒ", + "username": "ะ˜ะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั", + "userspace-reboot": "ะŸะตั€ะตะทะฐะณั€ัƒะทะธั‚ัŒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะพะต ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะพ", + "version": "ะ’ะตั€ัะธั", + "vibrant": "ะฏั€ะบะธะน", + "visualizer": "ะ’ะธะทัƒะฐะปะธะทะฐั‚ะพั€", + "volume": "ะžะฑัŠั‘ะผ", + "volumes": "ะขะพะผะฐ", + "wallpaper": "ะžะฑะพะธ", + "weather": "ะŸะพะณะพะดะฐ", + "weather-loading": "ะ—ะฐะณั€ัƒะทะบะฐ ะฟะพะณะพะดั‹...", + "weather-no-location": "ะฃัั‚ะฐะฝะพะฒะธั‚ะต ะผะตัั‚ะพะฟะพะปะพะถะตะฝะธะต ะฒ ะะฐัั‚ั€ะพะนะบะฐั…, ั‡ั‚ะพะฑั‹ ะฒะธะดะตั‚ัŒ ะฟะพะณะพะดัƒ", + "week": "ะะตะดะตะปั", + "widgets": "ะ’ะธะดะถะตั‚ั‹", + "width": "ะจะธั€ะธะฝะฐ", + "wifi": "Wi-Fi", + "windows": "ะžะบะฝะฐ ", + "yes": "ะ”ะฐ" + }, + "control-center": { + "power-profile": { + "tooltip-action": "ะŸั€ะพั„ะธะปัŒ ะฟะธั‚ะฐะฝะธั", + "tooltip-disabled": "ะฃัั‚ะฐะฝะพะฒะธั‚ะต power-profiles-daemon, ั‡ั‚ะพะฑั‹ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฟั€ะพั„ะธะปะธ ะฟะธั‚ะฐะฝะธั" + }, + "wifi": { + "label-disconnected": "Wi-Fi ะพั‚ะบะปัŽั‡ะตะฝ", + "label-ethernet": "Ethernet" + } + }, + "display-modes": { + "always-hide": "ะ’ัะตะณะดะฐ ัะบั€ั‹ะฒะฐั‚ัŒ", + "always-show": "ะ’ัะตะณะดะฐ ะฟะพะบะฐะทั‹ะฒะฐั‚ัŒ", + "force-open": "ะŸั€ะธะฝัƒะดะธั‚ะตะปัŒะฝะพ ะพั‚ะบั€ั‹ั‚ัŒ", + "on-hover": "ะŸั€ะธ ะฝะฐะฒะตะดะตะฝะธะธ" + }, + "hide-modes": { + "auto-hide": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะพะต ัะบั€ั‹ั‚ะธะต", + "hidden": "ะกะบั€ั‹ะฒะฐั‚ัŒ, ะบะพะณะดะฐ ะฟัƒัั‚ะพ", + "idle": "ะกะบั€ั‹ะฒะฐั‚ัŒ ะฒ ั€ะตะถะธะผะต ะฟั€ะพัั‚ะพั", + "non-exclusive": "ะะตะธัะบะปัŽั‡ะธั‚ะตะปัŒะฝั‹ะน", + "transparent": "ะŸั€ะพะทั€ะฐั‡ะฝั‹ะน, ะบะพะณะดะฐ ะฟัƒัั‚ะพะน", + "visible": "ะ’ัะตะณะดะฐ ะฒะธะดะตะฝ" + }, + "launcher": { + "categories": { + "all": "ะ’ัั‘", + "audiovideo": "ะัƒะดะธะพ ะธ ะฒะธะดะตะพ", + "chat": "ะงะฐั‚", + "development": "ะ ะฐะทั€ะฐะฑะพั‚ะบะฐ", + "education": "ะžะฑั€ะฐะทะพะฒะฐะฝะธะต", + "emoji-activity": "ะะบั‚ะธะฒะฝะพัั‚ัŒ", + "emoji-animals": "ะ–ะธะฒะพั‚ะฝั‹ะต", + "emoji-flags": "ะคะปะฐะณะธ", + "emoji-food": "ะ•ะดะฐ ะธ ะฝะฐะฟะธั‚ะบะธ", + "emoji-nature": "ะŸั€ะธั€ะพะดะฐ", + "emoji-objects": "ะžะฑัŠะตะบั‚ั‹", + "emoji-people": "ะ›ัŽะดะธ ะธ ั‚ะตะปะพ", + "emoji-recent": "ะะตะดะฐะฒะฝะธะต", + "emoji-symbols": "ะกะธะผะฒะพะปั‹", + "emoji-travel": "ะŸัƒั‚ะตัˆะตัั‚ะฒะธั ะธ ะผะตัั‚ะฐ", + "game": "ะ˜ะณั€ั‹", + "graphics": "ะ“ั€ะฐั„ะธะบะฐ", + "misc": "ะ ะฐะทะฝะพะต", + "office": "ะžั„ะธั", + "pinned": "ะ—ะฐะบั€ะตะฟะปะตะฝะพ", + "system": "ะกะธัั‚ะตะผะฐ", + "webbrowser": "ะ’ะตะฑ-ะฑั€ะฐัƒะทะตั€" + }, + "providers": { + "applications": "ะŸั€ะธะปะพะถะตะฝะธั", + "calculator": "ะšะฐะปัŒะบัƒะปัั‚ะพั€", + "calculator-deprecated": "ะšะพะผะฐะฝะดะฐ >calc ัƒัั‚ะฐั€ะตะปะฐ ะธ ัะบะพั€ะพ ะฑัƒะดะตั‚ ัƒะดะฐะปะตะฝะฐ. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฒะฒะพะดะธั‚ะต ะผะฐั‚ะตะผะฐั‚ะธั‡ะตัะบะธะต ะฒั‹ั€ะฐะถะตะฝะธั ะฟั€ัะผะพ ะฒ ะฟะพะธัะบ.", + "calculator-description": "ะšะฐะปัŒะบัƒะปัั‚ะพั€ โ€” ะฒั‹ั‡ะธัะปะตะฝะธะต ะผะฐั‚ะตะผะฐั‚ะธั‡ะตัะบะธั… ะฒั‹ั€ะฐะถะตะฝะธะน", + "calculator-enter-expression": "ะ’ะฒะตะดะธั‚ะต ะผะฐั‚ะตะผะฐั‚ะธั‡ะตัะบะพะต ะฒั‹ั€ะฐะถะตะฝะธะต", + "calculator-press-enter-to-copy": "ะะฐะถะผะธั‚ะต Enter, ั‡ั‚ะพะฑั‹ ัะบะพะฟะธั€ะพะฒะฐั‚ัŒ ั€ะตะทัƒะปัŒั‚ะฐั‚", + "clipboard": "ะ˜ัั‚ะพั€ะธั ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ", + "clipboard-clear-description": "ะžั‡ะธัั‚ะธั‚ัŒ ะฒััŽ ะธัั‚ะพั€ะธัŽ ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ", + "clipboard-clear-description-full": "ะฃะดะฐะปะธั‚ัŒ ะฒัะต ัะปะตะผะตะฝั‚ั‹ ะธะท ะธัั‚ะพั€ะธะธ ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ", + "clipboard-clear-history": "ะžั‡ะธัั‚ะธั‚ัŒ ะธัั‚ะพั€ะธัŽ ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ", + "clipboard-delete": "ะฃะดะฐะปะธั‚ัŒ ะทะฐะฟะธััŒ ะธะท ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ", + "clipboard-history-disabled": "ะ˜ัั‚ะพั€ะธั ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ ะพั‚ะบะปัŽั‡ะตะฝะฐ", + "clipboard-history-disabled-description": "ะ’ะบะปัŽั‡ะธั‚ะต ะธัั‚ะพั€ะธัŽ ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ ะฒ ะฝะฐัั‚ั€ะพะนะบะฐั… ะธะปะธ ัƒัั‚ะฐะฝะพะฒะธั‚ะต cliphist", + "clipboard-loading": "ะ—ะฐะณั€ัƒะทะบะฐ ะธัั‚ะพั€ะธะธ ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ...", + "clipboard-search-description": "ะŸะพะธัะบ ะฒ ะธัั‚ะพั€ะธะธ ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ", + "command-description": "ะ’ั‹ะฟะพะปะฝัั‚ัŒ ะบะพะผะฐะฝะดั‹ ะพะฑะพะปะพั‡ะบะธ", + "emoji": "ะ’ั‹ะฑะพั€ ัะผะพะดะทะธ", + "emoji-loading": "ะ—ะฐะณั€ัƒะทะบะฐ ัะผะพะดะทะธ...", + "emoji-loading-description": "ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฟะพะดะพะถะดะธั‚ะต", + "emoji-no-recent": "ะะตะดะฐะฒะฝะธั… ัะผะพะดะทะธ ะฟะพะบะฐ ะฝะตั‚", + "emoji-search-description": "ะŸะพะธัะบ ะธ ะบะพะฟะธั€ะพะฒะฐะฝะธะต ัะผะพะดะทะธ", + "settings-search-description": "ะŸะพะธัะบ ะธ ะฟะตั€ะตั…ะพะด ะบ ะฝะฐัั‚ั€ะพะนะบะฐะผ", + "windows-search-description": "ะŸะพะธัะบ ะธ ั„ะพะบัƒัะธั€ะพะฒะบะฐ ะพั‚ะบั€ั‹ั‚ั‹ั… ะพะบะพะฝ" + } + }, + "lock-screen": { + "authenticating": "ะัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธั...", + "password": "ะ’ะฒะตะดะธั‚ะต ะฒะฐัˆ ะฟะฐั€ะพะปัŒ...", + "restart": "ะŸะตั€ะตะทะฐะณั€ัƒะทะธั‚ัŒ", + "shut-down": "ะ’ั‹ะบะปัŽั‡ะธั‚ัŒ" + }, + "notifications": { + "panel": { + "click-to-collapse": "ะะฐะถะผะธั‚ะต, ั‡ั‚ะพะฑั‹ ัะฒะตั€ะฝัƒั‚ัŒ", + "click-to-expand": "ะะฐะถะผะธั‚ะต, ั‡ั‚ะพะฑั‹ ั€ะฐะทะฒะตั€ะฝัƒั‚ัŒ", + "description": "ะ’ะฐัˆะธ ัƒะฒะตะดะพะผะปะตะฝะธั ะฑัƒะดัƒั‚ ะฟะพัะฒะปัั‚ัŒัั ะทะดะตััŒ ะฟะพ ะผะตั€ะต ะธั… ะฟะพัั‚ัƒะฟะปะตะฝะธั.", + "no-notifications": "ะะตั‚ ัƒะฒะตะดะพะผะปะตะฝะธะน" + }, + "range": { + "earlier": "ะ ะฐะฝะตะต", + "today": "ะกะตะณะพะดะฝั", + "yesterday": "ะ’ั‡ะตั€ะฐ" + }, + "time": { + "diff-d": "1 ะดะตะฝัŒ ะฝะฐะทะฐะด", + "diff-dd": "{diff} ะดะฝะตะน ะฝะฐะทะฐะด", + "diff-h": "1 ั‡ะฐั ะฝะฐะทะฐะด", + "diff-hh": "{diff} ั‡ะฐัะพะฒ ะฝะฐะทะฐะด", + "diff-m": "1 ะผะธะฝัƒั‚ัƒ ะฝะฐะทะฐะด", + "diff-mm": "{diff} ะผะธะฝัƒั‚ ะฝะฐะทะฐะด", + "now": "ัะตะนั‡ะฐั" + } + }, + "options": { + "bar": { + "density-comfortable": "ะšะพะผั„ะพั€ั‚ะฝะฐั", + "density-compact": "ะšะพะผะฟะฐะบั‚ะฝะฐั", + "density-default": "ะŸะพ ัƒะผะพะปั‡ะฐะฝะธัŽ", + "density-mini": "ะœะธะฝะธ", + "density-spacious": "ะŸั€ะพัั‚ะพั€ะฝั‹ะน", + "display-mode-always-visible": "ะ’ัะตะณะดะฐ ะฒะธะดะฝั‹", + "type-floating": "ะŸะปะฐะฒะฐัŽั‰ะธะน", + "type-framed": "ะ’ ั€ะฐะผะบะต", + "type-simple": "ะŸั€ะพัั‚ะพะน" + }, + "control-center": { + "quick-settings-style-classic": "ะšะปะฐััะธั‡ะตัะบะธะน", + "quick-settings-style-modern": "ะกะพะฒั€ะตะผะตะฝะฝั‹ะน" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "ะšะพะผั„ะพั€ั‚ะฝั‹ะน", + "compact": "ะšะพะผะฟะฐะบั‚ะฝั‹ะน", + "default": "ะŸะพ ัƒะผะพะปั‡ะฐะฝะธัŽ" + }, + "launcher-view-mode": { + "grid": "ะกะตั‚ะบะฐ", + "list": "ะกะฟะธัะพะบ" + }, + "notification-density": { + "compact": "ะšะพะผะฟะฐะบั‚ะฝั‹ะน", + "default": "ะŸะพ ัƒะผะพะปั‡ะฐะฝะธัŽ" + }, + "scrolling-modes": { + "always": "ะ’ัะตะณะดะฐ ะฟั€ะพะบั€ัƒั‡ะธะฒะฐั‚ัŒ", + "hover": "ะŸั€ะพะบั€ัƒั‡ะธะฒะฐั‚ัŒ ะฟั€ะธ ะฝะฐะฒะตะดะตะฝะธะธ", + "never": "ะะธะบะพะณะดะฐ ะฝะต ะฟั€ะพะบั€ัƒั‡ะธะฒะฐั‚ัŒ" + }, + "session-menu-grid-layout": { + "grid": "ะกะตั‚ะบะฐ", + "single-row": "ะžะดะธะฝ ั€ัะด" + }, + "settings-panel-mode": { + "attached": "ะŸะฐะฝะตะปัŒ ะฟั€ะธะบั€ะตะฟะปะตะฝะฐ ะบ ะฟะฐะฝะตะปะธ", + "centered": "ะฆะตะฝั‚ั€ะธั€ะพะฒะฐะฝะฝะฐั ะฟะฐะฝะตะปัŒ", + "window": "ะžั‚ะดะตะปัŒะฝะพะต ะพะบะฝะพ" + }, + "visualizer-types": { + "linear": "ะ›ะธะฝะตะนะฝั‹ะน", + "mirrored": "ะ—ะตั€ะบะฐะปัŒะฝั‹ะน", + "wave": "ะ’ะพะปะฝะฐ" + }, + "visualizer-visibility": { + "with-background": "ะขะพะปัŒะบะพ ั ั„ะพะฝะพะผ" + }, + "workspace-labels": { + "index": "ะ˜ะฝะดะตะบั", + "index-and-name": "ะ˜ะฝะดะตะบั ะธ ะฝะฐะทะฒะฐะฝะธะต", + "name": "ะ˜ะผั" + } + }, + "panels": { + "about": { + "become-supporter": "ะกั‚ะฐั‚ัŒ ัะฟะพะฝัะพั€ะพะผ", + "changelog": "ะŸะพัะผะพั‚ั€ะตั‚ัŒ ัะฟะธัะพะบ ะธะทะผะตะฝะตะฝะธะน", + "changelog-on-startup": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะถัƒั€ะฝะฐะป ะธะทะผะตะฝะตะฝะธะน ะฟั€ะธ ะพะฑะฝะพะฒะปะตะฝะธะธ", + "changelog-on-startup-desc": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะฟะพะบะฐะทั‹ะฒะฐั‚ัŒ ะถัƒั€ะฝะฐะป ะธะทะผะตะฝะตะฝะธะน ะฟั€ะธ ะพะฑะฝะพะฒะปะตะฝะธะธ Noctalia.", + "contributors-description": "ะ‘ะปะฐะณะพะดะฐั€ะธะผ ะฝะฐัˆะตะณะพ ะทะฐะผะตั‡ะฐั‚ะตะปัŒะฝะพะณะพ ัƒั‡ะฐัั‚ะฝะธะบะฐ: {count}!", + "contributors-description-plural": "ะ‘ะปะฐะณะพะดะฐั€ะธะผ ะฝะฐัˆะธั… ะทะฐะผะตั‡ะฐั‚ะตะปัŒะฝั‹ั… ัƒั‡ะฐัั‚ะฝะธะบะพะฒ: {count}!", + "copy-info": "ะšะพะฟะธั€ะพะฒะฐั‚ัŒ ะธะฝั„ะพั€ะผะฐั†ะธัŽ", + "debug-disabled": "ะ ะตะถะธะผ ะพั‚ะปะฐะดะบะธ ะพั‚ะบะปัŽั‡ะตะฝ", + "debug-enabled": "ะ ะตะถะธะผ ะพั‚ะปะฐะดะบะธ ะฒะบะปัŽั‡ะตะฝ", + "info-copied": "ะ˜ะฝั„ะพั€ะผะฐั†ะธั ัะบะพะฟะธั€ะพะฒะฐะฝะฐ ะฒ ะฑัƒั„ะตั€ ะพะฑะผะตะฝะฐ", + "noctalia-available": "ะ”ะพัั‚ัƒะฟะฝะพ:", + "noctalia-desc": "ะ˜ะทัั‰ะฝะฐั ะธ ะผะธะฝะธะผะฐะปะธัั‚ะธั‡ะฝะฐั ะพะฑะพะปะพั‡ะบะฐ ั€ะฐะฑะพั‡ะตะณะพ ัั‚ะพะปะฐ, ั‚ั‰ะฐั‚ะตะปัŒะฝะพ ั€ะฐะทั€ะฐะฑะพั‚ะฐะฝะฝะฐั ะดะปั Wayland, ัะพะทะดะฐะฝะฝะฐั ั ะฟะพะผะพั‰ัŒัŽ Quickshell.", + "noctalia-git-commit": "Git ะบะพะผะผะธั‚:", + "noctalia-installed-version": "ะฃัั‚ะฐะฝะพะฒะปะตะฝะฝะฐั ะฒะตั€ัะธั:", + "noctalia-latest-version": "ะŸะพัะปะตะดะฝัั ะฒะตั€ัะธั:", + "noctalia-qs-version": "Noctalia QS ะฒะตั€ัะธั:", + "noctalia-title": "ะžะฑะพะปะพั‡ะบะฐ Noctalia", + "privacy-policy": "ะŸะพะปะธั‚ะธะบะฐ ะบะพะฝั„ะธะดะตะฝั†ะธะฐะปัŒะฝะพัั‚ะธ", + "support": "ะŸะพะดะดะตั€ะถะฐั‚ัŒ ะฝะฐั", + "supporter-badge": "ะกั‚ะพั€ะพะฝะฝะธะบ", + "supporters-desc": "ะžะณั€ะพะผะฝะพะต ัะฟะฐัะธะฑะพ ะฝะฐัˆะตะผัƒ ะทะฐะผะตั‡ะฐั‚ะตะปัŒะฝะพะผัƒ ัั‚ะพั€ะพะฝะฝะธะบัƒ!", + "supporters-desc-plural": "ะžะณั€ะพะผะฝะพะต ัะฟะฐัะธะฑะพ ะฝะฐัˆะธะผ {count} ะทะฐะผะตั‡ะฐั‚ะตะปัŒะฝั‹ะผ ัั‚ะพั€ะพะฝะฝะธะบะฐะผ!", + "supporters-loading": "ะ—ะฐะณั€ัƒะทะบะฐ ัะฟะพะฝัะพั€ะพะฒ...", + "system-board": "ะœะฐั‚ะตั€ะธะฝัะบะฐั ะฟะปะฐั‚ะฐ:", + "system-cpu": "ะฆะŸ:", + "system-disk": "ะ”ะธัะบ:", + "system-gpu": "ะ“ั€ะฐั„ะธั‡ะตัะบะธะน ะฟั€ะพั†ะตััะพั€", + "system-host": "ั…ะพัั‚:", + "system-install-hint": "ะฃัั‚ะฐะฝะพะฒะธั‚ะต fastfetch ะดะปั ะฟั€ะพัะผะพั‚ั€ะฐ ะธะฝั„ะพั€ะผะฐั†ะธะธ ะพ ัะธัั‚ะตะผะต", + "system-kernel": "ะฏะดั€ะพ:", + "system-loading": "ะ—ะฐะณั€ัƒะทะบะฐ ัะธัั‚ะตะผะฝะพะน ะธะฝั„ะพั€ะผะฐั†ะธะธ...", + "system-memory": "ะŸะฐะผัั‚ัŒ:", + "system-monitor": "ะœะพะฝะธั‚ะพั€:", + "system-not-installed": "fastfetch ะฝะต ัƒัั‚ะฐะฝะพะฒะปะตะฝ", + "system-os": "ะžะก:", + "system-packages": "ะŸะฐะบะตั‚ั‹:", + "system-product": "ะŸั€ะพะดัƒะบั‚:", + "system-title": "ะกะธัั‚ะตะผะฝะฐั ะธะฝั„ะพั€ะผะฐั†ะธั", + "system-uptime": "ะ’ั€ะตะผั ั€ะฐะฑะพั‚ั‹:", + "system-wm": "ะ’ะœ:", + "telemetry-data-copied": "ะ”ะฐะฝะฝั‹ะต ั‚ะตะปะตะผะตั‚ั€ะธะธ ัะบะพะฟะธั€ะพะฒะฐะฝั‹ ะฒ ะฑัƒั„ะตั€ ะพะฑะผะตะฝะฐ.", + "telemetry-desc": "ะŸะพะผะพะณะธั‚ะต ัƒะปัƒั‡ัˆะธั‚ัŒ Noctalia, ะฟะพะดะตะปะธะฒัˆะธััŒ ะฐะฝะพะฝะธะผะฝะพะน ะธะฝั„ะพั€ะผะฐั†ะธะตะน ะพ ัะธัั‚ะตะผะต (ั€ะฐะทั€ะตัˆะตะฝะธะต ัะบั€ะฐะฝะฐ, ะบะพะผะฟะพะทะธั‚ะพั€, ะดะธัั‚ั€ะธะฑัƒั‚ะธะฒ). ะžั‚ะฟั€ะฐะฒะปัะตั‚ัั ะพะดะธะฝ ั€ะฐะท ะฟั€ะธ ะทะฐะฟัƒัะบะต, ะฑะตะท ะพั‚ัะปะตะถะธะฒะฐะฝะธั, ะดะฐะฝะฝั‹ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ัƒะดะฐะปััŽั‚ัั ั‡ะตั€ะตะท 30 ะดะฝะตะน.", + "telemetry-enabled": "ะžั‚ะฟั€ะฐะฒะธั‚ัŒ ะฐะฝะพะฝะธะผะฝัƒัŽ ัะธัั‚ะตะผะฝัƒัŽ ะธะฝั„ะพั€ะผะฐั†ะธัŽ", + "telemetry-show-data": "ะŸั€ะพัะผะพั‚ั€ ะดะฐะฝะฝั‹ั…", + "telemetry-title": "ะšะพะฝั„ะธะดะตะฝั†ะธะฐะปัŒะฝะพัั‚ัŒ", + "title": "ะž ะฟั€ะพะณั€ะฐะผะผะต", + "up-to-date": "ะฃ ะฒะฐั ะฒัั‘ ะฐะบั‚ัƒะฐะปัŒะฝะพ!", + "update-available": "ะ”ะพัั‚ัƒะฟะฝะพ ะพะฑะฝะพะฒะปะตะฝะธะต", + "view-commit": "ะŸะพัะผะพั‚ั€ะตั‚ัŒ ะบะพะผะผะธั‚ ะฝะฐ GitHub" + }, + "audio": { + "devices-desc": "ะะฐัั‚ั€ะพะนะบะฐ ะดะพัั‚ัƒะฟะฝั‹ั… ัƒัั‚ั€ะพะนัั‚ะฒ ะฒะฒะพะดะฐ ะธ ะฒั‹ะฒะพะดะฐ ะฐัƒะดะธะพ.", + "devices-input-device-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ะถะตะปะฐะตะผะพะต ัƒัั‚ั€ะพะนัั‚ะฒะพ ะฒะฒะพะดะฐ ะฐัƒะดะธะพ.", + "devices-input-device-label": "ะฃัั‚ั€ะพะนัั‚ะฒะพ ะฒะฒะพะดะฐ", + "devices-output-device-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ะถะตะปะฐะตะผะพะต ัƒัั‚ั€ะพะนัั‚ะฒะพ ะฒั‹ะฒะพะดะฐ ะฐัƒะดะธะพ.", + "devices-output-device-label": "ะฃัั‚ั€ะพะนัั‚ะฒะพ ะฒั‹ะฒะพะดะฐ", + "devices-title": "ะัƒะดะธะพัƒัั‚ั€ะพะนัั‚ะฒะฐ", + "external-mixer-description": "ะ’ะฒะตะดะธั‚ะต ะบะพะผะฐะฝะดัƒ ะธะปะธ ะฟัƒั‚ัŒ ะบ ะฟั€ะธะปะพะถะตะฝะธัŽ ะดะปั ะทะฐะฟัƒัะบะฐ ะฟั€ะธ ะฐะบั‚ะธะฒะฐั†ะธะธ ั„ัƒะฝะบั†ะธะธ ะฒะฝะตัˆะฝะตะณะพ ะฐัƒะดะธะพะผะธะบัˆะตั€ะฐ.", + "external-mixer-label": "ะšะพะผะฐะฝะดะฐ ะฒะฝะตัˆะฝะตะณะพ ะฐัƒะดะธะพะผะธะบัˆะตั€ะฐ", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "ะฃัั‚ะฐะฝะพะฒะธั‚ะต ะฟั€ะตะดะฟะพั‡ะธั‚ะฐะตะผั‹ะต ะธ ะธะณะฝะพั€ะธั€ัƒะตะผั‹ะต ะผะตะดะธะฐ-ะฟั€ะธะปะพะถะตะฝะธั.", + "media-excluded-player-description": "ะ”ะพะฑะฐะฒัŒั‚ะต ะบะปัŽั‡ะตะฒั‹ะต ัะปะพะฒะฐ ะดะปั ะฟะปะตะตั€ะพะฒ, ะบะพั‚ะพั€ั‹ะต ัะธัั‚ะตะผะฐ ะดะพะปะถะฝะฐ ะธะณะฝะพั€ะธั€ะพะฒะฐั‚ัŒ. ะšะฐะถะดะพะต ะบะปัŽั‡ะตะฒะพะต ัะปะพะฒะพ ะดะพะปะถะฝะพ ะฑั‹ั‚ัŒ ะฝะฐ ะฝะพะฒะพะน ัั‚ั€ะพะบะต.", + "media-excluded-player-label": "ะ˜ัะบะปัŽั‡ะตะฝะฝั‹ะน ะฟะปะตะตั€", + "media-excluded-player-placeholder": "ะฒะฒะตะดะธั‚ะต ะฟะพะดัั‚ั€ะพะบัƒ ะธ ะฝะฐะถะผะธั‚ะต +", + "media-frame-rate-description": "ะ‘ะพะปะตะต ะฒั‹ัะพะบะธะต ั‡ะฐัั‚ะพั‚ั‹ ะบะฐะดั€ะพะฒ ะพะฑะตัะฟะตั‡ะธะฒะฐัŽั‚ ะฑะพะปะตะต ะฟะปะฐะฒะฝะพะต ะธะทะพะฑั€ะฐะถะตะฝะธะต, ะฝะพ ะธัะฟะพะปัŒะทัƒัŽั‚ ะฑะพะปัŒัˆะต ั€ะตััƒั€ัะพะฒ.", + "media-frame-rate-label": "ะงะฐัั‚ะพั‚ะฐ ะบะฐะดั€ะพะฒ", + "media-primary-player-description": "ะ’ะฒะตะดะธั‚ะต ะบะปัŽั‡ะตะฒะพะต ัะปะพะฒะพ ะดะปั ะธะดะตะฝั‚ะธั„ะธะบะฐั†ะธะธ ะฒะฐัˆะตะณะพ ะพัะฝะพะฒะฝะพะณะพ ะฟะปะตะตั€ะฐ.", + "media-primary-player-label": "ะžัะฝะพะฒะฝะพะน ะฟะปะตะตั€", + "media-primary-player-placeholder": "ะฝะฐะฟั€. spotify, vlc, mpv", + "media-scrolling-speed-description": "ะ’ั€ะตะผั ะฒ ัะตะบัƒะฝะดะฐั…, ะทะฐ ะบะพั‚ะพั€ะพะต ะทะฐะณะพะปะพะฒะพะบ ะฟั€ะพะบั€ัƒั‡ะธะฒะฐะตั‚ัั ะพั‚ ะฝะฐั‡ะฐะปะฐ ะดะพ ะบะพะฝั†ะฐ.", + "media-scrolling-speed-label": "ะกะบะพั€ะพัั‚ัŒ ะฟั€ะพะบั€ัƒั‚ะบะธ", + "media-scrolling-title-description": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะฝะตะฟั€ะตั€ั‹ะฒะฝัƒัŽ ะฟั€ะพะบั€ัƒั‚ะบัƒ ะดะปั ะดะปะธะฝะฝั‹ั… ะฝะฐะทะฒะฐะฝะธะน ะผะตะดะธะฐ.", + "media-scrolling-title-label": "ะŸั€ะพะบั€ัƒั‚ะบะฐ ะฝะฐะทะฒะฐะฝะธั", + "media-title": "ะœะตะดะธะฐะฟะปะตะตั€ั‹", + "on-middle-clicked-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟั€ะธ ะฝะฐะถะฐั‚ะธะธ ัั€ะตะดะฝะตะน ะบะฝะพะฟะบะธ ะผั‹ัˆะธ.", + "panel-applications-empty": "ะ’ ะดะฐะฝะฝั‹ะน ะผะพะผะตะฝั‚ ะฝะตั‚ ะฟั€ะธะปะพะถะตะฝะธะน, ะฒะพัะฟั€ะพะธะทะฒะพะดัั‰ะธั… ะทะฒัƒะบ", + "spectrum-mirrored-description": "ะ—ะตั€ะบะฐะปัŒะฝะพ ะพั‚ะพะฑั€ะฐะทะธั‚ะต ัะฟะตะบั‚ั€, ั‡ั‚ะพะฑั‹ ะฝะธะทะบะธะต ั‡ะฐัั‚ะพั‚ั‹ ะฑั‹ะปะธ ะฒ ั†ะตะฝั‚ั€ะต. ะšะพะณะดะฐ ะฒั‹ะบะปัŽั‡ะตะฝะพ, ะฝะธะทะบะธะต ั‡ะฐัั‚ะพั‚ั‹ ะฝะฐั…ะพะดัั‚ัั ัะปะตะฒะฐ, ะฐ ะฒั‹ัะพะบะธะต โ€” ัะฟั€ะฐะฒะฐ.", + "spectrum-mirrored-label": "ะ—ะตั€ะบะฐะปัŒะฝั‹ะน ัะฟะตะบั‚ั€", + "title": "ะัƒะดะธะพ", + "visualizer-type-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ั‚ะธะฟ ะฒะธะทัƒะฐะปะธะทะฐั†ะธะธ ะดะปั ะฒะพัะฟั€ะพะธะทะฒะตะดะตะฝะธั ะผะตะดะธะฐ.", + "visualizer-type-label": "ะขะธะฟ ะฒะธะทัƒะฐะปะธะทะฐั†ะธะธ", + "volumes-desc": "ะะฐัั‚ั€ะพะนะบะฐ ั€ะตะณัƒะปัั‚ะพั€ะพะฒ ะณั€ะพะผะบะพัั‚ะธ ะธ ัƒั€ะพะฒะฝะตะน ะฐัƒะดะธะพ.", + "volumes-feedback-sound-file-description": "ะŸัƒั‚ัŒ ะบ ะทะฒัƒะบะพะฒะพะผัƒ ั„ะฐะนะปัƒ, ะฒะพัะฟั€ะพะธะทะฒะพะดะธะผะพะผัƒ ะฟั€ะธ ะธะทะผะตะฝะตะฝะธะธ ะณั€ะพะผะบะพัั‚ะธ.", + "volumes-feedback-sound-file-label": "ะ—ะฒัƒะบะพะฒะพะน ั„ะฐะนะป ะพะฑั€ะฐั‚ะฝะพะน ัะฒัะทะธ ะณั€ะพะผะบะพัั‚ะธ", + "volumes-feedback-sound-file-select-title": "ะ’ั‹ะฑั€ะฐั‚ัŒ ะทะฒัƒะบะพะฒะพะน ั„ะฐะนะป ะพะฑั€ะฐั‚ะฝะพะน ัะฒัะทะธ ะณั€ะพะผะบะพัั‚ะธ", + "volumes-input-volume-description": "ะฃั€ะพะฒะตะฝัŒ ะณั€ะพะผะบะพัั‚ะธ ะฒะฒะพะดะฐ ั ะผะธะบั€ะพั„ะพะฝะฐ.", + "volumes-mute-input-description": "ะžั‚ะบะปัŽั‡ะธั‚ัŒ ัั‚ะฐะฝะดะฐั€ั‚ะฝั‹ะน ะฐัƒะดะธะพะฒั…ะพะด (ะผะธะบั€ะพั„ะพะฝ).", + "volumes-mute-input-label": "ะžั‚ะบะปัŽั‡ะธั‚ัŒ ะฐัƒะดะธะพะฒั…ะพะด", + "volumes-mute-output-description": "ะžั‚ะบะปัŽั‡ะธั‚ัŒ ะพัะฝะพะฒะฝะพะน ัะธัั‚ะตะผะฝั‹ะน ะฐัƒะดะธะพะฒั‹ั…ะพะด.", + "volumes-mute-output-label": "ะžั‚ะบะปัŽั‡ะธั‚ัŒ ะฐัƒะดะธะพะฒั‹ั…ะพะด", + "volumes-output-volume-description": "ะžะฑั‰ะตัะธัั‚ะตะผะฝั‹ะน ัƒั€ะพะฒะตะฝัŒ ะณั€ะพะผะบะพัั‚ะธ.", + "volumes-step-size-description": "ะะฐัั‚ั€ะพะนะบะฐ ัˆะฐะณะฐ ะธะทะผะตะฝะตะฝะธั ะณั€ะพะผะบะพัั‚ะธ (ะบะพะปะตัะพ ะฟั€ะพะบั€ัƒั‚ะบะธ, ัะพั‡ะตั‚ะฐะฝะธั ะบะปะฐะฒะธัˆ).", + "volumes-step-size-label": "ะจะฐะณ ะธะทะผะตะฝะตะฝะธั ะณั€ะพะผะบะพัั‚ะธ", + "volumes-volume-feedback-description": "ะ’ะพัะฟั€ะพะธะทะฒะพะดะธั‚ัŒ ะทะฒัƒะบ ะฟั€ะธ ั€ะตะณัƒะปะธั€ะพะฒะบะต ะณั€ะพะผะบะพัั‚ะธ.", + "volumes-volume-feedback-label": "ะ’ะพัะฟั€ะพะธะทะฒะตัั‚ะธ ะทะฒัƒะบ ะพะฑั€ะฐั‚ะฝะพะน ัะฒัะทะธ ะณั€ะพะผะบะพัั‚ะธ", + "volumes-volume-overdrive-description": "ะ ะฐะทั€ะตัˆะธั‚ัŒ ะฟะพะฒั‹ัˆะตะฝะธะต ะณั€ะพะผะบะพัั‚ะธ ะฒั‹ัˆะต 100%. ะœะพะถะตั‚ ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐั‚ัŒัั ะฒัะตะผ ะพะฑะพั€ัƒะดะพะฒะฐะฝะธะตะผ.", + "volumes-volume-overdrive-label": "ะ ะฐะทั€ะตัˆะธั‚ัŒ ะฟะตั€ะตะณั€ัƒะทะบัƒ ะณั€ะพะผะบะพัั‚ะธ" + }, + "bar": { + "appearance-auto-hide-delay-description": "ะ’ั€ะตะผั, ั‡ะตั€ะตะท ะบะพั‚ะพั€ะพะต Bar ัะบั€ะพะตั‚ัั ะฟะพัะปะต ั‚ะพะณะพ, ะบะฐะบ ะบัƒั€ัะพั€ ะผั‹ัˆะธ ะฟะพะบะธะฝะตั‚ ะตะณะพ.", + "appearance-auto-hide-delay-label": "ะ—ะฐะดะตั€ะถะบะฐ ัะบั€ั‹ั‚ะธั", + "appearance-auto-hide-exclusive-note": "ะŸั€ะธะผะตั‡ะฐะฝะธะต: ะญะบัะบะปัŽะทะธะฒะฝะฐั ะทะพะฝะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะพั‚ะบะปัŽั‡ะฐะตั‚ัั, ะบะพะณะดะฐ ะฐะฒั‚ะพัะบั€ั‹ั‚ะธะต ะฐะบั‚ะธะฒะฝะพ.", + "appearance-auto-show-delay-description": "ะ’ั€ะตะผั ะดะพ ะฟะพัะฒะปะตะฝะธั ะฟะฐะฝะตะปะธ, ะบะพะณะดะฐ ะผั‹ัˆัŒ ะฒั…ะพะดะธั‚ ะฒ ะบั€ะฐะน.", + "appearance-auto-show-delay-label": "ะ—ะฐะดะตั€ะถะบะฐ ะพั‚ะพะฑั€ะฐะถะตะฝะธั", + "appearance-background-opacity-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ะต ะฟั€ะพะทั€ะฐั‡ะฝะพัั‚ัŒ ั„ะพะฝะฐ ัะฟะตั†ะธะฐะปัŒะฝะพ ะดะปั ะฟะฐะฝะตะปะธ.", + "appearance-background-opacity-label": "ะŸั€ะพะทั€ะฐั‡ะฝะพัั‚ัŒ ั„ะพะฝะฐ ะฑะฐั€ะฐ", + "appearance-capsule-color-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ั†ะฒะตั‚ ะดะปั ะบะฐะฟััƒะป ะฟะฐะฝะตะปะธ ะธะปะธ ะฝะต ะธัะฟะพะปัŒะทัƒะนั‚ะต ะฝะธะบะฐะบะพะน ะดะปั ั†ะฒะตั‚ะฐ ะฟะพะฒะตั€ั…ะฝะพัั‚ะธ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ.", + "appearance-capsule-color-label": "ะฆะฒะตั‚ ะบะฐะฟััƒะปั‹", + "appearance-capsule-opacity-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ัƒั€ะพะฒะตะฝัŒ ะฝะตะฟั€ะพะทั€ะฐั‡ะฝะพัั‚ะธ ะดะปั ั„ะพะฝะฐ ะฒะธะดะถะตั‚ะพะฒ, ะบะพะณะดะฐ ะบะฐะฟััƒะปะฐ ะพั‚ะพะฑั€ะฐะถะฐะตั‚ัั.", + "appearance-capsule-opacity-label": "ะะตะฟั€ะพะทั€ะฐั‡ะฝะพัั‚ัŒ ะบะฐะฟััƒะปั‹", + "appearance-content-padding-description": "ะžั‚ั€ะตะณัƒะปะธั€ัƒะนั‚ะต ะพั‚ัั‚ัƒะฟ ะผะตะถะดัƒ ะบั€ะฐัะผะธ ะฟะฐะฝะตะปะธ ะธ ะฒะธะดะถะตั‚ะฐะผะธ.", + "appearance-content-padding-label": "ะžั‚ัั‚ัƒะฟ ัะพะดะตั€ะถะธะผะพะณะพ", + "appearance-density-description": "ะะฐัั‚ั€ะพะนะบะฐ ะพั‚ัั‚ัƒะฟะพะฒ ะฟะฐะฝะตะปะธ ะดะปั ะบะพะผะฟะฐะบั‚ะฝะพะณะพ ะธะปะธ ะฟั€ะพัั‚ะพั€ะฝะพะณะพ ะฒะธะดะฐ.", + "appearance-density-label": "ะŸะปะพั‚ะฝะพัั‚ัŒ ะฟะฐะฝะตะปะธ", + "appearance-desc": "ะะฐัั‚ั€ะพะนะบะฐ ะฒะฝะตัˆะฝะตะณะพ ะฒะธะดะฐ ะธ ะฟะพะปะพะถะตะฝะธั ะฟะฐะฝะตะปะธ.", + "appearance-display-mode-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะบะพะณะดะฐ ะฟะฐะฝะตะปัŒ ะฒะธะดะฝะฐ.", + "appearance-enable-exclusion-zone-inset-description": "ะฃะผะตะฝัŒัˆะธั‚ะต ะทะพะฝัƒ ะธัะบะปัŽั‡ะตะฝะธั ะฝะฐ 1 ั„ะธะทะธั‡ะตัะบะธะน ะฟะธะบัะตะปัŒ, ั‡ั‚ะพะฑั‹ ะพะบะฝะฐ ะฒะฟะปะพั‚ะฝัƒัŽ ะธะดะตะฐะปัŒะฝะพ ะทะฐั…ะพะดะธะปะธ ะฟะพะด ะบั€ะฐะน ะฟะฐะฝะตะปะธ.", + "appearance-enable-exclusion-zone-inset-label": "ะ’ะฝัƒั‚ั€ะตะฝะฝัั ะทะพะฝะฐ ะธัะบะปัŽั‡ะตะฝะธั", + "appearance-floating-description": "ะžั‚ะพะฑั€ะฐะถะฐะตั‚ ะฟะฐะฝะตะปัŒ ะบะฐะบ ะฟะปะฐะฒะฐัŽั‰ัƒัŽ 'ั‚ะฐะฑะปะตั‚ะบัƒ'.", + "appearance-floating-label": "ะŸะปะฐะฒะฐัŽั‰ะฐั ะฟะฐะฝะตะปัŒ", + "appearance-font-scale-description": "ะะฐัั‚ั€ะพะนั‚ะต ะผะฐััˆั‚ะฐะฑ ั€ะฐะทะผะตั€ะฐ ัˆั€ะธั„ั‚ะฐ ะดะปั ั‚ะตะบัั‚ะฐ, ะพั‚ะพะฑั€ะฐะถะฐะตะผะพะณะพ ะฝะฐ ะฟะฐะฝะตะปะธ.", + "appearance-font-scale-label": "ะœะฐััˆั‚ะฐะฑ ัˆั€ะธั„ั‚ะฐ", + "appearance-frame-radius": "ะ’ะฝัƒั‚ั€ะตะฝะฝะธะน ั€ะฐะดะธัƒั", + "appearance-frame-settings-description": "ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะปั‰ะธะฝัƒ ั€ะฐะผะบะธ ะธ ะฒะฝัƒั‚ั€ะตะฝะฝะธะน ั€ะฐะดะธัƒั ัƒะณะปะพะฒ", + "appearance-frame-settings-label": "ะะฐัั‚ั€ะพะนะบะธ ั€ะฐะผะบะธ", + "appearance-frame-thickness": "ะขะพะปั‰ะธะฝะฐ", + "appearance-hide-on-overview-description": "ะกะบั€ั‹ั‚ัŒ ะฟะฐะฝะตะปัŒ ะธ ะทะฐะบั€ั‹ั‚ัŒ ะฟะฐะฝะตะปะธ, ะบะพะณะดะฐ ะฐะบั‚ะธะฒะตะฝ ะพะฑะทะพั€ ะบะพะผะฟะพะฝะพะฒั‰ะธะบะฐ.", + "appearance-hide-on-overview-label": "ะกะบั€ั‹ั‚ัŒ ะฟะฐะฝะตะปัŒ ะฒ ะพะฑะทะพั€ะต", + "appearance-margins-description": "ะะฐัั‚ั€ะพะนะบะฐ ะพั‚ัั‚ัƒะฟะพะฒ ะฒะพะบั€ัƒะณ ะฟะปะฐะฒะฐัŽั‰ะตะน ะฟะฐะฝะตะปะธ.", + "appearance-margins-horizontal": "ะ“ะพั€ะธะทะพะฝั‚ะฐะปัŒะฝั‹ะน ะพั‚ัั‚ัƒะฟ", + "appearance-margins-vertical": "ะ’ะตั€ั‚ะธะบะฐะปัŒะฝั‹ะน ะพั‚ัั‚ัƒะฟ", + "appearance-outer-corners-description": "ะžั‚ะพะฑั€ะฐะถะฐะตั‚ ะฒะฝะตัˆะฝะต ะธะทะพะณะฝัƒั‚ั‹ะต ัƒะณะปั‹ ะฝะฐ ะฟะฐะฝะตะปะธ.", + "appearance-outer-corners-label": "ะ’ะฝะตัˆะฝะธะต ัƒะณะปั‹", + "appearance-position-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะณะดะต ั€ะฐะทะผะตัั‚ะธั‚ัŒ ะฟะฐะฝะตะปัŒ ะฝะฐ ัะบั€ะฐะฝะต.", + "appearance-position-label": "ะŸะพะปะพะถะตะฝะธะต ะฟะฐะฝะตะปะธ", + "appearance-show-capsule-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ั„ะพะฝ ะฒะธะดะถะตั‚ะพะฒ.", + "appearance-show-capsule-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะบะฐะฟััƒะปัƒ", + "appearance-show-on-workspace-switch-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะบั€ะฐั‚ะบะพ ะฟะพะบะฐะทั‹ะฒะฐั‚ัŒ Bar ะฟั€ะธ ัะผะตะฝะต ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะฐ.", + "appearance-show-on-workspace-switch-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฟะฐะฝะตะปัŒ ะฟั€ะธ ะฟะตั€ะตะบะปัŽั‡ะตะฝะธะธ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะฐ", + "appearance-show-outline-description": "ะžั‚ะพะฑั€ะฐะถะฐะตั‚ ะฒะธะดะธะผัƒัŽ ะณั€ะฐะฝะธั†ัƒ ะฒะพะบั€ัƒะณ ะบะฐะถะดะพะณะพ ะฒะธะดะถะตั‚ะฐ.", + "appearance-show-outline-label": "ะŸะพะบะฐะทะฐั‚ัŒ ะบะพะฝั‚ัƒั€ั‹ ะฒะธะดะถะตั‚ะพะฒ", + "appearance-type-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ัั‚ะธะปัŒ ะฟะฐะฝะตะปะธ: ะŸั€ะพัั‚ะพะน, ะŸะปะฐะฒะฐัŽั‰ะธะน ะธะปะธ ะ’ ั€ะฐะผะบะต.", + "appearance-type-label": "ะขะธะฟ ะฟะฐะฝะตะปะธ", + "appearance-use-separate-opacity-description": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะต ะพั‚ะดะตะปัŒะฝะพะณะพ ะทะฝะฐั‡ะตะฝะธั ะฟั€ะพะทั€ะฐั‡ะฝะพัั‚ะธ ะดะปั ั„ะพะฝะฐ ะฟะพะปะพัั‹.", + "appearance-use-separate-opacity-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ั€ะฐะทะดะตะปัŒะฝัƒัŽ ะฟั€ะพะทั€ะฐั‡ะฝะพัั‚ัŒ ัั‚ะพะปะฑั†ะพะฒ", + "appearance-widget-spacing-description": "ะžั‚ั€ะตะณัƒะปะธั€ัƒะนั‚ะต ะธะฝั‚ะตั€ะฒะฐะป ะผะตะถะดัƒ ะบะฐะถะดั‹ะผ ะฒะธะดะถะตั‚ะพะผ ะฝะฐ ะฟะฐะฝะตะปะธ.", + "appearance-widget-spacing-label": "ะ ะฐััั‚ะพัะฝะธะต ะผะตะถะดัƒ ะฒะธะดะถะตั‚ะฐะผะธ", + "behavior-middle-click-command-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟะพ ัั€ะตะดะฝะตะผัƒ ะบะปะธะบัƒ.", + "behavior-middle-click-command-label": "ะšะพะผะฐะฝะดะฐ ะกั€ะตะดะฝะตะณะพ ะšะปะธะบะฐ", + "behavior-middle-click-command-placeholder": "niri msg action ะฟะตั€ะตะบะปัŽั‡ะธั‚ัŒ ะพะฑะทะพั€", + "behavior-middle-click-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ะดะตะนัั‚ะฒะธะต ัั€ะตะดะฝะตะน ะบะฝะพะฟะบะธ ะผั‹ัˆะธ ะฝะฐ ะฟัƒัั‚ั‹ั… ะพะฑะปะฐัั‚ัั… ะŸะฐะฝะตะปะธ.", + "behavior-middle-click-follow-mouse-description": "ะžั‚ะบั€ั‹ั‚ัŒ ะฒั‹ะฑั€ะฐะฝะฝัƒัŽ ะฟะฐะฝะตะปัŒ ะฟะพ ัั€ะตะดะฝะตะผัƒ ะบะปะธะบัƒ ะฒ ะฟะพะทะธั†ะธะธ ะบัƒั€ัะพั€ะฐ.", + "behavior-middle-click-follow-mouse-label": "ะกั€ะตะดะฝะธะน ะบะปะธะบ ัะปะตะดัƒะตั‚ ะทะฐ ะบัƒั€ัะพั€ะพะผ", + "behavior-middle-click-label": "ะ”ะตะนัั‚ะฒะธะต ะฟะพ ัั€ะตะดะฝะตะผัƒ ะบะปะธะบัƒ ะฝะฐ ะŸะฐะฝะตะปะธ", + "behavior-right-click-command-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟะพ ะฟั€ะฐะฒะพะผัƒ ะบะปะธะบัƒ.", + "behavior-right-click-command-label": "ะšะพะผะฐะฝะดะฐ ะŸั€ะฐะฒะพะณะพ ะšะปะธะบะฐ", + "behavior-right-click-command-placeholder": "notify-send \"ะŸั€ะฐะฒั‹ะน ะบะปะธะบ\"", + "behavior-right-click-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ะดะตะนัั‚ะฒะธะต ะฟั€ะฐะฒะพะณะพ ั‰ะตะปั‡ะบะฐ ะผั‹ัˆะธ ะฝะฐ ะฟัƒัั‚ั‹ั… ะพะฑะปะฐัั‚ัั… ะฟะฐะฝะตะปะธ.", + "behavior-right-click-follow-mouse-description": "ะžั‚ะบั€ั‹ั‚ัŒ ะฒั‹ะฑั€ะฐะฝะฝัƒัŽ ะฟะฐะฝะตะปัŒ ะฟะพ ะฟั€ะฐะฒะพะผัƒ ะบะปะธะบัƒ ะฒ ะฟะพะทะธั†ะธะธ ะบัƒั€ัะพั€ะฐ.", + "behavior-right-click-follow-mouse-label": "ะŸั€ะฐะฒั‹ะน ะบะปะธะบ ัะปะตะดัƒะตั‚ ะทะฐ ะบัƒั€ัะพั€ะพะผ", + "behavior-right-click-label": "ะ”ะตะนัั‚ะฒะธะต ะฟะพ ะฟั€ะฐะฒะพะผัƒ ะบะปะธะบัƒ ะฝะฐ ะŸะฐะฝะตะปะธ", + "behavior-wheel-wrap-description": "ะ•ัะปะธ ะฒะบะปัŽั‡ะตะฝะพ, ะฟั€ะพะบั€ัƒั‚ะบะฐ ะฟั€ะพะดะพะปะถะฐะตั‚ัั ั ะฟะพัะปะตะดะฝะตะณะพ ัะปะตะผะตะฝั‚ะฐ ะบ ะฟะตั€ะฒะพะผัƒ.", + "behavior-wheel-wrap-label": "ะŸะพ ะบั€ัƒะณัƒ", + "behavior-workspace-scroll-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ั‡ั‚ะพ ะดะตะปะฐะตั‚ ะบะพะปะตัะพ ะผั‹ัˆะธ ะฝะฐ ะฟัƒัั‚ั‹ั… ะพะฑะปะฐัั‚ัั… ะฟะฐะฝะตะปะธ.", + "behavior-workspace-scroll-label": "ะ”ะตะนัั‚ะฒะธะต ะบะพะปะตัะฐ ะผั‹ัˆะธ ะŸะฐะฝะตะปะธ", + "behavior-workspace-scroll-option-content": "ะกะพะดะตั€ะถะธะผะพะต", + "behavior-workspace-scroll-option-workspace": "ะ ะฐะฑะพั‡ะฐั ะพะฑะปะฐัั‚ัŒ", + "monitor-configure-widgets": "ะะฐัั‚ั€ะพะธั‚ัŒ ะฒะธะดะถะตั‚ั‹", + "monitor-override-settings": "ะŸะตั€ะตะพะฟั€ะตะดะตะปะธั‚ัŒ ะณะปะพะฑะฐะปัŒะฝั‹ะต ะฝะฐัั‚ั€ะพะนะบะธ", + "monitor-override-settings-description": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะต ะฝะฐัั‚ั€ะพะนะบะธ ะดะปั ัั‚ะพะณะพ ะผะพะฝะธั‚ะพั€ะฐ.", + "monitor-reset-all": "ะกะฑั€ะพัะธั‚ัŒ ะฒัะต", + "monitor-widgets-title": "ะะฐัั‚ั€ะพะนะบะฐ ะฒะธะดะถะตั‚ะฐ - {monitor}", + "monitors-desc": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฟะฐะฝะตะปัŒ ะฝะฐ ะพะฟั€ะตะดะตะปั‘ะฝะฝั‹ั… ะผะพะฝะธั‚ะพั€ะฐั…. ะŸะพ ัƒะผะพะปั‡ะฐะฝะธัŽ ะฝะฐ ะฒัะตั…, ะตัะปะธ ะฝะธ ะพะดะธะฝ ะฝะต ะฒั‹ะฑั€ะฐะฝ.", + "monitors-desc-new": "ะะฐัั‚ั€ะพะนั‚ะต, ะฝะฐ ะบะฐะบะธั… ะผะพะฝะธั‚ะพั€ะฐั… ะพั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะฟะฐะฝะตะปัŒ, ะธ ะฝะฐัั‚ั€ะพะนั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ั‹ ะดะปั ะบะฐะถะดะพะณะพ ะผะพะฝะธั‚ะพั€ะฐ.", + "monitors-title": "ะžั‚ะพะฑั€ะฐะถะตะฝะธะต ะฝะฐ ะผะพะฝะธั‚ะพั€ะฐั…", + "title": "ะŸะฐะฝะตะปัŒ", + "tray-blacklist-description": "ะ”ะพะฑะฐะฒัŒั‚ะต ะฟั€ะฐะฒะธะปะฐ ะธัะบะปัŽั‡ะตะฝะธั ั‚ั€ะตั, ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ ะฟะพะดัั‚ะฐะฝะพะฒะพั‡ะฝั‹ะต ะทะฝะฐะบะธ (*).", + "tray-blacklist-label": "ะงั‘ั€ะฝั‹ะน ัะฟะธัะพะบ", + "tray-blacklist-placeholder": "ะฝะฐะฟั€. nm-applet, Fcitx*", + "tray-pin-application": "ะ—ะฐะบั€ะตะฟะธั‚ัŒ ะฟั€ะธะปะพะถะตะฝะธะต", + "tray-unpin-application": "ะžั‚ะบั€ะตะฟะธั‚ัŒ ะฟั€ะธะปะพะถะตะฝะธะต", + "use-global-widgets": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะณะปะพะฑะฐะปัŒะฝั‹ะต ะฒะธะดะถะตั‚ั‹", + "widgets-desc": "ะŸะตั€ะตั‚ะฐัะบะธะฒะฐะนั‚ะต ะฒะธะดะถะตั‚ั‹, ั‡ั‚ะพะฑั‹ ะธะทะผะตะฝะธั‚ัŒ ะธั… ะฟะพั€ัะดะพะบ. ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต ะบะพะฝั‚ะตะบัั‚ะฝะพะต ะผะตะฝัŽ (ะฟั€ะฐะฒะพะน ะบะฝะพะฟะบะพะน ะผั‹ัˆะธ), ั‡ั‚ะพะฑั‹ ะฟะตั€ะตะผะตั‰ะฐั‚ัŒ ะฒะธะดะถะตั‚ั‹ ะผะตะถะดัƒ ั€ะฐะทะดะตะปะฐะผะธ ะธะปะธ ัƒะดะฐะปัั‚ัŒ ะธั….", + "widgets-title": "ะ ะฐัะฟะพะปะพะถะตะฝะธะต ะฒะธะดะถะตั‚ะพะฒ" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "ะกะพะทะดะฐะฒะฐะนั‚ะต ั†ะฒะตั‚ะพะฒั‹ะต ัั…ะตะผั‹ ะฝะฐ ะพัะฝะพะฒะต ะฒะฐัˆะธั… ะพะฑะพะตะฒ. ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะธะทะฒะปะตะบะฐะตั‚ ั†ะฒะตั‚ะฐ ะดะปั ัะพะทะดะฐะฝะธั ั†ะตะปะพัั‚ะฝะพะน ั‚ะตะผั‹.", + "color-source-use-wallpaper-colors-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ั†ะฒะตั‚ะฐ ะพะฑะพะตะฒ", + "dark-mode-mode-description": "ะ’ะบะปัŽั‡ะฐะตั‚ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะพะต ะฟะตั€ะตะบะปัŽั‡ะตะฝะธะต ะผะตะถะดัƒ ัะฒะตั‚ะปั‹ะผ ะธ ั‚ั‘ะผะฝั‹ะผ ั€ะตะถะธะผะฐะผะธ.", + "dark-mode-mode-label": "ะ ะฐัะฟะธัะฐะฝะธะต ั‚ั‘ะผะฝะพะณะพ ั€ะตะถะธะผะฐ", + "dark-mode-mode-manual": "ะ’ั€ัƒั‡ะฝัƒัŽ", + "dark-mode-mode-off": "ะ’ั‹ะบะป", + "dark-mode-switch-description": "ะŸะตั€ะตะบะปัŽั‡ะฐะตั‚ัั ะฝะฐ ะฑะพะปะตะต ั‚ั‘ะผะฝัƒัŽ ั‚ะตะผัƒ ะดะปั ัƒะดะพะฑัั‚ะฒะฐ ะฟั€ะพัะผะพั‚ั€ะฐ ะฝะพั‡ัŒัŽ.", + "delete-error-description": "ะะต ัƒะดะฐะปะพััŒ ัƒะดะฐะปะธั‚ัŒ {scheme}", + "delete-error-title": "ะžัˆะธะฑะบะฐ ัƒะดะฐะปะตะฝะธั", + "delete-success-description": "{scheme} ัƒัะฟะตัˆะฝะพ ัƒะดะฐะปะตะฝะฐ", + "delete-success-title": "ะฆะฒะตั‚ะพะฒะฐั ัั…ะตะผะฐ ัƒะดะฐะปะตะฝะฐ", + "download-button": "ะ—ะฐะณั€ัƒะทะธั‚ัŒ ะตั‰ั‘", + "download-downloading": "ะ—ะฐะณั€ัƒะทะบะฐ...", + "download-empty": "ะะตั‚ ะดะพัั‚ัƒะฟะฝั‹ั… ั†ะฒะตั‚ะพะฒั‹ั… ัั…ะตะผ", + "download-error-api-error": "ะžัˆะธะฑะบะฐ API: {status}", + "download-error-description": "ะะต ัƒะดะฐะปะพััŒ ะทะฐะณั€ัƒะทะธั‚ัŒ {scheme}", + "download-error-download-failed": "ะžัˆะธะฑะบะฐ ะทะฐะณั€ัƒะทะบะธ ั ะบะพะดะพะผ ะฒั‹ั…ะพะดะฐ: {code}", + "download-error-invalid-response": "ะะตะฒะตั€ะฝั‹ะน ั„ะพั€ะผะฐั‚ ะพั‚ะฒะตั‚ะฐ API", + "download-error-no-files": "ะคะฐะนะปั‹ ะดะปั ัั…ะตะผั‹ ะฝะต ะฝะฐะนะดะตะฝั‹", + "download-error-parse-failed": "ะžัˆะธะฑะบะฐ ะพะฑั€ะฐะฑะพั‚ะบะธ ะพั‚ะฒะตั‚ะฐ API: {error}", + "download-error-rate-limit": "ะŸั€ะตะฒั‹ัˆะตะฝ ะปะธะผะธั‚ ะทะฐะฟั€ะพัะพะฒ GitHub API", + "download-error-title": "ะžัˆะธะฑะบะฐ ะทะฐะณั€ัƒะทะบะธ", + "download-fetching": "ะŸะพะปัƒั‡ะตะฝะธะต ะดะพัั‚ัƒะฟะฝั‹ั… ั†ะฒะตั‚ะพะฒั‹ั… ัั…ะตะผ...", + "download-success-description": "{scheme} ัƒัะฟะตัˆะฝะพ ะทะฐะณั€ัƒะถะตะฝะฐ", + "download-success-title": "ะฆะฒะตั‚ะพะฒะฐั ัั…ะตะผะฐ ะทะฐะณั€ัƒะถะตะฝะฐ", + "download-title": "ะ—ะฐะณั€ัƒะทะธั‚ัŒ ั†ะฒะตั‚ะพะฒั‹ะต ัั…ะตะผั‹", + "method-description": { + "content": "ะกั…ะตะผะฐ Material Design ั ะฒั‹ัะพะบะพั‚ะพั‡ะฝะพะน ัะบัั‚ั€ะฐะบั†ะธะตะน ั†ะฒะตั‚ะพะฒ, ะบะพั‚ะพั€ะฐั ั‚ะพั‡ะฝะพ ัะพะพั‚ะฒะตั‚ัั‚ะฒัƒะตั‚ ั„ะฐะบั‚ะธั‡ะตัะบะธะผ ั†ะฒะตั‚ะฐะผ ะธัั…ะพะดะฝะพะณะพ ะบะพะฝั‚ะตะฝั‚ะฐ.", + "dysfunctional": "ะšะฐะบ Faithful, ะฝะพ ะฒั‹ะฑะธั€ะฐะตั‚ ะฒั‚ะพั€ะพะต ะฟะพ ะดะพะผะธะฝะฐะฝั‚ะฝะพัั‚ะธ ั†ะฒะตั‚ะพะฒะพะต ัะตะผะตะนัั‚ะฒะพ ะฒ ะบะฐั‡ะตัั‚ะฒะต ะพัะฝะพะฒะฝะพะณะพ.", + "faithful": "ะŸั‹ั‚ะฐะตั‚ัั ะพัั‚ะฐะฒะฐั‚ัŒัั ะฑะปะธะทะบะธะผ ะบ ะธัั…ะพะดะฝะพะผัƒ ั†ะฒะตั‚ัƒ, ัะพะทะดะฐะฒะฐั ะฟั€ะธ ัั‚ะพะผ ะณะฐั€ะผะพะฝะธั‡ะฝัƒัŽ ะฟะฐะปะธั‚ั€ัƒ.", + "fruit-salad": "ะกั…ะตะผะฐ Material Design, ัะพะทะดะฐัŽั‰ะฐั ะธะณั€ะธะฒัƒัŽ, ัั€ะบัƒัŽ ะฟะฐะปะธั‚ั€ัƒ ั ั€ะฐะทะฝะพะพะฑั€ะฐะทะฝั‹ะผะธ ะพั‚ั‚ะตะฝะบะฐะผะธ.", + "monochrome": "ะกั…ะตะผะฐ Material Design, ะธัะฟะพะปัŒะทัƒัŽั‰ะฐั ะพะดะฝะพั‚ะพะฝะฝัƒัŽ ัˆะบะฐะปัƒ ัะตั€ะพะณะพ ั ะผะธะฝะธะผะฐะปัŒะฝั‹ะผ ั…ั€ะพะผะฐั‚ะธั‡ะตัะบะธะผ ัะพะดะตั€ะถะฐะฝะธะตะผ.", + "muted": "ะกะพะทะดะฐะตั‚ ะฑะพะปะตะต ะผัะณะบะธะต, ะฟั€ะธะณะปัƒัˆะตะฝะฝั‹ะต ั‚ะพะฝะฐ ะดะปั ัะฟะพะบะพะนะฝะพะณะพ ะธ ัะดะตั€ะถะฐะฝะฝะพะณะพ ะฒะธะดะฐ.", + "rainbow": "ะกั…ะตะผะฐ Material Design, ะบะพั‚ะพั€ะฐั ะณะตะฝะตั€ะธั€ัƒะตั‚ ั†ะฒะตั‚ะฐ ะฟะพ ะฒัะตะผัƒ ัะฟะตะบั‚ั€ัƒ ะดะปั ะผะฐะบัะธะผะฐะปัŒะฝะพะณะพ ั€ะฐะทะฝะพะพะฑั€ะฐะทะธั ั†ะฒะตั‚ะพะฒ.", + "tonal-spot": "ะะปะณะพั€ะธั‚ะผ Material Design ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ. ะกะพะทะดะฐะตั‚ ัะฑะฐะปะฐะฝัะธั€ะพะฒะฐะฝะฝั‹ะต ะฟะฐะปะธั‚ั€ั‹, ะธัะฟะพะปัŒะทัƒั ั‚ะพะฝะฐะปัŒะฝั‹ะต ะฒะฐั€ะธะฐั†ะธะธ ะธัั…ะพะดะฝะพะณะพ ั†ะฒะตั‚ะฐ.", + "vibrant": "ะœะฐะบัะธะผะธะทะธั€ัƒะตั‚ ะฝะฐัั‹ั‰ะตะฝะฝะพัั‚ัŒ ั†ะฒะตั‚ะฐ ะดะปั ัะผะตะปั‹ั…, ะฟั€ะธะฒะปะตะบะฐั‚ะตะปัŒะฝั‹ั… ะฟะฐะปะธั‚ั€ ั ัั€ะบะธะผะธ ั‚ะพะฝะฐะผะธ." + }, + "predefined-desc": "ะ’ั‹ะฑะตั€ะธั‚ะต ะธะท ะบะพะปะปะตะบั†ะธะธ ะฟั€ะตะดะพะฟั€ะตะดะตะปั‘ะฝะฝั‹ั… ั†ะฒะตั‚ะพะฒั‹ั… ัั…ะตะผ.", + "predefined-generate-templates-label": "ะ“ะตะฝะตั€ะธั€ะพะฒะฐั‚ัŒ ัˆะฐะฑะปะพะฝั‹ ะดะปั ะฟั€ะตะดะพะฟั€ะตะดะตะปั‘ะฝะฝั‹ั… ัั…ะตะผ", + "predefined-title": "ะŸั€ะตะดะพะฟั€ะตะดะตะปั‘ะฝะฝั‹ะต ั†ะฒะตั‚ะพะฒั‹ะต ัั…ะตะผั‹", + "sync-gsettings-description": "ะกะพะฟะพัั‚ะฐะฒะธั‚ัŒ ัะธัั‚ะตะผะฝัƒัŽ ั‚ะตะผัƒ ั ะฐะบั‚ะธะฒะฝั‹ะผ ัะฒะตั‚ะปั‹ะผ ะธะปะธ ั‚ะตะผะฝั‹ะผ ะฒะฐั€ะธะฐะฝั‚ะพะผ.", + "sync-gsettings-label": "ะกะธะฝั…ั€ะพะฝะธะทะธั€ะพะฒะฐั‚ัŒ ัะธัั‚ะตะผะฝัƒัŽ ั‚ะตะผัƒ", + "templates-desc": "ะŸั€ะธะผะตะฝะตะฝะธะต ั†ะฒะตั‚ะพะฒ ะบ ะฒะฝะตัˆะฝะธะผ ะฟั€ะธะปะพะถะตะฝะธัะผ.", + "templates-filter-description": "ะŸะพะบะฐะทะฐั‚ัŒ ัˆะฐะฑะปะพะฝั‹ ะธะท ะพะฟั€ะตะดะตะปั‘ะฝะฝะพะน ะบะฐั‚ะตะณะพั€ะธะธ.", + "templates-filter-label": "ะคะธะปัŒั‚ั€ะพะฒะฐั‚ัŒ ะฟะพ ะบะฐั‚ะตะณะพั€ะธะธ", + "templates-misc-description": "ะกะพะทะดะฐะนั‚ะต ัะฒะพะธ ัะพะฑัั‚ะฒะตะฝะฝั‹ะต ัˆะฐะฑะปะพะฝั‹.", + "templates-misc-label": "ะ”ะพะฟะพะปะฝะธั‚ะตะปัŒะฝะพ", + "templates-misc-user-templates-description": "ะ’ะบะปัŽั‡ะฐะนั‚ะต ั‚ะพะปัŒะบะพ ะตัะปะธ ะฒั‹ ะทะฝะฐะตั‚ะต, ั‡ั‚ะพ ะดะตะปะฐะตั‚ะต โ€” ะพะฑั€ะฐั‚ะธั‚ะตััŒ ะบ ะฝะฐัˆะตะน ะพะฝะปะฐะนะฝ-ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ.", + "templates-misc-user-templates-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะต ัˆะฐะฑะปะพะฝั‹", + "templates-none-detected": "ะะต ะพะฑะฝะฐั€ัƒะถะตะฝะพ", + "templates-write-path": "ะ—ะฐะฟะธัั‹ะฒะฐะตั‚: {filepath}", + "title": "ะฆะฒะตั‚ะพะฒะฐั ัั…ะตะผะฐ", + "wallpaper-method-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ะฒะฐัˆ ะปัŽะฑะธะผั‹ะน ะผะตั‚ะพะด ะณะตะฝะตั€ะฐั†ะธะธ ะฟะฐะปะธั‚ั€ั‹.", + "wallpaper-method-label": "ะœะตั‚ะพะด ะณะตะฝะตั€ะฐั†ะธะธ ะฟะฐะปะธั‚ั€ั‹", + "wallpaper-monitor-source-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ะผะพะฝะธั‚ะพั€, ะบะพั‚ะพั€ั‹ะน ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั ะดะปั ะธะทะฒะปะตั‡ะตะฝะธั ั†ะฒะตั‚ะพะฒ ะพะฑะพะตะฒ.", + "wallpaper-monitor-source-label": "ะ˜ัั‚ะพั‡ะฝะธะบ ะณะตะฝะตั€ะฐั†ะธะธ ั†ะฒะตั‚ะฐ" + }, + "connections": { + "authentication-required": "ะขั€ะตะฑัƒะตั‚ัั ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธั", + "bluetooth-auto-connect-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะฟะพะดะบะปัŽั‡ะฐั‚ัŒัั ะบ ะดะพะฒะตั€ะตะฝะฝั‹ะผ ัะพะฟั€ัะถะตะฝะฝั‹ะผ ัƒัั‚ั€ะพะนัั‚ะฒะฐะผ ะฟั€ะธ ะฒะบะปัŽั‡ะตะฝะธะธ Bluetooth.", + "bluetooth-auto-connect-label": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะฟะพะดะบะปัŽั‡ะฐั‚ัŒ ัะพะฟั€ัะถะตะฝะฝั‹ะต ัƒัั‚ั€ะพะนัั‚ะฒะฐ", + "bluetooth-devices-unnamed": "ะ‘ะตะทั‹ะผัะฝะฝั‹ะต ัƒัั‚ั€ะพะนัั‚ะฒะฐ ะฝะต ะพั‚ะพะฑั€ะฐะถะฐัŽั‚ัั.", + "bluetooth-discoverable": "ะญั‚ะพ ัƒัั‚ั€ะพะนัั‚ะฒะพ ะพะฑะฝะฐั€ัƒะถะธะฒะฐะตะผะพ ะบะฐะบ {hostName}, ะฟะพะบะฐ ะพั‚ะบั€ั‹ั‚ะฐ ัั‚ะฐ ะฒะบะปะฐะดะบะฐ ะฝะฐัั‚ั€ะพะตะบ.", + "bluetooth-rssi-polling-description": "ะŸะตั€ะธะพะดะธั‡ะตัะบะธ ะพะฟั€ะฐัˆะธะฒะฐะตั‚ RSSI ะดะปั ะฟะพะดะบะปัŽั‡ะตะฝะฝั‹ั… ัƒัั‚ั€ะพะนัั‚ะฒ ั‡ะตั€ะตะท bluetoothctl. ะœะพะถะตั‚ ะฑั‹ั‚ัŒ ะฝะตะดะพัั‚ัƒะฟะฝะพ ะดะปั ะฒัะตั… ัƒัั‚ั€ะพะนัั‚ะฒ; ะฟั€ะธ ะฒะบะปัŽั‡ะตะฝะธะธ ะธัะฟะพะปัŒะทัƒะตั‚ ะผะธะฝะธะผะฐะปัŒะฝั‹ะต ั€ะตััƒั€ัั‹.", + "bluetooth-rssi-polling-interval-description": "ะะฐัั‚ั€ะพะนั‚ะต, ะบะฐะบ ั‡ะฐัั‚ะพ ะพะฑะฝะพะฒะปัั‚ัŒ ัƒั€ะพะฒะตะฝัŒ ัะธะณะฝะฐะปะฐ ะดะปั ะฟะพะดะบะปัŽั‡ะตะฝะฝั‹ั… ัƒัั‚ั€ะพะนัั‚ะฒ.", + "bluetooth-rssi-polling-interval-label": "ะ˜ะฝั‚ะตั€ะฒะฐะป ะพะฟั€ะพัะฐ", + "bluetooth-rssi-polling-label": "ะžะฟั€ะพั ัะธะณะฝะฐะปะฐ Bluetooth", + "disable-discoverability-description": "ะกะบั€ั‹ั‚ัŒ ะฒะฐัˆะต ัƒัั‚ั€ะพะนัั‚ะฒะพ ะพั‚ ะฑะปะธะถะฐะนัˆะธั… Bluetooth-ัƒัั‚ั€ะพะนัั‚ะฒ.", + "disable-discoverability-label": "ะžั‚ะบะปัŽั‡ะธั‚ัŒ ะฒะธะดะธะผะพัั‚ัŒ ัƒัั‚ั€ะพะนัั‚ะฒะฐ", + "hide-unnamed-devices-description": "ะกะบั€ั‹ั‚ัŒ ัƒัั‚ั€ะพะนัั‚ะฒะฐ, ะบะพั‚ะพั€ั‹ะต ะพั‚ะพะฑั€ะฐะถะฐัŽั‚ัั ั‚ะพะปัŒะบะพ ะบะฐะบ Bluetooth-ะฐะดั€ะตัะฐ.", + "hide-unnamed-devices-label": "ะกะบั€ั‹ะฒะฐั‚ัŒ ะฑะตะทั‹ะผัะฝะฝั‹ะต ัƒัั‚ั€ะพะนัั‚ะฒะฐ", + "pin-instructions": "ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฒะฒะตะดะธั‚ะต PIN-ะบะพะด, ะพั‚ะพะฑั€ะฐะถะฐะตะผั‹ะน ะฝะฐ ะฒะฐัˆะตะผ ัƒัั‚ั€ะพะนัั‚ะฒะต.", + "title": "ะŸะพะดะบะปัŽั‡ะตะฝะธั", + "wifi-header-text": "ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต Wi-Fi, ั‡ั‚ะพะฑั‹ ะฟั€ะพัะผะฐั‚ั€ะธะฒะฐั‚ัŒ ะดะพัั‚ัƒะฟะฝั‹ะต ัะตั‚ะธ ะธ ะฟะพะดะบะปัŽั‡ะฐั‚ัŒัั ะบ ะธะฝั‚ะตั€ะฝะตั‚ัƒ." + }, + "control-center": { + "cards-desc": "ะะฐัั‚ั€ะพะนั‚ะต, ะบะฐะบะธะต ัะปะตะผะตะฝั‚ั‹ ัƒะฟั€ะฐะฒะปะตะฝะธั ะฟะพัะฒะปััŽั‚ัั ะฒ ั†ะตะฝั‚ั€ะต ัƒะฟั€ะฐะฒะปะตะฝะธั ะธ ะฒ ะบะฐะบะพะผ ะฟะพั€ัะดะบะต.", + "desc": "ะะฐัั‚ั€ะพะนะบะฐ ะฟะพะทะธั†ะธะพะฝะธั€ะพะฒะฐะฝะธั ะธ ะฟะพะฒะตะดะตะฝะธั ะฟะฐะฝะตะปะธ ั†ะตะฝั‚ั€ะฐ ัƒะฟั€ะฐะฒะปะตะฝะธั.", + "open-at-mouse-description": "ะŸั€ะธ ะฝะฐะถะฐั‚ะธะธ ะฟั€ะฐะฒะพะน ะบะฝะพะฟะบะพะน ะผั‹ัˆะธ ะฝะฐ ะฟัƒัั‚ัƒัŽ ะพะฑะปะฐัั‚ัŒ ะŸะฐะฝะตะปะธ, ะพั‚ะบั€ะพะนั‚ะต ะฆะตะฝั‚ั€ ัƒะฟั€ะฐะฒะปะตะฝะธั ั€ัะดะพะผ ั ะบัƒั€ัะพั€ะพะผ. ะ•ัะปะธ ะพั‚ะบะปัŽั‡ะตะฝะพ, ะฒะผะตัั‚ะพ ัั‚ะพะณะพ ะธัะฟะพะปัŒะทัƒะตั‚ัั ะฝะฐัั‚ั€ะพะนะบะฐ ะฟะพะปะพะถะตะฝะธั ะฒั‹ัˆะต.", + "open-at-mouse-label": "ะžั‚ะบั€ั‹ะฒะฐั‚ัŒ ัƒ ะบัƒั€ัะพั€ะฐ ะฟะพ ะฟั€ะฐะฒะพะผัƒ ะบะปะธะบัƒ", + "position-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะณะดะต ะฟะพัะฒะปัะตั‚ัั ะฟะฐะฝะตะปัŒ ั†ะตะฝั‚ั€ะฐ ัƒะฟั€ะฐะฒะปะตะฝะธั ะฟั€ะธ ะพั‚ะบั€ั‹ั‚ะธะธ.", + "shortcuts-custom-button-command-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟั€ะธ ะฝะฐะถะฐั‚ะธะธ ะฝะฐ ะบะฝะพะฟะบัƒ.", + "shortcuts-custom-button-enable-on-state-logic-description": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะฒั‚ะพั€ัƒัŽ ะธะบะพะฝะบัƒ ะธ 'ะฐะบั‚ะธะฒะฝะพะต' ัะพัั‚ะพัะฝะธะต ะฝะฐ ะพัะฝะพะฒะต ะบะพะผะฐะฝะดั‹ ะฟั€ะพะฒะตั€ะบะธ.", + "shortcuts-custom-button-enable-on-state-logic-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะปะพะณะธะบัƒ ะฐะบั‚ะธะฒะฝะพะณะพ ัะพัั‚ะพัะฝะธั", + "shortcuts-custom-button-icon-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ะทะฝะฐั‡ะพะบ ะธะท ะฑะธะฑะปะธะพั‚ะตะบะธ.", + "shortcuts-custom-button-on-clicked-label": "ะšะพะผะฐะฝะดะฐ ะดะปั ะปะตะฒะพะณะพ ะบะปะธะบะฐ", + "shortcuts-custom-button-on-middle-clicked-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟั€ะธ ะฝะฐะถะฐั‚ะธะธ ัั€ะตะดะฝะตะน ะบะฝะพะฟะบะธ ะผั‹ัˆะธ.", + "shortcuts-custom-button-on-state-command-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฟั€ะพะฒะตั€ะบะธ, ะดะพะปะถะฝะฐ ะปะธ ะบะฝะพะฟะบะฐ ะฝะฐั…ะพะดะธั‚ัŒัั ะฒ 'ะฐะบั‚ะธะฒะฝะพะผ' ัะพัั‚ะพัะฝะธะธ. ะ’ะพะทะฒั€ะฐั‰ะฐะตั‚ 0 ะดะปั 'ะฒะบะปัŽั‡ะตะฝะพ', ะฝะตะฝัƒะปะตะฒะพะต ะดะปั 'ะฒั‹ะบะปัŽั‡ะตะฝะพ'.", + "shortcuts-custom-button-on-state-command-label": "ะšะพะผะฐะฝะดะฐ ะฟั€ะพะฒะตั€ะบะธ ะฐะบั‚ะธะฒะฝะพะณะพ ัะพัั‚ะพัะฝะธั", + "shortcuts-custom-button-on-state-icon-description": "ะ˜ะบะพะฝะบะฐ ะดะปั ะบะฝะพะฟะบะธ, ะบะพะณะดะฐ ะพะฝะฐ ะฝะฐั…ะพะดะธั‚ัั ะฒ 'ะฐะบั‚ะธะฒะฝะพะผ' ัะพัั‚ะพัะฝะธะธ.", + "shortcuts-custom-button-on-state-icon-label": "ะ˜ะบะพะฝะบะฐ ะฐะบั‚ะธะฒะฝะพะณะพ ัะพัั‚ะพัะฝะธั", + "shortcuts-custom-button-state-checks-add": "ะ”ะพะฑะฐะฒะธั‚ัŒ ะฟั€ะพะฒะตั€ะบัƒ ัะพัั‚ะพัะฝะธั", + "shortcuts-custom-button-state-checks-command": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะดะปั ัั‚ะพะน ะฟั€ะพะฒะตั€ะบะธ ัะพัั‚ะพัะฝะธั", + "shortcuts-custom-button-state-checks-label": "ะŸั€ะพะฒะตั€ะบะธ ัะพัั‚ะพัะฝะธั", + "shortcuts-custom-button-state-checks-remove": "ะฃะดะฐะปะธั‚ัŒ", + "shortcuts-custom-button-tooltip-description": "ะŸะพะดัะบะฐะทะบะฐ, ะบะพั‚ะพั€ะฐั ะฑัƒะดะตั‚ ะพั‚ะพะฑั€ะฐะถะฐั‚ัŒัั ะฟั€ะธ ะฝะฐะฒะตะดะตะฝะธะธ ะฝะฐ ะบะฝะพะฟะบัƒ.", + "shortcuts-custom-button-tooltip-label": "ะŸะพะดัะบะฐะทะบะฐ", + "shortcuts-title": "ะ’ะธะดะถะตั‚ั‹ ัั€ะปั‹ะบะพะฒ", + "system-monitor-disk-path-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ั‚ะพั‡ะบัƒ ะผะพะฝั‚ะธั€ะพะฒะฐะฝะธั ะดะธัะบะฐ, ะบะพั‚ะพั€ัƒัŽ ะดะพะปะถะฝะฐ ะพั‚ัะปะตะถะธะฒะฐั‚ัŒ ะบะฐั€ั‚ะฐ ัะธัั‚ะตะผะฝะพะณะพ ะผะพะฝะธั‚ะพั€ะฐ ะฒ ั†ะตะฝั‚ั€ะต ัƒะฟั€ะฐะฒะปะตะฝะธั.", + "system-monitor-disk-path-label": "ะŸัƒั‚ัŒ ะบ ะดะธัะบัƒ ัะธัั‚ะตะผะฝะพะณะพ ะผะพะฝะธั‚ะพั€ะฐ", + "title": "ะฆะตะฝั‚ั€ ัƒะฟั€ะฐะฒะปะตะฝะธั" + }, + "desktop-widgets": { + "clock-enabled-description": "ะŸะพะบะฐะทะฐั‚ัŒ ะฒะธะดะถะตั‚ ั‡ะฐัะพะฒ ะฝะฐ ั€ะฐะฑะพั‡ะตะผ ัั‚ะพะปะต.", + "clock-enabled-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะฒะธะดะถะตั‚ ั‡ะฐัะพะฒ", + "clock-format-label": "ะคะพั€ะผะฐั‚", + "clock-height-description": "ะ’ั‹ัะพั‚ะฐ ะฒะธะดะถะตั‚ะฐ ั‡ะฐัะพะฒ ะฒ ะฟะธะบัะตะปัั….", + "clock-height-label": "ะ’ั‹ัะพั‚ะฐ", + "clock-minimal-mode-description": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะผะธะฝะธะผะฐะปัŒะฝะพะต ะพั‚ะพะฑั€ะฐะถะตะฝะธะต ั‡ะฐัะพะฒ ั ะฒั€ะตะผะตะฝะตะผ ะธ ะดะฐั‚ะพะน.", + "clock-minimal-mode-label": "ะœะธะฝะธะผะฐะปัŒะฝั‹ะน ั€ะตะถะธะผ", + "clock-rounded-corners-description": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ัะบั€ัƒะณะปั‘ะฝะฝั‹ะต ัƒะณะปั‹ ะดะปั ั„ะพะฝะฐ ะฒะธะดะถะตั‚ะฐ.", + "clock-rounded-corners-label": "ะกะบั€ัƒะณะปั‘ะฝะฝั‹ะต ัƒะณะปั‹", + "clock-show-background-description": "ะŸะพะบะฐะทะฐั‚ัŒ ั„ะพะฝะพะฒั‹ะน ะบะพะฝั‚ะตะนะฝะตั€ ะดะปั ะฒะธะดะถะตั‚ะฐ ั‡ะฐัะพะฒ.", + "clock-show-background-label": "ะŸะพะบะฐะทะฐั‚ัŒ ั„ะพะฝ", + "clock-show-date-description": "ะžั‚ะพะฑั€ะฐะทะธั‚ะต ั‚ะตะบัƒั‰ัƒัŽ ะดะฐั‚ัƒ ะฟะพะด ะฒั€ะตะผะตะฝะตะผ.", + "clock-show-date-label": "ะŸะพะบะฐะทะฐั‚ัŒ ะดะฐั‚ัƒ", + "clock-show-seconds-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ัะตะบัƒะฝะดั‹ ะฒะพ ะฒั€ะตะผะตะฝะธ.", + "clock-show-seconds-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ัะตะบัƒะฝะดั‹", + "clock-style-analog": "ะะฝะฐะปะพะณะพะฒั‹ะน", + "clock-style-binary": "ะ‘ะธะฝะฐั€ะฝั‹ะน", + "clock-style-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ัั‚ะธะปัŒ ะพั‚ะพะฑั€ะฐะถะตะฝะธั ั‡ะฐัะพะฒ.", + "clock-style-digital": "ะฆะธั„ั€ะพะฒะพะน", + "clock-style-label": "ะกั‚ะธะปัŒ ั‡ะฐัะพะฒ", + "clock-style-minimal": "ะœะธะฝะธะผะฐะปัŒะฝั‹ะน", + "clock-width-description": "ะจะธั€ะธะฝะฐ ะฒะธะดะถะตั‚ะฐ ั‡ะฐัะพะฒ ะฒ ะฟะธะบัะตะปัั….", + "cpu-intensive-note": "ะ’ะธะดะถะตั‚ั‹, ะพั‚ะผะตั‡ะตะฝะฝั‹ะต ะทะฝะฐะบะพะผ !, ะธัะฟะพะปัŒะทัƒัŽั‚ ะฑะพะปัŒัˆะต CPU ะธ ะดะพะปะถะฝั‹ ะฑั‹ั‚ัŒ ะฒะบะปัŽั‡ะตะฝั‹ ะพะฑะดัƒะผะฐะฝะฝะพ.", + "edit-mode-button-label": "ะ’ะพะนั‚ะธ ะฒ ั€ะตะถะธะผ ั€ะตะดะฐะบั‚ะธั€ะพะฒะฐะฝะธั", + "edit-mode-controls-explanation": "ะ›ะตะฒั‹ะน ะบะปะธะบ ะธ ะฟะตั€ะตั‚ะฐัะบะธะฒะฐะฝะธะต: ะŸะตั€ะตะผะตัั‚ะธั‚ัŒ ะธะปะธ ะธะทะผะตะฝะธั‚ัŒ ั€ะฐะทะผะตั€ ะฒะธะดะถะตั‚ะฐ.\nะŸั€ะฐะฒั‹ะน ะบะปะธะบ: ะžั‚ะบั€ั‹ั‚ัŒ ะบะพะฝั‚ะตะบัั‚ะฝะพะต ะผะตะฝัŽ.", + "edit-mode-description": "ะ’ะบะปัŽั‡ะธั‚ะต ั€ะตะถะธะผ ั€ะตะดะฐะบั‚ะธั€ะพะฒะฐะฝะธั, ั‡ั‚ะพะฑั‹ ะฟะตั€ะตะผะตั‰ะฐั‚ัŒ ะธ ะธะทะผะตะฝัั‚ัŒ ะฟะพะปะพะถะตะฝะธะต ะฒะธะดะถะตั‚ะพะฒ ั€ะฐะฑะพั‡ะตะณะพ ัั‚ะพะปะฐ. ะ’ ัั‚ะพะผ ั€ะตะถะธะผะต ะฒะธะดะถะตั‚ั‹ ะพั‚ะพะฑั€ะฐะถะฐัŽั‚ัั ั ะบะพะฝั‚ัƒั€ะพะผ ะฟะตั€ะตั‚ะฐัะบะธะฒะฐะฝะธั ะธ ะผะพะณัƒั‚ ะฑั‹ั‚ัŒ ะฟะตั€ะตะผะตั‰ะตะฝั‹.", + "edit-mode-exit-button": "ะ’ั‹ะนั‚ะธ ะธะท ั€ะตะถะธะผะฐ ั€ะตะดะฐะบั‚ะธั€ะพะฒะฐะฝะธั", + "edit-mode-grid-snap-label": "ะŸั€ะธะฒัะทะบะฐ ะบ ัะตั‚ะบะต", + "edit-mode-grid-snap-scale-label": "ะœะฐััˆั‚ะฐะฑ ะฟั€ะธะฒัะทะบะธ ะบ ัะตั‚ะบะต", + "edit-mode-label": "ะ ะตะถะธะผ ั€ะตะดะฐะบั‚ะธั€ะพะฒะฐะฝะธั", + "enabled-description": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะธะปะธ ะพั‚ะบะปัŽั‡ะธั‚ัŒ ะฒะธะดะถะตั‚ั‹ ั€ะฐะฑะพั‡ะตะณะพ ัั‚ะพะปะฐ ะฟะพะปะฝะพัั‚ัŒัŽ.", + "enabled-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะฒะธะดะถะตั‚ั‹ ั€ะฐะฑะพั‡ะตะณะพ ัั‚ะพะปะฐ", + "general-desc": "ะะฐัั‚ั€ะพะนั‚ะต ะฒะธะดะถะตั‚ั‹, ะพั‚ะพะฑั€ะฐะถะฐะตะผั‹ะต ะฝะฐ ะฒะฐัˆะตะผ ั€ะฐะฑะพั‡ะตะผ ัั‚ะพะปะต.", + "general-title": "ะะฐัั‚ะพะปัŒะฝั‹ะต ะฒะธะดะถะตั‚ั‹", + "media-player-enabled-description": "ะžั‚ะพะฑั€ะฐะทะธั‚ัŒ ะฒะธะดะถะตั‚ ะผะตะดะธะฐะฟะปะตะตั€ะฐ ะฝะฐ ั€ะฐะฑะพั‡ะตะผ ัั‚ะพะปะต.", + "media-player-enabled-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะฒะธะดะถะตั‚ ะผะตะดะธะฐะฟะปะตะตั€ะฐ", + "media-player-rounded-corners-description": "ะ’ะบะปัŽั‡ะธั‚ัŒ ัะบั€ัƒะณะปะตะฝะธะต ัƒะณะปะพะฒ ะฝะฐ ะบั€ะฐัั… ะฒะธะดะถะตั‚ะฐ.", + "media-player-show-album-art-description": "ะŸะพะบะฐะทะฐั‚ัŒ ะพะฑะปะพะถะบัƒ ะฐะปัŒะฑะพะผะฐ ะธ ะธะฝั„ะพั€ะผะฐั†ะธัŽ ะพ ั‚ั€ะตะบะฐั… (ะฝะฐะทะฒะฐะฝะธะต ะธ ะธัะฟะพะปะฝะธั‚ะตะปัŒ).", + "media-player-show-album-art-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะพะฑะปะพะถะบัƒ ะฐะปัŒะฑะพะผะฐ ะธ ะฝะฐะทะฒะฐะฝะธะต", + "media-player-show-background-description": "ะŸะพะบะฐะทะฐั‚ัŒ ั„ะพะฝะพะฒั‹ะน ะบะพะฝั‚ะตะนะฝะตั€ ะดะปั ะฒะธะดะถะตั‚ะฐ ะผะตะดะธะฐะฟะปะตะตั€ะฐ.", + "media-player-show-buttons-description": "ะŸะพะบะฐะทะฐั‚ัŒ ะบะฝะพะฟะบะธ ัƒะฟั€ะฐะฒะปะตะฝะธั ะผะตะดะธะฐ (ะฒะพัะฟั€ะพะธะทะฒะตะดะตะฝะธะต/ะฟะฐัƒะทะฐ, ะฟั€ะตะดั‹ะดัƒั‰ะธะน, ัะปะตะดัƒัŽั‰ะธะน) ะฝะฐ ะฒะธะดะถะตั‚ะต ะผะตะดะธะฐะฟะปะตะตั€ะฐ.", + "media-player-show-buttons-label": "ะŸะพะบะฐะทะฐั‚ัŒ ะบะฝะพะฟะบะธ", + "media-player-show-visualizer-description": "ะŸะพะบะฐะทะฐั‚ัŒ ะฝะฐะปะพะถะตะฝะธะต ะฐัƒะดะธะพะฒะธะทัƒะฐะปะธะทะฐั‚ะพั€ะฐ.", + "media-player-visualizer-type-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ั‚ะธะฟ ะฒะธะทัƒะฐะปะธะทะฐั†ะธะธ ะดะปั ั„ะพะฝะฐ ะผะตะดะธะฐะฟะปะตะตั€ะฐ.", + "overview-enabled-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฒะธะดะถะตั‚ั‹ ั€ะฐะฑะพั‡ะตะณะพ ัั‚ะพะปะฐ, ะฟะพะบะฐ ะพะฑะทะพั€ ะบะพะผะฟะพะทะธั‚ะพั€ะฐ ะพั‚ะบั€ั‹ั‚.", + "overview-enabled-label": "ะŸะพะบะฐะทะฐั‚ัŒ ะฒ ะพะฑะทะพั€ะต", + "system-stat-layout-bottom": "ะะธะท", + "system-stat-layout-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะบะฐะบ ะพั‚ะพะฑั€ะฐะถะฐะตั‚ัั ะปะตะณะตะฝะดะฐ ะพั‚ะฝะพัะธั‚ะตะปัŒะฝะพ ะณั€ะฐั„ะธะบะฐ.", + "system-stat-layout-label": "ะœะฐะบะตั‚", + "system-stat-layout-side": "ะกั‚ะพั€ะพะฝะฐ", + "system-stat-rounded-corners-description": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ัะบั€ัƒะณะปะตะฝะฝั‹ะต ัƒะณะปั‹ ะดะปั ั„ะพะฝะฐ ะฒะธะดะถะตั‚ะฐ.", + "system-stat-rounded-corners-label": "ะกะบั€ัƒะณะปะตะฝะฝั‹ะต ะฃะณะปั‹", + "system-stat-show-background-description": "ะŸะพะบะฐะทะฐั‚ัŒ ั„ะพะฝะพะฒั‹ะน ะบะพะฝั‚ะตะนะฝะตั€ ะดะปั ะฒะธะดะถะตั‚ะฐ ัะธัั‚ะตะผะฝะพะน ัั‚ะฐั‚ะธัั‚ะธะบะธ.", + "system-stat-show-background-label": "ะŸะพะบะฐะทะฐั‚ัŒ ั„ะพะฝ", + "system-stat-stat-type-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะบะฐะบัƒัŽ ัะธัั‚ะตะผะฝัƒัŽ ัั‚ะฐั‚ะธัั‚ะธะบัƒ ะพั‚ะพะฑั€ะฐะถะฐั‚ัŒ.", + "system-stat-stat-type-label": "ะขะธะฟ ะกั‚ะฐั‚ะธัั‚ะธะบะธ", + "title": "ะะฐัั‚ะพะปัŒะฝั‹ะต ะฒะธะดะถะตั‚ั‹", + "weather-enabled-description": "ะŸะพะบะฐะทะฐั‚ัŒ ะฟะพะณะพะดะฝั‹ะน ะฒะธะดะถะตั‚ ะฝะฐ ั€ะฐะฑะพั‡ะตะผ ัั‚ะพะปะต.", + "weather-enabled-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะฒะธะดะถะตั‚ ะฟะพะณะพะดั‹", + "weather-show-background-description": "ะŸะพะบะฐะทะฐั‚ัŒ ั„ะพะฝะพะฒั‹ะน ะบะพะฝั‚ะตะนะฝะตั€ ะดะปั ะฟะพะณะพะดะฝะพะณะพ ะฒะธะดะถะตั‚ะฐ." + }, + "display": { + "monitors-backlight-device-auto-option": "ะŸะพ ัƒะผะพะปั‡ะฐะฝะธัŽ", + "monitors-backlight-device-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ัƒัั‚ั€ะพะนัั‚ะฒะพ ะฟะพะดัะฒะตั‚ะบะธ ะดะปั ัั‚ะพะณะพ ะฒั‹ะฒะพะดะฐ.", + "monitors-backlight-device-label": "ะฃัั‚ั€ะพะนัั‚ะฒะพ ะŸะพะดัะฒะตั‚ะบะธ", + "monitors-brightness-step-description": "ะะฐัั‚ั€ะพะนะบะฐ ัˆะฐะณะฐ ะธะทะผะตะฝะตะฝะธั ัั€ะบะพัั‚ะธ (ะบะพะปะตัะพ ะฟั€ะพะบั€ัƒั‚ะบะธ ะธ ัะพั‡ะตั‚ะฐะฝะธั ะบะปะฐะฒะธัˆ).", + "monitors-brightness-step-label": "ะจะฐะณ ะธะทะผะตะฝะตะฝะธั ัั€ะบะพัั‚ะธ", + "monitors-brightness-unavailable-ddc-disabled": "ะฃะฟั€ะฐะฒะปะตะฝะธะต ัั€ะบะพัั‚ัŒัŽ ะฝะตะดะพัั‚ัƒะฟะฝะพ. ะ’ะบะปัŽั‡ะธั‚ะต \"ะŸะพะดะดะตั€ะถะบะฐ ะฒะฝะตัˆะฝะตะน ัั€ะบะพัั‚ะธ\", ั‡ั‚ะพะฑั‹ ัƒะฟั€ะฐะฒะปัั‚ัŒ ัั€ะบะพัั‚ัŒัŽ ัั‚ะพะณะพ ะดะธัะฟะปะตั.", + "monitors-brightness-unavailable-generic": "ะฃะฟั€ะฐะฒะปะตะฝะธะต ัั€ะบะพัั‚ัŒัŽ ะฝะตะดะพัั‚ัƒะฟะฝะพ ะดะปั ัั‚ะพะณะพ ะดะธัะฟะปะตั.", + "monitors-desc": "ะะฐัั‚ั€ะพะนะบะฐ ัั€ะบะพัั‚ะธ ะดะปั ะบะฐะถะดะพะณะพ ะดะธัะฟะปะตั.", + "monitors-enforce-minimum-description": "ะ ะตัˆะฐะตั‚ ะฟั€ะพะฑะปะตะผัƒ ะฟะพะปะฝะพะณะพ ะพั‚ะบะปัŽั‡ะตะฝะธั ะฟะพะดัะฒะตั‚ะบะธ ะฝะฐ ะฝะตะบะพั‚ะพั€ั‹ั… ะดะธัะฟะปะตัั… ะฟั€ะธ 0% ัั€ะบะพัั‚ะธ.", + "monitors-enforce-minimum-label": "ะžะฑะตัะฟะตั‡ะธั‚ัŒ ะผะธะฝะธะผะฐะปัŒะฝัƒัŽ ัั€ะบะพัั‚ัŒ (1%)", + "monitors-external-brightness-description": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะฟะพะดะดะตั€ะถะบัƒ DDCUtil ะดะปั ัƒะฟั€ะฐะฒะปะตะฝะธั ัั€ะบะพัั‚ัŒัŽ ะฝะฐ ะฒะฝะตัˆะฝะธั… ะดะธัะฟะปะตัั… ั‡ะตั€ะตะท ะฟั€ะพั‚ะพะบะพะป DDC/CI.", + "monitors-external-brightness-label": "ะŸะพะดะดะตั€ะถะบะฐ ะฒะฝะตัˆะฝะตะน ัั€ะบะพัั‚ะธ", + "monitors-global-brightness-description": "ะะฐัั‚ั€ะพะธั‚ัŒ ัั€ะบะพัั‚ัŒ ะฒัะตั… ะผะพะฝะธั‚ะพั€ะพะฒ ะพะดะฝะพะฒั€ะตะผะตะฝะฝะพ.", + "monitors-global-brightness-label": "ะ’ัะต ะผะพะฝะธั‚ะพั€ั‹", + "monitors-title": "ะะฐัั‚ั€ะพะนะบะธ ะดะปั ะบะฐะถะดะพะณะพ ะผะพะฝะธั‚ะพั€ะฐ", + "night-light-auto-schedule-description": "ะะฐ ะพัะฝะพะฒะต ะฒั€ะตะผะตะฝะธ ะทะฐะบะฐั‚ะฐ ะธ ะฒะพัั…ะพะดะฐ ัะพะปะฝั†ะฐ ะฒ {location} โ€” ั€ะตะบะพะผะตะฝะดัƒะตั‚ัั.", + "night-light-auto-schedule-label": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะพะต ั€ะฐัะฟะธัะฐะฝะธะต", + "night-light-desc": "ะฃะผะตะฝัŒัˆะตะฝะธะต ะธะทะปัƒั‡ะตะฝะธั ัะธะฝะตะณะพ ัะฒะตั‚ะฐ, ั‡ั‚ะพะฑั‹ ะฟะพะผะพั‡ัŒ ะฒะฐะผ ะปัƒั‡ัˆะต ัะฟะฐั‚ัŒ ะธ ัƒะผะตะฝัŒัˆะธั‚ัŒ ะฝะฐะฟั€ัะถะตะฝะธะต ะณะปะฐะท.", + "night-light-enable-description": "ะŸั€ะธะผะตะฝะธั‚ัŒ ั‚ั‘ะฟะปั‹ะน ั†ะฒะตั‚ะพะฒะพะน ั„ะธะปัŒั‚ั€ ะดะปั ัƒะผะตะฝัŒัˆะตะฝะธั ะธะทะปัƒั‡ะตะฝะธั ัะธะฝะตะณะพ ัะฒะตั‚ะฐ.", + "night-light-enable-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะฝะพั‡ะฝะพะน ัะฒะตั‚", + "night-light-force-activation-description": "ะ˜ะณะฝะพั€ะธั€ัƒะตั‚ ั€ะฐัะฟะธัะฐะฝะธะต ะธ ะฝะตะผะตะดะปะตะฝะฝะพ ะฟั€ะธะผะตะฝัะตั‚ ะฝะพั‡ะฝะพะน ั„ะธะปัŒั‚ั€.", + "night-light-force-activation-label": "ะŸั€ะธะฝัƒะดะธั‚ะตะปัŒะฝะฐั ะฐะบั‚ะธะฒะฐั†ะธั", + "night-light-manual-schedule-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะพะต ะฒั€ะตะผั ะฒะพัั…ะพะดะฐ ะธ ะทะฐะบะฐั‚ะฐ.", + "night-light-manual-schedule-label": "ะ ัƒั‡ะฝะพะต ั€ะฐัะฟะธัะฐะฝะธะต", + "night-light-manual-schedule-select-start": "ะ’ั‹ะฑั€ะฐั‚ัŒ ะฒั€ะตะผั ะฝะฐั‡ะฐะปะฐ", + "night-light-manual-schedule-select-stop": "ะ’ั‹ะฑั€ะฐั‚ัŒ ะฒั€ะตะผั ะพัั‚ะฐะฝะพะฒะบะธ", + "night-light-manual-schedule-sunrise": "ะ’ั€ะตะผั ะฒะพัั…ะพะดะฐ", + "night-light-manual-schedule-sunset": "ะ’ั€ะตะผั ะทะฐะบะฐั‚ะฐ", + "night-light-temperature-day": "ะ”ะตะฝัŒ", + "night-light-temperature-day-description": "ะฃะฟั€ะฐะฒะปัะตั‚ ั†ะฒะตั‚ะพะฒะพะน ั‚ะตะผะฟะตั€ะฐั‚ัƒั€ะพะน ะฒ ะดะฝะตะฒะฝะพะต ะฒั€ะตะผั.", + "night-light-temperature-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ะต ั†ะฒะตั‚ะพะฒัƒัŽ ั‚ะตะผะฟะตั€ะฐั‚ัƒั€ัƒ ะดะปั ะฝะพั‡ะฝะพะณะพ ะธ ะดะฝะตะฒะฝะพะณะพ ะฒั€ะตะผะตะฝะธ.", + "night-light-temperature-label": "ะฆะฒะตั‚ะพะฒะฐั ั‚ะตะผะฟะตั€ะฐั‚ัƒั€ะฐ", + "night-light-temperature-night": "ะะพั‡ัŒ", + "night-light-temperature-night-description": "ะฃะฟั€ะฐะฒะปัะตั‚ ั†ะฒะตั‚ะพะฒะพะน ั‚ะตะผะฟะตั€ะฐั‚ัƒั€ะพะน ะฒ ะฝะพั‡ะฝะพะต ะฒั€ะตะผั.", + "title": "ะ”ะธัะฟะปะตะน" + }, + "dock": { + "appearance-background-opacity-description": "ะะฐัั‚ั€ะพะนะบะฐ ะฝะตะฟั€ะพะทั€ะฐั‡ะฝะพัั‚ะธ ั„ะพะฝะฐ ะดะพะบะฐ.", + "appearance-border-radius-description": "ะ˜ะทะผะตะฝะธั‚ะต ั€ะฐะดะธัƒั ะณั€ะฐะฝะธั†ั‹ ะดะพะบะฐ.", + "appearance-border-radius-label": "ะ ะฐะดะธัƒั ัะบั€ัƒะณะปะตะฝะธั ะณั€ะฐะฝะธั†ั‹", + "appearance-colorize-icons-description": "ะŸั€ะธะผะตะฝะธั‚ัŒ ั†ะฒะตั‚ะฐ ั‚ะตะผั‹ ะบ ะธะบะพะฝะบะฐะผ ะฟั€ะธะปะพะถะตะฝะธะน ะฝะฐ ะดะพะบะต (ั‚ะพะปัŒะบะพ ะดะปั ะฟั€ะธะปะพะถะตะฝะธะน ะฝะต ะฒ ั„ะพะบัƒัะต).", + "appearance-colorize-icons-label": "ะ ะฐัะบั€ะฐัะธั‚ัŒ ะธะบะพะฝะบะธ", + "appearance-dead-opacity-description": "ะะฐัั‚ั€ะพะนั‚ะต ะฟั€ะพะทั€ะฐั‡ะฝะพัั‚ัŒ ะทะฝะฐั‡ะบะพะฒ ะฝะตะฐะบั‚ะธะฒะฝั‹ั… ะฟั€ะธะปะพะถะตะฝะธะน.", + "appearance-dead-opacity-label": "ะะตะฐะบั‚ะธะฒะฝั‹ะต ะฟั€ะธะปะพะถะตะฝะธั", + "appearance-desc": "ะะฐัั‚ั€ะพะนะบะฐ ะฟะพะฒะตะดะตะฝะธั ะธ ะฒะฝะตัˆะฝะตะณะพ ะฒะธะดะฐ ะดะพะบะฐ.", + "appearance-display-auto-hide": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ัะบั€ั‹ะฒะฐั‚ัŒ", + "appearance-display-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ะฟะพะฒะตะดะตะฝะธะต ะดะพะบะฐ.", + "appearance-display-exclusive": "ะ˜ัะบะปัŽั‡ะธั‚ะตะปัŒะฝะพ", + "appearance-dock-indicator-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฝะตะฑะพะปัŒัˆะพะน ะธะฝะดะธะบะฐั‚ะพั€, ะบะพะณะดะฐ ะดะพะบ ัะบั€ั‹ั‚.", + "appearance-dock-indicator-label": "ะ˜ะฝะดะธะบะฐั‚ะพั€ ะดะพะบะฐ", + "appearance-floating-distance-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ะต ั€ะฐััั‚ะพัะฝะธะต ะผะตะถะดัƒ ะดะพะบะพะผ ะธ ะบั€ะฐะตะผ ัะบั€ะฐะฝะฐ.", + "appearance-floating-distance-label": "ะ ะฐััั‚ะพัะฝะธะต ะฟะปะฐะฒะฐัŽั‰ะตะณะพ ะดะพะบะฐ", + "appearance-group-apps-description": "ะ“ั€ัƒะฟะฟะธั€ะพะฒะฐั‚ัŒ ะฝะตัะบะพะปัŒะบะพ ะพะบะพะฝ ะพะดะฝะพะณะพ ะธ ั‚ะพะณะพ ะถะต ะฟั€ะธะปะพะถะตะฝะธั ะฒ ะพะดะฝัƒ ะทะฐะฟะธััŒ Dock.", + "appearance-group-apps-label": "ะ“ั€ัƒะฟะฟะธั€ะพะฒะฐั‚ัŒ ะพะดะธะฝะฐะบะพะฒั‹ะต ะฟั€ะธะปะพะถะตะฝะธั", + "appearance-group-click-action-cycle": "ะŸะตั€ะตะบะปัŽั‡ะตะฝะธะต ะพะบะพะฝ", + "appearance-group-click-action-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ั‡ั‚ะพ ะดะตะปะฐะตั‚ ะปะตะฒั‹ะน ะบะปะธะบ ะดะปั ัะณั€ัƒะฟะฟะธั€ะพะฒะฐะฝะฝั‹ั… ะฟั€ะธะปะพะถะตะฝะธะน.", + "appearance-group-click-action-label": "ะ”ะตะนัั‚ะฒะธะต ะฟะพ ะบะปะธะบัƒ ัะณั€ัƒะฟะฟะธั€ะพะฒะฐะฝะฝะพะณะพ ะฟั€ะธะปะพะถะตะฝะธั", + "appearance-group-click-action-list": "ะžั‚ะบั€ั‹ั‚ัŒ ัะฟะธัะพะบ ะพะบะพะฝ", + "appearance-group-context-menu-mode-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะบะฐะบ ะบะพะฝั‚ะตะบัั‚ะฝะพะต ะผะตะฝัŽ ะฒะตะดะตั‚ ัะตะฑั ะดะปั ัะณั€ัƒะฟะฟะธั€ะพะฒะฐะฝะฝั‹ั… ะฟั€ะธะปะพะถะตะฝะธะน.", + "appearance-group-context-menu-mode-extended": "ะ ะฐััˆะธั€ะตะฝะฝั‹ะน", + "appearance-group-context-menu-mode-label": "ะ ะตะถะธะผ cะณั€ัƒะฟะฟะธั€ะพะฒะฐะฝะฝะพะณะพ ะผะตะฝัŽ ะฟั€ะธะปะพะถะตะฝะธะน", + "appearance-group-context-menu-mode-list": "ะกะฟะธัะพะบ ะพะบะพะฝ", + "appearance-group-indicator-style-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะบะฐะบ ัะณั€ัƒะฟะฟะธั€ะพะฒะฐะฝะฝั‹ะต ะธะฝะดะธะบะฐั‚ะพั€ั‹ ะทะฐะฟัƒั‰ะตะฝะฝั‹ั… ะฟั€ะธะปะพะถะตะฝะธะน ะพั‚ะพะฑั€ะฐะถะฐัŽั‚ ัะพัั‚ะพัะฝะธะต ะฐะบั‚ะธะฒะฝะพะณะพ ะพะบะฝะฐ.", + "appearance-group-indicator-style-dots": "ะขะพั‡ะบะธ", + "appearance-group-indicator-style-label": "ะกั‚ะธะปัŒ ัะณั€ัƒะฟะฟะธั€ะพะฒะฐะฝะฝะพะณะพ ะธะฝะดะธะบะฐั‚ะพั€ะฐ", + "appearance-group-indicator-style-number": "ะงะธัะปะพ", + "appearance-hide-show-speed-description": "ะะฐัั‚ั€ะพะนะบะฐ ัะบะพั€ะพัั‚ะธ ะฐะฝะธะผะฐั†ะธะธ ัะบั€ั‹ั‚ะธั/ะพั‚ะบั€ั‹ั‚ะธั ะดะพะบะฐ.", + "appearance-hide-show-speed-label": "ะกะบะพั€ะพัั‚ัŒ ะฐะฝะธะผะฐั†ะธะธ", + "appearance-icon-size-description": "ะะฐัั‚ั€ะพะนะบะฐ ะพะฑั‰ะตะณะพ ั€ะฐะทะผะตั€ะฐ ะดะพะบะฐ.", + "appearance-icon-size-label": "ะ ะฐะทะผะตั€ ะดะพะบะฐ", + "appearance-inactive-indicators-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะธะฝะดะธะบะฐั‚ะพั€ั‹ ะดะปั ะฒัะตั… ะทะฐะฟัƒั‰ะตะฝะฝั‹ั… ะฟั€ะธะปะพะถะตะฝะธะน, ะฐ ะฝะต ั‚ะพะปัŒะบะพ ะดะปั ะฐะบั‚ะธะฒะฝะพะณะพ.", + "appearance-inactive-indicators-label": "ะ˜ะฝะดะธะบะฐั‚ะพั€ั‹ ะพั‚ะบั€ั‹ั‚ั‹ั… ะฟั€ะธะปะพะถะตะฝะธะน", + "appearance-indicator-color-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ั†ะฒะตั‚ ะธะฝะดะธะบะฐั‚ะพั€ะฐ ัะบั€ั‹ั‚ะพะณะพ ะดะพะบะฐ.", + "appearance-indicator-color-label": "ะฆะฒะตั‚ ะธะฝะดะธะบะฐั‚ะพั€ะฐ", + "appearance-indicator-opacity-description": "ะะฐัั‚ั€ะพะนั‚ะต ะฝะตะฟั€ะพะทั€ะฐั‡ะฝะพัั‚ัŒ ะธะฝะดะธะบะฐั‚ะพั€ะฐ ัะบั€ั‹ั‚ะพะณะพ ะดะพะบะฐ.", + "appearance-indicator-opacity-label": "ะะตะฟั€ะพะทั€ะฐั‡ะฝะพัั‚ัŒ ะธะฝะดะธะบะฐั‚ะพั€ะฐ", + "appearance-indicator-thickness-description": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฑะพะปะตะต ั‚ะพะปัั‚ั‹ะน ะธะฝะดะธะบะฐั‚ะพั€ ัะบั€ั‹ั‚ะพะณะพ ะดะพะบะฐ (6 ะฟะบ ะฒะผะตัั‚ะพ 3 ะฟะบ).", + "appearance-indicator-thickness-label": "ะ‘ะพะปะตะต ั‚ะพะปัั‚ั‹ะน ะธะฝะดะธะบะฐั‚ะพั€", + "appearance-launcher-icon-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ะทะฝะฐั‡ะพะบ, ะธัะฟะพะปัŒะทัƒะตะผั‹ะน ะดะปั Dock Launcher.", + "appearance-launcher-icon-label": "ะ—ะฝะฐั‡ะพะบ ะปะฐัƒะฝั‡ะตั€ะฐ", + "appearance-launcher-position-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะณะดะต ะทะฝะฐั‡ะพะบ ะทะฐะฟัƒัะบะฐ ะฟั€ะธะปะพะถะตะฝะธะน ะพั‚ะพะฑั€ะฐะถะฐะตั‚ัั ะฒ ะดะพะบะต.", + "appearance-launcher-position-end": "ะšะพะฝะตั†", + "appearance-launcher-position-label": "ะŸะพะทะธั†ะธั ะทะฐะฟัƒัะบะฐั‚ะตะปั", + "appearance-launcher-position-start": "ะกั‚ะฐั€ั‚", + "appearance-launcher-use-distro-logo-description": "ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต ะปะพะณะพั‚ะธะฟ ะฒะฐัˆะตะณะพ ะดะธัั‚ั€ะธะฑัƒั‚ะธะฒะฐ ะฒ ะบะฐั‡ะตัั‚ะฒะต ะทะฝะฐั‡ะบะฐ Dock Launcher.", + "appearance-launcher-use-distro-logo-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะปะพะณะพั‚ะธะฟ ะดะธัั‚ั€ะธะฑัƒั‚ะธะฒะฐ ะฒะผะตัั‚ะพ ะธะบะพะฝะบะธ", + "appearance-pinned-static-description": "ะ ะฐัะฟะพะปะฐะณะฐั‚ัŒ ะทะฐะบั€ะตะฟะปั‘ะฝะฝั‹ะต ะทะฝะฐั‡ะบะธ ะฟั€ะธะปะพะถะตะฝะธะน ัะฒะปะตะฒะฐ (ัะฒะตั€ั…ัƒ) ะฒ ัั‚ะฐั‚ะธั‡ะฝะพะผ ะฟะพั€ัะดะบะต.", + "appearance-pinned-static-label": "ะ—ะฐะบั€ะตะฟะปั‘ะฝะฝั‹ะต ะฟั€ะธะปะพะถะตะฝะธั", + "appearance-position-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะณะดะต ะดะพะบ ะฑัƒะดะตั‚ ะพั‚ะพะฑั€ะฐะถะฐั‚ัŒัั ะฝะฐ ัะบั€ะฐะฝะต.", + "appearance-position-label": "ะŸะพะทะธั†ะธั", + "appearance-show-launcher-icon-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะทะฝะฐั‡ะพะบ ะทะฐะฟัƒัะบะฐ ะฟั€ะธะปะพะถะตะฝะธะน ะฒ ะดะพะบะต.", + "appearance-show-launcher-icon-label": "ะŸะพะบะฐะทะฐั‚ัŒ ะฟะฐะฝะตะปัŒ ะทะฐะฟัƒัะบะฐ ะฟั€ะธะปะพะถะตะฝะธะน", + "appearance-sit-on-frame-description": "ะ’ั‹ั€ะพะฒะฝัั‚ัŒ ะดะพะบ ะฒะฝัƒั‚ั€ะธ ะณั€ะฐะฝะธั†ั‹ ั€ะฐะผะบะธ, ะฐ ะฝะต ะฟะพะฒะตั€ั… ะฝะตะต.", + "appearance-sit-on-frame-label": "ะดะพะบ ั€ะฐัะฟะพะปะฐะณะฐะตั‚ัั ะฝะฐ ั€ะฐะผะบะต", + "appearance-type-attached": "ะ—ะฐะบั€ะตะฟะปั‘ะฝะฝะฐั", + "appearance-type-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ะผะตะถะดัƒ ะฟะปะฐะฒะฐัŽั‰ะตะน ะฟะธะปัŽะปะตะน ะธะปะธ ัั‚ะฐั‚ะธั‡ะตัะบะพะน ะฟะฐะฝะตะปัŒัŽ, ะฟั€ะธะบั€ะตะฟะปะตะฝะฝะพะน ะบ ะบั€ะฐัŽ.", + "appearance-type-floating": "ะŸะปะฐะฒะฐัŽั‰ะธะน", + "appearance-type-label": "ะกั‚ะธะปัŒ ะดะพะบะฐ", + "enabled-description": "ะŸะพะบะฐะทะฐั‚ัŒ ะธะปะธ ัะบั€ั‹ั‚ัŒ ะดะพะบ ั†ะตะปะธะบะพะผ.", + "enabled-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะดะพะบ", + "monitors-desc": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะดะพะบ ะฝะฐ ะพะฟั€ะตะดะตะปั‘ะฝะฝั‹ั… ะผะพะฝะธั‚ะพั€ะฐั…. ะŸะพ ัƒะผะพะปั‡ะฐะฝะธัŽ ะฝะฐ ะฒัะตั…, ะตัะปะธ ะฝะธ ะพะดะธะฝ ะฝะต ะฒั‹ะฑั€ะฐะฝ.", + "monitors-only-same-monitor-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ั‚ะพะปัŒะบะพ ะฟั€ะธะปะพะถะตะฝะธั ั ะผะพะฝะธั‚ะพั€ะฐ, ะณะดะต ะฝะฐั…ะพะดะธั‚ัั ะดะพะบ.", + "monitors-only-same-monitor-label": "ะขะพะปัŒะบะพ ะฟั€ะธะปะพะถะตะฝะธั ั ั‚ะพะณะพ ะถะต ะผะพะฝะธั‚ะพั€ะฐ", + "monitors-title": "ะžั‚ะพะฑั€ะฐะถะตะฝะธะต ะฝะฐ ะผะพะฝะธั‚ะพั€ะฐั…", + "title": "ะ”ะพะบ" + }, + "general": { + "copy-settings": "ะšะพะฟะธั€ะพะฒะฐั‚ัŒ ะฝะฐัั‚ั€ะพะนะบะธ", + "fonts-default-description": "ะžัะฝะพะฒะฝะพะน ัˆั€ะธั„ั‚, ะธัะฟะพะปัŒะทัƒะตะผั‹ะน ะฒะพ ะฒัะตะผ ะธะฝั‚ะตั€ั„ะตะนัะต.", + "fonts-default-label": "ะจั€ะธั„ั‚ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ", + "fonts-default-placeholder": "ะ’ั‹ะฑั€ะฐั‚ัŒ ัˆั€ะธั„ั‚ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ...", + "fonts-default-scale-description": "ะฃะฒะตะปะธั‡ะตะฝะธะต ะธะปะธ ัƒะผะตะฝัŒัˆะตะฝะธะต ั€ะฐะทะผะตั€ะฐ ัั‚ะฐะฝะดะฐั€ั‚ะฝะพะณะพ ั‚ะตะบัั‚ะฐ.", + "fonts-default-scale-label": "ะ ะฐะทะผะตั€ ัˆั€ะธั„ั‚ะฐ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ", + "fonts-default-search-placeholder": "ะŸะพะธัะบ ัˆั€ะธั„ั‚ะฐ...", + "fonts-desc": "ะ’ั‹ะฑะตั€ะธั‚ะต ัˆั€ะธั„ั‚ั‹, ะธัะฟะพะปัŒะทัƒะตะผั‹ะต ะฒะพ ะฒัะตะผ ะธะฝั‚ะตั€ั„ะตะนัะต.", + "fonts-monospace-description": "ะœะพะฝะพัˆะธั€ะธะฝะฝั‹ะน ัˆั€ะธั„ั‚, ะธัะฟะพะปัŒะทัƒะตะผั‹ะน ะดะปั ั‡ะธัะตะป ะธ ะพั‚ะพะฑั€ะฐะถะตะฝะธั ัั‚ะฐั‚ะธัั‚ะธะบะธ.", + "fonts-monospace-label": "ะœะพะฝะพัˆะธั€ะธะฝะฝั‹ะน ัˆั€ะธั„ั‚", + "fonts-monospace-placeholder": "ะ’ั‹ะฑั€ะฐั‚ัŒ ะผะพะฝะพัˆะธั€ะธะฝะฝั‹ะน ัˆั€ะธั„ั‚...", + "fonts-monospace-scale-description": "ะฃะฒะตะปะธั‡ะตะฝะธะต ะธะปะธ ัƒะผะตะฝัŒัˆะตะฝะธะต ั€ะฐะทะผะตั€ะฐ ะผะพะฝะพัˆะธั€ะธะฝะฝะพะณะพ ั‚ะตะบัั‚ะฐ.", + "fonts-monospace-scale-label": "ะ ะฐะทะผะตั€ ะผะพะฝะพัˆะธั€ะธะฝะฝะพะณะพ ัˆั€ะธั„ั‚ะฐ", + "fonts-monospace-search-placeholder": "ะŸะพะธัะบ ะผะพะฝะพัˆะธั€ะธะฝะฝะพะณะพ ัˆั€ะธั„ั‚ะฐ...", + "fonts-title": "ะจั€ะธั„ั‚ั‹", + "keybinds-conflict-description": "ะšะพะผะฑะธะฝะฐั†ะธั ะบะปะฐะฒะธัˆ ัƒะถะต ะฝะฐะทะฝะฐั‡ะตะฝะฐ ะดะปั {action}.", + "keybinds-conflict-title": "ะšะพะฝั„ะปะธะบั‚ ะณะพั€ัั‡ะธั… ะบะปะฐะฒะธัˆ", + "keybinds-description": "ะะฐัั‚ั€ะพะนั‚ะต ะณะปะพะฑะฐะปัŒะฝั‹ะต ะบะปะฐะฒะธัˆะธ ะฝะฐะฒะธะณะฐั†ะธะธ ะดะปั ะฟะฐะฝะตะปะตะน ะธ ะปะฐัƒะฝั‡ะตั€ะฐ.", + "keybinds-down": "ะŸะตั€ะตะผะตัั‚ะธั‚ัŒ ะฒะฝะธะท", + "keybinds-enter": "ะŸะพะดั‚ะฒะตั€ะดะธั‚ัŒ / ะ”ะตะนัั‚ะฒะธะต", + "keybinds-escape": "ะ—ะฐะบั€ั‹ั‚ัŒ / ะะฐะทะฐะด", + "keybinds-left": "ะŸะตั€ะตะผะตัั‚ะธั‚ัŒ ะฒะปะตะฒะพ", + "keybinds-modifier-description": "ะกะพั‡ะตั‚ะฐะฝะธั ะบะปะฐะฒะธัˆ ะดะพะปะถะฝั‹ ะฒะบะปัŽั‡ะฐั‚ัŒ ะบะปะฐะฒะธัˆัƒ-ะผะพะดะธั„ะธะบะฐั‚ะพั€ (Ctrl ะธะปะธ Alt).", + "keybinds-modifier-title": "ะขั€ะตะฑัƒะตั‚ัั ะผะพะดะธั„ะธะบะฐั‚ะพั€", + "keybinds-remove": "ะฃะดะฐะปะธั‚ัŒ", + "keybinds-right": "ะŸะตั€ะตะผะตัั‚ะธั‚ัŒ ะฒะฟั€ะฐะฒะพ", + "keybinds-title": "ะะฐะฒะธะณะฐั†ะธะพะฝะฝั‹ะต ะฟั€ะธะฒัะทะบะธ ะบะปะฐะฒะธัˆ", + "keybinds-up": "ะŸะตั€ะตะผะตัั‚ะธั‚ัŒ ะฒะฒะตั€ั…", + "language-desc": "ะ’ั‹ะฑะตั€ะธั‚ะต ะฟั€ะตะดะฟะพั‡ะธั‚ะฐะตะผั‹ะน ัะทั‹ะบ ะดะปั ะฟั€ะธะปะพะถะตะฝะธั.", + "language-select-auto-detect": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ", + "language-select-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ัะทั‹ะบ, ะธัะฟะพะปัŒะทัƒะตะผั‹ะน ะฒ ะธะฝั‚ะตั€ั„ะตะนัะต ะฟั€ะธะปะพะถะตะฝะธั.", + "language-select-label": "ะฏะทั‹ะบ ะฟั€ะธะปะพะถะตะฝะธั", + "launch-setup-wizard": "ะœะฐัั‚ะตั€ ะฝะฐัั‚ั€ะพะนะบะธ", + "profile-desc": "ะ ะตะดะฐะบั‚ะธั€ัƒะนั‚ะต ะดะฐะฝะฝั‹ะต ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะธ ะฐะฒะฐั‚ะฐั€.", + "profile-picture-description": "ะ’ะฐัˆะฐ ั„ะพั‚ะพะณั€ะฐั„ะธั ะฟั€ะพั„ะธะปั, ะบะพั‚ะพั€ะฐั ะฟะพัะฒะปัะตั‚ัั ะฒะพ ะฒัะตะผ ะธะฝั‚ะตั€ั„ะตะนัะต.", + "profile-picture-label": "ะคะพั‚ะพะณั€ะฐั„ะธั ะฟั€ะพั„ะธะปั", + "profile-select-avatar": "ะ’ั‹ะฑั€ะฐั‚ัŒ ะธะทะพะฑั€ะฐะถะตะฝะธะต ะฐะฒะฐั‚ะฐั€ะฐ", + "profile-title": "ะŸั€ะพั„ะธะปัŒ", + "profile-tooltip": "ะคะพั‚ะพะณั€ะฐั„ะธั ะฟั€ะพั„ะธะปั", + "reverse-scrolling-description": "ะ˜ะฝะฒะตั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะธะฝั‚ะตั€ะฟั€ะตั‚ะธั€ะพะฒะฐะฝะฝะพะต ะฝะฐะฟั€ะฐะฒะปะตะฝะธะต ะฟั€ะพะบั€ัƒั‚ะบะธ.", + "reverse-scrolling-label": "ะžะฑั€ะฐั‚ะฝะฐั ะฟั€ะพะบั€ัƒั‚ะบะฐ", + "screen-corners-desc": "ะะฐัั‚ั€ะพะนะบะฐ ัะบั€ัƒะณะปะตะฝะธั ัƒะณะปะพะฒ ัะบั€ะฐะฝะฐ ะธ ะฒะธะทัƒะฐะปัŒะฝั‹ั… ัั„ั„ะตะบั‚ะพะฒ.", + "screen-corners-radius-description": "ะะฐัั‚ั€ะพะนะบะฐ ัะบั€ัƒะณะปะตะฝะธั ัƒะณะปะพะฒ ัะบั€ะฐะฝะฐ.", + "screen-corners-radius-label": "ะ ะฐะดะธัƒั ัƒะณะปะพะฒ ัะบั€ะฐะฝะฐ", + "screen-corners-radius-reset": "ะกะฑั€ะพัะธั‚ัŒ ั€ะฐะดะธัƒั ัƒะณะปะพะฒ ัะบั€ะฐะฝะฐ", + "screen-corners-show-corners-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ัะบั€ัƒะณะปั‘ะฝะฝั‹ะต ัƒะณะปั‹ ะฟะพ ะบั€ะฐัŽ ัะบั€ะฐะฝะฐ.", + "screen-corners-show-corners-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ัƒะณะปั‹ ัะบั€ะฐะฝะฐ", + "screen-corners-solid-black-description": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ัะฟะปะพัˆะฝะพะน ั‡ั‘ั€ะฝั‹ะน ั†ะฒะตั‚ ะฒะผะตัั‚ะพ ั†ะฒะตั‚ะฐ ั„ะพะฝะฐ ะฟะฐะฝะตะปะธ.", + "screen-corners-solid-black-label": "ะกะฟะปะพัˆะฝั‹ะต ั‡ั‘ั€ะฝั‹ะต ัƒะณะปั‹", + "screen-corners-title": "ะฃะณะปั‹ ัะบั€ะฐะฝะฐ", + "settings-copied": "ะะฐัั‚ั€ะพะนะบะธ ัะบะพะฟะธั€ะพะฒะฐะฝั‹ ะฒ ะฑัƒั„ะตั€ ะพะฑะผะตะฝะฐ", + "smooth-scrolling-description": "ะะฝะธะผะธั€ะพะฒะฐั‚ัŒ ะฟั€ะพะบั€ัƒั‚ะบัƒ ัะฟะธัะบะพะฒ ะดะปั ะฑะพะปะตะต ะฟะปะฐะฒะฝะพะณะพ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั ะบะพะปะตัะฐ.", + "smooth-scrolling-label": "ะŸะปะฐะฒะฝะฐั ะฟั€ะพะบั€ัƒั‚ะบะฐ", + "tab-basics": "ะžัะฝะพะฒะฝะพะต", + "tab-keybinds": "ะ“ะพั€ัั‡ะธะต ะบะปะฐะฒะธัˆะธ" + }, + "hooks": { + "color-generation-description": "ะšะพะผะฐะฝะดะฐ, ะบะพั‚ะพั€ะฐั ะฑัƒะดะตั‚ ะฒั‹ะฟะพะปะฝะตะฝะฐ ะฟะพัะปะต ะทะฐะฒะตั€ัˆะตะฝะธั ะณะตะฝะตั€ะฐั†ะธะธ ั†ะฒะตั‚ะฐ (ะฝะฐะฟั€ะธะผะตั€, ะฟะพัะปะต ะฟั€ะธะผะตะฝะตะฝะธั ั‚ะตะผั‹ ะฝะฐ ะพัะฝะพะฒะต ะพะฑะพะตะฒ).", + "color-generation-label": "ะฆะฒะตั‚ะฐ ัะณะตะฝะตั€ะธั€ะพะฒะฐะฝั‹", + "color-generation-placeholder": "ะฝะฐะฟั€. notify-send \"ะฆะฒะตั‚ะฐ\" \"ะกะณะตะฝะตั€ะธั€ะพะฒะฐะฝะพ\"", + "info-command-info-description": "โ€ข ะšะพะผะฐะฝะดั‹ ะฒั‹ะฟะพะปะฝััŽั‚ัั ั‡ะตั€ะตะท shell (sh -lc)
โ€ข ะšะพะผะฐะฝะดั‹ ะฒั‹ะฟะพะปะฝััŽั‚ัั ะฒ ั„ะพะฝะพะฒะพะผ ั€ะตะถะธะผะต (ะพั‚ะดะตะปัŒะฝะพ)
โ€ข ะšะฝะพะฟะบะธ 'ะขะตัั‚' ะฒั‹ะฟะพะปะฝััŽั‚ัั ั ั‚ะตะบัƒั‰ะธะผะธ ะทะฝะฐั‡ะตะฝะธัะผะธ", + "info-parameters-description": "โ€ข Wallpaper hook: $1 = ะฟัƒั‚ัŒ ะบ ะพะฑะพัะผ, $2 = ะธะผั ัะบั€ะฐะฝะฐ, $3 = ั‚ะตะผะฐ (ั‚ะตะผะฝะฐั/ัะฒะตั‚ะปะฐั)
โ€ข Theme toggle hook: $1 = true/false (ัะพัั‚ะพัะฝะธะต ั‚ั‘ะผะฝะพะณะพ ั€ะตะถะธะผะฐ)
โ€ข Screen lock/unlock hooks: $1 = lock/unlock (ัะพัั‚ะพัะฝะธะต ะฑะปะพะบะธั€ะพะฒะบะธ ัะบั€ะฐะฝะฐ)
โ€ข Performance mode hooks: ะ‘ะตะท ะฟะฐั€ะฐะผะตั‚ั€ะพะฒ
โ€ข Session hook: $1 = action (shutdown/reboot)", + "info-parameters-label": "ะ”ะพัั‚ัƒะฟะฝั‹ะต ะฟะฐั€ะฐะผะตั‚ั€ั‹", + "noctalia-started-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟะพัะปะต ะทะฐะฒะตั€ัˆะตะฝะธั ะทะฐะณั€ัƒะทะบะธ Noctalia.", + "noctalia-started-label": "Noctalia ะทะฐะฟัƒั‰ะตะฝะฐ", + "noctalia-started-placeholder": "ะฝะฐะฟั€. notify-send 'Noctalia ะทะฐะณั€ัƒะถะตะฝะฐ'", + "performance-mode-disabled-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟั€ะธ ะฒั‹ะบะปัŽั‡ะตะฝะธะธ ั€ะตะถะธะผะฐ ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ะธ Noctalia.", + "performance-mode-disabled-label": "ะ ะตะถะธะผ ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ะธ ะฒั‹ะบะปัŽั‡ะตะฝ", + "performance-mode-disabled-placeholder": "ะฝะฐะฟั€. notify-send \"ะŸั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ัŒ\" \"ะ ะตะถะธะผ ะฒั‹ะบะปัŽั‡ะตะฝ\"", + "performance-mode-enabled-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟั€ะธ ะฒะบะปัŽั‡ะตะฝะธะธ ั€ะตะถะธะผะฐ ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ะธ Noctalia.", + "performance-mode-enabled-label": "ะ ะตะถะธะผ ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ะธ ะฒะบะปัŽั‡ะตะฝ", + "performance-mode-enabled-placeholder": "ะฝะฐะฟั€. notify-send \"ะŸั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ัŒ\" \"ะ ะตะถะธะผ ะฒะบะปัŽั‡ะตะฝ\"", + "screen-lock-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟั€ะธ ะฑะปะพะบะธั€ะพะฒะบะต ัะบั€ะฐะฝะฐ.", + "screen-lock-label": "ะญะบั€ะฐะฝ ะทะฐะฑะปะพะบะธั€ะพะฒะฐะฝ", + "screen-lock-placeholder": "ะฝะฐะฟั€. notify-send \"ะญะบั€ะฐะฝ\" \"ะ—ะฐะฑะปะพะบะธั€ะพะฒะฐะฝ\"", + "screen-unlock-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟั€ะธ ั€ะฐะทะฑะปะพะบะธั€ะพะฒะบะต ัะบั€ะฐะฝะฐ.", + "screen-unlock-label": "ะญะบั€ะฐะฝ ั€ะฐะทะฑะปะพะบะธั€ะพะฒะฐะฝ", + "screen-unlock-placeholder": "ะฝะฐะฟั€. notify-send \"ะญะบั€ะฐะฝ\" \"ะ ะฐะทะฑะปะพะบะธั€ะพะฒะฐะฝ\"", + "session-description": "ะšะพะผะฐะฝะดะฐ, ะฒั‹ะฟะพะปะฝัะตะผะฐั ะฟะตั€ะตะด ะฒั‹ะบะปัŽั‡ะตะฝะธะตะผ ะธะปะธ ะฟะตั€ะตะทะฐะณั€ัƒะทะบะพะน. ะŸะพะปัƒั‡ะฐะตั‚ ั‚ะธะฟ ะดะตะนัั‚ะฒะธั ะบะฐะบ $1 (ะฒั‹ะบะปัŽั‡ะตะฝะธะตะผ/ะฟะตั€ะตะทะฐะณั€ัƒะทะบะพะน).", + "session-label": "ะšะพะฝะตั† ัะตััะธะธ", + "session-placeholder": "ะฝะฐะฟั€. notify-send \"Session\" \"$1\"", + "system-hooks-desc": "ะะฐัั‚ั€ะพะนะบะฐ ะบะพะผะฐะฝะด ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟั€ะธ ัะธัั‚ะตะผะฝั‹ั… ัะพะฑั‹ั‚ะธัั….", + "system-hooks-enable-description": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะธะปะธ ะพั‚ะบะปัŽั‡ะธั‚ัŒ ะฒัะต ะบะพะผะฐะฝะดั‹ ั…ัƒะบะพะฒ.", + "system-hooks-enable-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ั…ัƒะบะธ", + "system-hooks-title": "ะกะธัั‚ะตะผะฝั‹ะต ั…ัƒะบะธ", + "theme-changed-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟั€ะธ ะฟะตั€ะตะบะปัŽั‡ะตะฝะธะธ ั‚ะตะผั‹ ะผะตะถะดัƒ ั‚ั‘ะผะฝั‹ะผ ะธ ัะฒะตั‚ะปั‹ะผ ั€ะตะถะธะผะฐะผะธ.", + "theme-changed-label": "ะขะตะผะฐ ะธะทะผะตะฝะตะฝะฐ", + "theme-changed-placeholder": "ะฝะฐะฟั€. notify-send \"ะขะตะผะฐ\" \"ะŸะตั€ะตะบะปัŽั‡ะตะฝะฐ\"", + "title": "ะฅัƒะบะธ", + "wallpaper-changed-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟั€ะธ ัะผะตะฝะต ะพะฑะพะตะฒ.", + "wallpaper-changed-label": "ะžะฑะพะธ ะธะทะผะตะฝะตะฝั‹", + "wallpaper-changed-placeholder": "ะฝะฐะฟั€. notify-send \"ะžะฑะพะธ\" \"ะ˜ะทะผะตะฝะตะฝั‹\"" + }, + "idle": { + "custom-add": "ะ”ะพะฑะฐะฒะธั‚ัŒ ะบะพะผะฐะฝะดัƒ", + "custom-description": "ะ’ั‹ะฟะพะปะฝะธั‚ัŒ ะบะพะผะฐะฝะดัƒ ะพะฑะพะปะพั‡ะบะธ ะฟะพัะปะต ะฟะตั€ะธะพะดะฐ ะฑะตะทะดะตะนัั‚ะฒะธั.", + "custom-entry-command": "ะšะพะผะฐะฝะดะฐ", + "custom-entry-delete": "ะฃะดะฐะปะธั‚ัŒ", + "custom-entry-edit": "ะ ะตะดะฐะบั‚ะธั€ะพะฒะฐั‚ัŒ ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบัƒัŽ ะšะพะผะฐะฝะดัƒ", + "custom-entry-name": "ะ˜ะผั", + "custom-entry-name-placeholder": "ะฝะฐะฟั€. ะ’ั‹ะบะปัŽั‡ะธั‚ัŒ ัะฒะตั‚", + "custom-entry-new": "ะะพะฒะฐั ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะฐั ะšะพะผะฐะฝะดะฐ", + "custom-entry-timeout": "ะ’ั€ะตะผั ะฑะตะทะดะตะนัั‚ะฒะธั", + "custom-entry-timeout-format": "{count} ัะตะบัƒะฝะดะฐ", + "custom-entry-timeout-format-plural": "{count} ัะตะบัƒะฝะด", + "custom-entry-unnamed": "ะ‘ะตะทั‹ะผัะฝะฝะฐั ะบะพะผะฐะฝะดะฐ", + "custom-label": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะต ะบะพะผะฐะฝะดั‹ ะฟั€ะพัั‚ะพั", + "enable-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะฒั‹ะบะปัŽั‡ะฐั‚ัŒ ัะบั€ะฐะฝ, ะฑะปะพะบะธั€ะพะฒะฐั‚ัŒ ะธะปะธ ะฟั€ะธะพัั‚ะฐะฝะฐะฒะปะธะฒะฐั‚ัŒ ั€ะฐะฑะพั‚ัƒ ะฟะพัะปะต ะฟะตั€ะธะพะดะฐ ะฑะตะทะดะตะนัั‚ะฒะธั.", + "enable-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ัƒะฟั€ะฐะฒะปะตะฝะธะต ะฑะตะทะดะตะนัั‚ะฒะธะตะผ", + "fade-duration-description": "ะกะตะบัƒะฝะดั‹ ะดะปั ะฐะฝะธะผะฐั†ะธะธ ะทะฐั‚ะตะผะฝะตะฝะธั ะฟะตั€ะตะด ะฒั‹ะฟะพะปะฝะตะฝะธะตะผ ะบะฐะถะดะพะณะพ ะดะตะนัั‚ะฒะธั. ะ›ัŽะฑะพะต ะดะฒะธะถะตะฝะธะต ะผั‹ัˆะธ ะพั‚ะผะตะฝัะตั‚ ะทะฐั‚ะตะผะฝะตะฝะธะต.", + "fade-duration-label": "ะ”ะปะธั‚ะตะปัŒะฝะพัั‚ัŒ ะทะฐั‚ัƒั…ะฐะฝะธั", + "lock-description": "ะกะตะบัƒะฝะดั‹ ะฑะตะทะดะตะนัั‚ะฒะธั ะดะพ ะฐะบั‚ะธะฒะฐั†ะธะธ ัะบั€ะฐะฝะฐ ะฑะปะพะบะธั€ะพะฒะบะธ.", + "lock-label": "ะ—ะฐะฑะปะพะบะธั€ะพะฒะฐั‚ัŒ ัะบั€ะฐะฝ", + "resume-command-label": "ะ’ะพะทะพะฑะฝะพะฒะธั‚ัŒ ะบะพะผะฐะฝะดัƒ", + "screen-off-description": "ะกะตะบัƒะฝะดั‹ ะฑะตะทะดะตะนัั‚ะฒะธั ะดะพ ะฒั‹ะบะปัŽั‡ะตะฝะธั ะผะพะฝะธั‚ะพั€ะพะฒ.", + "screen-off-label": "ะ’ั‹ะบะปัŽั‡ะธั‚ัŒ ัะบั€ะฐะฝ", + "status-description": "ะ’ั€ะตะผั ะฟั€ะพัั‚ะพั, ัะพะพะฑั‰ะฐะตะผะพะต ะบะพะผะฟะพะทะธั‚ะพั€ะพะผ.", + "status-label": "ะ’ั€ะตะผั ะฟั€ะพัั‚ะพั", + "suspend-description": "ะกะตะบัƒะฝะดั‹ ะฑะตะทะดะตะนัั‚ะฒะธั ะดะพ ะฟั€ะธะพัั‚ะฐะฝะพะฒะบะธ ั€ะฐะฑะพั‚ั‹ ัะธัั‚ะตะผั‹.", + "tab-behavior": "ะŸะพะฒะตะดะตะฝะธะต", + "tab-custom": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน", + "timeouts-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ะต 0, ั‡ั‚ะพะฑั‹ ะพั‚ะบะปัŽั‡ะธั‚ัŒ ัั‚ะฐะฟ. ะขะฐะนะผ-ะฐัƒั‚ั‹ ะฟั€ะธะพัั‚ะฐะฝะฐะฒะปะธะฒะฐัŽั‚ัั, ะฟะพะบะฐ 'ะะต ะทะฐัั‹ะฟะฐั‚ัŒ' ะฐะบั‚ะธะฒะตะฝ.", + "timeouts-label": "ะขะฐะนะผ-ะฐัƒั‚ั‹", + "title": "ะ‘ะตะทะดะตะนัั‚ะฒะธะต", + "unavailable": "ะะฐั‚ะธะฒะฝั‹ะน ะผะพะฝะธั‚ะพั€ะธะฝะณ ะฑะตะทะดะตะนัั‚ะฒะธั ะฝะตะดะพัั‚ัƒะฟะตะฝ ะฝะฐ ัั‚ะพะผ Compositorะต." + }, + "indicator": { + "default-value": "ะŸะพ ัƒะผะพะปั‡ะฐะฝะธัŽ: {value}", + "system-default": "ะŸะพ ัƒะผะพะปั‡ะฐะฝะธัŽ" + }, + "launcher": { + "clipboard-desc": "ะŸะพะปัƒั‡ะฐะนั‚ะต ะดะพัั‚ัƒะฟ ะบ ะธัั‚ะพั€ะธะธ ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ ะธ ัƒะฟั€ะฐะฒะปัะนั‚ะต ะตัŽ ะธะท ะปะฐัƒะฝั‡ะตั€ะฐ.", + "execute-desc": "ะะฐัั‚ั€ะพะนั‚ะต ัะฟะพัะพะฑ ะทะฐะฟัƒัะบะฐ ะฟั€ะธะปะพะถะตะฝะธะน.", + "execute-title": "ะ˜ัะฟะพะปะฝะตะฝะธะต", + "settings-annotation-tool-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะทะฐะฟัƒัะบะฐ ะฟั€ะธ ะฝะฐะถะฐั‚ะธะธ ะบะฝะพะฟะบะธ ะฐะฝะฝะพั‚ะธั€ะพะฒะฐะฝะธั ะฒ ะธัั‚ะพั€ะธะธ ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ. ะ˜ะทะพะฑั€ะฐะถะตะฝะธะต ะฑัƒะดะตั‚ ะฟะตั€ะตะดะฐะฝะพ ะฒ ัั‚ัƒ ะบะพะผะฐะฝะดัƒ.", + "settings-annotation-tool-label": "ะ˜ะฝัั‚ั€ัƒะผะตะฝั‚ ะฐะฝะฝะพั‚ะธั€ะพะฒะฐะฝะธั", + "settings-annotation-tool-placeholder": "ะฝะฐะฟั€. 'gradia', 'satty -f -'", + "settings-auto-paste-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะฒัั‚ะฐะฒะธั‚ัŒ ะฒั‹ะฑั€ะฐะฝะฝั‹ะน ัะปะตะผะตะฝั‚ ะธะท ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ. ะขั€ะตะฑัƒะตั‚ัั wtype.", + "settings-auto-paste-label": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะฐั ะฒัั‚ะฐะฒะบะฐ", + "settings-clip-chips-description": "ะŸะพะบะฐะทะฐั‚ัŒ ะฟะฐะฝะตะปัŒ ะฒะบะปะฐะดะพะบ ะดะปั ั„ะธะปัŒั‚ั€ะฐั†ะธะธ ะธัั‚ะพั€ะธะธ ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ ะฟะพ ั‚ะธะฟัƒ (ะ˜ะทะพะฑั€ะฐะถะตะฝะธั, ะกัั‹ะปะบะธ, ะคะฐะนะปั‹, ะšะพะด ะธ ั‚.ะด.).", + "settings-clip-chips-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ั‡ะธะฟั‹ ะบะฐั‚ะตะณะพั€ะธะน", + "settings-clip-preview-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฟั€ะตะดะฒะฐั€ะธั‚ะตะปัŒะฝั‹ะน ะฟั€ะพัะผะพั‚ั€ ัะพะดะตั€ะถะธะผะพะณะพ ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ ะฟั€ะธ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะธ ะบะพะผะฐะฝะดั‹ >clip.", + "settings-clip-preview-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะฟั€ะตะดะฒะฐั€ะธั‚ะตะปัŒะฝั‹ะน ะฟั€ะพัะผะพั‚ั€ ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ", + "settings-clip-smart-icons-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ัะฟะตั†ะธะฐะปัŒะฝั‹ะต ะทะฝะฐั‡ะบะธ ะดะปั ััั‹ะปะพะบ, ั„ะฐะนะปะพะฒ, ั†ะฒะตั‚ะพะฒ ะธ ะบะพะดะฐ ะฒะผะตัั‚ะพ ะพะฑั‰ะตะณะพ ะทะฝะฐั‡ะบะฐ ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ.", + "settings-clip-smart-icons-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ัƒะผะฝั‹ะต ะทะฝะฐั‡ะบะธ", + "settings-clip-wrap-text-description": "ะŸะตั€ะตะฝะพัะธั‚ัŒ ั‚ะตะบัั‚ ะฒ ัะฟะธัะบะต ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ ะฒะผะตัั‚ะพ ะพะฑั€ะตะทะฐะฝะธั.", + "settings-clip-wrap-text-label": "ะžะฑะตั€ะฝัƒั‚ัŒ ั‚ะตะบัั‚ ะธะท ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ", + "settings-clipboard-history-description": "ะ”ะพัั‚ัƒะฟ ะบ ั€ะฐะฝะตะต ัะบะพะฟะธั€ะพะฒะฐะฝะฝั‹ะผ ัะปะตะผะตะฝั‚ะฐะผ ะธะท ะทะฐะฟัƒัะบะฐ.", + "settings-clipboard-history-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะธัั‚ะพั€ะธัŽ ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ", + "settings-clipboard-watch-image-description": "ะŸะพะปะฝะฐั ัั‚ั€ะพะบะฐ ะบะพะผะฐะฝะดั‹, ะฟะตั€ะตะดะฐะฒะฐะตะผะฐั ะฒ wl-paste ะดะปั ะธะทะผะตะฝะตะฝะธั ะธะทะพะฑั€ะฐะถะตะฝะธะน (ั‚ั€ะตะฑัƒะตั‚ัั ะฟะตั€ะตะทะฐะฟัƒัะบ).", + "settings-clipboard-watch-image-label": "ะšะพะผะฐะฝะดะฐ ะžั‚ัะปะตะถะธะฒะฐะฝะธั ะ˜ะทะพะฑั€ะฐะถะตะฝะธะน", + "settings-clipboard-watch-text-description": "ะŸะพะปะฝะฐั ัั‚ั€ะพะบะฐ ะบะพะผะฐะฝะดั‹, ะฟะตั€ะตะดะฐะฝะฝะฐั ะฒ wl-paste ะดะปั ะธะทะผะตะฝะตะฝะธะน ั‚ะตะบัั‚ะฐ (ั‚ั€ะตะฑัƒะตั‚ัั ะฟะตั€ะตะทะฐะฟัƒัะบ).", + "settings-clipboard-watch-text-label": "ะšะพะผะฐะฝะดะฐ ะฝะฐะฑะปัŽะดะตะฝะธั ะทะฐ ั‚ะตะบัั‚ะพะผ", + "settings-custom-launch-prefix-description": "ะ”ะพะฑะฐะฒะปัั‚ัŒ ะฟั€ะตั„ะธะบั ะบ ะบะพะผะฐะฝะดะฐะผ ั ะฟะพะผะพั‰ัŒัŽ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะพะณะพ ะทะฐะฟัƒัะบะฐ (ะฝะฐะฟั€ะธะผะตั€, 'runapp' ะดะปั ะธะฝั‚ะตะณั€ะฐั†ะธะธ ั systemd).", + "settings-custom-launch-prefix-enabled-description": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ะฟั€ะตั„ะธะบั ะดะปั ะทะฐะฟัƒัะบะฐ ะฟั€ะธะปะพะถะตะฝะธะน ะฒะผะตัั‚ะพ ะผะตั‚ะพะดะฐ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ.", + "settings-custom-launch-prefix-enabled-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ะฟั€ะตั„ะธะบั ะทะฐะฟัƒัะบะฐ", + "settings-custom-launch-prefix-label": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ะฟั€ะตั„ะธะบั ะทะฐะฟัƒัะบะฐ", + "settings-density-description": "ะะฐัั‚ั€ะพะนั‚ะต ั€ะฐะทะผะตั€ ะทะฝะฐั‡ะบะพะฒ ะฟั€ะธะปะพะถะตะฝะธะน ะธ ะฟะปะพั‚ะฝะพัั‚ัŒ Launcher.", + "settings-density-label": "ะŸะปะพั‚ะฝะพัั‚ัŒ", + "settings-desc": "ะะฐัั‚ั€ะพะนะบะฐ ะฟะพะฒะตะดะตะฝะธั ะธ ะฒะฝะตัˆะฝะตะณะพ ะฒะธะดะฐ ะทะฐะฟัƒัะบะฐ.", + "settings-enable-session-search-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะดะตะนัั‚ะฒะธั ัะตะฐะฝัะฐ (ะฑะปะพะบะธั€ะพะฒะบะฐ, ะฒั‹ะบะปัŽั‡ะตะฝะธะต, ะฟะตั€ะตะทะฐะณั€ัƒะทะบะฐ ะธ ั‚.ะด.) ะฟั€ะธ ะฟะพะธัะบะต ะฒ Launcher.", + "settings-enable-session-search-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะฟะพะธัะบ ะฟะพ ัะตััะธะธ", + "settings-enable-settings-search-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ั€ะตะทัƒะปัŒั‚ะฐั‚ั‹ ะฝะฐัั‚ั€ะพะตะบ ะฟั€ะธ ะฟะพะธัะบะต ะฒ ะปะฐัƒะฝั‡ะตั€ะต.", + "settings-enable-settings-search-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะฟะพะธัะบ ะฒ ะฝะฐัั‚ั€ะพะนะบะฐั…", + "settings-enable-windows-search-description": "ะŸะพะธัะบ ะธ ั„ะพะบัƒัะธั€ะพะฒะบะฐ ะฐะบั‚ะธะฒะฝั‹ั… ะพะบะพะฝ.", + "settings-enable-windows-search-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะฟะพะธัะบ ะพะบะพะฝ", + "settings-grid-view-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ัะปะตะผะตะฝั‚ั‹ ะฒ ะฒะธะดะต ัะตั‚ะบะธ ะฒะผะตัั‚ะพ ัะฟะธัะบะฐ.", + "settings-icon-mode-description": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฝะฐั‚ะธะฒะฝั‹ะต ัะธัั‚ะตะผะฝั‹ะต ะธะบะพะฝะบะธ ะฒะผะตัั‚ะพ ะธะบะพะฝะพะบ Tabler.", + "settings-icon-mode-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฝะฐั‚ะธะฒะฝั‹ะต ะธะบะพะฝะบะธ", + "settings-ignore-mouse-input-description": "ะžั‚ะบะปัŽั‡ะธั‚ัŒ ะฒะทะฐะธะผะพะดะตะนัั‚ะฒะธะต ั ะผั‹ัˆัŒัŽ ะธ ะบะพะปะตัะพะผ ะฟั€ะพะบั€ัƒั‚ะบะธ ะฒ ะปะฐัƒะฝั‡ะตั€ะต.", + "settings-ignore-mouse-input-label": "ะ˜ะณะฝะพั€ะธั€ะพะฒะฐั‚ัŒ ะฒะฒะพะด ะผั‹ัˆะธ", + "settings-overlay-layer-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะทะฐะฟัƒัะบะฐั‚ะตะปัŒ ะฝะฐ ัะปะพะต ะฝะฐะปะพะถะตะฝะธั, ะฝะฐะด ะฟะพะปะฝะพัะบั€ะฐะฝะฝั‹ะผะธ ะพะบะฝะฐะผะธ. ะŸั€ะธ ะฒะบะปัŽั‡ะตะฝะธะธ ะทะฐะฟัƒัะบะฐั‚ะตะปัŒ ะฝะต ะฑัƒะดะตั‚ ะฟั€ะธะบั€ะตะฟะปะตะฝ ะบ ะฟะฐะฝะตะปะธ.", + "settings-overlay-layer-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฟะพะฒะตั€ั… ะฟะพะปะฝะพัะบั€ะฐะฝะฝะพะณะพ ั€ะตะถะธะผะฐ", + "settings-position-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะณะดะต ะฟะพัะฒะปัะตั‚ัั ะฟะฐะฝะตะปัŒ ะปะฐัƒะฝั‡ะตั€ะฐ.", + "settings-show-categories-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฒะบะปะฐะดะบะธ ะบะฐั‚ะตะณะพั€ะธะน ะดะปั ั„ะธะปัŒั‚ั€ะฐั†ะธะธ ะฟั€ะธะปะพะถะตะฝะธะน.", + "settings-show-categories-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะบะฐั‚ะตะณะพั€ะธะธ", + "settings-show-icon-background-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะทะฐะบั€ัƒะณะปั‘ะฝะฝั‹ะน ะฟั€ัะผะพัƒะณะพะปัŒะฝะธะบ ะฒ ะบะฐั‡ะตัั‚ะฒะต ั„ะพะฝะฐ ะดะปั ะธะบะพะฝะพะบ.", + "settings-show-icon-background-label": "ะŸะพะบะฐะทะฐั‚ัŒ ั„ะพะฝ ะทะฝะฐั‡ะบะฐ", + "settings-sort-by-usage-description": "ะ•ัะปะธ ะฒะบะปัŽั‡ะตะฝะพ, ั‡ะฐัั‚ะพ ะทะฐะฟัƒัะบะฐะตะผั‹ะต ะฟั€ะธะปะพะถะตะฝะธั ะฟะพัะฒะปััŽั‚ัั ะฒ ัะฟะธัะบะต ะฟะตั€ะฒั‹ะผะธ.", + "settings-sort-by-usage-label": "ะกะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะฟะพ ั‡ะฐัั‚ะพั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั", + "settings-terminal-command-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะทะฐะฟัƒัะบะฐ ั‚ะตั€ะผะธะฝะฐะปะฐ. ะะฐะฟั€ะธะผะตั€, 'kitty -e' ะธะปะธ 'gnome-terminal --'.", + "settings-terminal-command-label": "ะšะพะผะฐะฝะดะฐ ั‚ะตั€ะผะธะฝะฐะปะฐ", + "settings-use-app2unit-description": "ะ˜ัะฟะพะปัŒะทัƒะตั‚ ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝั‹ะน ะผะตั‚ะพะด ะทะฐะฟัƒัะบะฐ ะดะปั ะปัƒั‡ัˆะตะณะพ ัƒะฟั€ะฐะฒะปะตะฝะธั ะฟั€ะพั†ะตััะฐะผะธ ะฟั€ะธะปะพะถะตะฝะธะน ะธ ะฟั€ะตะดะพั‚ะฒั€ะฐั‰ะตะฝะธั ะฟั€ะพะฑะปะตะผ.", + "settings-use-app2unit-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ App2Unit ะดะปั ะทะฐะฟัƒัะบะฐ ะฟั€ะธะปะพะถะตะฝะธะน", + "settings-view-mode-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ะผะฐะบะตั‚ ะดะปั ะทะฐะฟะธัะตะน ะ—ะฐะฟัƒัะบะฐั‚ะตะปั.", + "settings-view-mode-label": "ะ ะตะถะธะผ ะฟั€ะพัะผะพั‚ั€ะฐ", + "title": "ะ›ะฐัƒะฝั‡ะตั€" + }, + "location": { + "auto-locate-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะพะฟั€ะตะดะตะปัั‚ัŒ ะฒะฐัˆะต ะผะตัั‚ะพะฟะพะปะพะถะตะฝะธะต ะฟะตั€ะธะพะดะธั‡ะตัะบะธ, ะธัะฟะพะปัŒะทัƒั ะฒะฐัˆ IP-ะฐะดั€ะตั.", + "auto-locate-label": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะพะต ะžะฟั€ะตะดะตะปะตะฝะธะต", + "calendar-cards-desc": "ะžั€ะณะฐะฝะธะทัƒะนั‚ะต ะธ ะฒะบะปัŽั‡ะฐะนั‚ะต/ะฒั‹ะบะปัŽั‡ะฐะนั‚ะต ะบะฐั€ั‚ะพั‡ะบะธ ะฒ ะฟะฐะฝะตะปะธ ะบะฐะปะตะฝะดะฐั€ั.", + "calendar-cards-title": "ะšะฐั€ั‚ะพั‡ะบะธ ะบะฐะปะตะฝะดะฐั€ั", + "calendar-header-label": "ะ—ะฐะณะพะปะพะฒะพะบ ะบะฐะปะตะฝะดะฐั€ั", + "calendar-month-label": "ะšะฐะปะตะฝะดะฐั€ะฝั‹ะน ะผะตััั†", + "date-time-12hour-format-description": "ะžั‚ะพะฑั€ะฐะถะฐะตั‚ ะฒั€ะตะผั ะฒ 12-ั‡ะฐัะพะฒะพะผ ั„ะพั€ะผะฐั‚ะต ะฝะฐ ัะบั€ะฐะฝะต ะฑะปะพะบะธั€ะพะฒะบะธ ะธ ะฒ ะบะฐะปะตะฝะดะฐั€ะต. ะงะฐัั‹ ะฝะฐ ะฟะฐะฝะตะปะธ ะธะผะตัŽั‚ ัะพะฑัั‚ะฒะตะฝะฝั‹ะต ะฝะฐัั‚ั€ะพะนะบะธ.", + "date-time-12hour-format-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ 12-ั‡ะฐัะพะฒะพะน ั„ะพั€ะผะฐั‚ ะฒั€ะตะผะตะฝะธ", + "date-time-desc": "ะะฐัั‚ั€ะพะนะบะฐ ะพั‚ะพะฑั€ะฐะถะตะฝะธั ะดะฐั‚ั‹ ะธ ะฒั€ะตะผะตะฝะธ.", + "date-time-first-day-of-week-automatic": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ (ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ัะธัั‚ะตะผะฝัƒัŽ ะปะพะบะฐะปัŒ)", + "date-time-first-day-of-week-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ั ะบะฐะบะพะณะพ ะดะฝั ะฝะฐั‡ะธะฝะฐะตั‚ัั ะฝะตะดะตะปั ะฒ ะบะฐะปะตะฝะดะฐั€ะต.", + "date-time-first-day-of-week-label": "ะŸะตั€ะฒั‹ะน ะดะตะฝัŒ ะฝะตะดะตะปะธ", + "date-time-show-events-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ัะพะฑั‹ั‚ะธั ะฝะฐ ะฟะฐะฝะตะปะธ ะบะฐะปะตะฝะดะฐั€ั.", + "date-time-show-events-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ัะพะฑั‹ั‚ะธั ะบะฐะปะตะฝะดะฐั€ั", + "date-time-title": "ะ”ะฐั‚ะฐ ะธ ะฒั€ะตะผั", + "date-time-use-analog-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฐะฝะฐะปะพะณะพะฒั‹ะต ั‡ะฐัั‹ ะฒ ะพะบะฝะต ะบะฐะปะตะฝะดะฐั€ั.", + "date-time-use-analog-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฐะฝะฐะปะพะณะพะฒั‹ะน ัั‚ะธะปัŒ ั‡ะฐัะพะฒ", + "date-time-week-numbers-description": "ะžั‚ะพะฑั€ะฐะถะฐะตั‚ ะฝะพะผะตั€ ะฝะตะดะตะปะธ ะฒ ะณะพะดัƒ (ะฝะฐะฟั€ะธะผะตั€, ะฝะตะดะตะปั 38) ะฒ ะบะฐะปะตะฝะดะฐั€ะต.", + "date-time-week-numbers-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฝะพะผะตั€ะฐ ะฝะตะดะตะปัŒ", + "geolocate-now-button": "ะะฐะนั‚ะธ ัะตะนั‡ะฐั", + "location-desc": "ะŸะพะปัƒั‡ะธั‚ะต ั‚ะพั‡ะฝัƒัŽ ะฟะพะณะพะดัƒ ะธ ั€ะฐัะฟะธัะฐะฝะธะต ะฝะพั‡ะฝะพะณะพ ัะฒะตั‚ะฐ, ัƒัั‚ะฐะฝะพะฒะธะฒ ัะฒะพั‘ ะผะตัั‚ะพะฟะพะปะพะถะตะฝะธะต.", + "location-search-description": "ะฝะฐะฟั€. ะœะพัะบะฒะฐ, RU", + "location-search-label": "ะŸะพะธัะบ ะผะตัั‚ะพะฟะพะปะพะถะตะฝะธั", + "location-search-placeholder": "ะ’ะฒะตะดะธั‚ะต ะฝะฐะทะฒะฐะฝะธะต ะผะตัั‚ะพะฟะพะปะพะถะตะฝะธั", + "location-title": "ะ’ะฐัˆะต ะผะตัั‚ะพะฟะพะปะพะถะตะฝะธะต", + "weather-desc": "ะ’ั‹ะฑะตั€ะธั‚ะต ะฟั€ะตะดะฟะพั‡ะธั‚ะฐะตะผัƒัŽ ะตะดะธะฝะธั†ัƒ ะธะทะผะตั€ะตะฝะธั ั‚ะตะผะฟะตั€ะฐั‚ัƒั€ั‹.", + "weather-enabled-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะธะฝั„ะพั€ะผะฐั†ะธัŽ ะพ ะฟะพะณะพะดะต ะฒะพ ะฒัะตะผ ะธะฝั‚ะตั€ั„ะตะนัะต ะธ ะฟะพะปัƒั‡ะฐั‚ัŒ ะดะฐะฝะฝั‹ะต ะพ ะฟะพะณะพะดะต. ะ•ัะปะธ ะพั‚ะบะปัŽั‡ะตะฝะพ, ะฒัะต ัะปะตะผะตะฝั‚ั‹ ะฟะพะณะพะดั‹ ะฑัƒะดัƒั‚ ัะบั€ั‹ั‚ั‹, ะธ ัะตั‚ะตะฒั‹ะต ะทะฐะฟั€ะพัั‹ ะฝะต ะฑัƒะดัƒั‚ ะฒั‹ะฟะพะปะฝัั‚ัŒัั.", + "weather-enabled-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะฟะพะณะพะดัƒ", + "weather-fahrenheit-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ั‚ะตะผะฟะตั€ะฐั‚ัƒั€ัƒ ะฒ ะคะฐั€ะตะฝะณะตะนั‚ะฐั… ะฒะผะตัั‚ะพ ะฆะตะปัŒัะธั.", + "weather-fahrenheit-label": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ั‚ะตะผะฟะตั€ะฐั‚ัƒั€ัƒ ะฒ ะคะฐั€ะตะฝะณะตะนั‚ะฐั… (ยฐF)", + "weather-hide-city-description": "ะกะบั€ั‹ั‚ัŒ ะฝะฐะทะฒะฐะฝะธะต ะณะพั€ะพะดะฐ ะธะท ะพั‚ะพะฑั€ะฐะถะตะฝะธั ะฟะพะณะพะดั‹ ะฒะพ ะฒัะตะผ ะธะฝั‚ะตั€ั„ะตะนัะต.", + "weather-hide-city-label": "ะกะบั€ั‹ั‚ัŒ ะฝะฐะทะฒะฐะฝะธะต ะณะพั€ะพะดะฐ", + "weather-hide-timezone-description": "ะกะบั€ั‹ั‚ัŒ ะฐะฑะฑั€ะตะฒะธะฐั‚ัƒั€ัƒ ั‡ะฐัะพะฒะพะณะพ ะฟะพััะฐ ะธะท ะพั‚ะพะฑั€ะฐะถะตะฝะธั ะฟะพะณะพะดั‹ ะฒะพ ะฒัะตะผ ะธะฝั‚ะตั€ั„ะตะนัะต.", + "weather-hide-timezone-label": "ะกะบั€ั‹ั‚ัŒ ั‡ะฐัะพะฒะพะน ะฟะพัั", + "weather-show-effects-description": "ะžั‚ะพะฑั€ะฐะถะฐะตั‚ ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝั‹ะต ะฒะธะทัƒะฐะปัŒะฝั‹ะต ัั„ั„ะตะบั‚ั‹ (ะฝะฐะฟั€ะธะผะตั€, ะดะพะถะดัŒ, ัะฝะตะณ ะธะปะธ ะผะพะปะฝะธัŽ) ะฝะฐ ะบะฐั€ั‚ะพั‡ะบะต ะฟะพะณะพะดั‹.", + "weather-show-effects-label": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะฟะพะณะพะดะฝั‹ะต ัั„ั„ะตะบั‚ั‹", + "weather-show-in-calendar-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะตะถะตะดะฝะตะฒะฝั‹ะน ะฟั€ะพะณะฝะพะท ะฟะพะณะพะดั‹ ะฝะตะฟะพัั€ะตะดัั‚ะฒะตะฝะฝะพ ะฒ ะฒะฐัˆะตะผ ะบะฐะปะตะฝะดะฐั€ะต.", + "weather-show-in-calendar-label": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะฟะพะณะพะดัƒ ะฒ ะบะฐะปะตะฝะดะฐั€ะต", + "weather-talia-mascot-always-description": "ะ’ัะตะณะดะฐ ะธัะฟะพะปัŒะทัƒะนั‚ะต Talia emojis ะฒะผะตัั‚ะพ tabler icons.", + "weather-talia-mascot-always-label": "ะ’ัะตะณะดะฐ ะฟะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฟะพะณะพะดะฝั‹ะต ัะผะพะดะทะธ Talia" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "ะšะพะณะดะฐ fprintd (ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธั ะฟะพ ะพั‚ะฟะตั‡ะฐั‚ะบัƒ ะฟะฐะปัŒั†ะฐ) ะฐะบั‚ะธะฒะตะฝ, ัั‚ะฐ ะพะฟั†ะธั ะฟะพะทะฒะพะปัะตั‚ ะฒะฐะผ ะฒั…ะพะดะธั‚ัŒ ะฒ ัะธัั‚ะตะผัƒ ั ะฟะพะผะพั‰ัŒัŽ ะฟะฐั€ะพะปั ะฒะผะตัั‚ะพ ะพั‚ะฟะตั‡ะฐั‚ะบะฐ ะฟะฐะปัŒั†ะฐ.", + "allow-password-with-fprintd-label": "ะ ะฐะทั€ะตัˆะธั‚ัŒ ะฒั…ะพะด ะฟะพ ะฟะฐั€ะพะปัŽ ั ะฟะพะผะพั‰ัŒัŽ fprintd", + "auto-start-auth-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะทะฐะฟัƒัะบะฐะตั‚ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธัŽ ะฟะพ ะพั‚ะฟะตั‡ะฐั‚ะบัƒ ะฟะฐะปัŒั†ะฐ ะฑะตะท ะฝะตะพะฑั…ะพะดะธะผะพัั‚ะธ ะฝะฐะถะฐั‚ะธั ะบะปะฐะฒะธัˆะธ ะธะปะธ ั‰ะตะปั‡ะบะฐ ะบะฝะพะฟะบะธ.", + "auto-start-auth-label": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธะน ะทะฐะฟัƒัะบ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ", + "clock-format-description": "ะะฐัั‚ั€ะพะนั‚ะต ั„ะพั€ะผะฐั‚ ั‡ะฐัะพะฒ, ะธัะฟะพะปัŒะทัƒั ั‚ะพะบะตะฝั‹ ัะธะฝั‚ะฐะบัะธัะฐ ะดะฐั‚ั‹/ะฒั€ะตะผะตะฝะธ.", + "clock-format-label": "ะคะพั€ะผะฐั‚ ั‡ะฐัะพะฒ", + "clock-style-analog": "ะะฝะฐะปะพะณะพะฒั‹ะน", + "clock-style-custom": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน", + "clock-style-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ะฒะธะทัƒะฐะปัŒะฝั‹ะน ัั‚ะธะปัŒ ั‡ะฐัะพะฒ ะฝะฐ ะญะบั€ะฐะฝะต ะฑะปะพะบะธั€ะพะฒะบะธ.", + "clock-style-digital": "ะฆะธั„ั€ะพะฒะพะน", + "clock-style-label": "ะกั‚ะธะปัŒ ะงะฐัะพะฒ", + "compact-lockscreen-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ั‚ะพะปัŒะบะพ ะฟะพะปะต ะดะปั ะฒะฒะพะดะฐ ะปะพะณะธะฝะฐ ะธ ัะธัั‚ะตะผะฝั‹ะต ัะปะตะผะตะฝั‚ั‹ ัƒะฟั€ะฐะฒะปะตะฝะธั, ัะบั€ั‹ะฒะฐั ะฒะธะดะถะตั‚ั‹ ะฟะพะณะพะดั‹ ะธ ะผะตะดะธะฐ.", + "compact-lockscreen-label": "ะšะพะผะฟะฐะบั‚ะฝั‹ะน ัะบั€ะฐะฝ ะฑะปะพะบะธั€ะพะฒะบะธ", + "enable-lockscreen-media-controls-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะธะฝั‚ะตั€ะฐะบั‚ะธะฒะฝั‹ะต ัะปะตะผะตะฝั‚ั‹ ัƒะฟั€ะฐะฒะปะตะฝะธั ะฒะพัะฟั€ะพะธะทะฒะตะดะตะฝะธะตะผ ะผะตะดะธะฐ ะฝะฐ ัะบั€ะฐะฝะต ะฑะปะพะบะธั€ะพะฒะบะธ.", + "enable-lockscreen-media-controls-label": "ะญะปะตะผะตะฝั‚ั‹ ะฃะฟั€ะฐะฒะปะตะฝะธั ะœัƒะปัŒั‚ะธะผะตะดะธะฐ ะฝะฐ ะญะบั€ะฐะฝะต ะ‘ะปะพะบะธั€ะพะฒะบะธ", + "lock-on-suspend-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะฑะปะพะบะธั€ะพะฒะฐั‚ัŒ ัะบั€ะฐะฝ ะฟั€ะธ ะฟั€ะธะพัั‚ะฐะฝะพะฒะบะต ั€ะฐะฑะพั‚ั‹ ัะธัั‚ะตะผั‹.", + "lock-on-suspend-label": "ะ‘ะปะพะบะธั€ะพะฒะฐั‚ัŒ ะฟั€ะธ ะฟั€ะธะพัั‚ะฐะฝะพะฒะบะต", + "lock-screen-animations-description": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะธะปะธ ะพั‚ะบะปัŽั‡ะธั‚ัŒ ะฐะฝะธะผะฐั†ะธะธ ัะบั€ะฐะฝะฐ ะฑะปะพะบะธั€ะพะฒะบะธ.", + "lock-screen-animations-label": "ะะฝะธะผะฐั†ะธะธ ะญะบั€ะฐะฝะฐ ะ‘ะปะพะบะธั€ะพะฒะบะธ", + "lock-screen-blur-strength-description": "ะŸั€ะธะผะตะฝัะตั‚ ัั„ั„ะตะบั‚ ั€ะฐะทะผั‹ั‚ะธั ะบ ะพะฑะพัะผ ัะบั€ะฐะฝะฐ ะฑะปะพะบะธั€ะพะฒะบะธ.", + "lock-screen-blur-strength-label": "ะกะธะปะฐ ั€ะฐะทะผั‹ั‚ะธั ัะบั€ะฐะฝะฐ ะฑะปะพะบะธั€ะพะฒะบะธ", + "lock-screen-tint-strength-description": "ะŸั€ะธะผะตะฝัะตั‚ ะฝะฐะปะพะถะตะฝะธะต ะพั‚ั‚ะตะฝะบะฐ ะบ ะพะฑะพัะผ ัะบั€ะฐะฝะฐ ะฑะปะพะบะธั€ะพะฒะบะธ.", + "lock-screen-tint-strength-label": "ะ˜ะฝั‚ะตะฝัะธะฒะฝะพัั‚ัŒ ั‚ะพะฝะธั€ะพะฒะบะธ ัะบั€ะฐะฝะฐ ะฑะปะพะบะธั€ะพะฒะบะธ", + "monitors-desc": "ะŸะพะบะฐะทะฐั‚ัŒ ัะบั€ะฐะฝ ะฑะปะพะบะธั€ะพะฒะบะธ ะฝะฐ ะพะฟั€ะตะดะตะปะตะฝะฝั‹ั… ะผะพะฝะธั‚ะพั€ะฐั…. ะŸะพ ัƒะผะพะปั‡ะฐะฝะธัŽ ะฝะฐ ะฒัะตั…, ะตัะปะธ ะฝะธ ะพะดะธะฝ ะฝะต ะฒั‹ะฑั€ะฐะฝ.", + "password-chars-description": "ะœะธะปั‹ะต ะธะบะพะฝะบะธ, ะธัะฟะพะปัŒะทัƒะตะผั‹ะต ะดะปั ัะบั€ั‹ั‚ะธั ะฒะฐัˆะตะณะพ ะฟะฐั€ะพะปั.", + "password-chars-label": "ะกะปัƒั‡ะฐะนะฝั‹ะต ะธะบะพะฝะบะธ ะฟะฐั€ะพะปะตะน", + "show-hibernate-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะพะฟั†ะธัŽ 'ัะฟัั‰ะธะน ั€ะตะถะธะผ' ะฒ ัะปะตะผะตะฝั‚ะฐั… ัƒะฟั€ะฐะฒะปะตะฝะธั ะฟะธั‚ะฐะฝะธะตะผ.", + "show-hibernate-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ัะฟัั‰ะธะน ั€ะตะถะธะผ", + "show-session-buttons-description": "ะ ะฐะทั€ะตัˆะธั‚ัŒ ะดะพัั‚ัƒะฟ ะบ ะฝะฐัั‚ั€ะพะนะบะฐะผ ะฟะธั‚ะฐะฝะธั ั ัะบั€ะฐะฝะฐ ะฑะปะพะบะธั€ะพะฒะบะธ.", + "show-session-buttons-label": "ะญะปะตะผะตะฝั‚ั‹ ัƒะฟั€ะฐะฒะปะตะฝะธั ะฟะธั‚ะฐะฝะธะตะผ", + "title": "ะญะบั€ะฐะฝ ะฑะปะพะบะธั€ะพะฒะบะธ" + }, + "network": { + "bluetooth-description": "ะ’ะบะปัŽั‡ะธั‚ัŒ ัƒะฟั€ะฐะฒะปะตะฝะธะต Bluetooth.", + "bluetooth-rssi-polling-description": "ะŸะตั€ะธะพะดะธั‡ะตัะบะธ ะพะฟั€ะฐัˆะธะฒะฐั‚ัŒ RSSI ะดะปั ะฟะพะดะบะปัŽั‡ั‘ะฝะฝั‹ั… ัƒัั‚ั€ะพะนัั‚ะฒ ั‡ะตั€ะตะท bluetoothctl. ะœะพะถะตั‚ ะฑั‹ั‚ัŒ ะฝะตะดะพัั‚ัƒะฟะฝะพ ะดะปั ะฒัะตั… ัƒัั‚ั€ะพะนัั‚ะฒ; ะธัะฟะพะปัŒะทัƒะตั‚ ะผะธะฝะธะผะฐะปัŒะฝั‹ะต ั€ะตััƒั€ัั‹ ะฟั€ะธ ะฒะบะปัŽั‡ะตะฝะธะธ.", + "bluetooth-rssi-polling-label": "ะžะฟั€ะพั ัะธะณะฝะฐะปะฐ Bluetooth", + "desc": "ะฃะฟั€ะฐะฒะปะตะฝะธะต Wi-Fi ะธ Bluetooth ะฟะพะดะบะปัŽั‡ะตะฝะธัะผะธ.", + "wifi-description": "ะฃะฟั€ะฐะฒะปะตะฝะธะต ะฑะตัะฟั€ะพะฒะพะดะฝั‹ะผะธ ัะตั‚ัะผะธ (ั‚ั€ะตะฑัƒะตั‚ัั NetworkManager)." + }, + "notifications": { + "duration-critical-urgency-description": "ะšะฐะบ ะดะพะปะณะพ ะพัั‚ะฐัŽั‚ัั ะฒะธะดะธะผั‹ะผะธ ัƒะฒะตะดะพะผะปะตะฝะธั ั ะบั€ะธั‚ะธั‡ะตัะบะธะผ ะฟั€ะธะพั€ะธั‚ะตั‚ะพะผ.", + "duration-critical-urgency-label": "ะšั€ะธั‚ะธั‡ะตัะบะฐั ัั€ะพั‡ะฝะพัั‚ัŒ", + "duration-desc": "ะะฐัั‚ั€ะพะนะบะฐ ะฒั€ะตะผะตะฝะธ ะฒะธะดะธะผะพัั‚ะธ ัƒะฒะตะดะพะผะปะตะฝะธะน ะฒ ะทะฐะฒะธัะธะผะพัั‚ะธ ะพั‚ ะธั… ัƒั€ะพะฒะฝั ัั€ะพั‡ะฝะพัั‚ะธ.", + "duration-low-urgency-description": "ะšะฐะบ ะดะพะปะณะพ ะพัั‚ะฐัŽั‚ัั ะฒะธะดะธะผั‹ะผะธ ัƒะฒะตะดะพะผะปะตะฝะธั ั ะฝะธะทะบะธะผ ะฟั€ะธะพั€ะธั‚ะตั‚ะพะผ.", + "duration-low-urgency-label": "ะะธะทะบะฐั ัั€ะพั‡ะฝะพัั‚ัŒ", + "duration-normal-urgency-description": "ะšะฐะบ ะดะพะปะณะพ ะพัั‚ะฐัŽั‚ัั ะฒะธะดะธะผั‹ะผะธ ัƒะฒะตะดะพะผะปะตะฝะธั ั ะฝะพั€ะผะฐะปัŒะฝั‹ะผ ะฟั€ะธะพั€ะธั‚ะตั‚ะพะผ.", + "duration-normal-urgency-label": "ะะพั€ะผะฐะปัŒะฝะฐั ัั€ะพั‡ะฝะพัั‚ัŒ", + "duration-respect-expire-description": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ั‚ะฐะนะผะฐัƒั‚ ะธัั‚ะตั‡ะตะฝะธั, ัƒัั‚ะฐะฝะพะฒะปะตะฝะฝั‹ะน ะฒ ัƒะฒะตะดะพะผะปะตะฝะธะธ.", + "duration-respect-expire-label": "ะฃั‡ะธั‚ั‹ะฒะฐั‚ัŒ ั‚ะฐะนะผะฐัƒั‚ ะธัั‚ะตั‡ะตะฝะธั", + "duration-title": "ะŸั€ะพะดะพะปะถะธั‚ะตะปัŒะฝะพัั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธะน", + "history-clear-dismiss-description": "ะฃะดะฐะปัั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธะต ะธะท ะธัั‚ะพั€ะธะธ ะฟั€ะธ ะทะฐะบั€ั‹ั‚ะธะธ.", + "history-clear-dismiss-label": "ะžั‡ะธั‰ะฐั‚ัŒ ะฟั€ะธ ะทะฐะบั€ั‹ั‚ะธะธ", + "history-critical-urgency-description": "ะกะพั…ั€ะฐะฝัั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธั ั ะบั€ะธั‚ะธั‡ะตัะบะธะผ ะฟั€ะธะพั€ะธั‚ะตั‚ะพะผ ะฒ ะธัั‚ะพั€ะธะธ.", + "history-critical-urgency-label": "ะกะพั…ั€ะฐะฝัั‚ัŒ ะบั€ะธั‚ะธั‡ะตัะบัƒัŽ ัั€ะพั‡ะฝะพัั‚ัŒ ะฒ ะธัั‚ะพั€ะธะธ", + "history-desc": "ะฃะฟั€ะฐะฒะปะตะฝะธะต ั‚ะตะผ, ะบะฐะบะธะต ัƒะฒะตะดะพะผะปะตะฝะธั ัะพั…ั€ะฐะฝััŽั‚ัั ะฒ ะธัั‚ะพั€ะธะธ ะฒ ะทะฐะฒะธัะธะผะพัั‚ะธ ะพั‚ ะธั… ัƒั€ะพะฒะฝั ัั€ะพั‡ะฝะพัั‚ะธ.", + "history-low-urgency-description": "ะกะพั…ั€ะฐะฝัั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธั ั ะฝะธะทะบะธะผ ะฟั€ะธะพั€ะธั‚ะตั‚ะพะผ ะฒ ะธัั‚ะพั€ะธะธ.", + "history-low-urgency-label": "ะกะพั…ั€ะฐะฝัั‚ัŒ ะฝะธะทะบัƒัŽ ัั€ะพั‡ะฝะพัั‚ัŒ ะฒ ะธัั‚ะพั€ะธะธ", + "history-normal-urgency-description": "ะกะพั…ั€ะฐะฝัั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธั ั ะฝะพั€ะผะฐะปัŒะฝั‹ะผ ะฟั€ะธะพั€ะธั‚ะตั‚ะพะผ ะฒ ะธัั‚ะพั€ะธะธ.", + "history-normal-urgency-label": "ะกะพั…ั€ะฐะฝัั‚ัŒ ะฝะพั€ะผะฐะปัŒะฝัƒัŽ ัั€ะพั‡ะฝะพัั‚ัŒ ะฒ ะธัั‚ะพั€ะธะธ", + "monitors-desc": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธั ะฝะฐ ะพะฟั€ะตะดะตะปั‘ะฝะฝั‹ั… ะผะพะฝะธั‚ะพั€ะฐั…. ะŸะพ ัƒะผะพะปั‡ะฐะฝะธัŽ ะฝะฐ ะฒัะตั…, ะตัะปะธ ะฝะธ ะพะดะธะฝ ะฝะต ะฒั‹ะฑั€ะฐะฝ.", + "rules-action-block": "ะ‘ะปะพะบ", + "rules-action-block-desc": "ะŸั€ะพะฟัƒัะบะฐะตั‚ ะฟะพะปะฝะพัั‚ัŒัŽ.", + "rules-action-hide": "ะกะบั€ั‹ั‚ัŒ", + "rules-action-hide-desc": "ะ‘ะตะท ะฒัะฟะปั‹ะฒะฐัŽั‰ะธั… ะพะบะพะฝ, ะฑะตะท ะทะฒัƒะบะฐ, ะดะพะฑะฐะฒะปัะตั‚ ะฒ ะธัั‚ะพั€ะธัŽ.", + "rules-action-label": "ะ”ะตะนัั‚ะฒะธะต", + "rules-action-mute": "ะ‘ะตะท ะทะฒัƒะบะฐ", + "rules-action-mute-desc": "ะะตั‚ ะทะฒัƒะบะฐ, ะฒัะฟะปั‹ะฒะฐัŽั‰ะตะต ะพะบะฝะพ ะฒัั‘ ั€ะฐะฒะฝะพ ะพั‚ะพะฑั€ะฐะถะฐะตั‚ัั ะธ ะฒ ะธัั‚ะพั€ะธะธ.", + "rules-add": "ะ”ะพะฑะฐะฒะธั‚ัŒ ะฟั€ะฐะฒะธะปะพ", + "rules-delete": "ะฃะดะฐะปะธั‚ัŒ ะฟั€ะฐะฒะธะปะพ", + "rules-description": "ะกะพะฟะพัั‚ะฐะฒะธั‚ัŒ ะฝะฐะทะฒะฐะฝะธะต ะฟั€ะธะปะพะถะตะฝะธั ะธะปะธ ัะพะดะตั€ะถะธะผะพะต. ะŸั€ะฐะฒะธะปะฐ ะฟั€ะพะฒะตั€ััŽั‚ัั ะฟะพ ะฟะพั€ัะดะบัƒ, ะธ ะฟะตั€ะฒะพะต ัะพะฒะฟะฐะดะตะฝะธะต ะฟั€ะธะผะตะฝัะตั‚ัั.", + "rules-edit": "ะ ะตะดะฐะบั‚ะธั€ะพะฒะฐั‚ัŒ ะฟั€ะฐะฒะธะปะพ", + "rules-label": "ะŸั€ะฐะฒะธะปะฐ ะคะธะปัŒั‚ั€ะฐั†ะธะธ", + "rules-pattern-label": "ะจะฐะฑะปะพะฝ", + "rules-pattern-placeholder": "firefox, discord, ะธะปะธ /regex/", + "rules-tab": "ะŸั€ะฐะฒะธะปะฐ", + "settings-always-on-top-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธั ะฟะพะฒะตั€ั… ะฟะพะปะฝะพัะบั€ะฐะฝะฝั‹ั… ะพะบะพะฝ ะธ ะดั€ัƒะณะธั… ัะปะพั‘ะฒ.", + "settings-background-opacity-description": "ะะฐัั‚ั€ะพะนะบะฐ ะฝะตะฟั€ะพะทั€ะฐั‡ะฝะพัั‚ะธ ั„ะพะฝะฐ ัƒะฒะตะดะพะผะปะตะฝะธะน.", + "settings-density-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ะฟะปะพั‚ะฝะพัั‚ัŒ ะบะฐั€ั‚ะพั‡ะบะธ ัƒะฒะตะดะพะผะปะตะฝะธะน.", + "settings-density-label": "ะŸะปะพั‚ะฝะพัั‚ัŒ", + "settings-desc": "ะะฐัั‚ั€ะพะนะบะฐ ะฒะฝะตัˆะฝะตะณะพ ะฒะธะดะฐ ะธ ะฟะพะฒะตะดะตะฝะธั ัƒะฒะตะดะพะผะปะตะฝะธะน.", + "settings-do-not-disturb-description": "ะžั‚ะบะปัŽั‡ะธั‚ัŒ ะฒัะต ะฒัะฟะปั‹ะฒะฐัŽั‰ะธะต ะพะบะฝะฐ ัƒะฒะตะดะพะผะปะตะฝะธะน, ะตัะปะธ ะฒะบะปัŽั‡ะตะฝะพ.", + "settings-enabled-description": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะธะปะธ ะพั‚ะบะปัŽั‡ะธั‚ัŒ ะดะตะผะพะฝ ัƒะฒะตะดะพะผะปะตะฝะธะน, ั‚ั€ะตะฑัƒะตั‚ัั ะฟะตั€ะตะทะฐะฟัƒัะบ Noctalia shell.", + "settings-enabled-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธั", + "settings-location-description": "ะ“ะดะต ัƒะฒะตะดะพะผะปะตะฝะธั ะฟะพัะฒะปััŽั‚ัั ะฝะฐ ัะบั€ะฐะฝะต.", + "settings-markdown-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ัะพะดะตั€ะถะธะผะพะต ัƒะฒะตะดะพะผะปะตะฝะธะน, ะธัะฟะพะปัŒะทัƒั ั„ะพั€ะผะฐั‚ะธั€ะพะฒะฐะฝะธะต Markdown.", + "settings-markdown-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ Markdown", + "sounds-desc": "ะะฐัั‚ั€ะพะธั‚ัŒ ะทะฒัƒะบะพะฒั‹ะต ัั„ั„ะตะบั‚ั‹ ะธ ะณั€ะพะผะบะพัั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธะน.", + "sounds-enabled-description": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะทะฒัƒะบะพะฒั‹ะต ัั„ั„ะตะบั‚ั‹ ะดะปั ะฒั…ะพะดัั‰ะธั… ัƒะฒะตะดะพะผะปะตะฝะธะน.", + "sounds-enabled-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะทะฒัƒะบะธ ัƒะฒะตะดะพะผะปะตะฝะธะน", + "sounds-excluded-apps-description": "ะŸั€ะพะฟัƒัะบะฐั‚ัŒ ะฒะพัะฟั€ะพะธะทะฒะตะดะตะฝะธะต ะฝะฐัั‚ั€ะพะตะฝะฝะพะณะพ ะทะฒัƒะบะฐ ัƒะฒะตะดะพะผะปะตะฝะธะน ะดะปั ะพะฟั€ะตะดะตะปั‘ะฝะฝั‹ั… ะฟั€ะธะปะพะถะตะฝะธะน, ะธะผะตัŽั‰ะธั… ัะพะฑัั‚ะฒะตะฝะฝั‹ะต ะฒัั‚ั€ะพะตะฝะฝั‹ะต ะทะฒัƒะบะธ.", + "sounds-excluded-apps-label": "ะ˜ัะบะปัŽั‡ั‘ะฝะฝั‹ะต ะฟั€ะธะปะพะถะตะฝะธั", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "ะŸัƒั‚ัŒ ะบ ะทะฒัƒะบะพะฒะพะผัƒ ั„ะฐะนะปัƒ, ะฒะพัะฟั€ะพะธะทะฒะพะดะธะผะพะผัƒ ะดะปั ัƒะฒะตะดะพะผะปะตะฝะธะน ั ะบั€ะธั‚ะธั‡ะตัะบะธะผ ะฟั€ะธะพั€ะธั‚ะตั‚ะพะผ.", + "sounds-files-critical-label": "ะ—ะฒัƒะบ ะบั€ะธั‚ะธั‡ะตัะบะพะน ะฒะฐะถะฝะพัั‚ะธ", + "sounds-files-critical-select-title": "ะ’ั‹ะฑั€ะฐั‚ัŒ ั„ะฐะนะป ะทะฒัƒะบะฐ ะบั€ะธั‚ะธั‡ะตัะบะพะน ะฒะฐะถะฝะพัั‚ะธ", + "sounds-files-desc": "ะะฐัั‚ั€ะพะธั‚ัŒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะต ะทะฒัƒะบะพะฒั‹ะต ั„ะฐะนะปั‹ ะดะปั ั€ะฐะทะฝั‹ั… ัƒั€ะพะฒะฝะตะน ะฒะฐะถะฝะพัั‚ะธ ัƒะฒะตะดะพะผะปะตะฝะธะน.", + "sounds-files-low-description": "ะŸัƒั‚ัŒ ะบ ะทะฒัƒะบะพะฒะพะผัƒ ั„ะฐะนะปัƒ, ะฒะพัะฟั€ะพะธะทะฒะพะดะธะผะพะผัƒ ะดะปั ัƒะฒะตะดะพะผะปะตะฝะธะน ั ะฝะธะทะบะธะผ ะฟั€ะธะพั€ะธั‚ะตั‚ะพะผ.", + "sounds-files-low-label": "ะ—ะฒัƒะบ ะฝะธะทะบะพะน ะฒะฐะถะฝะพัั‚ะธ", + "sounds-files-low-select-title": "ะ’ั‹ะฑั€ะฐั‚ัŒ ั„ะฐะนะป ะทะฒัƒะบะฐ ะฝะธะทะบะพะน ะฒะฐะถะฝะพัั‚ะธ", + "sounds-files-normal-description": "ะŸัƒั‚ัŒ ะบ ะทะฒัƒะบะพะฒะพะผัƒ ั„ะฐะนะปัƒ, ะฒะพัะฟั€ะพะธะทะฒะพะดะธะผะพะผัƒ ะดะปั ัƒะฒะตะดะพะผะปะตะฝะธะน ั ะฝะพั€ะผะฐะปัŒะฝั‹ะผ ะฟั€ะธะพั€ะธั‚ะตั‚ะพะผ.", + "sounds-files-normal-label": "ะ—ะฒัƒะบ ะฝะพั€ะผะฐะปัŒะฝะพะน ะฒะฐะถะฝะพัั‚ะธ", + "sounds-files-normal-select-title": "ะ’ั‹ะฑั€ะฐั‚ัŒ ั„ะฐะนะป ะทะฒัƒะบะฐ ะฝะพั€ะผะฐะปัŒะฝะพะน ะฒะฐะถะฝะพัั‚ะธ", + "sounds-files-placeholder": "ะ’ะฒะตะดะธั‚ะต ะฟัƒั‚ัŒ ะบ ะทะฒัƒะบะพะฒะพะผัƒ ั„ะฐะนะปัƒ", + "sounds-files-select-file": "ะ’ั‹ะฑั€ะฐั‚ัŒ ะทะฒัƒะบะพะฒะพะน ั„ะฐะนะป", + "sounds-files-title": "ะ—ะฒัƒะบะพะฒั‹ะต ั„ะฐะนะปั‹", + "sounds-files-unified-description": "ะŸัƒั‚ัŒ ะบ ะทะฒัƒะบะพะฒะพะผัƒ ั„ะฐะนะปัƒ, ะฒะพัะฟั€ะพะธะทะฒะพะดะธะผะพะผัƒ ะดะปั ัƒะฒะตะดะพะผะปะตะฝะธะน.", + "sounds-files-unified-label": "ะ—ะฒัƒะบ ัƒะฒะตะดะพะผะปะตะฝะธั", + "sounds-files-unified-select-title": "ะ’ั‹ะฑั€ะฐั‚ัŒ ั„ะฐะนะป ะทะฒัƒะบะฐ ัƒะฒะตะดะพะผะปะตะฝะธั", + "sounds-separate-description": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ั€ะฐะทะฝั‹ะต ะทะฒัƒะบะพะฒั‹ะต ั„ะฐะนะปั‹ ะดะปั ัƒะฒะตะดะพะผะปะตะฝะธะน ั ะฝะธะทะบะธะผ, ะฝะพั€ะผะฐะปัŒะฝั‹ะผ ะธ ะบั€ะธั‚ะธั‡ะตัะบะธะผ ะฟั€ะธะพั€ะธั‚ะตั‚ะพะผ.", + "sounds-separate-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ั€ะฐะทะฝั‹ะต ะทะฒัƒะบะธ ะดะปั ั€ะฐะทะฝั‹ั… ะฟั€ะธะพั€ะธั‚ะตั‚ะพะฒ", + "sounds-title": "ะะฐัั‚ั€ะพะนะบะธ ะทะฒัƒะบะฐ", + "sounds-unavailable-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ะต Qt6 Multimedia ะดะปั ะฒะบะปัŽั‡ะตะฝะธั ะทะฒัƒะบะพะฒ ัƒะฒะตะดะพะผะปะตะฝะธะน.", + "sounds-unavailable-label": "ะ—ะฒัƒะบะธ ัƒะฒะตะดะพะผะปะตะฝะธะน ะฝะตะดะพัั‚ัƒะฟะฝั‹", + "sounds-volume-description": "ะะฐัั‚ั€ะพะธั‚ัŒ ัƒั€ะพะฒะตะฝัŒ ะณั€ะพะผะบะพัั‚ะธ ะดะปั ะทะฒัƒะบะพะฒ ัƒะฒะตะดะพะผะปะตะฝะธะน.", + "sounds-volume-label": "ะ“ั€ะพะผะบะพัั‚ัŒ ะทะฒัƒะบะฐ", + "toast-battery-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฟั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะต, ะบะพะณะดะฐ ัƒั€ะพะฒะตะฝัŒ ะทะฐั€ัะดะฐ ะฑะฐั‚ะฐั€ะตะธ ะพะฟัƒัะบะฐะตั‚ัั ะฝะธะถะต ัั‚ะพะณะพ ะฟั€ะพั†ะตะฝั‚ะฐ.", + "toast-battery-label": "ะŸั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะต ะพ ะฑะฐั‚ะฐั€ะตะต", + "toast-desc": "ะะฐัั‚ั€ะพะธั‚ัŒ ะฒะฝะตัˆะฝะธะน ะฒะธะด ะธ ะฟะพะฒะตะดะตะฝะธะต ะฒัะฟะปั‹ะฒะฐัŽั‰ะธั… ัƒะฒะตะดะพะผะปะตะฝะธะน.", + "toast-keyboard-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฒัะฟะปั‹ะฒะฐัŽั‰ะตะต ัƒะฒะตะดะพะผะปะตะฝะธะต ะฟั€ะธ ัะผะตะฝะต ั€ะฐัะบะปะฐะดะบะธ ะบะปะฐะฒะธะฐั‚ัƒั€ั‹.", + "toast-keyboard-label": "ะ ะฐัะบะปะฐะดะบะฐ ะบะปะฐะฒะธะฐั‚ัƒั€ั‹", + "toast-media-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฒัะฟะปั‹ะฒะฐัŽั‰ะตะต ัƒะฒะตะดะพะผะปะตะฝะธะต ะฟั€ะธ ะธะทะผะตะฝะตะฝะธะธ ัะพัั‚ะพัะฝะธั ะฒะพัะฟั€ะพะธะทะฒะตะดะตะฝะธั ะผะตะดะธะฐ.", + "toast-media-label": "ะœะตะดะธะฐ" + }, + "osd": { + "always-on-top-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ OSD ะฟะพะฒะตั€ั… ะฟะพะปะฝะพัะบั€ะฐะฝะฝั‹ั… ะพะบะพะฝ ะธ ะดั€ัƒะณะธั… ัะปะพั‘ะฒ.", + "always-on-top-label": "ะ’ัะตะณะดะฐ ัะฒะตั€ั…ัƒ", + "background-opacity-description": "ะฃะฟั€ะฐะฒะปัะตั‚ ะฟั€ะพะทั€ะฐั‡ะฝะพัั‚ัŒัŽ ั„ะพะฝะฐ OSD.", + "background-opacity-label": "ะŸั€ะพะทั€ะฐั‡ะฝะพัั‚ัŒ ั„ะพะฝะฐ", + "description": "ะะฐัั‚ั€ะพะนะบะฐ ัะบั€ะฐะฝะฝั‹ั… ะธะฝะดะธะบะฐั‚ะพั€ะพะฒ, ั‚ะฐะบะธั… ะบะฐะบ ะพะฒะตั€ะปะตะธ ะณั€ะพะผะบะพัั‚ะธ ะธ ัั€ะบะพัั‚ะธ.", + "duration-auto-hide-description": "ะะฐัั‚ั€ะพะนะบะฐ ะฒั€ะตะผะตะฝะธ, ะฟั€ะตะถะดะต ั‡ะตะผ OSD ะธัั‡ะตะทะฝะตั‚.", + "duration-auto-hide-label": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ัะบั€ั‹ะฒะฐั‚ัŒ ั‡ะตั€ะตะท", + "duration-desc": "ะšะฐะบ ะดะพะปะณะพ OSD ะพัั‚ะฐั‘ั‚ัั ะฒะธะดะธะผั‹ะผ ะฟะตั€ะตะด ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธะผ ัะบั€ั‹ั‚ะธะตะผ.", + "duration-title": "ะขะฐะนะผะฐัƒั‚ ะฐะฒั‚ะพัะบั€ั‹ั‚ะธั", + "enabled-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะธะทะผะตะฝะตะฝะธั ะณั€ะพะผะบะพัั‚ะธ ะธ ัั€ะบะพัั‚ะธ ะฒ ั€ะตะฐะปัŒะฝะพะผ ะฒั€ะตะผะตะฝะธ.", + "enabled-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธั (OSD)", + "events-desc": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะฟะพ ะบะฐะบะธะผ ัะพะฑั‹ั‚ะธัะผ ะฒั‹ะฒะพะดะธั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธั (OSD).", + "general-desc": "ะะฐัั‚ั€ะพะธั‚ัŒ ะฒะธะดะธะผะพัั‚ัŒ ะธ ะฟะพะฒะตะดะตะฝะธะต ัƒะฒะตะดะพะผะปะตะฝะธะน (OSD).", + "location-description": "ะ ะฐัะฟะพะปะพะถะตะฝะธะต ัะบั€ะฐะฝะฝั‹ั… ัƒะฒะตะดะพะปะตะฝะธะน.", + "monitors-desc": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ OSD ะฝะฐ ะพะฟั€ะตะดะตะปั‘ะฝะฝั‹ั… ะผะพะฝะธั‚ะพั€ะฐั…. ะŸะพ ัƒะผะพะปั‡ะฐะฝะธัŽ ะฝะฐ ะฒัะตั…, ะตัะปะธ ะฝะธ ะพะดะธะฝ ะฝะต ะฒั‹ะฑั€ะฐะฝ.", + "title": "ะฃะฒะตะดะพะผะปะตะฝะธั", + "types-brightness-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ OSD ะฟั€ะธ ะธะทะผะตะฝะตะฝะธะธ ัั€ะบะพัั‚ะธ ัะบั€ะฐะฝะฐ.", + "types-brightness-label": "ะฏั€ะบะพัั‚ัŒ", + "types-custom-text-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ OSD ะดะปั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธั… ั‚ะตะบัั‚ะพะฒั‹ั… ัะพะพะฑั‰ะตะฝะธะน ั‡ะตั€ะตะท IPC.", + "types-custom-text-label": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ั‚ะตะบัั‚", + "types-desc": "ะ’ั‹ะฑะตั€ะธั‚ะต ัะพะฑั‹ั‚ะธั, ะบะพั‚ะพั€ั‹ะต ะดะพะปะถะฝั‹ ะทะฐะฟัƒัะบะฐั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธั (OSD).", + "types-input-volume-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ OSD ะฟั€ะธ ะธะทะผะตะฝะตะฝะธะธ ะณั€ะพะผะบะพัั‚ะธ ะผะธะบั€ะพั„ะพะฝะฐ.", + "types-input-volume-label": "ะ’ั…ะพะดะฝะฐั ะณั€ะพะผะบะพัั‚ัŒ", + "types-lockkey-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ OSD ะฟั€ะธ ะฟะตั€ะตะบะปัŽั‡ะตะฝะธะธ ะบะปะฐะฒะธัˆ Caps Lock, Num Lock ะธะปะธ Scroll Lock.", + "types-lockkey-label": "ะšะปะฐะฒะธัˆะธ ะฑะปะพะบะธั€ะพะฒะบะธ", + "types-media-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ OSD ะฟั€ะธ ะธะทะผะตะฝะตะฝะธะธ ัะพัั‚ะพัะฝะธั ะฒะพัะฟั€ะพะธะทะฒะตะดะตะฝะธั ะผะตะดะธะฐ (ะฒะพัะฟั€ะพะธะทะฒะตะดะตะฝะธะต, ะฟะฐัƒะทะฐ, ะฟั€ะพะฟัƒัะบ).", + "types-media-label": "ะ’ะพัะฟั€ะพะธะทะฒะตะดะตะฝะธะต ะผะตะดะธะฐ", + "types-title": "ะกะพะฑั‹ั‚ะธั, ะทะฐะฟัƒัะบะฐัŽั‰ะธะต ัƒะฒะตะดะพะผะปะตะฝะธั (OSD)", + "types-volume-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ OSD ะฟั€ะธ ะธะทะผะตะฝะตะฝะธะธ ะณั€ะพะผะบะพัั‚ะธ ะฐัƒะดะธะพะฒั‹ั…ะพะดะฐ.", + "types-volume-label": "ะ’ั‹ั…ะพะดะฝะฐั ะณั€ะพะผะบะพัั‚ัŒ" + }, + "plugins": { + "auto-update": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะพะต ะพะฑะฝะพะฒะปะตะฝะธะต ะฟะปะฐะณะธะฝะพะฒ", + "auto-update-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะพะฑะฝะพะฒะปัั‚ัŒ ะฒัะต ะฟะปะฐะณะธะฝั‹ ะฟั€ะธ ะทะฐะฟัƒัะบะต ะพะฑะพะปะพั‡ะบะธ.", + "available-description": "ะŸั€ะพัะผะพั‚ั€ ะธ ัƒัั‚ะฐะฝะพะฒะบะฐ ะฟะปะฐะณะธะฝะพะฒ ะธะท ะฝะฐัั‚ั€ะพะตะฝะฝั‹ั… ะธัั‚ะพั‡ะฝะธะบะพะฒ.", + "available-label": "ะ”ะพัั‚ัƒะฟะฝั‹ะต ะฟะปะฐะณะธะฝั‹", + "available-no-plugins-description": "ะŸั€ะพะฒะตั€ัŒั‚ะต ะธัั…ะพะดะฝะธะบะธ ะฒะฐัˆะตะณะพ ะฟะปะฐะณะธะฝะฐ ะธะปะธ ะพะฑะฝะพะฒะธั‚ะต ัะฟะธัะพะบ.", + "available-no-plugins-label": "ะะตั‚ ะดะพัั‚ัƒะฟะฝั‹ั… ะฟะปะฐะณะธะฝะพะฒ", + "check-for-updates": "ะŸั€ะพะฒะตั€ะธั‚ัŒ ะพะฑะฝะพะฒะปะตะฝะธั", + "checking-for-updates": "ะŸั€ะพะฒะตั€ะบะฐ ะพะฑะฝะพะฒะปะตะฝะธะน...", + "collision-already-installed": "ะญั‚ะพั‚ ะฟะปะฐะณะธะฝ ัƒะถะต ัƒัั‚ะฐะฝะพะฒะปะตะฝ", + "collision-custom-version-exists": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะฐั ะฒะตั€ัะธั ะธะท \"{source}\" ัƒะถะต ัƒัั‚ะฐะฝะพะฒะปะตะฝะฐ", + "collision-official-version-exists": "ะžั„ะธั†ะธะฐะปัŒะฝะฐั ะฒะตั€ัะธั ัั‚ะพะณะพ ะฟะปะฐะณะธะฝะฐ ัƒะถะต ัƒัั‚ะฐะฝะพะฒะปะตะฝะฐ", + "development-disable": "ะžั‚ะบะปัŽั‡ะธั‚ัŒ ั€ะตะถะธะผ ั€ะฐะทั€ะฐะฑะพั‚ะบะธ ะดะปั ัั‚ะพะณะพ ะฟะปะฐะณะธะฝะฐ", + "development-enable": "ะ’ะบะปัŽั‡ะธั‚ัŒ ั€ะตะถะธะผ ั€ะฐะทั€ะฐะฑะพั‚ะบะธ ะดะปั ัั‚ะพะณะพ ะฟะปะฐะณะธะฝะฐ.", + "filter-downloaded": "ะกะบะฐั‡ะฐะฝะพ", + "filter-not-downloaded": "ะะต ะทะฐะณั€ัƒะถะตะฝะพ", + "filter-tags-description": "ะคะธะปัŒั‚ั€ะพะฒะฐั‚ัŒ ะฟะปะฐะณะธะฝั‹ ะฟะพ ะบะฐั‚ะตะณะพั€ะธะธ ะธะปะธ ัั‚ะฐั‚ัƒััƒ ะทะฐะณั€ัƒะทะบะธ.", + "filter-tags-label": "ะขะตะณะธ", + "hot-reload-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะฟะตั€ะตะทะฐะณั€ัƒะถะฐั‚ัŒ ะฟะปะฐะณะธะฝั‹ ะฟั€ะธ ะธะทะผะตะฝะตะฝะธะธ ะธั… ั„ะฐะนะปะพะฒ. ะŸะพะปะตะทะฝะพ ะดะปั ั€ะฐะทั€ะฐะฑะพั‚ะบะธ ะฟะปะฐะณะธะฝะพะฒ.", + "hot-reload-label": "ะ“ะพั€ัั‡ะฐั ะฟะตั€ะตะทะฐะณั€ัƒะทะบะฐ (ั€ะตะถะธะผ ั€ะฐะทั€ะฐะฑะพั‚ะบะธ)", + "hot-reloaded": "ะŸะตั€ะตะทะฐะณั€ัƒะถะตะฝ ะฟะปะฐะณะธะฝ: {name}", + "install-error": "ะะต ัƒะดะฐะปะพััŒ ัƒัั‚ะฐะฝะพะฒะธั‚ัŒ: {error}", + "install-incompatible": "{plugin} ั‚ั€ะตะฑัƒะตั‚ Noctalia ะฒะตั€ัะธะธ {version} ะธะปะธ ะฒั‹ัˆะต", + "install-success": "ะฃัะฟะตัˆะฝะพ ัƒัั‚ะฐะฝะพะฒะปะตะฝ {plugin}", + "installed-description": "ะฃะฟั€ะฐะฒะปัะนั‚ะต ะธ ะฝะฐัั‚ั€ะฐะธะฒะฐะนั‚ะต ะฒัะต ะปะพะบะฐะปัŒะฝะพ ัƒัั‚ะฐะฝะพะฒะปะตะฝะฝั‹ะต ะฟะปะฐะณะธะฝั‹.", + "installed-label": "ะฃัั‚ะฐะฝะพะฒะปะตะฝะฝั‹ะต ะฟะปะฐะณะธะฝั‹", + "installed-no-plugins-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ะต ะฟะปะฐะณะธะฝั‹ ะธะท ั€ะฐะทะดะตะปะฐ \"ะ”ะพัั‚ัƒะฟะฝะพ\".", + "installed-no-plugins-label": "ะะตั‚ ัƒัั‚ะฐะฝะพะฒะปะตะฝะฝั‹ั… ะฟะปะฐะณะธะฝะพะฒ", + "installing": "ะฃัั‚ะฐะฝะพะฒะบะฐ {plugin}...", + "notify-updates": "ะฃะฒะตะดะพะผะปะตะฝะธั ะพะฑ ะพะฑะฝะพะฒะปะตะฝะธะธ ะฟะปะฐะณะธะฝะพะฒ", + "notify-updates-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฒัะฟะปั‹ะฒะฐัŽั‰ะตะต ัƒะฒะตะดะพะผะปะตะฝะธะต ะฟั€ะธ ะฝะฐะปะธั‡ะธะธ ะฝะพะฒั‹ั… ะพะฑะฝะพะฒะปะตะฝะธะน ะฟะปะฐะณะธะฝะพะฒ.", + "open-plugin-page": "ะžั‚ะบั€ั‹ั‚ัŒ ัั‚ั€ะฐะฝะธั†ัƒ ะฟะปะฐะณะธะฝะฐ", + "open-plugins-tab": "ะžั‚ะบั€ั‹ั‚ัŒ ะฝะฐัั‚ั€ะพะนะบะธ ะฟะปะฐะณะธะฝะพะฒ", + "plugin-settings-title": "ะะฐัั‚ั€ะพะนะบะธ {plugin}", + "refresh-refreshing": "ะžะฑะฝะพะฒะปะตะฝะธะต ัะฟะธัะบะฐ ะฟะปะฐะณะธะฝะพะฒ...", + "refresh-tooltip": "ะžะฑะฝะพะฒะธั‚ัŒ ะดะพัั‚ัƒะฟะฝั‹ะต ะฟะปะฐะณะธะฝั‹", + "settings-error-not-loaded": "ะŸะปะฐะณะธะฝ ะฝะต ะทะฐะณั€ัƒะถะตะฝ", + "settings-saved": "ะะฐัั‚ั€ะพะนะบะธ ะฟะปะฐะณะธะฝะฐ ัะพั…ั€ะฐะฝะตะฝั‹", + "settings-tooltip": "ะะฐัั‚ั€ะพะนะบะธ ะฟะปะฐะณะธะฝะฐ", + "source-custom": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ะธัั‚ะพั‡ะฝะธะบ", + "sources-add-custom": "ะ”ะพะฑะฐะฒะธั‚ัŒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ั€ะตะฟะพะทะธั‚ะพั€ะธะน", + "sources-add-dialog-description": "ะ”ะพะฑะฐะฒะธั‚ัŒ ั€ะตะฟะพะทะธั‚ะพั€ะธะน GitHub ะฒ ะบะฐั‡ะตัั‚ะฒะต ะธัั‚ะพั‡ะฝะธะบะฐ ะฟะปะฐะณะธะฝะพะฒ.", + "sources-add-dialog-error": "ะะต ัƒะดะฐะปะพััŒ ะดะพะฑะฐะฒะธั‚ัŒ ะธัั‚ะพั‡ะฝะธะบ ะฟะปะฐะณะธะฝะฐ", + "sources-add-dialog-name": "ะ˜ะผั ั€ะตะฟะพะทะธั‚ะพั€ะธั", + "sources-add-dialog-name-placeholder": "ะœะพะธ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะต ะฟะปะฐะณะธะฝั‹", + "sources-add-dialog-success": "ะ˜ัั‚ะพั‡ะฝะธะบ ะฟะปะฐะณะธะฝะฐ ัƒัะฟะตัˆะฝะพ ะดะพะฑะฐะฒะปะตะฝ", + "sources-add-dialog-title": "ะ”ะพะฑะฐะฒะธั‚ัŒ ะธัั…ะพะดะฝั‹ะน ะบะพะด ะฟะปะฐะณะธะฝะฐ", + "sources-add-dialog-url": "URL ั€ะตะฟะพะทะธั‚ะพั€ะธั", + "sources-description": "ะฃะฟั€ะฐะฒะปะตะฝะธะต ั€ะตะฟะพะทะธั‚ะพั€ะธัะผะธ ะฟะปะฐะณะธะฝะพะฒ.", + "sources-placeholder": "ะœะพะน ะบั€ัƒั‚ะพะน ั€ะตะฟะพะทะธั‚ะพั€ะธะน", + "sources-remove-tooltip": "ะฃะดะฐะปะธั‚ัŒ ะธัั…ะพะดะฝั‹ะน ะบะพะด ะฟะปะฐะณะธะฝะฐ", + "title": "ะŸะปะฐะณะธะฝั‹", + "translations-reloaded": "ะŸะตั€ะตะฒะพะดั‹ ะฟะตั€ะตะทะฐะณั€ัƒะถะตะฝั‹: {name}", + "uninstall-dialog-description": "ะ’ั‹ ัƒะฒะตั€ะตะฝั‹, ั‡ั‚ะพ ั…ะพั‚ะธั‚ะต ัƒะดะฐะปะธั‚ัŒ {plugin}? ะญั‚ะพ ัƒะดะฐะปะธั‚ ะฒัะต ะดะฐะฝะฝั‹ะต ะฟะปะฐะณะธะฝะฐ.", + "uninstall-dialog-title": "ะฃะดะฐะปะธั‚ัŒ ะฟะปะฐะณะธะฝ", + "uninstall-error": "ะะต ัƒะดะฐะปะพััŒ ัƒะดะฐะปะธั‚ัŒ: {error}", + "uninstall-success": "{plugin} ัƒัะฟะตัˆะฝะพ ัƒะดะฐะปั‘ะฝ", + "uninstalling": "ะฃะดะฐะปะตะฝะธะต {plugin}...", + "update-all": "ะžะฑะฝะพะฒะธั‚ัŒ ะฒัะต ({count})", + "update-all-success": "ะ’ัะต ะฟะปะฐะณะธะฝั‹ ัƒัะฟะตัˆะฝะพ ะพะฑะฝะพะฒะปะตะฝั‹", + "update-available": "ะ”ะพัั‚ัƒะฟะฝะพ ะฝะพะฒะพะต ะพะฑะฝะพะฒะปะตะฝะธะต ะฟะปะฐะณะธะฝะฐ", + "update-available-plural": "ะ”ะพัั‚ัƒะฟะฝั‹ ะฝะพะฒั‹ะต ะพะฑะฝะพะฒะปะตะฝะธั ะฟะปะฐะณะธะฝะพะฒ ({count})", + "update-error": "ะะต ัƒะดะฐะปะพััŒ ะพะฑะฝะพะฒะธั‚ัŒ ะฟะปะฐะณะธะฝ: {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (ั‚ั€ะตะฑัƒะตั‚ัั Noctalia v{required})", + "update-success": "ะŸะปะฐะณะธะฝ {plugin} ะพะฑะฝะพะฒะปั‘ะฝ ะดะพ ะฒะตั€ัะธะธ {version}", + "update-version": "v{current} โ†’ v{new}", + "updating": "ะžะฑะฝะพะฒะปะตะฝะธะต..." + }, + "region": { + "title": "ะ ะตะณะธะพะฝ" + }, + "session-menu": { + "countdown-duration-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ, ะบะฐะบ ะดะพะปะณะพ ะดะปะธั‚ัั ั‚ะฐะนะผะตั€ ะพะฑั€ะฐั‚ะฝะพะณะพ ะพั‚ัั‡ั‘ั‚ะฐ ะฟะตั€ะตะด ะฒั‹ะฟะพะปะฝะตะฝะธะตะผ ะดะตะนัั‚ะฒะธะน ะฟะธั‚ะฐะฝะธั.", + "countdown-duration-label": "ะ”ะปะธั‚ะตะปัŒะฝะพัั‚ัŒ ะพะฑั€ะฐั‚ะฝะพะณะพ ะพั‚ัั‡ั‘ั‚ะฐ", + "enable-countdown-description": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ั‚ะฐะนะผะตั€ ะพะฑั€ะฐั‚ะฝะพะณะพ ะพั‚ัั‡ั‘ั‚ะฐ ะฟะตั€ะตะด ะฒั‹ะฟะพะปะฝะตะฝะธะตะผ ะดะตะนัั‚ะฒะธะน ะฟะธั‚ะฐะฝะธั.", + "enable-countdown-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ั‚ะฐะนะผะตั€ ะพะฑั€ะฐั‚ะฝะพะณะพ ะพั‚ัั‡ั‘ั‚ะฐ", + "entries-desc": "ะะฐัั‚ั€ะพะธั‚ัŒ, ะบะฐะบะธะต ะดะตะนัั‚ะฒะธั ะฟะธั‚ะฐะฝะธั ะพั‚ะพะฑั€ะฐะถะฐัŽั‚ัั ะฒ ะผะตะฝัŽ ัะตะฐะฝัะฐ ะธ ะฒ ะบะฐะบะพะผ ะฟะพั€ัะดะบะต.", + "entries-title": "ะ”ะตะนัั‚ะฒะธั ะฟะธั‚ะฐะฝะธั", + "entry-settings-command-description": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะฐั ะบะพะผะฐะฝะดะฐ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ัั‚ะพะณะพ ะดะตะนัั‚ะฒะธั. ะžัั‚ะฐะฒัŒั‚ะต ะฟัƒัั‚ั‹ะผ, ั‡ั‚ะพะฑั‹ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ัะธัั‚ะตะผะฝัƒัŽ ะบะพะผะฐะฝะดัƒ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ.", + "entry-settings-command-placeholder": "ะฝะฐะฟั€. systemctl poweroff", + "entry-settings-default-command-lock": "ะ’ะฝัƒั‚ั€ะตะฝะฝะธะน ัะบั€ะฐะฝ ะฑะปะพะบะธั€ะพะฒะบะธ (ะฑะตะท ะบะพะผะฐะฝะดั‹)", + "entry-settings-default-command-logout": "ะ’ะฝัƒั‚ั€ะตะฝะฝะธะน ะฒั‹ั…ะพะด ะธะท ัะธัั‚ะตะผั‹ (ะฑะตะท ะบะพะผะฐะฝะดั‹)", + "entry-settings-default-info-description": "ะ•ัะปะธ ะฝะต ัƒะบะฐะทะฐะฝะฐ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะฐั ะบะพะผะฐะฝะดะฐ, ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐะฝะฐ ัะธัั‚ะตะผะฝะฐั ะบะพะผะฐะฝะดะฐ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ.", + "entry-settings-default-info-label": "ะšะพะผะฐะฝะดะฐ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ", + "entry-settings-keybind-description": "ะะฐะทะฝะฐั‡ัŒั‚ะต ะบะพะผะฑะธะฝะฐั†ะธัŽ ะบะปะฐะฒะธัˆ ะดะปั ะฒั‹ะทะพะฒะฐ ัั‚ะพะณะพ ะดะตะนัั‚ะฒะธั, ะบะพะณะดะฐ ะผะตะฝัŽ ัะตะฐะฝัะฐ ะพั‚ะบั€ั‹ั‚ะพ.", + "entry-settings-keybind-placeholder": "ะะฐะถะผะธั‚ะต ะ—ะฐะฟะธััŒ, ั‡ั‚ะพะฑั‹ ะฝะฐะทะฝะฐั‡ะธั‚ัŒ ะบะปะฐะฒะธัˆัƒ...", + "entry-settings-keybind-recording": "ะ—ะฐะฟะธััŒ... ะะฐะถะผะธั‚ะต ะปัŽะฑัƒัŽ ะบะพะผะฑะธะฝะฐั†ะธัŽ ะบะปะฐะฒะธัˆ", + "entry-settings-title": "ะะฐัั‚ั€ะพะธั‚ัŒ {entry}", + "entry-settings-tooltip": "ะะฐัั‚ั€ะพะธั‚ัŒ ะบะพะผะฐะฝะดัƒ", + "general-desc": "ะะฐัั‚ั€ะพะนะบะฐ ะฟะพะฒะตะดะตะฝะธั ะธ ะฒะฝะตัˆะฝะตะณะพ ะฒะธะดะฐ ะฟะฐะฝะตะปะธ ะผะตะฝัŽ ัะตะฐะฝัะฐ.", + "large-buttons-layout-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ัะฟะพัะพะฑ ะพั‚ะพะฑั€ะฐะถะตะฝะธั ะบะฝะพะฟะพะบ ะผะตะฝัŽ ัะตะฐะฝัะฐ.", + "large-buttons-layout-label": "ะšั€ัƒะฟะฝะฐั ั€ะฐัะบะปะฐะดะบะฐ ะบะฝะพะฟะพะบ", + "large-buttons-style-description": "ะžั‚ะพะฑั€ะฐะทะธั‚ัŒ ะผะตะฝัŽ ัะตะฐะฝัะฐ ั ะฑะพะปัŒัˆะธะผะธ ะบะฝะพะฟะบะฐะผะธ ะฒ ะฒะธะดะต ัะตั‚ะบะธ.", + "large-buttons-style-label": "ะกั‚ะธะปัŒ ะบั€ัƒะฟะฝั‹ะต ะบะฝะพะฟะบะธ", + "position-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะณะดะต ะฟะพัะฒะปัะตั‚ัั ะฟะฐะฝะตะปัŒ ะผะตะฝัŽ ัะตะฐะฝัะฐ ะฟั€ะธ ะพั‚ะบั€ั‹ั‚ะธะธ.", + "show-header-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะทะฐะณะพะปะพะฒะพะบ ะธ ะบะฝะพะฟะบัƒ ะทะฐะบั€ั‹ั‚ะธั ะฒ ะฒะตั€ั…ะฝะตะน ั‡ะฐัั‚ะธ ะผะตะฝัŽ ัะตะฐะฝัะฐ.", + "show-header-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะทะฐะณะพะปะพะฒะพะบ", + "show-keybinds-description": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะฟะพะดัะบะฐะทะบะธ ะฟะพ ะณะพั€ัั‡ะธะผ ะบะปะฐะฒะธัˆะฐะผ ะฒ ะฟะฐั€ะฐะผะตั‚ั€ะฐั… ัะตะฐะฝัะฐ.", + "show-keybinds-label": "ะŸะพะบะฐะทะฐั‚ัŒ ัะพั‡ะตั‚ะฐะฝะธั ะบะปะฐะฒะธัˆ" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "ะžัั‚ะฐะฒะปัั‚ัŒ ะฒะธะดะถะตั‚ั‹ ั€ะฐะฑะพั‡ะตะณะพ ัั‚ะพะปะฐ ะฒะธะดะธะผั‹ะผะธ, ะฟะพะบะฐ ั€ะตะถะธะผ ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ะธ Noctalia ะฒะบะปัŽั‡ะตะฝ.", + "noctalia-performance-disable-desktop-widgets-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะฒะธะดะถะตั‚ั‹ ั€ะฐะฑะพั‡ะตะณะพ ัั‚ะพะปะฐ ะฒ ั€ะตะถะธะผะต ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ะธ", + "noctalia-performance-disable-wallpaper-description": "ะกะพั…ั€ะฐะฝัะนั‚ะต ะฒะธะดะธะผั‹ะผะธ ะพะฑะพะธ ั€ะฐะฑะพั‡ะตะณะพ ัั‚ะพะปะฐ, ะพะฑะทะพั€ะฐ ะธ ัะบั€ะฐะฝะฐ ะฑะปะพะบะธั€ะพะฒะบะธ, ะฟะพะบะฐ ั€ะตะถะธะผ ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ะธ Noctalia ะฒะบะปัŽั‡ะตะฝ.", + "noctalia-performance-disable-wallpaper-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ั€ะตะฝะดะตั€ะธะฝะณ ะพะฑะพะตะฒ ะฒ ั€ะตะถะธะผะต ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ะธ", + "title": "ะกะธัั‚ะตะผะฐ" + }, + "system-monitor": { + "critical-color-label": "ะšั€ะธั‚ะธั‡ะตัะบะธะน ั†ะฒะตั‚", + "custom-highlight-colors-title-label": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะต ั†ะฒะตั‚ะฐ ะฒั‹ะดะตะปะตะฝะธั", + "disk-available-label": "ะ”ะพัั‚ัƒะฟะฝะพ ะผะตัั‚ะฐ ะฝะฐ ะดะธัะบะต", + "disk-section-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต ะดะธัะบะฐ", + "enable-dgpu-monitoring-description": "ะ’ะฝะธะผะฐะฝะธะต: ะญั‚ะพ ั€ะฐะทะฑัƒะดะธั‚ ะฒะฐัˆ ะดะธัะบั€ะตั‚ะฝั‹ะน ะณั€ะฐั„ะธั‡ะตัะบะธะน ะฟั€ะพั†ะตััะพั€ (NVIDIA/AMD), ั‡ั‚ะพ ะผะพะถะตั‚ ะทะฝะฐั‡ะธั‚ะตะปัŒะฝะพ ะฟะพะฒะปะธัั‚ัŒ ะฝะฐ ะฒั€ะตะผั ั€ะฐะฑะพั‚ั‹ ะพั‚ ะฐะบะบัƒะผัƒะปัั‚ะพั€ะฐ ะฝะฐ ะฝะพัƒั‚ะฑัƒะบะฐั… ั ะณะธะฑั€ะธะดะฝะพะน ะณั€ะฐั„ะธะบะพะน.", + "enable-dgpu-monitoring-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะผะพะฝะธั‚ะพั€ะธะฝะณ ะดะธัะบั€ะตั‚ะฝะพะณะพ GPU", + "external-monitor-description": "ะ’ะฒะตะดะธั‚ะต ะบะพะผะฐะฝะดัƒ ะธะปะธ ะฟัƒั‚ัŒ ะบ ะฟั€ะธะปะพะถะตะฝะธัŽ ะดะปั ะทะฐะฟัƒัะบะฐ ะฟั€ะธ ะฐะบั‚ะธะฒะฐั†ะธะธ ะฒะฝะตัˆะฝะตะณะพ ะฟั€ะธะปะพะถะตะฝะธั ะผะพะฝะธั‚ะพั€ะธะฝะณะฐ ัะธัั‚ะตะผั‹.", + "external-monitor-label": "ะšะพะผะฐะฝะดะฐ ะฒะฝะตัˆะฝะตะณะพ ะผะพะฝะธั‚ะพั€ะฐ ัะธัั‚ะตะผั‹", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "ะะฐัั‚ั€ะพะธั‚ัŒ ะฟะพะฒะตะดะตะฝะธะต ะธ ะฒะฝะตัˆะฝะธะน ะฒะธะด ัะธัั‚ะตะผะฝะพะณะพ ะผะพะฝะธั‚ะพั€ะฐ.", + "gpu-section-label": "ะขะตะผะฟะตั€ะฐั‚ัƒั€ะฐ GPU", + "highlight-colors-section-label": "ะฆะฒะตั‚ะฐ ะฒั‹ะดะตะปะตะฝะธั", + "polling-interval-label": "ะ˜ะฝั‚ะตั€ะฒะฐะป ะพะฟั€ะพัะฐ", + "polling-section-description": "ะะฐัั‚ั€ะพะนั‚ะต ั‡ะฐัั‚ะพั‚ัƒ ะพะฑะฝะพะฒะปะตะฝะธั ะบะฐะถะดะพะน ัะธัั‚ะตะผะฝะพะน ะผะตั‚ั€ะธะบะธ.", + "polling-section-label": "ะ˜ะฝั‚ะตั€ะฒะฐะปั‹ ะพะฟั€ะพัะฐ", + "threshold-critical": "ะšั€ะธั‚ะธั‡ะตัะบะธะน", + "threshold-warning": "ะŸั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะต", + "thresholds-section-description": "ะะฐัั‚ั€ะพะนั‚ะต ะฟะพั€ะพะณะธ ะฟั€ะตะดัƒะฟั€ะตะถะดะตะฝะธั/ะบั€ะธั‚ะธั‡ะตัะบะธะต ะฟะพั€ะพะณะธ ะธ ะธะฝั‚ะตั€ะฒะฐะปั‹ ะพะฟั€ะพัะฐ ะดะปั ะบะฐะถะดะพะน ัะธัั‚ะตะผะฝะพะน ะผะตั‚ั€ะธะบะธ.", + "use-custom-highlight-colors-description": "ะ•ัะปะธ ะพั‚ะบะปัŽั‡ะตะฝะพ, ะธัะฟะพะปัŒะทัƒัŽั‚ัั ั†ะฒะตั‚ะฐ ะฒั‹ะดะตะปะตะฝะธั ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ ั‚ะตะผั‹.", + "use-custom-highlight-colors-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะต ั†ะฒะตั‚ะฐ ะฒั‹ะดะตะปะตะฝะธั", + "warning-color-label": "ะฆะฒะตั‚ ะฟั€ะตะดัƒะฟั€ะตะถะดะตะฝะธั" + }, + "user-interface": { + "allow-panels-without-bar-description": "ะšะพะณะดะฐ ะฒะบะปัŽั‡ะตะฝะพ, ะฟะฐะฝะตะปะธ ะผะพะณัƒั‚ ะพั‚ะบั€ั‹ะฒะฐั‚ัŒัั ะฝะฐ ะปัŽะฑะพะผ ัะบั€ะฐะฝะต. ะšะพะณะดะฐ ะฒั‹ะบะปัŽั‡ะตะฝะพ, ะฟะฐะฝะตะปะธ ะฑัƒะดัƒั‚ ะพั‚ะบั€ั‹ะฒะฐั‚ัŒัั ั‚ะพะปัŒะบะพ ะฝะฐ ัะบั€ะฐะฝะฐั… ั ะฟะฐะฝะตะปัŒัŽ, ั‡ั‚ะพ ะผะพะถะตั‚ ัะฝะธะทะธั‚ัŒ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะต ะฟะฐะผัั‚ะธ.", + "allow-panels-without-bar-label": "ะ ะฐะทั€ะตัˆะธั‚ัŒ ะฟะฐะฝะตะปะธ ะฝะฐ ัะบั€ะฐะฝะฐั… ะฑะตะท ะฟะฐะฝะตะปะธ ะทะฐะดะฐั‡", + "animation-disable-description": "ะžั‚ะบะปัŽั‡ะธั‚ัŒ ะฒัะต ะฐะฝะธะผะฐั†ะธะธ ะดะปั ะฑะพะปะตะต ะฑั‹ัั‚ั€ะพะณะพ ะธ ะพั‚ะทั‹ะฒั‡ะธะฒะพะณะพ ะฒะทะฐะธะผะพะดะตะนัั‚ะฒะธั.", + "animation-disable-label": "ะžั‚ะบะปัŽั‡ะธั‚ัŒ ะฐะฝะธะผะฐั†ะธะธ ะธะฝั‚ะตั€ั„ะตะนัะฐ", + "animation-speed-description": "ะะฐัั‚ั€ะพะนะบะฐ ะณะปะพะฑะฐะปัŒะฝะพะน ัะบะพั€ะพัั‚ะธ ะฐะฝะธะผะฐั†ะธะธ.", + "animation-speed-label": "ะกะบะพั€ะพัั‚ัŒ ะฐะฝะธะผะฐั†ะธะธ", + "animation-speed-reset": "ะกะฑั€ะพัะธั‚ัŒ ัะบะพั€ะพัั‚ัŒ ะฐะฝะธะผะฐั†ะธะธ", + "appearance-desc": "ะะฐัั‚ั€ะพะนั‚ะต ะฒะธะทัƒะฐะปัŒะฝั‹ะต ัะปะตะผะตะฝั‚ั‹, ั‚ะฐะบะธะต ะบะฐะบ ะฒัะฟะปั‹ะฒะฐัŽั‰ะธะต ะฟะพะดัะบะฐะทะบะธ, ะณั€ะฐะฝะธั†ั‹ ะธ ั‚ะตะฝะธ.", + "blur-behind-description": "ะ ะฐะทะผั‹ะฒะฐะตั‚ ะพะฑะปะฐัั‚ัŒ ะทะฐ ะฟะฐะฝะตะปัะผะธ ะธ ะฒัะฟะปั‹ะฒะฐัŽั‰ะธะผะธ ะพะบะฝะฐะผะธ, ะธัะฟะพะปัŒะทัƒั ะฟั€ะพั‚ะพะบะพะป ั€ะฐะทะผั‹ั‚ะธั ะบะพะผะฟะพะทะธั‚ะพั€ะฐ.", + "blur-behind-label": "ะ ะฐะทะผั‹ั‚ะธะต ั„ะพะฝะฐ", + "box-border-description": "ะžั‚ะพะฑั€ะฐะถะฐะตั‚ ะบะพะฝั‚ัƒั€ ะฒะพะบั€ัƒะณ ะพะฑะปะฐัั‚ะตะน ัะพะดะตั€ะถะธะผะพะณะพ.", + "box-border-label": "ะšะพะฝั‚ัƒั€ ะบะพะฝั‚ะตะนะฝะตั€ะฐ", + "box-border-radius-description": "ะะฐัั‚ั€ะฐะธะฒะฐะตั‚ ัะบั€ัƒะณะปะตะฝะธะต ัƒะณะปะพะฒ ะพัะฝะพะฒะฝั‹ั… ั€ะฐะทะดะตะปะพะฒ ะผะฐะบะตั‚ะฐ, ั‚ะฐะบะธั… ะบะฐะบ ะฑะพะบะพะฒั‹ะต ะฟะฐะฝะตะปะธ, ะบะฐั€ั‚ะพั‡ะบะธ ะธ ะฟะฐะฝะตะปะธ ะบะพะฝั‚ะตะฝั‚ะฐ.", + "box-border-radius-label": "ะ ะฐะดะธัƒั ะบะพะฝั‚ะตะนะฝะตั€ะฐ", + "control-border-radius-description": "ะฃะฟั€ะฐะฒะปัะตั‚ ัะบั€ัƒะณะปะตะฝะธะตะผ ะธะฝั‚ะตั€ะฐะบั‚ะธะฒะฝั‹ั… ัะปะตะผะตะฝั‚ะพะฒ, ะฒะบะปัŽั‡ะฐั ะบะฝะพะฟะบะธ, ะฟะตั€ะตะบะปัŽั‡ะฐั‚ะตะปะธ ะธ ั‚ะตะบัั‚ะพะฒั‹ะต ะฟะพะปั.", + "control-border-radius-label": "ะ ะฐะดะธัƒั ัะปะตะผะตะฝั‚ะพะฒ ะฒะฒะพะดะฐ", + "desc": "ะะฐัั‚ั€ะพะนะบะฐ ะฒะฝะตัˆะฝะตะณะพ ะฒะธะดะฐ, ะพั‰ัƒั‰ะตะฝะธะน ะธ ะฟะพะฒะตะดะตะฝะธั ะธะฝั‚ะตั€ั„ะตะนัะฐ.", + "dim-desktop-description": "ะ—ะฐั‚ะตะผะฝัั‚ัŒ ั€ะฐะฑะพั‡ะธะน ัั‚ะพะป ะฟั€ะธ ะพั‚ะบั€ั‹ั‚ะธะธ ะฟะฐะฝะตะปะตะน ะธะปะธ ะผะตะฝัŽ.", + "dim-desktop-label": "ะ—ะฐั‚ะตะผะฝัั‚ัŒ ั€ะฐะฑะพั‡ะธะน ัั‚ะพะป", + "dimmer-opacity-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ัƒั€ะพะฒะตะฝัŒ ะฝะตะฟั€ะพะทั€ะฐั‡ะฝะพัั‚ะธ ะดะปั ะทะฐั‚ะตะผะฝะตะฝะธั ั€ะฐะฑะพั‡ะตะณะพ ัั‚ะพะปะฐ.", + "dimmer-opacity-label": "ะะตะฟั€ะพะทั€ะฐั‡ะฝะพัั‚ัŒ ะทะฐั‚ะตะผะฝั‘ะฝะฝะพะณะพ ั€ะฐะฑะพั‡ะตะณะพ ัั‚ะพะปะฐ", + "panel-background-opacity-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ะฟั€ะพะทั€ะฐั‡ะฝะพัั‚ัŒ ั„ะพะฝะฐ ะดะปั ะฒัะตั… ะฟะฐะฝะตะปะตะน (ะฒะตั€ั…ะฝะตะน ะฟะฐะฝะตะปะธ, ะฟะฐะฝะตะปะธ ะทะฐะฟัƒัะบะฐ, ะฝะฐัั‚ั€ะพะตะบ ะธ ั‚. ะด.).", + "panel-background-opacity-label": "ะŸั€ะพะทั€ะฐั‡ะฝะพัั‚ัŒ ั„ะพะฝะฐ ะฟะฐะฝะตะปะตะน", + "panels-attached-to-bar-description": "ะŸะฐะฝะตะปะธ ะฟั€ะธะบั€ะตะฟะปััŽั‚ัั ะบ ะฟะฐะฝะตะปะธ ะธ ะบั€ะฐัะผ ัะบั€ะฐะฝะฐ, ัะพะทะดะฐะฒะฐั ั†ะตะปัŒะฝั‹ะน ะฒะธะด ัะพ ัั‚ะธะปัŒะฝั‹ะผะธ ะธะฝะฒะตั€ั‚ะธั€ะพะฒะฐะฝะฝั‹ะผะธ ัƒะณะปะฐะผะธ.", + "panels-attached-to-bar-label": "ะŸั€ะธะบั€ะตะฟะธั‚ัŒ ะฟะฐะฝะตะปะธ ะบ ะบั€ะฐัะผ", + "panels-overlay-description": "ะžะฑะตัะฟะตั‡ะธะฒะฐะตั‚ ะฒะธะดะธะผะพัั‚ัŒ ะฟะฐะฝะตะปะตะน ะธ ะฟะฐะฝะตะปะธ ะทะฐะดะฐั‡ ะดะฐะถะต ะฟะพะฒะตั€ั… ะฟะพะปะฝะพัะบั€ะฐะฝะฝั‹ั… ะฟั€ะธะปะพะถะตะฝะธะน.", + "panels-overlay-label": "ะ”ะตั€ะถะฐั‚ัŒ ะฟะฐะฝะตะปะธ ะธ ะฟะฐะฝะตะปัŒ ะทะฐะดะฐั‡ ัะฒะตั€ั…ัƒ", + "scaling-description": "ะ˜ะทะผะตะฝัะตั‚ ั€ะฐะทะผะตั€ ะพะฑั‰ะตะณะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะพะณะพ ะธะฝั‚ะตั€ั„ะตะนัะฐ, ะธัะบะปัŽั‡ะฐั ะฟะฐะฝะตะปัŒ.", + "scaling-label": "ะœะฐััˆั‚ะฐะฑะธั€ะพะฒะฐะฝะธะต ะธะฝั‚ะตั€ั„ะตะนัะฐ", + "scrollbar-always-visible-description": "ะ’ัะตะณะดะฐ ะฟะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฟะพะปะพัั‹ ะฟั€ะพะบั€ัƒั‚ะบะธ, ะบะพะณะดะฐ ัะพะดะตั€ะถะธะผะพะต ะฟั€ะพะบั€ัƒั‡ะธะฒะฐะตั‚ัั, ะฒะผะตัั‚ะพ ั‚ะพะณะพ ั‡ั‚ะพะฑั‹ ะพั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะธั… ั‚ะพะปัŒะบะพ ะฟั€ะธ ะฝะฐะฒะตะดะตะฝะธะธ.", + "scrollbar-always-visible-label": "ะ’ัะตะณะดะฐ ะฟะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฟะพะปะพัั‹ ะฟั€ะพะบั€ัƒั‚ะบะธ", + "settings-panel-header": "ะŸะฐะฝะตะปัŒ ะฝะฐัั‚ั€ะพะตะบ", + "settings-panel-mode-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ั€ะฐัะบะปะฐะดะบัƒ ะฝะฐัั‚ั€ะพะตะบ (ะผะพะถะตั‚ ะฟะพั‚ั€ะตะฑะพะฒะฐั‚ัŒัั ะฟะตั€ะตะทะฐะฟัƒัะบ).", + "settings-panel-mode-label": "ะ ะตะถะธะผ ะฟะฐะฝะตะปะธ ะฝะฐัั‚ั€ะพะตะบ", + "settings-panel-sidebar-card-style-description": "ะžะฑะพั€ะฐั‡ะธะฒะฐะตั‚ ะฑะพะบะพะฒัƒัŽ ะฟะฐะฝะตะปัŒ ะฝะฐัั‚ั€ะพะตะบ ะฒ ะทะฐะฟะพะปะฝะตะฝะฝั‹ะน ั„ะพะฝ ัะพ ัะบั€ัƒะณะปะตะฝะฝั‹ะผะธ ัƒะณะปะฐะผะธ.", + "settings-panel-sidebar-card-style-label": "ะกั‚ะธะปัŒ ะบะฐั€ั‚ะพั‡ะตะบ ะฑะพะบะพะฒะพะน ะฟะฐะฝะตะปะธ", + "shadows-description": "ะ’ะบะปัŽั‡ะฐะตั‚ ะพั‚ะฑั€ะฐัั‹ะฒะฐะตะผั‹ะต ั‚ะตะฝะธ ะฟะพะด ะฟะฐะฝะตะปัะผะธ ะธ ะฟะฐะฝะตะปัŒัŽ ะทะฐะดะฐั‡.", + "shadows-direction-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะบัƒะดะฐ ะพั‚ะฑั€ะฐัั‹ะฒะฐะตั‚ัั ั‚ะตะฝัŒ.", + "shadows-direction-label": "ะะฐะฟั€ะฐะฒะปะตะฝะธะต ั‚ะตะฝะธ", + "shadows-label": "ะžั‚ะฑั€ะฐัั‹ะฒะฐะตะผั‹ะต ั‚ะตะฝะธ", + "title": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ะธะฝั‚ะตั€ั„ะตะนั", + "tooltips-description": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะธะปะธ ะพั‚ะบะปัŽั‡ะธั‚ัŒ ะฒัะฟะปั‹ะฒะฐัŽั‰ะธะต ะฟะพะดัะบะฐะทะบะธ ะฒะพ ะฒัะตะผ ะธะฝั‚ะตั€ั„ะตะนัะต.", + "tooltips-label": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฒัะฟะปั‹ะฒะฐัŽั‰ะธะต ะฟะพะดัะบะฐะทะบะธ", + "translucent-widgets-description": "ะกะดะตะปะฐะนั‚ะต ะบะฝะพะฟะบะธ, ะฒะบะปะฐะดะบะธ ะธ ะดั€ัƒะณะธะต ะฒะธะดะถะตั‚ั‹ ะฒะฝัƒั‚ั€ะธ ะฟะฐะฝะตะปะตะน ะฟะพะปัƒะฟั€ะพะทั€ะฐั‡ะฝั‹ะผะธ.", + "translucent-widgets-label": "ะŸะพะปัƒะฟั€ะพะทั€ะฐั‡ะฝั‹ะต ะฒะธะดะถะตั‚ั‹" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "ะะปั„ะฐะฒะธั‚ะฝั‹ะน", + "automation-change-mode-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะบะฐะบ ะฒั‹ะฑะธั€ะฐัŽั‚ัั ะพะฑะพะธ ะฟั€ะธ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะพะน ัะผะตะฝะต.", + "automation-change-mode-label": "ะ ะตะถะธะผ ัะผะตะฝั‹", + "automation-custom-interval-description": "ะ’ะฒะตะดะธั‚ะต ะฒั€ะตะผั ะฒ ั„ะพั€ะผะฐั‚ะต ะงะง:ะœะœ (ะฝะฐะฟั€. 01:30).", + "automation-custom-interval-label": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ะธะฝั‚ะตั€ะฒะฐะป", + "automation-interval-description": "ะงะฐัั‚ะพั‚ะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะพะน ัะผะตะฝั‹ ะพะฑะพะตะฒ.", + "automation-interval-label": "ะ˜ะฝั‚ะตั€ะฒะฐะป ัะผะตะฝั‹ ะพะฑะพะตะฒ", + "automation-random-wallpaper-description": "ะ—ะฐะฟะปะฐะฝะธั€ะพะฒะฐั‚ัŒ ัะผะตะฝัƒ ัะปัƒั‡ะฐะนะฝั‹ั… ะพะฑะพะตะฒ ั‡ะตั€ะตะท ั€ะตะณัƒะปัั€ะฝั‹ะต ะธะฝั‚ะตั€ะฒะฐะปั‹.", + "automation-scheduled-change-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะผะตะฝัั‚ัŒ ะพะฑะพะธ ั‡ะตั€ะตะท ั€ะตะณัƒะปัั€ะฝั‹ะต ะธะฝั‚ะตั€ะฒะฐะปั‹ ะฒั€ะตะผะตะฝะธ.", + "automation-scheduled-change-label": "ะ—ะฐะฟะปะฐะฝะธั€ะพะฒะฐะฝะฝะฐั ัะผะตะฝะฐ", + "look-feel-edge-smoothness-description": "ะŸั€ะธะผะตะฝัะตั‚ ะผัะณะบะธะน, ั€ะฐัั‚ัƒัˆั‘ะฒะฐะฝะฝั‹ะน ัั„ั„ะตะบั‚ ะบ ะบั€ะฐัŽ ะฟะตั€ะตั…ะพะดะพะฒ.", + "look-feel-edge-smoothness-label": "ะกะผัะณั‡ะธั‚ัŒ ะบั€ะฐะน ะฟะตั€ะตั…ะพะดะฐ", + "look-feel-fill-color-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ั†ะฒะตั‚ ะทะฐะปะธะฒะบะธ, ะบะพั‚ะพั€ั‹ะน ะผะพะถะตั‚ ะฟะพัะฒะธั‚ัŒัั ะทะฐ ะพะฑะพัะผะธ.", + "look-feel-fill-mode-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะบะฐะบ ะธะทะพะฑั€ะฐะถะตะฝะธะต ะดะพะปะถะฝะพ ะผะฐััˆั‚ะฐะฑะธั€ะพะฒะฐั‚ัŒัั, ั‡ั‚ะพะฑั‹ ัะพะพั‚ะฒะตั‚ัั‚ะฒะพะฒะฐั‚ัŒ ั€ะฐะทั€ะตัˆะตะฝะธัŽ ะฒะฐัˆะตะณะพ ะผะพะฝะธั‚ะพั€ะฐ.", + "look-feel-fill-mode-label": "ะ ะตะถะธะผ ะทะฐะฟะพะปะฝะตะฝะธั", + "look-feel-skip-startup-transition-description": "ะŸั€ะพะฟัƒัะบะฐั‚ัŒ ะฐะฝะธะผะฐั†ะธัŽ ะพะฑะพะตะฒ ะฟั€ะธ ะทะฐะฟัƒัะบะต ะพะฑะพะปะพั‡ะบะธ.", + "look-feel-skip-startup-transition-label": "ะŸั€ะพะฟัƒัั‚ะธั‚ัŒ ัั‚ะฐั€ั‚ะพะฒัƒัŽ ะฐะฝะธะผะฐั†ะธัŽ", + "look-feel-title": "ะ’ะฝะตัˆะฝะธะน ะฒะธะด", + "look-feel-transition-duration-description": "ะŸั€ะพะดะพะปะถะธั‚ะตะปัŒะฝะพัั‚ัŒ ะฐะฝะธะผะฐั†ะธะธ ะฟะตั€ะตั…ะพะดะฐ ะฒ ัะตะบัƒะฝะดะฐั….", + "look-feel-transition-duration-label": "ะŸั€ะพะดะพะปะถะธั‚ะตะปัŒะฝะพัั‚ัŒ ะฟะตั€ะตั…ะพะดะฐ", + "look-feel-transition-type-description": "ะขะธะฟ ะฐะฝะธะผะฐั†ะธะธ ะฟั€ะธ ะฟะตั€ะตะบะปัŽั‡ะตะฝะธะธ ะผะตะถะดัƒ ะพะฑะพัะผะธ.", + "look-feel-transition-type-label": "ะขะธะฟ ะฟะตั€ะตั…ะพะดะฐ", + "settings-clear-cache-button": "ะžั‡ะธัั‚ะธั‚ัŒ ะบััˆ", + "settings-clear-cache-description": "ะžั‡ะธัั‚ะธั‚ัŒ ะบััˆะธั€ะพะฒะฐะฝะฝั‹ะต ะผะฐััˆั‚ะฐะฑะธั€ะพะฒะฐะฝะฝั‹ะต ะพะฑะพะธ, ั‡ั‚ะพะฑั‹ ะพัะฒะพะฑะพะดะธั‚ัŒ ะผะตัั‚ะพ ะฝะฐ ะดะธัะบะต.", + "settings-clear-cache-label": "ะšััˆ ะพะฑะพะตะฒ", + "settings-clear-cache-toast": "ะšััˆ ะพะฑะพะตะฒ ะพั‡ะธั‰ะตะฝ", + "settings-desc": "ะฃะฟั€ะฐะฒะปะตะฝะธะต ั‚ะตะผ, ะบะฐะบ ะพะฑะพะธ ัƒะฟั€ะฐะฒะปััŽั‚ัั ะธ ะพั‚ะพะฑั€ะฐะถะฐัŽั‚ัั.", + "settings-enable-management-description": "ะฃะฟั€ะฐะฒะปัั‚ัŒ ะพะฑะพัะผะธ ั ะฟะพะผะพั‰ัŒัŽ Noctalia. ะกะฝะธะผะธั‚ะต ั„ะปะฐะถะพะบ, ะตัะปะธ ะฟั€ะตะดะฟะพั‡ะธั‚ะฐะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะดั€ัƒะณะพะต ะฟั€ะธะปะพะถะตะฝะธะต.", + "settings-enable-management-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ัƒะฟั€ะฐะฒะปะตะฝะธะต ะพะฑะพัะผะธ", + "settings-enable-overview-description": "ะŸั€ะธะผะตะฝัะตั‚ ั€ะฐะทะผั‹ั‚ั‹ะต ะธ ะทะฐั‚ะตะผะฝั‘ะฝะฝั‹ะต ะพะฑะพะธ ะบ ัะบั€ะฐะฝัƒ ะพะฑะทะพั€ะฐ.", + "settings-enable-overview-label": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะพะฑะพะธ ะพะฑะทะพั€ะฐ", + "settings-folder-description": "ะŸัƒั‚ัŒ ะบ ะฒะฐัˆะตะน ะพัะฝะพะฒะฝะพะน ะฟะฐะฟะบะต ั ะพะฑะพัะผะธ.", + "settings-folder-label": "ะŸะฐะฟะบะฐ ั ะพะฑะพัะผะธ", + "settings-hide-wallpaper-filenames-tooltip-hide": "ะกะบั€ั‹ั‚ัŒ ะธะผะตะฝะฐ ั„ะฐะนะปะพะฒ", + "settings-hide-wallpaper-filenames-tooltip-show": "ะŸะพะบะฐะทะฐั‚ัŒ ะธะผะตะฝะฐ ั„ะฐะนะปะพะฒ", + "settings-monitor-specific-description": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ะพั‚ะดะตะปัŒะฝัƒัŽ ะฟะฐะฟะบัƒ ั ะพะฑะพัะผะธ ะดะปั ะบะฐะถะดะพะณะพ ะผะพะฝะธั‚ะพั€ะฐ.", + "settings-monitor-specific-label": "ะšะฐั‚ะฐะปะพะณะธ ะดะปั ะบะพะฝะบั€ะตั‚ะฝะพะณะพ ะผะพะฝะธั‚ะพั€ะฐ", + "settings-monitor-specific-tooltip": "ะŸะฐะฟะบะฐ ะพะฑะพะตะฒ ะผะพะฝะธั‚ะพั€ะฐ", + "settings-overview-blur-strength-description": "ะŸั€ะธะผะตะฝัะตั‚ ัะธะปัƒ ั€ะฐะทะผั‹ั‚ะธั ะบ ะพะฑะทะพั€ัƒ.", + "settings-overview-blur-strength-label": "ะกะธะปะฐ ั€ะฐะทะผั‹ั‚ะธั ะพะฑะทะพั€ะฐ", + "settings-overview-tint-description": "ะŸั€ะธะผะตะฝัะตั‚ ะธะฝั‚ะตะฝัะธะฒะฝะพัั‚ัŒ ะพั‚ั‚ะตะฝะบะฐ ะบ ะพะฑะทะพั€ัƒ.", + "settings-overview-tint-label": "ะ˜ะฝั‚ะตะฝัะธะฒะฝะพัั‚ัŒ ั‚ะพะฝะธั€ะพะฒะบะธ ะพะฑะทะพั€ะฐ", + "settings-recursive-search-description": "ะขะฐะบะถะต ะธัะบะฐั‚ัŒ ะพะฑะพะธ ะฒะพ ะฒะปะพะถะตะฝะฝั‹ั… ะฟะฐะฟะบะฐั… ะบะฐั‚ะฐะปะพะณะฐ ะพะฑะพะตะฒ.", + "settings-recursive-search-label": "ะ˜ัะบะฐั‚ัŒ ะฒะพ ะฒะปะพะถะตะฝะฝั‹ั… ะฟะฐะฟะบะฐั…", + "settings-select-monitor-folder": "ะ’ั‹ะฑั€ะฐั‚ัŒ ะฟะฐะฟะบัƒ ั ะพะฑะพัะผะธ ะดะปั ะผะพะฝะธั‚ะพั€ะฐ", + "settings-selector-description": "ะŸะตั€ะตะนั‚ะธ ะฝะฐ ะฟะฐะฝะตะปัŒ ะฒั‹ะฑะพั€ะฐ ะพะฑะพะตะฒ.", + "settings-selector-position-description": "ะ’ั‹ะฑะตั€ะธั‚ะต, ะณะดะต ะฟะพัะฒะปัะตั‚ัั ะฟะฐะฝะตะปัŒ ะฒั‹ะฑะพั€ะฐ ะพะฑะพะตะฒ.", + "settings-show-hidden-files-tooltip-hide": "ะะต ะฟะพะบะฐะทั‹ะฒะฐั‚ัŒ ัะบั€ั‹ั‚ั‹ะต ั„ะฐะนะปั‹", + "settings-show-hidden-files-tooltip-show": "ะŸะพะบะฐะทะฐั‚ัŒ ัะบั€ั‹ั‚ั‹ะต ั„ะฐะนะปั‹", + "settings-title": "ะะฐัั‚ั€ะพะนะบะธ ะพะฑะพะตะฒ", + "settings-use-original-images-description": "ะŸั€ะพะฟัƒัะบะฐั‚ัŒ ะธะทะผะตะฝะตะฝะธะต ั€ะฐะทะผะตั€ะฐ ะพะฑะพะตะฒ ะฟะตั€ะตะด ะพั‚ะพะฑั€ะฐะถะตะฝะธะตะผ. ะญะบะพะฝะพะผะธั‚ ะผะตัั‚ะพ ะฝะฐ ะดะธัะบะต ะธ ัะฝะธะถะฐะตั‚ ะฝะฐะณั€ัƒะทะบัƒ ะฝะฐ CPU ะฒะพ ะฒั€ะตะผั ัะผะตะฝั‹ ะพะฑะพะตะฒ, ะฝะพ ะผะพะถะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฑะพะปัŒัˆะต ะฟะฐะผัั‚ะธ ะดะปั ะพั‡ะตะฝัŒ ะฑะพะปัŒัˆะธั… ะธะทะพะฑั€ะฐะถะตะฝะธะน.", + "settings-use-original-images-label": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะพั€ะธะณะธะฝะฐะปัŒะฝั‹ะต ะธะทะพะฑั€ะฐะถะตะฝะธั", + "settings-view-mode-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ัะฟะพัะพะฑ ะพั‚ะพะฑั€ะฐะถะตะฝะธั ะพะฑะพะตะฒ ะธะท ะฒะฐัˆะตะน ะดะธั€ะตะบั‚ะพั€ะธะธ.", + "settings-view-mode-label": "ะ ะตะถะธะผ ะฟั€ะพัะผะพั‚ั€ะฐ", + "view-mode-browse": "ะŸั€ะพัะผะพั‚ั€ ะบะฐั‚ะฐะปะพะณะพะฒ", + "view-mode-cycle-tooltip": "ะ ะตะถะธะผ ะฟั€ะพัะผะพั‚ั€ะฐ: {mode} (ะฝะฐะถะผะธั‚ะต, ั‡ั‚ะพะฑั‹ ะธะทะผะตะฝะธั‚ัŒ)", + "view-mode-recursive": "ะžะฑัŠะตะดะธะฝะธั‚ัŒ ั ะฟะพะดะบะฐั‚ะฐะปะพะณะฐะผะธ", + "view-mode-single": "ะšะพั€ะฝะตะฒะพะน ะบะฐั‚ะฐะปะพะณ" + } + }, + "placeholders": { + "add-new-keybind": "ะ”ะพะฑะฐะฒะธั‚ัŒ ะฝะพะฒัƒัŽ ะฟั€ะธะฒัะทะบัƒ ะบะปะฐะฒะธัˆ", + "command-example": "ะฝะฐะฟั€ะธะผะตั€, echo \"Hello World\"", + "enter-command": "ะ’ะฒะตะดะธั‚ะต ะบะพะผะฐะฝะดัƒ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั (ะฟั€ะธะปะพะถะตะฝะธะต ะธะปะธ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ัะบั€ะธะฟั‚)", + "enter-ipc-identifier": "ะ’ะฒะตะดะธั‚ะต ัƒะฝะธะบะฐะปัŒะฝั‹ะน ะธะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ ะดะปั ะบะพะผะฐะฝะด IPC", + "enter-path": "ะ’ะฒะตะดะธั‚ะต ะฟัƒั‚ัŒ...", + "enter-text-to-collapse": "ะฝะฐะฟั€. 'ะฝะธั‡ะตะณะพ ะฝะต ะธะณั€ะฐะตั‚'. ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต /regex/ ะดะปั ัˆะฐะฑะปะพะฝะพะฒ.", + "enter-tooltip": "ะ’ะฒะตะดะธั‚ะต ะฟะพะดัะบะฐะทะบัƒ", + "enter-width-pixels": "ะ’ะฒะตะดะธั‚ะต ัˆะธั€ะธะฝัƒ ะฒ ะฟะธะบัะตะปัั…", + "keybind-recording": "ะ—ะฐะฟะธััŒ ัะพั‡ะตั‚ะฐะฝะธั ะบะปะฐะฒะธัˆ...", + "search": "ะŸะพะธัะบ...", + "search-icons": "ะฝะฐะฟั€. noctalia, niri, battery, cloud", + "search-launcher": "ะŸะพะธัะบ ะทะฐะฟะธัะตะน... ะธะปะธ ะธัะฟะพะปัŒะทัƒะนั‚ะต > ะดะปั ะบะพะผะฐะฝะด", + "search-wallhaven": "ะŸะพะธัะบ ะฒ Wallhaven...", + "search-wallpapers": "ะ’ะฒะตะดะธั‚ะต ะดะปั ั„ะธะปัŒั‚ั€ะฐั†ะธะธ ะพะฑะพะตะฒ..." + }, + "positions": { + "bottom": "ะ’ะฝะธะทัƒ", + "bottom-center": "ะ’ะฝะธะทัƒ ะฟะพ ั†ะตะฝั‚ั€ัƒ", + "bottom-left": "ะ’ะฝะธะทัƒ ัะปะตะฒะฐ", + "bottom-right": "ะ’ะฝะธะทัƒ ัะฟั€ะฐะฒะฐ", + "center": "ะŸะพ ั†ะตะฝั‚ั€ัƒ", + "center-left": "ะŸะพ ั†ะตะฝั‚ั€ัƒ ัะปะตะฒะฐ", + "center-right": "ะŸะพ ั†ะตะฝั‚ั€ัƒ ัะฟั€ะฐะฒะฐ", + "close-to-bar": "ะ ัะดะพะผ ั ะบะฝะพะฟะบะพะน ะฟะฐะฝะตะปะธ", + "follow-bar": "ะกะปะตะดะพะฒะฐั‚ัŒ ะทะฐ ะฟะฐะฝะตะปัŒัŽ", + "left": "ะกะปะตะฒะฐ", + "right": "ะกะฟั€ะฐะฒะฐ", + "top": "ะ’ะฒะตั€ั…ัƒ", + "top-center": "ะ’ะฒะตั€ั…ัƒ ะฟะพ ั†ะตะฝั‚ั€ัƒ", + "top-left": "ะ’ะฒะตั€ั…ัƒ ัะปะตะฒะฐ", + "top-right": "ะ’ะฒะตั€ั…ัƒ ัะฟั€ะฐะฒะฐ" + }, + "session-menu": { + "action-in-seconds": "{action} ั‡ะตั€ะตะท {seconds} ัะตะบัƒะฝะด...", + "cancel-timer": "ะžั‚ะผะตะฝะธั‚ัŒ ั‚ะฐะนะผะตั€", + "title": "ะœะตะฝัŽ ัะตะฐะฝัะฐ" + }, + "setup": { + "all-done": "ะ“ะพั‚ะพะฒะพ!", + "appearance": { + "subheader": "ะ’ั‹ะฑะตั€ะธั‚ะต ั‚ะตะผะฝั‹ะน ั€ะตะถะธะผ ะธ ะธัั‚ะพั‡ะฝะธะบะธ ั†ะฒะตั‚ะฐ (ะพะฑะพะธ ะธะปะธ ะฟั€ะตะดะพะฟั€ะตะดะตะปะตะฝะฝั‹ะต)." + }, + "customize": { + "header": "ะะฐัั‚ั€ะพะนั‚ะต ัะฒะพะน ะพะฟั‹ั‚", + "subheader": "ะะฐัั‚ั€ะพะนั‚ะต ะฟะพะปะพะถะตะฝะธะต ะฟะฐะฝะตะปะธ, ะฟะปะพั‚ะฝะพัั‚ัŒ, ะผะฐััˆั‚ะฐะฑะธั€ะพะฒะฐะฝะธะต ะธ ะผะฝะพะณะพะต ะดั€ัƒะณะพะต." + }, + "skip-setup": "ะŸั€ะพะฟัƒัั‚ะธั‚ัŒ ะฝะฐัั‚ั€ะพะนะบัƒ", + "telemetry-wizard-done": "ะŸะพะฝัะป!", + "telemetry-wizard-note": "ะ’ั‹ ะฒัั‘ ะบะพะฝั‚ั€ะพะปะธั€ัƒะตั‚ะต โ€” ะฒั‹ ะผะพะถะตั‚ะต ะฒะบะปัŽั‡ะธั‚ัŒ ะธะปะธ ะพั‚ะบะปัŽั‡ะธั‚ัŒ ัั‚ะพ ะฒ ะปัŽะฑะพะต ะฒั€ะตะผั ะฒ ะฝะฐัั‚ั€ะพะนะบะฐั…", + "telemetry-wizard-subtitle": "ะœั‹ ะดะพะฑะฐะฒะธะปะธ ะฐะฝะพะฝะธะผะฝัƒัŽ ะฐะฝะฐะปะธั‚ะธะบัƒ, ั‡ั‚ะพะฑั‹ ะฟะพะผะพั‡ัŒ ัƒะปัƒั‡ัˆะธั‚ัŒ Noctalia", + "telemetry-wizard-title": "ะžะฑะฝะพะฒะปะตะฝะธะต ะบะพะฝั„ะธะดะตะฝั†ะธะฐะปัŒะฝะพัั‚ะธ", + "wallpaper": { + "choose-dir": "ะ’ั‹ะฑะตั€ะธั‚ะต ะบะฐั‚ะฐะปะพะณ, ัะพะดะตั€ะถะฐั‰ะธะน ะฒะฐัˆะธ ะธะทะพะฑั€ะฐะถะตะฝะธั ะพะฑะพะตะฒ", + "dir-browse": "ะžะฑะทะพั€ ะฟะฐะฟะบะธ ั ะพะฑะพัะผะธ", + "dir-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ะฟะฐะฟะบัƒ, ัะพะดะตั€ะถะฐั‰ัƒัŽ ะฒะฐัˆะธ ะพะฑะพะธ.", + "dir-label": "ะšะฐั‚ะฐะปะพะณ ะพะฑะพะตะฒ", + "dir-select-title": "ะ’ั‹ะฑั€ะฐั‚ัŒ ะฟะฐะฟะบัƒ ั ะพะฑะพัะผะธ", + "header": "ะ’ั‹ะฑะตั€ะธั‚ะต ัะฒะพะธ ะพะฑะพะธ", + "no-dir": "ะšะฐั‚ะฐะปะพะณ ะพะฑะพะตะฒ ะฝะต ะฒั‹ะฑั€ะฐะฝ", + "no-valid": "ะะต ะฝะฐะนะดะตะฝะพ ะดะพะฟัƒัั‚ะธะผั‹ั… ั„ะฐะนะปะพะฒ ะธะทะพะฑั€ะฐะถะตะฝะธะน ะฒ: {dir}", + "none-in-dir": "ะžะฑะพะธ ะฝะต ะฝะฐะนะดะตะฝั‹ ะฒ ะบะฐั‚ะฐะปะพะณะต", + "preview-error": "ะะต ัƒะดะฐะปะพััŒ ะทะฐะณั€ัƒะทะธั‚ัŒ ะธะทะพะฑั€ะฐะถะตะฝะธะต", + "select-prompt": "ะ’ั‹ะฑะตั€ะธั‚ะต ะพะฑะพะธ ะฝะธะถะต", + "subheader": "ะกะพะทะดะฐะนั‚ะต ะฝะฐัั‚ั€ะพะตะฝะธะต ั ะบั€ะฐัะธะฒั‹ะผ ั„ะพะฝะพะผ." + }, + "welcome": "ะ”ะพะฑั€ะพ ะฟะพะถะฐะปะพะฒะฐั‚ัŒ", + "welcome-note": "ะ’ัะตะณะพ ะฝะตัะบะพะปัŒะบะพ ะพัะฝะพะฒ ะดะปั ะฝะฐั‡ะฐะปะฐ โ€” ะฟะพะปะฝั‹ะต ะฝะฐัั‚ั€ะพะนะบะธ ะฝะฐั…ะพะดัั‚ัั ะฒ ะฝะฐัั‚ั€ะพะนะบะฐั…", + "welcome-subtitle": "ะ”ะฐะฒะฐะนั‚ะต ัะดะตะปะฐะตะผ ะฒะฐัˆ ั€ะฐะฑะพั‡ะธะน ัั‚ะพะป ัƒะฝะธะบะฐะปัŒะฝั‹ะผ", + "welcome-title": "ะ”ะพะฑั€ะพ ะฟะพะถะฐะปะพะฒะฐั‚ัŒ ะฒ Noctalia!" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "ะœะตะดะธะฐะฟะปะตะตั€ ะฝะต ะพะฑะฝะฐั€ัƒะถะตะฝ", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "ะะตะธะทะฒะตัั‚ะฝะพะต ะฟั€ะธะปะพะถะตะฝะธะต", + "uptime": "ะ’ั€ะตะผั ั€ะฐะฑะพั‚ั‹: {uptime}", + "user-requested": "ะ—ะฐะฟั€ะพัˆะตะฝะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะผ", + "welcome-back": "ะก ะฒะพะทะฒั€ะฐั‰ะตะฝะธะตะผ,", + "widget-settings-title": "ะะฐัั‚ั€ะพะนะบะธ ะฒะธะดะถะตั‚ะฐ {widget}" + }, + "system-monitor": { + "core-usage": "ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต ัะดั€ะฐ {id}", + "cpu-temp": "ะขะตะผะฟะตั€ะฐั‚ัƒั€ะฐ ะฆะŸ", + "cpu-usage": "ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต ะฆะŸ", + "disk": "ะ”ะธัะบ", + "download-speed": "ะกะบะพั€ะพัั‚ัŒ ะทะฐะณั€ัƒะทะบะธ", + "gpu-temp": "ะขะตะผะฟะตั€ะฐั‚ัƒั€ะฐ GPU", + "load-average": "ะกั€ะตะดะฝัั ะฝะฐะณั€ัƒะทะบะฐ", + "title": "ะกะธัั‚ะตะผะฝั‹ะน ะผะพะฝะธั‚ะพั€", + "upload-speed": "ะกะบะพั€ะพัั‚ัŒ ะทะฐะณั€ัƒะทะบะธ" + }, + "toast": { + "airplane-mode": { + "description": "ะžั‚ะบะปัŽั‡ะธั‚ัŒ ะฒัะต ะฑะตัะฟั€ะพะฒะพะดะฝั‹ะต ะบะพะผะผัƒะฝะธะบะฐั†ะธะธ.", + "title": "ะ ะตะถะธะผ ะฟะพะปั‘ั‚ะฐ" + }, + "battery": { + "critical": "ะšั€ะธั‚ะธั‡ะตัะบะธะน ะทะฐั€ัะด ะฑะฐั‚ะฐั€ะตะธ", + "critical-desc": "ะ‘ะฐั‚ะฐั€ะตั ะทะฐั€ัะถะตะฝะฐ ะฝะฐ {percent}% โ€” ะฝะตะผะตะดะปะตะฝะฝะพ ะฟะพะดะบะปัŽั‡ะธั‚ะต ะทะฐั€ัะดะฝะพะต ัƒัั‚ั€ะพะนัั‚ะฒะพ", + "low": "ะะธะทะบะธะน ะทะฐั€ัะด ะฑะฐั‚ะฐั€ะตะธ", + "low-desc": "ะ‘ะฐั‚ะฐั€ะตั ะทะฐั€ัะถะตะฝะฐ ะฝะฐ {percent}% โ€” ะฟะพะถะฐะปัƒะนัั‚ะฐ, ะฟะพะดะบะปัŽั‡ะธั‚ะต ะทะฐั€ัะดะฝะพะต ัƒัั‚ั€ะพะนัั‚ะฒะพ" + }, + "bluetooth": { + "address-copied": "ะะดั€ะตั ัะบะพะฟะธั€ะพะฒะฐะฝ ะฒ ะฑัƒั„ะตั€ ะพะฑะผะตะฝะฐ", + "auto-connect-disabled": "ะะฒั‚ะพะฟะพะดะบะปัŽั‡ะตะฝะธะต ะพั‚ะบะปัŽั‡ะตะฝะพ", + "auto-connect-enabled": "ะะฒั‚ะพะฟะพะดะบะปัŽั‡ะตะฝะธะต ะฒะบะปัŽั‡ะตะฝะพ", + "auto-connecting": "ะŸะพะดะบะปัŽั‡ะตะฝะธะต ะบ {count} ัƒัั‚ั€ะพะนัั‚ะฒัƒ(ะฐะผ)...", + "confirm-code": "ะŸะพะดั‚ะฒะตั€ะดะธั‚ะต ะบะพะด {value} ะฝะฐ ะดั€ัƒะณะพะผ ัƒัั‚ั€ะพะนัั‚ะฒะต.", + "connect-failed": "ะะต ัƒะดะฐะปะพััŒ ะฟะพะดะบะปัŽั‡ะธั‚ัŒัั ะบ ัƒัั‚ั€ะพะนัั‚ะฒัƒ", + "disconnect-failed": "ะะต ัƒะดะฐะปะพััŒ ะพั‚ะบะปัŽั‡ะธั‚ัŒัั ะพั‚ ัƒัั‚ั€ะพะนัั‚ะฒะฐ", + "discoverable-change-failed": "ะะต ัƒะดะฐะปะพััŒ ะธะทะผะตะฝะธั‚ัŒ ัะพัั‚ะพัะฝะธะต ะฒะธะดะธะผะพัั‚ะธ", + "discoverable-disabled": "ะžะฑะฝะฐั€ัƒะถะตะฝะธะต ะพั‚ะบะปัŽั‡ะตะฝะพ", + "discoverable-enabled": "ะžะฑะฝะฐั€ัƒะถะตะฝะธะต ะฒะบะปัŽั‡ะตะฝะพ", + "display-code": "ะ’ะฒะตะดะธั‚ะต ะบะพะด {value} ะฝะฐ ะดั€ัƒะณะพะผ ัƒัั‚ั€ะพะนัั‚ะฒะต.", + "forget-failed": "ะะต ัƒะดะฐะปะพััŒ ัƒะดะฐะปะธั‚ัŒ ัƒัั‚ั€ะพะนัั‚ะฒะพ", + "pair-failed": "ะะต ัƒะดะฐะปะพััŒ ะฒั‹ะฟะพะปะฝะธั‚ัŒ ัะพะฟั€ัะถะตะฝะธะต ัƒัั‚ั€ะพะนัั‚ะฒะฐ", + "passkey-required": "ะขั€ะตะฑัƒะตั‚ัั ะบะปัŽั‡ ะดะพัั‚ัƒะฟะฐ ัƒัั‚ั€ะพะนัั‚ะฒะฐ.", + "pincode-required": "PIN-ะบะพะด, ั‚ั€ะตะฑัƒะตะผั‹ะน ัƒัั‚ั€ะพะนัั‚ะฒะพะผ.", + "state-change-failed": "ะะต ัƒะดะฐะปะพััŒ ะธะทะผะตะฝะธั‚ัŒ ัะพัั‚ะพัะฝะธะต Bluetooth" + }, + "clipboard": { + "long-text": "ะ”ะปะธะฝะฝั‹ะน ั‚ะตะบัั‚", + "unavailable": "ะ˜ัั‚ะพั€ะธั ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ ะฝะตะดะพัั‚ัƒะฟะฝะฐ", + "unavailable-desc": "ะŸั€ะธะปะพะถะตะฝะธะต 'cliphist' ะฝะต ัƒัั‚ะฐะฝะพะฒะปะตะฝะพ. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ัƒัั‚ะฐะฝะพะฒะธั‚ะต ะตะณะพ, ั‡ั‚ะพะฑั‹ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ั„ัƒะฝะบั†ะธะธ ะธัั‚ะพั€ะธะธ ะฑัƒั„ะตั€ะฐ ะพะฑะผะตะฝะฐ" + }, + "custom-command-failed": { + "description": "ะšะพะผะฐะฝะดะฐ ะฝะต ะฒั‹ะฟะพะปะฝะตะฝะฐ: {command}\\nะšะพะด ะฒั‹ั…ะพะดะฐ: {code}", + "title": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะฐั ะบะพะผะฐะฝะดะฐ ะฝะต ะฒั‹ะฟะพะปะฝะตะฝะฐ" + }, + "do-not-disturb": { + "disabled": "ะ ะตะถะธะผ 'ะะต ะฑะตัะฟะพะบะพะธั‚ัŒ' ะพั‚ะบะปัŽั‡ะตะฝ", + "disabled-desc": "ะžั‚ะพะฑั€ะฐะถะฐัŽั‚ัั ะฒัะต ัƒะฒะตะดะพะผะปะตะฝะธั", + "enabled": "ะ ะตะถะธะผ 'ะะต ะฑะตัะฟะพะบะพะธั‚ัŒ' ะฒะบะปัŽั‡ะตะฝ", + "enabled-desc": "ะ’ั‹ ะฝะฐะนะดั‘ั‚ะต ัั‚ะธ ัƒะฒะตะดะพะผะปะตะฝะธั ะฒ ัะฒะพะตะน ะธัั‚ะพั€ะธะธ" + }, + "donation-opened": "ะกั‚ั€ะฐะฝะธั†ะฐ ะฟะพะถะตั€ั‚ะฒะพะฒะฐะฝะธะน ะพั‚ะบั€ั‹ั‚ะฐ ะฒ ะฒะฐัˆะตะผ ะฑั€ะฐัƒะทะตั€ะต", + "internet-limited": "ะŸะพะดะบะปัŽั‡ะตะฝะพ ะฑะตะท ะดะพัั‚ัƒะฟะฐ ะบ ะธะฝั‚ะตั€ะฝะตั‚ัƒ", + "keyboard-layout": { + "changed": "ะ ะฐัะบะปะฐะดะบะฐ ะบะปะฐะฒะธะฐั‚ัƒั€ั‹ ะธะทะผะตะฝะตะฝะฐ ะฝะฐ {layout}", + "title": "ะšะปะฐะฒะธะฐั‚ัƒั€ะฐ" + }, + "missing-control-center": { + "description": "ะ’ะธะดะถะตั‚ ั†ะตะฝั‚ั€ะฐ ัƒะฟั€ะฐะฒะปะตะฝะธั ะฑั‹ะป ัƒะดะฐะปั‘ะฝ ั ะฟะฐะฝะตะปะธ. ะงั‚ะพะฑั‹ ัะฝะพะฒะฐ ะฟะพะปัƒั‡ะธั‚ัŒ ะบ ะฝะตะผัƒ ะดะพัั‚ัƒะฟ ั ะฟะฐะฝะตะปะธ, ะฒะฐะผ ะฝัƒะถะฝะพ ะฑัƒะดะตั‚ ะฟะพะฒั‚ะพั€ะฝะพ ะดะพะฑะฐะฒะธั‚ัŒ ะฒะธะดะถะตั‚. ะ’ั‹ ั‚ะฐะบะถะต ะผะพะถะตั‚ะต ะพั‚ะบั€ั‹ั‚ัŒ ะตะณะพ, ะฝะฐะถะฐะฒ ะฟั€ะฐะฒะพะน ะบะฝะพะฟะบะพะน ะผั‹ัˆะธ ะฝะฐ ะฟะฐะฝะตะปัŒ", + "label": "ะŸะพัะปะตะดะฝะธะน ะฒะธะดะถะตั‚ ั†ะตะฝั‚ั€ะฐ ัƒะฟั€ะฐะฒะปะตะฝะธั ัƒะดะฐะปั‘ะฝ" + }, + "night-light": { + "forced": "ะŸั€ะธะฝัƒะดะธั‚ะตะปัŒะฝะฐั ะฐะบั‚ะธะฒะฐั†ะธั", + "normal": "ะะพั€ะผะฐะปัŒะฝั‹ะน ั€ะตะถะธะผ", + "not-installed": "wlsunset ะฝะต ัƒัั‚ะฐะฝะพะฒะปะตะฝ" + }, + "noctalia-performance": { + "disabled": "ะ ะตะถะธะผ ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ะธ Noctalia ะพั‚ะบะปัŽั‡ะตะฝ", + "enabled": "ะ ะตะถะธะผ ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ะธ Noctalia ะฒะบะปัŽั‡ะตะฝ", + "label": "ะŸั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ัŒ Noctalia" + }, + "power-profile": { + "changed": "ะŸั€ะพั„ะธะปัŒ ะฟะธั‚ะฐะฝะธั ะธะทะผะตะฝั‘ะฝ", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "ะŸั€ะพะธะทะพัˆะปะฐ ะพัˆะธะฑะบะฐ ะฟั€ะธ ะพะฑั€ะฐะฑะพั‚ะบะต ัˆะฐะฑะปะพะฝะพะฒ", + "title": "ะžัˆะธะฑะบะฐ ะพะฑั€ะฐะฑะพั‚ะบะธ ัˆะฐะฑะปะพะฝะฐ" + }, + "vpn": { + "connected": "ะŸะพะดะบะปัŽั‡ะตะฝะพ ะบ '{name}'", + "disconnected": "ะžั‚ะบะปัŽั‡ะตะฝะพ ะพั‚ '{name}'" + }, + "wallpaper-colors": { + "disabled": "ะฆะฒะตั‚ะฐ ะพะฑะพะตะฒ ะพั‚ะบะปัŽั‡ะตะฝั‹", + "enabled": "ะฆะฒะตั‚ะฐ ะพะฑะพะตะฒ ะฒะบะปัŽั‡ะตะฝั‹", + "label": "ะ“ะตะฝะตั€ะฐั†ะธั ั†ะฒะตั‚ะพะฒ ะพะฑะพะตะฒ" + }, + "wifi": { + "connected": "ะŸะพะดะบะปัŽั‡ะตะฝะพ ะบ '{ssid}'", + "connection-failed": "ะกะพะตะดะธะฝะตะฝะธะต ะฝะต ัƒัั‚ะฐะฝะพะฒะปะตะฝะพ", + "connection-timeout": "ะŸั€ะตะฒั‹ัˆะตะฝะพ ะฒั€ะตะผั ะพะถะธะดะฐะฝะธั ัะพะตะดะธะฝะตะฝะธั", + "disconnected": "ะžั‚ะบะปัŽั‡ะตะฝะพ ะพั‚ '{ssid}'", + "incorrect-password": "ะะตะฒะตั€ะฝั‹ะน ะฟะฐั€ะพะปัŒ", + "network-not-found": "ะกะตั‚ัŒ ะฝะต ะฝะฐะนะดะตะฝะฐ" + } + }, + "tooltips": { + "add-widget": "ะ”ะพะฑะฐะฒะธั‚ัŒ ะฒะธะดะถะตั‚", + "auto-connect": "ะŸะตั€ะตะบะปัŽั‡ะธั‚ัŒ ะฐะฒั‚ะพะฟะพะดะบะปัŽั‡ะตะฝะธะต ะดะปั ัั‚ะพะณะพ ัƒัั‚ั€ะพะนัั‚ะฒะฐ", + "bluetooth-auto-connect-off": "ะะฒั‚ะพะฟะพะดะบะปัŽั‡ะตะฝะธะต ะพั‚ะบะปัŽั‡ะตะฝะพ", + "bluetooth-auto-connect-on": "ะะฒั‚ะพะฟะพะดะบะปัŽั‡ะตะฝะธะต ะฒะบะปัŽั‡ะตะฝะพ", + "bluetooth-devices": "ะฃัั‚ั€ะพะนัั‚ะฒะฐ Bluetooth", + "brightness-at": "ะฏั€ะบะพัั‚ัŒ: {brightness}%", + "click-to-start-recording": "ะ—ะฐะฟะธััŒ ัะบั€ะฐะฝะฐ (ะฝะฐั‡ะฐั‚ัŒ ะทะฐะฟะธััŒ)", + "click-to-stop-recording": "ะ—ะฐะฟะธััŒ ัะบั€ะฐะฝะฐ (ะพัั‚ะฐะฝะพะฒะธั‚ัŒ ะทะฐะฟะธััŒ)", + "collapse": "ะกะฒะตั€ะฝัƒั‚ัŒ ะฑะพะบะพะฒัƒัŽ ะฟะฐะฝะตะปัŒ", + "copy-address": "ะšะพะฟะธั€ะพะฒะฐั‚ัŒ ะฐะดั€ะตั", + "delete-notification": "ะฃะดะฐะปะธั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธะต", + "dismiss-notification": "ะžั‚ะบะปะพะฝะธั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธะต", + "do-not-disturb-enabled": "ะะต ะฑะตัะฟะพะบะพะธั‚ัŒ", + "expand": "ะ ะฐะทะฒะตั€ะฝัƒั‚ัŒ ะฑะพะบะพะฒัƒัŽ ะฟะฐะฝะตะปัŒ", + "forget-network": "ะ—ะฐะฑั‹ั‚ัŒ ัะตั‚ัŒ", + "grid-view": "ะ’ะธะด ัะตั‚ะบะพะน", + "hidden-files-hide": "ะกะบั€ั‹ั‚ั‹ะต ั„ะฐะนะปั‹", + "hide-unnamed-devices": "ะกะบั€ั‹ั‚ัŒ ะฑะตะทั‹ะผัะฝะฝั‹ะต ัƒัั‚ั€ะพะนัั‚ะฒะฐ", + "home": "ะ”ะพะผะฐัˆะฝะธะน ะบะฐั‚ะฐะปะพะณ", + "input-muted": "ะ’ะบะปัŽั‡ะธั‚ัŒ/ะฒั‹ะบะปัŽั‡ะธั‚ัŒ ะพั‚ะบะปัŽั‡ะตะฝะธะต ะผะธะบั€ะพั„ะพะฝะฐ", + "keep-awake": "ะะต ะทะฐัั‹ะฟะฐั‚ัŒ", + "keyboard-layout": "ะ ะฐัะบะปะฐะดะบะฐ ะบะปะฐะฒะธะฐั‚ัƒั€ั‹ {layout}", + "list-view": "ะ’ะธะด ัะฟะธัะบะพะผ", + "manage-vpn": "VPN-ะฟะพะดะบะปัŽั‡ะตะฝะธั", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "ะ”ะพัั‚ะธะณะฝัƒั‚ะพ ะผะฐะบัะธะผะฐะปัŒะฝะพะต ะบะพะปะธั‡ะตัั‚ะฒะพ ะฒะธะดะถะตั‚ะพะฒ", + "microphone-volume-at": "ะ“ั€ะพะผะบะพัั‚ัŒ ะผะธะบั€ะพั„ะพะฝะฐ: {volume}%", + "move-to-section": "ะŸะตั€ะตะนั‚ะธ ะบ {section}", + "mute": "ะžั‚ะบะปัŽั‡ะธั‚ัŒ ะทะฒัƒะบ", + "next-media": "ะกะปะตะดัƒัŽั‰ะธะน ั‚ั€ะตะบ", + "next-month": "ะกะปะตะดัƒัŽั‰ะธะน ะผะตััั†", + "night-light-not-installed": "ะะพั‡ะฝะพะน ัะฒะตั‚ (ะฝะตะดะพัั‚ัƒะฟะตะฝ)", + "noctalia-performance-enabled": "ะ ะตะถะธะผ ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ะธ Noctalia", + "open-annotation-tool": "ะžั‚ะบั€ั‹ั‚ัŒ ั ะฟะพะผะพั‰ัŒัŽ ะธะฝัั‚ั€ัƒะผะตะฝั‚ะฐ ะฐะฝะฝะพั‚ะธั€ะพะฒะฐะฝะธั", + "open-control-center": "ะฆะตะฝั‚ั€ ัƒะฟั€ะฐะฒะปะตะฝะธั", + "open-notification-history-enable-dnd": "ะ˜ัั‚ะพั€ะธั ัƒะฒะตะดะพะผะปะตะฝะธะน", + "open-settings": "ะะฐัั‚ั€ะพะนะบะธ", + "open-tray-dropdown": "ะกะธัั‚ะตะผะฝั‹ะน ั‚ั€ะตะน", + "output-muted": "ะŸะตั€ะตะบะปัŽั‡ะธั‚ัŒ ะพั‚ะบะปัŽั‡ะตะฝะธะต ะทะฒัƒะบะฐ", + "power-profile": "{profile} ะฟั€ะพั„ะธะปัŒ ะฟะธั‚ะฐะฝะธั", + "previous-media": "ะŸั€ะตะดั‹ะดัƒั‰ะธะน ั‚ั€ะตะบ", + "previous-month": "ะŸั€ะตะดั‹ะดัƒั‰ะธะน ะผะตััั†", + "refresh-devices": "ะžะฑะฝะพะฒะธั‚ัŒ ัƒัั‚ั€ะพะนัั‚ะฒะฐ", + "refresh-wallhaven": "ะžะฑะฝะพะฒะธั‚ัŒ ั€ะตะทัƒะปัŒั‚ะฐั‚ั‹ Wallhaven", + "refresh-wallpaper-list": "ะžะฑะฝะพะฒะธั‚ัŒ ัะฟะธัะพะบ ะพะฑะพะตะฒ", + "remove-widget": "ะฃะดะฐะปะธั‚ัŒ ะฒะธะดะถะตั‚", + "search-close": "ะ—ะฐะบั€ั‹ั‚ัŒ ะฟะพะธัะบ", + "session-menu": "ะœะตะฝัŽ ัะตะฐะฝัะฐ", + "show-all-devices": "ะŸะพะบะฐะทะฐั‚ัŒ ะฒัะต ัƒัั‚ั€ะพะนัั‚ะฒะฐ", + "switch-to-dark-mode": "ะขั‘ะผะฝั‹ะน ั€ะตะถะธะผ", + "switch-to-light-mode": "ะกะฒะตั‚ะปั‹ะน ั€ะตะถะธะผ", + "unmute": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะทะฒัƒะบ", + "up": "ะ ะพะดะธั‚ะตะปัŒัะบะธะน ะบะฐั‚ะฐะปะพะณ", + "volume-at": "ะ“ั€ะพะผะบะพัั‚ัŒ ะฒั‹ะฒะพะดะฐ: {volume}%", + "wallpaper-selector": "ะ’ั‹ะฑะพั€ ะพะฑะพะตะฒ" + }, + "wallpaper": { + "browse": { + "empty-directory": "ะญั‚ะพั‚ ะบะฐั‚ะฐะปะพะณ ะฟัƒัั‚.", + "go-root": "ะŸะตั€ะตะนั‚ะธ ะฒ ะบะพั€ะฝะตะฒัƒัŽ ะฟะฐะฟะบัƒ ะพะฑะพะตะฒ", + "go-up": "ะŸะตั€ะตะนั‚ะธ ะฒ ั€ะพะดะธั‚ะตะปัŒัะบัƒัŽ ะฟะฐะฟะบัƒ", + "go-up-hint": "ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต ะบะฝะพะฟะบัƒ ยซะะฐะทะฐะดยป ะดะปั ะฝะฐะฒะธะณะฐั†ะธะธ ะฒะฒะตั€ั…." + }, + "configure-directory": "ะะฐัั‚ั€ะพะนั‚ะต ัะฒะพะน ะบะฐั‚ะฐะปะพะณ ะพะฑะพะตะฒ ั ะธะทะพะฑั€ะฐะถะตะฝะธัะผะธ.", + "fill-modes": { + "crop": "ะžะฑั€ะตะทะฐั‚ัŒ (ะ—ะฐะฟะพะปะฝะธั‚ัŒ)", + "fit": "ะŸะพะดะพะณะฝะฐั‚ัŒ (ะ’ะผะตัั‚ะธั‚ัŒ)", + "repeat": "ะŸะพะฒั‚ะพั€ะธั‚ัŒ (ะ—ะฐะผะพัั‚ะธั‚ัŒ)", + "stretch": "ะ ะฐัั‚ัะฝัƒั‚ัŒ" + }, + "no-match": "ะกะพะฒะฟะฐะดะตะฝะธะน ะฝะต ะฝะฐะนะดะตะฝะพ.", + "no-wallpaper": "ะžะฑะพะธ ะฝะต ะฝะฐะนะดะตะฝั‹.", + "panel": { + "apikey-help": "ะ”ะปั ะดะพัั‚ัƒะฟะฐ ะบ ะบะพะฝั‚ะตะฝั‚ัƒ NSFW ั‚ั€ะตะฑัƒะตั‚ัั ะบะปัŽั‡ API.", + "apikey-label": "ะšะปัŽั‡ API", + "apikey-managed-by-env": "ะฃะฟั€ะฐะฒะปัะตั‚ัั ั‡ะตั€ะตะท ะฟะตั€ะตะผะตะฝะฝัƒัŽ ะพะบั€ัƒะถะตะฝะธั NOCTALIA_WALLHAVEN_API_KEY.", + "apikey-placeholder": "ะ’ะฒะตะดะธั‚ะต ัะฒะพะน API-ะบะปัŽั‡ Wallhaven", + "appearance-dark-tab": "ะขะตะผะฝั‹ะน", + "appearance-light-tab": "ะกะฒะตั‚ะปั‹ะน", + "categories-anime": "ะะฝะธะผะต", + "categories-label": "ะšะฐั‚ะตะณะพั€ะธะธ", + "categories-people": "ะ›ัŽะดะธ", + "color-extraction-disabled": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะธะทะฒะปะตั‡ะตะฝะธะต ั†ะฒะตั‚ะพะฒ ะพะฑะพะตะฒ", + "color-extraction-enabled": "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฟั€ะตะดะพะฟั€ะตะดะตะปะตะฝะฝั‹ะต ั†ะฒะตั‚ะพะฒั‹ะต ัั…ะตะผั‹", + "header-devices-apply-all-tooltip": "ะ’ั‹ะฑั€ะฐั‚ัŒ ะพะฑะพะธ ะดะปั ะบะฐะถะดะพะณะพ ะผะพะฝะธั‚ะพั€ะฐ", + "header-devices-per-monitor-tooltip": "ะŸั€ะธะผะตะฝะธั‚ัŒ ะบะพ ะฒัะตะผ ะผะพะฝะธั‚ะพั€ะฐะผ", + "header-link-light-dark-tooltip": "ะกะพั…ั€ะฐะฝัั‚ัŒ ะพะฑะพะธ ะฟั€ะธ ัะผะตะฝะต ั‚ะตะผั‹", + "header-separate-light-dark-tooltip": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะฐั ัะผะตะฝะฐ ะพะฑะพะตะฒ ั ั‚ะตะผะพะน", + "header-sun-linked-tooltip": "ะ ะฐะทะดะตะปัŒะฝั‹ะต ัะฒะตั‚ะปั‹ะต ะธ ั‚ะตะผะฝั‹ะต ะพะฑะพะธ", + "header-sun-separate-tooltip": "ะžะดะฝะธ ะธ ั‚ะต ะถะต ะพะฑะพะธ ะดะปั ัะฒะตั‚ะปะพะน ะธ ั‚ะตะผะฝะพะน ั‚ะตะผั‹", + "order-asc": "ะŸะพ ะฒะพะทั€ะฐัั‚ะฐะฝะธัŽ", + "order-desc": "ะŸะพ ัƒะฑั‹ะฒะฐะฝะธัŽ", + "order-label": "ะŸะพั€ัะดะพะบ", + "purity-label": "ะคะธะปัŒั‚ั€ ะบะพะฝั‚ะตะฝั‚ะฐ", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "Sketchy", + "ratios-all-portrait": "ะ’ัะต ะฟะพั€ั‚ั€ะตั‚ะฝั‹ะต", + "ratios-all-wide": "ะ’ัะต ัˆะธั€ะพะบะธะต", + "ratios-any": "ะ›ัŽะฑะพะน", + "ratios-label": "ะกะพะพั‚ะฝะพัˆะตะฝะธะต ัั‚ะพั€ะพะฝ", + "resolution-atleast": "ะŸะพ ะบั€ะฐะนะฝะตะน ะผะตั€ะต", + "resolution-exact": "ะขะพั‡ะฝะพ", + "resolution-label": "ะ ะฐะทั€ะตัˆะตะฝะธะต", + "resolution-mode-label": "ะ ะตะถะธะผ", + "solid-color-tooltip": "ะžะดะฝะพั‚ะพะฝะฝั‹ะน ั„ะพะฝ", + "sort-date-asc": "ะกะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะพั‚ ัั‚ะฐั€ั‹ั… ะบ ะฝะพะฒั‹ะผ", + "sort-date-desc": "ะกะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะพั‚ ะฝะพะฒั‹ั… ะบ ัั‚ะฐั€ั‹ะผ", + "sort-name-asc": "ะกะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะฟะพ ะธะผะตะฝะธ (ะ-ะฏ)", + "sort-name-desc": "ะกะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะฟะพ ะธะผะตะฝะธ (ะฏ-ะ)", + "sort-random": "ะกะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ัะปัƒั‡ะฐะนะฝะพ", + "sorting-date-added": "ะ”ะฐั‚ะฐ ะดะพะฑะฐะฒะปะตะฝะธั", + "sorting-favorites": "ะ˜ะทะฑั€ะฐะฝะฝะพะต", + "sorting-label": "ะกะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะฟะพ", + "sorting-relevance": "ะ ะตะปะตะฒะฐะฝั‚ะฝะพัั‚ัŒ", + "sorting-toplist": "ะขะพะฟ-ะปะธัั‚", + "sorting-views": "ะŸั€ะพัะผะพั‚ั€ั‹", + "source-label": "ะ˜ัั‚ะพั‡ะฝะธะบ", + "source-wallhaven": "Wallhaven", + "title": "ะ’ั‹ะฑะพั€ ะพะฑะพะตะฒ", + "wallhaven-settings-title": "ะะฐัั‚ั€ะพะนะบะธ Wallhaven" + }, + "transitions": { + "disc": "ะ”ะธัะบ", + "fade": "ะ—ะฐั‚ัƒั…ะฐะฝะธะต", + "honeycomb": "ะกะพั‚ั‹", + "pixelate": "ะŸะธะบัะตะปะธะทะธั€ะพะฒะฐั‚ัŒ", + "stripes": "ะŸะพะปะพัั‹", + "wipe": "ะกั‚ะธั€ะฐะฝะธะต" + }, + "try-different-search": "ะŸะพะฟั€ะพะฑัƒะนั‚ะต ะดั€ัƒะณะพะน ะฟะพะธัะบะพะฒั‹ะน ะทะฐะฟั€ะพั.", + "wallhaven": { + "loading": "ะ—ะฐะณั€ัƒะทะบะฐ ะพะฑะพะตะฒ...", + "no-results": "ะžะฑะพะธ ะฝะต ะฝะฐะนะดะตะฝั‹. ะŸะพะฟั€ะพะฑัƒะนั‚ะต ะดั€ัƒะณะพะน ะฟะพะธัะบะพะฒั‹ะน ะทะฐะฟั€ะพั.", + "page": "{current} ะธะท {total}", + "page-prefix": "ะกั‚ั€ะฐะฝะธั†ะฐ", + "page-suffix": "ะธะท {total}" + } + }, + "weather": { + "clear-sky": "ะฏัะฝะพะต ะฝะตะฑะพ", + "drizzle": "ะœะพั€ะพััŒ", + "fog": "ะขัƒะผะฐะฝ", + "mainly-clear": "ะŸั€ะตะธะผัƒั‰ะตัั‚ะฒะตะฝะฝะพ ััะฝะพ", + "overcast": "ะŸะฐัะผัƒั€ะฝะพ", + "partly-cloudy": "ะŸะตั€ะตะผะตะฝะฝะฐั ะพะฑะปะฐั‡ะฝะพัั‚ัŒ", + "rain-showers": "ะ›ะธะฒะฝะธ", + "snow": "ะกะฝะตะณ", + "thunderstorm": "ะ“ั€ะพะทะฐ" + }, + "widgets": { + "color-picker": { + "palette-description": "ะ’ั‹ะฑะตั€ะธั‚ะต ะธะท ัˆะธั€ะพะบะพะณะพ ะดะธะฐะฟะฐะทะพะฝะฐ ะฟั€ะตะดะพะฟั€ะตะดะตะปั‘ะฝะฝั‹ั… ั†ะฒะตั‚ะพะฒ.", + "palette-label": "ะŸะฐะปะธั‚ั€ะฐ", + "palette-theme-colors": "ะ‘ั‹ัั‚ั€ั‹ะน ะดะพัั‚ัƒะฟ ะบ ั†ะฒะตั‚ะพะฒะพะน ะฟะฐะปะธั‚ั€ะต ะฒะฐัˆะตะน ั‚ะตะผั‹.", + "title": "ะ’ั‹ะฑะพั€ ั†ะฒะตั‚ะฐ" + }, + "datetime-tokens": { + "ampm-lowercase": "am/pm ะฒ ะฝะธะถะฝะตะผ ั€ะตะณะธัั‚ั€ะต", + "ampm-uppercase": "AM/PM ะฒ ะฒะตั€ั…ะฝะตะผ ั€ะตะณะธัั‚ั€ะต", + "common-12hour-time-minutes": "12-ั‡ะฐัะพะฒะพะต ะฒั€ะตะผั ั ะผะธะฝัƒั‚ะฐะผะธ", + "common-24hour-time-minutes": "24-ั‡ะฐัะพะฒะพะต ะฒั€ะตะผั ั ะผะธะฝัƒั‚ะฐะผะธ", + "common-24hour-time-seconds": "24-ั‡ะฐัะพะฒะพะต ะฒั€ะตะผั ั ัะตะบัƒะฝะดะฐะผะธ", + "common-european-date": "ะ•ะฒั€ะพะฟะตะนัะบะธะน ั„ะพั€ะผะฐั‚ ะดะฐั‚ั‹", + "common-iso-date": "ะคะพั€ะผะฐั‚ ะดะฐั‚ั‹ ISO", + "common-us-date": "ะคะพั€ะผะฐั‚ ะดะฐั‚ั‹ ะกะจะ", + "common-weekday-date": "ะ”ะตะฝัŒ ะฝะตะดะตะปะธ ั ะดะฐั‚ะพะน", + "common-weekday-month-day": "ะ”ะตะฝัŒ ะฝะตะดะตะปะธ, ะผะตััั† ะธ ั‡ะธัะปะพ", + "day-abbreviated": "ะกะพะบั€ะฐั‰ั‘ะฝะฝะพะต ะฝะฐะทะฒะฐะฝะธะต ะดะฝั", + "day-full": "ะŸะพะปะฝะพะต ะฝะฐะทะฒะฐะฝะธะต ะดะฝั", + "day-leading-zero": "ะ”ะตะฝัŒ ั ะฒะตะดัƒั‰ะธะผ ะฝัƒะปั‘ะผ (01-31)", + "day-no-leading-zero": "ะ”ะตะฝัŒ ะฑะตะท ะฒะตะดัƒั‰ะตะณะพ ะฝัƒะปั (1-31)", + "hour-leading-zero": "ะงะฐั ั ะฒะตะดัƒั‰ะธะผ ะฝัƒะปั‘ะผ (00-23) โ€” 24-ั‡ะฐัะพะฒะพะน ั„ะพั€ะผะฐั‚", + "hour-no-leading-zero": "ะงะฐั ะฑะตะท ะฒะตะดัƒั‰ะตะณะพ ะฝัƒะปั (0-23) โ€” 24-ั‡ะฐัะพะฒะพะน ั„ะพั€ะผะฐั‚", + "minute-leading-zero": "ะœะธะฝัƒั‚ะฐ ั ะฒะตะดัƒั‰ะธะผ ะฝัƒะปั‘ะผ (00-59)", + "minute-no-leading-zero": "ะœะธะฝัƒั‚ะฐ ะฑะตะท ะฒะตะดัƒั‰ะตะณะพ ะฝัƒะปั (0-59)", + "month-abbreviated": "ะกะพะบั€ะฐั‰ั‘ะฝะฝะพะต ะฝะฐะทะฒะฐะฝะธะต ะผะตััั†ะฐ", + "month-full": "ะŸะพะปะฝะพะต ะฝะฐะทะฒะฐะฝะธะต ะผะตััั†ะฐ", + "month-number-leading-zero": "ะœะตััั† ะบะฐะบ ั‡ะธัะปะพ ั ะฒะตะดัƒั‰ะธะผ ะฝัƒะปั‘ะผ (01-12)", + "month-number-no-zero": "ะœะตััั† ะบะฐะบ ั‡ะธัะปะพ ะฑะตะท ะฒะตะดัƒั‰ะตะณะพ ะฝัƒะปั (1-12)", + "second-leading-zero": "ะกะตะบัƒะฝะดะฐ ั ะฒะตะดัƒั‰ะธะผ ะฝัƒะปั‘ะผ (00-59)", + "second-no-leading-zero": "ะกะตะบัƒะฝะดะฐ ะฑะตะท ะฒะตะดัƒั‰ะตะณะพ ะฝัƒะปั (0-59)", + "timezone-abbreviation": "ะกะพะบั€ะฐั‰ะตะฝะธะต ั‡ะฐัะพะฒะพะณะพ ะฟะพััะฐ", + "year-four-digit": "ะ“ะพะด ะบะฐะบ ั‡ะตั‚ั‹ั€ั‘ั…ะทะฝะฐั‡ะฝะพะต ั‡ะธัะปะพ", + "year-two-digit": "ะ“ะพะด ะบะฐะบ ะดะฒัƒั…ะทะฝะฐั‡ะฝะพะต ั‡ะธัะปะพ (00-99)" + }, + "file-picker": { + "item": "ัะปะตะผะตะฝั‚", + "items": "ัะปะตะผะตะฝั‚ั‹", + "search-placeholder": "ะŸะพะธัะบ ั„ะฐะนะปะพะฒ ะธ ะฟะฐะฟะพะบ...", + "select-current": "ะ’ั‹ะฑั€ะฐั‚ัŒ ั‚ะตะบัƒั‰ะธะน", + "select-file": "ะ’ั‹ะฑั€ะฐั‚ัŒ ั„ะฐะนะป", + "select-folder": "ะ’ั‹ะฑั€ะฐั‚ัŒ ะฟะฐะฟะบัƒ", + "selected": "ะ’ั‹ะฑั€ะฐะฝะพ:", + "title": "ะ’ั‹ะฑะพั€ ั„ะฐะนะปะฐ" + }, + "file-picker-title": "ะ’ั‹ะฑะตั€ะธั‚ะต ั„ะฐะนะป", + "icon-picker": { + "title": "ะ’ั‹ะฑะพั€ ะธะบะพะฝะบะธ" + }, + "tooltip-placeholder": "ะ—ะฐะฟะพะปะฝะธั‚ะตะปัŒ" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "ะะฝะพะฝะธะผะฝะฐั ะ›ะธั‡ะฝะพัั‚ัŒ", + "ca-cert": "ะกะตั€ั‚ะธั„ะธะบะฐั‚ CA", + "eap-method": "ะœะตั‚ะพะด EAP", + "password": "ะŸะฐั€ะพะปัŒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั", + "phase2-auth": "ะัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธั ะคะฐะทั‹ 2", + "username": "ะ˜ะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ / ะ˜ะผั ะŸะพะปัŒะทะพะฒะฐั‚ะตะปั" + }, + "panel": { + "action-required": "ะขั€ะตะฑัƒะตั‚ัั ะดะตะนัั‚ะฒะธะต", + "add-hidden-network": "ะ”ะพะฑะฐะฒะธั‚ัŒ ัะบั€ั‹ั‚ัƒัŽ ัะตั‚ัŒ", + "add-network": "ะ”ะพะฑะฐะฒะธั‚ัŒ ัะตั‚ัŒ", + "available-interfaces": "ะ”ะพัั‚ัƒะฟะฝั‹ะต ะธะฝั‚ะตั€ั„ะตะนัั‹", + "available-networks": "ะ”ะพัั‚ัƒะฟะฝั‹ะต ัะตั‚ะธ", + "disabled": "Wi-Fi ะพั‚ะบะปัŽั‡ะตะฝ", + "disconnecting": "ะžั‚ะบะปัŽั‡ะตะฝะธะต...", + "dns": "DNS", + "enable-message": "ะ’ะบะปัŽั‡ะธั‚ะต Wi-Fi, ั‡ั‚ะพะฑั‹ ัƒะฒะธะดะตั‚ัŒ ะดะพัั‚ัƒะฟะฝั‹ะต ัะตั‚ะธ.", + "enter-password": "ะ’ะฒะตะดะธั‚ะต ะฟะฐั€ะพะปัŒ...", + "forget": "ะ—ะฐะฑั‹ั‚ัŒ", + "forget-network": "ะ—ะฐะฑั‹ั‚ัŒ ัั‚ัƒ ัะตั‚ัŒ?", + "forgetting": "ะ—ะฐะฑั‹ะฒะฐะฝะธะต...", + "hidden-network": "ะกะบั€ั‹ั‚ะฐั ัะตั‚ัŒ", + "interface": "ะกะตั‚ะตะฒะพะน ะธะฝั‚ะตั€ั„ะตะนั", + "internet-connected": "ะŸะพะดะบะปัŽั‡ะตะฝะพ ะบ ะธะฝั‚ะตั€ะฝะตั‚ัƒ", + "internet-limited": "ะะตั‚ ะธะฝั‚ะตั€ะฝะตั‚ะฐ", + "internet-status": "ะกั‚ะฐั‚ัƒั ะฟะพะดะบะปัŽั‡ะตะฝะธั ะบ ะธะฝั‚ะตั€ะฝะตั‚ัƒ", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "ะ˜ะทะฒะตัั‚ะฝั‹ะต ัะตั‚ะธ", + "link-speed": "ะกะบะพั€ะพัั‚ัŒ ัะพะตะดะธะฝะตะฝะธั", + "network-name-ssid": "ะ˜ะผั ัะตั‚ะธ (SSID)", + "no-ethernet-devices": "Ethernet-ัƒัั‚ั€ะพะนัั‚ะฒะฐ ะฝะต ะพะฑะฝะฐั€ัƒะถะตะฝั‹", + "no-networks": "ะกะตั‚ะธ ะฝะต ะฝะฐะนะดะตะฝั‹", + "saved": "ะกะพั…ั€ะฐะฝะตะฝะพ", + "scan-again": "ะกะบะฐะฝะธั€ะพะฒะฐั‚ัŒ ัะฝะพะฒะฐ", + "searching": "ะŸะพะธัะบ ะฑะปะธะถะฐะนัˆะธั… ัะตั‚ะตะน...", + "security-open": "ะžั‚ะบั€ั‹ั‚ะฐั", + "security-wep": "WEP", + "security-wpa": "WPA/WPA2/WPA3 ะŸะตั€ัะพะฝะฐะปัŒะฝั‹ะน", + "security-wpa-ent": "WPA Enterprise", + "security-wpa2-ent": "WPA2 Enterprise", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 ะญะฝั‚ะตั€ะฟั€ะฐะนะท", + "show-password": "ะŸะพะบะฐะทะฐั‚ัŒ ะฟะฐั€ะพะปัŒ", + "title": "Wi-Fi" + }, + "signal": { + "excellent": "ะžั‚ะปะธั‡ะฝั‹ะน", + "fair": "ะกั€ะตะดะฝะธะน", + "good": "ะฅะพั€ะพัˆะธะน", + "poor": "ะŸะปะพั…ะพะน", + "weak": "ะกะปะฐะฑั‹ะน" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/sv.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/sv.json new file mode 100644 index 0000000..5e8c8c4 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/sv.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "Tรถm historik", + "close-app": "Stรคng {app}", + "connect-vpn": "Anslut till {name}", + "cycle-visualizer": "Vรคxla visualiserare", + "disable-bluetooth": "Inaktivera Bluetooth", + "disable-dnd": "Inaktivera 'Stรถr inte'", + "disable-wifi": "Inaktivera Wi-Fi", + "disconnect-vpn": "Koppla frรฅn {name}", + "dock-settings": "Dockinstรคllningar", + "enable-bluetooth": "Aktivera Bluetooth", + "enable-dnd": "Aktivera 'Stรถr inte'", + "enable-wifi": "Aktivera Wi-Fi", + "launcher-settings": "Startinstรคllningar", + "lower-to-bottom": "Sรคnk till botten", + "open-calendar": "ร–ppna kalender", + "open-display-settings": "Skรคrminstรคllningar", + "open-launcher": "ร–ppna startprogram", + "open-settings": "ร–ppna instรคllningar", + "raise-to-top": "Flytta till toppen", + "random-wallpaper": "Slumpmรคssig bakgrundsbild", + "run-custom-command": "Kรถr anpassat kommando", + "show-active-only": "Visa endast aktiva", + "show-all": "Visa alla", + "toggle-mute": "Vรคxla mellan ljud av", + "widget-settings": "Widgetinstรคllningar" + }, + "authentication": { + "error": "Autentiseringsfel", + "failed": "Autentisering misslyckades" + }, + "bar": { + "active-window": { + "colorize-icons-description": "Tillรคmpa temafรคrger pรฅ ikonen fรถr det aktiva fรถnstret.", + "hide-mode-description": "Styr hur widgeten beter sig nรคr inget fรถnster รคr aktivt.", + "scrolling-mode-description": "Styr nรคr textrullning รคr aktiverad fรถr lรฅnga fรถnstertitlar. ", + "show-app-icon-description": "Visa applikationsikonen bredvid fรถnstertiteln.", + "show-app-icon-label": "Visa appikon", + "show-app-text-description": "Visa titeln pรฅ det aktiva fรถnstret.", + "show-app-text-label": "Visa fรถnstertitel" + }, + "audio-visualizer": { + "color-name-description": "Vรคlj fรคrg fรถr visualiseraren.", + "color-name-label": "Fyllningsfรคrg", + "height-description": "Anpassad komponentbredd.", + "hide-when-idle-description": "Nรคr denna funktion รคr aktiverad dรถljs visualiseraren om ingen spelare รคr aktiv.", + "hide-when-idle-label": "Dรถlj nรคr ingen media spelas upp", + "width-description": "Anpassad komponentbredd." + }, + "battery": { + "device-default": "Standard (visningsenhet)", + "device-description": "Vรคlj vilken batterienhet som ska visas.", + "device-label": "Batterienhet", + "display-mode-description": "Vรคlj hur batteriet visas i fรคltet.", + "display-mode-graphic": "Grafiskt batteri", + "display-mode-graphic-clean": "Grafiskt batteri (ingen %)", + "display-mode-icon-always": "Ikon - Visa alltid %", + "display-mode-icon-hover": "Ikon - Visa vid hovring", + "display-mode-icon-only": "Endast ikon", + "hide-if-idle-description": "Dรถlj widgeten nรคr batteriet inte laddas eller laddas ur.", + "hide-if-idle-label": "Dรถlj nรคr inaktiv", + "hide-if-not-detected-description": "Dรถlj widgeten nรคr inget batteri upptรคcks i systemet.", + "hide-if-not-detected-label": "Dรถlj nรคr inte upptรคckt", + "low-battery-threshold-label": "Varningsgrรคns fรถr lรฅgt batteri", + "show-noctalia-performance-description": "Visa vรคxeln fรถr Noctalia prestandalรคget i batteripanelen.
Inaktiverar skuggor och animationer i Noctalia fรถr att minska resursanvรคndningen.", + "show-noctalia-performance-label": "Visa Noctalia Prestandavรคxeln", + "show-power-profile-description": "Visa valet av strรถmprofil i batteripanelen.", + "show-power-profile-label": "Visa strรถmprofilkontroller" + }, + "brightness": { + "apply-all-description": "ร„ndra ljusstyrka fรถr alla bildskรคrmar nรคr du anvรคnder skrollhjulet.", + "apply-all-label": "Tillรคmpa rullningsรคndringar pรฅ alla bildskรคrmar" + }, + "clock": { + "clock-display-description": "Anpassa klockans visning genom att lรคgga till symboler frรฅn listan nedan. Fรถr att anvรคnda 12-timmarsformatet mรฅste du inkludera symbolen 'AP'.", + "clock-display-label": "Klockvisning", + "custom-font-description": "Vรคlj ett anpassat teckensnitt fรถr klockvisningen.", + "custom-font-label": "Anpassat teckensnitt", + "custom-font-placeholder": "Vรคlj anpassat teckensnitt...", + "custom-font-search-placeholder": "Sรถk teckensnitt...", + "horizontal-bar-description": "Tips: Anvรคnd \\n fรถr att skapa en radbrytning.", + "horizontal-bar-label": "Horisontell stapel", + "preview": "Fรถrhandsgranska", + "tooltip-format-description": "Formateringsstrรคng fรถr verktygstips som visas nรคr du hรฅller muspekaren รถver klockan. Lรคmna tomt fรถr att anvรคnda standardverktygstipset.", + "tooltip-format-label": "Verktygstipsformat", + "use-custom-font-description": "ร…sidosรคtt standardvalet av teckensnitt med ett anpassat teckensnitt fรถr klockan.", + "use-custom-font-label": "Anvรคnd anpassat teckensnitt", + "use-monospaced-font-description": "Nรคr denna funktion รคr aktiverad anvรคnder klockan ett monospaced-teckensnitt.", + "use-monospaced-font-label": "Anvรคnd monospaced-teckensnitt", + "vertical-bar-description": "Anvรคnd ett mellanslag fรถr att separera varje del till en ny rad.", + "vertical-bar-label": "Vertikal stapel" + }, + "control-center": { + "browse-file": "Blรคddra bland filer", + "browse-library": "Blรคddra bland bibliotek", + "color-selection-description": "Tillรคmpa temafรคrger pรฅ ikoner.", + "enable-colorization-description": "Aktivera fรคrglรคggning fรถr ikon, tillรคmpa temafรคrger.", + "icon-description": "Vรคlj en ikon frรฅn biblioteket eller en anpassad fil.", + "select-custom-icon": "Vรคlj en anpassad ikon", + "use-distro-logo-description": "Anvรคnd din distributions logotyp istรคllet fรถr en anpassad ikon.", + "use-distro-logo-label": "Anvรคnd distributionslogotyp istรคllet fรถr ikon" + }, + "custom-button": { + "collapse-condition-description": "Om utdatatexten matchar detta vรคrde kommer knappen att dรถljas.", + "collapse-condition-label": "Dรถlj villkor", + "color-selection-description": "Tillรคmpa temafรคrger pรฅ ikon och text.", + "default-tooltip": "Anpassad knapp, konfigurera i instรคllningarna", + "display-command-output-description": "Ange ett kommando som ska kรถras med jรคmna mellanrum. Den fรถrsta raden i utdata visas som text.", + "display-command-output-label": "Visa kommandoutdata", + "display-command-output-stream-description": "Ange ett kommando som ska kรถras kontinuerligt.", + "dynamic-text": "Dynamisk text", + "enable-colorization-description": "Aktivera fรคrglรคggning fรถr den anpassade knappikonen och texten, tillรคmpa temafรคrger.", + "enable-colorization-label": "Aktivera fรคrglรคggning", + "general-tooltip-text-description": "Anpassad text att visa i knappens verktygstips.", + "general-tooltip-text-label": "Anpassad verktygstipsstext", + "hide-mode-always-expanded": "Alltid expanderad", + "hide-mode-description": "Styr widgetens synlighet nรคr kommandot inte har nรฅgon utdata.", + "hide-mode-expand-with-output": "Expandera nรคr det finns utdata", + "hide-mode-label": "Dรถlj lรคge", + "hide-mode-max-transparent": "Maximalt expanderad men transparent", + "icon-description": "Vรคlj en ikon frรฅn biblioteket.", + "ipc-identifier-description": "Unik identifierare fรถr IPC-kommandon. Anvรคnd denna identifierare med 'qs -c noctalia-shell ipc call cb [action] [identifier]' fรถr att styra denna knapp via IPC.", + "ipc-identifier-label": "IPC-identifierare", + "left-click-description": "Kommando som ska utfรถras nรคr knappen vรคnsterklickas.", + "left-click-label": "Vรคnsterklick", + "left-click-update-text": "Uppdatera visad text vid vรคnsterklick", + "max-text-length-horizontal-description": "Maximalt antal tecken som ska visas i horisontell stapel (0 fรถr att dรถlja text).", + "max-text-length-horizontal-label": "Max textlรคngd (horisontell)", + "max-text-length-vertical-description": "Maximalt antal tecken som ska visas i vertikal stapel (0 fรถr att dรถlja text).", + "max-text-length-vertical-label": "Max textlรคngd (vertikal)", + "middle-click-description": "Kommando som ska utfรถras nรคr knappen klickas med mittknappen.", + "middle-click-label": "Mittklick", + "middle-click-update-text": "Uppdatera visad text vid mittklick", + "parse-json-description": "Analysera kommandoutmatningen som ett JSON-objekt fรถr att dynamiskt stรคlla in text och ikon.", + "parse-json-label": "Analysera utmatning som JSON", + "refresh-interval-description": "Intervall i millisekunder.", + "refresh-interval-label": "Uppdateringsintervall", + "right-click-description": "Kommando som ska utfรถras nรคr knappen hรถgerklickas.", + "right-click-label": "Hรถgerklicka", + "right-click-update-text": "Uppdatera visad text vid hรถgerklick", + "show-exec-tooltip-description": "Visa verktygstips med kommandodetaljer (vรคnster-/hรถger-/mittenklick, rullhjul).", + "show-exec-tooltip-label": "Visa kommandoverktygstips", + "show-icon-description": "Vรคxlar mellan att visa och dรถlja widgetens ikon.", + "show-icon-label": "Visa ikon", + "show-text-tooltip-description": "Visa verktygstips med utdata frรฅn textkommandot.", + "show-text-tooltip-label": "Visa dynamiska textverktygstips", + "text-stream-description": "Strรถmmade rader frรฅn kommandot visas som text pรฅ knappen.", + "text-stream-label": "Strรถm", + "wheel-description": "Kommando som ska utfรถras nรคr rullhjulet anvรคnds.
Anvรคnd $delta fรถr rullhjulets delta i kommandot.", + "wheel-down": "Skrolla ned", + "wheel-down-description": "Kommando som ska utfรถras nรคr rullhjulet rullas nedรฅt.", + "wheel-down-label": "Kommando fรถr att rulla nedรฅt", + "wheel-label": "Rullhjul", + "wheel-mode-separate-description": "Aktivera separata kommandon fรถr att rulla uppรฅt och nedรฅt.", + "wheel-mode-separate-label": "Separata hjulkommandon", + "wheel-up": "Rulla upp", + "wheel-up-description": "Kommando som ska utfรถras nรคr rullhjulet rullas uppรฅt.", + "wheel-up-label": "Kommando fรถr att rulla uppรฅt", + "wheel-update-text": "Uppdatera visad text vid rullning" + }, + "keyboard-layout": { + "show-icon-description": "Visa ikonen fรถr tangentbordslayout." + }, + "lock-keys": { + "hide-when-off-description": "Dรถlj indikatorn nรคr tangenten inte รคr aktiv.", + "hide-when-off-label": "Dรถlj nรคr av", + "show-caps-lock-description": "Visa status fรถr Caps Lock.", + "show-caps-lock-label": "Caps Lock", + "show-num-lock-description": "Visa status fรถr Num Lock.", + "show-num-lock-label": "Num Lock", + "show-scroll-lock-description": "Visa status fรถr Scroll Lock.", + "show-scroll-lock-label": "Scroll Lock" + }, + "media-mini": { + "compact-mode-description": "Aktivera en utrymmesbesparande layout fรถr mediaspelarpanelen.", + "compact-mode-label": "Kompakt lรคge", + "hide-mode-description": "Styr hur widgeten beter sig nรคr ingen media spelas upp.", + "max-width-description": "Stรคller in widgetens maximala horisontella storlek. Widgeten krymper fรถr att passa kortare innehรฅll.", + "no-active-player": "Ingen aktiv spelare", + "panel-section-description": "Konfigurera utseendet och beteendet fรถr mediaspelarpanelen.", + "panel-section-label": "Mediaspelarpanel", + "scrolling-mode-description": "Styr nรคr textrullning รคr aktiverad fรถr lรฅnga spรฅrtitlar.", + "scrolling-mode-label": "Rullningslรคge", + "show-album-art-description": "Visa albumomslaget fรถr det spรฅr som spelas upp fรถr nรคrvarande.", + "show-album-art-label": "Visa albumomslag", + "show-artist-first-description": "Visa artist - titel istรคllet fรถr titel - artist.", + "show-artist-first-label": "Visa artist fรถrst", + "show-progress-ring-description": "Visa en cirkulรคr fรถrloppsindikator som visar spรฅrets fรถrlopp.", + "show-progress-ring-label": "Visa fรถrloppsindikator", + "show-visualizer-description": "Visa en ljudvisualiserare nรคr musik spelas.", + "show-visualizer-label": "Visa visualiserare", + "use-fixed-width-description": "Nรคr denna funktion รคr aktiverad anvรคnder widgeten alltid maximal bredd istรคllet fรถr att dynamiskt anpassa sig efter innehรฅllet.", + "use-fixed-width-label": "Anvรคnd fast bredd", + "visualizer-type-description": "Vรคlj stil fรถr ljudvisualiseraren som ska visas.", + "visualizer-type-label": "Visualiseringstyp" + }, + "notification-history": { + "hide-widget-when-zero-description": "Dรถlj meddelandeikonen nรคr det inte finns nรฅgra meddelanden.", + "hide-widget-when-zero-label": "Dรถlj ikonen nรคr det inte finns nรฅgra meddelanden", + "hide-widget-when-zero-unread-description": "Dรถlj meddelandeikonen nรคr det inte finns nรฅgra olรคsta meddelanden.", + "hide-widget-when-zero-unread-label": "Dรถlj ikonen nรคr det inte finns nรฅgra olรคsta meddelanden", + "show-unread-badge-description": "Visa ett mรคrke som visar antalet olรคsta aviseringar.", + "show-unread-badge-label": "Visa mรคrke fรถr olรคsta", + "unread-badge-color-description": "Vรคlj fรคrg fรถr mรคrket fรถr olรคsta aviseringar.", + "unread-badge-color-label": "Fรคrg pรฅ olรคst mรคrke" + }, + "section-editor": { + "placeholder": "Vรคlj en widget att lรคgga till...", + "search-placeholder": "Sรถk widget..." + }, + "spacer": { + "width-description": "Avstรฅndsbredd i bildpunkter." + }, + "system-monitor": { + "compact-mode-description": "Visa statistik som minibar-diagram istรคllet fรถr textvรคrden. Fรถrhindrar layoutfรถrskjutning. ", + "compact-mode-label": "Kompakt lรคge", + "cpu-cores-description": "Visa CPU-kรคrnornas anvรคndning individuellt.", + "cpu-cores-label": "CPU-kรคrnor", + "cpu-frequency-description": "Visa aktuell CPU-klockhastighet i GHz.", + "cpu-frequency-label": "Visa CPU-frekvens", + "cpu-temperature-description": "Visa CPU-temperaturvรคrden om tillgรคngliga.", + "cpu-temperature-label": "CPU-temperatur", + "cpu-usage-description": "Visa aktuell CPU-anvรคndningsprocent.", + "cpu-usage-label": "CPU-anvรคndning", + "disk-path-description": "Vรคlj vilken diskmonteringspunkt som ska รถvervakas.", + "disk-path-label": "Disksรถkvรคg", + "gpu-temperature-description": "Visa GPU-temperaturvรคrden om tillgรคngliga.", + "load-average-description": "Visa systemets genomsnittliga belastning.", + "load-average-label": "Genomsnittlig belastning", + "memory-percentage-description": "Visa minnesanvรคndning som procentandel istรคllet fรถr absoluta vรคrden.", + "memory-percentage-label": "Minne i procent", + "memory-usage-description": "Visa aktuell information om RAM-anvรคndning.", + "memory-usage-label": "Minnesanvรคndning", + "network-traffic-description": "Visa nรคtverkets uppladdnings- och nedladdningshastigheter.", + "network-traffic-label": "Nรคtverkstrafik", + "storage-as-percentage-description": "Visa diskutrymme som procentandel istรคllet fรถr absoluta vรคrden.", + "storage-as-percentage-label": "Disk i procent", + "storage-available-description": "Visar hur mycket diskutrymme som รคr tillgรคngligt istรคllet fรถr hur mycket som anvรคnds.", + "storage-available-label": "Tillgรคngligt diskutrymme", + "storage-usage-description": "Visa information om diskutrymmesanvรคndning.", + "storage-usage-label": "Lagringsanvรคndning", + "swap-usage-description": "Visa anvรคndning av vรคxlingsminne.", + "swap-usage-label": "Vรคxlingsanvรคndning", + "use-monospace-font-description": "Anvรคnd monospace-teckensnitt fรถr enhetlig teckenbredd. ", + "use-monospace-font-label": "Teckensnitt fรถr fast bredd", + "use-padding-description": "Fyller ut textvรคrdena med inledande mellanslag fรถr att fรถrhindra layoutfรถrskjutning.", + "use-padding-description-disabled-monospace-font": "Ett monospace-typsnitt behรถvs fรถr den hรคr funktionen.", + "use-padding-description-disabled-vertical": "Vertikal aktivitetsfรคlt stรถder inte textutfyllnad.", + "use-padding-label": "Textutfyllnad" + }, + "taskbar": { + "colorize-icons-description": "Tillรคmpa temafรคrger pรฅ ikoner i aktivitetsfรคltet.", + "hide-mode-description": "Styr hur widgeten beter sig nรคr det inte finns nรฅgra matchande fรถnster.", + "hide-mode-label": "Dรถljningslรคge", + "icon-scale-description": "Stรคller in skalningsfaktorn fรถr ikoner i aktivitetsfรคltet.", + "icon-scale-label": "Ikonskalning", + "max-width-description": "Maximal bredd fรถr aktivitetsfรคltet som en procentandel av skรคrmens bredd.", + "max-width-label": "Maximal bredd", + "only-active-workspaces-description": "Visa endast appar frรฅn aktiva arbetsytor.", + "only-active-workspaces-label": "Endast frรฅn aktiva arbetsytor", + "only-same-monitor-description": "Visa endast appar frรฅn den skรคrm dรคr fรคltet finns.", + "only-same-monitor-label": "Endast frรฅn samma skรคrm", + "show-pinned-apps-description": "Visa fรคsta appar frรฅn dockningen i aktivitetsfรคltet.", + "show-pinned-apps-label": "Visa fรคsta appar", + "show-title-description": "Visa fรถnstertitlar i aktivitetsfรคltet.", + "show-title-description-disabled": "Vertikalt aktivitetsfรคlt stรถder inte visning av titlar.", + "show-title-label": "Visa titel", + "smart-width-description": "Justera automatiskt bredden pรฅ poster baserat pรฅ antalet poster.", + "smart-width-label": "Smart bredd", + "title-width-description": "Stรคll in bredden pรฅ fรถnstertitlar i aktivitetsfรคltet (i pixlar).", + "title-width-label": "Titelbredd", + "title-width-reset-tooltip": "ร…terstรคll titelbredd" + }, + "tray": { + "chevron-color-description": "Tillรคmpa temafรคrger pรฅ lรฅdans pil-ikon.", + "chevron-color-label": "Pilfรคrg", + "colorize-icons-description": "Tillรคmpa temafรคrger pรฅ ikoner i aktivitetsfรคltet.", + "colorize-icons-label": "Fรคrglรคgg ikoner", + "drawer-enabled-description": "Nรคr funktionen รคr aktiverad visas fackikoner som inte รคr fรคsta i en lรฅda.
Nรคr funktionen รคr inaktiverad visas alla fackikoner i rad.", + "drawer-enabled-label": "Aktivera lรฅda", + "hide-passive-description": "Nรคr funktionen รคr aktiverad dรถljs fackikoner med statusen 'Passiv'.", + "hide-passive-label": "Dรถlj passiva ikoner" + }, + "volume": { + "display-mode-description": "Vรคlj hur du vill att detta vรคrde ska visas." + }, + "workspace": { + "character-count-description": "Antal tecken som ska visas frรฅn arbetsytans namn (1โ€“10). ", + "character-count-label": "Teckenantal", + "empty-color-description": "Stรคll in bakgrundsfรคrgen fรถr tomma arbetsytor.", + "empty-color-label": "Fรคrg fรถr tomma arbetsytor", + "enable-scrollwheel-description": "Vรคxla mellan arbetsytor med musens rullhjul.", + "enable-scrollwheel-label": "Rulla fรถr att vรคxla mellan arbetsytor", + "focused-color-description": "Stรคll in bakgrundsfรคrgen fรถr det fokuserade arbetsomrรฅdet.", + "focused-color-label": "Fokuserad arbetsomrรฅdesfรคrg", + "follow-focused-screen-description": "Visa arbetsomrรฅden frรฅn den skรคrm som fรถr nรคrvarande รคr fokuserad, snarare รคn den skรคrm dรคr fรคltet finns.", + "follow-focused-screen-label": "Fรถlj fokuserad skรคrm", + "font-weight-description": "Stรคll in den visuella vikten fรถr text inom arbetsytan.", + "font-weight-label": "Teckensnittsvikt", + "grouped-border-opacity-description": "Stรคll in opacitetsnivรฅn fรถr arbetsytans behรฅllargrรคnser.", + "grouped-border-opacity-label": "Ramopacitet", + "hide-unoccupied-description": "Visa inte arbetsytor utan fรถnster.", + "hide-unoccupied-label": "Dรถlj oanvรคnda", + "label-mode-description": "Vรคlj hur arbetsytans etiketter ska visas.", + "label-mode-label": "Etikettlรคge", + "occupied-color-description": "Stรคll in bakgrundsfรคrgen fรถr anvรคnda arbetsytor.", + "occupied-color-label": "Fรคrg fรถr anvรคnda arbetsytor", + "pill-size-description": "Justera storleken pรฅ arbetsytepillerna (50%-100%).", + "pill-size-label": "Pillerstorlek", + "reverse-scrolling-description": "Vรคnd riktningen fรถr arbetsytevรคxling vid rullning.", + "reverse-scrolling-label": "Omvรคnd rullning", + "show-applications-description": "Visa applikationsikoner i varje arbetsyta.", + "show-applications-hover-description": "Visa programikoner i varje arbetsyta nรคr arbetsytan hovras รถver.", + "show-applications-hover-label": "Visa applikationer vid hovring", + "show-applications-label": "Visa applikationer", + "show-badge-description": "Visa arbetsytans nummerskylt i grupperat lรคge.", + "show-badge-label": "Visa arbetsytans skylt", + "show-labels-only-when-occupied-description": "Visa endast arbetsytans etiketter nรคr de innehรฅller fรถnster. ", + "show-labels-only-when-occupied-label": "Visa etiketter endast nรคr de รคr upptagna", + "unfocused-icons-opacity-description": "Stรคll in opacitetsnivรฅn fรถr appikoner som inte รคr i fokus.", + "unfocused-icons-opacity-label": "Opacitet fรถr ikoner som inte รคr i fokus" + } + }, + "battery": { + "all-batteries": "Alla batterier (kombinerat)", + "battery-health": "Batteriets hรคlsa", + "battery-level": "Batterinivรฅ", + "capacity-level": "Kapacitet: {level}", + "charging-rate": "Laddningshastighet: {rate}W", + "discharging-rate": "Urladdningshastighet: {rate}W", + "health": "Hรคlsa: {percent}%", + "inhibit-idle-description": "Hรฅller systemet vaket.", + "no-battery-detected": "Inget batteri upptรคckt", + "plugged-in": "Ansluten", + "power-profile": "Strรถmprofil", + "time-left": "ร…terstรฅende tid: {time}", + "time-until-full": "Tid till full laddning: {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "Tillgรคngliga enheter", + "blocked": "Blockerad", + "connected-devices": "Anslutna enheter", + "device-address": "Enhetsadress", + "disabled": "Bluetooth รคr inaktiverat", + "discoverable": "Upptรคckbar", + "enable-message": "Aktivera Bluetooth fรถr att se tillgรคngliga enheter.", + "known-devices": "Kรคnda enheter", + "no-devices": "Inga enheter tillgรคngliga", + "paired-devices": "Parkopplade enheter", + "pairing-mode": "Se till att din enhet รคr i parkopplingslรคge.", + "scanning": "Sรถker efter enheter...", + "signal-text-excellent": "Signal: Utmรคrkt", + "signal-text-fair": "Signal: God", + "signal-text-good": "Signal: Bra", + "signal-text-poor": "Signal: Dรฅlig", + "signal-text-unknown": "Signal: Okรคnd", + "signal-text-very-poor": "Signal: Mycket dรฅlig" + } + }, + "changelog": { + "error": { + "fetch-failed": "Det gรฅr inte att lรคsa in รคndringsloggdata. Fรถrsรถk igen senare.", + "rate-limit": "GitHub-hastighetsgrรคnsen รถverskreds. Fรถrsรถk igen om nรฅgra minuter." + }, + "panel": { + "buttons-discord": "Gรฅ med i vรฅr Discord", + "buttons-dismiss": "OK", + "buttons-feedback": "Ge feedback", + "empty": "Versionsfakta รคr รคnnu inte tillgรคngliga.", + "highlight-title": "Hรถjdpunkter", + "released": "Slรคpptes den {date}", + "subtitle-fresh": "Tack fรถr att du installerade Noctalia! Hรคr รคr vad som ingรฅr i den hรคr versionen.", + "subtitle-updated": "Uppdaterade frรฅn {previousVersion}", + "title": "Vad รคr nytt i {version}", + "version": "Version {version}", + "version-new-user": "Ny installation" + } + }, + "common": { + "actions": "ร…tgรคrder", + "active": "Aktiv", + "add": "Lรคgg till", + "appearance": "Utseende", + "apply": "Tillรคmpa", + "auto-connect": "Automatisk anslutning", + "automation": "Automation", + "available": "Tillgรคnglig", + "back": "Tillbaka", + "battery": "Batteri", + "behavior": "Beteende", + "bluetooth": "Bluetooth", + "brightness": "Ljusstyrka", + "browse": "Blรคddra", + "calendar": "Kalender", + "calendar-panel": "Kalenderpanel", + "cancel": "Avbryt", + "cards": "Kort", + "charging": "Laddning", + "clear": "Tรถm", + "clipboard": "Urklipp", + "close": "Stรคng", + "color-muted": "Dรคmpad", + "colors": "Fรคrger", + "command": "Kommando", + "confirm": "Bekrรคfta", + "connect": "Anslut", + "connected": "Ansluten", + "connecting": "Ansluter...", + "continue": "Fortsรคtt", + "contributors": "Bidragsgivare", + "copied-to-clipboard": "Kopierat till urklipp", + "countdown": "Nedrรคkning", + "customize": "Anpassa", + "date": "Datum", + "default": "Standard", + "delete": "Ta bort", + "devices": "Enheter", + "disabled": "Inaktiverad", + "discharging": "Urladdning", + "disconnect": "Koppla frรฅn", + "disconnected": "Frรฅnkopplad", + "disconnecting": "Kopplar frรฅn...", + "display-mode": "Visningslรคge", + "documentation": "Dokumentation", + "download": "Hรคmta ner", + "duration": "Varaktighet", + "dysfunctional": "Dysfunktionell", + "edit": "Redigera", + "enabled": "Aktiverad", + "error": "Fel", + "ethernet": "Ethernet", + "events": "Hรคndelser", + "execute": "Exekvera", + "faithful": "Trogen", + "focus": "Fokus", + "font-weight-bold": "Fet", + "font-weight-light": "Lรคtt", + "font-weight-medium": "Mellan", + "font-weight-regular": "Vanlig", + "font-weight-semibold": "Halvfet", + "frequency": "Frekvens", + "gateway": "Gateway", + "general": "Allmรคnt", + "height": "Hรถjd", + "hibernate": "Vilolรคge", + "history": "Historik", + "icon": "Ikon", + "idle": "Overksam", + "info": "Info", + "input": "Inmatning", + "install": "Installera", + "installed": "Installerad", + "interface": "Grรคnssnitt", + "internet": "Internet", + "keybind": "Tangentbindning", + "language": "Sprรฅk", + "loading": "Lรคser in...", + "local": "Lokal", + "location": "Plats", + "lock": "Lรฅs", + "logout": "Logga ut", + "look": "Titta", + "margins": "Marginaler", + "media": "Media", + "media-player": "Mediaspelare", + "memory": "Minne", + "monitors": "Skรคrmar", + "move-down": "Flytta ned", + "move-up": "Flytta upp", + "network": "Nรคtverk", + "next": "Nรคsta", + "night-light": "Nattlampa", + "no": "Nej", + "no-results": "Inga resultat", + "no-summary": "Ingen sammanfattning", + "none": "Ingen", + "not-found": "Hittades inte", + "notifications": "Meddelanden", + "official": "Officiellt", + "on-surface": "Pรฅ ytan", + "output": "Utgรฅng", + "pair": "Para ihop", + "paired": "Ihopparad", + "pairing": "Parar ihop...", + "panels": "Paneler", + "password": "Lรถsenord", + "pause": "Paus", + "performance": "Prestanda", + "pin": "Fรคst", + "play": "Spela upp", + "polling": "Pollning", + "position": "Position", + "previous": "Fรถregรฅende", + "primary": "Primรคr", + "random": "Slumpmรคssig", + "reboot": "Starta om", + "reboot-to-uefi": "Starta om till UEFI", + "record": "Starta inspelning", + "refresh": "Uppdatera", + "required": "(krรคvs)", + "reset": "ร…terstรคll", + "reset-to-default": "ร…terstรคll till Standard", + "result-count": "{count} resultat", + "result-count-plural": "{count} resultat", + "resume": "ร…teruppta", + "save": "Spara", + "scanning": "Sรถker igenom...", + "screen-corners": "Skรคrmhรถrn", + "search": "Sรถk", + "second": "{count} sekund", + "second-plural": "{count} sekunder", + "secondary": "Sekundรคr", + "security": "Sรคkerhet", + "select": "Vรคlj", + "select-color": "Vรคlj fรคrg", + "select-color-description": "Tillรคmpa temafรคrger fรถr betoning.", + "select-icon-color": "Vรคlj ikonfรคrg", + "settings": "Instรคllningar", + "shortcuts": "Genvรคgar", + "shutdown": "Stรคng av", + "signal": "Signal", + "sound": "Ljud", + "sources": "Kรคllor", + "start": "Starta", + "stop": "Stoppa", + "supporters": "Stรถdjare", + "suspend": "Strรถmsparlรคge", + "templates": "Mallar", + "tertiary": "Tertiรคr", + "test": "Testa", + "thresholds": "Trรถskelvรคrden", + "title": "Titel", + "toast": "Toast", + "trusted": "Betrodd", + "uninstall": "Avinstallera", + "unknown": "Okรคnd", + "unpair": "Avpara", + "unpin": "Avfรคst", + "update": "Uppdatera", + "upload": "Skicka upp", + "username": "Anvรคndarnamn", + "userspace-reboot": "Starta om anvรคndarutrymme", + "version": "Version", + "vibrant": "Levande", + "visualizer": "Visualiserare", + "volume": "Volym", + "volumes": "Volymer", + "wallpaper": "Bakgrundsbild", + "weather": "Vรคder", + "weather-loading": "Lรคser in vรคder...", + "weather-no-location": "Ange en plats i Instรคllningarna fรถr att se vรคdret", + "week": "Vecka", + "widgets": "Widgetar", + "width": "Bredd", + "wifi": "Wi-Fi", + "windows": "Fรถnster", + "yes": "Ja" + }, + "control-center": { + "power-profile": { + "tooltip-action": "Strรถmprofil", + "tooltip-disabled": "Installera power-profiles-daemon fรถr att anvรคnda strรถmprofiler" + }, + "wifi": { + "label-disconnected": "Wi-Fi frรฅnkopplat", + "label-ethernet": "Ethernet" + } + }, + "display-modes": { + "always-hide": "Dรถlj alltid", + "always-show": "Visa alltid", + "force-open": "Tvinga รถppning", + "on-hover": "Vid muspekning" + }, + "hide-modes": { + "auto-hide": "Dรถlj automatiskt", + "hidden": "Dรถlj nรคr tom", + "idle": "Dรถlj nรคr inaktiv", + "non-exclusive": "Icke-exklusiv", + "transparent": "Transparent nรคr tom", + "visible": "Alltid synlig" + }, + "launcher": { + "categories": { + "all": "Alla", + "audiovideo": "Ljud och video", + "chat": "Chatta", + "development": "Utveckling", + "education": "Utbildning", + "emoji-activity": "Aktivitet", + "emoji-animals": "Djur", + "emoji-flags": "Flaggor", + "emoji-food": "Mat och dryck", + "emoji-nature": "Natur", + "emoji-objects": "Objekt", + "emoji-people": "Mรคnniskor och kropp", + "emoji-recent": "Senaste", + "emoji-symbols": "Symboler", + "emoji-travel": "Resor och platser", + "game": "Spel", + "graphics": "Grafik", + "misc": "ร–vrigt", + "office": "Kontor", + "pinned": "Fรคst", + "system": "System", + "webbrowser": "Webblรคsare" + }, + "providers": { + "applications": "Applikationer", + "calculator": "Kalkylator", + "calculator-deprecated": "Kommandot >calc รคr fรถrรฅldrat och kommer snart att tas bort. Skriv istรคllet matematiska uttryck direkt i sรถkfรคltet.", + "calculator-description": "Kalkylator โ€” berรคkna matematiska uttryck", + "calculator-enter-expression": "Ange ett matematiskt uttryck", + "calculator-press-enter-to-copy": "Tryck pรฅ Enter fรถr att kopiera resultatet", + "clipboard": "Urklippshistorik", + "clipboard-clear-description": "Tรถm all urklippshistorik", + "clipboard-clear-description-full": "Ta bort alla objekt frรฅn urklippshistoriken", + "clipboard-clear-history": "Tรถm urklippshistoriken", + "clipboard-delete": "Ta bort urklippspost", + "clipboard-history-disabled": "Urklippshistorik inaktiverad", + "clipboard-history-disabled-description": "Aktivera urklippshistorik i instรคllningarna eller installera cliphist", + "clipboard-loading": "Lรคser in urklippshistorik...", + "clipboard-search-description": "Sรถk i urklippshistorik", + "command-description": "Kรถr skalkommandon", + "emoji": "Emoji-vรคljare", + "emoji-loading": "Lรคser in emojis...", + "emoji-loading-description": "Vรคnta", + "emoji-no-recent": "Inga nya emojis รคnnu", + "emoji-search-description": "Sรถk och kopiera emojis", + "settings-search-description": "Sรถk och navigera till instรคllningar", + "windows-search-description": "Sรถk och fokusera รถppna fรถnster" + } + }, + "lock-screen": { + "authenticating": "Autentiserar...", + "password": "Ange ditt lรถsenord...", + "restart": "Starta om", + "shut-down": "Stรคng av" + }, + "notifications": { + "panel": { + "click-to-collapse": "Klicka fรถr att fรคlla ihop", + "click-to-expand": "Klicka fรถr att expandera", + "description": "Dina aviseringar visas hรคr nรคr de kommer in. ", + "no-notifications": "Inga meddelanden" + }, + "range": { + "earlier": "Tidigare", + "today": "Idag", + "yesterday": "Igรฅr" + }, + "time": { + "diff-d": "1 dag sedan", + "diff-dd": "{diff} dagar sedan", + "diff-h": "1 timme sedan", + "diff-hh": "{diff} timmar sedan", + "diff-m": "1 minut sedan", + "diff-mm": "{diff} minuter sedan", + "now": "nu" + } + }, + "options": { + "bar": { + "density-comfortable": "Bekvรคm", + "density-compact": "Kompakt", + "density-default": "Standard", + "density-mini": "Mini", + "density-spacious": "Rymlig", + "display-mode-always-visible": "Alltid synlig", + "type-floating": "Flytande", + "type-framed": "Inramad", + "type-simple": "Enkel" + }, + "control-center": { + "quick-settings-style-classic": "Klassisk", + "quick-settings-style-modern": "Modern" + }, + "frame-rates-fps": "{fps} bilder/s", + "launcher-density": { + "comfortable": "Bekvรคm", + "compact": "Kompakt", + "default": "Standard" + }, + "launcher-view-mode": { + "grid": "Rutnรคt", + "list": "Lista" + }, + "notification-density": { + "compact": "Kompakt", + "default": "Standard" + }, + "scrolling-modes": { + "always": "Rulla alltid", + "hover": "Rulla vid muspekning", + "never": "Rulla aldrig" + }, + "session-menu-grid-layout": { + "grid": "Rutnรคt", + "single-row": "En rad" + }, + "settings-panel-mode": { + "attached": "Panel fรคst vid fรคltet", + "centered": "Centrerad panel", + "window": "Separat fรถnster" + }, + "visualizer-types": { + "linear": "Linjรคr", + "mirrored": "Speglad", + "wave": "Vรฅg" + }, + "visualizer-visibility": { + "with-background": "Endast med bakgrund" + }, + "workspace-labels": { + "index": "Index", + "index-and-name": "Index och namn", + "name": "Namn" + } + }, + "panels": { + "about": { + "become-supporter": "Bli supporter", + "changelog": "Visa รคndringslogg", + "changelog-on-startup": "Visa รคndringslogg vid uppdatering", + "changelog-on-startup-desc": "Visa รคndringsloggen automatiskt nรคr Noctalia uppdateras.", + "contributors-description": "Tack till vรฅra {count} fantastiska bidragsgivare!", + "contributors-description-plural": "Tack till vรฅra {count} fantastiska bidragsgivare!", + "copy-info": "Kopiera info", + "debug-disabled": "Felsรถkningslรคge inaktiverat", + "debug-enabled": "Felsรถkningslรคge aktiverat", + "info-copied": "Info kopierad till urklipp", + "noctalia-available": "Tillgรคnglig:", + "noctalia-desc": "Ett elegant och minimalistiskt skrivbordsskal som รคr noggrant utformat fรถr Wayland och byggt med Quickshell.", + "noctalia-git-commit": "Git-commit:", + "noctalia-installed-version": "Installerad version:", + "noctalia-latest-version": "Senaste version:", + "noctalia-qs-version": "Noctalia QS version:", + "noctalia-title": "Noctalia-skal", + "privacy-policy": "Sekretesspolicy", + "support": "Stรถd oss", + "supporter-badge": "Supporter", + "supporters-desc": "Ett stort tack till vรฅr fantastiska supporter!", + "supporters-desc-plural": "Ett stort tack till vรฅra {count} fantastiska supportrar!", + "supporters-loading": "Laddar supportrar...", + "system-board": "Moderkort:", + "system-cpu": "CPU:", + "system-disk": "Disk:", + "system-gpu": "GPU:", + "system-host": "Vรคrd:", + "system-install-hint": "Installera fastfetch fรถr att visa systeminformation", + "system-kernel": "Kรคrna:", + "system-loading": "Lรคser in systeminformation...", + "system-memory": "Minne:", + "system-monitor": "Skรคrm:", + "system-not-installed": "fastfetch รคr inte installerat", + "system-os": "Operativsystem:", + "system-packages": "Paket:", + "system-product": "Produkt:", + "system-title": "Systeminformation", + "system-uptime": "Driftstid:", + "system-wm": "WM:", + "telemetry-data-copied": "Telemetridata kopierad till urklipp", + "telemetry-desc": "Hjรคlp till att fรถrbรคttra Noctalia genom att dela anonym systeminformation (skรคrmupplรถsning, kompositรถr, distro). Skickas en gรฅng vid start, ingen spรฅrning, data raderas automatiskt efter 30 dagar.", + "telemetry-enabled": "Skicka anonym systeminformation", + "telemetry-show-data": "Visa data", + "telemetry-title": "Integritet", + "title": "Om", + "up-to-date": "Du รคr uppdaterad!", + "update-available": "Uppdatering tillgรคnglig", + "view-commit": "Visa commit pรฅ GitHub" + }, + "audio": { + "devices-desc": "Konfigurera tillgรคngliga ljudinmatnings- och ljudutmatningsenheter.", + "devices-input-device-description": "Vรคlj รถnskad ljudingรฅngsenhet.", + "devices-input-device-label": "Ingรฅngsenhet", + "devices-output-device-description": "Vรคlj รถnskad ljudutgรฅngsenhet.", + "devices-output-device-label": "Utgรฅngsenhet", + "devices-title": "Ljudenheter", + "external-mixer-description": "Ange kommandot eller applikationssรถkvรคgen som ska startas nรคr funktionen fรถr extern ljudmixer aktiveras.", + "external-mixer-label": "Kommando fรถr extern ljudmixer", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "Stรคll in dina fรถredragna och ignorerade medieapplikationer.", + "media-excluded-player-description": "Lรคgg till nyckelord fรถr uppspelare som du vill att systemet ska ignorera. Varje nyckelord ska stรฅ pรฅ en ny rad.", + "media-excluded-player-label": "Exkluderad spelare", + "media-excluded-player-placeholder": "skriv in delstrรคng och tryck pรฅ +", + "media-frame-rate-description": "Hรถgre hastigheter รคr smidigare men anvรคnder mer resurser.", + "media-frame-rate-label": "Bildfrekvens", + "media-primary-player-description": "Ange ett nyckelord fรถr att identifiera din huvudspelare.", + "media-primary-player-label": "Primรคr spelare", + "media-primary-player-placeholder": "t.ex. spotify, vlc, mpv", + "media-scrolling-speed-description": "Tid i sekunder fรถr titeln att rulla frรฅn bรถrjan till slut.", + "media-scrolling-speed-label": "Rullningshastighet", + "media-scrolling-title-description": "Aktivera kontinuerlig rullning fรถr lรฅnga medietitlar.", + "media-scrolling-title-label": "Rullningstitel", + "media-title": "Mediaspelare", + "on-middle-clicked-description": "Kommando som ska utfรถras nรคr knappen klickas pรฅ med mittknappen.", + "panel-applications-empty": "Inga applikationer spelar upp ljud fรถr nรคrvarande", + "spectrum-mirrored-description": "Spegla spektrumet sรฅ att lรฅga frekvenser รคr i mitten. Nรคr avstรคngt รคr lรฅga frekvenser till vรคnster och hรถga till hรถger.", + "spectrum-mirrored-label": "Speglad spektrum", + "title": "Ljud", + "visualizer-type-description": "Vรคlj en visualiseringstyp fรถr medieuppspelning.", + "visualizer-type-label": "Visualiseringstyp", + "volumes-desc": "Justera volymkontroller och ljudnivรฅer.", + "volumes-feedback-sound-file-description": "Sรถkvรคg till ljudfilen som spelas upp nรคr volymen รคndras.", + "volumes-feedback-sound-file-label": "Ljudfil fรถr volymfeedback", + "volumes-feedback-sound-file-select-title": "Vรคlj ljudfil fรถr volymfeedback", + "volumes-input-volume-description": "Mikrofonens ingรฅngsvolymnivรฅ.", + "volumes-mute-input-description": "Stรคng av standardljudingรฅngen (mikrofon).", + "volumes-mute-input-label": "Stรคng av ljudingรฅng", + "volumes-mute-output-description": "Stรคng av systemets huvudsakliga ljudutgรฅng.", + "volumes-mute-output-label": "Stรคng av ljudutgรฅng", + "volumes-output-volume-description": "Systemomfattande volymnivรฅ.", + "volumes-step-size-description": "Justera stegstorleken fรถr volymรคndringar (rullhjul, kortkommandon).", + "volumes-step-size-label": "Volymstegstorlek", + "volumes-volume-feedback-description": "Spela upp ett ljud nรคr volymen justeras.", + "volumes-volume-feedback-label": "Spela upp ljud vid volymjustering", + "volumes-volume-overdrive-description": "Tillรฅt att volymen hรถjs รถver 100 %. Stรถds kanske inte av all hรฅrdvara.", + "volumes-volume-overdrive-label": "Tillรฅt volymรถverstyrning" + }, + "bar": { + "appearance-auto-hide-delay-description": "Tid innan Bar dรถljs efter att musen lรคmnar den.", + "appearance-auto-hide-delay-label": "Dรถlj fรถrdrรถjning", + "appearance-auto-hide-exclusive-note": "Obs: Exklusiv zon inaktiveras automatiskt nรคr automatisk dรถljning รคr aktiv.", + "appearance-auto-show-delay-description": "Tid innan fรคltet visas nรคr musen nรฅr kanten.", + "appearance-auto-show-delay-label": "Visningsfรถrdrรถjning", + "appearance-background-opacity-description": "Stรคll in bakgrundens opacitet specifikt fรถr fรคltet.", + "appearance-background-opacity-label": "Fรคltets bakgrundsopacitet", + "appearance-capsule-color-description": "Vรคlj en fรคrg fรถr fรคltkapslar, eller anvรคnd ingen fรถr standardytfรคrgen.", + "appearance-capsule-color-label": "Kapselfรคrg", + "appearance-capsule-opacity-description": "Stรคll in opacitetsnivรฅn fรถr widgetbakgrunder nรคr kapseln visas.", + "appearance-capsule-opacity-label": "Kapselns opacitet", + "appearance-content-padding-description": "Justera avstรฅndet mellan fรคltets kanter och widgetar.", + "appearance-content-padding-label": "Innehรฅllsavstรฅnd", + "appearance-density-description": "Justera stapelns avstรฅnd fรถr ett kompakt eller rymligt utseende.", + "appearance-density-label": "Stapeldensitet", + "appearance-desc": "Anpassa stapelns utseende och position.", + "appearance-display-mode-description": "Vรคlj nรคr listen รคr synlig.", + "appearance-enable-exclusion-zone-inset-description": "Minska exklusionszonen med 1 fysisk pixel sรฅ att fรถnster som ligger kant i kant perfekt รถverlappar under stapelkanten.", + "appearance-enable-exclusion-zone-inset-label": "Infรคlld uteslutningszon", + "appearance-floating-description": "Visa stapeln som en flytande 'piller'.", + "appearance-floating-label": "Flytande stapel", + "appearance-font-scale-description": "Justera skalningen av teckenstorleken fรถr text som visas i fรคltet.", + "appearance-font-scale-label": "Teckensnittsskala", + "appearance-frame-radius": "Inre radie", + "appearance-frame-settings-description": "Justera ramens tjocklek och inre hรถrnradie", + "appearance-frame-settings-label": "Raminstรคllningar", + "appearance-frame-thickness": "Tjocklek", + "appearance-hide-on-overview-description": "Dรถlj fรคltet och stรคng panelerna nรคr kompositรถrsรถversikten รคr aktiv.", + "appearance-hide-on-overview-label": "Dรถlj fรคltet pรฅ รถversikten", + "appearance-margins-description": "Justera marginalerna runt det flytande fรคltet.", + "appearance-margins-horizontal": "Horisontell marginal", + "appearance-margins-vertical": "Vertikal marginal", + "appearance-outer-corners-description": "Visa utรฅtbรถjda hรถrn pรฅ stapeln.", + "appearance-outer-corners-label": "Ytterhรถrn", + "appearance-position-description": "Vรคlj var fรคltet ska placeras pรฅ skรคrmen.", + "appearance-position-label": "Stapelposition", + "appearance-show-capsule-description": "Visa widgetbakgrunder.", + "appearance-show-capsule-label": "Visa kapsel", + "appearance-show-on-workspace-switch-description": "Visa Bar automatiskt en kort stund nรคr arbetsytan รคndras.", + "appearance-show-on-workspace-switch-label": "Visa fรคlt vid arbetsytebyte", + "appearance-show-outline-description": "Visar en synlig kant runt varje widget.", + "appearance-show-outline-label": "Visa widgetkonturer", + "appearance-type-description": "Vรคlj stil fรถr fรคltet: Enkel, Flytande eller Inramad.", + "appearance-type-label": "Fรคlttyp", + "appearance-use-separate-opacity-description": "Aktivera fรถr att anvรคnda ett separat opacitetsvรคrde fรถr fรคltets bakgrund.", + "appearance-use-separate-opacity-label": "Anvรคnd separat opacitet fรถr fรคltet", + "appearance-widget-spacing-description": "Justera avstรฅndet mellan varje widget i fรคltet.", + "appearance-widget-spacing-label": "Widgetavstรฅnd", + "behavior-middle-click-command-description": "Kommando att kรถra vid mellanklick.", + "behavior-middle-click-command-label": "Mellanklick-kommando", + "behavior-middle-click-command-placeholder": "niri msg action vรคxla รถversikt", + "behavior-middle-click-description": "Vรคlj vad ett mellanklick gรถr pรฅ tomma omrรฅden av Fรคltet.", + "behavior-middle-click-follow-mouse-description": "ร–ppna den valda mellanklickspanelen vid muspekarens position.", + "behavior-middle-click-follow-mouse-label": "Mellanklick fรถljer mus", + "behavior-middle-click-label": "Fรคltets mittenklicksรฅtgรคrd", + "behavior-right-click-command-description": "Kommando att kรถra vid hรถgerklick.", + "behavior-right-click-command-label": "Hรถgerklickskommando", + "behavior-right-click-command-placeholder": "notify-send \"Hรถgerklick\"", + "behavior-right-click-description": "Vรคlj vad hรถgerklick gรถr pรฅ tomma omrรฅden av fรคltet.", + "behavior-right-click-follow-mouse-description": "ร–ppna den valda hรถgerklickspanelen vid markรถrpositionen.", + "behavior-right-click-follow-mouse-label": "Hรถgerklick fรถljer musen", + "behavior-right-click-label": "ร…tgรคrd vid hรถgerklick pรฅ Fรคltet", + "behavior-wheel-wrap-description": "Nรคr aktiverat fortsรคtter rullningen frรฅn det sista objektet till det fรถrsta.", + "behavior-wheel-wrap-label": "Cykla", + "behavior-workspace-scroll-description": "Vรคlj vad mushjulet gรถr pรฅ tomma omrรฅden i fรคltet.", + "behavior-workspace-scroll-label": "Musrullningsรฅtgรคrd fรถr Fรคltet", + "behavior-workspace-scroll-option-content": "Innehรฅll", + "behavior-workspace-scroll-option-workspace": "Arbetsyta", + "monitor-configure-widgets": "Konfigurera widgetar", + "monitor-override-settings": "ร…sidosรคtt globala instรคllningar", + "monitor-override-settings-description": "Anvรคnd anpassade instรคllningar fรถr denna skรคrm.", + "monitor-reset-all": "ร…terstรคll alla", + "monitor-widgets-title": "Widgetkonfiguration - {monitor}", + "monitors-desc": "Visa stapel pรฅ specifika skรคrmar. Standardinstรคllningen รคr alla om ingen รคr vald.", + "monitors-desc-new": "Konfigurera vilka skรคrmar som ska visa stapeln och anpassa instรคllningarna per skรคrm.", + "monitors-title": "Skรคrmar visar", + "title": "Stapel", + "tray-blacklist-description": "Lรคgg till uteslutningsregler fรถr facket, stรถder jokertecken (*).", + "tray-blacklist-label": "Svartlista", + "tray-blacklist-placeholder": "t.ex. nm-applet, Fcitx*", + "tray-pin-application": "Fรคst applikation", + "tray-unpin-application": "Ta bort fรคstning av applikation", + "use-global-widgets": "Anvรคnd globala widgetar", + "widgets-desc": "Dra widgetar fรถr att รคndra deras ordning. Anvรคnd hรถgerklickmenyn fรถr att flytta widgetar mellan sektioner eller ta bort dem.", + "widgets-title": "Position fรถr widgetar" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "Skapa fรคrgscheman frรฅn din bakgrundsbild. Extraherar automatiskt fรคrger fรถr att skapa ett sammanhรคngande tema.", + "color-source-use-wallpaper-colors-label": "Anvรคnd bakgrundsbildernas fรคrger", + "dark-mode-mode-description": "Aktiverar automatisk vรคxling mellan ljust och mรถrkt lรคge.", + "dark-mode-mode-label": "Schema fรถr mรถrkt lรคge", + "dark-mode-mode-manual": "Manuellt", + "dark-mode-mode-off": "Av", + "dark-mode-switch-description": "Vรคxlar till ett mรถrkare tema fรถr att underlรคtta visning pรฅ natten.", + "delete-error-description": "Det gick inte att ta bort {scheme}", + "delete-error-title": "Borttagning misslyckades", + "delete-success-description": "{scheme} har tagits bort", + "delete-success-title": "Fรคrgschemat togs bort", + "download-button": "Hรคmta ner fler", + "download-downloading": "Hรคmtar...", + "download-empty": "Inga fรคrgscheman tillgรคngliga", + "download-error-api-error": "API-fel: {status}", + "download-error-description": "Det gick inte att hรคmta ner {scheme}", + "download-error-download-failed": "Nedladdningen misslyckades med avslutskod: {code}", + "download-error-invalid-response": "Ogiltigt API-svarformat", + "download-error-no-files": "Inga filer hittades fรถr schemat", + "download-error-parse-failed": "Det gick inte att tolka API-svaret: {error}", + "download-error-rate-limit": "GitHub API-hastighetsgrรคnsen รถverskriden", + "download-error-title": "Hรคmtningen misslyckades", + "download-fetching": "Hรคmtar tillgรคngliga fรคrgscheman...", + "download-success-description": "{schema} har hรคmtats ner", + "download-success-title": "Fรคrgschema har hรคmtats ner", + "download-title": "Hรคmta ner fรคrgscheman", + "method-description": { + "content": "Material Design-schema med hรถgkvalitativ fรคrgutvinning som nรคra matchar kรคllinnehรฅllets faktiska fรคrger.", + "dysfunctional": "Som Faithful, men vรคljer den nรคst mest dominanta fรคrgfamiljen som primรคr.", + "faithful": "Fรถrsรถker hรฅlla sig nรคra kรคllfรคrgen samtidigt som en harmonisk palett genereras.", + "fruit-salad": "Material Design-schema som ger en lekfull, livfull palett med olika och varierande nyanser.", + "monochrome": "Material Design-schema som anvรคnder en enda nyans i grรฅskala med minimalt kromatiskt innehรฅll.", + "muted": "Skapar mjukare, desaturerade toner fรถr ett lugnt och diskret utseende.", + "rainbow": "Material Design-schema som skapar fรคrger รถver hela spektrumet fรถr maximal fรคrgmรฅngfald. ", + "tonal-spot": "Material Design-standardalgoritm. Skapar balanserade paletter med tonvariationer av kรคllfรคrgen.", + "vibrant": "Maximerar fรคrgmรคttnaden fรถr djรคrva, iรถgonfallande paletter med levande toner." + }, + "predefined-desc": "Vรคlj frรฅn en samling fรถrdefinierade fรคrgscheman.", + "predefined-generate-templates-label": "Generera mallar fรถr fรถrdefinierade scheman", + "predefined-title": "Fรถrdefinierade fรคrgscheman", + "sync-gsettings-description": "Matcha systemtemat med den aktiva ljusa eller mรถrka varianten.", + "sync-gsettings-label": "Synkronisera systemtema", + "templates-desc": "Tillรคmpa fรคrger pรฅ externa applikationer.", + "templates-filter-description": "Visa mallar frรฅn en specifik kategori.", + "templates-filter-label": "Filtrera efter kategori", + "templates-misc-description": "Skapa dina egna mallar.", + "templates-misc-label": "Avancerat", + "templates-misc-user-templates-description": "Aktivera endast om du vet vad du gรถr โ€” se vรฅr online-dokumentation.", + "templates-misc-user-templates-label": "Aktivera anvรคndarmallar", + "templates-none-detected": "Inga upptรคckta", + "templates-write-path": "Skriver: {filepath}", + "title": "Fรคrgschema", + "wallpaper-method-description": "Vรคlj din favoritmetod fรถr att generera paletter.", + "wallpaper-method-label": "Metod fรถr att generera palett", + "wallpaper-monitor-source-description": "Vรคlj vilken bildskรคrm som ska anvรคndas fรถr att extrahera bakgrundsbildernas fรคrger.", + "wallpaper-monitor-source-label": "Kรคlla fรถr fรคrggenerering" + }, + "connections": { + "authentication-required": "Autentisering krรคvs", + "bluetooth-auto-connect-description": "Anslut automatiskt till betrodda parkopplade enheter nรคr Bluetooth รคr aktiverat.", + "bluetooth-auto-connect-label": "Anslut parade enheter automatiskt", + "bluetooth-devices-unnamed": "Namnlรถsa enheter visas inte.", + "bluetooth-discoverable": "Denna enhet รคr upptรคckbar som {hostName} medan denna instรคllningsflik รคr รถppen.", + "bluetooth-rssi-polling-description": "Samplar periodiskt RSSI fรถr anslutna enheter via bluetoothctl. Kanske inte tillgรคngligt fรถr alla enheter; anvรคnder minimala resurser nรคr det รคr aktiverat.", + "bluetooth-rssi-polling-interval-description": "Konfigurera hur ofta signalstyrkan fรถr anslutna enheter ska uppdateras.", + "bluetooth-rssi-polling-interval-label": "Avsรถkningsintervall", + "bluetooth-rssi-polling-label": "Bluetooth-signalavsรถkning", + "disable-discoverability-description": "Dรถlj din enhet frรฅn nรคrliggande Bluetooth-enheter.", + "disable-discoverability-label": "Inaktivera enhetssynlighet", + "hide-unnamed-devices-description": "Dรถlj enheter som endast visas som Bluetooth-adresser.", + "hide-unnamed-devices-label": "Dรถlj namnlรถsa enheter", + "pin-instructions": "Ange PIN-koden som visas pรฅ din enhet.", + "title": "Anslutningar", + "wifi-header-text": "Anvรคnd Wi-Fi fรถr att se tillgรคngliga nรคtverk och ansluta till internet." + }, + "control-center": { + "cards-desc": "Anpassa vilka kontroller som ska visas i kontrollcentret och i vilken ordning.", + "desc": "Konfigurera kontrollpanelens placering och beteende.", + "open-at-mouse-description": "Nรคr du hรถgerklickar pรฅ ett tomt omrรฅde av Fรคltet, รถppna Kontrollcentret nรคra markรถren. Om inaktiverat anvรคnds istรคllet positioneringsinstรคllningen ovan.", + "open-at-mouse-label": "ร–ppna vid markรถren med hรถgerklick", + "position-description": "Vรคlj var kontrollpanelen ska visas nรคr den รถppnas.", + "shortcuts-custom-button-command-description": "Kommandot som ska utfรถras nรคr knappen klickas pรฅ.", + "shortcuts-custom-button-enable-on-state-logic-description": "Aktivera en andra ikon och 'aktivt' tillstรฅnd baserat pรฅ ett kontrollkommando.", + "shortcuts-custom-button-enable-on-state-logic-label": "Aktivera logik fรถr aktivt tillstรฅnd", + "shortcuts-custom-button-icon-description": "Vรคlj en ikon frรฅn biblioteket.", + "shortcuts-custom-button-on-clicked-label": "Kommando vid vรคnsterklick", + "shortcuts-custom-button-on-middle-clicked-description": "Kommando som ska kรถras nรคr knappen mittklickas.", + "shortcuts-custom-button-on-state-command-description": "Kommando som ska utfรถras fรถr att kontrollera om knappen ska vara i lรคge 'pรฅ'. Returnerar 0 fรถr pรฅ, icke-noll fรถr av.", + "shortcuts-custom-button-on-state-command-label": "Kommando fรถr att kontrollera om knappen รคr pรฅ", + "shortcuts-custom-button-on-state-icon-description": "Ikonen fรถr knappen nรคr den รคr 'pรฅ'.", + "shortcuts-custom-button-on-state-icon-label": "Ikon fรถr pรฅ-lรคge", + "shortcuts-custom-button-state-checks-add": "Lรคgg till tillstรฅndskontroll", + "shortcuts-custom-button-state-checks-command": "Kommando att utfรถra fรถr denna tillstรฅndskontroll", + "shortcuts-custom-button-state-checks-label": "Tillstรฅndskontroller", + "shortcuts-custom-button-state-checks-remove": "Ta bort", + "shortcuts-custom-button-tooltip-description": "Verktygstips som visas nรคr man hรฅller muspekaren รถver knappen.", + "shortcuts-custom-button-tooltip-label": "Verktygstips", + "shortcuts-title": "Genvรคgswidgetar", + "system-monitor-disk-path-description": "Vรคlj vilken diskmonteringspunkt som systemรถvervakningskortet i kontrollcentret ska รถvervaka.", + "system-monitor-disk-path-label": "Disksรถkvรคg fรถr systemรถvervakning", + "title": "Kontrollcenter" + }, + "desktop-widgets": { + "clock-enabled-description": "Visa en klockwidget pรฅ skrivbordet.", + "clock-enabled-label": "Aktivera klockwidget", + "clock-format-label": "Format", + "clock-height-description": "Klockwidgetens hรถjd i bildpunkter.", + "clock-height-label": "Hรถjd", + "clock-minimal-mode-description": "Anvรคnd en minimal klockskรคrm med tid och datum.", + "clock-minimal-mode-label": "Minimalt lรคge", + "clock-rounded-corners-description": "Anvรคnd rundade hรถrn fรถr widgetens bakgrund.", + "clock-rounded-corners-label": "Rundade hรถrn", + "clock-show-background-description": "Visa bakgrundsbehรฅllaren fรถr klockwidgeten.", + "clock-show-background-label": "Visa bakgrund", + "clock-show-date-description": "Visa aktuellt datum under tiden.", + "clock-show-date-label": "Visa datum", + "clock-show-seconds-description": "Visa sekunder i tiden.", + "clock-show-seconds-label": "Visa sekunder", + "clock-style-analog": "Analog", + "clock-style-binary": "Binรคr", + "clock-style-description": "Vรคlj klockans visningsstil.", + "clock-style-digital": "Digital", + "clock-style-label": "Klockstil", + "clock-style-minimal": "Minimal", + "clock-width-description": "Klockwidgets bredd i bildpunkter.", + "cpu-intensive-note": "Widgets markerade med ! anvรคnder mer CPU och bรถr aktiveras med eftertanke.", + "edit-mode-button-label": "Gรฅ till redigeringslรคge", + "edit-mode-controls-explanation": "Vรคnsterklicka och dra: Flytta eller รคndra storlek pรฅ widgeten.\nHรถgerklicka: ร–ppna kontextmenyns alternativ.", + "edit-mode-description": "Aktivera redigeringslรคge fรถr att flytta och omplacera skrivbordswidgets. Nรคr det รคr aktiverat visas en dragram runt widgets och de kan omplaceras.", + "edit-mode-exit-button": "Avsluta redigeringslรคge", + "edit-mode-grid-snap-label": "Rutnรคtsfรคstning", + "edit-mode-grid-snap-scale-label": "Rutnรคtsfรคstningsskala", + "edit-mode-label": "Redigeringslรคge", + "enabled-description": "Aktivera eller inaktivera skrivbordswidgets helt.", + "enabled-label": "Aktivera skrivbordswidgetar", + "general-desc": "Konfigurera widgetar som visas pรฅ skrivbordet.", + "general-title": "Skrivbordswidgetar", + "media-player-enabled-description": "Visa en mediaspelarwidget pรฅ skrivbordet.", + "media-player-enabled-label": "Aktivera mediaspelarwidget", + "media-player-rounded-corners-description": "Aktivera rundade hรถrn pรฅ widgetens kanter.", + "media-player-show-album-art-description": "Visa albumomslag och spรฅrinformation (titel och artist).", + "media-player-show-album-art-label": "Visa albumomslag och titel", + "media-player-show-background-description": "Visa bakgrundsbehรฅllaren.", + "media-player-show-buttons-description": "Visa mediekontrollknappar (spela/pausa, fรถregรฅende, nรคsta).", + "media-player-show-buttons-label": "Visa knappar", + "media-player-show-visualizer-description": "Visa ljudvisualiseringsรถverlรคgget.", + "media-player-visualizer-type-description": "Vรคlj en visualiseringstyp.", + "overview-enabled-description": "Visa skrivbordswidgetar medan kompositorns รถversikt รคr รถppen.", + "overview-enabled-label": "Visa i รถversikt", + "system-stat-layout-bottom": "Botten", + "system-stat-layout-description": "Vรคlj hur fรถrklaringen visas i fรถrhรฅllande till diagrammet.", + "system-stat-layout-label": "Layout", + "system-stat-layout-side": "Sida", + "system-stat-rounded-corners-description": "Anvรคnd rundade hรถrn fรถr widgetbakgrunden.", + "system-stat-rounded-corners-label": "Rundade Hรถrn", + "system-stat-show-background-description": "Visa bakgrundsbehรฅllaren fรถr systemstatistik-widgeten.", + "system-stat-show-background-label": "Visa bakgrund", + "system-stat-stat-type-description": "Vรคlj vilken systemstatistik som ska visas.", + "system-stat-stat-type-label": "Statistiktyp", + "title": "Skrivbordswidgetar", + "weather-enabled-description": "Visa en vรคderwidget pรฅ skrivbordet.", + "weather-enabled-label": "Aktivera vรคderwidget", + "weather-show-background-description": "Visa bakgrundsbehรฅllaren fรถr vรคderwidgeten." + }, + "display": { + "monitors-backlight-device-auto-option": "Standard", + "monitors-backlight-device-description": "Vรคlj en bakgrundsbelysningsenhet fรถr denna utgรฅng.", + "monitors-backlight-device-label": "Bakgrundsbelysningsenhet", + "monitors-brightness-step-description": "Justera stegstorleken fรถr ljusstyrkeรคndringar (rullhjul och kortkommandon).", + "monitors-brightness-step-label": "Ljusstyrkestegstorlek", + "monitors-brightness-unavailable-ddc-disabled": "Ljusstyrkekontroll otillgรคnglig. Aktivera \"Extern ljusstyrkestรถd\"fรถr att kontrollera ljusstyrkan pรฅ denna skรคrm.", + "monitors-brightness-unavailable-generic": "Ljusstyrkekontroll รคr inte tillgรคnglig fรถr denna skรคrm.", + "monitors-desc": "Justera instรคllningarna fรถr ljusstyrka fรถr varje skรคrm.", + "monitors-enforce-minimum-description": "Lรถser problemet med att bakgrundsbelysningen stรคngs av helt pรฅ vissa skรคrmar vid 0 % ljusstyrka.", + "monitors-enforce-minimum-label": "Tvinga fram minsta ljusstyrka (1 %)", + "monitors-external-brightness-description": "Aktivera DDCUtil-stรถd fรถr att kontrollera ljusstyrkan pรฅ externa skรคrmar via DDC/CI-protokollet.", + "monitors-external-brightness-label": "Externt ljusstyrkestรถd", + "monitors-global-brightness-description": "Justera ljusstyrkan fรถr alla bildskรคrmar samtidigt.", + "monitors-global-brightness-label": "Alla bildskรคrmar", + "monitors-title": "Instรคllningar per skรคrm", + "night-light-auto-schedule-description": "Baserat pรฅ solnedgรฅngs- och soluppgรฅngstiden i {location} โ€” rekommenderas. ", + "night-light-auto-schedule-label": "Automatisk schemalรคggning", + "night-light-desc": "Minska utslรคppet av blรฅtt ljus fรถr att hjรคlpa dig sova bรคttre och minska belastningen pรฅ รถgonen.", + "night-light-enable-description": "Anvรคnd ett varmt fรคrgfilter fรถr att minska utslรคppet av blรฅtt ljus.", + "night-light-enable-label": "Aktivera nattbelysning", + "night-light-force-activation-description": "Ignorerar schemalรคggningen och tillรคmpar nattfiltret omedelbart.", + "night-light-force-activation-label": "Tvinga aktivering", + "night-light-manual-schedule-description": "Stรคll in anpassade tider fรถr soluppgรฅng och solnedgรฅng.", + "night-light-manual-schedule-label": "Manuell schemalรคggning", + "night-light-manual-schedule-select-start": "Vรคlj starttid", + "night-light-manual-schedule-select-stop": "Vรคlj sluttid", + "night-light-manual-schedule-sunrise": "Soluppgรฅngstid", + "night-light-manual-schedule-sunset": "Solnedgรฅngstid", + "night-light-temperature-day": "Dag", + "night-light-temperature-day-description": "Styr temperaturen under dagtid.", + "night-light-temperature-description": "Stรคll in fรคrgvรคrmen fรถr natt och dag.", + "night-light-temperature-label": "Fรคrgtemperatur", + "night-light-temperature-night": "Natt", + "night-light-temperature-night-description": "Styr temperaturen under natten.", + "title": "Skรคrm" + }, + "dock": { + "appearance-background-opacity-description": "Justera dockans bakgrundsopacitet.", + "appearance-border-radius-description": "Justera dockans kantradie.", + "appearance-border-radius-label": "Kantradie", + "appearance-colorize-icons-description": "Tillรคmpa temafรคrger pรฅ dockappikoner (endast icke-fokuserade appar).", + "appearance-colorize-icons-label": "Fรคrglรคgg ikoner", + "appearance-dead-opacity-description": "Justera opaciteten fรถr appikoner som inte kรถrs. ", + "appearance-dead-opacity-label": "Opacitet fรถr inaktiva", + "appearance-desc": "Anpassa dockans beteende och utseende. ", + "appearance-display-auto-hide": "Dรถlj automatiskt", + "appearance-display-description": "Vรคlj hur dockan ska fungera.", + "appearance-display-exclusive": "Exklusiv", + "appearance-dock-indicator-description": "Visa en liten indikator nรคr dockan รคr dold.", + "appearance-dock-indicator-label": "Dockindikator", + "appearance-floating-distance-description": "Stรคll in avstรฅndet mellan dockan och skรคrmens kant.", + "appearance-floating-distance-label": "Dockens flytande avstรฅnd", + "appearance-group-apps-description": "Gruppera flera fรถnster frรฅn samma app till en Dock-post.", + "appearance-group-apps-label": "Gruppera samma appar", + "appearance-group-click-action-cycle": "Vรคxla fรถnster", + "appearance-group-click-action-description": "Vรคlj vad vรคnsterklick gรถr fรถr grupperade appar.", + "appearance-group-click-action-label": "Klickรฅtgรคrd fรถr grupperad app", + "appearance-group-click-action-list": "ร–ppna fรถnsterlista", + "appearance-group-context-menu-mode-description": "Vรคlj hur snabbmenyn fungerar fรถr grupperade appar.", + "appearance-group-context-menu-mode-extended": "Utรถkad", + "appearance-group-context-menu-mode-label": "Grupperat appmenylรคge", + "appearance-group-context-menu-mode-list": "Fรถnsterlista", + "appearance-group-indicator-style-description": "Vรคlj hur grupperade kรถrande indikatorer visar fokuserat fรถnstertillstรฅnd.", + "appearance-group-indicator-style-dots": "Punkter", + "appearance-group-indicator-style-label": "Grupperad indikatorstil", + "appearance-group-indicator-style-number": "Nummer", + "appearance-hide-show-speed-description": "Justera hastigheten fรถr dockens dรถljnings-/visningsanimation.", + "appearance-hide-show-speed-label": "Dรถljnings-/visningshastighet", + "appearance-icon-size-description": "Justera dockens totala storlek. ", + "appearance-icon-size-label": "Dockstorlek", + "appearance-inactive-indicators-description": "Visa indikatorer fรถr alla appar, inte bara den som รคr aktiv fรถr tillfรคllet.", + "appearance-inactive-indicators-label": "Indikatorer som kรถrs", + "appearance-indicator-color-description": "Vรคlj fรคrg fรถr den dolda dockans indikator.", + "appearance-indicator-color-label": "Indikatorfรคrg", + "appearance-indicator-opacity-description": "Justera opaciteten fรถr den dolda dockans indikator.", + "appearance-indicator-opacity-label": "Indikatorns opacitet", + "appearance-indicator-thickness-description": "Aktivera en tjockare indikator fรถr den dolda dockan (6 px istรคllet fรถr 3 px).", + "appearance-indicator-thickness-label": "Tjockare indikator", + "appearance-launcher-icon-description": "Vรคlj ikonen som anvรคnds fรถr Dock Launcher.", + "appearance-launcher-icon-label": "Launcher-ikon", + "appearance-launcher-position-description": "Vรคlj var startprogrammets ikon visas i dockan.", + "appearance-launcher-position-end": "Slut", + "appearance-launcher-position-label": "Startarens position", + "appearance-launcher-position-start": "Starta", + "appearance-launcher-use-distro-logo-description": "Anvรคnd din distributions logotyp som Dock Launcher-ikon.", + "appearance-launcher-use-distro-logo-label": "Anvรคnd distrologotyp istรคllet fรถr ikon", + "appearance-pinned-static-description": "Flytta alltid fastnรฅlade appikoner till vรคnster i statisk ordning. ", + "appearance-pinned-static-label": "Statiska fรคsta appar", + "appearance-position-description": "Vรคlj var dockan ska visas pรฅ skรคrmen.", + "appearance-position-label": "Position", + "appearance-show-launcher-icon-description": "Visa ikonen fรถr programstartaren i dockan.", + "appearance-show-launcher-icon-label": "Visa appstartare", + "appearance-sit-on-frame-description": "Justera dockan innanfรถr ramkanten istรคllet fรถr att ligga ovanpรฅ.", + "appearance-sit-on-frame-label": "Dockan sitter pรฅ ramen", + "appearance-type-attached": "Dockad", + "appearance-type-description": "Vรคlj mellan en flytande pillerform eller en statisk stapel fรคst vid kanten.", + "appearance-type-floating": "Flytande", + "appearance-type-label": "Dockstil", + "enabled-description": "Visa eller dรถlj dockningsstationen helt.", + "enabled-label": "Aktivera dockningsstation", + "monitors-desc": "Visa dockningsstationen pรฅ specifika bildskรคrmar. Standardinstรคllningen รคr alla om ingen รคr vald.", + "monitors-only-same-monitor-description": "Visa endast appar frรฅn den bildskรคrm dรคr dockningsstationen finns.", + "monitors-only-same-monitor-label": "Endast appar frรฅn samma skรคrm", + "monitors-title": "Skรคrmvisning", + "title": "Docka" + }, + "general": { + "copy-settings": "Kopiera instรคllningar", + "fonts-default-description": "Huvudtypsnitt som anvรคnds i hela grรคnssnittet.", + "fonts-default-label": "Standardteckensnitt", + "fonts-default-placeholder": "Vรคlj standardteckensnitt...", + "fonts-default-scale-description": "ร–ka eller minska storleken pรฅ standardtexten.", + "fonts-default-scale-label": "Standardteckensnittsstorlek", + "fonts-default-search-placeholder": "Sรถk teckensnitt.. .", + "fonts-desc": "Vรคlj de teckensnitt som anvรคnds i grรคnssnittet.", + "fonts-monospace-description": "Teckensnitt med fast bredd som anvรคnds fรถr visning av siffror och statistik.", + "fonts-monospace-label": "Teckensnitt med fast bredd", + "fonts-monospace-placeholder": "Vรคlj teckensnitt med fast bredd...", + "fonts-monospace-scale-description": "ร–ka eller minska storleken pรฅ text med fast bredd.", + "fonts-monospace-scale-label": "Teckensnittsstorlek med fast bredd", + "fonts-monospace-search-placeholder": "Sรถk teckensnitt med fast bredd...", + "fonts-title": "Teckensnitt", + "keybinds-conflict-description": "Tangentkombinationen รคr redan tilldelad {action}.", + "keybinds-conflict-title": "Tangentbindningskonflikt", + "keybinds-description": "Konfigurera globala navigeringsknappar fรถr paneler och startprogram.", + "keybinds-down": "Flytta nedรฅt", + "keybinds-enter": "Bekrรคfta / ร…tgรคrd", + "keybinds-escape": "Stรคng / Tillbaka", + "keybinds-left": "Flytta vรคnster", + "keybinds-modifier-description": "Genvรคgar mรฅste inkludera en modifieringstangent (Ctrl eller Alt).", + "keybinds-modifier-title": "Modifierare krรคvs", + "keybinds-remove": "Ta bort", + "keybinds-right": "Flytta hรถger", + "keybinds-title": "Navigeringstangentbindningar", + "keybinds-up": "Flytta upp", + "language-desc": "Vรคlj รถnskat sprรฅk fรถr applikationen.", + "language-select-auto-detect": "Automatiskt", + "language-select-description": "Vรคlj det sprรฅk som ska anvรคndas i programmets grรคnssnitt.", + "language-select-label": "Programsprรฅk", + "launch-setup-wizard": "Installationsguide", + "profile-desc": "Redigera dina anvรคndaruppgifter och avatar.", + "profile-picture-description": "Din profilbild som visas i hela grรคnssnittet.", + "profile-picture-label": "Profilbild", + "profile-select-avatar": "Vรคlj avatarbild", + "profile-title": "Profil", + "profile-tooltip": "Profilbild", + "reverse-scrolling-description": "Vรคnd den tolkade rullningsriktningen.", + "reverse-scrolling-label": "Omvรคnd rullning", + "screen-corners-desc": "Anpassa rundningen av skรคrmens hรถrn och visuella effekter.", + "screen-corners-radius-description": "Justera skรคrmens rundade hรถrn.", + "screen-corners-radius-label": "Skรคrmens hรถrnradie", + "screen-corners-radius-reset": "ร…terstรคll skรคrmens hรถrnradie", + "screen-corners-show-corners-description": "Visa rundade hรถrn pรฅ skรคrmens kanter. ", + "screen-corners-show-corners-label": "Visa skรคrmhรถrn", + "screen-corners-solid-black-description": "Anvรคnd helt svart istรคllet fรถr stapelns bakgrundsfรคrg.", + "screen-corners-solid-black-label": "Helt svarta hรถrn", + "screen-corners-title": "Skรคrmhรถrn", + "settings-copied": "Instรคllningar kopierade till urklipp", + "smooth-scrolling-description": "Animera listrullning fรถr en smidigare hjulupplevelse.", + "smooth-scrolling-label": "Mjuk rullning", + "tab-basics": "Grundlรคggande", + "tab-keybinds": "Tangentbindningar" + }, + "hooks": { + "color-generation-description": "Kommando som ska kรถras nรคr fรคrggenereringen รคr klar (t.ex. efter tapetbaserad tematisering).", + "color-generation-label": "Fรคrger genererade", + "color-generation-placeholder": "t.ex. notify-send \"Fรคrger\" \"Genererade\"", + "info-command-info-description": "โ€ข Kommandon exekveras via shell (sh -lc)
โ€ข Kommandon kรถrs i bakgrunden (fristรฅende)
โ€ข Testknappar exekveras med aktuella vรคrden", + "info-parameters-description": "โ€ข Wallpaper hook: $1 = bakgrundsbildens sรถkvรคg, $2 = skรคrmnamn, $3 = tema (mรถrkt/ljust)
โ€ข Theme toggle hook: $1 = true/false (mรถrkt lรคgestatus)
โ€ข Screen lock/unlock hooks: $1 = lock/unlock (skรคrmlรฅsstatus)
โ€ข Performance mode hooks: Inga parametrar
โ€ข Session hook: $1 = action (shutdown/reboot)", + "info-parameters-label": "Tillgรคngliga parametrar", + "noctalia-started-description": "Kommando som ska utfรถras nรคr Noctalia har laddats klart.", + "noctalia-started-label": "Noctalia startat", + "noctalia-started-placeholder": "t.ex. notify-send 'Noctalia laddat'", + "performance-mode-disabled-description": "Kommando som ska utfรถras nรคr Noctalia prestandalรคget รคr inaktiverat.", + "performance-mode-disabled-label": "Prestandalรคget inaktiverat", + "performance-mode-disabled-placeholder": "t.ex. notify-send \"Prestanda\"\"Lรคget inaktiverat\"", + "performance-mode-enabled-description": "Kommando som ska utfรถras nรคr Noctalia prestandalรคget รคr aktiverat.", + "performance-mode-enabled-label": "Prestandalรคget aktiverat", + "performance-mode-enabled-placeholder": "t.ex. notify-send \"Prestanda\"\"Lรคget aktiverat\"", + "screen-lock-description": "Kommando som ska utfรถras nรคr skรคrmen รคr lรฅst.", + "screen-lock-label": "Skรคrm lรฅst", + "screen-lock-placeholder": "t.ex. notify-send \"Skรคrm\"\"Lรฅst\"", + "screen-unlock-description": "Kommando som ska utfรถras nรคr lรฅsskรคrmen รคr upplรฅst.", + "screen-unlock-label": "Skรคrm upplรฅst", + "screen-unlock-placeholder": "t.ex. notify-send \"Skรคrm\"\"Upplรฅst\"", + "session-description": "Kommando som ska utfรถras fรถre avstรคngning eller omstart. Tar emot รฅtgรคrdstyp som $1 (avstรคngning/omstart). ", + "session-label": "Session slut", + "session-placeholder": "t.ex. notify-send \"Session\"\"$1\"", + "system-hooks-desc": "Konfigurera kommandon som ska utfรถras nรคr systemhรคndelser intrรคffar.", + "system-hooks-enable-description": "Aktivera eller inaktivera alla hook-kommandon.", + "system-hooks-enable-label": "Aktivera hooks", + "system-hooks-title": "Systemhooks", + "theme-changed-description": "Kommando som ska utfรถras nรคr temat vรคxlar mellan mรถrkt och ljust lรคge.", + "theme-changed-label": "Tema รคndrat", + "theme-changed-placeholder": "t.ex. notify-send \"Tema\"\"Vรคxlat\"", + "title": "Hooks", + "wallpaper-changed-description": "Kommando som ska utfรถras nรคr bakgrundsbilden รคndras.", + "wallpaper-changed-label": "Bakgrundsbild รคndrad", + "wallpaper-changed-placeholder": "t.ex. notify-send \"Bakgrundsbild\"\"ร„ndrad\"" + }, + "idle": { + "custom-add": "Lรคgg till kommando", + "custom-description": "Kรถr ett skal-kommando efter en period av inaktivitet.", + "custom-entry-command": "Kommando", + "custom-entry-delete": "Ta bort", + "custom-entry-edit": "Redigera Anpassat Kommando", + "custom-entry-name": "Namn", + "custom-entry-name-placeholder": "t.ex. Slรคck lamporna", + "custom-entry-new": "Nytt Anpassat Kommando", + "custom-entry-timeout": "Inaktivitetstid", + "custom-entry-timeout-format": "{count} sekund", + "custom-entry-timeout-format-plural": "{count} sekunder", + "custom-entry-unnamed": "Namnlรถst kommando", + "custom-label": "Anpassade inaktivitetskommandon", + "enable-description": "Stรคng automatiskt av skรคrmen, lรฅs eller fรถrsรคtt i vilolรคge efter en period av inaktivitet.", + "enable-label": "Aktivera inaktivitetshantering", + "fade-duration-description": "Sekunder fรถr animationen som tonar till svart innan varje รฅtgรคrd utlรถses. Alla musrรถrelser avbryter nedtoningen.", + "fade-duration-label": "Toningstid", + "lock-description": "Sekunder av inaktivitet innan lรฅsskรคrmen aktiveras.", + "lock-label": "Lรฅs skรคrm", + "resume-command-label": "ร…teruppta kommando", + "screen-off-description": "Sekunder av inaktivitet innan skรคrmarna stรคngs av.", + "screen-off-label": "Stรคng av skรคrmen", + "status-description": "Inaktiv tid som rapporterats av compositorn.", + "status-label": "Inaktiv tid", + "suspend-description": "Sekunder av inaktivitet innan systemet fรถrsรคtts i vilolรคge.", + "tab-behavior": "Beteende", + "tab-custom": "Anpassad", + "timeouts-description": "Stรคll in pรฅ 0 fรถr att inaktivera ett steg. Tidsgrรคnser pausas medan 'Hรฅll vaken' รคr aktivt.", + "timeouts-label": "Tidsgrรคnser", + "title": "Inaktivitet", + "unavailable": "Inbyggd inaktivitetsรถvervakning รคr inte tillgรคnglig pรฅ denna Compositor." + }, + "indicator": { + "default-value": "Standard: {vรคrde}", + "system-default": "Systemstandard" + }, + "launcher": { + "clipboard-desc": "ร–ppna och hantera din urklippshistorik frรฅn startprogrammet.", + "execute-desc": "Konfigurera hur program startas.", + "execute-title": "Exekvering", + "settings-annotation-tool-description": "Kommando som kรถrs nรคr du klickar pรฅ anteckningsknappen i klippbordshistoriken. Bilden kommer att skickas till detta kommando.", + "settings-annotation-tool-label": "Anteckningsverktyg", + "settings-annotation-tool-placeholder": "t.ex. 'gradia', 'satty -f -'", + "settings-auto-paste-description": "Klistra in det valda klippbordsinnehรฅllet automatiskt. Krรคver wtype. ", + "settings-auto-paste-label": "Automatisk klistra in", + "settings-clip-chips-description": "Visa en flikfรคlt fรถr att filtrera klippbordshistoriken efter typ (Bilder, Lรคnkar, Filer, Kod, etc.).", + "settings-clip-chips-label": "Aktivera kategorichips", + "settings-clip-preview-description": "Visa en fรถrhandsgranskning av klippbordets innehรฅll nรคr du anvรคnder kommandot >clip.", + "settings-clip-preview-label": "Aktivera fรถrhandsgranskning av klipp", + "settings-clip-smart-icons-description": "Visa specifika ikoner fรถr lรคnkar, filer, fรคrger och kod istรคllet fรถr en generisk urklippsikon.", + "settings-clip-smart-icons-label": "Aktivera smarta ikoner", + "settings-clip-wrap-text-description": "Vik text i klippbordslistan istรคllet fรถr att avkorta den.", + "settings-clip-wrap-text-label": "Vik in text i urklipp", + "settings-clipboard-history-description": "ร–ppna tidigare kopierade objekt frรฅn startprogrammet.", + "settings-clipboard-history-label": "Aktivera klippbordshistorik", + "settings-clipboard-watch-image-description": "Fullstรคndig kommandostrรคng som skickas till wl-paste fรถr bildรคndringar (krรคver omstart).", + "settings-clipboard-watch-image-label": "Bildรถvervakningskommando", + "settings-clipboard-watch-text-description": "Fullstรคndig kommandostrรคng skickad till wl-paste fรถr textรคndringar (krรคver omstart).", + "settings-clipboard-watch-text-label": "Textbevakningskommando", + "settings-custom-launch-prefix-description": "Prefixkommandon med en anpassad startfunktion (t.ex. 'runapp' fรถr systemd-integration).", + "settings-custom-launch-prefix-enabled-description": "Anvรคnd ett anpassat prefix fรถr att starta applikationer istรคllet fรถr standardmetoden.", + "settings-custom-launch-prefix-enabled-label": "Aktivera anpassat startprefix", + "settings-custom-launch-prefix-label": "Anpassat startprefix", + "settings-density-description": "Justera storleken pรฅ applikationsikoner och Launcherns densitet.", + "settings-density-label": "Densitet", + "settings-desc": "Anpassa startprogrammets beteende och utseende.", + "settings-enable-session-search-description": "Visa sessionsรฅtgรคrder (lรฅs, stรคng av, starta om, etc.) vid sรถkning i Launcher.", + "settings-enable-session-search-label": "Aktivera sessionssรถkning", + "settings-enable-settings-search-description": "Visa instรคllningsresultat vid sรถkning i startprogrammet.", + "settings-enable-settings-search-label": "Aktivera instรคllningssรถkning", + "settings-enable-windows-search-description": "Sรถk och fokusera aktiva fรถnster.", + "settings-enable-windows-search-label": "Aktivera fรถnstersรถkning", + "settings-grid-view-description": "Visa objekt i ett rutnรคt istรคllet fรถr en lista.", + "settings-icon-mode-description": "Anvรคnd inbyggda systemikoner istรคllet fรถr Tabler-ikoner.", + "settings-icon-mode-label": "Anvรคnd inbyggda ikoner", + "settings-ignore-mouse-input-description": "Inaktivera musinteraktion och rullhjul i startprogrammet.", + "settings-ignore-mouse-input-label": "Ignorera musinmatning", + "settings-overlay-layer-description": "Visa startprogrammet pรฅ รถverlagringslagret, ovanfรถr helskรคrmsfรถnster. Nรคr aktiverat fรคsts startprogrammet inte vid fรคltet.", + "settings-overlay-layer-label": "Visa ovanfรถr helskรคrm", + "settings-position-description": "Vรคlj var startprogrammet ska visas.", + "settings-show-categories-description": "Visa kategoriflikar fรถr att filtrera applikationer.", + "settings-show-categories-label": "Visa kategorier", + "settings-show-icon-background-description": "Visa en rundad rektangulรคr bakgrund bakom ikonerna.", + "settings-show-icon-background-label": "Visa ikonbakgrund", + "settings-sort-by-usage-description": "Nรคr denna funktion รคr aktiverad visas de program som anvรคnds oftast fรถrst i listan.", + "settings-sort-by-usage-label": "Sortera efter mest anvรคnda", + "settings-terminal-command-description": "Kommando fรถr att starta en terminal. T.ex. 'kitty -e' eller 'gnome-terminal --'.", + "settings-terminal-command-label": "Terminalkommando", + "settings-use-app2unit-description": "Anvรคnder en alternativ startmetod fรถr att bรคttre hantera app-processer och fรถrhindra problem.", + "settings-use-app2unit-label": "Anvรคnd App2Unit fรถr att starta applikationer", + "settings-view-mode-description": "Vรคlj layout fรถr startprogrammets poster.", + "settings-view-mode-label": "Visningslรคge", + "title": "Startprogram" + }, + "location": { + "auto-locate-description": "Upptรคck automatiskt din plats periodiskt med hjรคlp av din IP-adress.", + "auto-locate-label": "Automatisk Lokalisering", + "calendar-cards-desc": "Organisera och aktivera/inaktivera kort i kalenderpanelen.", + "calendar-cards-title": "Kalenderkort", + "calendar-header-label": "Kalenderrubrik", + "calendar-month-label": "Kalendermรฅnad", + "date-time-12hour-format-description": "Visa tiden i 12-timmarsformat pรฅ lรฅsskรคrmen och i kalendern. Bar-klockan har sina egna instรคllningar.", + "date-time-12hour-format-label": "Anvรคnd 12-timmarsformat", + "date-time-desc": "Anpassa hur datum och tid visas.", + "date-time-first-day-of-week-automatic": "Automatisk (anvรคnd systemets lokala instรคllningar)", + "date-time-first-day-of-week-description": "Vรคlj vilken dag som รคr veckans fรถrsta dag i kalendern.", + "date-time-first-day-of-week-label": "Veckans fรถrsta dag", + "date-time-show-events-description": "Visa hรคndelser i kalenderpanelen.", + "date-time-show-events-label": "Visa kalenderhรคndelser", + "date-time-title": "Datum och tid", + "date-time-use-analog-description": "Visa en analog klocka i kalenderfรถnstret.", + "date-time-use-analog-label": "Anvรคnd analog klocka", + "date-time-week-numbers-description": "Visa veckans nummer (t.ex. vecka 38) i kalendern.", + "date-time-week-numbers-label": "Visa veckans nummer", + "geolocate-now-button": "Lokalisera nu", + "location-desc": "Fรฅ exakt vรคderinformation och schemalรคggning av nattbelysning genom att ange din plats.", + "location-search-description": "t.ex. Stockholm, SE", + "location-search-label": "Sรถk efter en plats", + "location-search-placeholder": "Ange platsens namn", + "location-title": "Din plats", + "weather-desc": "Vรคlj รถnskad temperaturenhet.", + "weather-enabled-description": "Visa vรคderinformation i hela grรคnssnittet och hรคmta vรคderdata. Nรคr funktionen รคr inaktiverad dรถljs alla vรคderelement och inga nรคtverksfรถrfrรฅgningar gรถrs.", + "weather-enabled-label": "Aktivera vรคder", + "weather-fahrenheit-description": "Visa temperaturen i Fahrenheit istรคllet fรถr Celsius.", + "weather-fahrenheit-label": "Visa temperaturen i Fahrenheit (ยฐF)", + "weather-hide-city-description": "Dรถlj stadsnamnet frรฅn vรคdervisningarna i hela grรคnssnittet.", + "weather-hide-city-label": "Dรถlj stadsnamn", + "weather-hide-timezone-description": "Dรถlj tidszonfรถrkortningen frรฅn vรคdervisningarna i hela grรคnssnittet.", + "weather-hide-timezone-label": "Dรถlj tidszon", + "weather-show-effects-description": "Visa ytterligare visuella effekter (som regn, snรถ eller blixtar) pรฅ vรคderkortet.", + "weather-show-effects-label": "Visa vรคdereffekter", + "weather-show-in-calendar-description": "Visa den dagliga vรคderprognosen direkt i din kalendervy.", + "weather-show-in-calendar-label": "Visa vรคder i kalendern", + "weather-talia-mascot-always-description": "Anvรคnd alltid Talia emojis istรคllet fรถr tabler icons.", + "weather-talia-mascot-always-label": "Visa alltid Talia vรคder-emojis" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "Nรคr fprintd (fingeravtrycksautentisering) รคr aktivt, lรฅter det hรคr alternativet dig fortfarande logga in med ditt lรถsenord istรคllet fรถr ett fingeravtryck", + "allow-password-with-fprintd-label": "Tillรฅt lรถsenordsinloggning med fprintd", + "auto-start-auth-description": "Startar automatiskt fingeravtrycksautentisering utan att du behรถver trycka pรฅ en tangent eller knapp.", + "auto-start-auth-label": "Automatisk start av autentisering", + "clock-format-description": "Anpassa klockformatet med datum-/tidssyntax-tokens.", + "clock-format-label": "Klockformat", + "clock-style-analog": "Analog", + "clock-style-custom": "Anpassad", + "clock-style-description": "Vรคlj den visuella stilen fรถr klockan pรฅ lรฅsskรคrmen.", + "clock-style-digital": "Digital", + "clock-style-label": "Klockstil", + "compact-lockscreen-description": "Visa endast inloggningsfรคltet och systemkontrollerna, dรถlj vรคder- och mediewidgets.", + "compact-lockscreen-label": "Kompakt lรฅsskรคrm", + "enable-lockscreen-media-controls-description": "Visa interaktiva mediekontroller pรฅ lรฅsskรคrmen.", + "enable-lockscreen-media-controls-label": "Mediekontroller pรฅ Lรฅsskรคrmen", + "lock-on-suspend-description": "Lรฅs skรคrmen automatiskt nรคr systemet pausas.", + "lock-on-suspend-label": "Lรฅs vid paus", + "lock-screen-animations-description": "Aktivera eller inaktivera lรฅsskรคrmsanimeringar.", + "lock-screen-animations-label": "Lรฅsskรคrmsanimationer", + "lock-screen-blur-strength-description": "Tillรคmpar en oskรคrpaeffekt pรฅ lรฅsskรคrmens bakgrundsbild.", + "lock-screen-blur-strength-label": "Oskรคrpestyrka fรถr lรฅsskรคrmen", + "lock-screen-tint-strength-description": "Tillรคmpar en fรคrgtoningsรถverlagring pรฅ lรฅsskรคrmens bakgrundsbild.", + "lock-screen-tint-strength-label": "Lรฅsskรคrmens toningsstyrka", + "monitors-desc": "Visa lรฅsskรคrm pรฅ specifika bildskรคrmar. Standard รคr alla om inga vรคljs.", + "password-chars-description": "Sรถta ikoner som anvรคnds fรถr att dรถlja ditt lรถsenord.", + "password-chars-label": "Slumpmรคssiga lรถsenordsikoner", + "show-hibernate-description": "Visa alternativet 'vilolรคge' i strรถmkontrollerna.", + "show-hibernate-label": "Visa vilolรคge", + "show-session-buttons-description": "Tillรฅt รฅtkomst till strรถminstรคllningar frรฅn lรฅsskรคrmen. ", + "show-session-buttons-label": "Strรถmkontroller", + "title": "Lรฅsskรคrm" + }, + "network": { + "bluetooth-description": "Aktivera Bluetooth-hantering.", + "bluetooth-rssi-polling-description": "Sampla RSSI regelbundet fรถr anslutna enheter via bluetoothctl. Kan inte vara tillgรคngligt fรถr alla enheter; anvรคnder minimala resurser nรคr det รคr aktiverat.", + "bluetooth-rssi-polling-label": "Bluetooth-signalpollning", + "desc": "Hantera Wi-Fi- och Bluetooth-anslutningar.", + "wifi-description": "Hantera trรฅdlรถsa nรคtverk (krรคver NetworkManager)." + }, + "notifications": { + "duration-critical-urgency-description": "Hur lรคnge meddelanden med kritisk prioritet fรถrblir synliga.", + "duration-critical-urgency-label": "Kritisk brรฅdskande", + "duration-desc": "Konfigurera hur lรคnge meddelanden fรถrblir synliga baserat pรฅ deras brรฅdskande nivรฅ.", + "duration-low-urgency-description": "Hur lรคnge meddelanden med lรฅg prioritet fรถrblir synliga.", + "duration-low-urgency-label": "Lรฅg brรฅdskande", + "duration-normal-urgency-description": "Hur lรคnge meddelanden med normal prioritet fรถrblir synliga.", + "duration-normal-urgency-label": "Normal brรฅdskande", + "duration-respect-expire-description": "Anvรคnd den tidsgrรคns som anges i meddelandet.", + "duration-respect-expire-label": "Respektera tidsgrรคnsen fรถr utgรฅng", + "duration-title": "Meddelandets varaktighet", + "history-clear-dismiss-description": "Rensa avisering frรฅn historiken nรคr den avfรคrdas.", + "history-clear-dismiss-label": "Rensa vid avfรคrdande", + "history-critical-urgency-description": "Spara meddelanden med kritisk prioritet i historiken.", + "history-critical-urgency-label": "Spara kritiska meddelanden i historiken", + "history-desc": "Kontrollera vilka meddelanden som sparas i historiken baserat pรฅ deras brรฅdskande nivรฅ.", + "history-low-urgency-description": "Spara meddelanden med lรฅg prioritet i historiken.", + "history-low-urgency-label": "Spara lรฅg prioritet i historiken", + "history-normal-urgency-description": "Spara meddelanden med normal prioritet i historiken.", + "history-normal-urgency-label": "Spara normal prioritet i historiken", + "monitors-desc": "Visa meddelanden pรฅ specifika skรคrmar. Standardinstรคllningen รคr alla om ingen vรคljs.", + "rules-action-block": "Block", + "rules-action-block-desc": "Hoppar รถver helt.", + "rules-action-hide": "Dรถlj", + "rules-action-hide-desc": "Ingen popup, inget ljud, lรคggs till i historiken.", + "rules-action-label": "ร…tgรคrd", + "rules-action-mute": "Tysta", + "rules-action-mute-desc": "Inget ljud, visar fortfarande popup och i historiken.", + "rules-add": "Lรคgg till regel", + "rules-delete": "Ta bort regel", + "rules-description": "Matcha appnamn eller innehรฅll. Regler kontrolleras i ordning, och den fรถrsta matchningen tillรคmpas.", + "rules-edit": "Redigera regel", + "rules-label": "Filterregler", + "rules-pattern-label": "Mรถnster", + "rules-pattern-placeholder": "firefox, discord, eller /regex/", + "rules-tab": "Regler", + "settings-always-on-top-description": "Visa meddelanden ovanfรถr helskรคrmsfรถnster och andra lager.", + "settings-background-opacity-description": "Justera opaciteten fรถr meddelandenas bakgrund.", + "settings-density-description": "Vรคlj densitet fรถr aviseringskortet.", + "settings-density-label": "Densitet", + "settings-desc": "Konfigurera meddelandenas utseende och beteende.", + "settings-do-not-disturb-description": "Inaktivera alla popup-meddelanden nรคr funktionen รคr aktiverad.", + "settings-enabled-description": "Aktivera eller inaktivera meddelandedemonen, krรคver omstart av Noctalia-skalet.", + "settings-enabled-label": "Aktivera meddelanden", + "settings-location-description": "Var meddelanden visas pรฅ skรคrmen.", + "settings-markdown-description": "Rendera meddelandeinnehรฅll med Markdown-formatering.", + "settings-markdown-label": "Aktivera Markdown", + "sounds-desc": "Konfigurera ljud effekter och volym fรถr meddelanden.", + "sounds-enabled-description": "Aktivera ljudeffekter fรถr inkommande meddelanden.", + "sounds-enabled-label": "Aktivera meddelandeljud", + "sounds-excluded-apps-description": "Hoppa รถver att spela upp det konfigurerade meddelandeljudet fรถr specifika applikationer som har egna inbyggda ljud.", + "sounds-excluded-apps-label": "Exkluderade applikationer", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "Sรถkvรคg till ljudfilen som spelas upp fรถr meddelanden med kritisk prioritet.", + "sounds-files-critical-label": "Ljud fรถr kritisk brรฅdskande hรคndelse", + "sounds-files-critical-select-title": "Vรคlj ljudfil fรถr kritisk brรฅdskande hรคndelse", + "sounds-files-desc": "Konfigurera anpassade ljudfiler fรถr olika nivรฅer av brรฅdskande meddelanden.", + "sounds-files-low-description": "Sรถkvรคg till ljudfilen som spelas upp fรถr meddelanden med lรฅg prioritet.", + "sounds-files-low-label": "Ljud fรถr lรฅg prioritet", + "sounds-files-low-select-title": "Vรคlj ljudfil fรถr lรฅg prioritet", + "sounds-files-normal-description": "Sรถkvรคg till ljudfilen som spelas upp fรถr meddelanden med normal prioritet.", + "sounds-files-normal-label": "Ljud fรถr normal prioritet", + "sounds-files-normal-select-title": "Vรคlj ljudfil fรถr normal prioritet", + "sounds-files-placeholder": "Ange sรถkvรคg till ljudfil", + "sounds-files-select-file": "Vรคlj ljudfil", + "sounds-files-title": "Ljudfiler", + "sounds-files-unified-description": "Sรถkvรคg till ljudfilen som spelas upp fรถr aviseringar.", + "sounds-files-unified-label": "Meddelandeljud", + "sounds-files-unified-select-title": "Vรคlj meddelandeljudfil", + "sounds-separate-description": "Anvรคnd olika ljudfiler fรถr meddelanden med lรฅg, normal och kritisk prioritet.", + "sounds-separate-label": "Anvรคnd olika ljud per prioritet", + "sounds-title": "Ljudinstรคllningar", + "sounds-unavailable-description": "Installera Qt6 Multimedia fรถr att aktivera meddelandeljud.", + "sounds-unavailable-label": "Meddelandeljud otillgรคngliga", + "sounds-volume-description": "Justera volymnivรฅn fรถr meddelandeljud.", + "sounds-volume-label": "Ljudvolym", + "toast-battery-description": "Visa en varning nรคr batterinivรฅn sjunker under denna procentandel.", + "toast-battery-label": "Batterivarning", + "toast-desc": "Konfigurera utseende och beteende fรถr toast.", + "toast-keyboard-description": "Visa en toast nรคr tangentbordslayouten รคndras.", + "toast-keyboard-label": "Tangentbordslayout", + "toast-media-description": "Visa en toast nรคr mediaspelningsstatus รคndras.", + "toast-media-label": "Media" + }, + "osd": { + "always-on-top-description": "Visa OSD ovanfรถr helskรคrmsfรถnster och andra lager.", + "always-on-top-label": "Alltid รถverst", + "background-opacity-description": "Styr transparensen fรถr OSD-bakgrunden.", + "background-opacity-label": "Bakgrundens opacitet", + "description": "Konfigurera indikatorer pรฅ skรคrmen, sรฅsom volym- och ljusstyrkeรถverlรคgg.", + "duration-auto-hide-description": "Justera tiden innan OSD fรถrsvinner.", + "duration-auto-hide-label": "Dรถlj automatiskt efter", + "duration-desc": "Hur lรคnge OSD fรถrblir synligt innan det dรถljs automatiskt. ", + "duration-title": "Automatisk dรถljningstid", + "enabled-description": "Visa volym- och ljusstyrkeรคndringar i realtid.", + "enabled-label": "Aktivera skรคrmdisplay", + "events-desc": "Vรคlj vilka hรคndelser som ska utlรถsa skรคrmdisplayen.", + "general-desc": "Konfigurera OSD:s synlighet och beteende.", + "location-description": "Var skรคrmdisplayen visas.", + "monitors-desc": "Visa OSD pรฅ specifika skรคrmar. Standardinstรคllningen รคr alla om ingen รคr vald.", + "title": "Skรคrmdisplay", + "types-brightness-description": "Visa OSD nรคr skรคrmens ljusstyrka รคndras.", + "types-brightness-label": "Ljusstyrka", + "types-custom-text-description": "Visa OSD fรถr anpassade textmeddelanden frรฅn IPC.", + "types-custom-text-label": "Anpassad text", + "types-desc": "Vรคlj de hรคndelser som aktiverar OSD.", + "types-input-volume-description": "Visa OSD nรคr mikrofonvolymen รคndras. ", + "types-input-volume-label": "Ingรฅngsvolym", + "types-lockkey-description": "Visa OSD nรคr Caps Lock, Num Lock eller Scroll Lock aktiveras.", + "types-lockkey-label": "Lรฅsta tangenter", + "types-media-description": "Visa OSD nรคr mediaspelningsstatus รคndras (spela, pausa, hoppa รถver).", + "types-media-label": "Medieuppspelning", + "types-title": "OSD-utlรถsande hรคndelser", + "types-volume-description": "Visa OSD nรคr ljudutgรฅngsvolymen รคndras.", + "types-volume-label": "Utgรฅngsvolym" + }, + "plugins": { + "auto-update": "Uppdatera insticksmoduler automatiskt", + "auto-update-description": "Uppdatera automatiskt alla plugins nรคr skalet startar.", + "available-description": "Blรคddra bland och installera insticksmoduler frรฅn konfigurerade kรคllor.", + "available-label": "Tillgรคngliga insticksmoduler", + "available-no-plugins-description": "Kontrollera dina instickskรคllor eller uppdatera listan.", + "available-no-plugins-label": "Inga insticksmoduler tillgรคngliga", + "check-for-updates": "Sรถk efter uppdateringar", + "checking-for-updates": "Sรถker efter uppdateringar...", + "collision-already-installed": "Denna insticksmodul รคr redan installerad", + "collision-custom-version-exists": "En anpassad version frรฅn \"{source}\"รคr redan installerad", + "collision-official-version-exists": "Den officiella versionen av denna insticksmodul รคr redan installerad", + "development-disable": "Inaktivera utvecklingslรคge fรถr denna plugin", + "development-enable": "Aktivera utvecklingslรคge fรถr denna plugin.", + "filter-downloaded": "Hรคmtade", + "filter-not-downloaded": "Inte hรคmtade", + "filter-tags-description": "Filtrera insticksmoduler efter kategori eller hรคmtningsstatus.", + "filter-tags-label": "Taggar", + "hot-reload-description": "Ladda om insticksmoduler automatiskt nรคr deras filer รคndras. Anvรคndbart fรถr insticksutveckling.", + "hot-reload-label": "Hot reload (dev mode)", + "hot-reloaded": "Startade om insticksmodul: {name}", + "install-error": "Installation misslyckades: {error}", + "install-incompatible": "{plugin} krรคver Noctalia v{version} eller hรถgre", + "install-success": "{plugin} har installerats", + "installed-description": "Hantera och konfigurera alla lokalt installerade insticksmoduler.", + "installed-label": "Installerade insticksmoduler", + "installed-no-plugins-description": "Installera insticksmoduler frรฅn avsnittet \"Tillgรคngliga\".", + "installed-no-plugins-label": "Inga insticksmoduler installerade", + "installing": "Installerar {plugin}...", + "notify-updates": "Meddelanden om plugin-uppdateringar", + "notify-updates-description": "Visa en notifikationspopup nรคr nya plugin-uppdateringar รคr tillgรคngliga.", + "open-plugin-page": "ร–ppna insticksmodulsida", + "open-plugins-tab": "ร–ppna insticksmodulinstรคllningar", + "plugin-settings-title": "Instรคllningar fรถr {plugin}", + "refresh-refreshing": "Uppdaterar insticksmodullista...", + "refresh-tooltip": "Uppdatera tillgรคngliga insticksmoduler", + "settings-error-not-loaded": "Insticksmodul inte inlรคst", + "settings-saved": "Insticksmodulsinstรคllningar sparade", + "settings-tooltip": "Insticksmodulinstรคllningar", + "source-custom": "Anpassad kรคlla", + "sources-add-custom": "Lรคgg till anpassat arkiv", + "sources-add-dialog-description": "Lรคgg till ett GitHub-arkiv som insticksmodulkรคlla.", + "sources-add-dialog-error": "Det gick inte att lรคgga till insticksmodulkรคlla", + "sources-add-dialog-name": "Arkivnamn", + "sources-add-dialog-name-placeholder": "Mina anpassade insticksmoduler", + "sources-add-dialog-success": "Insticksmodulkรคlla har lagts till", + "sources-add-dialog-title": "Lรคgg till insticksmodulkรคlla", + "sources-add-dialog-url": "Arkivets URL", + "sources-description": "Hantera insticksmodularkiv.", + "sources-placeholder": "Mitt coola arkiv", + "sources-remove-tooltip": "Ta bort insticksmodulkรคlla", + "title": "Insticksmoduler", + "translations-reloaded": "ร–versรคttningar omladdade: {name}", + "uninstall-dialog-description": "ร„r du sรคker pรฅ att du vill avinstallera {plugin}? Detta kommer att ta bort all insticksmoduldata.", + "uninstall-dialog-title": "Avinstallera insticksmodul", + "uninstall-error": "Avinstallationen misslyckades: {error}", + "uninstall-success": "{plugin} har avinstallerats", + "uninstalling": "Avinstallerar {plugin}...", + "update-all": "Uppdatera alla ({count})", + "update-all-success": "Alla insticksmoduler har uppdaterats", + "update-available": "Ny insticksuppdatering tillgรคnglig", + "update-available-plural": "Insticksuppdateringar tillgรคngliga ({count})", + "update-error": "Misslyckades med att uppdatera insticksmodul: {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (krรคver Noctalia v{required})", + "update-success": "Uppdaterade {plugin} till v{version}", + "update-version": "v{current} โ†’ v{new}", + "updating": "Uppdaterar..." + }, + "region": { + "title": "Region" + }, + "session-menu": { + "countdown-duration-description": "Stรคll in hur lรคnge nedrรคkningstimern ska pรฅgรฅ innan strรถmรฅtgรคrder utfรถrs.", + "countdown-duration-label": "Nedrรคkningstid", + "enable-countdown-description": "Visa en nedrรคkningstimer innan strรถmรฅtgรคrder utfรถrs. ", + "enable-countdown-label": "Aktivera nedrรคkning", + "entries-desc": "Anpassa vilka strรถmรฅtgรคrder som visas i sessionsmenyn och i vilken ordning.", + "entries-title": "Strรถmรฅtgรคrder", + "entry-settings-command-description": "Anpassat kommando som ska utfรถras fรถr denna รฅtgรคrd. Lรคmna tomt fรถr att anvรคnda standardkommandot i systemet.", + "entry-settings-command-placeholder": "t.ex. systemctl poweroff", + "entry-settings-default-command-lock": "Intern lรฅsskรคrm (inget kommando)", + "entry-settings-default-command-logout": "Intern utloggning (inget kommando)", + "entry-settings-default-info-description": "Om inget anpassat kommando anges kommer standardkommandot fรถr systemet att anvรคndas.", + "entry-settings-default-info-label": "Standardkommando", + "entry-settings-keybind-description": "Tilldela en tangentkombination fรถr att utlรถsa denna รฅtgรคrd nรคr sessionsmenyn รคr รถppen.", + "entry-settings-keybind-placeholder": "Tryck pรฅ Spela in fรถr att tilldela en tangent...", + "entry-settings-keybind-recording": "Inspelning... Tryck pรฅ valfri tangentkombination", + "entry-settings-title": "Konfigurera {entry}", + "entry-settings-tooltip": "Konfigurera kommando", + "general-desc": "Konfigurera sessionens menypanels beteende och utseende.", + "large-buttons-layout-description": "Vรคlj hur sessionens menyknappar ska visas.", + "large-buttons-layout-label": "Stora knappar", + "large-buttons-style-description": "Visa sessionens meny med stora knappar i ett rutnรคt.", + "large-buttons-style-label": "Stor knappstil", + "position-description": "Vรคlj var sessionsmenypanelen ska visas nรคr den รถppnas.", + "show-header-description": "Visa titeln och stรคngningsknappen hรถgst upp i sessionsmenyn.", + "show-header-label": "Visa rubrik", + "show-keybinds-description": "Visa kortkommando-tips pรฅ sessionsalternativ.", + "show-keybinds-label": "Visa kortkommandon" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Hรฅll skrivbordswidgets synliga medan Noctalia prestandalรคget รคr aktiverat.", + "noctalia-performance-disable-desktop-widgets-label": "Aktivera skrivbordswidgetar i prestandalรคge", + "noctalia-performance-disable-wallpaper-description": "Behรฅll skrivbords-, รถversikts- och lรฅsskรคrmsbakgrundsbilder synliga nรคr Noctalia prestandalรคget รคr aktiverat.", + "noctalia-performance-disable-wallpaper-label": "Aktivera bakgrundsbildsรฅtergivning i prestandalรคge", + "title": "System" + }, + "system-monitor": { + "critical-color-label": "Kritisk fรคrg", + "custom-highlight-colors-title-label": "Anpassade markeringsfรคrger", + "disk-available-label": "Tillgรคngligt diskutrymme", + "disk-section-label": "Diskanvรคndning", + "enable-dgpu-monitoring-description": "Varning: Detta aktiverar din diskreta GPU (NVIDIA/AMD), vilket kan pรฅverka batteritiden avsevรคrt pรฅ bรคrbara datorer med hybridgrafik.", + "enable-dgpu-monitoring-label": "Aktivera รถvervakning av diskret GPU", + "external-monitor-description": "Ange kommandot eller applikationsvรคgen som ska startas nรคr den externa systemรถvervakningsapplikationen aktiveras.", + "external-monitor-label": "Kommando fรถr extern systemmonitor", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor ", + "general-desc": "Konfigurera systemรถvervakningens beteende och utseende.", + "gpu-section-label": "GPU-temperatur", + "highlight-colors-section-label": "Markeringsfรคrger", + "polling-interval-label": "Pollningsintervall", + "polling-section-description": "Konfigurera hur ofta varje systemmetrik uppdateras.", + "polling-section-label": "Pollningsintervall", + "threshold-critical": "Kritisk", + "threshold-warning": "Varning", + "thresholds-section-description": "Justera varnings-/kritiska trรถsklar fรถr varje systemmetrik.", + "use-custom-highlight-colors-description": "Nรคr denna funktion รคr inaktiverad anvรคnds standardfรคrgerna fรถr markeringar. ", + "use-custom-highlight-colors-label": "Anvรคnd anpassade markeringsfรคrger", + "warning-color-label": "Varningsfรคrg" + }, + "user-interface": { + "allow-panels-without-bar-description": "Nรคr denna funktion รคr aktiverad kan paneler รถppnas pรฅ valfri skรคrm. Nรคr den รคr inaktiverad รถppnas paneler endast pรฅ skรคrmar som har en bar, vilket kan minska minnesanvรคndningen.", + "allow-panels-without-bar-label": "Tillรฅt paneler pรฅ skรคrmar utan bar", + "animation-disable-description": "Inaktivera alla animationer fรถr en snabbare och mer responsiv upplevelse.", + "animation-disable-label": "Inaktivera UI-animationer", + "animation-speed-description": "Justera global animationshastighet.", + "animation-speed-label": "Animationshastighet", + "animation-speed-reset": "ร…terstรคll animationshastighet", + "appearance-desc": "Anpassa visuella element som verktygstips, kantlinjer och skuggor.", + "blur-behind-description": "Suddar ut omrรฅdet bakom fรคlt och paneler med hjรคlp av kompositorns suddighetsprotokoll.", + "blur-behind-label": "Bakgrundsoskรคrpa", + "box-border-description": "Visa en kontur runt innehรฅllsomrรฅden.", + "box-border-label": "Behรฅllarkontur", + "box-border-radius-description": "Justerar hรถrnrundningen fรถr stรถrre layoutsektioner, sรฅsom sidofรคlt, kort och innehรฅllspaneler.", + "box-border-radius-label": "Behรฅllarradie", + "control-border-radius-description": "Styr kurvaturen fรถr interaktiva element, inklusive knappar, vรคxlar och textfรคlt.", + "control-border-radius-label": "Inmatningsradie", + "desc": "Anpassa utseendet, kรคnslan och beteendet hos grรคnssnittet.", + "dim-desktop-description": "Dรคmpa skrivbordet nรคr paneler eller menyer รคr รถppna.", + "dim-desktop-label": "Dรคmpa skrivbordet", + "dimmer-opacity-description": "Stรคll in opacitetsnivรฅn fรถr dรคmpning av skrivbordet. ", + "dimmer-opacity-label": "Dimrad skrivbordsopacitet", + "panel-background-opacity-description": "Stรคll in bakgrundsopaciteten fรถr alla paneler (fรคlt, startprogram, instรคllningar osv.). ", + "panel-background-opacity-label": "Panelens bakgrundsopacitet", + "panels-attached-to-bar-description": "Panelerna lรฅses fast vid fรคltet och skรคrmens kanter, vilket skapar ett sรถmlรถst utseende med snygga inverterade hรถrn.", + "panels-attached-to-bar-label": "Fรคst paneler vid kanterna", + "panels-overlay-description": "Sรคkerstรคller att paneler och fรคltet fรถrblir synliga, รคven รถver helskรคrmsapplikationer. ", + "panels-overlay-label": "Hรฅll paneler och fรคlt รถverst", + "scaling-description": "ร„ndrar storleken pรฅ det allmรคnna anvรคndargrรคnssnittet, exklusive fรคltet.", + "scaling-label": "Grรคnssnittsskalning", + "scrollbar-always-visible-description": "Hรฅll rullningslister synliga nรคr innehรฅll รคr rullbart, istรคllet fรถr att bara visa dem vid hovring.", + "scrollbar-always-visible-label": "Visa alltid rullningslister", + "settings-panel-header": "Instรคllningspanel", + "settings-panel-mode-description": "Vรคlj instรคllningslayout (kan krรคva att du รถppnar om).", + "settings-panel-mode-label": "Instรคllningspanelens lรคge", + "settings-panel-sidebar-card-style-description": "Omsluter sidofรคltet fรถr instรคllningar i en fylld bakgrund med rundade hรถrn.", + "settings-panel-sidebar-card-style-label": "Sidopanelens kortstil", + "shadows-description": "Aktiverar skuggor under fรคlt och paneler.", + "shadows-direction-description": "Vรคlj var skuggan ska kastas.", + "shadows-direction-label": "Skuggans riktning", + "shadows-label": "Fallskugga", + "title": "Anvรคndargrรคnssnitt", + "tooltips-description": "Aktivera eller inaktivera verktygstips i hela grรคnssnittet.", + "tooltips-label": "Visa verktygstips", + "translucent-widgets-description": "Gรถr knappar, flikar och andra widgets inuti paneler halvtransparenta.", + "translucent-widgets-label": "Genomskinliga widgetar" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "Alfabetiskt", + "automation-change-mode-description": "Vรคlj hur bakgrundsbilder ska vรคljas nรคr de รคndras automatiskt.", + "automation-change-mode-label": "ร„ndra lรคge", + "automation-custom-interval-description": "Ange tid i formatet TT:MM (t.ex. 01:30).", + "automation-custom-interval-label": "Anpassat intervall", + "automation-interval-description": "Hur ofta bakgrundsbilder ska รคndras automatiskt. ", + "automation-interval-label": "Bakgrundsbildsintervall", + "automation-random-wallpaper-description": "Schemalรคgg slumpmรคssiga bakgrundsbildsbyten med jรคmna mellanrum.", + "automation-scheduled-change-description": "Byt bakgrundsbilder automatiskt med jรคmna mellanrum.", + "automation-scheduled-change-label": "Schemalagd รคndring", + "look-feel-edge-smoothness-description": "Tillรคmpar en mjuk, uttonad effekt pรฅ รถvergรฅngarnas kanter.", + "look-feel-edge-smoothness-label": "Mjuk รถvergรฅngskant", + "look-feel-fill-color-description": "Vรคlj en fyllningsfรคrg som kan visas bakom bakgrundsbilden.", + "look-feel-fill-mode-description": "Vรคlj hur bilden ska skalas fรถr att passa din skรคrms upplรถsning.", + "look-feel-fill-mode-label": "Fyllningslรคge", + "look-feel-skip-startup-transition-description": "Hoppa รถver bakgrundsbildsanimationen nรคr skalet startar.", + "look-feel-skip-startup-transition-label": "Hoppa รถver startรถvergรฅng", + "look-feel-title": "Utseende och kรคnsla", + "look-feel-transition-duration-description": "ร–vergรฅngsanimationernas varaktighet i sekunder.", + "look-feel-transition-duration-label": "ร–vergรฅngens varaktighet", + "look-feel-transition-type-description": "Animationstyp vid byte mellan bakgrundsbilder.", + "look-feel-transition-type-label": "ร–vergรฅngstyp", + "settings-clear-cache-button": "Rensa cache", + "settings-clear-cache-description": "Rensa cachade, storleksรคndrade bakgrundsbilder fรถr att frigรถra diskutrymme.", + "settings-clear-cache-label": "Bakgrundscache", + "settings-clear-cache-toast": "Bakgrundscache rensad", + "settings-desc": "Styr hur bakgrundsbilder hanteras och visas.", + "settings-enable-management-description": "Hantera bakgrundsbilder med Noctalia. Avmarkera om du fรถredrar att anvรคnda ett annat program.", + "settings-enable-management-label": "Aktivera bakgrundsbildshantering", + "settings-enable-overview-description": "Tillรคmpar en suddig och nedtonad bakgrundsbild pรฅ รถversiktsskรคrmen.", + "settings-enable-overview-label": "Aktivera รถversiktsbakgrundsbild", + "settings-folder-description": "Sรถkvรคg till din huvudsakliga bakgrundsbildsmapp.", + "settings-folder-label": "Bakgrundsbildsmapp", + "settings-hide-wallpaper-filenames-tooltip-hide": "Dรถlj filnamn", + "settings-hide-wallpaper-filenames-tooltip-show": "Visa filnamn", + "settings-monitor-specific-description": "Stรคll in en annan bakgrundsbildsmapp fรถr varje skรคrm.", + "settings-monitor-specific-label": "Skรคrmspecifika kataloger", + "settings-monitor-specific-tooltip": "Skรคrmens bakgrundsbildsmapp", + "settings-overview-blur-strength-description": "Tillรคmpar oskรคrpans styrka pรฅ รถversikten.", + "settings-overview-blur-strength-label": "ร–versiktens oskรคrpestyrka", + "settings-overview-tint-description": "Tillรคmpar toningsstyrkan pรฅ รถversikten.", + "settings-overview-tint-label": "ร–versiktstoningsstyrka", + "settings-recursive-search-description": "Sรถk รคven efter bakgrundsbilder i undermappar i bakgrundsbildskatalogen.", + "settings-recursive-search-label": "Sรถk i undermappar", + "settings-select-monitor-folder": "Vรคlj mapp fรถr bakgrundsbilder", + "settings-selector-description": "Vรคlj din bakgrundsbild.", + "settings-selector-position-description": "Vรคlj var panelen fรถr val av bakgrundsbild ska visas.", + "settings-show-hidden-files-tooltip-hide": "Dรถlj dolda filer", + "settings-show-hidden-files-tooltip-show": "Visa dolda filer", + "settings-title": "Bakgrundsinstรคllningar", + "settings-use-original-images-description": "Hoppa รถver att รคndra storlek pรฅ bakgrundsbilder fรถre visning. Sparar diskutrymme och minskar CPU-anvรคndning vid bakgrundsbildsbyten, men kan anvรคnda mer minne fรถr mycket stora bilder.", + "settings-use-original-images-label": "Anvรคnd originalbilder", + "settings-view-mode-description": "Vรคlj hur bakgrundsbilder ska visas frรฅn din katalog.", + "settings-view-mode-label": "Visningslรคge", + "view-mode-browse": "Blรคddra i kataloger", + "view-mode-cycle-tooltip": "Visningslรคge: {lรคge} (klicka fรถr att รคndra)", + "view-mode-recursive": "Platta underkataloger", + "view-mode-single": "Rotkatalog" + } + }, + "placeholders": { + "add-new-keybind": "Lรคgg till ny tangentbindning", + "command-example": "echo \"Hello World\"", + "enter-command": "Ange kommando att utfรถra (app eller anpassat skript)", + "enter-ipc-identifier": "Ange unik identifierare fรถr IPC-kommandon", + "enter-path": "Ange sรถkvรคg...", + "enter-text-to-collapse": "t.ex. 'ingenting spelas upp'. Anvรคnd /regex/ fรถr mรถnster.", + "enter-tooltip": "Ange verktygstips", + "enter-width-pixels": "Ange bredd i bildpunkter", + "keybind-recording": "Spelar in tangentbindning...", + "search": "Sรถk... ", + "search-icons": "t.ex. noctalia, niri, batteri, moln", + "search-launcher": "Sรถk poster... eller anvรคnd > fรถr kommandon", + "search-wallhaven": "Sรถk pรฅ Wallhaven...", + "search-wallpapers": "Skriv fรถr att filtrera bakgrundsbilder... " + }, + "positions": { + "bottom": "Nedre", + "bottom-center": "Nedre mitt", + "bottom-left": "Nedre vรคnster", + "bottom-right": "Nedre hรถger", + "center": "Mitten", + "center-left": "Vรคnster mitt", + "center-right": "Hรถger mitt", + "close-to-bar": "Stรคng till stapelknapp", + "follow-bar": "Fรถlj stapel", + "left": "Vรคnster", + "right": "Hรถger", + "top": "ร–verst", + "top-center": "ร–verst i mitten", + "top-left": "ร–verst till vรคnster", + "top-right": "ร–verst till hรถger" + }, + "session-menu": { + "action-in-seconds": "{action} pรฅ {sekunder} sekunder...", + "cancel-timer": "Avbryt timer", + "title": "Sessionsmeny" + }, + "setup": { + "all-done": "Allt klart!", + "appearance": { + "subheader": "Vรคlj mรถrkt lรคge och fรคrgkรคllor (bakgrundsbild eller fรถrdefinierade)." + }, + "customize": { + "header": "Anpassa din upplevelse", + "subheader": "Justera fรคltets position, densitet, skalning och mer." + }, + "skip-setup": "Hoppa รถver instรคllningar", + "telemetry-wizard-done": "Jag fattar!", + "telemetry-wizard-note": "Du har kontrollen โ€” aktivera eller inaktivera detta nรคr som helst i instรคllningar", + "telemetry-wizard-subtitle": "Vi har lagt till anonym analys fรถr att fรถrbรคttra Noctalia", + "telemetry-wizard-title": "Uppdatering av integritet", + "wallpaper": { + "choose-dir": "Vรคlj en katalog som innehรฅller dina bakgrundsbilder", + "dir-browse": "Blรคddra efter bakgrundsbildsmapp", + "dir-description": "Vรคlj mappen som innehรฅller dina bakgrundsbilder.", + "dir-label": "Bakgrundsbildskatalog", + "dir-select-title": "Vรคlj bakgrundsbildsmapp", + "header": "Vรคlj din bakgrundsbild", + "no-dir": "Ingen bakgrundsbildskatalog vald", + "no-valid": "Inga giltiga bildfiler hittades i: {dir}", + "none-in-dir": "Inga bakgrundsbilder hittades i katalogen", + "preview-error": "Det gick inte att lรคsa in bilden", + "select-prompt": "Vรคlj en bakgrundsbild nedan", + "subheader": "Skapa stรคmning med en vacker bakgrund." + }, + "welcome": "Vรคlkommen", + "welcome-note": "Bara nรฅgra grundlรคggande tips fรถr att komma igรฅng โ€” alla alternativ finns i instรคllningarna", + "welcome-subtitle": "Lรฅt oss gรถra ditt skrivbord unikt fรถr dig", + "welcome-title": "Vรคlkommen till Noctalia!" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "Ingen mediaspelare upptรคckt", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "Okรคnd app", + "uptime": "Driftstid: {uptime}", + "user-requested": "Begรคrt av anvรคndare", + "welcome-back": "Vรคlkommen tillbaka", + "widget-settings-title": "Instรคllningar fรถr {widget}" + }, + "system-monitor": { + "core-usage": "Kรคrna {id} anvรคndning", + "cpu-temp": "CPU-temperatur", + "cpu-usage": "CPU-anvรคndning", + "disk": "Disk", + "download-speed": "Hรคmtningshastighet", + "gpu-temp": "GPU-temperatur", + "load-average": "Genom. belastning", + "title": "Systemรถvervakare", + "upload-speed": "Sรคndningshastighet" + }, + "toast": { + "airplane-mode": { + "description": "Inaktivera all trรฅdlรถs kommunikation.", + "title": "Flygplanslรคge" + }, + "battery": { + "critical": "Kritisk batterinivรฅ", + "critical-desc": "Batteriet รคr pรฅ {percent}% โ€” anslut laddaren omedelbart", + "low": "Lรฅgt batteri", + "low-desc": "Batteriet รคr pรฅ {percent}% โ€” anslut laddaren" + }, + "bluetooth": { + "address-copied": "Adress kopierad till urklipp", + "auto-connect-disabled": "Automatisk anslutning inaktiverad", + "auto-connect-enabled": "Automatisk anslutning aktiverad", + "auto-connecting": "Ansluter till {count} enhet(er)...", + "confirm-code": "Bekrรคfta koden {value} pรฅ den andra enheten.", + "connect-failed": "Det gick inte att ansluta till enheten", + "disconnect-failed": "Det gick inte att koppla bort frรฅn enheten", + "discoverable-change-failed": "Det gick inte att รคndra synlighetsstatus", + "discoverable-disabled": "Upptรคckbar inaktiverad", + "discoverable-enabled": "Upptรคckbar aktiverad", + "display-code": "Ange koden {value} pรฅ den andra enheten.", + "forget-failed": "Det gick inte att glรถmma enheten", + "pair-failed": "Det gick inte att para ihop enheten", + "passkey-required": "Enheten krรคver lรถsenord.", + "pincode-required": "Enheten krรคver PIN-kod.", + "state-change-failed": "Det gick inte att รคndra Bluetooth-status" + }, + "clipboard": { + "long-text": "Lรฅng text", + "unavailable": "Urklippshistorik otillgรคnglig", + "unavailable-desc": "Applikationen 'cliphist' รคr inte installerad. Installera den fรถr att kunna anvรคnda klippbordshistorikfunktionerna" + }, + "custom-command-failed": { + "description": "Kommando misslyckades: {command}\\nAvslutningskod: {code}", + "title": "Anpassat kommando misslyckades" + }, + "do-not-disturb": { + "disabled": "'Stรถr inte' inaktiverat", + "disabled-desc": "Visar alla aviseringar", + "enabled": "'Stรถr inte' aktiverat", + "enabled-desc": "Du hittar dessa aviseringar i din historik" + }, + "donation-opened": "Donationssidan รถppnades i din webblรคsare", + "internet-limited": "Ansluten utan internet", + "keyboard-layout": { + "changed": "Tangentbordslayouten รคndrad till {layout}", + "title": "Tangentbord" + }, + "missing-control-center": { + "description": "Kontrollcenter-widgeten har tagits bort frรฅn fรคltet. Fรถr att komma รฅt den frรฅn fรคltet igen mรฅste du lรคgga till widgeten pรฅ nytt. Du kan ocksรฅ รถppna den genom att hรถgerklicka pรฅ fรคltet", + "label": "Senaste kontrollcenter-widgeten borttagen" + }, + "night-light": { + "forced": "Tvingad aktivering", + "normal": "Normalt lรคge", + "not-installed": "wlsunset inte installerat" + }, + "noctalia-performance": { + "disabled": "Prestandalรคget inaktiverat", + "enabled": "Prestandalรคget aktiverat", + "label": "Noctalia Prestanda" + }, + "power-profile": { + "changed": "Strรถmprofil รคndrad", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "Ett fel uppstod vid bearbetning av mallar", + "title": "Bearbetning av mallar misslyckades" + }, + "vpn": { + "connected": "Ansluten till '{name}'", + "disconnected": "Frรฅnkopplad frรฅn '{name}'" + }, + "wallpaper-colors": { + "disabled": "Bakgrundsfรคrger inaktiverade", + "enabled": "Bakgrundsfรคrger aktiverade", + "label": "Bakgrundsfรคrger" + }, + "wifi": { + "connected": "Ansluten till '{ssid}'", + "connection-failed": "Anslutningen misslyckades", + "connection-timeout": "Anslutningstidsgrรคns", + "disconnected": "Kopplade frรฅn '{ssid}'", + "incorrect-password": "Felaktigt lรถsenord", + "network-not-found": "Nรคtverket hittades inte" + } + }, + "tooltips": { + "add-widget": "Lรคgg till widget", + "auto-connect": "Vรคxla automatisk anslutning fรถr denna enhet", + "bluetooth-auto-connect-off": "Automatisk anslutning inaktiverad", + "bluetooth-auto-connect-on": "Automatisk anslutning aktiverad", + "bluetooth-devices": "Bluetooth-enheter", + "brightness-at": "Ljusstyrka: {brightness}%", + "click-to-start-recording": "Skรคrminspelare (starta inspelning)", + "click-to-stop-recording": "Skรคrminspelare (stoppa inspelning)", + "collapse": "Dรถlj sidofรคlt", + "copy-address": "Kopiera adress", + "delete-notification": "Ta bort avisering", + "dismiss-notification": "Avfรคrda avisering", + "do-not-disturb-enabled": "Stรถr inte", + "expand": "Expandera sidofรคlt", + "forget-network": "Glรถm nรคtverk", + "grid-view": "Rutnรคtsvy", + "hidden-files-hide": "Dolda filer", + "hide-unnamed-devices": "Dรถlj namnlรถsa enheter", + "home": "Hemkatalog", + "input-muted": "Vรคxla mellan ljud avstรคngt och ljud pรฅ", + "keep-awake": "Hรฅll vaken", + "keyboard-layout": "{layout} tangentbordslayout", + "list-view": "Listvy", + "manage-vpn": "VPN-anslutningar", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "Maximalt antal widgetar uppnรฅtt", + "microphone-volume-at": "Mikrofonvolym: {volume}%", + "move-to-section": "Flytta till {section}", + "mute": "Ljud av", + "next-media": "Nรคsta spรฅr", + "next-month": "Nรคsta mรฅnad", + "night-light-not-installed": "Nattlampa (inte tillgรคnglig)", + "noctalia-performance-enabled": "Noctalia prestandalรคget", + "open-annotation-tool": "ร–ppna med anteckningsverktyg", + "open-control-center": "Kontrollcenter", + "open-notification-history-enable-dnd": "Aviseringshistorik", + "open-settings": "Instรคllningar", + "open-tray-dropdown": "Systemfรคlt", + "output-muted": "Vรคxla mellan ljud avstรคngt", + "power-profile": "Energiprofilen {profil}", + "previous-media": "Fรถregรฅende spรฅr", + "previous-month": "Fรถregรฅende mรฅnad", + "refresh-devices": "Uppdatera enheter", + "refresh-wallhaven": "Uppdatera Wallhaven-resultat", + "refresh-wallpaper-list": "Uppdatera bakgrundsbildslista", + "remove-widget": "Ta bort widget", + "search-close": "Stรคng sรถkning", + "session-menu": "Sessionsmeny", + "show-all-devices": "Visa alla enheter", + "switch-to-dark-mode": "Mรถrkt lรคge", + "switch-to-light-mode": "Ljust lรคge", + "unmute": "Inaktivera ljud", + "up": "ร–verordnad katalog", + "volume-at": "Utgรฅngsvolym: {volume}%", + "wallpaper-selector": "Bakgrundsbildsvรคljare" + }, + "wallpaper": { + "browse": { + "empty-directory": "Denna katalog รคr tom.", + "go-root": "Gรฅ till bakgrundsbildens rotkatalog", + "go-up": "Gรฅ till รถverordnad mapp", + "go-up-hint": "Anvรคnd bakรฅtknappen fรถr att navigera uppรฅt." + }, + "configure-directory": "Konfigurera din bakgrundsbildskatalog med bilder.", + "fill-modes": { + "crop": "Beskรคr (Fyll)", + "fit": "Anpassa (Innehรฅll)", + "repeat": "Upprepa (Sida vid sida)", + "stretch": "Strรคck ut" + }, + "no-match": "Ingen matchning hittades.", + "no-wallpaper": "Ingen bakgrundsbild hittades.", + "panel": { + "apikey-help": "En API-nyckel krรคvs fรถr att fรฅ รฅtkomst till NSFW-innehรฅll.", + "apikey-label": "API-nyckel", + "apikey-managed-by-env": "Hanteras via miljรถvariabeln NOCTALIA_WALLHAVEN_API_KEY.", + "apikey-placeholder": "Ange din Wallhaven API-nyckel", + "appearance-dark-tab": "Mรถrk", + "appearance-light-tab": "Ljus", + "categories-anime": "Anime", + "categories-label": "Kategorier", + "categories-people": "Personer", + "color-extraction-disabled": "Anvรคnd bakgrundsfรคrgsutvinning", + "color-extraction-enabled": "Anvรคnd fรถrdefinierade fรคrgscheman", + "header-devices-apply-all-tooltip": "Vรคlj bakgrundsbild per skรคrm", + "header-devices-per-monitor-tooltip": "Tillรคmpa pรฅ alla bildskรคrmar", + "header-link-light-dark-tooltip": "Behรฅll bakgrundsbild vid temabyte", + "header-separate-light-dark-tooltip": "Byt bakgrundsbild automatiskt med tema", + "header-sun-linked-tooltip": "Separata ljusa och mรถrka bakgrundsbilder", + "header-sun-separate-tooltip": "Samma bakgrundsbild fรถr ljust och mรถrkt", + "order-asc": "Stigande", + "order-desc": "Fallande", + "order-label": "Ordning", + "purity-label": "Innehรฅllsfilter", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "Sketchy", + "ratios-all-portrait": "Alla portrรคtt", + "ratios-all-wide": "Alla breda", + "ratios-any": "Alla", + "ratios-label": "Bildfรถrhรฅllande", + "resolution-atleast": "Minst", + "resolution-exact": "Exakt", + "resolution-label": "Upplรถsning", + "resolution-mode-label": "Lรคge", + "solid-color-tooltip": "Enfรคrgad bakgrund", + "sort-date-asc": "Sortera efter รคldst fรถrst", + "sort-date-desc": "Sortera efter nyast fรถrst", + "sort-name-asc": "Sortera efter namn (A-Z)", + "sort-name-desc": "Sortera efter namn (Z-A)", + "sort-random": "Sortera slumpmรคssigt", + "sorting-date-added": "Datum tillagt", + "sorting-favorites": "Favoriter", + "sorting-label": "Sortera efter", + "sorting-relevance": "Relevans", + "sorting-toplist": "Topplista", + "sorting-views": "Visningar", + "source-label": "Kรคlla", + "source-wallhaven": "Wallhaven", + "title": "Bakgrundsbildsvรคljare", + "wallhaven-settings-title": "Wallhaven-instรคllningar" + }, + "transitions": { + "disc": "Skiva", + "fade": "Tonad", + "honeycomb": "Bikaka", + "pixelate": "Pixla", + "stripes": "Rรคnder", + "wipe": "Torkning" + }, + "try-different-search": "Prova en annan sรถkfrรฅga.", + "wallhaven": { + "loading": "Lรคser in bakgrundsbilder...", + "no-results": "Inga bakgrundsbilder hittades. Prova en annan sรถkfrรฅga.", + "page": "{current} av {total}", + "page-prefix": "Sida", + "page-suffix": "av {total}" + } + }, + "weather": { + "clear-sky": "Klar himmel", + "drizzle": "Duggregn", + "fog": "Dimma", + "mainly-clear": "Mestadels klart", + "overcast": "Molnigt", + "partly-cloudy": "Delvis molnigt", + "rain-showers": "Regnskurar", + "snow": "Snรถ", + "thunderstorm": "ร…skvรคder" + }, + "widgets": { + "color-picker": { + "palette-description": "Vรคlj bland ett stort utbud av fรถrdefinierade fรคrger.", + "palette-label": "Palett", + "palette-theme-colors": "Snabb รฅtkomst till ditt temas fรคrgpalett. ", + "title": "Fรคrgvรคljare" + }, + "datetime-tokens": { + "ampm-lowercase": "am/pm i gemener", + "ampm-uppercase": "AM/PM i versaler", + "common-12hour-time-minutes": "12-timmarsformat med minuter", + "common-24hour-time-minutes": "24-timmarsformat med minuter", + "common-24hour-time-seconds": "24-timmarsformat med sekunder", + "common-european-date": "Europeiskt datumformat", + "common-iso-date": "ISO-datumformat", + "common-us-date": "Amerikanskt datumformat", + "common-weekday-date": "Veckodag med datum", + "common-weekday-month-day": "Veckodag, mรฅnad och dag", + "day-abbreviated": "Fรถrkortat dagnamn", + "day-full": "Fullstรคndigt dagnamn", + "day-leading-zero": "Dag med inledande nolla (01-31)", + "day-no-leading-zero": "Dag utan inledande nolla (1-31)", + "hour-leading-zero": "Timme med inledande nolla (00-23) โ€” 24-timmarsformat", + "hour-no-leading-zero": "Timme utan inledande nolla (0-23) โ€” 24-timmarsformat", + "minute-leading-zero": "Minut med inledande nolla (00-59)", + "minute-no-leading-zero": "Minut utan inledande nolla (0-59)", + "month-abbreviated": "Fรถrkortat mรฅnadsnamn", + "month-full": "Fullstรคndigt mรฅnadsnamn", + "month-number-leading-zero": "Mรฅnad som nummer med inledande nolla (01-12)", + "month-number-no-zero": "Mรฅnad som nummer utan inledande nolla (1-12)", + "second-leading-zero": "Sekund med inledande nolla (00-59)", + "second-no-leading-zero": "Sekund utan inledande nolla (0-59)", + "timezone-abbreviation": "Fรถrkortning av tidszon", + "year-four-digit": "ร…r som fyrsiffrigt tal", + "year-two-digit": "ร…r som tvรฅsiffrigt tal (00-99)" + }, + "file-picker": { + "item": "objekt", + "items": "objekt", + "search-placeholder": "Sรถk efter filer och mappar...", + "select-current": "Vรคlj aktuell", + "select-file": "Vรคlj fil", + "select-folder": "Vรคlj mapp", + "selected": "Vald:", + "title": "Filvรคljare" + }, + "file-picker-title": "Vรคlj en fil", + "icon-picker": { + "title": "Ikonvรคljare" + }, + "tooltip-placeholder": "Platshรฅllare" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "Anonym Identitet", + "ca-cert": "CA-certifikat", + "eap-method": "EAP-metod", + "password": "Anvรคndarlรถsenord", + "phase2-auth": "Fas 2-autentisering", + "username": "Identitet / Anvรคndarnamn" + }, + "panel": { + "action-required": "ร…tgรคrd krรคvs", + "add-hidden-network": "Lรคgg till dolt nรคtverk", + "add-network": "Lรคgg till nรคtverk", + "available-interfaces": "Tillgรคngliga grรคnssnitt", + "available-networks": "Tillgรคngliga nรคtverk", + "disabled": "Wiโ€‘Fi รคr inaktiverat", + "disconnecting": "Kopplar frรฅn...", + "dns": "DNS", + "enable-message": "Aktivera Wiโ€‘Fi fรถr att se tillgรคngliga nรคtverk.", + "enter-password": "Ange Wiโ€‘Fi-lรถsenord... ", + "forget": "Glรถm", + "forget-network": "Glรถm detta nรคtverk", + "forgetting": "Glรถmmer...", + "hidden-network": "Dolt nรคtverk", + "interface": "Nรคtverksgrรคnssnitt", + "internet-connected": "Ansluten till internet", + "internet-limited": "Inget internet", + "internet-status": "Internetstatus", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "Kรคnda nรคtverk", + "link-speed": "Lรคnkhastighet", + "network-name-ssid": "Nรคtverksnamn (SSID)", + "no-ethernet-devices": "Inga Ethernet-enheter upptรคckta", + "no-networks": "Inga Wiโ€‘Fi-nรคtverk hittades", + "saved": "Sparat", + "scan-again": "Sรถk igen", + "searching": "Sรถker efter nรคtverk...", + "security-open": "ร–ppna", + "security-wep": "WEP", + "security-wpa": "WPA/WPA2/WPA3 Personligt", + "security-wpa-ent": "WPA Enterprise", + "security-wpa2-ent": "WPA2 Enterprise", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 Enterprise", + "show-password": "Visa lรถsenord", + "title": "Wiโ€‘Fi" + }, + "signal": { + "excellent": "Utmรคrkt", + "fair": "Ok", + "good": "Bra", + "poor": "Dรฅligt", + "weak": "Svag" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/tr.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/tr.json new file mode 100644 index 0000000..71c8a2e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/tr.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "GeรงmiลŸi temizle", + "close-app": "{app}'i kapat", + "connect-vpn": "{name} baฤŸlantฤฑsฤฑna baฤŸlan", + "cycle-visualizer": "Dรถngรผ gรถrselleลŸtirici", + "disable-bluetooth": "Bluetooth'u kapat", + "disable-dnd": "'Rahatsฤฑz etmeyin'i kapat", + "disable-wifi": "Kablosuz baฤŸlantฤฑyฤฑ kapat", + "disconnect-vpn": "{name} baฤŸlantฤฑsฤฑnฤฑ kes", + "dock-settings": "Dock ayarlarฤฑ", + "enable-bluetooth": "Bluetooth'u etkinleลŸtir", + "enable-dnd": "'Rahatsฤฑz etmeyin'i etkinleลŸtir", + "enable-wifi": "Kablosuz baฤŸlantฤฑyฤฑ etkinleลŸtir", + "launcher-settings": "BaลŸlatฤฑcฤฑ ayarlarฤฑ", + "lower-to-bottom": "Alta doฤŸru alรงalt", + "open-calendar": "Takvimi aรง", + "open-display-settings": "Ekran ayarlarฤฑ", + "open-launcher": "BaลŸlatฤฑcฤฑyฤฑ aรง", + "open-settings": "Ayarlarฤฑ aรง", + "raise-to-top": "En รผste taลŸฤฑ", + "random-wallpaper": "Rastgele duvar kรขฤŸฤฑdฤฑ", + "run-custom-command": "ร–zel komut รงalฤฑลŸtฤฑr", + "show-active-only": "Sadece etkin olanlarฤฑ gรถster", + "show-all": "Tรผmรผnรผ gรถster", + "toggle-mute": "Sesi kapat/aรง", + "widget-settings": "Araรง takฤฑmฤฑ ayarlarฤฑ" + }, + "authentication": { + "error": "Kimlik doฤŸrulama hatasฤฑ", + "failed": "Kimlik doฤŸrulama baลŸarฤฑsฤฑz" + }, + "bar": { + "active-window": { + "colorize-icons-description": "Aktif pencere simgelerine tema renklerini uygula.", + "hide-mode-description": "Pencere aktif deฤŸilken araรง takฤฑmlarฤฑnฤฑn nasฤฑl davrandฤฑฤŸฤฑnฤฑ kontrol eder.", + "scrolling-mode-description": "Uzun pencere baลŸlฤฑklarฤฑ iรงin metin kaydฤฑrmanฤฑn ne zaman etkinleลŸtirileceฤŸini kontrol et.", + "show-app-icon-description": "Pencere baลŸlฤฑฤŸฤฑ yanฤฑnda uygulama simgesini gรถster.", + "show-app-icon-label": "Uygulama simgesini gรถster", + "show-app-text-description": "Etkin pencerenin baลŸlฤฑฤŸฤฑnฤฑ gรถster.", + "show-app-text-label": "Pencere baลŸlฤฑฤŸฤฑnฤฑ gรถster" + }, + "audio-visualizer": { + "color-name-description": "GรถrselleลŸtirici iรงin renk seรงin.", + "color-name-label": "Dolgu rengi", + "height-description": "ร–zel bileลŸen geniลŸliฤŸi.", + "hide-when-idle-description": "EtkinleลŸtirildiฤŸinde, รงalar aktif bir ลŸekilde รงalmadฤฑฤŸฤฑ sรผrece gรถrselleลŸtirici gizlidir.", + "hide-when-idle-label": "BoลŸken gizle", + "width-description": "ร–zel bileลŸen geniลŸliฤŸi." + }, + "battery": { + "device-default": "Varsayฤฑlan (BileลŸik aygฤฑt)", + "device-description": "Gรถrรผntรผlenecek pil cihazฤฑnฤฑ seรงin.", + "device-label": "Pil cihazฤฑ", + "display-mode-description": "Pilin รงubukta nasฤฑl gรถrรผntรผleneceฤŸini seรงin.", + "display-mode-graphic": "Grafik pil", + "display-mode-graphic-clean": "Grafik pil (yรผzde yok)", + "display-mode-icon-always": "Simge - Her zaman % gรถster", + "display-mode-icon-hover": "Simge - รœzerine gelindiฤŸinde gรถster", + "display-mode-icon-only": "Yalnฤฑzca simge", + "hide-if-idle-description": "Pil ลŸarj olmuyor veya deลŸarj olmuyorken aracฤฑ gizle.", + "hide-if-idle-label": "BoลŸta kalฤฑnca gizle", + "hide-if-not-detected-description": "Sistemde pil algฤฑlanmadฤฑฤŸฤฑnda araรง รงubuฤŸunu gizle.", + "hide-if-not-detected-label": "Algฤฑlanmazsa gizle", + "low-battery-threshold-label": "DรผลŸรผk batarya eลŸiฤŸi", + "show-noctalia-performance-description": "Pil panelinde Noctalia performans modu anahtarฤฑnฤฑ gรถster.
Kaynak kullanฤฑmฤฑnฤฑ azaltmak iรงin Noctalia'da gรถlgeleri ve animasyonlarฤฑ devre dฤฑลŸฤฑ bฤฑrakฤฑr.", + "show-noctalia-performance-label": "Noctalia Performans anahtarฤฑnฤฑ gรถster", + "show-power-profile-description": "Pil panelinde gรผรง profili seรงimlerini gรถster.", + "show-power-profile-label": "Gรผรง profili denetimlerini gรถster" + }, + "brightness": { + "apply-all-description": "Kaydฤฑrma tekerleฤŸini kullanฤฑrken tรผm monitรถrlerin parlaklฤฑฤŸฤฑnฤฑ deฤŸiลŸtir.", + "apply-all-label": "Kaydฤฑrma deฤŸiลŸikliklerini tรผm monitรถrlere uygula" + }, + "clock": { + "clock-display-description": "Saatinizin gรถrรผntรผsรผnรผ รถzelleลŸtirmek iรงin aลŸaฤŸฤฑdaki listeden jetonlarฤฑ ekleyin. 12 saat formatฤฑnฤฑ kullanmak iรงin 'AP' jetonunu eklemelisiniz.", + "clock-display-label": "Saat gรถrรผntรผleme", + "custom-font-description": "Saat gรถrรผntรผleme iรงin รถzel bir yazฤฑ tipi seรงin.", + "custom-font-label": "ร–zel yazฤฑ tipi", + "custom-font-placeholder": "ร–zel yazฤฑ tipi seรงin...", + "custom-font-search-placeholder": "Yazฤฑ tiplerini ara...", + "horizontal-bar-description": "ฤฐpucu: Bir satฤฑr sonu oluลŸturmak iรงin \\n kullanฤฑn.", + "horizontal-bar-label": "Yatay รงubuk", + "preview": "ร–nizleme", + "tooltip-format-description": "Saat รผzerine geldiฤŸinizde gรถsterilen ipucu iรงin format dizesi. Varsayฤฑlan ipucunu kullanmak iรงin boลŸ bฤฑrakฤฑn.", + "tooltip-format-label": "ฤฐpucu formatฤฑ", + "use-custom-font-description": "Saat iรงin varsayฤฑlan yazฤฑ tipi seรงimini รถzel bir yazฤฑ tipiyle geรงersiz kฤฑlฤฑn.", + "use-custom-font-label": "ร–zel yazฤฑ tipi kullan", + "use-monospaced-font-description": "EtkinleลŸtirildiฤŸinde, saat tek aralฤฑklฤฑ yazฤฑ tipini kullanฤฑr.", + "use-monospaced-font-label": "Tek aralฤฑklฤฑ yazฤฑ tipi kullan", + "vertical-bar-description": "Her parรงayฤฑ yeni bir satฤฑra ayฤฑrmak iรงin boลŸluk kullanฤฑn.", + "vertical-bar-label": "Dikey รงubuk" + }, + "control-center": { + "browse-file": "Dosyaya gรถz at", + "browse-library": "Kรผtรผphaneye gรถz at", + "color-selection-description": "Simgeye tema renklerini uygular.", + "enable-colorization-description": "Simge iรงin renklendirmeyi etkinleลŸtir, tema renklerini uygula.", + "icon-description": "Kรผtรผphaneden veya รถzel bir dosyadan bir simge seรงin.", + "select-custom-icon": "ร–zel bir simge seรง", + "use-distro-logo-description": "ร–zel simge yerine daฤŸฤฑtฤฑmฤฑnฤฑzฤฑn logosunu kullanฤฑn.", + "use-distro-logo-label": "DaฤŸฤฑtฤฑm logosunu simge yerine kullan" + }, + "custom-button": { + "collapse-condition-description": "ร‡ฤฑktฤฑ metni bu deฤŸerle eลŸleลŸirse dรผฤŸme daralacak.", + "collapse-condition-label": "Daralma durumu", + "color-selection-description": "Tema renklerini simgeye ve metne uygula.", + "default-tooltip": "ร–zel dรผฤŸme, ayarlarda yapฤฑlandฤฑrฤฑn", + "display-command-output-description": "Dรผzenli aralฤฑklarla รงalฤฑลŸtฤฑrฤฑlacak bir komut girin. ร‡ฤฑktฤฑsฤฑnฤฑn ilk satฤฑrฤฑ metin olarak gรถsterilecektir.", + "display-command-output-label": "Komut รงฤฑktฤฑsฤฑnฤฑ gรถster", + "display-command-output-stream-description": "Sรผrekli รงalฤฑลŸtฤฑrฤฑlacak bir komut girin.", + "dynamic-text": "Hareketli metin", + "enable-colorization-description": "ร–zel dรผฤŸme simgesi ve metni iรงin renklendirmeyi etkinleลŸtir, tema renklerini uygular.", + "enable-colorization-label": "Renklendirmeyi etkinleลŸtir", + "general-tooltip-text-description": "DรผฤŸmenin araรง ipucunda gรถrรผntรผlenecek รถzel metin.", + "general-tooltip-text-label": "ร–zel ipucu metni", + "hide-mode-always-expanded": "Her zaman geniลŸ", + "hide-mode-description": "Komutun รงฤฑktฤฑ vermemesi durumunda gerecin gรถrรผnรผrlรผฤŸรผnรผ kontrol eder.", + "hide-mode-expand-with-output": "ร‡ฤฑktฤฑ olduฤŸunda geniลŸlet", + "hide-mode-label": "Gizleme modu", + "hide-mode-max-transparent": "En bรผyรผk geniลŸlik ama saydam", + "icon-description": "Kรผtรผphaneden bir simge seรงin.", + "ipc-identifier-description": "IPC komutlarฤฑ iรงin benzersiz tanฤฑmlayฤฑcฤฑ. Bu tanฤฑmlayฤฑcฤฑyฤฑ 'qs -c noctalia-shell ipc call cb [eylem] [tanฤฑmlayฤฑcฤฑ]' ile kullanarak bu dรผฤŸmeyi IPC aracฤฑlฤฑฤŸฤฑyla kontrol edin.", + "ipc-identifier-label": "IPC Tanฤฑmlayฤฑcฤฑsฤฑ", + "left-click-description": "DรผฤŸmeye sol tฤฑklandฤฑฤŸฤฑnda yรผrรผtรผlecek komut.", + "left-click-label": "Sol tฤฑklama", + "left-click-update-text": "Sol tฤฑklamayla gรถrรผntรผlenen metni gรผncelle", + "max-text-length-horizontal-description": "Yatay รงubukta gรถsterilecek en fazla karakter sayฤฑsฤฑ (metni gizlemek iรงin 0).", + "max-text-length-horizontal-label": "En uzun metin uzunluฤŸu (yatay)", + "max-text-length-vertical-description": "Dikey รงubukta gรถsterilecek en fazla karakter sayฤฑsฤฑ (metni gizlemek iรงin 0).", + "max-text-length-vertical-label": "En uzun metin uzunluฤŸu (dikey)", + "middle-click-description": "Orta dรผฤŸmeye tฤฑklandฤฑฤŸฤฑnda yรผrรผtรผlecek komut.", + "middle-click-label": "Orta tฤฑklama", + "middle-click-update-text": "Orta tฤฑklamayla gรถrรผntรผlenen metni gรผncelle", + "parse-json-description": "Komut รงฤฑktฤฑsฤฑnฤฑ hareketli metin ve simge olarak ayarlamak iรงin bir JSON nesnesi olarak ayrฤฑลŸtฤฑrฤฑn.", + "parse-json-label": "ร‡ฤฑktฤฑyฤฑ JSON olarak ayrฤฑลŸtฤฑr", + "refresh-interval-description": "milisaniye cinsinden aralฤฑk.", + "refresh-interval-label": "Yenileme aralฤฑฤŸฤฑ", + "right-click-description": "SaฤŸ dรผฤŸmeye tฤฑklandฤฑฤŸฤฑnda yรผrรผtรผlecek komut.", + "right-click-label": "SaฤŸ tฤฑklama", + "right-click-update-text": "SaฤŸ tฤฑklamada gรถrรผntรผlenen metni gรผncelle", + "show-exec-tooltip-description": "Komut ayrฤฑntฤฑlarฤฑnฤฑ iรงeren ipuรงlarฤฑnฤฑ gรถster (sol/saฤŸ/orta tฤฑklama, tekerlek).", + "show-exec-tooltip-label": "Komut ipuรงlarฤฑnฤฑ gรถster", + "show-icon-description": "Takฤฑm aracฤฑnฤฑn simgesinin gรถrรผnรผrlรผฤŸรผnรผ deฤŸiลŸtirir.", + "show-icon-label": "Simgeyi gรถster", + "show-text-tooltip-description": "Metin komutunun รงฤฑktฤฑsฤฑyla araรง ipuรงlarฤฑnฤฑ gรถster.", + "show-text-tooltip-label": "Dinamik metin ipuรงlarฤฑnฤฑ gรถster", + "text-stream-description": "Komuttan gelen akฤฑลŸ satฤฑrlarฤฑ dรผฤŸmede metin olarak gรถsterilecektir.", + "text-stream-label": "AkฤฑลŸ", + "wheel-description": "Kaydฤฑrma tekerleฤŸi kullanฤฑldฤฑฤŸฤฑnda yรผrรผtรผlecek komut.
Komutta kaydฤฑrma tekerleฤŸi deltasฤฑ iรงin $delta kullanฤฑn.", + "wheel-down": "AลŸaฤŸฤฑ kaydฤฑr", + "wheel-down-description": "Kaydฤฑrma tekerleฤŸi aลŸaฤŸฤฑ kaydฤฑrฤฑldฤฑฤŸฤฑnda yรผrรผtรผlecek komut.", + "wheel-down-label": "Kaydฤฑrma tekerleฤŸi aลŸaฤŸฤฑ komutu", + "wheel-label": "Kaydฤฑrma tekerleฤŸi", + "wheel-mode-separate-description": "Kaydฤฑrma tekerleฤŸi yukarฤฑ ve aลŸaฤŸฤฑ iรงin ayrฤฑ komutlarฤฑ etkinleลŸtir.", + "wheel-mode-separate-label": "Ayrฤฑ kaydฤฑrma tekerleฤŸi komutlarฤฑ", + "wheel-up": "Yukarฤฑ kaydฤฑr", + "wheel-up-description": "Kaydฤฑrma tekerleฤŸi yukarฤฑ kaydฤฑrฤฑldฤฑฤŸฤฑnda yรผrรผtรผlecek komut.", + "wheel-up-label": "Kaydฤฑrma tekerleฤŸi yukarฤฑ komutu", + "wheel-update-text": "Kaydฤฑrmada gรถsterilen metni gรผncelle" + }, + "keyboard-layout": { + "show-icon-description": "Klavye dรผzeni simgesini gรถrรผntรผle." + }, + "lock-keys": { + "hide-when-off-description": "TuลŸ etkin olmadฤฑฤŸฤฑnda gรถstergeyi gizle.", + "hide-when-off-label": "KapatฤฑldฤฑฤŸฤฑnda gizle", + "show-caps-lock-description": "Caps Lock dรผฤŸmesi durumunu gรถster.", + "show-caps-lock-label": "Caps Lock", + "show-num-lock-description": "Num Lock durumunu gรถster.", + "show-num-lock-label": "Num Lock", + "show-scroll-lock-description": "Scroll Lock durumunu gรถster.", + "show-scroll-lock-label": "Scroll Lock" + }, + "media-mini": { + "compact-mode-description": "Medya oynatฤฑcฤฑ paneli iรงin yerden tasarruf saฤŸlayan bir dรผzeni etkinleลŸtirin.", + "compact-mode-label": "SฤฑkฤฑลŸtฤฑrฤฑlmฤฑลŸ mod", + "hide-mode-description": "Ortam รงalฤฑnmadฤฑฤŸฤฑnda araรง takฤฑmฤฑnฤฑn nasฤฑl davrandฤฑฤŸฤฑnฤฑ kontrol eder.", + "max-width-description": "Araรง takฤฑmฤฑnฤฑn en fazla yatay boyutunu ayarlar. Araรง takฤฑmฤฑ daha kฤฑsa iรงerik iรงin kรผรงรผlecektir.", + "no-active-player": "Etkin รงalar yok", + "panel-section-description": "Medya oynatฤฑcฤฑ panelinin gรถrรผnรผmรผnรผ ve davranฤฑลŸฤฑnฤฑ yapฤฑlandฤฑrฤฑn.", + "panel-section-label": "Medya oynatฤฑcฤฑ paneli", + "scrolling-mode-description": "Uzun parรงa baลŸlฤฑklarฤฑ iรงin metin kaydฤฑrmanฤฑn ne zaman etkinleลŸtirileceฤŸini kontrol et.", + "scrolling-mode-label": "Kaydฤฑrma modu", + "show-album-art-description": "ลžu anda รงalan parรงanฤฑn albรผm kapaฤŸฤฑnฤฑ gรถster.", + "show-album-art-label": "Albรผm kapaฤŸฤฑnฤฑ gรถster", + "show-artist-first-description": "Sanatรงฤฑ - baลŸlฤฑk yerine baลŸlฤฑk - sanatรงฤฑ olarak gรถster.", + "show-artist-first-label": "ร–nce sanatรงฤฑyฤฑ gรถster", + "show-progress-ring-description": "Parรงa ilerlemesini gรถsteren dairesel bir ilerleme gรถstergesi gรถsterin.", + "show-progress-ring-label": "ฤฐlerleme halkasฤฑ gรถster", + "show-visualizer-description": "Mรผzik รงalarken bir ses gรถrselleลŸtirici gรถster.", + "show-visualizer-label": "GรถrselleลŸtiriciyi gรถster", + "use-fixed-width-description": "EtkinleลŸtirildiฤŸinde, araรง takฤฑmฤฑnฤฑ hareketli olarak iรงeriฤŸe gรถre ayarlamak yerine her zaman en fazla geniลŸliฤŸi kullanฤฑr.", + "use-fixed-width-label": "Sabit geniลŸlik kullan", + "visualizer-type-description": "Gรถsterilecek ses gรถrselleลŸtirici biรงemini seรงin.", + "visualizer-type-label": "GรถrselleลŸtirici tรผrรผ" + }, + "notification-history": { + "hide-widget-when-zero-description": "Bildirim yokken bildirim simgesini gizle.", + "hide-widget-when-zero-label": "Bildirim yokken simgeyi gizle", + "hide-widget-when-zero-unread-description": "OkunmamฤฑลŸ bildirim yokken bildirim simgesini gizle.", + "hide-widget-when-zero-unread-label": "OkunmamฤฑลŸ yokken simgeyi gizle", + "show-unread-badge-description": "OkunmamฤฑลŸ bildirim sayฤฑsฤฑnฤฑ gรถsteren bir rozet gรถster.", + "show-unread-badge-label": "OkunmamฤฑลŸ bildirimleri gรถster", + "unread-badge-color-description": "OkunmamฤฑลŸ bildirim rozeti iรงin rengi seรงin.", + "unread-badge-color-label": "OkunmamฤฑลŸ rozet rengi" + }, + "section-editor": { + "placeholder": "Eklenecek bir araรง takฤฑmฤฑ seรงin...", + "search-placeholder": "Araรง takฤฑmฤฑ ara..." + }, + "spacer": { + "width-description": "Piksel olarak aralฤฑk geniลŸliฤŸi." + }, + "system-monitor": { + "compact-mode-description": "ฤฐstatistikleri metin deฤŸerleri yerine mini รงubuk grafikler olarak gรถrรผntรผleyin. Dรผzen kaymasฤฑnฤฑ รถnler.", + "compact-mode-label": "Kompakt mod", + "cpu-cores-description": "CPU รงekirdeklerinin kullanฤฑmฤฑnฤฑ ayrฤฑ ayrฤฑ gรถrรผntรผle.", + "cpu-cores-label": "CPU ร‡ekirdekleri", + "cpu-frequency-description": "Mevcut CPU saat hฤฑzฤฑnฤฑ GHz cinsinden gรถrรผntรผle.", + "cpu-frequency-label": "CPU frekansฤฑnฤฑ gรถster", + "cpu-temperature-description": "Mevcut iลŸlemci sฤฑcaklฤฑk okumalarฤฑnฤฑ gรถsterir.", + "cpu-temperature-label": "ฤฐลŸlemci sฤฑcaklฤฑฤŸฤฑ", + "cpu-usage-description": "Mevcut iลŸlemci kullanฤฑm yรผzdesini gรถster.", + "cpu-usage-label": "ฤฐลŸlemci kullanฤฑmฤฑ", + "disk-path-description": "ฤฐzlenecek disk baฤŸlama noktasฤฑnฤฑ seรงin.", + "disk-path-label": "Disk yolu", + "gpu-temperature-description": "GPU sฤฑcaklฤฑk deฤŸerleri varsa gรถster.", + "load-average-description": "Sistem ortalama yรผkรผnรผ gรถster.", + "load-average-label": "Ortalama yรผk", + "memory-percentage-description": "Mutlak deฤŸerler yerine bellek kullanฤฑmฤฑnฤฑ yรผzde olarak gรถster.", + "memory-percentage-label": "Bellek yรผzde olarak", + "memory-usage-description": "Mevcut bellek kullanฤฑm bilgilerini gรถster.", + "memory-usage-label": "Bellek kullanฤฑmฤฑ", + "network-traffic-description": "AฤŸ yรผkleme ve indirme hฤฑzlarฤฑnฤฑ gรถster.", + "network-traffic-label": "AฤŸ trafiฤŸi", + "storage-as-percentage-description": "Disk alanฤฑnฤฑ mutlak deฤŸerler yerine yรผzde olarak gรถster.", + "storage-as-percentage-label": "Disk yรผzde olarak", + "storage-available-description": "Kullanฤฑlan miktar yerine ne kadar disk alanฤฑ olduฤŸunu gรถsterir.", + "storage-available-label": "Kullanฤฑlabilir disk alanฤฑ", + "storage-usage-description": "Disk alanฤฑ kullanฤฑm bilgilerini gรถster.", + "storage-usage-label": "Depolama kullanฤฑmฤฑ", + "swap-usage-description": "Takas belleฤŸi kullanฤฑmฤฑnฤฑ gรถster.", + "swap-usage-label": "Takas kullanฤฑmฤฑ", + "use-monospace-font-description": "Tekdรผze karakter geniลŸliฤŸi iรงin tek aralฤฑklฤฑ yazฤฑ tipini kullanฤฑn.", + "use-monospace-font-label": "Tek aralฤฑklฤฑ yazฤฑ tipi", + "use-padding-description": "Dรผzen kaymasฤฑnฤฑ รถnlemek iรงin metin deฤŸerlerini baลŸtaki boลŸluklarla doldurur.", + "use-padding-description-disabled-monospace-font": "Bu รถzellik iรงin tek aralฤฑklฤฑ bir yazฤฑ tipi gereklidir.", + "use-padding-description-disabled-vertical": "Dikey gรถrev รงubuฤŸu metin dolgusunu desteklemez.", + "use-padding-label": "Metin Dolgusu" + }, + "taskbar": { + "colorize-icons-description": "Gรถrev รงubuฤŸu simgelerine tema renklerini uygula.", + "hide-mode-description": "EลŸleลŸen pencere olmadฤฑฤŸฤฑnda araรง takฤฑmฤฑnฤฑn nasฤฑl davrandฤฑฤŸฤฑnฤฑ kontrol eder.", + "hide-mode-label": "Gizleme modu", + "icon-scale-description": "Gรถrev รงubuฤŸu simgeleri iรงin รถlรงeklendirme faktรถrรผnรผ ayarlar.", + "icon-scale-label": "Simge รถlรงeklendirme", + "max-width-description": "Gรถrev รงubuฤŸunun ekran geniลŸliฤŸine gรถre maksimum geniลŸliฤŸi yรผzdesi.", + "max-width-label": "Maksimum geniลŸlik", + "only-active-workspaces-description": "Sadece aktif รงalฤฑลŸma alanlarฤฑndan uygulamalarฤฑ gรถster.", + "only-active-workspaces-label": "Sadece aktif รงalฤฑลŸma alanlarฤฑndan", + "only-same-monitor-description": "ร‡ubuฤŸun bulunduฤŸu monitรถrdeki uygulamalarฤฑ gรถster.", + "only-same-monitor-label": "Aynฤฑ monitรถrden sadece", + "show-pinned-apps-description": "Dock'taki sabitlenmiลŸ uygulamalarฤฑ gรถrev รงubuฤŸunda gรถster.", + "show-pinned-apps-label": "SabitlenmiลŸ uygulamalarฤฑ gรถster", + "show-title-description": "Gรถrev รงubuฤŸunda pencere baลŸlฤฑklarฤฑnฤฑ gรถster.", + "show-title-description-disabled": "Dikey gรถrev รงubuฤŸu baลŸlฤฑk gรถstermeyi desteklemiyor.", + "show-title-label": "BaลŸlฤฑฤŸฤฑ gรถster", + "smart-width-description": "Girdi sayฤฑsฤฑna gรถre girdi geniลŸliฤŸini otomatik olarak ayarla.", + "smart-width-label": "Akฤฑllฤฑ geniลŸlik", + "title-width-description": "Gรถrev รงubuฤŸundaki pencere baลŸlฤฑklarฤฑnฤฑn geniลŸliฤŸini ayarla (piksel olarak).", + "title-width-label": "BaลŸlฤฑk geniลŸliฤŸi", + "title-width-reset-tooltip": "BaลŸlฤฑk geniลŸliฤŸini sฤฑfฤฑrla" + }, + "tray": { + "chevron-color-description": "ร‡ekmecenin ok simgesine tema renklerini uygula.", + "chevron-color-label": "Ok rengi", + "colorize-icons-description": "Tepsi simgelerine tema renklerini uygula.", + "colorize-icons-label": "Simgeleri renklendir", + "drawer-enabled-description": "EtkinleลŸtirildiฤŸinde, sabitlenmemiลŸ tepsi รถฤŸeleri bir รงekmece panelinde gรถsterilir.
Devre dฤฑลŸฤฑ bฤฑrakฤฑldฤฑฤŸฤฑnda, tรผm tepsi รถฤŸeleri satฤฑr iรงi gรถsterilir.", + "drawer-enabled-label": "ร‡ekmeceyi etkinleลŸtir", + "hide-passive-description": "EtkinleลŸtirildiฤŸinde, 'Pasif' durumdaki tepsi รถฤŸeleri gizlenir.", + "hide-passive-label": "Pasif รถฤŸeleri gizle" + }, + "volume": { + "display-mode-description": "Bu deฤŸerin nasฤฑl gรถrรผnmesini istediฤŸinizi seรงin." + }, + "workspace": { + "character-count-description": "ร‡alฤฑลŸma alanฤฑ adlarฤฑndan gรถsterilecek karakter sayฤฑsฤฑ (1-10).", + "character-count-label": "Karakter sayฤฑsฤฑ", + "empty-color-description": "BoลŸ Workspace'ler iรงin arka plan rengini ayarla.", + "empty-color-label": "BoลŸ รงalฤฑลŸma alanฤฑ rengi", + "enable-scrollwheel-description": "Fare tekerleฤŸi ile รงalฤฑลŸma alanlarฤฑ arasฤฑnda geรงiลŸ yapฤฑn.", + "enable-scrollwheel-label": "ร‡alฤฑลŸma alanlarฤฑ arasฤฑnda geรงiลŸ yapmak iรงin kaydฤฑrฤฑn", + "focused-color-description": "Odaklanฤฑlan Workspace iรงin arka plan rengini ayarla.", + "focused-color-label": "Odaklanฤฑlan รงalฤฑลŸma alanฤฑ rengi", + "follow-focused-screen-description": "ร‡ubuฤŸun bulunduฤŸu ekran yerine, ลŸu anda odaklanmฤฑลŸ ekrandaki รงalฤฑลŸma alanlarฤฑnฤฑ gรถster.", + "follow-focused-screen-label": "OdaklanmฤฑลŸ ekranฤฑ takip et", + "font-weight-description": "ร‡alฤฑลŸma alanฤฑ iรงindeki metin iรงin gรถrsel aฤŸฤฑrlฤฑฤŸฤฑ ayarlayฤฑn.", + "font-weight-label": "Yazฤฑ tipi aฤŸฤฑrlฤฑฤŸฤฑ", + "grouped-border-opacity-description": "ร‡alฤฑลŸma alanฤฑ kapsayฤฑcฤฑ kenarlฤฑklarฤฑnฤฑn opaklฤฑk dรผzeyini ayarlayฤฑn.", + "grouped-border-opacity-label": "Kenarlฤฑk opaklฤฑฤŸฤฑ", + "hide-unoccupied-description": "Penceresi olmayan รงalฤฑลŸma alanlarฤฑnฤฑ gรถsterme.", + "hide-unoccupied-label": "Dolu olmayanlarฤฑ gizle", + "label-mode-description": "ร‡alฤฑลŸma alanฤฑ etiketlerinin nasฤฑl gรถsterileceฤŸini seรงin.", + "label-mode-label": "Etiket Modu", + "occupied-color-description": "Dolu Workspaces iรงin arka plan rengini ayarla.", + "occupied-color-label": "Dolu รงalฤฑลŸma alanฤฑ rengi", + "pill-size-description": "ร‡alฤฑลŸma alanฤฑ haplarฤฑnฤฑn boyutunu ayarlayฤฑn (50%-100%).", + "pill-size-label": "Kapsรผl boyutu", + "reverse-scrolling-description": "Kaydฤฑrฤฑrken รงalฤฑลŸma alanฤฑ geรงiลŸ yรถnรผnรผ tersine รงevir.", + "reverse-scrolling-label": "Ters kaydฤฑrma", + "show-applications-description": "Her รงalฤฑลŸma alanฤฑnฤฑn iรงinde uygulama simgelerini gรถrรผntรผle.", + "show-applications-hover-description": "ร‡alฤฑลŸma alanฤฑnฤฑn รผzerine gelindiฤŸinde, her รงalฤฑลŸma alanฤฑnฤฑn iรงinde uygulama simgelerini gรถrรผntรผle.", + "show-applications-hover-label": "รœzerine gelindiฤŸinde uygulamalarฤฑ gรถster", + "show-applications-label": "Uygulamalarฤฑ gรถster", + "show-badge-description": "GruplandฤฑrฤฑlmฤฑลŸ modda รงalฤฑลŸma alanฤฑ numarasฤฑ iลŸaretini gรถster.", + "show-badge-label": "ร‡alฤฑลŸma alanฤฑ rozetini gรถster", + "show-labels-only-when-occupied-description": "Yalnฤฑzca pencere iรงeren รงalฤฑลŸma alanฤฑ etiketlerini gรถster.", + "show-labels-only-when-occupied-label": "Yalnฤฑzca dolu olduฤŸunda etiketleri gรถster", + "unfocused-icons-opacity-description": "OdaklanฤฑlmamฤฑลŸ uygulama simgelerinin opaklฤฑk dรผzeyini ayarlayฤฑn.", + "unfocused-icons-opacity-label": "Odak dฤฑลŸฤฑ simgelerin opaklฤฑฤŸฤฑ" + } + }, + "battery": { + "all-batteries": "Tรผm bataryalar (birleลŸik)", + "battery-health": "Pil saฤŸlฤฑฤŸฤฑ", + "battery-level": "Pil seviyesi", + "capacity-level": "Kapasite: {level}", + "charging-rate": "ลžarj oranฤฑ: {rate} W", + "discharging-rate": "DeลŸarj oranฤฑ: {rate} W", + "health": "SaฤŸlฤฑk: {percent}%", + "inhibit-idle-description": "Sistemi uyanฤฑk tutar.", + "no-battery-detected": "Pil tespit edilmedi", + "plugged-in": "Prize takฤฑlฤฑ", + "power-profile": "Gรผรง profili", + "time-left": "Kalan sรผre: {time}", + "time-until-full": "Dolma sรผresi: {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "Mevcut cihazlar", + "blocked": "Engellendi", + "connected-devices": "BaฤŸlฤฑ cihazlar", + "device-address": "Cihaz adresi", + "disabled": "Bluetooth devre dฤฑลŸฤฑ", + "discoverable": "KeลŸfedilebilir", + "enable-message": "Mevcut cihazlarฤฑ gรถrmek iรงin Bluetooth'u etkinleลŸtirin.", + "known-devices": "Bilinen cihazlar", + "no-devices": "Mevcut cihaz yok", + "paired-devices": "EลŸleลŸtirilmiลŸ cihazlar", + "pairing-mode": "Cihazฤฑnฤฑzฤฑn eลŸleลŸtirme modunda olduฤŸundan emin olun.", + "scanning": "Cihazlar tarฤฑnฤฑyor...", + "signal-text-excellent": "Sinyal: Mรผkemmel", + "signal-text-fair": "Sinyal: Adil", + "signal-text-good": "Sinyal: ฤฐyi", + "signal-text-poor": "Sinyal: Zayฤฑf", + "signal-text-unknown": "Sinyal: Bilinmiyor", + "signal-text-very-poor": "Sinyal: ร‡ok zayฤฑf" + } + }, + "changelog": { + "error": { + "fetch-failed": "DeฤŸiลŸiklik gรผnlรผฤŸรผ verileri yรผklenemedi. Lรผtfen daha sonra tekrar dene.", + "rate-limit": "GitHub sฤฑnฤฑrฤฑna ulaลŸฤฑldฤฑ. Lรผtfen birkaรง dakika sonra tekrar dene." + }, + "panel": { + "buttons-discord": "Discord sunucumuza katฤฑl", + "buttons-dismiss": "Tamam", + "buttons-feedback": "Geri bildirim verin", + "empty": "Sรผrรผm notlarฤฑ henรผz hazฤฑr deฤŸil.", + "highlight-title": "ร–ne รงฤฑkanlar", + "released": "{date} tarihinde yayฤฑmlandฤฑ", + "subtitle-fresh": "Noctaliaโ€™yฤฑ kurduฤŸun iรงin teลŸekkรผrler! Bu sรผrรผmde gelenler bunlar.", + "subtitle-updated": "{previousVersion} sรผrรผmรผnden gรผncellendi", + "title": "{version} sรผrรผmรผnde neler yeni", + "version": "Sรผrรผm {version}", + "version-new-user": "Yeni kurulum" + } + }, + "common": { + "actions": "Eylemler", + "active": "Aktif", + "add": "Ekle", + "appearance": "Gรถrรผnรผm", + "apply": "Uygula", + "auto-connect": "Otomatik baฤŸlan", + "automation": "Otomasyon", + "available": "Mevcut", + "back": "Geri", + "battery": "Pil", + "behavior": "DavranฤฑลŸ", + "bluetooth": "Bluetooth", + "brightness": "Parlaklฤฑk", + "browse": "Gรถz at", + "calendar": "Takvim", + "calendar-panel": "Takvim paneli", + "cancel": "ฤฐptal", + "cards": "Kartlar", + "charging": "ลžarj etme", + "clear": "Temizle", + "clipboard": "Panoya", + "close": "Kapat", + "color-muted": "Sessiz", + "colors": "Renkler", + "command": "Komut", + "confirm": "Onayla", + "connect": "BaฤŸlan", + "connected": "BaฤŸlฤฑ", + "connecting": "BaฤŸlanฤฑyor...", + "continue": "Devam Et", + "contributors": "Katkฤฑda bulunanlar", + "copied-to-clipboard": "Panoya kopyalandฤฑ", + "countdown": "Geri sayฤฑm", + "customize": "ร–zelleลŸtir", + "date": "Tarih", + "default": "Varsayฤฑlan", + "delete": "Sil", + "devices": "Cihazlar", + "disabled": "Devre dฤฑลŸฤฑ", + "discharging": "Taburcu etme", + "disconnect": "BaฤŸlantฤฑyฤฑ kes", + "disconnected": "BaฤŸlantฤฑ kesildi", + "disconnecting": "BaฤŸlantฤฑ kesiliyor...", + "display-mode": "Gรถrรผntรผ modu", + "documentation": "Dokรผmantasyon", + "download": "ฤฐndir", + "duration": "Sรผre", + "dysfunctional": "ฤฐลŸlevsiz", + "edit": "Dรผzenle", + "enabled": "EtkinleลŸtirildi", + "error": "Hata", + "ethernet": "Ethernet", + "events": "Etkinlikler", + "execute": "Yรผrรผt", + "faithful": "Sadฤฑk", + "focus": "Odaklanma", + "font-weight-bold": "Kalฤฑn", + "font-weight-light": "ฤฐnce", + "font-weight-medium": "Orta", + "font-weight-regular": "Normal", + "font-weight-semibold": "Yarฤฑ kalฤฑn", + "frequency": "Sฤฑklฤฑk", + "gateway": "Geรงit", + "general": "Genel", + "height": "Yรผkseklik", + "hibernate": "Hazฤฑrda beklet", + "history": "Tarih", + "icon": "Simge", + "idle": "BoลŸta", + "info": "Bilgi", + "input": "GiriลŸ", + "install": "Kurulum", + "installed": "Yรผklendi", + "interface": "Arayรผz", + "internet": "ฤฐnternet", + "keybind": "TuลŸ Atamasฤฑ", + "language": "Dil", + "loading": "Yรผkleniyor...", + "local": "Yerel", + "location": "Konum", + "lock": "Kilitle", + "logout": "ร‡ฤฑkฤฑลŸ yap", + "look": "Gรถrรผnรผm", + "margins": "Kenar boลŸluklarฤฑ", + "media": "Medya", + "media-player": "Medya oynatฤฑcฤฑ", + "memory": "Hafฤฑza", + "monitors": "Monitรถrler", + "move-down": "AลŸaฤŸฤฑ TaลŸฤฑ", + "move-up": "Yukarฤฑ taลŸฤฑ", + "network": "AฤŸ", + "next": "Sonraki", + "night-light": "Gece ฤฑลŸฤฑฤŸฤฑnฤฑ", + "no": "Hayฤฑr", + "no-results": "Sonuรง bulunamadฤฑ", + "no-summary": "ร–zet yok", + "none": "Hiรงbiri", + "not-found": "Bulunamadฤฑ", + "notifications": "Bildirimler", + "official": "Resmi", + "on-surface": "Yรผzeyde", + "output": "ร‡ฤฑktฤฑ", + "pair": "EลŸleลŸtir", + "paired": "EลŸleลŸtirildi", + "pairing": "EลŸleลŸtiriliyor...", + "panels": "Paneller", + "password": "ลžifre", + "pause": "Duraklat", + "performance": "Performans", + "pin": "Sabitle", + "play": "Oynat", + "polling": "Yoklama", + "position": "Pozisyon", + "previous": "ร–nceki", + "primary": "Birincil", + "random": "Rastgele", + "reboot": "Yeniden baลŸlat", + "reboot-to-uefi": "UEFI'ye yeniden baลŸlat", + "record": "Kaydฤฑ BaลŸlat", + "refresh": "Yenile", + "required": "(gerekli)", + "reset": "Sฤฑfฤฑrla", + "reset-to-default": "Varsayฤฑlanlara Sฤฑfฤฑrla", + "result-count": "{count} sonuรง", + "result-count-plural": "{count} sonuรงlar", + "resume": "Sรผrdรผr", + "save": "Kaydet", + "scanning": "Taranฤฑyor...", + "screen-corners": "Ekran kรถลŸeleri", + "search": "Arama", + "second": "{count} saniye", + "second-plural": "{count} saniye", + "secondary": "ฤฐkincil", + "security": "Gรผvenlik", + "select": "Seรง", + "select-color": "Renk Seรง", + "select-color-description": "Vurgu iรงin tema renklerini uygula.", + "select-icon-color": "Simge rengini seรงin", + "settings": "Ayarlar", + "shortcuts": "Kฤฑsayollar", + "shutdown": "Kapat", + "signal": "Sinyal", + "sound": "Ses", + "sources": "Kaynaklar", + "start": "BaลŸlat", + "stop": "Dur", + "supporters": "Destekรงiler", + "suspend": "Askฤฑya al", + "templates": "ลžablonlar", + "tertiary": "รœรงรผncรผl", + "test": "Test", + "thresholds": "EลŸikler", + "title": "BaลŸlฤฑk", + "toast": "Toast", + "trusted": "Gรผvenilir", + "uninstall": "Kaldฤฑr", + "unknown": "Bilinmiyor", + "unpair": "EลŸleลŸtirmeyi kaldฤฑr", + "unpin": "Sabitlemeyi kaldฤฑr", + "update": "Gรผncelleme", + "upload": "Yรผkle", + "username": "Kullanฤฑcฤฑ Adฤฑ", + "userspace-reboot": "Kullanฤฑcฤฑ alanฤฑnฤฑ yeniden baลŸlat", + "version": "Sรผrรผm", + "vibrant": "Canlฤฑ", + "visualizer": "GรถrselleลŸtirici", + "volume": "Hacim", + "volumes": "Hacimler", + "wallpaper": "Duvar kaฤŸฤฑdฤฑ", + "weather": "Hava durumu", + "weather-loading": "Hava durumu yรผkleniyor...", + "weather-no-location": "Hava durumunu gรถrmek iรงin Ayarlar'da bir konum belirleyin", + "week": "Hafta", + "widgets": "Araรงlar", + "width": "GeniลŸlik", + "wifi": "Wi-Fi", + "windows": "Pencereler", + "yes": "Evet" + }, + "control-center": { + "power-profile": { + "tooltip-action": "Gรผรง profili", + "tooltip-disabled": "Gรผรง profillerini kullanmak iรงin power-profiles-daemon kur" + }, + "wifi": { + "label-disconnected": "Kablosuz baฤŸlantฤฑ kesik", + "label-ethernet": "Ethernet" + } + }, + "display-modes": { + "always-hide": "Her zaman saklan", + "always-show": "Daima gรถster", + "force-open": "Zorla aรงmak", + "on-hover": "รœzerine gelindiฤŸinde" + }, + "hide-modes": { + "auto-hide": "Otomatik gizleme", + "hidden": "BoลŸ olduฤŸunda gizle", + "idle": "BoลŸta kalฤฑnca gizle", + "non-exclusive": "Mรผnhasฤฑr olmayan", + "transparent": "BoลŸken ลŸeffaf", + "visible": "Her zaman gรถrรผnรผr" + }, + "launcher": { + "categories": { + "all": "Tรผmรผ", + "audiovideo": "Ses ve gรถrรผntรผ", + "chat": "Sohbet", + "development": "GeliลŸtirme", + "education": "EฤŸitim", + "emoji-activity": "Aktivite", + "emoji-animals": "Hayvanlar", + "emoji-flags": "Bayraklar", + "emoji-food": "Yiyecek ve iรงecek", + "emoji-nature": "DoฤŸa", + "emoji-objects": "Nesneler", + "emoji-people": "ฤฐnsanlar ve vรผcut", + "emoji-recent": "Son kullanฤฑlanlar", + "emoji-symbols": "Semboller", + "emoji-travel": "Seyahat ve yerler", + "game": "Oyunlar", + "graphics": "Grafikler", + "misc": "ร‡eลŸitli", + "office": "Ofis", + "pinned": "Sabitlendi", + "system": "Sistem", + "webbrowser": "Tarayฤฑcฤฑ" + }, + "providers": { + "applications": "Uygulamalar", + "calculator": "Hesap makinesi", + "calculator-deprecated": ">calc komutu kullanฤฑmdan kaldฤฑrฤฑlmฤฑลŸtฤฑr ve yakฤฑnda kaldฤฑrฤฑlacaktฤฑr. Lรผtfen matematiksel ifadeleri doฤŸrudan aramaya yazฤฑn.", + "calculator-description": "Hesap makinesi โ€” matematiksel ifadeleri hesapla", + "calculator-enter-expression": "Matematiksel bir ifade girin", + "calculator-press-enter-to-copy": "Sonucu kopyalamak iรงin Enter'a basฤฑn", + "clipboard": "Panoya geรงmiลŸ", + "clipboard-clear-description": "Tรผm panoya geรงmiลŸini temizle", + "clipboard-clear-description-full": "Tรผm รถฤŸeleri panoya geรงmiลŸinden kaldฤฑr", + "clipboard-clear-history": "Panoya geรงmiลŸini temizle", + "clipboard-delete": "Panodaki รถฤŸeyi sil", + "clipboard-history-disabled": "Panoya geรงmiลŸi devre dฤฑลŸฤฑ", + "clipboard-history-disabled-description": "Panoya geรงmiลŸini ayarlarda etkinleลŸtir veya cliphist kur", + "clipboard-loading": "Panoya geรงmiลŸi yรผkleniyor...", + "clipboard-search-description": "Panoya geรงmiลŸini ara", + "command-description": "Kabuk komutlarฤฑnฤฑ รงalฤฑลŸtฤฑr", + "emoji": "Emoji seรงici", + "emoji-loading": "Emojiler yรผkleniyor...", + "emoji-loading-description": "Lรผtfen bekleyin", + "emoji-no-recent": "Henรผz yeni emoji yok", + "emoji-search-description": "Emoji arama ve kopyalama", + "settings-search-description": "Ayarlarฤฑ ara ve git", + "windows-search-description": "Aรงฤฑk pencereleri ara ve odakla" + } + }, + "lock-screen": { + "authenticating": "DoฤŸrulanฤฑyor...", + "password": "Parolanฤฑzฤฑ girin...", + "restart": "Yeniden baลŸlat", + "shut-down": "Kapat" + }, + "notifications": { + "panel": { + "click-to-collapse": "Daraltmak iรงin tฤฑklayฤฑn", + "click-to-expand": "GeniลŸletmek iรงin tฤฑklayฤฑn", + "description": "Bildirimler geldikรงe burada gรถrรผnecek.", + "no-notifications": "Bildirim yok" + }, + "range": { + "earlier": "ร–ncekiler", + "today": "Bugรผn", + "yesterday": "Dรผn" + }, + "time": { + "diff-d": "1 gรผn รถnce", + "diff-dd": "{diff} gรผn รถnce", + "diff-h": "1 saat รถnce", + "diff-hh": "{diff} saat รถnce", + "diff-m": "1 dakika รถnce", + "diff-mm": "{diff} dakika รถnce", + "now": "ลŸimdi" + } + }, + "options": { + "bar": { + "density-comfortable": "Rahat", + "density-compact": "Sฤฑkฤฑ", + "density-default": "Varsayฤฑlan", + "density-mini": "Ufak", + "density-spacious": "GeniลŸ", + "display-mode-always-visible": "Her Zaman Gรถrรผnรผr", + "type-floating": "Yรผzen", + "type-framed": "ร‡erรงeveli", + "type-simple": "Basit" + }, + "control-center": { + "quick-settings-style-classic": "Klasik", + "quick-settings-style-modern": "Modern" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "Konforlu", + "compact": "Kompakt", + "default": "Varsayฤฑlan" + }, + "launcher-view-mode": { + "grid": "Izgara", + "list": "Liste" + }, + "notification-density": { + "compact": "Kompakt", + "default": "Varsayฤฑlan" + }, + "scrolling-modes": { + "always": "Her zaman kaydฤฑr", + "hover": "รœzerine gelince kaydฤฑr", + "never": "Asla kaydฤฑrma" + }, + "session-menu-grid-layout": { + "grid": "Izgara", + "single-row": "Tek sฤฑra" + }, + "settings-panel-mode": { + "attached": "Panel รงubuฤŸa takฤฑlฤฑ", + "centered": "OrtalanmฤฑลŸ panel", + "window": "Ayrฤฑ pencere" + }, + "visualizer-types": { + "linear": "DoฤŸrusal", + "mirrored": "YansฤฑtฤฑlmฤฑลŸ", + "wave": "Dalga" + }, + "visualizer-visibility": { + "with-background": "Sadece arka planla" + }, + "workspace-labels": { + "index": "ฤฐndeks", + "index-and-name": "Dizin ve ad", + "name": "ฤฐsim" + } + }, + "panels": { + "about": { + "become-supporter": "Destekรงi ol", + "changelog": "DeฤŸiลŸiklik gรผnlรผฤŸรผnรผ gรถrรผntรผle", + "changelog-on-startup": "Gรผncellemede deฤŸiลŸiklik gรผnlรผฤŸรผnรผ gรถster", + "changelog-on-startup-desc": "Noctalia gรผncellendiฤŸinde deฤŸiลŸiklik gรผnlรผฤŸรผnรผ otomatik olarak gรถster.", + "contributors-description": "{count} harika katฤฑlฤฑmcฤฑmฤฑza teลŸekkรผrler!", + "contributors-description-plural": "{count} harika katฤฑlฤฑmcฤฑmฤฑza teลŸekkรผrler!", + "copy-info": "Bilgileri kopyala", + "debug-disabled": "Hata ayฤฑklama modu devre dฤฑลŸฤฑ bฤฑrakฤฑldฤฑ", + "debug-enabled": "Hata ayฤฑklama modu etkinleลŸtirildi", + "info-copied": "Bilgi panoya kopyalandฤฑ", + "noctalia-available": "Mevcut:", + "noctalia-desc": "Wayland iรงin dรผลŸรผnรผlmรผลŸ, Quickshell ile inลŸa edilmiลŸ ลŸฤฑk ve minimal masaรผstรผ kabuฤŸu.", + "noctalia-git-commit": "Git commit:", + "noctalia-installed-version": "Yรผklรผ sรผrรผm:", + "noctalia-latest-version": "En son sรผrรผm:", + "noctalia-qs-version": "Noctalia QS sรผrรผmรผ:", + "noctalia-title": "Noctalia kabuฤŸu", + "privacy-policy": "Gizlilik politikasฤฑ", + "support": "Bizi destekleyin", + "supporter-badge": "Destekรงi", + "supporters-desc": "Harika destekรงimize kocaman bir teลŸekkรผr!", + "supporters-desc-plural": "{count} harika destekรงimize kocaman teลŸekkรผrler!", + "supporters-loading": "Destekรงiler yรผkleniyor...", + "system-board": "Anakart:", + "system-cpu": "ฤฐลŸlemci:", + "system-disk": "Disk:", + "system-gpu": "GPU:", + "system-host": "Host:", + "system-install-hint": "Sistem bilgilerini gรถrรผntรผlemek iรงin fastfetch'i kurun", + "system-kernel": "ร‡ekirdek:", + "system-loading": "Sistem bilgileri yรผkleniyor...", + "system-memory": "Hafฤฑza:", + "system-monitor": "Monitรถr:", + "system-not-installed": "fastfetch kurulu deฤŸil", + "system-os": "ฤฐลŸletim sistemi:", + "system-packages": "Paketler:", + "system-product": "รœrรผn:", + "system-title": "Sistem Bilgisi", + "system-uptime": "ร‡alฤฑลŸma sรผresi:", + "system-wm": "WM:", + "telemetry-data-copied": "Telemetri verileri panoya kopyalandฤฑ.", + "telemetry-desc": "Anonim sistem bilgisi (ekran รงรถzรผnรผrlรผฤŸรผ, kompozitรถr, daฤŸฤฑtฤฑm) paylaลŸarak Noctalia'yฤฑ geliลŸtirmeye yardฤฑmcฤฑ olun. BaลŸlangฤฑรงta bir kez gรถnderilir, izleme yapฤฑlmaz, veriler 30 gรผn sonra otomatik olarak silinir.", + "telemetry-enabled": "Anonim sistem bilgisi gรถnder.", + "telemetry-show-data": "Verileri gรถrรผntรผle", + "telemetry-title": "Gizlilik", + "title": "Hakkฤฑnda", + "up-to-date": "Gรผncelsiniz!", + "update-available": "Gรผncelleme mevcut", + "view-commit": "GitHub'da taahhรผdรผ gรถrรผntรผle" + }, + "audio": { + "devices-desc": "Mevcut ses giriลŸ ve รงฤฑkฤฑลŸ cihazlarฤฑnฤฑ yapฤฑlandฤฑrฤฑn.", + "devices-input-device-description": "ฤฐstenilen ses giriลŸ cihazฤฑnฤฑ seรงin.", + "devices-input-device-label": "GiriลŸ cihazฤฑ", + "devices-output-device-description": "ฤฐstenilen ses รงฤฑkฤฑลŸ cihazฤฑnฤฑ seรงin.", + "devices-output-device-label": "ร‡ฤฑkฤฑลŸ cihazฤฑ", + "devices-title": "Ses cihazlarฤฑ", + "external-mixer-description": "Harici ses karฤฑลŸtฤฑrฤฑcฤฑsฤฑ รถzelliฤŸi etkinleลŸtirilirken baลŸlatฤฑlacak komutu veya uygulama yolunu girin.", + "external-mixer-label": "Harici ses karฤฑลŸtฤฑrฤฑcฤฑsฤฑ komutu", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "Tercih ettiฤŸiniz ve yoksayacaฤŸฤฑnฤฑz ortam uygulamalarฤฑnฤฑ ayarlayฤฑn.", + "media-excluded-player-description": "Sistemin yoksaymasฤฑnฤฑ istediฤŸiniz oynatฤฑcฤฑlar iรงin anahtar kelimeler ekleyin. Her anahtar kelime ayrฤฑ bir satฤฑrda olmalฤฑdฤฑr.", + "media-excluded-player-label": "DฤฑลŸlanan oynatฤฑcฤฑ", + "media-excluded-player-placeholder": "alt dize girin ve + tuลŸuna basฤฑn", + "media-frame-rate-description": "Daha yรผksek hฤฑzlar daha yumuลŸaktฤฑr ancak daha fazla kaynak kullanฤฑr.", + "media-frame-rate-label": "Kare hฤฑzฤฑ", + "media-primary-player-description": "Ana oynatฤฑcฤฑnฤฑzฤฑ belirlemek iรงin bir anahtar kelime girin.", + "media-primary-player-label": "Ana oynatฤฑcฤฑ", + "media-primary-player-placeholder": "รถrn. spotify, vlc, mpv", + "media-scrolling-speed-description": "BaลŸlฤฑฤŸฤฑn baลŸtan sona kaymasฤฑ iรงin saniye cinsinden sรผre.", + "media-scrolling-speed-label": "Kaydฤฑrma hฤฑzฤฑ", + "media-scrolling-title-description": "Uzun ortam baลŸlฤฑklarฤฑ iรงin sรผrekli kaydฤฑrmayฤฑ etkinleลŸtirin.", + "media-scrolling-title-label": "Kayan baลŸlฤฑk", + "media-title": "Ortam oynatฤฑcฤฑlarฤฑ", + "on-middle-clicked-description": "DรผฤŸmeye orta tฤฑklama yapฤฑldฤฑฤŸฤฑnda yรผrรผtรผlecek komut.", + "panel-applications-empty": "ลžu anda ses รงalan uygulama yok", + "spectrum-mirrored-description": "Spektrumu yansฤฑt, bรถylece dรผลŸรผk frekanslar merkezde olur. Kapalฤฑyken, dรผลŸรผk frekanslar solda ve yรผksek frekanslar saฤŸda olur.", + "spectrum-mirrored-label": "Spektrum aynalama", + "title": "Ses", + "visualizer-type-description": "Medya oynatma iรงin bir gรถrselleลŸtirme tรผrรผ seรงin.", + "visualizer-type-label": "GรถrselleลŸtirme tรผrรผ", + "volumes-desc": "Ses kontrollerini ve ses seviyelerini ayarlayฤฑn.", + "volumes-feedback-sound-file-description": "Ses seviyesi deฤŸiลŸtirilirken รงalฤฑnan ses dosyasฤฑnฤฑn yolu.", + "volumes-feedback-sound-file-label": "Ses geri bildirim ses dosyasฤฑ", + "volumes-feedback-sound-file-select-title": "Ses geri bildirimi ses dosyasฤฑ seรง", + "volumes-input-volume-description": "Mikrofon giriลŸ ses seviyesi.", + "volumes-mute-input-description": "Varsayฤฑlan ses giriลŸini (mikrofon) sessize al.", + "volumes-mute-input-label": "Ses giriลŸini sessize al", + "volumes-mute-output-description": "Sistemin ana ses รงฤฑkฤฑลŸฤฑnฤฑ sessize al.", + "volumes-mute-output-label": "Ses รงฤฑkฤฑลŸฤฑnฤฑ sessize al", + "volumes-output-volume-description": "Sistem genelindeki ses seviyesi.", + "volumes-step-size-description": "Ses deฤŸiลŸimleri iรงin adฤฑm boyutunu ayarlayฤฑn (tekerlek, klavye kฤฑsayollarฤฑ).", + "volumes-step-size-label": "Ses adฤฑm boyutu", + "volumes-volume-feedback-description": "Ses seviyesi ayarlanฤฑrken geri bildirim sesi รงal.", + "volumes-volume-feedback-label": "Ses dรผzeyi geri bildirim sesini รงal", + "volumes-volume-overdrive-description": "Sesi %100'รผn รผzerine รงฤฑkarmaya izin ver. Bazฤฑ donanฤฑmlar desteklemeyebilir.", + "volumes-volume-overdrive-label": "Ses aลŸฤฑmฤฑna izin ver" + }, + "bar": { + "appearance-auto-hide-delay-description": "Fare ayrฤฑldฤฑktan sonra Bar'ฤฑn gizlenmeden รถnceki sรผre.", + "appearance-auto-hide-delay-label": "Gizleme Gecikmesi", + "appearance-auto-hide-exclusive-note": "Not: Otomatik gizleme etkin olduฤŸunda รถzel bรถlge otomatik olarak devre dฤฑลŸฤฑ bฤฑrakฤฑlฤฑr.", + "appearance-auto-show-delay-description": "Fare kenara girdiฤŸinde รงubuฤŸun gรถrรผnmesinden รถnceki sรผre.", + "appearance-auto-show-delay-label": "Gรถsterme Gecikmesi", + "appearance-background-opacity-description": "ร‡ubuฤŸun arka plan opaklฤฑฤŸฤฑnฤฑ รถzel olarak ayarlayฤฑn.", + "appearance-background-opacity-label": "ร‡ubuk arka plan opaklฤฑฤŸฤฑ", + "appearance-capsule-color-description": "ร‡ubuk kapsรผlleri iรงin bir renk seรงin veya varsayฤฑlan yรผzey rengi iรงin hiรงbirini kullanmayฤฑn.", + "appearance-capsule-color-label": "Kapsรผl rengi", + "appearance-capsule-opacity-description": "Kapsรผl gรถsterildiฤŸinde araรง takฤฑmฤฑ arka planlarฤฑnฤฑn saydamlฤฑk seviyesini ayarla.", + "appearance-capsule-opacity-label": "Kapsรผl saydamlฤฑฤŸฤฑ", + "appearance-content-padding-description": "ร‡ubuk kenarlarฤฑ ile widget'lar arasฤฑndaki dolguyu ayarlayฤฑn.", + "appearance-content-padding-label": "ฤฐรงerik dolgusu", + "appearance-density-description": "Sฤฑkฤฑ veya geniลŸ bir gรถrรผnรผm iรงin araรง รงubuฤŸu dolgusunu ayarlayฤฑn.", + "appearance-density-label": "Araรง ร‡ubuฤŸu yoฤŸunluฤŸu", + "appearance-desc": "Araรง รงubuฤŸunun gรถrรผnรผmรผnรผ ve konumunu รถzelleลŸtirin.", + "appearance-display-mode-description": "ร‡ubuฤŸun ne zaman gรถrรผnรผr olacaฤŸฤฑnฤฑ seรงin.", + "appearance-enable-exclusion-zone-inset-description": "Hariรง tutma bรถlgesini 1 fiziksel piksel azaltฤฑn, bรถylece araรง รงubuฤŸu kenarฤฑnฤฑn altฤฑna tam oturan pencereler kusursuzca taลŸar.", + "appearance-enable-exclusion-zone-inset-label": "Girintili dฤฑลŸlama bรถlgesi", + "appearance-floating-description": "Araรง รงubuฤŸunu yรผzen bir 'kapsรผl' olarak gรถrรผntรผler. Not: Bu, ekran kรถลŸelerini kenarlara taลŸฤฑyacaktฤฑr.", + "appearance-floating-label": "Yรผzen araรง รงubuฤŸu", + "appearance-font-scale-description": "ร‡ubukta gรถrรผntรผlenen metin iรงin yazฤฑ tipi boyutu รถlรงeฤŸini ayarlayฤฑn.", + "appearance-font-scale-label": "Yazฤฑ tipi รถlรงeฤŸi", + "appearance-frame-radius": "ฤฐรง Yarฤฑรงap", + "appearance-frame-settings-description": "ร‡erรงeve kalฤฑnlฤฑฤŸฤฑnฤฑ ve iรง kรถลŸe yarฤฑรงapฤฑnฤฑ ayarlayฤฑn", + "appearance-frame-settings-label": "ร‡erรงeve Ayarlarฤฑ", + "appearance-frame-thickness": "Kalฤฑnlฤฑk", + "appearance-hide-on-overview-description": "Kompozitรถr รถnizlemesi aktif olduฤŸunda รงubuฤŸu gizle ve panelleri kapat.", + "appearance-hide-on-overview-label": "Genel bakฤฑลŸta รงubuฤŸu gizle", + "appearance-margins-description": "Yรผzen araรง รงubuฤŸunun etrafฤฑndaki kenar boลŸluklarฤฑnฤฑ ayarlayฤฑn.", + "appearance-margins-horizontal": "Yatay kenar boลŸluฤŸu", + "appearance-margins-vertical": "Dikey kenar boลŸluฤŸu", + "appearance-outer-corners-description": "ร‡ubuฤŸun dฤฑลŸa doฤŸru kavisli kรถลŸelerini gรถrรผntรผler.", + "appearance-outer-corners-label": "DฤฑลŸ kรถลŸeler", + "appearance-position-description": "Araรง รงubuฤŸunu ekranda nereye yerleลŸtireceฤŸinizi seรงin.", + "appearance-position-label": "Araรง รงubuฤŸu konumu", + "appearance-show-capsule-description": "Araรง takฤฑmฤฑ arka planlarฤฑnฤฑ gรถster.", + "appearance-show-capsule-label": "Kapsรผlรผ gรถster", + "appearance-show-on-workspace-switch-description": "ร‡alฤฑลŸma alanฤฑ deฤŸiลŸtiฤŸinde Bar'ฤฑ otomatik olarak kฤฑsa bir sรผre gรถster.", + "appearance-show-on-workspace-switch-label": "ร‡alฤฑลŸma alanฤฑ deฤŸiลŸtirildiฤŸinde รงubuฤŸu gรถster", + "appearance-show-outline-description": "Her widget'ฤฑn etrafฤฑnda gรถrรผnรผr bir kenarlฤฑk gรถrรผntรผler.", + "appearance-show-outline-label": "Araรง kutusu ana hatlarฤฑnฤฑ gรถster", + "appearance-type-description": "ร‡ubuk stilini seรงin: Basit, Yรผzen veya ร‡erรงeveli.", + "appearance-type-label": "ร‡ubuk tรผrรผ", + "appearance-use-separate-opacity-description": "ร‡ubuk arka planฤฑ iรงin ayrฤฑ bir opaklฤฑk deฤŸeri kullanmayฤฑ etkinleลŸtir.", + "appearance-use-separate-opacity-label": "Ayrฤฑ รงubuk opaklฤฑฤŸฤฑ kullan", + "appearance-widget-spacing-description": "ร‡ubuktaki her bir widget arasฤฑndaki boลŸluฤŸu ayarlayฤฑn.", + "appearance-widget-spacing-label": "Widget aralฤฑฤŸฤฑ", + "behavior-middle-click-command-description": "Orta tฤฑklamada รงalฤฑลŸtฤฑrฤฑlacak komut.", + "behavior-middle-click-command-label": "Orta Tฤฑklama Komutu", + "behavior-middle-click-command-placeholder": "niri msg action genel bakฤฑลŸฤฑ deฤŸiลŸtir", + "behavior-middle-click-description": "ร‡ubuฤŸun boลŸ alanlarฤฑnda orta tฤฑklamanฤฑn ne yapacaฤŸฤฑnฤฑ seรงin.", + "behavior-middle-click-follow-mouse-description": "Seรงilen orta tฤฑklama panelini imleรง konumunda aรง.", + "behavior-middle-click-follow-mouse-label": "Orta tฤฑklama fareyi takip etsin", + "behavior-middle-click-label": "ร‡ubuk orta tฤฑklama eylemi", + "behavior-right-click-command-description": "SaฤŸ tฤฑklamada รงalฤฑลŸtฤฑrฤฑlacak komut.", + "behavior-right-click-command-label": "SaฤŸ Tฤฑklama Komutu", + "behavior-right-click-command-placeholder": "notify-send \"SaฤŸ tฤฑklama\"", + "behavior-right-click-description": "ร‡ubuฤŸun boลŸ alanlarฤฑnda saฤŸ tฤฑklamanฤฑn ne yapacaฤŸฤฑnฤฑ seรงin.", + "behavior-right-click-follow-mouse-description": "Seรงili saฤŸ tฤฑk panelini imleรง konumunda aรง.", + "behavior-right-click-follow-mouse-label": "SaฤŸ tฤฑklama fareyi takip etsin", + "behavior-right-click-label": "ร‡ubuฤŸa saฤŸ tฤฑklama eylemi", + "behavior-wheel-wrap-description": "EtkinleลŸtirildiฤŸinde, kaydฤฑrma son รถฤŸeden ilk รถฤŸeye doฤŸru devam eder.", + "behavior-wheel-wrap-label": "Dรถngรผsel", + "behavior-workspace-scroll-description": "Fare tekerleฤŸinin รงubuฤŸun boลŸ alanlarฤฑnda ne yapacaฤŸฤฑnฤฑ seรงin.", + "behavior-workspace-scroll-label": "ร‡ubuk fare tekerleฤŸi eylemi", + "behavior-workspace-scroll-option-content": "ฤฐรงerik", + "behavior-workspace-scroll-option-workspace": "ร‡alฤฑลŸma Alanฤฑ", + "monitor-configure-widgets": "Araรงlarฤฑ yapฤฑlandฤฑr", + "monitor-override-settings": "Genel ayarlarฤฑ geรงersiz kฤฑl", + "monitor-override-settings-description": "Bu monitรถr iรงin รถzel ayarlarฤฑ kullan.", + "monitor-reset-all": "Hepsini sฤฑfฤฑrla", + "monitor-widgets-title": "Widget Yapฤฑlandฤฑrmasฤฑ - {monitor}", + "monitors-desc": "Araรง รงubuฤŸunu belirli ekranlarda gรถsterin. Hiรงbiri seรงilmezse varsayฤฑlan olarak tรผmรผnde gรถsterilir.", + "monitors-desc-new": "ร‡ubuฤŸun hangi monitรถrlerde gรถrรผntรผleneceฤŸini yapฤฑlandฤฑrฤฑn ve monitรถr baลŸฤฑna ayarlarฤฑ รถzelleลŸtirin.", + "monitors-title": "Ekranlar", + "title": "Araรง รงubuฤŸu", + "tray-blacklist-description": "Tepsi hariรง tutma kurallarฤฑ ekleyin, joker karakterleri destekler (*).", + "tray-blacklist-label": "Kara liste", + "tray-blacklist-placeholder": "รถrn. nm-applet, Fcitx*", + "tray-pin-application": "Uygulamayฤฑ sabitle", + "tray-unpin-application": "Sabitlemeyi kaldฤฑr", + "use-global-widgets": "Genel araรงlarฤฑ kullan", + "widgets-desc": "Araรง takฤฑmlarฤฑ sฤฑralarฤฑnฤฑ deฤŸiลŸtirmek iรงin sรผrรผkleyin. Araรง takฤฑmlarฤฑnฤฑ bรถlรผmler arasฤฑnda aktarmak veya kaldฤฑrmak iรงin saฤŸ tฤฑklama menรผsรผnรผ kullanฤฑn.", + "widgets-title": "Araรง takฤฑmlarฤฑnฤฑ konumlandฤฑrma" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "Duvar kaฤŸฤฑdฤฑnฤฑzdan renk ลŸemalarฤฑ oluลŸturun. Uyumlu bir tema oluลŸturmak iรงin renkleri otomatik olarak รงฤฑkarฤฑr.", + "color-source-use-wallpaper-colors-label": "Duvar kรขฤŸฤฑdฤฑ renklerini kullan", + "dark-mode-mode-description": "IลŸฤฑk ve koyu mod arasฤฑnda otomatik geรงiลŸi etkinleลŸtirir.", + "dark-mode-mode-label": "Koyu mod zamanlamasฤฑ", + "dark-mode-mode-manual": "El ile", + "dark-mode-mode-off": "Kapalฤฑ", + "dark-mode-switch-description": "Gece daha kolay gรถrรผntรผlemek iรงin daha koyu bir tema geรงiลŸ yapar.", + "delete-error-description": "{scheme} silinemedi", + "delete-error-title": "Silme baลŸarฤฑsฤฑz", + "delete-success-description": "{scheme} baลŸarฤฑyla silindi", + "delete-success-title": "Renk ลŸemasฤฑ silindi", + "download-button": "Daha fazla indir", + "download-downloading": "ฤฐndiriliyor...", + "download-empty": "Mevcut renk ลŸemasฤฑ yok", + "download-error-api-error": "API hatasฤฑ: {status}", + "download-error-description": "{scheme} indirilemedi", + "download-error-download-failed": "ฤฐndirme baลŸarฤฑsฤฑz, รงฤฑkฤฑลŸ kodu: {code}", + "download-error-invalid-response": "Geรงersiz API yanฤฑt formatฤฑ", + "download-error-no-files": "ลžema iรงin dosya bulunamadฤฑ", + "download-error-parse-failed": "API yanฤฑtฤฑ ayrฤฑลŸtฤฑrฤฑlamadฤฑ: {error}", + "download-error-rate-limit": "GitHub API hฤฑz sฤฑnฤฑrฤฑ aลŸฤฑldฤฑ", + "download-error-title": "ฤฐndirme baลŸarฤฑsฤฑz", + "download-fetching": "Mevcut renk ลŸemalarฤฑ alฤฑnฤฑyor...", + "download-success-description": "{scheme} baลŸarฤฑyla indirildi", + "download-success-title": "Renk ลŸemasฤฑ indirildi", + "download-title": "Renk ลŸemalarฤฑnฤฑ indir", + "method-description": { + "content": "Kaynak iรงeriฤŸin gerรงek renkleriyle yakฤฑndan eลŸleลŸen, yรผksek doฤŸruluklu renk รงฤฑkarma รถzelliฤŸine sahip Material Design ลŸemasฤฑ.", + "dysfunctional": "Faithful gibi, ancak ikinci en baskฤฑn renk ailesini birincil olarak seรงer.", + "faithful": "Uyumlu bir palet oluลŸtururken kaynak rengine yakฤฑn kalmaya รงalฤฑลŸฤฑr.", + "fruit-salad": "ร‡eลŸitli ve farklฤฑ tonlara sahip, eฤŸlenceli, canlฤฑ bir palet รผreten Material Design ลŸemasฤฑ.", + "monochrome": "Minimal kromatik iรงerikli tek tonlu gri tonlamalฤฑ Material Design ลŸemasฤฑ.", + "muted": "Sakin ve mรผtevazฤฑ bir gรถrรผnรผm iรงin daha yumuลŸak, doygunluฤŸu azaltฤฑlmฤฑลŸ tonlar oluลŸturur.", + "rainbow": "Maksimum renk รงeลŸitliliฤŸi iรงin tรผm spektrumda renkler รผreten Materyal Tasarฤฑmฤฑ ลŸemasฤฑ.", + "tonal-spot": "Varsayฤฑlan Material Design algoritmasฤฑ. Kaynak rengin ton varyasyonlarฤฑnฤฑ kullanarak dengeli paletler oluลŸturur.", + "vibrant": "Canlฤฑ tonlara sahip, cesur, gรถz alฤฑcฤฑ paletler iรงin renk doygunluฤŸunu en รผst dรผzeye รงฤฑkarฤฑr." + }, + "predefined-desc": "ร–nceden tanฤฑmlanmฤฑลŸ renk ลŸemalarฤฑ koleksiyonundan seรงim yapฤฑn.", + "predefined-generate-templates-label": "ร–nceden tanฤฑmlanmฤฑลŸ ลŸemalar iรงin ลŸablonlar oluลŸturun", + "predefined-title": "ร–nceden tanฤฑmlanmฤฑลŸ renk ลŸemalarฤฑ", + "sync-gsettings-description": "Sistem temasฤฑnฤฑ aktif aรงฤฑk veya koyu varyantla eลŸleลŸtir.", + "sync-gsettings-label": "Sistem temasฤฑnฤฑ senkronize et", + "templates-desc": "Renkleri harici uygulamalara uygula.", + "templates-filter-description": "Belirli bir kategoriden ลŸablonlarฤฑ gรถster.", + "templates-filter-label": "Kategoriye gรถre filtrele", + "templates-misc-description": "Kendi ลŸablonlarฤฑnฤฑzฤฑ oluลŸturun.", + "templates-misc-label": "GeliลŸmiลŸ", + "templates-misc-user-templates-description": "Yalnฤฑzca ne yaptฤฑฤŸฤฑnฤฑzฤฑ biliyorsanฤฑz etkinleลŸtirin โ€” รงevrimiรงi belgelerimize bakฤฑn.", + "templates-misc-user-templates-label": "Kullanฤฑcฤฑ ลŸablonlarฤฑnฤฑ etkinleลŸtir", + "templates-none-detected": "Hiรงbiri algฤฑlanmadฤฑ", + "templates-write-path": "Yazฤฑyor: {filepath}", + "title": "Renk ลŸemasฤฑ", + "wallpaper-method-description": "Favori palet oluลŸturma yรถnteminizi seรงin.", + "wallpaper-method-label": "Palet oluลŸturma yรถntemi", + "wallpaper-monitor-source-description": "Duvar kaฤŸฤฑdฤฑ renklerini รงฤฑkarmak iรงin hangi monitรถrรผn kullanฤฑlacaฤŸฤฑnฤฑ seรงin.", + "wallpaper-monitor-source-label": "Renk oluลŸturma kaynaฤŸฤฑ" + }, + "connections": { + "authentication-required": "Kimlik doฤŸrulama gerekli", + "bluetooth-auto-connect-description": "Bluetooth etkinleลŸtirildiฤŸinde gรผvenilir eลŸleลŸtirilmiลŸ cihazlara otomatik olarak baฤŸlan.", + "bluetooth-auto-connect-label": "EลŸleลŸtirilmiลŸ cihazlarฤฑ otomatik baฤŸla", + "bluetooth-devices-unnamed": "AdlandฤฑrฤฑlmamฤฑลŸ cihazlar gรถsterilmez.", + "bluetooth-discoverable": "Bu ayarlar sekmesi aรงฤฑkken bu cihaz {hostName} olarak keลŸfedilebilir.", + "bluetooth-rssi-polling-description": "bluetoothctl aracฤฑlฤฑฤŸฤฑyla baฤŸlฤฑ cihazlarฤฑn RSSI deฤŸerini periyodik olarak รถrnekler. Tรผm cihazlar iรงin mevcut olmayabilir; etkinleลŸtirildiฤŸinde minimum kaynak kullanฤฑr.", + "bluetooth-rssi-polling-interval-description": "BaฤŸlฤฑ cihazlar iรงin sinyal gรผcรผnรผn ne sฤฑklฤฑkta gรผncelleneceฤŸini yapฤฑlandฤฑrฤฑn.", + "bluetooth-rssi-polling-interval-label": "Yoklama AralฤฑฤŸฤฑ", + "bluetooth-rssi-polling-label": "Bluetooth sinyal yoklamasฤฑ", + "disable-discoverability-description": "Cihazฤฑnฤฑzฤฑ yakฤฑndaki Bluetooth cihazlarฤฑndan gizleyin.", + "disable-discoverability-label": "Cihaz gรถrรผnรผrlรผฤŸรผnรผ devre dฤฑลŸฤฑ bฤฑrak", + "hide-unnamed-devices-description": "Yalnฤฑzca Bluetooth adresleri olarak gรถrรผnen cihazlarฤฑ gizle.", + "hide-unnamed-devices-label": "ฤฐsimsiz cihazlarฤฑ gizle", + "pin-instructions": "Lรผtfen cihazฤฑnฤฑzda gรถsterilen PIN kodunu girin.", + "title": "BaฤŸlantฤฑlar", + "wifi-header-text": "Mevcut aฤŸlarฤฑ gรถrmek ve internete baฤŸlanmak iรงin Wi-Fi kullanฤฑn." + }, + "control-center": { + "cards-desc": "Kontrol merkezinde hangi kontrollerin ve hangi sฤฑrada gรถrรผneceฤŸini รถzelleลŸtirin.", + "desc": "Kontrol merkezi paneli konumunu ve davranฤฑลŸฤฑnฤฑ yapฤฑlandฤฑrฤฑn.", + "open-at-mouse-description": "Bar'ฤฑn boลŸ bir alanฤฑna saฤŸ tฤฑkladฤฑฤŸฤฑnฤฑzda, Kontrol Merkezi'ni imlecin yakฤฑnฤฑnda aรงฤฑn. Devre dฤฑลŸฤฑ bฤฑrakฤฑldฤฑฤŸฤฑnda, bunun yerine yukarฤฑdaki konum ayarฤฑ kullanฤฑlฤฑr.", + "open-at-mouse-label": "SaฤŸ tฤฑklamada imleรง konumunda aรง", + "position-description": "Kontrol merkezi paneli aรงฤฑldฤฑฤŸฤฑnda nerede gรถrรผneceฤŸini seรงin.", + "shortcuts-custom-button-command-description": "DรผฤŸmeye tฤฑklandฤฑฤŸฤฑnda รงalฤฑลŸtฤฑrฤฑlacak komut.", + "shortcuts-custom-button-enable-on-state-logic-description": "Bir kontrol komutuna baฤŸlฤฑ olarak ikinci bir simgeyi ve 'etkin' durumunu etkinleลŸtir.", + "shortcuts-custom-button-enable-on-state-logic-label": "Aรงฤฑk durum mantฤฑฤŸฤฑnฤฑ etkinleลŸtir", + "shortcuts-custom-button-icon-description": "Kรผtรผphaneden bir simge seรงin.", + "shortcuts-custom-button-on-clicked-label": "Sol tฤฑk komutu", + "shortcuts-custom-button-on-middle-clicked-description": "DรผฤŸmeye orta tฤฑklama yapฤฑldฤฑฤŸฤฑnda รงalฤฑลŸtฤฑrฤฑlacak komut.", + "shortcuts-custom-button-on-state-command-description": "DรผฤŸmenin 'aรงฤฑk' durumda olup olmadฤฑฤŸฤฑnฤฑ kontrol etmek iรงin รงalฤฑลŸtฤฑrฤฑlacak komut. Aรงฤฑk iรงin 0, kapalฤฑ iรงin sฤฑfฤฑr olmayan bir deฤŸer dรถndรผrรผr.", + "shortcuts-custom-button-on-state-command-label": "Durum kontrol komutu", + "shortcuts-custom-button-on-state-icon-description": "DรผฤŸme 'aรงฤฑk' durumdayken kullanฤฑlan simge.", + "shortcuts-custom-button-on-state-icon-label": "Durum simgesinde", + "shortcuts-custom-button-state-checks-add": "Durum kontrolรผ ekle", + "shortcuts-custom-button-state-checks-command": "Bu durum kontrolรผ iรงin yรผrรผtรผlecek komut", + "shortcuts-custom-button-state-checks-label": "Durum kontrolleri", + "shortcuts-custom-button-state-checks-remove": "Kaldฤฑr", + "shortcuts-custom-button-tooltip-description": "DรผฤŸme รผzerine gelindiฤŸinde gรถsterilecek araรง ipucu.", + "shortcuts-custom-button-tooltip-label": "Araรง ipucu", + "shortcuts-title": "Kฤฑsayol araรง takฤฑmlarฤฑ", + "system-monitor-disk-path-description": "Kontrol merkezindeki sistem izleyici kartฤฑnฤฑn izlemesi gereken disk baฤŸlama noktasฤฑnฤฑ seรงin.", + "system-monitor-disk-path-label": "Sistem izleyici disk yolu", + "title": "Kontrol merkezi" + }, + "desktop-widgets": { + "clock-enabled-description": "Masaรผstรผnde bir saat aracฤฑ gรถster.", + "clock-enabled-label": "Saat widget'ฤฑnฤฑ etkinleลŸtir", + "clock-format-label": "Biรงim", + "clock-height-description": "Saat aracฤฑ yรผksekliฤŸi piksel cinsinden.", + "clock-height-label": "Yรผkseklik", + "clock-minimal-mode-description": "Zaman ve tarih iรงeren minimal bir saat gรถrรผntรผsรผ kullanฤฑn.", + "clock-minimal-mode-label": "Minimal mod", + "clock-rounded-corners-description": "Widget arka planฤฑ iรงin yuvarlatฤฑlmฤฑลŸ kรถลŸeler kullanฤฑn.", + "clock-rounded-corners-label": "YuvarlatฤฑlmฤฑลŸ kรถลŸeler", + "clock-show-background-description": "Saat widget'ฤฑ iรงin arka plan kabฤฑnฤฑ gรถster.", + "clock-show-background-label": "Arka planฤฑ gรถster", + "clock-show-date-description": "Saati gรถsterdikten sonra, mevcut tarihi de gรถster.", + "clock-show-date-label": "Tarihi gรถster", + "clock-show-seconds-description": "Saniyeleri saatte gรถster.", + "clock-show-seconds-label": "Saniyeleri gรถster", + "clock-style-analog": "Analog", + "clock-style-binary": "ฤฐkili", + "clock-style-description": "Saat gรถrรผntรผleme stilini seรงin.", + "clock-style-digital": "Dijital", + "clock-style-label": "Saat stili", + "clock-style-minimal": "Minimal", + "clock-width-description": "Saat widget'ฤฑnฤฑn piksel cinsinden geniลŸliฤŸi.", + "cpu-intensive-note": "! ile iลŸaretlenmiลŸ widgetlar daha fazla CPU kullanฤฑr ve dikkatli bir ลŸekilde etkinleลŸtirilmelidir.", + "edit-mode-button-label": "Dรผzenleme moduna gir", + "edit-mode-controls-explanation": "Sol tฤฑklayฤฑp sรผrรผkleyin: BileลŸeni taลŸฤฑyฤฑn veya yeniden boyutlandฤฑrฤฑn.\nSaฤŸ tฤฑklayฤฑn: BaฤŸlam menรผsรผ seรงeneklerini aรงฤฑn.", + "edit-mode-description": "Masaรผstรผ araรงlarฤฑnฤฑ taลŸฤฑmak ve yeniden konumlandฤฑrmak iรงin dรผzenleme modunu etkinleลŸtirin. EtkinleลŸtirildiฤŸinde, araรงlar bir sรผrรผkleme ana hattฤฑ gรถsterir ve yeniden konumlandฤฑrฤฑlabilir.", + "edit-mode-exit-button": "Dรผzenleme modundan รงฤฑk", + "edit-mode-grid-snap-label": "Izgaraya hizala", + "edit-mode-grid-snap-scale-label": "Izgara hizalama รถlรงeฤŸi", + "edit-mode-label": "Dรผzenleme modu", + "enabled-description": "Masaรผstรผ araรงlarฤฑnฤฑ tamamen etkinleลŸtir veya devre dฤฑลŸฤฑ bฤฑrak.", + "enabled-label": "Masaรผstรผ araรงlarฤฑnฤฑ etkinleลŸtir", + "general-desc": "Masaรผstรผnรผzde gรถrรผnen araรงlarฤฑ yapฤฑlandฤฑrฤฑn.", + "general-title": "Masaรผstรผ araรงlarฤฑ", + "media-player-enabled-description": "Masaรผstรผnde bir medya oynatฤฑcฤฑ aracฤฑ gรถster.", + "media-player-enabled-label": "Medya oynatฤฑcฤฑ widget'ฤฑnฤฑ etkinleลŸtir", + "media-player-rounded-corners-description": "Widget kenarlarฤฑnda yuvarlak kรถลŸeleri etkinleลŸtir.", + "media-player-show-album-art-description": "Albรผm kapaฤŸฤฑnฤฑ ve parรงa bilgilerini (baลŸlฤฑk ve sanatรงฤฑ) gรถster.", + "media-player-show-album-art-label": "Albรผm kapaฤŸฤฑnฤฑ ve baลŸlฤฑฤŸฤฑnฤฑ gรถster", + "media-player-show-background-description": "Medya oynatฤฑcฤฑ widget'ฤฑ iรงin arka plan konteynerini gรถster.", + "media-player-show-buttons-description": "Medya oynatฤฑcฤฑ widget'ฤฑnda medya kontrol dรผฤŸmelerini (oynat/durdur, รถnceki, sonraki) gรถster.", + "media-player-show-buttons-label": "DรผฤŸmeleri gรถster", + "media-player-show-visualizer-description": "Ses gรถrselleลŸtirici katmanฤฑnฤฑ gรถster.", + "media-player-visualizer-type-description": "Masaรผstรผ medya oynatฤฑcฤฑ arka planฤฑ iรงin bir gรถrselleลŸtirme tรผrรผ seรงin.", + "overview-enabled-description": "Kompozitรถr genel gรถrรผnรผmรผ aรงฤฑkken masaรผstรผ araรง takฤฑmlarฤฑnฤฑ gรถster.", + "overview-enabled-label": "Genel bakฤฑลŸta gรถster", + "system-stat-layout-bottom": "Alt", + "system-stat-layout-description": "Efsanenin grafiฤŸe gรถre nasฤฑl gรถrรผntรผleneceฤŸini seรงin.", + "system-stat-layout-label": "Dรผzen", + "system-stat-layout-side": "Taraf", + "system-stat-rounded-corners-description": "Widget arka planฤฑ iรงin yuvarlatฤฑlmฤฑลŸ kรถลŸeler kullan.", + "system-stat-rounded-corners-label": "YuvarlatฤฑlmฤฑลŸ KรถลŸeler", + "system-stat-show-background-description": "Sistem istatistik Widget'ฤฑ iรงin arka plan kapsayฤฑcฤฑsฤฑnฤฑ gรถster.", + "system-stat-show-background-label": "Arka planฤฑ gรถster", + "system-stat-stat-type-description": "Hangi sistem istatistiฤŸini gรถrรผntรผleyeceฤŸini seรง.", + "system-stat-stat-type-label": "ฤฐstatistik Tรผrรผ", + "title": "Masaรผstรผ araรงlarฤฑ", + "weather-enabled-description": "Masaรผstรผnde bir hava durumu aracฤฑ gรถster.", + "weather-enabled-label": "Hava durumu araรง kutusunu etkinleลŸtir", + "weather-show-background-description": "Hava durumu widget'ฤฑ iรงin arka plan konteynerini gรถster." + }, + "display": { + "monitors-backlight-device-auto-option": "Varsayฤฑlan", + "monitors-backlight-device-description": "Bu รงฤฑkฤฑลŸ iรงin bir arka ฤฑลŸฤฑk cihazฤฑ seรงin.", + "monitors-backlight-device-label": "Arka IลŸฤฑk Cihazฤฑ", + "monitors-brightness-step-description": "Parlaklฤฑk deฤŸiลŸimleri iรงin adฤฑm boyutunu ayarlayฤฑn (tekerlek ve klavye kฤฑsayollarฤฑ).", + "monitors-brightness-step-label": "Parlaklฤฑk adฤฑm boyutu", + "monitors-brightness-unavailable-ddc-disabled": "Parlaklฤฑk kontrolรผ kullanฤฑlamฤฑyor. Bu ekranฤฑn parlaklฤฑฤŸฤฑnฤฑ kontrol etmek iรงin \"Harici parlaklฤฑk desteฤŸi\"ni etkinleลŸtirin.", + "monitors-brightness-unavailable-generic": "Bu ekran iรงin parlaklฤฑk kontrolรผ mevcut deฤŸil.", + "monitors-desc": "Her ekran iรงin parlaklฤฑk ayarlarฤฑnฤฑ yapฤฑn.", + "monitors-enforce-minimum-description": "Bazฤฑ ekranlarda %0 parlaklฤฑkta arka ฤฑลŸฤฑฤŸฤฑn tamamen kapanmasฤฑ sorununu รงรถzer.", + "monitors-enforce-minimum-label": "Minimum parlaklฤฑฤŸฤฑ zorla (%1)", + "monitors-external-brightness-description": "Harici ekranlarda parlaklฤฑฤŸฤฑ DDC/CI protokolรผ รผzerinden kontrol etmek iรงin DDCUtil desteฤŸini etkinleลŸtir.", + "monitors-external-brightness-label": "Harici parlaklฤฑk desteฤŸi", + "monitors-global-brightness-description": "Tรผm monitรถrlerin parlaklฤฑฤŸฤฑnฤฑ aynฤฑ anda ayarla.", + "monitors-global-brightness-label": "Tรผm monitรถrler", + "monitors-title": "Ekran baลŸฤฑna ayarlar", + "night-light-auto-schedule-description": "{location} konumunda gรผn batฤฑmฤฑ ve gรผn doฤŸumu zamanฤฑna gรถre โ€” รถnerilen.", + "night-light-auto-schedule-label": "Otomatik zamanlama", + "night-light-desc": "Daha iyi uyumanฤฑza ve gรถz yorgunluฤŸunu azaltmanฤฑza yardฤฑmcฤฑ olmak iรงin mavi ฤฑลŸฤฑk yayฤฑlฤฑmฤฑnฤฑ azaltฤฑn.", + "night-light-enable-description": "Mavi ฤฑลŸฤฑk yayฤฑlฤฑmฤฑnฤฑ azaltmak iรงin sฤฑcak renk filtresi uygulayฤฑn.", + "night-light-enable-label": "Gece ฤฑลŸฤฑฤŸฤฑnฤฑ etkinleลŸtir", + "night-light-force-activation-description": "Zamanlamayฤฑ yoksayar ve gece filtresini hemen uygular.", + "night-light-force-activation-label": "Zorla etkinleลŸtir", + "night-light-manual-schedule-description": "Gรผn doฤŸumu ve gรผn batฤฑmฤฑ iรงin รถzel zamanlar ayarlayฤฑn.", + "night-light-manual-schedule-label": "Manuel zamanlama", + "night-light-manual-schedule-select-start": "BaลŸlangฤฑรง zamanฤฑnฤฑ seรง", + "night-light-manual-schedule-select-stop": "BitiลŸ zamanฤฑnฤฑ seรง", + "night-light-manual-schedule-sunrise": "Gรผn doฤŸumu zamanฤฑ", + "night-light-manual-schedule-sunset": "Gรผn batฤฑmฤฑ zamanฤฑ", + "night-light-temperature-day": "Gรผndรผz", + "night-light-temperature-day-description": "Gรผndรผz saatlerindeki renk sฤฑcaklฤฑฤŸฤฑnฤฑ kontrol eder.", + "night-light-temperature-description": "Gece ve gรผndรผz iรงin renk sฤฑcaklฤฑฤŸฤฑnฤฑ ayarlayฤฑn.", + "night-light-temperature-label": "Renk sฤฑcaklฤฑฤŸฤฑ", + "night-light-temperature-night": "Gece", + "night-light-temperature-night-description": "Gece saatlerindeki renk sฤฑcaklฤฑฤŸฤฑnฤฑ kontrol eder.", + "title": "Gรถrรผntรผ" + }, + "dock": { + "appearance-background-opacity-description": "Dockโ€™un arka plan opaklฤฑฤŸฤฑnฤฑ ayarlayฤฑn.", + "appearance-border-radius-description": "Dockโ€™un kenar yarฤฑรงapฤฑnฤฑ ayarlayฤฑn.", + "appearance-border-radius-label": "Kenar yarฤฑรงapฤฑ", + "appearance-colorize-icons-description": "Dock uygulama simgelerine tema renklerini uygulayฤฑn (sadece odaklanฤฑlmamฤฑลŸ uygulamalar).", + "appearance-colorize-icons-label": "Simgeleri renklendir", + "appearance-dead-opacity-description": "ร‡alฤฑลŸmayan uygulama simgelerinin opaklฤฑฤŸฤฑnฤฑ ayarlayฤฑn.", + "appearance-dead-opacity-label": "ร–lรผ opaklฤฑk", + "appearance-desc": "Dockโ€™un davranฤฑลŸฤฑnฤฑ ve gรถrรผnรผmรผnรผ รถzelleลŸtirin.", + "appearance-display-auto-hide": "Otomatik gizle", + "appearance-display-description": "Dockโ€™un nasฤฑl davranacaฤŸฤฑnฤฑ seรงin.", + "appearance-display-exclusive": "ร–zel", + "appearance-dock-indicator-description": "Dock gizlendiฤŸinde kรผรงรผk bir gรถsterge gรถster.", + "appearance-dock-indicator-label": "Dock gรถstergesi", + "appearance-floating-distance-description": "Dock ile ekran kenarฤฑ arasฤฑndaki mesafeyi ayarlayฤฑn.", + "appearance-floating-distance-label": "Dock yรผzen mesafesi", + "appearance-group-apps-description": "Aynฤฑ uygulamanฤฑn birden fazla penceresini tek bir Dock giriลŸinde grupla.", + "appearance-group-apps-label": "Aynฤฑ uygulamalarฤฑ grupla", + "appearance-group-click-action-cycle": "Pencereler arasฤฑnda geรงiลŸ yap", + "appearance-group-click-action-description": "GruplandฤฑrฤฑlmฤฑลŸ uygulamalar iรงin sol tฤฑklamanฤฑn ne yapacaฤŸฤฑnฤฑ seรงin.", + "appearance-group-click-action-label": "GruplandฤฑrฤฑlmฤฑลŸ Uygulama Tฤฑklama Eylemi", + "appearance-group-click-action-list": "Pencere listesini aรง", + "appearance-group-context-menu-mode-description": "GruplandฤฑrฤฑlmฤฑลŸ uygulamalar iรงin baฤŸlam menรผsรผnรผn nasฤฑl davranacaฤŸฤฑnฤฑ seรงin.", + "appearance-group-context-menu-mode-extended": "GeniลŸletilmiลŸ", + "appearance-group-context-menu-mode-label": "GruplandฤฑrฤฑlmฤฑลŸ uygulama menรผsรผ modu", + "appearance-group-context-menu-mode-list": "Pencere listesi", + "appearance-group-indicator-style-description": "GruplandฤฑrฤฑlmฤฑลŸ รงalฤฑลŸan gรถstergelerin odaklanmฤฑลŸ pencere durumunu nasฤฑl gรถrรผntรผleyeceฤŸini seรงin.", + "appearance-group-indicator-style-dots": "Noktalar", + "appearance-group-indicator-style-label": "GruplandฤฑrฤฑlmฤฑลŸ gรถsterge stili", + "appearance-group-indicator-style-number": "Sayฤฑ", + "appearance-hide-show-speed-description": "Dockโ€™un gizle/gรถster animasyonunun hฤฑzฤฑnฤฑ ayarlayฤฑn.", + "appearance-hide-show-speed-label": "Gizle/gรถster hฤฑzฤฑ", + "appearance-icon-size-description": "Dockโ€™un genel boyutunu ayarlayฤฑn.", + "appearance-icon-size-label": "Dock boyutu", + "appearance-inactive-indicators-description": "Tรผm uygulamalar iรงin, sadece etkin olan iรงin deฤŸil, gรถsterge haplarฤฑnฤฑ gรถster.", + "appearance-inactive-indicators-label": "ร‡alฤฑลŸan gรถstergeler", + "appearance-indicator-color-description": "Gizli dock gรถstergesinin rengini seรงin.", + "appearance-indicator-color-label": "Gรถsterge rengi", + "appearance-indicator-opacity-description": "Gizli dock gรถstergesinin opaklฤฑฤŸฤฑnฤฑ ayarlayฤฑn.", + "appearance-indicator-opacity-label": "Gรถsterge opaklฤฑฤŸฤฑ", + "appearance-indicator-thickness-description": "Daha kalฤฑn bir gizli dock gรถstergesi kullanฤฑn (3 px yerine 6 px).", + "appearance-indicator-thickness-label": "Daha kalฤฑn gรถsterge", + "appearance-launcher-icon-description": "Dock Launcher iรงin kullanฤฑlan simgeyi seรงin.", + "appearance-launcher-icon-label": "BaลŸlatฤฑcฤฑ Simgesi", + "appearance-launcher-position-description": "BaลŸlatฤฑcฤฑ simgesinin dock'ta nerede gรถrรผneceฤŸini seรงin.", + "appearance-launcher-position-end": "Son", + "appearance-launcher-position-label": "BaลŸlatฤฑcฤฑ konumu", + "appearance-launcher-position-start": "BaลŸlat", + "appearance-launcher-use-distro-logo-description": "DaฤŸฤฑtฤฑmฤฑnฤฑzฤฑn logosunu Dock Launcher simgesi olarak kullanฤฑn.", + "appearance-launcher-use-distro-logo-label": "Simge yerine daฤŸฤฑtฤฑm logosunu kullan", + "appearance-pinned-static-description": "Sabit sฤฑrada, sabitlenmiลŸ uygulama simgelerini her zaman sola itin.", + "appearance-pinned-static-label": "SabitlenmiลŸ statik uygulamalar", + "appearance-position-description": "Dockโ€™un ekranda nerede gรถrรผneceฤŸini seรงin.", + "appearance-position-label": "Pozisyon", + "appearance-show-launcher-icon-description": "Uygulama baลŸlatฤฑcฤฑ simgesini dock'ta gรถster.", + "appearance-show-launcher-icon-label": "Uygulama baลŸlatฤฑcฤฑyฤฑ gรถster", + "appearance-sit-on-frame-description": "Dock'u รงerรงevenin kenar boลŸluฤŸunun iรงine hizalayฤฑn, รผstรผne oturmak yerine.", + "appearance-sit-on-frame-label": "Dock รงerรงevede yer alฤฑr", + "appearance-type-attached": "SabitlenmiลŸ", + "appearance-type-description": "Yรผzen bir hap veya kenara baฤŸlฤฑ sabit bir รงubuk arasฤฑndan seรงim yapฤฑn.", + "appearance-type-floating": "Yรผzen", + "appearance-type-label": "Dock stili", + "enabled-description": "Dockโ€™u tamamen gรถsterin veya gizleyin.", + "enabled-label": "Dockโ€™u etkinleลŸtir", + "monitors-desc": "Dockโ€™u belirli ekranlarda gรถsterin. Hiรงbiri seรงilmezse varsayฤฑlan olarak tรผmรผnde gรถsterilir.", + "monitors-only-same-monitor-description": "Yalnฤฑzca dockโ€™un bulunduฤŸu monitรถrdeki uygulamalarฤฑ gรถster.", + "monitors-only-same-monitor-label": "Yalnฤฑzca aynฤฑ monitรถrdeki uygulamalar", + "monitors-title": "Ekranlar", + "title": "Dock" + }, + "general": { + "copy-settings": "Ayarlarฤฑ kopyala", + "fonts-default-description": "Arayรผz boyunca kullanฤฑlan ana yazฤฑ tipi.", + "fonts-default-label": "Varsayฤฑlan yazฤฑ tipi", + "fonts-default-placeholder": "Varsayฤฑlan yazฤฑ tipi seรงin...", + "fonts-default-scale-description": "Standart metin boyutunu artฤฑrฤฑn veya azaltฤฑn.", + "fonts-default-scale-label": "Varsayฤฑlan yazฤฑ tipi boyutu", + "fonts-default-search-placeholder": "Yazฤฑ tipi ara...", + "fonts-desc": "Arayรผz boyunca kullanฤฑlacak yazฤฑ tiplerini seรงin.", + "fonts-monospace-description": "Sayฤฑlar ve istatistikler iรงin kullanฤฑlan eลŸ aralฤฑklฤฑ yazฤฑ tipi.", + "fonts-monospace-label": "EลŸ aralฤฑklฤฑ yazฤฑ tipi", + "fonts-monospace-placeholder": "EลŸ aralฤฑklฤฑ yazฤฑ tipi seรงin...", + "fonts-monospace-scale-description": "EลŸ aralฤฑklฤฑ metin boyutunu artฤฑrฤฑn veya azaltฤฑn.", + "fonts-monospace-scale-label": "EลŸ aralฤฑklฤฑ yazฤฑ tipi boyutu", + "fonts-monospace-search-placeholder": "EลŸ aralฤฑklฤฑ yazฤฑ tipi ara...", + "fonts-title": "Yazฤฑ tipleri", + "keybinds-conflict-description": "TuลŸ kombinasyonu zaten {action} iรงin atanmฤฑลŸ.", + "keybinds-conflict-title": "TuลŸ atamasฤฑ รงakฤฑลŸmasฤฑ", + "keybinds-description": "Paneller ve baลŸlatฤฑcฤฑ iรงin genel navigasyon tuลŸlarฤฑnฤฑ yapฤฑlandฤฑrฤฑn.", + "keybinds-down": "AลŸaฤŸฤฑ taลŸฤฑ", + "keybinds-enter": "Onayla / Eylem", + "keybinds-escape": "Kapat / Geri", + "keybinds-left": "Sola taลŸฤฑ", + "keybinds-modifier-description": "Kฤฑsayollar bir deฤŸiลŸtirici tuลŸ (Ctrl veya Alt) iรงermelidir.", + "keybinds-modifier-title": "DeฤŸiลŸtirici gerekli", + "keybinds-remove": "Kaldฤฑr / Sil", + "keybinds-right": "SaฤŸa taลŸฤฑ", + "keybinds-title": "Gezinme tuลŸ atamalarฤฑ", + "keybinds-up": "Yukarฤฑ taลŸฤฑ", + "language-desc": "Uygulama iรงin tercih ettiฤŸiniz dili seรงin.", + "language-select-auto-detect": "Otomatik", + "language-select-description": "Uygulama arayรผzรผnde kullanฤฑlacak dili seรงin.", + "language-select-label": "Uygulama dili", + "launch-setup-wizard": "Kurulum sihirbazฤฑ", + "profile-desc": "Kullanฤฑcฤฑ detaylarฤฑnฤฑzฤฑ ve avatarฤฑnฤฑzฤฑ dรผzenleyin.", + "profile-picture-description": "Arayรผzรผn tamamฤฑnda gรถrรผnen profil fotoฤŸrafฤฑnฤฑz.", + "profile-picture-label": "Profil resmi", + "profile-select-avatar": "Avatar resmini seรง", + "profile-title": "Avatar", + "profile-tooltip": "Avatar resmi", + "reverse-scrolling-description": "Yorumlanan kaydฤฑrma yรถnรผnรผ tersine รงevir.", + "reverse-scrolling-label": "Kaydฤฑrmayฤฑ tersine รงevir", + "screen-corners-desc": "Ekran kรถลŸesi yuvarlatma ve gรถrsel efektleri รถzelleลŸtirin.", + "screen-corners-radius-description": "Ekranฤฑn yuvarlatฤฑlmฤฑลŸ kรถลŸelerini ayarlayฤฑn.", + "screen-corners-radius-label": "Ekran kรถลŸesi yarฤฑรงapฤฑ", + "screen-corners-radius-reset": "Ekran kรถลŸesi yarฤฑรงapฤฑnฤฑ sฤฑfฤฑrla", + "screen-corners-show-corners-description": "Ekran kenarlarฤฑnda yuvarlatฤฑlmฤฑลŸ kรถลŸeler gรถrรผntรผle.", + "screen-corners-show-corners-label": "Ekran kรถลŸelerini gรถster", + "screen-corners-solid-black-description": "Araรง รงubuฤŸu arka plan rengi yerine tam siyah kullan.", + "screen-corners-solid-black-label": "Siyah kรถลŸeler", + "screen-corners-title": "Ekran kรถลŸeleri", + "settings-copied": "Ayarlar panoya kopyalandฤฑ", + "smooth-scrolling-description": "Daha akฤฑcฤฑ bir tekerlek deneyimi iรงin liste kaydฤฑrmayฤฑ canlandฤฑrฤฑn.", + "smooth-scrolling-label": "Akฤฑcฤฑ kaydฤฑrma", + "tab-basics": "Temeller", + "tab-keybinds": "TuลŸ atamalarฤฑ" + }, + "hooks": { + "color-generation-description": "Renk oluลŸturma tamamlandฤฑฤŸฤฑnda (รถrn. duvar kaฤŸฤฑdฤฑ tabanlฤฑ tema uygulandฤฑktan sonra) รงalฤฑลŸtฤฑrฤฑlacak komut.", + "color-generation-label": "Renkler oluลŸturuldu", + "color-generation-placeholder": "รถrn. notify-send \"Renkler\" \"OluลŸturuldu\"", + "info-command-info-description": "โ€ข Komutlar kabuk (sh -lc) ile yรผrรผtรผlรผr
โ€ข Komutlar arka planda yรผrรผtรผlรผr (ayrฤฑk)
โ€ข Test dรผฤŸmeleri mevcut deฤŸerlerle yรผrรผtรผlรผr", + "info-parameters-description": "โ€ข Wallpaper hook: $1 = duvar kaฤŸฤฑdฤฑ yolu, $2 = ekran adฤฑ, $3 = tema (koyu/aรงฤฑk)
โ€ข Theme toggle hook: $1 = true/false (koyu mod durumu)
โ€ข Screen lock/unlock hooks: $1 = lock/unlock (ekran kilidi durumu)
โ€ข Performance mode hooks: Parametre yok
โ€ข Session hook: $1 = action (shutdown/reboot)", + "info-parameters-label": "Kullanฤฑlabilir parametreler", + "noctalia-started-description": "Noctalia yรผklendiฤŸinde รงalฤฑลŸtฤฑrฤฑlacak komut.", + "noctalia-started-label": "Noctalia baลŸladฤฑ", + "noctalia-started-placeholder": "รถrn. notify-send 'Noctalia Yรผklendi'", + "performance-mode-disabled-description": "Noctalia performans modu devre dฤฑลŸฤฑ bฤฑrakฤฑldฤฑฤŸฤฑnda รงalฤฑลŸtฤฑrฤฑlacak komut.", + "performance-mode-disabled-label": "Performans modu devre dฤฑลŸฤฑ bฤฑrakฤฑldฤฑ", + "performance-mode-disabled-placeholder": "รถrn. notify-send \"Performans\" \"Mod devre dฤฑลŸฤฑ\"", + "performance-mode-enabled-description": "Noctalia performans modu etkinleลŸtirildiฤŸinde รงalฤฑลŸtฤฑrฤฑlacak komut.", + "performance-mode-enabled-label": "Performans modu etkinleลŸtirildi", + "performance-mode-enabled-placeholder": "รถrn. notify-send \"Performans\" \"Mod etkin\"", + "screen-lock-description": "Ekran kilitlendiฤŸinde รงalฤฑลŸtฤฑrฤฑlacak komut.", + "screen-lock-label": "Ekran kilitlendi", + "screen-lock-placeholder": "รถrn. notify-send \"Ekran\" \"Kilitlendi\"", + "screen-unlock-description": "Ekran kilidi aรงฤฑldฤฑฤŸฤฑnda รงalฤฑลŸtฤฑrฤฑlacak komut.", + "screen-unlock-label": "Ekran kilidi aรงฤฑldฤฑ", + "screen-unlock-placeholder": "รถrn. notify-send \"Ekran\" \"Aรงฤฑldฤฑ\"", + "session-description": "Kapatma veya yeniden baลŸlatma รถncesinde รงalฤฑลŸtฤฑrฤฑlacak komut. Eylem tรผrรผnรผ $1 olarak alฤฑr (kapatma/yeniden baลŸlatma).", + "session-label": "Oturum sonu", + "session-placeholder": "รถrn. notify-send \"Session\" \"$1\"", + "system-hooks-desc": "Sistem olaylarฤฑ gerรงekleลŸtiฤŸinde รงalฤฑลŸtฤฑrฤฑlacak komutlarฤฑ yapฤฑlandฤฑrฤฑn.", + "system-hooks-enable-description": "Tรผm kanca komutlarฤฑnฤฑ etkinleลŸtirin veya devre dฤฑลŸฤฑ bฤฑrakฤฑn.", + "system-hooks-enable-label": "Hooks etkinleลŸtir", + "system-hooks-title": "Sistem hooks", + "theme-changed-description": "Tema koyu ve aydฤฑnlฤฑk mod arasฤฑnda geรงiลŸte รงalฤฑลŸtฤฑrฤฑlacak komut.", + "theme-changed-label": "Tema deฤŸiลŸti", + "theme-changed-placeholder": "รถrn. notify-send \"Tema\" \"GeรงiลŸ yapฤฑldฤฑ\"", + "title": "Kancalar", + "wallpaper-changed-description": "Duvar kรขฤŸฤฑdฤฑ deฤŸiลŸtiฤŸinde รงalฤฑลŸtฤฑrฤฑlacak komut.", + "wallpaper-changed-label": "Duvar kรขฤŸฤฑdฤฑ deฤŸiลŸti", + "wallpaper-changed-placeholder": "รถrn. notify-send \"Duvar KรขฤŸฤฑdฤฑ\" \"DeฤŸiลŸti\"" + }, + "idle": { + "custom-add": "Komut Ekle", + "custom-description": "Bir sรผre hareketsizlikten sonra bir kabuk komutu รงalฤฑลŸtฤฑr.", + "custom-entry-command": "Komut", + "custom-entry-delete": "Sil", + "custom-entry-edit": "ร–zel Komutu Dรผzenle", + "custom-entry-name": "Ad", + "custom-entry-name-placeholder": "รถrn. IลŸฤฑklarฤฑ kapat", + "custom-entry-new": "Yeni ร–zel Komut", + "custom-entry-timeout": "BoลŸta kalma sรผresi", + "custom-entry-timeout-format": "{count} saniye", + "custom-entry-timeout-format-plural": "{count} saniye", + "custom-entry-unnamed": "ฤฐsimsiz komut", + "custom-label": "ร–zel boลŸta kalma komutlarฤฑ", + "enable-description": "Belirli bir sรผre iลŸlem yapฤฑlmadฤฑฤŸฤฑnda ekranฤฑ otomatik olarak kapat, kilitle veya askฤฑya al.", + "enable-label": "BoลŸta kalma yรถnetimini etkinleลŸtir", + "fade-duration-description": "Her eylem tetiklenmeden รถnce kararan animasyonun saniye cinsinden sรผresi. Herhangi bir fare hareketi kararmayฤฑ iptal eder.", + "fade-duration-label": "Solma sรผresi", + "lock-description": "Kilit ekranฤฑ etkinleลŸmeden รถnceki hareketsizlik sรผresi (saniye).", + "lock-label": "Ekranฤฑ Kilitle", + "resume-command-label": "Komutu devam ettir", + "screen-off-description": "Monitรถrler kapanmadan รถnceki hareketsizlik sรผresi (saniye).", + "screen-off-label": "Ekranฤฑ kapat", + "status-description": "Compositor tarafฤฑndan bildirilen boลŸta kalma sรผresi.", + "status-label": "BoลŸta kalma sรผresi", + "suspend-description": "Sistem askฤฑya alฤฑnmadan รถnceki hareketsizlik sรผresi (saniye).", + "tab-behavior": "DavranฤฑลŸ", + "tab-custom": "ร–zel", + "timeouts-description": "Bir aลŸamayฤฑ devre dฤฑลŸฤฑ bฤฑrakmak iรงin 0 olarak ayarlayฤฑn. 'Uyanฤฑk kal' aktifken zaman aลŸฤฑmlarฤฑ duraklatฤฑlฤฑr.", + "timeouts-label": "Zaman aลŸฤฑmlarฤฑ", + "title": "BoลŸta", + "unavailable": "Yerel boลŸta kalma izleme bu Compositor'da mevcut deฤŸil." + }, + "indicator": { + "default-value": "Varsayฤฑlan: {value}", + "system-default": "Sistem varsayฤฑlanฤฑ" + }, + "launcher": { + "clipboard-desc": "BaลŸlatฤฑcฤฑdan panonuzun geรงmiลŸine eriลŸin ve yรถnetin.", + "execute-desc": "Uygulamalarฤฑn nasฤฑl baลŸlatฤฑlacaฤŸฤฑnฤฑ yapฤฑlandฤฑrฤฑn.", + "execute-title": "ฤฐcra", + "settings-annotation-tool-description": "Panodaki geรงmiลŸte aรงฤฑklama ekle dรผฤŸmesine tฤฑklandฤฑฤŸฤฑnda รงalฤฑลŸtฤฑrฤฑlacak komut. Gรถrรผntรผ bu komuta yรถnlendirilecektir.", + "settings-annotation-tool-label": "Aรงฤฑklama aracฤฑ", + "settings-annotation-tool-placeholder": "รถrn. 'gradia', 'satty -f -'", + "settings-auto-paste-description": "Seรงili panodaki รถฤŸeyi otomatik olarak yapฤฑลŸtฤฑr. Wtype gerektirir.", + "settings-auto-paste-label": "Otomatik yapฤฑลŸtฤฑrma", + "settings-clip-chips-description": "Panoya geรงmiลŸini tรผre gรถre filtrelemek iรงin bir sekme รงubuฤŸu gรถster (Gรถrseller, BaฤŸlantฤฑlar, Dosyalar, Kod vb.).", + "settings-clip-chips-label": "Kategori ร‡iplerini EtkinleลŸtir", + "settings-clip-preview-description": ">clip komutu kullanฤฑlฤฑrken panodaki iรงeriฤŸin รถnizlemesini gรถsterir.", + "settings-clip-preview-label": "Panoyu รถnizlemeyi etkinleลŸtir", + "settings-clip-smart-icons-description": "Genel bir pano simgesi yerine baฤŸlantฤฑlar, dosyalar, renkler ve kod iรงin belirli simgeler gรถster.", + "settings-clip-smart-icons-label": "Akฤฑllฤฑ Simgeleri EtkinleลŸtir", + "settings-clip-wrap-text-description": "Panodaki metni kฤฑrpmak yerine listeye sฤฑฤŸdฤฑr.", + "settings-clip-wrap-text-label": "Panoyu metni kaydฤฑr", + "settings-clipboard-history-description": "BaลŸlatฤฑcฤฑdan daha รถnce kopyalanan รถฤŸelere eriลŸin.", + "settings-clipboard-history-label": "Pano geรงmiลŸini etkinleลŸtir", + "settings-clipboard-watch-image-description": "Gรถrรผntรผ deฤŸiลŸiklikleri iรงin wl-paste'e iletilen tam komut dizesi (yeniden baลŸlatma gerektirir).", + "settings-clipboard-watch-image-label": "Gรถrรผntรผ ฤฐzleme Komutu", + "settings-clipboard-watch-text-description": "Gรถrรผntรผ deฤŸiลŸiklikleri iรงin wl-paste'e iletilen tam komut dizesi (yeniden baลŸlatma gerektirir).", + "settings-clipboard-watch-text-label": "Metin izleme komutu", + "settings-custom-launch-prefix-description": "Komutlara รถzel bir baลŸlatฤฑcฤฑ ile รถn ek ekleyin (รถrn. systemd entegrasyonu iรงin 'runapp').", + "settings-custom-launch-prefix-enabled-description": "Uygulamalarฤฑ baลŸlatmak iรงin varsayฤฑlan yรถntem yerine รถzel bir รถn ek kullanฤฑn.", + "settings-custom-launch-prefix-enabled-label": "ร–zel baลŸlatma รถn ekini etkinleลŸtir", + "settings-custom-launch-prefix-label": "ร–zel baลŸlatma รถn eki", + "settings-density-description": "Uygulama simgelerinin boyutunu ve Launcher yoฤŸunluฤŸunu ayarlayฤฑn.", + "settings-density-label": "YoฤŸunluk", + "settings-desc": "BaลŸlatฤฑcฤฑnฤฑn davranฤฑลŸฤฑnฤฑ ve gรถrรผnรผmรผnรผ รถzelleลŸtirin.", + "settings-enable-session-search-description": "Launcher'da arama yaparken oturum eylemlerini (kilitle, kapat, yeniden baลŸlat vb.) gรถster.", + "settings-enable-session-search-label": "Oturum aramasฤฑnฤฑ etkinleลŸtir", + "settings-enable-settings-search-description": "BaลŸlatฤฑcฤฑda arama yaparken ayarlar sonuรงlarฤฑnฤฑ gรถster.", + "settings-enable-settings-search-label": "Ayarlar aramasฤฑnฤฑ etkinleลŸtir", + "settings-enable-windows-search-description": "Etkin pencereleri ara ve odakla.", + "settings-enable-windows-search-label": "Pencere aramasฤฑnฤฑ etkinleลŸtir", + "settings-grid-view-description": "ร–ฤŸeleri liste yerine ฤฑzgara dรผzeninde gรถrรผntรผle.", + "settings-icon-mode-description": "Tabler simgeleri yerine yerel sistem simgelerini kullanฤฑn.", + "settings-icon-mode-label": "Yerel simgeleri kullan", + "settings-ignore-mouse-input-description": "BaลŸlatฤฑcฤฑda fare etkileลŸimini ve kaydฤฑrma tekerleฤŸini devre dฤฑลŸฤฑ bฤฑrak.", + "settings-ignore-mouse-input-label": "Fare giriลŸini yoksay", + "settings-overlay-layer-description": "BaลŸlatฤฑcฤฑyฤฑ tam ekran pencerelerinin รผzerinde, yer paylaลŸฤฑmฤฑ katmanฤฑnda gรถrรผntรผle. EtkinleลŸtirildiฤŸinde, baลŸlatฤฑcฤฑ รงubuฤŸa takฤฑlmaz.", + "settings-overlay-layer-label": "Tam ekranฤฑn รผzerinde gรถster", + "settings-position-description": "BaลŸlatฤฑcฤฑ panelinin nerede gรถrรผneceฤŸini seรงin.", + "settings-show-categories-description": "Uygulamalarฤฑ filtrelemek iรงin kategori sekmelerini gรถster.", + "settings-show-categories-label": "Kategorileri gรถster", + "settings-show-icon-background-description": "Simgelerin arkasฤฑnda yuvarlak kรถลŸeli dikdรถrtgen bir arka plan gรถster.", + "settings-show-icon-background-label": "Simge arka planฤฑnฤฑ gรถster", + "settings-sort-by-usage-description": "EtkinleลŸtirildiฤŸinde, sฤฑkรงa baลŸlatฤฑlan uygulamalar listede ilk olarak gรถrรผnรผr.", + "settings-sort-by-usage-label": "En รงok kullanฤฑlana gรถre sฤฑrala", + "settings-terminal-command-description": "Bir terminal baลŸlatmak iรงin komut. ร–rn. 'kitty -e' veya 'gnome-terminal --'.", + "settings-terminal-command-label": "Terminal komutu", + "settings-use-app2unit-description": "Uygulama sรผreรงlerini daha iyi yรถnetmek ve sorunlarฤฑ รถnlemek iรงin alternatif bir baลŸlatma yรถntemi kullanฤฑr.", + "settings-use-app2unit-label": "Uygulamalarฤฑ baลŸlatmak iรงin App2Unit kullan", + "settings-view-mode-description": "BaลŸlatฤฑcฤฑ giriลŸleri iรงin dรผzeni seรงin.", + "settings-view-mode-label": "Gรถrรผnรผm modu", + "title": "BaลŸlatฤฑcฤฑ" + }, + "location": { + "auto-locate-description": "IP adresinizi kullanarak konumunuzu periyodik olarak otomatik olarak algฤฑlayฤฑn.", + "auto-locate-label": "Otomatik Konumlandฤฑrma", + "calendar-cards-desc": "Takvim panelinde kartlarฤฑ dรผzenleyin ve etkinleลŸtirin/devre dฤฑลŸฤฑ bฤฑrakฤฑn.", + "calendar-cards-title": "Takvim kartlarฤฑ", + "calendar-header-label": "Takvim baลŸlฤฑฤŸฤฑ", + "calendar-month-label": "Takvim ayฤฑ", + "date-time-12hour-format-description": "Zamanฤฑ kilit ekranฤฑnda ve takvimde 12 saatlik formatta gรถsterir. Araรง รงubuฤŸu saatinin kendi ayarlarฤฑ vardฤฑr.", + "date-time-12hour-format-label": "12 saatlik zaman formatฤฑnฤฑ kullan", + "date-time-desc": "Tarih ve saatin nasฤฑl gรถrรผneceฤŸini รถzelleลŸtirin.", + "date-time-first-day-of-week-automatic": "Otomatik (sistem yerel ayarฤฑnฤฑ kullan)", + "date-time-first-day-of-week-description": "Takvimde haftanฤฑn hangi gรผnรผ baลŸladฤฑฤŸฤฑnฤฑ seรงin.", + "date-time-first-day-of-week-label": "Haftanฤฑn ilk gรผnรผ", + "date-time-show-events-description": "Takvim panelindeki etkinlikleri gรถster.", + "date-time-show-events-label": "Takvim etkinliklerini gรถster", + "date-time-title": "Tarih & Saat", + "date-time-use-analog-description": "Takvim penceresinde analog tarzda bir saat gรถster.", + "date-time-use-analog-label": "Analog tarzฤฑ saat kullan", + "date-time-week-numbers-description": "Takvimde yฤฑlฤฑn haftasฤฑnฤฑ (รถrn. 38. Hafta) gรถsterir.", + "date-time-week-numbers-label": "Hafta numaralarฤฑnฤฑ gรถster", + "geolocate-now-button": "ลžimdi konumlandฤฑr", + "location-desc": "Konumunuzu ayarlayarak hassas hava durumu ve gece ฤฑลŸฤฑฤŸฤฑ zamanlamasฤฑ alฤฑn.", + "location-search-description": "รถrn. ฤฐstanbul, TR", + "location-search-label": "Konum ara", + "location-search-placeholder": "Konum adฤฑnฤฑ girin", + "location-title": "Konumunuz", + "weather-desc": "Tercih ettiฤŸiniz sฤฑcaklฤฑk birimini seรงin.", + "weather-enabled-description": "Arayรผz boyunca hava durumu bilgilerini gรถsterin ve hava durumu verileri alฤฑn. Devre dฤฑลŸฤฑ bฤฑrakฤฑldฤฑฤŸฤฑnda, tรผm hava durumu รถฤŸeleri gizlenecek ve aฤŸ istekleri yapฤฑlmayacaktฤฑr.", + "weather-enabled-label": "Hava durumunu etkinleลŸtir", + "weather-fahrenheit-description": "SฤฑcaklฤฑฤŸฤฑ santigrat yerine fahrenhayt cinsinden gรถsterin.", + "weather-fahrenheit-label": "SฤฑcaklฤฑฤŸฤฑ fahrenhayt (ยฐF) cinsinden gรถster", + "weather-hide-city-description": "Arayรผzรผn tamamฤฑnda hava durumu ekranlarฤฑnda ลŸehir adฤฑnฤฑ gizle.", + "weather-hide-city-label": "ลžehir adฤฑnฤฑ gizle", + "weather-hide-timezone-description": "Arayรผzรผn tamamฤฑndaki hava durumu ekranlarฤฑndan saat dilimi kฤฑsaltmasฤฑnฤฑ gizle.", + "weather-hide-timezone-label": "Saat dilimini gizle", + "weather-show-effects-description": "Hava durumu kartฤฑnda ek gรถrsel efektler (yaฤŸmur, kar veya ลŸimลŸek gibi) gรถsterir.", + "weather-show-effects-label": "Hava efektlerini gรถster", + "weather-show-in-calendar-description": "Gรผnlรผk hava durumu tahminini doฤŸrudan takvim gรถrรผnรผmรผnรผzde gรถsterin.", + "weather-show-in-calendar-label": "Takvimde hava durumunu gรถster", + "weather-talia-mascot-always-description": "tabler icons yerine her zaman Talia emojis kullanฤฑn.", + "weather-talia-mascot-always-label": "Talia hava durumu emojilerini her zaman gรถster" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "fprintd (parmak izi kimlik doฤŸrulamasฤฑ) etkin olduฤŸunda, bu seรงenek parmak izi yerine parolanฤฑzฤฑ kullanarak oturum aรงmanฤฑzฤฑ saฤŸlar.", + "allow-password-with-fprintd-label": "fprintd ile parola giriลŸine izin ver", + "auto-start-auth-description": "Bir tuลŸa basmaya veya dรผฤŸmeye tฤฑklamaya gerek kalmadan parmak izi kimlik doฤŸrulamasฤฑnฤฑ otomatik olarak baลŸlatฤฑr.", + "auto-start-auth-label": "Otomatik kimlik doฤŸrulama baลŸlatma", + "clock-format-description": "Tarih/saat sรถzdizimi belirteรงlerini kullanarak saat biรงimini รถzelleลŸtirin.", + "clock-format-label": "Saat Biรงimi", + "clock-style-analog": "Analog", + "clock-style-custom": "ร–zel", + "clock-style-description": "Kilit Ekranฤฑ'ndaki saatin gรถrsel stilini seรงin.", + "clock-style-digital": "Dijital", + "clock-style-label": "Saat Stili", + "compact-lockscreen-description": "Sadece giriลŸ girdisini ve sistem kontrollerini gรถster, hava durumu ve ortam araรง takฤฑmlarฤฑnฤฑ gizle.", + "compact-lockscreen-label": "Kompakt ekran kilidi", + "enable-lockscreen-media-controls-description": "Kilit ekranฤฑnda etkileลŸimli medya oynatma kontrollerini gรถster.", + "enable-lockscreen-media-controls-label": "Kilit Ekranฤฑ Medya Kontrolleri", + "lock-on-suspend-description": "Sistemi askฤฑya alฤฑrken otomatik olarak ekranฤฑ kilitler.", + "lock-on-suspend-label": "Askฤฑya alฤฑrken kilitle", + "lock-screen-animations-description": "Kilit ekranฤฑ animasyonlarฤฑnฤฑ etkinleลŸtir veya devre dฤฑลŸฤฑ bฤฑrak.", + "lock-screen-animations-label": "Kilit Ekranฤฑ Animasyonlarฤฑ", + "lock-screen-blur-strength-description": "Kilit ekranฤฑ duvar kaฤŸฤฑdฤฑna bulanฤฑklฤฑk efekti uygular.", + "lock-screen-blur-strength-label": "Kilit ekranฤฑ bulanฤฑklฤฑk gรผcรผ", + "lock-screen-tint-strength-description": "Kilit ekranฤฑ duvar kaฤŸฤฑdฤฑna bir renk tonu katmanฤฑ uygular.", + "lock-screen-tint-strength-label": "Kilit ekranฤฑ ton gรผcรผ", + "monitors-desc": "Kilit ekranฤฑnฤฑ belirli monitรถrlerde gรถster. Hiรงbiri seรงilmezse varsayฤฑlan olarak tรผm monitรถrlerde gรถsterilir.", + "password-chars-description": "ลžifrenizi gizlemek iรงin kullanฤฑlan sevimli simgeler.", + "password-chars-label": "Rastgele ลŸifre simgeleri", + "show-hibernate-description": "Gรผรง kontrollerinde 'hazฤฑrda beklet' seรงeneฤŸini gรถster.", + "show-hibernate-label": "Hazฤฑrda beklet seรงeneฤŸini gรถster", + "show-session-buttons-description": "Kilit ekranฤฑndan gรผรง ayarlarฤฑna eriลŸime izin ver.", + "show-session-buttons-label": "Gรผรง kontrolleri", + "title": "Ekran kilit" + }, + "network": { + "bluetooth-description": "Bluetooth yรถnetimini etkinleลŸtir.", + "bluetooth-rssi-polling-description": "BaฤŸlฤฑ cihazlar iรงin RSSI'yฤฑ bluetoothctl aracฤฑlฤฑฤŸฤฑyla periyodik olarak รถrnekleyin. Tรผm cihazlar iรงin mevcut olmayabilir; etkinleลŸtirildiฤŸinde minimum kaynak kullanฤฑr.", + "bluetooth-rssi-polling-label": "Bluetooth sinyal yoklamasฤฑ", + "desc": "Kablosuz ve Bluetooth baฤŸlantฤฑlarฤฑnฤฑ yรถnetin.", + "wifi-description": "Kablosuz aฤŸlarฤฑ yรถnet (NetworkManager gerektirir)." + }, + "notifications": { + "duration-critical-urgency-description": "Kritik รถncelikli bildirimlerin ne kadar sรผreyle gรถrรผneceฤŸi.", + "duration-critical-urgency-label": "Kritik รถncelikli", + "duration-desc": "Bildirimlerin aciliyet seviyelerine gรถre ne kadar sรผreyle gรถrรผneceฤŸini yapฤฑlandฤฑrฤฑn.", + "duration-low-urgency-description": "DรผลŸรผk รถncelikli bildirimlerin ne kadar sรผreyle gรถrรผneceฤŸi.", + "duration-low-urgency-label": "DรผลŸรผk รถncelikli", + "duration-normal-urgency-description": "Normal รถncelikli bildirimlerin ne kadar sรผreyle gรถrรผneceฤŸi.", + "duration-normal-urgency-label": "Normal รถncelikli", + "duration-respect-expire-description": "Bildirimde ayarlanan sona erme zaman aลŸฤฑmฤฑnฤฑ kullanฤฑn.", + "duration-respect-expire-label": "Sona erme zaman aลŸฤฑmฤฑna uyar", + "duration-title": "Bildirim sรผresi", + "history-clear-dismiss-description": "Bildirimi kapatฤฑldฤฑฤŸฤฑnda geรงmiลŸten sil.", + "history-clear-dismiss-label": "KapatฤฑldฤฑฤŸฤฑnda temizle", + "history-critical-urgency-description": "Kritik รถncelikli bildirimleri geรงmiลŸe kaydet.", + "history-critical-urgency-label": "Kritik aciliyeti geรงmiลŸe kaydet", + "history-desc": "Aciliyet seviyelerine gรถre hangi bildirimlerin geรงmiลŸe kaydedileceฤŸini kontrol edin.", + "history-low-urgency-description": "DรผลŸรผk รถncelikli bildirimleri geรงmiลŸe kaydet.", + "history-low-urgency-label": "DรผลŸรผk aciliyeti geรงmiลŸe kaydet", + "history-normal-urgency-description": "Normal รถncelikli bildirimleri geรงmiลŸe kaydet.", + "history-normal-urgency-label": "Normal aciliyeti geรงmiลŸe kaydet", + "monitors-desc": "Bildirimleri belirli ekranlarda gรถsterin. Hiรงbiri seรงilmezse varsayฤฑlan olarak tรผmรผnde gรถsterilir.", + "rules-action-block": "Blok", + "rules-action-block-desc": "Tamamen atlar.", + "rules-action-hide": "Gizle", + "rules-action-hide-desc": "Aรงฤฑlฤฑr pencere yok, ses yok, geรงmiลŸe eklenir.", + "rules-action-label": "Eylem", + "rules-action-mute": "Sesi Kapat", + "rules-action-mute-desc": "Ses yok, yine de aรงฤฑlฤฑr pencere gรถsteriyor ve geรงmiลŸte.", + "rules-add": "Kural Ekle", + "rules-delete": "Kuralฤฑ Sil", + "rules-description": "Uygulama adฤฑnฤฑ veya iรงeriฤŸi eลŸleลŸtir. Kurallar sฤฑrayla kontrol edilir ve ilk eลŸleลŸen uygulanฤฑr.", + "rules-edit": "Kuralฤฑ Dรผzenle", + "rules-label": "Filtre Kurallarฤฑ", + "rules-pattern-label": "Desen", + "rules-pattern-placeholder": "firefox, discord, veya /regex/", + "rules-tab": "Kurallar", + "settings-always-on-top-description": "Bildirimleri tam ekran pencerelerinin ve diฤŸer katmanlarฤฑn รผzerinde gรถrรผntรผle.", + "settings-background-opacity-description": "Bildirim arka planlarฤฑnฤฑn opaklฤฑฤŸฤฑnฤฑ ayarlayฤฑn.", + "settings-density-description": "Bildirim kartฤฑ yoฤŸunluฤŸunu seรงin.", + "settings-density-label": "YoฤŸunluk", + "settings-desc": "Bildirimlerin gรถrรผnรผmรผnรผ ve davranฤฑลŸฤฑnฤฑ yapฤฑlandฤฑrฤฑn.", + "settings-do-not-disturb-description": "EtkinleลŸtirildiฤŸinde tรผm bildirim aรงฤฑlฤฑr pencerelerini devre dฤฑลŸฤฑ bฤฑrakฤฑr.", + "settings-enabled-description": "Bildirim arka plan programฤฑnฤฑ etkinleลŸtir veya devre dฤฑลŸฤฑ bฤฑrak, Noctalia shell'in yeniden baลŸlatฤฑlmasฤฑnฤฑ gerektirir.", + "settings-enabled-label": "Bildirimleri etkinleลŸtir", + "settings-location-description": "Bildirimlerin ekranda nerede gรถrรผneceฤŸi.", + "settings-markdown-description": "Bildirim iรงeriฤŸini Markdown biรงimlendirmesi kullanarak oluลŸtur.", + "settings-markdown-label": "Markdown'ฤฑ EtkinleลŸtir", + "sounds-desc": "Bildirim ses efektlerini ve ses seviyesini yapฤฑlandฤฑrฤฑn.", + "sounds-enabled-description": "Gelen bildirimler iรงin ses efektlerini etkinleลŸtir.", + "sounds-enabled-label": "Bildirim seslerini etkinleลŸtir", + "sounds-excluded-apps-description": "Kendi dahili seslerine sahip belirli uygulamalar iรงin ayarlanan bildirim sesini รงalmayฤฑ es geรงin.", + "sounds-excluded-apps-label": "Hariรง tutulan uygulamalar", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "Kritik รถncelikli bildirimler iรงin รงalฤฑnan ses dosyasฤฑnฤฑn yolu.", + "sounds-files-critical-label": "Kritik aciliyet sesi", + "sounds-files-critical-select-title": "Kritik aciliyet ses dosyasฤฑ seรง", + "sounds-files-desc": "Farklฤฑ bildirim aciliyet seviyeleri iรงin รถzel ses dosyalarฤฑ yapฤฑlandฤฑrฤฑn.", + "sounds-files-low-description": "DรผลŸรผk รถncelikli bildirimler iรงin รงalฤฑnan ses dosyasฤฑnฤฑn yolu.", + "sounds-files-low-label": "DรผลŸรผk aciliyet sesi", + "sounds-files-low-select-title": "DรผลŸรผk aciliyet ses dosyasฤฑ seรง", + "sounds-files-normal-description": "Normal รถncelikli bildirimler iรงin รงalฤฑnan ses dosyasฤฑnฤฑn yolu.", + "sounds-files-normal-label": "Normal aciliyet sesi", + "sounds-files-normal-select-title": "Normal aciliyet ses dosyasฤฑ seรง", + "sounds-files-placeholder": "Ses dosyasฤฑ yolunu girin", + "sounds-files-select-file": "Ses dosyasฤฑ seรง", + "sounds-files-title": "Ses dosyalarฤฑ", + "sounds-files-unified-description": "Bildirimler iรงin รงalฤฑnan ses dosyasฤฑnฤฑn yolu.", + "sounds-files-unified-label": "Bildirim sesi", + "sounds-files-unified-select-title": "Bildirim ses dosyasฤฑ seรง", + "sounds-separate-description": "DรผลŸรผk, normal ve kritik รถncelikli bildirimler iรงin farklฤฑ ses dosyalarฤฑ kullanฤฑn.", + "sounds-separate-label": "ร–ncelik baลŸฤฑna farklฤฑ sesler kullan", + "sounds-title": "Ses ayarlarฤฑ", + "sounds-unavailable-description": "Bildirim seslerini etkinleลŸtirmek iรงin Qt6 Multimedia'yฤฑ kurun.", + "sounds-unavailable-label": "Bildirim sesleri kullanฤฑlamฤฑyor", + "sounds-volume-description": "Bildirim sesleri iรงin ses seviyesini ayarlayฤฑn.", + "sounds-volume-label": "Ses seviyesi", + "toast-battery-description": "Pil seviyesi bu yรผzdenin altฤฑna dรผลŸtรผฤŸรผnde bir uyarฤฑ gรถster.", + "toast-battery-label": "Pil uyarฤฑsฤฑ", + "toast-desc": "Bildirim penceresi gรถrรผnรผmรผnรผ ve davranฤฑลŸฤฑnฤฑ yapฤฑlandฤฑrฤฑn.", + "toast-keyboard-description": "Klavye dรผzeni deฤŸiลŸtiฤŸinde bir bildirim gรถster.", + "toast-keyboard-label": "Klavye dรผzeni", + "toast-media-description": "Medya oynatma durumu deฤŸiลŸtiฤŸinde bir bildirim gรถster.", + "toast-media-label": "Medya" + }, + "osd": { + "always-on-top-description": "OSD'yi tam ekran pencerelerinin ve diฤŸer katmanlarฤฑn รผzerinde gรถrรผntรผle.", + "always-on-top-label": "Her zaman รผstte", + "background-opacity-description": "OSD arka planฤฑnฤฑn ลŸeffaflฤฑฤŸฤฑnฤฑ kontrol eder.", + "background-opacity-label": "Arka plan opaklฤฑฤŸฤฑ", + "description": "Ses ve parlaklฤฑk kaplamalarฤฑ gibi ekran gรถstergelerini yapฤฑlandฤฑrฤฑn.", + "duration-auto-hide-description": "OSD'nin kaybolmadan รถnceki sรผreyi ayarlayฤฑn.", + "duration-auto-hide-label": "ลžu kadar sonra otomatik gizle", + "duration-desc": "OSD'nin otomatik olarak gizlenmeden รถnce ne kadar sรผreyle gรถrรผneceฤŸi.", + "duration-title": "Otomatik gizleme zaman aลŸฤฑmฤฑ", + "enabled-description": "Ses ve parlaklฤฑk deฤŸiลŸikliklerini gerรงek zamanlฤฑ olarak gรถsterin.", + "enabled-label": "Ekran รผstรผ gรถsterim (OSD) etkinleลŸtir", + "events-desc": "Ekran รผstรผ gรถsterimi tetikleyecek olaylarฤฑ seรงin.", + "general-desc": "OSD'nin gรถrรผnรผrlรผฤŸรผnรผ ve davranฤฑลŸฤฑnฤฑ yapฤฑlandฤฑrฤฑn.", + "location-description": "Ekran รผstรผ gรถsterimlerin gรถrรผneceฤŸi konum.", + "monitors-desc": "OSD'yi belirli ekranlarda gรถsterin. Hiรงbiri seรงilmezse varsayฤฑlan olarak tรผmรผnde gรถsterilir.", + "title": "Ekran รผstรผ gรถsterim", + "types-brightness-description": "Ekran parlaklฤฑฤŸฤฑ deฤŸiลŸtiฤŸinde OSD'yi gรถster.", + "types-brightness-label": "Parlaklฤฑk", + "types-custom-text-description": "IPC รผzerinden รถzel metin iletileri iรงin OSD gรถster.", + "types-custom-text-label": "ร–zel metin", + "types-desc": "OSD'yi tetikleyecek olaylarฤฑ seรงin.", + "types-input-volume-description": "Mikrofon ses dรผzeyi deฤŸiลŸtiฤŸinde OSD'yi gรถster.", + "types-input-volume-label": "GiriลŸ sesi", + "types-lockkey-description": "Caps Lock, Num Lock veya Scroll Lock deฤŸiลŸtirildiฤŸinde OSD'yi gรถster.", + "types-lockkey-label": "Kilit tuลŸlarฤฑ", + "types-media-description": "Medya oynatma durumu deฤŸiลŸtiฤŸinde OSD'yi gรถster (oynat, duraklat, atla).", + "types-media-label": "Medya oynatma", + "types-title": "OSD tetikleme olaylarฤฑ", + "types-volume-description": "Ses รงฤฑkฤฑลŸ dรผzeyi deฤŸiลŸtiฤŸinde OSD'yi gรถster.", + "types-volume-label": "ร‡ฤฑkฤฑลŸ sesi" + }, + "plugins": { + "auto-update": "Eklentileri otomatik gรผncelle", + "auto-update-description": "Shell baลŸladฤฑฤŸฤฑnda tรผm eklentileri otomatik olarak gรผncelle.", + "available-description": "YapฤฑlandฤฑrฤฑlmฤฑลŸ kaynaklardan eklentilere gรถz atฤฑn ve yรผkleyin.", + "available-label": "Mevcut eklentiler", + "available-no-plugins-description": "Eklenti kaynaklarฤฑnฤฑzฤฑ kontrol edin veya listeyi yenileyin.", + "available-no-plugins-label": "Kullanฤฑlabilir eklenti yok", + "check-for-updates": "Gรผncellemeleri kontrol et", + "checking-for-updates": "Gรผncellemeler kontrol ediliyor...", + "collision-already-installed": "Bu eklenti zaten yรผklรผ", + "collision-custom-version-exists": "\"{source}\" kaynaฤŸฤฑndan รถzel bir sรผrรผm zaten yรผklรผ", + "collision-official-version-exists": "Bu eklentinin resmi sรผrรผmรผ zaten yรผklรผ", + "development-disable": "Bu eklenti iรงin geliลŸtirme modunu devre dฤฑลŸฤฑ bฤฑrak", + "development-enable": "Bu eklenti iรงin geliลŸtirme modunu etkinleลŸtir.", + "filter-downloaded": "ฤฐndirildi", + "filter-not-downloaded": "ฤฐndirilmedi", + "filter-tags-description": "Eklentileri kategoriye veya indirme durumuna gรถre filtrele.", + "filter-tags-label": "Etiketler", + "hot-reload-description": "Eklenti dosyalarฤฑ deฤŸiลŸtiฤŸinde eklentileri otomatik olarak yeniden yรผkle. Eklenti geliลŸtirme iรงin kullanฤฑลŸlฤฑdฤฑr.", + "hot-reload-label": "Hฤฑzlฤฑ yeniden yรผkleme (geliลŸtirme modu)", + "hot-reloaded": "Yeniden yรผklenen eklenti: {name}", + "install-error": "Yรผkleme baลŸarฤฑsฤฑz oldu: {error}", + "install-incompatible": "{plugin} Noctalia v{version} veya daha รผstรผnรผ gerektiriyor", + "install-success": "{plugin} baลŸarฤฑyla kuruldu", + "installed-description": "Yerel olarak yรผklenmiลŸ tรผm eklentileri yรถnetin ve yapฤฑlandฤฑrฤฑn.", + "installed-label": "Yรผklรผ eklentiler", + "installed-no-plugins-description": "\"Mevcut\" bรถlรผmรผnden eklentileri kurun.", + "installed-no-plugins-label": "Eklenti kurulu deฤŸil", + "installing": "{eklenti} kuruluyor...", + "notify-updates": "Eklenti gรผncelleme bildirimleri", + "notify-updates-description": "Yeni eklenti gรผncellemeleri olduฤŸunda bir bildirim penceresi gรถster.", + "open-plugin-page": "Eklenti sayfasฤฑnฤฑ aรง", + "open-plugins-tab": "Eklenti ayarlarฤฑna git", + "plugin-settings-title": "{plugin} ayarlarฤฑ", + "refresh-refreshing": "Eklenti listesi yenileniyor...", + "refresh-tooltip": "Mevcut eklentileri yenile", + "settings-error-not-loaded": "Eklenti yรผklenmedi", + "settings-saved": "Eklenti ayarlarฤฑ kaydedildi", + "settings-tooltip": "Eklenti ayarlarฤฑ", + "source-custom": "ร–zel kaynak", + "sources-add-custom": "ร–zel depo ekle", + "sources-add-dialog-description": "Bir GitHub deposunu eklenti kaynaฤŸฤฑ olarak ekle.", + "sources-add-dialog-error": "Eklenti kaynaฤŸฤฑ eklenemedi", + "sources-add-dialog-name": "Depo adฤฑ", + "sources-add-dialog-name-placeholder": "ร–zel eklentilerim", + "sources-add-dialog-success": "Eklenti kaynaฤŸฤฑ baลŸarฤฑyla eklendi", + "sources-add-dialog-title": "Eklenti kaynaฤŸฤฑ ekle", + "sources-add-dialog-url": "Depo URL'si", + "sources-description": "Eklenti depolarฤฑnฤฑ yรถnetin.", + "sources-placeholder": "Havalฤฑ depom", + "sources-remove-tooltip": "Eklenti kaynaฤŸฤฑnฤฑ kaldฤฑr", + "title": "Eklentiler", + "translations-reloaded": "ร‡eviriler yeniden yรผklendi: {name}", + "uninstall-dialog-description": "{plugin} eklentisini kaldฤฑrmak istediฤŸinizden emin misiniz? Bu, tรผm eklenti verilerini kaldฤฑracaktฤฑr.", + "uninstall-dialog-title": "Eklentiyi kaldฤฑr", + "uninstall-error": "Kaldฤฑrma baลŸarฤฑsฤฑz oldu: {error}", + "uninstall-success": "{plugin} baลŸarฤฑyla kaldฤฑrฤฑldฤฑ", + "uninstalling": "{plugin} kaldฤฑrฤฑlฤฑyor...", + "update-all": "Tรผmรผnรผ gรผncelle ({count})", + "update-all-success": "Tรผm eklentiler baลŸarฤฑyla gรผncellendi", + "update-available": "Yeni eklenti gรผncellemesi mevcut", + "update-available-plural": "Yeni eklenti gรผncellemeleri mevcut ({count})", + "update-error": "Eklenti gรผncellenemedi: {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (Noctalia v{required} gerektirir)", + "update-success": "{plugin} v{version} sรผrรผmรผne gรผncellendi", + "update-version": "v{current} โ†’ v{new}", + "updating": "Gรผncelleniyor..." + }, + "region": { + "title": "Bรถlge" + }, + "session-menu": { + "countdown-duration-description": "Gรผรง eylemlerini รงalฤฑลŸtฤฑrmadan รถnce geri sayฤฑm zamanlayฤฑcฤฑsฤฑnฤฑn ne kadar sรผreceฤŸini ayarlayฤฑn.", + "countdown-duration-label": "Geri sayฤฑm sรผresi", + "enable-countdown-description": "Gรผรง eylemlerini รงalฤฑลŸtฤฑrmadan รถnce bir geri sayฤฑm zamanlayฤฑcฤฑsฤฑ gรถster.", + "enable-countdown-label": "Geri sayฤฑm zamanlayฤฑcฤฑsฤฑnฤฑ etkinleลŸtir", + "entries-desc": "Oturum menรผsรผnde hangi gรผรง eylemlerinin gรถrรผneceฤŸini ve hangi sฤฑrada gรถrรผneceฤŸini รถzelleลŸtirin.", + "entries-title": "Gรผรง eylemleri", + "entry-settings-command-description": "Bu iลŸlem iรงin yรผrรผtรผlecek รถzel komut. Varsayฤฑlan sistem komutunu kullanmak iรงin boลŸ bฤฑrakฤฑn.", + "entry-settings-command-placeholder": "รถrn. systemctl poweroff", + "entry-settings-default-command-lock": "Dahili kilit ekranฤฑ (komut yok)", + "entry-settings-default-command-logout": "Dahili oturum kapatma (komut yok)", + "entry-settings-default-info-description": "ร–zel bir komut belirtilmezse, varsayฤฑlan sistem komutu kullanฤฑlฤฑr.", + "entry-settings-default-info-label": "Varsayฤฑlan komut", + "entry-settings-keybind-description": "Oturum menรผsรผ aรงฤฑkken bu eylemi tetiklemek iรงin bir tuลŸ kombinasyonu atayฤฑn.", + "entry-settings-keybind-placeholder": "Bir tuลŸ atamak iรงin Kaydet'e basฤฑn...", + "entry-settings-keybind-recording": "Kaydediliyor... Herhangi bir tuลŸ kombinasyonuna basฤฑn", + "entry-settings-title": "{entry} yapฤฑlandฤฑr", + "entry-settings-tooltip": "Yapฤฑlandฤฑrma komutu", + "general-desc": "Oturum menรผsรผ panelinin davranฤฑลŸฤฑnฤฑ ve gรถrรผnรผmรผnรผ yapฤฑlandฤฑrฤฑn.", + "large-buttons-layout-description": "Oturum menรผsรผ dรผฤŸmelerinin nasฤฑl gรถrรผntรผleneceฤŸini seรงin.", + "large-buttons-layout-label": "Bรผyรผk dรผฤŸme dรผzeni", + "large-buttons-style-description": "Oturum menรผsรผnรผ bรผyรผk butonlarla ฤฑzgara dรผzeninde gรถrรผntรผle.", + "large-buttons-style-label": "Bรผyรผk dรผฤŸme stili", + "position-description": "Oturum menรผsรผ panelinin aรงฤฑldฤฑฤŸฤฑnda nerede gรถrรผneceฤŸini seรงin.", + "show-header-description": "Oturum menรผsรผnรผn รผst kฤฑsmฤฑnda baลŸlฤฑฤŸฤฑ ve kapat dรผฤŸmesini gรถster.", + "show-header-label": "BaลŸlฤฑฤŸฤฑ gรถster", + "show-keybinds-description": "Oturum seรงeneklerinde tuลŸ kombinasyonu ipuรงlarฤฑnฤฑ gรถrรผntรผle.", + "show-keybinds-label": "Klavye kฤฑsayollarฤฑnฤฑ gรถster" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Noctalia performans modu etkinleลŸtirildiฤŸinde masaรผstรผ widget'larฤฑnฤฑ gรถrรผnรผr tut.", + "noctalia-performance-disable-desktop-widgets-label": "Performans modunda masaรผstรผ widget'larฤฑnฤฑ etkinleลŸtir", + "noctalia-performance-disable-wallpaper-description": "Noctalia performans modu etkinleลŸtirildiฤŸinde masaรผstรผ, genel bakฤฑลŸ ve kilit ekranฤฑ duvar kaฤŸฤฑtlarฤฑnฤฑ gรถrรผnรผr tutun.", + "noctalia-performance-disable-wallpaper-label": "Performans modunda duvar kaฤŸฤฑdฤฑ oluลŸturmayฤฑ etkinleลŸtir", + "title": "Sistem" + }, + "system-monitor": { + "critical-color-label": "Kritik renk", + "custom-highlight-colors-title-label": "ร–zel vurgulama renkleri", + "disk-available-label": "Kullanฤฑlabilir disk alanฤฑ", + "disk-section-label": "Disk kullanฤฑmฤฑ", + "enable-dgpu-monitoring-description": "Uyarฤฑ: Bu, ayrฤฑk GPU'nuzu (NVIDIA/AMD) uyandฤฑracak ve bu da hibrit grafiklere sahip dizรผstรผ bilgisayarlarda pil รถmrรผnรผ รถnemli รถlรงรผde etkileyebilir.", + "enable-dgpu-monitoring-label": "Ayrฤฑk GPU izlemeyi etkinleลŸtir", + "external-monitor-description": "Harici sistem izleyici uygulamasฤฑnฤฑ etkinleลŸtirirken baลŸlatฤฑlacak komut veya uygulama yolunu girin.", + "external-monitor-label": "Harici sistem izleyici komutu", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "Sistem ฤฐzleyicisinin davranฤฑลŸฤฑnฤฑ ve gรถrรผnรผmรผnรผ yapฤฑlandฤฑrฤฑn.", + "gpu-section-label": "GPU sฤฑcaklฤฑฤŸฤฑ", + "highlight-colors-section-label": "Vurgulama renkleri", + "polling-interval-label": "Yoklama aralฤฑฤŸฤฑ", + "polling-section-description": "Her bir sistem metriฤŸinin ne sฤฑklฤฑkta gรผncelleneceฤŸini yapฤฑlandฤฑrฤฑn.", + "polling-section-label": "Yoklama aralฤฑklarฤฑ", + "threshold-critical": "Kritik", + "threshold-warning": "Uyarฤฑ", + "thresholds-section-description": "Her sistem รถlรงรผsรผ iรงin uyarฤฑ/kritik eลŸiklerini ve yoklama aralฤฑklarฤฑnฤฑ ayarlayฤฑn.", + "use-custom-highlight-colors-description": "Devre dฤฑลŸฤฑ bฤฑrakฤฑldฤฑฤŸฤฑnda, tema varsayฤฑlan vurgulama renkleri kullanฤฑlฤฑr.", + "use-custom-highlight-colors-label": "ร–zel vurgulama renklerini kullan", + "warning-color-label": "Uyarฤฑ rengi" + }, + "user-interface": { + "allow-panels-without-bar-description": "EtkinleลŸtirildiฤŸinde, paneller herhangi bir ekranda aรงฤฑlabilir. Devre dฤฑลŸฤฑ bฤฑrakฤฑldฤฑฤŸฤฑnda, paneller yalnฤฑzca bir รงubuฤŸu olan ekranlarda aรงฤฑlฤฑr, bu da bellek kullanฤฑmฤฑnฤฑ azaltabilir.", + "allow-panels-without-bar-label": "ร‡ubuksuz ekranlardaki panellere izin ver", + "animation-disable-description": "Daha hฤฑzlฤฑ ve daha duyarlฤฑ bir deneyim iรงin tรผm animasyonlarฤฑ devre dฤฑลŸฤฑ bฤฑrakฤฑn.", + "animation-disable-label": "Kullanฤฑcฤฑ arayรผzรผ animasyonlarฤฑnฤฑ devre dฤฑลŸฤฑ bฤฑrak", + "animation-speed-description": "Genel animasyon hฤฑzฤฑnฤฑ ayarlar.", + "animation-speed-label": "Animasyon hฤฑzฤฑ", + "animation-speed-reset": "Animasyon hฤฑzฤฑnฤฑ sฤฑfฤฑrla", + "appearance-desc": "Araรง ipuรงlarฤฑ, kenarlฤฑklar ve gรถlgeler gibi gรถrsel รถฤŸeleri รถzelleลŸtirin.", + "blur-behind-description": "ร‡ubuklarฤฑn ve panellerin arkasฤฑndaki alanฤฑ kompozitรถr bulanฤฑklฤฑk protokolรผnรผ kullanarak bulanฤฑklaลŸtฤฑrฤฑr.", + "blur-behind-label": "Arka plan bulanฤฑklฤฑฤŸฤฑ", + "box-border-description": "ฤฐรงerik alanlarฤฑnฤฑn etrafฤฑnda bir รงerรงeve gรถrรผntรผler.", + "box-border-label": "Konteyner ana hattฤฑ", + "box-border-radius-description": "Kenar รงubuklarฤฑ, kartlar ve iรงerik panelleri gibi ana dรผzen bรถlรผmlerinin kรถลŸe yuvarlaklฤฑฤŸฤฑnฤฑ ayarlar.", + "box-border-radius-label": "Konteyner yarฤฑรงapฤฑ", + "control-border-radius-description": "ฤฐnteraktif รถฤŸelerin, dรผฤŸmeler, geรงiลŸler ve metin alanlarฤฑ dahil olmak รผzere, eฤŸriliฤŸini kontrol eder.", + "control-border-radius-label": "GiriลŸ yarฤฑรงapฤฑ", + "desc": "Arayรผzรผn gรถrรผnรผmรผnรผ, hissini ve davranฤฑลŸฤฑnฤฑ รถzelleลŸtirin.", + "dim-desktop-description": "Paneller veya menรผler aรงฤฑkken masaรผstรผnรผ karart.", + "dim-desktop-label": "Masaรผstรผnรผ karart", + "dimmer-opacity-description": "Masaรผstรผ karartmasฤฑ iรงin opaklฤฑk seviyesini ayarlayฤฑn.", + "dimmer-opacity-label": "KarartฤฑlmฤฑลŸ masaรผstรผ opaklฤฑฤŸฤฑ", + "panel-background-opacity-description": "Tรผm panellerin (รงubuk, baลŸlatฤฑcฤฑ, ayarlar vb.) arka plan opaklฤฑฤŸฤฑnฤฑ ayarlayฤฑn.", + "panel-background-opacity-label": "Panel arka plan opaklฤฑฤŸฤฑ", + "panels-attached-to-bar-description": "Paneller รงubuฤŸa ve ekran kenarlarฤฑna kilitlenerek ลŸฤฑk ters kรถลŸelerle kusursuz bir gรถrรผnรผm oluลŸturur.", + "panels-attached-to-bar-label": "Kenarlarฤฑ yakala", + "panels-overlay-description": "Panellerin ve รงubuฤŸun, tam ekran uygulamalarda bile gรถrรผnรผr kalmasฤฑnฤฑ saฤŸlar.", + "panels-overlay-label": "Panelleri ve รงubuฤŸu รผstte tut", + "scaling-description": "ร‡ubuฤŸu hariรง tutarak genel kullanฤฑcฤฑ arayรผzรผnรผn boyutunu deฤŸiลŸtirir.", + "scaling-label": "Arayรผz รถlรงekleme", + "scrollbar-always-visible-description": "ฤฐรงerik kaydฤฑrฤฑlabilir olduฤŸunda kaydฤฑrma รงubuklarฤฑnฤฑ her zaman gรถrรผnรผr tut, yalnฤฑzca รผzerine gelindiฤŸinde gรถstermek yerine.", + "scrollbar-always-visible-label": "Kaydฤฑrma รงubuklarฤฑnฤฑ her zaman gรถster", + "settings-panel-header": "Ayarlar Paneli", + "settings-panel-mode-description": "Ayarlar dรผzenini seรงin (yeniden aรงฤฑlmasฤฑ gerekebilir).", + "settings-panel-mode-label": "Ayarlar paneli modu", + "settings-panel-sidebar-card-style-description": "Ayarlar kenar รงubuฤŸunu yuvarlatฤฑlmฤฑลŸ kรถลŸeli dolgulu bir arka planla sarar.", + "settings-panel-sidebar-card-style-label": "Kenar รงubuฤŸu kart stili", + "shadows-description": "ร‡ubuklarฤฑn ve panellerin altฤฑnda gรถlgelerin etkinleลŸtirilmesini saฤŸlar.", + "shadows-direction-description": "Gรถlgenin nereye dรผลŸeceฤŸini seรงin.", + "shadows-direction-label": "Gรถlge yรถnรผ", + "shadows-label": "Gรถlge efektleri", + "title": "Kullanฤฑcฤฑ arayรผzรผ", + "tooltips-description": "Arayรผz genelindeki ipuรงlarฤฑnฤฑ etkinleลŸtirin veya devre dฤฑลŸฤฑ bฤฑrakฤฑn.", + "tooltips-label": "ฤฐpuรงlarฤฑnฤฑ gรถster", + "translucent-widgets-description": "Panellerin iรงindeki dรผฤŸmeleri, sekmeleri ve diฤŸer widget'larฤฑ yarฤฑ saydam yapฤฑn.", + "translucent-widgets-label": "Yarฤฑ saydam widget'lar" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "Alfabetik", + "automation-change-mode-description": "Otomatik deฤŸiลŸtirirken duvar kรขฤŸฤฑtlarฤฑnฤฑn nasฤฑl seรงileceฤŸini seรงin.", + "automation-change-mode-label": "DeฤŸiลŸtirme modu", + "automation-custom-interval-description": "Zamanฤฑ SS:DD olarak girin (รถrn. 01:30).", + "automation-custom-interval-label": "ร–zel aralฤฑk", + "automation-interval-description": "Duvar kรขฤŸฤฑtlarฤฑnฤฑn otomatik deฤŸiลŸtirilme sฤฑklฤฑฤŸฤฑnฤฑ belirleyin.", + "automation-interval-label": "Duvar kรขฤŸฤฑdฤฑ aralฤฑฤŸฤฑ", + "automation-random-wallpaper-description": "Dรผzenli aralฤฑklarla rastgele duvar kรขฤŸฤฑdฤฑ deฤŸiลŸiklikleri planlayฤฑn.", + "automation-scheduled-change-description": "Dรผzenli aralฤฑklarla duvar kรขฤŸฤฑtlarฤฑnฤฑ otomatik olarak deฤŸiลŸtirin.", + "automation-scheduled-change-label": "ZamanlanmฤฑลŸ deฤŸiลŸiklik", + "look-feel-edge-smoothness-description": "GeรงiลŸlerin kenarฤฑna yumuลŸak, tรผylรผ bir efekt uygular.", + "look-feel-edge-smoothness-label": "GeรงiลŸ kenarฤฑnฤฑ yumuลŸat", + "look-feel-fill-color-description": "Duvar kรขฤŸฤฑdฤฑnฤฑn arkasฤฑnda gรถrรผnebilecek bir dolgu rengi seรงin.", + "look-feel-fill-mode-description": "Gรถrรผntรผnรผn ekran รงรถzรผnรผrlรผฤŸรผnรผzle eลŸleลŸmesi iรงin nasฤฑl รถlรงekleneceฤŸini seรงin.", + "look-feel-fill-mode-label": "Doldurma modu", + "look-feel-skip-startup-transition-description": "Kabuk baลŸladฤฑฤŸฤฑnda duvar kaฤŸฤฑdฤฑ animasyonunu atla.", + "look-feel-skip-startup-transition-label": "BaลŸlangฤฑรง geรงiลŸini atla", + "look-feel-title": "Gรถrรผnรผm ve his", + "look-feel-transition-duration-description": "GeรงiลŸ animasyonlarฤฑnฤฑn saniye cinsinden sรผresi.", + "look-feel-transition-duration-label": "GeรงiลŸ sรผresi", + "look-feel-transition-type-description": "Duvar kรขฤŸฤฑtlarฤฑ arasฤฑnda geรงiลŸ yaparken animasyon tรผrรผ.", + "look-feel-transition-type-label": "GeรงiลŸ tรผrรผ", + "settings-clear-cache-button": "ร–nbelleฤŸi temizle", + "settings-clear-cache-description": "ร–nbelleฤŸe alฤฑnmฤฑลŸ yeniden boyutlandฤฑrฤฑlmฤฑลŸ duvar kaฤŸฤฑtlarฤฑnฤฑ temizleyerek disk alanฤฑnฤฑ boลŸaltฤฑn.", + "settings-clear-cache-label": "Duvar kaฤŸฤฑdฤฑ รถnbelleฤŸi", + "settings-clear-cache-toast": "Duvar kaฤŸฤฑdฤฑ รถnbelleฤŸi temizlendi", + "settings-desc": "Duvar kรขฤŸฤฑtlarฤฑnฤฑn nasฤฑl yรถnetileceฤŸini ve gรถrรผntรผleneceฤŸini kontrol edin.", + "settings-enable-management-description": "Duvar kรขฤŸฤฑtlarฤฑnฤฑ Noctalia ile yรถnetin. BaลŸka bir uygulamayฤฑ kullanmayฤฑ tercih ediyorsanฤฑz iลŸareti kaldฤฑrฤฑn.", + "settings-enable-management-label": "Duvar kรขฤŸฤฑdฤฑ yรถnetimini etkinleลŸtir", + "settings-enable-overview-description": "Genel bakฤฑลŸ ekranฤฑna bulanฤฑk ve karartฤฑlmฤฑลŸ bir duvar kรขฤŸฤฑdฤฑ uygular.", + "settings-enable-overview-label": "Genel bakฤฑลŸ duvar kรขฤŸฤฑdฤฑnฤฑ etkinleลŸtir", + "settings-folder-description": "Ana duvar kรขฤŸฤฑdฤฑ klasรถrรผnรผzรผn yolu.", + "settings-folder-label": "Duvar kรขฤŸฤฑdฤฑ klasรถrรผ", + "settings-hide-wallpaper-filenames-tooltip-hide": "Dosya adlarฤฑnฤฑ gizleme", + "settings-hide-wallpaper-filenames-tooltip-show": "Dosya adlarฤฑnฤฑ gรถster", + "settings-monitor-specific-description": "Her ekran iรงin farklฤฑ bir duvar kรขฤŸฤฑdฤฑ klasรถrรผ ayarlayฤฑn.", + "settings-monitor-specific-label": "Ekrana รถzgรผ dizinler", + "settings-monitor-specific-tooltip": "Ekrana รถzgรผ duvar kรขฤŸฤฑdฤฑ klasรถrรผ", + "settings-overview-blur-strength-description": "Genel bakฤฑลŸa bulanฤฑklฤฑk gรผcรผnรผ uygular.", + "settings-overview-blur-strength-label": "Genel bakฤฑลŸ bulanฤฑklฤฑk gรผcรผ", + "settings-overview-tint-description": "Ton gรผcรผnรผ genel bakฤฑลŸa uygular.", + "settings-overview-tint-label": "Genel BakฤฑลŸ ton gรผcรผ", + "settings-recursive-search-description": "Duvar kรขฤŸฤฑdฤฑ dizinin alt klasรถrlerinde de duvar kรขฤŸฤฑdฤฑ arayฤฑn.", + "settings-recursive-search-label": "Alt klasรถrleri ara", + "settings-select-monitor-folder": "Ekran duvar kรขฤŸฤฑdฤฑ klasรถrรผnรผ seรง", + "settings-selector-description": "Duvar kรขฤŸฤฑdฤฑnฤฑzฤฑ seรงin.", + "settings-selector-position-description": "Duvar kรขฤŸฤฑdฤฑ seรงici panelinin nerede gรถrรผneceฤŸini seรงin.", + "settings-show-hidden-files-tooltip-hide": "Gizli dosyalarฤฑ gizle", + "settings-show-hidden-files-tooltip-show": "Gizli dosyalarฤฑ gรถster", + "settings-title": "Duvar kรขฤŸฤฑdฤฑ ayarlarฤฑ", + "settings-use-original-images-description": "Duvar kaฤŸฤฑtlarฤฑnฤฑ gรถrรผntรผlemeden รถnce yeniden boyutlandฤฑrmayฤฑ atla. Disk alanฤฑndan tasarruf saฤŸlar ve duvar kaฤŸฤฑdฤฑ deฤŸiลŸiklikleri sฤฑrasฤฑnda CPU kullanฤฑmฤฑnฤฑ azaltฤฑr, ancak รงok bรผyรผk gรถrรผntรผler iรงin daha fazla bellek kullanabilir.", + "settings-use-original-images-label": "Orijinal gรถrselleri kullan", + "settings-view-mode-description": "Duvar kaฤŸฤฑtlarฤฑnฤฑn dizininizden nasฤฑl gรถrรผntรผleneceฤŸini seรงin.", + "settings-view-mode-label": "Gรถrรผntรผleme modu", + "view-mode-browse": "Dizinlere gรถz at", + "view-mode-cycle-tooltip": "Gรถrรผnรผm modu: {mode} (deฤŸiลŸtirmek iรงin tฤฑklayฤฑn)", + "view-mode-recursive": "DรผzleลŸtirilmiลŸ alt dizinler", + "view-mode-single": "Kรถk dizin" + } + }, + "placeholders": { + "add-new-keybind": "Yeni tuลŸ atamasฤฑ ekle", + "command-example": "echo \"Hello World\"", + "enter-command": "ร‡alฤฑลŸtฤฑrฤฑlacak komutu girin (uygulama veya รถzel komut)", + "enter-ipc-identifier": "IPC komutlarฤฑ iรงin benzersiz bir tanฤฑmlayฤฑcฤฑ girin", + "enter-path": "Yolu girin...", + "enter-text-to-collapse": "รถrn. 'nothing is playing'. Desenler iรงin /regex/ kullanฤฑn.", + "enter-tooltip": "Araรง ipucunu girin", + "enter-width-pixels": "GeniลŸliฤŸi piksel olarak girin", + "keybind-recording": "TuลŸ kombinasyonu kaydediliyor...", + "search": "Ara...", + "search-icons": "รถrn. noctalia, niri, battery, cloud", + "search-launcher": "GiriลŸleri arayฤฑn... veya > komutlarฤฑ iรงin kullanฤฑn", + "search-wallhaven": "Wallhaven'da ara...", + "search-wallpapers": "Duvar kรขฤŸฤฑtlarฤฑnฤฑ filtrelemek iรงin yazฤฑn..." + }, + "positions": { + "bottom": "Alt", + "bottom-center": "Alt merkez", + "bottom-left": "Sol alt", + "bottom-right": "SaฤŸ alt", + "center": "Merkez", + "center-left": "Sol merkez", + "center-right": "SaฤŸ merkez", + "close-to-bar": "ร‡ubuฤŸa yakฤฑn kapat", + "follow-bar": "ร‡ubuฤŸu takip et", + "left": "Sol", + "right": "SaฤŸ", + "top": "รœst", + "top-center": "รœst merkez", + "top-left": "Sol รผst", + "top-right": "SaฤŸ รผst" + }, + "session-menu": { + "action-in-seconds": "{seconds} saniyede {action}...", + "cancel-timer": "Zamanlayฤฑcฤฑyฤฑ iptal et", + "title": "Oturum menรผsรผ" + }, + "setup": { + "all-done": "Tamamlandฤฑ!", + "appearance": { + "subheader": "Koyu modu ve renk kaynaklarฤฑnฤฑ (duvar kaฤŸฤฑdฤฑ veya รถnceden tanฤฑmlanmฤฑลŸ) seรงin." + }, + "customize": { + "header": "Deneyiminizi รถzelleลŸtirin", + "subheader": "Araรง รงubuฤŸu konumunu, yoฤŸunluฤŸunu, รถlรงeฤŸini ve daha fazlasฤฑnฤฑ ayarlayฤฑn." + }, + "skip-setup": "Kurulumu Atla", + "telemetry-wizard-done": "AnlaลŸฤฑldฤฑ!", + "telemetry-wizard-note": "Kontrol sizde โ€” istediฤŸiniz zaman ayarlar'dan etkinleลŸtirebilir veya devre dฤฑลŸฤฑ bฤฑrakabilirsiniz", + "telemetry-wizard-subtitle": "Noctalia'yฤฑ geliลŸtirmeye yardฤฑmcฤฑ olmak iรงin anonim analizler ekledik", + "telemetry-wizard-title": "Gizlilik Gรผncellemesi", + "wallpaper": { + "choose-dir": "Duvar kรขฤŸฤฑdฤฑ gรถrรผntรผlerinizi iรงeren bir dizin seรงin", + "dir-browse": "Duvar kรขฤŸฤฑdฤฑ klasรถrรผne gรถz at", + "dir-description": "Duvar kรขฤŸฤฑtlarฤฑnฤฑzฤฑ iรงeren klasรถrรผ seรงin.", + "dir-label": "Duvar kรขฤŸฤฑdฤฑ dizini", + "dir-select-title": "Duvar kรขฤŸฤฑdฤฑ klasรถrรผnรผ seรง", + "header": "Duvar kรขฤŸฤฑdฤฑnฤฑzฤฑ seรงin", + "no-dir": "Duvar kรขฤŸฤฑdฤฑ dizini seรงilmedi", + "no-valid": "ลžurada geรงerli gรถrรผntรผ dosyasฤฑ bulunamadฤฑ: {dir}", + "none-in-dir": "Dizinde duvar kรขฤŸฤฑdฤฑ bulunamadฤฑ", + "preview-error": "Gรถrรผntรผ yรผklenemedi", + "select-prompt": "AลŸaฤŸฤฑdan bir duvar kรขฤŸฤฑdฤฑ seรงin", + "subheader": "Gรผzel bir arka planla ortamฤฑ ayarlayฤฑn." + }, + "welcome": "HoลŸ Geldiniz", + "welcome-note": "BaลŸlamanฤฑz iรงin birkaรง temel ayar โ€” tam seรงenekler ayarlar'da", + "welcome-subtitle": "Masaรผstรผnรผzรผ size รถzel yapalฤฑm", + "welcome-title": "Noctalia'ya hoลŸ geldiniz!" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({koordinatlar})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "Hiรงbir medya oynatฤฑcฤฑ algฤฑlanmadฤฑ", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "Bilinmeyen uygulama", + "uptime": "ร‡alฤฑลŸma sรผresi: {uptime}", + "user-requested": "Kullanฤฑcฤฑ istedi", + "welcome-back": "HoลŸ geldiniz,", + "widget-settings-title": "{widget} ayarlarฤฑ" + }, + "system-monitor": { + "core-usage": "ร‡ekirdek {id} kullanฤฑmฤฑ", + "cpu-temp": "CPU sฤฑcaklฤฑฤŸฤฑ", + "cpu-usage": "CPU kullanฤฑmฤฑ", + "disk": "Disk", + "download-speed": "ฤฐndirme hฤฑzฤฑ", + "gpu-temp": "GPU sฤฑcaklฤฑฤŸฤฑ", + "load-average": "Ortalama yรผk", + "title": "Sistem iฬ‡zleyici", + "upload-speed": "Yรผkleme hฤฑzฤฑ" + }, + "toast": { + "airplane-mode": { + "description": "Tรผm kablosuz iletiลŸimleri devre dฤฑลŸฤฑ bฤฑrak.", + "title": "Uรงak modu" + }, + "battery": { + "critical": "Kritik pil seviyesi", + "critical-desc": "Pil {percent}% โ€” lรผtfen ลŸarj cihazฤฑnฤฑ hemen baฤŸlayฤฑn", + "low": "DรผลŸรผk batarya", + "low-desc": "Pil {percent}% seviyesinde โ€” lรผtfen ลŸarj cihazฤฑnฤฑ baฤŸlayฤฑn" + }, + "bluetooth": { + "address-copied": "Adres panoya kopyalandฤฑ", + "auto-connect-disabled": "Otomatik baฤŸlantฤฑ devre dฤฑลŸฤฑ", + "auto-connect-enabled": "Otomatik baฤŸlanma etkin", + "auto-connecting": "{count} cihaza baฤŸlanฤฑlฤฑyor...", + "confirm-code": "DiฤŸer cihazdaki {value} kodunu onayla.", + "connect-failed": "Cihaza baฤŸlanฤฑlamadฤฑ", + "disconnect-failed": "Cihazla baฤŸlantฤฑ kesilemedi", + "discoverable-change-failed": "KeลŸfedilebilir durum deฤŸiลŸtirilemedi", + "discoverable-disabled": "KeลŸfedilebilir devre dฤฑลŸฤฑ", + "discoverable-enabled": "KeลŸfedilebilir etkinleลŸtirildi", + "display-code": "DiฤŸer cihazda {value} kodunu girin.", + "forget-failed": "Cihazฤฑ unutma baลŸarฤฑsฤฑz oldu", + "pair-failed": "Cihaz eลŸleลŸtirilemedi", + "passkey-required": "Cihaz tarafฤฑndan istenen geรงiลŸ anahtarฤฑ.", + "pincode-required": "Cihaz tarafฤฑndan PIN kodu gerekiyor.", + "state-change-failed": "Bluetooth durumu deฤŸiลŸtirilemedi" + }, + "clipboard": { + "long-text": "Uzun metin", + "unavailable": "Panoya geรงmiลŸi kullanฤฑlamฤฑyor", + "unavailable-desc": "'cliphist' uygulamasฤฑ kurulu deฤŸil. Lรผtfen panoya geรงmiลŸi รถzelliklerini kullanmak iรงin kurun" + }, + "custom-command-failed": { + "description": "Komut baลŸarฤฑsฤฑz oldu: {command}\\nร‡ฤฑkฤฑลŸ kodu: {code}", + "title": "ร–zel komut baลŸarฤฑsฤฑz oldu" + }, + "do-not-disturb": { + "disabled": "'Rahatsฤฑz etme' devre dฤฑลŸฤฑ", + "disabled-desc": "Tรผm bildirimler gรถsteriliyor", + "enabled": "'Rahatsฤฑz etme' etin", + "enabled-desc": "Bu bildirimleri geรงmiลŸinizde bulacaksฤฑnฤฑz" + }, + "donation-opened": "BaฤŸฤฑลŸ sayfasฤฑ tarayฤฑcฤฑnฤฑzda aรงฤฑldฤฑ", + "internet-limited": "ฤฐnternetsiz baฤŸlandฤฑ", + "keyboard-layout": { + "changed": "Klavye dรผzeni {layout} olarak deฤŸiลŸtirildi", + "title": "Klavye" + }, + "missing-control-center": { + "description": "Kontrol merkezi araรง takฤฑmฤฑ araรง รงubuฤŸundan kaldฤฑrฤฑldฤฑ. Araรง รงubuฤŸundan tekrar eriลŸmek iรงin araรง takฤฑmฤฑnฤฑ yeniden eklemeniz gerekecek. Araรง รงubuฤŸuna saฤŸ tฤฑklayarak da aรงabilirsiniz", + "label": "Son kontrol merkezi araรง takฤฑmฤฑ kaldฤฑrฤฑldฤฑ" + }, + "night-light": { + "forced": "Zorla etkinleลŸtirme", + "normal": "Normal mod", + "not-installed": "wlsunset kurulu deฤŸil" + }, + "noctalia-performance": { + "disabled": "Performans modu devre dฤฑลŸฤฑ bฤฑrakฤฑldฤฑ", + "enabled": "Performans modu etkinleลŸtirildi", + "label": "Noctalia Performans" + }, + "power-profile": { + "changed": "Gรผรง profili deฤŸiลŸti", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "ลžablonlar iลŸlenirken bir hatayla karลŸฤฑlaลŸฤฑldฤฑ", + "title": "ลžablon iลŸleme baลŸarฤฑsฤฑz oldu" + }, + "vpn": { + "connected": "'{name}' ile baฤŸlantฤฑ kuruldu", + "disconnected": "'{name}' baฤŸlantฤฑsฤฑ kesildi" + }, + "wallpaper-colors": { + "disabled": "Duvar kรขฤŸฤฑdฤฑ renkleri devre dฤฑลŸฤฑ", + "enabled": "Duvar kรขฤŸฤฑdฤฑ renkleri etkin", + "label": "Duvar kรขฤŸฤฑdฤฑ renkleri oluลŸturma" + }, + "wifi": { + "connected": "'{ssid}' ile baฤŸlandฤฑ", + "connection-failed": "BaฤŸlantฤฑ baลŸarฤฑsฤฑz", + "connection-timeout": "BaฤŸlantฤฑ zaman aลŸฤฑmฤฑ", + "disconnected": "'{ssid}' baฤŸlantฤฑsฤฑ kesildi", + "incorrect-password": "YanlฤฑลŸ ลŸifre", + "network-not-found": "AฤŸ bulunamadฤฑ" + } + }, + "tooltips": { + "add-widget": "Araรง takฤฑmฤฑ ekle", + "auto-connect": "Bu cihaz iรงin otomatik baฤŸlanmayฤฑ aรง/kapa", + "bluetooth-auto-connect-off": "Otomatik baฤŸlanma kapalฤฑ", + "bluetooth-auto-connect-on": "Otomatik baฤŸlanma aรงฤฑk", + "bluetooth-devices": "Bluetooth cihazlarฤฑ", + "brightness-at": "Parlaklฤฑk: %{brightness}", + "click-to-start-recording": "Ekran kaydedici (kaydฤฑ baลŸlat)", + "click-to-stop-recording": "Ekran kaydedici (kaydฤฑ durdur)", + "collapse": "Kenar รงubuฤŸunu daralt", + "copy-address": "Adresi kopyala", + "delete-notification": "Bildiriyi sil", + "dismiss-notification": "Bildirimi kapat", + "do-not-disturb-enabled": "Rahatsฤฑz etme", + "expand": "Kenar รงubuฤŸunu geniลŸlet", + "forget-network": "AฤŸฤฑ unut", + "grid-view": "Izgara gรถrรผnรผmรผ", + "hidden-files-hide": "Gizli dosyalar", + "hide-unnamed-devices": "Adsฤฑz cihazlarฤฑ gizle", + "home": "Ana dizin", + "input-muted": "GiriลŸ sesini kapat/aรง", + "keep-awake": "Uyanฤฑk kal", + "keyboard-layout": "{layout} klavye dรผzeni", + "list-view": "Liste gรถrรผnรผmรผ", + "manage-vpn": "VPN baฤŸlantฤฑlarฤฑ", + "manage-wifi": "Kablosuz baฤŸlantฤฑ", + "max-widgets-reached": "Maksimum araรง sayฤฑsฤฑna ulaลŸฤฑldฤฑ", + "microphone-volume-at": "Mikrofon sesi: %{volume}", + "move-to-section": "{section} bรถlรผmรผne git", + "mute": "Sessiz", + "next-media": "Sonraki parรงa", + "next-month": "Sonraki ay", + "night-light-not-installed": "Gece ฤฑลŸฤฑฤŸฤฑ (mevcut deฤŸil)", + "noctalia-performance-enabled": "Noctalia performans modu", + "open-annotation-tool": "Aรงฤฑklama aracฤฑyla aรง", + "open-control-center": "Kontrol merkezi", + "open-notification-history-enable-dnd": "Bildirim geรงmiลŸi", + "open-settings": "Ayarlar", + "open-tray-dropdown": "Sistem tepsisi", + "output-muted": "ร‡ฤฑktฤฑ sesini kapat/aรง", + "power-profile": "{profile} gรผรง profili", + "previous-media": "ร–nceki parรงa", + "previous-month": "ร–nceki ay", + "refresh-devices": "Cihazlarฤฑ yenile", + "refresh-wallhaven": "Wallhaven sonuรงlarฤฑnฤฑ yenile", + "refresh-wallpaper-list": "Duvar kรขฤŸฤฑdฤฑ listesini yenile", + "remove-widget": "Araรง takฤฑmฤฑnฤฑ kaldฤฑr", + "search-close": "Aramayฤฑ kapat", + "session-menu": "Oturum menรผsรผ", + "show-all-devices": "Tรผm cihazlarฤฑ gรถster", + "switch-to-dark-mode": "Koyu mod", + "switch-to-light-mode": "Aรงฤฑk mod", + "unmute": "SessizliฤŸi aรง", + "up": "รœst dizin", + "volume-at": "ร‡ฤฑkฤฑลŸ sesi: %{volume}", + "wallpaper-selector": "Duvar kรขฤŸฤฑdฤฑ seรงici" + }, + "wallpaper": { + "browse": { + "empty-directory": "Bu dizin boลŸ.", + "go-root": "Duvar kaฤŸฤฑdฤฑ kรถkรผne git", + "go-up": "รœst klasรถre git", + "go-up-hint": "Yukarฤฑ gitmek iรงin geri dรผฤŸmesini kullanฤฑn." + }, + "configure-directory": "Resimler iรงeren duvar kรขฤŸฤฑdฤฑ dizininizi yapฤฑlandฤฑrฤฑn.", + "fill-modes": { + "crop": "Kฤฑrp (Doldur)", + "fit": "SฤฑฤŸ (ฤฐรงer)", + "repeat": "Tekrarla (DรถลŸe)", + "stretch": "Ger" + }, + "no-match": "EลŸleลŸme bulunamadฤฑ.", + "no-wallpaper": "Duvar kรขฤŸฤฑdฤฑ bulunamadฤฑ.", + "panel": { + "apikey-help": "NSFW iรงeriฤŸe eriลŸmek iรงin bir API anahtarฤฑ gereklidir.", + "apikey-label": "API anahtarฤฑ", + "apikey-managed-by-env": "NOCTALIA_WALLHAVEN_API_KEY ortam deฤŸiลŸkeni aracฤฑlฤฑฤŸฤฑyla yรถnetilir.", + "apikey-placeholder": "Wallhaven API anahtarฤฑnฤฑzฤฑ girin", + "appearance-dark-tab": "Koyu", + "appearance-light-tab": "Aรงฤฑk", + "categories-anime": "Anime", + "categories-label": "Kategoriler", + "categories-people": "ฤฐnsanlar", + "color-extraction-disabled": "Duvar kaฤŸฤฑdฤฑ renk รงฤฑkarma kullan", + "color-extraction-enabled": "ร–nceden tanฤฑmlanmฤฑลŸ renk ลŸemalarฤฑnฤฑ kullan", + "header-devices-apply-all-tooltip": "Monitรถr baลŸฤฑna duvar kaฤŸฤฑdฤฑ seรง", + "header-devices-per-monitor-tooltip": "Tรผm monitรถrlere uygula", + "header-link-light-dark-tooltip": "Tema deฤŸiลŸtirirken duvar kaฤŸฤฑdฤฑnฤฑ koru", + "header-separate-light-dark-tooltip": "Temayla birlikte duvar kaฤŸฤฑdฤฑnฤฑ otomatik deฤŸiลŸtir", + "header-sun-linked-tooltip": "Aรงฤฑk ve koyu duvar kaฤŸฤฑtlarฤฑnฤฑ ayฤฑr", + "header-sun-separate-tooltip": "Aรงฤฑk ve koyu iรงin aynฤฑ duvar kaฤŸฤฑdฤฑ", + "order-asc": "Artan", + "order-desc": "Azalan", + "order-label": "Sฤฑra", + "purity-label": "ฤฐรงerik filtresi", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "Sketchy", + "ratios-all-portrait": "Tรผm dikey", + "ratios-all-wide": "Tรผm geniลŸ", + "ratios-any": "Herhangi biri", + "ratios-label": "En boy oranฤฑ", + "resolution-atleast": "En az", + "resolution-exact": "Tam", + "resolution-label": "ร‡รถzรผnรผrlรผk", + "resolution-mode-label": "Mod", + "solid-color-tooltip": "Dรผz renk arka plan", + "sort-date-asc": "En eskiden en yeniye sฤฑrala", + "sort-date-desc": "En yeniden en eskiye sฤฑrala", + "sort-name-asc": "Ada gรถre sฤฑrala (A-Z)", + "sort-name-desc": "Ada gรถre sฤฑrala (Z-A)", + "sort-random": "Rastgele sฤฑrala", + "sorting-date-added": "Eklenme tarihi", + "sorting-favorites": "Favoriler", + "sorting-label": "Sฤฑrala", + "sorting-relevance": "Alaka", + "sorting-toplist": "En iyi liste", + "sorting-views": "Gรถrรผntรผlemeler", + "source-label": "Kaynak", + "source-wallhaven": "Wallhaven", + "title": "Duvar kรขฤŸฤฑdฤฑ seรงici", + "wallhaven-settings-title": "Wallhaven ayarlarฤฑ" + }, + "transitions": { + "disc": "Disk", + "fade": "BelirsizleลŸme", + "honeycomb": "Petek", + "pixelate": "PikselleลŸtir", + "stripes": "ร‡izgiler", + "wipe": "Silme" + }, + "try-different-search": "Farklฤฑ bir arama sorgusu deneyin.", + "wallhaven": { + "loading": "Duvar kรขฤŸฤฑtlarฤฑ yรผkleniyor...", + "no-results": "Duvar kรขฤŸฤฑdฤฑ bulunamadฤฑ. Farklฤฑ bir arama sorgusu deneyin.", + "page": "{current} / {total}", + "page-prefix": "Sayfa", + "page-suffix": "{total} taneden" + } + }, + "weather": { + "clear-sky": "Aรงฤฑk hava", + "drizzle": "YaฤŸmurlu", + "fog": "Sisli", + "mainly-clear": "Genellikle aรงฤฑk", + "overcast": "Bulutlu", + "partly-cloudy": "Parรงalฤฑ bulutlu", + "rain-showers": "SaฤŸanak yaฤŸmur", + "snow": "Karlฤฑ", + "thunderstorm": "Fฤฑrtฤฑna" + }, + "widgets": { + "color-picker": { + "palette-description": "ร–nceden tanฤฑmlฤฑ renkler geniลŸ yelpazesinden seรงin.", + "palette-label": "Palet", + "palette-theme-colors": "Temanฤฑzฤฑn renk paletine hฤฑzlฤฑ eriลŸim.", + "title": "Renk seรงici" + }, + "datetime-tokens": { + "ampm-lowercase": "Kรผรงรผk harfli am/pm", + "ampm-uppercase": "Bรผyรผk harfli AM/PM", + "common-12hour-time-minutes": "12 saatlik zaman ile dakika", + "common-24hour-time-minutes": "24 saatlik zaman ile dakika", + "common-24hour-time-seconds": "24 saatlik zaman ile saniye", + "common-european-date": "Avrupa tarih formatฤฑ", + "common-iso-date": "ISO tarih formatฤฑ", + "common-us-date": "ABD tarih formatฤฑ", + "common-weekday-date": "Tarih ile hafta gรผnรผ", + "common-weekday-month-day": "Hafta gรผnรผ, ay ve gรผn", + "day-abbreviated": "KฤฑsaltฤฑlmฤฑลŸ gรผn adฤฑ", + "day-full": "Tam gรผn adฤฑ", + "day-leading-zero": "Sฤฑfฤฑr ile baลŸlayan gรผn (01-31)", + "day-no-leading-zero": "Sฤฑfฤฑr olmadan gรผn (1-31)", + "hour-leading-zero": "Sฤฑfฤฑr ile baลŸlayan saat (00-23) โ€” 24 saat formatฤฑ", + "hour-no-leading-zero": "Sฤฑfฤฑr olmadan saat (0-23) โ€” 24 saat formatฤฑ", + "minute-leading-zero": "Sฤฑfฤฑr ile baลŸlayan dakika (00-59)", + "minute-no-leading-zero": "Sฤฑfฤฑr olmadan dakika (0-59)", + "month-abbreviated": "KฤฑsaltฤฑlmฤฑลŸ ay adฤฑ", + "month-full": "Tam ay adฤฑ", + "month-number-leading-zero": "Sฤฑfฤฑr ile baลŸlayan sayฤฑ olarak ay (01-12)", + "month-number-no-zero": "Sฤฑfฤฑr olmadan sayฤฑ olarak ay (1-12)", + "second-leading-zero": "Sฤฑfฤฑr ile baลŸlayan saniye (00-59)", + "second-no-leading-zero": "Sฤฑfฤฑr olmadan saniye (0-59)", + "timezone-abbreviation": "Saat dilimi kฤฑsaltmasฤฑ", + "year-four-digit": "Dรถrt haneli sayฤฑ olarak yฤฑl", + "year-two-digit": "ฤฐki haneli sayฤฑ olarak yฤฑl (00-99)" + }, + "file-picker": { + "item": "รถฤŸe", + "items": "รถฤŸeler", + "search-placeholder": "Dosya ve klasรถrlerde ara...", + "select-current": "Mevcudu seรง", + "select-file": "Dosya seรง", + "select-folder": "Klasรถr seรง", + "selected": "Seรงilen:", + "title": "Dosya seรงici" + }, + "file-picker-title": "Bir dosya seรงin", + "icon-picker": { + "title": "Simge seรงici" + }, + "tooltip-placeholder": "Yer tutucu" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "Anonim Kimlik", + "ca-cert": "CA Sertifikasฤฑ", + "eap-method": "EAP Yรถntemi", + "password": "Kullanฤฑcฤฑ ลžifresi", + "phase2-auth": "Faz 2 Kimlik DoฤŸrulama", + "username": "Kimlik / Kullanฤฑcฤฑ Adฤฑ" + }, + "panel": { + "action-required": "Eylem gerekiyor", + "add-hidden-network": "Gizli aฤŸ ekle", + "add-network": "AฤŸ ekle", + "available-interfaces": "Kullanฤฑlabilir arayรผzler", + "available-networks": "Kullanฤฑlabilir aฤŸlar", + "disabled": "Kablosuz baฤŸlantฤฑ devre dฤฑลŸฤฑ", + "disconnecting": "BaฤŸlantฤฑ kesiliyor...", + "dns": "DNS", + "enable-message": "Mevcut aฤŸlarฤฑ gรถrmek iรงin kablosuz baฤŸlantฤฑyฤฑ etkinleลŸtirin.", + "enter-password": "Parola girin...", + "forget": "Unut", + "forget-network": "Bu aฤŸฤฑ unut?", + "forgetting": "Unutuluyor...", + "hidden-network": "Gizli aฤŸ", + "interface": "AฤŸ arayรผzรผ", + "internet-connected": "ฤฐnternete baฤŸlฤฑ", + "internet-limited": "ฤฐnternet yok", + "internet-status": "ฤฐnternet durumu", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "Bilinen aฤŸlar", + "link-speed": "BaฤŸlantฤฑ hฤฑzฤฑ", + "network-name-ssid": "AฤŸ adฤฑ (SSID)", + "no-ethernet-devices": "Ethernet aygฤฑtฤฑ algฤฑlanmadฤฑ", + "no-networks": "AฤŸ bulunamadฤฑ", + "saved": "Kaydedildi", + "scan-again": "Yeniden tara", + "searching": "Yakฤฑndaki aฤŸlar aranฤฑyor...", + "security-open": "Aรง", + "security-wep": "WEP", + "security-wpa": "WPA/WPA2/WPA3 KiลŸisel", + "security-wpa-ent": "WPA Enterprise", + "security-wpa2-ent": "WPA2 Enterprise", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 Enterprise", + "show-password": "ลžifreyi gรถster", + "title": "Kablosuz baฤŸlantฤฑ" + }, + "signal": { + "excellent": "Mรผkemmel", + "fair": "Orta", + "good": "ฤฐyi", + "poor": "Kรถtรผ", + "weak": "Zayฤฑf" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/uk-UA.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/uk-UA.json new file mode 100644 index 0000000..073933f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/uk-UA.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "ะžั‡ะธัั‚ะธั‚ะธ ั–ัั‚ะพั€ั–ัŽ", + "close-app": "ะ—ะฐะบั€ะธั‚ะธ {app}", + "connect-vpn": "ะŸั–ะดะบะปัŽั‡ะธั‚ะธัั ะดะพ {name}", + "cycle-visualizer": "ะ’ั–ะทัƒะฐะปั–ะทะฐั‚ะพั€ ั†ะธะบะปั–ะฒ", + "disable-bluetooth": "ะ’ะธะผะบะฝัƒั‚ะธ Bluetooth", + "disable-dnd": "ะ’ะธะผะบะฝัƒั‚ะธ ั€ะตะถะธะผ 'ะะต ั‚ัƒั€ะฑัƒะฒะฐั‚ะธ'", + "disable-wifi": "ะ’ะธะผะบะฝัƒั‚ะธ Wi-Fi", + "disconnect-vpn": "ะ’ั–ะดะบะปัŽั‡ะธั‚ะธ {name}", + "dock-settings": "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ะดะพะบะฐ", + "enable-bluetooth": "ะฃะฒั–ะผะบะฝัƒั‚ะธ Bluetooth", + "enable-dnd": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ั€ะตะถะธะผ 'ะะต ั‚ัƒั€ะฑัƒะฒะฐั‚ะธ'", + "enable-wifi": "ะฃะฒั–ะผะบะฝัƒั‚ะธ Wi-Fi", + "launcher-settings": "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ะฟะฐะฝะตะปั– ะทะฐะฟัƒัะบัƒ", + "lower-to-bottom": "ะžะฟัƒัั‚ะธั‚ะธ ะดะพะฝะธะทัƒ", + "open-calendar": "ะ’ั–ะดะบั€ะธั‚ะธ ะบะฐะปะตะฝะดะฐั€", + "open-display-settings": "ะŸะฐั€ะฐะผะตั‚ั€ะธ ะดะธัะฟะปะตั", + "open-launcher": "ะ’ั–ะดะบั€ะธั‚ะธ ะฟะฐะฝะตะปัŒ ะทะฐะฟัƒัะบัƒ", + "open-settings": "ะ’ั–ะดะบั€ะธั‚ะธ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั", + "raise-to-top": "ะŸั–ะดะฝัั‚ะธ ะฒะณะพั€ัƒ", + "random-wallpaper": "ะ’ะธะฟะฐะดะบะพะฒั– ัˆะฟะฐะปะตั€ะธ", + "run-custom-command": "ะ’ะธะบะพะฝะฐั‚ะธ ะฒะปะฐัะฝัƒ ะบะพะผะฐะฝะดัƒ", + "show-active-only": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะปะธัˆะต ะฐะบั‚ะธะฒะฝั–", + "show-all": "ะŸะพะบะฐะทะฐั‚ะธ ะฒัะต", + "toggle-mute": "ะฃะฒั–ะผะบะฝัƒั‚ะธ/ะฒะธะผะบะฝัƒั‚ะธ ะทะฒัƒะบ", + "widget-settings": "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ะฒั–ะดะถะตั‚ะฐ" + }, + "authentication": { + "error": "ะŸะพะผะธะปะบะฐ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั—", + "failed": "ะะตะฒะดะฐะปะฐ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั" + }, + "bar": { + "active-window": { + "colorize-icons-description": "ะ—ะฐัั‚ะพััƒะฒะฐั‚ะธ ะบะพะปัŒะพั€ะธ ั‚ะตะผะธ ะดะพ ะทะฝะฐั‡ะบะฐ ะฐะบั‚ะธะฒะฝะพะณะพ ะฒั–ะบะฝะฐ.", + "hide-mode-description": "ะšะตั€ัƒั” ะฟะพะฒะตะดั–ะฝะบะพัŽ ะฒั–ะดะถะตั‚ะฐ, ะบะพะปะธ ะฝะตะผะฐั” ะฐะบั‚ะธะฒะฝะพะณะพ ะฒั–ะบะฝะฐ.", + "scrolling-mode-description": "ะšะตั€ัƒะฒะฐะฝะฝั, ะบะพะปะธ ัƒะฒั–ะผะบะฝะตะฝะพ ะฟั€ะพะบั€ัƒั‚ะบัƒ ั‚ะตะบัั‚ัƒ ะดะปั ะดะพะฒะณะธั… ะทะฐะณะพะปะพะฒะบั–ะฒ ะฒั–ะบะพะฝ.", + "show-app-icon-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะทะฝะฐั‡ะพะบ ะทะฐัั‚ะพััƒะฝะบะฐ ะฑั–ะปั ะทะฐะณะพะปะพะฒะบะฐ ะฒั–ะบะฝะฐ.", + "show-app-icon-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะทะฝะฐั‡ะพะบ ะทะฐัั‚ะพััƒะฝะบะฐ", + "show-app-text-description": "ะ’ั–ะดะพะฑั€ะฐะทะธั‚ะธ ะทะฐะณะพะปะพะฒะพะบ ะฐะบั‚ะธะฒะฝะพะณะพ ะฒั–ะบะฝะฐ.", + "show-app-text-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะทะฐะณะพะปะพะฒะพะบ ะฒั–ะบะฝะฐ" + }, + "audio-visualizer": { + "color-name-description": "ะ’ะธะฑั€ะฐั‚ะธ ะบะพะปั–ั€ ะดะปั ะฒั–ะทัƒะฐะปั–ะทะฐั‚ะพั€ะฐ.", + "color-name-label": "ะšะพะปั–ั€ ะทะฐะฟะพะฒะฝะตะฝะฝั", + "height-description": "ะšะพั€ะธัั‚ัƒะฒะฐั†ัŒะบะฐ ัˆะธั€ะธะฝะฐ ะบะพะผะฟะพะฝะตะฝั‚ะฐ.", + "hide-when-idle-description": "ะšะพะปะธ ัƒะฒั–ะผะบะฝะตะฝะพ, ะฒั–ะทัƒะฐะปั–ะทะฐั‚ะพั€ ะฟั€ะธั…ะพะฒะฐะฝะพ, ัะบั‰ะพ ะฟะปะตั”ั€ ะฐะบั‚ะธะฒะฝะพ ะฝะต ะฒั–ะดั‚ะฒะพั€ัŽั”.", + "hide-when-idle-label": "ะŸั€ะธั…ะพะฒะฐั‚ะธ, ะบะพะปะธ ะผะตะดั–ะฐ ะฝะต ะฒั–ะดั‚ะฒะพั€ัŽั”ั‚ัŒัั", + "width-description": "ะ’ะปะฐัะฝะฐ ัˆะธั€ะธะฝะฐ ะบะพะผะฟะพะฝะตะฝั‚ะฐ." + }, + "battery": { + "device-default": "ะŸั€ะธัั‚ั€ั–ะน ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ", + "device-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะฟั€ะธัั‚ั€ั–ะน ะท ะฐะบัƒะผัƒะปัั‚ะพั€ะพะผ ะดะปั ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั.", + "device-label": "ะŸั€ะธัั‚ั€ั–ะน ะถะธะฒะปะตะฝะฝั ะฒั–ะด ะฑะฐั‚ะฐั€ะตั—", + "display-mode-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ัะบ ะฒั–ะดะพะฑั€ะฐะถะฐั”ั‚ัŒัั ะฑะฐั‚ะฐั€ะตั ะฝะฐ ะฟะฐะฝะตะปั–.", + "display-mode-graphic": "ะ“ั€ะฐั„ั–ั‡ะฝะฐ ะฑะฐั‚ะฐั€ะตั", + "display-mode-graphic-clean": "ะ“ั€ะฐั„ั–ั‡ะฝะฐ ะฑะฐั‚ะฐั€ะตั (ะฑะตะท %)", + "display-mode-icon-always": "ะ†ะบะพะฝะบะฐ - ะ—ะฐะฒะถะดะธ ะฟะพะบะฐะทัƒะฒะฐั‚ะธ %", + "display-mode-icon-hover": "ะ†ะบะพะฝะบะฐ - ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟั€ะธ ะฝะฐะฒะตะดะตะฝะฝั–", + "display-mode-icon-only": "ะ›ะธัˆะต ะทะฝะฐั‡ะพะบ", + "hide-if-idle-description": "ะŸั€ะธั…ะพะฒะฐั‚ะธ ะฒั–ะดะถะตั‚, ะบะพะปะธ ะฐะบัƒะผัƒะปัั‚ะพั€ ะฝะต ะทะฐั€ัะดะถะฐั”ั‚ัŒัั ั‚ะฐ ะฝะต ั€ะพะทั€ัะดะถะฐั”ั‚ัŒัั.", + "hide-if-idle-label": "ะŸั€ะธั…ะพะฒะฐั‚ะธ, ะบะพะปะธ ะฝะตะฐะบั‚ะธะฒะฝะธะน", + "hide-if-not-detected-description": "ะŸั€ะธั…ะพะฒะฐั‚ะธ ะฒั–ะดะถะตั‚, ะบะพะปะธ ะฒ ัะธัั‚ะตะผั– ะฝะต ะฒะธัะฒะปะตะฝะพ ะฐะบัƒะผัƒะปัั‚ะพั€.", + "hide-if-not-detected-label": "ะŸั€ะธั…ะพะฒะฐั‚ะธ, ัะบั‰ะพ ะฝะต ะฒะธัะฒะปะตะฝะพ", + "low-battery-threshold-label": "ะŸะพั€ั–ะณ ะฟะพะฟะตั€ะตะดะถะตะฝะฝั ะฟั€ะพ ะฝะธะทัŒะบะธะน ะทะฐั€ัะด ะฑะฐั‚ะฐั€ะตั—", + "show-noctalia-performance-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะฟะตั€ะตะผะธะบะฐั‡ ั€ะตะถะธะผัƒ ะฟั€ะพะดัƒะบั‚ะธะฒะฝะพัั‚ั– Noctalia ะฒ ะฟะฐะฝะตะปั– ะฑะฐั‚ะฐั€ะตั—.
ะ’ะธะผะธะบะฐั” ั‚ั–ะฝั– ั‚ะฐ ะฐะฝั–ะผะฐั†ั–ั— ะฒ Noctalia ะดะปั ะทะผะตะฝัˆะตะฝะฝั ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ั€ะตััƒั€ัั–ะฒ.", + "show-noctalia-performance-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟะตั€ะตะผะธะบะฐั‡ ะŸั€ะพะดัƒะบั‚ะธะฒะฝั–ัั‚ัŒ Noctalia", + "show-power-profile-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะฒะธะฑั–ั€ ะฟั€ะพั„ั–ะปัŽ ะถะธะฒะปะตะฝะฝั ะฒ ะฟะฐะฝะตะปั– ะฑะฐั‚ะฐั€ะตั—.", + "show-power-profile-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะตะปะตะผะตะฝั‚ะธ ะบะตั€ัƒะฒะฐะฝะฝั ะฟั€ะพั„ั–ะปะตะผ ะถะธะฒะปะตะฝะฝั" + }, + "brightness": { + "apply-all-description": "ะ—ะผั–ะฝัŽะฒะฐั‚ะธ ััะบั€ะฐะฒั–ัั‚ัŒ ะดะปั ะฒัั–ั… ะผะพะฝั–ั‚ะพั€ั–ะฒ ะฟั€ะธ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั– ะบะพะปะตัะฐ ะฟั€ะพะบั€ัƒั‚ะบะธ.", + "apply-all-label": "ะ—ะฐัั‚ะพััƒะฒะฐั‚ะธ ะทะผั–ะฝะธ ะฟั€ะพะบั€ัƒั‚ะบะธ ะดะพ ะฒัั–ั… ะผะพะฝั–ั‚ะพั€ั–ะฒ" + }, + "clock": { + "clock-display-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ะณะพะดะธะฝะฝะธะบะฐ, ะดะพะดะฐะฒัˆะธ ั‚ะพะบะตะฝะธ ะทั– ัะฟะธัะบัƒ ะฝะธะถั‡ะต. ะ”ะปั ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั 12-ะณะพะดะธะฝะฝะพะณะพ ั„ะพั€ะผะฐั‚ัƒ ะฒะธ ะฟะพะฒะธะฝะฝั– ะฒะบะปัŽั‡ะธั‚ะธ ั‚ะพะบะตะฝ 'AP'.", + "clock-display-label": "ะ’ั–ะดะพะฑั€ะฐะถะตะฝะฝั ะณะพะดะธะฝะฝะธะบะฐ", + "custom-font-description": "ะ’ะธะฑั€ะฐั‚ะธ ะฒะปะฐัะฝะธะน ัˆั€ะธั„ั‚ ะดะปั ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ะณะพะดะธะฝะฝะธะบะฐ.", + "custom-font-label": "ะ’ะปะฐัะฝะธะน ัˆั€ะธั„ั‚", + "custom-font-placeholder": "ะ’ะธะฑั€ะฐั‚ะธ ะฒะปะฐัะฝะธะน ัˆั€ะธั„ั‚...", + "custom-font-search-placeholder": "ะŸะพัˆัƒะบ ัˆั€ะธั„ั‚ั–ะฒ...", + "horizontal-bar-description": "ะŸะพั€ะฐะดะฐ: ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต \\n ะดะปั ัั‚ะฒะพั€ะตะฝะฝั ั€ะพะทั€ะธะฒัƒ ั€ัะดะบะฐ.", + "horizontal-bar-label": "ะ“ะพั€ะธะทะพะฝั‚ะฐะปัŒะฝะฐ ะฟะฐะฝะตะปัŒ", + "preview": "ะŸะพะฟะตั€ะตะดะฝั–ะน ะฟะตั€ะตะณะปัะด", + "tooltip-format-description": "ะ ัะดะพะบ ั„ะพั€ะผะฐั‚ัƒ ะดะปั ะฟั–ะดะบะฐะทะบะธ, ั‰ะพ ะฒั–ะดะพะฑั€ะฐะถะฐั”ั‚ัŒัั ะฟั€ะธ ะฝะฐะฒะตะดะตะฝะฝั– ะฝะฐ ะณะพะดะธะฝะฝะธะบ. ะ—ะฐะปะธัˆั‚ะต ะฟะพั€ะพะถะฝั–ะผ, ั‰ะพะฑ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ัั‚ะฐะฝะดะฐั€ั‚ะฝัƒ ะฟั–ะดะบะฐะทะบัƒ.", + "tooltip-format-label": "ะคะพั€ะผะฐั‚ ะฟั–ะดะบะฐะทะบะธ", + "use-custom-font-description": "ะ—ะฐะผั–ะฝะธั‚ะธ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะน ะฒะธะฑั–ั€ ัˆั€ะธั„ั‚ัƒ ะฒะปะฐัะฝะธะผ ัˆั€ะธั„ั‚ะพะผ ะดะปั ะณะพะดะธะฝะฝะธะบะฐ.", + "use-custom-font-label": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฒะปะฐัะฝะธะน ัˆั€ะธั„ั‚", + "use-monospaced-font-description": "ะšะพะปะธ ัƒะฒั–ะผะบะฝะตะฝะพ, ะณะพะดะธะฝะฝะธะบ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธะผะต ะผะพะฝะพัˆะธั€ะธะฝะฝะธะน ัˆั€ะธั„ั‚.", + "use-monospaced-font-label": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะผะพะฝะพัˆะธั€ะธะฝะฝะธะน ัˆั€ะธั„ั‚", + "vertical-bar-description": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ะฟั€ะพะฑั–ะป ะดะปั ั€ะพะทะดั–ะปะตะฝะฝั ะบะพะถะฝะพั— ั‡ะฐัั‚ะธะฝะธ ะฝะฐ ะฝะพะฒะธะน ั€ัะดะพะบ.", + "vertical-bar-label": "ะ’ะตั€ั‚ะธะบะฐะปัŒะฝะฐ ะฟะฐะฝะตะปัŒ" + }, + "control-center": { + "browse-file": "ะžะณะปัะด ั„ะฐะนะปัƒ", + "browse-library": "ะžะณะปัะด ะฑั–ะฑะปั–ะพั‚ะตะบะธ", + "color-selection-description": "ะ—ะฐัั‚ะพัะพะฒัƒั” ะบะพะปัŒะพั€ะธ ั‚ะตะผะธ ะดะพ ั–ะบะพะฝะพะบ.", + "enable-colorization-description": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ั€ะพะทั„ะฐั€ะฑะพะฒัƒะฒะฐะฝะฝั ั–ะบะพะฝะบะธ ั–ะท ะทะฐัั‚ะพััƒะฒะฐะฝะฝัะผ ะบะพะปัŒะพั€ั–ะฒ ั‚ะตะผะธ.", + "icon-description": "ะ’ะธะฑั€ะฐั‚ะธ ะทะฝะฐั‡ะพะบ ะท ะฑั–ะฑะปั–ะพั‚ะตะบะธ ะฐะฑะพ ะฒะปะฐัะฝะธะน ั„ะฐะนะป.", + "select-custom-icon": "ะ’ะธะฑั€ะฐั‚ะธ ะฒะปะฐัะฝะธะน ะทะฝะฐั‡ะพะบ", + "use-distro-logo-description": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะปะพะณะพั‚ะธะฟ ะฒะฐัˆะพะณะพ ะดะธัั‚ั€ะธะฑัƒั‚ะธะฒะฐ ะทะฐะผั–ัั‚ัŒ ะฒะปะฐัะฝะพะณะพ ะทะฝะฐั‡ะบะฐ.", + "use-distro-logo-label": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะปะพะณะพั‚ะธะฟ ะดะธัั‚ั€ะธะฑัƒั‚ะธะฒะฐ ะทะฐะผั–ัั‚ัŒ ะทะฝะฐั‡ะบะฐ" + }, + "custom-button": { + "collapse-condition-description": "ะฏะบั‰ะพ ะฒะธั…ั–ะดะฝะธะน ั‚ะตะบัั‚ ะทะฑั–ะณะฐั”ั‚ัŒัั ะท ั†ะธะผ ะทะฝะฐั‡ะตะฝะฝัะผ, ะบะฝะพะฟะบะฐ ะทะณะพั€ะฝะตั‚ัŒัั.", + "collapse-condition-label": "ะฃะผะพะฒะฐ ะทะณะพั€ั‚ะฐะฝะฝั", + "color-selection-description": "ะ—ะฐัั‚ะพััƒะฒะฐั‚ะธ ะบะพะปัŒะพั€ะธ ั‚ะตะผะธ ะดะพ ะทะฝะฐั‡ะบะฐ ั‚ะฐ ั‚ะตะบัั‚ัƒ.", + "default-tooltip": "ะšะพั€ะธัั‚ัƒะฒะฐั†ัŒะบะฐ ะบะฝะพะฟะบะฐ, ะฝะฐะปะฐัˆั‚ัƒะนั‚ะต ะฒ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝัั…", + "display-command-output-description": "ะ’ะฒะตะดั–ั‚ัŒ ะบะพะผะฐะฝะดัƒ ะดะปั ะทะฐะฟัƒัะบัƒ ะท ั€ะตะณัƒะปัั€ะฝะธะผ ั–ะฝั‚ะตั€ะฒะฐะปะพะผ. ะŸะตั€ัˆะธะน ั€ัะดะพะบ ั—ั— ะฒะธะฒะตะดะตะฝะฝั ะฒั–ะดะพะฑั€ะฐะถะฐั‚ะธะผะตั‚ัŒัั ัะบ ั‚ะตะบัั‚.", + "display-command-output-label": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะฒะธะฒะตะดะตะฝะฝั ะบะพะผะฐะฝะดะธ", + "display-command-output-stream-description": "ะ’ะฒะตะดั–ั‚ัŒ ะบะพะผะฐะฝะดัƒ ะดะปั ะฑะตะทะฟะตั€ะตั€ะฒะฝะพะณะพ ะทะฐะฟัƒัะบัƒ.", + "dynamic-text": "ะ”ะธะฝะฐะผั–ั‡ะฝะธะน ั‚ะตะบัั‚", + "enable-colorization-description": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ั€ะพะทั„ะฐั€ะฑะพะฒัƒะฒะฐะฝะฝั ะดะปั ะทะฝะฐั‡ะบะฐ ั‚ะฐ ั‚ะตะบัั‚ัƒ ะฒะปะฐัะฝะพั— ะบะฝะพะฟะบะธ, ะทะฐัั‚ะพัะพะฒัƒัŽั‡ะธ ะบะพะปัŒะพั€ะธ ั‚ะตะผะธ.", + "enable-colorization-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ั€ะพะทั„ะฐั€ะฑะพะฒัƒะฒะฐะฝะฝั", + "general-tooltip-text-description": "ะ’ะปะฐัะฝะธะน ั‚ะตะบัั‚ ะดะปั ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ัƒ ะฟั–ะดะบะฐะทั†ั– ะบะฝะพะฟะบะธ.", + "general-tooltip-text-label": "ะšะพั€ะธัั‚ัƒะฒะฐั†ัŒะบะธะน ั‚ะตะบัั‚ ะฟั–ะดะบะฐะทะบะธ", + "hide-mode-always-expanded": "ะ—ะฐะฒะถะดะธ ั€ะพะทัˆะธั€ะตะฝะพ", + "hide-mode-description": "ะšะพะฝั‚ั€ะพะปัŽั” ะฒะธะดะธะผั–ัั‚ัŒ ะฒั–ะดะถะตั‚ะฐ, ะบะพะปะธ ะบะพะผะฐะฝะดะฐ ะฝะต ะผะฐั” ะฒะธะฒะตะดะตะฝะฝั.", + "hide-mode-expand-with-output": "ะ ะพะทัˆะธั€ะธั‚ะธ, ัะบั‰ะพ ั” ะฒะธะฒะตะดะตะฝะฝั", + "hide-mode-label": "ะ ะตะถะธะผ ะฟั€ะธั…ะพะฒัƒะฒะฐะฝะฝั", + "hide-mode-max-transparent": "ะœะฐะบัะธะผะฐะปัŒะฝะพ ั€ะพะทัˆะธั€ะตะฝะพ, ะฐะปะต ะฟั€ะพะทะพั€ะพ", + "icon-description": "ะ’ะธะฑั€ะฐั‚ะธ ะทะฝะฐั‡ะพะบ ะท ะฑั–ะฑะปั–ะพั‚ะตะบะธ.", + "ipc-identifier-description": "ะฃะฝั–ะบะฐะปัŒะฝะธะน ั–ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ ะดะปั ะบะพะผะฐะฝะด IPC. ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ั†ะตะน ั–ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ ะท 'qs -c noctalia-shell ipc call cb [ะดั–ั] [ั–ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€]', ั‰ะพะฑ ะบะตั€ัƒะฒะฐั‚ะธ ั†ั–ั”ัŽ ะบะฝะพะฟะบะพัŽ ั‡ะตั€ะตะท IPC.", + "ipc-identifier-label": "ะ†ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ IPC", + "left-click-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟั€ะธ ะปั–ะฒะพะผัƒ ะบะปั–ะบัƒ ะฝะฐ ะบะฝะพะฟะบัƒ.", + "left-click-label": "ะ›ั–ะฒะธะน ะบะปั–ะบ", + "left-click-update-text": "ะžะฝะพะฒะธั‚ะธ ั‚ะตะบัั‚, ั‰ะพ ะฒั–ะดะพะฑั€ะฐะถะฐั”ั‚ัŒัั, ะฟั€ะธ ะฝะฐั‚ะธัะบะฐะฝะฝั– ะปั–ะฒะพัŽ ะบะฝะพะฟะบะพัŽ ะผะธัˆั–", + "max-text-length-horizontal-description": "ะœะฐะบัะธะผะฐะปัŒะฝะฐ ะบั–ะปัŒะบั–ัั‚ัŒ ัะธะผะฒะพะปั–ะฒ ะดะปั ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ะฒ ะณะพั€ะธะทะพะฝั‚ะฐะปัŒะฝั–ะน ะฟะฐะฝะตะปั– (0 ั‰ะพะฑ ะฟั€ะธั…ะพะฒะฐั‚ะธ ั‚ะตะบัั‚).", + "max-text-length-horizontal-label": "ะœะฐะบั. ะดะพะฒะถะธะฝะฐ ั‚ะตะบัั‚ัƒ (ะณะพั€ะธะทะพะฝั‚ะฐะปัŒะฝะพ)", + "max-text-length-vertical-description": "ะœะฐะบัะธะผะฐะปัŒะฝะฐ ะบั–ะปัŒะบั–ัั‚ัŒ ัะธะผะฒะพะปั–ะฒ ะดะปั ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ะฒ ะฒะตั€ั‚ะธะบะฐะปัŒะฝั–ะน ะฟะฐะฝะตะปั– (0 ั‰ะพะฑ ะฟั€ะธั…ะพะฒะฐั‚ะธ ั‚ะตะบัั‚).", + "max-text-length-vertical-label": "ะœะฐะบั. ะดะพะฒะถะธะฝะฐ ั‚ะตะบัั‚ัƒ (ะฒะตั€ั‚ะธะบะฐะปัŒะฝะพ)", + "middle-click-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟั€ะธ ัะตั€ะตะดะฝัŒะพะผัƒ ะบะปั–ะบัƒ ะฝะฐ ะบะฝะพะฟะบัƒ.", + "middle-click-label": "ะกะตั€ะตะดะฝั–ะน ะบะปั–ะบ", + "middle-click-update-text": "ะžะฝะพะฒะธั‚ะธ ะฒั–ะดะพะฑั€ะฐะถัƒะฒะฐะฝะธะน ั‚ะตะบัั‚ ะฟั€ะธ ะฝะฐั‚ะธัะบะฐะฝะฝั– ัะตั€ะตะดะฝัŒะพัŽ ะบะฝะพะฟะบะพัŽ ะผะธัˆั–", + "parse-json-description": "ะ ะพะทะฑะธั€ะฐั‚ะธ ะฒะธะฒะตะดะตะฝะฝั ะบะพะผะฐะฝะดะธ ัะบ JSON-ะพะฑ'ั”ะบั‚ ะดะปั ะดะธะฝะฐะผั–ั‡ะฝะพะณะพ ะฒัั‚ะฐะฝะพะฒะปะตะฝะฝั ั‚ะตะบัั‚ัƒ ั‚ะฐ ะทะฝะฐั‡ะบะฐ.", + "parse-json-label": "ะ ะพะทะฑะธั€ะฐั‚ะธ ะฒะธะฒะตะดะตะฝะฝั ัะบ JSON", + "refresh-interval-description": "ะ†ะฝั‚ะตั€ะฒะฐะป ัƒ ะผั–ะปั–ัะตะบัƒะฝะดะฐั….", + "refresh-interval-label": "ะ†ะฝั‚ะตั€ะฒะฐะป ะพะฝะพะฒะปะตะฝะฝั", + "right-click-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟั€ะธ ะฟั€ะฐะฒะพะผัƒ ะบะปั–ะบัƒ ะฝะฐ ะบะฝะพะฟะบัƒ.", + "right-click-label": "ะŸั€ะฐะฒะธะน ะบะปั–ะบ", + "right-click-update-text": "ะžะฝะพะฒะธั‚ะธ ั‚ะตะบัั‚, ั‰ะพ ะฒั–ะดะพะฑั€ะฐะถะฐั”ั‚ัŒัั, ะฟั€ะธ ะฝะฐั‚ะธัะบะฐะฝะฝั– ะฟั€ะฐะฒะพัŽ ะบะฝะพะฟะบะพัŽ ะผะธัˆั–", + "show-exec-tooltip-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟั–ะดะบะฐะทะบะธ ะท ะดะตั‚ะฐะปัะผะธ ะบะพะผะฐะฝะด (ะปั–ะฒะธะน/ะฟั€ะฐะฒะธะน/ัะตั€ะตะดะฝั–ะน ะบะปั–ะบ, ะบะพะปะตัะพ).", + "show-exec-tooltip-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟั–ะดะบะฐะทะบะธ ะบะพะผะฐะฝะด", + "show-icon-description": "ะŸะตั€ะตะผะธะบะฐั” ะฒะธะดะธะผั–ัั‚ัŒ ะทะฝะฐั‡ะบะฐ ะฒั–ะดะถะตั‚ะฐ.", + "show-icon-label": "ะŸะพะบะฐะทะฐั‚ะธ ะทะฝะฐั‡ะพะบ", + "show-text-tooltip-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟั–ะดะบะฐะทะบะธ ะท ะฒะธะฒะพะดะพะผ ั‚ะตะบัั‚ะพะฒะพั— ะบะพะผะฐะฝะดะธ.", + "show-text-tooltip-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะดะธะฝะฐะผั–ั‡ะฝั– ั‚ะตะบัั‚ะพะฒั– ะฟั–ะดะบะฐะทะบะธ", + "text-stream-description": "ะŸะพั‚ะพะบะพะฒั– ั€ัะดะบะธ ะท ะบะพะผะฐะฝะดะธ ะฒั–ะดะพะฑั€ะฐะถะฐั‚ะธะผัƒั‚ัŒัั ัะบ ั‚ะตะบัั‚ ะฝะฐ ะบะฝะพะฟั†ั–.", + "text-stream-label": "ะŸะพั‚ั–ะบ", + "wheel-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟั€ะธ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั– ะบะพะปะตัะฐ ะฟั€ะพะบั€ัƒั‚ะบะธ.
ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต $delta ะดะปั ะดะตะปัŒั‚ะธ ะบะพะปะตัะฐ ะฟั€ะพะบั€ัƒั‚ะบะธ ะฒ ะบะพะผะฐะฝะดั–.", + "wheel-down": "ะšะพะปะตัะพ ะฒะฝะธะท", + "wheel-down-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟั€ะธ ะฟั€ะพะบั€ัƒั‚ั†ั– ะบะพะปะตัะฐ ะฒะฝะธะท.", + "wheel-down-label": "ะšะพะผะฐะฝะดะฐ ะฟั€ะพะบั€ัƒั‚ะบะธ ะบะพะปะตัะฐ ะฒะฝะธะท", + "wheel-label": "ะšะพะปะตัะพ ะฟั€ะพะบั€ัƒั‚ะบะธ", + "wheel-mode-separate-description": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะพะบั€ะตะผั– ะบะพะผะฐะฝะดะธ ะดะปั ะบะพะปะตัะฐ ะฟั€ะพะบั€ัƒั‚ะบะธ ะฒะณะพั€ัƒ ั‚ะฐ ะฒะฝะธะท.", + "wheel-mode-separate-label": "ะžะบั€ะตะผั– ะบะพะผะฐะฝะดะธ ะบะพะปะตัะฐ ะฟั€ะพะบั€ัƒั‚ะบะธ", + "wheel-up": "ะšะพะปะตัะพ ะฒะณะพั€ัƒ", + "wheel-up-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟั€ะธ ะฟั€ะพะบั€ัƒั‚ั†ั– ะบะพะปะตัะฐ ะฒะณะพั€ัƒ.", + "wheel-up-label": "ะšะพะผะฐะฝะดะฐ ะฟั€ะพะบั€ัƒั‚ะบะธ ะบะพะปะตัะฐ ะฒะณะพั€ัƒ", + "wheel-update-text": "ะžะฝะพะฒะธั‚ะธ ะฒั–ะดะพะฑั€ะฐะถัƒะฒะฐะฝะธะน ั‚ะตะบัั‚ ะฟั€ะธ ะฟั€ะพะบั€ัƒั‚ั†ั–" + }, + "keyboard-layout": { + "show-icon-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟั–ะบั‚ะพะณั€ะฐะผัƒ ั€ะพะทะบะปะฐะดะบะธ ะบะปะฐะฒั–ะฐั‚ัƒั€ะธ." + }, + "lock-keys": { + "hide-when-off-description": "ะŸั€ะธั…ะพะฒัƒะฒะฐั‚ะธ ั–ะฝะดะธะบะฐั‚ะพั€, ะบะพะปะธ ะบะปะฐะฒั–ัˆะฐ ะฝะตะฐะบั‚ะธะฒะฝะฐ.", + "hide-when-off-label": "ะŸั€ะธั…ะพะฒัƒะฒะฐั‚ะธ ะบะพะปะธ ะฒะธะผะบะฝะตะฝะพ", + "show-caps-lock-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ัั‚ะฐะฝ caps lock.", + "show-caps-lock-label": "Caps Lock", + "show-num-lock-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ัั‚ะฐะฝ num lock.", + "show-num-lock-label": "Num Lock", + "show-scroll-lock-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ัั‚ะฐะฝ scroll lock.", + "show-scroll-lock-label": "Scroll Lock" + }, + "media-mini": { + "compact-mode-description": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะบะพะผะฟะฐะบั‚ะฝะต ั€ะพะทั‚ะฐัˆัƒะฒะฐะฝะฝั ะดะปั ะฟะฐะฝะตะปั– ะผะตะดั–ะฐะฟะปะตั”ั€ะฐ.", + "compact-mode-label": "ะšะพะผะฟะฐะบั‚ะฝะธะน ั€ะตะถะธะผ", + "hide-mode-description": "ะšะตั€ัƒั” ะฟะพะฒะตะดั–ะฝะบะพัŽ ะฒั–ะดะถะตั‚ะฐ, ะบะพะปะธ ะผะตะดั–ะฐ ะฝะต ะฒั–ะดั‚ะฒะพั€ัŽั”ั‚ัŒัั.", + "max-width-description": "ะ’ัั‚ะฐะฝะพะฒะปัŽั” ะผะฐะบัะธะผะฐะปัŒะฝะธะน ะณะพั€ะธะทะพะฝั‚ะฐะปัŒะฝะธะน ั€ะพะทะผั–ั€ ะฒั–ะดะถะตั‚ะฐ. ะ’ั–ะดะถะตั‚ ะทะผะตะฝัˆัƒั”ั‚ัŒัั ะดะปั ะบะพั€ะพั‚ัˆะพะณะพ ะฒะผั–ัั‚ัƒ.", + "no-active-player": "ะะตะผะฐั” ะฐะบั‚ะธะฒะฝะพะณะพ ะฟะปะตั”ั€ะฐ", + "panel-section-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะทะพะฒะฝั–ัˆะฝั–ะน ะฒะธะณะปัะด ั‚ะฐ ะฟะพะฒะตะดั–ะฝะบัƒ ะฟะฐะฝะตะปั– ะผะตะดั–ะฐะฟะปะตั”ั€ะฐ.", + "panel-section-label": "ะŸะฐะฝะตะปัŒ ะผะตะดั–ะฐะฟะปะตั”ั€ะฐ", + "scrolling-mode-description": "ะšะตั€ัƒะฒะฐะฝะฝั, ะบะพะปะธ ัƒะฒั–ะผะบะฝะตะฝะพ ะฟั€ะพะบั€ัƒั‚ะบัƒ ั‚ะตะบัั‚ัƒ ะดะปั ะดะพะฒะณะธั… ะฝะฐะทะฒ ั‚ั€ะตะบั–ะฒ.", + "scrolling-mode-label": "ะ ะตะถะธะผ ะฟั€ะพะบั€ัƒั‚ะบะธ", + "show-album-art-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะพะฑะบะปะฐะดะธะฝะบัƒ ะฐะปัŒะฑะพะผัƒ ะดะปั ะฟะพั‚ะพั‡ะฝะพะณะพ ั‚ั€ะตะบัƒ.", + "show-album-art-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะพะฑะบะปะฐะดะธะฝะบัƒ ะฐะปัŒะฑะพะผัƒ", + "show-artist-first-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะฒะธะบะพะฝะฐะฒะตั†ัŒ - ะฝะฐะทะฒะฐ ะทะฐะผั–ัั‚ัŒ ะฝะฐะทะฒะฐ - ะฒะธะบะพะฝะฐะฒะตั†ัŒ.", + "show-artist-first-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ัะฟะพั‡ะฐั‚ะบัƒ ะฒะธะบะพะฝะฐะฒั†ั", + "show-progress-ring-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะบั€ัƒะณะพะฒะธะน ั–ะฝะดะธะบะฐั‚ะพั€ ะฟั€ะพะณั€ะตััƒ, ั‰ะพ ะฟะพะบะฐะทัƒั” ะฟั€ะพััƒะฒะฐะฝะฝั ั‚ั€ะตะบัƒ.", + "show-progress-ring-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะบั–ะปัŒั†ะต ะฟั€ะพะณั€ะตััƒ", + "show-visualizer-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะฐัƒะดั–ะพะฒั–ะทัƒะฐะปั–ะทะฐั‚ะพั€ ะฟั–ะด ั‡ะฐั ะฒั–ะดั‚ะฒะพั€ะตะฝะฝั ะผัƒะทะธะบะธ.", + "show-visualizer-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฒั–ะทัƒะฐะปั–ะทะฐั‚ะพั€", + "use-fixed-width-description": "ะšะพะปะธ ัƒะฒั–ะผะบะฝะตะฝะพ, ะฒั–ะดะถะตั‚ ะทะฐะฒะถะดะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธะผะต ะผะฐะบัะธะผะฐะปัŒะฝัƒ ัˆะธั€ะธะฝัƒ ะทะฐะผั–ัั‚ัŒ ะดะธะฝะฐะผั–ั‡ะฝะพะณะพ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ะดะพ ะฒะผั–ัั‚ัƒ.", + "use-fixed-width-label": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั„ั–ะบัะพะฒะฐะฝัƒ ัˆะธั€ะธะฝัƒ", + "visualizer-type-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ัั‚ะธะปัŒ ะฐัƒะดั–ะพะฒั–ะทัƒะฐะปั–ะทะฐั‚ะพั€ะฐ ะดะปั ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั.", + "visualizer-type-label": "ะขะธะฟ ะฒั–ะทัƒะฐะปั–ะทะฐั‚ะพั€ะฐ" + }, + "notification-history": { + "hide-widget-when-zero-description": "ะŸั€ะธั…ะพะฒัƒะฒะฐั‚ะธ ะทะฝะฐั‡ะพะบ ัะฟะพะฒั–ั‰ะตะฝัŒ, ัะบั‰ะพ ัะฟะพะฒั–ั‰ะตะฝัŒ ะฝะตะผะฐั”.", + "hide-widget-when-zero-label": "ะŸั€ะธั…ะพะฒัƒะฒะฐั‚ะธ ะทะฝะฐั‡ะพะบ ะฑะตะท ัะฟะพะฒั–ั‰ะตะฝัŒ", + "hide-widget-when-zero-unread-description": "ะŸั€ะธั…ะพะฒัƒะฒะฐั‚ะธ ะทะฝะฐั‡ะพะบ ัะฟะพะฒั–ั‰ะตะฝัŒ, ัะบั‰ะพ ะฝะตะผะฐั” ะฝะตะฟั€ะพั‡ะธั‚ะฐะฝะธั….", + "hide-widget-when-zero-unread-label": "ะŸั€ะธั…ะพะฒัƒะฒะฐั‚ะธ ะทะฝะฐั‡ะพะบ ะฑะตะท ะฝะตะฟั€ะพั‡ะธั‚ะฐะฝะธั…", + "show-unread-badge-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะทะฝะฐั‡ะพะบ ะท ะบั–ะปัŒะบั–ัั‚ัŽ ะฝะตะฟั€ะพั‡ะธั‚ะฐะฝะธั… ัะฟะพะฒั–ั‰ะตะฝัŒ.", + "show-unread-badge-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะทะฝะฐั‡ะพะบ ะฝะตะฟั€ะพั‡ะธั‚ะฐะฝะธั…", + "unread-badge-color-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะบะพะปั–ั€ ะดะปั ะทะฝะฐั‡ะบะฐ ะฝะตะฟั€ะพั‡ะธั‚ะฐะฝะธั… ัะฟะพะฒั–ั‰ะตะฝัŒ.", + "unread-badge-color-label": "ะšะพะปั–ั€ ะทะฝะฐั‡ะบะฐ ะฝะตะฟั€ะพั‡ะธั‚ะฐะฝะธั…" + }, + "section-editor": { + "placeholder": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะฒั–ะดะถะตั‚ ะดะปั ะดะพะดะฐะฒะฐะฝะฝั...", + "search-placeholder": "ะŸะพัˆัƒะบ ะฒั–ะดะถะตั‚ะฐ..." + }, + "spacer": { + "width-description": "ะจะธั€ะธะฝะฐ ะฒั–ะดัั‚ัƒะฟัƒ ะฒ ะฟั–ะบัะตะปัั…." + }, + "system-monitor": { + "compact-mode-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ัั‚ะฐั‚ะธัั‚ะธะบัƒ ัƒ ะฒะธะณะปัะดั– ะผั–ะฝั–-ะดั–ะฐะณั€ะฐะผ ะทะฐะผั–ัั‚ัŒ ั‚ะตะบัั‚ะพะฒะธั… ะทะฝะฐั‡ะตะฝัŒ. ะ—ะฐะฟะพะฑั–ะณะฐั” ะทััƒะฒัƒ ะผะฐะบะตั‚ะฐ.", + "compact-mode-label": "ะšะพะผะฟะฐะบั‚ะฝะธะน ั€ะตะถะธะผ", + "cpu-cores-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ัะดะตั€ CPU ะพะบั€ะตะผะพ.", + "cpu-cores-label": "ะฏะดั€ะฐ CPU", + "cpu-frequency-description": "ะ’ั–ะดะพะฑั€ะฐะทะธั‚ะธ ะฟะพั‚ะพั‡ะฝัƒ ั‚ะฐะบั‚ะพะฒัƒ ั‡ะฐัั‚ะพั‚ัƒ CPU ัƒ ะ“ะ“ั†.", + "cpu-frequency-label": "ะŸะพะบะฐะทะฐั‚ะธ ั‡ะฐัั‚ะพั‚ัƒ CPU", + "cpu-temperature-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟะพะบะฐะทะฝะธะบะธ ั‚ะตะผะฟะตั€ะฐั‚ัƒั€ะธ ะฆะŸ, ัะบั‰ะพ ะดะพัั‚ัƒะฟะฝะพ.", + "cpu-temperature-label": "ะขะตะผะฟะตั€ะฐั‚ัƒั€ะฐ ะฆะŸ", + "cpu-usage-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะฟะพั‚ะพั‡ะฝะธะน ะฒั–ะดัะพั‚ะพะบ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะฆะŸ.", + "cpu-usage-label": "ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ะฆะŸ", + "disk-path-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ั‚ะพั‡ะบัƒ ะผะพะฝั‚ัƒะฒะฐะฝะฝั ะดะธัะบะฐ ะดะปั ะผะพะฝั–ั‚ะพั€ะธะฝะณัƒ.", + "disk-path-label": "ะจะปัั… ะดะพ ะดะธัะบะฐ", + "gpu-temperature-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟะพะบะฐะทะฝะธะบะธ ั‚ะตะผะฟะตั€ะฐั‚ัƒั€ะธ GPU, ัะบั‰ะพ ะดะพัั‚ัƒะฟะฝั–.", + "load-average-description": "ะŸะพะบะฐะทะฐั‚ะธ ัะตั€ะตะดะฝั” ะฝะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ัะธัั‚ะตะผะธ.", + "load-average-label": "ะกะตั€ะตะดะฝั” ะฝะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั", + "memory-percentage-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะฟะฐะผ'ัั‚ั– ัƒ ะฒั–ะดัะพั‚ะบะฐั… ะทะฐะผั–ัั‚ัŒ ะฐะฑัะพะปัŽั‚ะฝะธั… ะทะฝะฐั‡ะตะฝัŒ.", + "memory-percentage-label": "ะŸะฐะผ'ัั‚ัŒ ัƒ ะฒั–ะดัะพั‚ะบะฐั…", + "memory-usage-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ ะฟั€ะพ ะฟะพั‚ะพั‡ะฝะต ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะพะฟะตั€ะฐั‚ะธะฒะฝะพั— ะฟะฐะผ'ัั‚ั–.", + "memory-usage-label": "ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ะฟะฐะผ'ัั‚ั–", + "network-traffic-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ัˆะฒะธะดะบั–ัั‚ัŒ ะฟั€ะธะนะพะผัƒ ั‚ะฐ ะฟะตั€ะตะดะฐั‡ั– ะดะฐะฝะธั….", + "network-traffic-label": "ะœะตั€ะตะถะตะฒะธะน ั‚ั€ะฐั„ั–ะบ", + "storage-as-percentage-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะผั–ัั†ะต ะฝะฐ ะดะธัะบัƒ ัƒ ะฒั–ะดัะพั‚ะบะฐั… ะทะฐะผั–ัั‚ัŒ ะฐะฑัะพะปัŽั‚ะฝะธั… ะทะฝะฐั‡ะตะฝัŒ.", + "storage-as-percentage-label": "ะ”ะธัะบ ัƒ ะฒั–ะดัะพั‚ะบะฐั…", + "storage-available-description": "ะŸะพะบะฐะทัƒั”, ัะบั–ะปัŒะบะธ ะผั–ัั†ั ะฝะฐ ะดะธัะบัƒ ะดะพัั‚ัƒะฟะฝะพ, ะทะฐะผั–ัั‚ัŒ ั‚ะพะณะพ, ัะบั–ะปัŒะบะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั.", + "storage-available-label": "ะ”ะพัั‚ัƒะฟะฝะต ะผั–ัั†ะต ะฝะฐ ะดะธัะบัƒ", + "storage-usage-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ ะฟั€ะพ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะดะธัะบะพะฒะพะณะพ ะฟั€ะพัั‚ะพั€ัƒ.", + "storage-usage-label": "ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ัั…ะพะฒะธั‰ะฐ", + "swap-usage-description": "ะŸะพะบะฐะทะฐั‚ะธ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั swap-ะฟะฐะผ'ัั‚ั–.", + "swap-usage-label": "ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั swap", + "use-monospace-font-description": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ะผะพะฝะพัˆะธั€ะธะฝะฝะธะน ัˆั€ะธั„ั‚ ะดะปั ะทะฐะฑะตะทะฟะตั‡ะตะฝะฝั ัั‚ะฐะปะพั— ัˆะธั€ะธะฝะธ ัะธะผะฒะพะปั–ะฒ.", + "use-monospace-font-label": "ะœะพะฝะพัˆะธั€ะธะฝะฝะธะน ัˆั€ะธั„ั‚", + "use-padding-description": "ะ”ะพะดะฐั” ะดะพ ั‚ะตะบัั‚ะพะฒะธั… ะทะฝะฐั‡ะตะฝัŒ ะฟะพั‡ะฐั‚ะบะพะฒั– ะฟั€ะพะฑั–ะปะธ, ั‰ะพะฑ ะทะฐะฟะพะฑั–ะณั‚ะธ ะทััƒะฒัƒ ะผะฐะบะตั‚ะฐ.", + "use-padding-description-disabled-monospace-font": "ะ”ะปั ั†ั–ั”ั— ั„ัƒะฝะบั†ั–ั— ะฟะพั‚ั€ั–ะฑะตะฝ ะผะพะฝะพัˆะธั€ะธะฝะฝะธะน ัˆั€ะธั„ั‚.", + "use-padding-description-disabled-vertical": "ะ’ะตั€ั‚ะธะบะฐะปัŒะฝะฐ ะฟะฐะฝะตะปัŒ ะทะฐะฒะดะฐะฝัŒ ะฝะต ะฟั–ะดั‚ั€ะธะผัƒั” ั‚ะตะบัั‚ะพะฒั– ะฒั–ะดัั‚ัƒะฟะธ.", + "use-padding-label": "ะ’ั–ะดัั‚ัƒะฟ ะขะตะบัั‚ัƒ" + }, + "taskbar": { + "colorize-icons-description": "ะ—ะฐัั‚ะพััƒะฒะฐั‚ะธ ะบะพะปัŒะพั€ะธ ั‚ะตะผะธ ะดะพ ะทะฝะฐั‡ะบั–ะฒ ะฟะฐะฝะตะปั– ะทะฐะฒะดะฐะฝัŒ.", + "hide-mode-description": "ะšะตั€ัƒั” ะฟะพะฒะตะดั–ะฝะบะพัŽ ะฒั–ะดะถะตั‚ะฐ, ะบะพะปะธ ะฝะตะผะฐั” ะฒั–ะดะฟะพะฒั–ะดะฝะธั… ะฒั–ะบะพะฝ.", + "hide-mode-label": "ะ ะตะถะธะผ ะฟั€ะธั…ะพะฒัƒะฒะฐะฝะฝั", + "icon-scale-description": "ะ’ัั‚ะฐะฝะพะฒะปัŽั” ะบะพะตั„ั–ั†ั–ั”ะฝั‚ ะผะฐััˆั‚ะฐะฑัƒะฒะฐะฝะฝั ะดะปั ะทะฝะฐั‡ะบั–ะฒ ะฟะฐะฝะตะปั– ะทะฐะฒะดะฐะฝัŒ.", + "icon-scale-label": "ะœะฐััˆั‚ะฐะฑัƒะฒะฐะฝะฝั ั–ะบะพะฝะพะบ", + "max-width-description": "ะœะฐะบัะธะผะฐะปัŒะฝะฐ ัˆะธั€ะธะฝะฐ ะฟะฐะฝะตะปั– ะทะฐะฒะดะฐะฝัŒ ัƒ ะฒั–ะดัะพั‚ะบะฐั… ะฒั–ะด ัˆะธั€ะธะฝะธ ะตะบั€ะฐะฝะฐ.", + "max-width-label": "ะœะฐะบัะธะผะฐะปัŒะฝะฐ ัˆะธั€ะธะฝะฐ", + "only-active-workspaces-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ั‚ั–ะปัŒะบะธ ะทะฐัั‚ะพััƒะฝะบะธ ะท ะฐะบั‚ะธะฒะฝะธั… ั€ะพะฑะพั‡ะธั… ะฟั€ะพัั‚ะพั€ั–ะฒ.", + "only-active-workspaces-label": "ะขั–ะปัŒะบะธ ะท ะฐะบั‚ะธะฒะฝะธั… ั€ะพะฑะพั‡ะธั… ะฟั€ะพัั‚ะพั€ั–ะฒ", + "only-same-monitor-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะปะธัˆะต ะทะฐัั‚ะพััƒะฝะบะธ ะท ะผะพะฝั–ั‚ะพั€ะฐ, ะดะต ั€ะพะทั‚ะฐัˆะพะฒะฐะฝะฐ ะฟะฐะฝะตะปัŒ.", + "only-same-monitor-label": "ะขั–ะปัŒะบะธ ะท ั‚ะพะณะพ ะถ ะผะพะฝั–ั‚ะพั€ะฐ", + "show-pinned-apps-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะทะฐะบั€ั–ะฟะปะตะฝั– ะฟั€ะพะณั€ะฐะผะธ ะท ะดะพะบ-ะฟะฐะฝะตะปั– ะฝะฐ ะฟะฐะฝะตะปั– ะทะฐะฒะดะฐะฝัŒ.", + "show-pinned-apps-label": "ะŸะพะบะฐะทะฐั‚ะธ ะทะฐะบั€ั–ะฟะปะตะฝั– ะฟั€ะพะณั€ะฐะผะธ", + "show-title-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะทะฐะณะพะปะพะฒะบะธ ะฒั–ะบะพะฝ ะฝะฐ ะฟะฐะฝะตะปั– ะทะฐะฒะดะฐะฝัŒ.", + "show-title-description-disabled": "ะ’ะตั€ั‚ะธะบะฐะปัŒะฝะฐ ะฟะฐะฝะตะปัŒ ะทะฐะฒะดะฐะฝัŒ ะฝะต ะฟั–ะดั‚ั€ะธะผัƒั” ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ะทะฐะณะพะปะพะฒะบั–ะฒ.", + "show-title-label": "ะŸะพะบะฐะทะฐั‚ะธ ะทะฐะณะพะปะพะฒะพะบ", + "smart-width-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ั€ะตะณัƒะปัŽะฒะฐั‚ะธ ัˆะธั€ะธะฝัƒ ะทะฐะฟะธัั–ะฒ ะทะฐะปะตะถะฝะพ ะฒั–ะด ั—ั… ะบั–ะปัŒะบะพัั‚ั–.", + "smart-width-label": "ะ ะพะทัƒะผะฝะฐ ัˆะธั€ะธะฝะฐ", + "title-width-description": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ัˆะธั€ะธะฝัƒ ะทะฐะณะพะปะพะฒะบั–ะฒ ะฒั–ะบะพะฝ ะฝะฐ ะฟะฐะฝะตะปั– ะทะฐะฒะดะฐะฝัŒ (ัƒ ะฟั–ะบัะตะปัั…).", + "title-width-label": "ะจะธั€ะธะฝะฐ ะทะฐะณะพะปะพะฒะบะฐ", + "title-width-reset-tooltip": "ะกะบะธะฝัƒั‚ะธ ัˆะธั€ะธะฝัƒ ะทะฐะณะพะปะพะฒะบะฐ" + }, + "tray": { + "chevron-color-description": "ะ—ะฐัั‚ะพััƒะฒะฐั‚ะธ ะบะพะปัŒะพั€ะธ ั‚ะตะผะธ ะดะพ ั–ะบะพะฝะบะธ ัˆะตะฒั€ะพะฝะฐ ัˆัƒั…ะปัะดะธ.", + "chevron-color-label": "ะšะพะปั–ั€ ัั‚ั€ั–ะปะบะธ", + "colorize-icons-description": "ะ—ะฐัั‚ะพััƒะฒะฐั‚ะธ ะบะพะปัŒะพั€ะธ ั‚ะตะผะธ ะดะพ ะทะฝะฐั‡ะบั–ะฒ ั‚ั€ะตั.", + "colorize-icons-label": "ะ ะพะทั„ะฐั€ะฑัƒะฒะฐั‚ะธ ะทะฝะฐั‡ะบะธ", + "drawer-enabled-description": "ะšะพะปะธ ัƒะฒั–ะผะบะฝะตะฝะพ, ะฝะต ะทะฐะบั€ั–ะฟะปะตะฝั– ะตะปะตะผะตะฝั‚ะธ ั‚ั€ะตั ะฒั–ะดะพะฑั€ะฐะถะฐัŽั‚ัŒัั ะฝะฐ ะฟะฐะฝะตะปั– ัั‰ะธะบะฐ.
ะšะพะปะธ ะฒะธะผะบะฝะตะฝะพ, ะฒัั– ะตะปะตะผะตะฝั‚ะธ ั‚ั€ะตั ะฒั–ะดะพะฑั€ะฐะถะฐัŽั‚ัŒัั ะฒ ั€ัะดะบัƒ.", + "drawer-enabled-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฒะธััƒะฒะฝัƒ ะฟะฐะฝะตะปัŒ", + "hide-passive-description": "ะฏะบั‰ะพ ะฒะฒั–ะผะบะฝะตะฝะพ, ะตะปะตะผะตะฝั‚ะธ ะฒ ั‚ั€ะตั— ะทั– ัั‚ะฐั‚ัƒัะพะผ 'ะŸะฐัะธะฒะฝะธะน' ะฑัƒะดัƒั‚ัŒ ะฟั€ะธั…ะพะฒะฐะฝั–.", + "hide-passive-label": "ะŸั€ะธั…ะพะฒะฐั‚ะธ ะฟะฐัะธะฒะฝั– ะฟั€ะตะดะผะตั‚ะธ" + }, + "volume": { + "display-mode-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ัะบ ะฒะธ ั…ะพั‡ะตั‚ะต, ั‰ะพะฑ ั†ะต ะทะฝะฐั‡ะตะฝะฝั ะฒั–ะดะพะฑั€ะฐะถะฐะปะพัั." + }, + "workspace": { + "character-count-description": "ะšั–ะปัŒะบั–ัั‚ัŒ ัะธะผะฒะพะปั–ะฒ ะดะปั ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ะท ะฝะฐะทะฒ ั€ะพะฑะพั‡ะธั… ะฟั€ะพัั‚ะพั€ั–ะฒ (1-10).", + "character-count-label": "ะšั–ะปัŒะบั–ัั‚ัŒ ัะธะผะฒะพะปั–ะฒ", + "empty-color-description": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ะบะพะปั–ั€ ั„ะพะฝัƒ ะดะปั ะฟะพั€ะพะถะฝั–ั… Workspace.", + "empty-color-label": "ะšะพะปั–ั€ ะฟะพั€ะพะถะฝัŒะพะณะพ ั€ะพะฑะพั‡ะพะณะพ ัั‚ะพะปัƒ", + "enable-scrollwheel-description": "ะŸะตั€ะตะผะธะบะฐะนั‚ะตัั ะผั–ะถ ั€ะพะฑะพั‡ะธะผะธ ัั‚ะพะปะฐะผะธ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ะบะพะปั–ั‰ะฐั‚ะบะฐ ะผะธัˆั–.", + "enable-scrollwheel-label": "ะŸั€ะพะบั€ัƒั‚ั–ั‚ัŒ, ั‰ะพะฑ ะฟะตั€ะตะผะธะบะฐั‚ะธ ั€ะพะฑะพั‡ั– ะฟั€ะพัั‚ะพั€ะธ", + "focused-color-description": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ะบะพะปั–ั€ ั‚ะปะฐ ะดะปั ัั„ะพะบัƒัะพะฒะฐะฝะพะณะพ Workspace.", + "focused-color-label": "ะšะพะปั–ั€ ัั„ะพะบัƒัะพะฒะฐะฝะพะณะพ ั€ะพะฑะพั‡ะพะณะพ ัั‚ะพะปัƒ", + "follow-focused-screen-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ั€ะพะฑะพั‡ั– ะฟั€ะพัั‚ะพั€ะธ ะท ะฟะพั‚ะพั‡ะฝะพะณะพ ะฐะบั‚ะธะฒะฝะพะณะพ ะตะบั€ะฐะฝะฐ, ะฐ ะฝะต ะท ะตะบั€ะฐะฝะฐ, ะฝะฐ ัะบะพะผัƒ ั€ะพะทั‚ะฐัˆะพะฒะฐะฝะฐ ะฟะฐะฝะตะปัŒ.", + "follow-focused-screen-label": "ะกะปั–ะดัƒะฒะฐั‚ะธ ะทะฐ ะฐะบั‚ะธะฒะฝะธะผ ะตะบั€ะฐะฝะพะผ", + "font-weight-description": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ะฒั–ะทัƒะฐะปัŒะฝัƒ ะฒะฐะณัƒ ั‚ะตะบัั‚ัƒ ะฒ ั€ะพะฑะพั‡ะพะผัƒ ะฟั€ะพัั‚ะพั€ั–.", + "font-weight-label": "ะขะพะฒั‰ะธะฝะฐ ัˆั€ะธั„ั‚ัƒ", + "grouped-border-opacity-description": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ั€ั–ะฒะตะฝัŒ ะฝะตะฟั€ะพะทะพั€ะพัั‚ั– ะดะปั ะผะตะถ ะบะพะฝั‚ะตะนะฝะตั€ั–ะฒ ั€ะพะฑะพั‡ะพั— ะพะฑะปะฐัั‚ั–.", + "grouped-border-opacity-label": "ะŸั€ะพะทะพั€ั–ัั‚ัŒ ะผะตะถั–", + "hide-unoccupied-description": "ะะต ะฒั–ะดะพะฑั€ะฐะถะฐั‚ะธ ั€ะพะฑะพั‡ั– ะฟั€ะพัั‚ะพั€ะธ ะฑะตะท ะฒั–ะบะพะฝ.", + "hide-unoccupied-label": "ะŸั€ะธั…ะพะฒะฐั‚ะธ ะฝะตะทะฐะนะฝัั‚ั–", + "label-mode-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ัะบ ะฒั–ะดะพะฑั€ะฐะถะฐัŽั‚ัŒัั ะผั–ั‚ะบะธ ั€ะพะฑะพั‡ะธั… ะฟั€ะพัั‚ะพั€ั–ะฒ.", + "label-mode-label": "ะ ะตะถะธะผ ะผั–ั‚ะพะบ", + "occupied-color-description": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ะบะพะปั–ั€ ั„ะพะฝัƒ ะดะปั ะทะฐะนะฝัั‚ะธั… Workspace.", + "occupied-color-label": "ะšะพะปั–ั€ ะทะฐะนะฝัั‚ะพะณะพ ั€ะพะฑะพั‡ะพะณะพ ัั‚ะพะปัƒ", + "pill-size-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ั€ะพะทะผั–ั€ ะฟั–ะณัƒะปะพะบ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพัั‚ะพั€ัƒ (50%-100%).", + "pill-size-label": "ะ ะพะทะผั–ั€ ะบะฐะฟััƒะปะธ", + "reverse-scrolling-description": "ะ—ะผั–ะฝะธั‚ะธ ะฝะฐะฟั€ัะผะพะบ ะฟะตั€ะตะผะธะบะฐะฝะฝั ั€ะพะฑะพั‡ะธั… ะฟั€ะพัั‚ะพั€ั–ะฒ ะฟั–ะด ั‡ะฐั ะฟั€ะพะบั€ัƒั‡ัƒะฒะฐะฝะฝั.", + "reverse-scrolling-label": "ะ—ะฒะพั€ะพั‚ะฝะต ะฟั€ะพะบั€ัƒั‡ัƒะฒะฐะฝะฝั", + "show-applications-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะทะฝะฐั‡ะบะธ ะฟั€ะพะณั€ะฐะผ ัƒ ะบะพะถะฝะพะผัƒ ั€ะพะฑะพั‡ะพะผัƒ ะฟั€ะพัั‚ะพั€ั–.", + "show-applications-hover-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะฟั–ะบั‚ะพะณั€ะฐะผะธ ะฟั€ะพะณั€ะฐะผ ะฒัะตั€ะตะดะธะฝั– ะบะพะถะฝะพะณะพ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพัั‚ะพั€ัƒ ะฟั€ะธ ะฝะฐะฒะตะดะตะฝะฝั– ะฝะฐ ะฝัŒะพะณะพ.", + "show-applications-hover-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟั€ะพะณั€ะฐะผะธ ะฟั€ะธ ะฝะฐะฒะตะดะตะฝะฝั–", + "show-applications-label": "ะŸะพะบะฐะทะฐั‚ะธ ะทะฐัั‚ะพััƒะฝะบะธ", + "show-badge-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะทะฝะฐั‡ะพะบ ะฝะพะผะตั€ะฐ ั€ะพะฑะพั‡ะพะณะพ ัั‚ะพะปัƒ ะฒ ะทะณั€ัƒะฟะพะฒะฐะฝะพะผัƒ ั€ะตะถะธะผั–.", + "show-badge-label": "ะŸะพะบะฐะทะฐั‚ะธ ะทะฝะฐั‡ะพะบ ั€ะพะฑะพั‡ะพะณะพ ัั‚ะพะปัƒ", + "show-labels-only-when-occupied-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะผั–ั‚ะบะธ ั€ะพะฑะพั‡ะธั… ะฟั€ะพัั‚ะพั€ั–ะฒ ะปะธัˆะต ั‚ะพะดั–, ะบะพะปะธ ะฒะพะฝะธ ะผั–ัั‚ัั‚ัŒ ะฒั–ะบะฝะฐ.", + "show-labels-only-when-occupied-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะผั–ั‚ะบะธ ะปะธัˆะต ะบะพะปะธ ะฟะพะปะต ะทะฐะฟะพะฒะฝะตะฝะต", + "unfocused-icons-opacity-description": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ั€ั–ะฒะตะฝัŒ ะฝะตะฟั€ะพะทะพั€ะพัั‚ั– ะดะปั ะฝะตะฐะบั‚ะธะฒะฝะธั… ั–ะบะพะฝะพะบ ะทะฐัั‚ะพััƒะฝะบั–ะฒ.", + "unfocused-icons-opacity-label": "ะะตะฐะบั‚ะธะฒะฝั– ั–ะบะพะฝะบะธ, ะฝะตะฟั€ะพะทะพั€ั–ัั‚ัŒ" + } + }, + "battery": { + "all-batteries": "ะฃัั– ะฑะฐั‚ะฐั€ะตั— (ะพะฑ'ั”ะดะฝะฐะฝั–)", + "battery-health": "ะกั‚ะฐะฝ ะฑะฐั‚ะฐั€ะตั—", + "battery-level": "ะ ั–ะฒะตะฝัŒ ะทะฐั€ัะดัƒ ะฐะบัƒะผัƒะปัั‚ะพั€ะฐ", + "capacity-level": "ะœั–ัั‚ะบั–ัั‚ัŒ: {level}", + "charging-rate": "ะจะฒะธะดะบั–ัั‚ัŒ ะทะฐั€ัะดะบะธ: {rate} ะ’ั‚", + "discharging-rate": "ะจะฒะธะดะบั–ัั‚ัŒ ั€ะพะทั€ัะดะบะธ: {rate} ะ’ั‚", + "health": "ะกั‚ะฐะฝ: {percent}%", + "inhibit-idle-description": "ะŸั–ะดั‚ั€ะธะผัƒั” ัะธัั‚ะตะผัƒ ะฐะบั‚ะธะฒะฝะพัŽ.", + "no-battery-detected": "ะ‘ะฐั‚ะฐั€ะตัŽ ะฝะต ะฒะธัะฒะปะตะฝะพ", + "plugged-in": "ะŸั–ะดะบะปัŽั‡ะตะฝะพ", + "power-profile": "ะŸั€ะพั„ั–ะปัŒ ะถะธะฒะปะตะฝะฝั", + "time-left": "ะ—ะฐะปะธัˆะธะปะพัั: {time}", + "time-until-full": "ะ”ะพ ะฟะพะฒะฝะพะณะพ ะทะฐั€ัะดัƒ: {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "ะ”ะพัั‚ัƒะฟะฝั– ะฟั€ะธัั‚ั€ะพั—", + "blocked": "ะ—ะฐะฑะปะพะบะพะฒะฐะฝะพ", + "connected-devices": "ะŸั–ะดะบะปัŽั‡ะตะฝั– ะฟั€ะธัั‚ั€ะพั—", + "device-address": "ะะดั€ะตัะฐ ะฟั€ะธัั‚ั€ะพัŽ", + "disabled": "Bluetooth ะฒะธะผะบะฝะตะฝะพ", + "discoverable": "ะ’ะธัะฒะฝะธะน", + "enable-message": "ะฃะฒั–ะผะบะฝั–ั‚ัŒ Bluetooth, ั‰ะพะฑ ะฟะพะฑะฐั‡ะธั‚ะธ ะดะพัั‚ัƒะฟะฝั– ะฟั€ะธัั‚ั€ะพั—.", + "known-devices": "ะ’ั–ะดะพะผั– ะฟั€ะธัั‚ั€ะพั—", + "no-devices": "ะŸั€ะธัั‚ั€ะพั— ะฝะตะดะพัั‚ัƒะฟะฝั–", + "paired-devices": "ะ—'ั”ะดะฝะฐะฝั– ะฟั€ะธัั‚ั€ะพั—", + "pairing-mode": "ะŸะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะฒะฐัˆ ะฟั€ะธัั‚ั€ั–ะน ัƒ ั€ะตะถะธะผั– ะท'ั”ะดะฝะฐะฝะฝั.", + "scanning": "ะกะบะฐะฝัƒะฒะฐะฝะฝั ะฟั€ะธัั‚ั€ะพั—ะฒ...", + "signal-text-excellent": "ะกะธะณะฝะฐะป: ะ’ั–ะดะผั–ะฝะฝะธะน", + "signal-text-fair": "ะกะธะณะฝะฐะป: ะ—ะฐะดะพะฒั–ะปัŒะฝะธะน", + "signal-text-good": "ะกะธะณะฝะฐะป: ะ”ะพะฑั€ะต", + "signal-text-poor": "ะกะธะณะฝะฐะป: ะŸะพะณะฐะฝะธะน", + "signal-text-unknown": "ะกะธะณะฝะฐะป: ะะตะฒั–ะดะพะผะธะน", + "signal-text-very-poor": "ะกะธะณะฝะฐะป: ะ”ัƒะถะต ัะปะฐะฑะบะธะน" + } + }, + "changelog": { + "error": { + "fetch-failed": "ะะต ะฒะดะฐะปะพัั ะทะฐะฒะฐะฝั‚ะฐะถะธั‚ะธ ะดะฐะฝั– ะถัƒั€ะฝะฐะปัƒ ะทะผั–ะฝ. ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ัะฟั€ะพะฑัƒะนั‚ะต ะฟั–ะทะฝั–ัˆะต.", + "rate-limit": "ะŸะตั€ะตะฒะธั‰ะตะฝะพ ะปั–ะผั–ั‚ GitHub. ะกะฟั€ะพะฑัƒะนั‚ะต ั‰ะต ั€ะฐะท ะทะฐ ะบั–ะปัŒะบะฐ ั…ะฒะธะปะธะฝ." + }, + "panel": { + "buttons-discord": "ะŸั€ะธั”ะดะฝะฐั‚ะธัั ะดะพ ะฝะฐัˆะพะณะพ Discord", + "buttons-dismiss": "ะžะš", + "buttons-feedback": "ะะฐะดะฐั‚ะธ ะฒั–ะดะณัƒะบ", + "empty": "ะŸั€ะธะผั–ั‚ะบะธ ะดะพ ั€ะตะปั–ะทัƒ ั‰ะต ะฝะตะดะพัั‚ัƒะฟะฝั–.", + "highlight-title": "ะžัะฝะพะฒะฝั– ะทะผั–ะฝะธ", + "released": "ะ’ะธะฟัƒั‰ะตะฝะพ {date}", + "subtitle-fresh": "ะ”ัะบัƒั”ะผะพ, ั‰ะพ ะฒัั‚ะฐะฝะพะฒะธะปะธ Noctalia! ะžััŒ ั‰ะพ ะผั–ัั‚ะธั‚ัŒ ั†ะตะน ะทะฑั–ั€ะบะฐ.", + "subtitle-updated": "ะžะฝะพะฒะปะตะฝะพ ะท {previousVersion}", + "title": "ะฉะพ ะฝะพะฒะพะณะพ ัƒ {version}", + "version": "ะ’ะตั€ัั–ั {version}", + "version-new-user": "ะะพะฒะต ะฒัั‚ะฐะฝะพะฒะปะตะฝะฝั" + } + }, + "common": { + "actions": "ะ”ั–ั—", + "active": "ะะบั‚ะธะฒะฝะธะน", + "add": "ะ”ะพะดะฐั‚ะธ", + "appearance": "ะ—ะพะฒะฝั–ัˆะฝั–ะน ะฒะธะณะปัะด", + "apply": "ะŸะพะดะฐั‚ะธ ะทะฐัะฒะบัƒ", + "auto-connect": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะต ะฟั–ะดะบะปัŽั‡ะตะฝะฝั", + "automation": "ะะฒั‚ะพะผะฐั‚ะธะทะฐั†ั–ั", + "available": "ะ”ะพัั‚ัƒะฟะฝะธะน", + "back": "ะะฐะทะฐะด", + "battery": "ะะบัƒะผัƒะปัั‚ะพั€", + "behavior": "ะŸะพะฒะตะดั–ะฝะบะฐ", + "bluetooth": "Bluetooth", + "brightness": "ะฏัะบั€ะฐะฒั–ัั‚ัŒ", + "browse": "ะŸะตั€ะตะณะปัะด", + "calendar": "ะšะฐะปะตะฝะดะฐั€", + "calendar-panel": "ะŸะฐะฝะตะปัŒ ะบะฐะปะตะฝะดะฐั€ั", + "cancel": "ะกะบะฐััƒะฒะฐั‚ะธ", + "cards": "ะšะฐั€ั‚ะบะธ", + "charging": "ะ—ะฐั€ัะดะถะฐะฝะฝั", + "clear": "ะžั‡ะธัั‚ะธั‚ะธ", + "clipboard": "ะ‘ัƒั„ะตั€ ะพะฑะผั–ะฝัƒ", + "close": "ะ—ะฐะบั€ะธั‚ะธ", + "color-muted": "ะ’ะธะผะบะฝะตะฝะพ ะทะฒัƒะบ", + "colors": "ะšะพะปัŒะพั€ะธ", + "command": "ะšะพะผะฐะฝะดะฐ", + "confirm": "ะŸั–ะดั‚ะฒะตั€ะดะธั‚ะธ", + "connect": "ะŸั–ะดะบะปัŽั‡ะธั‚ะธ", + "connected": "ะŸั–ะดะบะปัŽั‡ะตะฝะพ", + "connecting": "ะŸั–ะดะบะปัŽั‡ะตะฝะฝั...", + "continue": "ะŸั€ะพะดะพะฒะถะธั‚ะธ", + "contributors": "ะฃั‡ะฐัะฝะธะบะธ", + "copied-to-clipboard": "ะกะบะพะฟั–ะนะพะฒะฐะฝะพ ะดะพ ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ", + "countdown": "ะ—ะฒะพั€ะพั‚ะฝะธะน ะฒั–ะดะปั–ะบ", + "customize": "ะะฐะปะฐัˆั‚ัƒะฒะฐั‚ะธ", + "date": "ะ”ะฐั‚ะฐ", + "default": "ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ", + "delete": "ะ’ะธะดะฐะปะธั‚ะธ", + "devices": "ะŸั€ะธัั‚ั€ะพั—", + "disabled": "ะ’ะธะผะบะฝะตะฝะพ", + "discharging": "ะ ะพะทั€ัะดะถะตะฝะฝั", + "disconnect": "ะ’ั–ะด'ั”ะดะฝะฐั‚ะธ", + "disconnected": "ะ’ั–ะด'ั”ะดะฝะฐะฝะพ", + "disconnecting": "ะ’ั–ะด'ั”ะดะฝะฐะฝะฝั...", + "display-mode": "ะ ะตะถะธะผ ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั", + "documentation": "ะ”ะพะบัƒะผะตะฝั‚ะฐั†ั–ั", + "download": "ะ—ะฐะฒะฐะฝั‚ะฐะถะธั‚ะธ", + "duration": "ะขั€ะธะฒะฐะปั–ัั‚ัŒ", + "dysfunctional": "ะ”ะธัั„ัƒะฝะบั†ั–ะพะฝะฐะปัŒะฝะธะน", + "edit": "ะ ะตะดะฐะณัƒะฒะฐั‚ะธ", + "enabled": "ะฃะฒั–ะผะบะฝะตะฝะพ", + "error": "ะŸะพะผะธะปะบะฐ", + "ethernet": "Ethernet", + "events": "ะŸะพะดั–ั—", + "execute": "ะ’ะธะบะพะฝะฐั‚ะธ", + "faithful": "ะ’ั–ั€ะฝะธะน", + "focus": "ะ—ะพัะตั€ะตะดะถะตะฝั–ัั‚ัŒ", + "font-weight-bold": "ะ–ะธั€ะฝะธะน", + "font-weight-light": "ะขะพะฝะบะธะน", + "font-weight-medium": "ะกะตั€ะตะดะฝั–ะน", + "font-weight-regular": "ะ—ะฒะธั‡ะฐะนะฝะธะน", + "font-weight-semibold": "ะะฐะฟั–ะฒะถะธั€ะฝะธะน", + "frequency": "ะงะฐัั‚ะพั‚ะฐ", + "gateway": "ะจะปัŽะท", + "general": "ะ—ะฐะณะฐะปัŒะฝะต", + "height": "ะ’ะธัะพั‚ะฐ", + "hibernate": "ะ“ั–ะฑะตั€ะฝะฐั†ั–ั", + "history": "ะ†ัั‚ะพั€ั–ั", + "icon": "ะ†ะบะพะฝะบะฐ", + "idle": "ะŸั€ะพัั‚ั–ะน", + "info": "ะ†ะฝั„ะพั€ะผะฐั†ั–ั", + "input": "ะ’ั…ั–ะด", + "install": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ", + "installed": "ะ’ัั‚ะฐะฝะพะฒะปะตะฝะพ", + "interface": "ะ†ะฝั‚ะตั€ั„ะตะนั", + "internet": "ะ†ะฝั‚ะตั€ะฝะตั‚", + "keybind": "ะŸั€ะธะฒ'ัะทะบะฐ ะบะปะฐะฒั–ัˆ", + "language": "ะœะพะฒะฐ", + "loading": "ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั...", + "local": "ะœั–ัั†ะตะฒะธะน", + "location": "ะœั–ัั†ะตะทะฝะฐั…ะพะดะถะตะฝะฝั", + "lock": "ะ—ะฐะฑะปะพะบัƒะฒะฐั‚ะธ", + "logout": "ะ’ะธะนั‚ะธ", + "look": "ะ’ะธะณะปัะด", + "margins": "ะŸะพะปั", + "media": "ะœะตะดั–ะฐ", + "media-player": "ะœะตะดั–ะฐะฟั€ะพะณั€ะฐะฒะฐั‡", + "memory": "ะŸะฐะผ'ัั‚ัŒ", + "monitors": "ะœะพะฝั–ั‚ะพั€ะธ", + "move-down": "ะŸะตั€ะตะผั–ัั‚ะธั‚ะธ ะฒะฝะธะท", + "move-up": "ะŸะตั€ะตะผั–ัั‚ะธั‚ะธ ะฒะณะพั€ัƒ", + "network": "ะœะตั€ะตะถะฐ", + "next": "ะะฐัั‚ัƒะฟะฝะธะน", + "night-light": "ะั–ั‡ะฝะต ัะฒั–ั‚ะปะพ", + "no": "ะั–", + "no-results": "ะะตะผะฐั” ั€ะตะทัƒะปัŒั‚ะฐั‚ั–ะฒ", + "no-summary": "ะะตะผะฐั” ะพะฟะธััƒ", + "none": "ะ–ะพะดะตะฝ", + "not-found": "ะะต ะทะฝะฐะนะดะตะฝะพ", + "notifications": "ะกะฟะพะฒั–ั‰ะตะฝะฝั", + "official": "ะžั„ั–ั†ั–ะนะฝะธะน", + "on-surface": "ะะฐ ะฟะพะฒะตั€ั…ะฝั–", + "output": "ะ’ะธั…ั–ะด", + "pair": "ะกะฟะฐั€ัƒะฒะฐั‚ะธ", + "paired": "ะกะฟะฐั€ะพะฒะฐะฝะพ", + "pairing": "ะกะฟะฐั€ัƒะฒะฐะฝะฝั...", + "panels": "ะŸะฐะฝะตะปั–", + "password": "ะŸะฐั€ะพะปัŒ", + "pause": "ะŸะฐัƒะทะฐ", + "performance": "ะŸั€ะพะดัƒะบั‚ะธะฒะฝั–ัั‚ัŒ", + "pin": "ะ—ะฐะบั€ั–ะฟะธั‚ะธ", + "play": "ะ’ั–ะดั‚ะฒะพั€ะธั‚ะธ", + "polling": "ะžะฟะธั‚ัƒะฒะฐะฝะฝั", + "position": "ะŸะพะทะธั†ั–ั", + "previous": "ะŸะพะฟะตั€ะตะดะฝั–ะน", + "primary": "ะžัะฝะพะฒะฝะธะน", + "random": "ะ’ะธะฟะฐะดะบะพะฒะธะน", + "reboot": "ะŸะตั€ะตะทะฐะฒะฐะฝั‚ะฐะถะธั‚ะธ", + "reboot-to-uefi": "ะŸะตั€ะตะทะฐะฒะฐะฝั‚ะฐะถะธั‚ะธ ะฒ UEFI", + "record": "ะŸะพั‡ะฐั‚ะธ ะทะฐะฟะธั", + "refresh": "ะžะฝะพะฒะธั‚ะธ", + "required": "(ะพะฑะพะฒ'ัะทะบะพะฒะพ)", + "reset": "ะกะบะธะฝัƒั‚ะธ", + "reset-to-default": "ะกะบะธะฝัƒั‚ะธ ะทะฐ ะ—ะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ", + "result-count": "{count} ั€ะตะทัƒะปัŒั‚ะฐั‚", + "result-count-plural": "{count} ั€ะตะทัƒะปัŒั‚ะฐั‚ะธ", + "resume": "ะŸั€ะพะดะพะฒะถะธั‚ะธ", + "save": "ะ—ะฑะตั€ะตะณั‚ะธ", + "scanning": "ะกะบะฐะฝัƒะฒะฐะฝะฝั...", + "screen-corners": "ะšัƒั‚ะธ ะตะบั€ะฐะฝะฐ", + "search": "ะŸะพัˆัƒะบ", + "second": "{count} ัะตะบัƒะฝะดะฐ", + "second-plural": "{count} ัะตะบัƒะฝะด", + "secondary": "ะ’ั‚ะพั€ะธะฝะฝะธะน", + "security": "ะ‘ะตะทะฟะตะบะฐ", + "select": "ะ’ะธะฑั€ะฐั‚ะธ", + "select-color": "ะ’ะธะฑั€ะฐั‚ะธ ะบะพะปั–ั€", + "select-color-description": "ะ—ะฐัั‚ะพััƒะฒะฐั‚ะธ ะบะพะปัŒะพั€ะธ ั‚ะตะผะธ ะดะปั ะฒะธะดั–ะปะตะฝะฝั.", + "select-icon-color": "ะ’ะธะฑั€ะฐั‚ะธ ะบะพะปั–ั€ ะทะฝะฐั‡ะบะฐ", + "settings": "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั", + "shortcuts": "ะฏั€ะปะธะบะธ", + "shutdown": "ะ’ะธะผะบะฝัƒั‚ะธ", + "signal": "ะกะธะณะฝะฐะป", + "sound": "ะ—ะฒัƒะบ", + "sources": "ะ”ะถะตั€ะตะปะฐ", + "start": "ะŸะพั‡ะฐั‚ะธ", + "stop": "ะ—ัƒะฟะธะฝั–ั‚ัŒัั", + "supporters": "ะŸั€ะธั…ะธะปัŒะฝะธะบะธ", + "suspend": "ะŸั€ะธะทัƒะฟะธะฝะธั‚ะธ", + "templates": "ะจะฐะฑะปะพะฝะธ", + "tertiary": "ะขั€ะตั‚ะธะฝะฝะธะน", + "test": "ะขะตัั‚", + "thresholds": "ะŸะพั€ะพะณะพะฒั– ะทะฝะฐั‡ะตะฝะฝั", + "title": "ะะฐะทะฒะฐ", + "toast": "Toast", + "trusted": "ะะฐะดั–ะนะฝะธะน", + "uninstall": "ะ’ะธะดะฐะปะธั‚ะธ", + "unknown": "ะะตะฒั–ะดะพะผะพ", + "unpair": "ะ ะพะทั–ั€ะฒะฐั‚ะธ ะฟะฐั€ัƒ", + "unpin": "ะ’ั–ะดะบั€ั–ะฟะธั‚ะธ", + "update": "ะžะฝะพะฒะปะตะฝะฝั", + "upload": "ะ’ะธะฒะฐะฝั‚ะฐะถะธั‚ะธ", + "username": "ะ†ะผ'ั ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ", + "userspace-reboot": "ะŸะตั€ะตะทะฐะฒะฐะฝั‚ะฐะถะธั‚ะธ ะฟั€ะพัั‚ั–ั€ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ", + "version": "ะ’ะตั€ัั–ั", + "vibrant": "ะฏัะบั€ะฐะฒะธะน", + "visualizer": "ะ’ั–ะทัƒะฐะปั–ะทะฐั‚ะพั€", + "volume": "ะžะฑ'ั”ะผ", + "volumes": "ะžะฑััะณะธ", + "wallpaper": "ะจะฟะฐะปะตั€ะธ", + "weather": "ะŸะพะณะพะดะฐ", + "weather-loading": "ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ะฟะพะณะพะดะธ...", + "weather-no-location": "ะ’ัั‚ะฐะฝะพะฒั–ั‚ัŒ ะผั–ัั†ะตะทะฝะฐั…ะพะดะถะตะฝะฝั ะฒ ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝัั…, ั‰ะพะฑ ะฑะฐั‡ะธั‚ะธ ะฟะพะณะพะดัƒ", + "week": "ะขะธะถะดะตะฝัŒ", + "widgets": "ะ’ั–ะดะถะตั‚ะธ", + "width": "ะจะธั€ะธะฝะฐ", + "wifi": "Wi-Fi", + "windows": "ะ’ั–ะบะฝะฐ", + "yes": "ะขะฐะบ" + }, + "control-center": { + "power-profile": { + "tooltip-action": "ะŸั€ะพั„ั–ะปัŒ ะถะธะฒะปะตะฝะฝั", + "tooltip-disabled": "ะ’ัั‚ะฐะฝะพะฒั–ั‚ัŒ power-profiles-daemon ะดะปั ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะฟั€ะพั„ั–ะปั–ะฒ ะถะธะฒะปะตะฝะฝั" + }, + "wifi": { + "label-disconnected": "Wi-Fi ะฒั–ะดะบะปัŽั‡ะตะฝะพ", + "label-ethernet": "Ethernet" + } + }, + "display-modes": { + "always-hide": "ะ—ะฐะฒะถะดะธ ั…ะพะฒะฐะนัั", + "always-show": "ะ—ะฐะฒะถะดะธ ะฟะพะบะฐะทัƒะฒะฐั‚ะธ", + "force-open": "ะ—ะปะฐะผะฐั‚ะธ ัะธะปะพัŽ", + "on-hover": "ะŸั€ะธ ะฝะฐะฒะตะดะตะฝะฝั–" + }, + "hide-modes": { + "auto-hide": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะต ะฟั€ะธั…ะพะฒัƒะฒะฐะฝะฝั", + "hidden": "ะŸั€ะธั…ะพะฒัƒะฒะฐั‚ะธ, ะบะพะปะธ ะฟะพั€ะพะถะฝัŒะพ", + "idle": "ะŸั€ะธั…ะพะฒัƒะฒะฐั‚ะธ, ะบะพะปะธ ะฝะตะฐะบั‚ะธะฒะฝะธะน", + "non-exclusive": "ะะตะตะบัะบะปัŽะทะธะฒะฝะธะน", + "transparent": "ะŸั€ะพะทะพั€ะธะน, ะบะพะปะธ ะฟะพั€ะพะถะฝั–ะน", + "visible": "ะ—ะฐะฒะถะดะธ ะฒะธะดะธะผะธะน" + }, + "launcher": { + "categories": { + "all": "ะ’ัะต", + "audiovideo": "ะัƒะดั–ะพ ั‚ะฐ ะฒั–ะดะตะพ", + "chat": "ะงะฐั‚", + "development": "ะ ะพะทะฒะธั‚ะพะบ", + "education": "ะžัะฒั–ั‚ะฐ", + "emoji-activity": "ะะบั‚ะธะฒะฝั–ัั‚ัŒ", + "emoji-animals": "ะขะฒะฐั€ะธะฝะธ", + "emoji-flags": "ะŸั€ะฐะฟะพั€ะธ", + "emoji-food": "ะ‡ะถะฐ ั‚ะฐ ะฝะฐะฟะพั—", + "emoji-nature": "ะŸั€ะธั€ะพะดะฐ", + "emoji-objects": "ะžะฑ'ั”ะบั‚ะธ", + "emoji-people": "ะ›ัŽะดะธ ั‚ะฐ ั‚ั–ะปะพ", + "emoji-recent": "ะะตั‰ะพะดะฐะฒะฝั–", + "emoji-symbols": "ะกะธะผะฒะพะปะธ", + "emoji-travel": "ะŸะพะดะพั€ะพะถั– ั‚ะฐ ะผั–ัั†ั", + "game": "ะ†ะณั€ะธ", + "graphics": "ะ“ั€ะฐั„ั–ะบะฐ", + "misc": "ะ ั–ะทะฝะต", + "office": "ะžั„ั–ั", + "pinned": "ะ—ะฐะบั€ั–ะฟะปะตะฝะพ", + "system": "ะกะธัั‚ะตะผะฐ", + "webbrowser": "ะ’ะตะฑ-ะฑั€ะฐัƒะทะตั€" + }, + "providers": { + "applications": "ะ—ะฐัั‚ะพััƒะฝะบะธ", + "calculator": "ะšะฐะปัŒะบัƒะปัั‚ะพั€", + "calculator-deprecated": "ะšะพะผะฐะฝะดะฐ >calc ะทะฐัั‚ะฐั€ั–ะปะฐ ั– ัะบะพั€ะพ ะฑัƒะดะต ะฒะธะดะฐะปะตะฝะฐ. ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฒะฒะตะดั–ั‚ัŒ ะผะฐั‚ะตะผะฐั‚ะธั‡ะฝั– ะฒะธั€ะฐะทะธ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะฒ ะฟะพัˆัƒะบัƒ.", + "calculator-description": "ะšะฐะปัŒะบัƒะปัั‚ะพั€ โ€” ะพะฑั‡ะธัะปะตะฝะฝั ะผะฐั‚ะตะผะฐั‚ะธั‡ะฝะธั… ะฒะธั€ะฐะทั–ะฒ", + "calculator-enter-expression": "ะ’ะฒะตะดั–ั‚ัŒ ะผะฐั‚ะตะผะฐั‚ะธั‡ะฝะธะน ะฒะธั€ะฐะท", + "calculator-press-enter-to-copy": "ะะฐั‚ะธัะฝั–ั‚ัŒ Enter, ั‰ะพะฑ ัะบะพะฟั–ัŽะฒะฐั‚ะธ ั€ะตะทัƒะปัŒั‚ะฐั‚", + "clipboard": "ะ†ัั‚ะพั€ั–ั ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ", + "clipboard-clear-description": "ะžั‡ะธัั‚ะธั‚ะธ ะฒััŽ ั–ัั‚ะพั€ั–ัŽ ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ", + "clipboard-clear-description-full": "ะ’ะธะดะฐะปะธั‚ะธ ะฒัั– ะตะปะตะผะตะฝั‚ะธ ะท ั–ัั‚ะพั€ั–ั— ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ", + "clipboard-clear-history": "ะžั‡ะธัั‚ะธั‚ะธ ั–ัั‚ะพั€ั–ัŽ ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ", + "clipboard-delete": "ะ’ะธะดะฐะปะธั‚ะธ ะทะฐะฟะธั ะท ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ", + "clipboard-history-disabled": "ะ†ัั‚ะพั€ั–ั ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ ะฒะธะผะบะฝะตะฝะฐ", + "clipboard-history-disabled-description": "ะฃะฒั–ะผะบะฝั–ั‚ัŒ ั–ัั‚ะพั€ั–ัŽ ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ ะฒ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝัั… ะฐะฑะพ ะฒัั‚ะฐะฝะพะฒั–ั‚ัŒ cliphist", + "clipboard-loading": "ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ั–ัั‚ะพั€ั–ั— ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ...", + "clipboard-search-description": "ะŸะพัˆัƒะบ ะฒ ั–ัั‚ะพั€ั–ั— ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ", + "command-description": "ะ’ะธะบะพะฝะฐั‚ะธ ะบะพะผะฐะฝะดะธ ะพะฑะพะปะพะฝะบะธ", + "emoji": "ะžะฑั€ะฐั‚ะธ ะตะผะพะดะทั–", + "emoji-loading": "ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ะตะผะพะดะทั–...", + "emoji-loading-description": "ะ—ะฐั‡ะตะบะฐะนั‚ะต, ะฑัƒะดัŒ ะปะฐัะบะฐ", + "emoji-no-recent": "ะะตะผะฐั” ะฝะตั‰ะพะดะฐะฒะฝั–ั… ะตะผะพะดะทั–", + "emoji-search-description": "ะŸะพัˆัƒะบ ั– ะบะพะฟั–ัŽะฒะฐะฝะฝั ะตะผะพะดะทั–", + "settings-search-description": "ะŸะพัˆัƒะบ ั‚ะฐ ะฟะตั€ะตั…ั–ะด ะดะพ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝัŒ", + "windows-search-description": "ะŸะพัˆัƒะบ ั– ั„ะพะบัƒััƒะฒะฐะฝะฝั ะฒั–ะดะบั€ะธั‚ะธั… ะฒั–ะบะพะฝ" + } + }, + "lock-screen": { + "authenticating": "ะะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั...", + "password": "ะ’ะฒะตะดั–ั‚ัŒ ะฒะฐัˆ ะฟะฐั€ะพะปัŒ...", + "restart": "ะŸะตั€ะตะทะฐะฟัƒัั‚ะธั‚ะธ", + "shut-down": "ะ’ะธะผะบะฝัƒั‚ะธ" + }, + "notifications": { + "panel": { + "click-to-collapse": "ะะฐั‚ะธัะฝั–ั‚ัŒ, ั‰ะพะฑ ะทะณะพั€ะฝัƒั‚ะธ", + "click-to-expand": "ะšะปะฐั†ะฝั–ั‚ัŒ ะดะปั ั€ะพะทะณะพั€ั‚ะฐะฝะฝั", + "description": "ะ’ะฐัˆั– ัะฟะพะฒั–ั‰ะตะฝะฝั ะท'ัะฒะปัั‚ะธะผัƒั‚ัŒัั ั‚ัƒั‚ ะฟะพ ะผั–ั€ั– ะฝะฐะดั…ะพะดะถะตะฝะฝั.", + "no-notifications": "ะะตะผะฐั” ัะฟะพะฒั–ั‰ะตะฝัŒ" + }, + "range": { + "earlier": "ะ ะฐะฝั–ัˆะต", + "today": "ะกัŒะพะณะพะดะฝั–", + "yesterday": "ะฃั‡ะพั€ะฐ" + }, + "time": { + "diff-d": "1 ะดะตะฝัŒ ั‚ะพะผัƒ", + "diff-dd": "{diff} ะดะฝั–ะฒ ั‚ะพะผัƒ", + "diff-h": "1 ะณะพะดะธะฝัƒ ั‚ะพะผัƒ", + "diff-hh": "{diff} ะณะพะดะธะฝ ั‚ะพะผัƒ", + "diff-m": "1 ั…ะฒะธะปะธะฝัƒ ั‚ะพะผัƒ", + "diff-mm": "{diff} ั…ะฒะธะปะธะฝ ั‚ะพะผัƒ", + "now": "ะทะฐั€ะฐะท" + } + }, + "options": { + "bar": { + "density-comfortable": "ะŸั€ะพัั‚ะพั€ะธะน", + "density-compact": "ะšะพะผะฟะฐะบั‚ะฝะธะน", + "density-default": "ะกั‚ะฐะฝะดะฐั€ั‚ะฝะธะน", + "density-mini": "ะœั–ะฝั–", + "density-spacious": "ะŸั€ะพัั‚ะพั€ะธะน", + "display-mode-always-visible": "ะ—ะฐะฒะถะดะธ ะฒะธะดะธะผะธะน", + "type-floating": "ะŸะปะฐะฒะฐัŽั‡ะธะน", + "type-framed": "ะฃ ั€ะฐะผั†ั–", + "type-simple": "ะŸั€ะพัั‚ะธะน" + }, + "control-center": { + "quick-settings-style-classic": "ะšะปะฐัะธั‡ะฝะธะน", + "quick-settings-style-modern": "ะกัƒั‡ะฐัะฝะธะน" + }, + "frame-rates-fps": "{fps} ะบ/ั", + "launcher-density": { + "comfortable": "ะšะพะผั„ะพั€ั‚ะฝะธะน", + "compact": "ะšะพะผะฟะฐะบั‚ะฝะธะน", + "default": "ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ" + }, + "launcher-view-mode": { + "grid": "ะกั–ั‚ะบะฐ", + "list": "ะกะฟะธัะพะบ" + }, + "notification-density": { + "compact": "ะšะพะผะฟะฐะบั‚ะฝะธะน", + "default": "ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ" + }, + "scrolling-modes": { + "always": "ะ—ะฐะฒะถะดะธ ะฟั€ะพะบั€ัƒั‡ัƒะฒะฐั‚ะธ", + "hover": "ะŸั€ะพะบั€ัƒั‡ัƒะฒะฐั‚ะธ ะฟั€ะธ ะฝะฐะฒะตะดะตะฝะฝั–", + "never": "ะั–ะบะพะปะธ ะฝะต ะฟั€ะพะบั€ัƒั‡ัƒะฒะฐั‚ะธ" + }, + "session-menu-grid-layout": { + "grid": "ะกั–ั‚ะบะฐ", + "single-row": "ะžะดะธะฝ ั€ัะดะพะบ" + }, + "settings-panel-mode": { + "attached": "ะŸะฐะฝะตะปัŒ ะฟั€ะธะบั€ั–ะฟะปะตะฝะฐ ะดะพ ะฟะฐะฝะตะปั–", + "centered": "ะฆะตะฝั‚ั€ะพะฒะฐะฝะฐ ะฟะฐะฝะตะปัŒ", + "window": "ะžะบั€ะตะผะต ะฒั–ะบะฝะพ" + }, + "visualizer-types": { + "linear": "ะ›ั–ะฝั–ะนะฝะธะน", + "mirrored": "ะ”ะทะตั€ะบะฐะปัŒะฝะธะน", + "wave": "ะฅะฒะธะปั" + }, + "visualizer-visibility": { + "with-background": "ะขั–ะปัŒะบะธ ะท ั„ะพะฝะพะผ" + }, + "workspace-labels": { + "index": "ะ†ะฝะดะตะบั", + "index-and-name": "ะ†ะฝะดะตะบั ั‚ะฐ ะฝะฐะทะฒะฐ", + "name": "ะะฐะทะฒะฐ" + } + }, + "panels": { + "about": { + "become-supporter": "ะกั‚ะฐั‚ะธ ะฟั€ะธั…ะธะปัŒะฝะธะบะพะผ", + "changelog": "ะŸะตั€ะตะณะปัะฝัƒั‚ะธ ะถัƒั€ะฝะฐะป ะทะผั–ะฝ", + "changelog-on-startup": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะถัƒั€ะฝะฐะป ะทะผั–ะฝ ะฟั–ะด ั‡ะฐั ะพะฝะพะฒะปะตะฝะฝั", + "changelog-on-startup-desc": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟะพะบะฐะทัƒะฒะฐั‚ะธ ะถัƒั€ะฝะฐะป ะทะผั–ะฝ ะฟั€ะธ ะพะฝะพะฒะปะตะฝะฝั– Noctalia.", + "contributors-description": "ะŸะพะดัะบะฐ ะฝะฐัˆะพะผัƒ {count} ั‡ัƒะดะพะฒะพะผัƒ ัƒั‡ะฐัะฝะธะบัƒ!", + "contributors-description-plural": "ะŸะพะดัะบะฐ ะฝะฐัˆะธะผ {count} ั‡ัƒะดะพะฒะธะผ ัƒั‡ะฐัะฝะธะบะฐะผ!", + "copy-info": "ะšะพะฟั–ัŽะฒะฐั‚ะธ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ", + "debug-disabled": "ะ ะตะถะธะผ ะฝะฐะปะฐะณะพะดะถะตะฝะฝั ะฒะธะผะบะฝะตะฝะพ", + "debug-enabled": "ะ ะตะถะธะผ ะฝะฐะปะฐะณะพะดะถะตะฝะฝั ัƒะฒั–ะผะบะฝะตะฝะพ", + "info-copied": "ะ†ะฝั„ะพั€ะผะฐั†ั–ัŽ ัะบะพะฟั–ะนะพะฒะฐะฝะพ ะดะพ ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ", + "noctalia-available": "ะ”ะพัั‚ัƒะฟะฝะพ:", + "noctalia-desc": "ะ•ะปะตะณะฐะฝั‚ะฝะฐ ั‚ะฐ ะผั–ะฝั–ะผะฐะปั–ัั‚ะธั‡ะฝะฐ ะพะฑะพะปะพะฝะบะฐ ั€ะพะฑะพั‡ะพะณะพ ัั‚ะพะปัƒ, ั€ะตั‚ะตะปัŒะฝะพ ัั‚ะฒะพั€ะตะฝะฐ ะดะปั Wayland, ะฟะพะฑัƒะดะพะฒะฐะฝะฐ ะฝะฐ Quickshell.", + "noctalia-git-commit": "Git ะบะพะผั–ั‚:", + "noctalia-installed-version": "ะ’ัั‚ะฐะฝะพะฒะปะตะฝะฐ ะฒะตั€ัั–ั:", + "noctalia-latest-version": "ะžัั‚ะฐะฝะฝั ะฒะตั€ัั–ั:", + "noctalia-qs-version": "Noctalia QS ะฒะตั€ัั–ั:", + "noctalia-title": "ะžะฑะพะปะพะฝะบะฐ Noctalia", + "privacy-policy": "ะŸะพะปั–ั‚ะธะบะฐ ะบะพะฝั„ั–ะดะตะฝั†ั–ะนะฝะพัั‚ั–", + "support": "ะŸั–ะดั‚ั€ะธะผะฐั‚ะธ ะฝะฐั", + "supporter-badge": "ะŸั€ะธั…ะธะปัŒะฝะธะบ", + "supporters-desc": "ะ’ะตะปะธั‡ะตะทะฝะต ะดัะบัƒัŽ ะฝะฐัˆะพะผัƒ ั‡ัƒะดะพะฒะพะผัƒ ะฟั€ะธั…ะธะปัŒะฝะธะบัƒ!", + "supporters-desc-plural": "ะ’ะตะปะธั‡ะตะทะฝะฐ ะฟะพะดัะบะฐ ะฝะฐัˆะธะผ {count} ั‡ัƒะดะพะฒะธะผ ะฟั€ะธั…ะธะปัŒะฝะธะบะฐะผ!", + "supporters-loading": "ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ะฟั€ะธั…ะธะปัŒะฝะธะบั–ะฒ...", + "system-board": "ะœะฐั‚ะตั€ะธะฝััŒะบะฐ ะฟะปะฐั‚ะฐ:", + "system-cpu": "ะฆะŸ:", + "system-disk": "ะ”ะธัะบ:", + "system-gpu": "GPU:", + "system-host": "ั…ะพัั‚:", + "system-install-hint": "ะ’ัั‚ะฐะฝะพะฒั–ั‚ัŒ fastfetch ะดะปั ะฟะตั€ะตะณะปัะดัƒ ั–ะฝั„ะพั€ะผะฐั†ั–ั— ะฟั€ะพ ัะธัั‚ะตะผัƒ", + "system-kernel": "ะฏะดั€ะพ:", + "system-loading": "ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ัะธัั‚ะตะผะฝะพั— ั–ะฝั„ะพั€ะผะฐั†ั–ั—...", + "system-memory": "ะŸะฐะผ'ัั‚ัŒ:", + "system-monitor": "ะœะพะฝั–ั‚ะพั€:", + "system-not-installed": "fastfetch ะฝะต ะฒัั‚ะฐะฝะพะฒะปะตะฝะพ", + "system-os": "ะžะก:", + "system-packages": "ะŸะฐะบัƒะฝะบะธ:", + "system-product": "ะŸั€ะพะดัƒะบั‚:", + "system-title": "ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ัะธัั‚ะตะผัƒ", + "system-uptime": "ะงะฐั ะฑะตะทะฟะตั€ะตะฑั–ะนะฝะพั— ั€ะพะฑะพั‚ะธ:", + "system-wm": "WM:\nะ’ะœ:", + "telemetry-data-copied": "ะ”ะฐะฝั– ั‚ะตะปะตะผะตั‚ั€ั–ั— ัะบะพะฟั–ะนะพะฒะฐะฝะพ ะดะพ ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ", + "telemetry-desc": "ะ”ะพะฟะพะผะพะถั–ั‚ัŒ ะฟะพะบั€ะฐั‰ะธั‚ะธ Noctalia, ะฟะพะดั–ะปะธะฒัˆะธััŒ ะฐะฝะพะฝั–ะผะฝะพัŽ ั–ะฝั„ะพั€ะผะฐั†ั–ั”ัŽ ะฟั€ะพ ัะธัั‚ะตะผัƒ (ั€ะพะทะดั–ะปัŒะฝะฐ ะทะดะฐั‚ะฝั–ัั‚ัŒ ะตะบั€ะฐะฝะฐ, ะบะพะผะฟะพะทะธั‚ะพั€, ะดะธัั‚ั€ะธะฑัƒั‚ะธะฒ). ะะฐะดัะธะปะฐั”ั‚ัŒัั ะพะดะธะฝ ั€ะฐะท ะฟั–ะด ั‡ะฐั ะทะฐะฟัƒัะบัƒ, ะฑะตะท ะฒั–ะดัั‚ะตะถะตะฝะฝั, ะดะฐะฝั– ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฒะธะดะฐะปััŽั‚ัŒัั ั‡ะตั€ะตะท 30 ะดะฝั–ะฒ.", + "telemetry-enabled": "ะ’ั–ะดะฟั€ะฐะฒะธั‚ะธ ะฐะฝะพะฝั–ะผะฝัƒ ัะธัั‚ะตะผะฝัƒ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ", + "telemetry-show-data": "ะŸะตั€ะตะณะปัะด ะดะฐะฝะธั…", + "telemetry-title": "ะšะพะฝั„ั–ะดะตะฝั†ั–ะนะฝั–ัั‚ัŒ", + "title": "ะŸั€ะพ Noctalia", + "up-to-date": "ะฃ ะฒะฐั ะพัั‚ะฐะฝะฝั ะฒะตั€ัั–ั!", + "update-available": "ะ”ะพัั‚ัƒะฟะฝะต ะพะฝะพะฒะปะตะฝะฝั", + "view-commit": "ะŸะตั€ะตะณะปัะฝัƒั‚ะธ ะบะพะผั–ั‚ ะฝะฐ GitHub" + }, + "audio": { + "devices-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะดะพัั‚ัƒะฟะฝั– ะฟั€ะธัั‚ั€ะพั— ะฒะฒะตะดะตะฝะฝั ั‚ะฐ ะฒะธะฒะตะดะตะฝะฝั ะทะฒัƒะบัƒ.", + "devices-input-device-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะฑะฐะถะฐะฝะธะน ะฟั€ะธัั‚ั€ั–ะน ะฒะฒะตะดะตะฝะฝั ะทะฒัƒะบัƒ.", + "devices-input-device-label": "ะŸั€ะธัั‚ั€ั–ะน ะฒะฒะตะดะตะฝะฝั", + "devices-output-device-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะฑะฐะถะฐะฝะธะน ะฟั€ะธัั‚ั€ั–ะน ะฒะธะฒะตะดะตะฝะฝั ะทะฒัƒะบัƒ.", + "devices-output-device-label": "ะŸั€ะธัั‚ั€ั–ะน ะฒะธะฒะตะดะตะฝะฝั", + "devices-title": "ะัƒะดั–ะพะฟั€ะธัั‚ั€ะพั—", + "external-mixer-description": "ะ’ะฒะตะดั–ั‚ัŒ ะบะพะผะฐะฝะดัƒ ะฐะฑะพ ัˆะปัั… ะดะพ ะทะฐัั‚ะพััƒะฝะบัƒ ะดะปั ะทะฐะฟัƒัะบัƒ ะฟั€ะธ ะฐะบั‚ะธะฒะฐั†ั–ั— ั„ัƒะฝะบั†ั–ั— ะทะพะฒะฝั–ัˆะฝัŒะพะณะพ ะฐัƒะดั–ะพะผั–ะบัˆะตั€ะฐ.", + "external-mixer-label": "ะšะพะผะฐะฝะดะฐ ะทะพะฒะฝั–ัˆะฝัŒะพะณะพ ะฐัƒะดั–ะพะผั–ะบัˆะตั€ะฐ", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "ะ’ัั‚ะฐะฝะพะฒั–ั‚ัŒ ะฑะฐะถะฐะฝั– ั‚ะฐ ั–ะณะฝะพั€ะพะฒะฐะฝั– ะผะตะดั–ะฐะทะฐัั‚ะพััƒะฝะบะธ.", + "media-excluded-player-description": "ะ”ะพะดะฐะนั‚ะต ะบะปัŽั‡ะพะฒั– ัะปะพะฒะฐ ะดะปั ะฟะปะตั”ั€ั–ะฒ, ัะบั– ัะธัั‚ะตะผะฐ ะผะฐั” ั–ะณะฝะพั€ัƒะฒะฐั‚ะธ. ะšะพะถะฝะต ะบะปัŽั‡ะพะฒะต ัะปะพะฒะพ ะฝะฐ ะฝะพะฒะพะผัƒ ั€ัะดะบัƒ.", + "media-excluded-player-label": "ะ†ะณะฝะพั€ะพะฒะฐะฝะธะน ะฟะปะตั”ั€", + "media-excluded-player-placeholder": "ะฒะฒะตะดั–ั‚ัŒ ั‡ะฐัั‚ะธะฝัƒ ะฝะฐะทะฒะธ ั‚ะฐ ะฝะฐั‚ะธัะฝั–ั‚ัŒ +", + "media-frame-rate-description": "ะ’ะธั‰ั– ะทะฝะฐั‡ะตะฝะฝั ะฟะปะฐะฒะฝั–ัˆั–, ะฐะปะต ัะฟะพะถะธะฒะฐัŽั‚ัŒ ะฑั–ะปัŒัˆะต ั€ะตััƒั€ัั–ะฒ.", + "media-frame-rate-label": "ะงะฐัั‚ะพั‚ะฐ ะบะฐะดั€ั–ะฒ", + "media-primary-player-description": "ะ’ะฒะตะดั–ั‚ัŒ ะบะปัŽั‡ะพะฒะต ัะปะพะฒะพ ะดะปั ั–ะดะตะฝั‚ะธั„ั–ะบะฐั†ั–ั— ะพัะฝะพะฒะฝะพะณะพ ะฟะปะตั”ั€ะฐ.", + "media-primary-player-label": "ะžัะฝะพะฒะฝะธะน ะฟะปะตั”ั€", + "media-primary-player-placeholder": "ะฝะฐะฟั€. spotify, vlc, mpv", + "media-scrolling-speed-description": "ะงะฐั ัƒ ัะตะบัƒะฝะดะฐั… ะดะปั ะฟั€ะพะบั€ัƒั‚ะบะธ ะฝะฐะทะฒะธ ะฒั–ะด ะฟะพั‡ะฐั‚ะบัƒ ะดะพ ะบั–ะฝั†ั.", + "media-scrolling-speed-label": "ะจะฒะธะดะบั–ัั‚ัŒ ะฟั€ะพะบั€ัƒั‚ะบะธ", + "media-scrolling-title-description": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฑะตะทะฟะตั€ะตั€ะฒะฝัƒ ะฟั€ะพะบั€ัƒั‚ะบัƒ ะดะปั ะดะพะฒะณะธั… ะฝะฐะทะฒ ะผะตะดั–ะฐ.", + "media-scrolling-title-label": "ะŸั€ะพะบั€ัƒั‚ะบะฐ ะฝะฐะทะฒะธ", + "media-title": "ะœะตะดั–ะฐะฟะปะตั”ั€ะธ", + "on-middle-clicked-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟั€ะธ ะฝะฐั‚ะธัะบะฐะฝะฝั– ัะตั€ะตะดะฝัŒะพัŽ ะบะฝะพะฟะบะพัŽ ะผะธัˆั–.", + "panel-applications-empty": "ะ—ะฐั€ะฐะท ะถะพะดะตะฝ ะดะพะดะฐั‚ะพะบ ะฝะต ะฒั–ะดั‚ะฒะพั€ัŽั” ะฐัƒะดั–ะพ", + "spectrum-mirrored-description": "ะ’ั–ะดะดะทะตั€ะบะฐะปัŽะนั‚ะต ัะฟะตะบั‚ั€, ั‰ะพะฑ ะฝะธะทัŒะบั– ั‡ะฐัั‚ะพั‚ะธ ะฑัƒะปะธ ะฒ ั†ะตะฝั‚ั€ั–. ะšะพะปะธ ะฒะธะผะบะฝะตะฝะพ, ะฝะธะทัŒะบั– ั‡ะฐัั‚ะพั‚ะธ ะทะฝะฐั…ะพะดัั‚ัŒัั ะปั–ะฒะพั€ัƒั‡, ะฐ ะฒะธัะพะบั– โ€” ะฟั€ะฐะฒะพั€ัƒั‡.", + "spectrum-mirrored-label": "ะ”ะทะตั€ะบะฐะปัŒะฝะธะน ัะฟะตะบั‚ั€", + "title": "ะัƒะดั–ะพ", + "visualizer-type-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ั‚ะธะฟ ะฒั–ะทัƒะฐะปั–ะทะฐั†ั–ั— ะดะปั ะฒั–ะดั‚ะฒะพั€ะตะฝะฝั ะผะตะดั–ะฐ.", + "visualizer-type-label": "ะขะธะฟ ะฒั–ะทัƒะฐะปั–ะทะฐั†ั–ั—", + "volumes-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ั€ะตะณัƒะปัั‚ะพั€ะธ ะณัƒั‡ะฝะพัั‚ั– ั‚ะฐ ั€ั–ะฒะฝั– ะทะฒัƒะบัƒ.", + "volumes-feedback-sound-file-description": "ะจะปัั… ะดะพ ะทะฒัƒะบะพะฒะพะณะพ ั„ะฐะนะปัƒ, ัะบะธะน ะฒั–ะดั‚ะฒะพั€ัŽั”ั‚ัŒัั ะฟั–ะด ั‡ะฐั ะทะผั–ะฝะธ ะณัƒั‡ะฝะพัั‚ั–.", + "volumes-feedback-sound-file-label": "ะ—ะฒัƒะบะพะฒะธะน ั„ะฐะนะป ะทะฒะพั€ะพั‚ะฝะพะณะพ ะทะฒ'ัะทะบัƒ ะณัƒั‡ะฝะพัั‚ั–", + "volumes-feedback-sound-file-select-title": "ะ’ะธะฑั€ะฐั‚ะธ ะทะฒัƒะบะพะฒะธะน ั„ะฐะนะป ะทะฒะพั€ะพั‚ะฝะพะณะพ ะทะฒ'ัะทะบัƒ ะณัƒั‡ะฝะพัั‚ั–", + "volumes-input-volume-description": "ะ ั–ะฒะตะฝัŒ ะณัƒั‡ะฝะพัั‚ั– ะผั–ะบั€ะพั„ะพะฝะฐ.", + "volumes-mute-input-description": "ะ’ะธะผะบะฝัƒั‚ะธ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะน ะฐัƒะดั–ะพะฒั…ั–ะด (ะผั–ะบั€ะพั„ะพะฝ).", + "volumes-mute-input-label": "ะ’ะธะผะบะฝัƒั‚ะธ ะฒั…ั–ะดะฝะธะน ะทะฒัƒะบ", + "volumes-mute-output-description": "ะ’ะธะผะบะฝัƒั‚ะธ ะพัะฝะพะฒะฝะธะน ะฐัƒะดั–ะพะฒะธั…ั–ะด ัะธัั‚ะตะผะธ.", + "volumes-mute-output-label": "ะ’ะธะผะบะฝัƒั‚ะธ ะฒะธั…ั–ะดะฝะธะน ะทะฒัƒะบ", + "volumes-output-volume-description": "ะกะธัั‚ะตะผะฝะธะน ั€ั–ะฒะตะฝัŒ ะณัƒั‡ะฝะพัั‚ั–.", + "volumes-step-size-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะบั€ะพะบ ะทะผั–ะฝะธ ะณัƒั‡ะฝะพัั‚ั– (ะบะพะปะตัะพ ะผะธัˆั–, ะณะฐั€ัั‡ั– ะบะปะฐะฒั–ัˆั–).", + "volumes-step-size-label": "ะšั€ะพะบ ะทะผั–ะฝะธ ะณัƒั‡ะฝะพัั‚ั–", + "volumes-volume-feedback-description": "ะ’ั–ะดั‚ะฒะพั€ัŽะฒะฐั‚ะธ ะทะฒัƒะบ ะทะฒะพั€ะพั‚ะฝะพะณะพ ะทะฒ'ัะทะบัƒ ะฟั–ะด ั‡ะฐั ั€ะตะณัƒะปัŽะฒะฐะฝะฝั ะณัƒั‡ะฝะพัั‚ั–.", + "volumes-volume-feedback-label": "ะ’ั–ะดั‚ะฒะพั€ะธั‚ะธ ะทะฒัƒะบ ะทะฒะพั€ะพั‚ะฝะพะณะพ ะทะฒ'ัะทะบัƒ ะณัƒั‡ะฝะพัั‚ั–", + "volumes-volume-overdrive-description": "ะ”ะพะทะฒะพะปะธั‚ะธ ะฟั–ะดะฒะธั‰ะตะฝะฝั ะณัƒั‡ะฝะพัั‚ั– ะฟะพะฝะฐะด 100%. ะœะพะถะต ะฝะต ะฟั–ะดั‚ั€ะธะผัƒะฒะฐั‚ะธััŒ ัƒัั–ะผ ะพะฑะปะฐะดะฝะฐะฝะฝัะผ.", + "volumes-volume-overdrive-label": "ะ“ัƒั‡ะฝั–ัั‚ัŒ ะฟะพะฝะฐะด 100%" + }, + "bar": { + "appearance-auto-hide-delay-description": "ะงะฐั, ั‡ะตั€ะตะท ัะบะธะน Bar ัั…ะพะฒะฐั”ั‚ัŒัั ะฟั–ัะปั ั‚ะพะณะพ, ัะบ ะบัƒั€ัะพั€ ะผะธัˆั– ะทะฐะปะธัˆะธั‚ัŒ ะนะพะณะพ.", + "appearance-auto-hide-delay-label": "ะ—ะฐั‚ั€ะธะผะบะฐ ะฟั€ะธั…ะพะฒัƒะฒะฐะฝะฝั", + "appearance-auto-hide-exclusive-note": "ะŸั€ะธะผั–ั‚ะบะฐ: ะ•ะบัะบะปัŽะทะธะฒะฝะฐ ะทะพะฝะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฒะธะผะธะบะฐั”ั‚ัŒัั, ะบะพะปะธ ะฐะฒั‚ะพะฟั€ะธั…ะพะฒัƒะฒะฐะฝะฝั ะฐะบั‚ะธะฒะฝะต.", + "appearance-auto-show-delay-description": "ะงะฐั ะดะพ ะฟะพัะฒะธ ะฟะฐะฝะตะปั–, ะบะพะปะธ ะผะธัˆะฐ ะฒั…ะพะดะธั‚ัŒ ัƒ ะบั€ะฐะน.", + "appearance-auto-show-delay-label": "ะ—ะฐั‚ั€ะธะผะบะฐ ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั", + "appearance-background-opacity-description": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ะฟั€ะพะทะพั€ั–ัั‚ัŒ ั„ะพะฝัƒ ัะฟะตั†ั–ะฐะปัŒะฝะพ ะดะปั ะฟะฐะฝะตะปั–.", + "appearance-background-opacity-label": "ะŸั€ะพะทะพั€ั–ัั‚ัŒ ั„ะพะฝัƒ ะฟะฐะฝะตะปั–", + "appearance-capsule-color-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะบะพะปั–ั€ ะดะปั ะบะฐะฟััƒะป ะฟะฐะฝะตะปั– ะฐะฑะพ ะฝะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ะถะพะดะฝะพะณะพ ะดะปั ัั‚ะฐะฝะดะฐั€ั‚ะฝะพะณะพ ะบะพะปัŒะพั€ัƒ ะฟะพะฒะตั€ั…ะฝั–.", + "appearance-capsule-color-label": "ะšะพะปั–ั€ ะบะฐะฟััƒะปะธ", + "appearance-capsule-opacity-description": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ั€ั–ะฒะตะฝัŒ ะฝะตะฟั€ะพะทะพั€ะพัั‚ั– ะดะปั ั„ะพะฝัƒ ะฒั–ะดะถะตั‚ั–ะฒ, ะบะพะปะธ ะฟะพะบะฐะทะฐะฝะพ ะบะฐะฟััƒะปัƒ.", + "appearance-capsule-opacity-label": "ะะตะฟั€ะพะทะพั€ั–ัั‚ัŒ ะบะฐะฟััƒะปะธ", + "appearance-content-padding-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฒั–ะดัั‚ัƒะฟ ะผั–ะถ ะบั€ะฐัะผะธ ะฟะฐะฝะตะปั– ั‚ะฐ ะฒั–ะดะถะตั‚ะฐะผะธ.", + "appearance-content-padding-label": "ะ’ั–ะดัั‚ัƒะฟ ะฒะผั–ัั‚ัƒ", + "appearance-density-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฒั–ะดัั‚ัƒะฟะธ ะฟะฐะฝะตะปั– ะดะปั ะบะพะผะฟะฐะบั‚ะฝะพะณะพ ะฐะฑะพ ะฟั€ะพัั‚ะพั€ะพะณะพ ะฒะธะณะปัะดัƒ.", + "appearance-density-label": "ะฉั–ะปัŒะฝั–ัั‚ัŒ ะฟะฐะฝะตะปั–", + "appearance-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะทะพะฒะฝั–ัˆะฝั–ะน ะฒะธะณะปัะด ั‚ะฐ ะฟะพะปะพะถะตะฝะฝั ะฟะฐะฝะตะปั–.", + "appearance-display-mode-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ะบะพะปะธ ะฟะฐะฝะตะปัŒ ะฒะธะดะธะผะฐ.", + "appearance-enable-exclusion-zone-inset-description": "ะ—ะผะตะฝัˆั–ั‚ัŒ ะทะพะฝัƒ ะฒะธะบะปัŽั‡ะตะฝะฝั ะฝะฐ 1 ั„ั–ะทะธั‡ะฝะธะน ะฟั–ะบัะตะปัŒ, ั‰ะพะฑ ะฒั–ะบะฝะฐ ะฒะฟั€ะธั‚ัƒะป ั–ะดะตะฐะปัŒะฝะพ ะทะฐั…ะพะดะธะปะธ ะฟั–ะด ะบั€ะฐะน ะฟะฐะฝะตะปะธ.", + "appearance-enable-exclusion-zone-inset-label": "ะ’ะฝัƒั‚ั€ั–ัˆะฝั ะทะพะฝะฐ ะฒะธะบะปัŽั‡ะตะฝะฝั", + "appearance-floating-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะฟะฐะฝะตะปัŒ ัะบ ะฟะปะฐะฒะฐัŽั‡ัƒ 'ะบะฐะฟััƒะปัƒ'. ะŸั€ะธะผั–ั‚ะบะฐ: ะฆะต ะฟะตั€ะตะผั–ัั‚ะธั‚ัŒ ะบัƒั‚ะธ ะตะบั€ะฐะฝะฐ ะดะพ ะบั€ะฐั—ะฒ.", + "appearance-floating-label": "ะŸะปะฐะฒะฐัŽั‡ะฐ ะฟะฐะฝะตะปัŒ (ะžัั‚ั€ั–ะฒะตั†ัŒ)", + "appearance-font-scale-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะผะฐััˆั‚ะฐะฑ ั€ะพะทะผั–ั€ัƒ ัˆั€ะธั„ั‚ัƒ ะดะปั ั‚ะตะบัั‚ัƒ, ั‰ะพ ะฒั–ะดะพะฑั€ะฐะถะฐั”ั‚ัŒัั ะฝะฐ ะฟะฐะฝะตะปั–.", + "appearance-font-scale-label": "ะœะฐััˆั‚ะฐะฑ ัˆั€ะธั„ั‚ัƒ", + "appearance-frame-radius": "ะ’ะฝัƒั‚ั€ั–ัˆะฝั–ะน ั€ะฐะดั–ัƒั", + "appearance-frame-settings-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ั‚ะพะฒั‰ะธะฝัƒ ั€ะฐะผะบะธ ั‚ะฐ ะฒะฝัƒั‚ั€ั–ัˆะฝั–ะน ั€ะฐะดั–ัƒั ะบัƒั‚ั–ะฒ", + "appearance-frame-settings-label": "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ั€ะฐะผะบะธ", + "appearance-frame-thickness": "ะขะพะฒั‰ะธะฝะฐ", + "appearance-hide-on-overview-description": "ะŸั€ะธั…ะพะฒะฐั‚ะธ ะฟะฐะฝะตะปัŒ ั– ะทะฐะบั€ะธั‚ะธ ะฟะฐะฝะตะปั–, ะบะพะปะธ ะฐะบั‚ะธะฒะฝะธะน ะพะณะปัะด ะบะพะผะฟะพะฝัƒะฒะฐะปัŒะฝะธะบะฐ.", + "appearance-hide-on-overview-label": "ะŸั€ะธั…ะพะฒะฐั‚ะธ ะฟะฐะฝะตะปัŒ ะฝะฐ ะพะณะปัะดั–", + "appearance-margins-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฟะพะปั ะฝะฐะฒะบะพะปะพ ะฟะปะฐะฒะฐัŽั‡ะพั— ะฟะฐะฝะตะปั–.", + "appearance-margins-horizontal": "ะ“ะพั€ะธะทะพะฝั‚ะฐะปัŒะฝะธะน ะฒั–ะดัั‚ัƒะฟ", + "appearance-margins-vertical": "ะ’ะตั€ั‚ะธะบะฐะปัŒะฝะธะน ะฒั–ะดัั‚ัƒะฟ", + "appearance-outer-corners-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั” ะฝะฐะทะพะฒะฝั– ะทะฐะบั€ัƒะณะปะตะฝั– ะบัƒั‚ะธ ะฝะฐ ะฟะฐะฝะตะปั–.", + "appearance-outer-corners-label": "ะ—ะพะฒะฝั–ัˆะฝั– ะบัƒั‚ะธ", + "appearance-position-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ะดะต ั€ะพะทะผั–ัั‚ะธั‚ะธ ะฟะฐะฝะตะปัŒ ะฝะฐ ะตะบั€ะฐะฝั–.", + "appearance-position-label": "ะŸะพะปะพะถะตะฝะฝั ะฟะฐะฝะตะปั–", + "appearance-show-capsule-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ั„ะพะฝ ะฒั–ะดะถะตั‚ั–ะฒ.", + "appearance-show-capsule-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะบะฐะฟััƒะปัƒ", + "appearance-show-on-workspace-switch-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะบะพั€ะพั‚ะบะพ ะฟะพะบะฐะทัƒะฒะฐั‚ะธ Bar ะฟั€ะธ ะทะผั–ะฝั– ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพัั‚ะพั€ัƒ.", + "appearance-show-on-workspace-switch-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟะฐะฝะตะปัŒ ะฟั€ะธ ะฟะตั€ะตะผะธะบะฐะฝะฝั– ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพัั‚ะพั€ัƒ", + "appearance-show-outline-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั” ะฒะธะดะธะผัƒ ั€ะฐะผะบัƒ ะฝะฐะฒะบะพะปะพ ะบะพะถะฝะพะณะพ ะฒั–ะดะถะตั‚ะฐ.", + "appearance-show-outline-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะบะพะฝั‚ัƒั€ะธ ะฒั–ะดะถะตั‚ั–ะฒ", + "appearance-type-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ัั‚ะธะปัŒ ะฟะฐะฝะตะปั–: ะŸั€ะพัั‚ะธะน, ะŸะปะฐะฒะฐัŽั‡ะธะน ะฐะฑะพ ะฃ ั€ะฐะผั†ั–.", + "appearance-type-label": "ะขะธะฟ ะฟะฐะฝะตะปั–", + "appearance-use-separate-opacity-description": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะพะบั€ะตะผะพะณะพ ะทะฝะฐั‡ะตะฝะฝั ะฟั€ะพะทะพั€ะพัั‚ั– ะดะปั ั„ะพะฝัƒ ะฟะฐะฝะตะปั–.", + "appearance-use-separate-opacity-label": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะพะบั€ะตะผัƒ ะฟั€ะพะทะพั€ั–ัั‚ัŒ ัั‚ะพะฒะฟั†ั–ะฒ", + "appearance-widget-spacing-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ั–ะฝั‚ะตั€ะฒะฐะป ะผั–ะถ ะบะพะถะฝะธะผ ะฒั–ะดะถะตั‚ะพะผ ะฝะฐ ะฟะฐะฝะตะปั–.", + "appearance-widget-spacing-label": "ะ’ั–ะดัั‚ะฐะฝัŒ ะผั–ะถ ะฒั–ะดะถะตั‚ะฐะผะธ", + "behavior-middle-click-command-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะทะฐ ัะตั€ะตะดะฝั–ะผ ะบะปั–ะบะพะผ.", + "behavior-middle-click-command-label": "ะšะพะผะฐะฝะดะฐ ะกะตั€ะตะดะฝัŒะพะณะพ ะšะปั–ะบัƒ", + "behavior-middle-click-command-placeholder": "niri msg action ะฟะตั€ะตะผะบะฝัƒั‚ะธ ะพะณะปัะด", + "behavior-middle-click-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ั‰ะพ ั€ะพะฑะธั‚ัŒ ัะตั€ะตะดะฝั–ะน ะบะปั–ะบ ะฝะฐ ะฟะพั€ะพะถะฝั–ั… ะดั–ะปัะฝะบะฐั… ะŸะฐะฝะตะปั–.", + "behavior-middle-click-follow-mouse-description": "ะ’ั–ะดะบั€ะธั‚ะธ ะฒะธะฑั€ะฐะฝัƒ ะฟะฐะฝะตะปัŒ ัะตั€ะตะดะฝัŒะพะณะพ ะบะปั–ะบัƒ ะฒ ะฟะพะทะธั†ั–ั— ะบัƒั€ัะพั€ะฐ.", + "behavior-middle-click-follow-mouse-label": "ะกะตั€ะตะดะฝั–ะน ะบะปั–ะบ ัะปั–ะดัƒั” ะทะฐ ะบัƒั€ัะพั€ะพะผ", + "behavior-middle-click-label": "ะ”ั–ั ัะตั€ะตะดะฝัŒะพะณะพ ะบะปั–ะบัƒ ะฝะฐ ะŸะฐะฝะตะปั–", + "behavior-right-click-command-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟั€ะธ ะฟั€ะฐะฒะพะผัƒ ะบะปั–ะบัƒ.", + "behavior-right-click-command-label": "ะšะพะผะฐะฝะดะฐ ะŸั€ะฐะฒะพะณะพ ะšะปั–ะบะฐ", + "behavior-right-click-command-placeholder": "notify-send \"ะŸั€ะฐะฒะธะน ะบะปั–ะบ\"", + "behavior-right-click-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ั‰ะพ ั€ะพะฑะธั‚ัŒ ะฟั€ะฐะฒะธะน ะบะปั–ะบ ะฝะฐ ะฟะพั€ะพะถะฝั–ั… ะพะฑะปะฐัั‚ัั… ะฟะฐะฝะตะปั–.", + "behavior-right-click-follow-mouse-description": "ะ’ั–ะดะบั€ะธั‚ะธ ะฒะธะฑั€ะฐะฝัƒ ะฟะฐะฝะตะปัŒ ะฟั€ะฐะฒะพะณะพ ะบะปั–ะบะฐ ะฒ ะฟะพะทะธั†ั–ั— ะบัƒั€ัะพั€ะฐ.", + "behavior-right-click-follow-mouse-label": "ะŸั€ะฐะฒะธะน ะบะปั–ะบ ัะปั–ะดัƒั” ะทะฐ ะบัƒั€ัะพั€ะพะผ", + "behavior-right-click-label": "ะ”ั–ั ะฟั€ะฐะฒะพะณะพ ะบะปั–ะบัƒ ะฝะฐ ะŸะฐะฝะตะปั–", + "behavior-wheel-wrap-description": "ะฏะบั‰ะพ ะฒะฒั–ะผะบะฝะตะฝะพ, ะฟั€ะพะบั€ัƒั‚ะบะฐ ะฟั€ะพะดะพะฒะถัƒั”ั‚ัŒัั ะฒั–ะด ะพัั‚ะฐะฝะฝัŒะพะณะพ ะตะปะตะผะตะฝั‚ะฐ ะดะพ ะฟะตั€ัˆะพะณะพ.", + "behavior-wheel-wrap-label": "ะŸะพ ะบะพะปัƒ", + "behavior-workspace-scroll-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ั‰ะพ ั€ะพะฑะธั‚ัŒ ะบะพะปั–ั‰ะฐั‚ะบะพ ะผะธัˆั– ะฝะฐ ะฟะพั€ะพะถะฝั–ั… ะดั–ะปัะฝะบะฐั… ะฟะฐะฝะตะปั–.", + "behavior-workspace-scroll-label": "ะ”ั–ั ะบะพะปะตัะฐ ะผะธัˆั– ะŸะฐะฝะตะปั–", + "behavior-workspace-scroll-option-content": "ะ’ะผั–ัั‚", + "behavior-workspace-scroll-option-workspace": "ะ ะพะฑะพั‡ะฐ ะพะฑะปะฐัั‚ัŒ", + "monitor-configure-widgets": "ะะฐะปะฐัˆั‚ัƒะฒะฐั‚ะธ ะฒั–ะดะถะตั‚ะธ", + "monitor-override-settings": "ะŸะตั€ะตะฒะธะทะฝะฐั‡ะธั‚ะธ ะณะปะพะฑะฐะปัŒะฝั– ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั", + "monitor-override-settings-description": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฒะปะฐัะฝั– ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ะดะปั ั†ัŒะพะณะพ ะผะพะฝั–ั‚ะพั€ะฐ.", + "monitor-reset-all": "ะกะบะธะฝัƒั‚ะธ ะฒัะต", + "monitor-widgets-title": "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ะฒั–ะดะถะตั‚ะฐ - {monitor}", + "monitors-desc": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟะฐะฝะตะปัŒ ะฝะฐ ะฟะตะฒะฝะธั… ะผะพะฝั–ั‚ะพั€ะฐั…. ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฝะฐ ะฒัั–ั…, ัะบั‰ะพ ะฝะต ะฒะธะฑั€ะฐะฝะพ.", + "monitors-desc-new": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต, ะฝะฐ ัะบะธั… ะผะพะฝั–ั‚ะพั€ะฐั… ะฒั–ะดะพะฑั€ะฐะถะฐั‚ะธ Bar, ั‚ะฐ ะฝะฐะปะฐัˆั‚ัƒะนั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ะธ ะดะปั ะบะพะถะฝะพะณะพ ะผะพะฝั–ั‚ะพั€ะฐ.", + "monitors-title": "ะ’ั–ะดะพะฑั€ะฐะถะตะฝะฝั ะฝะฐ ะผะพะฝั–ั‚ะพั€ะฐั…", + "title": "ะŸะฐะฝะตะปัŒ", + "tray-blacklist-description": "ะ”ะพะดะฐะนั‚ะต ะฟั€ะฐะฒะธะปะฐ ะฒะธะบะปัŽั‡ะตะฝะฝั ะท ั‚ั€ะตั, ะฟั–ะดั‚ั€ะธะผัƒั” ัˆะฐะฑะปะพะฝะธ (*).", + "tray-blacklist-label": "ะงะพั€ะฝะธะน ัะฟะธัะพะบ", + "tray-blacklist-placeholder": "ะฝะฐะฟั€. nm-applet, Fcitx*", + "tray-pin-application": "ะ—ะฐะบั€ั–ะฟะธั‚ะธ ะทะฐัั‚ะพััƒะฝะพะบ", + "tray-unpin-application": "ะ’ั–ะดะบั€ั–ะฟะธั‚ะธ ะทะฐัั‚ะพััƒะฝะพะบ", + "use-global-widgets": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะณะปะพะฑะฐะปัŒะฝั– ะฒั–ะดะถะตั‚ะธ", + "widgets-desc": "ะŸะตั€ะตั‚ัะณัƒะนั‚ะต ะฒั–ะดะถะตั‚ะธ, ั‰ะพะฑ ะทะผั–ะฝะธั‚ะธ ั—ั… ะฟะพั€ัะดะพะบ. ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ะบะพะฝั‚ะตะบัั‚ะฝะต ะผะตะฝัŽ (ะบะปั–ะบ ะฟั€ะฐะฒะพัŽ ะบะฝะพะฟะบะพัŽ ะผะธัˆั–), ั‰ะพะฑ ะฟะตั€ะตะผั–ั‰ัƒะฒะฐั‚ะธ ะฒั–ะดะถะตั‚ะธ ะผั–ะถ ัะตะบั†ั–ัะผะธ ะฐะฑะพ ะฒะธะดะฐะปัั‚ะธ ั—ั….", + "widgets-title": "ะ ะพะทะผั–ั‰ะตะฝะฝั ะฒั–ะดะถะตั‚ั–ะฒ" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "ะกั‚ะฒะพั€ัŽะนั‚ะต ะบะพะปั–ั€ะฝั– ัั…ะตะผะธ ะทั– ัะฒะพั—ั… ัˆะฟะฐะปะตั€. ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฒะธะดะพะฑัƒะฒะฐั” ะบะพะปัŒะพั€ะธ ะดะปั ัั‚ะฒะพั€ะตะฝะฝั ั†ั–ะปั–ัะฝะพั— ั‚ะตะผะธ.", + "color-source-use-wallpaper-colors-label": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะบะพะปัŒะพั€ะธ ัˆะฟะฐะปะตั€", + "dark-mode-mode-description": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะต ะฟะตั€ะตะผะธะบะฐะฝะฝั ะผั–ะถ ัะฒั–ั‚ะปะธะผ ั– ั‚ะตะผะฝะธะผ ั€ะตะถะธะผะพะผ.", + "dark-mode-mode-label": "ะ ะพะทะบะปะฐะด ั‚ะตะผะฝะพะณะพ ั€ะตะถะธะผัƒ", + "dark-mode-mode-manual": "ะ’ั€ัƒั‡ะฝัƒ", + "dark-mode-mode-off": "ะ’ะธะผะบะฝะตะฝะพ", + "dark-mode-switch-description": "ะŸะตั€ะตะผะธะบะฐั” ะฝะฐ ั‚ะตะผะฝั–ัˆัƒ ั‚ะตะผัƒ ะดะปั ะทั€ัƒั‡ะฝะพะณะพ ะฟะตั€ะตะณะปัะดัƒ ะฒะฝะพั‡ั–.", + "delete-error-description": "ะะต ะฒะดะฐะปะพัั ะฒะธะดะฐะปะธั‚ะธ {scheme}", + "delete-error-title": "ะŸะพะผะธะปะบะฐ ะฒะธะดะฐะปะตะฝะฝั", + "delete-success-description": "{scheme} ัƒัะฟั–ัˆะฝะพ ะฒะธะดะฐะปะตะฝะพ", + "delete-success-title": "ะšะพะปัŒะพั€ะพะฒัƒ ัั…ะตะผัƒ ะฒะธะดะฐะปะตะฝะพ", + "download-button": "ะ—ะฐะฒะฐะฝั‚ะฐะถะธั‚ะธ ั‰ะต", + "download-downloading": "ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั...", + "download-empty": "ะะตะผะฐั” ะดะพัั‚ัƒะฟะฝะธั… ะบะพะปัŒะพั€ะพะฒะธั… ัั…ะตะผ", + "download-error-api-error": "ะŸะพะผะธะปะบะฐ API: {status}", + "download-error-description": "ะะต ะฒะดะฐะปะพัั ะทะฐะฒะฐะฝั‚ะฐะถะธั‚ะธ {scheme}", + "download-error-download-failed": "ะŸะพะผะธะปะบะฐ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ะท ะบะพะดะพะผ ะฒะธั…ะพะดัƒ: {code}", + "download-error-invalid-response": "ะะตะฒั–ั€ะฝะธะน ั„ะพั€ะผะฐั‚ ะฒั–ะดะฟะพะฒั–ะดั– API", + "download-error-no-files": "ะคะฐะนะปะธ ะดะปั ัั…ะตะผะธ ะฝะต ะทะฝะฐะนะดะตะฝะพ", + "download-error-parse-failed": "ะŸะพะผะธะปะบะฐ ะฟะฐั€ัะธะฝะณัƒ ะฒั–ะดะฟะพะฒั–ะดั– API: {error}", + "download-error-rate-limit": "ะŸะตั€ะตะฒะธั‰ะตะฝะพ ะปั–ะผั–ั‚ ะทะฐะฟะธั‚ั–ะฒ GitHub API", + "download-error-title": "ะŸะพะผะธะปะบะฐ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั", + "download-fetching": "ะžั‚ั€ะธะผะฐะฝะฝั ะดะพัั‚ัƒะฟะฝะธั… ะบะพะปัŒะพั€ะพะฒะธั… ัั…ะตะผ...", + "download-success-description": "{scheme} ัƒัะฟั–ัˆะฝะพ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะพ", + "download-success-title": "ะšะพะปัŒะพั€ะพะฒัƒ ัั…ะตะผัƒ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะพ", + "download-title": "ะ—ะฐะฒะฐะฝั‚ะฐะถะธั‚ะธ ะบะพะปัŒะพั€ะพะฒั– ัั…ะตะผะธ", + "method-description": { + "content": "ะกั…ะตะผะฐ Material Design ะท ะฒะธัะพะบะพั‚ะพั‡ะฝะพัŽ ะตะบัั‚ั€ะฐะบั†ั–ั”ัŽ ะบะพะปัŒะพั€ั–ะฒ, ัะบะฐ ั‚ะพั‡ะฝะพ ะฒั–ะดะฟะพะฒั–ะดะฐั” ั„ะฐะบั‚ะธั‡ะฝะธะผ ะบะพะปัŒะพั€ะฐะผ ะฒะธั…ั–ะดะฝะพะณะพ ะบะพะฝั‚ะตะฝั‚ัƒ.", + "dysfunctional": "ะฏะบ Faithful, ะฐะปะต ะฒะธะฑะธั€ะฐั” ะดั€ัƒะณัƒ ะทะฐ ะดะพะผั–ะฝะฐะฝั‚ะฝั–ัั‚ัŽ ะบะพะปั–ั€ะฝัƒ ั€ะพะดะธะฝัƒ ัะบ ะพัะฝะพะฒะฝัƒ.", + "faithful": "ะะฐะผะฐะณะฐั”ั‚ัŒัั ะทะฐะปะธัˆะฐั‚ะธัั ะฑะปะธะทัŒะบะธะผ ะดะพ ะฒะธั…ั–ะดะฝะพะณะพ ะบะพะปัŒะพั€ัƒ, ัั‚ะฒะพั€ัŽัŽั‡ะธ ะฟั€ะธ ั†ัŒะพะผัƒ ะณะฐั€ะผะพะฝั–ะนะฝัƒ ะฟะฐะปั–ั‚ั€ัƒ.", + "fruit-salad": "ะกั…ะตะผะฐ Material Design, ัะบะฐ ัั‚ะฒะพั€ัŽั” ะณั€ะฐะนะปะธะฒัƒ, ััะบั€ะฐะฒัƒ ะฟะฐะปั–ั‚ั€ัƒ ะท ั€ั–ะทะฝะพะผะฐะฝั–ั‚ะฝะธะผะธ ะฒั–ะดั‚ั–ะฝะบะฐะผะธ.", + "monochrome": "ะกั…ะตะผะฐ Material Design, ั‰ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” ะพะดะฝะพั‚ะพะฝะฝัƒ ะณั€ะฐะดะฐั†ั–ัŽ ัั–ั€ะพะณะพ ะท ะผั–ะฝั–ะผะฐะปัŒะฝะธะผ ั…ั€ะพะผะฐั‚ะธั‡ะฝะธะผ ะฒะผั–ัั‚ะพะผ.", + "muted": "ะกั‚ะฒะพั€ัŽั” ะผ'ัะบั–ัˆั–, ะดะตะฝะฐัะธั‡ะตะฝั– ั‚ะพะฝะธ ะดะปั ัะฟะพะบั–ะนะฝะพะณะพ ั‚ะฐ ัั‚ั€ะธะผะฐะฝะพะณะพ ะฒะธะณะปัะดัƒ.", + "rainbow": "ะกั…ะตะผะฐ Material Design, ัะบะฐ ะณะตะฝะตั€ัƒั” ะบะพะปัŒะพั€ะธ ะฟะพ ะฒััŒะพะผัƒ ัะฟะตะบั‚ั€ัƒ ะดะปั ะผะฐะบัะธะผะฐะปัŒะฝะพั— ั€ั–ะทะฝะพะผะฐะฝั–ั‚ะฝะพัั‚ั– ะบะพะปัŒะพั€ั–ะฒ.", + "tonal-spot": "ะกั‚ะฐะฝะดะฐั€ั‚ะฝะธะน ะฐะปะณะพั€ะธั‚ะผ Material Design. ะกั‚ะฒะพั€ัŽั” ะทะฑะฐะปะฐะฝัะพะฒะฐะฝั– ะฟะฐะปั–ั‚ั€ะธ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ั‚ะพะฝะฐะปัŒะฝั– ะฒะฐั€ั–ะฐั†ั–ั— ะฒะธั…ั–ะดะฝะพะณะพ ะบะพะปัŒะพั€ัƒ.", + "vibrant": "ะœะฐะบัะธะผั–ะทัƒั” ะฝะฐัะธั‡ะตะฝั–ัั‚ัŒ ะบะพะปัŒะพั€ั–ะฒ ะดะปั ัะผั–ะปะธะฒะธั…, ะฟั€ะธะฒะฐะฑะปะธะฒะธั… ะฟะฐะปั–ั‚ั€ ะท ััะบั€ะฐะฒะธะผะธ ั‚ะพะฝะฐะผะธ." + }, + "predefined-desc": "ะ’ะธะฑะตั€ั–ั‚ัŒ ั–ะท ะบะพะปะตะบั†ั–ั— ะฟะพะฟะตั€ะตะดะฝัŒะพ ะฒะธะทะฝะฐั‡ะตะฝะธั… ะบะพะปั–ั€ะฝะธั… ัั…ะตะผ.", + "predefined-generate-templates-label": "ะ“ะตะฝะตั€ัƒะฒะฐั‚ะธ ัˆะฐะฑะปะพะฝะธ ะดะปั ะฟะพะฟะตั€ะตะดะฝัŒะพ ะฒะธะทะฝะฐั‡ะตะฝะธั… ัั…ะตะผ", + "predefined-title": "ะŸะพะฟะตั€ะตะดะฝัŒะพ ะฒะธะทะฝะฐั‡ะตะฝั– ะบะพะปั–ั€ะฝั– ัั…ะตะผะธ", + "sync-gsettings-description": "ะ—ั–ัั‚ะฐะฒะธั‚ะธ ัะธัั‚ะตะผะฝัƒ ั‚ะตะผัƒ ะท ะฐะบั‚ะธะฒะฝะธะผ ัะฒั–ั‚ะปะธะผ ะฐะฑะพ ั‚ะตะผะฝะธะผ ะฒะฐั€ั–ะฐะฝั‚ะพะผ.", + "sync-gsettings-label": "ะกะธะฝั…ั€ะพะฝั–ะทัƒะฒะฐั‚ะธ ัะธัั‚ะตะผะฝัƒ ั‚ะตะผัƒ", + "templates-desc": "ะ—ะฐัั‚ะพัะพะฒัƒะฒะฐั‚ะธ ะบะพะปัŒะพั€ะธ ะดะพ ะทะพะฒะฝั–ัˆะฝั–ั… ะทะฐัั‚ะพััƒะฝะบั–ะฒ.", + "templates-filter-description": "ะŸะพะบะฐะทะฐั‚ะธ ัˆะฐะฑะปะพะฝะธ ะท ะฟะตะฒะฝะพั— ะบะฐั‚ะตะณะพั€ั–ั—.", + "templates-filter-label": "ะคั–ะปัŒั‚ั€ัƒะฒะฐั‚ะธ ะทะฐ ะบะฐั‚ะตะณะพั€ั–ั”ัŽ", + "templates-misc-description": "ะกั‚ะฒะพั€ั–ั‚ัŒ ะฒะปะฐัะฝั– ัˆะฐะฑะปะพะฝะธ.", + "templates-misc-label": "ะ ะพะทัˆะธั€ะตะฝะพ", + "templates-misc-user-templates-description": "ะฃะฒั–ะผะบะฝั–ั‚ัŒ ะปะธัˆะต ัะบั‰ะพ ะฒะธ ะทะฝะฐั”ั‚ะต, ั‰ะพ ั€ะพะฑะธั‚ะต โ€” ะทะฒะตั€ะฝั–ั‚ัŒัั ะดะพ ะฝะฐัˆะพั— ะพะฝะปะฐะนะฝ-ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—.", + "templates-misc-user-templates-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะบะพั€ะธัั‚ัƒะฒะฐั†ัŒะบั– ัˆะฐะฑะปะพะฝะธ", + "templates-none-detected": "ะะต ะฒะธัะฒะปะตะฝะพ", + "templates-write-path": "ะ—ะฐะฟะธััƒั”: {filepath}", + "title": "ะšะพะปั–ั€ะฝะฐ ัั…ะตะผะฐ", + "wallpaper-method-description": "ะžะฑะตั€ั–ั‚ัŒ ัะฒั–ะน ัƒะปัŽะฑะปะตะฝะธะน ะผะตั‚ะพะด ัั‚ะฒะพั€ะตะฝะฝั ะฟะฐะปั–ั‚ั€ะธ.", + "wallpaper-method-label": "ะœะตั‚ะพะด ัั‚ะฒะพั€ะตะฝะฝั ะฟะฐะปั–ั‚ั€ะธ", + "wallpaper-monitor-source-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะผะพะฝั–ั‚ะพั€, ัะบะธะน ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะดะปั ะฒะธะปัƒั‡ะตะฝะฝั ะบะพะปัŒะพั€ั–ะฒ ัˆะฟะฐะปะตั€.", + "wallpaper-monitor-source-label": "ะ”ะถะตั€ะตะปะพ ะณะตะฝะตั€ะฐั†ั–ั— ะบะพะปัŒะพั€ัƒ" + }, + "connections": { + "authentication-required": "ะŸะพั‚ั€ั–ะฑะฝะฐ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั", + "bluetooth-auto-connect-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั–ะดะบะปัŽั‡ะฐั‚ะธัั ะดะพ ะดะพะฒั–ั€ะตะฝะธั… ัะฟะพะปัƒั‡ะตะฝะธั… ะฟั€ะธัั‚ั€ะพั—ะฒ, ะบะพะปะธ Bluetooth ัƒะฒั–ะผะบะฝะตะฝะพ.", + "bluetooth-auto-connect-label": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั–ะดะบะปัŽั‡ะฐั‚ะธ ัะฟะฐั€ะตะฝั– ะฟั€ะธัั‚ั€ะพั—", + "bluetooth-devices-unnamed": "ะ‘ะตะทั–ะผะตะฝะฝั– ะฟั€ะธัั‚ั€ะพั— ะฝะต ะฒั–ะดะพะฑั€ะฐะถะฐัŽั‚ัŒัั.", + "bluetooth-discoverable": "ะฆะตะน ะฟั€ะธัั‚ั€ั–ะน ะผะพะถะฝะฐ ะฒะธัะฒะธั‚ะธ ัะบ {hostName}, ะฟะพะบะธ ั†ั ะฒะบะปะฐะดะบะฐ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝัŒ ะฒั–ะดะบั€ะธั‚ะฐ.", + "bluetooth-rssi-polling-description": "ะŸะตั€ั–ะพะดะธั‡ะฝะพ ะฒะธะผั–ั€ัŽั” RSSI ะดะปั ะฟั–ะดะบะปัŽั‡ะตะฝะธั… ะฟั€ะธัั‚ั€ะพั—ะฒ ั‡ะตั€ะตะท bluetoothctl. ะœะพะถะต ะฑัƒั‚ะธ ะฝะตะดะพัั‚ัƒะฟะฝะพ ะดะปั ะฒัั–ั… ะฟั€ะธัั‚ั€ะพั—ะฒ; ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” ะผั–ะฝั–ะผะฐะปัŒะฝั– ั€ะตััƒั€ัะธ, ัะบั‰ะพ ัƒะฒั–ะผะบะฝะตะฝะพ.", + "bluetooth-rssi-polling-interval-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต, ัะบ ั‡ะฐัั‚ะพ ะพะฝะพะฒะปัŽะฒะฐั‚ะธ ั€ั–ะฒะตะฝัŒ ัะธะณะฝะฐะปัƒ ะดะปั ะฟั–ะดะบะปัŽั‡ะตะฝะธั… ะฟั€ะธัั‚ั€ะพั—ะฒ.", + "bluetooth-rssi-polling-interval-label": "ะ†ะฝั‚ะตั€ะฒะฐะป ะพะฟะธั‚ัƒะฒะฐะฝะฝั", + "bluetooth-rssi-polling-label": "ะžะฟะธั‚ัƒะฒะฐะฝะฝั ัะธะณะฝะฐะปัƒ Bluetooth", + "disable-discoverability-description": "ะŸั€ะธั…ะพะฒะฐั‚ะธ ะฒะฐัˆ ะฟั€ะธัั‚ั€ั–ะน ะฒั–ะด ะฝะฐะนะฑะปะธะถั‡ะธั… ะฟั€ะธัั‚ั€ะพั—ะฒ Bluetooth.", + "disable-discoverability-label": "ะ’ะธะผะบะฝัƒั‚ะธ ะฒะธะดะธะผั–ัั‚ัŒ ะฟั€ะธัั‚ั€ะพัŽ", + "hide-unnamed-devices-description": "ะŸั€ะธั…ะพะฒะฐั‚ะธ ะฟั€ะธัั‚ั€ะพั—, ัะบั– ะฒั–ะดะพะฑั€ะฐะถะฐัŽั‚ัŒัั ะปะธัˆะต ัะบ Bluetooth-ะฐะดั€ะตัะธ.", + "hide-unnamed-devices-label": "ะŸั€ะธั…ะพะฒะฐั‚ะธ ะฑะตะทั–ะผะตะฝะฝั– ะฟั€ะธัั‚ั€ะพั—", + "pin-instructions": "ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฒะฒะตะดั–ั‚ัŒ PIN-ะบะพะด, ั‰ะพ ะฒั–ะดะพะฑั€ะฐะถะฐั”ั‚ัŒัั ะฝะฐ ะฒะฐัˆะพะผัƒ ะฟั€ะธัั‚ั€ะพั—.", + "title": "ะŸั–ะดะบะปัŽั‡ะตะฝะฝั", + "wifi-header-text": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต Wi-Fi, ั‰ะพะฑ ะฟะตั€ะตะณะปัะดะฐั‚ะธ ะดะพัั‚ัƒะฟะฝั– ะผะตั€ะตะถั– ั‚ะฐ ะฟั–ะดะบะปัŽั‡ะฐั‚ะธัั ะดะพ ั–ะฝั‚ะตั€ะฝะตั‚ัƒ." + }, + "control-center": { + "cards-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต, ัะบั– ะตะปะตะผะตะฝั‚ะธ ะบะตั€ัƒะฒะฐะฝะฝั ะท'ัะฒะปััŽั‚ัŒัั ะฒ ั†ะตะฝั‚ั€ั– ะบะตั€ัƒะฒะฐะฝะฝั ั‚ะฐ ะฒ ัะบะพะผัƒ ะฟะพั€ัะดะบัƒ.", + "desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฟะพะปะพะถะตะฝะฝั ั‚ะฐ ะฟะพะฒะตะดั–ะฝะบัƒ ะฟะฐะฝะตะปั– ั†ะตะฝั‚ั€ัƒ ะบะตั€ัƒะฒะฐะฝะฝั.", + "open-at-mouse-description": "ะŸั€ะธ ะฝะฐั‚ะธัะบะฐะฝะฝั– ะฟั€ะฐะฒะพัŽ ะบะฝะพะฟะบะพัŽ ะผะธัˆั– ะฝะฐ ะฟะพั€ะพะถะฝั–ะน ะพะฑะปะฐัั‚ั– ะŸะฐะฝะตะปั–, ะฒั–ะดะบั€ะธะนั‚ะต ะฆะตะฝั‚ั€ ะบะตั€ัƒะฒะฐะฝะฝั ะฟะพั€ัƒั‡ ะท ะบัƒั€ัะพั€ะพะผ. ะฏะบั‰ะพ ะฒะธะผะบะฝะตะฝะพ, ะทะฐะผั–ัั‚ัŒ ั†ัŒะพะณะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ะฟะพะทะธั†ั–ั— ะฒะธั‰ะต.", + "open-at-mouse-label": "ะ’ั–ะดะบั€ะธะฒะฐั‚ะธ ะฑั–ะปั ะบัƒั€ัะพั€ะฐ ะทะฐ ะฟั€ะฐะฒะธะผ ะบะปั–ะบะพะผ", + "position-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ะดะต ะท'ัะฒะปัั”ั‚ัŒัั ะฟะฐะฝะตะปัŒ ั†ะตะฝั‚ั€ัƒ ะบะตั€ัƒะฒะฐะฝะฝั ะฟั€ะธ ะฒั–ะดะบั€ะธั‚ั‚ั–.", + "shortcuts-custom-button-command-description": "ะšะพะผะฐะฝะดะฐ, ัะบะฐ ะฑัƒะดะต ะฒะธะบะพะฝะฐะฝะฐ ะฟั€ะธ ะฝะฐั‚ะธัะบะฐะฝะฝั– ะบะฝะพะฟะบะธ.", + "shortcuts-custom-button-enable-on-state-logic-description": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะดั€ัƒะณัƒ ั–ะบะพะฝะบัƒ ั‚ะฐ 'ะณะฐั€ัั‡ะธะน' ัั‚ะฐะฝ ะฝะฐ ะพัะฝะพะฒั– ะบะพะผะฐะฝะดะธ ะฟะตั€ะตะฒั–ั€ะบะธ.", + "shortcuts-custom-button-enable-on-state-logic-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะปะพะณั–ะบัƒ ัƒะฒั–ะผะบะฝะตะฝะพะณะพ ัั‚ะฐะฝัƒ", + "shortcuts-custom-button-icon-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ั–ะบะพะฝะบัƒ ะท ะฑั–ะฑะปั–ะพั‚ะตะบะธ.", + "shortcuts-custom-button-on-clicked-label": "ะšะพะผะฐะฝะดะฐ ะปั–ะฒะพะณะพ ะบะปั–ะบัƒ", + "shortcuts-custom-button-on-middle-clicked-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟั€ะธ ะฝะฐั‚ะธัะบะฐะฝะฝั– ัะตั€ะตะดะฝัŒะพั— ะบะฝะพะฟะบะธ ะผะธัˆั–.", + "shortcuts-custom-button-on-state-command-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฟะตั€ะตะฒั–ั€ะบะธ, ั‡ะธ ะฟะพะฒะธะฝะฝะฐ ะบะฝะพะฟะบะฐ ะฑัƒั‚ะธ ัƒ ัั‚ะฐะฝั– 'ัƒะฒั–ะผะบะฝะตะฝะพ'. ะŸะพะฒะตั€ั‚ะฐั” 0 ะดะปั ัƒะฒั–ะผะบะฝะตะฝะพะณะพ ัั‚ะฐะฝัƒ, ะฝะตะฝัƒะปัŒะพะฒะต ะทะฝะฐั‡ะตะฝะฝั ะดะปั ะฒะธะผะบะฝะตะฝะพะณะพ.", + "shortcuts-custom-button-on-state-command-label": "ะšะพะผะฐะฝะดะฐ ะฟะตั€ะตะฒั–ั€ะบะธ ัƒะฒั–ะผะบะฝะตะฝะพะณะพ ัั‚ะฐะฝัƒ", + "shortcuts-custom-button-on-state-icon-description": "ะ†ะบะพะฝะบะฐ ะดะปั ะบะฝะพะฟะบะธ, ะบะพะปะธ ะฒะพะฝะฐ ัƒ ัั‚ะฐะฝั– 'ัƒะฒั–ะผะบะฝะตะฝะพ'.", + "shortcuts-custom-button-on-state-icon-label": "ะ†ะบะพะฝะบะฐ ัƒะฒั–ะผะบะฝะตะฝะพะณะพ ัั‚ะฐะฝัƒ", + "shortcuts-custom-button-state-checks-add": "ะ”ะพะดะฐั‚ะธ ะฟะตั€ะตะฒั–ั€ะบัƒ ัั‚ะฐะฝัƒ", + "shortcuts-custom-button-state-checks-command": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะดะปั ั†ั–ั”ั— ะฟะตั€ะตะฒั–ั€ะบะธ ัั‚ะฐะฝัƒ", + "shortcuts-custom-button-state-checks-label": "ะŸะตั€ะตะฒั–ั€ะบะธ ัั‚ะฐะฝัƒ", + "shortcuts-custom-button-state-checks-remove": "ะ’ะธะดะฐะปะธั‚ะธ", + "shortcuts-custom-button-tooltip-description": "ะŸั–ะดะบะฐะทะบะฐ, ัะบะฐ ะฑัƒะดะต ะฒั–ะดะพะฑั€ะฐะถะฐั‚ะธัั ะฟั€ะธ ะฝะฐะฒะตะดะตะฝะฝั– ะฝะฐ ะบะฝะพะฟะบัƒ.", + "shortcuts-custom-button-tooltip-label": "ะŸั–ะดะบะฐะทะบะฐ", + "shortcuts-title": "ะ’ั–ะดะถะตั‚ะธ ัˆะฒะธะดะบะพะณะพ ะดะพัั‚ัƒะฟัƒ", + "system-monitor-disk-path-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ั‚ะพั‡ะบัƒ ะผะพะฝั‚ัƒะฒะฐะฝะฝั ะดะธัะบะฐ, ัะบัƒ ะฟะพะฒะธะฝะฝะฐ ะฒั–ะดัั‚ะตะถัƒะฒะฐั‚ะธ ะบะฐั€ั‚ะฐ ัะธัั‚ะตะผะฝะพะณะพ ะผะพะฝั–ั‚ะพั€ะฐ ะฒ ั†ะตะฝั‚ั€ั– ะบะตั€ัƒะฒะฐะฝะฝั.", + "system-monitor-disk-path-label": "ะจะปัั… ะดะพ ะดะธัะบะฐ ัะธัั‚ะตะผะฝะพะณะพ ะผะพะฝั–ั‚ะพั€ะฐ", + "title": "ะฆะตะฝั‚ั€ ะบะตั€ัƒะฒะฐะฝะฝั" + }, + "desktop-widgets": { + "clock-enabled-description": "ะŸะพะบะฐะทะฐั‚ะธ ะฒั–ะดะถะตั‚ ะณะพะดะธะฝะฝะธะบะฐ ะฝะฐ ั€ะพะฑะพั‡ะพะผัƒ ัั‚ะพะปั–.", + "clock-enabled-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฒั–ะดะถะตั‚ ะณะพะดะธะฝะฝะธะบะฐ", + "clock-format-label": "ะคะพั€ะผะฐั‚", + "clock-height-description": "ะ’ะธัะพั‚ะฐ ะฒั–ะดะถะตั‚ะฐ ะณะพะดะธะฝะฝะธะบะฐ ะฒ ะฟั–ะบัะตะปัั….", + "clock-height-label": "ะ’ะธัะพั‚ะฐ", + "clock-minimal-mode-description": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะผั–ะฝั–ะผะฐะปัŒะฝะต ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ะณะพะดะธะฝะฝะธะบะฐ ะท ั‡ะฐัะพะผ ั– ะดะฐั‚ะพัŽ.", + "clock-minimal-mode-label": "ะœั–ะฝั–ะผะฐะปัŒะฝะธะน ั€ะตะถะธะผ", + "clock-rounded-corners-description": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ะทะฐะพะบั€ัƒะณะปะตะฝั– ะบัƒั‚ะธ ะดะปั ั„ะพะฝัƒ ะฒั–ะดะถะตั‚ะฐ.", + "clock-rounded-corners-label": "ะ—ะฐะพะบั€ัƒะณะปะตะฝั– ะบัƒั‚ะธ", + "clock-show-background-description": "ะŸะพะบะฐะทะฐั‚ะธ ั„ะพะฝะพะฒะธะน ะบะพะฝั‚ะตะนะฝะตั€ ะดะปั ะฒั–ะดะถะตั‚ะฐ ะณะพะดะธะฝะฝะธะบะฐ.", + "clock-show-background-label": "ะŸะพะบะฐะทะฐั‚ะธ ั„ะพะฝ", + "clock-show-date-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟะพั‚ะพั‡ะฝัƒ ะดะฐั‚ัƒ ะฟั–ะด ั‡ะฐัะพะผ.", + "clock-show-date-label": "ะŸะพะบะฐะทะฐั‚ะธ ะดะฐั‚ัƒ", + "clock-show-seconds-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ัะตะบัƒะฝะดะธ ะฒ ั‡ะฐัั–.", + "clock-show-seconds-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ัะตะบัƒะฝะดะธ", + "clock-style-analog": "ะะฝะฐะปะพะณะพะฒะธะน", + "clock-style-binary": "ะ”ะฒั–ะนะบะพะฒะธะน", + "clock-style-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ัั‚ะธะปัŒ ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ะณะพะดะธะฝะฝะธะบะฐ.", + "clock-style-digital": "ะฆะธั„ั€ะพะฒะธะน", + "clock-style-label": "ะกั‚ะธะปัŒ ะณะพะดะธะฝะฝะธะบะฐ", + "clock-style-minimal": "ะœั–ะฝั–ะผะฐะปัŒะฝะธะน", + "clock-width-description": "ะจะธั€ะธะฝะฐ ะฒั–ะดะถะตั‚ะฐ ะณะพะดะธะฝะฝะธะบะฐ ะฒ ะฟั–ะบัะตะปัั….", + "cpu-intensive-note": "ะ’ั–ะดะถะตั‚ะธ, ะฟะพะทะฝะฐั‡ะตะฝั– !, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒ ะฑั–ะปัŒัˆะต CPU, ั– ั—ั… ัะปั–ะด ะฒะผะธะบะฐั‚ะธ ะพะฑะตั€ะตะถะฝะพ.", + "edit-mode-button-label": "ะฃะฒั–ะนั‚ะธ ะฒ ั€ะตะถะธะผ ั€ะตะดะฐะณัƒะฒะฐะฝะฝั", + "edit-mode-controls-explanation": "ะšะปะฐั†ะฐะฝะฝั ะปั–ะฒะพัŽ ะบะฝะพะฟะบะพัŽ ะผะธัˆั– ั‚ะฐ ะฟะตั€ะตั‚ัะณัƒะฒะฐะฝะฝั: ะŸะตั€ะตะผั–ัั‚ะธั‚ะธ ะฐะฑะพ ะทะผั–ะฝะธั‚ะธ ั€ะพะทะผั–ั€ ะฒั–ะดะถะตั‚ะฐ.\nะšะปะฐั†ะฐะฝะฝั ะฟั€ะฐะฒะพัŽ ะบะฝะพะฟะบะพัŽ ะผะธัˆั–: ะ’ั–ะดะบั€ะธั‚ะธ ะบะพะฝั‚ะตะบัั‚ะฝะต ะผะตะฝัŽ ะท ะพะฟั†ั–ัะผะธ.", + "edit-mode-description": "ะฃะฒั–ะผะบะฝั–ั‚ัŒ ั€ะตะถะธะผ ั€ะตะดะฐะณัƒะฒะฐะฝะฝั, ั‰ะพะฑ ะฟะตั€ะตะผั–ั‰ัƒะฒะฐั‚ะธ ั‚ะฐ ะทะผั–ะฝัŽะฒะฐั‚ะธ ั€ะพะทั‚ะฐัˆัƒะฒะฐะฝะฝั ะฒั–ะดะถะตั‚ั–ะฒ ั€ะพะฑะพั‡ะพะณะพ ัั‚ะพะปัƒ. ะฃ ะฒะฒั–ะผะบะฝะตะฝะพะผัƒ ัั‚ะฐะฝั– ะฒั–ะดะถะตั‚ะธ ะฒั–ะดะพะฑั€ะฐะถะฐัŽั‚ัŒ ะบะพะฝั‚ัƒั€ ะฟะตั€ะตั‚ัะณัƒะฒะฐะฝะฝั, ั– ั—ั… ะผะพะถะฝะฐ ะฟะตั€ะตะผั–ั‰ัƒะฒะฐั‚ะธ.", + "edit-mode-exit-button": "ะ’ะธะนั‚ะธ ะท ั€ะตะถะธะผัƒ ั€ะตะดะฐะณัƒะฒะฐะฝะฝั", + "edit-mode-grid-snap-label": "ะŸั€ะธะฒ'ัะทะบะฐ ะดะพ ัั–ั‚ะบะธ", + "edit-mode-grid-snap-scale-label": "ะœะฐััˆั‚ะฐะฑ ะฟั€ะธะฒ'ัะทะบะธ ะดะพ ัั–ั‚ะบะธ", + "edit-mode-label": "ะ ะตะถะธะผ ั€ะตะดะฐะณัƒะฒะฐะฝะฝั", + "enabled-description": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฐะฑะพ ะฒะธะผะบะฝัƒั‚ะธ ะฒั–ะดะถะตั‚ะธ ั€ะพะฑะพั‡ะพะณะพ ัั‚ะพะปัƒ ะฟะพะฒะฝั–ัั‚ัŽ.", + "enabled-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฒั–ะดะถะตั‚ะธ ั€ะพะฑะพั‡ะพะณะพ ัั‚ะพะปัƒ", + "general-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฒั–ะดะถะตั‚ะธ, ัะบั– ะฒั–ะดะพะฑั€ะฐะถะฐัŽั‚ัŒัั ะฝะฐ ะฒะฐัˆะพะผัƒ ั€ะพะฑะพั‡ะพะผัƒ ัั‚ะพะปั–.", + "general-title": "ะะฐัั‚ั–ะปัŒะฝั– ะฒั–ะดะถะตั‚ะธ", + "media-player-enabled-description": "ะŸะพะบะฐะทะฐั‚ะธ ะฒั–ะดะถะตั‚ ะผะตะดั–ะฐะฟะปะตั”ั€ะฐ ะฝะฐ ั€ะพะฑะพั‡ะพะผัƒ ัั‚ะพะปั–.", + "media-player-enabled-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฒั–ะดะถะตั‚ ะผะตะดั–ะฐะฟะปะตั”ั€ะฐ", + "media-player-rounded-corners-description": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะทะฐะพะบั€ัƒะณะปะตะฝั– ะบัƒั‚ะธ ะฝะฐ ะบั€ะฐัั… ะฒั–ะดะถะตั‚ะฐ.", + "media-player-show-album-art-description": "ะŸะพะบะฐะทะฐั‚ะธ ะพะฑะบะปะฐะดะธะฝะบัƒ ะฐะปัŒะฑะพะผัƒ ั‚ะฐ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ ะฟั€ะพ ั‚ั€ะตะบะธ (ะฝะฐะทะฒะฐ ั‚ะฐ ะฒะธะบะพะฝะฐะฒะตั†ัŒ).", + "media-player-show-album-art-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะพะฑะบะปะฐะดะธะฝะบัƒ ะฐะปัŒะฑะพะผัƒ ั‚ะฐ ะฝะฐะทะฒัƒ", + "media-player-show-background-description": "ะŸะพะบะฐะทะฐั‚ะธ ั„ะพะฝะพะฒะธะน ะบะพะฝั‚ะตะนะฝะตั€ ะดะปั ะฒั–ะดะถะตั‚ะฐ ะผะตะดั–ะฐะฟะปะตั”ั€ะฐ.", + "media-player-show-buttons-description": "ะŸะพะบะฐะทะฐั‚ะธ ะบะฝะพะฟะบะธ ะบะตั€ัƒะฒะฐะฝะฝั ะผะตะดั–ะฐ (ะฒั–ะดั‚ะฒะพั€ะตะฝะฝั/ะฟะฐัƒะทะฐ, ะฟะพะฟะตั€ะตะดะฝั–ะน, ะฝะฐัั‚ัƒะฟะฝะธะน) ะฝะฐ ะฒั–ะดะถะตั‚ั– ะผะตะดั–ะฐะฟะปะตั”ั€ะฐ.", + "media-player-show-buttons-label": "ะŸะพะบะฐะทะฐั‚ะธ ะบะฝะพะฟะบะธ", + "media-player-show-visualizer-description": "ะŸะพะบะฐะทะฐั‚ะธ ะฝะฐะบะปะฐะดะฐะฝะฝั ะฐัƒะดั–ะพะฒั–ะทัƒะฐะปั–ะทะฐั‚ะพั€ะฐ.", + "media-player-visualizer-type-description": "ะžะฑะตั€ั–ั‚ัŒ ั‚ะธะฟ ะฒั–ะทัƒะฐะปั–ะทะฐั†ั–ั— ะดะปั ั„ะพะฝัƒ ะผะตะดั–ะฐะฟะปะตั”ั€ะฐ ะฝะฐ ั€ะพะฑะพั‡ะพะผัƒ ัั‚ะพะปั–.", + "overview-enabled-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฒั–ะดะถะตั‚ะธ ั€ะพะฑะพั‡ะพะณะพ ัั‚ะพะปัƒ, ะฟะพะบะธ ะพะณะปัะด ะบะพะผะฟะพะทะธั‚ะพั€ะฐ ะฒั–ะดะบั€ะธั‚ะธะน.", + "overview-enabled-label": "ะŸะพะบะฐะทะฐั‚ะธ ะฒ ะพะณะปัะดั–", + "system-stat-layout-bottom": "ะะธะท", + "system-stat-layout-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ัะบ ะฒั–ะดะพะฑั€ะฐะถะฐั”ั‚ัŒัั ะปะตะณะตะฝะดะฐ ะฒั–ะดะฝะพัะฝะพ ะณั€ะฐั„ั–ะบะฐ.", + "system-stat-layout-label": "ะœะฐะบะตั‚", + "system-stat-layout-side": "ะกั‚ะพั€ะพะฝะฐ", + "system-stat-rounded-corners-description": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะทะฐะพะบั€ัƒะณะปะตะฝั– ะบัƒั‚ะธ ะดะปั ั„ะพะฝัƒ ะฒั–ะดะถะตั‚ะฐ.", + "system-stat-rounded-corners-label": "ะ—ะฐะบั€ัƒะณะปะตะฝั– ะšัƒั‚ะธ", + "system-stat-show-background-description": "ะŸะพะบะฐะทะฐั‚ะธ ั„ะพะฝะพะฒะธะน ะบะพะฝั‚ะตะนะฝะตั€ ะดะปั ะฒั–ะดะถะตั‚ะฐ ัะธัั‚ะตะผะฝะพั— ัั‚ะฐั‚ะธัั‚ะธะบะธ.", + "system-stat-show-background-label": "ะŸะพะบะฐะทะฐั‚ะธ ั„ะพะฝ", + "system-stat-stat-type-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ัะบัƒ ัะธัั‚ะตะผะฝัƒ ัั‚ะฐั‚ะธัั‚ะธะบัƒ ะฒั–ะดะพะฑั€ะฐะถะฐั‚ะธ.", + "system-stat-stat-type-label": "ะขะธะฟ ะกั‚ะฐั‚ะธัั‚ะธะบะธ", + "title": "ะะฐัั‚ั–ะปัŒะฝั– ะฒั–ะดะถะตั‚ะธ", + "weather-enabled-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟะพะณะพะดะฝะธะน ะฒั–ะดะถะตั‚ ะฝะฐ ั€ะพะฑะพั‡ะพะผัƒ ัั‚ะพะปั–.", + "weather-enabled-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฒั–ะดะถะตั‚ ะฟะพะณะพะดะธ", + "weather-show-background-description": "ะŸะพะบะฐะทะฐั‚ะธ ั„ะพะฝะพะฒะธะน ะบะพะฝั‚ะตะนะฝะตั€ ะดะปั ะฟะพะณะพะดะฝะพะณะพ ะฒั–ะดะถะตั‚ะฐ." + }, + "display": { + "monitors-backlight-device-auto-option": "ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ", + "monitors-backlight-device-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะฟั€ะธัั‚ั€ั–ะน ะฟั–ะดัะฒั–ั‡ัƒะฒะฐะฝะฝั ะดะปั ั†ัŒะพะณะพ ะฒะธั…ะพะดัƒ.", + "monitors-backlight-device-label": "ะŸั€ะธัั‚ั€ั–ะน ะŸั–ะดัะฒั–ั‡ัƒะฒะฐะฝะฝั", + "monitors-brightness-step-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะบั€ะพะบ ะทะผั–ะฝะธ ััะบั€ะฐะฒะพัั‚ั– (ะบะพะปะตัะพ ะผะธัˆั– ั‚ะฐ ะณะฐั€ัั‡ั– ะบะปะฐะฒั–ัˆั–).", + "monitors-brightness-step-label": "ะšั€ะพะบ ะทะผั–ะฝะธ ััะบั€ะฐะฒะพัั‚ั–", + "monitors-brightness-unavailable-ddc-disabled": "ะ ะตะณัƒะปัŽะฒะฐะฝะฝั ััะบั€ะฐะฒะพัั‚ั– ะฝะตะดะพัั‚ัƒะฟะฝะต. ะฃะฒั–ะผะบะฝั–ั‚ัŒ \"ะŸั–ะดั‚ั€ะธะผะบัƒ ะทะพะฒะฝั–ัˆะฝัŒะพั— ััะบั€ะฐะฒะพัั‚ั–\", ั‰ะพะฑ ะบะตั€ัƒะฒะฐั‚ะธ ััะบั€ะฐะฒั–ัั‚ัŽ ั†ัŒะพะณะพ ะดะธัะฟะปะตั.", + "monitors-brightness-unavailable-generic": "ะ ะตะณัƒะปัŽะฒะฐะฝะฝั ััะบั€ะฐะฒะพัั‚ั– ะดะปั ั†ัŒะพะณะพ ะดะธัะฟะปะตั ะฝะตะดะพัั‚ัƒะฟะฝะต.", + "monitors-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ััะบั€ะฐะฒั–ัั‚ัŒ ะดะปั ะบะพะถะฝะพะณะพ ะดะธัะฟะปะตั.", + "monitors-enforce-minimum-description": "ะ’ะธั€ั–ัˆัƒั” ะฟั€ะพะฑะปะตะผัƒ ะฟะพะฒะฝะพะณะพ ะฒะธะผะบะฝะตะฝะฝั ะฟั–ะดัะฒั–ั‚ะบะธ ะฝะฐ ะดะตัะบะธั… ะดะธัะฟะปะตัั… ะฟั€ะธ ััะบั€ะฐะฒะพัั‚ั– 0%.", + "monitors-enforce-minimum-label": "ะŸั€ะธะผัƒัะพะฒะฐ ะผั–ะฝั–ะผะฐะปัŒะฝะฐ ััะบั€ะฐะฒั–ัั‚ัŒ (1%)", + "monitors-external-brightness-description": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฟั–ะดั‚ั€ะธะผะบัƒ DDCUtil ะดะปั ะบะตั€ัƒะฒะฐะฝะฝั ััะบั€ะฐะฒั–ัั‚ัŽ ะฝะฐ ะทะพะฒะฝั–ัˆะฝั–ั… ะดะธัะฟะปะตัั… ั‡ะตั€ะตะท ะฟั€ะพั‚ะพะบะพะป DDC/CI.", + "monitors-external-brightness-label": "ะŸั–ะดั‚ั€ะธะผะบะฐ ะทะพะฒะฝั–ัˆะฝัŒะพั— ััะบั€ะฐะฒะพัั‚ั–", + "monitors-global-brightness-description": "ะะฐะปะฐัˆั‚ัƒะฒะฐั‚ะธ ััะบั€ะฐะฒั–ัั‚ัŒ ัƒัั–ั… ะผะพะฝั–ั‚ะพั€ั–ะฒ ะพะดะฝะพั‡ะฐัะฝะพ.", + "monitors-global-brightness-label": "ะฃัั– ะผะพะฝั–ั‚ะพั€ะธ", + "monitors-title": "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ะดะปั ะบะพะถะฝะพะณะพ ะผะพะฝั–ั‚ะพั€ะฐ", + "night-light-auto-schedule-description": "ะะฐ ะพัะฝะพะฒั– ั‡ะฐััƒ ะทะฐั…ะพะดัƒ ั‚ะฐ ัั…ะพะดัƒ ัะพะฝั†ั ะฒ {location} โ€” ั€ะตะบะพะผะตะฝะดะพะฒะฐะฝะพ.", + "night-light-auto-schedule-label": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะต ะฟะปะฐะฝัƒะฒะฐะฝะฝั", + "night-light-desc": "ะ—ะผะตะฝัˆั–ั‚ัŒ ะฒะธะฟั€ะพะผั–ะฝัŽะฒะฐะฝะฝั ัะธะฝัŒะพะณะพ ัะฒั–ั‚ะปะฐ ะดะปั ะบั€ะฐั‰ะพะณะพ ัะฝัƒ ั‚ะฐ ะทะผะตะฝัˆะตะฝะฝั ะฒั‚ะพะผะธ ะพั‡ะตะน.", + "night-light-enable-description": "ะ—ะฐัั‚ะพััƒะฒะฐั‚ะธ ั‚ะตะฟะปะธะน ะบะพะปั–ั€ะฝะธะน ั„ั–ะปัŒั‚ั€ ะดะปั ะทะผะตะฝัˆะตะฝะฝั ะฒะธะฟั€ะพะผั–ะฝัŽะฒะฐะฝะฝั ัะธะฝัŒะพะณะพ ัะฒั–ั‚ะปะฐ.", + "night-light-enable-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฝั–ั‡ะฝะต ัะฒั–ั‚ะปะพ", + "night-light-force-activation-description": "ะ†ะณะฝะพั€ัƒั” ั€ะพะทะบะปะฐะด ั– ะฝะตะณะฐะนะฝะพ ะทะฐัั‚ะพัะพะฒัƒั” ะฝั–ั‡ะฝะธะน ั„ั–ะปัŒั‚ั€.", + "night-light-force-activation-label": "ะŸั€ะธะผัƒัะพะฒะฐ ะฐะบั‚ะธะฒะฐั†ั–ั", + "night-light-manual-schedule-description": "ะ’ัั‚ะฐะฝะพะฒั–ั‚ัŒ ะฒะปะฐัะฝะธะน ั‡ะฐั ัั…ะพะดัƒ ั‚ะฐ ะทะฐั…ะพะดัƒ ัะพะฝั†ั.", + "night-light-manual-schedule-label": "ะ ัƒั‡ะฝะต ะฟะปะฐะฝัƒะฒะฐะฝะฝั", + "night-light-manual-schedule-select-start": "ะ’ะธะฑะตั€ั–ั‚ัŒ ั‡ะฐั ะฟะพั‡ะฐั‚ะบัƒ", + "night-light-manual-schedule-select-stop": "ะ’ะธะฑะตั€ั–ั‚ัŒ ั‡ะฐั ะทัƒะฟะธะฝะบะธ", + "night-light-manual-schedule-sunrise": "ะงะฐั ัั…ะพะดัƒ ัะพะฝั†ั", + "night-light-manual-schedule-sunset": "ะงะฐั ะทะฐั…ะพะดัƒ ัะพะฝั†ั", + "night-light-temperature-day": "ะ”ะตะฝัŒ", + "night-light-temperature-day-description": "ะšะตั€ัƒั” ะบะพะปั–ั€ะฝะพัŽ ั‚ะตะผะฟะตั€ะฐั‚ัƒั€ะพัŽ ะฒะดะตะฝัŒ.", + "night-light-temperature-description": "ะ’ัั‚ะฐะฝะพะฒั–ั‚ัŒ ั‚ะตะฟะปะพั‚ัƒ ะบะพะปัŒะพั€ัƒ ะดะปั ะฝั–ั‡ะฝะพะณะพ ั‚ะฐ ะดะตะฝะฝะพะณะพ ั‡ะฐััƒ.", + "night-light-temperature-label": "ะšะพะปั–ั€ะฝะฐ ั‚ะตะผะฟะตั€ะฐั‚ัƒั€ะฐ", + "night-light-temperature-night": "ะั–ั‡", + "night-light-temperature-night-description": "ะšะตั€ัƒั” ะบะพะปั–ั€ะฝะพัŽ ั‚ะตะผะฟะตั€ะฐั‚ัƒั€ะพัŽ ะฒะฝะพั‡ั–.", + "title": "ะ”ะธัะฟะปะตะน" + }, + "dock": { + "appearance-background-opacity-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฝะตะฟั€ะพะทะพั€ั–ัั‚ัŒ ั„ะพะฝัƒ ะดะพะบะฐ.", + "appearance-border-radius-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ั€ะฐะดั–ัƒั ะทะฐะพะบั€ัƒะณะปะตะฝะฝั ะบั€ะฐั—ะฒ ะดะพะบ-ะฟะฐะฝะตะปั–.", + "appearance-border-radius-label": "ะ ะฐะดั–ัƒั ะทะฐะพะบั€ัƒะณะปะตะฝะฝั", + "appearance-colorize-icons-description": "ะ—ะฐัั‚ะพััƒะฒะฐั‚ะธ ะบะพะปัŒะพั€ะธ ั‚ะตะผะธ ะดะพ ะทะฝะฐั‡ะบั–ะฒ ะทะฐัั‚ะพััƒะฝะบั–ะฒ ัƒ ะดะพั†ั– (ั‚ั–ะปัŒะบะธ ะฝะตะฐะบั‚ะธะฒะฝั– ะทะฐัั‚ะพััƒะฝะบะธ).", + "appearance-colorize-icons-label": "ะ ะพะทั„ะฐั€ะฑัƒะฒะฐั‚ะธ ะทะฝะฐั‡ะบะธ", + "appearance-dead-opacity-description": "ะ’ั–ะดั€ะตะณัƒะปัŽะนั‚ะต ะฟั€ะพะทะพั€ั–ัั‚ัŒ ั–ะบะพะฝะพะบ ะฟั€ะพะณั€ะฐะผ, ัะบั– ะฝะต ะทะฐะฟัƒั‰ะตะฝั–.", + "appearance-dead-opacity-label": "ะœะตั€ั‚ะฒะฐ ะฝะตะฟั€ะพะทะพั€ั–ัั‚ัŒ", + "appearance-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฟะพะฒะตะดั–ะฝะบัƒ ั‚ะฐ ะทะพะฒะฝั–ัˆะฝั–ะน ะฒะธะณะปัะด ะดะพะบะฐ.", + "appearance-display-auto-hide": "ะะฒั‚ะพะฟั€ะธั…ะพะฒัƒะฒะฐะฝะฝั", + "appearance-display-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ัะบ ะฟะพะฒะพะดะธั‚ัŒัั ะดะพะบ.", + "appearance-display-exclusive": "ะ’ะธะฝัั‚ะบะพะฒะธะน", + "appearance-dock-indicator-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฝะตะฒะตะปะธะบะธะน ั–ะฝะดะธะบะฐั‚ะพั€, ะบะพะปะธ ะดะพะบ ะฟั€ะธั…ะพะฒะฐะฝะพ.", + "appearance-dock-indicator-label": "ะ†ะฝะดะธะบะฐั‚ะพั€ ะดะพะบะฐ", + "appearance-floating-distance-description": "ะ’ัั‚ะฐะฝะพะฒั–ั‚ัŒ ะฒั–ะดัั‚ะฐะฝัŒ ะผั–ะถ ะดะพะบะพะผ ั– ะบั€ะฐั”ะผ ะตะบั€ะฐะฝะฐ.", + "appearance-floating-distance-label": "ะ’ั–ะดัั‚ะฐะฝัŒ ะฟะปะฐะฒะฐะฝะฝั ะดะพะบะฐ", + "appearance-group-apps-description": "ะ“ั€ัƒะฟัƒะฒะฐั‚ะธ ะบั–ะปัŒะบะฐ ะฒั–ะบะพะฝ ะท ะพะดะฝั–ั”ั— ะฟั€ะพะณั€ะฐะผะธ ะฒ ะพะดะธะฝ ะทะฐะฟะธั Dock.", + "appearance-group-apps-label": "ะ“ั€ัƒะฟัƒะฒะฐั‚ะธ ะพะดะฝะฐะบะพะฒั– ะฟั€ะพะณั€ะฐะผะธ", + "appearance-group-click-action-cycle": "ะŸะตั€ะตะผะธะบะฐั‚ะธ ะฒั–ะบะฝะฐ", + "appearance-group-click-action-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ั‰ะพ ั€ะพะฑะธั‚ัŒ ะปั–ะฒะธะน ะบะปั–ะบ ะดะปั ะทะณั€ัƒะฟะพะฒะฐะฝะธั… ะฟั€ะพะณั€ะฐะผ.", + "appearance-group-click-action-label": "ะ”ั–ั ะทะฐ ะบะปั–ะบะพะผ ะทะณั€ัƒะฟะพะฒะฐะฝะพั— ะฟั€ะพะณั€ะฐะผะธ", + "appearance-group-click-action-list": "ะ’ั–ะดะบั€ะธั‚ะธ ัะฟะธัะพะบ ะฒั–ะบะพะฝ", + "appearance-group-context-menu-mode-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ัะบ ะบะพะฝั‚ะตะบัั‚ะฝะต ะผะตะฝัŽ ะฟะพะฒะพะดะธั‚ัŒัั ะดะปั ะทะณั€ัƒะฟะพะฒะฐะฝะธั… ะฟั€ะพะณั€ะฐะผ.", + "appearance-group-context-menu-mode-extended": "ะ ะพะทัˆะธั€ะตะฝะธะน", + "appearance-group-context-menu-mode-label": "ะ ะตะถะธะผ ะทะณั€ัƒะฟะพะฒะฐะฝะพะณะพ ะผะตะฝัŽ ะฟั€ะพะณั€ะฐะผ", + "appearance-group-context-menu-mode-list": "ะกะฟะธัะพะบ ะฒั–ะบะพะฝ", + "appearance-group-indicator-style-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ัะบ ะทะณั€ัƒะฟะพะฒะฐะฝั– ั–ะฝะดะธะบะฐั‚ะพั€ะธ ะทะฐะฟัƒั‰ะตะฝะธั… ะฟั€ะพะณั€ะฐะผ ะฒั–ะดะพะฑั€ะฐะถะฐัŽั‚ัŒ ัั‚ะฐะฝ ะฐะบั‚ะธะฒะฝะพะณะพ ะฒั–ะบะฝะฐ.", + "appearance-group-indicator-style-dots": "ะšั€ะฐะฟะบะธ", + "appearance-group-indicator-style-label": "ะกั‚ะธะปัŒ ะทะณั€ัƒะฟะพะฒะฐะฝะพะณะพ ั–ะฝะดะธะบะฐั‚ะพั€ะฐ", + "appearance-group-indicator-style-number": "ะงะธัะปะพ", + "appearance-hide-show-speed-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ัˆะฒะธะดะบั–ัั‚ัŒ ะฐะฝั–ะผะฐั†ั–ั— ะฟั€ะธั…ะพะฒัƒะฒะฐะฝะฝั/ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ะดะพะบะฐ.", + "appearance-hide-show-speed-label": "ะจะฒะธะดะบั–ัั‚ัŒ ะฟั€ะธั…ะพะฒัƒะฒะฐะฝะฝั/ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั", + "appearance-icon-size-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะทะฐะณะฐะปัŒะฝะธะน ั€ะพะทะผั–ั€ ะดะพะบะฐ.", + "appearance-icon-size-label": "ะ ะพะทะผั–ั€ ะดะพะบะฐ", + "appearance-inactive-indicators-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ั–ะฝะดะธะบะฐั‚ะพั€ะธ-ะฟั–ะณัƒะปะบะธ ะดะปั ะฒัั–ั… ะฟั€ะพะณั€ะฐะผ, ะฐ ะฝะต ะปะธัˆะต ะดะปั ะฟะพั‚ะพั‡ะฝะพั— ะฐะบั‚ะธะฒะฝะพั—.", + "appearance-inactive-indicators-label": "ะ†ะฝะดะธะบะฐั‚ะพั€ะธ ะฑั–ะณัƒ", + "appearance-indicator-color-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะบะพะปั–ั€ ั–ะฝะดะธะบะฐั‚ะพั€ะฐ ะฟั€ะธั…ะพะฒะฐะฝะพะณะพ ะดะพะบัƒ.", + "appearance-indicator-color-label": "ะšะพะปั–ั€ ั–ะฝะดะธะบะฐั‚ะพั€ะฐ", + "appearance-indicator-opacity-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฝะตะฟั€ะพะทะพั€ั–ัั‚ัŒ ั–ะฝะดะธะบะฐั‚ะพั€ะฐ ะฟั€ะธั…ะพะฒะฐะฝะพะณะพ ะดะพะบัƒ.", + "appearance-indicator-opacity-label": "ะะตะฟั€ะพะทะพั€ั–ัั‚ัŒ ั–ะฝะดะธะบะฐั‚ะพั€ะฐ", + "appearance-indicator-thickness-description": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ั‚ะพะฒัั‚ั–ัˆะธะน ั–ะฝะดะธะบะฐั‚ะพั€ ะฟั€ะธั…ะพะฒะฐะฝะพะณะพ ะดะพะบัƒ (6 ะฟั–ะบัะตะปั–ะฒ ะทะฐะผั–ัั‚ัŒ 3 ะฟั–ะบัะตะปั–ะฒ).", + "appearance-indicator-thickness-label": "ะขะพะฒัั‚ั–ัˆะธะน ั–ะฝะดะธะบะฐั‚ะพั€", + "appearance-launcher-icon-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะฟั–ะบั‚ะพะณั€ะฐะผัƒ, ั‰ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ะดะปั Dock Launcher.", + "appearance-launcher-icon-label": "ะŸั–ะบั‚ะพะณั€ะฐะผะฐ ะปะฐัƒะฝั‡ะตั€ะฐ", + "appearance-launcher-position-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ะดะต ะทะฝะฐั‡ะพะบ ะทะฐะฟัƒัะบัƒ ะฟั€ะพะณั€ะฐะผ ะท'ัะฒะปัั”ั‚ัŒัั ะฒ ะดะพั†ั–.", + "appearance-launcher-position-end": "ะšั–ะฝะตั†ัŒ", + "appearance-launcher-position-label": "ะŸะพะทะธั†ั–ั ะทะฐะฟัƒัะบะฐั‡ะฐ", + "appearance-launcher-position-start": "ะกั‚ะฐั€ั‚", + "appearance-launcher-use-distro-logo-description": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ะปะพะณะพั‚ะธะฟ ะฒะฐัˆะพะณะพ ะดะธัั‚ั€ะธะฑัƒั‚ะธะฒะฐ ัะบ ะทะฝะฐั‡ะพะบ Dock Launcher.", + "appearance-launcher-use-distro-logo-label": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะปะพะณะพั‚ะธะฟ ะดะธัั‚ั€ะธะฑัƒั‚ะธะฒะฐ ะทะฐะผั–ัั‚ัŒ ั–ะบะพะฝะบะธ", + "appearance-pinned-static-description": "ะ—ะฐะฒะถะดะธ ะทะฐะบั€ั–ะฟะปัŽะนั‚ะต ะทะฝะฐั‡ะบะธ ะฟั€ะพะณั€ะฐะผ ะปั–ะฒะพั€ัƒั‡ ัƒ ัั‚ะฐั‚ะธั‡ะฝะพะผัƒ ะฟะพั€ัะดะบัƒ.", + "appearance-pinned-static-label": "ะกั‚ะฐั‚ะธั‡ะฝะพ ะทะฐะบั€ั–ะฟะปะตะฝั– ะฟั€ะพะณั€ะฐะผะธ", + "appearance-position-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ะดะต ะดะพะบ ะฒั–ะดะพะฑั€ะฐะถะฐั‚ะธะผะตั‚ัŒัั ะฝะฐ ะตะบั€ะฐะฝั–.", + "appearance-position-label": "ะŸะพะทะธั†ั–ั", + "appearance-show-launcher-icon-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟั–ะบั‚ะพะณั€ะฐะผัƒ ะทะฐะฟัƒัะบัƒ ะฟั€ะพะณั€ะฐะผ ัƒ ะดะพั†ั–.", + "appearance-show-launcher-icon-label": "ะŸะพะบะฐะทะฐั‚ะธ ะทะฐะฟัƒัะบ ะฟั€ะพะณั€ะฐะผ", + "appearance-sit-on-frame-description": "ะ’ะธั€ั–ะฒะฝัะนั‚ะต ะดะพะบ ะฒัะตั€ะตะดะธะฝั– ะผะตะถั– ั€ะฐะผะบะธ, ะฐ ะฝะต ะฟะพะฒะตั€ั… ะฝะตั—.", + "appearance-sit-on-frame-label": "ะ”ะพะบ ั€ะพะทะผั–ั‰ัƒั”ั‚ัŒัั ะฝะฐ ั€ะฐะผั†ั–", + "appearance-type-attached": "ะ—ะฐะบั€ั–ะฟะปะตะฝะฐ", + "appearance-type-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะผั–ะถ ะฟะปะฐะฒะฐัŽั‡ะพัŽ ะฟั–ะณัƒะปะบะพัŽ ะฐะฑะพ ัั‚ะฐั‚ะธั‡ะฝะพัŽ ะฟะฐะฝะตะปะปัŽ, ะฟั€ะธะบั€ั–ะฟะปะตะฝะพัŽ ะดะพ ะบั€ะฐัŽ.", + "appearance-type-floating": "ะŸะปะฐะฒะฐัŽั‡ะธะน", + "appearance-type-label": "ะกั‚ะธะปัŒ ะดะพะบะฐ", + "enabled-description": "ะŸะพะบะฐะทะฐั‚ะธ ะฐะฑะพ ัั…ะพะฒะฐั‚ะธ ะดะพะบ ะฟะพะฒะฝั–ัั‚ัŽ.", + "enabled-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะดะพะบ", + "monitors-desc": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะดะพะบ ะฝะฐ ะฟะตะฒะฝะธั… ะผะพะฝั–ั‚ะพั€ะฐั…. ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฝะฐ ะฒัั–ั…, ัะบั‰ะพ ะฝะต ะฒะธะฑั€ะฐะฝะพ.", + "monitors-only-same-monitor-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะปะธัˆะต ะทะฐัั‚ะพััƒะฝะบะธ ะท ะผะพะฝั–ั‚ะพั€ะฐ, ะดะต ั€ะพะทั‚ะฐัˆะพะฒะฐะฝะพ Dock.", + "monitors-only-same-monitor-label": "ะ›ะธัˆะต ะทะฐัั‚ะพััƒะฝะบะธ ะท ั‚ะพะณะพ ะถ ะผะพะฝั–ั‚ะพั€ะฐ", + "monitors-title": "ะ’ั–ะดะพะฑั€ะฐะถะตะฝะฝั ะฝะฐ ะผะพะฝั–ั‚ะพั€ะฐั…", + "title": "ะ”ะพะบ" + }, + "general": { + "copy-settings": "ะšะพะฟั–ัŽะฒะฐั‚ะธ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั", + "fonts-default-description": "ะžัะฝะพะฒะฝะธะน ัˆั€ะธั„ั‚, ั‰ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ะฒ ั–ะฝั‚ะตั€ั„ะตะนัั–.", + "fonts-default-label": "ะกั‚ะฐะฝะดะฐั€ั‚ะฝะธะน ัˆั€ะธั„ั‚", + "fonts-default-placeholder": "ะ’ะธะฑะตั€ั–ั‚ัŒ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะน ัˆั€ะธั„ั‚...", + "fonts-default-scale-description": "ะ—ะฑั–ะปัŒัˆะธั‚ะธ ะฐะฑะพ ะทะผะตะฝัˆะธั‚ะธ ั€ะพะทะผั–ั€ ะทะฒะธั‡ะฐะนะฝะพะณะพ ั‚ะตะบัั‚ัƒ.", + "fonts-default-scale-label": "ะœะฐััˆั‚ะฐะฑ ัั‚ะฐะฝะดะฐั€ั‚ะฝะพะณะพ ัˆั€ะธั„ั‚ัƒ", + "fonts-default-search-placeholder": "ะŸะพัˆัƒะบ ัˆั€ะธั„ั‚ัƒ...", + "fonts-desc": "ะ’ะธะฑะตั€ั–ั‚ัŒ ัˆั€ะธั„ั‚ะธ, ั‰ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒัั ะฒ ั–ะฝั‚ะตั€ั„ะตะนัั–.", + "fonts-monospace-description": "ะœะพะฝะพัˆะธั€ะธะฝะฝะธะน ัˆั€ะธั„ั‚ ะดะปั ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ั‡ะธัะตะป ั‚ะฐ ัั‚ะฐั‚ะธัั‚ะธะบะธ.", + "fonts-monospace-label": "ะœะพะฝะพัˆะธั€ะธะฝะฝะธะน ัˆั€ะธั„ั‚", + "fonts-monospace-placeholder": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะผะพะฝะพัˆะธั€ะธะฝะฝะธะน ัˆั€ะธั„ั‚...", + "fonts-monospace-scale-description": "ะ—ะฑั–ะปัŒัˆะธั‚ะธ ะฐะฑะพ ะทะผะตะฝัˆะธั‚ะธ ั€ะพะทะผั–ั€ ะผะพะฝะพัˆะธั€ะธะฝะฝะพะณะพ ั‚ะตะบัั‚ัƒ.", + "fonts-monospace-scale-label": "ะ ะพะทะผั–ั€ ะผะพะฝะพัˆะธั€ะธะฝะฝะพะณะพ ัˆั€ะธั„ั‚ัƒ", + "fonts-monospace-search-placeholder": "ะŸะพัˆัƒะบ ะผะพะฝะพัˆะธั€ะธะฝะฝะพะณะพ ัˆั€ะธั„ั‚ัƒ...", + "fonts-title": "ะจั€ะธั„ั‚ะธ", + "keybinds-conflict-description": "ะšะพะผะฑั–ะฝะฐั†ั–ั ะบะปะฐะฒั–ัˆ ะฒะถะต ะฟั€ะธะทะฝะฐั‡ะตะฝะฐ ะดะปั {action}.", + "keybinds-conflict-title": "ะšะพะฝั„ะปั–ะบั‚ ะณะฐั€ัั‡ะธั… ะบะปะฐะฒั–ัˆ", + "keybinds-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะณะปะพะฑะฐะปัŒะฝั– ะบะปะฐะฒั–ัˆั– ะฝะฐะฒั–ะณะฐั†ั–ั— ะดะปั ะฟะฐะฝะตะปะตะน ั‚ะฐ ะทะฐะฟัƒัะบะฐั‡ะฐ.", + "keybinds-down": "ะŸะตั€ะตะผั–ัั‚ะธั‚ะธ ะฒะฝะธะท", + "keybinds-enter": "ะŸั–ะดั‚ะฒะตั€ะดะธั‚ะธ / ะ”ั–ั", + "keybinds-escape": "ะ—ะฐะบั€ะธั‚ะธ / ะะฐะทะฐะด", + "keybinds-left": "ะŸะตั€ะตะผั–ัั‚ะธั‚ะธ ะฒะปั–ะฒะพ", + "keybinds-modifier-description": "ะšะพะผะฑั–ะฝะฐั†ั–ั— ะบะปะฐะฒั–ัˆ ะฟะพะฒะธะฝะฝั– ะฒะบะปัŽั‡ะฐั‚ะธ ะบะปะฐะฒั–ัˆัƒ-ะผะพะดะธั„ั–ะบะฐั‚ะพั€ (Ctrl ะฐะฑะพ Alt).", + "keybinds-modifier-title": "ะŸะพั‚ั€ั–ะฑะตะฝ ะผะพะดะธั„ั–ะบะฐั‚ะพั€", + "keybinds-remove": "ะ’ะธะดะฐะปะธั‚ะธ", + "keybinds-right": "ะŸะตั€ะตะผั–ัั‚ะธั‚ะธ ะฒะฟั€ะฐะฒะพ", + "keybinds-title": "ะŸั€ะธะฒ'ัะทะบะธ ะบะปะฐะฒั–ัˆ ะฝะฐะฒั–ะณะฐั†ั–ั—", + "keybinds-up": "ะŸะตั€ะตะผั–ัั‚ะธั‚ะธ ะฒะณะพั€ัƒ", + "language-desc": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะฑะฐะถะฐะฝัƒ ะผะพะฒัƒ ะทะฐัั‚ะพััƒะฝะบัƒ.", + "language-select-auto-detect": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ", + "language-select-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะผะพะฒัƒ ั–ะฝั‚ะตั€ั„ะตะนััƒ ะทะฐัั‚ะพััƒะฝะบัƒ.", + "language-select-label": "ะœะพะฒะฐ ะทะฐัั‚ะพััƒะฝะบัƒ", + "launch-setup-wizard": "ะœะฐะนัั‚ะตั€ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั", + "profile-desc": "ะ ะตะดะฐะณัƒะนั‚ะต ะดะฐะฝั– ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ั‚ะฐ ะฐะฒะฐั‚ะฐั€.", + "profile-picture-description": "ะ’ะฐัˆะต ั„ะพั‚ะพ ะฟั€ะพั„ั–ะปัŽ, ั‰ะพ ะฒั–ะดะพะฑั€ะฐะถะฐั”ั‚ัŒัั ะฒ ั–ะฝั‚ะตั€ั„ะตะนัั–.", + "profile-picture-label": "ะคะพั‚ะพ ะฟั€ะพั„ั–ะปัŽ", + "profile-select-avatar": "ะ’ะธะฑั€ะฐั‚ะธ ะทะพะฑั€ะฐะถะตะฝะฝั ะฐะฒะฐั‚ะฐั€ะฐ", + "profile-title": "ะŸั€ะพั„ั–ะปัŒ", + "profile-tooltip": "ะคะพั‚ะพ ะฟั€ะพั„ั–ะปัŽ", + "reverse-scrolling-description": "ะžะฑะตั€ะฝัƒั‚ะธ ั–ะฝั‚ะตั€ะฟั€ะตั‚ะพะฒะฐะฝะธะน ะฝะฐะฟั€ัะผะพะบ ะฟั€ะพะบั€ัƒั‡ัƒะฒะฐะฝะฝั.", + "reverse-scrolling-label": "ะ—ะฒะพั€ะพั‚ะฝะต ะฟั€ะพะบั€ัƒั‡ัƒะฒะฐะฝะฝั", + "screen-corners-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะทะฐะพะบั€ัƒะณะปะตะฝะฝั ะบัƒั‚ั–ะฒ ะตะบั€ะฐะฝะฐ ั‚ะฐ ะฒั–ะทัƒะฐะปัŒะฝั– ะตั„ะตะบั‚ะธ.", + "screen-corners-radius-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะทะฐะพะบั€ัƒะณะปะตะฝะฝั ะบัƒั‚ั–ะฒ ะตะบั€ะฐะฝะฐ.", + "screen-corners-radius-label": "ะ ะฐะดั–ัƒั ะบัƒั‚ั–ะฒ ะตะบั€ะฐะฝะฐ", + "screen-corners-radius-reset": "ะกะบะธะฝัƒั‚ะธ ั€ะฐะดั–ัƒั ะบัƒั‚ั–ะฒ ะตะบั€ะฐะฝะฐ", + "screen-corners-show-corners-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะทะฐะพะบั€ัƒะณะปะตะฝั– ะบัƒั‚ะธ ะฝะฐ ะบั€ะฐัั… ะตะบั€ะฐะฝะฐ.", + "screen-corners-show-corners-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะบัƒั‚ะธ ะตะบั€ะฐะฝะฐ", + "screen-corners-solid-black-description": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ััƒั†ั–ะปัŒะฝะธะน ั‡ะพั€ะฝะธะน ะบะพะปั–ั€ ะทะฐะผั–ัั‚ัŒ ะบะพะปัŒะพั€ัƒ ั„ะพะฝัƒ ะฟะฐะฝะตะปั–.", + "screen-corners-solid-black-label": "ะกัƒั†ั–ะปัŒะฝั– ั‡ะพั€ะฝั– ะบัƒั‚ะธ", + "screen-corners-title": "ะšัƒั‚ะธ ะตะบั€ะฐะฝะฐ", + "settings-copied": "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ัะบะพะฟั–ะนะพะฒะฐะฝะพ ะดะพ ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ", + "smooth-scrolling-description": "ะะฝั–ะผัƒะฒะฐั‚ะธ ะฟั€ะพะบั€ัƒั‡ัƒะฒะฐะฝะฝั ัะฟะธัะบั–ะฒ ะดะปั ะฟะปะฐะฒะฝั–ัˆะพะณะพ ะดะพัะฒั–ะดัƒ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะบะพะปะตัะฐ.", + "smooth-scrolling-label": "ะŸะปะฐะฒะฝะต ะฟั€ะพะบั€ัƒั‡ัƒะฒะฐะฝะฝั", + "tab-basics": "ะžัะฝะพะฒะธ", + "tab-keybinds": "ะ“ะฐั€ัั‡ั– ะบะปะฐะฒั–ัˆั–" + }, + "hooks": { + "color-generation-description": "ะšะพะผะฐะฝะดะฐ, ัะบะฐ ะฑัƒะดะต ะฒะธะบะพะฝะฐะฝะฐ ะฟั–ัะปั ะทะฐะฒะตั€ัˆะตะฝะฝั ะณะตะฝะตั€ะฐั†ั–ั— ะบะพะปัŒะพั€ัƒ (ะฝะฐะฟั€ะธะบะปะฐะด, ะฟั–ัะปั ะทะฐัั‚ะพััƒะฒะฐะฝะฝั ั‚ะตะผะธ ะฝะฐ ะพัะฝะพะฒั– ัˆะฟะฐะปะตั€).", + "color-generation-label": "ะšะพะปัŒะพั€ะธ ะทะณะตะฝะตั€ะพะฒะฐะฝะพ", + "color-generation-placeholder": "ะฝะฐะฟั€. notify-send \"ะšะพะปัŒะพั€ะธ\" \"ะ—ะณะตะฝะตั€ะพะฒะฐะฝะพ\"", + "info-command-info-description": "โ€ข ะšะพะผะฐะฝะดะธ ะฒะธะบะพะฝัƒัŽั‚ัŒัั ั‡ะตั€ะตะท ะพะฑะพะปะพะฝะบัƒ (sh -lc)
โ€ข ะšะพะผะฐะฝะดะธ ะทะฐะฟัƒัะบะฐัŽั‚ัŒัั ัƒ ั„ะพะฝั– (ะฒั–ะดะพะบั€ะตะผะปะตะฝะพ)
โ€ข ะšะฝะพะฟะบะธ ั‚ะตัั‚ัƒะฒะฐะฝะฝั ะฒะธะบะพะฝัƒัŽั‚ัŒ ะบะพะผะฐะฝะดะธ ะท ะฟะพั‚ะพั‡ะฝะธะผะธ ะทะฝะฐั‡ะตะฝะฝัะผะธ", + "info-parameters-description": "โ€ข Wallpaper hook: $1 = ัˆะปัั… ะดะพ ัˆะฟะฐะปะตั€, $2 = ะฝะฐะทะฒะฐ ะตะบั€ะฐะฝะฐ, $3 = ั‚ะตะผะฐ (ั‚ะตะผะฝะฐ/ัะฒั–ั‚ะปะฐ)
โ€ข Theme toggle hook: $1 = true/false (ัั‚ะฐะฝ ั‚ะตะผะฝะพะณะพ ั€ะตะถะธะผัƒ)
โ€ข Screen lock/unlock hooks: $1 = lock/unlock (ัั‚ะฐะฝ ะฑะปะพะบัƒะฒะฐะฝะฝั ะตะบั€ะฐะฝะฐ)
โ€ข Performance mode hooks: ะ‘ะตะท ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ
โ€ข Session hook: $1 = action (shutdown/reboot)", + "info-parameters-label": "ะ”ะพัั‚ัƒะฟะฝั– ะฟะฐั€ะฐะผะตั‚ั€ะธ", + "noctalia-started-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟั–ัะปั ะทะฐะฒะตั€ัˆะตะฝะฝั ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั Noctalia.", + "noctalia-started-label": "Noctalia ะทะฐะฟัƒั‰ะตะฝะพ", + "noctalia-started-placeholder": "ะฝะฐะฟั€. notify-send 'Noctalia ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะพ'", + "performance-mode-disabled-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟั€ะธ ะฒะธะผะบะฝะตะฝะฝั– ั€ะตะถะธะผัƒ ะฟั€ะพะดัƒะบั‚ะธะฒะฝะพัั‚ั– Noctalia.", + "performance-mode-disabled-label": "ะ ะตะถะธะผ ะฟั€ะพะดัƒะบั‚ะธะฒะฝะพัั‚ั– ะฒะธะผะบะฝะตะฝะพ", + "performance-mode-disabled-placeholder": "ะฝะฐะฟั€. notify-send \"ะŸั€ะพะดัƒะบั‚ะธะฒะฝั–ัั‚ัŒ\" \"ะ ะตะถะธะผ ะฒะธะผะบะฝะตะฝะพ\"", + "performance-mode-enabled-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟั€ะธ ัƒะฒั–ะผะบะฝะตะฝะฝั– ั€ะตะถะธะผัƒ ะฟั€ะพะดัƒะบั‚ะธะฒะฝะพัั‚ั– Noctalia.", + "performance-mode-enabled-label": "ะ ะตะถะธะผ ะฟั€ะพะดัƒะบั‚ะธะฒะฝะพัั‚ั– ัƒะฒั–ะผะบะฝะตะฝะพ", + "performance-mode-enabled-placeholder": "ะฝะฐะฟั€. notify-send \"ะŸั€ะพะดัƒะบั‚ะธะฒะฝั–ัั‚ัŒ\" \"ะ ะตะถะธะผ ัƒะฒั–ะผะบะฝะตะฝะพ\"", + "screen-lock-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟั€ะธ ะฑะปะพะบัƒะฒะฐะฝะฝั– ะตะบั€ะฐะฝะฐ.", + "screen-lock-label": "ะ•ะบั€ะฐะฝ ะทะฐะฑะปะพะบะพะฒะฐะฝะพ", + "screen-lock-placeholder": "ะฝะฐะฟั€. notify-send \"ะ•ะบั€ะฐะฝ\" \"ะ—ะฐะฑะปะพะบะพะฒะฐะฝะพ\"", + "screen-unlock-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟั€ะธ ั€ะพะทะฑะปะพะบัƒะฒะฐะฝะฝั– ะตะบั€ะฐะฝะฐ.", + "screen-unlock-label": "ะ•ะบั€ะฐะฝ ั€ะพะทะฑะปะพะบะพะฒะฐะฝะพ", + "screen-unlock-placeholder": "ะฝะฐะฟั€. notify-send \"ะ•ะบั€ะฐะฝ\" \"ะ ะพะทะฑะปะพะบะพะฒะฐะฝะพ\"", + "session-description": "ะšะพะผะฐะฝะดะฐ, ั‰ะพ ะฒะธะบะพะฝัƒั”ั‚ัŒัั ะฟะตั€ะตะด ะฒะธะผะบะฝะตะฝะฝัะผ ะฐะฑะพ ะฟะตั€ะตะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝัะผ. ะžั‚ั€ะธะผัƒั” ั‚ะธะฟ ะดั–ั— ัะบ $1 (ะฒะธะผะบะฝะตะฝะฝั/ะฟะตั€ะตะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั).", + "session-label": "ะ—ะฐะฒะตั€ัˆะตะฝะฝั ัะตัั–ั—", + "session-placeholder": "ะฝะฐะฟั€. notify-send \"Session\" \"$1\"", + "system-hooks-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะบะพะผะฐะฝะดะธ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟั€ะธ ัะธัั‚ะตะผะฝะธั… ะฟะพะดั–ัั….", + "system-hooks-enable-description": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฐะฑะพ ะฒะธะผะบะฝัƒั‚ะธ ะฒัั– ะบะพะผะฐะฝะดะธ ั…ัƒะบั–ะฒ.", + "system-hooks-enable-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ั…ัƒะบะธ", + "system-hooks-title": "ะกะธัั‚ะตะผะฝั– ั…ัƒะบะธ", + "theme-changed-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟั€ะธ ะฟะตั€ะตะผะธะบะฐะฝะฝั– ั‚ะตะผะธ ะผั–ะถ ั‚ะตะผะฝะธะผ ั– ัะฒั–ั‚ะปะธะผ ั€ะตะถะธะผะพะผ.", + "theme-changed-label": "ะขะตะผัƒ ะทะผั–ะฝะตะฝะพ", + "theme-changed-placeholder": "ะฝะฐะฟั€. notify-send \"ะขะตะผะฐ\" \"ะŸะตั€ะตะผะบะฝัƒั‚ะพ\"", + "title": "ะฅัƒะบะธ", + "wallpaper-changed-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟั€ะธ ะทะผั–ะฝั– ัˆะฟะฐะปะตั€.", + "wallpaper-changed-label": "ะจะฟะฐะปะตั€ะธ ะทะผั–ะฝะตะฝะพ", + "wallpaper-changed-placeholder": "ะฝะฐะฟั€. notify-send \"ะจะฟะฐะปะตั€ะธ\" \"ะ—ะผั–ะฝะตะฝะพ\"" + }, + "idle": { + "custom-add": "ะ”ะพะดะฐั‚ะธ ะบะพะผะฐะฝะดัƒ", + "custom-description": "ะ’ะธะบะพะฝะฐั‚ะธ ะบะพะผะฐะฝะดัƒ ะพะฑะพะปะพะฝะบะธ ะฟั–ัะปั ะฟะตั€ั–ะพะดัƒ ะฑะตะทะดั–ัะปัŒะฝะพัั‚ั–.", + "custom-entry-command": "ะšะพะผะฐะฝะดะฐ", + "custom-entry-delete": "ะ’ะธะดะฐะปะธั‚ะธ", + "custom-entry-edit": "ะ ะตะดะฐะณัƒะฒะฐั‚ะธ ะ’ะปะฐัะฝัƒ ะšะพะผะฐะฝะดัƒ", + "custom-entry-name": "ะ†ะผ'ั", + "custom-entry-name-placeholder": "ะฝะฐะฟั€. ะ’ะธะผะบะฝัƒั‚ะธ ัะฒั–ั‚ะปะพ", + "custom-entry-new": "ะะพะฒะฐ ะšะพั€ะธัั‚ัƒะฒะฐั†ัŒะบะฐ ะšะพะผะฐะฝะดะฐ", + "custom-entry-timeout": "ะงะฐั ะฑะตะทะดั–ัะปัŒะฝะพัั‚ั–", + "custom-entry-timeout-format": "{count} ัะตะบัƒะฝะดะฐ", + "custom-entry-timeout-format-plural": "{count} ัะตะบัƒะฝะด", + "custom-entry-unnamed": "ะ‘ะตะทั–ะผะตะฝะฝะฐ ะบะพะผะฐะฝะดะฐ", + "custom-label": "ะšะพั€ะธัั‚ัƒะฒะฐั†ัŒะบั– ะบะพะผะฐะฝะดะธ ะฑะตะทะดั–ัะปัŒะฝะพัั‚ั–", + "enable-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฒะธะผะธะบะฐั‚ะธ ะตะบั€ะฐะฝ, ะฑะปะพะบัƒะฒะฐั‚ะธ ะฐะฑะพ ะฟั€ะธะทัƒะฟะธะฝัั‚ะธ ั€ะพะฑะพั‚ัƒ ะฟั–ัะปั ะฟะตั€ั–ะพะดัƒ ะฑะตะทะดั–ัะปัŒะฝะพัั‚ั–.", + "enable-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะบะตั€ัƒะฒะฐะฝะฝั ะฑะตะทะดั–ัะปัŒะฝั–ัั‚ัŽ", + "fade-duration-description": "ะกะตะบัƒะฝะดะธ ะดะปั ะฐะฝั–ะผะฐั†ั–ั— ะทะฐั‚ะตะผะฝะตะฝะฝั ะดะพ ั‡ะพั€ะฝะพะณะพ ะฟะตั€ะตะด ะฒะธะบะพะฝะฐะฝะฝัะผ ะบะพะถะฝะพั— ะดั–ั—. ะ‘ัƒะดัŒ-ัะบะธะน ั€ัƒั… ะผะธัˆั– ัะบะฐัะพะฒัƒั” ะทะฐั‚ะตะผะฝะตะฝะฝั.", + "fade-duration-label": "ะขั€ะธะฒะฐะปั–ัั‚ัŒ ะทะณะฐัะฐะฝะฝั", + "lock-description": "ะกะตะบัƒะฝะดะธ ะฑะตะทะดั–ัะปัŒะฝะพัั‚ั– ะดะพ ะฐะบั‚ะธะฒะฐั†ั–ั— ะตะบั€ะฐะฝะฐ ะฑะปะพะบัƒะฒะฐะฝะฝั.", + "lock-label": "ะ—ะฐะฑะปะพะบัƒะฒะฐั‚ะธ ะตะบั€ะฐะฝ", + "resume-command-label": "ะ’ั–ะดะฝะพะฒะธั‚ะธ ะบะพะผะฐะฝะดัƒ", + "screen-off-description": "ะกะตะบัƒะฝะดะธ ะฑะตะทะดั–ัะปัŒะฝะพัั‚ั– ะดะพ ะฒะธะผะบะฝะตะฝะฝั ะผะพะฝั–ั‚ะพั€ั–ะฒ.", + "screen-off-label": "ะ’ะธะผะบะฝัƒั‚ะธ ะตะบั€ะฐะฝ", + "status-description": "ะงะฐั ะฟั€ะพัั‚ะพัŽ, ัะบะธะน ะฟะพะฒั–ะดะพะผะปัั” ะบะพะผะฟะพะทะธั‚ะพั€.", + "status-label": "ะงะฐั ะฟั€ะพัั‚ะพัŽ", + "suspend-description": "ะกะตะบัƒะฝะดะธ ะฑะตะทะดั–ัะปัŒะฝะพัั‚ั– ะดะพ ะฟั€ะธะทัƒะฟะธะฝะตะฝะฝั ั€ะพะฑะพั‚ะธ ัะธัั‚ะตะผะธ.", + "tab-behavior": "ะŸะพะฒะตะดั–ะฝะบะฐ", + "tab-custom": "ะšะพั€ะธัั‚ัƒะฒะฐั†ัŒะบะธะน", + "timeouts-description": "ะ’ัั‚ะฐะฝะพะฒั–ั‚ัŒ 0, ั‰ะพะฑ ะฒะธะผะบะฝัƒั‚ะธ ะตั‚ะฐะฟ. ะขะฐะนะผ-ะฐัƒั‚ะธ ะฟั€ะธะทัƒะฟะธะฝััŽั‚ัŒัั, ะฟะพะบะธ 'ะ—ะฐะฑะพั€ะพะฝะฐ ัะฝัƒ' ะฐะบั‚ะธะฒะฝะธะน.", + "timeouts-label": "ะขะฐะนะผ-ะฐัƒั‚ะธ", + "title": "ะ‘ะตะทะดั–ัะปัŒะฝั–ัั‚ัŒ", + "unavailable": "ะะฐั‚ะธะฒะฝะธะน ะผะพะฝั–ั‚ะพั€ะธะฝะณ ะฑะตะทะดั–ัะปัŒะฝะพัั‚ั– ะฝะตะดะพัั‚ัƒะฟะฝะธะน ะฝะฐ ั†ัŒะพะผัƒ Compositorั–." + }, + "indicator": { + "default-value": "ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ: {value}", + "system-default": "ะกะธัั‚ะตะผะฝั– ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ" + }, + "launcher": { + "clipboard-desc": "ะžั‚ั€ะธะผัƒะนั‚ะต ะดะพัั‚ัƒะฟ ะดะพ ั–ัั‚ะพั€ั–ั— ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ ั‚ะฐ ะบะตั€ัƒะนั‚ะต ะฝะตัŽ ะท ะฟะฐะฝะตะปั– ะทะฐะฟัƒัะบัƒ.", + "execute-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ัะฟะพัั–ะฑ ะทะฐะฟัƒัะบัƒ ะฟั€ะพะณั€ะฐะผ.", + "execute-title": "ะ’ะธะบะพะฝะฐะฝะฝั", + "settings-annotation-tool-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะทะฐะฟัƒัะบัƒ ะฟั€ะธ ะฝะฐั‚ะธัะบะฐะฝะฝั– ะบะฝะพะฟะบะธ ะฐะฝะพั‚ัƒะฒะฐะฝะฝั ะฒ ั–ัั‚ะพั€ั–ั— ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ. ะ—ะพะฑั€ะฐะถะตะฝะฝั ะฑัƒะดะต ะฟะตั€ะตะดะฐะฝะพ ะฒ ั†ัŽ ะบะพะผะฐะฝะดัƒ.", + "settings-annotation-tool-label": "ะ†ะฝัั‚ั€ัƒะผะตะฝั‚ ะฐะฝะพั‚ะฐั†ั–ะน", + "settings-annotation-tool-placeholder": "ะฝะฐะฟั€. 'gradia', 'satty -f -'", + "settings-auto-paste-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฒัั‚ะฐะฒะธั‚ะธ ะฒะธะฑั€ะฐะฝะธะน ะตะปะตะผะตะฝั‚ ะท ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ. ะŸะพั‚ั€ะตะฑัƒั” wtype.", + "settings-auto-paste-label": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะฐ ะฒัั‚ะฐะฒะบะฐ", + "settings-clip-chips-description": "ะŸะพะบะฐะทะฐั‚ะธ ะฟะฐะฝะตะปัŒ ะฒะบะปะฐะดะพะบ ะดะปั ั„ั–ะปัŒั‚ั€ะฐั†ั–ั— ั–ัั‚ะพั€ั–ั— ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ ะทะฐ ั‚ะธะฟะพะผ (ะ—ะพะฑั€ะฐะถะตะฝะฝั, ะŸะพัะธะปะฐะฝะฝั, ะคะฐะนะปะธ, ะšะพะด ั‚ะพั‰ะพ).", + "settings-clip-chips-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ั‡ั–ะฟะธ ะบะฐั‚ะตะณะพั€ั–ะน", + "settings-clip-preview-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟะพะฟะตั€ะตะดะฝั–ะน ะฟะตั€ะตะณะปัะด ะฒะผั–ัั‚ัƒ ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ ะฟั€ะธ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั– ะบะพะผะฐะฝะดะธ >clip.", + "settings-clip-preview-label": "ะŸะพะฟะตั€ะตะดะฝั–ะน ะฟะตั€ะตะณะปัะด ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ", + "settings-clip-smart-icons-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ัะฟะตั†ั–ะฐะปัŒะฝั– ั–ะบะพะฝะบะธ ะดะปั ะฟะพัะธะปะฐะฝัŒ, ั„ะฐะนะปั–ะฒ, ะบะพะปัŒะพั€ั–ะฒ ั‚ะฐ ะบะพะดัƒ ะทะฐะผั–ัั‚ัŒ ะทะฐะณะฐะปัŒะฝะพั— ั–ะบะพะฝะบะธ ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ.", + "settings-clip-smart-icons-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ั€ะพะทัƒะผะฝั– ั–ะบะพะฝะบะธ", + "settings-clip-wrap-text-description": "ะŸะตั€ะตะฝะพัะธั‚ะธ ั‚ะตะบัั‚ ัƒ ัะฟะธัะบัƒ ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ ะทะฐะผั–ัั‚ัŒ ะพะฑั€ั–ะทะฐะฝะฝั.", + "settings-clip-wrap-text-label": "ะ—ะฐะณะพั€ะฝัƒั‚ะธ ั‚ะตะบัั‚ ะท ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ", + "settings-clipboard-history-description": "ะžั‚ั€ะธะผะฐั‚ะธ ะดะพัั‚ัƒะฟ ะดะพ ั€ะฐะฝั–ัˆะต ัะบะพะฟั–ะนะพะฒะฐะฝะธั… ะตะปะตะผะตะฝั‚ั–ะฒ ั–ะท ะปะฐัƒะฝั‡ะตั€ะฐ.", + "settings-clipboard-history-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ั–ัั‚ะพั€ั–ัŽ ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ", + "settings-clipboard-watch-image-description": "ะŸะพะฒะฝะธะน ั€ัะดะพะบ ะบะพะผะฐะฝะดะธ, ะฟะตั€ะตะดะฐะฝะธะน ะดะพ wl-paste ะดะปั ะทะผั–ะฝะธ ะทะพะฑั€ะฐะถะตะฝัŒ (ะฟะพั‚ั€ั–ะฑะตะฝ ะฟะตั€ะตะทะฐะฟัƒัะบ).", + "settings-clipboard-watch-image-label": "ะšะพะผะฐะฝะดะฐ ะกะฟะพัั‚ะตั€ะตะถะตะฝะฝั ะทะฐ ะ—ะพะฑั€ะฐะถะตะฝะฝัะผะธ", + "settings-clipboard-watch-text-description": "ะŸะพะฒะฝะธะน ั€ัะดะพะบ ะบะพะผะฐะฝะดะธ, ะฟะตั€ะตะดะฐะฝะธะน ะดะพ wl-paste ะดะปั ะทะผั–ะฝ ั‚ะตะบัั‚ัƒ (ะฟะพั‚ั€ะตะฑัƒั” ะฟะตั€ะตะทะฐะฟัƒัะบัƒ).", + "settings-clipboard-watch-text-label": "ะšะพะผะฐะฝะดะฐ ัะฟะพัั‚ะตั€ะตะถะตะฝะฝั ะทะฐ ั‚ะตะบัั‚ะพะผ", + "settings-custom-launch-prefix-description": "ะ”ะพะดะฐั‚ะธ ะฟั€ะตั„ั–ะบั ะดะพ ะบะพะผะฐะฝะด ะทะฐะฟัƒัะบัƒ ะปะฐัƒะฝั‡ะตั€ะพะผ (ะฝะฐะฟั€. 'runapp' ะดะปั ั–ะฝั‚ะตะณั€ะฐั†ั–ั— ะท systemd).", + "settings-custom-launch-prefix-enabled-description": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฒะปะฐัะฝะธะน ะฟั€ะตั„ั–ะบั ะดะปั ะทะฐะฟัƒัะบัƒ ะทะฐัั‚ะพััƒะฝะบั–ะฒ ะทะฐะผั–ัั‚ัŒ ัั‚ะฐะฝะดะฐั€ั‚ะฝะพะณะพ ะผะตั‚ะพะดัƒ.", + "settings-custom-launch-prefix-enabled-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะบะพั€ะธัั‚ัƒะฒะฐั†ัŒะบะธะน ะฟั€ะตั„ั–ะบั ะทะฐะฟัƒัะบัƒ", + "settings-custom-launch-prefix-label": "ะšะพั€ะธัั‚ัƒะฒะฐั†ัŒะบะธะน ะฟั€ะตั„ั–ะบั ะทะฐะฟัƒัะบัƒ", + "settings-density-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ั€ะพะทะผั–ั€ ั–ะบะพะฝะพะบ ะฟั€ะพะณั€ะฐะผ ั– ั‰ั–ะปัŒะฝั–ัั‚ัŒ Launcher.", + "settings-density-label": "ะฉั–ะปัŒะฝั–ัั‚ัŒ", + "settings-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฟะพะฒะตะดั–ะฝะบัƒ ั‚ะฐ ะทะพะฒะฝั–ัˆะฝั–ะน ะฒะธะณะปัะด ะปะฐัƒะฝั‡ะตั€ะฐ.", + "settings-enable-session-search-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะดั–ั— ัะตะฐะฝััƒ (ะฑะปะพะบัƒะฒะฐะฝะฝั, ะฒะธะผะบะฝะตะฝะฝั, ะฟะตั€ะตะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ั‚ะพั‰ะพ) ะฟั–ะด ั‡ะฐั ะฟะพัˆัƒะบัƒ ะฒ Launcher.", + "settings-enable-session-search-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฟะพัˆัƒะบ ัะตัั–ั—", + "settings-enable-settings-search-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ั€ะตะทัƒะปัŒั‚ะฐั‚ะธ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝัŒ ะฟั–ะด ั‡ะฐั ะฟะพัˆัƒะบัƒ ะฒ Launcher.", + "settings-enable-settings-search-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฟะพัˆัƒะบ ัƒ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝัั…", + "settings-enable-windows-search-description": "ะจัƒะบะฐั‚ะธ ั‚ะฐ ั„ะพะบัƒััƒะฒะฐั‚ะธ ะฐะบั‚ะธะฒะฝั– ะฒั–ะบะฝะฐ.", + "settings-enable-windows-search-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฟะพัˆัƒะบ ะฒั–ะบะพะฝ", + "settings-grid-view-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะตะปะตะผะตะฝั‚ะธ ัƒ ะฒะธะณะปัะดั– ัั–ั‚ะบะธ ะทะฐะผั–ัั‚ัŒ ัะฟะธัะบัƒ.", + "settings-icon-mode-description": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฝะฐั‚ะธะฒะฝั– ัะธัั‚ะตะผะฝั– ั–ะบะพะฝะบะธ ะทะฐะผั–ัั‚ัŒ ั–ะบะพะฝะพะบ Tabler.", + "settings-icon-mode-label": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฝะฐั‚ะธะฒะฝั– ั–ะบะพะฝะบะธ", + "settings-ignore-mouse-input-description": "ะ’ะธะผะบะฝัƒั‚ะธ ะฒะทะฐั”ะผะพะดั–ัŽ ะท ะผะธัˆะตัŽ ั‚ะฐ ะบะพะปั–ั‰ะฐั‚ะบะพ ะฟั€ะพะบั€ัƒั‚ะบะธ ะฒ ะปะฐัƒะฝั‡ะตั€ั–.", + "settings-ignore-mouse-input-label": "ะ†ะณะฝะพั€ัƒะฒะฐั‚ะธ ะฒะฒะตะดะตะฝะฝั ะผะธัˆั–", + "settings-overlay-layer-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะทะฐะฟัƒัะบะฐั‡ ะฝะฐ ัˆะฐั€ั– ะฝะฐะบะปะฐะดะฐะฝะฝั, ะฝะฐะด ะฟะพะฒะฝะพะตะบั€ะฐะฝะฝะธะผะธ ะฒั–ะบะฝะฐะผะธ. ะšะพะปะธ ัƒะฒั–ะผะบะฝะตะฝะพ, ะทะฐะฟัƒัะบะฐั‡ ะฝะต ะฑัƒะดะต ะฟั€ะธะบั€ั–ะฟะปะตะฝะพ ะดะพ ะฟะฐะฝะตะปั–.", + "settings-overlay-layer-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฝะฐะด ะฟะพะฒะฝะพะตะบั€ะฐะฝะฝะธะผ ั€ะตะถะธะผะพะผ", + "settings-position-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ะดะต ะท'ัะฒะปัั”ั‚ัŒัั ะฟะฐะฝะตะปัŒ ะปะฐัƒะฝั‡ะตั€ะฐ.", + "settings-show-categories-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฒะบะปะฐะดะบะธ ะบะฐั‚ะตะณะพั€ั–ะน ะดะปั ั„ั–ะปัŒั‚ั€ะฐั†ั–ั— ะดะพะดะฐั‚ะบั–ะฒ.", + "settings-show-categories-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะบะฐั‚ะตะณะพั€ั–ั—", + "settings-show-icon-background-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะทะฐะพะบั€ัƒะณะปะตะฝะธะน ะฟั€ัะผะพะบัƒั‚ะฝะธะบ ัะบ ั„ะพะฝ ะทะฐ ั–ะบะพะฝะบะฐะผะธ.", + "settings-show-icon-background-label": "ะŸะพะบะฐะทะฐั‚ะธ ั„ะพะฝ ั–ะบะพะฝะบะธ", + "settings-sort-by-usage-description": "ะšะพะปะธ ัƒะฒั–ะผะบะฝะตะฝะพ, ั‡ะฐัั‚ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐะฝั– ะทะฐัั‚ะพััƒะฝะบะธ ะท'ัะฒะปััŽั‚ัŒัั ะฟะตั€ัˆะธะผะธ ะฒ ัะฟะธัะบัƒ.", + "settings-sort-by-usage-label": "ะกะพั€ั‚ัƒะฒะฐั‚ะธ ะทะฐ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝัะผ", + "settings-terminal-command-description": "ะšะพะผะฐะฝะดะฐ ะดะปั ะทะฐะฟัƒัะบัƒ ั‚ะตั€ะผั–ะฝะฐะปัƒ. ะะฐะฟั€. 'kitty -e' ะฐะฑะพ 'gnome-terminal --'.", + "settings-terminal-command-label": "ะšะพะผะฐะฝะดะฐ ั‚ะตั€ะผั–ะฝะฐะปัƒ", + "settings-use-app2unit-description": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒั” ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะธะน ะผะตั‚ะพะด ะทะฐะฟัƒัะบัƒ ะดะปั ะบั€ะฐั‰ะพะณะพ ะบะตั€ัƒะฒะฐะฝะฝั ะฟั€ะพั†ะตัะฐะผะธ ะทะฐัั‚ะพััƒะฝะบั–ะฒ ั– ะทะฐะฟะพะฑั–ะณะฐะฝะฝั ะฟั€ะพะฑะปะตะผะฐะผ.", + "settings-use-app2unit-label": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ App2Unit ะดะปั ะทะฐะฟัƒัะบัƒ ะทะฐัั‚ะพััƒะฝะบั–ะฒ", + "settings-view-mode-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะผะฐะบะตั‚ ะดะปั ะทะฐะฟะธัั–ะฒ ะ—ะฐะฟัƒัะบะฐั‡ะฐ.", + "settings-view-mode-label": "ะ ะตะถะธะผ ะฟะตั€ะตะณะปัะดัƒ", + "title": "ะ›ะฐัƒะฝั‡ะตั€" + }, + "location": { + "auto-locate-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฒะธะทะฝะฐั‡ะฐั‚ะธ ะฒะฐัˆะต ะผั–ัั†ะตะทะฝะฐั…ะพะดะถะตะฝะฝั ะฟะตั€ั–ะพะดะธั‡ะฝะพ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะฒะฐัˆัƒ IP-ะฐะดั€ะตััƒ.", + "auto-locate-label": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะต ะ’ะธะทะฝะฐั‡ะตะฝะฝั", + "calendar-cards-desc": "ะžั€ะณะฐะฝั–ะทัƒะนั‚ะต ั‚ะฐ ะฒะผะธะบะฐะนั‚ะต/ะฒะธะผะธะบะฐะนั‚ะต ะบะฐั€ั‚ะบะธ ะฝะฐ ะฟะฐะฝะตะปั– ะบะฐะปะตะฝะดะฐั€ั.", + "calendar-cards-title": "ะšะฐะปะตะฝะดะฐั€ะฝั– ะบะฐั€ั‚ะบะธ", + "calendar-header-label": "ะ—ะฐะณะพะปะพะฒะพะบ ะบะฐะปะตะฝะดะฐั€ั", + "calendar-month-label": "ะœั–ััั†ัŒ ะบะฐะปะตะฝะดะฐั€ั", + "date-time-12hour-format-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ั‡ะฐั ัƒ 12-ะณะพะดะธะฝะฝะพะผัƒ ั„ะพั€ะผะฐั‚ั– ะฝะฐ ะตะบั€ะฐะฝั– ะฑะปะพะบัƒะฒะฐะฝะฝั ั‚ะฐ ะฒ ะบะฐะปะตะฝะดะฐั€ั–. ะ“ะพะดะธะฝะฝะธะบ ะฝะฐ ะฟะฐะฝะตะปั– ะผะฐั” ะฒะปะฐัะฝั– ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั.", + "date-time-12hour-format-label": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ 12-ะณะพะดะธะฝะฝะธะน ั„ะพั€ะผะฐั‚ ั‡ะฐััƒ", + "date-time-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต, ัะบ ะฒั–ะดะพะฑั€ะฐะถะฐัŽั‚ัŒัั ะดะฐั‚ะฐ ั‚ะฐ ั‡ะฐั.", + "date-time-first-day-of-week-automatic": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ (ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ัะธัั‚ะตะผะฝัƒ ะปะพะบะฐะปัŒ)", + "date-time-first-day-of-week-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ัะบะธะน ะดะตะฝัŒ ะฟะพั‡ะธะฝะฐั” ั‚ะธะถะดะตะฝัŒ ัƒ ะบะฐะปะตะฝะดะฐั€ั–.", + "date-time-first-day-of-week-label": "ะŸะตั€ัˆะธะน ะดะตะฝัŒ ั‚ะธะถะฝั", + "date-time-show-events-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะฟะพะดั–ั— ะฝะฐ ะฟะฐะฝะตะปั– ะบะฐะปะตะฝะดะฐั€ั.", + "date-time-show-events-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟะพะดั–ั— ะบะฐะปะตะฝะดะฐั€ั", + "date-time-title": "ะ”ะฐั‚ะฐ ั– ั‡ะฐั", + "date-time-use-analog-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฐะฝะฐะปะพะณะพะฒะธะน ะณะพะดะธะฝะฝะธะบ ัƒ ะฒั–ะบะฝั– ะบะฐะปะตะฝะดะฐั€ั.", + "date-time-use-analog-label": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฐะฝะฐะปะพะณะพะฒะธะน ะณะพะดะธะฝะฝะธะบ", + "date-time-week-numbers-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะฝะพะผะตั€ ั‚ะธะถะฝั ั€ะพะบัƒ (ะฝะฐะฟั€. ั‚ะธะถะดะตะฝัŒ 38) ัƒ ะบะฐะปะตะฝะดะฐั€ั–.", + "date-time-week-numbers-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฝะพะผะตั€ะธ ั‚ะธะถะฝั–ะฒ", + "geolocate-now-button": "ะ—ะฝะฐะนั‚ะธ ะทะฐั€ะฐะท", + "location-desc": "ะžั‚ั€ะธะผัƒะนั‚ะต ั‚ะพั‡ะฝัƒ ะฟะพะณะพะดัƒ ั‚ะฐ ะฟะปะฐะฝัƒะฒะฐะฝะฝั ะฝั–ั‡ะฝะพะณะพ ัะฒั–ั‚ะปะฐ, ะฒัั‚ะฐะฝะพะฒะธะฒัˆะธ ะฒะฐัˆะต ั€ะพะทั‚ะฐัˆัƒะฒะฐะฝะฝั.", + "location-search-description": "ะฝะฐะฟั€. ะขะพั€ะพะฝั‚ะพ, ะžะ", + "location-search-label": "ะจัƒะบะฐั‚ะธ ั€ะพะทั‚ะฐัˆัƒะฒะฐะฝะฝั", + "location-search-placeholder": "ะ’ะฒะตะดั–ั‚ัŒ ะฝะฐะทะฒัƒ ะผั–ัั†ั", + "location-title": "ะ’ะฐัˆะต ั€ะพะทั‚ะฐัˆัƒะฒะฐะฝะฝั", + "weather-desc": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะฑะฐะถะฐะฝัƒ ะพะดะธะฝะธั†ัŽ ั‚ะตะผะฟะตั€ะฐั‚ัƒั€ะธ.", + "weather-enabled-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ ะฟั€ะพ ะฟะพะณะพะดัƒ ะฒ ั–ะฝั‚ะตั€ั„ะตะนัั– ั‚ะฐ ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ะดะฐะฝั– ะฟั€ะพ ะฟะพะณะพะดัƒ. ะšะพะปะธ ะฒะธะผะบะฝะตะฝะพ, ะฒัั– ะตะปะตะผะตะฝั‚ะธ ะฟะพะณะพะดะธ ะฑัƒะดัƒั‚ัŒ ะฟั€ะธั…ะพะฒะฐะฝั– ั‚ะฐ ะผะตั€ะตะถะตะฒั– ะทะฐะฟะธั‚ะธ ะฝะต ะฒะธะบะพะฝัƒะฒะฐั‚ะธะผัƒั‚ัŒัั.", + "weather-enabled-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฟะพะณะพะดัƒ", + "weather-fahrenheit-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ั‚ะตะผะฟะตั€ะฐั‚ัƒั€ัƒ ะฒ ะคะฐั€ะตะฝะณะตะนั‚ะฐั… ะทะฐะผั–ัั‚ัŒ ะฆะตะปัŒัั–ั.", + "weather-fahrenheit-label": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ั‚ะตะผะฟะตั€ะฐั‚ัƒั€ัƒ ะฒ ะคะฐั€ะตะฝะณะตะนั‚ะฐั… (ยฐF)", + "weather-hide-city-description": "ะŸั€ะธั…ะพะฒะฐั‚ะธ ะฝะฐะทะฒัƒ ะผั–ัั‚ะฐ ะท ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ะฟะพะณะพะดะธ ะฒ ัƒััŒะพะผัƒ ั–ะฝั‚ะตั€ั„ะตะนัั–.", + "weather-hide-city-label": "ะŸั€ะธั…ะพะฒะฐั‚ะธ ะฝะฐะทะฒัƒ ะผั–ัั‚ะฐ", + "weather-hide-timezone-description": "ะŸั€ะธั…ะพะฒะฐั‚ะธ ะฐะฑั€ะตะฒั–ะฐั‚ัƒั€ัƒ ั‡ะฐัะพะฒะพะณะพ ะฟะพัััƒ ะท ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ะฟะพะณะพะดะธ ะฒ ัƒััŒะพะผัƒ ั–ะฝั‚ะตั€ั„ะตะนัั–.", + "weather-hide-timezone-label": "ะŸั€ะธั…ะพะฒะฐั‚ะธ ั‡ะฐัะพะฒะธะน ะฟะพัั", + "weather-show-effects-description": "ะŸะพะบะฐะทัƒั” ะดะพะดะฐั‚ะบะพะฒั– ะฒั–ะทัƒะฐะปัŒะฝั– ะตั„ะตะบั‚ะธ (ะฝะฐะฟั€ะธะบะปะฐะด, ะดะพั‰, ัะฝั–ะณ ะฐะฑะพ ะฑะปะธัะบะฐะฒะบัƒ) ะฝะฐ ะบะฐั€ั‚ั†ั– ะฟะพะณะพะดะธ.", + "weather-show-effects-label": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะฟะพะณะพะดะฝั– ะตั„ะตะบั‚ะธ", + "weather-show-in-calendar-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ั‰ะพะดะตะฝะฝะธะน ะฟั€ะพะณะฝะพะท ะฟะพะณะพะดะธ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะฒ ะบะฐะปะตะฝะดะฐั€ั–.", + "weather-show-in-calendar-label": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะฟะพะณะพะดัƒ ะฒ ะบะฐะปะตะฝะดะฐั€ั–", + "weather-talia-mascot-always-description": "ะ—ะฐะฒะถะดะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต Talia emojis ะทะฐะผั–ัั‚ัŒ tabler icons.", + "weather-talia-mascot-always-label": "ะ—ะฐะฒะถะดะธ ะฟะพะบะฐะทัƒะฒะฐั‚ะธ ะฟะพะณะพะดะฝั– ะตะผะพะดะทั– Talia" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "ะšะพะปะธ fprintd (ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั ะทะฐ ะฒั–ะดะฑะธั‚ะบะพะผ ะฟะฐะปัŒั†ั) ะฐะบั‚ะธะฒะฝะธะน, ั†ั ะพะฟั†ั–ั ะดะพะทะฒะพะปัั” ะฒั…ะพะดะธั‚ะธ ะฒ ัะธัั‚ะตะผัƒ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ะฟะฐั€ะพะปั ะทะฐะผั–ัั‚ัŒ ะฒั–ะดะฑะธั‚ะบะฐ ะฟะฐะปัŒั†ั.", + "allow-password-with-fprintd-label": "ะ”ะพะทะฒะพะปะธั‚ะธ ะฒั…ั–ะด ะทะฐ ะฟะฐั€ะพะปะตะผ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ fprintd", + "auto-start-auth-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะทะฐะฟัƒัะบะฐั” ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ัŽ ะทะฐ ะฒั–ะดะฑะธั‚ะบะพะผ ะฟะฐะปัŒั†ั ะฑะตะท ะฝะตะพะฑั…ั–ะดะฝะพัั‚ั– ะฝะฐั‚ะธัะบะฐะฝะฝั ะบะปะฐะฒั–ัˆั– ะฐะฑะพ ะบะปะฐั†ะฐะฝะฝั ะบะฝะพะฟะบะธ.", + "auto-start-auth-label": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะธะน ะทะฐะฟัƒัะบ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั—", + "clock-format-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ั„ะพั€ะผะฐั‚ ะณะพะดะธะฝะฝะธะบะฐ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ั‚ะพะบะตะฝะธ ัะธะฝั‚ะฐะบัะธััƒ ะดะฐั‚ะธ/ั‡ะฐััƒ.", + "clock-format-label": "ะคะพั€ะผะฐั‚ ะณะพะดะธะฝะฝะธะบะฐ", + "clock-style-analog": "ะะฝะฐะปะพะณะพะฒะธะน", + "clock-style-custom": "ะšะพั€ะธัั‚ัƒะฒะฐั†ัŒะบะธะน", + "clock-style-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะฒั–ะทัƒะฐะปัŒะฝะธะน ัั‚ะธะปัŒ ะณะพะดะธะฝะฝะธะบะฐ ะฝะฐ ะ•ะบั€ะฐะฝั– ะฑะปะพะบัƒะฒะฐะฝะฝั.", + "clock-style-digital": "ะฆะธั„ั€ะพะฒะธะน", + "clock-style-label": "ะกั‚ะธะปัŒ ะ“ะพะดะธะฝะฝะธะบะฐ", + "compact-lockscreen-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ั‚ั–ะปัŒะบะธ ะฟะพะปะต ะฒั…ะพะดัƒ ั‚ะฐ ัะธัั‚ะตะผะฝั– ะตะปะตะผะตะฝั‚ะธ ะบะตั€ัƒะฒะฐะฝะฝั, ะฟั€ะธั…ะพะฒัƒัŽั‡ะธ ะฒั–ะดะถะตั‚ะธ ะฟะพะณะพะดะธ ั‚ะฐ ะผะตะดั–ะฐ.", + "compact-lockscreen-label": "ะšะพะผะฟะฐะบั‚ะฝะธะน ะตะบั€ะฐะฝ ะฑะปะพะบัƒะฒะฐะฝะฝั", + "enable-lockscreen-media-controls-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝั– ะตะปะตะผะตะฝั‚ะธ ะบะตั€ัƒะฒะฐะฝะฝั ะฒั–ะดั‚ะฒะพั€ะตะฝะฝัะผ ะผะตะดั–ะฐ ะฝะฐ ะตะบั€ะฐะฝั– ะฑะปะพะบัƒะฒะฐะฝะฝั.", + "enable-lockscreen-media-controls-label": "ะ•ะปะตะผะตะฝั‚ะธ ะšะตั€ัƒะฒะฐะฝะฝั ะœะตะดั–ะฐ ะฝะฐ ะ•ะบั€ะฐะฝั– ะ‘ะปะพะบัƒะฒะฐะฝะฝั", + "lock-on-suspend-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฑะปะพะบัƒะฒะฐั‚ะธ ะตะบั€ะฐะฝ ะฟั€ะธ ะฟั€ะธะทัƒะฟะธะฝะตะฝะฝั– ัะธัั‚ะตะผะธ.", + "lock-on-suspend-label": "ะ‘ะปะพะบัƒะฒะฐั‚ะธ ะฟั€ะธ ะฟั€ะธะทัƒะฟะธะฝะตะฝะฝั–", + "lock-screen-animations-description": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฐะฑะพ ะฒะธะผะบะฝัƒั‚ะธ ะฐะฝั–ะผะฐั†ั–ั— ะตะบั€ะฐะฝะฐ ะฑะปะพะบัƒะฒะฐะฝะฝั.", + "lock-screen-animations-label": "ะะฝั–ะผะฐั†ั–ั— ะ•ะบั€ะฐะฝะฐ ะ‘ะปะพะบัƒะฒะฐะฝะฝั", + "lock-screen-blur-strength-description": "ะ—ะฐัั‚ะพัะพะฒัƒั” ะตั„ะตะบั‚ ั€ะพะทะผะธั‚ั‚ั ะดะพ ัˆะฟะฐะปะตั€ ะตะบั€ะฐะฝะฐ ะฑะปะพะบัƒะฒะฐะฝะฝั.", + "lock-screen-blur-strength-label": "ะกะธะปะฐ ั€ะพะทะผะธั‚ั‚ั ะตะบั€ะฐะฝะฐ ะฑะปะพะบัƒะฒะฐะฝะฝั", + "lock-screen-tint-strength-description": "ะ—ะฐัั‚ะพัะพะฒัƒั” ะฝะฐะบะปะฐะดะฐะฝะฝั ะฒั–ะดั‚ั–ะฝะบัƒ ะดะพ ัˆะฟะฐะปะตั€ ะตะบั€ะฐะฝะฐ ะฑะปะพะบัƒะฒะฐะฝะฝั.", + "lock-screen-tint-strength-label": "ะ†ะฝั‚ะตะฝัะธะฒะฝั–ัั‚ัŒ ั‚ะพะฝัƒะฒะฐะฝะฝั ะตะบั€ะฐะฝะฐ ะฑะปะพะบัƒะฒะฐะฝะฝั", + "monitors-desc": "ะŸะพะบะฐะทะฐั‚ะธ ะตะบั€ะฐะฝ ะฑะปะพะบัƒะฒะฐะฝะฝั ะฝะฐ ะฟะตะฒะฝะธั… ะผะพะฝั–ั‚ะพั€ะฐั…. ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฝะฐ ะฒัั–ั…, ัะบั‰ะพ ะถะพะดะตะฝ ะฝะต ะพะฑั€ะฐะฝะพ.", + "password-chars-description": "ะœะธะปั– ั–ะบะพะฝะบะธ, ั‰ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒัั ะดะปั ะฟั€ะธั…ะพะฒัƒะฒะฐะฝะฝั ะฒะฐัˆะพะณะพ ะฟะฐั€ะพะปั.", + "password-chars-label": "ะ’ะธะฟะฐะดะบะพะฒั– ั–ะบะพะฝะบะธ ะฟะฐั€ะพะปั–ะฒ", + "show-hibernate-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะพะฟั†ั–ัŽ 'ัะฟะปัั‡ะธะน ั€ะตะถะธะผ' ะฒ ะตะปะตะผะตะฝั‚ะฐั… ะบะตั€ัƒะฒะฐะฝะฝั ะถะธะฒะปะตะฝะฝัะผ.", + "show-hibernate-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ัะฟะปัั‡ะธะน ั€ะตะถะธะผ", + "show-session-buttons-description": "ะ”ะพะทะฒะพะปะธั‚ะธ ะดะพัั‚ัƒะฟ ะดะพ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝัŒ ะถะธะฒะปะตะฝะฝั ะท ะตะบั€ะฐะฝะฐ ะฑะปะพะบัƒะฒะฐะฝะฝั.", + "show-session-buttons-label": "ะ•ะปะตะผะตะฝั‚ะธ ะบะตั€ัƒะฒะฐะฝะฝั ะถะธะฒะปะตะฝะฝัะผ", + "title": "ะ•ะบั€ะฐะฝ ะฑะปะพะบัƒะฒะฐะฝะฝั" + }, + "network": { + "bluetooth-description": "ะะบั‚ะธะฒัƒะฒะฐั‚ะธ ะบะตั€ัƒะฒะฐะฝะฝั Bluetooth.", + "bluetooth-rssi-polling-description": "ะŸะตั€ั–ะพะดะธั‡ะฝะพ ะทะฑะธั€ะฐั‚ะธ ะดะฐะฝั– RSSI ะดะปั ะฟั–ะดะบะปัŽั‡ะตะฝะธั… ะฟั€ะธัั‚ั€ะพั—ะฒ ั‡ะตั€ะตะท bluetoothctl. ะœะพะถะต ะฑัƒั‚ะธ ะฝะตะดะพัั‚ัƒะฟะฝะธะผ ะดะปั ะฒัั–ั… ะฟั€ะธัั‚ั€ะพั—ะฒ; ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” ะผั–ะฝั–ะผัƒะผ ั€ะตััƒั€ัั–ะฒ, ะบะพะปะธ ะฒะฒั–ะผะบะฝะตะฝะพ.", + "bluetooth-rssi-polling-label": "ะžะฟะธั‚ัƒะฒะฐะฝะฝั ัะธะณะฝะฐะปัƒ Bluetooth", + "desc": "ะšะตั€ัƒะฒะฐั‚ะธ ะฟั–ะดะบะปัŽั‡ะตะฝะฝัะผะธ Wi-Fi ั‚ะฐ Bluetooth.", + "wifi-description": "ะšะตั€ัƒะฒะฐะฝะฝั ะฑะตะทะดั€ะพั‚ะพะฒะธะผะธ ะผะตั€ะตะถะฐะผะธ (ะฟะพั‚ั€ะตะฑัƒั” NetworkManager)." + }, + "notifications": { + "duration-critical-urgency-description": "ะฏะบ ะดะพะฒะณะพ ะทะฐะปะธัˆะฐัŽั‚ัŒัั ะฒะธะดะธะผะธะผะธ ะบั€ะธั‚ะธั‡ะฝั– ัะฟะพะฒั–ั‰ะตะฝะฝั.", + "duration-critical-urgency-label": "ะšั€ะธั‚ะธั‡ะฝะฐ ั‚ะตั€ะผั–ะฝะพะฒั–ัั‚ัŒ", + "duration-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต, ัะบ ะดะพะฒะณะพ ัะฟะพะฒั–ั‰ะตะฝะฝั ะทะฐะปะธัˆะฐัŽั‚ัŒัั ะฒะธะดะธะผะธะผะธ ะทะฐะปะตะถะฝะพ ะฒั–ะด ั€ั–ะฒะฝั ั‚ะตั€ะผั–ะฝะพะฒะพัั‚ั–.", + "duration-low-urgency-description": "ะฏะบ ะดะพะฒะณะพ ะทะฐะปะธัˆะฐัŽั‚ัŒัั ะฒะธะดะธะผะธะผะธ ัะฟะพะฒั–ั‰ะตะฝะฝั ะฝะธะทัŒะบะพะณะพ ะฟั€ั–ะพั€ะธั‚ะตั‚ัƒ.", + "duration-low-urgency-label": "ะะธะทัŒะบะฐ ั‚ะตั€ะผั–ะฝะพะฒั–ัั‚ัŒ", + "duration-normal-urgency-description": "ะฏะบ ะดะพะฒะณะพ ะทะฐะปะธัˆะฐัŽั‚ัŒัั ะฒะธะดะธะผะธะผะธ ัะฟะพะฒั–ั‰ะตะฝะฝั ะทะฒะธั‡ะฐะนะฝะพะณะพ ะฟั€ั–ะพั€ะธั‚ะตั‚ัƒ.", + "duration-normal-urgency-label": "ะ—ะฒะธั‡ะฐะนะฝะฐ ั‚ะตั€ะผั–ะฝะพะฒั–ัั‚ัŒ", + "duration-respect-expire-description": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั‚ั€ะธะฒะฐะปั–ัั‚ัŒ ะฟะพะบะฐะทัƒ, ะฒัั‚ะฐะฝะพะฒะปะตะฝะธะน ัƒ ัะฟะพะฒั–ั‰ะตะฝะฝั–.", + "duration-respect-expire-label": "ะ’ั€ะฐั…ะพะฒัƒะฒะฐั‚ะธ ั‚ั€ะธะฒะฐะปั–ัั‚ัŒ ะฟะพะบะฐะทัƒ", + "duration-title": "ะขั€ะธะฒะฐะปั–ัั‚ัŒ ัะฟะพะฒั–ั‰ะตะฝัŒ", + "history-clear-dismiss-description": "ะ’ะธะดะฐะปัั‚ะธ ัะฟะพะฒั–ั‰ะตะฝะฝั ะท ั–ัั‚ะพั€ั–ั— ะฟั–ัะปั ะทะฐะบั€ะธั‚ั‚ั.", + "history-clear-dismiss-label": "ะžั‡ะธั‰ะฐั‚ะธ ะฟั€ะธ ะทะฐะบั€ะธั‚ั‚ั–", + "history-critical-urgency-description": "ะ—ะฑะตั€ั–ะณะฐั‚ะธ ัะฟะพะฒั–ั‰ะตะฝะฝั ะท ะบั€ะธั‚ะธั‡ะฝะธะผ ะฟั€ั–ะพั€ะธั‚ะตั‚ะพะผ ะฒ ั–ัั‚ะพั€ั–ั—.", + "history-critical-urgency-label": "ะ—ะฑะตั€ั–ะณะฐั‚ะธ ะบั€ะธั‚ะธั‡ะฝัƒ ั‚ะตั€ะผั–ะฝะพะฒั–ัั‚ัŒ ะฒ ั–ัั‚ะพั€ั–ั—", + "history-desc": "ะšะตั€ัƒะฒะฐะฝะฝั ั‚ะธะผ, ัะบั– ัะฟะพะฒั–ั‰ะตะฝะฝั ะทะฑะตั€ั–ะณะฐัŽั‚ัŒัั ะฒ ั–ัั‚ะพั€ั–ั— ะทะฐะปะตะถะฝะพ ะฒั–ะด ั€ั–ะฒะฝั ั‚ะตั€ะผั–ะฝะพะฒะพัั‚ั–.", + "history-low-urgency-description": "ะ—ะฑะตั€ั–ะณะฐั‚ะธ ัะฟะพะฒั–ั‰ะตะฝะฝั ะท ะฝะธะทัŒะบะธะผ ะฟั€ั–ะพั€ะธั‚ะตั‚ะพะผ ะฒ ั–ัั‚ะพั€ั–ั—.", + "history-low-urgency-label": "ะ—ะฑะตั€ั–ะณะฐั‚ะธ ะฝะธะทัŒะบัƒ ั‚ะตั€ะผั–ะฝะพะฒั–ัั‚ัŒ ะฒ ั–ัั‚ะพั€ั–ั—", + "history-normal-urgency-description": "ะ—ะฑะตั€ั–ะณะฐั‚ะธ ัะฟะพะฒั–ั‰ะตะฝะฝั ะท ะฝะพั€ะผะฐะปัŒะฝะธะผ ะฟั€ั–ะพั€ะธั‚ะตั‚ะพะผ ะฒ ั–ัั‚ะพั€ั–ั—.", + "history-normal-urgency-label": "ะ—ะฑะตั€ั–ะณะฐั‚ะธ ะฝะพั€ะผะฐะปัŒะฝัƒ ั‚ะตั€ะผั–ะฝะพะฒั–ัั‚ัŒ ะฒ ั–ัั‚ะพั€ั–ั—", + "monitors-desc": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ัะฟะพะฒั–ั‰ะตะฝะฝั ะฝะฐ ะฟะตะฒะฝะธั… ะผะพะฝั–ั‚ะพั€ะฐั…. ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฝะฐ ะฒัั–ั…, ัะบั‰ะพ ะฝะต ะฒะธะฑั€ะฐะฝะพ.", + "rules-action-block": "ะ‘ะปะพะบ", + "rules-action-block-desc": "ะŸั€ะพะฟัƒัะบะฐั” ะฟะพะฒะฝั–ัั‚ัŽ.", + "rules-action-hide": "ะกั…ะพะฒะฐั‚ะธ", + "rules-action-hide-desc": "ะ‘ะตะท ัะฟะปะธะฒะฐัŽั‡ะธั… ะฒั–ะบะพะฝ, ะฑะตะท ะทะฒัƒะบัƒ, ะดะพะดะฐั”ั‚ัŒัั ะดะพ ั–ัั‚ะพั€ั–ั—.", + "rules-action-label": "ะ”ั–ั", + "rules-action-mute": "ะ‘ะตะท ะทะฒัƒะบัƒ", + "rules-action-mute-desc": "ะะตะผะฐั” ะทะฒัƒะบัƒ, ะฒัะต ะพะดะฝะพ ะฟะพะบะฐะทัƒั” ัะฟะปะธะฒะฐัŽั‡ะต ะฒั–ะบะฝะพ ั‚ะฐ ะฒ ั–ัั‚ะพั€ั–ั—.", + "rules-add": "ะ”ะพะดะฐั‚ะธ ะฟั€ะฐะฒะธะปะพ", + "rules-delete": "ะ’ะธะดะฐะปะธั‚ะธ ะฟั€ะฐะฒะธะปะพ", + "rules-description": "ะ—ั–ัั‚ะฐะฒะธั‚ะธ ะฝะฐะทะฒัƒ ะทะฐัั‚ะพััƒะฝะบัƒ ะฐะฑะพ ะฒะผั–ัั‚. ะŸั€ะฐะฒะธะปะฐ ะฟะตั€ะตะฒั–ั€ััŽั‚ัŒัั ะฟะพ ั‡ะตั€ะทั–, ั– ะฟะตั€ัˆะต ัะฟั–ะฒะฟะฐะดั–ะฝะฝั ะทะฐัั‚ะพัะพะฒัƒั”ั‚ัŒัั.", + "rules-edit": "ะ ะตะดะฐะณัƒะฒะฐั‚ะธ ะฟั€ะฐะฒะธะปะพ", + "rules-label": "ะŸั€ะฐะฒะธะปะฐ ะคั–ะปัŒั‚ั€ะฐั†ั–ั—", + "rules-pattern-label": "ะจะฐะฑะปะพะฝ", + "rules-pattern-placeholder": "firefox, discord, ะฐะฑะพ /regex/", + "rules-tab": "ะŸั€ะฐะฒะธะปะฐ", + "settings-always-on-top-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ัะฟะพะฒั–ั‰ะตะฝะฝั ะฟะพะฒะตั€ั… ะฟะพะฒะฝะพะตะบั€ะฐะฝะฝะธั… ะฒั–ะบะพะฝ ั‚ะฐ ั–ะฝัˆะธั… ัˆะฐั€ั–ะฒ.", + "settings-background-opacity-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฝะตะฟั€ะพะทะพั€ั–ัั‚ัŒ ั„ะพะฝัƒ ัะฟะพะฒั–ั‰ะตะฝัŒ.", + "settings-density-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ั‰ั–ะปัŒะฝั–ัั‚ัŒ ะบะฐั€ั‚ะบะธ ัะฟะพะฒั–ั‰ะตะฝัŒ.", + "settings-density-label": "ะฉั–ะปัŒะฝั–ัั‚ัŒ", + "settings-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะทะพะฒะฝั–ัˆะฝั–ะน ะฒะธะณะปัะด ั‚ะฐ ะฟะพะฒะตะดั–ะฝะบัƒ ัะฟะพะฒั–ั‰ะตะฝัŒ.", + "settings-do-not-disturb-description": "ะ’ะธะผะบะฝัƒั‚ะธ ะฒัั– ัะฟะปะธะฒะฐัŽั‡ั– ัะฟะพะฒั–ั‰ะตะฝะฝั, ะบะพะปะธ ัƒะฒั–ะผะบะฝะตะฝะพ.", + "settings-enabled-description": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฐะฑะพ ะฒะธะผะบะฝัƒั‚ะธ ะดะตะผะพะฝ ัะฟะพะฒั–ั‰ะตะฝัŒ, ะฒะธะผะฐะณะฐั” ะฟะตั€ะตะทะฐะฟัƒัะบัƒ Noctalia shell.", + "settings-enabled-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ัะฟะพะฒั–ั‰ะตะฝะฝั", + "settings-location-description": "ะ”ะต ะท'ัะฒะปััŽั‚ัŒัั ัะฟะพะฒั–ั‰ะตะฝะฝั ะฝะฐ ะตะบั€ะฐะฝั–.", + "settings-markdown-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะฒะผั–ัั‚ ัะฟะพะฒั–ั‰ะตะฝัŒ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ั„ะพั€ะผะฐั‚ัƒะฒะฐะฝะฝั Markdown.", + "settings-markdown-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ Markdown", + "sounds-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะทะฒัƒะบะพะฒั– ะตั„ะตะบั‚ะธ ั‚ะฐ ะณัƒั‡ะฝั–ัั‚ัŒ ัะฟะพะฒั–ั‰ะตะฝัŒ.", + "sounds-enabled-description": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะทะฒัƒะบะพะฒั– ะตั„ะตะบั‚ะธ ะดะปั ะฒั…ั–ะดะฝะธั… ัะฟะพะฒั–ั‰ะตะฝัŒ.", + "sounds-enabled-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะทะฒัƒะบะธ ัะฟะพะฒั–ั‰ะตะฝัŒ", + "sounds-excluded-apps-description": "ะŸั€ะพะฟัƒัะบะฐั‚ะธ ะฒั–ะดั‚ะฒะพั€ะตะฝะฝั ะฝะฐะปะฐัˆั‚ะพะฒะฐะฝะพะณะพ ะทะฒัƒะบัƒ ัะฟะพะฒั–ั‰ะตะฝัŒ ะดะปั ะฟะตะฒะฝะธั… ะฟั€ะพะณั€ะฐะผ, ัะบั– ะผะฐัŽั‚ัŒ ะฒะปะฐัะฝั– ะฒะฑัƒะดะพะฒะฐะฝั– ะทะฒัƒะบะธ.", + "sounds-excluded-apps-label": "ะ’ะธะบะปัŽั‡ะตะฝั– ะฟั€ะพะณั€ะฐะผะธ", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "ะจะปัั… ะดะพ ะทะฒัƒะบะพะฒะพะณะพ ั„ะฐะนะปัƒ, ั‰ะพ ะฒั–ะดั‚ะฒะพั€ัŽั”ั‚ัŒัั ะดะปั ัะฟะพะฒั–ั‰ะตะฝัŒ ั–ะท ะบั€ะธั‚ะธั‡ะฝะธะผ ะฟั€ั–ะพั€ะธั‚ะตั‚ะพะผ.", + "sounds-files-critical-label": "ะ—ะฒัƒะบ ะบั€ะธั‚ะธั‡ะฝะพั— ั‚ะตั€ะผั–ะฝะพะฒะพัั‚ั–", + "sounds-files-critical-select-title": "ะ’ะธะฑั€ะฐั‚ะธ ั„ะฐะนะป ะทะฒัƒะบัƒ ะบั€ะธั‚ะธั‡ะฝะพั— ั‚ะตั€ะผั–ะฝะพะฒะพัั‚ั–", + "sounds-files-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฒะปะฐัะฝั– ะทะฒัƒะบะพะฒั– ั„ะฐะนะปะธ ะดะปั ั€ั–ะทะฝะธั… ั€ั–ะฒะฝั–ะฒ ั‚ะตั€ะผั–ะฝะพะฒะพัั‚ั– ัะฟะพะฒั–ั‰ะตะฝัŒ.", + "sounds-files-low-description": "ะจะปัั… ะดะพ ะทะฒัƒะบะพะฒะพะณะพ ั„ะฐะนะปัƒ, ั‰ะพ ะฒั–ะดั‚ะฒะพั€ัŽั”ั‚ัŒัั ะดะปั ัะฟะพะฒั–ั‰ะตะฝัŒ ั–ะท ะฝะธะทัŒะบะธะผ ะฟั€ั–ะพั€ะธั‚ะตั‚ะพะผ.", + "sounds-files-low-label": "ะ—ะฒัƒะบ ะฝะธะทัŒะบะพั— ั‚ะตั€ะผั–ะฝะพะฒะพัั‚ั–", + "sounds-files-low-select-title": "ะ’ะธะฑั€ะฐั‚ะธ ั„ะฐะนะป ะทะฒัƒะบัƒ ะฝะธะทัŒะบะพั— ั‚ะตั€ะผั–ะฝะพะฒะพัั‚ั–", + "sounds-files-normal-description": "ะจะปัั… ะดะพ ะทะฒัƒะบะพะฒะพะณะพ ั„ะฐะนะปัƒ, ั‰ะพ ะฒั–ะดั‚ะฒะพั€ัŽั”ั‚ัŒัั ะดะปั ัะฟะพะฒั–ั‰ะตะฝัŒ ั–ะท ะฝะพั€ะผะฐะปัŒะฝะธะผ ะฟั€ั–ะพั€ะธั‚ะตั‚ะพะผ.", + "sounds-files-normal-label": "ะ—ะฒัƒะบ ะฝะพั€ะผะฐะปัŒะฝะพั— ั‚ะตั€ะผั–ะฝะพะฒะพัั‚ั–", + "sounds-files-normal-select-title": "ะ’ะธะฑั€ะฐั‚ะธ ั„ะฐะนะป ะทะฒัƒะบัƒ ะฝะพั€ะผะฐะปัŒะฝะพั— ั‚ะตั€ะผั–ะฝะพะฒะพัั‚ั–", + "sounds-files-placeholder": "ะ’ะฒะตะดั–ั‚ัŒ ัˆะปัั… ะดะพ ะทะฒัƒะบะพะฒะพะณะพ ั„ะฐะนะปัƒ", + "sounds-files-select-file": "ะ’ะธะฑั€ะฐั‚ะธ ะทะฒัƒะบะพะฒะธะน ั„ะฐะนะป", + "sounds-files-title": "ะ—ะฒัƒะบะพะฒั– ั„ะฐะนะปะธ", + "sounds-files-unified-description": "ะจะปัั… ะดะพ ะทะฒัƒะบะพะฒะพะณะพ ั„ะฐะนะปัƒ, ั‰ะพ ะฒั–ะดั‚ะฒะพั€ัŽั”ั‚ัŒัั ะดะปั ัะฟะพะฒั–ั‰ะตะฝัŒ.", + "sounds-files-unified-label": "ะ—ะฒัƒะบ ัะฟะพะฒั–ั‰ะตะฝะฝั", + "sounds-files-unified-select-title": "ะ’ะธะฑั€ะฐั‚ะธ ั„ะฐะนะป ะทะฒัƒะบัƒ ัะฟะพะฒั–ั‰ะตะฝะฝั", + "sounds-separate-description": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั€ั–ะทะฝั– ะทะฒัƒะบะพะฒั– ั„ะฐะนะปะธ ะดะปั ัะฟะพะฒั–ั‰ะตะฝัŒ ั–ะท ะฝะธะทัŒะบะธะผ, ะฝะพั€ะผะฐะปัŒะฝะธะผ ั‚ะฐ ะบั€ะธั‚ะธั‡ะฝะธะผ ะฟั€ั–ะพั€ะธั‚ะตั‚ะพะผ.", + "sounds-separate-label": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั€ั–ะทะฝั– ะทะฒัƒะบะธ ะดะปั ั€ั–ะทะฝะธั… ะฟั€ั–ะพั€ะธั‚ะตั‚ั–ะฒ", + "sounds-title": "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ะทะฒัƒะบัƒ", + "sounds-unavailable-description": "ะ’ัั‚ะฐะฝะพะฒั–ั‚ัŒ Qt6 Multimedia, ั‰ะพะฑ ัƒะฒั–ะผะบะฝัƒั‚ะธ ะทะฒัƒะบะธ ัะฟะพะฒั–ั‰ะตะฝัŒ.", + "sounds-unavailable-label": "ะ—ะฒัƒะบะธ ัะฟะพะฒั–ั‰ะตะฝัŒ ะฝะตะดะพัั‚ัƒะฟะฝั–", + "sounds-volume-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ั€ั–ะฒะตะฝัŒ ะณัƒั‡ะฝะพัั‚ั– ะดะปั ะทะฒัƒะบั–ะฒ ัะฟะพะฒั–ั‰ะตะฝัŒ.", + "sounds-volume-label": "ะ“ัƒั‡ะฝั–ัั‚ัŒ ะทะฒัƒะบัƒ", + "toast-battery-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟะพะฟะตั€ะตะดะถะตะฝะฝั, ะบะพะปะธ ั€ั–ะฒะตะฝัŒ ะทะฐั€ัะดัƒ ะฑะฐั‚ะฐั€ะตั— ะฟะฐะดะฐั” ะฝะธะถั‡ะต ั†ัŒะพะณะพ ะฒั–ะดัะพั‚ะบะฐ.", + "toast-battery-label": "ะŸะพะฟะตั€ะตะดะถะตะฝะฝั ะฟั€ะพ ะฑะฐั‚ะฐั€ะตัŽ", + "toast-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะทะพะฒะฝั–ัˆะฝั–ะน ะฒะธะณะปัะด ั– ะฟะพะฒะตะดั–ะฝะบัƒ ัะฟะปะธะฒะฐัŽั‡ะธั… ะฟะพะฒั–ะดะพะผะปะตะฝัŒ.", + "toast-keyboard-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ัะฟะพะฒั–ั‰ะตะฝะฝั ะฟั€ะธ ะทะผั–ะฝั– ั€ะพะทะบะปะฐะดะบะธ ะบะปะฐะฒั–ะฐั‚ัƒั€ะธ.", + "toast-keyboard-label": "ะ ะพะทะบะปะฐะดะบะฐ ะบะปะฐะฒั–ะฐั‚ัƒั€ะธ", + "toast-media-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ัะฟะปะธะฒะฐัŽั‡ะต ะฟะพะฒั–ะดะพะผะปะตะฝะฝั, ะบะพะปะธ ะทะผั–ะฝัŽั”ั‚ัŒัั ัั‚ะฐะฝ ะฒั–ะดั‚ะฒะพั€ะตะฝะฝั ะผะตะดั–ะฐ.", + "toast-media-label": "ะœะตะดั–ะฐ" + }, + "osd": { + "always-on-top-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะตะบั€ะฐะฝะฝัƒ ั–ะฝะดะธะบะฐั†ั–ัŽ ะฟะพะฒะตั€ั… ะฟะพะฒะฝะพะตะบั€ะฐะฝะฝะธั… ะฒั–ะบะพะฝ ั‚ะฐ ั–ะฝัˆะธั… ัˆะฐั€ั–ะฒ.", + "always-on-top-label": "ะ—ะฐะฒะถะดะธ ะทะฒะตั€ั…ัƒ", + "background-opacity-description": "ะšะตั€ัƒั” ะฟั€ะพะทะพั€ั–ัั‚ัŽ ั„ะพะฝัƒ OSD.", + "background-opacity-label": "ะŸั€ะพะทะพั€ั–ัั‚ัŒ ั„ะพะฝัƒ", + "description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะตะบั€ะฐะฝะฝั– ั–ะฝะดะธะบะฐั‚ะพั€ะธ, ั‚ะฐะบั– ัะบ ะฝะฐะบะปะฐะดะบะธ ะณัƒั‡ะฝะพัั‚ั– ั‚ะฐ ััะบั€ะฐะฒะพัั‚ั–.", + "duration-auto-hide-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ั‡ะฐั ะดะพ ะทะฝะธะบะฝะตะฝะฝั ะตะบั€ะฐะฝะฝะพั— ั–ะฝะดะธะบะฐั†ั–ั—.", + "duration-auto-hide-label": "ะะฒั‚ะพะฟั€ะธั…ะพะฒัƒะฒะฐะฝะฝั ั‡ะตั€ะตะท", + "duration-desc": "ะฏะบ ะดะพะฒะณะพ ะตะบั€ะฐะฝะฝะฐ ั–ะฝะดะธะบะฐั†ั–ั ะทะฐะปะธัˆะฐั”ั‚ัŒัั ะฒะธะดะธะผะพัŽ ะฟะตั€ะตะด ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะธะผ ะฟั€ะธั…ะพะฒัƒะฒะฐะฝะฝัะผ.", + "duration-title": "ะงะฐั ะฐะฒั‚ะพะฟั€ะธั…ะพะฒัƒะฒะฐะฝะฝั", + "enabled-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะทะผั–ะฝะธ ะณัƒั‡ะฝะพัั‚ั– ั‚ะฐ ััะบั€ะฐะฒะพัั‚ั– ะฒ ั€ะตะฐะปัŒะฝะพะผัƒ ั‡ะฐัั–.", + "enabled-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะตะบั€ะฐะฝะฝัƒ ั–ะฝะดะธะบะฐั†ั–ัŽ", + "events-desc": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ัะบั– ะฟะพะดั–ั— ะฒะธะบะปะธะบะฐัŽั‚ัŒ ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ะฝะฐ ะตะบั€ะฐะฝั–.", + "general-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฒะธะดะธะผั–ัั‚ัŒ ั– ะฟะพะฒะตะดั–ะฝะบัƒ ะตะบั€ะฐะฝะฝะพะณะพ ะผะตะฝัŽ (OSD).", + "location-description": "ะ”ะต ะท'ัะฒะปััŽั‚ัŒัั ะตะบั€ะฐะฝะฝั– ั–ะฝะดะธะบะฐั†ั–ั—.", + "monitors-desc": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะตะบั€ะฐะฝะฝัƒ ั–ะฝะดะธะบะฐั†ั–ัŽ ะฝะฐ ะฟะตะฒะฝะธั… ะผะพะฝั–ั‚ะพั€ะฐั…. ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฝะฐ ะฒัั–ั…, ัะบั‰ะพ ะฝะต ะฒะธะฑั€ะฐะฝะพ.", + "title": "ะ•ะบั€ะฐะฝะฝะฐ ั–ะฝะดะธะบะฐั†ั–ั", + "types-brightness-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ OSD, ะบะพะปะธ ะทะผั–ะฝัŽั”ั‚ัŒัั ััะบั€ะฐะฒั–ัั‚ัŒ ะตะบั€ะฐะฝะฐ.", + "types-brightness-label": "ะฏัะบั€ะฐะฒั–ัั‚ัŒ", + "types-custom-text-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ OSD ะดะปั ะบะพั€ะธัั‚ัƒะฒะฐั†ัŒะบะธั… ั‚ะตะบัั‚ะพะฒะธั… ะฟะพะฒั–ะดะพะผะปะตะฝัŒ ั‡ะตั€ะตะท IPC.", + "types-custom-text-label": "ะšะพั€ะธัั‚ัƒะฒะฐั†ัŒะบะธะน ั‚ะตะบัั‚", + "types-desc": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะฟะพะดั–ั—, ัะบั– ะทะฐะฟัƒัะบะฐัŽั‚ัŒ ะตะบั€ะฐะฝะฝะต ะผะตะฝัŽ.", + "types-input-volume-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ OSD, ะบะพะปะธ ะทะผั–ะฝัŽั”ั‚ัŒัั ะณัƒั‡ะฝั–ัั‚ัŒ ะผั–ะบั€ะพั„ะพะฝะฐ.", + "types-input-volume-label": "ะ’ั…ั–ะดะฝะฐ ะณัƒั‡ะฝั–ัั‚ัŒ", + "types-lockkey-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ OSD, ะบะพะปะธ ะฟะตั€ะตะผะธะบะฐัŽั‚ัŒัั Caps Lock, Num Lock ะฐะฑะพ Scroll Lock.", + "types-lockkey-label": "ะšะปะฐะฒั–ัˆั– ะฑะปะพะบัƒะฒะฐะฝะฝั", + "types-media-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะ•ะบั€ะฐะฝะฝะต ะผะตะฝัŽ (OSD) ะฟั€ะธ ะทะผั–ะฝั– ัั‚ะฐะฝัƒ ะฒั–ะดั‚ะฒะพั€ะตะฝะฝั ะผะตะดั–ะฐ (ะฒั–ะดั‚ะฒะพั€ะตะฝะฝั, ะฟะฐัƒะทะฐ, ะฟั€ะพะฟัƒัะบ).", + "types-media-label": "ะ’ั–ะดั‚ะฒะพั€ะตะฝะฝั ะผะตะดั–ะฐ", + "types-title": "ะŸะพะดั–ั—, ั‰ะพ ะทะฐะฟัƒัะบะฐัŽั‚ัŒ OSD", + "types-volume-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ OSD, ะบะพะปะธ ะทะผั–ะฝัŽั”ั‚ัŒัั ะณัƒั‡ะฝั–ัั‚ัŒ ะฐัƒะดั–ะพะฒะธั…ะพะดัƒ.", + "types-volume-label": "ะ’ะธั…ั–ะดะฝะฐ ะณัƒั‡ะฝั–ัั‚ัŒ" + }, + "plugins": { + "auto-update": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะต ะพะฝะพะฒะปะตะฝะฝั ะฟะปะฐะณั–ะฝั–ะฒ", + "auto-update-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะพะฝะพะฒะปัŽะฒะฐั‚ะธ ะฒัั– ะฟะปะฐะณั–ะฝะธ ะฟั€ะธ ะทะฐะฟัƒัะบัƒ ะพะฑะพะปะพะฝะบะธ.", + "available-description": "ะŸะตั€ะตะณะปัะดะฐะนั‚ะต ั‚ะฐ ะฒัั‚ะฐะฝะพะฒะปัŽะนั‚ะต ะฟะปะฐะณั–ะฝะธ ะท ะฝะฐะปะฐัˆั‚ะพะฒะฐะฝะธั… ะดะถะตั€ะตะป.", + "available-label": "ะ”ะพัั‚ัƒะฟะฝั– ะฟะปะฐะณั–ะฝะธ", + "available-no-plugins-description": "ะŸะตั€ะตะฒั–ั€ั‚ะต ะฒะธั…ั–ะดะฝั– ะบะพะดะธ ะฒะฐัˆะพะณะพ ะฟะปะฐะณั–ะฝะฐ ะฐะฑะพ ะพะฝะพะฒั–ั‚ัŒ ัะฟะธัะพะบ.", + "available-no-plugins-label": "ะะตะผะฐั” ะดะพัั‚ัƒะฟะฝะธั… ะฟะปะฐะณั–ะฝั–ะฒ", + "check-for-updates": "ะŸะตั€ะตะฒั–ั€ะธั‚ะธ ะพะฝะพะฒะปะตะฝะฝั", + "checking-for-updates": "ะŸะตั€ะตะฒั–ั€ะบะฐ ะพะฝะพะฒะปะตะฝัŒ...", + "collision-already-installed": "ะฆะตะน ะฟะปะฐะณั–ะฝ ะฒะถะต ะฒัั‚ะฐะฝะพะฒะปะตะฝะพ", + "collision-custom-version-exists": "ะ’ะถะต ะฒัั‚ะฐะฝะพะฒะปะตะฝะพ ะฒะปะฐัะฝัƒ ะฒะตั€ัั–ัŽ ะท \"{source}\"", + "collision-official-version-exists": "ะžั„ั–ั†ั–ะนะฝะฐ ะฒะตั€ัั–ั ั†ัŒะพะณะพ ะฟะปะฐะณั–ะฝะฐ ะฒะถะต ะฒัั‚ะฐะฝะพะฒะปะตะฝะฐ", + "development-disable": "ะ’ะธะผะบะฝัƒั‚ะธ ั€ะตะถะธะผ ั€ะพะทั€ะพะฑะบะธ ะดะปั ั†ัŒะพะณะพ ะฟะปะฐะณั–ะฝะฐ", + "development-enable": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ั€ะตะถะธะผ ั€ะพะทั€ะพะฑะบะธ ะดะปั ั†ัŒะพะณะพ ะฟะปะฐะณั–ะฝะฐ.", + "filter-downloaded": "ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะพ", + "filter-not-downloaded": "ะะต ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะพ", + "filter-tags-description": "ะคั–ะปัŒั‚ั€ัƒะฒะฐั‚ะธ ะฟะปะฐะณั–ะฝะธ ะทะฐ ะบะฐั‚ะตะณะพั€ั–ั”ัŽ ะฐะฑะพ ัั‚ะฐั‚ัƒัะพะผ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั.", + "filter-tags-label": "ะขะตะณะธ", + "hot-reload-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟะตั€ะตะทะฐะฒะฐะฝั‚ะฐะถัƒะฒะฐั‚ะธ ะฟะปะฐะณั–ะฝะธ ะฟั€ะธ ะทะผั–ะฝั– ั—ั…ะฝั–ั… ั„ะฐะนะปั–ะฒ. ะšะพั€ะธัะฝะพ ะดะปั ั€ะพะทั€ะพะฑะบะธ ะฟะปะฐะณั–ะฝั–ะฒ.", + "hot-reload-label": "ะ“ะฐั€ัั‡ะต ะฟะตั€ะตะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั (ั€ะตะถะธะผ ั€ะพะทั€ะพะฑะบะธ)", + "hot-reloaded": "ะŸะตั€ะตะทะฐะฒะฐะฝั‚ะฐะถะตะฝะพ ะฟะปะฐะณั–ะฝ: {name}", + "install-error": "ะะต ะฒะดะฐะปะพัั ะฒัั‚ะฐะฝะพะฒะธั‚ะธ: {error}", + "install-incompatible": "{plugin} ะฒะธะผะฐะณะฐั” Noctalia v{version} ะฐะฑะพ ะฒะธั‰ะต", + "install-success": "ะฃัะฟั–ัˆะฝะพ ะฒัั‚ะฐะฝะพะฒะปะตะฝะพ {plugin}", + "installed-description": "ะšะตั€ัƒะนั‚ะต ั‚ะฐ ะฝะฐะปะฐัˆั‚ะพะฒัƒะนั‚ะต ะฒัั– ะปะพะบะฐะปัŒะฝะพ ะฒัั‚ะฐะฝะพะฒะปะตะฝั– ะฟะปะฐะณั–ะฝะธ.", + "installed-label": "ะ’ัั‚ะฐะฝะพะฒะปะตะฝั– ะฟะปะฐะณั–ะฝะธ", + "installed-no-plugins-description": "ะ’ัั‚ะฐะฝะพะฒั–ั‚ัŒ ะฟะปะฐะณั–ะฝะธ ะท ั€ะพะทะดั–ะปัƒ \"ะ”ะพัั‚ัƒะฟะฝะธะน\".", + "installed-no-plugins-label": "ะะตะผะฐั” ะฒัั‚ะฐะฝะพะฒะปะตะฝะธั… ะฟะปะฐะณั–ะฝั–ะฒ", + "installing": "ะ’ัั‚ะฐะฝะพะฒะปะตะฝะฝั {plugin}...", + "notify-updates": "ะกะฟะพะฒั–ั‰ะตะฝะฝั ะฟั€ะพ ะพะฝะพะฒะปะตะฝะฝั ะฟะปะฐะณั–ะฝั–ะฒ", + "notify-updates-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ัะฟะปะธะฒะฐัŽั‡ะต ัะฟะพะฒั–ั‰ะตะฝะฝั, ะบะพะปะธ ะดะพัั‚ัƒะฟะฝั– ะฝะพะฒั– ะพะฝะพะฒะปะตะฝะฝั ะฟะปะฐะณั–ะฝั–ะฒ.", + "open-plugin-page": "ะ’ั–ะดะบั€ะธั‚ะธ ัั‚ะพั€ั–ะฝะบัƒ ะฟะปะฐะณั–ะฝะฐ", + "open-plugins-tab": "ะ’ั–ะดะบั€ะธั‚ะธ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ะฟะปะฐะณั–ะฝั–ะฒ", + "plugin-settings-title": "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั {plugin}", + "refresh-refreshing": "ะžะฝะพะฒะปะตะฝะฝั ัะฟะธัะบัƒ ะฟะปะฐะณั–ะฝั–ะฒ...", + "refresh-tooltip": "ะžะฝะพะฒะธั‚ะธ ะดะพัั‚ัƒะฟะฝั– ะฟะปะฐะณั–ะฝะธ", + "settings-error-not-loaded": "ะŸะปะฐะณั–ะฝ ะฝะต ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะพ", + "settings-saved": "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ะฟะปะฐะณั–ะฝะฐ ะทะฑะตั€ะตะถะตะฝะพ", + "settings-tooltip": "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ะฟะปะฐะณั–ะฝะฐ", + "source-custom": "ะ’ะปะฐัะฝะต ะดะถะตั€ะตะปะพ", + "sources-add-custom": "ะ”ะพะดะฐั‚ะธ ะฒะปะฐัะฝะธะน ั€ะตะฟะพะทะธั‚ะพั€ั–ะน", + "sources-add-dialog-description": "ะ”ะพะดะฐั‚ะธ ั€ะตะฟะพะทะธั‚ะพั€ั–ะน GitHub ัะบ ะดะถะตั€ะตะปะพ ะฟะปะฐะณั–ะฝั–ะฒ.", + "sources-add-dialog-error": "ะะต ะฒะดะฐะปะพัั ะดะพะดะฐั‚ะธ ะดะถะตั€ะตะปะพ ะฟะปะฐะณั–ะฝั–ะฒ", + "sources-add-dialog-name": "ะะฐะทะฒะฐ ั€ะตะฟะพะทะธั‚ะพั€ั–ัŽ", + "sources-add-dialog-name-placeholder": "ะœะพั— ะฒะปะฐัะฝั– ะฟะปะฐะณั–ะฝะธ", + "sources-add-dialog-success": "ะ”ะถะตั€ะตะปะพ ะฟะปะฐะณั–ะฝะฐ ัƒัะฟั–ัˆะฝะพ ะดะพะดะฐะฝะพ", + "sources-add-dialog-title": "ะ”ะพะดะฐั‚ะธ ะดะถะตั€ะตะปะพ ะฟะปะฐะณั–ะฝะฐ", + "sources-add-dialog-url": "URL ั€ะตะฟะพะทะธั‚ะพั€ั–ัŽ", + "sources-description": "ะšะตั€ัƒะฒะฐะฝะฝั ั€ะตะฟะพะทะธั‚ะพั€ั–ัะผะธ ะฟะปะฐะณั–ะฝั–ะฒ.", + "sources-placeholder": "ะœั–ะน ะบั€ัƒั‚ะธะน ั€ะตะฟะพะทะธั‚ะพั€ั–ะน", + "sources-remove-tooltip": "ะ’ะธะดะฐะปะธั‚ะธ ะดะถะตั€ะตะปะพ ะฟะปะฐะณั–ะฝะฐ", + "title": "ะŸะปะฐะณั–ะฝะธ", + "translations-reloaded": "ะŸะตั€ะตะบะปะฐะดะธ ะฟะตั€ะตะทะฐะฒะฐะฝั‚ะฐะถะตะฝะพ: {name}", + "uninstall-dialog-description": "ะ’ะธ ะฒะฟะตะฒะฝะตะฝั–, ั‰ะพ ั…ะพั‡ะตั‚ะต ะฒะธะดะฐะปะธั‚ะธ {plugin}? ะฆะต ะฟั€ะธะทะฒะตะดะต ะดะพ ะฒะธะดะฐะปะตะฝะฝั ะฒัั–ั… ะดะฐะฝะธั… ะฟะปะฐะณั–ะฝะฐ.", + "uninstall-dialog-title": "ะ’ะธะดะฐะปะธั‚ะธ ะฟะปะฐะณั–ะฝ", + "uninstall-error": "ะะต ะฒะดะฐะปะพัั ะฒะธะดะฐะปะธั‚ะธ: {error}", + "uninstall-success": "ะฃัะฟั–ัˆะฝะพ ะฒะธะดะฐะปะตะฝะพ {plugin}", + "uninstalling": "ะ’ะธะดะฐะปะตะฝะฝั {plugin}...", + "update-all": "ะžะฝะพะฒะธั‚ะธ ะฒัะต ({count})", + "update-all-success": "ะฃัั– ะฟะปะฐะณั–ะฝะธ ัƒัะฟั–ัˆะฝะพ ะพะฝะพะฒะปะตะฝะพ", + "update-available": "ะ”ะพัั‚ัƒะฟะฝะต ะฝะพะฒะต ะพะฝะพะฒะปะตะฝะฝั ะฟะปะฐะณั–ะฝะฐ", + "update-available-plural": "ะ”ะพัั‚ัƒะฟะฝั– ะฝะพะฒั– ะพะฝะพะฒะปะตะฝะฝั ะฟะปะฐะณั–ะฝั–ะฒ ({count})", + "update-error": "ะะต ะฒะดะฐะปะพัั ะพะฝะพะฒะธั‚ะธ ะฟะปะฐะณั–ะฝ: {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (ะฟะพั‚ั€ั–ะฑะฝะพ Noctalia v{required})", + "update-success": "ะžะฝะพะฒะปะตะฝะพ {plugin} ะดะพ ะฒะตั€ัั–ั— {version}", + "update-version": "v{current} โ†’ v{new}", + "updating": "ะžะฝะพะฒะปะตะฝะฝั..." + }, + "region": { + "title": "ะ ะตะณั–ะพะฝ" + }, + "session-menu": { + "countdown-duration-description": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ, ัะบ ะดะพะฒะณะพ ั‚ั€ะธะฒะฐั” ั‚ะฐะนะผะตั€ ะทะฒะพั€ะพั‚ะฝะพะณะพ ะฒั–ะดะปั–ะบัƒ ะฟะตั€ะตะด ะฒะธะบะพะฝะฐะฝะฝัะผ ะดั–ะน ะถะธะฒะปะตะฝะฝั.", + "countdown-duration-label": "ะขั€ะธะฒะฐะปั–ัั‚ัŒ ะทะฒะพั€ะพั‚ะฝะพะณะพ ะฒั–ะดะปั–ะบัƒ", + "enable-countdown-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ั‚ะฐะนะผะตั€ ะทะฒะพั€ะพั‚ะฝะพะณะพ ะฒั–ะดะปั–ะบัƒ ะฟะตั€ะตะด ะฒะธะบะพะฝะฐะฝะฝัะผ ะดั–ะน ะถะธะฒะปะตะฝะฝั.", + "enable-countdown-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ั‚ะฐะนะผะตั€ ะทะฒะพั€ะพั‚ะฝะพะณะพ ะฒั–ะดะปั–ะบัƒ", + "entries-desc": "ะะฐะปะฐัˆั‚ัƒะฒะฐั‚ะธ, ัะบั– ะดั–ั— ะถะธะฒะปะตะฝะฝั ะฒั–ะดะพะฑั€ะฐะถะฐัŽั‚ัŒัั ะฒ ะผะตะฝัŽ ัะตะฐะฝััƒ ั‚ะฐ ะฒ ัะบะพะผัƒ ะฟะพั€ัะดะบัƒ.", + "entries-title": "ะ”ั–ั— ะถะธะฒะปะตะฝะฝั", + "entry-settings-command-description": "ะกะฟะตั†ั–ะฐะปัŒะฝะฐ ะบะพะผะฐะฝะดะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ั†ั–ั”ั— ะดั–ั—. ะ—ะฐะปะธัˆั‚ะต ะฟะพะปะต ะฟะพั€ะพะถะฝั–ะผ, ั‰ะพะฑ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ัะธัั‚ะตะผะฝัƒ ะบะพะผะฐะฝะดัƒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ.", + "entry-settings-command-placeholder": "ะฝะฐะฟั€. systemctl poweroff", + "entry-settings-default-command-lock": "ะ’ะฝัƒั‚ั€ั–ัˆะฝั–ะน ะตะบั€ะฐะฝ ะฑะปะพะบัƒะฒะฐะฝะฝั (ะฑะตะท ะบะพะผะฐะฝะดะธ)", + "entry-settings-default-command-logout": "ะ’ะฝัƒั‚ั€ั–ัˆะฝั–ะน ะฒะธั…ั–ะด ะท ัะธัั‚ะตะผะธ (ะฑะตะท ะบะพะผะฐะฝะดะธ)", + "entry-settings-default-info-description": "ะฏะบั‰ะพ ะฝะต ะฒะบะฐะทะฐะฝะพ ะถะพะดะฝะพั— ะฒะปะฐัะฝะพั— ะบะพะผะฐะฝะดะธ, ะฑัƒะดะต ะฒะธะบะพั€ะธัั‚ะฐะฝะพ ัะธัั‚ะตะผะฝัƒ ะบะพะผะฐะฝะดัƒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ.", + "entry-settings-default-info-label": "ะšะพะผะฐะฝะดะฐ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ", + "entry-settings-keybind-description": "ะŸั€ะธะทะฝะฐั‡ั‚ะต ะบะพะผะฑั–ะฝะฐั†ั–ัŽ ะบะปะฐะฒั–ัˆ ะดะปั ะทะฐะฟัƒัะบัƒ ั†ั–ั”ั— ะดั–ั—, ะบะพะปะธ ะผะตะฝัŽ ัะตัั–ั— ะฒั–ะดะบั€ะธั‚ะพ.", + "entry-settings-keybind-placeholder": "ะะฐั‚ะธัะฝั–ั‚ัŒ ะ—ะฐะฟะธั, ั‰ะพะฑ ะฟั€ะธะทะฝะฐั‡ะธั‚ะธ ะบะปะฐะฒั–ัˆัƒ...", + "entry-settings-keybind-recording": "ะ—ะฐะฟะธั... ะะฐั‚ะธัะฝั–ั‚ัŒ ะฑัƒะดัŒ-ัะบัƒ ะบะพะผะฑั–ะฝะฐั†ั–ัŽ ะบะปะฐะฒั–ัˆ", + "entry-settings-title": "ะะฐะปะฐัˆั‚ัƒะฒะฐั‚ะธ {entry}", + "entry-settings-tooltip": "ะะฐะปะฐัˆั‚ัƒะฒะฐั‚ะธ ะบะพะผะฐะฝะดัƒ", + "general-desc": "ะะฐะปะฐัˆั‚ัƒะฒะฐั‚ะธ ะฟะพะฒะตะดั–ะฝะบัƒ ั‚ะฐ ะทะพะฒะฝั–ัˆะฝั–ะน ะฒะธะณะปัะด ะฟะฐะฝะตะปั– ะผะตะฝัŽ ัะตะฐะฝััƒ.", + "large-buttons-layout-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ัะฟะพัั–ะฑ ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ะบะฝะพะฟะพะบ ะผะตะฝัŽ ัะตะฐะฝััƒ.", + "large-buttons-layout-label": "ะ ะพะทะบะปะฐะดะบะฐ ะท ะฒะตะปะธะบะธะผะธ ะบะฝะพะฟะบะฐะผะธ", + "large-buttons-style-description": "ะ’ั–ะดะพะฑั€ะฐะทะธั‚ะธ ะผะตะฝัŽ ัะตะฐะฝััƒ ะฒะตะปะธะบะธะผะธ ะบะฝะพะฟะบะฐะผะธ ัƒ ะฒะธะณะปัะดั– ัั–ั‚ะบะธ.", + "large-buttons-style-label": "ะกั‚ะธะปัŒ ะฒะตะปะธะบะธั… ะบะฝะพะฟะพะบ", + "position-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ะดะต ะท'ัะฒะปัั”ั‚ัŒัั ะฟะฐะฝะตะปัŒ ะผะตะฝัŽ ัะตะฐะฝััƒ ะฟั€ะธ ะฒั–ะดะบั€ะธั‚ั‚ั–.", + "show-header-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะทะฐะณะพะปะพะฒะพะบ ั‚ะฐ ะบะฝะพะฟะบัƒ ะทะฐะบั€ะธั‚ั‚ั ะฒ ะฒะตั€ั…ะฝั–ะน ั‡ะฐัั‚ะธะฝั– ะผะตะฝัŽ ัะตะฐะฝััƒ.", + "show-header-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะทะฐะณะพะปะพะฒะพะบ", + "show-keybinds-description": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟั–ะดะบะฐะทะบะธ ั‰ะพะดะพ ะบะพะผะฑั–ะฝะฐั†ั–ะน ะบะปะฐะฒั–ัˆ ัƒ ะฟะฐั€ะฐะผะตั‚ั€ะฐั… ัะตะฐะฝััƒ.", + "show-keybinds-label": "ะŸะพะบะฐะทะฐั‚ะธ ะบะพะผะฑั–ะฝะฐั†ั–ั— ะบะปะฐะฒั–ัˆ" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "ะ—ะฐะปะธัˆะฐั‚ะธ ะฒั–ะดะถะตั‚ะธ ั€ะพะฑะพั‡ะพะณะพ ัั‚ะพะปัƒ ะฒะธะดะธะผะธะผะธ, ะฟะพะบะธ ั€ะตะถะธะผ ะฟั€ะพะดัƒะบั‚ะธะฒะฝะพัั‚ั– Noctalia ัƒะฒั–ะผะบะฝะตะฝะพ.", + "noctalia-performance-disable-desktop-widgets-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฒั–ะดะถะตั‚ะธ ั€ะพะฑะพั‡ะพะณะพ ัั‚ะพะปัƒ ะฒ ั€ะตะถะธะผั– ะฟั€ะพะดัƒะบั‚ะธะฒะฝะพัั‚ั–", + "noctalia-performance-disable-wallpaper-description": "ะ—ะฐะปะธัˆะฐะนั‚ะต ัˆะฟะฐะปะตั€ะธ ั€ะพะฑะพั‡ะพะณะพ ัั‚ะพะปัƒ, ะพะณะปัะดัƒ ั‚ะฐ ะตะบั€ะฐะฝะฐ ะฑะปะพะบัƒะฒะฐะฝะฝั ะฒะธะดะธะผะธะผะธ, ะบะพะปะธ ั€ะตะถะธะผ ะฟั€ะพะดัƒะบั‚ะธะฒะฝะพัั‚ั– Noctalia ัƒะฒั–ะผะบะฝะตะฝะพ.", + "noctalia-performance-disable-wallpaper-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฒั–ะดั‚ะฒะพั€ะตะฝะฝั ัˆะฟะฐะปะตั€ ัƒ ั€ะตะถะธะผั– ะฟั€ะพะดัƒะบั‚ะธะฒะฝะพัั‚ั–", + "title": "ะกะธัั‚ะตะผะฐ" + }, + "system-monitor": { + "critical-color-label": "ะšั€ะธั‚ะธั‡ะฝะธะน ะบะพะปั–ั€", + "custom-highlight-colors-title-label": "ะ’ะปะฐัะฝั– ะบะพะปัŒะพั€ะธ ะฟั–ะดัะฒั–ั‡ัƒะฒะฐะฝะฝั", + "disk-available-label": "ะ”ะพัั‚ัƒะฟะฝะพ ะผั–ัั†ั ะฝะฐ ะดะธัะบัƒ", + "disk-section-label": "ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ะดะธัะบะฐ", + "enable-dgpu-monitoring-description": "ะฃะฒะฐะณะฐ: ะฆะต ั€ะพะทะฑัƒะดะธั‚ัŒ ะฒะฐัˆ ะดะธัะบั€ะตั‚ะฝะธะน ะณั€ะฐั„ั–ั‡ะฝะธะน ะฟั€ะพั†ะตัะพั€ (NVIDIA/AMD), ั‰ะพ ะผะพะถะต ะทะฝะฐั‡ะฝะพ ะฒะฟะปะธะฝัƒั‚ะธ ะฝะฐ ั‡ะฐั ั€ะพะฑะพั‚ะธ ะฐะบัƒะผัƒะปัั‚ะพั€ะฐ ะฝะฐ ะฝะพัƒั‚ะฑัƒะบะฐั… ะท ะณั–ะฑั€ะธะดะฝะพัŽ ะณั€ะฐั„ั–ะบะพัŽ.", + "enable-dgpu-monitoring-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะผะพะฝั–ั‚ะพั€ะธะฝะณ ะดะธัะบั€ะตั‚ะฝะพะณะพ GPU", + "external-monitor-description": "ะ’ะฒะตะดั–ั‚ัŒ ะบะพะผะฐะฝะดัƒ ะฐะฑะพ ัˆะปัั… ะดะพ ะฟั€ะพะณั€ะฐะผะธ ะดะปั ะทะฐะฟัƒัะบัƒ ะฟั€ะธ ะฐะบั‚ะธะฒะฐั†ั–ั— ะทะพะฒะฝั–ัˆะฝัŒะพั— ะฟั€ะพะณั€ะฐะผะธ ะผะพะฝั–ั‚ะพั€ะธะฝะณัƒ ัะธัั‚ะตะผะธ.", + "external-monitor-label": "ะšะพะผะฐะฝะดะฐ ะทะพะฒะฝั–ัˆะฝัŒะพะณะพ ะผะพะฝั–ั‚ะพั€ะฐ ัะธัั‚ะตะผะธ", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฟะพะฒะตะดั–ะฝะบัƒ ั‚ะฐ ะทะพะฒะฝั–ัˆะฝั–ะน ะฒะธะณะปัะด ัะธัั‚ะตะผะฝะพะณะพ ะผะพะฝั–ั‚ะพั€ะฐ.", + "gpu-section-label": "ะขะตะผะฟะตั€ะฐั‚ัƒั€ะฐ GPU", + "highlight-colors-section-label": "ะšะพะปัŒะพั€ะธ ะฟั–ะดัะฒั–ั‡ัƒะฒะฐะฝะฝั", + "polling-interval-label": "ะ†ะฝั‚ะตั€ะฒะฐะป ะพะฟะธั‚ัƒะฒะฐะฝะฝั", + "polling-section-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต, ัะบ ั‡ะฐัั‚ะพ ะพะฝะพะฒะปัŽั”ั‚ัŒัั ะบะพะถะฝะฐ ัะธัั‚ะตะผะฝะฐ ะผะตั‚ั€ะธะบะฐ.", + "polling-section-label": "ะ†ะฝั‚ะตั€ะฒะฐะปะธ ะพะฟะธั‚ัƒะฒะฐะฝะฝั", + "threshold-critical": "ะšั€ะธั‚ะธั‡ะฝะธะน", + "threshold-warning": "ะŸะพะฟะตั€ะตะดะถะตะฝะฝั", + "thresholds-section-description": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฟะพั€ะพะณะธ ะฟะพะฟะตั€ะตะดะถะตะฝะฝั/ะบั€ะธั‚ะธั‡ะฝั– ะฟะพั€ะพะณะธ ั‚ะฐ ั–ะฝั‚ะตั€ะฒะฐะปะธ ะพะฟะธั‚ัƒะฒะฐะฝะฝั ะดะปั ะบะพะถะฝะพะณะพ ัะธัั‚ะตะผะฝะพะณะพ ะฟะพะบะฐะทะฝะธะบะฐ.", + "use-custom-highlight-colors-description": "ะฏะบั‰ะพ ะฒะธะผะบะฝะตะฝะพ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒัั ะบะพะปัŒะพั€ะธ ะฟั–ะดัะฒั–ั‡ัƒะฒะฐะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ั‚ะตะผะธ.", + "use-custom-highlight-colors-label": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฒะปะฐัะฝั– ะบะพะปัŒะพั€ะธ ะฟั–ะดัะฒั–ั‡ัƒะฒะฐะฝะฝั", + "warning-color-label": "ะšะพะปั–ั€ ะฟะพะฟะตั€ะตะดะถะตะฝะฝั" + }, + "user-interface": { + "allow-panels-without-bar-description": "ะฏะบั‰ะพ ะฒะฒั–ะผะบะฝะตะฝะพ, ะฟะฐะฝะตะปั– ะผะพะถัƒั‚ัŒ ะฒั–ะดะบั€ะธะฒะฐั‚ะธัั ะฝะฐ ะฑัƒะดัŒ-ัะบะพะผัƒ ะตะบั€ะฐะฝั–. ะฏะบั‰ะพ ะฒะธะผะบะฝะตะฝะพ, ะฟะฐะฝะตะปั– ะฒั–ะดะบั€ะธะฒะฐั‚ะธะผัƒั‚ัŒัั ะปะธัˆะต ะฝะฐ ะตะบั€ะฐะฝะฐั…, ะดะต ั” ะฟะฐะฝะตะปัŒ ะทะฐะฒะดะฐะฝัŒ, ั‰ะพ ะผะพะถะต ะทะผะตะฝัˆะธั‚ะธ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะฟะฐะผ'ัั‚ั–.", + "allow-panels-without-bar-label": "ะ”ะพะทะฒะพะปะธั‚ะธ ะฟะฐะฝะตะปั– ะฝะฐ ะตะบั€ะฐะฝะฐั… ะฑะตะท ะฟะฐะฝะตะปั– ะทะฐะฒะดะฐะฝัŒ", + "animation-disable-description": "ะ’ะธะผะบะฝัƒั‚ะธ ะฒัั– ะฐะฝั–ะผะฐั†ั–ั— ะดะปั ัˆะฒะธะดัˆะพะณะพ, ะฑั–ะปัŒัˆ ะฒั–ะดะณัƒะบะพะฒะพะณะพ ะดะพัะฒั–ะดัƒ.", + "animation-disable-label": "ะ’ะธะผะบะฝัƒั‚ะธ ะฐะฝั–ะผะฐั†ั–ั— ั–ะฝั‚ะตั€ั„ะตะนััƒ", + "animation-speed-description": "ะะฐะปะฐัˆั‚ัƒะฒะฐั‚ะธ ะณะปะพะฑะฐะปัŒะฝัƒ ัˆะฒะธะดะบั–ัั‚ัŒ ะฐะฝั–ะผะฐั†ั–ั—.", + "animation-speed-label": "ะจะฒะธะดะบั–ัั‚ัŒ ะฐะฝั–ะผะฐั†ั–ั—", + "animation-speed-reset": "ะกะบะธะฝัƒั‚ะธ ัˆะฒะธะดะบั–ัั‚ัŒ ะฐะฝั–ะผะฐั†ั–ั—", + "appearance-desc": "ะะฐะปะฐัˆั‚ะพะฒัƒะนั‚ะต ะฒั–ะทัƒะฐะปัŒะฝั– ะตะปะตะผะตะฝั‚ะธ, ั‚ะฐะบั– ัะบ ะฟั–ะดะบะฐะทะบะธ, ั€ะฐะผะบะธ ั‚ะฐ ั‚ั–ะฝั–.", + "blur-behind-description": "ะ ะพะทะผะธะฒะฐั” ะพะฑะปะฐัั‚ัŒ ะทะฐ ะฟะฐะฝะตะปัะผะธ ั‚ะฐ ัะฟะปะธะฒะฐัŽั‡ะธะผะธ ะฒั–ะบะฝะฐะผะธ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะฟั€ะพั‚ะพะบะพะป ั€ะพะทะผะธั‚ั‚ั ะบะพะผะฟะพะทะธั‚ะพั€ะฐ.", + "blur-behind-label": "ะ ะพะทะผะธั‚ั‚ั ั„ะพะฝัƒ", + "box-border-description": "ะ’ั–ะดะพะฑั€ะฐะถะฐั” ะบะพะฝั‚ัƒั€ ะฝะฐะฒะบะพะปะพ ะพะฑะปะฐัั‚ะตะน ะฒะผั–ัั‚ัƒ.", + "box-border-label": "ะšะพะฝั‚ัƒั€ ะบะพะฝั‚ะตะนะฝะตั€ะฐ", + "box-border-radius-description": "ะะฐะปะฐัˆั‚ะพะฒัƒั” ะทะฐะพะบั€ัƒะณะปะตะฝะฝั ะบัƒั‚ั–ะฒ ะพัะฝะพะฒะฝะธั… ั€ะพะทะดั–ะปั–ะฒ ะผะฐะบะตั‚ะฐ, ั‚ะฐะบะธั… ัะบ ะฑั–ั‡ะฝั– ะฟะฐะฝะตะปั–, ะบะฐั€ั‚ะบะธ ั‚ะฐ ะฟะฐะฝะตะปั– ะบะพะฝั‚ะตะฝั‚ัƒ.", + "box-border-radius-label": "ะ ะฐะดั–ัƒั ะบะพะฝั‚ะตะนะฝะตั€ะฐ", + "control-border-radius-description": "ะšะตั€ัƒั” ะบั€ะธะฒะธะทะฝะพัŽ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะธั… ะตะปะตะผะตะฝั‚ั–ะฒ, ะฒะบะปัŽั‡ะฝะพ ะท ะบะฝะพะฟะบะฐะผะธ, ะฟะตั€ะตะผะธะบะฐั‡ะฐะผะธ ั‚ะฐ ั‚ะตะบัั‚ะพะฒะธะผะธ ะฟะพะปัะผะธ.", + "control-border-radius-label": "ะ’ะฒะตะดั–ั‚ัŒ ั€ะฐะดั–ัƒั", + "desc": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฒะธะณะปัะด, ะฒั–ะดั‡ัƒั‚ั‚ั ั‚ะฐ ะฟะพะฒะตะดั–ะฝะบัƒ ั–ะฝั‚ะตั€ั„ะตะนััƒ.", + "dim-desktop-description": "ะ—ะฐั‚ะตะผะฝัŽะฒะฐั‚ะธ ั€ะพะฑะพั‡ะธะน ัั‚ั–ะป, ะบะพะปะธ ะฒั–ะดะบั€ะธั‚ั– ะฟะฐะฝะตะปั– ะฐะฑะพ ะผะตะฝัŽ.", + "dim-desktop-label": "ะŸั€ะธะณะปัƒัˆะธั‚ะธ ั€ะพะฑะพั‡ะธะน ัั‚ั–ะป", + "dimmer-opacity-description": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ั€ั–ะฒะตะฝัŒ ะฝะตะฟั€ะพะทะพั€ะพัั‚ั– ะดะปั ะทะฐั‚ะตะผะฝะตะฝะฝั ั€ะพะฑะพั‡ะพะณะพ ัั‚ะพะปัƒ.", + "dimmer-opacity-label": "ะะตะฟั€ะพะทะพั€ั–ัั‚ัŒ ะฟั€ะธะณะปัƒัˆะตะฝะพะณะพ ั€ะพะฑะพั‡ะพะณะพ ัั‚ะพะปัƒ", + "panel-background-opacity-description": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ะฟั€ะพะทะพั€ั–ัั‚ัŒ ั„ะพะฝัƒ ะดะปั ะฒัั–ั… ะฟะฐะฝะตะปะตะน (ะฟะฐะฝะตะปัŒ ะทะฐะดะฐั‡, ะฟะฐะฝะตะปัŒ ะทะฐะฟัƒัะบัƒ, ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ั‚ะพั‰ะพ...).", + "panel-background-opacity-label": "ะŸั€ะพะทะพั€ั–ัั‚ัŒ ั„ะพะฝัƒ ะฟะฐะฝะตะปะตะน", + "panels-attached-to-bar-description": "ะŸะฐะฝะตะปั– ั„ั–ะบััƒัŽั‚ัŒัั ะดะพ ัˆั‚ะฐะฝะณะธ ั‚ะฐ ะบั€ะฐั—ะฒ ะตะบั€ะฐะฝะฐ, ัั‚ะฒะพั€ัŽัŽั‡ะธ ะฑะตะทะดะพะณะฐะฝะฝะธะน ะฒะธะณะปัะด ะทั– ัั‚ะธะปัŒะฝะธะผะธ ะฟะตั€ะตะฒะตั€ะฝัƒั‚ะธะผะธ ะบัƒั‚ะฐะผะธ.", + "panels-attached-to-bar-label": "ะŸั€ะธะฒ'ัะทัƒะฒะฐั‚ะธ ะฟะฐะฝะตะปั– ะดะพ ะบั€ะฐั—ะฒ", + "panels-overlay-description": "ะ—ะฐะฑะตะทะฟะตั‡ัƒั” ะฒะธะดะธะผั–ัั‚ัŒ ะฟะฐะฝะตะปะตะน ั– ะฟะฐะฝะตะปั– ะทะฐะฒะดะฐะฝัŒ, ะฝะฐะฒั–ั‚ัŒ ะฟะพะฒะตั€ั… ะฟะพะฒะฝะพะตะบั€ะฐะฝะฝะธั… ะทะฐัั‚ะพััƒะฝะบั–ะฒ.", + "panels-overlay-label": "ะขั€ะธะผะฐั‚ะธ ะฟะฐะฝะตะปั– ั‚ะฐ ะฟะฐะฝะตะปัŒ ะทะฒะตั€ั…ัƒ", + "scaling-description": "ะ—ะผั–ะฝัŽั” ั€ะพะทะผั–ั€ ะทะฐะณะฐะปัŒะฝะพะณะพ ะบะพั€ะธัั‚ัƒะฒะฐั†ัŒะบะพะณะพ ั–ะฝั‚ะตั€ั„ะตะนััƒ, ะพะบั€ั–ะผ ะฟะฐะฝะตะปั–.", + "scaling-label": "ะœะฐััˆั‚ะฐะฑัƒะฒะฐะฝะฝั ั–ะฝั‚ะตั€ั„ะตะนััƒ", + "scrollbar-always-visible-description": "ะ—ะฐะฒะถะดะธ ะฟะพะบะฐะทัƒะฒะฐั‚ะธ ัะผัƒะณะธ ะฟั€ะพะบั€ัƒั‚ะบะธ, ะบะพะปะธ ะฒะผั–ัั‚ ะผะพะถะฝะฐ ะฟั€ะพะบั€ัƒั‡ัƒะฒะฐั‚ะธ, ะทะฐะผั–ัั‚ัŒ ั‚ะพะณะพ, ั‰ะพะฑ ะฒั–ะดะพะฑั€ะฐะถะฐั‚ะธ ั—ั… ะปะธัˆะต ะฟั€ะธ ะฝะฐะฒะตะดะตะฝะฝั–.", + "scrollbar-always-visible-label": "ะ—ะฐะฒะถะดะธ ะฟะพะบะฐะทัƒะฒะฐั‚ะธ ัะผัƒะณะธ ะฟั€ะพะบั€ัƒั‚ะบะธ", + "settings-panel-header": "ะŸะฐะฝะตะปัŒ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝัŒ", + "settings-panel-mode-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะผะฐะบะตั‚ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝัŒ (ะผะพะถะต ะทะฝะฐะดะพะฑะธั‚ะธัั ะฟะตั€ะตะทะฐะฟัƒัะบ).", + "settings-panel-mode-label": "ะ ะตะถะธะผ ะฟะฐะฝะตะปั– ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝัŒ", + "settings-panel-sidebar-card-style-description": "ะžะฑะณะพั€ั‚ะฐั” ะฑั–ั‡ะฝัƒ ะฟะฐะฝะตะปัŒ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝัŒ ัƒ ะทะฐะฟะพะฒะฝะตะฝะธะน ั„ะพะฝ ั–ะท ะทะฐะพะบั€ัƒะณะปะตะฝะธะผะธ ะบัƒั‚ะฐะผะธ.", + "settings-panel-sidebar-card-style-label": "ะกั‚ะธะปัŒ ะบะฐั€ั‚ะพะบ ะฑั–ั‡ะฝะพั— ะฟะฐะฝะตะปั–", + "shadows-description": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ั‚ั–ะฝั– ะฟั–ะด ะฟะฐะฝะตะปัะผะธ ั‚ะฐ ัะผัƒะณะฐะผะธ.", + "shadows-direction-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ะบัƒะดะธ ะฒั–ะดะบะธะดะฐั”ั‚ัŒัั ั‚ั–ะฝัŒ.", + "shadows-direction-label": "ะะฐะฟั€ัะผะพะบ ั‚ั–ะฝั–", + "shadows-label": "ะขั–ะฝั–", + "title": "ะšะพั€ะธัั‚ัƒะฒะฐั†ัŒะบะธะน ั–ะฝั‚ะตั€ั„ะตะนั", + "tooltips-description": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะฐะฑะพ ะฒะธะผะบะฝัƒั‚ะธ ะฟั–ะดะบะฐะทะบะธ ะฒ ั–ะฝั‚ะตั€ั„ะตะนัั–.", + "tooltips-label": "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟั–ะดะบะฐะทะบะธ", + "translucent-widgets-description": "ะ—ั€ะพะฑั–ั‚ัŒ ะบะฝะพะฟะบะธ, ะฒะบะปะฐะดะบะธ ั‚ะฐ ั–ะฝัˆั– ะฒั–ะดะถะตั‚ะธ ะฒัะตั€ะตะดะธะฝั– ะฟะฐะฝะตะปะตะน ะฝะฐะฟั–ะฒะฟั€ะพะทะพั€ะธะผะธ.", + "translucent-widgets-label": "ะะฐะฟั–ะฒะฟั€ะพะทะพั€ั– ะฒั–ะดะถะตั‚ะธ" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "ะะปั„ะฐะฒั–ั‚ะฝะธะน", + "automation-change-mode-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ัะบ ะฒะธะฑะธั€ะฐัŽั‚ัŒัั ัˆะฟะฐะปะตั€ะธ ะฟั€ะธ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝั–ะน ะทะผั–ะฝั–.", + "automation-change-mode-label": "ะ ะตะถะธะผ ะทะผั–ะฝะธ", + "automation-custom-interval-description": "ะ’ะฒะตะดั–ั‚ัŒ ั‡ะฐั ัะบ ะ“ะ“:ะฅะฅ (ะฝะฐะฟั€. 01:30).", + "automation-custom-interval-label": "ะ’ะปะฐัะฝะธะน ั–ะฝั‚ะตั€ะฒะฐะป", + "automation-interval-description": "ะฏะบ ั‡ะฐัั‚ะพ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะทะผั–ะฝัŽะฒะฐั‚ะธ ัˆะฟะฐะปะตั€ะธ.", + "automation-interval-label": "ะ†ะฝั‚ะตั€ะฒะฐะป ัˆะฟะฐะปะตั€", + "automation-random-wallpaper-description": "ะŸะปะฐะฝัƒะฒะฐั‚ะธ ะฒะธะฟะฐะดะบะพะฒัƒ ะทะผั–ะฝัƒ ัˆะฟะฐะปะตั€ ั‡ะตั€ะตะท ั€ะตะณัƒะปัั€ะฝั– ั–ะฝั‚ะตั€ะฒะฐะปะธ.", + "automation-scheduled-change-description": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะทะผั–ะฝัŽะฒะฐั‚ะธ ัˆะฟะฐะปะตั€ะธ ั‡ะตั€ะตะท ั€ะตะณัƒะปัั€ะฝั– ั–ะฝั‚ะตั€ะฒะฐะปะธ.", + "automation-scheduled-change-label": "ะ—ะฐะฟะปะฐะฝะพะฒะฐะฝะฐ ะทะผั–ะฝะฐ", + "look-feel-edge-smoothness-description": "ะ—ะฐัั‚ะพัะพะฒัƒั” ะผ'ัะบะธะน, ั€ะพะทะผะธั‚ะธะน ะตั„ะตะบั‚ ะดะพ ะบั€ะฐัŽ ะฟะตั€ะตั…ะพะดั–ะฒ.", + "look-feel-edge-smoothness-label": "ะŸะพะผ'ัะบัˆะตะฝะฝั ะบั€ะฐัŽ ะฟะตั€ะตั…ะพะดัƒ", + "look-feel-fill-color-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะบะพะปั–ั€ ะทะฐะฟะพะฒะฝะตะฝะฝั, ั‰ะพ ะผะพะถะต ะท'ัะฒะปัั‚ะธัั ะทะฐ ัˆะฟะฐะปะตั€ะฐะผะธ.", + "look-feel-fill-mode-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ัะบ ะทะพะฑั€ะฐะถะตะฝะฝั ะผะฐั” ะผะฐััˆั‚ะฐะฑัƒะฒะฐั‚ะธัั ะฒั–ะดะฟะพะฒั–ะดะฝะพ ะดะพ ั€ะพะทะดั–ะปัŒะฝะพั— ะทะดะฐั‚ะฝะพัั‚ั– ะผะพะฝั–ั‚ะพั€ะฐ.", + "look-feel-fill-mode-label": "ะ ะตะถะธะผ ะทะฐะฟะพะฒะฝะตะฝะฝั", + "look-feel-skip-startup-transition-description": "ะŸั€ะพะฟัƒัะบะฐั‚ะธ ะฐะฝั–ะผะฐั†ั–ัŽ ัˆะฟะฐะปะตั€ ะฟั€ะธ ะทะฐะฟัƒัะบัƒ ะพะฑะพะปะพะฝะบะธ.", + "look-feel-skip-startup-transition-label": "ะŸั€ะพะฟัƒัั‚ะธั‚ะธ ัั‚ะฐั€ั‚ะพะฒัƒ ะฐะฝั–ะผะฐั†ั–ัŽ", + "look-feel-title": "ะ—ะพะฒะฝั–ัˆะฝั–ะน ะฒะธะณะปัะด", + "look-feel-transition-duration-description": "ะขั€ะธะฒะฐะปั–ัั‚ัŒ ะฐะฝั–ะผะฐั†ั–ะน ะฟะตั€ะตั…ะพะดัƒ ะฒ ัะตะบัƒะฝะดะฐั….", + "look-feel-transition-duration-label": "ะขั€ะธะฒะฐะปั–ัั‚ัŒ ะฟะตั€ะตั…ะพะดัƒ", + "look-feel-transition-type-description": "ะขะธะฟ ะฐะฝั–ะผะฐั†ั–ั— ะฟั€ะธ ะฟะตั€ะตะผะธะบะฐะฝะฝั– ะผั–ะถ ัˆะฟะฐะปะตั€ะฐะผะธ.", + "look-feel-transition-type-label": "ะขะธะฟ ะฟะตั€ะตั…ะพะดัƒ", + "settings-clear-cache-button": "ะžั‡ะธัั‚ะธั‚ะธ ะบะตัˆ", + "settings-clear-cache-description": "ะžั‡ะธัั‚ะธั‚ะธ ะบะตัˆะพะฒะฐะฝั– ะผะฐััˆั‚ะฐะฑะพะฒะฐะฝั– ัˆะฟะฐะปะตั€ะธ, ั‰ะพะฑ ะทะฒั–ะปัŒะฝะธั‚ะธ ะผั–ัั†ะต ะฝะฐ ะดะธัะบัƒ.", + "settings-clear-cache-label": "ะšะตัˆ ัˆะฟะฐะปะตั€", + "settings-clear-cache-toast": "ะšะตัˆ ัˆะฟะฐะปะตั€ ะพั‡ะธั‰ะตะฝะพ", + "settings-desc": "ะšะตั€ัƒะนั‚ะต ัƒะฟั€ะฐะฒะปั–ะฝะฝัะผ ั‚ะฐ ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝัะผ ัˆะฟะฐะปะตั€.", + "settings-enable-management-description": "ะšะตั€ัƒะฒะฐั‚ะธ ัˆะฟะฐะปะตั€ะฐะผะธ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ Noctalia. ะ’ะธะผะบะฝั–ั‚ัŒ, ัะบั‰ะพ ะฝะฐะดะฐั”ั‚ะต ะฟะตั€ะตะฒะฐะณัƒ ั–ะฝัˆะพะผัƒ ะทะฐัั‚ะพััƒะฝะบัƒ.", + "settings-enable-management-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะบะตั€ัƒะฒะฐะฝะฝั ัˆะฟะฐะปะตั€ะฐะผะธ", + "settings-enable-overview-description": "ะ—ะฐัั‚ะพัะพะฒัƒั” ั€ะพะทะผะธั‚ั– ั‚ะฐ ะทะฐั‚ะตะผะฝะตะฝั– ัˆะฟะฐะปะตั€ะธ ะดะพ ะตะบั€ะฐะฝะฐ ะพะณะปัะดัƒ.", + "settings-enable-overview-label": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ัˆะฟะฐะปะตั€ะธ ะพะณะปัะดัƒ", + "settings-folder-description": "ะจะปัั… ะดะพ ะพัะฝะพะฒะฝะพั— ั‚ะตะบะธ ัˆะฟะฐะปะตั€.", + "settings-folder-label": "ะขะตะบะฐ ัˆะฟะฐะปะตั€", + "settings-hide-wallpaper-filenames-tooltip-hide": "ะŸั€ะธั…ะพะฒะฐั‚ะธ ั–ะผะตะฝะฐ ั„ะฐะนะปั–ะฒ", + "settings-hide-wallpaper-filenames-tooltip-show": "ะ’ั–ะดะพะฑั€ะฐะถะฐั‚ะธ ั–ะผะตะฝะฐ ั„ะฐะนะปั–ะฒ", + "settings-monitor-specific-description": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ั€ั–ะทะฝั– ั‚ะตะบะธ ัˆะฟะฐะปะตั€ ะดะปั ะบะพะถะฝะพะณะพ ะผะพะฝั–ั‚ะพั€ะฐ.", + "settings-monitor-specific-label": "ะžะบั€ะตะผั– ั‚ะตะบะธ ะดะปั ะผะพะฝั–ั‚ะพั€ั–ะฒ", + "settings-monitor-specific-tooltip": "ะขะตะบะฐ ัˆะฟะฐะปะตั€ ะผะพะฝั–ั‚ะพั€ะฐ", + "settings-overview-blur-strength-description": "ะ—ะฐัั‚ะพัะพะฒัƒั” ัะธะปัƒ ั€ะพะทะผะธั‚ั‚ั ะดะพ ะพะณะปัะดัƒ.", + "settings-overview-blur-strength-label": "ะกะธะปะฐ ั€ะพะทะผะธั‚ั‚ั ะพะณะปัะดัƒ", + "settings-overview-tint-description": "ะ—ะฐัั‚ะพัะพะฒัƒั” ั–ะฝั‚ะตะฝัะธะฒะฝั–ัั‚ัŒ ะฒั–ะดั‚ั–ะฝะบัƒ ะดะพ ะพะณะปัะดัƒ.", + "settings-overview-tint-label": "ะ†ะฝั‚ะตะฝัะธะฒะฝั–ัั‚ัŒ ั‚ะพะฝัƒะฒะฐะฝะฝั ะพะณะปัะดัƒ", + "settings-recursive-search-description": "ะขะฐะบะพะถ ัˆัƒะบะฐั‚ะธ ัˆะฟะฐะปะตั€ะธ ะฒ ะฟั–ะดั‚ะตะบะฐั… ั‚ะตะบะธ ัˆะฟะฐะปะตั€.", + "settings-recursive-search-label": "ะจัƒะบะฐั‚ะธ ะฒ ะฟั–ะดั‚ะตะบะฐั…", + "settings-select-monitor-folder": "ะ’ะธะฑั€ะฐั‚ะธ ั‚ะตะบัƒ ัˆะฟะฐะปะตั€ ะผะพะฝั–ั‚ะพั€ะฐ", + "settings-selector-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ัะฒะพั— ัˆะฟะฐะปะตั€ะธ.", + "settings-selector-position-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ, ะดะต ะฒั–ะดะพะฑั€ะฐะถะฐั‚ะธะผะตั‚ัŒัั ะฟะฐะฝะตะปัŒ ะฒะธะฑะพั€ัƒ ัˆะฟะฐะปะตั€.", + "settings-show-hidden-files-tooltip-hide": "ะŸั€ะธั…ะพะฒะฐั‚ะธ ะฟั€ะธั…ะพะฒะฐะฝั– ั„ะฐะนะปะธ", + "settings-show-hidden-files-tooltip-show": "ะŸะพะบะฐะทะฐั‚ะธ ะฟั€ะธั…ะพะฒะฐะฝั– ั„ะฐะนะปะธ", + "settings-title": "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ัˆะฟะฐะปะตั€", + "settings-use-original-images-description": "ะŸั€ะพะฟัƒัะบะฐั‚ะธ ะทะผั–ะฝัƒ ั€ะพะทะผั–ั€ัƒ ัˆะฟะฐะปะตั€ ะฟะตั€ะตะด ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝัะผ. ะ•ะบะพะฝะพะผะธั‚ัŒ ะผั–ัั†ะต ะฝะฐ ะดะธัะบัƒ ั‚ะฐ ะทะผะตะฝัˆัƒั” ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั CPU ะฟั–ะด ั‡ะฐั ะทะผั–ะฝะธ ัˆะฟะฐะปะตั€, ะฐะปะต ะผะพะถะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฑั–ะปัŒัˆะต ะฟะฐะผ'ัั‚ั– ะดะปั ะดัƒะถะต ะฒะตะปะธะบะธั… ะทะพะฑั€ะฐะถะตะฝัŒ.", + "settings-use-original-images-label": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะพั€ะธะณั–ะฝะฐะปัŒะฝั– ะทะพะฑั€ะฐะถะตะฝะฝั", + "settings-view-mode-description": "ะžะฑะตั€ั–ั‚ัŒ ัะฟะพัั–ะฑ ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ัˆะฟะฐะปะตั€ ะท ะฒะฐัˆะพะณะพ ะบะฐั‚ะฐะปะพะณัƒ.", + "settings-view-mode-label": "ะ ะตะถะธะผ ะฟะตั€ะตะณะปัะดัƒ", + "view-mode-browse": "ะŸะตั€ะตะณะปัะด ะบะฐั‚ะฐะปะพะณั–ะฒ", + "view-mode-cycle-tooltip": "ะ ะตะถะธะผ ะฟะตั€ะตะณะปัะดัƒ: {mode} (ะฝะฐั‚ะธัะฝั–ั‚ัŒ, ั‰ะพะฑ ะทะผั–ะฝะธั‚ะธ)", + "view-mode-recursive": "ะ—ะฒะตะดะตะฝั– ะฟั–ะดะบะฐั‚ะฐะปะพะณะธ", + "view-mode-single": "ะšะพั€ะตะฝะตะฒะธะน ะบะฐั‚ะฐะปะพะณ" + } + }, + "placeholders": { + "add-new-keybind": "ะ”ะพะดะฐั‚ะธ ะฝะพะฒัƒ ะฟั€ะธะฒ'ัะทะบัƒ ะบะปะฐะฒั–ัˆ", + "command-example": "echo \"ะŸั€ะธะฒั–ั‚, ะกะฒั–ั‚\"", + "enter-command": "ะ’ะฒะตะดั–ั‚ัŒ ะบะพะผะฐะฝะดัƒ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั (ะทะฐัั‚ะพััƒะฝะพะบ ะฐะฑะพ ะฒะปะฐัะฝะธะน ัะบั€ะธะฟั‚)", + "enter-ipc-identifier": "ะ’ะฒะตะดั–ั‚ัŒ ัƒะฝั–ะบะฐะปัŒะฝะธะน ั–ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ ะดะปั ะบะพะผะฐะฝะด IPC", + "enter-path": "ะ’ะฒะตะดั–ั‚ัŒ ัˆะปัั…...", + "enter-text-to-collapse": "ะฝะฐะฟั€. 'ะฝั–ั‡ะพะณะพ ะฝะต ะฒั–ะดั‚ะฒะพั€ัŽั”ั‚ัŒัั'. ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต /regex/ ะดะปั ัˆะฐะฑะปะพะฝั–ะฒ.", + "enter-tooltip": "ะ’ะฒะตะดั–ั‚ัŒ ะฟั–ะดะบะฐะทะบัƒ", + "enter-width-pixels": "ะ’ะฒะตะดั–ั‚ัŒ ัˆะธั€ะธะฝัƒ ะฒ ะฟั–ะบัะตะปัั…", + "keybind-recording": "ะ—ะฐะฟะธั ะบะพะผะฑั–ะฝะฐั†ั–ั— ะบะปะฐะฒั–ัˆ...", + "search": "ะŸะพัˆัƒะบ...", + "search-icons": "ะฝะฐะฟั€. noctalia, niri, battery, cloud", + "search-launcher": "ะŸะพัˆัƒะบ ะทะฐะฟะธัั–ะฒ... ะฐะฑะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต > ะดะปั ะบะพะผะฐะฝะด", + "search-wallhaven": "ะŸะพัˆัƒะบ ัƒ Wallhaven...", + "search-wallpapers": "ะ’ะฒะตะดั–ั‚ัŒ ะดะปั ั„ั–ะปัŒั‚ั€ะฐั†ั–ั— ัˆะฟะฐะปะตั€..." + }, + "positions": { + "bottom": "ะ—ะฝะธะทัƒ", + "bottom-center": "ะ—ะฝะธะทัƒ ะฟะพ ั†ะตะฝั‚ั€ัƒ", + "bottom-left": "ะ—ะฝะธะทัƒ ะปั–ะฒะพั€ัƒั‡", + "bottom-right": "ะ—ะฝะธะทัƒ ะฟั€ะฐะฒะพั€ัƒั‡", + "center": "ะฆะตะฝั‚ั€", + "center-left": "ะ—ะปั–ะฒะฐ ะฟะพ ั†ะตะฝั‚ั€ัƒ", + "center-right": "ะกะฟั€ะฐะฒะฐ ะฟะพ ั†ะตะฝั‚ั€ัƒ", + "close-to-bar": "ะ‘ั–ะปั ะบะฝะพะฟะบะธ ะฟะฐะฝะตะปั–", + "follow-bar": "ะกะปั–ะดัƒะฒะฐั‚ะธ ะทะฐ ะฟะฐะฝะตะปะปัŽ", + "left": "ะ›ั–ะฒะธะน", + "right": "ะŸั€ะฐะฒะธะน", + "top": "ะ—ะฒะตั€ั…ัƒ", + "top-center": "ะ—ะฒะตั€ั…ัƒ ะฟะพ ั†ะตะฝั‚ั€ัƒ", + "top-left": "ะ—ะณะพั€ะธ ะปั–ะฒะพั€ัƒั‡", + "top-right": "ะ—ะณะพั€ะธ ะฟั€ะฐะฒะพั€ัƒั‡" + }, + "session-menu": { + "action-in-seconds": "{action} ั‡ะตั€ะตะท {seconds} ัะตะบัƒะฝะด...", + "cancel-timer": "ะกะบะฐััƒะฒะฐั‚ะธ ั‚ะฐะนะผะตั€", + "title": "ะœะตะฝัŽ ัะตะฐะฝััƒ" + }, + "setup": { + "all-done": "ะ“ะพั‚ะพะฒะพ!", + "appearance": { + "subheader": "ะ’ะธะฑะตั€ั–ั‚ัŒ ั‚ะตะผะฝะธะน ั€ะตะถะธะผ ั– ะดะถะตั€ะตะปะฐ ะบะพะปัŒะพั€ั–ะฒ (ัˆะฟะฐะปะตั€ะธ ะฐะฑะพ ะฟะพะฟะตั€ะตะดะฝัŒะพ ะฒะธะทะฝะฐั‡ะตะฝั–)." + }, + "customize": { + "header": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฒะฐัˆ ะดะพัะฒั–ะด", + "subheader": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฟะพะปะพะถะตะฝะฝั ะฟะฐะฝะตะปั–, ั‰ั–ะปัŒะฝั–ัั‚ัŒ, ะผะฐััˆั‚ะฐะฑัƒะฒะฐะฝะฝั ั‚ะฐ ั–ะฝัˆะต." + }, + "skip-setup": "ะŸั€ะพะฟัƒัั‚ะธั‚ะธ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั", + "telemetry-wizard-done": "ะ—ั€ะพะทัƒะผั–ะปะพ!", + "telemetry-wizard-note": "ะ’ะธ ะฒัะต ะบะพะฝั‚ั€ะพะปัŽั”ั‚ะต โ€” ัƒะฒั–ะผะบะฝั–ั‚ัŒ ะฐะฑะพ ะฒะธะผะบะฝั–ั‚ัŒ ั†ะต ะฑัƒะดัŒ-ะบะพะปะธ ะฒ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝัั…", + "telemetry-wizard-subtitle": "ะœะธ ะดะพะดะฐะปะธ ะฐะฝะพะฝั–ะผะฝัƒ ะฐะฝะฐะปั–ั‚ะธะบัƒ, ั‰ะพะฑ ะดะพะฟะพะผะพะณั‚ะธ ะฟะพะบั€ะฐั‰ะธั‚ะธ Noctalia", + "telemetry-wizard-title": "ะžะฝะพะฒะปะตะฝะฝั ะบะพะฝั„ั–ะดะตะฝั†ั–ะนะฝะพัั‚ั–", + "wallpaper": { + "choose-dir": "ะ’ะธะฑะตั€ั–ั‚ัŒ ั‚ะตะบัƒ, ั‰ะพ ะผั–ัั‚ะธั‚ัŒ ะฒะฐัˆั– ะทะพะฑั€ะฐะถะตะฝะฝั ัˆะฟะฐะปะตั€", + "dir-browse": "ะžะณะปัะด ั‚ะตะบะธ ัˆะฟะฐะปะตั€", + "dir-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ั‚ะตะบัƒ, ั‰ะพ ะผั–ัั‚ะธั‚ัŒ ะฒะฐัˆั– ัˆะฟะฐะปะตั€ะธ.", + "dir-label": "ะขะตะบะฐ ัˆะฟะฐะปะตั€", + "dir-select-title": "ะ’ะธะฑั€ะฐั‚ะธ ั‚ะตะบัƒ ัˆะฟะฐะปะตั€", + "header": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะฒะฐัˆั– ัˆะฟะฐะปะตั€ะธ", + "no-dir": "ะขะตะบะฐ ัˆะฟะฐะปะตั€ ะฝะต ะฒะธะฑั€ะฐะฝะฐ", + "no-valid": "ะะต ะทะฝะฐะนะดะตะฝะพ ะดั–ะนัะฝะธั… ั„ะฐะนะปั–ะฒ ะทะพะฑั€ะฐะถะตะฝัŒ ัƒ: {dir}", + "none-in-dir": "ะจะฟะฐะปะตั€ะธ ะฝะต ะทะฝะฐะนะดะตะฝะพ ะฒ ั‚ะตั†ั–", + "preview-error": "ะะต ะฒะดะฐะปะพัั ะทะฐะฒะฐะฝั‚ะฐะถะธั‚ะธ ะทะพะฑั€ะฐะถะตะฝะฝั", + "select-prompt": "ะ’ะธะฑะตั€ั–ั‚ัŒ ัˆะฟะฐะปะตั€ะธ ะฝะธะถั‡ะต", + "subheader": "ะกั‚ะฒะพั€ั–ั‚ัŒ ะฝะฐัั‚ั€ั–ะน ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ะบั€ะฐัะธะฒะพะณะพ ั„ะพะฝัƒ." + }, + "welcome": "ะ›ะฐัะบะฐะฒะพ ะฟั€ะพัะธะผะพ", + "welcome-note": "ะ›ะธัˆะต ะบั–ะปัŒะบะฐ ะพัะฝะพะฒะฝะธั… ั€ะตั‡ะตะน ะดะปั ะฟะพั‡ะฐั‚ะบัƒ โ€” ะฟะพะฒะฝั– ะพะฟั†ั–ั— ะฒ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝัั…", + "welcome-subtitle": "ะ—ั€ะพะฑั–ะผะพ ะฒะฐัˆ ั€ะพะฑะพั‡ะธะน ัั‚ั–ะป ัƒะฝั–ะบะฐะปัŒะฝะธะผ", + "welcome-title": "ะ’ั–ั‚ะฐั”ะผะพ ะฒ Noctalia!" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "ะœะตะดั–ะฐะฟะปะตั”ั€ ะฝะต ะฒะธัะฒะปะตะฝะพ", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "ะะตะฒั–ะดะพะผะธะน ะทะฐัั‚ะพััƒะฝะพะบ", + "uptime": "ะงะฐั ั€ะพะฑะพั‚ะธ: {uptime}", + "user-requested": "ะะฐ ะทะฐะฟะธั‚ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ", + "welcome-back": "ะ— ะฟะพะฒะตั€ะฝะตะฝะฝัะผ,", + "widget-settings-title": "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั {widget}" + }, + "system-monitor": { + "core-usage": "ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ัะดั€ะฐ {id}", + "cpu-temp": "ะขะตะผะฟะตั€ะฐั‚ัƒั€ะฐ ะฆะŸ", + "cpu-usage": "ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ะฆะŸ", + "disk": "ะ”ะธัะบ", + "download-speed": "ะจะฒะธะดะบั–ัั‚ัŒ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั", + "gpu-temp": "ะขะตะผะฟะตั€ะฐั‚ัƒั€ะฐ GPU", + "load-average": "ะกะตั€ะตะดะฝั” ะฝะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั", + "title": "ะกะธัั‚ะตะผะฝะธะน ะผะพะฝั–ั‚ะพั€", + "upload-speed": "ะจะฒะธะดะบั–ัั‚ัŒ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั" + }, + "toast": { + "airplane-mode": { + "description": "ะ’ะธะผะบะฝัƒั‚ะธ ะฒัั– ะฑะตะทะดั€ะพั‚ะพะฒั– ะบะพะผัƒะฝั–ะบะฐั†ั–ั—.", + "title": "ะ ะตะถะธะผ ะฟะพะปัŒะพั‚ัƒ" + }, + "battery": { + "critical": "ะšั€ะธั‚ะธั‡ะฝะธะน ั€ั–ะฒะตะฝัŒ ะทะฐั€ัะดัƒ ะฑะฐั‚ะฐั€ะตั—", + "critical-desc": "ะ‘ะฐั‚ะฐั€ะตั ะทะฐั€ัะดะถะตะฝะฐ ะฝะฐ {percent}% โ€” ะฝะตะณะฐะนะฝะพ ะฟั–ะดะบะปัŽั‡ั–ั‚ัŒ ะทะฐั€ัะดะฝะธะน ะฟั€ะธัั‚ั€ั–ะน", + "low": "ะะธะทัŒะบะธะน ะทะฐั€ัะด ะฑะฐั‚ะฐั€ะตั—", + "low-desc": "ะ‘ะฐั‚ะฐั€ะตั ะทะฐั€ัะดะถะตะฝะฐ ะฝะฐ {percent}% โ€” ะฑัƒะดัŒ ะปะฐัะบะฐ, ะฟั–ะดะบะปัŽั‡ั–ั‚ัŒ ะทะฐั€ัะดะฝะธะน ะฟั€ะธัั‚ั€ั–ะน" + }, + "bluetooth": { + "address-copied": "ะะดั€ะตััƒ ัะบะพะฟั–ะนะพะฒะฐะฝะพ ะดะพ ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ", + "auto-connect-disabled": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะต ะฟั–ะดะบะปัŽั‡ะตะฝะฝั ะฒะธะผะบะฝะตะฝะพ", + "auto-connect-enabled": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะต ะฟั–ะดะบะปัŽั‡ะตะฝะฝั ัƒะฒั–ะผะบะฝะตะฝะพ", + "auto-connecting": "ะŸั–ะดะบะปัŽั‡ะตะฝะฝั ะดะพ {count} ะฟั€ะธัั‚ั€ะพัŽ(ั—ะฒ)...", + "confirm-code": "ะŸั–ะดั‚ะฒะตั€ะดัŒั‚ะต ะบะพะด {value} ะฝะฐ ั–ะฝัˆะพะผัƒ ะฟั€ะธัั‚ั€ะพั—.", + "connect-failed": "ะะต ะฒะดะฐะปะพัั ะฟั–ะด'ั”ะดะฝะฐั‚ะธัั ะดะพ ะฟั€ะธัั‚ั€ะพัŽ", + "disconnect-failed": "ะะต ะฒะดะฐะปะพัั ะฒั–ะด'ั”ะดะฝะฐั‚ะธัั ะฒั–ะด ะฟั€ะธัั‚ั€ะพัŽ", + "discoverable-change-failed": "ะะต ะฒะดะฐะปะพัั ะทะผั–ะฝะธั‚ะธ ัั‚ะฐะฝ ะฒะธะดะธะผะพัั‚ั–", + "discoverable-disabled": "ะ’ะธัะฒะปะตะฝะฝั ะฒะธะผะบะฝะตะฝะพ", + "discoverable-enabled": "ะ’ะธัะฒะปะตะฝะฝั ะฒะฒั–ะผะบะฝะตะฝะพ", + "display-code": "ะ’ะฒะตะดั–ั‚ัŒ ะบะพะด {value} ะฝะฐ ั–ะฝัˆะพะผัƒ ะฟั€ะธัั‚ั€ะพั—.", + "forget-failed": "ะะต ะฒะดะฐะปะพัั ะทะฐะฑัƒั‚ะธ ะฟั€ะธัั‚ั€ั–ะน", + "pair-failed": "ะะต ะฒะดะฐะปะพัั ัั‚ะฒะพั€ะธั‚ะธ ะฟะฐั€ัƒ ะท ะฟั€ะธัั‚ั€ะพั”ะผ", + "passkey-required": "ะŸะพั‚ั€ั–ะฑะตะฝ ะบะปัŽั‡ ะดะพัั‚ัƒะฟัƒ, ั‰ะพ ะทะฑะตั€ั–ะณะฐั”ั‚ัŒัั ะฝะฐ ะฟั€ะธัั‚ั€ะพั—.", + "pincode-required": "PIN-ะบะพะด, ะฝะตะพะฑั…ั–ะดะฝะธะน ะฟั€ะธัั‚ั€ะพั”ะผ.", + "state-change-failed": "ะะต ะฒะดะฐะปะพัั ะทะผั–ะฝะธั‚ะธ ัั‚ะฐะฝ Bluetooth" + }, + "clipboard": { + "long-text": "ะ”ะพะฒะณะธะน ั‚ะตะบัั‚", + "unavailable": "ะ†ัั‚ะพั€ั–ั ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ ะฝะตะดะพัั‚ัƒะฟะฝะฐ", + "unavailable-desc": "ะ—ะฐัั‚ะพััƒะฝะพะบ 'cliphist' ะฝะต ะฒัั‚ะฐะฝะพะฒะปะตะฝะธะน. ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฒัั‚ะฐะฝะพะฒั–ั‚ัŒ ะนะพะณะพ ะดะปั ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ั„ัƒะฝะบั†ั–ะน ั–ัั‚ะพั€ั–ั— ะฑัƒั„ะตั€ะฐ ะพะฑะผั–ะฝัƒ" + }, + "custom-command-failed": { + "description": "ะšะพะผะฐะฝะดะฐ ะฝะต ะฒะธะบะพะฝะฐะฝะฐ: {command}\\nะšะพะด ะฒะธั…ะพะดัƒ: {code}", + "title": "ะšะพั€ะธัั‚ัƒะฒะฐั†ัŒะบะฐ ะบะพะผะฐะฝะดะฐ ะฝะต ะฒะธะบะพะฝะฐะฝะฐ" + }, + "do-not-disturb": { + "disabled": "'ะะต ั‚ัƒั€ะฑัƒะฒะฐั‚ะธ' ะฒะธะผะบะฝะตะฝะพ", + "disabled-desc": "ะŸะพะบะฐะท ัƒัั–ั… ัะฟะพะฒั–ั‰ะตะฝัŒ", + "enabled": "'ะะต ั‚ัƒั€ะฑัƒะฒะฐั‚ะธ' ัƒะฒั–ะผะบะฝะตะฝะพ", + "enabled-desc": "ะ’ะธ ะทะฝะฐะนะดะตั‚ะต ั†ั– ัะฟะพะฒั–ั‰ะตะฝะฝั ะฒ ัะฒะพั—ะน ั–ัั‚ะพั€ั–ั—" + }, + "donation-opened": "ะกั‚ะพั€ั–ะฝะบะฐ ะฟะพะถะตั€ั‚ะฒ ะฒั–ะดะบั€ะธั‚ะพ ัƒ ะฒะฐัˆะพะผัƒ ะฑั€ะฐัƒะทะตั€ั–", + "internet-limited": "ะŸั–ะดะบะปัŽั‡ะตะฝะพ ะฑะตะท ั–ะฝั‚ะตั€ะฝะตั‚ัƒ", + "keyboard-layout": { + "changed": "ะ ะพะทะบะปะฐะดะบะฐ ะบะปะฐะฒั–ะฐั‚ัƒั€ะธ ะทะผั–ะฝะตะฝะฐ ะฝะฐ {layout}", + "title": "ะšะปะฐะฒั–ะฐั‚ัƒั€ะฐ" + }, + "missing-control-center": { + "description": "ะ’ั–ะดะถะตั‚ ั†ะตะฝั‚ั€ัƒ ะบะตั€ัƒะฒะฐะฝะฝั ะฒะธะดะฐะปะตะฝะพ ะท ะฟะฐะฝะตะปั–. ะฉะพะฑ ะทะฝะพะฒัƒ ะพั‚ั€ะธะผะฐั‚ะธ ะดะพ ะฝัŒะพะณะพ ะดะพัั‚ัƒะฟ ะท ะฟะฐะฝะตะปั–, ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะพะฒั‚ะพั€ะฝะพ ะดะพะดะฐั‚ะธ ะฒั–ะดะถะตั‚. ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒั–ะดะบั€ะธั‚ะธ ะนะพะณะพ, ะบะปะฐั†ะฝัƒะฒัˆะธ ะฟั€ะฐะฒะพัŽ ะบะฝะพะฟะบะพัŽ ะผะธัˆั– ะฝะฐ ะฟะฐะฝะตะปั–", + "label": "ะžัั‚ะฐะฝะฝั–ะน ะฒั–ะดะถะตั‚ ั†ะตะฝั‚ั€ัƒ ะบะตั€ัƒะฒะฐะฝะฝั ะฒะธะดะฐะปะตะฝะพ" + }, + "night-light": { + "forced": "ะŸั€ะธะผัƒัะพะฒะฐ ะฐะบั‚ะธะฒะฐั†ั–ั", + "normal": "ะ—ะฒะธั‡ะฐะนะฝะธะน ั€ะตะถะธะผ", + "not-installed": "wlsunset ะฝะต ะฒัั‚ะฐะฝะพะฒะปะตะฝะพ" + }, + "noctalia-performance": { + "disabled": "ะ ะตะถะธะผ ะฟั€ะพะดัƒะบั‚ะธะฒะฝะพัั‚ั– ะฒะธะผะบะฝะตะฝะพ", + "enabled": "ะ ะตะถะธะผ ะฟั€ะพะดัƒะบั‚ะธะฒะฝะพัั‚ั– ะฒะฒั–ะผะบะฝะตะฝะพ", + "label": "ะŸั€ะพะดัƒะบั‚ะธะฒะฝั–ัั‚ัŒ Noctalia" + }, + "power-profile": { + "changed": "ะŸั€ะพั„ั–ะปัŒ ะถะธะฒะปะตะฝะฝั ะทะผั–ะฝะตะฝะพ", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "ะ’ะธะฝะธะบะปะฐ ะฟะพะผะธะปะบะฐ ะฟั–ะด ั‡ะฐั ะพะฑั€ะพะฑะบะธ ัˆะฐะฑะปะพะฝั–ะฒ", + "title": "ะŸะพะผะธะปะบะฐ ะพะฑั€ะพะฑะบะธ ัˆะฐะฑะปะพะฝั–ะฒ" + }, + "vpn": { + "connected": "ะŸั–ะดะบะปัŽั‡ะตะฝะพ ะดะพ '{name}'", + "disconnected": "ะ’ั–ะดะบะปัŽั‡ะตะฝะพ ะฒั–ะด '{name}'" + }, + "wallpaper-colors": { + "disabled": "ะšะพะปัŒะพั€ะธ ัˆะฟะฐะปะตั€ ะฒะธะผะบะฝะตะฝะพ", + "enabled": "ะšะพะปัŒะพั€ะธ ัˆะฟะฐะปะตั€ ัƒะฒั–ะผะบะฝะตะฝะพ", + "label": "ะ“ะตะฝะตั€ะฐั†ั–ั ะบะพะปัŒะพั€ั–ะฒ ัˆะฟะฐะปะตั€" + }, + "wifi": { + "connected": "ะŸั–ะดะบะปัŽั‡ะตะฝะพ ะดะพ '{ssid}'", + "connection-failed": "ะ—'ั”ะดะฝะฐะฝะฝั ะฝะต ะฒะดะฐะปะพัั", + "connection-timeout": "ะงะฐั ะพั‡ั–ะบัƒะฒะฐะฝะฝั ะท'ั”ะดะฝะฐะฝะฝั ะฒะธั‡ะตั€ะฟะฐะฝะพ", + "disconnected": "ะ’ั–ะดะบะปัŽั‡ะตะฝะพ ะฒั–ะด '{ssid}'", + "incorrect-password": "ะะตะฟั€ะฐะฒะธะปัŒะฝะธะน ะฟะฐั€ะพะปัŒ", + "network-not-found": "ะœะตั€ะตะถัƒ ะฝะต ะทะฝะฐะนะดะตะฝะพ" + } + }, + "tooltips": { + "add-widget": "ะ”ะพะดะฐั‚ะธ ะฒั–ะดะถะตั‚", + "auto-connect": "ะŸะตั€ะตะผะบะฝัƒั‚ะธ ะฐะฒั‚ะพะฟั–ะดะบะปัŽั‡ะตะฝะฝั ะดะปั ั†ัŒะพะณะพ ะฟั€ะธัั‚ั€ะพัŽ", + "bluetooth-auto-connect-off": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะต ะฟั–ะดะบะปัŽั‡ะตะฝะฝั ะฒะธะผะบะฝะตะฝะพ", + "bluetooth-auto-connect-on": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะต ะฟั–ะดะบะปัŽั‡ะตะฝะฝั ัƒะฒั–ะผะบะฝะตะฝะพ", + "bluetooth-devices": "ะŸั€ะธัั‚ั€ะพั— Bluetooth", + "brightness-at": "ะฏัะบั€ะฐะฒั–ัั‚ัŒ: {brightness}%", + "click-to-start-recording": "ะ—ะฐะฟะธั ะตะบั€ะฐะฝะฐ (ะฟะพั‡ะฐั‚ะธ ะทะฐะฟะธั)", + "click-to-stop-recording": "ะ—ะฐะฟะธั ะตะบั€ะฐะฝะฐ (ะทัƒะฟะธะฝะธั‚ะธ ะทะฐะฟะธั)", + "collapse": "ะ—ะณะพั€ะฝัƒั‚ะธ ะฑั–ั‡ะฝัƒ ะฟะฐะฝะตะปัŒ", + "copy-address": "ะšะพะฟั–ัŽะฒะฐั‚ะธ ะฐะดั€ะตััƒ", + "delete-notification": "ะ’ะธะดะฐะปะธั‚ะธ ัะฟะพะฒั–ั‰ะตะฝะฝั", + "dismiss-notification": "ะ’ั–ะดั…ะธะปะธั‚ะธ ัะฟะพะฒั–ั‰ะตะฝะฝั", + "do-not-disturb-enabled": "ะะต ั‚ัƒั€ะฑัƒะฒะฐั‚ะธ", + "expand": "ะ ะพะทะณะพั€ะฝัƒั‚ะธ ะฑั–ั‡ะฝัƒ ะฟะฐะฝะตะปัŒ", + "forget-network": "ะ—ะฐะฑัƒั‚ะธ ะผะตั€ะตะถัƒ", + "grid-view": "ะœะตั€ะตะถะตะฒะธะน ะฒะธะณะปัะด", + "hidden-files-hide": "ะŸั€ะธั…ะพะฒะฐะฝั– ั„ะฐะนะปะธ", + "hide-unnamed-devices": "ะŸั€ะธั…ะพะฒะฐั‚ะธ ะฟั€ะธัั‚ั€ะพั— ะฑะตะท ะฝะฐะทะฒะธ", + "home": "ะ”ะพะผะฐัˆะฝั–ะน ะบะฐั‚ะฐะปะพะณ", + "input-muted": "ะฃะฒั–ะผะบะฝัƒั‚ะธ/ะฒะธะผะบะฝัƒั‚ะธ ะฒะธะผะบะฝะตะฝะฝั ะทะฒัƒะบัƒ ะผั–ะบั€ะพั„ะพะฝะฐ", + "keep-awake": "ะ—ะฐะฑะพั€ะพะฝะฐ ัะฝัƒ", + "keyboard-layout": "ะ ะพะทะบะปะฐะดะบะฐ ะบะปะฐะฒั–ะฐั‚ัƒั€ะธ {layout}", + "list-view": "ะกะฟะธัะพะบ", + "manage-vpn": "ะŸั–ะดะบะปัŽั‡ะตะฝะฝั VPN", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "ะ”ะพััะณะฝัƒั‚ะพ ะผะฐะบัะธะผะฐะปัŒะฝะพั— ะบั–ะปัŒะบะพัั‚ั– ะฒั–ะดะถะตั‚ั–ะฒ", + "microphone-volume-at": "ะ“ัƒั‡ะฝั–ัั‚ัŒ ะผั–ะบั€ะพั„ะพะฝะฐ: {volume}%", + "move-to-section": "ะŸะตั€ะตะนั‚ะธ ะดะพ {section}", + "mute": "ะ’ะธะผะบะฝัƒั‚ะธ ะทะฒัƒะบ", + "next-media": "ะะฐัั‚ัƒะฟะฝะธะน ั‚ั€ะตะบ", + "next-month": "ะะฐัั‚ัƒะฟะฝะธะน ะผั–ััั†ัŒ", + "night-light-not-installed": "ะั–ั‡ะฝะต ัะฒั–ั‚ะปะพ (ะฝะตะดะพัั‚ัƒะฟะฝะต)", + "noctalia-performance-enabled": "ะ ะตะถะธะผ ะฟั€ะพะดัƒะบั‚ะธะฒะฝะพัั‚ั– Noctalia", + "open-annotation-tool": "ะ’ั–ะดะบั€ะธั‚ะธ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ัƒ ะฐะฝะพั‚ะฐั†ั–ะน", + "open-control-center": "ะฆะตะฝั‚ั€ ะบะตั€ัƒะฒะฐะฝะฝั", + "open-notification-history-enable-dnd": "ะ†ัั‚ะพั€ั–ั ัะฟะพะฒั–ั‰ะตะฝัŒ", + "open-settings": "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั", + "open-tray-dropdown": "ะกะธัั‚ะตะผะฝะธะน ั‚ั€ะตะน", + "output-muted": "ะ’ะธะผะบะฝัƒั‚ะธ ะทะฒัƒะบ ะฒะธะฒะพะดัƒ", + "power-profile": "{profile} ะฟั€ะพั„ั–ะปัŒ ะถะธะฒะปะตะฝะฝั", + "previous-media": "ะŸะพะฟะตั€ะตะดะฝั–ะน ั‚ั€ะตะบ", + "previous-month": "ะŸะพะฟะตั€ะตะดะฝั–ะน ะผั–ััั†ัŒ", + "refresh-devices": "ะžะฝะพะฒะธั‚ะธ ะฟั€ะธัั‚ั€ะพั—", + "refresh-wallhaven": "ะžะฝะพะฒะธั‚ะธ ั€ะตะทัƒะปัŒั‚ะฐั‚ะธ Wallhaven", + "refresh-wallpaper-list": "ะžะฝะพะฒะธั‚ะธ ัะฟะธัะพะบ ัˆะฟะฐะปะตั€", + "remove-widget": "ะ’ะธะดะฐะปะธั‚ะธ ะฒั–ะดะถะตั‚", + "search-close": "ะ—ะฐะบั€ะธั‚ะธ ะฟะพัˆัƒะบ", + "session-menu": "ะœะตะฝัŽ ัะตะฐะฝััƒ", + "show-all-devices": "ะŸะพะบะฐะทะฐั‚ะธ ะฒัั– ะฟั€ะธัั‚ั€ะพั—", + "switch-to-dark-mode": "ะขะตะผะฝะธะน ั€ะตะถะธะผ", + "switch-to-light-mode": "ะกะฒั–ั‚ะปะธะน ั€ะตะถะธะผ", + "unmute": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะทะฒัƒะบ", + "up": "ะ‘ะฐั‚ัŒะบั–ะฒััŒะบะธะน ะบะฐั‚ะฐะปะพะณ", + "volume-at": "ะ’ะธั…ั–ะดะฝะฐ ะณัƒั‡ะฝั–ัั‚ัŒ: {volume}%", + "wallpaper-selector": "ะ’ะธะฑั–ั€ ัˆะฟะฐะปะตั€" + }, + "wallpaper": { + "browse": { + "empty-directory": "ะฆะตะน ะบะฐั‚ะฐะปะพะณ ะฟะพั€ะพะถะฝั–ะน.", + "go-root": "ะŸะตั€ะตะนั‚ะธ ะดะพ ะบะพั€ะตะฝะตะฒะพั— ะฟะฐะฟะบะธ ัˆะฟะฐะปะตั€", + "go-up": "ะŸะตั€ะตะนั‚ะธ ะดะพ ะฑะฐั‚ัŒะบั–ะฒััŒะบะพั— ะฟะฐะฟะบะธ", + "go-up-hint": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ะบะฝะพะฟะบัƒ ยซะะฐะทะฐะดยป ะดะปั ะฝะฐะฒั–ะณะฐั†ั–ั— ะฒะณะพั€ัƒ." + }, + "configure-directory": "ะะฐะปะฐัˆั‚ัƒะนั‚ะต ะฒะฐัˆัƒ ั‚ะตะบัƒ ัˆะฟะฐะปะตั€ ะทั– ะทะพะฑั€ะฐะถะตะฝะฝัะผะธ.", + "fill-modes": { + "crop": "ะžะฑั€ั–ะทะฐั‚ะธ (ะ—ะฐะฟะพะฒะฝะธั‚ะธ)", + "fit": "ะ’ะผั–ัั‚ะธั‚ะธ (ะ’ะฟะธัะฐั‚ะธ)", + "repeat": "ะŸะพะฒั‚ะพั€ะธั‚ะธ (ะŸะปะธั‚ะบะฐ)", + "stretch": "ะ ะพะทั‚ัะณะฝัƒั‚ะธ" + }, + "no-match": "ะ—ะฑั–ะณั–ะฒ ะฝะต ะทะฝะฐะนะดะตะฝะพ.", + "no-wallpaper": "ะจะฟะฐะปะตั€ะธ ะฝะต ะทะฝะฐะนะดะตะฝะพ.", + "panel": { + "apikey-help": "ะ”ะปั ะดะพัั‚ัƒะฟัƒ ะดะพ ะบะพะฝั‚ะตะฝั‚ัƒ NSFW ะฟะพั‚ั€ั–ะฑะตะฝ ะบะปัŽั‡ API.", + "apikey-label": "ะšะปัŽั‡ API", + "apikey-managed-by-env": "ะšะตั€ัƒั”ั‚ัŒัั ั‡ะตั€ะตะท ะทะผั–ะฝะฝัƒ ัะตั€ะตะดะพะฒะธั‰ะฐ NOCTALIA_WALLHAVEN_API_KEY.", + "apikey-placeholder": "ะ’ะฒะตะดั–ั‚ัŒ ัะฒั–ะน API-ะบะปัŽั‡ Wallhaven", + "appearance-dark-tab": "ะขะตะผะฝะธะน", + "appearance-light-tab": "ะกะฒั–ั‚ะปะธะน", + "categories-anime": "ะะฝั–ะผะต", + "categories-label": "ะšะฐั‚ะตะณะพั€ั–ั—", + "categories-people": "ะ›ัŽะดะธ", + "color-extraction-disabled": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฒะธะปัƒั‡ะตะฝะฝั ะบะพะปัŒะพั€ั–ะฒ ัˆะฟะฐะปะตั€", + "color-extraction-enabled": "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฟะพะฟะตั€ะตะดะฝัŒะพ ะฒะธะทะฝะฐั‡ะตะฝั– ะบะพะปัŒะพั€ะพะฒั– ัั…ะตะผะธ", + "header-devices-apply-all-tooltip": "ะ’ะธะฑั€ะฐั‚ะธ ัˆะฟะฐะปะตั€ะธ ะดะปั ะบะพะถะฝะพะณะพ ะผะพะฝั–ั‚ะพั€ะฐ", + "header-devices-per-monitor-tooltip": "ะ—ะฐัั‚ะพััƒะฒะฐั‚ะธ ะดะพ ะฒัั–ั… ะผะพะฝั–ั‚ะพั€ั–ะฒ", + "header-link-light-dark-tooltip": "ะ—ะฑะตั€ั–ะณะฐั‚ะธ ัˆะฟะฐะปะตั€ะธ ะฟั€ะธ ะทะผั–ะฝั– ั‚ะตะผะธ", + "header-separate-light-dark-tooltip": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะฐ ะทะผั–ะฝะฐ ัˆะฟะฐะปะตั€ ะท ั‚ะตะผะพัŽ", + "header-sun-linked-tooltip": "ะžะบั€ะตะผั– ัะฒั–ั‚ะปั– ั‚ะฐ ั‚ะตะผะฝั– ัˆะฟะฐะปะตั€ะธ", + "header-sun-separate-tooltip": "ะžะดะฝะฐะบะพะฒั– ัˆะฟะฐะปะตั€ะธ ะดะปั ัะฒั–ั‚ะปะพะณะพ ั‚ะฐ ั‚ะตะผะฝะพะณะพ ั€ะตะถะธะผั–ะฒ", + "order-asc": "ะ—ะฐ ะทั€ะพัั‚ะฐะฝะฝัะผ", + "order-desc": "ะ—ะฐ ัะฟะฐะดะฐะฝะฝัะผ", + "order-label": "ะŸะพั€ัะดะพะบ", + "purity-label": "ะคั–ะปัŒั‚ั€ ะบะพะฝั‚ะตะฝั‚ัƒ", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "Sketchy", + "ratios-all-portrait": "ะฃัั– ะฟะพั€ั‚ั€ะตั‚ะฝั–", + "ratios-all-wide": "ะฃัั– ัˆะธั€ะพะบั–", + "ratios-any": "ะ‘ัƒะดัŒ-ัะบะธะน", + "ratios-label": "ะกะฟั–ะฒะฒั–ะดะฝะพัˆะตะฝะฝั ัั‚ะพั€ั–ะฝ", + "resolution-atleast": "ะŸั€ะธะฝะฐะนะผะฝั–", + "resolution-exact": "ะขะพั‡ะฝะพ", + "resolution-label": "ะ ะพะทะดั–ะปัŒะฝะฐ ะทะดะฐั‚ะฝั–ัั‚ัŒ", + "resolution-mode-label": "ะ ะตะถะธะผ", + "solid-color-tooltip": "ะžะดะฝะพั‚ะพะฝะฝะธะน ั„ะพะฝ", + "sort-date-asc": "ะกะพั€ั‚ัƒะฒะฐั‚ะธ ะฒั–ะด ะฝะฐะนัั‚ะฐั€ั–ัˆะธั… ะดะพ ะฝะพะฒะธั…", + "sort-date-desc": "ะกะพั€ั‚ัƒะฒะฐั‚ะธ ะฒั–ะด ะฝะพะฒะธั… ะดะพ ะฝะฐะนัั‚ะฐั€ั–ัˆะธั…", + "sort-name-asc": "ะกะพั€ั‚ัƒะฒะฐั‚ะธ ะทะฐ ะฝะฐะทะฒะพัŽ (ะ-ะฏ)", + "sort-name-desc": "ะกะพั€ั‚ัƒะฒะฐั‚ะธ ะทะฐ ะฝะฐะทะฒะพัŽ (ะฏ-ะ)", + "sort-random": "ะกะพั€ั‚ัƒะฒะฐั‚ะธ ะฒะธะฟะฐะดะบะพะฒะพ", + "sorting-date-added": "ะ”ะฐั‚ะฐ ะดะพะดะฐะฒะฐะฝะฝั", + "sorting-favorites": "ะžะฑั€ะฐะฝะต", + "sorting-label": "ะกะพั€ั‚ัƒะฒะฐั‚ะธ ะทะฐ", + "sorting-relevance": "ะ ะตะปะตะฒะฐะฝั‚ะฝั–ัั‚ัŒ", + "sorting-toplist": "ะขะพะฟ-ะปะธัั‚", + "sorting-views": "ะŸะตั€ะตะณะปัะดะธ", + "source-label": "ะ”ะถะตั€ะตะปะพ", + "source-wallhaven": "Wallhaven", + "title": "ะ’ะธะฑั–ั€ ัˆะฟะฐะปะตั€", + "wallhaven-settings-title": "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั Wallhaven" + }, + "transitions": { + "disc": "ะ”ะธัะบ", + "fade": "ะ—ะฐั‚ัƒั…ะฐะฝะฝั", + "honeycomb": "ะกั‚ั–ะปัŒะฝะธะบะธ", + "pixelate": "ะŸั–ะบัะตะปั–ะทัƒะฒะฐั‚ะธ", + "stripes": "ะกะผัƒะณะธ", + "wipe": "ะ—ะผั–ั‚ะฐะฝะฝั" + }, + "try-different-search": "ะกะฟั€ะพะฑัƒะนั‚ะต ั–ะฝัˆะธะน ะฟะพัˆัƒะบะพะฒะธะน ะทะฐะฟะธั‚.", + "wallhaven": { + "loading": "ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ัˆะฟะฐะปะตั€...", + "no-results": "ะจะฟะฐะปะตั€ะธ ะฝะต ะทะฝะฐะนะดะตะฝะพ. ะกะฟั€ะพะฑัƒะนั‚ะต ั–ะฝัˆะธะน ะฟะพัˆัƒะบะพะฒะธะน ะทะฐะฟะธั‚.", + "page": "{current} ะท {total}", + "page-prefix": "ะกั‚ะพั€ั–ะฝะบะฐ", + "page-suffix": "ะท {total}" + } + }, + "weather": { + "clear-sky": "ะฏัะฝะต ะฝะตะฑะพ", + "drizzle": "ะœั€ัะบะฐ", + "fog": "ะขัƒะผะฐะฝ", + "mainly-clear": "ะŸะตั€ะตะฒะฐะถะฝะพ ััะฝะพ", + "overcast": "ะŸะพั…ะผัƒั€ะพ", + "partly-cloudy": "ะงะฐัั‚ะบะพะฒะพ ั…ะผะฐั€ะฝะพ", + "rain-showers": "ะ”ะพั‰ะพะฒั– ะทะปะธะฒะธ", + "snow": "ะกะฝั–ะณ", + "thunderstorm": "ะ“ั€ะพะทะฐ" + }, + "widgets": { + "color-picker": { + "palette-description": "ะ’ะธะฑะตั€ั–ั‚ัŒ ั–ะท ัˆะธั€ะพะบะพะณะพ ะดั–ะฐะฟะฐะทะพะฝัƒ ะฟะพะฟะตั€ะตะดะฝัŒะพ ะฒะธะทะฝะฐั‡ะตะฝะธั… ะบะพะปัŒะพั€ั–ะฒ.", + "palette-label": "ะŸะฐะปั–ั‚ั€ะฐ", + "palette-theme-colors": "ะจะฒะธะดะบะธะน ะดะพัั‚ัƒะฟ ะดะพ ะบะพะปั–ั€ะฝะพั— ะฟะฐะปั–ั‚ั€ะธ ะฒะฐัˆะพั— ั‚ะตะผะธ.", + "title": "ะ’ะธะฑั–ั€ ะบะพะปัŒะพั€ัƒ" + }, + "datetime-tokens": { + "ampm-lowercase": "am/pm ะผะฐะปะธะผะธ ะปั–ั‚ะตั€ะฐะผะธ", + "ampm-uppercase": "AM/PM ะฒะตะปะธะบะธะผะธ ะปั–ั‚ะตั€ะฐะผะธ", + "common-12hour-time-minutes": "12-ะณะพะดะธะฝะฝะธะน ั‡ะฐั ะท ั…ะฒะธะปะธะฝะฐะผะธ", + "common-24hour-time-minutes": "24-ะณะพะดะธะฝะฝะธะน ั‡ะฐั ะท ั…ะฒะธะปะธะฝะฐะผะธ", + "common-24hour-time-seconds": "24-ะณะพะดะธะฝะฝะธะน ั‡ะฐั ะท ัะตะบัƒะฝะดะฐะผะธ", + "common-european-date": "ะ„ะฒั€ะพะฟะตะนััŒะบะธะน ั„ะพั€ะผะฐั‚ ะดะฐั‚ะธ", + "common-iso-date": "ะคะพั€ะผะฐั‚ ะดะฐั‚ะธ ISO", + "common-us-date": "ะคะพั€ะผะฐั‚ ะดะฐั‚ะธ ะกะจะ", + "common-weekday-date": "ะ”ะตะฝัŒ ั‚ะธะถะฝั ั– ะดะฐั‚ะฐ", + "common-weekday-month-day": "ะ”ะตะฝัŒ ั‚ะธะถะฝั, ะผั–ััั†ัŒ ั– ะดะตะฝัŒ", + "day-abbreviated": "ะกะบะพั€ะพั‡ะตะฝะฐ ะฝะฐะทะฒะฐ ะดะฝั", + "day-full": "ะŸะพะฒะฝะฐ ะฝะฐะทะฒะฐ ะดะฝั", + "day-leading-zero": "ะ”ะตะฝัŒ ะท ะฝัƒะปะตะผ ะฟะพะฟะตั€ะตะดัƒ (01-31)", + "day-no-leading-zero": "ะ”ะตะฝัŒ ะฑะตะท ะฝัƒะปั ะฟะพะฟะตั€ะตะดัƒ (1-31)", + "hour-leading-zero": "ะ“ะพะดะธะฝะฐ ะท ะฝัƒะปะตะผ ะฟะพะฟะตั€ะตะดัƒ (00-23) โ€” 24-ะณะพะดะธะฝะฝะธะน ั„ะพั€ะผะฐั‚", + "hour-no-leading-zero": "ะ“ะพะดะธะฝะฐ ะฑะตะท ะฝัƒะปั ะฟะพะฟะตั€ะตะดัƒ (0-23) โ€” 24-ะณะพะดะธะฝะฝะธะน ั„ะพั€ะผะฐั‚", + "minute-leading-zero": "ะฅะฒะธะปะธะฝะฐ ะท ะฝัƒะปะตะผ ะฟะพะฟะตั€ะตะดัƒ (00-59)", + "minute-no-leading-zero": "ะฅะฒะธะปะธะฝะฐ ะฑะตะท ะฝัƒะปั ะฟะพะฟะตั€ะตะดัƒ (0-59)", + "month-abbreviated": "ะกะบะพั€ะพั‡ะตะฝะฐ ะฝะฐะทะฒะฐ ะผั–ััั†ั", + "month-full": "ะŸะพะฒะฝะฐ ะฝะฐะทะฒะฐ ะผั–ััั†ั", + "month-number-leading-zero": "ะœั–ััั†ัŒ ัะบ ั‡ะธัะปะพ ะท ะฝัƒะปะตะผ ะฟะพะฟะตั€ะตะดัƒ (01-12)", + "month-number-no-zero": "ะœั–ััั†ัŒ ัะบ ั‡ะธัะปะพ ะฑะตะท ะฝัƒะปั ะฟะพะฟะตั€ะตะดัƒ (1-12)", + "second-leading-zero": "ะกะตะบัƒะฝะดะฐ ะท ะฝัƒะปะตะผ ะฟะพะฟะตั€ะตะดัƒ (00-59)", + "second-no-leading-zero": "ะกะตะบัƒะฝะดะฐ ะฑะตะท ะฝัƒะปั ะฟะพะฟะตั€ะตะดัƒ (0-59)", + "timezone-abbreviation": "ะะฑั€ะตะฒั–ะฐั‚ัƒั€ะฐ ั‡ะฐัะพะฒะพะณะพ ะฟะพัััƒ", + "year-four-digit": "ะ ั–ะบ ัะบ ั‡ะพั‚ะธั€ะธะทะฝะฐั‡ะฝะต ั‡ะธัะปะพ", + "year-two-digit": "ะ ั–ะบ ัะบ ะดะฒะพะทะฝะฐั‡ะฝะต ั‡ะธัะปะพ (00-99)" + }, + "file-picker": { + "item": "ะตะปะตะผะตะฝั‚", + "items": "ะตะปะตะผะตะฝั‚ะธ", + "search-placeholder": "ะŸะพัˆัƒะบ ั„ะฐะนะปั–ะฒ ั‚ะฐ ั‚ะตะบ...", + "select-current": "ะ’ะธะฑั€ะฐั‚ะธ ะฟะพั‚ะพั‡ะฝะต", + "select-file": "ะ’ะธะฑั€ะฐั‚ะธ ั„ะฐะนะป", + "select-folder": "ะ’ะธะฑั€ะฐั‚ะธ ั‚ะตะบัƒ", + "selected": "ะ’ะธะฑั€ะฐะฝะพ:", + "title": "ะ’ะธะฑั–ั€ ั„ะฐะนะปัƒ" + }, + "file-picker-title": "ะ’ะธะฑะตั€ั–ั‚ัŒ ั„ะฐะนะป", + "icon-picker": { + "title": "ะ’ะธะฑั–ั€ ะทะฝะฐั‡ะบะฐ" + }, + "tooltip-placeholder": "ะ—ะฐะฟะพะฒะฝัŽะฒะฐั‡" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "ะะฝะพะฝั–ะผะฝะฐ ะ†ะดะตะฝั‚ะธั‡ะฝั–ัั‚ัŒ", + "ca-cert": "ะกะตั€ั‚ะธั„ั–ะบะฐั‚ CA", + "eap-method": "ะœะตั‚ะพะด EAP", + "password": "ะŸะฐั€ะพะปัŒ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ", + "phase2-auth": "ะะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั ะคะฐะทะธ 2", + "username": "ะ†ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ / ะ†ะผ'ั ะšะพั€ะธัั‚ัƒะฒะฐั‡ะฐ" + }, + "panel": { + "action-required": "ะ’ะธะผะฐะณะฐั”ั‚ัŒัั ะดั–ั", + "add-hidden-network": "ะ”ะพะดะฐั‚ะธ ะฟั€ะธั…ะพะฒะฐะฝัƒ ะผะตั€ะตะถัƒ", + "add-network": "ะ”ะพะดะฐั‚ะธ ะผะตั€ะตะถัƒ", + "available-interfaces": "ะ”ะพัั‚ัƒะฟะฝั– ั–ะฝั‚ะตั€ั„ะตะนัะธ", + "available-networks": "ะ”ะพัั‚ัƒะฟะฝั– ะผะตั€ะตะถั–", + "disabled": "Wi-Fi ะฒะธะผะบะฝะตะฝะพ", + "disconnecting": "ะ’ั–ะดะบะปัŽั‡ะตะฝะฝั...", + "dns": "DNS", + "enable-message": "ะฃะฒั–ะผะบะฝั–ั‚ัŒ Wi-Fi, ั‰ะพะฑ ะฟะพะฑะฐั‡ะธั‚ะธ ะดะพัั‚ัƒะฟะฝั– ะผะตั€ะตะถั–.", + "enter-password": "ะ’ะฒะตะดั–ั‚ัŒ ะฟะฐั€ะพะปัŒ...", + "forget": "ะ—ะฐะฑัƒั‚ะธ", + "forget-network": "ะ—ะฐะฑัƒั‚ะธ ั†ัŽ ะผะตั€ะตะถัƒ?", + "forgetting": "ะ—ะฐะฑัƒะฒะฐะฝะฝั...", + "hidden-network": "ะŸั€ะธั…ะพะฒะฐะฝะฐ ะผะตั€ะตะถะฐ", + "interface": "ะœะตั€ะตะถะตะฒะธะน ั–ะฝั‚ะตั€ั„ะตะนั", + "internet-connected": "ะŸั–ะดะบะปัŽั‡ะตะฝะพ ะดะพ ะ†ะฝั‚ะตั€ะฝะตั‚ัƒ", + "internet-limited": "ะะตะผะฐั” ั–ะฝั‚ะตั€ะฝะตั‚ัƒ", + "internet-status": "ะกั‚ะฐั‚ัƒั ะ†ะฝั‚ะตั€ะฝะตั‚ัƒ", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "ะ’ั–ะดะพะผั– ะผะตั€ะตะถั–", + "link-speed": "ะจะฒะธะดะบั–ัั‚ัŒ ะท'ั”ะดะฝะฐะฝะฝั", + "network-name-ssid": "ะ†ะผ'ั ะผะตั€ะตะถั– (SSID)", + "no-ethernet-devices": "ะะต ะฒะธัะฒะปะตะฝะพ Ethernet-ะฟั€ะธัั‚ั€ะพั—ะฒ", + "no-networks": "ะœะตั€ะตะถ ะฝะต ะทะฝะฐะนะดะตะฝะพ", + "saved": "ะ—ะฑะตั€ะตะถะตะฝะพ", + "scan-again": "ะกะบะฐะฝัƒะฒะฐั‚ะธ ะทะฝะพะฒัƒ", + "searching": "ะŸะพัˆัƒะบ ะฑะปะธะทัŒะบะธั… ะผะตั€ะตะถ...", + "security-open": "ะ’ั–ะดะบั€ะธั‚ะธ", + "security-wep": "WEP", + "security-wpa": "WPA/WPA2/WPA3 ะŸะตั€ัะพะฝะฐะปัŒะฝะธะน", + "security-wpa-ent": "WPA Enterprise", + "security-wpa2-ent": "WPA2 Enterprise", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 ะ•ะฝั‚ะตั€ะฟั€ะฐะนะท", + "show-password": "ะŸะพะบะฐะทะฐั‚ะธ ะฟะฐั€ะพะปัŒ", + "title": "Wi-Fi" + }, + "signal": { + "excellent": "ะ’ั–ะดะผั–ะฝะฝะธะน", + "fair": "ะกะตั€ะตะดะฝั–ะน", + "good": "ะฅะพั€ะพัˆะธะน", + "poor": "ะŸะพะณะฐะฝะธะน", + "weak": "ะกะปะฐะฑะบะธะน" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/vi.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/vi.json new file mode 100644 index 0000000..f9e5dd3 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/vi.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "Xรณa lแป‹ch sแปญ", + "close-app": "ฤรณng {app}", + "connect-vpn": "Kแบฟt nแป‘i tแป›i {name}", + "cycle-visualizer": "Chuyแปƒn ฤ‘แป•i visualizer cho รขm thanh", + "disable-bluetooth": "Tแบฏt Bluetooth", + "disable-dnd": "Tแบฏt chแบฟ ฤ‘แป™ khรดng lร m phiแปn", + "disable-wifi": "Tแบฏt Wi-Fi", + "disconnect-vpn": "Ngแบฏt kแบฟt nแป‘i {name}", + "dock-settings": "Cร i ฤ‘แบทt Dock", + "enable-bluetooth": "Bแบญt Bluetooth", + "enable-dnd": "Bแบญt Khรดng lร m phiแปn", + "enable-wifi": "Bแบญt Wi-Fi", + "launcher-settings": "Cร i ฤ‘แบทt Trรฌnh khแปŸi chแบกy", + "lower-to-bottom": "Chuyแปƒn xuแป‘ng dฦฐแป›i cรนng", + "open-calendar": "MแปŸ lแป‹ch", + "open-display-settings": "Cร i ฤ‘แบทt hiแปƒn thแป‹", + "open-launcher": "MแปŸ trรฌnh khแปŸi chแบกy", + "open-settings": "MแปŸ cร i ฤ‘แบทt", + "raise-to-top": "ฤฦฐa lรชn trรชn cรนng", + "random-wallpaper": "Hรฌnh nแปn ngแบซu nhiรชn", + "run-custom-command": "Chแบกy cรขu lแป‡nh tแปฑ tรนy chแป‰nh", + "show-active-only": "Chแป‰ hiแปƒn thแป‹ ฤ‘ang hoแบกt ฤ‘แป™ng", + "show-all": "Hiแปƒn thแป‹ tแบฅt cแบฃ", + "toggle-mute": "Bแบญt/tแบฏt tiแบฟng", + "widget-settings": "Cร i ฤ‘แบทt Tiแป‡n รญch" + }, + "authentication": { + "error": "Lแป—i xรกc thแปฑc", + "failed": "Xรกc thแปฑc thแบฅt bแบกi" + }, + "bar": { + "active-window": { + "colorize-icons-description": "รp dแปฅng mร u giao diแป‡n cho biแปƒu tฦฐแปฃng cแปญa sแป• ฤ‘ang hoแบกt ฤ‘แป™ng.", + "hide-mode-description": "ฤiแปu khiแปƒn cรกch tiแป‡n รญch hoแบกt ฤ‘แป™ng khi khรดng cรณ cแปญa sแป• ฤ‘ang hoแบกt ฤ‘แป™ng.", + "scrolling-mode-description": "ฤiแปu khiแปƒn khi nร o bแบญt cuแป™n vฤƒn bแบฃn cho cรกc tiรชu ฤ‘แป cแปญa sแป• dร i.", + "show-app-icon-description": "Hiแปƒn thแป‹ biแปƒu tฦฐแปฃng แปฉng dแปฅng bรชn cแบกnh tiรชu ฤ‘แป cแปญa sแป•.", + "show-app-icon-label": "Hiแปƒn thแป‹ biแปƒu tฦฐแปฃng แปฉng dแปฅng", + "show-app-text-description": "Hiแปƒn thแป‹ tiรชu ฤ‘แป cแปงa cแปญa sแป• ฤ‘ang hoแบกt ฤ‘แป™ng.", + "show-app-text-label": "Hiแปƒn thแป‹ tiรชu ฤ‘แป cแปญa sแป•" + }, + "audio-visualizer": { + "color-name-description": "Chแปn mร u cho bแป™ hiแปƒn thแป‹ trแปฑc quan.", + "color-name-label": "Tรด toร n bแป™ mร u cho audio visualizer", + "height-description": "Tรนy chแป‰nh ฤ‘แป™ cao cแปงa thร nh phแบงn", + "hide-when-idle-description": "Khi bแบญt, bแป™ hiแปƒn thแป‹ sแบฝ bแป‹ แบฉn trแปซ khi cรณ trรฌnh phรกt ฤ‘ang phรกt.", + "hide-when-idle-label": "แบจn khi khรดng phรกt ฤ‘a phฦฐฦกng tiแป‡n", + "width-description": "Tรนy chแป‰nh ฤ‘แป™ rแป™ng cแปงa thร nh phแบงn" + }, + "battery": { + "device-default": "Mแบทc ฤ‘แป‹nh (Thiแบฟt bแป‹ hiแปƒn thแป‹)", + "device-description": "Chแปn pin ฤ‘แปƒ hiแปƒn thแป‹.", + "device-label": "Pin", + "display-mode-description": "Chแปn cรกch pin ฤ‘ฦฐแปฃc hiแปƒn thแป‹ trรชn thanh Bar.", + "display-mode-graphic": "Dแบกng pin cรณ ฤ‘แป“ hแปa", + "display-mode-graphic-clean": "Dแบกng pin cรณ ฤ‘แป“ hแปa (khรดng %)", + "display-mode-icon-always": "Biแปƒu tฦฐแปฃng - Luรดn hiแปƒn thแป‹ %", + "display-mode-icon-hover": "Biแปƒu tฦฐแปฃng - Hiแปƒn thแป‹ khi di chuแป™t", + "display-mode-icon-only": "Chแป‰ biแปƒu tฦฐแปฃng", + "hide-if-idle-description": "แบจn tiแป‡n รญch khi pin khรดng sแบกc hoแบทc xแบฃ.", + "hide-if-idle-label": "แบจn khi khรดng hoแบกt ฤ‘แป™ng", + "hide-if-not-detected-description": "แบจn tiแป‡n รญch khi khรดng tรฌm thแบฅy pin trรชn hแป‡ thแป‘ng.", + "hide-if-not-detected-label": "แบจn khi khรดng ฤ‘ฦฐแปฃc phรกt hiแป‡n", + "low-battery-threshold-label": "Ngฦฐแปกng cแบฃnh bรกo pin yแบฟu", + "show-noctalia-performance-description": "Hiแปƒn thแป‹ nรบt chuyแปƒn ฤ‘แป•i sang chแบฟ ฤ‘แป™ hiแป‡u nฤƒng cแปงa Noctalia bรชn trong bแบฃng ฤ‘iแปu khiแปƒn pin.
Tแบฏt ฤ‘แป• bรณng vร  hoแบกt แบฃnh trong Noctalia ฤ‘แปƒ giแบฃm mแปฉc sแปญ dแปฅng tร i nguyรชn.", + "show-noctalia-performance-label": "Hiแปƒn thแป‹ cรดng tแบฏc hiแป‡u nฤƒng cแปงa Noctalia", + "show-power-profile-description": "Hiแปƒn thแป‹ lแปฑa chแปn cแบฅu hรฌnh nguแป“n bรชn trong bแบฃng ฤ‘iแปu khiแปƒn pin.", + "show-power-profile-label": "Hiแปƒn thแป‹ ฤ‘iแปu khiแปƒn hแป“ sฦก nฤƒng lฦฐแปฃng" + }, + "brightness": { + "apply-all-description": "Thay ฤ‘แป•i ฤ‘แป™ sรกng cho mแปi mร n hรฌnh khi cuแป™n chuแป™t.", + "apply-all-label": "รp dแปฅng thay ฤ‘แป•i cuแป™n cho tแบฅt cแบฃ mร n hรฌnh" + }, + "clock": { + "clock-display-description": "Tรนy chแป‰nh hiแปƒn thแป‹ ฤ‘แป“ng hแป“ cแปงa bแบกn bแบฑng cรกch thรชm cรกc token tแปซ danh sรกch bรชn dฦฐแป›i. ฤแปƒ sแปญ dแปฅng ฤ‘แป‹nh dแบกng 12 giแป, bแบกn phแบฃi bao gแป“m token 'AP'.", + "clock-display-label": "Hiแปƒn thแป‹ ฤแป“ng hแป“", + "custom-font-description": "Chแปn mแป™t phรดng chแปฏ tรนy chแป‰nh cho hiแปƒn thแป‹ ฤ‘แป“ng hแป“.", + "custom-font-label": "Phรดng chแปฏ tรนy chแป‰nh", + "custom-font-placeholder": "Chแปn phรดng chแปฏ tรนy chแป‰nh...", + "custom-font-search-placeholder": "Tรฌm kiแบฟm phรดng chแปฏ...", + "horizontal-bar-description": "Mแบนo: Dรนng \\n ฤ‘แปƒ xuแป‘ng dรฒng.", + "horizontal-bar-label": "Thanh ngang", + "preview": "Xem trฦฐแป›c", + "tooltip-format-description": "Chuแป—i ฤ‘แป‹nh dแบกng cho chรบ giแบฃi cรดng cแปฅ hiแปƒn thแป‹ khi di chuแป™t qua ฤ‘แป“ng hแป“. ฤแปƒ trแป‘ng ฤ‘แปƒ dรนng chรบ giแบฃi cรดng cแปฅ mแบทc ฤ‘แป‹nh.", + "tooltip-format-label": "ฤแป‹nh dแบกng chรบ giแบฃi cรดng cแปฅ", + "use-custom-font-description": "Ghi ฤ‘รจ lแปฑa chแปn phรดng chแปฏ mแบทc ฤ‘แป‹nh bแบฑng mแป™t phรดng chแปฏ tรนy chแป‰nh cho ฤ‘แป“ng hแป“.", + "use-custom-font-label": "Sแปญ dแปฅng phรดng chแปฏ tรนy chแป‰nh", + "use-monospaced-font-description": "Khi ฤ‘ฦฐแปฃc bแบญt, ฤ‘แป“ng hแป“ sแบฝ sแปญ dแปฅng phรดng chแปฏ ฤ‘ฦกn cรกch.", + "use-monospaced-font-label": "Sแปญ dแปฅng phรดng chแปฏ ฤ‘ฦกn cรกch", + "vertical-bar-description": "Sแปญ dแปฅng mแป™t dแบฅu cรกch ฤ‘แปƒ phรขn tรกch mแป—i phแบงn xuแป‘ng mแป™t dรฒng mแป›i.", + "vertical-bar-label": "Thanh dแปc" + }, + "control-center": { + "browse-file": "Duyแป‡t Tแป‡p", + "browse-library": "Duyแป‡t Thฦฐ viแป‡n", + "color-selection-description": "รp dแปฅng mร u sแบฏc chแปง ฤ‘แป cho biแปƒu tฦฐแปฃng.", + "enable-colorization-description": "Bแบญt tรด mร u cho biแปƒu tฦฐแปฃng, รกp dแปฅng mร u sแบฏc chแปง ฤ‘แป.", + "icon-description": "Chแปn mแป™t biแปƒu tฦฐแปฃng tแปซ thฦฐ viแป‡n hoแบทc mแป™t tแป‡p tรนy chแป‰nh.", + "select-custom-icon": "Chแปn mแป™t biแปƒu tฦฐแปฃng tรนy chแป‰nh", + "use-distro-logo-description": "Sแปญ dแปฅng biแปƒu tฦฐแปฃng cแปงa bแบฃn phรขn phแป‘i cแปงa bแบกn thay vรฌ mแป™t biแปƒu tฦฐแปฃng tรนy chแป‰nh.", + "use-distro-logo-label": "Sแปญ dแปฅng logo distro thay vรฌ biแปƒu tฦฐแปฃng" + }, + "custom-button": { + "collapse-condition-description": "Nแบฟu vฤƒn bแบฃn ฤ‘แบงu ra khแป›p vแป›i giรก trแป‹ nร y, nรบt sแบฝ thu gแปn.", + "collapse-condition-label": "ฤiแปu kiแป‡n thu gแปn", + "color-selection-description": "รp dแปฅng mร u sแบฏc chแปง ฤ‘แป cho biแปƒu tฦฐแปฃng vร  vฤƒn bแบฃn.", + "default-tooltip": "Nรบt tรนy chแป‰nh, cแบฅu hรฌnh trong cร i ฤ‘แบทt", + "display-command-output-description": "Nhแบญp mแป™t lแป‡nh ฤ‘แปƒ chแบกy theo mแป™t khoแบฃng thแปi gian ฤ‘แปu ฤ‘แบทn. Dรฒng ฤ‘แบงu tiรชn cแปงa ฤ‘แบงu ra cแปงa nรณ sแบฝ ฤ‘ฦฐแปฃc hiแปƒn thแป‹ dฦฐแป›i dแบกng vฤƒn bแบฃn.", + "display-command-output-label": "Hiแปƒn thแป‹ kแบฟt quแบฃ lแป‡nh", + "display-command-output-stream-description": "Nhแบญp lแป‡nh ฤ‘แปƒ chแบกy liรชn tแปฅc.", + "dynamic-text": "Vฤƒn bแบฃn ฤ‘แป™ng", + "enable-colorization-description": "Bแบญt tรด mร u cho biแปƒu tฦฐแปฃng vร  vฤƒn bแบฃn nรบt tรนy chแป‰nh, รกp dแปฅng mร u sแบฏc chแปง ฤ‘แป.", + "enable-colorization-label": "Bแบญt tรด mร u", + "general-tooltip-text-description": "Vฤƒn bแบฃn tรนy chแป‰nh hiแปƒn thแป‹ trong chรบ giแบฃi cรดng cแปฅ cแปงa nรบt.", + "general-tooltip-text-label": "Vฤƒn bแบฃn chรบ giแบฃi tรนy chแป‰nh", + "hide-mode-always-expanded": "Luรดn mแปŸ rแป™ng", + "hide-mode-description": "ฤiแปu khiแปƒn hiแปƒn thแป‹ tiแป‡n รญch khi lแป‡nh khรดng cรณ ฤ‘แบงu ra.", + "hide-mode-expand-with-output": "MแปŸ rแป™ng khi cรณ ฤ‘แบงu ra", + "hide-mode-label": "Chแบฟ ฤ‘แป™ แบฉn", + "hide-mode-max-transparent": "MแปŸ rแป™ng tแป‘i ฤ‘a nhฦฐng trong suแป‘t", + "icon-description": "Chแปn mแป™t biแปƒu tฦฐแปฃng tแปซ thฦฐ viแป‡n.", + "ipc-identifier-description": "Mรฃ ฤ‘แป‹nh danh duy nhแบฅt cho cรกc lแป‡nh IPC. Sแปญ dแปฅng mรฃ ฤ‘แป‹nh danh nร y vแป›i 'qs -c noctalia-shell ipc call cb [action] [identifier]' ฤ‘แปƒ ฤ‘iแปu khiแปƒn nรบt nร y qua IPC.", + "ipc-identifier-label": "Mรฃ ฤ‘แป‹nh danh IPC", + "left-click-description": "Lแป‡nh thแปฑc thi khi nรบt ฤ‘ฦฐแปฃc nhแบฅp chuแป™t trรกi.", + "left-click-label": "Nhแบฅp chuแป™t trรกi", + "left-click-update-text": "Cแบญp nhแบญt vฤƒn bแบฃn hiแปƒn thแป‹ khi nhแบฅp chuแป™t trรกi", + "max-text-length-horizontal-description": "Sแป‘ kรฝ tแปฑ tแป‘i ฤ‘a hiแปƒn thแป‹ trรชn Thanh ngang (0 ฤ‘แปƒ แบฉn vฤƒn bแบฃn).", + "max-text-length-horizontal-label": "ฤแป™ dร i vฤƒn bแบฃn tแป‘i ฤ‘a (ngang)", + "max-text-length-vertical-description": "Sแป‘ kรฝ tแปฑ tแป‘i ฤ‘a hiแปƒn thแป‹ trong thanh dแปc (0 ฤ‘แปƒ แบฉn vฤƒn bแบฃn).", + "max-text-length-vertical-label": "Chiแปu dร i vฤƒn bแบฃn tแป‘i ฤ‘a (chiแปu dแปc)", + "middle-click-description": "Lแป‡nh sแบฝ thแปฑc thi khi nรบt ฤ‘ฦฐแปฃc nhแบฅp chuแป™t giแปฏa.", + "middle-click-label": "Nhแบฅp chuแป™t giแปฏa", + "middle-click-update-text": "Cแบญp nhแบญt chแปฏ hiแปƒn thแป‹ khi nhแบฅp chuแป™t giแปฏa", + "parse-json-description": "Phรขn tรญch ฤ‘แบงu ra cแปงa lแป‡nh dฦฐแป›i dแบกng mแป™t ฤ‘แป‘i tฦฐแปฃng JSON ฤ‘แปƒ thiแบฟt lแบญp ฤ‘แป™ng vฤƒn bแบฃn vร  biแปƒu tฦฐแปฃng.", + "parse-json-label": "Phรขn tรญch ฤ‘แบงu ra dฦฐแป›i dแบกng JSON", + "refresh-interval-description": "Khoแบฃng thแปi gian tรญnh bแบฑng mili giรขy.", + "refresh-interval-label": "Khoแบฃng thแปi gian lร m mแป›i", + "right-click-description": "Lแป‡nh thแปฑc thi khi nhแบฅp chuแป™t phแบฃi vร o nรบt.", + "right-click-label": "Nhแบฅp chuแป™t phแบฃi", + "right-click-update-text": "Cแบญp nhแบญt vฤƒn bแบฃn hiแปƒn thแป‹ khi nhแบฅp chuแป™t phแบฃi", + "show-exec-tooltip-description": "Hiแปƒn thแป‹ chรบ giแบฃi cรดng cแปฅ vแป›i chi tiแบฟt lแป‡nh (nhแบฅp chuแป™t trรกi/phแบฃi/giแปฏa, cuแป™n).", + "show-exec-tooltip-label": "Hiแปƒn thแป‹ chรบ giแบฃi cรดng cแปฅ lแป‡nh.", + "show-icon-description": "Bแบญt/tแบฏt hiแปƒn thแป‹ biแปƒu tฦฐแปฃng cแปงa tiแป‡n รญch.", + "show-icon-label": "Hiแปƒn thแป‹ biแปƒu tฦฐแปฃng", + "show-text-tooltip-description": "Hiแปƒn thแป‹ chรบ giแบฃi cรดng cแปฅ vแป›i ฤ‘แบงu ra tแปซ lแป‡nh vฤƒn bแบฃn.", + "show-text-tooltip-label": "Hiแปƒn thแป‹ chรบ giแบฃi cรดng cแปฅ vฤƒn bแบฃn ฤ‘แป™ng", + "text-stream-description": "Cรกc dรฒng ฤ‘แบงu ra tแปซ lแป‡nh sแบฝ ฤ‘ฦฐแปฃc hiแปƒn thแป‹ dฦฐแป›i dแบกng vฤƒn bแบฃn trรชn nรบt.", + "text-stream-label": "Luแป“ng", + "wheel-description": "Lแป‡nh ฤ‘แปƒ thแปฑc thi khi con lฤƒn chuแป™t ฤ‘ฦฐแปฃc sแปญ dแปฅng.
Sแปญ dแปฅng $delta cho giรก trแป‹ delta cแปงa con lฤƒn chuแป™t trong lแป‡nh.", + "wheel-down": "Cuแป™n xuแป‘ng", + "wheel-down-description": "Lแป‡nh thแปฑc thi khi con lฤƒn chuแป™t cuแป™n xuแป‘ng.", + "wheel-down-label": "Lแป‡nh cuแป™n xuแป‘ng", + "wheel-label": "Con lฤƒn chuแป™t", + "wheel-mode-separate-description": "Bแบญt cรกc lแป‡nh riรชng biแป‡t cho cuแป™n lรชn vร  xuแป‘ng.", + "wheel-mode-separate-label": "Tรกch riรชng cรกc lแป‡nh cuแป™n", + "wheel-up": "Cuแป™n lรชn", + "wheel-up-description": "Lแป‡nh thแปฑc thi khi con lฤƒn chuแป™t cuแป™n lรชn.", + "wheel-up-label": "Lแป‡nh cuแป™n lรชn", + "wheel-update-text": "Cแบญp nhแบญt vฤƒn bแบฃn hiแปƒn thแป‹ khi cuแป™n" + }, + "keyboard-layout": { + "show-icon-description": "Hiแปƒn thแป‹ biแปƒu tฦฐแปฃng bแป‘ cแปฅc bร n phรญm." + }, + "lock-keys": { + "hide-when-off-description": "แบจn chแป‰ bรกo khi phรญm khรดng hoแบกt ฤ‘แป™ng.", + "hide-when-off-label": "แบจn khi tแบฏt", + "show-caps-lock-description": "Hiแปƒn thแป‹ trแบกng thรกi Caps Lock.", + "show-caps-lock-label": "Caps Lock", + "show-num-lock-description": "Hiแปƒn thแป‹ trแบกng thรกi Num Lock.", + "show-num-lock-label": "Num Lock", + "show-scroll-lock-description": "Hiแปƒn thแป‹ trแบกng thรกi Scroll Lock.", + "show-scroll-lock-label": "Scroll Lock" + }, + "media-mini": { + "compact-mode-description": "Bแบญt bแป‘ cแปฅc tiแบฟt kiแป‡m khรดng gian cho bแบฃng ฤ‘iแปu khiแปƒn trรฌnh phรกt media.", + "compact-mode-label": "Chแบฟ ฤ‘แป™ thu gแปn", + "hide-mode-description": "ฤiแปu khiแปƒn cรกch tiแป‡n รญch hoแบกt ฤ‘แป™ng khi khรดng cรณ phฦฐฦกng tiแป‡n nร o ฤ‘ang phรกt.", + "max-width-description": "ฤแบทt kรญch thฦฐแป›c ngang tแป‘i ฤ‘a cแปงa tiแป‡n รญch. Tiแป‡n รญch sแบฝ co lแบกi ฤ‘แปƒ vแปซa vแป›i nแป™i dung ngแบฏn hฦกn.", + "no-active-player": "Khรดng cรณ trรฌnh phรกt nร o ฤ‘ang hoแบกt ฤ‘แป™ng", + "panel-section-description": "Cแบฅu hรฌnh giao diแป‡n vร  hร nh vi cแปงa bแบฃng ฤ‘iแปu khiแปƒn trรฌnh phรกt media.", + "panel-section-label": "Bแบฃng ฤ‘iแปu khiแปƒn trรฌnh phรกt ฤ‘a phฦฐฦกng tiแป‡n", + "scrolling-mode-description": "ฤiแปu khiแปƒn khi tรญnh nฤƒng cuแป™n vฤƒn bแบฃn ฤ‘ฦฐแปฃc bแบญt cho cรกc tiรชu ฤ‘แป bร i hรกt dร i.", + "scrolling-mode-label": "Chแบฟ ฤ‘แป™ cuแป™n", + "show-album-art-description": "Hiแปƒn thแป‹ แบฃnh bรฌa album cแปงa bร i hรกt ฤ‘ang phรกt.", + "show-album-art-label": "Hiแปƒn thแป‹ แบฃnh bรฌa album", + "show-artist-first-description": "Hiแปƒn thแป‹ nghแป‡ sฤฉ - tiรชu ฤ‘แป thay vรฌ tiรชu ฤ‘แป - nghแป‡ sฤฉ.", + "show-artist-first-label": "Hiแป‡n nghแป‡ sฤฉ trฦฐแป›c", + "show-progress-ring-description": "Hiแปƒn thแป‹ chแป‰ bรกo tiแบฟn ฤ‘แป™ dแบกng vรฒng trรฒn cho biแบฟt tiแบฟn ฤ‘แป™ bร i hรกt.", + "show-progress-ring-label": "Hiแปƒn thแป‹ vรฒng tiแบฟn trรฌnh", + "show-visualizer-description": "Hiแปƒn thแป‹ trแปฑc quan hรณa รขm thanh khi nhแบกc ฤ‘ang phรกt.", + "show-visualizer-label": "Hiแปƒn thแป‹ trรฌnh hiแปƒn thแป‹", + "use-fixed-width-description": "Khi ฤ‘ฦฐแปฃc bแบญt, tiแป‡n รญch sแบฝ luรดn sแปญ dแปฅng chiแปu rแป™ng tแป‘i ฤ‘a thay vรฌ tแปฑ ฤ‘แป™ng ฤ‘iแปu chแป‰nh theo nแป™i dung.", + "use-fixed-width-label": "Sแปญ dแปฅng chiแปu rแป™ng cแป‘ ฤ‘แป‹nh", + "visualizer-type-description": "Chแปn kiแปƒu trแปฑc quan hรณa รขm thanh ฤ‘แปƒ hiแปƒn thแป‹.", + "visualizer-type-label": "Kiแปƒu hiแปƒn thแป‹" + }, + "notification-history": { + "hide-widget-when-zero-description": "แบจn biแปƒu tฦฐแปฃng thรดng bรกo khi khรดng cรณ thรดng bรกo nร o.", + "hide-widget-when-zero-label": "แบจn biแปƒu tฦฐแปฃng khi khรดng cรณ thรดng bรกo", + "hide-widget-when-zero-unread-description": "แบจn biแปƒu tฦฐแปฃng thรดng bรกo khi khรดng cรณ thรดng bรกo chฦฐa ฤ‘แปc nร o.", + "hide-widget-when-zero-unread-label": "แบจn biแปƒu tฦฐแปฃng khi khรดng cรณ thรดng bรกo chฦฐa ฤ‘แปc", + "show-unread-badge-description": "Hiแปƒn thแป‹ mแป™t chแป‰ bรกo cho biแบฟt sแป‘ lฦฐแปฃng thรดng bรกo chฦฐa ฤ‘แปc.", + "show-unread-badge-label": "Hiแปƒn thแป‹ dแบฅu hiแป‡u chฦฐa ฤ‘แปc", + "unread-badge-color-description": "Chแปn mร u cho huy hiแป‡u thรดng bรกo chฦฐa ฤ‘แปc.", + "unread-badge-color-label": "Mร u huy hiแป‡u chฦฐa ฤ‘แปc" + }, + "section-editor": { + "placeholder": "Chแปn mแป™t tiแป‡n รญch ฤ‘แปƒ thรชm...", + "search-placeholder": "Tรฌm kiแบฟm tiแป‡n รญch..." + }, + "spacer": { + "width-description": "Chiแปu rแป™ng khoแบฃng cรกch theo pixel." + }, + "system-monitor": { + "compact-mode-description": "Hiแปƒn thแป‹ sแป‘ liแป‡u thแป‘ng kรช dฦฐแป›i dแบกng biแปƒu ฤ‘แป“ thanh nhแป thay vรฌ giรก trแป‹ vฤƒn bแบฃn. Ngฤƒn chแบทn viแป‡c dแป‹ch chuyแปƒn bแป‘ cแปฅc.", + "compact-mode-label": "Chแบฟ ฤ‘แป™ thu gแปn", + "cpu-cores-description": "Hiแปƒn thแป‹ mแปฉc sแปญ dแปฅng tแปซng lรตi CPU riรชng lแบป.", + "cpu-cores-label": "Lรตi CPU", + "cpu-frequency-description": "Hiแปƒn thแป‹ tแป‘c ฤ‘แป™ xung nhแป‹p CPU hiแป‡n tแบกi tรญnh bแบฑng GHz.", + "cpu-frequency-label": "Hiแปƒn thแป‹ tแบงn sแป‘ CPU", + "cpu-temperature-description": "Hiแปƒn thแป‹ nhiแป‡t ฤ‘แป™ CPU nแบฟu cรณ.", + "cpu-temperature-label": "Nhiแป‡t ฤ‘แป™ CPU", + "cpu-usage-description": "Hiแปƒn thแป‹ phแบงn trฤƒm sแปญ dแปฅng CPU hiแป‡n tแบกi.", + "cpu-usage-label": "Mแปฉc sแปญ dแปฅng CPU", + "disk-path-description": "Chแปn ฤ‘iแปƒm gแบฏn kแบฟt ฤ‘ฤฉa nร o ฤ‘แปƒ giรกm sรกt.", + "disk-path-label": "ฤฦฐแปng dแบซn แป• ฤ‘ฤฉa", + "gpu-temperature-description": "Hiแปƒn thแป‹ thรดng sแป‘ nhiแป‡t ฤ‘แป™ GPU nแบฟu cรณ.", + "load-average-description": "Hiแปƒn thแป‹ mแปฉc tแบฃi trแปng trung bรฌnh cแปงa hแป‡ thแป‘ng.", + "load-average-label": "Mแปฉc tแบฃi trแปng trung bรฌnh", + "memory-percentage-description": "Hiแปƒn thแป‹ mแปฉc sแปญ dแปฅng bแป™ nhแป› dฦฐแป›i dแบกng phแบงn trฤƒm thay vรฌ giรก trแป‹ tuyแป‡t ฤ‘แป‘i.", + "memory-percentage-label": "Bแป™ nhแป› theo phแบงn trฤƒm", + "memory-usage-description": "Hiแปƒn thแป‹ thรดng tin sแปญ dแปฅng RAM hiแป‡n tแบกi.", + "memory-usage-label": "Sแปญ dแปฅng bแป™ nhแป›", + "network-traffic-description": "Hiแปƒn thแป‹ tแป‘c ฤ‘แป™ tแบฃi lรชn vร  tแบฃi xuแป‘ng mแบกng.", + "network-traffic-label": "Lฦฐu lฦฐแปฃng mแบกng", + "storage-as-percentage-description": "Hiแปƒn thแป‹ dung lฦฐแปฃng ฤ‘ฤฉa dฦฐแป›i dแบกng phแบงn trฤƒm thay vรฌ giรก trแป‹ tuyแป‡t ฤ‘แป‘i.", + "storage-as-percentage-label": "แป” ฤ‘ฤฉa dฦฐแป›i dแบกng phแบงn trฤƒm", + "storage-available-description": "Hiแปƒn thแป‹ dung lฦฐแปฃng ฤ‘ฤฉa khแบฃ dแปฅng thay vรฌ dung lฦฐแปฃng ฤ‘รฃ sแปญ dแปฅng.", + "storage-available-label": "Dung lฦฐแปฃng ฤ‘ฤฉa khแบฃ dแปฅng", + "storage-usage-description": "Hiแปƒn thแป‹ thรดng tin dung lฦฐแปฃng แป• ฤ‘ฤฉa.", + "storage-usage-label": "Sแปญ dแปฅng bแป™ nhแป›", + "swap-usage-description": "Hiแปƒn thแป‹ mแปฉc sแปญ dแปฅng bแป™ nhแป› swap.", + "swap-usage-label": "Mแปฉc sแปญ dแปฅng bแป™ nhแป› swap", + "use-monospace-font-description": "Sแปญ dแปฅng phรดng chแปฏ ฤ‘ฦกn cรกch ฤ‘แปƒ cรณ chiแปu rแป™ng kรฝ tแปฑ nhแบฅt quรกn.", + "use-monospace-font-label": "Phรดng chแปฏ ฤ‘ฦกn cรกch", + "use-padding-description": "ฤแป‡m cรกc giรก trแป‹ vฤƒn bแบฃn bแบฑng khoแบฃng trแบฏng ฤ‘แบงu dรฒng ฤ‘แปƒ ngฤƒn chแบทn sแปฑ dแป‹ch chuyแปƒn bแป‘ cแปฅc.", + "use-padding-description-disabled-monospace-font": "Tรญnh nฤƒng nร y cแบงn mแป™t phรดng chแปฏ ฤ‘ฦกn cรกch.", + "use-padding-description-disabled-vertical": "Thanh tรกc vแปฅ dแปc khรดng hแป— trแปฃ khoแบฃng ฤ‘แป‡m vฤƒn bแบฃn.", + "use-padding-label": "Khoแบฃng ฤ‘แป‡m vฤƒn bแบฃn" + }, + "taskbar": { + "colorize-icons-description": "รp dแปฅng mร u sแบฏc chแปง ฤ‘แป cho biแปƒu tฦฐแปฃng thanh tรกc vแปฅ.", + "hide-mode-description": "ฤiแปu khiแปƒn cรกch tiแป‡n รญch con hoแบกt ฤ‘แป™ng khi khรดng cรณ cแปญa sแป• phรน hแปฃp nร o.", + "hide-mode-label": "Chแบฟ ฤ‘แป™ แบฉn", + "icon-scale-description": "ฤแบทt hแป‡ sแป‘ tแป‰ lแป‡ cho cรกc biแปƒu tฦฐแปฃng trรชn thanh tรกc vแปฅ.", + "icon-scale-label": "Kรญch thฦฐแป›c biแปƒu tฦฐแปฃng", + "max-width-description": "Chiแปu rแป™ng tแป‘i ฤ‘a cแปงa taskbar dฦฐแป›i dแบกng phแบงn trฤƒm chiแปu rแป™ng mร n hรฌnh.", + "max-width-label": "Chiแปu rแป™ng tแป‘i ฤ‘a", + "only-active-workspaces-description": "Chแป‰ hiแปƒn thแป‹ แปฉng dแปฅng tแปซ khรดng gian lร m viแป‡c ฤ‘ang hoแบกt ฤ‘แป™ng.", + "only-active-workspaces-label": "Chแป‰ tแปซ cรกc khรดng gian lร m viแป‡c ฤ‘ang hoแบกt ฤ‘แป™ng", + "only-same-monitor-description": "Chแป‰ hiแปƒn thแป‹ แปฉng dแปฅng tแปซ mร n hรฌnh nฦกi Bar ฤ‘ฦฐแปฃc ฤ‘แบทt.", + "only-same-monitor-label": "Chแป‰ tแปซ cรนng mร n hรฌnh", + "show-pinned-apps-description": "Hiแปƒn thแป‹ แปฉng dแปฅng ฤ‘รฃ ghim tแปซ thanh Dock trong thanh tรกc vแปฅ.", + "show-pinned-apps-label": "Hiแปƒn thแป‹ แปฉng dแปฅng ฤ‘รฃ ghim", + "show-title-description": "Hiแปƒn thแป‹ tiรชu ฤ‘แป cแปญa sแป• trรชn thanh tรกc vแปฅ.", + "show-title-description-disabled": "Thanh tรกc vแปฅ dแปc khรดng hแป— trแปฃ hiแปƒn thแป‹ tiรชu ฤ‘แป.", + "show-title-label": "Hiแปƒn thแป‹ tiรชu ฤ‘แป", + "smart-width-description": "Tแปฑ ฤ‘แป™ng ฤ‘iแปu chแป‰nh ฤ‘แป™ rแป™ng mแปฅc nhแบญp dแปฑa trรชn sแป‘ lฦฐแปฃng mแปฅc nhแบญp.", + "smart-width-label": "Chiแปu rแป™ng thรดng minh", + "title-width-description": "ฤแบทt chiแปu rแป™ng cแปงa tiรชu ฤ‘แป cแปญa sแป• trรชn thanh tรกc vแปฅ (bแบฑng pixel).", + "title-width-label": "Chiแปu rแป™ng tiรชu ฤ‘แป", + "title-width-reset-tooltip": "ฤแบทt lแบกi chiแปu rแป™ng tiรชu ฤ‘แป" + }, + "tray": { + "chevron-color-description": "รp dแปฅng mร u chแปง ฤ‘แป cho biแปƒu tฦฐแปฃng mลฉi tรชn ngฤƒn kรฉo.", + "chevron-color-label": "Mร u mลฉi tรชn", + "colorize-icons-description": "รp dแปฅng mร u sแบฏc chแปง ฤ‘แป cho cรกc biแปƒu tฦฐแปฃng khay.", + "colorize-icons-label": "Tรด mร u biแปƒu tฦฐแปฃng", + "drawer-enabled-description": "Khi bแบญt, cรกc mแปฅc khay chฦฐa ghim sแบฝ hiแปƒn thแป‹ trong mแป™t Panel ngฤƒn kรฉo.
Khi tแบฏt, tแบฅt cแบฃ cรกc mแปฅc khay sแบฝ hiแปƒn thแป‹ trแปฑc tiแบฟp.", + "drawer-enabled-label": "Bแบญt ngฤƒn", + "hide-passive-description": "Khi bแบญt, cรกc mแปฅc khay cรณ trแบกng thรกi 'Passive' sแบฝ bแป‹ แบฉn.", + "hide-passive-label": "แบจn cรกc mแปฅc khรดng hoแบกt ฤ‘แป™ng" + }, + "volume": { + "display-mode-description": "Chแปn cรกch bแบกn muแป‘n giรก trแป‹ nร y hiแปƒn thแป‹." + }, + "workspace": { + "character-count-description": "Sแป‘ kรฝ tแปฑ hiแปƒn thแป‹ tแปซ tรชn khรดng gian lร m viแป‡c (1-10).", + "character-count-label": "Sแป‘ kรฝ tแปฑ", + "empty-color-description": "ฤแบทt mร u nแปn cho cรกc khรดng gian lร m viแป‡c trแป‘ng.", + "empty-color-label": "Mร u khรดng gian lร m viแป‡c trแป‘ng", + "enable-scrollwheel-description": "Chuyแปƒn ฤ‘แป•i giแปฏa cรกc mร n hรฌnh lร m viแป‡c bแบฑng con lฤƒn chuแป™t.", + "enable-scrollwheel-label": "Cuแป™n ฤ‘แปƒ ฤ‘แป•i khรดng gian lร m viแป‡c", + "focused-color-description": "ฤแบทt mร u nแปn cho khรดng gian lร m viแป‡c ฤ‘ang hoแบกt ฤ‘แป™ng.", + "focused-color-label": "Mร u sแบฏc khรดng gian lร m viแป‡c ฤ‘ang chแปn", + "follow-focused-screen-description": "Hiแปƒn thแป‹ cรกc khรดng gian lร m viแป‡c tแปซ mร n hรฌnh ฤ‘ang ฤ‘ฦฐแปฃc tแบญp trung, thay vรฌ mร n hรฌnh nฦกi Thanh ฤ‘ฦฐแปฃc ฤ‘แบทt.", + "follow-focused-screen-label": "Theo dรตi mร n hรฌnh ฤ‘ang hoแบกt ฤ‘แป™ng", + "font-weight-description": "ฤiแปu chแป‰nh ฤ‘แป™ ฤ‘แบญm nhแบกt cแปงa chแปฏ trong khรดng gian lร m viแป‡c.", + "font-weight-label": "ฤแป™ ฤ‘แบญm phรดng chแปฏ", + "grouped-border-opacity-description": "ฤแบทt ฤ‘แป™ mแป cho viแปn vรนng lร m viแป‡c.", + "grouped-border-opacity-label": "ฤแป™ mแป viแปn", + "hide-unoccupied-description": "Khรดng hiแปƒn thแป‹ khรดng gian lร m viแป‡c khรดng cรณ cแปญa sแป•.", + "hide-unoccupied-label": "แบจn khi trแป‘ng", + "label-mode-description": "Chแปn cรกch nhรฃn khรดng gian lร m viแป‡c ฤ‘ฦฐแปฃc hiแปƒn thแป‹.", + "label-mode-label": "Chแบฟ ฤ‘แป™ nhรฃn", + "occupied-color-description": "ฤแบทt mร u nแปn cho cรกc khรดng gian lร m viแป‡c ฤ‘ang sแปญ dแปฅng.", + "occupied-color-label": "Mร u khรดng gian lร m viแป‡c ฤ‘ang sแปญ dแปฅng", + "pill-size-description": "ฤiแปu chแป‰nh kรญch thฦฐแป›c cแปงa cรกc chแป‰ bรกo khรดng gian lร m viแป‡c.", + "pill-size-label": "Kรญch thฦฐแป›c viรชn nang", + "reverse-scrolling-description": "ฤแบฃo ngฦฐแปฃc hฦฐแป›ng chuyแปƒn ฤ‘แป•i khรดng gian lร m viแป‡c khi cuแป™n.", + "reverse-scrolling-label": "ฤแบฃo ngฦฐแปฃc cuแป™n", + "show-applications-description": "Hiแปƒn thแป‹ biแปƒu tฦฐแปฃng แปฉng dแปฅng trong mแป—i khรดng gian lร m viแป‡c.", + "show-applications-hover-description": "Hiแปƒn thแป‹ biแปƒu tฦฐแปฃng แปฉng dแปฅng bรชn trong mแป—i khรดng gian lร m viแป‡c khi di chuแป™t qua.", + "show-applications-hover-label": "Hiแปƒn thแป‹ แปฉng dแปฅng khi di chuแป™t qua", + "show-applications-label": "Hiแปƒn thแป‹ แปฉng dแปฅng", + "show-badge-description": "Hiแปƒn thแป‹ huy hiแป‡u sแป‘ khรดng gian lร m viแป‡c แปŸ chแบฟ ฤ‘แป™ nhรณm.", + "show-badge-label": "Hiแปƒn thแป‹ huy hiแป‡u khรดng gian lร m viแป‡c", + "show-labels-only-when-occupied-description": "Chแป‰ hiแปƒn thแป‹ nhรฃn khรดng gian lร m viแป‡c khi chรบng chแปฉa cแปญa sแป•.", + "show-labels-only-when-occupied-label": "Hiแปƒn thแป‹ nhรฃn chแป‰ khi cรณ nแป™i dung", + "unfocused-icons-opacity-description": "ฤแบทt ฤ‘แป™ trong suแป‘t cho cรกc biแปƒu tฦฐแปฃng แปฉng dแปฅng khรดng ฤ‘ฦฐแปฃc lแบฅy nรฉt.", + "unfocused-icons-opacity-label": "ฤแป™ mแป cแปงa biแปƒu tฦฐแปฃng khรดng ฤ‘ฦฐแปฃc lแบฅy nรฉt" + } + }, + "battery": { + "all-batteries": "Tแบฅt cแบฃ pin (kแบฟt hแปฃp)", + "battery-health": "Tรฌnh trแบกng pin", + "battery-level": "Mแปฉc pin", + "capacity-level": "Dung lฦฐแปฃng: {level}", + "charging-rate": "Tแป‘c ฤ‘แป™ sแบกc: {rate} W", + "discharging-rate": "Tแป‘c ฤ‘แป™ xแบฃ: {rate} W", + "health": "Sแปฉc khแปe: {percent}%", + "inhibit-idle-description": "Giแปฏ cho hแป‡ thแป‘ng khรดng ngแปง.", + "no-battery-detected": "Khรดng phรกt hiแป‡n thแบฅy pin", + "plugged-in": "ฤรฃ cแบฏm sแบกc", + "power-profile": "Chแบฟ ฤ‘แป™ nฤƒng lฦฐแปฃng", + "time-left": "Thแปi gian cรฒn lแบกi: {time}", + "time-until-full": "Thแปi gian sแบกc ฤ‘แบงy: {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "Thiแบฟt bแป‹ khแบฃ dแปฅng", + "blocked": "ฤรฃ chแบทn", + "connected-devices": "Thiแบฟt bแป‹ ฤ‘รฃ kแบฟt nแป‘i", + "device-address": "ฤแป‹a chแป‰ thiแบฟt bแป‹", + "disabled": "Bluetooth ฤ‘รฃ tแบฏt", + "discoverable": "Cรณ thแปƒ phรกt hiแป‡n", + "enable-message": "Bแบญt Bluetooth ฤ‘แปƒ xem cรกc thiแบฟt bแป‹ khแบฃ dแปฅng.", + "known-devices": "Thiแบฟt bแป‹ ฤ‘รฃ biแบฟt", + "no-devices": "Khรดng cรณ thiแบฟt bแป‹ nร o", + "paired-devices": "Thiแบฟt bแป‹ ฤ‘รฃ ghรฉp nแป‘i", + "pairing-mode": "ฤแบฃm bแบฃo thiแบฟt bแป‹ cแปงa bแบกn ฤ‘ang แปŸ chแบฟ ฤ‘แป™ ghรฉp nแป‘i.", + "scanning": "ฤang quรฉt thiแบฟt bแป‹...", + "signal-text-excellent": "Tรญn hiแป‡u: Tuyแป‡t vแปi", + "signal-text-fair": "Tรญn hiแป‡u: Khรก", + "signal-text-good": "Tรญn hiแป‡u: Tแป‘t", + "signal-text-poor": "Tรญn hiแป‡u: Yแบฟu", + "signal-text-unknown": "Tรญn hiแป‡u: Khรดng xรกc ฤ‘แป‹nh", + "signal-text-very-poor": "Tรญn hiแป‡u: Rแบฅt yแบฟu" + } + }, + "changelog": { + "error": { + "fetch-failed": "Khรดng thแปƒ tแบฃi dแปฏ liแป‡u nhแบญt kรฝ thay ฤ‘แป•i. Vui lรฒng thแปญ lแบกi sau.", + "rate-limit": "ฤรฃ vฦฐแปฃt quรก giแป›i hแบกn tแป‘c ฤ‘แป™ cแปงa GitHub. Vui lรฒng thแปญ lแบกi sau vร i phรบt." + }, + "panel": { + "buttons-discord": "Tham gia Discord cแปงa chรบng tรดi", + "buttons-dismiss": "ฤแป“ng รฝ", + "buttons-feedback": "Gแปญi phแบฃn hแป“i", + "empty": "Ghi chรบ phรกt hร nh chฦฐa cรณ sแบตn.", + "highlight-title": "ฤiแปƒm nแป•i bแบญt", + "released": "Phรกt hร nh vร o {date}", + "subtitle-fresh": "Cแบฃm ฦกn bแบกn ฤ‘รฃ cร i ฤ‘แบทt Noctalia! ฤรขy lร  nhแปฏng gรฌ cรณ trong bแบฃn dแปฑng nร y.", + "subtitle-updated": "ฤรฃ cแบญp nhแบญt tแปซ {previousVersion}", + "title": "Cรณ gรฌ mแป›i trong {version}", + "version": "Phiรชn bแบฃn {version}", + "version-new-user": "Cร i ฤ‘แบทt mแป›i" + } + }, + "common": { + "actions": "Hร nh ฤ‘แป™ng", + "active": "Hoแบกt ฤ‘แป™ng", + "add": "Thรชm", + "appearance": "Giao diแป‡n", + "apply": "รp dแปฅng", + "auto-connect": "Tแปฑ ฤ‘แป™ng kแบฟt nแป‘i", + "automation": "Tแปฑ ฤ‘แป™ng hรณa", + "available": "Khแบฃ dแปฅng", + "back": "Quay lแบกi", + "battery": "Pin", + "behavior": "Hร nh vi", + "bluetooth": "Bluetooth", + "brightness": "ฤแป™ sรกng", + "browse": "Duyแป‡t", + "calendar": "Lแป‹ch", + "calendar-panel": "Bแบฃng Lแป‹ch", + "cancel": "Hแปงy", + "cards": "Thแบป", + "charging": "ฤang sแบกc", + "clear": "Xรณa", + "clipboard": "Bแบฃng tแบกm", + "close": "ฤรณng", + "color-muted": "Tแบฏt tiแบฟng", + "colors": "Mร u sแบฏc", + "command": "Lแป‡nh", + "confirm": "Xรกc nhแบญn", + "connect": "Kแบฟt nแป‘i", + "connected": "ฤรฃ kแบฟt nแป‘i", + "connecting": "ฤang kแบฟt nแป‘i...", + "continue": "Tiแบฟp tแปฅc", + "contributors": "Ngฦฐแปi ฤ‘รณng gรณp", + "copied-to-clipboard": "ฤรฃ sao chรฉp vร o bแป™ nhแป› tแบกm", + "countdown": "ฤแบฟm ngฦฐแปฃc", + "customize": "Tรนy chแป‰nh", + "date": "Ngร y", + "default": "Mแบทc ฤ‘แป‹nh", + "delete": "Xรณa", + "devices": "Thiแบฟt bแป‹", + "disabled": "ฤรฃ tแบฏt", + "discharging": "ฤang xแบฃ", + "disconnect": "Ngแบฏt kแบฟt nแป‘i", + "disconnected": "ฤรฃ ngแบฏt kแบฟt nแป‘i", + "disconnecting": "ฤang ngแบฏt kแบฟt nแป‘i...", + "display-mode": "Chแบฟ ฤ‘แป™ hiแปƒn thแป‹", + "documentation": "Tร i liแป‡u", + "download": "Tแบฃi xuแป‘ng", + "duration": "Thแปi lฦฐแปฃng", + "dysfunctional": "Trแปฅc trแบทc", + "edit": "Chแป‰nh sแปญa", + "enabled": "ฤรฃ bแบญt", + "error": "Lแป—i", + "ethernet": "Ethernet", + "events": "Sแปฑ kiแป‡n", + "execute": "Thแปฑc thi", + "faithful": "Trung thร nh", + "focus": "Tแบญp trung", + "font-weight-bold": "In ฤ‘แบญm", + "font-weight-light": "Sรกng", + "font-weight-medium": "Trung bรฌnh", + "font-weight-regular": "Thฦฐแปng", + "font-weight-semibold": "Bรกn ฤ‘แบญm", + "frequency": "Tแบงn suแบฅt", + "gateway": "Cแป•ng", + "general": "Chung", + "height": "Chiแปu cao", + "hibernate": "Ngแปง ฤ‘รดng", + "history": "Lแป‹ch sแปญ", + "icon": "Biแปƒu tฦฐแปฃng", + "idle": "Khรดng hoแบกt ฤ‘แป™ng", + "info": "Thรดng tin", + "input": "ฤแบงu vร o", + "install": "Cร i ฤ‘แบทt", + "installed": "ฤรฃ cร i ฤ‘แบทt", + "interface": "Giao diแป‡n", + "internet": "Internet", + "keybind": "Phรญm tแบฏt", + "language": "Ngรดn ngแปฏ", + "loading": "ฤang tแบฃi...", + "local": "Cแปฅc bแป™", + "location": "Vแป‹ trรญ", + "lock": "Khรณa", + "logout": "ฤฤƒng xuแบฅt", + "look": "Giao diแป‡n", + "margins": "Lแป", + "media": "ฤa phฦฐฦกng tiแป‡n", + "media-player": "Trรฌnh phรกt ฤ‘a phฦฐฦกng tiแป‡n", + "memory": "Bแป™ nhแป›", + "monitors": "Mร n hรฌnh", + "move-down": "Di chuyแปƒn xuแป‘ng", + "move-up": "Di chuyแปƒn lรชn", + "network": "Mแบกng", + "next": "Tiแบฟp theo", + "night-light": "Lแปc รกnh sรกng xanh", + "no": "Khรดng", + "no-results": "Khรดng cรณ kแบฟt quแบฃ", + "no-summary": "Khรดng cรณ tรณm tแบฏt", + "none": "Khรดng", + "not-found": "Khรดng tรฌm thแบฅy", + "notifications": "Thรดng bรกo", + "official": "Chรญnh thแปฉc", + "on-surface": "Trรชn mร n hรฌnh", + "output": "ฤแบงu ra", + "pair": "Ghรฉp nแป‘i", + "paired": "ฤรฃ ghรฉp nแป‘i", + "pairing": "ฤang ghรฉp nแป‘i...", + "panels": "Cรกc bแบฃng ฤ‘iแปu khiแปƒn", + "password": "Mแบญt khแบฉu", + "pause": "Tแบกm dแปซng", + "performance": "Hiแป‡u nฤƒng", + "pin": "Ghim", + "play": "Phรกt", + "polling": "Thฤƒm dรฒ", + "position": "Vแป‹ trรญ", + "previous": "Trฦฐแป›c", + "primary": "Chรญnh", + "random": "Ngแบซu nhiรชn", + "reboot": "KhแปŸi ฤ‘แป™ng lแบกi", + "reboot-to-uefi": "KhแปŸi ฤ‘แป™ng lแบกi vร o UEFI", + "record": "Bแบฏt ฤ‘แบงu ghi hรฌnh", + "refresh": "Lร m mแป›i", + "required": "(bแบฏt buแป™c)", + "reset": "ฤแบทt lแบกi", + "reset-to-default": "ฤแบทt lแบกi vแป Mแบทc ฤ‘แป‹nh", + "result-count": "{count} kแบฟt quแบฃ", + "result-count-plural": "{count} kแบฟt quแบฃ", + "resume": "Tiแบฟp tแปฅc", + "save": "Lฦฐu", + "scanning": "ฤang quรฉt...", + "screen-corners": "Cรกc gรณc mร n hรฌnh", + "search": "Tรฌm kiแบฟm", + "second": "{count} giรขy", + "second-plural": "{count} giรขy", + "secondary": "Thแปฉ cแบฅp", + "security": "Bแบฃo mแบญt", + "select": "Chแปn", + "select-color": "Chแปn mร u", + "select-color-description": "รp dแปฅng mร u sแบฏc chแปง ฤ‘แป ฤ‘แปƒ nhแบฅn mแบกnh.", + "select-icon-color": "Chแปn mร u biแปƒu tฦฐแปฃng", + "settings": "Cร i ฤ‘แบทt", + "shortcuts": "Phรญm tแบฏt", + "shutdown": "Tแบฏt mรกy", + "signal": "Tรญn hiแป‡u", + "sound": "ร‚m thanh", + "sources": "Nguแป“n", + "start": "Bแบฏt ฤ‘แบงu", + "stop": "Dแปซng", + "supporters": "Ngฦฐแปi แปงng hแป™", + "suspend": "Tแบกm dแปซng", + "templates": "Mแบซu", + "tertiary": "Thแปฉ ba", + "test": "Kiแปƒm tra", + "thresholds": "Ngฦฐแปกng", + "title": "Tiรชu ฤ‘แป", + "toast": "Thรดng bรกo nhanh", + "trusted": "ฤรกng tin cแบญy", + "uninstall": "Gแปก cร i ฤ‘แบทt", + "unknown": "Khรดng xรกc ฤ‘แป‹nh", + "unpair": "Hแปงy ghรฉp ฤ‘รดi", + "unpin": "Bแป ghim", + "update": "Cแบญp nhแบญt", + "upload": "Tแบฃi lรชn", + "username": "Tรชn ngฦฐแปi dรนng", + "userspace-reboot": "KhแปŸi ฤ‘แป™ng lแบกi khรดng gian ngฦฐแปi dรนng", + "version": "Phiรชn bแบฃn", + "vibrant": "Sแป‘ng ฤ‘แป™ng", + "visualizer": "Trรฌnh hiแปƒn thแป‹", + "volume": "ร‚m lฦฐแปฃng", + "volumes": "ร‚m lฦฐแปฃng", + "wallpaper": "Hรฌnh nแปn", + "weather": "Thแปi tiแบฟt", + "weather-loading": "ฤang tแบฃi thแปi tiแบฟt...", + "weather-no-location": "ฤแบทt vแป‹ trรญ trong Cร i ฤ‘แบทt ฤ‘แปƒ xem thแปi tiแบฟt", + "week": "Tuแบงn", + "widgets": "Tiแป‡n รญch", + "width": "Chiแปu rแป™ng", + "wifi": "Wi-Fi", + "windows": "Cแปญa sแป•", + "yes": "Cรณ" + }, + "control-center": { + "power-profile": { + "tooltip-action": "Cแบฅu hรฌnh Nguแป“n", + "tooltip-disabled": "Cร i ฤ‘แบทt power-profiles-daemon ฤ‘แปƒ sแปญ dแปฅng cรกc chแบฟ ฤ‘แป™ nguแป“n" + }, + "wifi": { + "label-disconnected": "Wi-Fi ฤ‘รฃ ngแบฏt kแบฟt nแป‘i", + "label-ethernet": "Ethernet" + } + }, + "display-modes": { + "always-hide": "Luรดn แบฉn", + "always-show": "Luรดn hiแปƒn thแป‹", + "force-open": "Buแป™c mแปŸ", + "on-hover": "Khi di chuแป™t" + }, + "hide-modes": { + "auto-hide": "Tแปฑ ฤ‘แป™ng แบฉn", + "hidden": "แบจn khi trแป‘ng", + "idle": "แบจn khi khรดng hoแบกt ฤ‘แป™ng", + "non-exclusive": "Khรดng ฤ‘แป™c quyแปn", + "transparent": "Trong suแป‘t khi trแป‘ng", + "visible": "Luรดn hiแปƒn thแป‹" + }, + "launcher": { + "categories": { + "all": "Tแบฅt cแบฃ", + "audiovideo": "ร‚m thanh & Video", + "chat": "Trรฒ chuyแป‡n", + "development": "Phรกt triแปƒn", + "education": "Giรกo dแปฅc", + "emoji-activity": "Hoแบกt ฤ‘แป™ng", + "emoji-animals": "ฤแป™ng vแบญt", + "emoji-flags": "Cแป", + "emoji-food": "ฤแป“ ฤƒn & Thแปฉc uแป‘ng", + "emoji-nature": "Thiรชn nhiรชn", + "emoji-objects": "ฤแป‘i tฦฐแปฃng", + "emoji-people": "Ngฦฐแปi & Cฦก thแปƒ", + "emoji-recent": "Gแบงn ฤ‘รขy", + "emoji-symbols": "Biแปƒu tฦฐแปฃng", + "emoji-travel": "Du lแป‹ch & ฤแป‹a ฤ‘iแปƒm", + "game": "Trรฒ chฦกi", + "graphics": "ฤแป“ hแปa", + "misc": "Khรกc", + "office": "Vฤƒn phรฒng", + "pinned": "ฤรฃ ghim", + "system": "Hแป‡ thแป‘ng", + "webbrowser": "Trรฌnh duyแป‡t web" + }, + "providers": { + "applications": "แปจng dแปฅng", + "calculator": "Mรกy tรญnh", + "calculator-deprecated": "Lแป‡nh >calc ฤ‘รฃ lแป—i thแปi vร  sแบฝ sแป›m bแป‹ gแปก bแป. Thay vร o ฤ‘รณ, vui lรฒng nhแบญp cรกc biแปƒu thแปฉc toรกn hแปc trแปฑc tiแบฟp vร o mแปฅc tรฌm kiแบฟm.", + "calculator-description": "Mรกy tรญnh โ€” tรญnh toรกn cรกc biแปƒu thแปฉc toรกn hแปc", + "calculator-enter-expression": "Nhแบญp mแป™t biแปƒu thแปฉc toรกn hแปc", + "calculator-press-enter-to-copy": "Nhแบฅn Enter ฤ‘แปƒ sao chรฉp kแบฟt quแบฃ", + "clipboard": "Lแป‹ch sแปญ bแบฃng nhแป› tแบกm", + "clipboard-clear-description": "Xรณa toร n bแป™ lแป‹ch sแปญ bแบฃng nhแป› tแบกm", + "clipboard-clear-description-full": "Xรณa tแบฅt cแบฃ mแปฅc khแปi bแบฃng nhแป› tแบกm", + "clipboard-clear-history": "Xรณa lแป‹ch sแปญ bแบฃng nhแป› tแบกm", + "clipboard-delete": "Xรณa mแปฅc khแปi bแบฃng nhแป› tแบกm", + "clipboard-history-disabled": "Lแป‹ch sแปญ bแบฃng nhแป› tแบกm ฤ‘รฃ tแบฏt", + "clipboard-history-disabled-description": "Bแบญt bแบฃng nhแป› tแบกm trong phแบงn cร i ฤ‘แบทt hoแบทc tแบฃi cliphist", + "clipboard-loading": "ฤang tแบฃi lแป‹ch sแปญ bแบฃng nhแป› tแบกm...", + "clipboard-search-description": "Tรฌm kiแบฟm lแป‹ch sแปญ bแบฃng nhแป› tแบกm", + "command-description": "Chแบกy lแป‡nh shell", + "emoji": "Bแบฃng chแปn biแปƒu tฦฐแปฃng cแบฃm xรบc", + "emoji-loading": "ฤang tแบฃi biแปƒu tฦฐแปฃng cแบฃm xรบc...", + "emoji-loading-description": "Vui lรฒng ฤ‘แปฃi", + "emoji-no-recent": "Chฦฐa cรณ biแปƒu tฦฐแปฃng cแบฃm xรบc nร o gแบงn ฤ‘รขy", + "emoji-search-description": "Tรฌm kiแบฟm vร  sao chรฉp biแปƒu tฦฐแปฃng cแบฃm xรบc", + "settings-search-description": "Tรฌm kiแบฟm vร  ฤ‘i tแป›i cร i ฤ‘แบทt", + "windows-search-description": "Tรฌm kiแบฟm vร  tแบญp trung vร o cรกc cแปญa sแป• ฤ‘ang mแปŸ" + } + }, + "lock-screen": { + "authenticating": "ฤang xรกc thแปฑc...", + "password": "Nhแบญp mแบญt khแบฉu cแปงa bแบกn...", + "restart": "KhแปŸi ฤ‘แป™ng lแบกi", + "shut-down": "Tแบฏt mรกy" + }, + "notifications": { + "panel": { + "click-to-collapse": "Nhแบฅn ฤ‘แปƒ thu gแปn", + "click-to-expand": "Nhแบฅn ฤ‘แปƒ mแปŸ rแป™ng", + "description": "Thรดng bรกo cแปงa bแบกn sแบฝ hiแปƒn thแป‹ แปŸ ฤ‘รขy khi chรบng ฤ‘แบฟn.", + "no-notifications": "Khรดng cรณ thรดng bรกo" + }, + "range": { + "earlier": "Trฦฐแป›c ฤ‘รณ", + "today": "Hรดm nay", + "yesterday": "Hรดm qua" + }, + "time": { + "diff-d": "1 ngร y trฦฐแป›c", + "diff-dd": "{diff} ngร y trฦฐแป›c", + "diff-h": "1 giแป trฦฐแป›c", + "diff-hh": "{diff} giแป trฦฐแป›c", + "diff-m": "1 phรบt trฦฐแป›c", + "diff-mm": "{diff} phรบt trฦฐแป›c", + "now": "bรขy giแป" + } + }, + "options": { + "bar": { + "density-comfortable": "Thoแบฃi mรกi", + "density-compact": "Gแปn", + "density-default": "Mแบทc ฤ‘แป‹nh", + "density-mini": "Mini", + "density-spacious": "Rแป™ng rรฃi", + "display-mode-always-visible": "Luรดn hiแปƒn thแป‹", + "type-floating": "Nแป•i", + "type-framed": "ฤฦฐแปฃc ฤ‘รณng khung", + "type-simple": "ฤฦกn giแบฃn" + }, + "control-center": { + "quick-settings-style-classic": "Cแป• ฤ‘iแปƒn", + "quick-settings-style-modern": "Hiแป‡n ฤ‘แบกi" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "Thoแบฃi mรกi", + "compact": "Gแปn", + "default": "Mแบทc ฤ‘แป‹nh" + }, + "launcher-view-mode": { + "grid": "Lฦฐแป›i", + "list": "Danh sรกch" + }, + "notification-density": { + "compact": "Gแปn", + "default": "Mแบทc ฤ‘แป‹nh" + }, + "scrolling-modes": { + "always": "Luรดn cuแป™n", + "hover": "Cuแป™n khi di chuแป™t", + "never": "Khรดng cuแป™n" + }, + "session-menu-grid-layout": { + "grid": "Lฦฐแป›i", + "single-row": "Mแป™t hร ng" + }, + "settings-panel-mode": { + "attached": "Bแบฃng ฤ‘iแปu khiแปƒn gแบฏn vร o thanh", + "centered": "Bแบฃng giแปฏa", + "window": "Cแปญa sแป• riรชng biแป‡t" + }, + "visualizer-types": { + "linear": "Tuyแบฟn tรญnh", + "mirrored": "ฤรฃ nhรขn bแบฃn", + "wave": "Sรณng" + }, + "visualizer-visibility": { + "with-background": "Chแป‰ nแปn" + }, + "workspace-labels": { + "index": "Chแป‰ mแปฅc", + "index-and-name": "Chแป‰ mแปฅc vร  tรชn", + "name": "Tรชn" + } + }, + "panels": { + "about": { + "become-supporter": "TrแปŸ thร nh ngฦฐแปi แปงng hแป™", + "changelog": "Xem nhแบญt kรฝ thay ฤ‘แป•i", + "changelog-on-startup": "Hiแปƒn thแป‹ nhแบญt kรฝ thay ฤ‘แป•i khi cแบญp nhแบญt", + "changelog-on-startup-desc": "Tแปฑ ฤ‘แป™ng hiแปƒn thแป‹ nhแบญt kรฝ thay ฤ‘แป•i khi Noctalia ฤ‘ฦฐแปฃc cแบญp nhแบญt", + "contributors-description": "Lแปi cแบฃm ฦกn ฤ‘แบฟn {count} ngฦฐแปi ฤ‘รณng gรณp tuyแป‡t vแปi cแปงa chรบng tรดi!", + "contributors-description-plural": "Xin gแปญi lแปi cแบฃm ฦกn ฤ‘แบฟn {count} ngฦฐแปi ฤ‘รณng gรณp tuyแป‡t vแปi cแปงa chรบng tรดi!", + "copy-info": "Sao chรฉp thรดng tin", + "debug-disabled": "Chแบฟ ฤ‘แป™ gแปก lแป—i ฤ‘รฃ tแบฏt", + "debug-enabled": "Chแบฟ ฤ‘แป™ gแปก lแป—i ฤ‘รฃ bแบญt", + "info-copied": "Thรดng tin ฤ‘รฃ sao chรฉp vร o bแบฃng tแบกm", + "noctalia-available": "Khแบฃ dแปฅng:", + "noctalia-desc": "Mแป™t giao diแป‡n desktop gแปn gร ng vร  tแป‘i giแบฃn, ฤ‘ฦฐแปฃc chแบฟ tรกc tแป‰ mแป‰ dร nh cho Wayland, xรขy dแปฑng bแบฑng Quickshell.", + "noctalia-git-commit": "Git commit:", + "noctalia-installed-version": "Phiรชn bแบฃn ฤ‘รฃ cร i ฤ‘แบทt:", + "noctalia-latest-version": "Phiรชn bแบฃn mแป›i nhแบฅt:", + "noctalia-qs-version": "Noctalia QS phiรชn bแบฃn:", + "noctalia-title": "Giao diแป‡n Noctalia", + "privacy-policy": "Chรญnh sรกch quyแปn riรชng tฦฐ", + "support": "แปฆng hแป™ chรบng tรดi", + "supporter-badge": "Ngฦฐแปi แปงng hแป™", + "supporters-desc": "Xin chรขn thร nh cแบฃm ฦกn ngฦฐแปi แปงng hแป™ tuyแป‡t vแปi cแปงa chรบng tรดi!", + "supporters-desc-plural": "Cแบฃm ฦกn rแบฅt nhiแปu tแป›i {count} ngฦฐแปi แปงng hแป™ tuyแป‡t vแปi cแปงa chรบng tรดi!", + "supporters-loading": "ฤang tแบฃi nhแปฏng ngฦฐแปi แปงng hแป™...", + "system-board": "Bแบฃng:", + "system-cpu": "CPU:", + "system-disk": "แป” ฤ‘ฤฉa:", + "system-gpu": "GPU:", + "system-host": "Mรกy chแปง:", + "system-install-hint": "Cร i ฤ‘แบทt fastfetch ฤ‘แปƒ xem thรดng tin hแป‡ thแป‘ng", + "system-kernel": "Nhรขn:", + "system-loading": "ฤang tแบฃi thรดng tin hแป‡ thแป‘ng...", + "system-memory": "Bแป™ nhแป›:", + "system-monitor": "Mร n hรฌnh:", + "system-not-installed": "fastfetch chฦฐa ฤ‘ฦฐแปฃc cร i ฤ‘แบทt", + "system-os": "HฤH:", + "system-packages": "Gรณi:", + "system-product": "Sแบฃn phแบฉm:", + "system-title": "Thรดng tin hแป‡ thแป‘ng", + "system-uptime": "Thแปi gian hoแบกt ฤ‘แป™ng:", + "system-wm": "WM:", + "telemetry-data-copied": "Dแปฏ liแป‡u ฤ‘o tแปซ xa ฤ‘รฃ sao chรฉp vร o bแบฃng tแบกm", + "telemetry-desc": "Giรบp cแบฃi thiแป‡n Noctalia bแบฑng cรกch chia sแบป thรดng tin hแป‡ thแป‘ng แบฉn danh (ฤ‘แป™ phรขn giแบฃi mร n hรฌnh, compositor, distro). Gแปญi mแป™t lแบงn khi khแปŸi ฤ‘แป™ng, khรดng theo dรตi, dแปฏ liแป‡u tแปฑ ฤ‘แป™ng xรณa sau 30 ngร y.", + "telemetry-enabled": "Gแปญi thรดng tin hแป‡ thแป‘ng แบฉn danh", + "telemetry-show-data": "Xem dแปฏ liแป‡u", + "telemetry-title": "Quyแปn riรชng tฦฐ", + "title": "Giแป›i thiแป‡u", + "up-to-date": "Bแบกn ฤ‘รฃ cแบญp nhแบญt!", + "update-available": "Cรณ bแบฃn cแบญp nhแบญt mแป›i", + "view-commit": "Xem commit trรชn GitHub" + }, + "audio": { + "devices-desc": "Cแบฅu hรฌnh cรกc thiแบฟt bแป‹ ฤ‘แบงu vร o vร  ฤ‘แบงu ra รขm thanh cรณ sแบตn.", + "devices-input-device-description": "Chแปn thiแบฟt bแป‹ ฤ‘แบงu vร o รขm thanh mong muแป‘n.", + "devices-input-device-label": "Thiแบฟt bแป‹ ฤ‘แบงu vร o", + "devices-output-device-description": "Chแปn thiแบฟt bแป‹ ฤ‘แบงu ra รขm thanh mong muแป‘n.", + "devices-output-device-label": "Thiแบฟt bแป‹ ฤ‘แบงu ra", + "devices-title": "Thiแบฟt bแป‹ รขm thanh", + "external-mixer-description": "Nhแบญp lแป‡nh hoแบทc ฤ‘ฦฐแปng dแบซn แปฉng dแปฅng ฤ‘แปƒ khแปŸi chแบกy khi kรญch hoแบกt tรญnh nฤƒng bแป™ trแป™n รขm thanh ngoร i.", + "external-mixer-label": "Lแป‡nh bแป™ trแป™n รขm thanh bรชn ngoร i", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "Thiแบฟt lแบญp cรกc แปฉng dแปฅng ฤ‘a phฦฐฦกng tiแป‡n ฦฐa thรญch vร  bแป‹ bแป qua cแปงa bแบกn.", + "media-excluded-player-description": "Thรชm tแปซ khรณa cho ngฦฐแปi chฦกi mร  bแบกn muแป‘n hแป‡ thแป‘ng bแป qua. Mแป—i tแปซ khรณa nรชn nแบฑm trรชn mแป™t dรฒng mแป›i.", + "media-excluded-player-label": "Trรฌnh phรกt bแป‹ loแบกi trแปซ", + "media-excluded-player-placeholder": "nhแบญp chuแป—i con vร  nhแบฅn +", + "media-frame-rate-description": "Tแป‘c ฤ‘แป™ cao hฦกn mฦฐแปฃt mร  hฦกn nhฦฐng tแป‘n nhiแปu tร i nguyรชn hฦกn.", + "media-frame-rate-label": "Tแป‘c ฤ‘แป™ khung hรฌnh", + "media-primary-player-description": "Nhแบญp mแป™t tแปซ khรณa ฤ‘แปƒ xรกc ฤ‘แป‹nh trรฌnh phรกt chรญnh cแปงa bแบกn.", + "media-primary-player-label": "Trรฌnh phรกt chรญnh", + "media-primary-player-placeholder": "vรญ dแปฅ: spotify, vlc, mpv", + "media-scrolling-speed-description": "Thแปi gian tรญnh bแบฑng giรขy ฤ‘แปƒ tiรชu ฤ‘แป cuแป™n tแปซ ฤ‘แบงu ฤ‘แบฟn cuแป‘i.", + "media-scrolling-speed-label": "Tแป‘c ฤ‘แป™ cuแป™n", + "media-scrolling-title-description": "Bแบญt cuแป™n liรชn tแปฅc cho cรกc tiรชu ฤ‘แป media dร i.", + "media-scrolling-title-label": "Tiรชu ฤ‘แป cuแป™n", + "media-title": "Trรฌnh phรกt ฤ‘a phฦฐฦกng tiแป‡n", + "on-middle-clicked-description": "Lแป‡nh ฤ‘แปƒ thแปฑc thi khi nรบt ฤ‘ฦฐแปฃc nhแบฅp chuแป™t giแปฏa.", + "panel-applications-empty": "Khรดng cรณ แปฉng dแปฅng nร o ฤ‘ang phรกt รขm thanh", + "spectrum-mirrored-description": "ฤแบฃo ngฦฐแปฃc phแป• tแบงn ฤ‘แปƒ cรกc tแบงn sแป‘ thแบฅp nแบฑm แปŸ giแปฏa. Khi tแบฏt, cรกc tแบงn sแป‘ thแบฅp nแบฑm แปŸ bรชn trรกi vร  cรกc tแบงn sแป‘ cao nแบฑm แปŸ bรชn phแบฃi.", + "spectrum-mirrored-label": "Quang phแป• phแบฃn chiแบฟu", + "title": "ร‚m thanh", + "visualizer-type-description": "Chแปn kiแปƒu trแปฑc quan hรณa cho phรกt lแบกi ฤ‘a phฦฐฦกng tiแป‡n.", + "visualizer-type-label": "Kiแปƒu hiแปƒn thแป‹", + "volumes-desc": "ฤiแปu chแป‰nh รขm lฦฐแปฃng vร  mแปฉc รขm thanh.", + "volumes-feedback-sound-file-description": "ฤฦฐแปng dแบซn ฤ‘แบฟn tแป‡p รขm thanh ฤ‘ฦฐแปฃc phรกt khi thay ฤ‘แป•i รขm lฦฐแปฃng.", + "volumes-feedback-sound-file-label": "Tแป‡p รขm thanh phแบฃn hแป“i รขm lฦฐแปฃng", + "volumes-feedback-sound-file-select-title": "Chแปn tแป‡p รขm thanh phแบฃn hแป“i รขm lฦฐแปฃng", + "volumes-input-volume-description": "Mแปฉc รขm lฦฐแปฃng ฤ‘แบงu vร o micrรด.", + "volumes-mute-input-description": "Tแบฏt tiแบฟng ฤ‘แบงu vร o รขm thanh mแบทc ฤ‘แป‹nh (micrรด).", + "volumes-mute-input-label": "Tแบฏt tiแบฟng ฤ‘แบงu vร o รขm thanh", + "volumes-mute-output-description": "Tแบฏt tiแบฟng ฤ‘แบงu ra รขm thanh chรญnh cแปงa hแป‡ thแป‘ng.", + "volumes-mute-output-label": "Tแบฏt tiแบฟng ฤ‘แบงu ra รขm thanh", + "volumes-output-volume-description": "Mแปฉc รขm lฦฐแปฃng toร n hแป‡ thแป‘ng.", + "volumes-step-size-description": "ฤiแปu chแป‰nh bฦฐแป›c nhแบฃy khi thay ฤ‘แป•i รขm lฦฐแปฃng (con lฤƒn chuแป™t, phรญm tแบฏt).", + "volumes-step-size-label": "Kรญch thฦฐแป›c bฦฐแป›c รขm lฦฐแปฃng", + "volumes-volume-feedback-description": "Phรกt รขm thanh phแบฃn hแป“i khi ฤ‘iแปu chแป‰nh รขm lฦฐแปฃng.", + "volumes-volume-feedback-label": "Phรกt รขm thanh phแบฃn hแป“i รขm lฦฐแปฃng", + "volumes-volume-overdrive-description": "Cho phรฉp tฤƒng รขm lฦฐแปฃng lรชn trรชn 100%. Cรณ thแปƒ khรดng ฤ‘ฦฐแปฃc hแป— trแปฃ bแปŸi tแบฅt cแบฃ phแบงn cแปฉng.", + "volumes-volume-overdrive-label": "Cho phรฉp tฤƒng รขm lฦฐแปฃng vฦฐแปฃt mแปฉc" + }, + "bar": { + "appearance-auto-hide-delay-description": "Thแปi gian trฦฐแป›c khi thanh แบฉn ฤ‘i sau khi chuแป™t rแปi ฤ‘i", + "appearance-auto-hide-delay-label": "ฤแป™ trแป… แบฉn", + "appearance-auto-hide-exclusive-note": "Lฦฐu รฝ: Vรนng ฤ‘แป™c quyแปn tแปฑ ฤ‘แป™ng bแป‹ vรด hiแป‡u hรณa khi chแบฟ ฤ‘แป™ tแปฑ ฤ‘แป™ng แบฉn ฤ‘ang hoแบกt ฤ‘แป™ng", + "appearance-auto-show-delay-description": "Thแปi gian chแป thanh hiแปƒn thแป‹ khi chuแป™t di chuyแปƒn vร o cแบกnh", + "appearance-auto-show-delay-label": "Thแปi gian trแป… hiแปƒn thแป‹", + "appearance-background-opacity-description": "ฤแบทt ฤ‘แป™ mแป nแปn riรชng cho thanh.", + "appearance-background-opacity-label": "ฤแป™ mแป nแปn Thanh", + "appearance-capsule-color-description": "Chแปn mร u cho cรกc Capsule cแปงa Thanh, hoแบทc khรดng chแปn ฤ‘แปƒ dรนng mร u nแปn mแบทc ฤ‘แป‹nh.", + "appearance-capsule-color-label": "Mร u vรนng chแปฉa", + "appearance-capsule-opacity-description": "ฤแบทt ฤ‘แป™ mแป cho nแปn cแปงa cรกc widget khi capsule ฤ‘ฦฐแปฃc hiแปƒn thแป‹.", + "appearance-capsule-opacity-label": "ฤแป™ mแป ฤ‘แปฅc cแปงa vแป bแปc", + "appearance-content-padding-description": "ฤiแปu chแป‰nh khoแบฃng ฤ‘แป‡m giแปฏa cรกc cแบกnh cแปงa thanh vร  cรกc tiแป‡n รญch.", + "appearance-content-padding-label": "Khoแบฃng ฤ‘แป‡m Nแป™i dung", + "appearance-density-description": "ฤiแปu chแป‰nh khoแบฃng ฤ‘แป‡m cแปงa thanh ฤ‘แปƒ cรณ giao diแป‡n gแปn gร ng hoแบทc rแป™ng rรฃi.", + "appearance-density-label": "Mแบญt ฤ‘แป™ Thanh", + "appearance-desc": "Tรนy chแป‰nh giao diแป‡n vร  vแป‹ trรญ cแปงa Thanh.", + "appearance-display-mode-description": "Chแปn khi nร o thanh hiแปƒn thแป‹.", + "appearance-enable-exclusion-zone-inset-description": "Giแบฃm vรนng loแบกi trแปซ ฤ‘i 1 pixel vแบญt lรฝ ฤ‘แปƒ cรกc cแปญa sแป• sรกt mรฉp trร n hoร n hแบฃo xuแป‘ng dฦฐแป›i mรฉp thanh.", + "appearance-enable-exclusion-zone-inset-label": "Vรนng loแบกi trแปซ cรณ ฤ‘แป‡m", + "appearance-floating-description": "Hiแปƒn thแป‹ Thanh dฦฐแป›i dแบกng 'viรชn thuแป‘c' nแป•i.", + "appearance-floating-label": "Thanh nแป•i", + "appearance-font-scale-description": "ฤiแปu chแป‰nh tแปท lแป‡ kรญch thฦฐแป›c phรดng chแปฏ cho vฤƒn bแบฃn hiแปƒn thแป‹ trรชn Bar.", + "appearance-font-scale-label": "Kรญch thฦฐแป›c phรดng chแปฏ", + "appearance-frame-radius": "Bรกn kรญnh trong", + "appearance-frame-settings-description": "ฤiแปu chแป‰nh ฤ‘แป™ dร y khung vร  bรกn kรญnh gรณc trong", + "appearance-frame-settings-label": "Cร i ฤ‘แบทt Khung", + "appearance-frame-thickness": "ฤแป™ dร y", + "appearance-hide-on-overview-description": "แบจn Thanh vร  ฤ‘รณng cรกc Bแบฃng ฤ‘iแปu khiแปƒn khi chแบฟ ฤ‘แป™ Tแป•ng quan cแปงa trรฌnh tแป•ng hแปฃp ฤ‘ang hoแบกt ฤ‘แป™ng.", + "appearance-hide-on-overview-label": "แบจn thanh khi แปŸ tแป•ng quan", + "appearance-margins-description": "ฤiแปu chแป‰nh cรกc lแป xung quanh thanh nแป•i.", + "appearance-margins-horizontal": "Lแป Ngang", + "appearance-margins-vertical": "Lแป Dแปc", + "appearance-outer-corners-description": "Hiแปƒn thแป‹ cรกc gรณc bo cong ra ngoร i trรชn Thanh.", + "appearance-outer-corners-label": "Cรกc gรณc ngoร i", + "appearance-position-description": "Chแปn vแป‹ trรญ ฤ‘แบทt Thanh trรชn mร n hรฌnh.", + "appearance-position-label": "Vแป‹ trรญ Thanh", + "appearance-show-capsule-description": "Hiแปƒn thแป‹ nแปn cแปงa tiแป‡n รญch.", + "appearance-show-capsule-label": "Hiแปƒn thแป‹ viรชn nang", + "appearance-show-on-workspace-switch-description": "Tแปฑ ฤ‘แป™ng hiแปƒn thแป‹ thanh Bar thoรกng qua khi khรดng gian lร m viแป‡c thay ฤ‘แป•i.", + "appearance-show-on-workspace-switch-label": "Hiแปƒn thแป‹ thanh khi chuyแปƒn ฤ‘แป•i khรดng gian lร m viแป‡c", + "appearance-show-outline-description": "Hiแปƒn thแป‹ mแป™t ฤ‘ฦฐแปng viแปn rรต rร ng xung quanh mแป—i tiแป‡n รญch.", + "appearance-show-outline-label": "Hiแปƒn thแป‹ ฤ‘ฦฐแปng viแปn tiแป‡n รญch", + "appearance-type-description": "Chแปn kiแปƒu cแปงa thanh Bar: ฤฦกn giแบฃn, Nแป•i hoแบทc Cรณ khung.", + "appearance-type-label": "Kiแปƒu thanh", + "appearance-use-separate-opacity-description": "Bแบญt ฤ‘แปƒ sแปญ dแปฅng mแป™t giรก trแป‹ ฤ‘แป™ mแป riรชng biแป‡t cho nแปn cแปงa thanh.", + "appearance-use-separate-opacity-label": "Sแปญ dแปฅng ฤ‘แป™ mแป ฤ‘แปฅc riรชng cho Thanh", + "appearance-widget-spacing-description": "ฤiแปu chแป‰nh khoแบฃng cรกch giแปฏa cรกc thร nh phแบงn trรชn thanh Bar.", + "appearance-widget-spacing-label": "Khoแบฃng cรกch tiแป‡n รญch", + "behavior-middle-click-command-description": "Lแป‡nh ฤ‘แปƒ thแปฑc thi khi nhแบฅp chuแป™t giแปฏa.", + "behavior-middle-click-command-label": "Lแป‡nh Nhแบฅp Chuแป™t Giแปฏa", + "behavior-middle-click-command-placeholder": "niri msg action toggle-overview", + "behavior-middle-click-description": "Chแปn ฤ‘iแปu gรฌ xแบฃy ra khi nhแบฅp chuแป™t giแปฏa vร o cรกc khu vแปฑc trแป‘ng cแปงa thanh.", + "behavior-middle-click-follow-mouse-description": "MแปŸ panel nhแบฅp chuแป™t giแปฏa ฤ‘รฃ chแปn แปŸ vแป‹ trรญ con trแป.", + "behavior-middle-click-follow-mouse-label": "Nhแบฅp chuแป™t giแปฏa theo con trแป chuแป™t", + "behavior-middle-click-label": "Hร nh ฤ‘แป™ng nhแบฅp chuแป™t giแปฏa thanh", + "behavior-right-click-command-description": "Lแป‡nh ฤ‘แปƒ thแปฑc thi khi nhแบฅp chuแป™t phแบฃi.", + "behavior-right-click-command-label": "Lแป‡nh nhแบฅp chuแป™t phแบฃi", + "behavior-right-click-command-placeholder": "notify-send \"Nhแบฅp chuแป™t phแบฃi\"", + "behavior-right-click-description": "Chแปn chแปฉc nฤƒng cแปงa nhแบฅp chuแป™t phแบฃi trรชn cรกc vรนng trแป‘ng cแปงa thanh.", + "behavior-right-click-follow-mouse-description": "MแปŸ bแบฃng chuแป™t phแบฃi ฤ‘รฃ chแปn แปŸ vแป‹ trรญ con trแป.", + "behavior-right-click-follow-mouse-label": "Menu chuแป™t phแบฃi theo con trแป", + "behavior-right-click-label": "Hร nh ฤ‘แป™ng nhแบฅp chuแป™t phแบฃi trรชn Thanh", + "behavior-wheel-wrap-description": "Khi ฤ‘ฦฐแปฃc bแบญt, cuแป™n tiแบฟp tแปฅc tแปซ mแปฅc cuแป‘i cรนng ฤ‘แบฟn mแปฅc ฤ‘แบงu tiรชn.", + "behavior-wheel-wrap-label": "Cuแป™n vรฒng", + "behavior-workspace-scroll-description": "Chแปn chแปฉc nฤƒng cแปงa con lฤƒn chuแป™t trรชn cรกc vรนng trแป‘ng cแปงa Bar.", + "behavior-workspace-scroll-label": "Hร nh ฤ‘แป™ng bรกnh xe chuแป™t trรชn Thanh", + "behavior-workspace-scroll-option-content": "Nแป™i dung", + "behavior-workspace-scroll-option-workspace": "Khรดng gian lร m viแป‡c", + "monitor-configure-widgets": "Cแบฅu hรฌnh tiแป‡n รญch", + "monitor-override-settings": "Ghi ฤ‘รจ cร i ฤ‘แบทt chung", + "monitor-override-settings-description": "Sแปญ dแปฅng cร i ฤ‘แบทt tรนy chแป‰nh cho mร n hรฌnh nร y.", + "monitor-reset-all": "ฤแบทt lแบกi tแบฅt cแบฃ", + "monitor-widgets-title": "Cแบฅu hรฌnh Tiแป‡n รญch - {monitor}", + "monitors-desc": "Hiแปƒn thแป‹ thanh trรชn cรกc mร n hรฌnh cแปฅ thแปƒ. Mแบทc ฤ‘แป‹nh lร  tแบฅt cแบฃ nแบฟu khรดng cรณ mร n hรฌnh nร o ฤ‘ฦฐแปฃc chแปn.", + "monitors-desc-new": "Cแบฅu hรฌnh mร n hรฌnh nร o hiแปƒn thแป‹ thanh Bar vร  tรนy chแป‰nh cร i ฤ‘แบทt cho tแปซng mร n hรฌnh.", + "monitors-title": "Hiแปƒn thแป‹ Mร n hรฌnh", + "title": "Thanh", + "tray-blacklist-description": "Thรชm quy tแบฏc loแบกi trแปซ khay hแป‡ thแป‘ng, hแป— trแปฃ kรฝ tแปฑ ฤ‘แบกi diแป‡n (*).", + "tray-blacklist-label": "Danh sรกch ฤ‘en", + "tray-blacklist-placeholder": "Vรญ dแปฅ: nm-applet, Fcitx*", + "tray-pin-application": "Ghim แปฉng dแปฅng", + "tray-unpin-application": "Gแปก ghim แปฉng dแปฅng", + "use-global-widgets": "Sแปญ dแปฅng tiแป‡n รญch toร n cแปฅc", + "widgets-desc": "Kรฉo cรกc tiแป‡n รญch ฤ‘แปƒ thay ฤ‘แป•i thแปฉ tแปฑ cแปงa chรบng. Dรนng menu chuแป™t phแบฃi ฤ‘แปƒ chuyแปƒn cรกc tiแป‡n รญch giแปฏa cรกc phแบงn hoแบทc xรณa chรบng.", + "widgets-title": "Vแป‹ trรญ Tiแป‡n รญch" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "Tแบกo sฦก ฤ‘แป“ mร u tแปซ hรฌnh nแปn cแปงa bแบกn. Tแปฑ ฤ‘แป™ng trรญch xuแบฅt mร u sแบฏc ฤ‘แปƒ tแบกo mแป™t chแปง ฤ‘แป mแบกch lแบกc.", + "color-source-use-wallpaper-colors-label": "Sแปญ dแปฅng mร u sแบฏc hรฌnh nแปn", + "dark-mode-mode-description": "Bแบญt chuyแปƒn ฤ‘แป•i tแปฑ ฤ‘แป™ng giแปฏa Chแบฟ ฤ‘แป™ Sรกng vร  Chแบฟ ฤ‘แป™ Tแป‘i.", + "dark-mode-mode-label": "Lแป‹ch trรฌnh Chแบฟ ฤ‘แป™ tแป‘i", + "dark-mode-mode-manual": "Hฦฐแป›ng dแบซn", + "dark-mode-mode-off": "Tแบฏt", + "dark-mode-switch-description": "Chuyแปƒn sang giao diแป‡n tแป‘i hฦกn ฤ‘แปƒ dแป… nhรฌn hฦกn vร o ban ฤ‘รชm.", + "delete-error-description": "Khรดng thแปƒ xรณa {scheme}", + "delete-error-title": "Xรณa thแบฅt bแบกi", + "delete-success-description": "ฤรฃ xรณa thร nh cรดng {scheme}", + "delete-success-title": "Sฦก ฤ‘แป“ mร u ฤ‘รฃ xรณa", + "download-button": "Tแบฃi xuแป‘ng thรชm", + "download-downloading": "ฤang tแบฃi xuแป‘ng...", + "download-empty": "Khรดng cรณ bแบฃng mร u nร o khแบฃ dแปฅng", + "download-error-api-error": "Lแป—i API: {status}", + "download-error-description": "Tแบฃi xuแป‘ng {scheme} thแบฅt bแบกi", + "download-error-download-failed": "Tแบฃi xuแป‘ng thแบฅt bแบกi vแป›i mรฃ thoรกt: {code}", + "download-error-invalid-response": "ฤแป‹nh dแบกng phแบฃn hแป“i API khรดng hแปฃp lแป‡", + "download-error-no-files": "Khรดng tรฌm thแบฅy tแป‡p nร o cho lฦฐแปฃc ฤ‘แป“", + "download-error-parse-failed": "Khรดng thแปƒ phรขn tรญch phแบฃn hแป“i API: {error}", + "download-error-rate-limit": "ฤรฃ vฦฐแปฃt quรก giแป›i hแบกn tแป‘c ฤ‘แป™ API GitHub", + "download-error-title": "Tแบฃi xuแป‘ng thแบฅt bแบกi", + "download-fetching": "ฤang tแบฃi cรกc bแบฃng mร u cรณ sแบตn...", + "download-success-description": "ฤรฃ tแบฃi xuแป‘ng thร nh cรดng {scheme}", + "download-success-title": "Bแบฃng mร u ฤ‘รฃ tแบฃi xuแป‘ng", + "download-title": "Tแบฃi Xuแป‘ng Sฦก ฤแป“ Mร u", + "method-description": { + "content": "Sฦก ฤ‘แป“ Material Design vแป›i tรญnh nฤƒng trรญch xuแบฅt mร u sแบฏc ฤ‘แป™ trung thแปฑc cao, khแป›p sรกt vแป›i mร u sแบฏc thแปฑc tแบฟ cแปงa nแป™i dung nguแป“n.", + "dysfunctional": "Giแป‘ng nhฦฐ Faithful nhฦฐng chแปn nhรณm mร u nแป•i nแป•i bแบญt thแปฉ hai lร m mร u chรญnh.", + "faithful": "Cแป‘ gแบฏng bรกm sรกt mร u gแป‘c trong khi vแบซn tแบกo ra mแป™t bแบฃng mร u hร i hรฒa.", + "fruit-salad": "Sฦก ฤ‘แป“ Material Design tแบกo ra mแป™t bแบฃng mร u sแป‘ng ฤ‘แป™ng, vui tฦฐฦกi vแป›i cรกc sแบฏc thรกi ฤ‘a dแบกng vร  phong phรบ.", + "monochrome": "Sฦก ฤ‘แป“ Material Design sแปญ dแปฅng thang ฤ‘แป™ xรกm ฤ‘ฦกn sแบฏc vแป›i nแป™i dung mร u sแบฏc tแป‘i thiแปƒu.", + "muted": "Tแบกo ra cรกc tรดng mร u dแป‹u nhแบน, รญt bรฃo hรฒa ฤ‘แปƒ cรณ vแบป ngoร i trแบงm tฤฉnh vร  tinh tแบฟ.", + "rainbow": "Phแป‘i mร u Material Design tแบกo ra mร u sแบฏc trรชn toร n bแป™ quang phแป• ฤ‘แปƒ ฤ‘แบกt ฤ‘ฦฐแปฃc sแปฑ ฤ‘a dแบกng mร u sแบฏc tแป‘i ฤ‘a.", + "tonal-spot": "Thuแบญt toรกn mแบทc ฤ‘แป‹nh cแปงa Material Design. Tแบกo ra cรกc bแบฃng mร u cรขn bแบฑng bแบฑng cรกch sแปญ dแปฅng cรกc biแบฟn thแปƒ tรดng mร u cแปงa mร u gแป‘c.", + "vibrant": "Tแป‘i ฤ‘a hรณa ฤ‘แป™ bรฃo hรฒa mร u sแบฏc cho cรกc bแบฃng mร u ฤ‘แบญm, bแบฏt mแบฏt vแป›i tรดng mร u sแป‘ng ฤ‘แป™ng." + }, + "predefined-desc": "Chแปn tแปซ mแป™t bแป™ sฦฐu tแบญp cรกc bแบฃng mร u cรณ sแบตn.", + "predefined-generate-templates-label": "Tแบกo mแบซu cho cรกc lฦฐแปฃc ฤ‘แป“ cร i sแบตn", + "predefined-title": "Sฦก ฤแป“ Mร u ฤแป‹nh Sแบตn", + "sync-gsettings-description": "Khแป›p chแปง ฤ‘แป hแป‡ thแป‘ng vแป›i biแบฟn thแปƒ sรกng hoแบทc tแป‘i ฤ‘ang hoแบกt ฤ‘แป™ng.", + "sync-gsettings-label": "ฤแป“ng bแป™ chแปง ฤ‘แป hแป‡ thแป‘ng", + "templates-desc": "รp dแปฅng mร u sแบฏc cho cรกc แปฉng dแปฅng bรชn ngoร i.", + "templates-filter-description": "Hiแปƒn thแป‹ cรกc mแบซu tแปซ mแป™t danh mแปฅc cแปฅ thแปƒ.", + "templates-filter-label": "Lแปc theo danh mแปฅc", + "templates-misc-description": "Tแบกo mแบซu cแปงa riรชng bแบกn.", + "templates-misc-label": "Nรขng cao", + "templates-misc-user-templates-description": "Chแป‰ bแบญt nแบฟu bแบกn biแบฟt mรฌnh ฤ‘ang lร m gรฌ โ€” tham khแบฃo tร i liแป‡u trแปฑc tuyแบฟn cแปงa chรบng tรดi.", + "templates-misc-user-templates-label": "Bแบญt cรกc mแบซu ngฦฐแปi dรนng", + "templates-none-detected": "Khรดng phรกt hiแป‡n", + "templates-write-path": "Ghi: {filepath}", + "title": "Sฦก ฤแป“ Mร u", + "wallpaper-method-description": "Chแปn phฦฐฦกng phรกp tแบกo bแบฃng mร u yรชu thรญch cแปงa bแบกn.", + "wallpaper-method-label": "Phฦฐฦกng phรกp tแบกo bแบฃng mร u", + "wallpaper-monitor-source-description": "Chแปn mร n hรฌnh ฤ‘แปƒ trรญch xuแบฅt mร u nแปn.", + "wallpaper-monitor-source-label": "Nguแป“n tแบกo mร u" + }, + "connections": { + "authentication-required": "Yรชu cแบงu xรกc thแปฑc", + "bluetooth-auto-connect-description": "Tแปฑ ฤ‘แป™ng kแบฟt nแป‘i vแป›i cรกc thiแบฟt bแป‹ ฤ‘รฃ ghรฉp nแป‘i ฤ‘รกng tin cแบญy khi Bluetooth ฤ‘ฦฐแปฃc bแบญt.", + "bluetooth-auto-connect-label": "Tแปฑ ฤ‘แป™ng kแบฟt nแป‘i thiแบฟt bแป‹ ฤ‘รฃ ghรฉp ฤ‘รดi", + "bluetooth-devices-unnamed": "Cรกc thiแบฟt bแป‹ khรดng tรชn khรดng hiแปƒn thแป‹.", + "bluetooth-discoverable": "Thiแบฟt bแป‹ nร y cรณ thแปƒ ฤ‘ฦฐแปฃc phรกt hiแป‡n lร  {hostName} khi tab cร i ฤ‘แบทt nร y ฤ‘ang mแปŸ.", + "bluetooth-rssi-polling-description": "Lแบฅy mแบซu RSSI ฤ‘แป‹nh kแปณ cho cรกc thiแบฟt bแป‹ ฤ‘รฃ kแบฟt nแป‘i qua bluetoothctl. Cรณ thแปƒ khรดng khแบฃ dแปฅng cho tแบฅt cแบฃ thiแบฟt bแป‹; sแปญ dแปฅng tร i nguyรชn tแป‘i thiแปƒu khi ฤ‘ฦฐแปฃc bแบญt.", + "bluetooth-rssi-polling-interval-description": "Cแบฅu hรฌnh tแบงn suแบฅt cแบญp nhแบญt cฦฐแปng ฤ‘แป™ tรญn hiแป‡u cho cรกc thiแบฟt bแป‹ ฤ‘รฃ kแบฟt nแป‘i.", + "bluetooth-rssi-polling-interval-label": "Chu kแปณ kiแปƒm tra", + "bluetooth-rssi-polling-label": "Kiแปƒm tra tรญn hiแป‡u Bluetooth ฤ‘แป‹nh kแปณ", + "disable-discoverability-description": "แบจn thiแบฟt bแป‹ cแปงa bแบกn khแปi cรกc thiแบฟt bแป‹ Bluetooth lรขn cแบญn.", + "disable-discoverability-label": "Tแบฏt hiแปƒn thแป‹ thiแบฟt bแป‹", + "hide-unnamed-devices-description": "แบจn cรกc thiแบฟt bแป‹ chแป‰ hiแปƒn thแป‹ dฦฐแป›i dแบกng ฤ‘แป‹a chแป‰ Bluetooth.", + "hide-unnamed-devices-label": "แบจn cรกc thiแบฟt bแป‹ khรดng tรชn", + "pin-instructions": "Vui lรฒng nhแบญp mรฃ PIN hiแปƒn thแป‹ trรชn thiแบฟt bแป‹ cแปงa bแบกn.", + "title": "Cรกc kแบฟt nแป‘i", + "wifi-header-text": "Sแปญ dแปฅng Wi-Fi ฤ‘แปƒ xem cรกc mแบกng khแบฃ dแปฅng vร  kแบฟt nแป‘i Internet." + }, + "control-center": { + "cards-desc": "Tรนy chแป‰nh cรกc ฤ‘iแปu khiแปƒn nร o xuแบฅt hiแป‡n trong Trung tรขm ฤ‘iแปu khiแปƒn vร  theo thแปฉ tแปฑ nร o.", + "desc": "Cแบฅu hรฌnh vแป‹ trรญ vร  hoแบกt ฤ‘แป™ng cแปงa bแบฃng ฤ‘iแปu khiแปƒn Trung tรขm ฤ‘iแปu khiแปƒn.", + "open-at-mouse-description": "Khi nhแบฅp chuแป™t phแบฃi vร o mแป™t khu vแปฑc trแป‘ng cแปงa Bar, mแปŸ Control Center gแบงn con trแป. Khi tแบฏt, cร i ฤ‘แบทt vแป‹ trรญ แปŸ trรชn sแบฝ ฤ‘ฦฐแปฃc sแปญ dแปฅng thay thแบฟ.", + "open-at-mouse-label": "MแปŸ tแบกi vแป‹ trรญ con trแป khi nhแบฅp chuแป™t phแบฃi", + "position-description": "Chแปn vแป‹ trรญ bแบฃng ฤ‘iแปu khiแปƒn Trung tรขm ฤ‘iแปu khiแปƒn xuแบฅt hiแป‡n khi mแปŸ.", + "shortcuts-custom-button-command-description": "Lแป‡nh sแบฝ thแปฑc thi khi nรบt ฤ‘ฦฐแปฃc nhแบฅp.", + "shortcuts-custom-button-enable-on-state-logic-description": "Bแบญt biแปƒu tฦฐแปฃng thแปฉ hai vร  trแบกng thรกi 'hot' dแปฑa trรชn mแป™t lแป‡nh kiแปƒm tra.", + "shortcuts-custom-button-enable-on-state-logic-label": "Bแบญt logic cho trแบกng thรกi bแบญt", + "shortcuts-custom-button-icon-description": "Chแปn mแป™t biแปƒu tฦฐแปฃng tแปซ thฦฐ viแป‡n.", + "shortcuts-custom-button-on-clicked-label": "Lแป‡nh nhแบฅp chuแป™t trรกi", + "shortcuts-custom-button-on-middle-clicked-description": "Lแป‡nh ฤ‘แปƒ thแปฑc thi khi nรบt ฤ‘ฦฐแปฃc nhแบฅp chuแป™t giแปฏa.", + "shortcuts-custom-button-on-state-command-description": "Lแป‡nh thแปฑc thi ฤ‘แปƒ kiแปƒm tra xem nรบt cรณ nรชn แปŸ trแบกng thรกi 'bแบญt' hay khรดng. Trแบฃ vแป 0 nแบฟu bแบญt, khรกc 0 nแบฟu tแบฏt.", + "shortcuts-custom-button-on-state-command-label": "Lแป‡nh kiแปƒm tra trแบกng thรกi bแบญt", + "shortcuts-custom-button-on-state-icon-description": "Biแปƒu tฦฐแปฃng cho nรบt khi nรณ แปŸ trแบกng thรกi 'bแบญt'.", + "shortcuts-custom-button-on-state-icon-label": "Biแปƒu tฦฐแปฃng khi bแบญt", + "shortcuts-custom-button-state-checks-add": "Thรชm kiแปƒm tra trแบกng thรกi", + "shortcuts-custom-button-state-checks-command": "Lแป‡nh ฤ‘แปƒ thแปฑc thi cho viแป‡c kiแปƒm tra trแบกng thรกi nร y", + "shortcuts-custom-button-state-checks-label": "Kiแปƒm tra trแบกng thรกi", + "shortcuts-custom-button-state-checks-remove": "Xรณa", + "shortcuts-custom-button-tooltip-description": "Chรบ giแบฃi cรดng cแปฅ hiแปƒn thแป‹ khi di chuแป™t qua nรบt.", + "shortcuts-custom-button-tooltip-label": "Chรบ giแบฃi cรดng cแปฅ", + "shortcuts-title": "Tiแป‡n รญch Lแป‘i tแบฏt", + "system-monitor-disk-path-description": "Chแปn ฤ‘iแปƒm gแบฏn kแบฟt ฤ‘ฤฉa mร  thแบป giรกm sรกt hแป‡ thแป‘ng trong Trung tรขm ฤ‘iแปu khiแปƒn sแบฝ theo dรตi.", + "system-monitor-disk-path-label": "Giรกm Sรกt Hแป‡ Thแป‘ng ฤฦฐแปng Dแบซn แป” ฤฤฉa", + "title": "Trung tรขm ฤ‘iแปu khiแปƒn" + }, + "desktop-widgets": { + "clock-enabled-description": "Hiแปƒn thแป‹ mแป™t tiแป‡n รญch ฤ‘แป“ng hแป“ trรชn mร n hรฌnh nแปn.", + "clock-enabled-label": "Bแบญt tiแป‡n รญch ฤ‘แป“ng hแป“", + "clock-format-label": "ฤแป‹nh dแบกng", + "clock-height-description": "Chiแปu cao cแปงa tiแป‡n รญch ฤ‘แป“ng hแป“ tรญnh bแบฑng ฤ‘iแปƒm แบฃnh.", + "clock-height-label": "Chiแปu cao", + "clock-minimal-mode-description": "Sแปญ dแปฅng mร n hรฌnh ฤ‘แป“ng hแป“ tแป‘i giแบฃn vแป›i thแปi gian vร  ngร y thรกng.", + "clock-minimal-mode-label": "Chแบฟ ฤ‘แป™ tแป‘i giแบฃn", + "clock-rounded-corners-description": "Sแปญ dแปฅng gรณc bo trรฒn cho nแปn tiแป‡n รญch.", + "clock-rounded-corners-label": "Gรณc Bo Trรฒn", + "clock-show-background-description": "Hiแปƒn thแป‹ vแป bแปc cho tiแป‡n รญch ฤ‘แป“ng hแป“.", + "clock-show-background-label": "Hiแปƒn thแป‹ nแปn", + "clock-show-date-description": "Hiแปƒn thแป‹ ngร y bรชn dฦฐแป›i thแปi gian.", + "clock-show-date-label": "Hiแปƒn thแป‹ ngร y", + "clock-show-seconds-description": "Hiแปƒn thแป‹ giรขy trong ฤ‘แป“ng hแป“.", + "clock-show-seconds-label": "Hiแปƒn thแป‹ giรขy", + "clock-style-analog": "ฤแป“ng hแป“ kim", + "clock-style-binary": "Nhแป‹ phรขn", + "clock-style-description": "Chแปn kiแปƒu hiแปƒn thแป‹ ฤ‘แป“ng hแป“.", + "clock-style-digital": "ฤแป“ng hแป“ sแป‘", + "clock-style-label": "Kiแปƒu ฤแป“ng Hแป“", + "clock-style-minimal": "Tแป‘i giแบฃn", + "clock-width-description": "Chiแปu rแป™ng cแปงa tiแป‡n รญch ฤ‘แป“ng hแป“ ฤ‘แป“ng hแป“ tรญnh bแบฑng pixel.", + "cpu-intensive-note": "Cรกc Widget ฤ‘ฦฐแปฃc ฤ‘รกnh dแบฅu ! sแปญ dแปฅng nhiแปu CPU hฦกn vร  nรชn ฤ‘ฦฐแปฃc bแบญt mแป™t cรกch cแบฉn trแปng.", + "edit-mode-button-label": "Vร o chแบฟ ฤ‘แป™ chแป‰nh sแปญa", + "edit-mode-controls-explanation": "Nhแบฅp chuแป™t trรกi & kรฉo: Di chuyแปƒn hoแบทc thay ฤ‘แป•i kรญch thฦฐแป›c tiแป‡n รญch. Nhแบฅp chuแป™t phแบฃi: MแปŸ cรกc tรนy chแปn menu ngแปฏ cแบฃnh.", + "edit-mode-description": "Bแบญt chแบฟ ฤ‘แป™ chแป‰nh sแปญa ฤ‘แปƒ di chuyแปƒn vร  sแบฏp xแบฟp lแบกi cรกc widget trรชn mร n hรฌnh nแปn. Khi ฤ‘ฦฐแปฃc bแบญt, cรกc widget sแบฝ hiแปƒn thแป‹ ฤ‘ฦฐแปng viแปn kรฉo vร  cรณ thแปƒ ฤ‘ฦฐแปฃc sแบฏp xแบฟp lแบกi.", + "edit-mode-exit-button": "Thoรกt chแบฟ ฤ‘แป™ chแป‰nh sแปญa", + "edit-mode-grid-snap-label": "Cฤƒn theo lฦฐแป›i", + "edit-mode-grid-snap-scale-label": "ฤแป™ chia lฦฐแป›i", + "edit-mode-label": "Chแบฟ ฤ‘แป™ chแป‰nh sแปญa", + "enabled-description": "Bแบญt hoแบทc tแบฏt hoร n toร n cรกc tiแป‡n รญch trรชn mร n hรฌnh nแปn.", + "enabled-label": "Bแบญt tiแป‡n รญch mร n hรฌnh nแปn", + "general-desc": "Cแบฅu hรฌnh cรกc tiแป‡n รญch hiแปƒn thแป‹ trรชn mร n hรฌnh cแปงa bแบกn.", + "general-title": "Tiแป‡n รญch mร n hรฌnh nแปn", + "media-player-enabled-description": "Hiแปƒn thแป‹ tiแป‡n รญch trรฌnh phรกt ฤ‘a phฦฐฦกng tiแป‡n trรชn mร n hรฌnh nแปn.", + "media-player-enabled-label": "Bแบญt tiแป‡n รญch trรฌnh phรกt ฤ‘a phฦฐฦกng tiแป‡n", + "media-player-rounded-corners-description": "Bแบญt bo trรฒn gรณc cho cรกc tiแป‡n รญch.", + "media-player-show-album-art-description": "Hiแปƒn thแป‹ แบฃnh bรฌa album vร  thรดng tin bร i hรกt (tiรชu ฤ‘แป vร  nghแป‡ sฤฉ).", + "media-player-show-album-art-label": "Hiแปƒn thแป‹ แบฃnh bรฌa album vร  tiรชu ฤ‘แป", + "media-player-show-background-description": "Hiแปƒn thแป‹ vรนng chแปฉa nแปn.", + "media-player-show-buttons-description": "Hiแปƒn thแป‹ cรกc nรบt ฤ‘iแปu khiแปƒn ฤ‘a phฦฐฦกng tiแป‡n (phรกt/tแบกm dแปซng, trฦฐแป›c, tiแบฟp theo).", + "media-player-show-buttons-label": "Hiแปƒn thแป‹ Nรบt", + "media-player-show-visualizer-description": "Hiแปƒn thแป‹ lแป›p phแปง trแปฑc quan hรณa รขm thanh.", + "media-player-visualizer-type-description": "Chแปn mแป™t loแบกi hiแปƒn thแป‹.", + "overview-enabled-description": "Hiแปƒn thแป‹ widget mร n hรฌnh nแปn trong khi chแบฟ ฤ‘แป™ Overview cแปงa trรฌnh tแป•ng hแปฃp ฤ‘ang mแปŸ.", + "overview-enabled-label": "Hiแปƒn thแป‹ trong tแป•ng quan", + "system-stat-layout-bottom": "Dฦฐแป›i", + "system-stat-layout-description": "Chแปn cรกch chรบ giแบฃi ฤ‘ฦฐแปฃc hiแปƒn thแป‹ so vแป›i biแปƒu ฤ‘แป“.", + "system-stat-layout-label": "Bแป‘ cแปฅc", + "system-stat-layout-side": "Cแบกnh", + "system-stat-rounded-corners-description": "Sแปญ dแปฅng gรณc bo trรฒn cho nแปn tiแป‡n รญch.", + "system-stat-rounded-corners-label": "Gรณc bo trรฒn", + "system-stat-show-background-description": "Hiแปƒn thแป‹ khung nแปn cho tiแป‡n รญch thแป‘ng kรช hแป‡ thแป‘ng.", + "system-stat-show-background-label": "Hiแปƒn thแป‹ nแปn", + "system-stat-stat-type-description": "Chแปn sแป‘ liแป‡u thแป‘ng kรช hแป‡ thแป‘ng nร o ฤ‘แปƒ hiแปƒn thแป‹.", + "system-stat-stat-type-label": "Loแบกi Thแป‘ng Kรช", + "title": "Tiแป‡n รญch Mร n hรฌnh nแปn", + "weather-enabled-description": "Hiแปƒn thแป‹ mแป™t tiแป‡n รญch thแปi tiแบฟt trรชn mร n hรฌnh nแปn.", + "weather-enabled-label": "Bแบญt tiแป‡n รญch thแปi tiแบฟt", + "weather-show-background-description": "Hiแปƒn thแป‹ vแป bแปc cho tiแป‡n รญch thแปi tiแบฟt." + }, + "display": { + "monitors-backlight-device-auto-option": "Mแบทc ฤ‘แป‹nh", + "monitors-backlight-device-description": "Chแปn mแป™t thiแบฟt bแป‹ ฤ‘รจn nแปn cho ฤ‘แบงu ra nร y.", + "monitors-backlight-device-label": "Thiแบฟt bแป‹ ฤ‘รจn nแปn", + "monitors-brightness-step-description": "ฤiแปu chแป‰nh bฦฐแป›c nhแบฃy cho cรกc thay ฤ‘แป•i ฤ‘แป™ sรกng (con lฤƒn chuแป™t vร  phรญm tแบฏt).", + "monitors-brightness-step-label": "Kรญch thฦฐแป›c bฦฐแป›c ฤ‘แป™ sรกng", + "monitors-brightness-unavailable-ddc-disabled": "ฤiแปu khiแปƒn ฤ‘แป™ sรกng khรดng khแบฃ dแปฅng. Bแบญt 'Hแป— trแปฃ ฤ‘แป™ sรกng ngoร i' ฤ‘แปƒ ฤ‘iแปu khiแปƒn ฤ‘แป™ sรกng cแปงa mร n hรฌnh nร y.", + "monitors-brightness-unavailable-generic": "ฤiแปu khiแปƒn ฤ‘แป™ sรกng khรดng khแบฃ dแปฅng cho mร n hรฌnh nร y.", + "monitors-desc": "ฤiแปu chแป‰nh ฤ‘แป™ sรกng mร n hรฌnh cho tแปซng mร n hรฌnh.", + "monitors-enforce-minimum-description": "Giแบฃi quyแบฟt vแบฅn ฤ‘แป ฤ‘รจn nแปn tแบฏt hoร n toร n trรชn mแป™t sแป‘ mร n hรฌnh แปŸ ฤ‘แป™ sรกng 0%.", + "monitors-enforce-minimum-label": "Buแป™c ฤ‘แป™ sรกng tแป‘i thiแปƒu (1%)", + "monitors-external-brightness-description": "Bแบญt hแป— trแปฃ DDCUtil ฤ‘แปƒ ฤ‘iแปu khiแปƒn ฤ‘แป™ sรกng trรชn mร n hรฌnh ngoร i qua giao thแปฉc DDC/CI.", + "monitors-external-brightness-label": "Hแป— trแปฃ ฤ‘แป™ sรกng bรชn ngoร i", + "monitors-global-brightness-description": "ฤiแปu chแป‰nh ฤ‘แป™ sรกng cho tแบฅt cแบฃ mร n hรฌnh cรนng mแป™t lรบc.", + "monitors-global-brightness-label": "Tแบฅt cแบฃ mร n hรฌnh", + "monitors-title": "Cร i ฤแบทt Theo Mร n Hรฌnh", + "night-light-auto-schedule-description": "Dแปฑa trรชn thแปi gian mแบทt trแปi lแบทn vร  mแปc tแบกi {location} โ€” ฤ‘ฦฐแปฃc ฤ‘แป xuแบฅt.", + "night-light-auto-schedule-label": "Lรชn lแป‹ch tแปฑ ฤ‘แป™ng", + "night-light-desc": "Giแบฃm phรกt xแบก รกnh sรกng xanh ฤ‘แปƒ giรบp bแบกn ngแปง ngon hฦกn vร  giแบฃm mแปi mแบฏt.", + "night-light-enable-description": "รp dแปฅng bแป™ lแปc mร u แบฅm ฤ‘แปƒ giแบฃm phรกt xแบก รกnh sรกng xanh.", + "night-light-enable-label": "Bแบญt Bแป™ lแปc รกnh sรกng xanh", + "night-light-force-activation-description": "Bแป qua lแป‹ch trรฌnh vร  รกp dแปฅng bแป™ lแปc รกnh sรกng xanh ngay lแบญp tแปฉc.", + "night-light-force-activation-label": "Buแป™c kรญch hoแบกt", + "night-light-manual-schedule-description": "ฤแบทt thแปi gian tรนy chแป‰nh cho bรฌnh minh vร  hoร ng hรดn.", + "night-light-manual-schedule-label": "Lรชn lแป‹ch thแปง cรดng", + "night-light-manual-schedule-select-start": "Chแปn thแปi gian bแบฏt ฤ‘แบงu", + "night-light-manual-schedule-select-stop": "Chแปn thแปi gian dแปซng", + "night-light-manual-schedule-sunrise": "Thแปi gian mแบทt trแปi mแปc", + "night-light-manual-schedule-sunset": "Thแปi gian mแบทt trแปi lแบทn", + "night-light-temperature-day": "Ngร y", + "night-light-temperature-day-description": "ฤiแปu khiแปƒn nhiแป‡t ฤ‘แป™ vร o ban ngร y.", + "night-light-temperature-description": "ฤiแปu chแป‰nh ฤ‘แป™ แบฅm mร u sแบฏc cho ban ฤ‘รชm vร  ban ngร y.", + "night-light-temperature-label": "Nhiแป‡t ฤ‘แป™ mร u", + "night-light-temperature-night": "ฤรชm", + "night-light-temperature-night-description": "ฤiแปu khiแปƒn nhiแป‡t ฤ‘แป™ vร o ban ฤ‘รชm.", + "title": "Mร n hรฌnh" + }, + "dock": { + "appearance-background-opacity-description": "ฤiแปu chแป‰nh ฤ‘แป™ mแป nแปn cแปงa Dock.", + "appearance-border-radius-description": "ฤiแปu chแป‰nh bรกn kรญnh bo gรณc cแปงa Dock.", + "appearance-border-radius-label": "Bรกn kรญnh bo gรณc", + "appearance-colorize-icons-description": "รp dแปฅng mร u sแบฏc chแปง ฤ‘แป cho cรกc biแปƒu tฦฐแปฃng แปฉng dแปฅng trรชn thanh Dock (chแป‰ cรกc แปฉng dแปฅng khรดng ฤ‘ฦฐแปฃc tแบญp trung).", + "appearance-colorize-icons-label": "Tรด mร u biแปƒu tฦฐแปฃng", + "appearance-dead-opacity-description": "ฤiแปu chแป‰nh ฤ‘แป™ mแป cแปงa cรกc biแปƒu tฦฐแปฃng แปฉng dแปฅng khรดng ฤ‘ang chแบกy.", + "appearance-dead-opacity-label": "ฤแป™ mแป khi khรดng hoแบกt ฤ‘แป™ng", + "appearance-desc": "Tรนy chแป‰nh hร nh vi vร  giao diแป‡n cแปงa Dock.", + "appearance-display-auto-hide": "Tแปฑ ฤ‘แป™ng แบฉn", + "appearance-display-description": "Chแปn cรกch thanh Dock hoแบกt ฤ‘แป™ng.", + "appearance-display-exclusive": "ฤแบทc biแป‡t", + "appearance-dock-indicator-description": "Hiแปƒn thแป‹ mแป™t chแป‰ bรกo nhแป khi dock bแป‹ แบฉn.", + "appearance-dock-indicator-label": "Chแป‰ bรกo Dock", + "appearance-floating-distance-description": "ฤแบทt khoแบฃng cรกch giแปฏa Dock vร  mรฉp mร n hรฌnh.", + "appearance-floating-distance-label": "Khoแบฃng cรกch nแป•i cแปงa Thanh Dock", + "appearance-group-apps-description": "Nhรณm nhiแปu cแปญa sแป• cแปงa cรนng mแป™t แปฉng dแปฅng vร o mแป™t mแปฅc trรชn thanh แปฉng dแปฅng.", + "appearance-group-apps-label": "Nhรณm แปฉng dแปฅng cรนng loแบกi", + "appearance-group-click-action-cycle": "Chuyแปƒn ฤ‘แป•i cแปญa sแป•", + "appearance-group-click-action-description": "Chแปn hร nh ฤ‘แป™ng khi nhแบฅp chuแป™t trรกi cho cรกc แปฉng dแปฅng ฤ‘ฦฐแปฃc nhรณm.", + "appearance-group-click-action-label": "Thao tรกc nhแบฅp แปฉng dแปฅng nhรณm", + "appearance-group-click-action-list": "MแปŸ danh sรกch cแปญa sแป•", + "appearance-group-context-menu-mode-description": "Chแปn cรกch menu ngแปฏ cแบฃnh hoแบกt ฤ‘แป™ng ฤ‘แป‘i vแป›i cรกc แปฉng dแปฅng ฤ‘ฦฐแปฃc nhรณm.", + "appearance-group-context-menu-mode-extended": "MแปŸ rแป™ng", + "appearance-group-context-menu-mode-label": "Chแบฟ ฤ‘แป™ menu แปฉng dแปฅng ฤ‘ฦฐแปฃc nhรณm", + "appearance-group-context-menu-mode-list": "Danh sรกch cแปญa sแป•", + "appearance-group-indicator-style-description": "Chแปn cรกch cรกc chแป‰ bรกo nhรณm แปฉng dแปฅng ฤ‘ang chแบกy hiแปƒn thแป‹ trแบกng thรกi cแปญa sแป• ฤ‘ang tแบญp trung.", + "appearance-group-indicator-style-dots": "Cรกc chแบฅm", + "appearance-group-indicator-style-label": "Kiแปƒu chแป‰ bรกo nhรณm", + "appearance-group-indicator-style-number": "Sแป‘", + "appearance-hide-show-speed-description": "ฤiแปu chแป‰nh tแป‘c ฤ‘แป™ hoแบกt แบฃnh แบฉn/hiแป‡n cแปงa Thanh Dock.", + "appearance-hide-show-speed-label": "แบจn/hiแป‡n tแป‘c ฤ‘แป™", + "appearance-icon-size-description": "ฤiแปu chแป‰nh kรญch thฦฐแป›c tแป•ng thแปƒ cแปงa Dock.", + "appearance-icon-size-label": "Kรญch thฦฐแป›c Dock", + "appearance-inactive-indicators-description": "Hiแปƒn thแป‹ chแบฅm chแป‰ bรกo cho tแบฅt cแบฃ แปฉng dแปฅng, khรดng chแป‰ แปฉng dแปฅng ฤ‘ang hoแบกt ฤ‘แป™ng hiแป‡n tแบกi.", + "appearance-inactive-indicators-label": "Chแป‰ bรกo ฤ‘ang chแบกy", + "appearance-indicator-color-description": "Chแปn mร u cแปงa chแป‰ bรกo dock แบฉn.", + "appearance-indicator-color-label": "Mร u Chแป‰ Bรกo", + "appearance-indicator-opacity-description": "ฤiแปu chแป‰nh ฤ‘แป™ mแป cแปงa chแป‰ bรกo Dock แบฉn.", + "appearance-indicator-opacity-label": "ฤแป™ mแป chแป‰ bรกo", + "appearance-indicator-thickness-description": "Bแบญt/Tแบฏt chแป‰ bรกo Dock แบฉn dร y hฦกn (6px thay vรฌ 3px).", + "appearance-indicator-thickness-label": "Chแป‰ bรกo ฤ‘แบญm hฦกn", + "appearance-launcher-icon-description": "Chแปn biแปƒu tฦฐแปฃng ฤ‘ฦฐแปฃc sแปญ dแปฅng cho Trรฌnh khแปŸi chแบกy Dock.", + "appearance-launcher-icon-label": "Biแปƒu tฦฐแปฃng Trรฌnh khแปŸi chแบกy", + "appearance-launcher-position-description": "Chแปn nฦกi biแปƒu tฦฐแปฃng Trรฌnh khแปŸi chแบกy xuแบฅt hiแป‡n trรชn Dock.", + "appearance-launcher-position-end": "Kแบฟt thรบc", + "appearance-launcher-position-label": "Vแป‹ trรญ Trรฌnh khแปŸi chแบกy", + "appearance-launcher-position-start": "Bแบฏt ฤ‘แบงu", + "appearance-launcher-use-distro-logo-description": "Sแปญ dแปฅng biแปƒu tฦฐแปฃng phรขn phแป‘i cแปงa bแบกn lร m biแปƒu tฦฐแปฃng dock launcher.", + "appearance-launcher-use-distro-logo-label": "Sแปญ dแปฅng logo bแบฃn phรขn phแป‘i thay vรฌ biแปƒu tฦฐแปฃng", + "appearance-pinned-static-description": "Luรดn luรดn ฤ‘แบฉy cรกc biแปƒu tฦฐแปฃng แปฉng dแปฅng ฤ‘รฃ ghim sang bรชn trรกi theo thแปฉ tแปฑ cแป‘ ฤ‘แป‹nh.", + "appearance-pinned-static-label": "แปจng dแปฅng ghim cแป‘ ฤ‘แป‹nh", + "appearance-position-description": "Chแปn vแป‹ trรญ hiแปƒn thแป‹ cแปงa Dock trรชn mร n hรฌnh.", + "appearance-position-label": "Vแป‹ trรญ", + "appearance-show-launcher-icon-description": "Hiแปƒn thแป‹ biแปƒu tฦฐแปฃng trรฌnh khแปŸi chแบกy แปฉng dแปฅng trรชn thanh Dock.", + "appearance-show-launcher-icon-label": "Hiแปƒn thแป‹ trรฌnh khแปŸi chแบกy แปฉng dแปฅng", + "appearance-sit-on-frame-description": "Cฤƒn chแป‰nh thanh Dock bรชn trong viแปn khung thay vรฌ nแบฑm chแป“ng lรชn trรชn.", + "appearance-sit-on-frame-label": "Thanh Dock gแบฏn vร o khung", + "appearance-type-attached": "ฤรญnh kรจm", + "appearance-type-description": "Chแปn giแปฏa mแป™t thanh nแป•i hรฌnh viรชn thuแป‘c hoแบทc mแป™t thanh cแป‘ ฤ‘แป‹nh gแบฏn vร o cแบกnh.", + "appearance-type-floating": "Nแป•i", + "appearance-type-label": "Kiแปƒu Dock", + "enabled-description": "Hiแปƒn thแป‹ hoแบทc แบฉn Dock hoร n toร n.", + "enabled-label": "Bแบญt thanh dock", + "monitors-desc": "Hiแปƒn thแป‹ Dock trรชn cรกc mร n hรฌnh cแปฅ thแปƒ. Mแบทc ฤ‘แป‹nh lร  tแบฅt cแบฃ nแบฟu khรดng cรณ mร n hรฌnh nร o ฤ‘ฦฐแปฃc chแปn.", + "monitors-only-same-monitor-description": "Chแป‰ hiแปƒn thแป‹ แปฉng dแปฅng tแปซ mร n hรฌnh nฦกi thanh Dock nแบฑm.", + "monitors-only-same-monitor-label": "Chแป‰ แปฉng dแปฅng tแปซ cรนng mร n hรฌnh", + "monitors-title": "Mร n hรฌnh", + "title": "Thanh Dock" + }, + "general": { + "copy-settings": "Sao chรฉp cร i ฤ‘แบทt", + "fonts-default-description": "Phรดng chแปฏ chรญnh ฤ‘ฦฐแปฃc sแปญ dแปฅng trรชn toร n bแป™ giao diแป‡n.", + "fonts-default-label": "Phรดng chแปฏ Mแบทc ฤ‘แป‹nh", + "fonts-default-placeholder": "Chแปn phรดng chแปฏ mแบทc ฤ‘แป‹nh...", + "fonts-default-scale-description": "Tฤƒng hoแบทc giแบฃm kรญch thฦฐแป›c cแปงa vฤƒn bแบฃn tiรชu chuแบฉn.", + "fonts-default-scale-label": "Cแปก Chแปฏ Mแบทc ฤแป‹nh", + "fonts-default-search-placeholder": "Tรฌm phรดng chแปฏ...", + "fonts-desc": "Chแปn phรดng chแปฏ ฤ‘ฦฐแปฃc sแปญ dแปฅng trรชn toร n bแป™ giao diแป‡n.", + "fonts-monospace-description": "Phรดng chแปฏ ฤ‘ฦกn cรกch ฤ‘ฦฐแปฃc sแปญ dแปฅng ฤ‘แปƒ hiแปƒn thแป‹ sแป‘ vร  chแป‰ sแป‘.", + "fonts-monospace-label": "Phรดng chแปฏ ฤ‘ฦกn cรกch", + "fonts-monospace-placeholder": "Chแปn phรดng chแปฏ ฤ‘ฦกn cรกch...", + "fonts-monospace-scale-description": "Tฤƒng hoแบทc giแบฃm kรญch thฦฐแป›c cแปงa vฤƒn bแบฃn ฤ‘ฦกn cรกch.", + "fonts-monospace-scale-label": "Cแปก chแปฏ ฤ‘ฦกn cรกch", + "fonts-monospace-search-placeholder": "Tรฌm kiแบฟm phรดng chแปฏ ฤ‘ฦกn cรกch...", + "fonts-title": "Phรดng chแปฏ", + "keybinds-conflict-description": "Phรญm tแบฏt ฤ‘รฃ ฤ‘ฦฐแปฃc gรกn cho {action}.", + "keybinds-conflict-title": "Xung ฤ‘แป™t phรญm tแบฏt", + "keybinds-description": "Cแบฅu hรฌnh phรญm ฤ‘iแปu hฦฐแป›ng toร n cแปฅc cho cรกc bแบฃng ฤ‘iแปu khiแปƒn vร  trรฌnh khแปŸi chแบกy.", + "keybinds-down": "Di chuyแปƒn xuแป‘ng", + "keybinds-enter": "Xรกc nhแบญn / Hร nh ฤ‘แป™ng", + "keybinds-escape": "ฤรณng / Quay lแบกi", + "keybinds-left": "Di chuyแปƒn sang trรกi", + "keybinds-modifier-description": "Phรญm tแบฏt phแบฃi bao gแป“m mแป™t phรญm bแป• trแปฃ (Ctrl hoแบทc Alt).", + "keybinds-modifier-title": "Cแบงn phรญm bแป• trแปฃ", + "keybinds-remove": "Xรณa", + "keybinds-right": "Di chuyแปƒn sang phแบฃi", + "keybinds-title": "Phรญm tแบฏt ฤ‘iแปu hฦฐแป›ng", + "keybinds-up": "Di chuyแปƒn lรชn", + "language-desc": "Chแปn ngรดn ngแปฏ bแบกn muแป‘n dรนng cho แปฉng dแปฅng.", + "language-select-auto-detect": "Tแปฑ ฤ‘แป™ng", + "language-select-description": "Chแปn ngรดn ngแปฏ ฤ‘ฦฐแปฃc sแปญ dแปฅng trong giao diแป‡n แปฉng dแปฅng.", + "language-select-label": "Ngรดn ngแปฏ แปฉng dแปฅng", + "launch-setup-wizard": "Trรฌnh hฦฐแป›ng dแบซn cร i ฤ‘แบทt", + "profile-desc": "Chแป‰nh sแปญa thรดng tin ngฦฐแปi dรนng vร  แบฃnh ฤ‘แบกi diแป‡n cแปงa bแบกn.", + "profile-picture-description": "แบขnh ฤ‘แบกi diแป‡n cแปงa bแบกn xuแบฅt hiแป‡n trรชn toร n bแป™ giao diแป‡n.", + "profile-picture-label": "แบขnh ฤ‘แบกi diแป‡n", + "profile-select-avatar": "Chแปn แบฃnh ฤ‘แบกi diแป‡n", + "profile-title": "Hแป“ sฦก", + "profile-tooltip": "แบขnh ฤ‘แบกi diแป‡n", + "reverse-scrolling-description": "ฤแบฃo ngฦฐแปฃc hฦฐแป›ng cuแป™n ฤ‘ฦฐแปฃc hiแปƒu", + "reverse-scrolling-label": "Cuแป™n ฤ‘แบฃo ngฦฐแปฃc", + "screen-corners-desc": "Tรนy chแป‰nh bo gรณc mร n hรฌnh vร  hiแป‡u แปฉng hรฌnh แบฃnh.", + "screen-corners-radius-description": "ฤiแปu chแป‰nh cรกc gรณc bo trรฒn cแปงa mร n hรฌnh.", + "screen-corners-radius-label": "ฤแป™ bo trรฒn gรณc mร n hรฌnh", + "screen-corners-radius-reset": "ฤแบทt lแบกi bรกn kรญnh gรณc mร n hรฌnh", + "screen-corners-show-corners-description": "Hiแปƒn thแป‹ cรกc gรณc bo trรฒn แปŸ cแบกnh mร n hรฌnh.", + "screen-corners-show-corners-label": "Hiแปƒn thแป‹ cรกc gรณc mร n hรฌnh", + "screen-corners-solid-black-description": "Sแปญ dแปฅng mร u ฤ‘en tuyแปn thay vรฌ mร u nแปn cแปงa thanh Bar.", + "screen-corners-solid-black-label": "Gรณc ฤ‘en ฤ‘แบทc", + "screen-corners-title": "Cรกc gรณc mร n hรฌnh", + "settings-copied": "Cร i ฤ‘แบทt ฤ‘รฃ sao chรฉp vร o bแป™ nhแป› tแบกm", + "smooth-scrolling-description": "Tแบกo hiแป‡u แปฉng cuแป™n danh sรกch ฤ‘แปƒ cรณ trแบฃi nghiแป‡m cuแป™n mฦฐแปฃt mร  hฦกn.", + "smooth-scrolling-label": "Cuแป™n mฦฐแปฃt", + "tab-basics": "Cฦก bแบฃn", + "tab-keybinds": "Phรญm Tแบฏt" + }, + "hooks": { + "color-generation-description": "Lแป‡nh sแบฝ ฤ‘ฦฐแปฃc thแปฑc thi khi quรก trรฌnh tแบกo mร u hoร n tแบฅt (vรญ dแปฅ: sau khi รกp dแปฅng chแปง ฤ‘แป dแปฑa trรชn hรฌnh nแปn).", + "color-generation-label": "Mร u sแบฏc ฤ‘รฃ tแบกo", + "color-generation-placeholder": "Vรญ dแปฅ: notify-send \"Colors\" \"Generated\"", + "info-command-info-description": "โ€ข Lแป‡nh ฤ‘ฦฐแปฃc thแปฑc thi qua shell (sh -lc)
โ€ข Lแป‡nh chแบกy ngแบงm (tรกch rแปi)
โ€ข Nรบt kiแปƒm tra thแปฑc thi vแป›i cรกc giรก trแป‹ hiแป‡n tแบกi", + "info-parameters-description": "โ€ข Hook hรฌnh nแปn: $1 = ฤ‘ฦฐแปng dแบซn hรฌnh nแปn, $2 = tรชn mร n hรฌnh, $3 = chแปง ฤ‘แป (sรกng/tแป‘i)
โ€ข Hook tแบกo mร u: $1 = chแปง ฤ‘แป (sรกng/tแป‘i)
โ€ข Hook chuyแปƒn ฤ‘แป•i chแปง ฤ‘แป: $1 = true/false (trแบกng thรกi Dark Mode)
โ€ข Hook khรณa/mแปŸ khรณa mร n hรฌnh: $1 = khรณa/mแปŸ khรณa (trแบกng thรกi khรณa mร n hรฌnh)
โ€ข Hook chแบฟ ฤ‘แป™ hiแป‡u suแบฅt: Khรดng cรณ tham sแป‘
โ€ข Hook phiรชn: $1 = hร nh ฤ‘แป™ng (tแบฏt mรกy/khแปŸi ฤ‘แป™ng lแบกi)", + "info-parameters-label": "Cรกc tham sแป‘ cรณ sแบตn", + "noctalia-started-description": "Lแป‡nh ฤ‘แปƒ thแปฑc thi khi Noctalia ฤ‘รฃ tแบฃi xong.", + "noctalia-started-label": "Noctalia ฤ‘รฃ khแปŸi chแบกy", + "noctalia-started-placeholder": "Vรญ dแปฅ: notify-send 'Noctalia Loaded'", + "performance-mode-disabled-description": "Lแป‡nh sแบฝ chแบกy khi chแบฟ ฤ‘แป™ hiแป‡u suแบฅt cแปงa Noctalia bแป‹ tแบฏt.", + "performance-mode-disabled-label": "Chแบฟ ฤ‘แป™ hiแป‡u suแบฅt ฤ‘รฃ tแบฏt", + "performance-mode-disabled-placeholder": "Vรญ dแปฅ: notify-send \"Performance\" \"Mode disabled\"", + "performance-mode-enabled-description": "Lแป‡nh sแบฝ ฤ‘ฦฐแปฃc chแบกy khi chแบฟ ฤ‘แป™ hiแป‡u nฤƒng cแปงa Noctalia ฤ‘ฦฐแปฃc bแบญt.", + "performance-mode-enabled-label": "Chแบฟ ฤ‘แป™ hiแป‡u nฤƒng ฤ‘รฃ bแบญt", + "performance-mode-enabled-placeholder": "Vรญ dแปฅ: notify-send \"Performance\" \"Mode enabled\"", + "screen-lock-description": "Lแป‡nh ฤ‘ฦฐแปฃc thแปฑc thi khi mร n hรฌnh bแป‹ khรณa.", + "screen-lock-label": "Mร n hรฌnh ฤ‘รฃ khรณa", + "screen-lock-placeholder": "Vรญ dแปฅ: notify-send \"Screen\" \"Locked\"", + "screen-unlock-description": "Lแป‡nh sแบฝ ฤ‘ฦฐแปฃc thแปฑc thi khi mร n hรฌnh khรณa ฤ‘ฦฐแปฃc mแปŸ khรณa.", + "screen-unlock-label": "Mร n hรฌnh ฤ‘รฃ mแปŸ khรณa", + "screen-unlock-placeholder": "Vรญ dแปฅ: notify-send \"Screen\" \"Unlocked\"", + "session-description": "Lแป‡nh sแบฝ ฤ‘ฦฐแปฃc thแปฑc thi trฦฐแป›c khi tแบฏt mรกy hoแบทc khแปŸi ฤ‘แป™ng lแบกi. Nhแบญn loแบกi hร nh ฤ‘แป™ng dฦฐแป›i dแบกng $1 (tแบฏt mรกy/khแปŸi ฤ‘แป™ng lแบกi).", + "session-label": "Kแบฟt thรบc phiรชn", + "session-placeholder": "Vรญ dแปฅ: notify-send \"Session\" \"$1\"", + "system-hooks-desc": "Cแบฅu hรฌnh cรกc lแป‡nh sแบฝ ฤ‘ฦฐแปฃc thแปฑc thi khi cรกc sแปฑ kiแป‡n hแป‡ thแป‘ng xแบฃy ra.", + "system-hooks-enable-description": "Bแบญt hoแบทc tแบฏt tแบฅt cแบฃ lแป‡nh hook.", + "system-hooks-enable-label": "Bแบญt mรณc nแป‘i", + "system-hooks-title": "System Hooks", + "theme-changed-description": "Lแป‡nh sแบฝ ฤ‘ฦฐแปฃc thแปฑc thi khi chแปง ฤ‘แป chuyแปƒn ฤ‘แป•i giแปฏa Chแบฟ ฤ‘แป™ tแป‘i vร  Chแบฟ ฤ‘แป™ sรกng.", + "theme-changed-label": "Chแปง ฤ‘แป ฤ‘รฃ thay ฤ‘แป•i", + "theme-changed-placeholder": "Vรญ dแปฅ: notify-send \"Theme\" \"Toggled\"", + "title": "Mรณc nแป‘i", + "wallpaper-changed-description": "Lแป‡nh sแบฝ ฤ‘ฦฐแปฃc thแปฑc thi khi hรฌnh nแปn thay ฤ‘แป•i.", + "wallpaper-changed-label": "Hรฌnh nแปn ฤ‘รฃ thay ฤ‘แป•i", + "wallpaper-changed-placeholder": "Vรญ dแปฅ: notify-send \"Wallpaper\" \"Changed\"" + }, + "idle": { + "custom-add": "Thรชm lแป‡nh", + "custom-description": "Chแบกy mแป™t lแป‡nh shell sau mแป™t khoแบฃng thแปi gian khรดng hoแบกt ฤ‘แป™ng.", + "custom-entry-command": "Lแป‡nh", + "custom-entry-delete": "Xรณa", + "custom-entry-edit": "Chแป‰nh Sแปญa Lแป‡nh Tรนy Chแป‰nh", + "custom-entry-name": "Tรชn", + "custom-entry-name-placeholder": "Vรญ dแปฅ: Tแบฏt ฤ‘รจn", + "custom-entry-new": "Lแป‡nh Tรนy Chแป‰nh Mแป›i", + "custom-entry-timeout": "Thแปi gian khรดng hoแบกt ฤ‘แป™ng", + "custom-entry-timeout-format": "{count} giรขy", + "custom-entry-timeout-format-plural": "{count} giรขy", + "custom-entry-unnamed": "Lแป‡nh khรดng tรชn", + "custom-label": "Lแป‡nh Tรนy Chแป‰nh Khi Khรดng Hoแบกt ฤแป™ng", + "enable-description": "Tแปฑ ฤ‘แป™ng tแบฏt mร n hรฌnh, khรณa hoแบทc tแบกm dแปซng sau mแป™t khoแบฃng thแปi gian khรดng hoแบกt ฤ‘แป™ng.", + "enable-label": "Bแบญt quแบฃn lรฝ chแบฟ ฤ‘แป™ chแป", + "fade-duration-description": "Sแป‘ giรขy cho hiแป‡u แปฉng mแป dแบงn sang ฤ‘en trฦฐแป›c khi mแป—i hร nh ฤ‘แป™ng ฤ‘ฦฐแปฃc kรญch hoแบกt. Mแปi chuyแปƒn ฤ‘แป™ng chuแป™t sแบฝ hแปงy bแป hiแป‡u แปฉng mแป.", + "fade-duration-label": "Thแปi lฦฐแปฃng mแป dแบงn", + "lock-description": "Sแป‘ giรขy khรดng hoแบกt ฤ‘แป™ng trฦฐแป›c khi Mร n hรฌnh khรณa kรญch hoแบกt.", + "lock-label": "Mร n hรฌnh khรณa", + "resume-command-label": "Lแป‡nh tiแบฟp tแปฅc", + "screen-off-description": "Sแป‘ giรขy khรดng hoแบกt ฤ‘แป™ng trฦฐแป›c khi tแบฏt mร n hรฌnh.", + "screen-off-label": "Tแบฏt mร n hรฌnh", + "status-description": "Thแปi gian khรดng hoแบกt ฤ‘แป™ng ฤ‘ฦฐแปฃc bรกo cรกo bแปŸi compositor.", + "status-label": "Thแปi gian nhร n rแป—i", + "suspend-description": "Sแป‘ giรขy khรดng hoแบกt ฤ‘แป™ng trฦฐแป›c khi hแป‡ thแป‘ng tแบกm dแปซng.", + "tab-behavior": "Hร nh vi", + "tab-custom": "Tรนy chแป‰nh", + "timeouts-description": "ฤแบทt thร nh 0 ฤ‘แปƒ tแบฏt mแป™t giai ฤ‘oแบกn. Thแปi gian chแป bแป‹ tแบกm dแปซng khi Giแปฏ mร n hรฌnh bแบญt ฤ‘ฦฐแปฃc kรญch hoแบกt.", + "timeouts-label": "Thแปi gian chแป", + "title": "Khรดng hoแบกt ฤ‘แป™ng", + "unavailable": "Giรกm sรกt trแบกng thรกi rแบฃnh rแป—i tรญch hแปฃp khรดng khแบฃ dแปฅng trรชn compositor nร y." + }, + "indicator": { + "default-value": "Mแบทc ฤ‘แป‹nh: {value}", + "system-default": "Mแบทc ฤ‘แป‹nh hแป‡ thแป‘ng" + }, + "launcher": { + "clipboard-desc": "Truy cแบญp vร  quแบฃn lรฝ lแป‹ch sแปญ khay nhแป› tแบกm cแปงa bแบกn tแปซ Trรฌnh khแปŸi chแบกy.", + "execute-desc": "Cแบฅu hรฌnh cรกch แปฉng dแปฅng ฤ‘ฦฐแปฃc khแปŸi chแบกy.", + "execute-title": "Thแปฑc thi", + "settings-annotation-tool-description": "Lแป‡nh ฤ‘แปƒ chแบกy khi nhแบฅp vร o nรบt chรบ thรญch trong lแป‹ch sแปญ bแบฃng tแบกm. Hรฌnh แบฃnh sแบฝ ฤ‘ฦฐแปฃc chuyแปƒn qua ฤ‘ฦฐแปng แป‘ng tแป›i lแป‡nh nร y.", + "settings-annotation-tool-label": "Cรดng cแปฅ chรบ thรญch", + "settings-annotation-tool-placeholder": "Vรญ dแปฅ: 'gradia', 'satty -f -'", + "settings-auto-paste-description": "Tแปฑ ฤ‘แป™ng dรกn mแปฅc ฤ‘รฃ chแปn tแปซ khay nhแป› tแบกm. Yรชu cแบงu wtype.", + "settings-auto-paste-label": "Tแปฑ ฤ‘แป™ng dรกn", + "settings-clip-chips-description": "Hiแปƒn thแป‹ mแป™t thanh tab ฤ‘แปƒ lแปc lแป‹ch sแปญ bแบฃng tแบกm theo loแบกi (Hรฌnh แบฃnh, Liรชn kแบฟt, Tแป‡p, Mรฃ, v.v.).", + "settings-clip-chips-label": "Bแบญt Thแบป Danh mแปฅc", + "settings-clip-preview-description": "Hiแปƒn thแป‹ bแบฃn xem trฦฐแป›c nแป™i dung khay nhแป› tแบกm khi dรนng lแป‡nh >clip.", + "settings-clip-preview-label": "Bแบญt xem trฦฐแป›c clip", + "settings-clip-smart-icons-description": "Hiแปƒn thแป‹ cรกc biแปƒu tฦฐแปฃng cแปฅ thแปƒ cho liรชn kแบฟt, tแป‡p, mร u sแบฏc vร  mรฃ thay vรฌ biแปƒu tฦฐแปฃng bแบฃng tแบกm chung chung.", + "settings-clip-smart-icons-label": "Bแบญt Biแปƒu tฦฐแปฃng thรดng minh", + "settings-clip-wrap-text-description": "Ngแบฏt dรฒng vฤƒn bแบฃn trong danh sรกch bแบฃng tแบกm thay vรฌ cแบฏt bแป›t.", + "settings-clip-wrap-text-label": "Bแปc vฤƒn bแบฃn ฤ‘รฃ sao chรฉp", + "settings-clipboard-history-description": "Truy cแบญp cรกc mแปฅc ฤ‘รฃ sao chรฉp trฦฐแป›c ฤ‘รณ tแปซ Trรฌnh khแปŸi chแบกy.", + "settings-clipboard-history-label": "Bแบญt lแป‹ch sแปญ khay nhแป› tแบกm", + "settings-clipboard-watch-image-description": "Chuแป—i lแป‡nh ฤ‘แบงy ฤ‘แปง ฤ‘ฦฐแปฃc truyแปn tแป›i wl-paste ฤ‘แปƒ thay ฤ‘แป•i hรฌnh แบฃnh. (yรชu cแบงu khแปŸi ฤ‘แป™ng lแบกi)", + "settings-clipboard-watch-image-label": "Lแป‡nh theo dรตi hรฌnh แบฃnh", + "settings-clipboard-watch-text-description": "Chuแป—i lแป‡nh ฤ‘แบงy ฤ‘แปง ฤ‘ฦฐแปฃc truyแปn cho wl-paste ฤ‘แปƒ thay ฤ‘แป•i vฤƒn bแบฃn. (yรชu cแบงu khแปŸi ฤ‘แป™ng lแบกi)", + "settings-clipboard-watch-text-label": "Lแป‡nh theo dรตi vฤƒn bแบฃn", + "settings-custom-launch-prefix-description": "Tiแปn tแป‘ cรกc lแป‡nh bแบฑng mแป™t trรฌnh khแปŸi chแบกy tรนy chแป‰nh (vรญ dแปฅ: 'runapp' ฤ‘แปƒ tรญch hแปฃp systemd).", + "settings-custom-launch-prefix-enabled-description": "Sแปญ dแปฅng tiแปn tแป‘ tรนy chแป‰nh ฤ‘แปƒ khแปŸi chแบกy แปฉng dแปฅng thay vรฌ phฦฐฦกng phรกp mแบทc ฤ‘แป‹nh.", + "settings-custom-launch-prefix-enabled-label": "Bแบญt tiแปn tแป‘ khแปŸi chแบกy tรนy chแป‰nh", + "settings-custom-launch-prefix-label": "Tiแปn Tแป‘ KhแปŸi Chแบกy Tรนy Chแป‰nh", + "settings-density-description": "ฤiแปu chแป‰nh kรญch thฦฐแป›c biแปƒu tฦฐแปฃng แปฉng dแปฅng vร  mแบญt ฤ‘แป™ cแปงa Trรฌnh khแปŸi chแบกy.", + "settings-density-label": "Mแบญt ฤ‘แป™", + "settings-desc": "Tรนy chแป‰nh hร nh vi vร  giao diแป‡n cแปงa Trรฌnh khแปŸi chแบกy", + "settings-enable-session-search-description": "Hiแปƒn thแป‹ cรกc hร nh ฤ‘แป™ng cho phiรชn hoแบกt ฤ‘แป™ng(khรณa, tแบฏt mรกy, khแปŸi ฤ‘แป™ng lแบกi, v.v.) khi tรฌm kiแบฟm trong trรฌnh khแปŸi chแบกy.", + "settings-enable-session-search-label": "Bแบญt tรฌm kiแบฟm phiรชn", + "settings-enable-settings-search-description": "Hiแปƒn thแป‹ kแบฟt quแบฃ cร i ฤ‘แบทt khi tรฌm kiแบฟm trong Trรฌnh khแปŸi chแบกy.", + "settings-enable-settings-search-label": "Bแบญt tรฌm kiแบฟm cร i ฤ‘แบทt", + "settings-enable-windows-search-description": "Tรฌm kiแบฟm vร  tแบญp trung vร o cรกc cแปญa sแป• ฤ‘ang hoแบกt ฤ‘แป™ng.", + "settings-enable-windows-search-label": "Bแบญt tรฌm kiแบฟm cแปญa sแป•", + "settings-grid-view-description": "Hiแปƒn thแป‹ cรกc mแปฅc theo bแป‘ cแปฅc dแบกng lฦฐแป›i thay vรฌ danh sรกch.", + "settings-icon-mode-description": "Sแปญ dแปฅng biแปƒu tฦฐแปฃng hแป‡ thแป‘ng gแป‘c thay vรฌ Tabler icons.", + "settings-icon-mode-label": "Sแปญ dแปฅng biแปƒu tฦฐแปฃng gแป‘c", + "settings-ignore-mouse-input-description": "Vรด hiแป‡u hรณa tฦฐฦกng tรกc chuแป™t vร  con lฤƒn chuแป™t trong Trรฌnh khแปŸi chแบกy.", + "settings-ignore-mouse-input-label": "Bแป qua ฤ‘แบงu vร o cแปงa chuแป™t", + "settings-overlay-layer-description": "Hiแปƒn thแป‹ trรฌnh khแปŸi chแบกy trรชn lแป›p phแปง, phรญa trรชn cรกc cแปญa sแป• toร n mร n hรฌnh. Khi ฤ‘ฦฐแปฃc bแบญt, trรฌnh khแปŸi chแบกy sแบฝ khรดng gแบฏn vร o thanh Bar.", + "settings-overlay-layer-label": "Hiแปƒn thแป‹ trรชn toร n mร n hรฌnh", + "settings-position-description": "Chแปn vแป‹ trรญ hiแปƒn thแป‹ cแปงa Bแบฃng ฤ‘iแปu khiแปƒn Trรฌnh khแปŸi chแบกy.", + "settings-show-categories-description": "Hiแปƒn thแป‹ cรกc tab danh mแปฅc ฤ‘แปƒ lแปc แปฉng dแปฅng.", + "settings-show-categories-label": "Hiแปƒn thแป‹ danh mแปฅc", + "settings-show-icon-background-description": "Hiแปƒn thแป‹ nแปn hรฌnh chแปฏ nhแบญt bo trรฒn phรญa sau cรกc biแปƒu tฦฐแปฃng.", + "settings-show-icon-background-label": "Hiแปƒn thแป‹ nแปn sau biแปƒu tฦฐแปฃng", + "settings-sort-by-usage-description": "Khi ฤ‘ฦฐแปฃc bแบญt, cรกc แปฉng dแปฅng thฦฐแปng xuyรชn mแปŸ sแบฝ xuแบฅt hiแป‡n ฤ‘แบงu tiรชn trong danh sรกch.", + "settings-sort-by-usage-label": "Sแบฏp xแบฟp theo mแปฉc ฤ‘แป™ sแปญ dแปฅng nhiแปu nhแบฅt", + "settings-terminal-command-description": "Lแป‡nh ฤ‘แปƒ khแปŸi chแบกy mแป™t terminal. Vรญ dแปฅ: 'kitty -e' hoแบทc 'gnome-terminal --'.", + "settings-terminal-command-label": "Lแป‡nh Terminal", + "settings-use-app2unit-description": "Sแปญ dแปฅng mแป™t phฦฐฦกng phรกp khแปŸi chแบกy thay thแบฟ ฤ‘แปƒ quแบฃn lรฝ tแป‘t hฦกn cรกc tiแบฟn trรฌnh แปฉng dแปฅng vร  ngฤƒn ngแปซa sแปฑ cแป‘.", + "settings-use-app2unit-label": "Dรนng App2Unit ฤ‘แปƒ khแปŸi chแบกy แปฉng dแปฅng", + "settings-view-mode-description": "Chแปn bแป‘ cแปฅc cho cรกc mแปฅc cแปงa Trรฌnh khแปŸi chแบกy.", + "settings-view-mode-label": "Chแบฟ ฤ‘แป™ xem", + "title": "Trรฌnh khแปŸi chแบกy" + }, + "location": { + "auto-locate-description": "Tแปฑ ฤ‘แป™ng phรกt hiแป‡n vแป‹ trรญ cแปงa bแบกn ฤ‘แป‹nh kแปณ bแบฑng ฤ‘แป‹a chแป‰ IP cแปงa bแบกn.", + "auto-locate-label": "Tแปฑ ฤแป™ng ฤแป‹nh Vแป‹", + "calendar-cards-desc": "Sแบฏp xแบฟp vร  bแบญt/tแบฏt cรกc thแบป trong bแบฃng ฤ‘iแปu khiแปƒn lแป‹ch.", + "calendar-cards-title": "Thแบป lแป‹ch", + "calendar-header-label": "Tiรชu ฤ‘แป Lแป‹ch", + "calendar-month-label": "Thรกng Lแป‹ch", + "date-time-12hour-format-description": "Hiแปƒn thแป‹ thแปi gian theo ฤ‘แป‹nh dแบกng 12 giแป trรชn mร n hรฌnh khรณa vร  lแป‹ch. ฤแป“ng hแป“ trรชn thanh cรณ cร i ฤ‘แบทt riรชng.", + "date-time-12hour-format-label": "Sแปญ dแปฅng ฤ‘แป‹nh dแบกng thแปi gian 12 giแป", + "date-time-desc": "Tรนy chแป‰nh cรกch hiแปƒn thแป‹ ngร y vร  giแป.", + "date-time-first-day-of-week-automatic": "Tแปฑ ฤ‘แป™ng (sแปญ dแปฅng ngรดn ngแปฏ hแป‡ thแป‘ng)", + "date-time-first-day-of-week-description": "Chแปn ngร y bแบฏt ฤ‘แบงu tuแบงn trong lแป‹ch.", + "date-time-first-day-of-week-label": "Ngร y ฤ‘แบงu tiรชn cแปงa tuแบงn", + "date-time-show-events-description": "Hiแปƒn thแป‹ sแปฑ kiแป‡n trong bแบฃng ฤ‘iแปu khiแปƒn lแป‹ch.", + "date-time-show-events-label": "Hiแป‡n sแปฑ kiแป‡n lแป‹ch", + "date-time-title": "Ngร y & giแป", + "date-time-use-analog-description": "Hiแปƒn thแป‹ ฤ‘แป“ng hแป“ kim trรชn cแปญa sแป• lแป‹ch.", + "date-time-use-analog-label": "Sแปญ dแปฅng ฤ‘แป“ng hแป“ kiแปƒu kim", + "date-time-week-numbers-description": "Hiแปƒn thแป‹ tuแบงn trong nฤƒm (vรญ dแปฅ: tuแบงn 38) trong lแป‹ch.", + "date-time-week-numbers-label": "Hiแปƒn thแป‹ sแป‘ tuแบงn", + "geolocate-now-button": "ฤแป‹nh vแป‹ ngay", + "location-desc": "Nhแบญn thรดng tin thแปi tiแบฟt chรญnh xรกc vร  lรชn lแป‹ch รnh sรกng ฤ‘รชm bแบฑng cรกch ฤ‘แบทt vแป‹ trรญ cแปงa bแบกn.", + "location-search-description": "vรญ dแปฅ: Toronto, ON", + "location-search-label": "Tรฌm kiแบฟm mแป™t ฤ‘แป‹a ฤ‘iแปƒm", + "location-search-placeholder": "Nhแบญp tรชn ฤ‘แป‹a ฤ‘iแปƒm", + "location-title": "Vแป‹ trรญ cแปงa bแบกn", + "weather-desc": "Chแปn ฤ‘ฦกn vแป‹ nhiแป‡t ฤ‘แป™ ฦฐa thรญch cแปงa bแบกn.", + "weather-enabled-description": "Hiแปƒn thแป‹ thรดng tin thแปi tiแบฟt trรชn toร n bแป™ giao diแป‡n vร  tรฌm nแบกp dแปฏ liแป‡u thแปi tiแบฟt. Khi bแป‹ tแบฏt, tแบฅt cแบฃ cรกc yแบฟu tแป‘ thแปi tiแบฟt sแบฝ bแป‹ แบฉn vร  khรดng cรณ yรชu cแบงu mแบกng nร o ฤ‘ฦฐแปฃc thแปฑc hiแป‡n.", + "weather-enabled-label": "Bแบญt thแปi tiแบฟt", + "weather-fahrenheit-description": "Hiแปƒn thแป‹ nhiแป‡t ฤ‘แป™ bแบฑng ฤ‘แป™ Fahrenheit thay vรฌ ฤ‘แป™ Celsius.", + "weather-fahrenheit-label": "Hiแปƒn thแป‹ nhiแป‡t ฤ‘แป™ theo ฤ‘แป™ Fahrenheit (ยฐF)", + "weather-hide-city-description": "แบจn tรชn thร nh phแป‘ khแปi cรกc hiแปƒn thแป‹ thแปi tiแบฟt khแบฏp giao diแป‡n.", + "weather-hide-city-label": "แบจn tรชn thร nh phแป‘", + "weather-hide-timezone-description": "แบจn chแปฏ viแบฟt tแบฏt mรบi giแป khแปi hiแปƒn thแป‹ thแปi tiแบฟt trรชn toร n bแป™ giao diแป‡n.", + "weather-hide-timezone-label": "แบจn mรบi giแป", + "weather-show-effects-description": "Hiแปƒn thแป‹ cรกc hiแป‡u แปฉng hรฌnh แบฃnh bแป• sung (nhฦฐ mฦฐa, tuyแบฟt, hoแบทc sแบฅm sรฉt) trรชn thแบป thแปi tiแบฟt.", + "weather-show-effects-label": "Hiแปƒn thแป‹ hiแป‡u แปฉng thแปi tiแบฟt", + "weather-show-in-calendar-description": "Hiแปƒn thแป‹ dแปฑ bรกo thแปi tiแบฟt hร ng ngร y trแปฑc tiแบฟp trong chแบฟ ฤ‘แป™ xem lแป‹ch cแปงa bแบกn.", + "weather-show-in-calendar-label": "Hiแปƒn thแป‹ thแปi tiแบฟt trong lแป‹ch", + "weather-talia-mascot-always-description": "Luรดn sแปญ dแปฅng Talia emojis thay vรฌ tabler icons.", + "weather-talia-mascot-always-label": "Luรดn hiแปƒn thแป‹ biแปƒu tฦฐแปฃng cแบฃm xรบc thแปi tiแบฟt Talia" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "Khi fprintd (xรกc thแปฑc vรขn tay) ฤ‘ang hoแบกt ฤ‘แป™ng, tรนy chแปn nร y cho phรฉp bแบกn vแบซn ฤ‘ฤƒng nhแบญp bแบฑng mแบญt khแบฉu ฤ‘ฦฐแปฃc thay vรฌ vรขn tay", + "allow-password-with-fprintd-label": "Cho phรฉp ฤ‘ฤƒng nhแบญp bแบฑng mแบญt khแบฉu vแป›i fprintd", + "auto-start-auth-description": "Tแปฑ ฤ‘แป™ng bแบฏt ฤ‘แบงu xรกc thแปฑc vรขn tay mร  khรดng yรชu cแบงu nhแบฅn phรญm hoแบทc nhแบฅp nรบt.", + "auto-start-auth-label": "Tแปฑ ฤ‘แป™ng bแบฏt ฤ‘แบงu xรกc thแปฑc vรขn tay", + "clock-format-description": "Tรนy chแป‰nh ฤ‘แป‹nh dแบกng ฤ‘แป“ng hแป“ bแบฑng cรกc token cรบ phรกp ngร y/giแป.", + "clock-format-label": "ฤแป‹nh dแบกng ฤ‘แป“ng hแป“", + "clock-style-analog": "Analog", + "clock-style-custom": "Tรนy chแป‰nh", + "clock-style-description": "Chแปn kiแปƒu hiแปƒn thแป‹ cแปงa ฤ‘แป“ng hแป“ trรชn Mร n hรฌnh khรณa.", + "clock-style-digital": "Kแปน thuแบญt sแป‘", + "clock-style-label": "Kiแปƒu ฤแป“ng Hแป“", + "compact-lockscreen-description": "Chแป‰ hiแปƒn thแป‹ trฦฐแปng nhแบญp ฤ‘ฤƒng nhแบญp vร  cรกc ฤ‘iแปu khiแปƒn hแป‡ thแป‘ng, แบฉn cรกc tiแป‡n รญch thแปi tiแบฟt vร  ฤ‘a phฦฐฦกng tiแป‡n.", + "compact-lockscreen-label": "Mร n hรฌnh khรณa thu gแปn", + "enable-lockscreen-media-controls-description": "Hiแปƒn thแป‹ ฤ‘iแปu khiแปƒn phรกt ฤ‘a phฦฐฦกng tiแป‡n tฦฐฦกng tรกc trรชn mร n hรฌnh khรณa.", + "enable-lockscreen-media-controls-label": "ฤiแปu khiแปƒn phฦฐฦกng tiแป‡n trรชn mร n hรฌnh khรณa", + "lock-on-suspend-description": "Tแปฑ ฤ‘แป™ng khรณa mร n hรฌnh khi hแป‡ thแป‘ng tแบกm dแปซng.", + "lock-on-suspend-label": "Khรณa khi treo mรกy", + "lock-screen-animations-description": "Bแบญt hoแบทc tแบฏt hoแบกt แบฃnh mร n hรฌnh khรณa.", + "lock-screen-animations-label": "Hoแบกt แบฃnh Mร n hรฌnh khรณa", + "lock-screen-blur-strength-description": "รp dแปฅng hiแป‡u แปฉng lร m mแป cho hรฌnh nแปn mร n hรฌnh khรณa.", + "lock-screen-blur-strength-label": "ฤแป™ mแป mร n hรฌnh khรณa", + "lock-screen-tint-strength-description": "รp dแปฅng mแป™t lแป›p phแปง mร u lรชn hรฌnh nแปn Mร n hรฌnh khรณa.", + "lock-screen-tint-strength-label": "Cฦฐแปng ฤ‘แป™ lแป›p phแปง mร u mร n hรฌnh khรณa", + "monitors-desc": "Hiแปƒn thแป‹ Mร n hรฌnh khรณa trรชn cรกc mร n hรฌnh cแปฅ thแปƒ. Mแบทc ฤ‘แป‹nh lร  tแบฅt cแบฃ nแบฟu khรดng cรณ mร n hรฌnh nร o ฤ‘ฦฐแปฃc chแปn.", + "password-chars-description": "Icon dแป… thฦฐฦกng dรนng ฤ‘แปƒ แบฉn mแบญt khแบฉu cแปงa bแบกn.", + "password-chars-label": "Biแปƒu tฦฐแปฃng mแบญt khแบฉu ngแบซu nhiรชn", + "show-hibernate-description": "Hiแปƒn thแป‹ tรนy chแปn 'ngแปง ฤ‘รดng' trong cรกc ฤ‘iแปu khiแปƒn nguแป“n.", + "show-hibernate-label": "Hiแปƒn thแป‹ ngแปง ฤ‘รดng", + "show-session-buttons-description": "Cho phรฉp truy cแบญp cร i ฤ‘แบทt nguแป“n tแปซ mร n hรฌnh khรณa.", + "show-session-buttons-label": "ฤiแปu khiแปƒn nguแป“n", + "title": "Mร n hรฌnh khรณa" + }, + "network": { + "bluetooth-description": "Kรญch hoแบกt quแบฃn lรฝ Bluetooth.", + "bluetooth-rssi-polling-description": "ฤแป‹nh kแปณ lแบฅy mแบซu RSSI cho cรกc thiแบฟt bแป‹ ฤ‘รฃ kแบฟt nแป‘i qua bluetoothctl. Cรณ thแปƒ khรดng khแบฃ dแปฅng cho tแบฅt cแบฃ thiแบฟt bแป‹; sแปญ dแปฅng tร i nguyรชn tแป‘i thiแปƒu khi ฤ‘ฦฐแปฃc bแบญt.", + "bluetooth-rssi-polling-label": "Thฤƒm dรฒ tรญn hiแป‡u Bluetooth", + "desc": "Quแบฃn lรฝ kแบฟt nแป‘i Wi-Fi vร  Bluetooth.", + "wifi-description": "Quแบฃn lรฝ mแบกng khรดng dรขy (yรชu cแบงu NetworkManager)." + }, + "notifications": { + "duration-critical-urgency-description": "Thแปi gian hiแปƒn thแป‹ cแปงa thรดng bรกo ฦฐu tiรชn khแบฉn cแบฅp.", + "duration-critical-urgency-label": "Mแปฉc ฤ‘แป™ khแบฉn cแบฅp nghiรชm trแปng", + "duration-desc": "Cแบฅu hรฌnh thแปi gian hiแปƒn thแป‹ thรดng bรกo dแปฑa trรชn mแปฉc ฤ‘แป™ khแบฉn cแบฅp cแปงa chรบng.", + "duration-low-urgency-description": "Thแปi gian hiแปƒn thแป‹ thรดng bรกo ฦฐu tiรชn thแบฅp.", + "duration-low-urgency-label": "ฦฏu tiรชn thแบฅp", + "duration-normal-urgency-description": "Thแปi gian hiแปƒn thแป‹ cแปงa thรดng bรกo ฦฐu tiรชn bรฌnh thฦฐแปng.", + "duration-normal-urgency-label": "ฦฏu tiรชn bรฌnh thฦฐแปng", + "duration-respect-expire-description": "Sแปญ dแปฅng thแปi gian chแป hแบฟt hแบกn ฤ‘ฦฐแปฃc ฤ‘แบทt trong thรดng bรกo.", + "duration-respect-expire-label": "Tuรขn thแปง thแปi gian chแป hแบฟt hแบกn", + "duration-title": "Thแปi lฦฐแปฃng thรดng bรกo", + "history-clear-dismiss-description": "Xรณa thรดng bรกo khแปi lแป‹ch sแปญ khi ฤ‘รณng.", + "history-clear-dismiss-label": "Xรณa khi ฤ‘รณng", + "history-critical-urgency-description": "Lฦฐu thรดng bรกo ฦฐu tiรชn quan trแปng vร o lแป‹ch sแปญ.", + "history-critical-urgency-label": "Lฦฐu khแบฉn cแบฅp nghiรชm trแปng vร o lแป‹ch sแปญ", + "history-desc": "Kiแปƒm soรกt nhแปฏng thรดng bรกo nร o ฤ‘ฦฐแปฃc lฦฐu vร o lแป‹ch sแปญ dแปฑa trรชn mแปฉc ฤ‘แป™ khแบฉn cแบฅp cแปงa chรบng.", + "history-low-urgency-description": "Lฦฐu thรดng bรกo ฦฐu tiรชn thแบฅp vร o lแป‹ch sแปญ.", + "history-low-urgency-label": "Lฦฐu thรดng bรกo ฤ‘แป™ ฦฐu tiรชn thแบฅp vร o lแป‹ch sแปญ", + "history-normal-urgency-description": "Lฦฐu thรดng bรกo ฦฐu tiรชn bรฌnh thฦฐแปng vร o lแป‹ch sแปญ.", + "history-normal-urgency-label": "Lฦฐu ฤ‘แป™ khแบฉn cแบฅp thรดng thฦฐแปng vร o lแป‹ch sแปญ", + "monitors-desc": "Hiแปƒn thแป‹ thรดng bรกo trรชn cรกc mร n hรฌnh cแปฅ thแปƒ. Mแบทc ฤ‘แป‹nh lร  tแบฅt cแบฃ nแบฟu khรดng cรณ mร n hรฌnh nร o ฤ‘ฦฐแปฃc chแปn.", + "rules-action-block": "Khแป‘i", + "rules-action-block-desc": "Bแป qua hoร n toร n.", + "rules-action-hide": "แบจn", + "rules-action-hide-desc": "Khรดng bแบญt lรชn, khรดng รขm thanh, thรชm vร o lแป‹ch sแปญ.", + "rules-action-label": "Hร nh ฤ‘แป™ng", + "rules-action-mute": "Tแบฏt tiแบฟng", + "rules-action-mute-desc": "Khรดng cรณ รขm thanh, vแบซn hiแปƒn thแป‹ cแปญa sแป• bแบญt lรชn vร  trong lแป‹ch sแปญ.", + "rules-add": "Thรชm quy tแบฏc", + "rules-delete": "Xรณa quy tแบฏc", + "rules-description": "Khแป›p tรชn แปฉng dแปฅng hoแบทc nแป™i dung. Cรกc quy tแบฏc ฤ‘ฦฐแปฃc kiแปƒm tra theo thแปฉ tแปฑ, vร  kแบฟt quแบฃ khแป›p ฤ‘แบงu tiรชn sแบฝ ฤ‘ฦฐแปฃc รกp dแปฅng.", + "rules-edit": "Sแปญa quy tแบฏc", + "rules-label": "Quy tแบฏc lแปc", + "rules-pattern-label": "Mแบซu", + "rules-pattern-placeholder": "firefox, discord, hoแบทc /regex/", + "rules-tab": "Quy tแบฏc", + "settings-always-on-top-description": "Hiแปƒn thแป‹ thรดng bรกo trรชn cรกc cแปญa sแป• toร n mร n hรฌnh vร  cรกc lแป›p khรกc.", + "settings-background-opacity-description": "ฤiแปu chแป‰nh ฤ‘แป™ trong suแป‘t cแปงa nแปn thรดng bรกo.", + "settings-density-description": "Chแปn mแบญt ฤ‘แป™ thแบป thรดng bรกo.", + "settings-density-label": "Mแบญt ฤ‘แป™", + "settings-desc": "Cแบฅu hรฌnh giao diแป‡n vร  hร nh vi cแปงa thรดng bรกo.", + "settings-do-not-disturb-description": "Tแบฏt tแบฅt cแบฃ cรกc cแปญa sแป• thรดng bรกo bแบญt lรชn khi ฤ‘ฦฐแปฃc bแบญt.", + "settings-enabled-description": "Bแบญt hoแบทc tแบฏt tiแบฟn trรฌnh nแปn thรดng bรกo, yรชu cแบงu khแปŸi ฤ‘แป™ng lแบกi Noctalia shell.", + "settings-enabled-label": "Bแบญt thรดng bรกo", + "settings-location-description": "Nฦกi thรดng bรกo xuแบฅt hiแป‡n trรชn mร n hรฌnh.", + "settings-markdown-description": "Hiแปƒn thแป‹ nแป™i dung thรดng bรกo bแบฑng ฤ‘แป‹nh dแบกng Markdown.", + "settings-markdown-label": "Bแบญt Markdown", + "sounds-desc": "Cแบฅu hรฌnh hiแป‡u แปฉng รขm thanh thรดng bรกo vร  รขm lฦฐแปฃng.", + "sounds-enabled-description": "Bแบญt hiแป‡u แปฉng รขm thanh cho thรดng bรกo ฤ‘แบฟn.", + "sounds-enabled-label": "Bแบญt รขm thanh thรดng bรกo", + "sounds-excluded-apps-description": "Bแป qua viแป‡c phรกt รขm thanh thรดng bรกo ฤ‘รฃ cแบฅu hรฌnh cho cรกc แปฉng dแปฅng cแปฅ thแปƒ cรณ รขm thanh tรญch hแปฃp riรชng cแปงa chรบng.", + "sounds-excluded-apps-label": "แปจng dแปฅng bแป‹ loแบกi trแปซ", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "ฤฦฐแปng dแบซn ฤ‘แบฟn tแป‡p รขm thanh phรกt cho thรดng bรกo ฦฐu tiรชn khแบฉn cแบฅp.", + "sounds-files-critical-label": "ร‚m thanh khแบฉn cแบฅp", + "sounds-files-critical-select-title": "Chแปn tแป‡p รขm thanh khแบฉn cแบฅp", + "sounds-files-desc": "Cแบฅu hรฌnh cรกc tแป‡p รขm thanh tรนy chแป‰nh cho cรกc mแปฉc ฤ‘แป™ khแบฉn cแบฅp thรดng bรกo khรกc nhau.", + "sounds-files-low-description": "ฤฦฐแปng dแบซn ฤ‘แบฟn tแป‡p รขm thanh ฤ‘ฦฐแปฃc phรกt cho cรกc thรดng bรกo ฦฐu tiรชn thแบฅp.", + "sounds-files-low-label": "ร‚m thanh ฦฐu tiรชn thแบฅp", + "sounds-files-low-select-title": "Chแปn tแป‡p รขm thanh ฦฐu tiรชn thแบฅp", + "sounds-files-normal-description": "ฤฦฐแปng dแบซn ฤ‘แบฟn tแป‡p รขm thanh ฤ‘ฦฐแปฃc phรกt cho cรกc thรดng bรกo ฦฐu tiรชn bรฌnh thฦฐแปng.", + "sounds-files-normal-label": "ร‚m thanh khแบฉn cแบฅp thรดng thฦฐแปng", + "sounds-files-normal-select-title": "Chแปn tแป‡p รขm thanh cho ฤ‘แป™ khแบฉn cแบฅp thรดng thฦฐแปng", + "sounds-files-placeholder": "Nhแบญp ฤ‘ฦฐแปng dแบซn ฤ‘แบฟn tแป‡p รขm thanh", + "sounds-files-select-file": "Chแปn tแป‡p รขm thanh", + "sounds-files-title": "Tแป‡p รขm thanh", + "sounds-files-unified-description": "ฤฦฐแปng dแบซn ฤ‘แบฟn tแป‡p รขm thanh ฤ‘ฦฐแปฃc phรกt cho thรดng bรกo.", + "sounds-files-unified-label": "ร‚m thanh thรดng bรกo", + "sounds-files-unified-select-title": "Chแปn tแป‡p รขm thanh thรดng bรกo", + "sounds-separate-description": "Sแปญ dแปฅng cรกc tแป‡p รขm thanh khรกc nhau cho thรดng bรกo ฦฐu tiรชn thแบฅp, bรฌnh thฦฐแปng vร  quan trแปng.", + "sounds-separate-label": "Dรนng รขm thanh khรกc nhau cho tแปซng ฦฐu tiรชn.", + "sounds-title": "Cร i ฤ‘แบทt รขm thanh", + "sounds-unavailable-description": "Cร i ฤ‘แบทt Qt6 Multimedia ฤ‘แปƒ bแบญt รขm thanh thรดng bรกo.", + "sounds-unavailable-label": "ร‚m thanh thรดng bรกo khรดng khแบฃ dแปฅng", + "sounds-volume-description": "ฤiแปu chแป‰nh mแปฉc รขm lฦฐแปฃng cho รขm thanh thรดng bรกo.", + "sounds-volume-label": "ร‚m lฦฐแปฃng", + "toast-battery-description": "Hiแปƒn thแป‹ cแบฃnh bรกo khi mแปฉc pin xuแป‘ng dฦฐแป›i phแบงn trฤƒm nร y.", + "toast-battery-label": "Cแบฃnh bรกo pin", + "toast-desc": "Cแบฅu hรฌnh giao diแป‡n vร  hร nh vi cแปงa thรดng bรกo nhanh.", + "toast-keyboard-description": "Hiแปƒn thแป‹ thรดng bรกo nhanh khi bแป‘ cแปฅc bร n phรญm thay ฤ‘แป•i.", + "toast-keyboard-label": "Bแป‘ cแปฅc bร n phรญm", + "toast-media-description": "Hiแป‡n thรดng bรกo nhanh khi trแบกng thรกi phรกt ฤ‘a phฦฐฦกng tiแป‡n thay ฤ‘แป•i.", + "toast-media-label": "ฤa phฦฐฦกng tiแป‡n" + }, + "osd": { + "always-on-top-description": "Hiแปƒn thแป‹ OSD trรชn cแปญa sแป• toร n mร n hรฌnh vร  cรกc lแป›p khรกc.", + "always-on-top-label": "Luรดn hiแปƒn thแป‹ trรชn cรนng", + "background-opacity-description": "ฤiแปu khiแปƒn ฤ‘แป™ trong suแป‘t cแปงa nแปn OSD.", + "background-opacity-label": "ฤแป™ mแป nแปn", + "description": "Cแบฅu hรฌnh cรกc chแป‰ bรกo trรชn mร n hรฌnh nhฦฐ lแป›p phแปง รขm lฦฐแปฃng vร  ฤ‘แป™ sรกng.", + "duration-auto-hide-description": "ฤiแปu chแป‰nh thแปi gian trฦฐแป›c khi OSD biแบฟn mแบฅt.", + "duration-auto-hide-label": "Tแปฑ ฤ‘แป™ng แบฉn sau", + "duration-desc": "Thแปi gian OSD hiแปƒn thแป‹ trฦฐแป›c khi tแปฑ ฤ‘แป™ng แบฉn ฤ‘i.", + "duration-title": "Thแปi gian tแปฑ ฤ‘แป™ng แบฉn", + "enabled-description": "Hiแปƒn thแป‹ thay ฤ‘แป•i รขm lฦฐแปฃng vร  ฤ‘แป™ sรกng theo thแปi gian thแปฑc.", + "enabled-label": "Bแบญt hiแปƒn thแป‹ trรชn mร n hรฌnh", + "events-desc": "Chแปn nhแปฏng sแปฑ kiแป‡n nร o kรญch hoแบกt OSD.", + "general-desc": "Cแบฅu hรฌnh hiแปƒn thแป‹ vร  hร nh vi cแปงa OSD.", + "location-description": "Nฦกi cรกc OSD xuแบฅt hiแป‡n.", + "monitors-desc": "Hiแปƒn thแป‹ OSD trรชn cรกc mร n hรฌnh cแปฅ thแปƒ. Mแบทc ฤ‘แป‹nh lร  tแบฅt cแบฃ nแบฟu khรดng cรณ mร n hรฌnh nร o ฤ‘ฦฐแปฃc chแปn.", + "title": "Hiแปƒn thแป‹ trรชn mร n hรฌnh", + "types-brightness-description": "Hiแปƒn thแป‹ OSD khi ฤ‘แป™ sรกng mร n hรฌnh thay ฤ‘แป•i.", + "types-brightness-label": "ฤแป™ sรกng", + "types-custom-text-description": "Hiแปƒn thแป‹ OSD cho cรกc tin nhแบฏn vฤƒn bแบฃn tรนy chแป‰nh tแปซ IPC.", + "types-custom-text-label": "Vฤƒn bแบฃn Tรนy chแป‰nh", + "types-desc": "Chแปn cรกc sแปฑ kiแป‡n kรญch hoแบกt OSD.", + "types-input-volume-description": "Hiแปƒn thแป‹ OSD khi รขm lฦฐแปฃng micrรด thay ฤ‘แป•i.", + "types-input-volume-label": "ร‚m lฦฐแปฃng ฤ‘แบงu vร o", + "types-lockkey-description": "Hiแปƒn thแป‹ OSD khi Caps Lock, Num Lock hoแบทc Scroll Lock ฤ‘ฦฐแปฃc bแบญt/tแบฏt.", + "types-lockkey-label": "Cรกc phรญm khรณa", + "types-media-description": "Hiแปƒn thแป‹ OSD khi trแบกng thรกi phรกt ฤ‘a phฦฐฦกng tiแป‡n thay ฤ‘แป•i (phรกt, tแบกm dแปซng, bแป qua).", + "types-media-label": "Phรกt lแบกi ฤ‘a phฦฐฦกng tiแป‡n", + "types-title": "Sแปฑ kiแป‡n kรญch hoแบกt OSD", + "types-volume-description": "Hiแปƒn thแป‹ OSD khi รขm lฦฐแปฃng ฤ‘แบงu ra thay ฤ‘แป•i.", + "types-volume-label": "ร‚m lฦฐแปฃng ฤ‘แบงu ra" + }, + "plugins": { + "auto-update": "Tแปฑ ฤ‘แป™ng cแบญp nhแบญt plugin", + "auto-update-description": "Tแปฑ ฤ‘แป™ng cแบญp nhแบญt tแบฅt cแบฃ cรกc plugin khi shell khแปŸi ฤ‘แป™ng.", + "available-description": "Duyแป‡t vร  cร i ฤ‘แบทt cรกc plugin tแปซ cรกc nguแป“n ฤ‘รฃ cแบฅu hรฌnh.", + "available-label": "Cรกc plugin cรณ sแบตn", + "available-no-plugins-description": "Kiแปƒm tra nguแป“n plugin cแปงa bแบกn hoแบทc lร m mแป›i danh sรกch.", + "available-no-plugins-label": "Khรดng cรณ plugin nร o khแบฃ dแปฅng", + "check-for-updates": "Kiแปƒm tra cแบญp nhแบญt", + "checking-for-updates": "ฤang kiแปƒm tra cแบญp nhแบญt...", + "collision-already-installed": "Plugin nร y ฤ‘รฃ ฤ‘ฦฐแปฃc cร i ฤ‘แบทt", + "collision-custom-version-exists": "Mแป™t phiรชn bแบฃn tรนy chแป‰nh tแปซ \"{source}\" ฤ‘รฃ ฤ‘ฦฐแปฃc cร i ฤ‘แบทt", + "collision-official-version-exists": "Phiรชn bแบฃn chรญnh thแปฉc cแปงa plugin nร y ฤ‘รฃ ฤ‘ฦฐแปฃc cร i ฤ‘แบทt", + "development-disable": "Tแบฏt chแบฟ ฤ‘แป™ phรกt triแปƒn cho plugin nร y", + "development-enable": "Bแบญt chแบฟ ฤ‘แป™ phรกt triแปƒn cho plugin nร y", + "filter-downloaded": "ฤรฃ tแบฃi xuแป‘ng", + "filter-not-downloaded": "Chฦฐa tแบฃi xuแป‘ng", + "filter-tags-description": "Lแปc plugin theo danh mแปฅc hoแบทc trแบกng thรกi tแบฃi xuแป‘ng.", + "filter-tags-label": "Thแบป", + "hot-reload-description": "Tแปฑ ฤ‘แป™ng tแบฃi lแบกi cรกc plugin khi tแป‡p cแปงa chรบng thay ฤ‘แป•i. Hแปฏu รญch cho viแป‡c phรกt triแปƒn plugin.", + "hot-reload-label": "Hot reload (chแบฟ ฤ‘แป™ dev)", + "hot-reloaded": "ฤรฃ tแบฃi lแบกi tiแป‡n รญch: {name}", + "install-error": "Khรดng thแปƒ cร i ฤ‘แบทt: {error}", + "install-incompatible": "{plugin} yรชu cแบงu Noctalia v{version} trแปŸ lรชn", + "install-success": "ฤรฃ cร i ฤ‘แบทt thร nh cรดng {plugin}", + "installed-description": "Quแบฃn lรฝ vร  cแบฅu hรฌnh tแบฅt cแบฃ cรกc bแป• trแปฃ ฤ‘รฃ cร i ฤ‘แบทt cแปฅc bแป™.", + "installed-label": "Tiแป‡n รญch ฤ‘รฃ cร i ฤ‘แบทt", + "installed-no-plugins-description": "Cร i ฤ‘แบทt plugin tแปซ phแบงn \"Khแบฃ dแปฅng\".", + "installed-no-plugins-label": "Chฦฐa cร i ฤ‘แบทt plugin nร o", + "installing": "ฤang cร i ฤ‘แบทt {plugin}...", + "notify-updates": "Thรดng bรกo cแบญp nhแบญt tiแป‡n รญch", + "notify-updates-description": "Hiแปƒn thแป‹ mแป™t thรดng bรกo bแบญt lรชn khi cรณ bแบฃn cแบญp nhแบญt plugin mแป›i.", + "open-plugin-page": "MแปŸ trang tiแป‡n รญch mแปŸ rแป™ng", + "open-plugins-tab": "MแปŸ cร i ฤ‘แบทt plugin", + "plugin-settings-title": "Cร i ฤ‘แบทt {plugin}", + "refresh-refreshing": "ฤang lร m mแป›i danh sรกch plugin...", + "refresh-tooltip": "Lร m mแป›i cรกc plugin cรณ sแบตn", + "settings-error-not-loaded": "Plugin chฦฐa tแบฃi ฤ‘ฦฐแปฃc", + "settings-saved": "ฤรฃ lฦฐu cร i ฤ‘แบทt plugin", + "settings-tooltip": "Cร i ฤ‘แบทt Plugin", + "source-custom": "Nguแป“n tรนy chแป‰nh", + "sources-add-custom": "Thรชm kho lฦฐu trแปฏ tรนy chแป‰nh", + "sources-add-dialog-description": "Thรชm mแป™t kho lฦฐu trแปฏ GitHub lร m nguแป“n plugin.", + "sources-add-dialog-error": "Khรดng thแปƒ thรชm nguแป“n plugin", + "sources-add-dialog-name": "Tรชn kho lฦฐu trแปฏ", + "sources-add-dialog-name-placeholder": "Cรกc Plugin Tรนy Chแป‰nh Cแปงa Tรดi", + "sources-add-dialog-success": "ฤรฃ thรชm nguแป“n plugin thร nh cรดng", + "sources-add-dialog-title": "Thรชm Nguแป“n Plugin", + "sources-add-dialog-url": "URL Kho lฦฐu trแปฏ", + "sources-description": "Quแบฃn lรฝ kho plugin.", + "sources-placeholder": "Kho lฦฐu trแปฏ tuyแป‡t vแปi cแปงa tรดi", + "sources-remove-tooltip": "Gแปก bแป nguแป“n plugin", + "title": "Plugin", + "translations-reloaded": "ฤรฃ tแบฃi lแบกi bแบฃn dแป‹ch: {name}", + "uninstall-dialog-description": "Bแบกn cรณ chแบฏc chแบฏn muแป‘n gแปก cร i ฤ‘แบทt {plugin} khรดng? Thao tรกc nร y sแบฝ xรณa tแบฅt cแบฃ dแปฏ liแป‡u cแปงa plugin.", + "uninstall-dialog-title": "Gแปก cร i ฤ‘แบทt tiแป‡n รญch mแปŸ rแป™ng", + "uninstall-error": "Gแปก cร i ฤ‘แบทt thแบฅt bแบกi: {error}", + "uninstall-success": "ฤรฃ gแปก cร i ฤ‘แบทt thร nh cรดng {plugin}", + "uninstalling": "ฤang gแปก cร i ฤ‘แบทt {plugin}...", + "update-all": "Cแบญp nhแบญt tแบฅt cแบฃ ({count})", + "update-all-success": "Tแบฅt cแบฃ plugin ฤ‘รฃ cแบญp nhแบญt thร nh cรดng", + "update-available": "Cรณ bแบฃn cแบญp nhแบญt plugin mแป›i", + "update-available-plural": "Cรณ {count} bแบฃn cแบญp nhแบญt Plugin", + "update-error": "Cแบญp nhแบญt plugin thแบฅt bแบกi: {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (yรชu cแบงu Noctalia v{required})", + "update-success": "ฤรฃ cแบญp nhแบญt {plugin} lรชn v{version}", + "update-version": "v{current} โ†’ v{new}", + "updating": "ฤang cแบญp nhแบญt..." + }, + "region": { + "title": "Vรนng" + }, + "session-menu": { + "countdown-duration-description": "Thiแบฟt lแบญp thแปi gian ฤ‘แบฟm ngฦฐแปฃc kรฉo dร i bao lรขu trฦฐแป›c khi thแปฑc hiแป‡n cรกc hร nh ฤ‘แป™ng nguแป“n.", + "countdown-duration-label": "Thแปi lฦฐแปฃng ฤ‘แบฟm ngฦฐแปฃc", + "enable-countdown-description": "Hiแปƒn thแป‹ ฤ‘แบฟm ngฦฐแปฃc trฦฐแป›c khi thแปฑc hiแป‡n cรกc hร nh ฤ‘แป™ng nguแป“n.", + "enable-countdown-label": "Bแบญt bแป™ ฤ‘แบฟm ngฦฐแปฃc", + "entries-desc": "Tรนy chแป‰nh nhแปฏng hร nh ฤ‘แป™ng nguแป“n nร o xuแบฅt hiแป‡n trong Session Menu vร  theo thแปฉ tแปฑ nร o.", + "entries-title": "Hร nh ฤ‘แป™ng liรชn quan tแป›i nguแป“n", + "entry-settings-command-description": "Lแป‡nh tรนy chแป‰nh ฤ‘แปƒ thแปฑc thi cho hร nh ฤ‘แป™ng nร y. ฤแปƒ trแป‘ng ฤ‘แปƒ sแปญ dแปฅng lแป‡nh hแป‡ thแป‘ng mแบทc ฤ‘แป‹nh.", + "entry-settings-command-placeholder": "vรญ dแปฅ systemctl poweroff", + "entry-settings-default-command-lock": "Mร n hรฌnh khรณa nแป™i bแป™ (khรดng cรณ lแป‡nh)", + "entry-settings-default-command-logout": "ฤฤƒng xuแบฅt nแป™i bแป™ (khรดng lแป‡nh)", + "entry-settings-default-info-description": "Nแบฟu khรดng cรณ lแป‡nh tรนy chแป‰nh nร o ฤ‘ฦฐแปฃc chแป‰ ฤ‘แป‹nh, lแป‡nh hแป‡ thแป‘ng mแบทc ฤ‘แป‹nh sแบฝ ฤ‘ฦฐแปฃc sแปญ dแปฅng.", + "entry-settings-default-info-label": "Lแป‡nh Mแบทc ฤแป‹nh", + "entry-settings-keybind-description": "Gรกn mแป™t tแป• hแปฃp phรญm ฤ‘แปƒ kรญch hoแบกt hร nh ฤ‘แป™ng nร y khi menu phiรชn ฤ‘ang mแปŸ.", + "entry-settings-keybind-placeholder": "Nhแบฅn Ghi ฤ‘แปƒ gรกn phรญm...", + "entry-settings-keybind-recording": "ฤang ghi... Nhแบฅn bแบฅt kแปณ tแป• hแปฃp phรญm nร o", + "entry-settings-title": "Cแบฅu hรฌnh {entry}", + "entry-settings-tooltip": "Cแบฅu hรฌnh lแป‡nh", + "general-desc": "Cแบฅu hรฌnh hร nh vi vร  giao diแป‡n cแปงa bแบฃng menu phiรชn.", + "large-buttons-layout-description": "Chแปn cรกch hiแปƒn thแป‹ cรกc nรบt trong Menu phiรชn.", + "large-buttons-layout-label": "Bแป‘ cแปฅc nรบt lแป›n", + "large-buttons-style-description": "Hiแปƒn thแป‹ Menu Phiรชn vแป›i cรกc nรบt lแป›n trong bแป‘ cแปฅc dแบกng lฦฐแป›i.", + "large-buttons-style-label": "Kiแปƒu nรบt lแป›n", + "position-description": "Chแปn vแป‹ trรญ bแบฃng ฤ‘iแปu khiแปƒn thแปฑc ฤ‘ฦกn nguแป“n xuแบฅt hiแป‡n khi ฤ‘ฦฐแปฃc mแปŸ.", + "show-header-description": "Hiแปƒn thแป‹ tiรชu ฤ‘แป vร  nรบt ฤ‘รณng แปŸ ฤ‘แบงu menu phiรชn.", + "show-header-label": "Hiแปƒn thแป‹ tiรชu ฤ‘แป", + "show-keybinds-description": "Hiแปƒn thแป‹ gแปฃi รฝ phรญm tแบฏt trรชn cรกc tรนy chแปn phiรชn lร m viแป‡c.", + "show-keybinds-label": "Hiแป‡n phรญm tแบฏt" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Vแบซn hiแปƒn thแป‹ cรกc tiแป‡n รญch mร n hรฌnh nแปn khi chแบฟ ฤ‘แป™ hiแป‡u suแบฅt cแปงa Noctalia ฤ‘ฦฐแปฃc bแบญt.", + "noctalia-performance-disable-desktop-widgets-label": "Bแบญt widget mร n hรฌnh nแปn แปŸ chแบฟ ฤ‘แป™ hiแป‡u suแบฅt", + "noctalia-performance-disable-wallpaper-description": "Giแปฏ hรฌnh nแปn mร n hรฌnh nแปn, tแป•ng quan vร  mร n hรฌnh khรณa hiแปƒn thแป‹ khi chแบฟ ฤ‘แป™ hiแป‡u suแบฅt cแปงa Noctalia ฤ‘ฦฐแปฃc bแบญt.", + "noctalia-performance-disable-wallpaper-label": "Bแบญt hiแปƒn thแป‹ hรฌnh nแปn แปŸ chแบฟ ฤ‘แป™ hiแป‡u suแบฅt", + "title": "Hแป‡ thแป‘ng" + }, + "system-monitor": { + "critical-color-label": "Mร u nguy cแบฅp", + "custom-highlight-colors-title-label": "Mร u Nhแบฅn Tรนy Chแป‰nh", + "disk-available-label": "Dung lฦฐแปฃng ฤ‘ฤฉa khแบฃ dแปฅng", + "disk-section-label": "Sแปญ dแปฅng ฤ‘ฤฉa", + "enable-dgpu-monitoring-description": "Cแบฃnh bรกo: ฤiแปu nร y sแบฝ kรญch hoแบกt GPU rแปi (NVIDIA/AMD) cแปงa bแบกn, cรณ thแปƒ แบฃnh hฦฐแปŸng ฤ‘รกng kแปƒ ฤ‘แบฟn thแปi lฦฐแปฃng pin trรชn laptop cรณ ฤ‘แป“ hแปa lai.", + "enable-dgpu-monitoring-label": "Bแบญt theo dรตi GPU rแปi", + "external-monitor-description": "Nhแบญp lแป‡nh hoแบทc ฤ‘ฦฐแปng dแบซn แปฉng dแปฅng ฤ‘แปƒ khแปŸi chแบกy khi kรญch hoแบกt แปฉng dแปฅng giรกm sรกt hแป‡ thแป‘ng bรชn ngoร i.", + "external-monitor-label": "Lแป‡nh Giรกm sรกt Hแป‡ thแป‘ng Bรชn ngoร i", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "Cแบฅu hรฌnh hร nh vi vร  giao diแป‡n cแปงa giรกm sรกt hแป‡ thแป‘ng.", + "gpu-section-label": "Nhiแป‡t ฤ‘แป™ GPU", + "highlight-colors-section-label": "Mร u sแบฏc ฤ‘iแปƒm nhแบฅn", + "polling-interval-label": "Khoแบฃng thแปi gian thฤƒm dรฒ", + "polling-section-description": "Cแบฅu hรฌnh tแบงn suแบฅt cแบญp nhแบญt cแปงa mแป—i chแป‰ sแป‘ hแป‡ thแป‘ng.", + "polling-section-label": "Khoแบฃng thแปi gian thฤƒm dรฒ", + "threshold-critical": "Ngฦฐแปกng nguy hiแปƒm", + "threshold-warning": "Ngฦฐแปกng cแบฃnh bรกo", + "thresholds-section-description": "ฤiแปu chแป‰nh ngฦฐแปกng cแบฃnh bรกo/nguy hiแปƒm cho tแปซng chแป‰ sแป‘ hแป‡ thแป‘ng.", + "use-custom-highlight-colors-description": "Khi tแบฏt, mร u nแป•i bแบญt mแบทc ฤ‘แป‹nh cแปงa chแปง ฤ‘แป sแบฝ ฤ‘ฦฐแปฃc sแปญ dแปฅng.", + "use-custom-highlight-colors-label": "Sแปญ dแปฅng mร u nแป•i bแบญt tรนy chแป‰nh", + "warning-color-label": "Mร u cแบฃnh bรกo" + }, + "user-interface": { + "allow-panels-without-bar-description": "Khi ฤ‘ฦฐแปฃc bแบญt, cรกc bแบฃng ฤ‘iแปu khiแปƒn cรณ thแปƒ mแปŸ trรชn bแบฅt kแปณ mร n hรฌnh nร o. Khi bแป‹ tแบฏt, cรกc bแบฃng ฤ‘iแปu khiแปƒn sแบฝ chแป‰ mแปŸ trรชn cรกc mร n hรฌnh cรณ thanh, ฤ‘iแปu nร y cรณ thแปƒ giแบฃm mแปฉc sแปญ dแปฅng bแป™ nhแป›.", + "allow-panels-without-bar-label": "Cho phรฉp bแบฃng ฤ‘iแปu khiแปƒn trรชn mร n hรฌnh khรดng cรณ thanh", + "animation-disable-description": "Tแบฏt tแบฅt cแบฃ hoแบกt แบฃnh ฤ‘แปƒ cรณ trแบฃi nghiแป‡m nhanh hฦกn, phแบฃn hแป“i tแป‘t hฦกn.", + "animation-disable-label": "Tแบฏt hiแป‡u แปฉng ฤ‘แป™ng giao diแป‡n ngฦฐแปi dรนng", + "animation-speed-description": "ฤiแปu chแป‰nh tแป‘c ฤ‘แป™ hoแบกt แบฃnh toร n cแปฅc.", + "animation-speed-label": "Tแป‘c ฤ‘แป™ hoแบกt แบฃnh", + "animation-speed-reset": "ฤแบทt lแบกi tแป‘c ฤ‘แป™ hoแบกt แบฃnh", + "appearance-desc": "Tรนy chแป‰nh cรกc yแบฟu tแป‘ hรฌnh แบฃnh nhฦฐ chรบ giแบฃi cรดng cแปฅ, viแปn vร  bรณng ฤ‘แป•.", + "blur-behind-description": "Lร m mแป khu vแปฑc phรญa sau cรกc Thanh vร  Bแบฃng ฤ‘iแปu khiแปƒn bแบฑng giao thแปฉc lร m mแป cแปงa trรฌnh tแป•ng hแปฃp.", + "blur-behind-label": "Lร m mแป nแปn", + "box-border-description": "Hiแปƒn thแป‹ ฤ‘ฦฐแปng viแปn xung quanh cรกc khu vแปฑc nแป™i dung.", + "box-border-label": "Viแปn Vรนng Chแปฉa", + "box-border-radius-description": "ฤiแปu chแป‰nh ฤ‘แป™ bo trรฒn gรณc cแปงa cรกc phแบงn bแป‘ cแปฅc chรญnh, nhฦฐ thanh bรชn, thแบป vร  bแบฃng nแป™i dung.", + "box-border-radius-label": "Bo Gรณc Vรนng Chแปฉa", + "control-border-radius-description": "ฤiแปu khiแปƒn ฤ‘แป™ cong cแปงa cรกc yแบฟu tแป‘ tฦฐฦกng tรกc, bao gแป“m cรกc nรบt, cรดng tแบฏc vร  trฦฐแปng vฤƒn bแบฃn.", + "control-border-radius-label": "Nhแบญp bรกn kรญnh", + "desc": "Tรนy chแป‰nh vแบป ngoร i, cแบฃm nhแบญn vร  hร nh vi cแปงa giao diแป‡n.", + "dim-desktop-description": "Lร m mแป mร n hรฌnh nแปn khi cรกc Bแบฃng ฤ‘iแปu khiแปƒn hoแบทc menu ฤ‘ang mแปŸ.", + "dim-desktop-label": "Lร m mแป mร n hรฌnh nแปn", + "dimmer-opacity-description": "ฤแบทt mแปฉc ฤ‘แป™ mแป cho tรญnh nฤƒng lร m mแป mร n hรฌnh nแปn.", + "dimmer-opacity-label": "ฤแป™ mแป ฤ‘แปฅc mร n hรฌnh nแปn ฤ‘รฃ lร m mแป", + "panel-background-opacity-description": "ฤแบทt ฤ‘แป™ mแป nแปn cho tแบฅt cแบฃ cรกc bแบฃng ฤ‘iแปu khiแปƒn (thanh, trรฌnh khแปŸi chแบกy, cร i ฤ‘แบทt, v.v...).", + "panel-background-opacity-label": "ฤแป™ mแป nแปn cแปงa Bแบฃng ฤ‘iแปu khiแปƒn", + "panels-attached-to-bar-description": "Cรกc Bแบฃng ฤ‘iแปu khiแปƒn khรณa vร o Thanh vร  cรกc cแบกnh mร n hรฌnh, tแบกo ra mแป™t giao diแป‡n liแปn mแบกch vแป›i cรกc gรณc bo ngฦฐแปฃc ฤ‘แบงy phong cรกch.", + "panels-attached-to-bar-label": "Hรญt bแบฃng vร o cรกc cแบกnh", + "panels-overlay-description": "ฤแบฃm bแบฃo cรกc Panel vร  Bar luรดn hiแปƒn thแป‹, ngay cแบฃ trรชn cรกc แปฉng dแปฅng toร n mร n hรฌnh.", + "panels-overlay-label": "Giแปฏ bแบฃng ฤ‘iแปu khiแปƒn & thanh แปŸ trรชn cรนng", + "scaling-description": "Thay ฤ‘แป•i kรญch thฦฐแป›c cแปงa giao diแป‡n ngฦฐแปi dรนng chung, trแปซ thanh Bar.", + "scaling-label": "Tแปท lแป‡ giao diแป‡n", + "scrollbar-always-visible-description": "Giแปฏ thanh cuแป™n hiแปƒn thแป‹ bแบฅt cแปฉ khi nร o nแป™i dung cรณ thแปƒ cuแป™n ฤ‘ฦฐแปฃc, thay vรฌ chแป‰ hiแปƒn thแป‹ chรบng khi di chuแป™t qua.", + "scrollbar-always-visible-label": "Luรดn hiแปƒn thแป‹ thanh cuแป™n", + "settings-panel-header": "Bแบฃng Cร i ฤแบทt", + "settings-panel-mode-description": "Chแปn bแป‘ cแปฅc cร i ฤ‘แบทt (cรณ thแปƒ yรชu cแบงu mแปŸ lแบกi).", + "settings-panel-mode-label": "Chแบฟ ฤ‘แป™ bแบฃng cร i ฤ‘แบทt", + "settings-panel-sidebar-card-style-description": "Bแปc thanh bรชn cร i ฤ‘แบทt trong mแป™t nแปn ฤ‘ฦฐแปฃc tรด ฤ‘แบงy vแป›i cรกc gรณc bo trรฒn.", + "settings-panel-sidebar-card-style-label": "Kiแปƒu Thแบป Thanh Bรชn", + "shadows-description": "Bแบญt ฤ‘แป• bรณng dฦฐแป›i thanh vร  bแบฃng.", + "shadows-direction-description": "Chแปn vแป‹ trรญ ฤ‘แป• bรณng.", + "shadows-direction-label": "Hฦฐแป›ng ฤ‘แป• bรณng", + "shadows-label": "Bรณng ฤ‘แป•", + "title": "Giao Diแป‡n Ngฦฐแปi Dรนng", + "tooltips-description": "Bแบญt hoแบทc tแบฏt gแปฃi รฝ trรชn toร n bแป™ giao diแป‡n.", + "tooltips-label": "Hiแปƒn thแป‹ chรบ giแบฃi cรดng cแปฅ", + "translucent-widgets-description": "Lร m cho cรกc nรบt, tab vร  cรกc tiแป‡n รญch khรกc bรชn trong cรกc Bแบฃng ฤ‘iแปu khiแปƒn trong suแป‘t mแป™t phแบงn.", + "translucent-widgets-label": "Tiแป‡n รญch trong mแป" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "Theo thแปฉ tแปฑ bแบฃng chแปฏ cรกi", + "automation-change-mode-description": "Chแปn cรกch hรฌnh nแปn ฤ‘ฦฐแปฃc chแปn khi tแปฑ ฤ‘แป™ng thay ฤ‘แป•i.", + "automation-change-mode-label": "Thay ฤแป•i Chแบฟ ฤแป™", + "automation-custom-interval-description": "Nhแบญp thแปi gian theo ฤ‘แป‹nh dแบกng HH:MM (vรญ dแปฅ: 01:30).", + "automation-custom-interval-label": "Khoแบฃng thแปi gian tรนy chแป‰nh", + "automation-interval-description": "Tแบงn suแบฅt thay ฤ‘แป•i hรฌnh nแปn tแปฑ ฤ‘แป™ng.", + "automation-interval-label": "Khoแบฃng thแปi gian hรฌnh nแปn", + "automation-random-wallpaper-description": "Lรชn lแป‹ch thay ฤ‘แป•i hรฌnh nแปn ngแบซu nhiรชn theo cรกc khoแบฃng thแปi gian ฤ‘แปu ฤ‘แบทn.", + "automation-scheduled-change-description": "Tแปฑ ฤ‘แป™ng thay ฤ‘แป•i hรฌnh nแปn ฤ‘แป‹nh kแปณ.", + "automation-scheduled-change-label": "Thay ฤ‘แป•i theo lแป‹ch trรฌnh", + "look-feel-edge-smoothness-description": "รp dแปฅng hiแป‡u แปฉng lร m mแป viแปn mแปm mแบกi cho cรกc chuyแปƒn ฤ‘แป•i.", + "look-feel-edge-smoothness-label": "Lร m mแปm cแบกnh chuyแปƒn tiแบฟp", + "look-feel-fill-color-description": "Chแปn mแป™t mร u nแปn cรณ thแปƒ xuแบฅt hiแป‡n phรญa sau hรฌnh nแปn.", + "look-feel-fill-mode-description": "Chแปn cรกch hรฌnh แบฃnh sแบฝ ฤ‘iแปu chแป‰nh kรญch thฦฐแป›c ฤ‘แปƒ phรน hแปฃp vแป›i ฤ‘แป™ phรขn giแบฃi mร n hรฌnh cแปงa bแบกn.", + "look-feel-fill-mode-label": "Chแบฟ ฤ‘แป™ lแบฅp ฤ‘แบงy", + "look-feel-skip-startup-transition-description": "Bแป qua hoแบกt แบฃnh hรฌnh nแปn khi shell khแปŸi ฤ‘แป™ng.", + "look-feel-skip-startup-transition-label": "Bแป qua chuyแปƒn tiแบฟp khแปŸi ฤ‘แป™ng", + "look-feel-title": "Giao diแป‡n & Trแบฃi nghiแป‡m", + "look-feel-transition-duration-description": "Thแปi lฦฐแปฃng cแปงa cรกc hoแบกt แบฃnh chuyแปƒn tiแบฟp, tรญnh bแบฑng giรขy.", + "look-feel-transition-duration-label": "Thแปi lฦฐแปฃng chuyแปƒn tiแบฟp", + "look-feel-transition-type-description": "Kiแปƒu hoแบกt แบฃnh khi chuyแปƒn ฤ‘แป•i giแปฏa cรกc hรฌnh nแปn.", + "look-feel-transition-type-label": "Kiแปƒu chuyแปƒn tiแบฟp", + "settings-clear-cache-button": "Xรณa bแป™ nhแป› ฤ‘แป‡m", + "settings-clear-cache-description": "Xรณa hรฌnh nแปn ฤ‘รฃ thay ฤ‘แป•i kรญch thฦฐแป›c ฤ‘ฦฐแปฃc lฦฐu vร o bแป™ nhแป› ฤ‘แป‡m ฤ‘แปƒ giแบฃi phรณng dung lฦฐแปฃng ฤ‘ฤฉa.", + "settings-clear-cache-label": "Bแป™ nhแป› ฤ‘แป‡m hรฌnh nแปn", + "settings-clear-cache-toast": "ฤรฃ xรณa bแป™ nhแป› ฤ‘แป‡m hรฌnh nแปn", + "settings-desc": "Kiแปƒm soรกt cรกch hรฌnh nแปn ฤ‘ฦฐแปฃc quแบฃn lรฝ vร  hiแปƒn thแป‹.", + "settings-enable-management-description": "Quแบฃn lรฝ hรฌnh nแปn vแป›i Noctalia. Bแป chแปn nแบฟu bแบกn thรญch dรนng แปฉng dแปฅng khรกc.", + "settings-enable-management-label": "Bแบญt quแบฃn lรฝ hรฌnh nแปn", + "settings-enable-overview-description": "รp dแปฅng hรฌnh nแปn mแป vร  tแป‘i vร o mร n hรฌnh tแป•ng quan.", + "settings-enable-overview-label": "Bแบญt hรฌnh nแปn tแป•ng quan", + "settings-folder-description": "ฤฦฐแปng dแบซn ฤ‘แบฟn thฦฐ mแปฅc hรฌnh nแปn chรญnh cแปงa bแบกn.", + "settings-folder-label": "Thฦฐ mแปฅc hรฌnh nแปn", + "settings-hide-wallpaper-filenames-tooltip-hide": "แบจn tรชn tแป‡p", + "settings-hide-wallpaper-filenames-tooltip-show": "Hiแปƒn thแป‹ tรชn tแป‡p", + "settings-monitor-specific-description": "ฤแบทt mแป™t thฦฐ mแปฅc hรฌnh nแปn khรกc cho mแป—i mร n hรฌnh.", + "settings-monitor-specific-label": "Thฦฐ mแปฅc dร nh riรชng cho mร n hรฌnh", + "settings-monitor-specific-tooltip": "Theo dรตi thฦฐ mแปฅc hรฌnh nแปn", + "settings-overview-blur-strength-description": "รp dแปฅng cฦฐแปng ฤ‘แป™ lร m mแป cho tแป•ng quan.", + "settings-overview-blur-strength-label": "Cฦฐแปng ฤ‘แป™ lร m mแป Tแป•ng quan", + "settings-overview-tint-description": "รp dแปฅng cฦฐแปng ฤ‘แป™ sแบฏc thรกi cho chแบฟ ฤ‘แป™ tแป•ng quan.", + "settings-overview-tint-label": "Cฦฐแปng ฤ‘แป™ mร u phแปง tแป•ng quan", + "settings-recursive-search-description": "Cลฉng tรฌm kiแบฟm hรฌnh nแปn trong cรกc thฦฐ mแปฅc con cแปงa thฦฐ mแปฅc hรฌnh nแปn.", + "settings-recursive-search-label": "Tรฌm kiแบฟm thฦฐ mแปฅc con", + "settings-select-monitor-folder": "Chแปn thฦฐ mแปฅc hรฌnh nแปn mร n hรฌnh", + "settings-selector-description": "Chแปn hรฌnh nแปn cแปงa bแบกn.", + "settings-selector-position-description": "Chแปn nฦกi bแบฃng chแปn hรฌnh nแปn xuแบฅt hiแป‡n.", + "settings-show-hidden-files-tooltip-hide": "แบจn cรกc tแป‡p แบฉn", + "settings-show-hidden-files-tooltip-show": "Hiแป‡n tแป‡p แบฉn", + "settings-title": "Cร i ฤ‘แบทt Hรฌnh nแปn", + "settings-use-original-images-description": "Bแป qua viแป‡c thay ฤ‘แป•i kรญch thฦฐแป›c hรฌnh nแปn trฦฐแป›c khi hiแปƒn thแป‹. Tiแบฟt kiแป‡m dung lฦฐแปฃng ฤ‘ฤฉa vร  giแบฃm mแปฉc sแปญ dแปฅng CPU trong quรก trรฌnh thay ฤ‘แป•i hรฌnh nแปn, nhฦฐng cรณ thแปƒ sแปญ dแปฅng nhiแปu bแป™ nhแป› hฦกn cho cรกc hรฌnh แบฃnh rแบฅt lแป›n.", + "settings-use-original-images-label": "Sแปญ dแปฅng hรฌnh แบฃnh gแป‘c", + "settings-view-mode-description": "Chแปn cรกch hรฌnh nแปn ฤ‘ฦฐแปฃc hiแปƒn thแป‹ tแปซ thฦฐ mแปฅc cแปงa bแบกn.", + "settings-view-mode-label": "Chแบฟ ฤ‘แป™ xem", + "view-mode-browse": "Duyแป‡t thฦฐ mแปฅc", + "view-mode-cycle-tooltip": "Chแบฟ ฤ‘แป™ xem: {mode} (nhแบฅn ฤ‘แปƒ thay ฤ‘แป•i)", + "view-mode-recursive": "Thฦฐ mแปฅc con ฤ‘ฦฐแปฃc lร m phแบณng", + "view-mode-single": "Thฦฐ mแปฅc gแป‘c" + } + }, + "placeholders": { + "add-new-keybind": "Thรชm phรญm tแบฏt mแป›i", + "command-example": "echo \"Hello World\"", + "enter-command": "Nhแบญp lแป‡nh ฤ‘แปƒ thแปฑc thi (แปฉng dแปฅng hoแบทc tแบญp lแป‡nh tรนy chแป‰nh)", + "enter-ipc-identifier": "Nhแบญp mรฃ ฤ‘แป‹nh danh duy nhแบฅt cho cรกc lแป‡nh IPC", + "enter-path": "Nhแบญp ฤ‘ฦฐแปng dแบซn...", + "enter-text-to-collapse": "vรญ dแปฅ: 'khรดng cรณ gรฌ ฤ‘ang phรกt'. Sแปญ dแปฅng /regex/ cho cรกc mแบซu.", + "enter-tooltip": "Nhแบญp Chรบ Giแบฃi Cรดng Cแปฅ", + "enter-width-pixels": "Nhแบญp chiแปu rแป™ng (pixel)", + "keybind-recording": "ฤang ghi phรญm tแบฏt...", + "search": "Tรฌm kiแบฟm...", + "search-icons": "Vรญ dแปฅ: noctalia, niri, battery, cloud", + "search-launcher": "Tรฌm kiแบฟm mแปฅc nhแบญp... hoแบทc dรนng > cho lแป‡nh", + "search-wallhaven": "Tรฌm kiแบฟm Wallhaven...", + "search-wallpapers": "Gรต ฤ‘แปƒ lแปc hรฌnh nแปn..." + }, + "positions": { + "bottom": "Dฦฐแป›i cรนng", + "bottom-center": "Giแปฏa dฦฐแป›i", + "bottom-left": "Dฦฐแป›i cรนng bรชn trรกi", + "bottom-right": "Dฦฐแป›i cรนng bรชn phแบฃi", + "center": "Giแปฏa", + "center-left": "Giแปฏa bรชn trรกi", + "center-right": "Giแปฏa phแบฃi", + "close-to-bar": "Nรบt ฤ‘รณng vแป Bar", + "follow-bar": "Theo dรตi thanh", + "left": "Trรกi", + "right": "Phแบฃi", + "top": "Trรชn cรนng", + "top-center": "Giแปฏa trรชn cรนng", + "top-left": "Trรชn cรนng bรชn trรกi", + "top-right": "Trรชn cรนng bรชn phแบฃi" + }, + "session-menu": { + "action-in-seconds": "{action} trong {seconds} giรขy...", + "cancel-timer": "Hแปงy hแบนn giแป", + "title": "Menu Hแป‡ thแป‘ng" + }, + "setup": { + "all-done": "Hoร n tแบฅt!", + "appearance": { + "subheader": "Chแปn Chแบฟ ฤ‘แป™ tแป‘i vร  nguแป“n mร u (hรฌnh nแปn hoแบทc cร i ฤ‘แบทt sแบตn)." + }, + "customize": { + "header": "Tรนy chแป‰nh trแบฃi nghiแป‡m cแปงa bแบกn", + "subheader": "ฤiแปu chแป‰nh vแป‹ trรญ, mแบญt ฤ‘แป™, tแป‰ lแป‡ cแปงa Thanh Bar vร  nhiแปu hฦกn nแปฏa." + }, + "skip-setup": "Bแป qua thiแบฟt lแบญp", + "telemetry-wizard-done": "ฤรฃ hiแปƒu!", + "telemetry-wizard-note": "Bแบกn cรณ toร n quyแปn kiแปƒm soรกt โ€” bแบญt hoแบทc tแบฏt tรญnh nฤƒng nร y bแบฅt cแปฉ lรบc nร o trong cร i ฤ‘แบทt", + "telemetry-wizard-subtitle": "Chรบng tรดi ฤ‘รฃ thรชm phรขn tรญch แบฉn danh ฤ‘แปƒ giรบp cแบฃi thiแป‡n Noctalia", + "telemetry-wizard-title": "Cแบญp nhแบญt Quyแปn riรชng tฦฐ", + "wallpaper": { + "choose-dir": "Chแปn mแป™t thฦฐ mแปฅc chแปฉa hรฌnh nแปn cแปงa bแบกn", + "dir-browse": "Duyแป‡t thฦฐ mแปฅc hรฌnh nแปn", + "dir-description": "Chแปn thฦฐ mแปฅc chแปฉa hรฌnh nแปn cแปงa bแบกn.", + "dir-label": "Thฦฐ mแปฅc hรฌnh nแปn", + "dir-select-title": "Chแปn thฦฐ mแปฅc hรฌnh nแปn", + "header": "Chแปn hรฌnh nแปn", + "no-dir": "Chฦฐa chแปn thฦฐ mแปฅc hรฌnh nแปn", + "no-valid": "Khรดng tรฌm thแบฅy tแป‡p hรฌnh แบฃnh hแปฃp lแป‡ trong: {dir}", + "none-in-dir": "Khรดng tรฌm thแบฅy hรฌnh nแปn nร o trong thฦฐ mแปฅc", + "preview-error": "Khรดng thแปƒ tแบฃi hรฌnh แบฃnh", + "select-prompt": "Chแปn mแป™t hรฌnh nแปn bรชn dฦฐแป›i", + "subheader": "Tแบกo khรดng khรญ vแป›i mแป™t hรฌnh nแปn ฤ‘แบนp." + }, + "welcome": "Chร o mแปซng", + "welcome-note": "Chแป‰ mแป™t vร i ฤ‘iแปu cฦก bแบฃn ฤ‘แปƒ bแบกn bแบฏt ฤ‘แบงu โ€” cรกc tรนy chแปn ฤ‘แบงy ฤ‘แปง nแบฑm trong cร i ฤ‘แบทt", + "welcome-subtitle": "Hรฃy cรนng cรก nhรขn hรณa mร n hรฌnh cแปงa bแบกn mแป™t cรกch ฤ‘แป™c ฤ‘รกo.", + "welcome-title": "Chร o mแปซng ฤ‘แบฟn vแป›i Noctalia!" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "Khรดng phรกt hiแป‡n trรฌnh phรกt ฤ‘a phฦฐฦกng tiแป‡n", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "แปจng dแปฅng khรดng xรกc ฤ‘แป‹nh", + "uptime": "Thแปi gian hoแบกt ฤ‘แป™ng: {uptime}", + "user-requested": "Ngฦฐแปi dรนng yรชu cแบงu", + "welcome-back": "Chร o mแปซng trแปŸ lแบกi,", + "widget-settings-title": "Cร i ฤ‘แบทt {widget}" + }, + "system-monitor": { + "core-usage": "Mแปฉc sแปญ dแปฅng lรตi {id}", + "cpu-temp": "Nhiแป‡t ฤ‘แป™ CPU", + "cpu-usage": "Mแปฉc sแปญ dแปฅng CPU", + "disk": "แป” ฤ‘ฤฉa", + "download-speed": "Tแป‘c ฤ‘แป™ tแบฃi xuแป‘ng", + "gpu-temp": "Nhiแป‡t ฤ‘แป™ GPU", + "load-average": "Tแบฃi trแปng trung bรฌnh ", + "title": "Giรกm sรกt Hแป‡ thแป‘ng", + "upload-speed": "Tแป‘c ฤ‘แป™ tแบฃi lรชn" + }, + "toast": { + "airplane-mode": { + "description": "Tแบฏt tแบฅt cแบฃ cรกc kแบฟt nแป‘i khรดng dรขy.", + "title": "Chแบฟ ฤ‘แป™ Mรกy bay" + }, + "battery": { + "critical": "Pin yแบฟu nghiรชm trแปng", + "critical-desc": "Pin cรฒn {percent}% โ€” vui lรฒng kแบฟt nแป‘i bแป™ sแบกc ngay lแบญp tแปฉc", + "low": "Pin yแบฟu", + "low-desc": "Pin cรฒn {percent}% โ€” vui lรฒng kแบฟt nแป‘i bแป™ sแบกc" + }, + "bluetooth": { + "address-copied": "ฤแป‹a chแป‰ ฤ‘รฃ sao chรฉp vร o bแบฃng tแบกm", + "auto-connect-disabled": "Tแปฑ ฤ‘แป™ng kแบฟt nแป‘i ฤ‘รฃ tแบฏt", + "auto-connect-enabled": "Tแปฑ ฤ‘แป™ng kแบฟt nแป‘i ฤ‘รฃ bแบญt", + "auto-connecting": "ฤang kแบฟt nแป‘i vแป›i {count} thiแบฟt bแป‹...", + "confirm-code": "Xรกc nhแบญn mรฃ {value} trรชn thiแบฟt bแป‹ khรกc.", + "connect-failed": "Khรดng thแปƒ kแบฟt nแป‘i vแป›i thiแบฟt bแป‹", + "disconnect-failed": "Khรดng thแปƒ ngแบฏt kแบฟt nแป‘i khแปi thiแบฟt bแป‹", + "discoverable-change-failed": "Khรดng thแปƒ thay ฤ‘แป•i trแบกng thรกi hiแปƒn thแป‹", + "discoverable-disabled": "Khรดng thแปƒ phรกt hiแป‡n", + "discoverable-enabled": "Chแบฟ ฤ‘แป™ khรกm phรก ฤ‘รฃ bแบญt", + "display-code": "Nhแบญp mรฃ {value} trรชn thiแบฟt bแป‹ khรกc.", + "forget-failed": "Khรดng thแปƒ quรชn thiแบฟt bแป‹", + "pair-failed": "Ghรฉp nแป‘i thiแบฟt bแป‹ thแบฅt bแบกi", + "passkey-required": "Thiแบฟt bแป‹ yรชu cแบงu mรฃ khรณa.", + "pincode-required": "Mรฃ PIN ฤ‘ฦฐแปฃc yรชu cแบงu bแปŸi thiแบฟt bแป‹.", + "state-change-failed": "Khรดng thแปƒ thay ฤ‘แป•i trแบกng thรกi Bluetooth" + }, + "clipboard": { + "long-text": "Vฤƒn bแบฃn dร i", + "unavailable": "Lแป‹ch sแปญ khay nhแป› tแบกm khรดng khแบฃ dแปฅng", + "unavailable-desc": "แปจng dแปฅng 'cliphist' chฦฐa ฤ‘ฦฐแปฃc cร i ฤ‘แบทt. Vui lรฒng cร i ฤ‘แบทt nรณ ฤ‘แปƒ sแปญ dแปฅng tรญnh nฤƒng lแป‹ch sแปญ khay nhแป› tแบกm." + }, + "custom-command-failed": { + "description": "Lแป‡nh thแบฅt bแบกi: {command}\nMรฃ thoรกt: {code}", + "title": "Lแป‡nh tรนy chแป‰nh thแบฅt bแบกi" + }, + "do-not-disturb": { + "disabled": "Chแบฟ ฤ‘แป™ Khรดng lร m phiแปn ฤ‘รฃ tแบฏt", + "disabled-desc": "ฤang hiแปƒn thแป‹ tแบฅt cแบฃ thรดng bรกo", + "enabled": "Khรดng lร m phiแปn ฤ‘รฃ bแบญt", + "enabled-desc": "Bแบกn sแบฝ tรฌm thแบฅy nhแปฏng thรดng bรกo nร y trong lแป‹ch sแปญ cแปงa mรฌnh" + }, + "donation-opened": "Trang quyรชn gรณp ฤ‘รฃ mแปŸ trong trรฌnh duyแป‡t cแปงa bแบกn", + "internet-limited": "ฤรฃ kแบฟt nแป‘i, khรดng cรณ Internet", + "keyboard-layout": { + "changed": "Bแป‘ cแปฅc bร n phรญm ฤ‘รฃ ฤ‘แป•i thร nh {layout}", + "title": "Bร n phรญm" + }, + "missing-control-center": { + "description": "Tiแป‡n รญch Trung tรขm ฤ‘iแปu khiแปƒn ฤ‘รฃ bแป‹ xรณa khแปi thanh. ฤแปƒ truy cแบญp lแบกi tแปซ thanh, bแบกn cแบงn thรชm lแบกi tiแป‡n รญch. Bแบกn cลฉng cรณ thแปƒ mแปŸ nรณ bแบฑng cรกch nhแบฅp chuแป™t phแบฃi vร o thanh.", + "label": "ฤรฃ xรณa tiแป‡n รญch Trung tรขm ฤ‘iแปu khiแปƒn cuแป‘i cรนng" + }, + "night-light": { + "forced": "Kรญch hoแบกt bแบฏt buแป™c", + "normal": "Chแบฟ ฤ‘แป™ bรฌnh thฦฐแปng", + "not-installed": "wlsunset chฦฐa cร i ฤ‘แบทt" + }, + "noctalia-performance": { + "disabled": "Chแบฟ ฤ‘แป™ hiแป‡u nฤƒng ฤ‘รฃ tแบฏt", + "enabled": "Chแบฟ ฤ‘แป™ hiแป‡u nฤƒng ฤ‘รฃ bแบญt", + "label": "Chแบฟ ฤ‘แป™ hiแป‡u nฤƒng cแปงa Noctalia" + }, + "power-profile": { + "changed": "Cแบฅu hรฌnh nguแป“n ฤ‘รฃ thay ฤ‘แป•i", + "profile-name": "Cแบฅu hรฌnh" + }, + "theming-processor-failed": { + "desc-generic": "Gแบทp lแป—i khi xแปญ lรฝ mแบซu", + "title": "Xแปญ lรฝ mแบซu thแบฅt bแบกi" + }, + "vpn": { + "connected": "ฤรฃ kแบฟt nแป‘i vแป›i '{name}'", + "disconnected": "ฤรฃ ngแบฏt kแบฟt nแป‘i khแปi '{name}'" + }, + "wallpaper-colors": { + "disabled": "Mร u sแบฏc hรฌnh nแปn ฤ‘รฃ tแบฏt", + "enabled": "Mร u nแปn ฤ‘รฃ bแบญt", + "label": "Mร u sแบฏc hรฌnh nแปn" + }, + "wifi": { + "connected": "ฤรฃ kแบฟt nแป‘i vแป›i '{ssid}'", + "connection-failed": "Kแบฟt nแป‘i thแบฅt bแบกi", + "connection-timeout": "Hแบฟt thแปi gian chแป kแบฟt nแป‘i", + "disconnected": "ฤรฃ ngแบฏt kแบฟt nแป‘i khแปi '{ssid}'", + "incorrect-password": "Mแบญt khแบฉu khรดng ฤ‘รบng", + "network-not-found": "Khรดng tรฌm thแบฅy mแบกng" + } + }, + "tooltips": { + "add-widget": "Thรชm tiแป‡n รญch", + "auto-connect": "Bแบญt/Tแบฏt tแปฑ ฤ‘แป™ng kแบฟt nแป‘i cho thiแบฟt bแป‹ nร y", + "bluetooth-auto-connect-off": "Tแปฑ ฤ‘แป™ng kแบฟt nแป‘i ฤ‘ang tแบฏt", + "bluetooth-auto-connect-on": "Tแปฑ ฤ‘แป™ng kแบฟt nแป‘i ฤ‘ang bแบญt", + "bluetooth-devices": "Thiแบฟt bแป‹ Bluetooth", + "brightness-at": "ฤแป™ sรกng: {brightness}%", + "click-to-start-recording": "Mรกy ghi mร n hรฌnh (bแบฏt ฤ‘แบงu ghi)", + "click-to-stop-recording": "Ghi mร n hรฌnh (dแปซng ghi)", + "collapse": "Thu gแปn thanh bรชn", + "copy-address": "Sao chรฉp ฤ‘แป‹a chแป‰", + "delete-notification": "Xรณa thรดng bรกo", + "dismiss-notification": "Bแป qua thรดng bรกo", + "do-not-disturb-enabled": "Khรดng lร m phiแปn", + "expand": "MแปŸ rแป™ng thanh bรชn", + "forget-network": "Quรชn mแบกng", + "grid-view": "Dแบกng Lฦฐแป›i", + "hidden-files-hide": "Tแป‡p แบฉn", + "hide-unnamed-devices": "แบจn thiแบฟt bแป‹ khรดng tรชn", + "home": "Thฦฐ mแปฅc chรญnh", + "input-muted": "Bแบญt/Tแบฏt tiแบฟng ฤ‘แบงu vร o", + "keep-awake": "Giแปฏ Mร n Hรฌnh Luรดn Bแบญt", + "keyboard-layout": "Bแป‘ cแปฅc bร n phรญm {layout}", + "list-view": "Chแบฟ ฤ‘แป™ xem danh sรกch", + "manage-vpn": "Kแบฟt nแป‘i VPN", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "ฤรฃ ฤ‘แบกt sแป‘ lฦฐแปฃng tiแป‡n รญch tแป‘i ฤ‘a", + "microphone-volume-at": "ร‚m lฦฐแปฃng micro: {volume}%", + "move-to-section": "Chuyแปƒn ฤ‘แบฟn {section}", + "mute": "Tแบฏt tiแบฟng", + "next-media": "Bร i hรกt tiแบฟp theo", + "next-month": "Thรกng tแป›i", + "night-light-not-installed": "รnh sรกng ฤ‘รชm (khรดng khแบฃ dแปฅng)", + "noctalia-performance-enabled": "Chแบฟ ฤ‘แป™ hiแป‡u nฤƒng Noctalia", + "open-annotation-tool": "MแปŸ bแบฑng cรดng cแปฅ chรบ thรญch", + "open-control-center": "Trung tรขm ฤ‘iแปu khiแปƒn", + "open-notification-history-enable-dnd": "Lแป‹ch sแปญ thรดng bรกo", + "open-settings": "Cร i ฤ‘แบทt", + "open-tray-dropdown": "Khay hแป‡ thแป‘ng", + "output-muted": "Bแบญt/tแบฏt tiแบฟng ฤ‘แบงu ra", + "power-profile": "{profile} cแบฅu hรฌnh nguแป“n", + "previous-media": "Bร i hรกt trฦฐแป›c", + "previous-month": "Thรกng trฦฐแป›c", + "refresh-devices": "Lร m mแป›i thiแบฟt bแป‹", + "refresh-wallhaven": "Lร m mแป›i kแบฟt quแบฃ Wallhaven", + "refresh-wallpaper-list": "Lร m mแป›i danh sรกch hรฌnh nแปn", + "remove-widget": "Gแปก bแป tiแป‡n รญch", + "search-close": "ฤรณng tรฌm kiแบฟm", + "session-menu": "Menu Hแป‡ thแป‘ng", + "show-all-devices": "Hiแปƒn thแป‹ tแบฅt cแบฃ thiแบฟt bแป‹", + "switch-to-dark-mode": "Chแบฟ ฤ‘แป™ tแป‘i", + "switch-to-light-mode": "Chแบฟ ฤ‘แป™ sรกng", + "unmute": "Bแบญt tiแบฟng", + "up": "Thฦฐ mแปฅc cha", + "volume-at": "ร‚m lฦฐแปฃng ฤ‘แบงu ra: {volume}%", + "wallpaper-selector": "Bแป™ chแปn hรฌnh nแปn" + }, + "wallpaper": { + "browse": { + "empty-directory": "Thฦฐ mแปฅc nร y trแป‘ng.", + "go-root": "ฤi ฤ‘แบฟn thฦฐ mแปฅc gแป‘c hรฌnh nแปn", + "go-up": "ฤi tแป›i thฦฐ mแปฅc cha", + "go-up-hint": "Sแปญ dแปฅng nรบt quay lแบกi ฤ‘แปƒ trแปŸ vแป." + }, + "configure-directory": "Cแบฅu hรฌnh thฦฐ mแปฅc hรฌnh nแปn cแปงa bแบกn vแป›i cรกc hรฌnh แบฃnh.", + "fill-modes": { + "crop": "Cแบฏt (Lแบฅp ฤ‘แบงy)", + "fit": "Vแปซa (Giแปฏ nguyรชn tแปท lแป‡)", + "repeat": "Lแบทp lแบกi (Xแบฟp kแป)", + "stretch": "Kรฉo giรฃn" + }, + "no-match": "Khรดng tรฌm thแบฅy kแบฟt quแบฃ phรน hแปฃp.", + "no-wallpaper": "Khรดng tรฌm thแบฅy hรฌnh nแปn.", + "panel": { + "apikey-help": "Cแบงn cรณ khรณa API ฤ‘แปƒ truy cแบญp nแป™i dung NSFW.", + "apikey-label": "Khรณa API", + "apikey-managed-by-env": "ฤฦฐแปฃc quแบฃn lรฝ thรดng qua biแบฟn mรดi trฦฐแปng NOCTALIA_WALLHAVEN_API_KEY.", + "apikey-placeholder": "Nhแบญp Wallhaven API Key cแปงa bแบกn", + "appearance-dark-tab": "Tแป‘i", + "appearance-light-tab": "Sรกng", + "categories-anime": "Anime", + "categories-label": "Danh mแปฅc", + "categories-people": "Mแปi ngฦฐแปi", + "color-extraction-disabled": "Sแปญ dแปฅng trรญch xuแบฅt mร u hรฌnh nแปn.", + "color-extraction-enabled": "Sแปญ dแปฅng cรกc bแบฃng mร u tรญch hแปฃp sแบตn", + "header-devices-apply-all-tooltip": "Chแปn hรฌnh nแปn cho tแปซng mร n hรฌnh", + "header-devices-per-monitor-tooltip": "รp dแปฅng cho tแบฅt cแบฃ mร n hรฌnh", + "header-link-light-dark-tooltip": "Giแปฏ hรฌnh nแปn khi chuyแปƒn ฤ‘แป•i chแปง ฤ‘แป", + "header-separate-light-dark-tooltip": "Tแปฑ ฤ‘แป™ng chuyแปƒn ฤ‘แป•i hรฌnh nแปn theo chแปง ฤ‘แป", + "header-sun-linked-tooltip": "Tรกch riรชng hรฌnh nแปn sรกng vร  tแป‘i", + "header-sun-separate-tooltip": "Cรนng hรฌnh nแปn cho chแบฟ ฤ‘แป™ sรกng vร  tแป‘i", + "order-asc": "Tฤƒng dแบงn", + "order-desc": "Giแบฃm dแบงn", + "order-label": "Thแปฉ tแปฑ", + "purity-label": "Bแป™ lแปc nแป™i dung", + "purity-nsfw": "NSFW", + "purity-sfw": "SFW", + "purity-sketchy": "ฤรกng ngแป", + "ratios-all-portrait": "Tแบฅt cแบฃ chแบฟ ฤ‘แป™ dแปc", + "ratios-all-wide": "Rแป™ng toร n bแป™", + "ratios-any": "Bแบฅt kแปณ", + "ratios-label": "Tแปท lแป‡ khung hรฌnh", + "resolution-atleast": "รt nhแบฅt", + "resolution-exact": "Chรญnh xรกc", + "resolution-label": "ฤแป™ phรขn giแบฃi", + "resolution-mode-label": "Chแบฟ ฤ‘แป™", + "solid-color-tooltip": "Nแปn mร u ฤ‘ฦกn sแบฏc", + "sort-date-asc": "Sแบฏp xแบฟp theo cลฉ nhแบฅt trฦฐแป›c", + "sort-date-desc": "Sแบฏp xแบฟp theo mแป›i nhแบฅt trฦฐแป›c", + "sort-name-asc": "Sแบฏp xแบฟp theo tรชn (A-Z)", + "sort-name-desc": "Sแบฏp xแบฟp theo tรชn (Z-A)", + "sort-random": "Sแบฏp xแบฟp ngแบซu nhiรชn", + "sorting-date-added": "Ngร y thรชm", + "sorting-favorites": "Mแปฅc yรชu thรญch", + "sorting-label": "Sแบฏp xแบฟp theo", + "sorting-relevance": "Mแปฉc ฤ‘แป™ liรชn quan", + "sorting-toplist": "Top", + "sorting-views": "Cรกc chแบฟ ฤ‘แป™ xem", + "source-label": "Nguแป“n", + "source-wallhaven": "Wallhaven", + "title": "Trรฌnh chแปn Hรฌnh nแปn", + "wallhaven-settings-title": "Cร i ฤ‘แบทt Wallhaven" + }, + "transitions": { + "disc": "แป” ฤ‘ฤฉa", + "fade": "Mแป dแบงn", + "honeycomb": "Tแป• ong", + "pixelate": "Pixel hรณa", + "stripes": "Sแปc", + "wipe": "Xรณa" + }, + "try-different-search": "Thแปญ mแป™t truy vแบฅn tรฌm kiแบฟm khรกc.", + "wallhaven": { + "loading": "ฤang tแบฃi hรฌnh nแปn...", + "no-results": "Khรดng tรฌm thแบฅy hรฌnh nแปn nร o. Thแปญ mแป™t truy vแบฅn tรฌm kiแบฟm khรกc.", + "page": "{current} trรชn {total}", + "page-prefix": "Trang", + "page-suffix": "trong tแป•ng sแป‘ {total}" + } + }, + "weather": { + "clear-sky": "Trแปi quang", + "drizzle": "Mฦฐa phรนn", + "fog": "Sฦฐฦกng mรน", + "mainly-clear": "Chแปง yแบฟu quang ฤ‘รฃng", + "overcast": "U รm", + "partly-cloudy": "Trแปi nhiแปu mรขy", + "rain-showers": "Mฦฐa rร o", + "snow": "Tuyแบฟt", + "thunderstorm": "Bรฃo" + }, + "widgets": { + "color-picker": { + "palette-description": "Chแปn tแปซ nhiแปu mร u sแบฏc cรณ sแบตn.", + "palette-label": "Bแบฃng mร u", + "palette-theme-colors": "Truy cแบญp nhanh vร o bแบฃng mร u cแปงa chแปง ฤ‘แป cแปงa bแบกn.", + "title": "Bแป™ chแปn mร u" + }, + "datetime-tokens": { + "ampm-lowercase": "am/pm chแปฏ thฦฐแปng", + "ampm-uppercase": "AM/PM chแปฏ hoa", + "common-12hour-time-minutes": "Thแปi gian 12 giแป kรจm phรบt", + "common-24hour-time-minutes": "Thแปi gian 24 giแป kรจm phรบt", + "common-24hour-time-seconds": "Thแปi gian 24 giแป kรจm giรขy", + "common-european-date": "ฤแป‹nh dแบกng ngร y thรกng kiแปƒu chรขu ร‚u", + "common-iso-date": "ฤแป‹nh dแบกng ngร y ISO", + "common-us-date": "ฤแป‹nh dแบกng ngร y thรกng kiแปƒu Mแปน", + "common-weekday-date": "Thแปฉ, Ngร y", + "common-weekday-month-day": "Thแปฉ, ngร y vร  thรกng", + "day-abbreviated": "Tรชn ngร y viแบฟt tแบฏt", + "day-full": "Tรชn ngร y ฤ‘แบงy ฤ‘แปง", + "day-leading-zero": "Ngร y vแป›i sแป‘ 0 ฤ‘แปฉng ฤ‘แบงu (01-31)", + "day-no-leading-zero": "Ngร y khรดng cรณ sแป‘ 0 แปŸ ฤ‘แบงu (1-31)", + "hour-leading-zero": "Giแป cรณ sแป‘ 0 ฤ‘แปฉng ฤ‘แบงu (00-23) โ€” ฤ‘แป‹nh dแบกng 24 giแป", + "hour-no-leading-zero": "Giแป khรดng cรณ sแป‘ 0 ฤ‘แปฉng ฤ‘แบงu (0-23) โ€” ฤ‘แป‹nh dแบกng 24 giแป", + "minute-leading-zero": "Phรบt vแป›i sแป‘ 0 แปŸ ฤ‘แบงu (00-59)", + "minute-no-leading-zero": "Phรบt khรดng cรณ sแป‘ 0 ฤ‘แปฉng ฤ‘แบงu (0-59)", + "month-abbreviated": "Tรชn thรกng viแบฟt tแบฏt", + "month-full": "Tรชn thรกng ฤ‘แบงy ฤ‘แปง", + "month-number-leading-zero": "Thรกng dฦฐแป›i dแบกng sแป‘ vแป›i sแป‘ 0 แปŸ ฤ‘แบงu (01-12)", + "month-number-no-zero": "Thรกng dฦฐแป›i dแบกng sแป‘ khรดng cรณ sแป‘ 0 แปŸ ฤ‘แบงu (1-12)", + "second-leading-zero": "Giรขy vแป›i sแป‘ 0 ฤ‘แปฉng trฦฐแป›c (00-59)", + "second-no-leading-zero": "Giรขy khรดng cรณ sแป‘ 0 แปŸ ฤ‘แบงu (0-59)", + "timezone-abbreviation": "Viแบฟt tแบฏt mรบi giแป", + "year-four-digit": "Nฤƒm dฦฐแป›i dแบกng sแป‘ cรณ bแป‘n chแปฏ sแป‘", + "year-two-digit": "Nฤƒm dฦฐแป›i dแบกng sแป‘ cรณ hai chแปฏ sแป‘ (00-99)" + }, + "file-picker": { + "item": "mแปฅc", + "items": "mแปฅc", + "search-placeholder": "Tรฌm kiแบฟm tแป‡p vร  thฦฐ mแปฅc...", + "select-current": "Chแปn Hiแป‡n tแบกi", + "select-file": "Chแปn Tแป‡p", + "select-folder": "Chแปn Thฦฐ mแปฅc", + "selected": "ฤรฃ chแปn:", + "title": "Trรฌnh chแปn tแป‡p" + }, + "file-picker-title": "Chแปn mแป™t tแป‡p", + "icon-picker": { + "title": "Bแป™ chแปn biแปƒu tฦฐแปฃng" + }, + "tooltip-placeholder": "Giแปฏ chแป—" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "Danh tรญnh แบจn danh", + "ca-cert": "Chแปฉng chแป‰ CA", + "eap-method": "Phฦฐฦกng thแปฉc EAP", + "password": "Mแบญt khแบฉu ngฦฐแปi dรนng", + "phase2-auth": "Xรกc thแปฑc giai ฤ‘oแบกn 2", + "username": "Danh tรญnh / Tรชn ngฦฐแปi dรนng" + }, + "panel": { + "action-required": "Cแบงn hร nh ฤ‘แป™ng", + "add-hidden-network": "Thรชm mแบกng แบฉn", + "add-network": "Thรชm mแบกng", + "available-interfaces": "Cรกc giao diแป‡n cรณ sแบตn", + "available-networks": "Mแบกng khแบฃ dแปฅng", + "disabled": "Wiโ€‘Fi ฤ‘รฃ tแบฏt", + "disconnecting": "ฤang ngแบฏt kแบฟt nแป‘i...", + "dns": "DNS", + "enable-message": "Bแบญt Wiโ€‘Fi ฤ‘แปƒ xem cรกc mแบกng khแบฃ dแปฅng.", + "enter-password": "Nhแบญp mแบญt khแบฉu Wiโ€‘Fi...", + "forget": "Quรชn", + "forget-network": "Quรชn mแบกng nร y", + "forgetting": "ฤang quรชn...", + "hidden-network": "Mแบกng แบฉn", + "interface": "Giao diแป‡n mแบกng", + "internet-connected": "ฤรฃ kแบฟt nแป‘i Internet", + "internet-limited": "Khรดng cรณ internet", + "internet-status": "Trแบกng thรกi Internet", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "Mแบกng ฤ‘รฃ biแบฟt", + "link-speed": "Tแป‘c ฤ‘แป™ liรชn kแบฟt", + "network-name-ssid": "Tรชn mแบกng (SSID)", + "no-ethernet-devices": "Khรดng phรกt hiแป‡n thiแบฟt bแป‹ Ethernet nร o", + "no-networks": "Khรดng tรฌm thแบฅy mแบกng Wi-Fi nร o", + "saved": "ฤรฃ lฦฐu", + "scan-again": "Quรฉt lแบกi", + "searching": "ฤang tรฌm kiแบฟm mแบกng...", + "security-open": "MแปŸ", + "security-wep": "WEP", + "security-wpa": "WPA", + "security-wpa-ent": "WPA Enterprise", + "security-wpa2-ent": "WPA2 Doanh nghiแป‡p", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 Doanh nghiแป‡p", + "show-password": "Hiแป‡n mแบญt khแบฉu", + "title": "Wiโ€‘Fi" + }, + "signal": { + "excellent": "Tuyแป‡t vแปi", + "fair": "Vแปซa phแบฃi", + "good": "Tแป‘t", + "poor": "Kรฉm", + "weak": "Yแบฟu" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/zh-CN.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/zh-CN.json new file mode 100644 index 0000000..71be369 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/zh-CN.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "ๆธ…้™คๅކๅฒ่ฎฐๅฝ•", + "close-app": "ๅ…ณ้—ญ {app}", + "connect-vpn": "่ฟžๆŽฅๅˆฐ {name}", + "cycle-visualizer": "ๅˆ‡ๆขๅฏ่ง†ๅŒ–ๅ™จๆ ทๅผ", + "disable-bluetooth": "ๅ…ณ้—ญ่“็‰™", + "disable-dnd": "ๅ…ณ้—ญๅ‹ฟๆ‰ฐๆจกๅผ", + "disable-wifi": "ๅ…ณ้—ญ Wi-Fi", + "disconnect-vpn": "ๆ–ญๅผ€ไธŽ {name} ็š„่ฟžๆŽฅ", + "dock-settings": "Dock ่ฎพ็ฝฎ", + "enable-bluetooth": "ๅฏ็”จ่“็‰™", + "enable-dnd": "ๅฏ็”จๅ‹ฟๆ‰ฐๆจกๅผ", + "enable-wifi": "ๅฏ็”จ Wi-Fi", + "launcher-settings": "ๅฏๅŠจๅ™จ่ฎพ็ฝฎ", + "lower-to-bottom": "้™่‡ณๅบ•้ƒจ", + "open-calendar": "ๆ‰“ๅผ€ๆ—ฅๅކ", + "open-display-settings": "ๆ˜พ็คบ่ฎพ็ฝฎ", + "open-launcher": "ๆ‰“ๅผ€ๅฏๅŠจๅ™จ", + "open-settings": "ๆ‰“ๅผ€่ฎพ็ฝฎ", + "raise-to-top": "็ฝฎ้กถ", + "random-wallpaper": "้šๆœบๅฃ็บธ", + "run-custom-command": "่ฟ่กŒ่‡ชๅฎšไน‰ๅ‘ฝไปค", + "show-active-only": "ไป…ๆ˜พ็คบๆดปๅŠจ้กน", + "show-all": "ๆ˜พ็คบๅ…จ้ƒจ", + "toggle-mute": "ๅˆ‡ๆข้™้Ÿณ", + "widget-settings": "ๅฐ้ƒจไปถ่ฎพ็ฝฎ" + }, + "authentication": { + "error": "่ฎค่ฏ้”™่ฏฏ", + "failed": "่ฎค่ฏๅคฑ่ดฅ" + }, + "bar": { + "active-window": { + "colorize-icons-description": "ๅฐ†ไธป้ข˜้ขœ่‰ฒๅบ”็”จๅˆฐๆดปๅŠจ็ช—ๅฃๅ›พๆ ‡ใ€‚", + "hide-mode-description": "ๆŽงๅˆถๅฝ“ๆฒกๆœ‰ๆดปๅŠจ็ช—ๅฃๆ—ถๅฐ้ƒจไปถ็š„่กŒไธบใ€‚", + "scrolling-mode-description": "ๆŽงๅˆถไฝ•ๆ—ถไธบ้•ฟ็ช—ๅฃๆ ‡้ข˜ๅฏ็”จๆ–‡ๆœฌๆปšๅŠจใ€‚", + "show-app-icon-description": "ๅœจ็ช—ๅฃๆ ‡้ข˜ๆ—่พนๆ˜พ็คบๅบ”็”จ็จ‹ๅบๅ›พๆ ‡ใ€‚", + "show-app-icon-label": "ๆ˜พ็คบๅบ”็”จๅ›พๆ ‡", + "show-app-text-description": "ๆ˜พ็คบๆดปๅŠจ็ช—ๅฃ็š„ๆ ‡้ข˜ใ€‚", + "show-app-text-label": "ๆ˜พ็คบ็ช—ๅฃๆ ‡้ข˜" + }, + "audio-visualizer": { + "color-name-description": "้€‰ๆ‹ฉๅฏ่ง†ๅŒ–ๅทฅๅ…ท็š„้ขœ่‰ฒใ€‚", + "color-name-label": "ๅกซๅ……้ขœ่‰ฒ", + "height-description": "่‡ชๅฎšไน‰็ป„ไปถๅฎฝๅบฆใ€‚", + "hide-when-idle-description": "ๅฏ็”จๅŽ๏ผŒ้™ค้žๆญฃๅœจๆ’ญๆ”พๅช’ไฝ“๏ผŒๅฆๅˆ™้š่—ๅฏ่ง†ๅŒ–ๆ˜พ็คบใ€‚", + "hide-when-idle-label": "ๆ— ๅช’ไฝ“ๆ’ญๆ”พๆ—ถ้š่—", + "width-description": "่‡ชๅฎšไน‰็ป„ไปถ็š„ๅฎฝๅบฆใ€‚" + }, + "battery": { + "device-default": "้ป˜่ฎค๏ผˆๆ˜พ็คบ่ฎพๅค‡๏ผ‰", + "device-description": "้€‰ๆ‹ฉ่ฆๆ˜พ็คบ็š„็”ตๆฑ ่ฎพๅค‡ใ€‚", + "device-label": "็”ตๆฑ ่ฎพๅค‡", + "display-mode-description": "้€‰ๆ‹ฉ็”ตๆฑ ๅœจไปปๅŠกๆ ไธญ็š„ๆ˜พ็คบๆ–นๅผใ€‚", + "display-mode-graphic": "ๅ›พๅฝขๅŒ–็”ตๆฑ ", + "display-mode-graphic-clean": "ๅ›พๅฝข็”ตๆฑ ๏ผˆๆ— %๏ผ‰", + "display-mode-icon-always": "ๅ›พๆ ‡ - ๅง‹็ปˆๆ˜พ็คบ %", + "display-mode-icon-hover": "ๅ›พๆ ‡ - ๆ‚ฌๅœๆ—ถๆ˜พ็คบ", + "display-mode-icon-only": "ไป…ๅ›พๆ ‡", + "hide-if-idle-description": "ๅฝ“็”ตๆฑ ็ฉบ้—ฒๆ—ถ๏ผˆๆฒกๆœ‰ๅ……็”ตๆˆ–่€…ๆ”พ็”ต็”ตๆตๆ—ถ๏ผ‰๏ผŒ้š่—ๅฐ้ƒจไปถใ€‚", + "hide-if-idle-label": "็ฉบ้—ฒๆ—ถ้š่—", + "hide-if-not-detected-description": "ๅฝ“็ณป็ปŸๆœชๆฃ€ๆต‹ๅˆฐ็”ตๆฑ ๆ—ถ๏ผŒ้š่—่ฏฅๅฐ้ƒจไปถใ€‚", + "hide-if-not-detected-label": "ๆœชๆฃ€ๆต‹ๅˆฐ็”ตๆฑ ๆ—ถ๏ผŒ้š่—่ฏฅๅฐ้ƒจไปถ", + "low-battery-threshold-label": "ไฝŽ็”ต้‡่ญฆๅ‘Š้˜ˆๅ€ผ", + "show-noctalia-performance-description": "ๅœจ็”ตๆฑ ้ขๆฟไธญๆ˜พ็คบ Noctalia ๆ€ง่ƒฝๆจกๅผๅผ€ๅ…ณใ€‚
่ฏฅๆจกๅผ้€š่ฟ‡็ฆ็”จ Noctalia ไธญ็š„้˜ดๅฝฑๅ’ŒๅŠจ็”ปๅ‡ๅฐ‘ๆ€ง่ƒฝๅผ€้”€ใ€‚", + "show-noctalia-performance-label": "ๆ˜พ็คบ Noctalia ๆ€ง่ƒฝๆจกๅผๅผ€ๅ…ณ", + "show-power-profile-description": "ๅœจ็”ตๆฑ ้ขๆฟไธญๆ˜พ็คบ็”ตๆบ้…็ฝฎๆ–‡ไปถ้€‰้กนใ€‚", + "show-power-profile-label": "ๆ˜พ็คบ็”ตๆบ้…็ฝฎๆ–‡ไปถๆŽงไปถ" + }, + "brightness": { + "apply-all-description": "ไฝฟ็”จๆปš่ฝฎๆ—ถๆ›ดๆ”นๆ‰€ๆœ‰ๆ˜พ็คบๅ™จ็š„ไบฎๅบฆใ€‚", + "apply-all-label": "ๅฐ†ๆปšๅŠจๆ›ดๆ”นๅบ”็”จๅˆฐๆ‰€ๆœ‰ๆ˜พ็คบๅ™จ" + }, + "clock": { + "clock-display-description": "้€š่ฟ‡ไปŽไธ‹้ข็š„ๅˆ—่กจๆทปๅŠ ๆ ‡่ฎฐๆฅ่‡ชๅฎšไน‰ๆ—ถ้’Ÿๆ ทๅผใ€‚่ฆไฝฟ็”จ 12 ๅฐๆ—ถๅˆถ๏ผŒๅˆ™ๅฟ…้กปๅŒ…ๅซ 'AP' ๆ ‡่ฎฐใ€‚", + "clock-display-label": "ๆ—ถ้’Ÿๆ˜พ็คบ", + "custom-font-description": "่‡ชๅฎšไน‰ๆ—ถ้’Ÿไฝฟ็”จ็š„ๅญ—ไฝ“ใ€‚", + "custom-font-label": "่‡ชๅฎšไน‰ๅญ—ไฝ“", + "custom-font-placeholder": "้€‰ๆ‹ฉ่‡ชๅฎšไน‰ๅญ—ไฝ“...", + "custom-font-search-placeholder": "ๆœ็ดขๅญ—ไฝ“...", + "horizontal-bar-description": "ๆ็คบ๏ผšไฝฟ็”จ \\n ๅˆ›ๅปบๆข่กŒใ€‚", + "horizontal-bar-label": "ๆฐดๅนณๆ ", + "preview": "้ข„่งˆ", + "tooltip-format-description": "่‡ชๅฎšไน‰้ผ ๆ ‡ๆ‚ฌๅœๅœจๆ—ถ้’ŸไธŠๆ—ถๆ˜พ็คบ็š„ๆ็คบไฟกๆฏ๏ผŒๅฏไปฅไฝฟ็”จๆ ผๅผๅŒ–ๅญ—็ฌฆไธฒใ€‚็•™็ฉบไปฅไฝฟ็”จ้ป˜่ฎคๆ็คบไฟกๆฏใ€‚", + "tooltip-format-label": "ๆ็คบไฟกๆฏๆ ผๅผ", + "use-custom-font-description": "ไฝฟ็”จ่‡ชๅฎšไน‰ๅญ—ไฝ“่ฆ†็›–ๆ—ถ้’Ÿ็š„้ป˜่ฎคๅญ—ไฝ“้€‰ๆ‹ฉใ€‚", + "use-custom-font-label": "ไฝฟ็”จ่‡ชๅฎšไน‰ๅญ—ไฝ“", + "use-monospaced-font-description": "ๅฏ็”จๅŽ๏ผŒๆ—ถ้’Ÿๅฐ†ไฝฟ็”จ็ญ‰ๅฎฝๅญ—ไฝ“ใ€‚", + "use-monospaced-font-label": "ไฝฟ็”จ็ญ‰ๅฎฝๅญ—ไฝ“", + "vertical-bar-description": "ไฝฟ็”จ็ฉบๆ ผๅฐ†ๆฏไธช้ƒจๅˆ†ๅˆ†้š”ๅˆฐๆ–ฐ่กŒใ€‚", + "vertical-bar-label": "ๅž‚็›ดๆ " + }, + "control-center": { + "browse-file": "ๆต่งˆๆ–‡ไปถ", + "browse-library": "ๆต่งˆๅบ“", + "color-selection-description": "ๅฐ†ไธป้ข˜้ขœ่‰ฒๅบ”็”จไบŽๅ›พๆ ‡ใ€‚", + "enable-colorization-description": "ไธบๅ›พๆ ‡ๅฏ็”จ็€่‰ฒ๏ผŒๅบ”็”จไธป้ข˜้ขœ่‰ฒใ€‚", + "icon-description": "ไปŽๅบ“ไธญ้€‰ๆ‹ฉๅ›พๆ ‡ๆˆ–่‡ชๅฎšไน‰ๆ–‡ไปถใ€‚", + "select-custom-icon": "้€‰ๆ‹ฉ่‡ชๅฎšไน‰ๅ›พๆ ‡", + "use-distro-logo-description": "ไฝฟ็”จๆ‚จ็š„ๅ‘่กŒ็‰ˆๅพฝๆ ‡่€Œไธๆ˜ฏ่‡ชๅฎšไน‰ๅ›พๆ ‡ใ€‚", + "use-distro-logo-label": "ไฝฟ็”จๅ‘่กŒ็‰ˆๅพฝๆ ‡่€Œไธๆ˜ฏๅ›พๆ ‡" + }, + "custom-button": { + "collapse-condition-description": "ๅฆ‚ๆžœ่พ“ๅ‡บๆ–‡ๆœฌไธŽๆญคๅ€ผๅŒน้…๏ผŒๆŒ‰้’ฎๅฐ†ๆŠ˜ๅ ใ€‚", + "collapse-condition-label": "ๆŠ˜ๅ ๆกไปถ", + "color-selection-description": "ๅฐ†ไธป้ข˜้ขœ่‰ฒๅบ”็”จๅˆฐๅ›พๆ ‡ๅ’Œๆ–‡ๆœฌใ€‚", + "default-tooltip": "่‡ชๅฎšไน‰ๆŒ‰้’ฎ๏ผŒๅœจ่ฎพ็ฝฎไธญ้…็ฝฎ", + "display-command-output-description": "่พ“ๅ…ฅไธ€ไธชๅฎšๆœŸ่ฟ่กŒ็š„ๅ‘ฝไปคใ€‚ๅ…ถ่พ“ๅ‡บ็š„็ฌฌไธ€่กŒๅฐ†ๆ˜พ็คบไธบๆ–‡ๆœฌใ€‚", + "display-command-output-label": "ๆ˜พ็คบๅ‘ฝไปค่พ“ๅ‡บ", + "display-command-output-stream-description": "่พ“ๅ…ฅไธ€ไธช่ฆๆŒ็ปญ่ฟ่กŒ็š„ๅ‘ฝไปคใ€‚", + "dynamic-text": "ๅŠจๆ€ๆ–‡ๆœฌ", + "enable-colorization-description": "ๅฏ็”จ่‡ชๅฎšไน‰ๆŒ‰้’ฎๅ›พๆ ‡ๅ’Œๆ–‡ๆœฌ็š„็€่‰ฒ๏ผŒๅบ”็”จไธป้ข˜้ขœ่‰ฒใ€‚", + "enable-colorization-label": "ๅฏ็”จ็€่‰ฒ", + "general-tooltip-text-description": "ๅœจๆŒ‰้’ฎๅทฅๅ…ทๆ็คบไธญๆ˜พ็คบ็š„่‡ชๅฎšไน‰ๆ–‡ๆœฌใ€‚", + "general-tooltip-text-label": "่‡ชๅฎšไน‰ๅทฅๅ…ทๆ็คบๆ–‡ๆœฌ", + "hide-mode-always-expanded": "ๅง‹็ปˆๅฑ•ๅผ€", + "hide-mode-description": "ๆŽงๅˆถๅ‘ฝไปคๆ— ่พ“ๅ‡บๆ—ถๅฐ้ƒจไปถ็š„ๅฏ่งๆ€งใ€‚", + "hide-mode-expand-with-output": "ๆœ‰่พ“ๅ‡บๆ—ถๅฑ•ๅผ€", + "hide-mode-label": "้š่—ๆจกๅผ", + "hide-mode-max-transparent": "ๆœ€ๅคงๅฑ•ๅผ€ไฝ†้€ๆ˜Ž", + "icon-description": "ไปŽๅบ“ไธญ้€‰ๆ‹ฉๅ›พๆ ‡ใ€‚", + "ipc-identifier-description": "IPC ๅ‘ฝไปค็š„ๅ”ฏไธ€ๆ ‡่ฏ†็ฌฆใ€‚ไฝฟ็”จๆญคๆ ‡่ฏ†็ฌฆไปฅๅŠโ€œqs -c noctalia-shell ipc call cb [ๆ“ไฝœ] [ๆ ‡่ฏ†็ฌฆ]โ€้€š่ฟ‡ IPC ๆŽงๅˆถๆญคๆŒ‰้’ฎใ€‚", + "ipc-identifier-label": "IPCๆ ‡่ฏ†็ฌฆ", + "left-click-description": "ๅทฆ้”ฎๅ•ๅ‡ปๆŒ‰้’ฎๆ—ถๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚", + "left-click-label": "ๅทฆ้”ฎๅ•ๅ‡ป", + "left-click-update-text": "ๅทฆ้”ฎๅ•ๅ‡ปๆ—ถๆ›ดๆ–ฐๆ˜พ็คบ็š„ๆ–‡ๆœฌ", + "max-text-length-horizontal-description": "ๅœจๆฐดๅนณๆ ไธญๆ˜พ็คบ็š„ๆœ€ๅคงๅญ—็ฌฆๆ•ฐ๏ผˆ0 ไธบ้š่—ๆ–‡ๆœฌ๏ผ‰ใ€‚", + "max-text-length-horizontal-label": "ๆœ€ๅคงๆ–‡ๆœฌ้•ฟๅบฆ๏ผˆๆฐดๅนณ๏ผ‰", + "max-text-length-vertical-description": "ๅœจๅž‚็›ดๆ ไธญๆ˜พ็คบ็š„ๆœ€ๅคงๅญ—็ฌฆๆ•ฐ๏ผˆ0 ไธบ้š่—ๆ–‡ๆœฌ๏ผ‰ใ€‚", + "max-text-length-vertical-label": "ๆœ€ๅคงๆ–‡ๆœฌ้•ฟๅบฆ๏ผˆๅž‚็›ด๏ผ‰", + "middle-click-description": "ไธญ้”ฎๅ•ๅ‡ปๆŒ‰้’ฎๆ—ถๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚", + "middle-click-label": "ไธญ้”ฎๅ•ๅ‡ป", + "middle-click-update-text": "ไธญ้”ฎๅ•ๅ‡ปๆ—ถๆ›ดๆ–ฐๆ˜พ็คบ็š„ๆ–‡ๆœฌ", + "parse-json-description": "ๅฐ†ๅ‘ฝไปค่พ“ๅ‡บ่งฃๆžไธบ JSON ๅฏน่ฑก๏ผŒไปฅๅŠจๆ€่ฎพ็ฝฎๆ–‡ๆœฌๅ’Œๅ›พๆ ‡ใ€‚", + "parse-json-label": "ๅฐ†่พ“ๅ‡บ่งฃๆžไธบ JSON", + "refresh-interval-description": "้—ด้š”ๆ—ถ้—ด๏ผˆๆฏซ็ง’๏ผ‰ใ€‚", + "refresh-interval-label": "ๅˆทๆ–ฐ้—ด้š”", + "right-click-description": "ๅณ้”ฎๅ•ๅ‡ปๆŒ‰้’ฎๆ—ถๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚", + "right-click-label": "ๅณ้”ฎๅ•ๅ‡ป", + "right-click-update-text": "ๅณ้”ฎๅ•ๅ‡ปๆ—ถๆ›ดๆ–ฐๆ˜พ็คบ็š„ๆ–‡ๆœฌ", + "show-exec-tooltip-description": "ๆ˜พ็คบๅธฆๆœ‰ๅ‘ฝไปค่ฏฆๆƒ…็š„ๅทฅๅ…ทๆ็คบ (ๅทฆ้”ฎ/ๅณ้”ฎ/ไธญ้”ฎ็‚นๅ‡ป๏ผŒๆปš่ฝฎ)ใ€‚", + "show-exec-tooltip-label": "ๆ˜พ็คบๅ‘ฝไปคๅทฅๅ…ทๆ็คบ", + "show-icon-description": "ๅˆ‡ๆขๅฐ้ƒจไปถๅ›พๆ ‡็š„ๅฏ่งๆ€งใ€‚", + "show-icon-label": "ๆ˜พ็คบๅ›พๆ ‡", + "show-text-tooltip-description": "ๆ˜พ็คบๅธฆๆœ‰ๆ–‡ๆœฌๅ‘ฝไปค่พ“ๅ‡บ็š„ๅทฅๅ…ทๆ็คบใ€‚", + "show-text-tooltip-label": "ๆ˜พ็คบๅŠจๆ€ๆ–‡ๆœฌๅทฅๅ…ทๆ็คบ", + "text-stream-description": "ๆฅ่‡ชๅ‘ฝไปค็š„ๆตๅผ่พ“ๅ‡บ่กŒๅฐ†ไฝœไธบๆ–‡ๆœฌๆ˜พ็คบๅœจๆŒ‰้’ฎไธŠใ€‚", + "text-stream-label": "ๆต", + "wheel-description": "ไฝฟ็”จๆปš่ฝฎๆ—ถๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚
ๅœจๅ‘ฝไปคไธญไฝฟ็”จ $delta ่กจ็คบๆปš่ฝฎๅขž้‡ใ€‚", + "wheel-down": "ๆปš่ฝฎ ไธ‹", + "wheel-down-description": "ๆปš่ฝฎๅ‘ไธ‹ๆปšๅŠจๆ—ถๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚", + "wheel-down-label": "ๆปš่ฝฎๅ‘ไธ‹ๅ‘ฝไปค", + "wheel-label": "ๆปš่ฝฎ", + "wheel-mode-separate-description": "ไธบๆปš่ฝฎๅ‘ไธŠๅ’Œๅ‘ไธ‹ๅฏ็”จๅ•็‹ฌ็š„ๅ‘ฝไปคใ€‚", + "wheel-mode-separate-label": "ๅˆ†ๅผ€ๆปš่ฝฎๅ‘ฝไปค", + "wheel-up": "ๆปš่ฝฎ ไธŠ", + "wheel-up-description": "ๆปš่ฝฎๅ‘ไธŠๆปšๅŠจๆ—ถๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚", + "wheel-up-label": "ๆปš่ฝฎๅ‘ไธŠๅ‘ฝไปค", + "wheel-update-text": "ๆปš่ฝฎๆปšๅŠจๆ—ถๆ›ดๆ–ฐๆ˜พ็คบ็š„ๆ–‡ๆœฌ" + }, + "keyboard-layout": { + "show-icon-description": "ๆ˜พ็คบ้”ฎ็›˜ๅธƒๅฑ€ๅ›พๆ ‡ใ€‚" + }, + "lock-keys": { + "hide-when-off-description": "ๅฝ“ๆŒ‰้”ฎไธๆดป่ทƒๆ—ถ้š่—ๆŒ‡็คบๅ™จใ€‚", + "hide-when-off-label": "ๅ…ณ้—ญๆ—ถ้š่—", + "show-caps-lock-description": "ๆ˜พ็คบๅคงๅ†™้”ๅฎš็Šถๆ€ใ€‚", + "show-caps-lock-label": "ๅคงๅ†™้”ๅฎš", + "show-num-lock-description": "ๆ˜พ็คบๆ•ฐๅญ—้”ๅฎš้”ฎ็Šถๆ€ใ€‚", + "show-num-lock-label": "ๆ•ฐๅญ—้”ๅฎš", + "show-scroll-lock-description": "ๆ˜พ็คบๆปšๅŠจ้”ๅฎš็Šถๆ€ใ€‚", + "show-scroll-lock-label": "ๆปšๅŠจ้”ๅฎš" + }, + "media-mini": { + "compact-mode-description": "ๅฏ็”จๅช’ไฝ“ๆ’ญๆ”พๅ™จ้ขๆฟ็š„่Š‚็œ็ฉบ้—ดๅธƒๅฑ€ใ€‚", + "compact-mode-label": "็ดงๅ‡‘ๆจกๅผ", + "hide-mode-description": "ๆŽงๅˆถๅฝ“ๆฒกๆœ‰ๅช’ไฝ“ๆ’ญๆ”พๆ—ถๅฐ้ƒจไปถ็š„่กŒไธบใ€‚", + "max-width-description": "่ฎพ็ฝฎๅฐ้ƒจไปถ็š„ๆœ€ๅคงๆฐดๅนณๅฐบๅฏธใ€‚ๅฝ“ๅ†…ๅฎน่พƒ็Ÿญๆ—ถ๏ผŒๅฐ้ƒจไปถไผš่‡ชๅŠจๆ”ถ็ผฉไปฅ้€‚ๅบ”ๅ†…ๅฎนใ€‚", + "no-active-player": "ๆ— ๆดปๅŠจๆ’ญๆ”พๅ™จ", + "panel-section-description": "้…็ฝฎๅช’ไฝ“ๆ’ญๆ”พๅ™จ้ขๆฟ็š„ๅค–่ง‚ๅ’Œ่กŒไธบใ€‚", + "panel-section-label": "ๅช’ไฝ“ๆ’ญๆ”พๅ™จ้ขๆฟ", + "scrolling-mode-description": "ๆŽงๅˆถไฝ•ๆ—ถไธบ้•ฟๆ›ฒ็›ฎๆ ‡้ข˜ๅฏ็”จๆ–‡ๆœฌๆปšๅŠจใ€‚", + "scrolling-mode-label": "ๆปšๅŠจๆจกๅผ", + "show-album-art-description": "ๆ˜พ็คบๅฝ“ๅ‰ๆ’ญๆ”พๆ›ฒ็›ฎ็š„ไธ“่พ‘ๅฐ้ขใ€‚", + "show-album-art-label": "ๆ˜พ็คบไธ“่พ‘ๅฐ้ข", + "show-artist-first-description": "ๆ˜พ็คบ่‰บๆœฏๅฎถ - ๆ ‡้ข˜๏ผŒ่€Œไธๆ˜ฏๆ ‡้ข˜ - ่‰บๆœฏๅฎถใ€‚", + "show-artist-first-label": "ๅ…ˆๆ˜พ็คบ่‰บๆœฏๅฎถ", + "show-progress-ring-description": "ๆ˜พ็คบๆ˜พ็คบๆ›ฒ็›ฎ่ฟ›ๅบฆ็š„ๅœ†ๅฝข่ฟ›ๅบฆๆŒ‡็คบๅ™จใ€‚", + "show-progress-ring-label": "ๆ˜พ็คบ่ฟ›ๅบฆ็Žฏ", + "show-visualizer-description": "ๆ’ญๆ”พ้Ÿณไนๆ—ถๆ˜พ็คบ้Ÿณ้ข‘ๅฏ่ง†ๅŒ–ๅ™จใ€‚", + "show-visualizer-label": "ๆ˜พ็คบๅฏ่ง†ๅŒ–ๅ™จ", + "use-fixed-width-description": "ๅฏ็”จๅŽ๏ผŒๅฐ้ƒจไปถๅฐ†ๅง‹็ปˆไฝฟ็”จๆœ€ๅคงๅฎฝๅบฆ๏ผŒ่€Œไธๆ นๆฎๅ†…ๅฎนๅŠจๆ€่ฐƒๆ•ดใ€‚", + "use-fixed-width-label": "ไฝฟ็”จๅ›บๅฎšๅฎฝๅบฆ", + "visualizer-type-description": "้€‰ๆ‹ฉ่ฆๆ˜พ็คบ็š„้Ÿณ้ข‘ๅฏ่ง†ๅŒ–ๅ™จๆ ทๅผใ€‚", + "visualizer-type-label": "ๅฏ่ง†ๅŒ–ๅ™จ็ฑปๅž‹" + }, + "notification-history": { + "hide-widget-when-zero-description": "ๆฒกๆœ‰้€š็Ÿฅๆ—ถ้š่—้€š็Ÿฅๅ›พๆ ‡ใ€‚", + "hide-widget-when-zero-label": "ๆ— ้€š็Ÿฅๆ—ถ้š่—ๅ›พๆ ‡", + "hide-widget-when-zero-unread-description": "ๆฒกๆœ‰ๆœช่ฏป้€š็Ÿฅๆ—ถ้š่—้€š็Ÿฅๅ›พๆ ‡ใ€‚", + "hide-widget-when-zero-unread-label": "ๆ— ๆœช่ฏปๆ—ถ้š่—ๅ›พๆ ‡", + "show-unread-badge-description": "ๆ˜พ็คบไธ€ไธช็”จไบŽๅฑ•็คบๆœช่ฏป้€š็Ÿฅๆ•ฐ้‡็š„ๅพฝ็ซ ใ€‚", + "show-unread-badge-label": "ๆ˜พ็คบๆœช่ฏปๅพฝ็ซ ", + "unread-badge-color-description": "้€‰ๆ‹ฉๆœช่ฏป้€š็Ÿฅๅพฝ็ซ ็š„้ขœ่‰ฒใ€‚", + "unread-badge-color-label": "ๆœช่ฏปๅพฝ็ซ ้ขœ่‰ฒ" + }, + "section-editor": { + "placeholder": "้€‰ๆ‹ฉ่ฆๆทปๅŠ ็š„ๅฐ้ƒจไปถ...", + "search-placeholder": "ๆœ็ดขๅฐ้ƒจไปถ..." + }, + "spacer": { + "width-description": "้—ด่ทๅฎฝๅบฆ๏ผˆๅƒ็ด ๏ผ‰ใ€‚" + }, + "system-monitor": { + "compact-mode-description": "ๅฐ†ๆ•ฐๆฎไปฅๆกๅฝขๅ›พๆ˜พ็คบไปฅ้˜ฒๆญขๅธƒๅฑ€ๅ็งปใ€‚", + "compact-mode-label": "็ดงๅ‡‘ๆจกๅผ", + "cpu-cores-description": "ๅ•็‹ฌๆ˜พ็คบ CPU ๆ ธๅฟƒไฝฟ็”จ็އใ€‚", + "cpu-cores-label": "CPU ๆ ธๅฟƒ", + "cpu-frequency-description": "ๆ˜พ็คบๅฝ“ๅ‰ CPU ๆ—ถ้’Ÿ้€Ÿๅบฆ๏ผˆGHz๏ผ‰ใ€‚", + "cpu-frequency-label": "ๆ˜พ็คบ CPU ้ข‘็އ", + "cpu-temperature-description": "ๆ˜พ็คบ CPU ๆธฉๅบฆ๏ผˆๅฆ‚ๆžœๅฏ็”จ๏ผ‰ใ€‚", + "cpu-temperature-label": "CPU ๆธฉๅบฆ", + "cpu-usage-description": "ไปฅ็™พๅˆ†ๆฏ”ๆ˜พ็คบๅฝ“ๅ‰ CPU ไฝฟ็”จ็އ", + "cpu-usage-label": "CPU ไฝฟ็”จ็އ", + "disk-path-description": "้€‰ๆ‹ฉ่ฆ็›‘ๆŽง็š„็ฃ็›˜ๆŒ‚่ฝฝ็‚นใ€‚", + "disk-path-label": "็ฃ็›˜่ทฏๅพ„", + "gpu-temperature-description": "ๆ˜พ็คบ GPU ๆธฉๅบฆ๏ผˆๅฆ‚ๆžœๅฏ็”จ๏ผ‰ใ€‚", + "load-average-description": "ๆ˜พ็คบ็ณป็ปŸๅนณๅ‡่ดŸ่ฝฝใ€‚", + "load-average-label": "ๅนณๅ‡่ดŸ่ฝฝ", + "memory-percentage-description": "ไปฅ็™พๅˆ†ๆฏ”๏ผˆ่€Œ้ž็ปๅฏนๅ€ผ๏ผ‰ๆ˜พ็คบๅ†…ๅญ˜็”จ้‡ใ€‚", + "memory-percentage-label": "ๅ†…ๅญ˜็™พๅˆ†ๆฏ”", + "memory-usage-description": "ๆ˜พ็คบๅฝ“ๅ‰ๅ†…ๅญ˜็”จ้‡ไฟกๆฏใ€‚", + "memory-usage-label": "ๅ†…ๅญ˜ไฝฟ็”จ็އ", + "network-traffic-description": "ๆ˜พ็คบ็ฝ‘็ปœไธŠไผ ๅ’Œไธ‹่ฝฝ้€Ÿๅบฆใ€‚", + "network-traffic-label": "็ฝ‘็ปœๆต้‡", + "storage-as-percentage-description": "ไปฅ็™พๅˆ†ๆฏ”่€Œ้ž็ปๅฏนๅ€ผๆ˜พ็คบ็ฃ็›˜็ฉบ้—ดใ€‚", + "storage-as-percentage-label": "็ฃ็›˜็™พๅˆ†ๆฏ”", + "storage-available-description": "ๆ˜พ็คบๅฏ็”จ็ฃ็›˜็ฉบ้—ด๏ผŒ่€Œ้žๅทฒ็”จ็ฉบ้—ดใ€‚", + "storage-available-label": "ๅฏ็”จ็ฃ็›˜็ฉบ้—ด", + "storage-usage-description": "ๆ˜พ็คบ็ฃ็›˜็ฉบ้—ดไฝฟ็”จๆƒ…ๅ†ตใ€‚", + "storage-usage-label": "ๅญ˜ๅ‚จ็”จ้‡", + "swap-usage-description": "ๆ˜พ็คบ Swap ไฝฟ็”จๆƒ…ๅ†ตใ€‚", + "swap-usage-label": "Swap", + "use-monospace-font-description": "ไฝฟ็”จ็ญ‰ๅฎฝๅญ—ไฝ“ไปฅไฟๆŒๅญ—็ฌฆๅฎฝๅบฆไธ€่‡ดใ€‚", + "use-monospace-font-label": "็ญ‰ๅฎฝๅญ—ไฝ“", + "use-padding-description": "็”จๅ‰ๅฏผ็ฉบๆ ผๅกซๅ……ๆ–‡ๆœฌๅ€ผ๏ผŒไปฅ้˜ฒๆญขๅธƒๅฑ€ๅ็งปใ€‚", + "use-padding-description-disabled-monospace-font": "ๆญคๅŠŸ่ƒฝ้œ€่ฆ็ญ‰ๅฎฝๅญ—ไฝ“ใ€‚", + "use-padding-description-disabled-vertical": "ๅž‚็›ดไปปๅŠกๆ ไธๆ”ฏๆŒๆ–‡ๆœฌๅ†…่พน่ทใ€‚", + "use-padding-label": "ๆ–‡ๆœฌๅกซๅ……" + }, + "taskbar": { + "colorize-icons-description": "ๅฐ†ไธป้ข˜้ขœ่‰ฒๅบ”็”จๅˆฐไปปๅŠกๆ ๅ›พๆ ‡ใ€‚", + "hide-mode-description": "ๆŽงๅˆถๆ— ๅŒน้…็ช—ๅฃๆ—ถๅฐ้ƒจไปถ็š„่กŒไธบใ€‚", + "hide-mode-label": "้š่—ๆจกๅผ", + "icon-scale-description": "่ฎพ็ฝฎไปปๅŠกๆ ๅ›พๆ ‡็š„็ผฉๆ”พๆฏ”ไพ‹ใ€‚", + "icon-scale-label": "ๅ›พๆ ‡็ผฉๆ”พ", + "max-width-description": "ไปปๅŠกๆ ็š„ๆœ€ๅคงๅฎฝๅบฆ๏ผˆๅ ๅฑๅน•ๅฎฝๅบฆ็š„็™พๅˆ†ๆฏ”๏ผ‰ใ€‚", + "max-width-label": "ๆœ€ๅคงๅฎฝๅบฆ", + "only-active-workspaces-description": "ไป…ๆ˜พ็คบๆฅ่‡ชๆดปๅŠจๅทฅไฝœๅŒบ็š„ๅบ”็”จ็จ‹ๅบใ€‚", + "only-active-workspaces-label": "ไป…ๆ˜พ็คบๆดปๅŠจๅทฅไฝœๅŒบ", + "only-same-monitor-description": "ไป…ๆ˜พ็คบไปปๅŠกๆ ๆ‰€ๅœจๆ˜พ็คบๅ™จไธŠ็š„ๅบ”็”จใ€‚", + "only-same-monitor-label": "ไป…้™ๅŒไธ€ๆ˜พ็คบๅ™จ", + "show-pinned-apps-description": "ๅœจไปปๅŠกๆ ไธญๆ˜พ็คบ Dock ๆ ไธŠๅ›บๅฎš็š„ๅบ”็”จใ€‚", + "show-pinned-apps-label": "ๆ˜พ็คบๅทฒ็ฝฎ้กถ็š„ๅบ”็”จ", + "show-title-description": "ๅœจไปปๅŠกๆ ไธญๆ˜พ็คบ็ช—ๅฃๆ ‡้ข˜ใ€‚", + "show-title-description-disabled": "ๅž‚็›ดไปปๅŠกๆ ไธๆ”ฏๆŒๆ˜พ็คบๆ ‡้ข˜ใ€‚", + "show-title-label": "ๆ˜พ็คบๆ ‡้ข˜", + "smart-width-description": "ๆ นๆฎๆก็›ฎๆ•ฐ้‡่‡ชๅŠจ่ฐƒๆ•ดๆก็›ฎๅฎฝๅบฆใ€‚", + "smart-width-label": "ๆ™บ่ƒฝๅฎฝๅบฆ", + "title-width-description": "่ฎพ็ฝฎไปปๅŠกๆ ไธญ็ช—ๅฃๆ ‡้ข˜็š„ๅฎฝๅบฆ๏ผˆไปฅๅƒ็ด ไธบๅ•ไฝ๏ผ‰ใ€‚", + "title-width-label": "ๆ ‡้ข˜ๅฎฝๅบฆ", + "title-width-reset-tooltip": "้‡็ฝฎๆ ‡้ข˜ๅฎฝๅบฆ" + }, + "tray": { + "chevron-color-description": "ๅฐ†ไธป้ข˜้ขœ่‰ฒๅบ”็”จไบŽๆŠฝๅฑ‰็š„็ฎญๅคดๅ›พๆ ‡ใ€‚", + "chevron-color-label": "็ฎญๅคด้ขœ่‰ฒ", + "colorize-icons-description": "ๅฐ†ไธป้ข˜้ขœ่‰ฒๅบ”็”จๅˆฐ็ณป็ปŸๆ‰˜็›˜ๅ›พๆ ‡ใ€‚", + "colorize-icons-label": "็€่‰ฒๅ›พๆ ‡", + "drawer-enabled-description": "ๅฏ็”จๆ—ถ๏ผŒๆœชๅ›บๅฎš็š„ๆ‰˜็›˜้กนๆ˜พ็คบๅœจๆŠฝๅฑ‰้ขๆฟไธญใ€‚
็ฆ็”จๆ—ถ๏ผŒๆ‰€ๆœ‰ๆ‰˜็›˜้กน้ƒฝๅ†…่”ๆ˜พ็คบใ€‚", + "drawer-enabled-label": "ๅฏ็”จๆŠฝๅฑ‰", + "hide-passive-description": "ๅฏ็”จๅŽ๏ผŒๅฐ†้š่—็Šถๆ€ไธบโ€œ่ขซๅŠจโ€็š„ๆ‰˜็›˜ๅ›พๆ ‡ใ€‚", + "hide-passive-label": "้š่—่ขซๅŠจ็‰ฉๅ“" + }, + "volume": { + "display-mode-description": "้€‰ๆ‹ฉๆ‚จๅธŒๆœ›ๆญคๅ€ผๆ˜พ็คบ็š„ๆ–นๅผใ€‚" + }, + "workspace": { + "character-count-description": "ๆ˜พ็คบๅทฅไฝœๅŒบๅ็งฐ็š„ๅญ—็ฌฆๆ•ฐ้‡๏ผˆ1-10๏ผ‰ใ€‚", + "character-count-label": "ๅญ—็ฌฆๆ•ฐ้‡", + "empty-color-description": "่ฎพ็ฝฎ็ฉบๅทฅไฝœๅŒบ็š„่ƒŒๆ™ฏ้ขœ่‰ฒใ€‚", + "empty-color-label": "็ฉบ็™ฝๅทฅไฝœๅŒบ้ขœ่‰ฒ", + "enable-scrollwheel-description": "ไฝฟ็”จ้ผ ๆ ‡ๆปš่ฝฎๅˆ‡ๆขๅทฅไฝœๅŒบใ€‚", + "enable-scrollwheel-label": "ๆปšๅŠจๅˆ‡ๆขๅทฅไฝœๅŒบ", + "focused-color-description": "่ฎพ็ฝฎ็„ฆ็‚น Workspace ็š„่ƒŒๆ™ฏ้ขœ่‰ฒใ€‚", + "focused-color-label": "่š็„ฆๅทฅไฝœๅŒบ้ขœ่‰ฒ", + "follow-focused-screen-description": "ๆ˜พ็คบๅฝ“ๅ‰็„ฆ็‚นๅฑๅน•็š„ๅทฅไฝœๅŒบ๏ผŒ่€Œไธๆ˜ฏไปปๅŠกๆ ๆ‰€ๅœจๅฑๅน•็š„ๅทฅไฝœๅŒบใ€‚", + "follow-focused-screen-label": "่ทŸ้š็„ฆ็‚นๅฑๅน•", + "font-weight-description": "่ฎพ็ฝฎๅทฅไฝœๅŒบๅ†…ๆ–‡ๆœฌ็š„่ง†่ง‰ๆƒ้‡ใ€‚", + "font-weight-label": "ๅญ—ไฝ“็ฒ—็ป†", + "grouped-border-opacity-description": "่ฎพ็ฝฎๅทฅไฝœๅŒบๅฎนๅ™จ่พนๆก†็š„ไธ้€ๆ˜Žๅบฆ็บงๅˆซใ€‚", + "grouped-border-opacity-label": "่พนๆก†ไธ้€ๆ˜Žๅบฆ", + "hide-unoccupied-description": "ไธๆ˜พ็คบๆฒกๆœ‰็ช—ๅฃ็š„ๅทฅไฝœๅŒบใ€‚", + "hide-unoccupied-label": "้š่—ๆœชๅ ็”จ", + "label-mode-description": "้€‰ๆ‹ฉๅทฅไฝœๅŒบๆ ‡็ญพ็š„ๆ˜พ็คบๆ–นๅผใ€‚", + "label-mode-label": "ๆ ‡็ญพๆจกๅผ", + "occupied-color-description": "่ฎพ็ฝฎๅทฒๅ ็”จWorkspace็š„่ƒŒๆ™ฏ้ขœ่‰ฒใ€‚", + "occupied-color-label": "ๅทฒๅ ็”จๅทฅไฝœๅŒบ้ขœ่‰ฒ", + "pill-size-description": "่ฐƒๆ•ดๅทฅไฝœๅŒบๆŒ‡็คบๅ™จ็š„ๅคงๅฐ (50%-100%)ใ€‚", + "pill-size-label": "่ƒถๅ›Šๅคงๅฐ", + "reverse-scrolling-description": "ๆปšๅŠจๆ—ถๅ่ฝฌๅทฅไฝœๅŒบๅˆ‡ๆขๆ–นๅ‘ใ€‚", + "reverse-scrolling-label": "ๅ่ฝฌๆปšๅŠจ", + "show-applications-description": "ๅœจๆฏไธชๅทฅไฝœๅŒบๅ†…ๆ˜พ็คบๅบ”็”จ็จ‹ๅบๅ›พๆ ‡ใ€‚", + "show-applications-hover-description": "ๅฝ“้ผ ๆ ‡ๆ‚ฌๅœๅœจๅทฅไฝœๅŒบไธŠๆ—ถ๏ผŒๅœจๆฏไธชๅทฅไฝœๅŒบๅ†…ๆ˜พ็คบๅบ”็”จ็จ‹ๅบๅ›พๆ ‡ใ€‚", + "show-applications-hover-label": "้ผ ๆ ‡ๆ‚ฌๅœๆ—ถๆ˜พ็คบๅบ”็”จ็จ‹ๅบ", + "show-applications-label": "ๆ˜พ็คบๅบ”็”จ็จ‹ๅบ", + "show-badge-description": "ๅœจๅˆ†็ป„ๆจกๅผไธ‹ๆ˜พ็คบๅทฅไฝœๅŒบ็ผ–ๅทๅพฝ็ซ ใ€‚", + "show-badge-label": "ๆ˜พ็คบๅทฅไฝœๅŒบๅพฝ็ซ ", + "show-labels-only-when-occupied-description": "ไป…ๅœจๅทฅไฝœๅŒบๅŒ…ๅซ็ช—ๅฃๆ—ถๆ˜พ็คบๅทฅไฝœๅŒบๆ ‡็ญพใ€‚", + "show-labels-only-when-occupied-label": "ไป…ๅœจ่ขซๅ ็”จๆ—ถๆ˜พ็คบๆ ‡็ญพ", + "unfocused-icons-opacity-description": "่ฎพ็ฝฎๆœช่š็„ฆๅบ”็”จๅ›พๆ ‡็š„ไธ้€ๆ˜Žๅบฆ็บงๅˆซใ€‚", + "unfocused-icons-opacity-label": "ๆœช่š็„ฆๅ›พๆ ‡ไธ้€ๆ˜Žๅบฆ" + } + }, + "battery": { + "all-batteries": "ๆ‰€ๆœ‰็”ตๆฑ ๏ผˆ็ป„ๅˆ๏ผ‰", + "battery-health": "็”ตๆฑ ๅฅๅบท", + "battery-level": "็”ตๆฑ ็”ต้‡", + "capacity-level": "ๅฎน้‡๏ผš{level}", + "charging-rate": "ๅ……็”ต้€Ÿ็އ๏ผš{rate} W", + "discharging-rate": "ๆ”พ็”ต้€Ÿ็އ๏ผš{rate} W", + "health": "็”ตๆฑ ๅฅๅบท๏ผš{percent}%", + "inhibit-idle-description": "ไฟๆŒ็ณป็ปŸๅ”ค้†’ใ€‚", + "no-battery-detected": "ๆœชๆฃ€ๆต‹ๅˆฐ็”ตๆฑ ", + "plugged-in": "ๅทฒๆŽฅ้€š็”ตๆบ", + "power-profile": "็”ตๆบๆจกๅผ", + "time-left": "็ปญ่ˆชๅ‰ฉไฝ™๏ผš{time}", + "time-until-full": "ๅ……ๆปกๆ‰€้œ€ๆ—ถ้—ด๏ผš{time}" + }, + "bluetooth": { + "panel": { + "available-devices": "ๅฏ็”จ่ฎพๅค‡", + "blocked": "ๅทฒ้˜ปๆญข", + "connected-devices": "ๅทฒ่ฟžๆŽฅ่ฎพๅค‡", + "device-address": "่ฎพๅค‡ๅœฐๅ€", + "disabled": "่“็‰™ๅทฒ็ฆ็”จ", + "discoverable": "ๅฏๅ‘็Žฐ็š„", + "enable-message": "ๅฏ็”จ่“็‰™ไปฅๆŸฅ็œ‹ๅฏ็”จ่ฎพๅค‡ใ€‚", + "known-devices": "ๅทฒ็Ÿฅ่ฎพๅค‡", + "no-devices": "ๆฒกๆœ‰ๅฏ็”จ่ฎพๅค‡", + "paired-devices": "ๅทฒ้…ๅฏน็š„่ฎพๅค‡", + "pairing-mode": "็กฎไฟๆ‚จ็š„่ฎพๅค‡ๅค„ไบŽ้…ๅฏนๆจกๅผใ€‚", + "scanning": "ๆญฃๅœจๆ‰ซๆ่ฎพๅค‡...", + "signal-text-excellent": "ไฟกๅท๏ผšๆžไฝณ", + "signal-text-fair": "ไฟกๅท๏ผš่‰ฏๅฅฝ", + "signal-text-good": "ไฟกๅท๏ผš่‰ฏๅฅฝ", + "signal-text-poor": "ไฟกๅท๏ผšๅผฑ", + "signal-text-unknown": "ไฟกๅท๏ผšๆœช็Ÿฅ", + "signal-text-very-poor": "ไฟกๅท๏ผš้žๅธธๅทฎ" + } + }, + "changelog": { + "error": { + "fetch-failed": "ๆ— ๆณ•ๅŠ ่ฝฝๆ›ดๆ–ฐๆ—ฅๅฟ—ๆ•ฐๆฎ๏ผŒ่ฏท็จๅŽๅ†่ฏ•ใ€‚", + "rate-limit": "ๅทฒ่พพๅˆฐ GitHub ้€Ÿ็އ้™ๅˆถ๏ผŒ่ฏท็จๅŽๅ†่ฏ•ใ€‚" + }, + "panel": { + "buttons-discord": "ๅŠ ๅ…ฅๆˆ‘ไปฌ็š„ Discord", + "buttons-dismiss": "็กฎๅฎš", + "buttons-feedback": "ๆไพ›ๅ้ฆˆ", + "empty": "ๆš‚ๆ—ถๆฒกๆœ‰ๅฏ็”จ็š„ๅ‘่กŒ่ฏดๆ˜Žใ€‚", + "highlight-title": "้‡็‚นๆ›ดๆ–ฐ", + "released": "{date} ๅ‘ๅธƒ", + "subtitle-fresh": "ๆ„Ÿ่ฐขๅฎ‰่ฃ… Noctalia๏ผไปฅไธ‹ๆ˜ฏๆœฌๆฌกๆž„ๅปบๅŒ…ๅซ็š„ๅ†…ๅฎนใ€‚", + "subtitle-updated": "ๅทฒไปŽ {previousVersion} ๆ›ดๆ–ฐ", + "title": "{version} ๆœ‰ๅ“ชไบ›ๆ›ดๆ–ฐ", + "version": "็‰ˆๆœฌ {version}", + "version-new-user": "ๅ…จๆ–ฐๅฎ‰่ฃ…" + } + }, + "common": { + "actions": "ๆ“ไฝœ", + "active": "ๆดป่ทƒ", + "add": "ๆทปๅŠ ", + "appearance": "ๅค–่ง‚", + "apply": "ๅบ”็”จ", + "auto-connect": "่‡ชๅŠจ่ฟžๆŽฅ", + "automation": "่‡ชๅŠจๅŒ–", + "available": "ๅฏ็”จ", + "back": "่ฟ”ๅ›ž", + "battery": "็”ตๆฑ ", + "behavior": "่กŒไธบ", + "bluetooth": "่“็‰™", + "brightness": "ไบฎๅบฆ", + "browse": "ๆต่งˆ", + "calendar": "ๆ—ฅๅކ", + "calendar-panel": "ๆ—ฅๅކ้ขๆฟ", + "cancel": "ๅ–ๆถˆ", + "cards": "ๅก็‰‡", + "charging": "ๅ……็”ต", + "clear": "ๆธ…้™ค", + "clipboard": "ๅ‰ช่ดดๆฟ", + "close": "ๅ…ณ้—ญ", + "color-muted": "ๅทฒ้™้Ÿณ", + "colors": "้ขœ่‰ฒ", + "command": "ๅ‘ฝไปค", + "confirm": "็กฎ่ฎค", + "connect": "่ฟžๆŽฅ", + "connected": "ๅทฒ่ฟžๆŽฅ", + "connecting": "ๆญฃๅœจ่ฟžๆŽฅ...", + "continue": "็ปง็ปญ", + "contributors": "่ดก็Œฎ่€…", + "copied-to-clipboard": "ๅทฒๅคๅˆถๅˆฐๅ‰ช่ดดๆฟ", + "countdown": "ๅ€’่ฎกๆ—ถ", + "customize": "่‡ชๅฎšไน‰", + "date": "ๆ—ฅๆœŸ", + "default": "้ป˜่ฎค", + "delete": "ๅˆ ้™ค", + "devices": "่ฎพๅค‡", + "disabled": "ๅทฒ็ฆ็”จ", + "discharging": "ๆ”พ็”ต", + "disconnect": "ๆ–ญๅผ€่ฟžๆŽฅ", + "disconnected": "ๅทฒๆ–ญๅผ€่ฟžๆŽฅ", + "disconnecting": "ๆญฃๅœจๆ–ญๅผ€่ฟžๆŽฅ...", + "display-mode": "ๆ˜พ็คบๆจกๅผ", + "documentation": "ๆ–‡ๆกฃ", + "download": "ไธ‹่ฝฝ", + "duration": "ๆ—ถ้•ฟ", + "dysfunctional": "ๅŠŸ่ƒฝๅคฑ่ฐƒ", + "edit": "็ผ–่พ‘", + "enabled": "ๅทฒๅฏ็”จ", + "error": "้”™่ฏฏ", + "ethernet": "ไปฅๅคช็ฝ‘", + "events": "ไบ‹ไปถ", + "execute": "ๆ‰ง่กŒ", + "faithful": "ๅฟ ๅฎž", + "focus": "ไธ“ๆณจ", + "font-weight-bold": "็ฒ—ไฝ“", + "font-weight-light": "็ป†", + "font-weight-medium": "ไธญ", + "font-weight-regular": "ๅธธ่ง„", + "font-weight-semibold": "ๅŠ็ฒ—ไฝ“", + "frequency": "้ข‘็އ", + "gateway": "็ฝ‘ๅ…ณ", + "general": "้€š็”จ", + "height": "้ซ˜ๅบฆ", + "hibernate": "ไผ‘็œ ", + "history": "ๅކๅฒ", + "icon": "ๅ›พๆ ‡", + "idle": "็ฉบ้—ฒ", + "info": "ไฟกๆฏ", + "input": "่พ“ๅ…ฅ", + "install": "ๅฎ‰่ฃ…", + "installed": "ๅทฒๅฎ‰่ฃ…", + "interface": "็•Œ้ข", + "internet": "ไบ’่”็ฝ‘", + "keybind": "ๆŒ‰้”ฎ็ป‘ๅฎš", + "language": "่ฏญ่จ€", + "loading": "ๆญฃๅœจๅŠ ่ฝฝ...", + "local": "ๆœฌๅœฐ", + "location": "ไฝ็ฝฎ", + "lock": "้”ๅฑ", + "logout": "้€€ๅ‡บ็™ปๅฝ•", + "look": "ๆ ทๅผ", + "margins": "่พน่ท", + "media": "ๅช’ไฝ“", + "media-player": "ๅช’ไฝ“ๆ’ญๆ”พๅ™จ", + "memory": "ๅ†…ๅญ˜", + "monitors": "ๆ˜พ็คบๅ™จ", + "move-down": "ไธ‹็งป", + "move-up": "ไธŠ็งป", + "network": "็ฝ‘็ปœ", + "next": "ไธ‹ไธ€ไธช", + "night-light": "ๅคœ้—ดๆจกๅผ", + "no": "ๅฆ", + "no-results": "ๆฒกๆœ‰็ป“ๆžœ", + "no-summary": "ๆ— ๆ‘˜่ฆ", + "none": "ๆ— ", + "not-found": "ๆœชๆ‰พๅˆฐ", + "notifications": "้€š็Ÿฅ", + "official": "ๅฎ˜ๆ–น", + "on-surface": "ๅœจ่กจ้ขไธŠ", + "output": "่พ“ๅ‡บ", + "pair": "้…ๅฏน", + "paired": "ๅทฒ้…ๅฏน", + "pairing": "ๆญฃๅœจ้…ๅฏน...", + "panels": "้ขๆฟ", + "password": "ๅฏ†็ ", + "pause": "ๆš‚ๅœ", + "performance": "ๆ€ง่ƒฝ", + "pin": "ๅ›บๅฎš", + "play": "ๆ’ญๆ”พ", + "polling": "ๆ•ฐๆฎ่ฝฎ่ฏข", + "position": "ไฝ็ฝฎ", + "previous": "ไธŠไธ€ไธช", + "primary": "ไธป่ฆ็š„", + "random": "้šๆœบ", + "reboot": "้‡ๅฏ", + "reboot-to-uefi": "้‡ๅฏๅˆฐ UEFI", + "record": "ๅผ€ๅง‹ๅฝ•ๅˆถ", + "refresh": "ๅˆทๆ–ฐ", + "required": "(ๅฟ…่ฆ)", + "reset": "้‡็ฝฎ", + "reset-to-default": "้‡็ฝฎไธบ้ป˜่ฎคๅ€ผ", + "result-count": "{count} ็ป“ๆžœ", + "result-count-plural": "{count} ็ป“ๆžœ", + "resume": "็ปง็ปญ", + "save": "ไฟๅญ˜", + "scanning": "ๆ‰ซๆไธญ...", + "screen-corners": "ๅฑๅน•่พน่ง’", + "search": "ๆœ็ดข", + "second": "{count} ็ง’", + "second-plural": "{count} ็ง’", + "secondary": "่พ…ๅŠฉ", + "security": "ๅฎ‰ๅ…จ", + "select": "้€‰ๆ‹ฉ", + "select-color": "้€‰ๆ‹ฉ้ขœ่‰ฒ", + "select-color-description": "ๅบ”็”จไธป้ข˜้ขœ่‰ฒไปฅ็คบๅผบ่ฐƒใ€‚", + "select-icon-color": "้€‰ๆ‹ฉๅ›พๆ ‡้ขœ่‰ฒ", + "settings": "่ฎพ็ฝฎ", + "shortcuts": "ๅฟซๆทๆ–นๅผ", + "shutdown": "ๅ…ณๆœบ", + "signal": "ไฟกๅท", + "sound": "ๅฃฐ้Ÿณ", + "sources": "ๆฅๆบ", + "start": "ๅผ€ๅง‹", + "stop": "ๅœๆญข", + "supporters": "ๆ”ฏๆŒ่€…", + "suspend": "ๆŒ‚่ตท", + "templates": "ๆจกๆฟ", + "tertiary": "็ฌฌไธ‰", + "test": "ๆต‹่ฏ•", + "thresholds": "้˜ˆๅ€ผ", + "title": "ๆ ‡้ข˜", + "toast": "Toast ๆถˆๆฏ", + "trusted": "ๅฏไฟก่ต–็š„", + "uninstall": "ๅธ่ฝฝ", + "unknown": "ๆœช็Ÿฅ", + "unpair": "ๅ–ๆถˆ้…ๅฏน", + "unpin": "ๅ–ๆถˆๅ›บๅฎš", + "update": "ๆ›ดๆ–ฐ", + "upload": "ไธŠไผ ", + "username": "็”จๆˆทๅ", + "userspace-reboot": "้‡ๅฏ็”จๆˆท็ฉบ้—ด", + "version": "็‰ˆๆœฌ", + "vibrant": "้ฒœ่‰ณ", + "visualizer": "ๅฏ่ง†ๅŒ–ๅทฅๅ…ท", + "volume": "้Ÿณ้‡", + "volumes": "้Ÿณ้‡", + "wallpaper": "ๅฃ็บธ", + "weather": "ๅคฉๆฐ”", + "weather-loading": "ๆญฃๅœจๅŠ ่ฝฝๅคฉๆฐ”...", + "weather-no-location": "ๅœจ่ฎพ็ฝฎไธญ่ฎพ็ฝฎไฝ็ฝฎไปฅๆŸฅ็œ‹ๅคฉๆฐ”", + "week": "ๅ‘จ", + "widgets": "ๅฐ้ƒจไปถ", + "width": "ๅฎฝๅบฆ", + "wifi": "Wi-Fi", + "windows": "็ช—ๅฃ", + "yes": "ๆ˜ฏ" + }, + "control-center": { + "power-profile": { + "tooltip-action": "็”ตๆบๆจกๅผ", + "tooltip-disabled": "ๅฎ‰่ฃ… power-profiles-daemon ไปฅไฝฟ็”จ็”ตๆบๆจกๅผ" + }, + "wifi": { + "label-disconnected": "Wi-Fi ๅทฒๆ–ญๅผ€", + "label-ethernet": "ไปฅๅคช็ฝ‘" + } + }, + "display-modes": { + "always-hide": "ๅง‹็ปˆ้š่—", + "always-show": "ๆ€ปๆ˜ฏๆ˜พ็คบ", + "force-open": "ๅผบ่กŒๆ‰“ๅผ€", + "on-hover": "ๆ‚ฌๅœๆ—ถ" + }, + "hide-modes": { + "auto-hide": "่‡ชๅŠจ้š่—", + "hidden": "ๅฝ“ไธบ็ฉบๆ—ถ้š่—", + "idle": "็ฉบ้—ฒๆ—ถ้š่—", + "non-exclusive": "้ž็‹ฌๅ ", + "transparent": "็ฉบๆ—ถ้€ๆ˜Ž", + "visible": "ๅง‹็ปˆๅฏ่ง" + }, + "launcher": { + "categories": { + "all": "ๅ…จ้ƒจ", + "audiovideo": "้Ÿณ้ข‘ๅ’Œ่ง†้ข‘", + "chat": "่Šๅคฉ", + "development": "ๅผ€ๅ‘", + "education": "ๆ•™่‚ฒ", + "emoji-activity": "ๆดปๅŠจ", + "emoji-animals": "ๅŠจ็‰ฉ", + "emoji-flags": "ๆ——ๅธœ", + "emoji-food": "้ฃŸ็‰ฉๅ’Œ้ฅฎๆ–™", + "emoji-nature": "่‡ช็„ถ", + "emoji-objects": "็‰ฉๅ“", + "emoji-people": "ไบบ็‰ฉๅ’Œ่‚ขไฝ“", + "emoji-recent": "ๆœ€่ฟ‘ไฝฟ็”จ", + "emoji-symbols": "็ฌฆๅท", + "emoji-travel": "ๆ—…่กŒๅ’Œๅœฐ็‚น", + "game": "ๆธธๆˆ", + "graphics": "ๅ›พๅฝข", + "misc": "ๆ‚้กน", + "office": "ๅŠžๅ…ฌ", + "pinned": "ๅทฒๅ›บๅฎš", + "system": "็ณป็ปŸ", + "webbrowser": "็ฝ‘้กตๆต่งˆๅ™จ" + }, + "providers": { + "applications": "ๅบ”็”จ็จ‹ๅบ", + "calculator": "่ฎก็ฎ—ๅ™จ", + "calculator-deprecated": ">calc ๅ‘ฝไปคๅทฒๅผƒ็”จ๏ผŒๅณๅฐ†็งป้™คใ€‚่ฏท็›ดๆŽฅๅœจๆœ็ดขไธญ่พ“ๅ…ฅๆ•ฐๅญฆ่กจ่พพๅผใ€‚", + "calculator-description": "่ฎก็ฎ—ๅ™จ โ€” ่ฎก็ฎ—ๆ•ฐๅญฆ่กจ่พพๅผ", + "calculator-enter-expression": "่พ“ๅ…ฅๆ•ฐๅญฆ่กจ่พพๅผ", + "calculator-press-enter-to-copy": "ๆŒ‰ Enter ้”ฎๅคๅˆถ็ป“ๆžœ", + "clipboard": "ๅ‰ช่ดดๆฟๅކๅฒ่ฎฐๅฝ•", + "clipboard-clear-description": "ๆธ…้™คๆ‰€ๆœ‰ๅ‰ช่ดดๆฟๅކๅฒ่ฎฐๅฝ•", + "clipboard-clear-description-full": "ไปŽๅ‰ช่ดดๆฟๅކๅฒ่ฎฐๅฝ•ไธญ็งป้™คๆ‰€ๆœ‰้กน็›ฎ", + "clipboard-clear-history": "ๆธ…้™คๅ‰ช่ดดๆฟๅކๅฒ่ฎฐๅฝ•", + "clipboard-delete": "ๅˆ ้™คๅ‰ช่ดดๆฟๆก็›ฎ", + "clipboard-history-disabled": "ๅ‰ช่ดดๆฟๅކๅฒ่ฎฐๅฝ•ๅทฒ็ฆ็”จ", + "clipboard-history-disabled-description": "ๅœจ่ฎพ็ฝฎไธญๅฏ็”จๅ‰ช่ดดๆฟๅކๅฒ่ฎฐๅฝ•ๆˆ–ๅฎ‰่ฃ… cliphist", + "clipboard-loading": "ๆญฃๅœจๅŠ ่ฝฝๅ‰ช่ดดๆฟๅކๅฒ่ฎฐๅฝ•...", + "clipboard-search-description": "ๆœ็ดขๅ‰ช่ดดๆฟๅކๅฒ่ฎฐๅฝ•", + "command-description": "่ฟ่กŒ shell ๅ‘ฝไปค", + "emoji": "่กจๆƒ…็ฌฆๅท้€‰ๆ‹ฉๅ™จ", + "emoji-loading": "ๆญฃๅœจๅŠ ่ฝฝ่กจๆƒ…็ฌฆๅท...", + "emoji-loading-description": "่ฏท็จๅ€™", + "emoji-no-recent": "ๅฐšๆ— ๆœ€่ฟ‘ไฝฟ็”จ็š„่กจๆƒ…็ฌฆๅท", + "emoji-search-description": "ๆœ็ดขๅ’Œๅคๅˆถ่กจๆƒ…็ฌฆๅท", + "settings-search-description": "ๆœ็ดขๅนถๅ‰ๅพ€่ฎพ็ฝฎ", + "windows-search-description": "ๆœ็ดขๅนถ่š็„ฆๆ‰“ๅผ€็š„็ช—ๅฃ" + } + }, + "lock-screen": { + "authenticating": "ๆญฃๅœจ้ชŒ่ฏ...", + "password": "่ฏท่พ“ๅ…ฅๅฏ†็ ...", + "restart": "้‡ๅฏ", + "shut-down": "ๅ…ณๆœบ" + }, + "notifications": { + "panel": { + "click-to-collapse": "็‚นๅ‡ปๆŠ˜ๅ ", + "click-to-expand": "็‚นๅ‡ปๅฑ•ๅผ€", + "description": "ๆ‚จ็š„้€š็Ÿฅๅฐ†ๅœจๅˆฐ่พพๆ—ถๆ˜พ็คบๅœจๆญคๅค„ใ€‚", + "no-notifications": "ๆ— ้€š็Ÿฅ" + }, + "range": { + "earlier": "ๆ›ดๆ—ฉ", + "today": "ไปŠๅคฉ", + "yesterday": "ๆ˜จๅคฉ" + }, + "time": { + "diff-d": "1 ๅคฉๅ‰", + "diff-dd": "{diff} ๅคฉๅ‰", + "diff-h": "1 ๅฐๆ—ถๅ‰", + "diff-hh": "{diff} ๅฐๆ—ถๅ‰", + "diff-m": "1 ๅˆ†้’Ÿๅ‰", + "diff-mm": "{diff} ๅˆ†้’Ÿๅ‰", + "now": "็Žฐๅœจ" + } + }, + "options": { + "bar": { + "density-comfortable": "่ˆ’้€‚", + "density-compact": "็ดงๅ‡‘", + "density-default": "้ป˜่ฎค", + "density-mini": "่ฟทไฝ ", + "density-spacious": "ๅฎฝๆ•ž", + "display-mode-always-visible": "ๅง‹็ปˆๅฏ่ง", + "type-floating": "ๆ‚ฌๆตฎ", + "type-framed": "่พนๆก†", + "type-simple": "็ฎ€็บฆ" + }, + "control-center": { + "quick-settings-style-classic": "็ปๅ…ธ", + "quick-settings-style-modern": "็Žฐไปฃ" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "่ˆ’้€‚", + "compact": "็ดงๅ‡‘", + "default": "้ป˜่ฎค" + }, + "launcher-view-mode": { + "grid": "็ฝ‘ๆ ผ", + "list": "ๅˆ—่กจ" + }, + "notification-density": { + "compact": "็ดงๅ‡‘", + "default": "้ป˜่ฎค" + }, + "scrolling-modes": { + "always": "ๅง‹็ปˆๆปšๅŠจ", + "hover": "ๆ‚ฌๅœๆ—ถๆปšๅŠจ", + "never": "ไปŽไธๆปšๅŠจ" + }, + "session-menu-grid-layout": { + "grid": "็ฝ‘ๆ ผ", + "single-row": "ๅ•่กŒ" + }, + "settings-panel-mode": { + "attached": "้™„ๅฑžไบŽ็Šถๆ€ๆ ๆ˜พ็คบ", + "centered": "ๅฑ…ไธญๅฑๅน•ๆ˜พ็คบ", + "window": "ๆ˜พ็คบไธบ็‹ฌ็ซ‹็ช—ๅฃ" + }, + "visualizer-types": { + "linear": "็บฟๆ€ง", + "mirrored": "้•œๅƒ", + "wave": "ๆณขๅฝข" + }, + "visualizer-visibility": { + "with-background": "ไป…ๅœจๆœ‰่ƒŒๆ™ฏๆ—ถ" + }, + "workspace-labels": { + "index": "็ดขๅผ•", + "index-and-name": "็ดขๅผ•ๅ’Œๅ็งฐ", + "name": "ๅ็งฐ" + } + }, + "panels": { + "about": { + "become-supporter": "ๆˆไธบๆ”ฏๆŒ่€…", + "changelog": "ๆŸฅ็œ‹ๆ›ดๆ”นๆ—ฅๅฟ—", + "changelog-on-startup": "ๆ›ดๆ–ฐๆ—ถๆ˜พ็คบๆ›ดๆ–ฐๆ—ฅๅฟ—", + "changelog-on-startup-desc": "Noctalia ๆ›ดๆ–ฐๆ—ถ่‡ชๅŠจๆ˜พ็คบๆ›ดๆ–ฐๆ—ฅๅฟ—ใ€‚", + "contributors-description": "ๅ‘ๆˆ‘ไปฌ {count} ไฝ่ถ…ๆฃ’็š„่ดก็Œฎ่€…่‡ดๆ•ฌ๏ผ", + "contributors-description-plural": "ๅ‘ๆˆ‘ไปฌ {count} ไฝ่ถ…ๆฃ’็š„่ดก็Œฎ่€…่‡ดๆ•ฌ๏ผ", + "copy-info": "ๅคๅˆถไฟกๆฏ", + "debug-disabled": "่ฐƒ่ฏ•ๆจกๅผๅทฒ็ฆ็”จ", + "debug-enabled": "่ฐƒ่ฏ•ๆจกๅผๅทฒๅฏ็”จ", + "info-copied": "ไฟกๆฏๅทฒๅคๅˆถๅˆฐๅ‰ช่ดดๆฟ", + "noctalia-available": "ๅฏ็”จ:", + "noctalia-desc": "ไธ€ๆฌพไธบ Wayland ็ฒพๅฟƒๆ‰“้€ ็š„ๆ—ถๅฐš็ฎ€็บฆๆกŒ้ข shell๏ผŒๅŸบไบŽ Quickshell ๆž„ๅปบใ€‚", + "noctalia-git-commit": "Git ๆไบค:", + "noctalia-installed-version": "ๅทฒๅฎ‰่ฃ…็‰ˆๆœฌ๏ผš", + "noctalia-latest-version": "ๆœ€ๆ–ฐ็‰ˆๆœฌ๏ผš", + "noctalia-qs-version": "Noctalia QS ็‰ˆๆœฌ:", + "noctalia-title": "Noctalia shell", + "privacy-policy": "้š็งๆ”ฟ็ญ–", + "support": "ๆ”ฏๆŒๆˆ‘ไปฌ", + "supporter-badge": "ๆ”ฏๆŒ่€…", + "supporters-desc": "้žๅธธๆ„Ÿ่ฐขๆˆ‘ไปฌๅ‡บ่‰ฒ็š„ๆ”ฏๆŒ่€…๏ผ", + "supporters-desc-plural": "้žๅธธๆ„Ÿ่ฐขๆˆ‘ไปฌ็š„{count}ไฝๅ‡บ่‰ฒๆ”ฏๆŒ่€…๏ผ", + "supporters-loading": "ๆญฃๅœจๅŠ ่ฝฝๆ”ฏๆŒ่€…...", + "system-board": "ไธปๆฟ:", + "system-cpu": "ไธญๅคฎๅค„็†ๅ™จ๏ผš", + "system-disk": "็ฃ็›˜๏ผš", + "system-gpu": "ๅ›พๅฝขๅค„็†ๅ™จ๏ผš", + "system-host": "่ฎพๅค‡ๅž‹ๅท๏ผš", + "system-install-hint": "ๅฎ‰่ฃ… fastfetch ไปฅๆŸฅ็œ‹็ณป็ปŸไฟกๆฏ", + "system-kernel": "ๅ†…ๆ ธ๏ผš", + "system-loading": "ๆญฃๅœจๅŠ ่ฝฝ็ณป็ปŸไฟกๆฏ...", + "system-memory": "ๅ†…ๅญ˜๏ผš", + "system-monitor": "็›‘่ง†ๅ™จ๏ผš", + "system-not-installed": "ๆœชๅฎ‰่ฃ… fastfetch", + "system-os": "ๆ“ไฝœ็ณป็ปŸ๏ผš", + "system-packages": "่ฝฏไปถๅŒ…๏ผš", + "system-product": "ไบงๅ“๏ผš", + "system-title": "็ณป็ปŸไฟกๆฏ", + "system-uptime": "ๆญฃๅธธ่ฟ่กŒๆ—ถ้—ด๏ผš", + "system-wm": "็ช—ๅฃ็ฎก็†ๅ™จ๏ผš", + "telemetry-data-copied": "้ฅๆต‹ๆ•ฐๆฎๅทฒๅคๅˆถๅˆฐๅ‰ช่ดดๆฟ", + "telemetry-desc": "้€š่ฟ‡ๅˆ†ไบซๅŒฟๅ็ณป็ปŸไฟกๆฏ๏ผˆๅฑๅน•ๅˆ†่พจ็އใ€ๅˆๆˆๅ™จใ€ๅ‘่กŒ็‰ˆ๏ผ‰ๆฅๅธฎๅŠฉๆ”น่ฟ› Noctaliaใ€‚ ๅฏๅŠจๆ—ถๅ‘้€ไธ€ๆฌก๏ผŒไธ่ฟ›่กŒ่ทŸ่ธช๏ผŒๆ•ฐๆฎไผšๅœจ 30 ๅคฉๅŽ่‡ชๅŠจๅˆ ้™คใ€‚", + "telemetry-enabled": "ๅ‘้€ๅŒฟๅ็ณป็ปŸไฟกๆฏ", + "telemetry-show-data": "ๆŸฅ็œ‹ๆ•ฐๆฎ", + "telemetry-title": "้š็ง", + "title": "ๅ…ณไบŽ", + "up-to-date": "ๆ‚จๅทฒๆ˜ฏๆœ€ๆ–ฐ็‰ˆๆœฌ๏ผ", + "update-available": "ๆœ‰ๅฏ็”จๆ›ดๆ–ฐ", + "view-commit": "ๅœจ GitHub ไธŠๆŸฅ็œ‹ๆไบค" + }, + "audio": { + "devices-desc": "้…็ฝฎๅฏ็”จ็š„้Ÿณ้ข‘่พ“ๅ…ฅๅ’Œ่พ“ๅ‡บ่ฎพๅค‡ใ€‚", + "devices-input-device-description": "้€‰ๆ‹ฉๆ‰€้œ€็š„้Ÿณ้ข‘่พ“ๅ…ฅ่ฎพๅค‡ใ€‚", + "devices-input-device-label": "่พ“ๅ…ฅ่ฎพๅค‡", + "devices-output-device-description": "้€‰ๆ‹ฉๆ‰€้œ€็š„้Ÿณ้ข‘่พ“ๅ‡บ่ฎพๅค‡ใ€‚", + "devices-output-device-label": "่พ“ๅ‡บ่ฎพๅค‡", + "devices-title": "้Ÿณ้ข‘่ฎพๅค‡", + "external-mixer-description": "่พ“ๅ…ฅๆฟ€ๆดปๅค–้ƒจ้Ÿณ้ข‘ๆทท้Ÿณๅ™จๅŠŸ่ƒฝๆ—ถ่ฆๅฏๅŠจ็š„ๅ‘ฝไปคๆˆ–ๅบ”็”จ็จ‹ๅบ่ทฏๅพ„ใ€‚", + "external-mixer-label": "ๅค–้ƒจ้Ÿณ้ข‘ๆทท้Ÿณๅ™จๅ‘ฝไปค", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "่ฎพ็ฝฎๆ‚จ้ฆ–้€‰ๅ’Œ่ฆๅฟฝ็•ฅ็š„ๅช’ไฝ“ๅบ”็”จ็จ‹ๅบใ€‚", + "media-excluded-player-description": "ๆทปๅŠ ๆ‚จๅธŒๆœ›็ณป็ปŸๅฟฝ็•ฅ็š„ๆ’ญๆ”พๅ™จๅ…ณ้”ฎ่ฏใ€‚ๆฏไธชๅ…ณ้”ฎ่ฏๅบ”ๅ•็‹ฌๅ ไธ€่กŒใ€‚", + "media-excluded-player-label": "ๆŽ’้™ค็š„ๆ’ญๆ”พๅ™จ", + "media-excluded-player-placeholder": "่พ“ๅ…ฅๅ…ณ้”ฎ่ฏๅนถๆŒ‰ + ๆทปๅŠ ", + "media-frame-rate-description": "ๅธง็އ่ถŠ้ซ˜่ถŠๆต็•…๏ผŒไฝ†ไผšๅ ็”จๆ›ดๅคš่ต„ๆบใ€‚", + "media-frame-rate-label": "ๅธง็އ", + "media-primary-player-description": "่พ“ๅ…ฅๅ…ณ้”ฎ่ฏไปฅ่ฏ†ๅˆซๆ‚จ็š„ไธป่ฆๆ’ญๆ”พๅ™จใ€‚", + "media-primary-player-label": "ไธป่ฆๆ’ญๆ”พๅ™จ", + "media-primary-player-placeholder": "ไพ‹ๅฆ‚๏ผšspotify, vlc, mpv", + "media-scrolling-speed-description": "ๆ ‡้ข˜ไปŽๅคดๅˆฐๅฐพๆปšๅŠจๆ‰€้œ€็š„ๆ—ถ้—ด๏ผˆ็ง’๏ผ‰ใ€‚", + "media-scrolling-speed-label": "ๆปšๅŠจ้€Ÿๅบฆ", + "media-scrolling-title-description": "ไธบ้•ฟๅช’ไฝ“ๆ ‡้ข˜ๅฏ็”จ่ฟž็ปญๆปšๅŠจใ€‚", + "media-scrolling-title-label": "ๆ ‡้ข˜ๆปšๅŠจ", + "media-title": "ๅช’ไฝ“ๆ’ญๆ”พๅ™จ", + "on-middle-clicked-description": "้ผ ๆ ‡ไธญ้”ฎ็‚นๅ‡ปๆ—ถๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚", + "panel-applications-empty": "็›ฎๅ‰ๆฒกๆœ‰ๅบ”็”จ็จ‹ๅบๆญฃๅœจๆ’ญๆ”พ้Ÿณ้ข‘", + "spectrum-mirrored-description": "้•œๅƒ้ข‘่ฐฑ๏ผŒไฝฟไฝŽ้ข‘ไฝไบŽไธญๅฟƒใ€‚ๅ…ณ้—ญๆ—ถ๏ผŒไฝŽ้ข‘ๅœจๅทฆไพง๏ผŒ้ซ˜้ข‘ๅœจๅณไพงใ€‚", + "spectrum-mirrored-label": "้•œๅƒ้ข‘่ฐฑ", + "title": "้Ÿณ้ข‘", + "visualizer-type-description": "้€‰ๆ‹ฉๅช’ไฝ“ๆ’ญๆ”พ็š„ๅฏ่ง†ๅŒ–็ฑปๅž‹ใ€‚", + "visualizer-type-label": "ๅฏ่ง†ๅŒ–็ฑปๅž‹", + "volumes-desc": "่ฐƒๆ•ด้Ÿณ้‡ๆŽงๅˆถๅ’Œ้Ÿณ้ข‘็บงๅˆซใ€‚", + "volumes-feedback-sound-file-description": "ๆ›ดๆ”น้Ÿณ้‡ๆ—ถๆ’ญๆ”พ็š„ๅฃฐ้Ÿณๆ–‡ไปถ่ทฏๅพ„ใ€‚", + "volumes-feedback-sound-file-label": "้Ÿณ้‡ๅ้ฆˆๅฃฐ้Ÿณๆ–‡ไปถ", + "volumes-feedback-sound-file-select-title": "้€‰ๆ‹ฉ้Ÿณ้‡ๅ้ฆˆๅฃฐ้Ÿณๆ–‡ไปถ", + "volumes-input-volume-description": "้บฆๅ…‹้ฃŽ่พ“ๅ…ฅ้Ÿณ้‡็บงๅˆซใ€‚", + "volumes-mute-input-description": "้™้Ÿณ้ป˜่ฎค้Ÿณ้ข‘่พ“ๅ…ฅ๏ผˆ้บฆๅ…‹้ฃŽ๏ผ‰ใ€‚", + "volumes-mute-input-label": "้™้Ÿณ่พ“ๅ…ฅ", + "volumes-mute-output-description": "้™้Ÿณ็ณป็ปŸไธป้Ÿณ้ข‘่พ“ๅ‡บใ€‚", + "volumes-mute-output-label": "้™้Ÿณ่พ“ๅ‡บ", + "volumes-output-volume-description": "็ณป็ปŸๅ…จๅฑ€้Ÿณ้‡็บงๅˆซใ€‚", + "volumes-step-size-description": "่ฐƒๆ•ด้Ÿณ้‡ๅ˜ๅŒ–็š„ๆญฅ้•ฟ๏ผˆๆปš่ฝฎใ€้”ฎ็›˜ๅฟซๆท้”ฎ๏ผ‰ใ€‚", + "volumes-step-size-label": "้Ÿณ้‡ๆญฅ้•ฟ", + "volumes-volume-feedback-description": "่ฐƒๆ•ด้Ÿณ้‡ๆ—ถๆ’ญๆ”พๅ้ฆˆ้Ÿณๆ•ˆใ€‚", + "volumes-volume-feedback-label": "ๆ’ญๆ”พ้Ÿณ้‡ๅ้ฆˆ", + "volumes-volume-overdrive-description": "ๅ…่ฎธๅฐ†้Ÿณ้‡ๆ้ซ˜ๅˆฐ100%ไปฅไธŠใ€‚ๅฏ่ƒฝไธ่ขซๆ‰€ๆœ‰็กฌไปถๆ”ฏๆŒใ€‚", + "volumes-volume-overdrive-label": "ๅ…่ฎธ้Ÿณ้‡่ฟ‡่ฝฝ" + }, + "bar": { + "appearance-auto-hide-delay-description": "้ผ ๆ ‡็ฆปๅผ€ๅŽBar้š่—ๆ‰€้œ€ๆ—ถ้—ด", + "appearance-auto-hide-delay-label": "้š่—ๅปถ่ฟŸ", + "appearance-auto-hide-exclusive-note": "ๆณจๆ„: ๅฝ“่‡ชๅŠจ้š่—ๅค„ไบŽๆดปๅŠจ็Šถๆ€ๆ—ถ๏ผŒไธ“ๅฑžๅŒบๅŸŸไผš่‡ชๅŠจ็ฆ็”จใ€‚", + "appearance-auto-show-delay-description": "้ผ ๆ ‡่ฟ›ๅ…ฅ่พน็ผ˜ๆ—ถ๏ผŒๆ ๆ˜พ็คบๅ‰็š„ๅปถ่ฟŸๆ—ถ้—ดใ€‚", + "appearance-auto-show-delay-label": "ๆ˜พ็คบๅปถ่ฟŸ", + "appearance-background-opacity-description": "ไธบ็Šถๆ€ๆ ่ฎพ็ฝฎ่ƒŒๆ™ฏไธ้€ๆ˜Žๅบฆใ€‚", + "appearance-background-opacity-label": "็Šถๆ€ๆ ่ƒŒๆ™ฏไธ้€ๆ˜Žๅบฆ", + "appearance-capsule-color-description": "ไธบๆ ่ƒถๅ›Š้€‰ๆ‹ฉไธ€็ง้ขœ่‰ฒ๏ผŒๆˆ–ไธไฝฟ็”จไปปไฝ•้ขœ่‰ฒไปฅ้‡‡็”จ้ป˜่ฎค่กจ้ข้ขœ่‰ฒใ€‚", + "appearance-capsule-color-label": "่ƒถๅ›Š้ขœ่‰ฒ", + "appearance-capsule-opacity-description": "่ฎพ็ฝฎๆ˜พ็คบ่ƒถๅ›Šๆ—ถๅฐ้ƒจไปถ่ƒŒๆ™ฏ็š„ไธ้€ๆ˜Žๅบฆใ€‚", + "appearance-capsule-opacity-label": "่ƒถๅ›Šไธ้€ๆ˜Žๅบฆ", + "appearance-content-padding-description": "่ฐƒๆ•ดๆ ่พน็ผ˜ไธŽๅฐ้ƒจไปถไน‹้—ด็š„ๅ†…่พน่ทใ€‚", + "appearance-content-padding-label": "ๅ†…ๅฎนๅ†…่พน่ท", + "appearance-density-description": "่ฐƒๆ•ด็Šถๆ€ๆ ็š„ๅ†…่พน่ทไปฅ่Žทๅพ—็ดงๅ‡‘ๆˆ–ๅฎฝๆพ็š„ๅค–่ง‚ใ€‚", + "appearance-density-label": "็Šถๆ€ๆ ๅฏ†ๅบฆ", + "appearance-desc": "่‡ชๅฎšไน‰็Šถๆ€ๆ ็š„ๅค–่ง‚ๅ’Œไฝ็ฝฎใ€‚", + "appearance-display-mode-description": "้€‰ๆ‹ฉๆ ไฝ•ๆ—ถๅฏ่งใ€‚", + "appearance-enable-exclusion-zone-inset-description": "ๅ‡ๅฐ‘ๆŽ’้™คๅŒบๅŸŸ1ไธช็‰ฉ็†ๅƒ็ด ๏ผŒไปฅไพฟ้ฝๅนณ็š„็ช—ๅฃๅฎŒ็พŽๅœฐๅปถไผธๅˆฐ็Šถๆ€ๆ ่พน็ผ˜ไธ‹ๆ–นใ€‚", + "appearance-enable-exclusion-zone-inset-label": "ๅ†…ๅตŒๆŽ’้™คๅŒบๅŸŸ", + "appearance-floating-description": "ๅฐ†็Šถๆ€ๆ ๆ˜พ็คบไธบๆตฎๅŠจ็š„โ€œ่ฏไธธโ€ๅฝข็Šถใ€‚", + "appearance-floating-label": "ๆตฎๅŠจ็Šถๆ€ๆ ", + "appearance-font-scale-description": "่ฐƒๆ•ดๆ ไธญๆ˜พ็คบ็š„ๆ–‡ๆœฌ็š„ๅญ—ไฝ“ๅคงๅฐๆฏ”ไพ‹ใ€‚", + "appearance-font-scale-label": "ๅญ—ไฝ“็ผฉๆ”พ", + "appearance-frame-radius": "ๅ†…ๅœ†่ง’", + "appearance-frame-settings-description": "่ฐƒๆ•ด่พนๆก†็ฒ—็ป†ๅ’Œๅ†…ๅœ†่ง’ๅŠๅพ„", + "appearance-frame-settings-label": "่พนๆก†่ฎพ็ฝฎ", + "appearance-frame-thickness": "็ฒ—็ป†", + "appearance-hide-on-overview-description": "ๅฝ“ๅˆๆˆๅ™จๆฆ‚่งˆๅค„ไบŽๆดปๅŠจ็Šถๆ€ๆ—ถ๏ผŒ้š่—ๆ ๅนถๅ…ณ้—ญ้ขๆฟใ€‚", + "appearance-hide-on-overview-label": "ๅœจๆฆ‚่งˆไธญ้š่—ๆ ", + "appearance-margins-description": "่ฐƒๆ•ดๆตฎๅŠจ็Šถๆ€ๆ ๅ‘จๅ›ด็š„่พน่ทใ€‚", + "appearance-margins-horizontal": "ๆฐดๅนณ่พน่ท", + "appearance-margins-vertical": "ๅž‚็›ด่พน่ท", + "appearance-outer-corners-description": "ๅœจๆ ไธŠๆ˜พ็คบๅ‘ๅค–ๅผฏๆ›ฒ็š„่ง’ใ€‚", + "appearance-outer-corners-label": "ๅค–่ง’", + "appearance-position-description": "้€‰ๆ‹ฉๅœจๅฑๅน•ไธŠ็š„ไฝ็ฝฎใ€‚", + "appearance-position-label": "็Šถๆ€ๆ ไฝ็ฝฎ", + "appearance-show-capsule-description": "ไธบๆฏไธชๅฐ้ƒจไปถๆทปๅŠ ่ƒŒๆ™ฏใ€‚", + "appearance-show-capsule-label": "ๆ˜พ็คบ็ป„ไปถ่ƒŒๆ™ฏ", + "appearance-show-on-workspace-switch-description": "ๅˆ‡ๆขๅทฅไฝœๅŒบๆ—ถ่‡ชๅŠจ็Ÿญๆš‚ๆ˜พ็คบ็Šถๆ€ๆ ใ€‚", + "appearance-show-on-workspace-switch-label": "ๅทฅไฝœๅŒบๅˆ‡ๆขๆ—ถๆ˜พ็คบ็Šถๆ€ๆ ", + "appearance-show-outline-description": "ๅœจๆฏไธชๅฐ้ƒจไปถๅ‘จๅ›ดๆ˜พ็คบๅฏ่ง่พนๆก†ใ€‚", + "appearance-show-outline-label": "ๆ˜พ็คบๅฐ้ƒจไปถ่ฝฎๅป“", + "appearance-type-description": "้€‰ๆ‹ฉๆ ็š„ๆ ทๅผ๏ผš็ฎ€็บฆใ€ๆ‚ฌๆตฎๆˆ–่พนๆก†ใ€‚", + "appearance-type-label": "ๆ ็ฑปๅž‹", + "appearance-use-separate-opacity-description": "ๅฏ็”จๅŽไธบ็Šถๆ€ๆ ่ƒŒๆ™ฏไฝฟ็”จๅ•็‹ฌ็š„ไธ้€ๆ˜Žๅบฆๅ€ผใ€‚", + "appearance-use-separate-opacity-label": "ไฝฟ็”จๅ•็‹ฌ็š„็Šถๆ€ๆ ไธ้€ๆ˜Žๅบฆ", + "appearance-widget-spacing-description": "่ฐƒๆ•ดๆ ไธญๆฏไธชๅฐ้ƒจไปถไน‹้—ด็š„้—ด่ทใ€‚", + "appearance-widget-spacing-label": "ๅฐ้ƒจไปถ้—ด่ท", + "behavior-middle-click-command-description": "ไธญ้”ฎ็‚นๅ‡ปๆ—ถๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚", + "behavior-middle-click-command-label": "ไธญ้”ฎ็‚นๅ‡ปๅ‘ฝไปค", + "behavior-middle-click-command-placeholder": "niri msg action ๅˆ‡ๆขๆฆ‚่งˆ", + "behavior-middle-click-description": "้€‰ๆ‹ฉๅœจๆ ็š„็ฉบ็™ฝๅŒบๅŸŸไธญ้”ฎๅ•ๅ‡ป็š„ๆ“ไฝœใ€‚", + "behavior-middle-click-follow-mouse-description": "ๅœจๅ…‰ๆ ‡ไฝ็ฝฎๆ‰“ๅผ€้€‰ๅฎš็š„ไธญ้”ฎ็‚นๅ‡ป้ขๆฟใ€‚", + "behavior-middle-click-follow-mouse-label": "ไธญ้”ฎ็‚นๅ‡ป่ทŸ้š้ผ ๆ ‡", + "behavior-middle-click-label": "ๆ ไธญ้”ฎ็‚นๅ‡ปๆ“ไฝœ", + "behavior-right-click-command-description": "ๅณ้”ฎ็‚นๅ‡ปๆ—ถๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚", + "behavior-right-click-command-label": "ๅณ้”ฎ็‚นๅ‡ปๅ‘ฝไปค", + "behavior-right-click-command-placeholder": "notify-send \"ๅณ้”ฎ็‚นๅ‡ป\"", + "behavior-right-click-description": "้€‰ๆ‹ฉๅœจๆ ็š„็ฉบ็™ฝๅŒบๅŸŸๅณ้”ฎ็‚นๅ‡ปๆ—ถๆ‰ง่กŒ็š„ๆ“ไฝœใ€‚", + "behavior-right-click-follow-mouse-description": "ๅœจๅ…‰ๆ ‡ไฝ็ฝฎๆ‰“ๅผ€้€‰ๅฎš็š„ๅณ้”ฎ้ขๆฟใ€‚", + "behavior-right-click-follow-mouse-label": "ๅณ้”ฎ่ทŸ้š้ผ ๆ ‡", + "behavior-right-click-label": "ๆ ๅณ้”ฎ็‚นๅ‡ปๆ“ไฝœ", + "behavior-wheel-wrap-description": "ๅฏ็”จๅŽ๏ผŒๆปšๅŠจไผšไปŽๆœ€ๅŽไธ€้กน็ปง็ปญๅˆฐ็ฌฌไธ€้กนใ€‚", + "behavior-wheel-wrap-label": "ๅพช็Žฏ", + "behavior-workspace-scroll-description": "้€‰ๆ‹ฉ้ผ ๆ ‡ๆปš่ฝฎๅœจๆ ็š„็ฉบ็™ฝๅŒบๅŸŸๆ‰ง่กŒ็š„ๆ“ไฝœใ€‚", + "behavior-workspace-scroll-label": "ๆ ้ผ ๆ ‡ๆปš่ฝฎๆ“ไฝœ", + "behavior-workspace-scroll-option-content": "ๅ†…ๅฎน", + "behavior-workspace-scroll-option-workspace": "ๅทฅไฝœๅŒบ", + "monitor-configure-widgets": "้…็ฝฎๅฐ้ƒจไปถ", + "monitor-override-settings": "่ฆ†็›–ๅ…จๅฑ€่ฎพ็ฝฎ", + "monitor-override-settings-description": "ไธบๆญคๆ˜พ็คบๅ™จไฝฟ็”จ่‡ชๅฎšไน‰่ฎพ็ฝฎใ€‚", + "monitor-reset-all": "ๅ…จ้ƒจ้‡็ฝฎ", + "monitor-widgets-title": "ๅฐ้ƒจไปถ้…็ฝฎ - {monitor}", + "monitors-desc": "ๅœจ็‰นๅฎšๆ˜พ็คบๅ™จไธŠๆ˜พ็คบ็Šถๆ€ๆ ใ€‚ๅฆ‚ๆžœๆœช้€‰ๆ‹ฉ๏ผŒๅˆ™้ป˜่ฎคไธบๅ…จ้ƒจใ€‚", + "monitors-desc-new": "้…็ฝฎๅ“ชไบ›ๆ˜พ็คบๅ™จๆ˜พ็คบ Bar๏ผŒๅนถ่‡ชๅฎšไน‰ๆฏไธชๆ˜พ็คบๅ™จ็š„่ฎพ็ฝฎใ€‚", + "monitors-title": "ๆ˜พ็คบๅ™จๆ˜พ็คบ", + "title": "็Šถๆ€ๆ ", + "tray-blacklist-description": "ๆทปๅŠ ๆ‰˜็›˜ๆŽ’้™ค่ง„ๅˆ™๏ผŒๆ”ฏๆŒ้€š้…็ฌฆ (*)ใ€‚", + "tray-blacklist-label": "้ป‘ๅๅ•", + "tray-blacklist-placeholder": "ไพ‹ๅฆ‚๏ผšnm-applet, Fcitx*", + "tray-pin-application": "ๅ›บๅฎšๅบ”็”จ็จ‹ๅบ", + "tray-unpin-application": "ๅ–ๆถˆๅ›บๅฎšๅบ”็”จ็จ‹ๅบ", + "use-global-widgets": "ไฝฟ็”จๅ…จๅฑ€ๅฐ้ƒจไปถ", + "widgets-desc": "ๆ‹–ๅŠจๅฐ้ƒจไปถไปฅๆ›ดๆ”นๅฎƒไปฌ็š„้กบๅบใ€‚ไฝฟ็”จๅณ้”ฎ่œๅ•ๅœจๅ„้ƒจๅˆ†ไน‹้—ด่ฝฌ็งปๅฐ้ƒจไปถๆˆ–็งป้™คๅฎƒไปฌใ€‚", + "widgets-title": "ๅฐ้ƒจไปถๅฎšไฝ" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "ไปŽๅฃ็บธ็”Ÿๆˆ้…่‰ฒๆ–นๆกˆใ€‚่‡ชๅŠจๆๅ–้ขœ่‰ฒไปฅๅˆ›ๅปบ็ปŸไธ€็š„ไธป้ข˜ใ€‚", + "color-source-use-wallpaper-colors-label": "ไฝฟ็”จๅฃ็บธ้ขœ่‰ฒ", + "dark-mode-mode-description": "ๅฏ็”จ่‡ชๅŠจๅˆ‡ๆขๆต…่‰ฒๅ’Œๆทฑ่‰ฒๆจกๅผใ€‚", + "dark-mode-mode-label": "ๆทฑ่‰ฒๆจกๅผ่ฎกๅˆ’", + "dark-mode-mode-manual": "ๆ‰‹ๅŠจ", + "dark-mode-mode-off": "ๅ…ณ", + "dark-mode-switch-description": "ๅˆ‡ๆขๅˆฐๆ›ดๆš—็š„ไธป้ข˜๏ผŒไพฟไบŽๅคœ้—ด่ง‚็œ‹ใ€‚", + "delete-error-description": "ๅˆ ้™ค {scheme} ๅคฑ่ดฅ", + "delete-error-title": "ๅˆ ้™คๅคฑ่ดฅ", + "delete-success-description": "ๆˆๅŠŸๅˆ ้™ค {scheme}", + "delete-success-title": "้…่‰ฒๆ–นๆกˆๅทฒๅˆ ้™ค", + "download-button": "ไธ‹่ฝฝๆ›ดๅคš", + "download-downloading": "ๆญฃๅœจไธ‹่ฝฝ...", + "download-empty": "ๆฒกๆœ‰ๅฏ็”จ็š„้…่‰ฒๆ–นๆกˆ", + "download-error-api-error": "API ้”™่ฏฏ: {status}", + "download-error-description": "ไธ‹่ฝฝ {scheme} ๅคฑ่ดฅ", + "download-error-download-failed": "ไธ‹่ฝฝๅคฑ่ดฅ๏ผŒ้€€ๅ‡บไปฃ็ : {code}", + "download-error-invalid-response": "ๆ— ๆ•ˆ็š„ API ๅ“ๅบ”ๆ ผๅผ", + "download-error-no-files": "ๆœชๆ‰พๅˆฐๆ–นๆกˆ็š„ๆ–‡ไปถ", + "download-error-parse-failed": "่งฃๆž API ๅ“ๅบ”ๅคฑ่ดฅ: {error}", + "download-error-rate-limit": "GitHub API ้€Ÿ็އ้™ๅˆถๅทฒ่ถ…ๅ‡บ", + "download-error-title": "ไธ‹่ฝฝๅคฑ่ดฅ", + "download-fetching": "ๆญฃๅœจ่Žทๅ–ๅฏ็”จ็š„้…่‰ฒๆ–นๆกˆ...", + "download-success-description": "ๆˆๅŠŸไธ‹่ฝฝ {scheme}", + "download-success-title": "้…่‰ฒๆ–นๆกˆๅทฒไธ‹่ฝฝ", + "download-title": "ไธ‹่ฝฝ้…่‰ฒๆ–นๆกˆ", + "method-description": { + "content": "ๅ…ทๆœ‰้ซ˜ไฟ็œŸ่‰ฒๅฝฉๆๅ–็š„ Material Design ๆ–นๆกˆ๏ผŒๅฏไธŽๆบๅ†…ๅฎน็š„ๅฎž้™…้ขœ่‰ฒ็ดงๅฏ†ๅŒน้…ใ€‚", + "dysfunctional": "ไธŽ Faithful ็ฑปไผผ๏ผŒไฝ†้€‰ๆ‹ฉ็ฌฌไบŒไธปๅฏผ็š„้ขœ่‰ฒๅฎถๆ—ไฝœไธบไธป่ฆ้ขœ่‰ฒใ€‚", + "faithful": "ๅฐ่ฏ•ๅœจ็”Ÿๆˆๅ’Œ่ฐ่ฐƒ่‰ฒๆฟ็š„ๅŒๆ—ถ๏ผŒๅฐฝๅฏ่ƒฝๆŽฅ่ฟ‘ๆบ้ขœ่‰ฒใ€‚", + "fruit-salad": "Material Design ๆ–นๆกˆ๏ผŒไบง็”Ÿไธ€ไธชไฟ็šฎใ€ๅ……ๆปกๆดปๅŠ›็š„่ฐƒ่‰ฒๆฟ๏ผŒๅ…ทๆœ‰ๅคšๆ ทๅ’Œๅ˜ๅŒ–็š„่‰ฒ่ฐƒใ€‚", + "monochrome": "ไฝฟ็”จๅ•่‰ฒ่ฐƒ็ฐ้˜ถๅ’Œๆœ€ๅฐ‘่‰ฒๅฝฉๅ†…ๅฎน็š„ Material Design ๆ–นๆกˆใ€‚", + "muted": "็”Ÿๆˆๆ›ดๆŸ”ๅ’Œใ€ๆ›ดไฝŽ้ฅฑๅ’Œๅบฆ็š„่‰ฒ่ฐƒ๏ผŒ่ฅ้€ ๅนณ้™่€ŒไฝŽ่ฐƒ็š„ๅค–่ง‚ใ€‚", + "rainbow": "Material Design ๆ–นๆกˆ๏ผŒๅฏๅœจๆ•ดไธชๅ…‰่ฐฑไธญ็”Ÿๆˆ้ขœ่‰ฒ๏ผŒไปฅๅฎž็Žฐๆœ€ๅคง็š„้ขœ่‰ฒๅคšๆ ทๆ€งใ€‚", + "tonal-spot": "Material Design ้ป˜่ฎค็ฎ—ๆณ•ใ€‚ไฝฟ็”จๆบ้ขœ่‰ฒ็š„่‰ฒ่ฐƒๅ˜ๅŒ–ๅˆ›ๅปบๅนณ่กก็š„่ฐƒ่‰ฒๆฟใ€‚", + "vibrant": "ๆœ€ๅคงๅŒ–่‰ฒๅฝฉ้ฅฑๅ’Œๅบฆ๏ผŒๆ‰“้€ ๅคง่ƒ†้†’็›ฎ็š„่ฐƒ่‰ฒๆฟ๏ผŒๅ‘ˆ็Žฐ็”ŸๅŠจ่‰ฒ่ฐƒใ€‚" + }, + "predefined-desc": "ไปŽ้ข„ๅฎšไน‰้…่‰ฒๆ–นๆกˆ้›†ๅˆไธญ้€‰ๆ‹ฉใ€‚", + "predefined-generate-templates-label": "ไธบ้ข„ๅฎšไน‰ๆ–นๆกˆ็”Ÿๆˆๆจกๆฟ", + "predefined-title": "้ข„ๅฎšไน‰้…่‰ฒๆ–นๆกˆ", + "sync-gsettings-description": "ๅฐ†็ณป็ปŸไธป้ข˜ไธŽๆดปๅŠจ็š„ๆต…่‰ฒๆˆ–ๆทฑ่‰ฒๅ˜ไฝ“ๅŒน้…ใ€‚", + "sync-gsettings-label": "ๅŒๆญฅ็ณป็ปŸไธป้ข˜", + "templates-desc": "ๅฐ†้ขœ่‰ฒๅบ”็”จไบŽๅค–้ƒจๅบ”็”จ็จ‹ๅบใ€‚", + "templates-filter-description": "ๆ˜พ็คบ็‰นๅฎš็ฑปๅˆซ็š„ๆจกๆฟใ€‚", + "templates-filter-label": "ๆŒ‰็ฑปๅˆซ็ญ›้€‰", + "templates-misc-description": "ๅˆ›ๅปบๆ‚จ่‡ชๅทฑ็š„ๆจกๆฟใ€‚", + "templates-misc-label": "้ซ˜็บง", + "templates-misc-user-templates-description": "ไป…ๅœจๆ‚จ็Ÿฅ้“่‡ชๅทฑๅœจๅšไป€ไนˆๆ—ถๅฏ็”จ๏ผŒ่ฏทๅ‚้˜…ๆˆ‘ไปฌ็š„ๅœจ็บฟๆ–‡ๆกฃใ€‚", + "templates-misc-user-templates-label": "ๅฏ็”จ็”จๆˆทๆจกๆฟ", + "templates-none-detected": "ๆœชๆฃ€ๆต‹ๅˆฐ", + "templates-write-path": "ๅ†™ๅ…ฅ๏ผš{filepath}", + "title": "้…่‰ฒๆ–นๆกˆ", + "wallpaper-method-description": "้€‰ๆ‹ฉๆ‚จๅ–œๆฌข็š„่ฐƒ่‰ฒๆฟ็”Ÿๆˆๆ–นๆณ•ใ€‚", + "wallpaper-method-label": "่ฐƒ่‰ฒๆฟ็”Ÿๆˆๆ–นๆณ•", + "wallpaper-monitor-source-description": "้€‰ๆ‹ฉ็”จไบŽๆๅ–ๅฃ็บธ้ขœ่‰ฒ็š„ๆ˜พ็คบๅ™จใ€‚", + "wallpaper-monitor-source-label": "้ขœ่‰ฒ็”Ÿๆˆๆบ" + }, + "connections": { + "authentication-required": "้œ€่ฆ่ฎค่ฏ", + "bluetooth-auto-connect-description": "ๅฝ“ Bluetooth ๅฏ็”จๆ—ถ๏ผŒ่‡ชๅŠจ่ฟžๆŽฅๅˆฐๅ—ไฟกไปป็š„ๅทฒ้…ๅฏน่ฎพๅค‡ใ€‚", + "bluetooth-auto-connect-label": "่‡ชๅŠจ่ฟžๆŽฅๅทฒ้…ๅฏน่ฎพๅค‡", + "bluetooth-devices-unnamed": "ๆœชๅ‘ฝๅ่ฎพๅค‡ไธๆ˜พ็คบใ€‚", + "bluetooth-discoverable": "ๅฝ“ๆญค่ฎพ็ฝฎ้€‰้กนๅกๆ‰“ๅผ€ๆ—ถ๏ผŒๆญค่ฎพๅค‡ๅฏ่ขซๅ‘็Žฐไธบ {hostName}ใ€‚", + "bluetooth-rssi-polling-description": "้€š่ฟ‡ bluetoothctl ๅฎšๆœŸ้‡‡ๆ ทๅทฒ่ฟžๆŽฅ่ฎพๅค‡็š„ RSSIใ€‚ๅฏ่ƒฝๅนถ้žๆ‰€ๆœ‰่ฎพๅค‡้ƒฝๅฏ็”จ๏ผ›ๅฏ็”จๆ—ถๅ ็”จๆœ€ๅฐ‘่ต„ๆบใ€‚", + "bluetooth-rssi-polling-interval-description": "้…็ฝฎๅทฒ่ฟžๆŽฅ่ฎพๅค‡็š„ไฟกๅทๅผบๅบฆๆ›ดๆ–ฐ้ข‘็އใ€‚", + "bluetooth-rssi-polling-interval-label": "่ฝฎ่ฏข้—ด้š”", + "bluetooth-rssi-polling-label": "่“็‰™ไฟกๅท่ฝฎ่ฏข", + "disable-discoverability-description": "ๅฐ†ๆ‚จ็š„่ฎพๅค‡ไปŽ้™„่ฟ‘็š„ Bluetooth ่ฎพๅค‡ไธญ้š่—ใ€‚", + "disable-discoverability-label": "็ฆ็”จ่ฎพๅค‡ๅฏ่งๆ€ง", + "hide-unnamed-devices-description": "้š่—ไป…ๆ˜พ็คบไธบ Bluetooth ๅœฐๅ€็š„่ฎพๅค‡ใ€‚", + "hide-unnamed-devices-label": "้š่—ๆœชๅ‘ฝๅ่ฎพๅค‡", + "pin-instructions": "่ฏท่พ“ๅ…ฅๆ‚จ่ฎพๅค‡ไธŠๆ˜พ็คบ็š„PIN็ ใ€‚", + "title": "่ฟžๆŽฅ", + "wifi-header-text": "ไฝฟ็”จ Wi-Fi ๆŸฅ็œ‹ๅฏ็”จ็ฝ‘็ปœๅนถ่ฟžๆŽฅๅˆฐไบ’่”็ฝ‘ใ€‚" + }, + "control-center": { + "cards-desc": "่‡ชๅฎšไน‰ๅœจๆŽงๅˆถไธญๅฟƒๆ˜พ็คบ็š„ๆŽงๅˆถ้กนๅŠๅ…ถ้กบๅบใ€‚", + "desc": "้…็ฝฎๆŽงๅˆถไธญๅฟƒ้ขๆฟ็š„ไฝ็ฝฎๅ’Œ่กŒไธบใ€‚", + "open-at-mouse-description": "ๅณ้”ฎ็‚นๅ‡ปๆ ็š„็ฉบ็™ฝๅŒบๅŸŸๆ—ถ๏ผŒๅœจๅ…‰ๆ ‡้™„่ฟ‘ๆ‰“ๅผ€ๆŽงๅˆถไธญๅฟƒใ€‚็ฆ็”จๆ—ถ๏ผŒๅฐ†ไฝฟ็”จไธŠๆ–น็š„ไฝ็ฝฎ่ฎพ็ฝฎใ€‚", + "open-at-mouse-label": "ๅณ้”ฎ็‚นๅ‡ปๆ—ถๅœจๅ…‰ๆ ‡ๅค„ๆ‰“ๅผ€", + "position-description": "้€‰ๆ‹ฉๆ‰“ๅผ€ๆŽงๅˆถไธญๅฟƒๆ—ถ้ขๆฟๅ‡บ็Žฐ็š„ไฝ็ฝฎใ€‚", + "shortcuts-custom-button-command-description": "ๅ•ๅ‡ปๆŒ‰้’ฎๆ—ถๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚", + "shortcuts-custom-button-enable-on-state-logic-description": "ๆ นๆฎๆฃ€ๆŸฅๅ‘ฝไปคๅฏ็”จ็ฌฌไบŒไธชๅ›พๆ ‡ๅ’Œโ€œ้ซ˜ไบฎโ€็Šถๆ€ใ€‚", + "shortcuts-custom-button-enable-on-state-logic-label": "ๅฏ็”จๅผ€ๅฏ็Šถๆ€้€ป่พ‘", + "shortcuts-custom-button-icon-description": "ไปŽๅบ“ไธญ้€‰ๆ‹ฉไธ€ไธชๅ›พๆ ‡ใ€‚", + "shortcuts-custom-button-on-clicked-label": "ๅทฆ้”ฎๅ•ๅ‡ปๅ‘ฝไปค", + "shortcuts-custom-button-on-middle-clicked-description": "ๅฝ“ๆŒ‰้’ฎ่ขซไธญ้”ฎ็‚นๅ‡ปๆ—ถๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚", + "shortcuts-custom-button-on-state-command-description": "ๆ‰ง่กŒไปฅๆฃ€ๆŸฅๆŒ‰้’ฎๆ˜ฏๅฆๅบ”ๅค„ไบŽโ€œๅผ€ๅฏโ€็Šถๆ€็š„ๅ‘ฝไปคใ€‚่ฟ”ๅ›ž 0 ่กจ็คบๅผ€ๅฏ๏ผŒ้ž้›ถ่กจ็คบๅ…ณ้—ญใ€‚", + "shortcuts-custom-button-on-state-command-label": "ๅผ€ๅฏ็Šถๆ€ๆฃ€ๆŸฅๅ‘ฝไปค", + "shortcuts-custom-button-on-state-icon-description": "ๆŒ‰้’ฎๅค„ไบŽโ€œๅผ€ๅฏโ€็Šถๆ€ๆ—ถ็š„ๅ›พๆ ‡ใ€‚", + "shortcuts-custom-button-on-state-icon-label": "ๅผ€ๅฏ็Šถๆ€ๅ›พๆ ‡", + "shortcuts-custom-button-state-checks-add": "ๆทปๅŠ ็Šถๆ€ๆฃ€ๆŸฅ", + "shortcuts-custom-button-state-checks-command": "่ฆไธบๆญค็Šถๆ€ๆฃ€ๆŸฅๆ‰ง่กŒ็š„ๅ‘ฝไปค", + "shortcuts-custom-button-state-checks-label": "็Šถๆ€ๆฃ€ๆŸฅ", + "shortcuts-custom-button-state-checks-remove": "็งป้™ค", + "shortcuts-custom-button-tooltip-description": "ๆ‚ฌๅœๅœจๆŒ‰้’ฎไธŠๆ—ถๆ˜พ็คบ็š„ๅทฅๅ…ทๆ็คบใ€‚", + "shortcuts-custom-button-tooltip-label": "ๆ็คบไฟกๆฏ๏ผˆTooltip๏ผ‰", + "shortcuts-title": "ๅฟซๆทๆ–นๅผๅฐ้ƒจไปถ", + "system-monitor-disk-path-description": "้€‰ๆ‹ฉๆŽงๅˆถไธญๅฟƒไธญ็š„็ณป็ปŸ็›‘่ง†ๅ™จๅก็‰‡ๅบ”็›‘่ง†็š„็ฃ็›˜ๆŒ‚่ฝฝ็‚นใ€‚", + "system-monitor-disk-path-label": "็ณป็ปŸ็›‘่ง†ๅ™จ็ฃ็›˜่ทฏๅพ„", + "title": "ๆŽงๅˆถไธญๅฟƒ" + }, + "desktop-widgets": { + "clock-enabled-description": "ๅœจๆกŒ้ขไธŠๆ˜พ็คบไธ€ไธชๆ—ถ้’Ÿๅฐ้ƒจไปถใ€‚", + "clock-enabled-label": "ๅฏ็”จๆ—ถ้’Ÿๅฐ้ƒจไปถ", + "clock-format-label": "ๆ ผๅผ", + "clock-height-description": "ๆ—ถ้’Ÿๅฐ้ƒจไปถ็š„้ซ˜ๅบฆ๏ผŒไปฅๅƒ็ด ไธบๅ•ไฝใ€‚", + "clock-height-label": "้ซ˜ๅบฆ", + "clock-minimal-mode-description": "ไฝฟ็”จๅŒ…ๅซๆ—ถ้—ดๅ’Œๆ—ฅๆœŸ็š„็ฎ€็บฆๆ—ถ้’Ÿๆ˜พ็คบใ€‚", + "clock-minimal-mode-label": "็ฎ€็บฆๆจกๅผ", + "clock-rounded-corners-description": "ไฝฟ็”จๅœ†่ง’ไฝœไธบๅฐ้ƒจไปถ่ƒŒๆ™ฏใ€‚", + "clock-rounded-corners-label": "ๅœ†่ง’", + "clock-show-background-description": "ๆ˜พ็คบๆ—ถ้’Ÿๅฐ้ƒจไปถ็š„่ƒŒๆ™ฏๅฎนๅ™จใ€‚", + "clock-show-background-label": "ๆ˜พ็คบ่ƒŒๆ™ฏ", + "clock-show-date-description": "ๅœจๆ—ถ้—ดไธ‹ๆ–นๆ˜พ็คบๅฝ“ๅ‰ๆ—ฅๆœŸใ€‚", + "clock-show-date-label": "ๆ˜พ็คบๆ—ฅๆœŸ", + "clock-show-seconds-description": "ๅœจๆ—ถ้—ดไธญๆ˜พ็คบ็ง’ใ€‚", + "clock-show-seconds-label": "ๆ˜พ็คบ็ง’ๆ•ฐ", + "clock-style-analog": "ๆจกๆ‹Ÿ", + "clock-style-binary": "ไบŒ่ฟ›ๅˆถ", + "clock-style-description": "้€‰ๆ‹ฉๆ—ถ้’Ÿๆ˜พ็คบๆ ทๅผใ€‚", + "clock-style-digital": "ๆ•ฐๅญ—", + "clock-style-label": "ๆ—ถ้’Ÿๆ ทๅผ", + "clock-style-minimal": "็ฎ€็บฆ", + "clock-width-description": "ๆ—ถ้’Ÿๅฐ้ƒจไปถ็š„ๅฎฝๅบฆ๏ผŒไปฅๅƒ็ด ไธบๅ•ไฝใ€‚", + "cpu-intensive-note": "ๆ ‡ๆœ‰ ! ็š„Widgetไผšๅ ็”จๆ›ดๅคš CPU๏ผŒ่ฏท่ฐจๆ…Žๅฏ็”จใ€‚", + "edit-mode-button-label": "่ฟ›ๅ…ฅ็ผ–่พ‘ๆจกๅผ", + "edit-mode-controls-explanation": "ๅทฆ้”ฎ็‚นๅ‡ปๅนถๆ‹–ๅŠจ๏ผš็งปๅŠจๆˆ–่ฐƒๆ•ดๅฐ้ƒจไปถๅคงๅฐใ€‚\nๅณ้”ฎ็‚นๅ‡ป๏ผšๆ‰“ๅผ€ไธŠไธ‹ๆ–‡่œๅ•้€‰้กนใ€‚", + "edit-mode-description": "ๅฏ็”จ็ผ–่พ‘ๆจกๅผไปฅ็งปๅŠจๅ’Œ้‡ๆ–ฐๅฎšไฝๆกŒ้ขๅฐ็ป„ไปถใ€‚ๅฏ็”จๅŽ๏ผŒๅฐ็ป„ไปถไผšๆ˜พ็คบๆ‹–ๅŠจ่ฝฎๅป“๏ผŒๅนถไธ”ๅฏไปฅ้‡ๆ–ฐๅฎšไฝใ€‚", + "edit-mode-exit-button": "้€€ๅ‡บ็ผ–่พ‘ๆจกๅผ", + "edit-mode-grid-snap-label": "็ฝ‘ๆ ผๅฏน้ฝ", + "edit-mode-grid-snap-scale-label": "็ฝ‘ๆ ผๅธ้™„ๆฏ”ไพ‹", + "edit-mode-label": "็ผ–่พ‘ๆจกๅผ", + "enabled-description": "ๅฎŒๅ…จๅฏ็”จๆˆ–็ฆ็”จๆกŒ้ขๅฐ้ƒจไปถใ€‚", + "enabled-label": "ๅฏ็”จๆกŒ้ขๅฐ้ƒจไปถ", + "general-desc": "้…็ฝฎๆ˜พ็คบๅœจๆ‚จๆกŒ้ขไธŠ็š„ๅฐ้ƒจไปถใ€‚", + "general-title": "ๆกŒ้ขๅฐ้ƒจไปถ", + "media-player-enabled-description": "ๅœจๆกŒ้ขไธŠๆ˜พ็คบๅช’ไฝ“ๆ’ญๆ”พๅ™จๅฐ้ƒจไปถใ€‚", + "media-player-enabled-label": "ๅฏ็”จๅช’ไฝ“ๆ’ญๆ”พๅ™จๅฐ้ƒจไปถ", + "media-player-rounded-corners-description": "ๅฏ็”จๅฐ้ƒจไปถ่พน็ผ˜็š„ๅœ†่ง’ใ€‚", + "media-player-show-album-art-description": "ๆ˜พ็คบไธ“่พ‘ๅฐ้ขๅ’Œๆ›ฒ็›ฎไฟกๆฏ๏ผˆๆ ‡้ข˜ๅ’Œ่‰บๆœฏๅฎถ๏ผ‰ใ€‚", + "media-player-show-album-art-label": "ๆ˜พ็คบไธ“่พ‘ๅฐ้ขๅ’Œๆ ‡้ข˜", + "media-player-show-background-description": "ๆ˜พ็คบๅช’ไฝ“ๆ’ญๆ”พๅ™จๅฐ้ƒจไปถ็š„่ƒŒๆ™ฏๅฎนๅ™จใ€‚", + "media-player-show-buttons-description": "ๅœจๅช’ไฝ“ๆ’ญๆ”พๅ™จๅฐ้ƒจไปถไธŠๆ˜พ็คบๅช’ไฝ“ๆŽงๅˆถๆŒ‰้’ฎ๏ผˆๆ’ญๆ”พ/ๆš‚ๅœใ€ไธŠไธ€ไธชใ€ไธ‹ไธ€ไธช๏ผ‰ใ€‚", + "media-player-show-buttons-label": "ๆ˜พ็คบๆŒ‰้’ฎ", + "media-player-show-visualizer-description": "ๆ˜พ็คบ้Ÿณ้ข‘ๅฏ่ง†ๅŒ–ๅ ๅŠ ๅฑ‚ใ€‚", + "media-player-visualizer-type-description": "้€‰ๆ‹ฉๆกŒ้ขๅช’ไฝ“ๆ’ญๆ”พๅ™จ็š„่ƒŒๆ™ฏๅฏ่ง†ๅŒ–็ฑปๅž‹ใ€‚", + "overview-enabled-description": "ๅฝ“ๅˆๆˆๅ™จๆฆ‚่งˆๆ‰“ๅผ€ๆ—ถ๏ผŒๆ˜พ็คบๆกŒ้ขๅฐ้ƒจไปถใ€‚", + "overview-enabled-label": "ๅœจๆฆ‚่งˆไธญๆ˜พ็คบ", + "system-stat-layout-bottom": "ๅบ•้ƒจ", + "system-stat-layout-description": "้€‰ๆ‹ฉๅ›พไพ‹็›ธๅฏนไบŽๅ›พ่กจ็š„ๆ˜พ็คบๆ–นๅผใ€‚", + "system-stat-layout-label": "ๅธƒๅฑ€", + "system-stat-layout-side": "่พน", + "system-stat-rounded-corners-description": "ๅฐ็ป„ไปถ่ƒŒๆ™ฏไฝฟ็”จๅœ†่ง’ใ€‚", + "system-stat-rounded-corners-label": "ๅœ†่ง’", + "system-stat-show-background-description": "ๆ˜พ็คบ็ณป็ปŸ็ปŸ่ฎกๅฐ้ƒจไปถ็š„่ƒŒๆ™ฏๅฎนๅ™จใ€‚", + "system-stat-show-background-label": "ๆ˜พ็คบ่ƒŒๆ™ฏ", + "system-stat-stat-type-description": "้€‰ๆ‹ฉ่ฆๆ˜พ็คบ็š„็ณป็ปŸ็ปŸ่ฎกไฟกๆฏใ€‚", + "system-stat-stat-type-label": "็ปŸ่ฎก็ฑปๅž‹", + "title": "ๆกŒ้ขๅฐ้ƒจไปถ", + "weather-enabled-description": "ๅœจๆกŒ้ขไธŠๆ˜พ็คบๅคฉๆฐ”ๅฐ้ƒจไปถใ€‚", + "weather-enabled-label": "ๅฏ็”จๅคฉๆฐ”ๅฐ้ƒจไปถ", + "weather-show-background-description": "ๆ˜พ็คบๅคฉๆฐ”ๅฐ้ƒจไปถ็š„่ƒŒๆ™ฏๅฎนๅ™จใ€‚" + }, + "display": { + "monitors-backlight-device-auto-option": "้ป˜่ฎค", + "monitors-backlight-device-description": "ไธบๆญค่พ“ๅ‡บ้€‰ๆ‹ฉไธ€ไธช่ƒŒๅ…‰่ฎพๅค‡ใ€‚", + "monitors-backlight-device-label": "่ƒŒๅ…‰่ฎพๅค‡", + "monitors-brightness-step-description": "่ฐƒๆ•ดไบฎๅบฆๅ˜ๅŒ–็š„ๆญฅ้•ฟ๏ผˆๆปš่ฝฎๅ’Œ้”ฎ็›˜ๅฟซๆท้”ฎ๏ผ‰ใ€‚", + "monitors-brightness-step-label": "ไบฎๅบฆๆญฅ้•ฟ", + "monitors-brightness-unavailable-ddc-disabled": "ไบฎๅบฆๆŽงๅˆถไธๅฏ็”จใ€‚ๅฏ็”จโ€œๅค–้ƒจไบฎๅบฆๆ”ฏๆŒโ€ไปฅๆŽงๅˆถๆญคๆ˜พ็คบๅ™จ็š„ไบฎๅบฆใ€‚", + "monitors-brightness-unavailable-generic": "ๆญคๆ˜พ็คบๅ™จๆ— ๆณ•ไฝฟ็”จไบฎๅบฆๆŽงๅˆถใ€‚", + "monitors-desc": "่ฐƒๆ•ดๆฏไธชๆ˜พ็คบๅ™จ็š„ไบฎๅบฆ่ฎพ็ฝฎใ€‚", + "monitors-enforce-minimum-description": "่งฃๅ†ณไบฎๅบฆไธบ 0% ๆ—ถ้ƒจๅˆ†ๆ˜พ็คบๅ™จ่ƒŒๅ…‰ๅฎŒๅ…จๅ…ณ้—ญ็š„้—ฎ้ข˜ใ€‚", + "monitors-enforce-minimum-label": "้™ๅˆถๆœ€ๅฐไบฎๅบฆไธบ 1%", + "monitors-external-brightness-description": "ๅฏ็”จ DDCUtil ๆ”ฏๆŒ๏ผŒ้€š่ฟ‡ DDC/CI ๅ่ฎฎๆŽงๅˆถๅค–้ƒจๆ˜พ็คบๅ™จ็š„ไบฎๅบฆใ€‚", + "monitors-external-brightness-label": "ๅค–้ƒจไบฎๅบฆๆ”ฏๆŒ", + "monitors-global-brightness-description": "ๅŒๆ—ถ่ฐƒๆ•ดๆ‰€ๆœ‰ๆ˜พ็คบๅ™จ็š„ไบฎๅบฆใ€‚", + "monitors-global-brightness-label": "ๆ‰€ๆœ‰ๆ˜พ็คบๅ™จ", + "monitors-title": "ๆ˜พ็คบๅ™จ่ฎพ็ฝฎ", + "night-light-auto-schedule-description": "ๅŸบไบŽ {location} ็š„ๆ—ฅ่ฝๅ’Œๆ—ฅๅ‡บๆ—ถ้—ด โ€” ๆŽจ่ใ€‚", + "night-light-auto-schedule-label": "่‡ชๅŠจ่ฐƒๅบฆ", + "night-light-desc": "ๅ‡ๅฐ‘่“ๅ…‰ๅ‘ๅฐ„๏ผŒๅธฎๅŠฉๆ‚จๆ›ดๅฅฝๅœฐ็ก็œ ๅนถๅ‡่ฝป็œผ็›็–ฒๅŠณใ€‚", + "night-light-enable-description": "ๅบ”็”จๆš–่‰ฒๆปค้•œไปฅๅ‡ๅฐ‘่“ๅ…‰ๅ‘ๅฐ„ใ€‚", + "night-light-enable-label": "ๅฏ็”จๅคœ้—ดๆจกๅผ", + "night-light-force-activation-description": "ๅฟฝ็•ฅ่ฐƒๅบฆๅนถ็ซ‹ๅณๅบ”็”จๅคœ้—ดๆปค้•œใ€‚", + "night-light-force-activation-label": "ๅผบๅˆถๆฟ€ๆดป", + "night-light-manual-schedule-description": "ไธบๆ—ฅๅ‡บๅ’Œๆ—ฅ่ฝ่ฎพ็ฝฎ่‡ชๅฎšไน‰ๆ—ถ้—ดใ€‚", + "night-light-manual-schedule-label": "ๆ‰‹ๅŠจ่ฐƒๅบฆ", + "night-light-manual-schedule-select-start": "้€‰ๆ‹ฉๅผ€ๅง‹ๆ—ถ้—ด", + "night-light-manual-schedule-select-stop": "้€‰ๆ‹ฉๅœๆญขๆ—ถ้—ด", + "night-light-manual-schedule-sunrise": "ๆ—ฅๅ‡บๆ—ถ้—ด", + "night-light-manual-schedule-sunset": "ๆ—ฅ่ฝๆ—ถ้—ด", + "night-light-temperature-day": "็™ฝๅคฉ", + "night-light-temperature-day-description": "ๆŽงๅˆถ็™ฝๅคฉ็š„่‰ฒๆธฉใ€‚", + "night-light-temperature-description": "่ฎพ็ฝฎๅคœ้—ดๅ’Œ็™ฝๅคฉ็š„้ขœ่‰ฒๆธฉๆš–ๅบฆใ€‚", + "night-light-temperature-label": "่‰ฒๆธฉ", + "night-light-temperature-night": "ๅคœ้—ด", + "night-light-temperature-night-description": "ๆŽงๅˆถๅคœ้—ด็š„่‰ฒๆธฉใ€‚", + "title": "ๆ˜พ็คบ" + }, + "dock": { + "appearance-background-opacity-description": "่ฐƒๆ•ด Dock ๆ ็š„่ƒŒๆ™ฏไธ้€ๆ˜Žๅบฆใ€‚", + "appearance-border-radius-description": "่ฐƒๆ•ด Dock ๆ ็š„่พนๆก†ๅŠๅพ„ใ€‚", + "appearance-border-radius-label": "่พนๆก†ๅŠๅพ„", + "appearance-colorize-icons-description": "ๅฐ†ไธป้ข˜้ขœ่‰ฒๅบ”็”จๅˆฐ Dock ๆ ๅบ”็”จๅ›พๆ ‡๏ผˆไป…้™้ž็„ฆ็‚นๅบ”็”จ๏ผ‰ใ€‚", + "appearance-colorize-icons-label": "็€่‰ฒๅ›พๆ ‡", + "appearance-dead-opacity-description": "่ฐƒๆ•ดๆœช่ฟ่กŒ็š„ๅบ”็”จ็จ‹ๅบๅ›พๆ ‡็š„ไธ้€ๆ˜Žๅบฆใ€‚", + "appearance-dead-opacity-label": "้žๆดป่ทƒๅบ”็”จไธ้€ๆ˜Žๅบฆ", + "appearance-desc": "่‡ชๅฎšไน‰ Dock ๆ ็š„่กŒไธบๅ’Œๅค–่ง‚ใ€‚", + "appearance-display-auto-hide": "่‡ชๅŠจ้š่—", + "appearance-display-description": "้€‰ๆ‹ฉ Dock ๆ ็š„่กŒไธบๆ–นๅผใ€‚", + "appearance-display-exclusive": "็‹ฌๅ ", + "appearance-dock-indicator-description": "ๅฝ“ Dock ้š่—ๆ—ถ๏ผŒๆ˜พ็คบไธ€ไธชๅฐๆŒ‡็คบๅ™จใ€‚", + "appearance-dock-indicator-label": "Dock ๆŒ‡็คบๅ™จ", + "appearance-floating-distance-description": "่ฐƒๆ•ด่ท็ฆปๅฑๅน•่พน็ผ˜็š„ๆตฎๅŠจ่ท็ฆปใ€‚", + "appearance-floating-distance-label": "Dock ๆ ๆตฎๅŠจ่ท็ฆป", + "appearance-group-apps-description": "ๅฐ†ๅŒไธ€ๅบ”็”จ็š„ๅคšไธช็ช—ๅฃๅˆ†็ป„ๅˆฐไธ€ไธช Dock ๆก็›ฎไธญใ€‚", + "appearance-group-apps-label": "ๅˆ†็ป„็›ธๅŒๅบ”็”จ", + "appearance-group-click-action-cycle": "ๅˆ‡ๆข็ช—ๅฃ", + "appearance-group-click-action-description": "้€‰ๆ‹ฉๅทฆ้”ฎๅ•ๅ‡ปๅฏนๅˆ†็ป„ๅบ”็”จๆ‰ง่กŒ็š„ๆ“ไฝœใ€‚", + "appearance-group-click-action-label": "ๅˆ†็ป„ๅบ”็”จ็‚นๅ‡ปๆ“ไฝœ", + "appearance-group-click-action-list": "ๆ‰“ๅผ€็ช—ๅฃๅˆ—่กจ", + "appearance-group-context-menu-mode-description": "้€‰ๆ‹ฉๅˆ†็ป„ๅบ”็”จไธŠไธ‹ๆ–‡่œๅ•็š„่กŒไธบๆ–นๅผใ€‚", + "appearance-group-context-menu-mode-extended": "ๆ‰ฉๅฑ•", + "appearance-group-context-menu-mode-label": "ๅˆ†็ป„ๅบ”็”จ่œๅ•ๆจกๅผ", + "appearance-group-context-menu-mode-list": "็ช—ๅฃๅˆ—่กจ", + "appearance-group-indicator-style-description": "้€‰ๆ‹ฉๅˆ†็ป„่ฟ่กŒๆŒ‡็คบๅ™จๅฆ‚ไฝ•ๆ˜พ็คบ็„ฆ็‚น็ช—ๅฃ็Šถๆ€ใ€‚", + "appearance-group-indicator-style-dots": "็‚น", + "appearance-group-indicator-style-label": "ๅˆ†็ป„ๆŒ‡็คบๅ™จๆ ทๅผ", + "appearance-group-indicator-style-number": "ๆ•ฐๅญ—", + "appearance-hide-show-speed-description": "่ฐƒๆ•ด Dock ๆ ้š่—/ๆ˜พ็คบๅŠจ็”ป็š„้€Ÿๅบฆใ€‚", + "appearance-hide-show-speed-label": "้š่—/ๆ˜พ็คบ้€Ÿๅบฆ", + "appearance-icon-size-description": "่ฐƒๆ•ด Dock ๆ ็š„ๆ•ดไฝ“ๅคงๅฐใ€‚", + "appearance-icon-size-label": "Dock ๆ ๅคงๅฐ", + "appearance-inactive-indicators-description": "ไธบๆ‰€ๆœ‰ๅบ”็”จๆ˜พ็คบๆŒ‡็คบๅ™จๅฐ่ฏไธธ๏ผŒ่€Œไธไป…ไป…ๆ˜ฏๅฝ“ๅ‰ๆดป่ทƒ็š„ๅบ”็”จใ€‚", + "appearance-inactive-indicators-label": "่ฟ่กŒๆŒ‡็คบๅ™จ", + "appearance-indicator-color-description": "้€‰ๆ‹ฉ้š่—ๅœ้ ๆ ๆŒ‡็คบๅ™จ็š„้ขœ่‰ฒใ€‚", + "appearance-indicator-color-label": "ๆŒ‡็คบๅ™จ้ขœ่‰ฒ", + "appearance-indicator-opacity-description": "่ฐƒๆ•ด้š่—ๅœ้ ๆ ๆŒ‡็คบๅ™จ็š„ไธ้€ๆ˜Žๅบฆใ€‚", + "appearance-indicator-opacity-label": "ๆŒ‡็คบๅ™จไธ้€ๆ˜Žๅบฆ", + "appearance-indicator-thickness-description": "ๅฏ็”จ่พƒ็ฒ—็š„้š่—ๅœ้ ๆ ๆŒ‡็คบๅ™จ๏ผˆไปŽ 3px ๅขžๅŠ ๅˆฐ 6px๏ผ‰ใ€‚", + "appearance-indicator-thickness-label": "่พƒ็ฒ—็š„ๆŒ‡็คบๅ™จ", + "appearance-launcher-icon-description": "้€‰ๆ‹ฉ็”จไบŽ Dock Launcher ็š„ๅ›พๆ ‡ใ€‚", + "appearance-launcher-icon-label": "ๅฏๅŠจๅ™จๅ›พๆ ‡", + "appearance-launcher-position-description": "้€‰ๆ‹ฉๅฏๅŠจๅ™จๅ›พๆ ‡ๅœจๅœ้ ๆ ไธญ็š„ๆ˜พ็คบไฝ็ฝฎใ€‚", + "appearance-launcher-position-end": "็ป“ๆŸ", + "appearance-launcher-position-label": "ๅฏๅŠจๅ™จไฝ็ฝฎ", + "appearance-launcher-position-start": "ๅผ€ๅง‹", + "appearance-launcher-use-distro-logo-description": "ไฝฟ็”จไฝ ็š„ๅ‘่กŒ็‰ˆๆ ‡ๅฟ—ไฝœไธบ Dock Launcher ๅ›พๆ ‡ใ€‚", + "appearance-launcher-use-distro-logo-label": "ไฝฟ็”จๅ‘่กŒ็‰ˆๆ ‡ๅฟ—่€Œ้žๅ›พๆ ‡", + "appearance-pinned-static-description": "ๅง‹็ปˆๅฐ†ๅ›บๅฎš็š„ๅบ”็”จๅ›พๆ ‡ๆŒ‰้™ๆ€้กบๅบๆŽจๅˆฐๅทฆไพงใ€‚", + "appearance-pinned-static-label": "้™ๆ€ๅ›บๅฎšๅบ”็”จ", + "appearance-position-description": "้€‰ๆ‹ฉ Dock ๆ ๅœจๅฑๅน•ไธŠๆ˜พ็คบ็š„ไฝ็ฝฎใ€‚", + "appearance-position-label": "ไฝ็ฝฎ", + "appearance-show-launcher-icon-description": "ๅœจ Dock ไธญๆ˜พ็คบๅบ”็”จ็จ‹ๅบๅฏๅŠจๅ™จๅ›พๆ ‡ใ€‚", + "appearance-show-launcher-icon-label": "ๆ˜พ็คบๅบ”็”จๅฏๅŠจๅ™จ", + "appearance-sit-on-frame-description": "ๅฐ†ๅœ้ ๆ ๅฏน้ฝๅœจๆก†ๆžถ่พนๆก†ๅ†…๏ผŒ่€Œไธๆ˜ฏไฝไบŽ้กถ้ƒจใ€‚", + "appearance-sit-on-frame-label": "ๅœ้ ๆ ไฝไบŽๆก†ๆžถไธŠ", + "appearance-type-attached": "ๅœ้ ", + "appearance-type-description": "้€‰ๆ‹ฉๆตฎๅŠจ่ฏไธธๆ ทๅผๆˆ–้™„็€ๅœจ่พน็ผ˜็š„้™ๆ€ๆ ใ€‚", + "appearance-type-floating": "ๆตฎๅŠจ", + "appearance-type-label": "Dock ๆ ทๅผ", + "enabled-description": "ๅฎŒๅ…จๆ˜พ็คบๆˆ–้š่— Dock ๆ ใ€‚", + "enabled-label": "ๅฏ็”จ Dock ๆ ", + "monitors-desc": "ๅœจ็‰นๅฎšๆ˜พ็คบๅ™จไธŠๆ˜พ็คบ Dock ๆ ใ€‚ๅฆ‚ๆžœๆœช้€‰ๆ‹ฉไปปไฝ•ๆ˜พ็คบๅ™จ๏ผŒๅˆ™้ป˜่ฎคๅœจๆ‰€ๆœ‰ๆ˜พ็คบๅ™จไธŠๆ˜พ็คบใ€‚", + "monitors-only-same-monitor-description": "ไป…ๆ˜พ็คบ Dock ๆ ๆ‰€ๅœจๆ˜พ็คบๅ™จไธŠ็š„ๅบ”็”จ็จ‹ๅบใ€‚", + "monitors-only-same-monitor-label": "ไป…้™ๆฅ่‡ชๅŒไธ€ๆ˜พ็คบๅ™จ็š„ๅบ”็”จ", + "monitors-title": "ๆ˜พ็คบๅ™จๆ˜พ็คบ", + "title": "Dock ๆ " + }, + "general": { + "copy-settings": "ๅคๅˆถ่ฎพ็ฝฎ", + "fonts-default-description": "ๆ•ดไธช็•Œ้ขไฝฟ็”จ็š„ไธป่ฆๅญ—ไฝ“ใ€‚", + "fonts-default-label": "้ป˜่ฎคๅญ—ไฝ“", + "fonts-default-placeholder": "้€‰ๆ‹ฉ้ป˜่ฎคๅญ—ไฝ“...", + "fonts-default-scale-description": "ๅขžๅคงๆˆ–ๅ‡ๅฐๆ ‡ๅ‡†ๆ–‡ๆœฌ็š„ๅฐบๅฏธใ€‚", + "fonts-default-scale-label": "้ป˜่ฎคๅญ—ไฝ“ๅคงๅฐ", + "fonts-default-search-placeholder": "ๆœ็ดขๅญ—ไฝ“...", + "fonts-desc": "้€‰ๆ‹ฉๆ•ดไธช็•Œ้ขไธญไฝฟ็”จ็š„ๅญ—ไฝ“ใ€‚", + "fonts-monospace-description": "็”จไบŽๆ•ฐๅญ—ๅ’Œ็ปŸ่ฎกไฟกๆฏๆ˜พ็คบ็š„็ญ‰ๅฎฝๅญ—ไฝ“ใ€‚", + "fonts-monospace-label": "็ญ‰ๅฎฝๅญ—ไฝ“", + "fonts-monospace-placeholder": "้€‰ๆ‹ฉ็ญ‰ๅฎฝๅญ—ไฝ“...", + "fonts-monospace-scale-description": "ๅขžๅคงๆˆ–ๅ‡ๅฐ็ญ‰ๅฎฝๆ–‡ๆœฌ็š„ๅฐบๅฏธใ€‚", + "fonts-monospace-scale-label": "้ป˜่ฎค็ญ‰ๅฎฝๅญ—ไฝ“ๅคงๅฐ", + "fonts-monospace-search-placeholder": "ๆœ็ดข็ญ‰ๅฎฝๅญ—ไฝ“...", + "fonts-title": "ๅญ—ไฝ“", + "keybinds-conflict-description": "ๆญคๆŒ‰้”ฎ็ป„ๅˆๅทฒๅˆ†้…็ป™ {action}ใ€‚", + "keybinds-conflict-title": "ๆŒ‰้”ฎ็ป‘ๅฎšๅ†ฒ็ช", + "keybinds-description": "้…็ฝฎ้ขๆฟๅ’ŒๅฏๅŠจๅ™จ็š„ๅ…จๅฑ€ๅฏผ่ˆช้”ฎใ€‚", + "keybinds-down": "ไธ‹็งป", + "keybinds-enter": "็กฎ่ฎค / ๆ“ไฝœ", + "keybinds-escape": "ๅ…ณ้—ญ / ่ฟ”ๅ›ž", + "keybinds-left": "ๅทฆ็งป", + "keybinds-modifier-description": "ๅฟซๆท้”ฎๅฟ…้กปๅŒ…ๅซไฟฎ้ฅฐ้”ฎ (Ctrl ๆˆ– Alt)ใ€‚", + "keybinds-modifier-title": "้œ€่ฆไฟฎ้ฅฐ้”ฎ", + "keybinds-remove": "ๅˆ ้™ค", + "keybinds-right": "ๅณ็งป", + "keybinds-title": "ๅฏผ่ˆชๆŒ‰้”ฎ็ป‘ๅฎš", + "keybinds-up": "ไธŠ็งป", + "language-desc": "้€‰ๆ‹ฉๆ‚จๅๅฅฝ็š„ๅบ”็”จ็จ‹ๅบ่ฏญ่จ€ใ€‚", + "language-select-auto-detect": "่‡ชๅŠจๆฃ€ๆต‹", + "language-select-description": "้€‰ๆ‹ฉๅบ”็”จ็จ‹ๅบ็•Œ้ขไธญไฝฟ็”จ็š„่ฏญ่จ€ใ€‚", + "language-select-label": "ๅบ”็”จ็จ‹ๅบ่ฏญ่จ€", + "launch-setup-wizard": "่ฎพ็ฝฎๅ‘ๅฏผ", + "profile-desc": "็ผ–่พ‘ๆ‚จ็š„็”จๆˆท่ฏฆ็ป†ไฟกๆฏๅ’Œๅคดๅƒใ€‚", + "profile-picture-description": "ๅœจๆ•ดไธช็•Œ้ขไธญๆ˜พ็คบๆ‚จ็š„ไธชไบบๅคดๅƒใ€‚", + "profile-picture-label": "ๅคดๅƒ", + "profile-select-avatar": "้€‰ๆ‹ฉๅคดๅƒๅ›พ็‰‡", + "profile-title": "ไธชไบบ่ต„ๆ–™", + "profile-tooltip": "ๅคดๅƒ", + "reverse-scrolling-description": "ๅ่ฝฌ่งฃ้‡Š็š„ๆปšๅŠจๆ–นๅ‘ใ€‚", + "reverse-scrolling-label": "ๅ่ฝฌๆปšๅŠจ", + "screen-corners-desc": "่‡ชๅฎšไน‰ๅฑๅน•่พน่ง’็š„ๅœ†ๅบฆๅ’Œ่ง†่ง‰ๆ•ˆๆžœใ€‚", + "screen-corners-radius-description": "่ฐƒๆ•ดๅฑๅน•ๅœ†่ง’็š„ๅผงๅบฆใ€‚", + "screen-corners-radius-label": "ๅฑๅน•่พน่ง’ๅŠๅพ„", + "screen-corners-radius-reset": "้‡็ฝฎๅฑๅน•ๅœ†่ง’ๅŠๅพ„", + "screen-corners-show-corners-description": "ๅœจๅฑๅน•่พน็ผ˜ๆ˜พ็คบๅœ†่ง’ใ€‚", + "screen-corners-show-corners-label": "ๆ˜พ็คบๅฑๅน•ๅœ†่ง’", + "screen-corners-solid-black-description": "ไฝฟ็”จๅฎžๅฟƒ้ป‘่‰ฒ่€Œ้ž็Šถๆ€ๆ ่ƒŒๆ™ฏ่‰ฒใ€‚", + "screen-corners-solid-black-label": "ๅฎžๅฟƒ้ป‘่‰ฒ่พน่ง’", + "screen-corners-title": "ๅฑๅน•่พน่ง’", + "settings-copied": "่ฎพ็ฝฎๅทฒๅคๅˆถๅˆฐๅ‰ช่ดดๆฟ", + "smooth-scrolling-description": "ไธบๅˆ—่กจๆปšๅŠจๆทปๅŠ ๅŠจ็”ป๏ผŒไปฅ่Žทๅพ—ๆ›ดๆต็•…็š„ๆปš่ฝฎไฝ“้ชŒใ€‚", + "smooth-scrolling-label": "ๅนณๆป‘ๆปšๅŠจ", + "tab-basics": "ๅŸบ็ก€", + "tab-keybinds": "ๆŒ‰้”ฎ็ป‘ๅฎš" + }, + "hooks": { + "color-generation-description": "้ขœ่‰ฒ็”ŸๆˆๅฎŒๆˆๅŽๆ‰ง่กŒ็š„ๅ‘ฝไปค๏ผˆไพ‹ๅฆ‚๏ผŒๅŸบไบŽๅฃ็บธ็š„ไธป้ข˜่ฎพ็ฝฎๅŽ๏ผ‰ใ€‚", + "color-generation-label": "้ขœ่‰ฒๅทฒ็”Ÿๆˆ", + "color-generation-placeholder": "ไพ‹ๅฆ‚๏ผšnotify-send \"้ขœ่‰ฒ\" \"ๅทฒ็”Ÿๆˆ\"", + "info-command-info-description": "โ€ข ๅ‘ฝไปค้€š่ฟ‡ shell ๆ‰ง่กŒ๏ผˆsh -lc๏ผ‰
โ€ข ๅ‘ฝไปคๅœจๅŽๅฐ่ฟ่กŒ๏ผˆๅˆ†็ฆป๏ผ‰
โ€ข ๆต‹่ฏ•ๆŒ‰้’ฎไฝฟ็”จๅฝ“ๅ‰ๅ€ผๆ‰ง่กŒ", + "info-parameters-description": "โ€ข Wallpaper hook: $1 = ๅฃ็บธ่ทฏๅพ„, $2 = ๅฑๅน•ๅ็งฐ, $3 = ไธป้ข˜ (ๆทฑ่‰ฒ/ๆต…่‰ฒ)
โ€ข Theme toggle hook: $1 = true/false (ๆทฑ่‰ฒๆจกๅผ็Šถๆ€)
โ€ข Screen lock/unlock hooks: $1 = lock/unlock (ๅฑๅน•้”ๅฎš็Šถๆ€)
โ€ข Performance mode hooks: ๆ— ๅ‚ๆ•ฐ
โ€ข Session hook: $1 = action (shutdown/reboot)", + "info-parameters-label": "ๅฏ็”จๅ‚ๆ•ฐ", + "noctalia-started-description": "Noctalia ๅŠ ่ฝฝๅฎŒๆˆๅŽ่ฆๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚", + "noctalia-started-label": "Noctalia ๅทฒๅฏๅŠจ", + "noctalia-started-placeholder": "ไพ‹ๅฆ‚๏ผšnotify-send 'Noctalia ๅทฒๅŠ ่ฝฝ'", + "performance-mode-disabled-description": "็ฆ็”จ Noctalia ๆ€ง่ƒฝๆจกๅผๆ—ถๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚", + "performance-mode-disabled-label": "ๆ€ง่ƒฝๆจกๅผๅทฒ็ฆ็”จ", + "performance-mode-disabled-placeholder": "ไพ‹ๅฆ‚๏ผšnotify-send \"ๆ€ง่ƒฝ\" \"ๆจกๅผๅทฒ็ฆ็”จ\"", + "performance-mode-enabled-description": "ๅฏ็”จ Noctalia ๆ€ง่ƒฝๆจกๅผๆ—ถๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚", + "performance-mode-enabled-label": "ๆ€ง่ƒฝๆจกๅผๅทฒๅฏ็”จ", + "performance-mode-enabled-placeholder": "ไพ‹ๅฆ‚๏ผšnotify-send \"ๆ€ง่ƒฝ\" \"ๆจกๅผๅทฒๅฏ็”จ\"", + "screen-lock-description": "ๅฑๅน•้”ๅฎšๆ—ถๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚", + "screen-lock-label": "ๅฑๅน•ๅทฒ้”ๅฎš", + "screen-lock-placeholder": "ไพ‹ๅฆ‚๏ผšnotify-send \"ๅฑๅน•\" \"ๅทฒ้”ๅฎš\"", + "screen-unlock-description": "ๅฑๅน•่งฃ้”ๆ—ถๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚", + "screen-unlock-label": "ๅฑๅน•ๅทฒ่งฃ้”", + "screen-unlock-placeholder": "ไพ‹ๅฆ‚๏ผšnotify-send \"ๅฑๅน•\" \"ๅทฒ่งฃ้”\"", + "session-description": "ๅœจๅ…ณๆœบๆˆ–้‡ๅฏๅ‰ๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚ๆŽฅๆ”ถๆ“ไฝœ็ฑปๅž‹ไฝœไธบ $1๏ผˆๅ…ณๆœบ/้‡ๅฏ๏ผ‰ใ€‚", + "session-label": "ไผš่ฏ็ป“ๆŸ", + "session-placeholder": "ไพ‹ๅฆ‚๏ผšnotify-send \"Session\" \"$1\"", + "system-hooks-desc": "้…็ฝฎๅœจ็ณป็ปŸไบ‹ไปถๅ‘็”Ÿๆ—ถๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚", + "system-hooks-enable-description": "ๅฏ็”จๆˆ–็ฆ็”จๆ‰€ๆœ‰้’ฉๅญๅ‘ฝไปคใ€‚", + "system-hooks-enable-label": "ๅฏ็”จ้’ฉๅญ", + "system-hooks-title": "็ณป็ปŸ้’ฉๅญ", + "theme-changed-description": "ไธป้ข˜ๅœจๆทฑ่‰ฒๅ’Œๆต…่‰ฒๆจกๅผไน‹้—ดๅˆ‡ๆขๆ—ถๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚", + "theme-changed-label": "ไธป้ข˜ๅทฒๆ›ดๆ”น", + "theme-changed-placeholder": "ไพ‹ๅฆ‚๏ผšnotify-send \"ไธป้ข˜\" \"ๅทฒๅˆ‡ๆข\"", + "title": "้’ฉๅญ", + "wallpaper-changed-description": "ๅฃ็บธๆ›ดๆ”นๆ—ถๆ‰ง่กŒ็š„ๅ‘ฝไปคใ€‚", + "wallpaper-changed-label": "ๅฃ็บธๅทฒๆ›ดๆ”น", + "wallpaper-changed-placeholder": "ไพ‹ๅฆ‚๏ผšnotify-send \"ๅฃ็บธ\" \"ๅทฒๆ›ดๆ”น\"" + }, + "idle": { + "custom-add": "ๆทปๅŠ ๅ‘ฝไปค", + "custom-description": "ๅœจไธ€ๆฎตๆ—ถ้—ดไธๆดปๅŠจๅŽ่ฟ่กŒ shell ๅ‘ฝไปคใ€‚", + "custom-entry-command": "ๅ‘ฝไปค", + "custom-entry-delete": "ๅˆ ้™ค", + "custom-entry-edit": "็ผ–่พ‘่‡ชๅฎšไน‰ๅ‘ฝไปค", + "custom-entry-name": "ๅ็งฐ", + "custom-entry-name-placeholder": "ไพ‹ๅฆ‚๏ผšๅ…ณ็ฏ", + "custom-entry-new": "ๆ–ฐๅปบ่‡ชๅฎšไน‰ๅ‘ฝไปค", + "custom-entry-timeout": "็ฉบ้—ฒๆ—ถ้—ด", + "custom-entry-timeout-format": "{count} ็ง’", + "custom-entry-timeout-format-plural": "{count} ็ง’", + "custom-entry-unnamed": "ๆœชๅ‘ฝๅๅ‘ฝไปค", + "custom-label": "่‡ชๅฎšไน‰็ฉบ้—ฒๅ‘ฝไปค", + "enable-description": "ๅœจไธ€ๆฎตๆ—ถ้—ดไธๆดปๅŠจๅŽ๏ผŒ่‡ชๅŠจๅ…ณ้—ญๅฑๅน•ใ€้”ๅฎšๆˆ–ไผ‘็œ ใ€‚", + "enable-label": "ๅฏ็”จ็ฉบ้—ฒ็ฎก็†", + "fade-duration-description": "ๆฏๆฌกๆ“ไฝœ่งฆๅ‘ๅ‰๏ผŒๅฑๅน•ๆธๅ˜ไธบ้ป‘็š„ๅŠจ็”ปๆŒ็ปญๆ—ถ้—ด๏ผˆ็ง’๏ผ‰ใ€‚ไปปไฝ•้ผ ๆ ‡็งปๅŠจ้ƒฝไผšๅ–ๆถˆๆธๅ˜ใ€‚", + "fade-duration-label": "ๆทกๅ…ฅๆทกๅ‡บๆ—ถ้•ฟ", + "lock-description": "้”ๅฑๆฟ€ๆดปๅ‰็š„้žๆดปๅŠจ็ง’ๆ•ฐใ€‚", + "lock-label": "้”ๅฎšๅฑๅน•", + "resume-command-label": "ๆขๅคๅ‘ฝไปค", + "screen-off-description": "ๆ˜พ็คบๅ™จๅ…ณ้—ญๅ‰็š„ไธๆดปๅŠจ็ง’ๆ•ฐใ€‚", + "screen-off-label": "ๅ…ณ้—ญๅฑๅน•", + "status-description": "็”ฑๆททๆˆๅ™จๆŠฅๅ‘Š็š„็ฉบ้—ฒๆ—ถ้—ดใ€‚", + "status-label": "็ฉบ้—ฒๆ—ถ้—ด", + "suspend-description": "็ณป็ปŸๆŒ‚่ตทๅ‰็š„้žๆดปๅŠจ็ง’ๆ•ฐใ€‚", + "tab-behavior": "่กŒไธบ", + "tab-custom": "่‡ชๅฎšไน‰", + "timeouts-description": "่ฎพ็ฝฎไธบ0ไปฅ็ฆ็”จไธ€ไธช้˜ถๆฎตใ€‚ไฟๆŒๅ”ค้†’ๆฟ€ๆดปๆ—ถ๏ผŒ่ถ…ๆ—ถไผšๆš‚ๅœใ€‚", + "timeouts-label": "่ถ…ๆ—ถ", + "title": "็ฉบ้—ฒ", + "unavailable": "ๆญค Compositor ไธๆ”ฏๆŒๅŽŸ็”Ÿ็ฉบ้—ฒ็›‘ๆŽงใ€‚" + }, + "indicator": { + "default-value": "้ป˜่ฎคๅ€ผ๏ผš{value}", + "system-default": "็ณป็ปŸ้ป˜่ฎค" + }, + "launcher": { + "clipboard-desc": "ไปŽๅฏๅŠจๅ™จ่ฎฟ้—ฎๅ’Œ็ฎก็†ไฝ ็š„ๅ‰ช่ดดๆฟๅކๅฒ่ฎฐๅฝ•ใ€‚", + "execute-desc": "้…็ฝฎๅบ”็”จ็จ‹ๅบ็š„ๅฏๅŠจๆ–นๅผใ€‚", + "execute-title": "ๆ‰ง่กŒ", + "settings-annotation-tool-description": "็‚นๅ‡ปๅ‰ช่ดดๆฟๅކๅฒ่ฎฐๅฝ•ไธญ็š„ๆณจ้‡ŠๆŒ‰้’ฎๆ—ถ่ฆ่ฟ่กŒ็š„ๅ‘ฝไปคใ€‚ๅ›พๅƒๅฐ†่ขซ็ฎก้“ไผ ่พ“ๅˆฐๆญคๅ‘ฝไปคใ€‚", + "settings-annotation-tool-label": "ๆ ‡ๆณจๅทฅๅ…ท", + "settings-annotation-tool-placeholder": "ไพ‹ๅฆ‚๏ผš'gradia'๏ผŒ'satty -f -'", + "settings-auto-paste-description": "่‡ชๅŠจ็ฒ˜่ดด้€‰ๅฎš็š„ๅ‰ช่ดดๆฟ้กน็›ฎใ€‚้œ€่ฆ wtypeใ€‚", + "settings-auto-paste-label": "่‡ชๅŠจ็ฒ˜่ดด", + "settings-clip-chips-description": "ๆ˜พ็คบไธ€ไธชๆ ‡็ญพๆ ๏ผŒไปฅๆŒ‰็ฑปๅž‹๏ผˆๅ›พ็‰‡ใ€้“พๆŽฅใ€ๆ–‡ไปถใ€ไปฃ็ ็ญ‰๏ผ‰่ฟ‡ๆปคๅ‰ช่ดดๆฟๅކๅฒ่ฎฐๅฝ•ใ€‚", + "settings-clip-chips-label": "ๅฏ็”จ็ฑปๅˆซๆ ‡็ญพ", + "settings-clip-preview-description": "ๅœจไฝฟ็”จ >clip ๅ‘ฝไปคๆ—ถๆ˜พ็คบๅ‰ช่ดดๆฟๅ†…ๅฎน็š„้ข„่งˆใ€‚", + "settings-clip-preview-label": "ๅฏ็”จๅ‰ช่ดดๆฟ้ข„่งˆ", + "settings-clip-smart-icons-description": "ไธบ้“พๆŽฅใ€ๆ–‡ไปถใ€้ขœ่‰ฒๅ’Œไปฃ็ ๆ˜พ็คบ็‰นๅฎšๅ›พๆ ‡๏ผŒ่€Œไธๆ˜ฏ้€š็”จ็š„ๅ‰ช่ดดๆฟๅ›พๆ ‡ใ€‚", + "settings-clip-smart-icons-label": "ๅฏ็”จๆ™บ่ƒฝๅ›พๆ ‡", + "settings-clip-wrap-text-description": "ๅฐ†ๅ‰ช่ดดๆฟๅˆ—่กจไธญ็š„ๆ–‡ๆœฌๆข่กŒๆ˜พ็คบ๏ผŒ่€Œไธๆ˜ฏๆˆชๆ–ญใ€‚", + "settings-clip-wrap-text-label": "็Žฏ็ป•ๅ‰ช่ดดๆฟๆ–‡ๆœฌ", + "settings-clipboard-history-description": "ไปŽๅฏๅŠจๅ™จ่ฎฟ้—ฎไน‹ๅ‰ๅคๅˆถ็š„้กน็›ฎใ€‚", + "settings-clipboard-history-label": "ๅฏ็”จๅ‰ช่ดดๆฟๅކๅฒ่ฎฐๅฝ•", + "settings-clipboard-watch-image-description": "ไผ ้€’็ป™ wl-paste ไปฅ่ฟ›่กŒๅ›พๅƒๆ›ดๆ”น็š„ๅฎŒๆ•ดๅ‘ฝไปคๅญ—็ฌฆไธฒ๏ผˆ้œ€่ฆ้‡ๅฏ๏ผ‰ใ€‚", + "settings-clipboard-watch-image-label": "ๅ›พๅƒ็›‘่ง†ๅ‘ฝไปค", + "settings-clipboard-watch-text-description": "ไผ ้€’็ป™ wl-paste ไปฅ่ฟ›่กŒๆ–‡ๆœฌๆ›ดๆ”น็š„ๅฎŒๆ•ดๅ‘ฝไปคๅญ—็ฌฆไธฒ๏ผˆ้œ€่ฆ้‡ๅฏ๏ผ‰ใ€‚", + "settings-clipboard-watch-text-label": "ๆ–‡ๆœฌ็›‘่ง†ๅ‘ฝไปค", + "settings-custom-launch-prefix-description": "ไฝฟ็”จ่‡ชๅฎšไน‰ๅฏๅŠจๅ™จๅ‰็ผ€ๅ‘ฝไปค๏ผˆไพ‹ๅฆ‚๏ผš'runapp'็”จไบŽsystemd้›†ๆˆ๏ผ‰ใ€‚", + "settings-custom-launch-prefix-enabled-description": "ไฝฟ็”จ่‡ชๅฎšไน‰ๅ‰็ผ€ๅฏๅŠจๅบ”็”จ็จ‹ๅบ๏ผŒ่€Œไธๆ˜ฏ้ป˜่ฎคๆ–นๆณ•ใ€‚", + "settings-custom-launch-prefix-enabled-label": "ๅฏ็”จ่‡ชๅฎšไน‰ๅฏๅŠจๅ‰็ผ€", + "settings-custom-launch-prefix-label": "่‡ชๅฎšไน‰ๅฏๅŠจๅ‰็ผ€", + "settings-density-description": "่ฐƒๆ•ดๅบ”็”จ็จ‹ๅบๅ›พๆ ‡ๅคงๅฐๅ’Œ Launcher ๅฏ†ๅบฆใ€‚", + "settings-density-label": "ๅฏ†ๅบฆ", + "settings-desc": "่‡ชๅฎšไน‰ๅฏๅŠจๅ™จ็š„่กŒไธบๅ’Œๅค–่ง‚ใ€‚", + "settings-enable-session-search-description": "ๅœจ Launcher ไธญๆœ็ดขๆ—ถๆ˜พ็คบไผš่ฏๆ“ไฝœ๏ผˆ้”ๅฎšใ€ๅ…ณๆœบใ€้‡ๅฏ็ญ‰๏ผ‰ใ€‚", + "settings-enable-session-search-label": "ๅฏ็”จไผš่ฏๆœ็ดข", + "settings-enable-settings-search-description": "ๅœจๅฏๅŠจๅ™จไธญๆœ็ดขๆ—ถๆ˜พ็คบ่ฎพ็ฝฎ็ป“ๆžœใ€‚", + "settings-enable-settings-search-label": "ๅฏ็”จ่ฎพ็ฝฎๆœ็ดข", + "settings-enable-windows-search-description": "ๆœ็ดขๅนถ่š็„ฆๆดปๅŠจ็ช—ๅฃใ€‚", + "settings-enable-windows-search-label": "ๅฏ็”จ็ช—ๅฃๆœ็ดข", + "settings-grid-view-description": "ไปฅ็ฝ‘ๆ ผๅธƒๅฑ€่€Œ้žๅˆ—่กจๆ˜พ็คบ้กน็›ฎใ€‚", + "settings-icon-mode-description": "ไฝฟ็”จๆœฌๅœฐ็ณป็ปŸๅ›พๆ ‡่€Œไธๆ˜ฏ Tabler ๅ›พๆ ‡ใ€‚", + "settings-icon-mode-label": "ไฝฟ็”จๆœฌๅœฐๅ›พๆ ‡", + "settings-ignore-mouse-input-description": "็ฆ็”จๅฏๅŠจๅ™จไธญ็š„้ผ ๆ ‡ไบคไบ’ๅ’Œๆปš่ฝฎใ€‚", + "settings-ignore-mouse-input-label": "ๅฟฝ็•ฅ้ผ ๆ ‡่พ“ๅ…ฅ", + "settings-overlay-layer-description": "ๅœจ่ฆ†็›–ๅฑ‚ไธŠๆ˜พ็คบๅฏๅŠจๅ™จ๏ผŒไฝไบŽๅ…จๅฑ็ช—ๅฃไน‹ไธŠใ€‚ๅฏ็”จๆ—ถ๏ผŒๅฏๅŠจๅ™จไธไผš้™„ๅŠ ๅˆฐๆ ใ€‚", + "settings-overlay-layer-label": "ๅœจๅ…จๅฑไธŠๆ–นๆ˜พ็คบ", + "settings-position-description": "้€‰ๆ‹ฉๅฏๅŠจๅ™จ้ขๆฟๅ‡บ็Žฐ็š„ไฝ็ฝฎใ€‚", + "settings-show-categories-description": "ๆ˜พ็คบ็”จไบŽ็ญ›้€‰ๅบ”็”จ็จ‹ๅบ็š„็ฑปๅˆซๆ ‡็ญพใ€‚", + "settings-show-categories-label": "ๆ˜พ็คบ็ฑปๅˆซ", + "settings-show-icon-background-description": "ๅœจๅ›พๆ ‡ๅŽๆ˜พ็คบไธ€ไธชๅœ†่ง’็Ÿฉๅฝข่ƒŒๆ™ฏใ€‚", + "settings-show-icon-background-label": "ๆ˜พ็คบๅ›พๆ ‡่ƒŒๆ™ฏ", + "settings-sort-by-usage-description": "ๅฏ็”จๅŽ๏ผŒ็ปๅธธๅฏๅŠจ็š„ๅบ”็”จ็จ‹ๅบๅฐ†ๆ˜พ็คบๅœจๅˆ—่กจ้ฆ–ไฝใ€‚", + "settings-sort-by-usage-label": "ๆŒ‰ไฝฟ็”จ้ข‘็އๆŽ’ๅบ", + "settings-terminal-command-description": "ๅฏๅŠจ็ปˆ็ซฏ็š„ๅ‘ฝไปคใ€‚ไพ‹ๅฆ‚๏ผš'kitty -e'ๆˆ–'gnome-terminal --'ใ€‚", + "settings-terminal-command-label": "็ปˆ็ซฏๅ‘ฝไปค", + "settings-use-app2unit-description": "ไฝฟ็”จๆ›ฟไปฃๅฏๅŠจๆ–นๆณ•ไปฅๆ›ดๅฅฝๅœฐ็ฎก็†ๅบ”็”จ็จ‹ๅบ่ฟ›็จ‹ๅนถ้˜ฒๆญข้—ฎ้ข˜ใ€‚", + "settings-use-app2unit-label": "ไฝฟ็”จ App2Unit ๅฏๅŠจๅบ”็”จ็จ‹ๅบ", + "settings-view-mode-description": "้€‰ๆ‹ฉๅฏๅŠจๅ™จๆก็›ฎ็š„ๅธƒๅฑ€ใ€‚", + "settings-view-mode-label": "่ง†ๅ›พๆจกๅผ", + "title": "ๅฏๅŠจๅ™จ" + }, + "location": { + "auto-locate-description": "ไฝฟ็”จๆ‚จ็š„ IP ๅœฐๅ€ๅฎšๆœŸ่‡ชๅŠจๆฃ€ๆต‹ๆ‚จ็š„ไฝ็ฝฎใ€‚", + "auto-locate-label": "่‡ชๅŠจๅฎšไฝ", + "calendar-cards-desc": "ๅœจๆ—ฅๅކ้ขๆฟไธญ็ป„็ป‡ๅ’Œๅฏ็”จ/็ฆ็”จๅก็‰‡ใ€‚", + "calendar-cards-title": "ๆ—ฅๅކๅก", + "calendar-header-label": "ๆ—ฅๅކๆ ‡้ข˜", + "calendar-month-label": "ๆ—ฅๅކๆœˆ", + "date-time-12hour-format-description": "ๅœจ้”ๅฑๅ’Œๆ—ฅๅކไธŠไปฅ 12 ๅฐๆ—ถๅˆถๆ ผๅผๆ˜พ็คบๆ—ถ้—ดใ€‚็Šถๆ€ๆ ๆ—ถ้’Ÿๆœ‰ๅ…ถ่‡ชๅทฑ็š„่ฎพ็ฝฎใ€‚", + "date-time-12hour-format-label": "ไฝฟ็”จ12ๅฐๆ—ถๅˆถๆ—ถ้—ดๆ ผๅผ", + "date-time-desc": "่‡ชๅฎšไน‰ๆ—ฅๆœŸๅ’Œๆ—ถ้—ด็š„ๆ˜พ็คบๆ–นๅผใ€‚", + "date-time-first-day-of-week-automatic": "่‡ชๅŠจ๏ผˆไฝฟ็”จ็ณป็ปŸๅŒบๅŸŸ่ฎพ็ฝฎ๏ผ‰", + "date-time-first-day-of-week-description": "้€‰ๆ‹ฉๆ—ฅๅކไธญไธ€ๅ‘จ็š„่ตทๅง‹ๆ—ฅใ€‚", + "date-time-first-day-of-week-label": "ไธ€ๅ‘จ็š„็ฌฌไธ€ๅคฉ", + "date-time-show-events-description": "ๅœจๆ—ฅๅކ้ขๆฟไธญๆ˜พ็คบไบ‹ไปถใ€‚", + "date-time-show-events-label": "ๆ˜พ็คบๆ—ฅๅކไบ‹ไปถ", + "date-time-title": "ๆ—ฅๆœŸๅ’Œๆ—ถ้—ด", + "date-time-use-analog-description": "ๅœจๆ—ฅๅކ็ช—ๅฃไธŠๆ˜พ็คบๆจกๆ‹Ÿๆ—ถ้’Ÿใ€‚", + "date-time-use-analog-label": "ไฝฟ็”จๆจกๆ‹Ÿๆ—ถ้’Ÿๆ ทๅผ", + "date-time-week-numbers-description": "ๅœจๆ—ฅๅކไธญๆ˜พ็คบไธ€ๅนดไธญ็š„็ฌฌๅ‡ ๅ‘จ๏ผˆไพ‹ๅฆ‚๏ผš็ฌฌ 38 ๅ‘จ๏ผ‰ใ€‚", + "date-time-week-numbers-label": "ๆ˜พ็คบๅ‘จๆ•ฐ", + "geolocate-now-button": "็ซ‹ๅณๅฎšไฝ", + "location-desc": "้€š่ฟ‡่ฎพ็ฝฎๆ‚จ็š„ไฝ็ฝฎ่Žทๅ–ๅ‡†็กฎ็š„ๅคฉๆฐ”ๅ’Œ่ฐƒๅบฆๅคœ้—ดๆจกๅผใ€‚", + "location-search-description": "ไพ‹ๅฆ‚๏ผšๅคšไผฆๅคš, ๅฎ‰ๅคง็•ฅ็œ", + "location-search-label": "ๆœ็ดขไฝ็ฝฎ", + "location-search-placeholder": "่พ“ๅ…ฅไฝ็ฝฎๅ็งฐ", + "location-title": "ๆ‚จ็š„ไฝ็ฝฎ", + "weather-desc": "้€‰ๆ‹ฉๆ‚จๅ–œๆฌข็š„ๆธฉๅบฆๅ•ไฝใ€‚", + "weather-enabled-description": "ๅœจๆ•ดไธช็•Œ้ขไธญๆ˜พ็คบๅคฉๆฐ”ไฟกๆฏๅนถ่Žทๅ–ๅคฉๆฐ”ๆ•ฐๆฎใ€‚็ฆ็”จๅŽ๏ผŒๆ‰€ๆœ‰ๅคฉๆฐ”ๅ…ƒ็ด ๅฐ†่ขซ้š่—๏ผŒไธไผš่ฟ›่กŒ็ฝ‘็ปœ่ฏทๆฑ‚ใ€‚", + "weather-enabled-label": "ๅฏ็”จๅคฉๆฐ”", + "weather-fahrenheit-description": "ไปฅๅŽๆฐๅบฆ่€Œ้žๆ‘„ๆฐๅบฆๆ˜พ็คบๆธฉๅบฆใ€‚", + "weather-fahrenheit-label": "ไปฅๅŽๆฐๅบฆๆ˜พ็คบๆธฉๅบฆ (ยฐF)", + "weather-hide-city-description": "ๅœจๆ•ดไธช็•Œ้ขไธญ้š่—ๅคฉๆฐ”ๆ˜พ็คบไธญ็š„ๅŸŽๅธ‚ๅ็งฐใ€‚", + "weather-hide-city-label": "้š่—ๅŸŽๅธ‚ๅ็งฐ", + "weather-hide-timezone-description": "ๅœจๆ•ดไธช็•Œ้ขไธญ๏ผŒ้š่—ๅคฉๆฐ”ๆ˜พ็คบไธญ็š„ๆ—ถๅŒบ็ผฉๅ†™ใ€‚", + "weather-hide-timezone-label": "้š่—ๆ—ถๅŒบ", + "weather-show-effects-description": "ๅœจๅคฉๆฐ”ๅก็‰‡ไธŠๆ˜พ็คบ้ขๅค–็š„่ง†่ง‰ๆ•ˆๆžœ๏ผˆๅฆ‚้›จใ€้›ชๆˆ–้—ช็”ต๏ผ‰ใ€‚", + "weather-show-effects-label": "ๆ˜พ็คบๅคฉๆฐ”ๆ•ˆๆžœ", + "weather-show-in-calendar-description": "็›ดๆŽฅๅœจๆ‚จ็š„ๆ—ฅๅކ่ง†ๅ›พไธญๆ˜พ็คบๆฏๆ—ฅๅคฉๆฐ”้ข„ๆŠฅใ€‚", + "weather-show-in-calendar-label": "ๅœจๆ—ฅๅކไธญๆ˜พ็คบๅคฉๆฐ”", + "weather-talia-mascot-always-description": "ๅง‹็ปˆไฝฟ็”จ Talia emojis ่€Œไธๆ˜ฏ tabler iconsใ€‚", + "weather-talia-mascot-always-label": "ๅง‹็ปˆๆ˜พ็คบTaliaๅคฉๆฐ”่กจๆƒ…" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "ๅฝ“ fprintd๏ผˆๆŒ‡็บน่ฎค่ฏ๏ผ‰ๅค„ไบŽๆดปๅŠจ็Šถๆ€ๆ—ถ๏ผŒๆญค้€‰้กนๅ…่ฎธๆ‚จไฝฟ็”จๅฏ†็ ่€Œ้žๆŒ‡็บน็™ปๅฝ•ใ€‚", + "allow-password-with-fprintd-label": "ๅ…่ฎธไฝฟ็”จ fprintd ่ฟ›่กŒๅฏ†็ ็™ปๅฝ•", + "auto-start-auth-description": "่‡ชๅŠจๅฏๅŠจๆŒ‡็บน้ชŒ่ฏ๏ผŒๆ— ้œ€ๆŒ‰้”ฎๆˆ–ๅ•ๅ‡ปๆŒ‰้’ฎใ€‚", + "auto-start-auth-label": "่‡ชๅŠจๅฏๅŠจ่บซไปฝ้ชŒ่ฏ", + "clock-format-description": "ไฝฟ็”จๆ—ฅๆœŸ/ๆ—ถ้—ด่ฏญๆณ•ไปค็‰Œ่‡ชๅฎšไน‰ๆ—ถ้’Ÿๆ ผๅผใ€‚", + "clock-format-label": "ๆ—ถ้’Ÿๆ ผๅผ", + "clock-style-analog": "ๆจกๆ‹Ÿ", + "clock-style-custom": "่‡ชๅฎšไน‰", + "clock-style-description": "้€‰ๆ‹ฉ้”ๅฑๆ—ถ้’Ÿ็š„่ง†่ง‰ๆ ทๅผใ€‚", + "clock-style-digital": "ๆ•ฐๅญ—", + "clock-style-label": "ๆ—ถ้’Ÿๆ ทๅผ", + "compact-lockscreen-description": "ไป…ๆ˜พ็คบ็™ปๅฝ•่พ“ๅ…ฅๅ’Œ็ณป็ปŸๆŽงๅˆถ๏ผŒ้š่—ๅคฉๆฐ”ๅ’Œๅช’ไฝ“ๅฐ้ƒจไปถใ€‚", + "compact-lockscreen-label": "็ดงๅ‡‘ๅž‹้”ๅฑ", + "enable-lockscreen-media-controls-description": "ๅœจ้”ๅฑ็•Œ้ขๆ˜พ็คบไบคไบ’ๅผๅช’ไฝ“ๆ’ญๆ”พๆŽงไปถใ€‚", + "enable-lockscreen-media-controls-label": "้”ๅฑๅช’ไฝ“ๆŽงไปถ", + "lock-on-suspend-description": "็ณป็ปŸๆŒ‚่ตทๆ—ถ่‡ชๅŠจ้”ๅฎšๅฑๅน•ใ€‚", + "lock-on-suspend-label": "ๆŒ‚่ตทๆ—ถ้”ๅฎš", + "lock-screen-animations-description": "ๅฏ็”จๆˆ–็ฆ็”จ้”ๅฑๅŠจ็”ปใ€‚", + "lock-screen-animations-label": "้”ๅฑๅŠจ็”ป", + "lock-screen-blur-strength-description": "ๅฏน้”ๅฑๅฃ็บธๅบ”็”จๆจก็ณŠๆ•ˆๆžœใ€‚", + "lock-screen-blur-strength-label": "้”ๅฑๆจก็ณŠๅผบๅบฆ", + "lock-screen-tint-strength-description": "ๅฐ†่‰ฒ่ฐƒๅ ๅŠ ๅบ”็”จไบŽ้”ๅฑๅฃ็บธใ€‚", + "lock-screen-tint-strength-label": "้”ๅฑ็€่‰ฒๅผบๅบฆ", + "monitors-desc": "ๅœจๆŒ‡ๅฎšๆ˜พ็คบๅ™จไธŠๆ˜พ็คบ้”ๅฑใ€‚ๅฆ‚ๆžœๆœช้€‰ๆ‹ฉ๏ผŒๅˆ™้ป˜่ฎคไธบๆ‰€ๆœ‰ๆ˜พ็คบๅ™จใ€‚", + "password-chars-description": "็”จไบŽ้š่—ๅฏ†็ ็š„ๅฏ็ˆฑๅ›พๆ ‡ใ€‚", + "password-chars-label": "้šๆœบๅฏ†็ ๅ›พๆ ‡", + "show-hibernate-description": "ๅœจ็”ตๆบๆŽงๅˆถไธญๆ˜พ็คบ'ไผ‘็œ '้€‰้กนใ€‚", + "show-hibernate-label": "ๆ˜พ็คบไผ‘็œ ", + "show-session-buttons-description": "ๅ…่ฎธไปŽ้”ๅฑ่ฎฟ้—ฎ็”ตๆบ่ฎพ็ฝฎใ€‚", + "show-session-buttons-label": "็”ตๆบๆŽงๅˆถ", + "title": "้”ๅฑ" + }, + "network": { + "bluetooth-description": "ๅฏ็”จ่“็‰™็ฎก็†ใ€‚", + "bluetooth-rssi-polling-description": "ๅฎšๆœŸ้€š่ฟ‡ bluetoothctl ้‡‡ๆ ทๅทฒ่ฟžๆŽฅ่ฎพๅค‡็š„ RSSI ๅ€ผใ€‚ๅฏ่ƒฝๅนถ้ž้€‚็”จไบŽๆ‰€ๆœ‰่ฎพๅค‡๏ผ›ๅฏ็”จๅŽๅ ็”จๆžๅฐ‘่ต„ๆบใ€‚", + "bluetooth-rssi-polling-label": "่“็‰™ไฟกๅท่ฝฎ่ฏข", + "desc": "็ฎก็† Wi-Fi ๅ’Œ่“็‰™่ฟžๆŽฅใ€‚", + "wifi-description": "็ฎก็†ๆ— ็บฟ็ฝ‘็ปœ๏ผˆ้œ€่ฆ NetworkManager๏ผ‰ใ€‚" + }, + "notifications": { + "duration-critical-urgency-description": "้ซ˜ไผ˜ๅ…ˆ็บง้€š็ŸฅไฟๆŒๅฏ่ง็š„ๆ—ถ้—ดใ€‚", + "duration-critical-urgency-label": "้ซ˜็ดงๆ€ฅๅบฆ", + "duration-desc": "ๆ นๆฎ็ดงๆ€ฅ็บงๅˆซ้…็ฝฎ้€š็ŸฅไฟๆŒๅฏ่ง็š„ๆ—ถ้—ดใ€‚", + "duration-low-urgency-description": "ไฝŽไผ˜ๅ…ˆ็บง้€š็ŸฅไฟๆŒๅฏ่ง็š„ๆ—ถ้—ดใ€‚", + "duration-low-urgency-label": "ไฝŽ็ดงๆ€ฅๅบฆ", + "duration-normal-urgency-description": "ๆญฃๅธธไผ˜ๅ…ˆ็บง้€š็ŸฅไฟๆŒๅฏ่ง็š„ๆ—ถ้—ดใ€‚", + "duration-normal-urgency-label": "ๆญฃๅธธ็ดงๆ€ฅๅบฆ", + "duration-respect-expire-description": "ไฝฟ็”จ้€š็Ÿฅไธญ่ฎพ็ฝฎ็š„่ฟ‡ๆœŸ่ถ…ๆ—ถใ€‚", + "duration-respect-expire-label": "ๅฐŠ้‡่ฟ‡ๆœŸ่ถ…ๆ—ถ", + "duration-title": "้€š็ŸฅๆŒ็ปญๆ—ถ้—ด", + "history-clear-dismiss-description": "ๅ…ณ้—ญๆ—ถไปŽๅކๅฒ่ฎฐๅฝ•ไธญๆธ…้™ค้€š็Ÿฅใ€‚", + "history-clear-dismiss-label": "ๅ…ณ้—ญๆ—ถๆธ…้™ค", + "history-critical-urgency-description": "ๅฐ†ๅ…ณ้”ฎไผ˜ๅ…ˆ็บง้€š็Ÿฅไฟๅญ˜ๅˆฐๅކๅฒ่ฎฐๅฝ•ใ€‚", + "history-critical-urgency-label": "ๅฐ†้ซ˜็ดงๆ€ฅๅบฆไฟๅญ˜ๅˆฐๅކๅฒ่ฎฐๅฝ•", + "history-desc": "ๆ นๆฎ็ดงๆ€ฅ็บงๅˆซๆŽงๅˆถๅ“ชไบ›้€š็Ÿฅไฟๅญ˜ๅˆฐๅކๅฒ่ฎฐๅฝ•ใ€‚", + "history-low-urgency-description": "ๅฐ†ไฝŽไผ˜ๅ…ˆ็บง้€š็Ÿฅไฟๅญ˜ๅˆฐๅކๅฒ่ฎฐๅฝ•ใ€‚", + "history-low-urgency-label": "ๅฐ†ไฝŽ็ดงๆ€ฅๅบฆไฟๅญ˜ๅˆฐๅކๅฒ่ฎฐๅฝ•", + "history-normal-urgency-description": "ๅฐ†ๆญฃๅธธไผ˜ๅ…ˆ็บง้€š็Ÿฅไฟๅญ˜ๅˆฐๅކๅฒ่ฎฐๅฝ•ใ€‚", + "history-normal-urgency-label": "ๅฐ†ๆญฃๅธธ็ดงๆ€ฅๅบฆไฟๅญ˜ๅˆฐๅކๅฒ่ฎฐๅฝ•", + "monitors-desc": "ๅœจ็‰นๅฎšๆ˜พ็คบๅ™จไธŠๆ˜พ็คบ้€š็Ÿฅใ€‚ๅฆ‚ๆžœๆœช้€‰ๆ‹ฉ๏ผŒๅˆ™้ป˜่ฎคไธบๅ…จ้ƒจใ€‚", + "rules-action-block": "็ฆๆญข", + "rules-action-block-desc": "ๅฎŒๅ…จ็ฆๆญข้€š็Ÿฅ", + "rules-action-hide": "้š่—", + "rules-action-hide-desc": "ๆ— ๅผน็ช—๏ผŒๆ— ๅฃฐ้Ÿณ๏ผŒๆทปๅŠ ๅˆฐๅކๅฒ่ฎฐๅฝ•ใ€‚", + "rules-action-label": "ๆ“ไฝœ", + "rules-action-mute": "้™้Ÿณ", + "rules-action-mute-desc": "ๆ— ๅฃฐ้Ÿณ๏ผŒไปๆ˜พ็คบๅผน็ช—ๅนถ่ฎฐๅฝ•ๅœจๅކๅฒไธญใ€‚", + "rules-add": "ๆทปๅŠ ่ง„ๅˆ™", + "rules-delete": "ๅˆ ้™ค่ง„ๅˆ™", + "rules-description": "ๅŒน้…ๅบ”็”จๅ็งฐๆˆ–ๅ†…ๅฎนใ€‚่ง„ๅˆ™ๆŒ‰้กบๅบๆฃ€ๆŸฅ๏ผŒๅนถๅบ”็”จ็ฌฌไธ€ไธชๅŒน้…้กนใ€‚", + "rules-edit": "็ผ–่พ‘่ง„ๅˆ™", + "rules-label": "็ญ›้€‰่ง„ๅˆ™", + "rules-pattern-label": "ๆจกๅผ", + "rules-pattern-placeholder": "firefox, discord, ๆˆ– /regex/", + "rules-tab": "่ง„ๅˆ™", + "settings-always-on-top-description": "ๅœจๅ…จๅฑ็ช—ๅฃๅ’Œๅ…ถไป–ๅ›พๅฑ‚ไธŠๆ–นๆ˜พ็คบ้€š็Ÿฅใ€‚", + "settings-background-opacity-description": "่ฐƒๆ•ด้€š็Ÿฅ่ƒŒๆ™ฏ็š„้€ๆ˜Žๅบฆใ€‚", + "settings-density-description": "้€‰ๆ‹ฉ้€š็Ÿฅๅกๅฏ†ๅบฆใ€‚", + "settings-density-label": "ๅฏ†ๅบฆ", + "settings-desc": "้…็ฝฎ้€š็Ÿฅ็š„ๅค–่ง‚ๅ’Œ่กŒไธบใ€‚", + "settings-do-not-disturb-description": "ๅฏ็”จๅŽ็ฆ็”จๆ‰€ๆœ‰้€š็Ÿฅๅผนๅ‡บ็ช—ๅฃใ€‚", + "settings-enabled-description": "ๅฏ็”จๆˆ–็ฆ็”จ้€š็ŸฅๅฎˆๆŠค่ฟ›็จ‹๏ผŒ้œ€่ฆ้‡ๅฏ Noctalia shellใ€‚", + "settings-enabled-label": "ๅฏ็”จ้€š็Ÿฅ", + "settings-location-description": "้€š็Ÿฅๅœจๅฑๅน•ไธŠๅ‡บ็Žฐ็š„ไฝ็ฝฎใ€‚", + "settings-markdown-description": "ไฝฟ็”จ Markdown ๆ ผๅผๆธฒๆŸ“้€š็Ÿฅๅ†…ๅฎนใ€‚", + "settings-markdown-label": "ๅฏ็”จ Markdown", + "sounds-desc": "้…็ฝฎ้€š็Ÿฅ้Ÿณๆ•ˆๅ’Œ้Ÿณ้‡ใ€‚", + "sounds-enabled-description": "ๅฏ็”จๆ”ถๅˆฐ้€š็Ÿฅๆ—ถ็š„้Ÿณๆ•ˆใ€‚", + "sounds-enabled-label": "ๅฏ็”จ้€š็Ÿฅๅฃฐ้Ÿณ", + "sounds-excluded-apps-description": "ๅฏนไบŽ่‡ชๅธฆๅ†…็ฝฎ้Ÿณๆ•ˆ็š„็‰นๅฎšๅบ”็”จ๏ผŒ่ทณ่ฟ‡ๆ’ญๆ”พๅทฒ้…็ฝฎ็š„้€š็Ÿฅ้Ÿณๆ•ˆใ€‚", + "sounds-excluded-apps-label": "ๆŽ’้™ค็š„ๅบ”็”จ็จ‹ๅบ", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "ไธบ้ซ˜ไผ˜ๅ…ˆ็บง้€š็Ÿฅๆ’ญๆ”พ็š„ๅฃฐ้Ÿณๆ–‡ไปถ่ทฏๅพ„ใ€‚", + "sounds-files-critical-label": "้ซ˜็ดงๆ€ฅๅบฆๅฃฐ้Ÿณ", + "sounds-files-critical-select-title": "้€‰ๆ‹ฉ้ซ˜็ดงๆ€ฅๅบฆๅฃฐ้Ÿณๆ–‡ไปถ", + "sounds-files-desc": "ไธบไธๅŒ็š„้€š็Ÿฅ็ดงๆ€ฅ็บงๅˆซ้…็ฝฎ่‡ชๅฎšไน‰ๅฃฐ้Ÿณๆ–‡ไปถใ€‚", + "sounds-files-low-description": "ไธบไฝŽไผ˜ๅ…ˆ็บง้€š็Ÿฅๆ’ญๆ”พ็š„ๅฃฐ้Ÿณๆ–‡ไปถ่ทฏๅพ„ใ€‚", + "sounds-files-low-label": "ไฝŽ็ดงๆ€ฅๅบฆๅฃฐ้Ÿณ", + "sounds-files-low-select-title": "้€‰ๆ‹ฉไฝŽ็ดงๆ€ฅๅบฆๅฃฐ้Ÿณๆ–‡ไปถ", + "sounds-files-normal-description": "ไธบๆญฃๅธธไผ˜ๅ…ˆ็บง้€š็Ÿฅๆ’ญๆ”พ็š„ๅฃฐ้Ÿณๆ–‡ไปถ่ทฏๅพ„ใ€‚", + "sounds-files-normal-label": "ๆญฃๅธธ็ดงๆ€ฅๅบฆๅฃฐ้Ÿณ", + "sounds-files-normal-select-title": "้€‰ๆ‹ฉๆญฃๅธธ็ดงๆ€ฅๅบฆๅฃฐ้Ÿณๆ–‡ไปถ", + "sounds-files-placeholder": "่พ“ๅ…ฅๅฃฐ้Ÿณๆ–‡ไปถ่ทฏๅพ„", + "sounds-files-select-file": "้€‰ๆ‹ฉๅฃฐ้Ÿณๆ–‡ไปถ", + "sounds-files-title": "ๅฃฐ้Ÿณๆ–‡ไปถ", + "sounds-files-unified-description": "ไธบ้€š็Ÿฅๆ’ญๆ”พ็š„ๅฃฐ้Ÿณๆ–‡ไปถ่ทฏๅพ„ใ€‚", + "sounds-files-unified-label": "้€š็Ÿฅๅฃฐ้Ÿณ", + "sounds-files-unified-select-title": "้€‰ๆ‹ฉ้€š็Ÿฅๅฃฐ้Ÿณๆ–‡ไปถ", + "sounds-separate-description": "ไธบไฝŽใ€ๆญฃๅธธๅ’Œ้ซ˜ไผ˜ๅ…ˆ็บง้€š็Ÿฅไฝฟ็”จไธๅŒ็š„ๅฃฐ้Ÿณๆ–‡ไปถใ€‚", + "sounds-separate-label": "ๆŒ‰ไผ˜ๅ…ˆ็บงไฝฟ็”จไธๅŒๅฃฐ้Ÿณ", + "sounds-title": "ๅฃฐ้Ÿณ่ฎพ็ฝฎ", + "sounds-unavailable-description": "ๅฎ‰่ฃ… Qt6 Multimedia ไปฅๅฏ็”จ้€š็Ÿฅๅฃฐ้Ÿณใ€‚", + "sounds-unavailable-label": "้€š็Ÿฅๅฃฐ้Ÿณไธๅฏ็”จ", + "sounds-volume-description": "่ฐƒๆ•ด้€š็Ÿฅๅฃฐ้Ÿณ็š„้Ÿณ้‡็บงๅˆซใ€‚", + "sounds-volume-label": "ๅฃฐ้Ÿณ้Ÿณ้‡", + "toast-battery-description": "ๅฝ“็”ตๆฑ ็”ต้‡ไฝŽไบŽๆญค็™พๅˆ†ๆฏ”ๆ—ถๆ˜พ็คบ่ญฆๅ‘Šใ€‚", + "toast-battery-label": "ไฝŽ็”ต้‡่ญฆๅ‘Š", + "toast-desc": "้…็ฝฎๆฐ”ๆณก้€š็Ÿฅ็š„ๅค–่ง‚ๅ’Œ่กŒไธบใ€‚", + "toast-keyboard-description": "้”ฎ็›˜ๅธƒๅฑ€ๆ”นๅ˜ๆ—ถๆ˜พ็คบไธ€ไธช Toastใ€‚", + "toast-keyboard-label": "้”ฎ็›˜ๅธƒๅฑ€", + "toast-media-description": "ๅช’ไฝ“ๆ’ญๆ”พ็Šถๆ€ๆ”นๅ˜ๆ—ถๆ˜พ็คบไธ€ไธช Toastใ€‚", + "toast-media-label": "ๅช’ไฝ“" + }, + "osd": { + "always-on-top-description": "ๅœจๅ…จๅฑ็ช—ๅฃๅ’Œๅ…ถไป–ๅ›พๅฑ‚ไน‹ไธŠๆ˜พ็คบ OSDใ€‚", + "always-on-top-label": "ๅง‹็ปˆ็ฝฎ้กถ", + "background-opacity-description": "่ฐƒๆ•ด OSD ่ƒŒๆ™ฏ็š„ไธ้€ๆ˜Žๅบฆใ€‚", + "background-opacity-label": "่ƒŒๆ™ฏไธ้€ๆ˜Žๅบฆ", + "description": "้…็ฝฎ็”จไบŽๅœจๅฑๅน•ไบฎๅบฆใ€้Ÿณ้‡็ญ‰็Šถๆ€ๅ‘็”Ÿๅ˜ๅŒ–ๆ—ถๆไพ›ๅ้ฆˆ็š„ OSDใ€‚", + "duration-auto-hide-description": "่ฐƒๆ•ด OSD ๆถˆๅคฑๅ‰็š„ๆ—ถ้—ดใ€‚", + "duration-auto-hide-label": "่‡ชๅŠจ้š่—่ถ…ๆ—ถ", + "duration-desc": "ๅฑๅน•ๆ˜พ็คบ่‡ชๅŠจ้š่—ๅ‰ไฟๆŒๅฏ่ง็š„ๆ—ถ้•ฟใ€‚", + "duration-title": "่‡ชๅŠจ้š่—่ถ…ๆ—ถ", + "enabled-description": "ๅฎžๆ—ถๆ˜พ็คบ้Ÿณ้‡ไธŽไบฎๅบฆๅ˜ๅŒ–ใ€‚", + "enabled-label": "ๅฏ็”จ็Šถๆ€ๅ้ฆˆ๏ผˆOSD๏ผ‰", + "events-desc": "้€‰ๆ‹ฉๅ“ชไบ›ไบ‹ไปถ่งฆๅ‘ๅฑๅน•ๆ˜พ็คบใ€‚", + "general-desc": "้…็ฝฎ OSD ็š„ๅฏ่งๆ€งๅ’Œ่กŒไธบใ€‚", + "location-description": "OSD ๅ‡บ็Žฐ็š„ไฝ็ฝฎใ€‚", + "monitors-desc": "ๅœจ็‰นๅฎšๆ˜พ็คบๅ™จไธŠๆ˜พ็คบ OSDใ€‚่‹ฅๆœช้€‰ๆ‹ฉ๏ผŒๅˆ™้ป˜่ฎคๅ…จ้ƒจใ€‚", + "title": "็Šถๆ€ๅ้ฆˆ", + "types-brightness-description": "ๅฝ“ๅฑๅน•ไบฎๅบฆๅ‘็”Ÿๅ˜ๅŒ–ๆ—ถๆ˜พ็คบ OSDใ€‚", + "types-brightness-label": "ไบฎๅบฆ", + "types-custom-text-description": "ๆ˜พ็คบๆฅ่‡ช IPC ็š„่‡ชๅฎšไน‰ๆ–‡ๆœฌๆถˆๆฏ็š„ OSDใ€‚", + "types-custom-text-label": "่‡ชๅฎšไน‰ๆ–‡ๆœฌ", + "types-desc": "้€‰ๆ‹ฉ่งฆๅ‘ OSD ็š„ไบ‹ไปถใ€‚", + "types-input-volume-description": "ๅฝ“้บฆๅ…‹้ฃŽ้Ÿณ้‡ๅ‘็”Ÿๅ˜ๅŒ–ๆ—ถๆ˜พ็คบ OSDใ€‚", + "types-input-volume-label": "่พ“ๅ…ฅ้Ÿณ้‡", + "types-lockkey-description": "ๅฝ“ๅˆ‡ๆขๅคงๅ†™้”ๅฎšใ€ๆ•ฐๅญ—้”ๅฎšๆˆ–ๆปšๅŠจ้”ๅฎšๆ—ถๆ˜พ็คบ OSDใ€‚", + "types-lockkey-label": "้”ๅฎš้”ฎ", + "types-media-description": "ๅฝ“ๅช’ไฝ“ๆ’ญๆ”พ็Šถๆ€ๆ”นๅ˜๏ผˆๆ’ญๆ”พใ€ๆš‚ๅœใ€่ทณ่ฟ‡๏ผ‰ๆ—ถๆ˜พ็คบ OSD ใ€‚", + "types-media-label": "ๅช’ไฝ“ๆ’ญๆ”พ", + "types-title": "OSD ่งฆๅ‘ไบ‹ไปถ", + "types-volume-description": "ๅฝ“้Ÿณ้ข‘่พ“ๅ‡บ้Ÿณ้‡ๅ‘็”Ÿๅ˜ๅŒ–ๆ—ถๆ˜พ็คบ OSDใ€‚", + "types-volume-label": "่พ“ๅ‡บ้Ÿณ้‡" + }, + "plugins": { + "auto-update": "่‡ชๅŠจๆ›ดๆ–ฐๆ’ไปถ", + "auto-update-description": "ๅœจ shell ๅฏๅŠจๆ—ถ่‡ชๅŠจๆ›ดๆ–ฐๆ‰€ๆœ‰ๆ’ไปถใ€‚", + "available-description": "ๆต่งˆๅนถๅฎ‰่ฃ…ๆฅ่‡ชๅทฒ้…็ฝฎๆบ็š„ๆ’ไปถใ€‚", + "available-label": "ๅฏ็”จๆ’ไปถ", + "available-no-plugins-description": "ๆฃ€ๆŸฅไฝ ็š„ๆ’ไปถๆบๆˆ–ๅˆทๆ–ฐๅˆ—่กจใ€‚", + "available-no-plugins-label": "ๆฒกๆœ‰ๅฏ็”จ็š„ๆ’ไปถ", + "check-for-updates": "ๆฃ€ๆŸฅๆ›ดๆ–ฐ", + "checking-for-updates": "ๆญฃๅœจๆฃ€ๆŸฅๆ›ดๆ–ฐ...", + "collision-already-installed": "ๆญคๆ’ไปถๅทฒๅฎ‰่ฃ…", + "collision-custom-version-exists": "ๆฅ่‡ชโ€œ{source}โ€็š„่‡ชๅฎšไน‰็‰ˆๆœฌๅทฒๅฎ‰่ฃ…", + "collision-official-version-exists": "ๆญคๆ’ไปถ็š„ๅฎ˜ๆ–น็‰ˆๆœฌๅทฒๅฎ‰่ฃ…", + "development-disable": "็ฆ็”จๆญคๆ’ไปถ็š„ๅผ€ๅ‘ๆจกๅผ", + "development-enable": "ไธบๆญคๆ’ไปถๅฏ็”จๅผ€ๅ‘ๆจกๅผใ€‚", + "filter-downloaded": "ๅทฒไธ‹่ฝฝ", + "filter-not-downloaded": "ๆœชไธ‹่ฝฝ", + "filter-tags-description": "ๆŒ‰็ฑปๅˆซๆˆ–ไธ‹่ฝฝ็Šถๆ€็ญ›้€‰ๆ’ไปถใ€‚", + "filter-tags-label": "ๆ ‡็ญพ", + "hot-reload-description": "ๅฝ“ๆ’ไปถๆ–‡ไปถๅ‘็”Ÿๆ›ดๆ”นๆ—ถ่‡ชๅŠจ้‡ๆ–ฐๅŠ ่ฝฝๆ’ไปถใ€‚ๅฏนๆ’ไปถๅผ€ๅ‘ๅพˆๆœ‰็”จใ€‚", + "hot-reload-label": "็ƒญ้‡่ฝฝ๏ผˆๅผ€ๅ‘ๆจกๅผ๏ผ‰", + "hot-reloaded": "ๅทฒ้‡ๆ–ฐๅŠ ่ฝฝๆ’ไปถ๏ผš{name}", + "install-error": "ๅฎ‰่ฃ…ๅคฑ่ดฅ๏ผš{error}", + "install-incompatible": "{plugin} ้œ€่ฆ Noctalia v{version} ๆˆ–ๆ›ด้ซ˜็‰ˆๆœฌ", + "install-success": "ๆˆๅŠŸๅฎ‰่ฃ… {plugin}", + "installed-description": "็ฎก็†ๅ’Œ้…็ฝฎๆ‰€ๆœ‰ๆœฌๅœฐๅฎ‰่ฃ…็š„ๆ’ไปถใ€‚", + "installed-label": "ๅทฒๅฎ‰่ฃ…ๆ’ไปถ", + "installed-no-plugins-description": "ไปŽโ€œๅฏ็”จโ€้ƒจๅˆ†ๅฎ‰่ฃ…ๆ’ไปถใ€‚", + "installed-no-plugins-label": "ๆœชๅฎ‰่ฃ…ๆ’ไปถ", + "installing": "ๆญฃๅœจๅฎ‰่ฃ… {plugin}...", + "notify-updates": "ๆ’ไปถๆ›ดๆ–ฐ้€š็Ÿฅ", + "notify-updates-description": "ๅฝ“ๆœ‰ๆ–ฐ็š„ๆ’ไปถๆ›ดๆ–ฐๅฏ็”จๆ—ถ๏ผŒๆ˜พ็คบ้€š็Ÿฅๅผน็ช—ใ€‚", + "open-plugin-page": "ๆ‰“ๅผ€ๆ’ไปถ้กต้ข", + "open-plugins-tab": "ๆ‰“ๅผ€ๆ’ไปถ่ฎพ็ฝฎ", + "plugin-settings-title": "{plugin} ่ฎพ็ฝฎ", + "refresh-refreshing": "ๆญฃๅœจๅˆทๆ–ฐๆ’ไปถๅˆ—่กจ...", + "refresh-tooltip": "ๅˆทๆ–ฐๅฏ็”จๆ’ไปถ", + "settings-error-not-loaded": "ๆ’ไปถๆœชๅŠ ่ฝฝ", + "settings-saved": "ๆ’ไปถ่ฎพ็ฝฎๅทฒไฟๅญ˜", + "settings-tooltip": "ๆ’ไปถ่ฎพ็ฝฎ", + "source-custom": "่‡ชๅฎšไน‰ๆบ", + "sources-add-custom": "ๆทปๅŠ ่‡ชๅฎšไน‰ๅญ˜ๅ‚จๅบ“", + "sources-add-dialog-description": "ๆทปๅŠ ไธ€ไธช GitHub ไป“ๅบ“ไฝœไธบๆ’ไปถๆบใ€‚", + "sources-add-dialog-error": "ๆ— ๆณ•ๆทปๅŠ ๆ’ไปถๆบ", + "sources-add-dialog-name": "ไป“ๅบ“ๅ็งฐ", + "sources-add-dialog-name-placeholder": "ๆˆ‘็š„่‡ชๅฎšไน‰ๆ’ไปถ", + "sources-add-dialog-success": "ๆ’ไปถๆบๆทปๅŠ ๆˆๅŠŸ", + "sources-add-dialog-title": "ๆทปๅŠ ๆ’ไปถๆบ", + "sources-add-dialog-url": "ไป“ๅบ“ URL", + "sources-description": "็ฎก็†ๆ’ไปถไป“ๅบ“ใ€‚", + "sources-placeholder": "ๆˆ‘็š„้…ทไป“ๅบ“", + "sources-remove-tooltip": "็งป้™คๆ’ไปถๆบ", + "title": "ๆ’ไปถ", + "translations-reloaded": "ๅทฒ้‡ๆ–ฐๅŠ ่ฝฝ็ฟป่ฏ‘๏ผš{name}", + "uninstall-dialog-description": "ไฝ ็กฎๅฎš่ฆๅธ่ฝฝ {plugin} ๅ—๏ผŸ ่ฟ™ๅฐ†็งป้™คๆ‰€ๆœ‰ๆ’ไปถๆ•ฐๆฎใ€‚", + "uninstall-dialog-title": "ๅธ่ฝฝๆ’ไปถ", + "uninstall-error": "ๅธ่ฝฝๅคฑ่ดฅ๏ผš{error}", + "uninstall-success": "ๆˆๅŠŸๅธ่ฝฝ {plugin}", + "uninstalling": "ๆญฃๅœจๅธ่ฝฝ {plugin}...", + "update-all": "ๅ…จ้ƒจๆ›ดๆ–ฐ๏ผˆ{count}๏ผ‰", + "update-all-success": "ๆ‰€ๆœ‰ๆ’ไปถๅทฒๆˆๅŠŸๆ›ดๆ–ฐ", + "update-available": "ๆ–ฐๆ’ไปถๆ›ดๆ–ฐๅฏ็”จ", + "update-available-plural": "ๆ–ฐๆ’ไปถๆ›ดๆ–ฐๅฏ็”จ๏ผˆ{count}ไธช๏ผ‰", + "update-error": "ๆ›ดๆ–ฐๆ’ไปถๅคฑ่ดฅ๏ผš{plugin}๏ผš{error}", + "update-pending": "v{current} โ†’ v{new} (้œ€่ฆ Noctalia v{required})", + "update-success": "ๅทฒๅฐ† {plugin} ๆ›ดๆ–ฐ่‡ณ v{version}", + "update-version": "v{current} โ†’ v{new}", + "updating": "ๆญฃๅœจๆ›ดๆ–ฐ..." + }, + "region": { + "title": "ๅœฐๅŒบ" + }, + "session-menu": { + "countdown-duration-description": "่ฎพ็ฝฎๅ€’่ฎกๆ—ถ่ฎกๆ—ถๅ™จๅœจๆ‰ง่กŒ็”ตๆบๆ“ไฝœไน‹ๅ‰ๆŒ็ปญๅคš้•ฟๆ—ถ้—ดใ€‚", + "countdown-duration-label": "ๅ€’่ฎกๆ—ถๆŒ็ปญๆ—ถ้—ด", + "enable-countdown-description": "ๅœจๆ‰ง่กŒ็”ตๆบๆ“ไฝœไน‹ๅ‰ๆ˜พ็คบๅ€’่ฎกๆ—ถ่ฎกๆ—ถๅ™จใ€‚", + "enable-countdown-label": "ๅฏ็”จๅ€’่ฎกๆ—ถ่ฎกๆ—ถๅ™จ", + "entries-desc": "่‡ชๅฎšไน‰ไผš่ฏ่œๅ•ไธญๆ˜พ็คบๅ“ชไบ›็”ตๆบๆ“ไฝœๅŠๅ…ถ้กบๅบใ€‚", + "entries-title": "็”ตๆบๆ“ไฝœ", + "entry-settings-command-description": "ไธบๆญคๆ“ไฝœๆ‰ง่กŒ็š„่‡ชๅฎšไน‰ๅ‘ฝไปคใ€‚็•™็ฉบๅˆ™ไฝฟ็”จ้ป˜่ฎค็ณป็ปŸๅ‘ฝไปคใ€‚", + "entry-settings-command-placeholder": "ไพ‹ๅฆ‚๏ผšsystemctl poweroff", + "entry-settings-default-command-lock": "ๅ†…้ƒจ้”ๅฑ๏ผˆๆ— ๅ‘ฝไปค๏ผ‰", + "entry-settings-default-command-logout": "ๅ†…้ƒจๆณจ้”€๏ผˆๆ— ๅ‘ฝไปค๏ผ‰", + "entry-settings-default-info-description": "ๅฆ‚ๆžœๆฒกๆœ‰ๆŒ‡ๅฎš่‡ชๅฎšไน‰ๅ‘ฝไปค๏ผŒๅฐ†ไฝฟ็”จ้ป˜่ฎค็š„็ณป็ปŸๅ‘ฝไปคใ€‚", + "entry-settings-default-info-label": "้ป˜่ฎคๅ‘ฝไปค", + "entry-settings-keybind-description": "ๅฝ“ไผš่ฏ่œๅ•ๆ‰“ๅผ€ๆ—ถ๏ผŒๅˆ†้…ไธ€ไธชๆŒ‰้”ฎ็ป„ๅˆๆฅ่งฆๅ‘ๆญคๆ“ไฝœใ€‚", + "entry-settings-keybind-placeholder": "ๆŒ‰ไธ‹ๅฝ•ๅˆถไปฅๅˆ†้…ไธ€ไธช้”ฎ...", + "entry-settings-keybind-recording": "ๅฝ•ๅˆถไธญ... ๆŒ‰ไปปๆ„็ป„ๅˆ้”ฎ", + "entry-settings-title": "้…็ฝฎ {entry}", + "entry-settings-tooltip": "้…็ฝฎๅ‘ฝไปค", + "general-desc": "้…็ฝฎไผš่ฏ่œๅ•้ขๆฟ็š„่กŒไธบๅ’Œๅค–่ง‚ใ€‚", + "large-buttons-layout-description": "้€‰ๆ‹ฉไผš่ฏ่œๅ•ๆŒ‰้’ฎ็š„ๆ˜พ็คบๆ–นๅผใ€‚", + "large-buttons-layout-label": "ๅคงๆŒ‰้’ฎๅธƒๅฑ€", + "large-buttons-style-description": "ไปฅ็ฝ‘ๆ ผๅธƒๅฑ€ๆ˜พ็คบๅธฆๆœ‰ๅคงๆŒ‰้’ฎ็š„ไผš่ฏ่œๅ•ใ€‚", + "large-buttons-style-label": "ๅคงๆŒ‰้’ฎๆ ทๅผ", + "position-description": "้€‰ๆ‹ฉไผš่ฏ่œๅ•้ขๆฟๆ‰“ๅผ€ๆ—ถๅ‡บ็Žฐ็š„ไฝ็ฝฎใ€‚", + "show-header-description": "ๅœจไผš่ฏ่œๅ•้กถ้ƒจๆ˜พ็คบๆ ‡้ข˜ๅ’Œๅ…ณ้—ญๆŒ‰้’ฎใ€‚", + "show-header-label": "ๆ˜พ็คบๆ ‡้ข˜", + "show-keybinds-description": "ๅœจไผš่ฏ้€‰้กนไธŠๆ˜พ็คบๅฟซๆท้”ฎๆ็คบใ€‚", + "show-keybinds-label": "ๆ˜พ็คบๅฟซๆท้”ฎ" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "ๅœจ Noctalia ๆ€ง่ƒฝๆจกๅผๅฏ็”จๆ—ถไฟๆŒๆกŒ้ขๅฐ้ƒจไปถๅฏ่งใ€‚", + "noctalia-performance-disable-desktop-widgets-label": "ๅœจๆ€ง่ƒฝๆจกๅผไธ‹ๅฏ็”จๆกŒ้ขๅฐ้ƒจไปถ", + "noctalia-performance-disable-wallpaper-description": "ๅœจๅฏ็”จ Noctalia ๆ€ง่ƒฝๆจกๅผๆ—ถ๏ผŒไฟๆŒๆกŒ้ขใ€ๆฆ‚่งˆๅ’Œ้”ๅฑๅฃ็บธๅฏ่งใ€‚", + "noctalia-performance-disable-wallpaper-label": "ๅœจๆ€ง่ƒฝๆจกๅผไธ‹ๅฏ็”จๅฃ็บธๆธฒๆŸ“", + "title": "็ณป็ปŸ" + }, + "system-monitor": { + "critical-color-label": "ไธฅ้‡้ขœ่‰ฒ", + "custom-highlight-colors-title-label": "่‡ชๅฎšไน‰้ซ˜ไบฎ้ขœ่‰ฒ", + "disk-available-label": "ๅฏ็”จ็ฃ็›˜็ฉบ้—ด", + "disk-section-label": "็ฃ็›˜ไฝฟ็”จ็އ", + "enable-dgpu-monitoring-description": "่ญฆๅ‘Š๏ผš่ฟ™ๅฐ†ๅ”ค้†’ๆ‚จ็š„็‹ฌ็ซ‹ๆ˜พๅก (NVIDIA/AMD)๏ผŒ่ฟ™ๅฏ่ƒฝไผšๆ˜พ่‘—ๅฝฑๅ“้…ๅค‡ๆททๅˆๆ˜พๅก็š„็ฌ”่ฎฐๆœฌ็”ต่„‘็š„็”ตๆฑ ็ปญ่ˆชๆ—ถ้—ดใ€‚", + "enable-dgpu-monitoring-label": "ๅฏ็”จ็‹ฌ็ซ‹ๆ˜พๅก็›‘ๆŽง", + "external-monitor-description": "่พ“ๅ…ฅๅฏๅŠจๅค–้ƒจ็ณป็ปŸ็›‘่ง†ๅ™จๅบ”็”จ็จ‹ๅบๆ—ถ่ฆๆ‰ง่กŒ็š„ๅ‘ฝไปคๆˆ–ๅบ”็”จ็จ‹ๅบ่ทฏๅพ„ใ€‚", + "external-monitor-label": "ๅค–้ƒจ็ณป็ปŸ็›‘่ง†ๅ™จๅ‘ฝไปค", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "้…็ฝฎ็ณป็ปŸ็›‘่ง†ๅ™จ็š„่กŒไธบๅ’Œๅค–่ง‚ใ€‚", + "gpu-section-label": "GPUๆธฉๅบฆ", + "highlight-colors-section-label": "้ซ˜ไบฎ้ขœ่‰ฒ", + "polling-interval-label": "่ฝฎ่ฏข้—ด้š”", + "polling-section-description": "้…็ฝฎๆฏไธช็ณป็ปŸๆŒ‡ๆ ‡็š„ๆ›ดๆ–ฐ้ข‘็އใ€‚", + "polling-section-label": "่ฝฎ่ฏข้—ด้š”", + "threshold-critical": "ไธฅ้‡", + "threshold-warning": "่ญฆๅ‘Š", + "thresholds-section-description": "ไธบๆฏไธช็ณป็ปŸๆŒ‡ๆ ‡่ฐƒๆ•ด่ญฆๅ‘Š/ไธฅ้‡้˜ˆๅ€ผๅ’Œ่ฝฎ่ฏข้—ด้š”ใ€‚", + "use-custom-highlight-colors-description": "็ฆ็”จๆ—ถๅฐ†ไฝฟ็”จไธป้ข˜้ป˜่ฎค้ซ˜ไบฎ้ขœ่‰ฒใ€‚", + "use-custom-highlight-colors-label": "ไฝฟ็”จ่‡ชๅฎšไน‰้ซ˜ไบฎ้ขœ่‰ฒ", + "warning-color-label": "่ญฆๅ‘Š้ขœ่‰ฒ" + }, + "user-interface": { + "allow-panels-without-bar-description": "ๅฏ็”จๅŽ๏ผŒ้ขๆฟๅฏไปฅๅœจไปปไฝ•ๅฑๅน•ไธŠๆ‰“ๅผ€ใ€‚็ฆ็”จๅŽ๏ผŒ้ขๆฟๅฐ†ไป…ๅœจๆœ‰ๆ ็š„ๅฑๅน•ไธŠๆ‰“ๅผ€๏ผŒ่ฟ™ๅฏไปฅๅ‡ๅฐ‘ๅ†…ๅญ˜ไฝฟ็”จใ€‚", + "allow-panels-without-bar-label": "ๅ…่ฎธๅœจๆฒกๆœ‰ๆ ็š„ๅฑๅน•ไธŠๆ˜พ็คบ้ขๆฟ", + "animation-disable-description": "็ฆ็”จๆ‰€ๆœ‰ๅŠจ็”ปไปฅ่Žทๅพ—ๆ›ดๅฟซใ€ๆ›ดๆต็•…็š„ไฝ“้ชŒใ€‚", + "animation-disable-label": "็ฆ็”จ UI ๅŠจ็”ป", + "animation-speed-description": "่ฐƒๆ•ดๅ…จๅฑ€ๅŠจ็”ป้€Ÿๅบฆใ€‚", + "animation-speed-label": "ๅŠจ็”ป้€Ÿๅบฆ", + "animation-speed-reset": "้‡็ฝฎๅŠจ็”ป้€Ÿๅบฆ", + "appearance-desc": "่‡ชๅฎšไน‰่ง†่ง‰ๅ…ƒ็ด ๏ผŒๅฆ‚ๆ็คบไฟกๆฏใ€่พนๆก†ๅ’Œ้˜ดๅฝฑใ€‚", + "blur-behind-description": "ไฝฟ็”จๅˆๆˆๅ™จๆจก็ณŠๅ่ฎฎ๏ผŒๆจก็ณŠๆ ๅ’Œ้ขๆฟๅŽ้ข็š„ๅŒบๅŸŸใ€‚", + "blur-behind-label": "่ƒŒๆ™ฏๆจก็ณŠ", + "box-border-description": "ๆ˜พ็คบๅ†…ๅฎนๅŒบๅŸŸๅ‘จๅ›ด็š„่ฝฎๅป“ใ€‚", + "box-border-label": "ๅฎนๅ™จ่ฝฎๅป“", + "box-border-radius-description": "่ฐƒๆ•ดไธป่ฆๅธƒๅฑ€้ƒจๅˆ†๏ผˆๅฆ‚ไพง่พนๆ ใ€ๅก็‰‡ๅ’Œๅ†…ๅฎน้ขๆฟ๏ผ‰็š„ๅœ†่ง’็จ‹ๅบฆใ€‚", + "box-border-radius-label": "ๅฎนๅ™จๅŠๅพ„", + "control-border-radius-description": "ๆŽงๅˆถไบคไบ’ๅ…ƒ็ด ็š„ๆ›ฒ็އ๏ผŒๅŒ…ๆ‹ฌๆŒ‰้’ฎใ€ๅผ€ๅ…ณๅ’Œๆ–‡ๆœฌๅญ—ๆฎตใ€‚", + "control-border-radius-label": "่พ“ๅ…ฅๅŠๅพ„", + "desc": "่‡ชๅฎšไน‰็•Œ้ข็š„ๅค–่ง‚ใ€ๆ„Ÿ่ง‰ๅ’Œ่กŒไธบใ€‚", + "dim-desktop-description": "ๅฝ“้ขๆฟๆˆ–่œๅ•ๆ‰“ๅผ€ๆ—ถ๏ผŒๆกŒ้ขๅ˜ๆš—ใ€‚", + "dim-desktop-label": "ๆ˜ๆš—็š„ๆกŒ้ข", + "dimmer-opacity-description": "่ฎพ็ฝฎๆกŒ้ขๅ˜ๆš—ๆ—ถ็š„ไธ้€ๆ˜Žๅบฆใ€‚", + "dimmer-opacity-label": "ๅ˜ๆš—ๆกŒ้ขไธ้€ๆ˜Žๅบฆ", + "panel-background-opacity-description": "่ฎพ็ฝฎๆ‰€ๆœ‰้ขๆฟ๏ผˆๆ ใ€ๅฏๅŠจๅ™จใ€่ฎพ็ฝฎ็ญ‰๏ผ‰็š„่ƒŒๆ™ฏไธ้€ๆ˜Žๅบฆใ€‚", + "panel-background-opacity-label": "้ขๆฟ่ƒŒๆ™ฏไธ้€ๆ˜Žๅบฆ", + "panels-attached-to-bar-description": "้ขๆฟ้”ๅฎšๅˆฐ่พนๆ ๅ’Œๅฑๅน•่พน็ผ˜๏ผŒๅฝขๆˆๆ— ็ผๅค–่ง‚๏ผŒๅนถๅ…ทๆœ‰ๆ—ถๅฐš็š„ๅ€’่ง’ใ€‚", + "panels-attached-to-bar-label": "ๅฐ†้ขๆฟๅฏน้ฝๅˆฐ่พน็ผ˜", + "panels-overlay-description": "็กฎไฟ้ขๆฟๅ’Œๆ ไฟๆŒๅฏ่ง๏ผŒๅณไฝฟๅœจๅ…จๅฑๅบ”็”จ็จ‹ๅบไธŠไนŸๆ˜ฏๅฆ‚ๆญคใ€‚", + "panels-overlay-label": "ๅฐ†้ขๆฟๅ’Œๆ ไฟๆŒๅœจ้กถ้ƒจ", + "scaling-description": "ๆ›ดๆ”น้€š็”จ็”จๆˆท็•Œ้ขๅคงๅฐ๏ผŒไธๅŒ…ๆ‹ฌๆ ใ€‚", + "scaling-label": "็•Œ้ข็ผฉๆ”พ", + "scrollbar-always-visible-description": "ๅฝ“ๅ†…ๅฎนๅฏๆปšๅŠจๆ—ถ๏ผŒไฟๆŒๆปšๅŠจๆกๅง‹็ปˆๅฏ่ง๏ผŒ่€Œไธๆ˜ฏไป…ๅœจๆ‚ฌๅœๆ—ถๆ˜พ็คบใ€‚", + "scrollbar-always-visible-label": "ๅง‹็ปˆๆ˜พ็คบๆปšๅŠจๆก", + "settings-panel-header": "่ฎพ็ฝฎ้ขๆฟ", + "settings-panel-mode-description": "้€‰ๆ‹ฉใ€Œ่ฎพ็ฝฎใ€้ขๆฟ็š„ๅธƒๅฑ€๏ผˆๅฏ่ƒฝ้œ€่ฆ้‡ๆ–ฐๆ‰“ๅผ€ใ€Œ่ฎพ็ฝฎใ€้ขๆฟๆ‰ไผš็”Ÿๆ•ˆ๏ผ‰ใ€‚", + "settings-panel-mode-label": "ใ€Œ่ฎพ็ฝฎใ€้ขๆฟๆจกๅผ", + "settings-panel-sidebar-card-style-description": "็”จๅœ†่ง’ๅกซๅ……่ƒŒๆ™ฏๅŒ…่ฃน่ฎพ็ฝฎไพง่พนๆ ใ€‚", + "settings-panel-sidebar-card-style-label": "ไพง่พนๆ ๅก็‰‡ๆ ทๅผ", + "shadows-description": "ๅฏ็”จๆกๅฝขๅ›พๅ’Œ้ขๆฟไธ‹็š„้˜ดๅฝฑใ€‚", + "shadows-direction-description": "้€‰ๆ‹ฉ้˜ดๅฝฑๆŠ•ๅฐ„็š„ไฝ็ฝฎใ€‚", + "shadows-direction-label": "้˜ดๅฝฑๆ–นๅ‘", + "shadows-label": "้˜ดๅฝฑ", + "title": "็”จๆˆท็•Œ้ข", + "tooltips-description": "ๅฏ็”จๆˆ–็ฆ็”จๆ•ดไธช็•Œ้ข็š„ๆ็คบไฟกๆฏใ€‚", + "tooltips-label": "ๆ˜พ็คบๆ็คบไฟกๆฏ๏ผˆTooltip๏ผ‰", + "translucent-widgets-description": "ไฝฟ้ขๆฟๅ†…็š„ๆŒ‰้’ฎใ€้€‰้กนๅกๅ’Œๅ…ถไป–ๅฐ้ƒจไปถๅŠ้€ๆ˜Žใ€‚", + "translucent-widgets-label": "ๅŠ้€ๆ˜Žๅฐ้ƒจไปถ" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "ๆŒ‰ๅญ—ๆฏ้กบๅบ", + "automation-change-mode-description": "้€‰ๆ‹ฉ่‡ชๅŠจๆ›ดๆ”นๆ—ถๅฆ‚ไฝ•้€‰ๆ‹ฉๅฃ็บธใ€‚", + "automation-change-mode-label": "ๆ›ดๆ”นๆจกๅผ", + "automation-custom-interval-description": "ไปฅ HH:MM ๆ ผๅผ่พ“ๅ…ฅๆ—ถ้—ด๏ผˆไพ‹ๅฆ‚๏ผš01:30๏ผ‰ใ€‚", + "automation-custom-interval-label": "่‡ชๅฎšไน‰้—ด้š”", + "automation-interval-description": "่‡ชๅŠจๆ›ดๆ”นๅฃ็บธ็š„้ข‘็އใ€‚", + "automation-interval-label": "ๅฃ็บธ้—ด้š”", + "automation-random-wallpaper-description": "ๆŒ‰ๅ›บๅฎšๆ—ถ้—ด้—ด้š”้šๆœบๆ›ดๆ”นๅฃ็บธใ€‚", + "automation-scheduled-change-description": "ๆŒ‰ๅ›บๅฎšๆ—ถ้—ด้—ด้š”่‡ชๅŠจๆ›ดๆ”นๅฃ็บธใ€‚", + "automation-scheduled-change-label": "ๅฎšๆ—ถๆ›ดๆ”น", + "look-feel-edge-smoothness-description": "ๅฏน่ฟ‡ๆธก่พน็ผ˜ๅบ”็”จๆŸ”ๅ’Œใ€็พฝๅŒ–็š„ๆ•ˆๆžœใ€‚", + "look-feel-edge-smoothness-label": "่ฝฏๅŒ–่ฟ‡ๆธก่พน็ผ˜", + "look-feel-fill-color-description": "้€‰ๆ‹ฉๅฏ่ƒฝๅ‡บ็Žฐๅœจๅฃ็บธๅŽ้ข็š„ๅกซๅ……้ขœ่‰ฒใ€‚", + "look-feel-fill-mode-description": "้€‰ๆ‹ฉๅ›พๅƒๅบ”ๅฆ‚ไฝ•็ผฉๆ”พไปฅๅŒน้…ๆ˜พ็คบๅ™จ็š„ๅˆ†่พจ็އใ€‚", + "look-feel-fill-mode-label": "ๅกซๅ……ๆจกๅผ", + "look-feel-skip-startup-transition-description": "ๅœจ shell ๅฏๅŠจๆ—ถ่ทณ่ฟ‡ๅฃ็บธๅŠจ็”ปใ€‚", + "look-feel-skip-startup-transition-label": "่ทณ่ฟ‡ๅฏๅŠจ่ฟ‡ๆธกๅŠจ็”ป", + "look-feel-title": "ๅค–่ง‚ไธŽไฝ“้ชŒ", + "look-feel-transition-duration-description": "่ฟ‡ๆธกๅŠจ็”ป็š„ๆŒ็ปญๆ—ถ้—ด๏ผˆ็ง’๏ผ‰ใ€‚", + "look-feel-transition-duration-label": "่ฟ‡ๆธกๆŒ็ปญๆ—ถ้—ด", + "look-feel-transition-type-description": "ๅˆ‡ๆขๅฃ็บธๆ—ถ็š„ๅŠจ็”ป็ฑปๅž‹ใ€‚", + "look-feel-transition-type-label": "่ฟ‡ๆธก็ฑปๅž‹", + "settings-clear-cache-button": "ๆธ…้™ค็ผ“ๅญ˜", + "settings-clear-cache-description": "ๆธ…้™ค็ผ“ๅญ˜็š„ๅทฒ่ฐƒๆ•ดๅคงๅฐ็š„ๅฃ็บธไปฅ้‡Šๆ”พ็ฃ็›˜็ฉบ้—ดใ€‚", + "settings-clear-cache-label": "ๅฃ็บธ็ผ“ๅญ˜", + "settings-clear-cache-toast": "ๅฃ็บธ็ผ“ๅญ˜ๅทฒๆธ…้™ค", + "settings-desc": "ๆŽงๅˆถๅฃ็บธ็š„็ฎก็†ๅ’Œๆ˜พ็คบๆ–นๅผใ€‚", + "settings-enable-management-description": "ไฝฟ็”จ Noctalia ็ฎก็†ๅฃ็บธใ€‚ๅฆ‚ๆžœๆ‚จๆ›ดๅ–œๆฌขไฝฟ็”จๅ…ถไป–ๅบ”็”จ็จ‹ๅบ๏ผŒ่ฏทๅ–ๆถˆ้€‰ไธญใ€‚", + "settings-enable-management-label": "ๅฏ็”จๅฃ็บธ็ฎก็†", + "settings-enable-overview-description": "ๅฐ†ๆจก็ณŠไธ”ๅ˜ๆš—็š„ๅฃ็บธๅบ”็”จไบŽๆฆ‚่งˆๅฑๅน•ใ€‚", + "settings-enable-overview-label": "ๅฏ็”จๆฆ‚่งˆๅฃ็บธ", + "settings-folder-description": "ๆ‚จ็š„ไธปๅฃ็บธๆ–‡ไปถๅคน่ทฏๅพ„ใ€‚", + "settings-folder-label": "ๅฃ็บธๆ–‡ไปถๅคน", + "settings-hide-wallpaper-filenames-tooltip-hide": "้š่—ๆ–‡ไปถๅ", + "settings-hide-wallpaper-filenames-tooltip-show": "ๆ˜พ็คบๆ–‡ไปถๅ", + "settings-monitor-specific-description": "ไธบๆฏไธชๆ˜พ็คบๅ™จ่ฎพ็ฝฎไธๅŒ็š„ๅฃ็บธๆ–‡ไปถๅคนใ€‚", + "settings-monitor-specific-label": "ๆ˜พ็คบๅ™จ็‰นๅฎš็›ฎๅฝ•", + "settings-monitor-specific-tooltip": "ๆ˜พ็คบๅ™จๅฃ็บธๆ–‡ไปถๅคน", + "settings-overview-blur-strength-description": "ๅฐ†ๆจก็ณŠๅผบๅบฆๅบ”็”จไบŽๆฆ‚่งˆใ€‚", + "settings-overview-blur-strength-label": "ๆฆ‚่งˆๆจก็ณŠๅผบๅบฆ", + "settings-overview-tint-description": "ๅฐ†็€่‰ฒๅผบๅบฆๅบ”็”จๅˆฐๆฆ‚่งˆใ€‚", + "settings-overview-tint-label": "ๆฆ‚่งˆ็€่‰ฒๅผบๅบฆ", + "settings-recursive-search-description": "ๅŒๆ—ถๅœจๅฃ็บธ็›ฎๅฝ•็š„ๅญๆ–‡ไปถๅคนไธญๆœ็ดขๅฃ็บธใ€‚", + "settings-recursive-search-label": "ๆœ็ดขๅญๆ–‡ไปถๅคน", + "settings-select-monitor-folder": "้€‰ๆ‹ฉๆ˜พ็คบๅ™จๅฃ็บธๆ–‡ไปถๅคน", + "settings-selector-description": "้€‰ๆ‹ฉไฝ ็š„ๅฃ็บธใ€‚", + "settings-selector-position-description": "้€‰ๆ‹ฉๅฃ็บธ้€‰ๆ‹ฉ้ขๆฟ็š„ๆ˜พ็คบไฝ็ฝฎใ€‚", + "settings-show-hidden-files-tooltip-hide": "ไธๆ˜พ็คบ้š่—ๆ–‡ไปถ", + "settings-show-hidden-files-tooltip-show": "ๆ˜พ็คบ้š่—ๆ–‡ไปถ", + "settings-title": "ๅฃ็บธ่ฎพ็ฝฎ", + "settings-use-original-images-description": "่ทณ่ฟ‡ๅœจๆ˜พ็คบๅ‰่ฐƒๆ•ดๅฃ็บธๅคงๅฐใ€‚่Š‚็œ็ฃ็›˜็ฉบ้—ดๅนถๅ‡ๅฐ‘ๅฃ็บธๆ›ดๆขๆ—ถ็š„ CPU ไฝฟ็”จ็އ๏ผŒไฝ†ๅฏนไบŽ้žๅธธๅคง็š„ๅ›พๅƒๅฏ่ƒฝไผšๅ ็”จๆ›ดๅคšๅ†…ๅญ˜ใ€‚", + "settings-use-original-images-label": "ไฝฟ็”จๅŽŸๅง‹ๅ›พ็‰‡", + "settings-view-mode-description": "้€‰ๆ‹ฉไปŽๆ‚จ็š„็›ฎๅฝ•ๆ˜พ็คบๅฃ็บธ็š„ๆ–นๅผใ€‚", + "settings-view-mode-label": "ๆŸฅ็œ‹ๆจกๅผ", + "view-mode-browse": "ๆต่งˆ็›ฎๅฝ•", + "view-mode-cycle-tooltip": "ๆŸฅ็œ‹ๆจกๅผ๏ผš{mode}๏ผˆ็‚นๅ‡ปๆ›ดๆ”น๏ผ‰", + "view-mode-recursive": "ๆ‰ๅนณๅŒ–ๅญ็›ฎๅฝ•", + "view-mode-single": "ๆ น็›ฎๅฝ•" + } + }, + "placeholders": { + "add-new-keybind": "ๆทปๅŠ ๆ–ฐ้”ฎไฝ็ป‘ๅฎš", + "command-example": "echo \"Hello World\"", + "enter-command": "่พ“ๅ…ฅ่ฆๆ‰ง่กŒ็š„ๅ‘ฝไปค๏ผˆๅบ”็”จ็จ‹ๅบๆˆ–่‡ชๅฎšไน‰่„šๆœฌ๏ผ‰", + "enter-ipc-identifier": "่พ“ๅ…ฅ IPC ๅ‘ฝไปค็š„ๅ”ฏไธ€ๆ ‡่ฏ†็ฌฆ", + "enter-path": "่พ“ๅ…ฅ่ทฏๅพ„...", + "enter-text-to-collapse": "ไพ‹ๅฆ‚๏ผšโ€œnothing is playingโ€ใ€‚ไฝฟ็”จ /regex/ ่กจ็คบๅŒน้…ๆจกๅผใ€‚", + "enter-tooltip": "่พ“ๅ…ฅๆ็คบไฟกๆฏ", + "enter-width-pixels": "่พ“ๅ…ฅๅฎฝๅบฆ๏ผˆๅƒ็ด ๏ผ‰", + "keybind-recording": "ๆญฃๅœจๅฝ•ๅˆถๆŒ‰้”ฎ็ป‘ๅฎš...", + "search": "ๆœ็ดข...", + "search-icons": "ไพ‹ๅฆ‚๏ผšnoctalia, niri, battery, cloud", + "search-launcher": "ๆœ็ดขๆก็›ฎ...ๆˆ–ไฝฟ็”จ > ๆ‰ง่กŒๅ‘ฝไปค", + "search-wallhaven": "ๆœ็ดข Wallhaven...", + "search-wallpapers": "่พ“ๅ…ฅไปฅ็ญ›้€‰ๅฃ็บธ..." + }, + "positions": { + "bottom": "ๅบ•้ƒจ", + "bottom-center": "ๅบ•้ƒจๅฑ…ไธญ", + "bottom-left": "ๅทฆไธ‹", + "bottom-right": "ๅณไธ‹", + "center": "ๅฑ…ไธญ", + "center-left": "ๅทฆไพงๅฑ…ไธญ", + "center-right": "ๅณไพงๅฑ…ไธญ", + "close-to-bar": "้ ่ฟ‘ๆ ๆŒ‰้’ฎ", + "follow-bar": "่ทŸ้šๆ ", + "left": "ๅทฆ", + "right": "ๅณ", + "top": "้กถ้ƒจ", + "top-center": "้กถ้ƒจๅฑ…ไธญ", + "top-left": "ๅทฆไธŠ", + "top-right": "ๅณไธŠ" + }, + "session-menu": { + "action-in-seconds": "ๅœจ {seconds} ็ง’ๅŽๅฐ†{action}...", + "cancel-timer": "ๅ–ๆถˆๅฎšๆ—ถๅ™จ", + "title": "ไผš่ฏ่œๅ•" + }, + "setup": { + "all-done": "ๅฎŒๆˆ๏ผ", + "appearance": { + "subheader": "้€‰ๆ‹ฉๆทฑ่‰ฒๆจกๅผๅ’Œ้ขœ่‰ฒๆฅๆบ๏ผˆๅฃ็บธๆˆ–้ข„่ฎพ๏ผ‰ใ€‚" + }, + "customize": { + "header": "่‡ชๅฎšไน‰ไฝ“้ชŒ", + "subheader": "่ฐƒๆ•ด็Šถๆ€ๆ ไฝ็ฝฎใ€ๅฏ†ๅบฆใ€็ผฉๆ”พ็ญ‰ใ€‚" + }, + "skip-setup": "่ทณ่ฟ‡่ฎพ็ฝฎ", + "telemetry-wizard-done": "ๆ˜Ž็™ฝไบ†๏ผ", + "telemetry-wizard-note": "ๅฐฝๅœจๆŽŒๆŽงโ€”โ€”ๆ‚จ้šๆ—ถๅฏๅœจ่ฎพ็ฝฎไธญๅฏ็”จๆˆ–็ฆ็”จๆญคๅŠŸ่ƒฝ", + "telemetry-wizard-subtitle": "ๆˆ‘ไปฌๆทปๅŠ ไบ†ๅŒฟๅๅˆ†ๆžๅŠŸ่ƒฝไปฅๅธฎๅŠฉๆ”น่ฟ› Noctalia", + "telemetry-wizard-title": "้š็งๆƒๆ›ดๆ–ฐ", + "wallpaper": { + "choose-dir": "้€‰ๆ‹ฉๅŒ…ๅซๅฃ็บธๅ›พ็‰‡็š„็›ฎๅฝ•", + "dir-browse": "ๆต่งˆๆ–‡ไปถๅคน", + "dir-description": "้€‰ๆ‹ฉๅญ˜ๆ”พๅฃ็บธ็š„ๆ–‡ไปถๅคนใ€‚", + "dir-label": "ๅฃ็บธ็›ฎๅฝ•", + "dir-select-title": "้€‰ๆ‹ฉๅฃ็บธๆ–‡ไปถๅคน", + "header": "้€‰ๆ‹ฉไฝ ็š„ๅฃ็บธ", + "no-dir": "ๆœช้€‰ๆ‹ฉๅฃ็บธ็›ฎๅฝ•", + "no-valid": "ๅœจ {dir} ไธญๆœชๆ‰พๅˆฐๆœ‰ๆ•ˆๅ›พ็‰‡ๆ–‡ไปถ", + "none-in-dir": "็›ฎๅฝ•ไธญๆœชๆ‰พๅˆฐๅฃ็บธ", + "preview-error": "ๅ›พ็‰‡ๅŠ ่ฝฝๅคฑ่ดฅ", + "select-prompt": "ๅœจไธ‹ๆ–น้€‰ๆ‹ฉไธ€ๅผ ๅฃ็บธ", + "subheader": "็”จ็ฒพ็พŽๅฃ็บธ่ฅ้€ ๆฐ›ๅ›ดใ€‚" + }, + "welcome": "ๆฌข่ฟŽ", + "welcome-note": "ๅ…ˆ่ฟ›่กŒไธ€ไบ›ๅŸบ็ก€่ฎพ็ฝฎโ€”โ€”ๆ›ดๅคš้€‰้กนๅฏๅœจโ€œ่ฎพ็ฝฎโ€ไธญๆ‰พๅˆฐ", + "welcome-subtitle": "่ฎฉๆˆ‘ไปฌไธ€่ตทๆ‰“้€ ็‹ฌไธ€ๆ— ไบŒ็š„ๆกŒ้ข", + "welcome-title": "ๆฌข่ฟŽๆฅๅˆฐ Noctalia๏ผ" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "ๆœชๆฃ€ๆต‹ๅˆฐๅช’ไฝ“ๆ’ญๆ”พๅ™จ", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "ๆœช็Ÿฅๅบ”็”จ", + "uptime": "็ณป็ปŸ่ฟ่กŒๆ—ถ้—ด๏ผš{uptime}", + "user-requested": "็”จๆˆท่ฏทๆฑ‚", + "welcome-back": "ๆฌข่ฟŽๅ›žๆฅ๏ผŒ", + "widget-settings-title": "{widget} ่ฎพ็ฝฎ" + }, + "system-monitor": { + "core-usage": "ๆ ธๅฟƒ {id} ไฝฟ็”จ็އ", + "cpu-temp": "CPU ๆธฉๅบฆ", + "cpu-usage": "CPU ไฝฟ็”จ็އ", + "disk": "็ฃ็›˜", + "download-speed": "ไธ‹่ฝฝ้€Ÿๅบฆ", + "gpu-temp": "GPU ๆธฉๅบฆ", + "load-average": "ๅนณๅ‡่ดŸ่ฝฝ", + "title": "็ณป็ปŸ็›‘่ง†ๅ™จ", + "upload-speed": "ไธŠไผ ้€Ÿๅบฆ" + }, + "toast": { + "airplane-mode": { + "description": "็ฆ็”จๆ‰€ๆœ‰ๆ— ็บฟ้€šไฟกใ€‚", + "title": "้ฃž่กŒๆจกๅผ" + }, + "battery": { + "critical": "็”ต้‡ไธฅ้‡ไธ่ถณ", + "critical-desc": "็”ต้‡ไป…ๅ‰ฉ {percent}% โ€” ่ฏท็ซ‹ๅณ่ฟžๆŽฅๅ……็”ตๅ™จ", + "low": "็”ตๆฑ ็”ต้‡ไฝŽ", + "low-desc": "็”ต้‡ไป…ๅ‰ฉ {percent}% โ€” ่ฏท่ฟžๆŽฅๅ……็”ตๅ™จ" + }, + "bluetooth": { + "address-copied": "ๅœฐๅ€ๅทฒๅคๅˆถๅˆฐๅ‰ช่ดดๆฟ", + "auto-connect-disabled": "่‡ชๅŠจ่ฟžๆŽฅๅทฒ็ฆ็”จ", + "auto-connect-enabled": "่‡ชๅŠจ่ฟžๆŽฅๅทฒๅฏ็”จ", + "auto-connecting": "ๆญฃๅœจ่ฟžๆŽฅ {count} ไธช่ฎพๅค‡...", + "confirm-code": "ๅœจๅฆไธ€ๅฐ่ฎพๅค‡ไธŠ็กฎ่ฎคไปฃ็  {value}ใ€‚", + "connect-failed": "ๆ— ๆณ•่ฟžๆŽฅๅˆฐ่ฎพๅค‡", + "disconnect-failed": "ๆ— ๆณ•ๆ–ญๅผ€ไธŽ่ฎพๅค‡็š„่ฟžๆŽฅ", + "discoverable-change-failed": "ๆ— ๆณ•ๆ›ดๆ”นๅฏๅ‘็Žฐ็Šถๆ€", + "discoverable-disabled": "ๅฏๅ‘็Žฐๆ€งๅทฒ็ฆ็”จ", + "discoverable-enabled": "ๅทฒๅฏ็”จๅฏๅ‘็Žฐๆ€ง", + "display-code": "ๅœจๅฆไธ€ๅฐ่ฎพๅค‡ไธŠ่พ“ๅ…ฅไปฃ็  {value}ใ€‚", + "forget-failed": "ๆ— ๆณ•ๅฟ˜่ฎฐ่ฎพๅค‡", + "pair-failed": "้…ๅฏน่ฎพๅค‡ๅคฑ่ดฅ", + "passkey-required": "่ฎพๅค‡้œ€่ฆ้€š่กŒๅฏ†้’ฅใ€‚", + "pincode-required": "่ฎพๅค‡้œ€่ฆPIN็ ใ€‚", + "state-change-failed": "ๆ— ๆณ•ๆ›ดๆ”น่“็‰™็Šถๆ€" + }, + "clipboard": { + "long-text": "้•ฟๆ–‡ๆœฌ", + "unavailable": "ๅ‰ช่ดดๆฟๅކๅฒ่ฎฐๅฝ•ไธๅฏ็”จ", + "unavailable-desc": "ๆœชๅฎ‰่ฃ… 'cliphist' ๅบ”็”จ็จ‹ๅบใ€‚่ฏทๅฎ‰่ฃ…ๅฎƒไปฅไฝฟ็”จๅ‰ช่ดดๆฟๅކๅฒ่ฎฐๅฝ•ๅŠŸ่ƒฝ" + }, + "custom-command-failed": { + "description": "ๅ‘ฝไปคๅคฑ่ดฅ: {command}\\n้€€ๅ‡บไปฃ็ : {code}", + "title": "่‡ชๅฎšไน‰ๅ‘ฝไปคๅคฑ่ดฅ" + }, + "do-not-disturb": { + "disabled": "'ๅ‹ฟๆ‰ฐๆจกๅผ'ๅทฒ็ฆ็”จ", + "disabled-desc": "ๆ˜พ็คบๆ‰€ๆœ‰้€š็Ÿฅ", + "enabled": "'ๅ‹ฟๆ‰ฐๆจกๅผ'ๅทฒๅฏ็”จ", + "enabled-desc": "ๆ‚จๅฏไปฅๅœจๅކๅฒ่ฎฐๅฝ•ไธญๆ‰พๅˆฐ่ฟ™ไบ›้€š็Ÿฅ" + }, + "donation-opened": "ๆ่ต ้กต้ขๅทฒๅœจๆ‚จ็š„ๆต่งˆๅ™จไธญๆ‰“ๅผ€", + "internet-limited": "ๅทฒ่ฟžๆŽฅไฝ†ๆ— ไบ’่”็ฝ‘", + "keyboard-layout": { + "changed": "้”ฎ็›˜ๅธƒๅฑ€ๅทฒๆ›ดๆ”นไธบ {layout}", + "title": "้”ฎ็›˜" + }, + "missing-control-center": { + "description": "ๆŽงๅˆถไธญๅฟƒๅฐ้ƒจไปถๅทฒไปŽ็Šถๆ€ๆ ไธญ็งป้™คใ€‚่ฆๅ†ๆฌกไปŽ็Šถๆ€ๆ ่ฎฟ้—ฎๅฎƒ๏ผŒๆ‚จ้œ€่ฆ้‡ๆ–ฐๆทปๅŠ ๅฐ้ƒจไปถใ€‚ๆ‚จไนŸๅฏไปฅ้€š่ฟ‡ๅณ้”ฎ็‚นๅ‡ป็Šถๆ€ๆ ๆฅๆ‰“ๅผ€ๅฎƒ", + "label": "ๆœ€ๅŽไธ€ไธชๆŽงๅˆถไธญๅฟƒๅฐ้ƒจไปถๅทฒ็งป้™ค" + }, + "night-light": { + "forced": "ๅผบๅˆถๆฟ€ๆดป", + "normal": "ๆญฃๅธธๆจกๅผ", + "not-installed": "ๆœชๅฎ‰่ฃ… wlsunset" + }, + "noctalia-performance": { + "disabled": "ๆ€ง่ƒฝๆจกๅผๅทฒ็ฆ็”จ", + "enabled": "ๅทฒๅฏ็”จๆ€ง่ƒฝๆจกๅผ", + "label": "Noctalia ๆ€ง่ƒฝๆจกๅผ" + }, + "power-profile": { + "changed": "็”ตๆบๆจกๅผๅทฒๆ›ดๆ”น", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "ๅค„็†ๆจกๆฟๆ—ถ้‡ๅˆฐ้”™่ฏฏ", + "title": "ๆจกๆฟๅค„็†ๅคฑ่ดฅ" + }, + "vpn": { + "connected": "ๅทฒ่ฟžๆŽฅๅˆฐโ€œ{name}โ€", + "disconnected": "ๅทฒๆ–ญๅผ€ไธŽโ€œ{name}โ€็š„่ฟžๆŽฅ" + }, + "wallpaper-colors": { + "disabled": "ๅฃ็บธ้ขœ่‰ฒๅทฒ็ฆ็”จ", + "enabled": "ๅฃ็บธ้ขœ่‰ฒๅทฒๅฏ็”จ", + "label": "ๅฃ็บธ้ขœ่‰ฒ็”Ÿๆˆ" + }, + "wifi": { + "connected": "ๅทฒ่ฟžๆŽฅๅˆฐ '{ssid}'", + "connection-failed": "่ฟžๆŽฅๅคฑ่ดฅ", + "connection-timeout": "่ฟžๆŽฅ่ถ…ๆ—ถ", + "disconnected": "ๅทฒๆ–ญๅผ€ไธŽ '{ssid}' ็š„่ฟžๆŽฅ", + "incorrect-password": "ๅฏ†็ ้”™่ฏฏ", + "network-not-found": "ๆœชๆ‰พๅˆฐ็ฝ‘็ปœ" + } + }, + "tooltips": { + "add-widget": "ๆทปๅŠ ๅฐ้ƒจไปถ", + "auto-connect": "ๅˆ‡ๆขๆญค่ฎพๅค‡็š„่‡ชๅŠจ่ฟžๆŽฅ", + "bluetooth-auto-connect-off": "่‡ชๅŠจ่ฟžๆŽฅๅทฒ็ฆ็”จ", + "bluetooth-auto-connect-on": "่‡ชๅŠจ่ฟžๆŽฅๅทฒๅฏ็”จ", + "bluetooth-devices": "่“็‰™่ฎพๅค‡", + "brightness-at": "ไบฎๅบฆ๏ผš{brightness}%", + "click-to-start-recording": "ๅฑๅน•ๅฝ•ๅˆถๅ™จ๏ผˆๅผ€ๅง‹ๅฝ•ๅˆถ๏ผ‰", + "click-to-stop-recording": "ๅฑๅน•ๅฝ•ๅˆถๅ™จ๏ผˆๅœๆญขๅฝ•ๅˆถ๏ผ‰", + "collapse": "ๆŠ˜ๅ ไพง่พนๆ ", + "copy-address": "ๅคๅˆถๅœฐๅ€", + "delete-notification": "ๅˆ ้™ค้€š็Ÿฅ", + "dismiss-notification": "ๅ…ณ้—ญ้€š็Ÿฅ", + "do-not-disturb-enabled": "ๅ‹ฟๆ‰ฐๆจกๅผ", + "expand": "ๅฑ•ๅผ€ไพง่พนๆ ", + "forget-network": "ๅฟ˜่ฎฐ็ฝ‘็ปœ", + "grid-view": "็ฝ‘ๆ ผ่ง†ๅ›พ", + "hidden-files-hide": "้š่—ๆ–‡ไปถ", + "hide-unnamed-devices": "้š่—ๆœชๅ‘ฝๅ็š„่ฎพๅค‡", + "home": "ไธป็›ฎๅฝ•", + "input-muted": "ๅˆ‡ๆข่พ“ๅ…ฅ้™้Ÿณ", + "keep-awake": "ไฟๆŒๅ”ค้†’", + "keyboard-layout": "{layout} ้”ฎ็›˜ๅธƒๅฑ€", + "list-view": "ๅˆ—่กจ่ง†ๅ›พ", + "manage-vpn": "VPN ่ฟžๆŽฅ", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "ๅทฒ่พพๅˆฐๆœ€ๅคงๅฐ้ƒจไปถๆ•ฐ้‡", + "microphone-volume-at": "้บฆๅ…‹้ฃŽ้Ÿณ้‡๏ผš{volume}%", + "move-to-section": "็งป่‡ณ{section}", + "mute": "้™้Ÿณ", + "next-media": "ไธ‹ไธ€้ฆ–", + "next-month": "ไธ‹ไธชๆœˆ", + "night-light-not-installed": "ๅคœ้—ดๆจกๅผ๏ผˆไธๅฏ็”จ๏ผ‰", + "noctalia-performance-enabled": "Noctalia ๆ€ง่ƒฝๆจกๅผ", + "open-annotation-tool": "ไฝฟ็”จๆณจ้‡Šๅทฅๅ…ทๆ‰“ๅผ€", + "open-control-center": "ๆŽงๅˆถไธญๅฟƒ", + "open-notification-history-enable-dnd": "้€š็Ÿฅๅކๅฒ่ฎฐๅฝ•", + "open-settings": "่ฎพ็ฝฎ", + "open-tray-dropdown": "็ณป็ปŸๆ‰˜็›˜", + "output-muted": "ๅˆ‡ๆข่พ“ๅ‡บ้™้Ÿณ", + "power-profile": "{profile} ็”ตๆบๆจกๅผ", + "previous-media": "ไธŠไธ€้ฆ–", + "previous-month": "ไธŠไธชๆœˆ", + "refresh-devices": "ๅˆทๆ–ฐ่ฎพๅค‡", + "refresh-wallhaven": "ๅˆทๆ–ฐ Wallhaven ็ป“ๆžœ", + "refresh-wallpaper-list": "ๅˆทๆ–ฐๅฃ็บธๅˆ—่กจ", + "remove-widget": "็งป้™คๅฐ้ƒจไปถ", + "search-close": "ๅ…ณ้—ญๆœ็ดข", + "session-menu": "ไผš่ฏ่œๅ•", + "show-all-devices": "ๆ˜พ็คบๆ‰€ๆœ‰่ฎพๅค‡", + "switch-to-dark-mode": "ๆทฑ่‰ฒๆจกๅผ", + "switch-to-light-mode": "ๆต…่‰ฒๆจกๅผ", + "unmute": "ๅ–ๆถˆ้™้Ÿณ", + "up": "ไธŠ็บง็›ฎๅฝ•", + "volume-at": "่พ“ๅ‡บ้Ÿณ้‡๏ผš{volume}%", + "wallpaper-selector": "ๅฃ็บธ้€‰ๆ‹ฉๅ™จ" + }, + "wallpaper": { + "browse": { + "empty-directory": "ๆญค็›ฎๅฝ•ไธบ็ฉบใ€‚", + "go-root": "่ฝฌๅˆฐๅฃ็บธๆ น็›ฎๅฝ•", + "go-up": "่ฝฌๅˆฐ็ˆถๆ–‡ไปถๅคน", + "go-up-hint": "ไฝฟ็”จๅŽ้€€ๆŒ‰้’ฎๅ‘ไธŠๅฏผ่ˆชใ€‚" + }, + "configure-directory": "้…็ฝฎๆ‚จ็š„ๅฃ็บธๅ›พ็‰‡็›ฎๅฝ•ใ€‚", + "fill-modes": { + "crop": "่ฃๅ‰ช๏ผˆๅกซๅ……๏ผ‰", + "fit": "้€‚ๅบ”๏ผˆๅŒ…ๅซ๏ผ‰", + "repeat": "้‡ๅค๏ผˆๅนณ้“บ๏ผ‰", + "stretch": "ๆ‹‰ไผธ" + }, + "no-match": "ๆœชๆ‰พๅˆฐๅŒน้…้กนใ€‚", + "no-wallpaper": "ๆœชๆ‰พๅˆฐๅฃ็บธใ€‚", + "panel": { + "apikey-help": "่ฎฟ้—ฎ NSFW ๅ†…ๅฎน้œ€่ฆ API ๅฏ†้’ฅใ€‚", + "apikey-label": "API ๅฏ†้’ฅ", + "apikey-managed-by-env": "้€š่ฟ‡ NOCTALIA_WALLHAVEN_API_KEY ็Žฏๅขƒๅ˜้‡่ฟ›่กŒ็ฎก็†ใ€‚", + "apikey-placeholder": "่พ“ๅ…ฅๆ‚จ็š„ Wallhaven API ๅฏ†้’ฅ", + "appearance-dark-tab": "ๆทฑ่‰ฒ", + "appearance-light-tab": "ๆต…่‰ฒ", + "categories-anime": "ๅŠจๆผซ", + "categories-label": "ๅˆ†็ฑป", + "categories-people": "ไบบ็‰ฉ", + "color-extraction-disabled": "ไฝฟ็”จๅฃ็บธ้ขœ่‰ฒๆๅ–", + "color-extraction-enabled": "ไฝฟ็”จ้ข„่ฎพ้…่‰ฒๆ–นๆกˆ", + "header-devices-apply-all-tooltip": "ไธบๆฏไธชๆ˜พ็คบๅ™จ้€‰ๆ‹ฉๅฃ็บธ", + "header-devices-per-monitor-tooltip": "ๅบ”็”จๅˆฐๆ‰€ๆœ‰ๆ˜พ็คบๅ™จ", + "header-link-light-dark-tooltip": "ๅˆ‡ๆขไธป้ข˜ๆ—ถไฟ็•™ๅฃ็บธ", + "header-separate-light-dark-tooltip": "้šไธป้ข˜่‡ชๅŠจๅˆ‡ๆขๅฃ็บธ", + "header-sun-linked-tooltip": "ๅˆ†็ฆปๆต…่‰ฒๅ’Œๆทฑ่‰ฒๅฃ็บธ", + "header-sun-separate-tooltip": "ๆ˜Žๆš—ๆจกๅผไฝฟ็”จ็›ธๅŒๅฃ็บธ", + "order-asc": "ๅ‡ๅบ", + "order-desc": "้™ๅบ", + "order-label": "้กบๅบ", + "purity-label": "ๅ†…ๅฎน่ฟ‡ๆปคๅ™จ", + "purity-nsfw": "ๅฐ‘ๅ„ฟไธๅฎœ", + "purity-sfw": "ๅฎ‰ๅ…จ", + "purity-sketchy": "ๅฏ็–‘", + "ratios-all-portrait": "็บตๅ‘", + "ratios-all-wide": "ๆจชๅ‘", + "ratios-any": "ไปปๆ„", + "ratios-label": "ๅฎฝ้ซ˜ๆฏ”", + "resolution-atleast": "่‡ณๅฐ‘", + "resolution-exact": "็ฒพ็กฎ", + "resolution-label": "ๅˆ†่พจ็އ", + "resolution-mode-label": "ๅŒน้…ๆจกๅผ", + "solid-color-tooltip": "็บฏ่‰ฒ่ƒŒๆ™ฏ", + "sort-date-asc": "ๆŒ‰ๆœ€ๆ—ฉๆŽ’ๅบ", + "sort-date-desc": "ๆŒ‰ๆœ€ๆ–ฐๆŽ’ๅบ", + "sort-name-asc": "ๆŒ‰ๅ็งฐๆŽ’ๅบ (A-Z)", + "sort-name-desc": "ๆŒ‰ๅ็งฐๆŽ’ๅบ (Z-A)", + "sort-random": "้šๆœบๆŽ’ๅบ", + "sorting-date-added": "ไธŠไผ ๆ—ฅๆœŸ", + "sorting-favorites": "ๆ”ถ่—้‡", + "sorting-label": "ๆŽ’ๅบๆ–นๅผ", + "sorting-relevance": "็›ธๅ…ณๆ€ง", + "sorting-toplist": "ๆŽ’่กŒๆฆœ", + "sorting-views": "ๆต่งˆ้‡", + "source-label": "ๆฅๆบ", + "source-wallhaven": "Wallhaven", + "title": "ๅฃ็บธ้€‰ๆ‹ฉๅ™จ", + "wallhaven-settings-title": "Wallhaven ่ฎพ็ฝฎ" + }, + "transitions": { + "disc": "ๅœ†็›˜", + "fade": "ๆทกๅ…ฅๆทกๅ‡บ", + "honeycomb": "่œ‚ๅทข", + "pixelate": "ๅƒ็ด ๅŒ–", + "stripes": "ๆก็บน", + "wipe": "ๆ“ฆ้™ค" + }, + "try-different-search": "ๅฐ่ฏ•ไธๅŒ็š„ๆœ็ดขๆŸฅ่ฏขใ€‚", + "wallhaven": { + "loading": "ๆญฃๅœจๅŠ ่ฝฝๅฃ็บธ...", + "no-results": "ๆœชๆ‰พๅˆฐๅฃ็บธใ€‚่ฏทๅฐ่ฏ•ไธๅŒ็š„ๆœ็ดขๆŸฅ่ฏขใ€‚", + "page": "{current} / {total}", + "page-prefix": "้กต้ข", + "page-suffix": "ๅ…ฑ {total} ไธช" + } + }, + "weather": { + "clear-sky": "ๆ™ดๆœ—", + "drizzle": "ๆฏ›ๆฏ›้›จ", + "fog": "้›พ", + "mainly-clear": "ๅŸบๆœฌๆ™ดๆœ—", + "overcast": "้˜ดๅคฉ", + "partly-cloudy": "ๅฑ€้ƒจๅคšไบ‘", + "rain-showers": "้˜ต้›จ", + "snow": "้›ช", + "thunderstorm": "้›ทๆšด" + }, + "widgets": { + "color-picker": { + "palette-description": "ไปŽๅ„็ง้ข„ๅฎšไน‰้ขœ่‰ฒไธญ้€‰ๆ‹ฉใ€‚", + "palette-label": "่ฐƒ่‰ฒๆฟ", + "palette-theme-colors": "ๅฟซ้€Ÿ่ฎฟ้—ฎๆ‚จไธป้ข˜็š„่ฐƒ่‰ฒๆฟใ€‚", + "title": "้ขœ่‰ฒ้€‰ๆ‹ฉๅ™จ" + }, + "datetime-tokens": { + "ampm-lowercase": "am/pm ๅฐๅ†™", + "ampm-uppercase": "AM/PM ๅคงๅ†™", + "common-12hour-time-minutes": "12ๅฐๆ—ถๅˆถๅธฆๅˆ†้’Ÿ", + "common-24hour-time-minutes": "24ๅฐๆ—ถๅˆถๅธฆๅˆ†้’Ÿ", + "common-24hour-time-seconds": "24ๅฐๆ—ถๅˆถๅธฆ็ง’", + "common-european-date": "ๆฌงๆดฒๆ—ฅๆœŸๆ ผๅผ", + "common-iso-date": "ISO ๆ—ฅๆœŸๆ ผๅผ", + "common-us-date": "็พŽๅ›ฝๆ—ฅๆœŸๆ ผๅผ", + "common-weekday-date": "ๆ˜ŸๆœŸๅ’Œๆ—ฅๆœŸ", + "common-weekday-month-day": "ๆ˜ŸๆœŸใ€ๆœˆไปฝๅ’Œๆ—ฅๆœŸ", + "day-abbreviated": "ๆ˜ŸๆœŸๅ็งฐ็ผฉๅ†™", + "day-full": "ๆ˜ŸๆœŸๅ็งฐๅ…จ็งฐ", + "day-leading-zero": "ๆ—ฅๆœŸๆœ‰ๅ‰ๅฏผ้›ถ (01-31)", + "day-no-leading-zero": "ๆ—ฅๆœŸๆ— ๅ‰ๅฏผ้›ถ (1-31)", + "hour-leading-zero": "ๅฐๆ—ถๆœ‰ๅ‰ๅฏผ้›ถ (00-23) โ€” 24ๅฐๆ—ถๅˆถ", + "hour-no-leading-zero": "ๅฐๆ—ถๆ— ๅ‰ๅฏผ้›ถ (0-23) โ€” 24ๅฐๆ—ถๅˆถ", + "minute-leading-zero": "ๅˆ†้’Ÿๆœ‰ๅ‰ๅฏผ้›ถ (00-59)", + "minute-no-leading-zero": "ๅˆ†้’Ÿๆ— ๅ‰ๅฏผ้›ถ (0-59)", + "month-abbreviated": "ๆœˆไปฝๅ็งฐ็ผฉๅ†™", + "month-full": "ๆœˆไปฝๅ็งฐๅ…จ็งฐ", + "month-number-leading-zero": "ๆœˆไปฝๆ•ฐๅญ—ๆœ‰ๅ‰ๅฏผ้›ถ (01-12)", + "month-number-no-zero": "ๆœˆไปฝๆ•ฐๅญ—ๆ— ๅ‰ๅฏผ้›ถ (1-12)", + "second-leading-zero": "็ง’ๆœ‰ๅ‰ๅฏผ้›ถ (00-59)", + "second-no-leading-zero": "็ง’ๆ— ๅ‰ๅฏผ้›ถ (0-59)", + "timezone-abbreviation": "ๆ—ถๅŒบ็ผฉๅ†™", + "year-four-digit": "ๅนดไปฝไธบๅ››ไฝๆ•ฐ", + "year-two-digit": "ๅนดไปฝไธบไธคไฝๆ•ฐ (00-99)" + }, + "file-picker": { + "item": "้กน็›ฎ", + "items": "้กน็›ฎ", + "search-placeholder": "ๆœ็ดขๆ–‡ไปถๅ’Œๆ–‡ไปถๅคน...", + "select-current": "้€‰ๆ‹ฉๅฝ“ๅ‰", + "select-file": "้€‰ๆ‹ฉๆ–‡ไปถ", + "select-folder": "้€‰ๆ‹ฉๆ–‡ไปถๅคน", + "selected": "ๅทฒ้€‰ๆ‹ฉ๏ผš", + "title": "ๆ–‡ไปถ้€‰ๆ‹ฉๅ™จ" + }, + "file-picker-title": "้€‰ๆ‹ฉๆ–‡ไปถ", + "icon-picker": { + "title": "ๅ›พๆ ‡้€‰ๆ‹ฉๅ™จ" + }, + "tooltip-placeholder": "ๅ ไฝ็ฌฆ" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "ๅŒฟๅ่บซไปฝ", + "ca-cert": "CA ่ฏไนฆ", + "eap-method": "EAP ๆ–นๆณ•", + "password": "็”จๆˆทๅฏ†็ ", + "phase2-auth": "็ฌฌไบŒ้˜ถๆฎต่ฎค่ฏ", + "username": "่บซไปฝ / ็”จๆˆทๅ" + }, + "panel": { + "action-required": "้œ€่ฆๅŠจไฝœ", + "add-hidden-network": "ๆทปๅŠ ้š่—็ฝ‘็ปœ", + "add-network": "ๆทปๅŠ ็ฝ‘็ปœ", + "available-interfaces": "ๅฏ็”จๆŽฅๅฃ", + "available-networks": "ๅฏ็”จ็ฝ‘็ปœ", + "disabled": "Wi-Fi ๅทฒ็ฆ็”จ", + "disconnecting": "ๆญฃๅœจๆ–ญๅผ€่ฟžๆŽฅ...", + "dns": "DNS", + "enable-message": "ๅฏ็”จ Wi-Fi ไปฅๆŸฅ็œ‹ๅฏ็”จ็ฝ‘็ปœใ€‚", + "enter-password": "่พ“ๅ…ฅๅฏ†็ ...", + "forget": "ๅฟ˜่ฎฐ", + "forget-network": "ๅฟ˜่ฎฐๆญค็ฝ‘็ปœ๏ผŸ", + "forgetting": "ๆญฃๅœจๅฟ˜่ฎฐ...", + "hidden-network": "้š่—็ฝ‘็ปœ", + "interface": "็ฝ‘็ปœๆŽฅๅฃ", + "internet-connected": "ๅทฒ่ฟžๆŽฅไบ’่”็ฝ‘", + "internet-limited": "ๆ— ็ฝ‘็ปœ", + "internet-status": "ไบ’่”็ฝ‘็Šถๆ€", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "ๅทฒ็Ÿฅ็ฝ‘็ปœ", + "link-speed": "้“พ่ทฏ้€Ÿๅบฆ", + "network-name-ssid": "็ฝ‘็ปœๅ็งฐ (SSID)", + "no-ethernet-devices": "ๆœชๆฃ€ๆต‹ๅˆฐไปฅๅคช็ฝ‘่ฎพๅค‡", + "no-networks": "ๆœชๆ‰พๅˆฐ็ฝ‘็ปœ", + "saved": "ๅทฒไฟๅญ˜", + "scan-again": "้‡ๆ–ฐๆ‰ซๆ", + "searching": "ๆญฃๅœจๆœ็ดข้™„่ฟ‘็ฝ‘็ปœ...", + "security-open": "ๆ‰“ๅผ€", + "security-wep": "WEP", + "security-wpa": "WPA/WPA2/WPA3 ไธชไบบ", + "security-wpa-ent": "WPA ไผไธš็‰ˆ", + "security-wpa2-ent": "WPA2 ไผไธš็‰ˆ", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 ไผไธš็‰ˆ", + "show-password": "ๆ˜พ็คบๅฏ†็ ", + "title": "Wi-Fi" + }, + "signal": { + "excellent": "ไผ˜็ง€", + "fair": "ไธ€่ˆฌ", + "good": "ๅฅฝ", + "poor": "ๅทฎ", + "weak": "ๅผฑ" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Translations/zh-TW.json b/arch/.config/quickshell/noctalia-shell/Assets/Translations/zh-TW.json new file mode 100644 index 0000000..9cdd000 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/Translations/zh-TW.json @@ -0,0 +1,2252 @@ +{ + "actions": { + "clear-history": "ๆธ…้™คๆญทๅฒ", + "close-app": "้—œ้–‰ {app}", + "connect-vpn": "้€ฃ็ทš่‡ณ {name}", + "cycle-visualizer": "ๅˆ‡ๆ›่ฆ–่ฆบๆ•ˆๆžœๆจฃๅผ", + "disable-bluetooth": "้—œ้–‰่—็‰™", + "disable-dnd": "ๅœ็”จๅ‹ฟๆ“พๆจกๅผ", + "disable-wifi": "้—œ้–‰ Wi-Fi", + "disconnect-vpn": "่‡ช {name} ไธญๆ–ท้€ฃ็ทš", + "dock-settings": "Dock ่จญๅฎš", + "enable-bluetooth": "ๅ•Ÿ็”จ่—็‰™", + "enable-dnd": "ๅ•Ÿ็”จๅ‹ฟๆ“พๆจกๅผ", + "enable-wifi": "ๅ•Ÿ็”จ Wi-Fi", + "launcher-settings": "ๅ•Ÿๅ‹•ๅ™จ่จญๅฎš", + "lower-to-bottom": "ไธ‹็งป่‡ณๅบ•้ƒจ", + "open-calendar": "้–‹ๅ•Ÿ่กŒไบ‹ๆ›†", + "open-display-settings": "้กฏ็คบๅ™จ่จญๅฎš", + "open-launcher": "ๆ‰“้–‹ๅ•Ÿๅ‹•ๅ™จ", + "open-settings": "้–‹ๅ•Ÿ่จญๅฎš", + "raise-to-top": "ไธŠ็งป่‡ณ้ ‚้ƒจ", + "random-wallpaper": "้šจๆฉŸๆกŒๅธƒ", + "run-custom-command": "ๅŸท่กŒ่‡ช่จ‚ๆŒ‡ไปค", + "show-active-only": "ๅƒ…้กฏ็คบไฝœ็”จไธญ", + "show-all": "้กฏ็คบๅ…จ้ƒจ", + "toggle-mute": "้œ้Ÿณ้–‹้—œ", + "widget-settings": "ๅฐๅทฅๅ…ท่จญๅฎš" + }, + "authentication": { + "error": "้ฉ—่ญ‰้Œฏ่ชค", + "failed": "้ฉ—่ญ‰ๅคฑๆ•—" + }, + "bar": { + "active-window": { + "colorize-icons-description": "ๅฐ‡ๆดปๅ‹•่ฆ–็ช—็š„ๅœ–็คบๅฅ—็”จไธป้กŒ็”จ่‰ฒ", + "hide-mode-description": "ๆŽงๅˆถๅฐๅทฅๅ…ทๅœจๆฒ’ๆœ‰ๆดปๅ‹•่ฆ–็ช—ๆ™‚็š„่กŒ็‚บ", + "scrolling-mode-description": "ๆŽงๅˆถ่ฆ–็ช—้•ทๆจ™้กŒๆ™‚ๆ˜ฏๅฆๅ•Ÿ็”จๆ–‡ๅญ—ๆฒๅ‹•", + "show-app-icon-description": "ๅœจ่ฆ–็ช—ๆจ™้กŒๆ—้กฏ็คบ็จ‹ๅผๅœ–็คบ", + "show-app-icon-label": "้กฏ็คบๆ‡‰็”จ็จ‹ๅผ็š„ๅœ–็คบ", + "show-app-text-description": "้กฏ็คบๆดปๅ‹•่ฆ–็ช—็š„ๆจ™้กŒใ€‚", + "show-app-text-label": "้กฏ็คบ่ฆ–็ช—ๆจ™้กŒ" + }, + "audio-visualizer": { + "color-name-description": "้ธๅ–่ฆ–่ฆบๆ•ˆๆžœ็š„้ก่‰ฒ", + "color-name-label": "ๅกซๅ……้ก่‰ฒ", + "height-description": "่‡ช่จ‚ๅ…ƒไปถๅฏฌๅบฆใ€‚", + "hide-when-idle-description": "็•ถๅ•Ÿ็”จๆ™‚, ๅฆ‚ๆžœๆฒ’ๆœ‰ๆญฃๅœจๆ’ญๆ”พ็š„ๅช’้ซ”ๅฐฑ้šฑ่—่ฆ–่ฆบๆ•ˆๆžœ", + "hide-when-idle-label": "็„กไปปไฝ•ๅช’้ซ”ๆ’ญๆ”พๆ™‚้šฑ่—", + "width-description": "่‡ช่จ‚้ƒจไปถๅฏฌๅบฆ" + }, + "battery": { + "device-default": "้ ่จญ (้กฏ็คบ่ฃ็ฝฎ)", + "device-description": "้ธๅ–่ฆ้กฏ็คบๅ“ชๅ€‹่ฃ็ฝฎ็š„้›ปๆฑ ", + "device-label": "้›ปๆฑ ่ฃ็ฝฎ", + "display-mode-description": "้ธๆ“‡้›ปๆฑ ๅœจๅทฅๅ…ทๅˆ—ไธญ็š„้กฏ็คบๆ–นๅผใ€‚", + "display-mode-graphic": "ๅœ–ๅฝขๅŒ–้›ปๆฑ ", + "display-mode-graphic-clean": "ๅœ–ๅฝข้›ปๆฑ ๏ผˆ็„ก%๏ผ‰", + "display-mode-icon-always": "ๅœ–็คบ - ๅง‹็ต‚้กฏ็คบ %", + "display-mode-icon-hover": "ๅœ–็คบ - ๆ‡ธๅœๆ™‚้กฏ็คบ", + "display-mode-icon-only": "ๅƒ…ๅœ–็คบ", + "hide-if-idle-description": "็•ถ้›ปๆฑ ็‹€ๆ…‹็‚บ้–’็ฝฎๆ™‚้šฑ่—ๅฐๅทฅๅ…ท", + "hide-if-idle-label": "้–’็ฝฎๆ™‚้šฑ่—", + "hide-if-not-detected-description": "ๅฆ‚ๆžœ็ณป็ตฑๆฒ’ๆœ‰ๅตๆธฌๅˆฐ้›ปๆฑ ๅฐฑ้šฑ่—ๅฐๅทฅๅ…ท", + "hide-if-not-detected-label": "ๆฒ’ๆœ‰ๅตๆธฌๅˆฐๆ™‚้šฑ่—", + "low-battery-threshold-label": "ไฝŽ้›ป้‡่ญฆๅ‘Š", + "show-noctalia-performance-description": "ๅœจ้›ปๆฑ ้ขๆฟๅ…ง้กฏ็คบ Noctalia ๆ•ˆ่ƒฝๆจกๅผ้–‹้—œ", + "show-noctalia-performance-label": "้กฏ็คบ Noctalia ๆ•ˆ่ƒฝๆจกๅผ้–‹้—œ", + "show-power-profile-description": "ๅœจ้›ปๆฑ ้ขๆฟๅ…ง้กฏ็คบๅฏๅˆ‡ๆ›็š„้›ปๆบ้…็ฝฎ", + "show-power-profile-label": "้กฏ็คบ้›ปๆบ้…็ฝฎ" + }, + "brightness": { + "apply-all-description": "ไฝฟ็”จๆปพ่ผชๆ™‚ๆ›ดๆ”นๆ‰€ๆœ‰้กฏ็คบๅ™จ็š„ไบฎๅบฆใ€‚", + "apply-all-label": "ๅฐ‡ๆฒๅ‹•่ฎŠๆ›ดๅฅ—็”จๅˆฐๆ‰€ๆœ‰่žขๅน•" + }, + "clock": { + "clock-display-description": "ๅˆฉ็”จไธ‹้ขๅˆ—่กจ็š„ๆจ™่จ˜่‡ช่จ‚ไฝ ๆƒณ่ฆ็š„ๆ™‚้˜ๆ ผๅผ; ๅฆ‚ๆžœๆƒณไฝฟ็”จ12ๅฐๆ™‚ๅˆถ, ไฝ ๅฟ…้ ˆ่ฆๅ†ๅขžๅŠ  \"AP\" ๆจ™่จ˜", + "clock-display-label": "ๆ™‚้˜้กฏ็คบ", + "custom-font-description": "้ธๅ–ๆ™‚้˜้กฏ็คบไฝฟ็”จ็š„่‡ช่จ‚ๅญ—ๅž‹", + "custom-font-label": "่‡ช่จ‚ๅญ—ๅž‹", + "custom-font-placeholder": "้ธๆ“‡่‡ช่จ‚ๅญ—ๅž‹...", + "custom-font-search-placeholder": "ๆœๅฐ‹ๅญ—ๅž‹...", + "horizontal-bar-description": "ๆ็คบ: ๅฏไปฅๅˆฉ็”จ \\n ๆ›่กŒ", + "horizontal-bar-label": "ๆฐดๅนณๅˆ—้กฏ็คบ", + "preview": "้ ่ฆฝ", + "tooltip-format-description": "ๅœจๆธธๆจ™็งป้Žๆ™‚ๆ็คบๆก†ๆ‰€้กฏ็คบ็š„ๆ ผๅผ, ๅฆ‚ๆžœ็•™็ฉบๅ‰‡ไฝฟ็”จ้ ่จญๅ€ผ", + "tooltip-format-label": "ๆ็คบๆก†ๆ ผๅผ", + "use-custom-font-description": "็‚บๆ™‚้˜ๅผทๅˆถๅฅ—็”จ่‡ช่จ‚็š„ๅญ—ๅž‹", + "use-custom-font-label": "ไฝฟ็”จ่‡ช่จ‚ๅญ—ๅž‹", + "use-monospaced-font-description": "็•ถๅ•Ÿ็”จๆ™‚, ๆ™‚้˜ๆœƒไฝฟ็”จ็ญ‰ๅฏฌๅญ—ๅž‹้กฏ็คบ", + "use-monospaced-font-label": "ไฝฟ็”จ็ญ‰ๅฏฌๅญ—ๅž‹", + "vertical-bar-description": "ไฝฟ็”จ็ฉบๆ ผๆŠŠๅ„ๅ€‹้ƒจไปถๆ‹†ๅˆฐไธ‹ไธ€่กŒ", + "vertical-bar-label": "ๅž‚็›ดๅˆ—้กฏ็คบ" + }, + "control-center": { + "browse-file": "็€่ฆฝๆช”ๆกˆ", + "browse-library": "็€่ฆฝๅœ–็คบๅบซ", + "color-selection-description": "ๅฐ‡ไธป้กŒ็”จ่‰ฒๅฅ—็”จ่‡ณๅœ–็คบไธŠ", + "enable-colorization-description": "็‚บๅœ–็คบๅ•Ÿ็”จ่‘—่‰ฒ๏ผŒๆ‡‰็”จไธป้กŒ้ก่‰ฒใ€‚", + "icon-description": "่‡ชๅœ–็คบๅบซๆˆ–ๆช”ๆกˆ้ธๆ“‡ไธ€ๅ€‹ๅœ–็คบ", + "select-custom-icon": "้ธๅ–ไธ€ๅ€‹่‡ช่จ‚ๅœ–็คบ", + "use-distro-logo-description": "ไฝฟ็”จไฝ ็พๅœจไฝฟ็”จ็š„็™ผ่กŒ็‰ˆ Logo ไฝœ็‚บๅœ–็คบ", + "use-distro-logo-label": "ไฝฟ็”จ็™ผ่กŒ็‰ˆๅœ–็คบ" + }, + "custom-button": { + "collapse-condition-description": "่‹ฅ่ผธๅ‡บๆ–‡ๅญ—็ฌฆๅˆๆญคๅ€ผ๏ผŒๆŒ‰้ˆ•ๅฐ‡ๆœƒๆ‘บ็–Šใ€‚", + "collapse-condition-label": "ๆ‘บ็–Šๆขไปถ", + "color-selection-description": "ๅฐ‡ไธป้กŒ็”จ่‰ฒๅฅ—็”จ่‡ณๅœ–็คบๅŠๆ–‡ๅญ—ไธŠ", + "default-tooltip": "่‡ช่จ‚ๆŒ‰้ˆ•๏ผŒๅœจ่จญๅฎšไธญ้…็ฝฎ", + "display-command-output-description": "่ผธๅ…ฅไธ€ๅ€‹ๆƒณ่ฆๅฎšๆ™‚้‡่ค‡ๅŸท่กŒ็š„ๆŒ‡ไปค, ๆŒ‡ไปค็š„็ฌฌไธ€่กŒ่ผธๅ‡บๅฐ‡ๆœƒไฝœ็‚บ้กฏ็คบ็š„ๆ–‡ๅญ—", + "display-command-output-label": "้กฏ็คบๆŒ‡ไปค็š„่ผธๅ‡บ", + "display-command-output-stream-description": "่ผธๅ…ฅไธ€ๅ€‹่ฆๆŒ็บŒๅŸท่กŒ็š„ๆŒ‡ไปค", + "dynamic-text": "ๅ‹•ๆ…‹ๆ–‡ๅญ—", + "enable-colorization-description": "ๆ›ฟ่‡ช่จ‚ๆŒ‰้ˆ•็š„ๅœ–็คบๅŠๆ–‡ๅญ—ๅ•Ÿ็”จไธŠ่‰ฒ, ๅฅ—็”จไธป้กŒ็”จ่‰ฒ", + "enable-colorization-label": "ๅ•Ÿ็”จไธŠ่‰ฒ", + "general-tooltip-text-description": "ๅœจๆŒ‰้ˆ•ๅทฅๅ…ทๆ็คบไธญ้กฏ็คบ็š„่‡ช่จ‚ๆ–‡ๅญ—ใ€‚", + "general-tooltip-text-label": "่‡ช่จ‚ๅทฅๅ…ทๆ็คบๆ–‡ๅญ—", + "hide-mode-always-expanded": "็ธฝๆ˜ฏๅฑ•้–‹", + "hide-mode-description": "ๆŽงๅˆถ็•ถๆŒ‡ไปคๆฒ’ๆœ‰่ผธๅ‡บๆ™‚๏ผŒๅ…ƒไปถไน‹ๅฏ่ฆ‹ๆ€ง", + "hide-mode-expand-with-output": "ๆœ‰่จŠๆฏ่ผธๅ‡บๆ™‚ๅฑ•้–‹", + "hide-mode-label": "้šฑ่—ๆจกๅผ", + "hide-mode-max-transparent": "ๆœ€ๅคงๅฑ•้–‹ไฝ†้€ๆ˜Ž", + "icon-description": "ๅพžๅœ–็คบๅบซ้ธๅ–ไธ€ๅ€‹ๅœ–็คบ", + "ipc-identifier-description": "IPC ๆŒ‡ไปค็š„ๅ”ฏไธ€่ญ˜ๅˆฅ็ขผ, ๅœจไฝฟ็”จ 'qs -c noctalia-shell ipc call cb [ๅ‹•ไฝœ] [่ญ˜ๅˆฅ็ขผ]' ๆ™‚ๅˆฉ็”จๆญค่ญ˜ๅˆฅ็ขผๅฏ้€้Ž IPC ๆŽงๅˆถๆญคๆŒ‰้ˆ•ใ€‚", + "ipc-identifier-label": "IPC ่ญ˜ๅˆฅ็ขผ", + "left-click-description": "ๅœจ้ปžๆ“Šๅทฆ้ตๆ™‚่ฆๅŸท่กŒ็š„ๆŒ‡ไปค", + "left-click-label": "้ปžๆ“Šๅทฆ้ต", + "left-click-update-text": "ๅœจ้ปžๆ“Šๅทฆ้ตๆ™‚ๆ›ดๆ–ฐ้กฏ็คบๆ–‡ๅญ—", + "max-text-length-horizontal-description": "ๅœจๆฐดๅนณๅˆ—ๆ‰€้กฏ็คบ็š„ๆœ€ๅคงๅญ—ๅ…ƒๆ•ธ้‡ (0็‚บ้šฑ่—)", + "max-text-length-horizontal-label": "ๆœ€ๅคงๆ–‡ๅญ—้•ทๅบฆ (ๆฐดๅนณๅˆ—)", + "max-text-length-vertical-description": "ๅœจๅž‚็›ดๅˆ—ๆ‰€้กฏ็คบ็š„ๆœ€ๅคงๅญ—ๅ…ƒๆ•ธ้‡ (0็‚บ้šฑ่—)", + "max-text-length-vertical-label": "ๆœ€ๅคงๆ–‡ๅญ—้•ทๅบฆ (ๅž‚็›ดๅˆ—)", + "middle-click-description": "ๅœจ้ปžๆ“Šไธญ้ตๆ™‚่ฆๅŸท่กŒ็š„ๆŒ‡ไปค", + "middle-click-label": "้ปžๆ“Šไธญ้ต", + "middle-click-update-text": "ๅœจ้ปžๆ“Šไธญ้ตๆ™‚ๆ›ดๆ–ฐ้กฏ็คบๆ–‡ๅญ—", + "parse-json-description": "ๅฐ‡ๅŸท่กŒ็š„ๆŒ‡ไปค่ผธๅ‡บ็‚บ JSON ็‰ฉไปถไพ†ๅ‹•ๆ…‹่จญ็ฝฎๆ–‡ๅญ—ๅŠๅœ–็คบ", + "parse-json-label": "ๅฐ‡่ผธๅ‡บ่งฃๆž็‚บ JSON", + "refresh-interval-description": "้–“ๆ ผ ไปฅๆฏซ็ง’ (ms) ่จˆ", + "refresh-interval-label": "้‡ๆ•ด้–“ๆ ผ", + "right-click-description": "ๅœจ้ปžๆ“Šๅณ้ตๆ™‚่ฆๅŸท่กŒ็š„ๆŒ‡ไปค", + "right-click-label": "้ปžๆ“Šๅณ้ต", + "right-click-update-text": "ๅœจ้ปžๆ“Šๅณ้ตๆ™‚ๆ›ดๆ–ฐ้กฏ็คบๆ–‡ๅญ—", + "show-exec-tooltip-description": "้กฏ็คบๅธถๆœ‰ๆŒ‡ไปค่ฉณ็ดฐ่ณ‡่จŠ็š„ๅทฅๅ…ทๆ็คบ (ๅทฆ้ต/ๅณ้ต/ไธญ้ต้ปžๆ“Š๏ผŒๆปพ่ผช)ใ€‚", + "show-exec-tooltip-label": "้กฏ็คบๆŒ‡ไปคๅทฅๅ…ทๆ็คบ", + "show-icon-description": "ๅˆ‡ๆ›ๅฐๅทฅๅ…ทๅœ–็คบ็š„่ƒฝ่ฆ‹ๆ€ง", + "show-icon-label": "้กฏ็คบๅœ–็คบ", + "show-text-tooltip-description": "้กฏ็คบๅธถๆœ‰ๆ–‡ๅญ—ๅ‘ฝไปค่ผธๅ‡บ็š„ๅทฅๅ…ทๆ็คบใ€‚", + "show-text-tooltip-label": "้กฏ็คบๅ‹•ๆ…‹ๆ–‡ๅญ—ๅทฅๅ…ทๆ็คบ", + "text-stream-description": "ๅ‘ฝไปคๅˆ—็š„ไธฒๆตๅ…งๅฎนๅฐ‡ไปฅๆ–‡ๅญ—ๅฝขๅผ้กฏ็คบๅœจๆŒ‰้ˆ•ไธŠใ€‚", + "text-stream-label": "ไธฒๆต", + "wheel-description": "ๅœจๆปพ่ผชๆปพๅ‹•ๆ™‚่ฆๅŸท่กŒ็š„ๆŒ‡ไปค
ๅœจๆŒ‡ไปค่ฃกไฝฟ็”จ $delta ่กจ็คบๆŒ‡ไปคไธญๆปพๅ‹•ๅคšๅฐ‘็š„ๅ€ผ", + "wheel-down": "ๆปพ่ผช ไธ‹", + "wheel-down-description": "ๅœจๆปพ่ผชไธ‹ๆปพๆ™‚่ฆๅŸท่กŒ็š„ๆŒ‡ไปค", + "wheel-down-label": "ๆปพ่ผชไธ‹ๆปพ", + "wheel-label": "ๆป‘้ผ ๆปพ่ผช", + "wheel-mode-separate-description": "ๅˆ†ๅˆฅๅœจๆปพ่ผชไธŠๆปพๅŠไธ‹ๆปพๆ™‚ๅŸท่กŒไธๅŒๆŒ‡ไปค", + "wheel-mode-separate-label": "ๅˆ†้–‹่จญๅฎšๆปพ่ผชๆŒ‡ไปค", + "wheel-up": "ๆปพ่ผช ไธŠ", + "wheel-up-description": "ๅœจๆปพ่ผชไธŠๆปพๆ™‚่ฆๅŸท่กŒ็š„ๆŒ‡ไปค", + "wheel-up-label": "ๆปพ่ผชไธŠๆปพ", + "wheel-update-text": "ๅœจๆปพๅ‹•ๆปพ่ผชๆ™‚ๆ›ดๆ–ฐ้กฏ็คบๆ–‡ๅญ—" + }, + "keyboard-layout": { + "show-icon-description": "ไปฅๅœ–็คบ้กฏ็คบ้ต็›คไฝˆๅฑ€" + }, + "lock-keys": { + "hide-when-off-description": "็•ถๆŒ‰้ตไธๆ˜ฏๅ•Ÿๅ‹•็‹€ๆ…‹ๆ™‚้šฑ่—", + "hide-when-off-label": "ๅœ็”จๆ™‚้šฑ่—", + "show-caps-lock-description": "้กฏ็คบ Caps Lock็š„็‹€ๆ…‹", + "show-caps-lock-label": "Caps Lock", + "show-num-lock-description": "้กฏ็คบ Num Lock ็š„็‹€ๆ…‹", + "show-num-lock-label": "Num Lock", + "show-scroll-lock-description": "้กฏ็คบ Scroll Lock ็š„็‹€ๆ…‹", + "show-scroll-lock-label": "Scroll Lock" + }, + "media-mini": { + "compact-mode-description": "ๆ›ฟๅช’้ซ”ๆ’ญๆ”พ้ขๆฟๆ›ไธ€ๅ€‹็œ็ฉบ้–“็š„ไฝˆๅฑ€", + "compact-mode-label": "็ทŠๆนŠๆจกๅผ", + "hide-mode-description": "ๆŽงๅˆถ็•ถๆฒ’ๆœ‰ๅช’้ซ”ๆ’ญๆ”พๆ™‚ๅฐๅทฅๅ…ท็š„่กŒ็‚บ", + "max-width-description": "่จญๅฎšๅฐๅทฅๅ…ท็š„ๆœ€ๅคงๆฐดๅนณๅฏฌๅบฆ, ็•ถๆจ™้กŒ็ฐก็Ÿญๆ™‚ๅฐๅทฅๅ…ทๆœƒ่ทŸ่‘—็ธฎ็Ÿญ", + "no-active-player": "ๆฒ’ๆœ‰ๆดปๅ‹•็š„ๅช’้ซ”", + "panel-section-description": "ๆŽงๅˆถๅช’้ซ”ๆ’ญๆ”พ้ขๆฟ็š„ๅค–่ง€ๅŠ่กŒ็‚บ", + "panel-section-label": "ๅช’้ซ”ๆ’ญๆ”พ้ขๆฟ", + "scrolling-mode-description": "ๆŽงๅˆถๆญŒๆ›ฒ้•ทๆจ™้กŒๆ™‚ๆ˜ฏๅฆๅ•Ÿ็”จๆ–‡ๅญ—ๆฒๅ‹•", + "scrolling-mode-label": "ๆฒๅ‹•ๆจกๅผ", + "show-album-art-description": "็‚บ็พๅœจๆ’ญๆ”พ็š„ๆ›ฒ็›ฎ้กฏ็คบๅฐˆ่ผฏๅฐ้ข", + "show-album-art-label": "้กฏ็คบๅฐˆ่ผฏๅฐ้ข", + "show-artist-first-description": "ไปฅ ๆผ”ๅ‡บ่€… - ๆ›ฒ็›ฎ ้กฏ็คบ่€Œไธๆ˜ฏ ๆ›ฒ็›ฎ - ๆผ”ๅ‡บ่€…", + "show-artist-first-label": "ๆผ”ๅ‡บ่€…ๅœจๅ‰", + "show-progress-ring-description": "็”จไธ€ๅ€‹้€ฒๅบฆๅœ“ๅœˆ้กฏ็คบๆ’ญๆ”พ้€ฒๅบฆ", + "show-progress-ring-label": "้กฏ็คบ้€ฒๅบฆๅœ“ๅœˆ", + "show-visualizer-description": "ๅœจ้Ÿณๆจ‚ๆ’ญๆ”พๆ™‚็‚บ้Ÿณ่จŠ้กฏ็คบ่ฆ–่ฆบๆ•ˆๆžœ", + "show-visualizer-label": "้กฏ็คบ่ฆ–่ฆบๆ•ˆๆžœ", + "use-fixed-width-description": "็•ถๅ•Ÿ็”จๆ™‚, ๅฐๅทฅๅ…ทๅฐ‡็ธฝๆ˜ฏไฝฟ็”จๆœ€ๅคงๅฏฌๅบฆ่€Œไธๆœƒ้…ๅˆๆจ™้กŒ่ชฟๆ•ด", + "use-fixed-width-label": "ไฝฟ็”จๅ›บๅฎšๅฏฌๅบฆ", + "visualizer-type-description": "้ธๆ“‡่ฆ–่ฆบๆ•ˆๆžœ็š„้กฏ็คบ้ขจๆ ผ", + "visualizer-type-label": "่ฆ–่ฆบๆ•ˆๆžœๆจฃๅผ" + }, + "notification-history": { + "hide-widget-when-zero-description": "ๅฆ‚ๆžœๆฒ’ๆœ‰ๆœช่ฎ€้€š็Ÿฅๅฐฑ้šฑ่—้€š็Ÿฅๅœ–็คบ", + "hide-widget-when-zero-label": "ๆฒ’ๆœ‰้€š็Ÿฅๆ™‚้šฑ่—ๅœ–็คบ", + "hide-widget-when-zero-unread-description": "ๆฒ’ๆœ‰ๆœช่ฎ€้€š็Ÿฅๆ™‚้šฑ่—้€š็Ÿฅๅœ–็คบใ€‚", + "hide-widget-when-zero-unread-label": "ๆฒ’ๆœ‰ๆœช่ฎ€้€š็Ÿฅๆ™‚้šฑ่—ๅœ–็คบ", + "show-unread-badge-description": "้กฏ็คบๅธถๆœ‰ๆ•ธ้‡็š„ๆœช่ฎ€ๆจ™่จ˜", + "show-unread-badge-label": "้กฏ็คบๆœช่ฎ€ๆจ™่จ˜", + "unread-badge-color-description": "้ธๆ“‡ๆœช่ฎ€้€š็Ÿฅๅพฝ็ซ ็š„้ก่‰ฒใ€‚", + "unread-badge-color-label": "ๆœช่ฎ€ๅพฝ็ซ ้ก่‰ฒ" + }, + "section-editor": { + "placeholder": "้ธๅ–ไธ€ๅ€‹ๅฐๅทฅๅ…ทไพ†ๆ–ฐๅขž...", + "search-placeholder": "ๆœๅฐ‹ๅฐๅทฅๅ…ท..." + }, + "spacer": { + "width-description": "ๅˆ†้š”้–“่ท ไปฅๅƒ็ด  (px) ่จˆ" + }, + "system-monitor": { + "compact-mode-description": "็”จ่ฟทไฝ ้•ทๆขไพ†ๅ–ไปฃๆ–‡ๅญ—ๆ•ธๅ€ผ, ๅฏไปฅ้ฟๅ…ไฝˆๅฑ€่ท‘็‰ˆ", + "compact-mode-label": "็ทŠๆนŠๆจกๅผ", + "cpu-cores-description": "ๅ€‹ๅˆฅ้กฏ็คบ CPU ๆ ธๅฟƒไฝฟ็”จ็އใ€‚", + "cpu-cores-label": "CPU ๆ ธๅฟƒ", + "cpu-frequency-description": "้กฏ็คบ็›ฎๅ‰ CPU ๆ™‚่„ˆ้€Ÿๅบฆ๏ผˆGHz๏ผ‰ใ€‚", + "cpu-frequency-label": "้กฏ็คบ CPU ้ ป็އ", + "cpu-temperature-description": "ๅฆ‚ๆžœๅฏ็”จๅณ้กฏ็คบ CPU ๆบซๅบฆ", + "cpu-temperature-label": "CPU ๆบซๅบฆ", + "cpu-usage-description": "้กฏ็คบ็พๅœจ CPU ็š„ไฝฟ็”จ็™พๅˆ†ๆฏ”", + "cpu-usage-label": "CPU ไฝฟ็”จ็އ", + "disk-path-description": "้ธๅ–่ฆ็›ฃ่ฆ–็š„็ฃ็ขŸๆŽ›่ผ‰้ปž", + "disk-path-label": "็ฃ็ขŸ่ทฏๅพ‘", + "gpu-temperature-description": "ๅฆ‚ๆžœๅฏ็”จๅณ้กฏ็คบ GPU ๆบซๅบฆ", + "load-average-description": "้กฏ็คบ็ณป็ตฑ่ฒ ่ผ‰", + "load-average-label": "็ณป็ตฑ่ฒ ่ผ‰", + "memory-percentage-description": "้กฏ็คบ่จ˜ๆ†ถ้ซ”็š„ไฝฟ็”จ็™พๅˆ†ๆฏ”่€Œไธๆ˜ฏๆ•ธๅ€ผ่กจ็คบ", + "memory-percentage-label": "่จ˜ๆ†ถ้ซ”็™พๅˆ†ๆฏ”", + "memory-usage-description": "้กฏ็คบ็พๅœจ็š„่จ˜ๆ†ถ้ซ”ไฝฟ็”จ้‡", + "memory-usage-label": "่จ˜ๆ†ถ้ซ”ไฝฟ็”จ้‡", + "network-traffic-description": "้กฏ็คบ็ถฒ่ทฏไธŠๅ‚ณๅŠไธ‹่ผ‰็š„้€Ÿๅบฆ", + "network-traffic-label": "็ถฒ่ทฏๆต้‡", + "storage-as-percentage-description": "ไปฅ็™พๅˆ†ๆฏ”่€Œ้ž็ต•ๅฐๅ€ผ้กฏ็คบ็ฃ็ขŸ็ฉบ้–“ใ€‚", + "storage-as-percentage-label": "็ฃ็ขŸ็™พๅˆ†ๆฏ”", + "storage-available-description": "้กฏ็คบๅฏ็”จ็ฃ็ขŸ็ฉบ้–“๏ผŒ่€Œ้žๅทฒ็”จ็ฉบ้–“ใ€‚", + "storage-available-label": "ๅฏ็”จ็ฃ็ขŸ็ฉบ้–“", + "storage-usage-description": "้กฏ็คบ็ฃ็ขŸ็ฉบ้–“็š„ไฝฟ็”จ้‡", + "storage-usage-label": "็ฃ็ขŸไฝฟ็”จ้‡", + "swap-usage-description": "้กฏ็คบไบคๆ›่จ˜ๆ†ถ้ซ”ไฝฟ็”จ้‡", + "swap-usage-label": "ไบคๆ›่จ˜ๆ†ถ้ซ”ไฝฟ็”จ้‡", + "use-monospace-font-description": "ไฝฟ็”จ็ญ‰ๅฏฌๅญ—ๅž‹ๅฏไปฅ้”ๆˆไธ€่‡ด็š„ๅญ—ๅ…ƒๅฏฌๅบฆ", + "use-monospace-font-label": "็ญ‰ๅฏฌๅญ—ๅž‹", + "use-padding-description": "็”จๅ‰ๅฐŽ็ฉบๆ ผๅกซๅ……ๆ–‡ๅญ—ๅ€ผ๏ผŒไปฅ้˜ฒๆญขไฝˆๅฑ€ๅ็งปใ€‚", + "use-padding-description-disabled-monospace-font": "ๆญคๅŠŸ่ƒฝ้œ€่ฆ็ญ‰ๅฏฌๅญ—้ซ”ใ€‚", + "use-padding-description-disabled-vertical": "ๅž‚็›ดๅทฅไฝœๅˆ—ไธๆ”ฏๆดๆ–‡ๅญ—ๅ…ง้‚Š่ทใ€‚", + "use-padding-label": "ๆ–‡ๅญ—ๅกซๅ……" + }, + "taskbar": { + "colorize-icons-description": "ๅฐ‡ๅทฅๅ…ทๅˆ—ๅœ–็คบๅฅ—็”จไธป้กŒ็”จ่‰ฒ", + "hide-mode-description": "็•ถๆฒ’ๆœ‰็ฌฆๅˆ็š„่ฆ–็ช—ๆ™‚ๆŽงๅˆถๅฐๅทฅๅ…ท็š„่กŒ็‚บ", + "hide-mode-label": "้šฑ่—ๆ–นๅผ", + "icon-scale-description": "่จญๅฎšๅทฅๅ…ทๅˆ—ๅœ–็คบ็š„็ธฎๆ”พๆฏ”ไพ‹", + "icon-scale-label": "ๅœ–็คบๆฏ”ไพ‹", + "max-width-description": "ๅทฅๅ…ทๅˆ—ไฝ”่žขๅน•ๅฏฌๅบฆ็™พๅˆ†ๆฏ”็š„ๆœ€ๅคงๅฏฌๅบฆ", + "max-width-label": "ๆœ€ๅคงๅฏฌๅบฆ", + "only-active-workspaces-description": "ๅช้กฏ็คบๆดป่บไธญ็š„ๅทฅไฝœๅ€ๆ‰€ๅŸท่กŒ็š„็จ‹ๅผ", + "only-active-workspaces-label": "ไพ†่‡ช็พๅœจๆดป่บ็š„ๅทฅไฝœๅ€", + "only-same-monitor-description": "ๅช้กฏ็คบๅทฅๅ…ทๅˆ—ๆ‰€ๅœจ็š„้€™ๅ€‹้กฏ็คบๅ™จๅœจๅŸท่กŒ็š„็จ‹ๅผ", + "only-same-monitor-label": "ไพ†่‡ชๅŒๅ€‹้กฏ็คบๅ™จ", + "show-pinned-apps-description": "้กฏ็คบๆœ‰ๅœจ Dock ๆจ™่จ˜็‚บ้‡˜้ธ็š„็จ‹ๅผ", + "show-pinned-apps-label": "้กฏ็คบ้‡˜้ธ็š„็จ‹ๅผ", + "show-title-description": "้กฏ็คบ่ฆ–็ช—็š„ๆจ™้กŒ", + "show-title-description-disabled": "็•ถๅž‚็›ด้กฏ็คบๆ™‚ไธๆ”ฏๆด้กฏ็คบๆจ™้กŒ", + "show-title-label": "้กฏ็คบๆจ™้กŒ", + "smart-width-description": "ๅŸบๆ–ผ่ฆ–็ช—็š„ๅคšๅฏก่‡ชๅ‹•่ชฟๆ•ดๆฏๅ€‹่ฆ–็ช—ไฝ”็”จ็š„ๅฏฌๅบฆ", + "smart-width-label": "ๆ™บๆ…งๅฏฌๅบฆ", + "title-width-description": "่จญๅฎšๅทฅๅ…ทๅˆ—่ฆ–็ช—ๆจ™้กŒ็š„ๅฏฌๅบฆ (ไปฅๅƒ็ด  (px) ่จˆ)", + "title-width-label": "ๆจ™้กŒๅฏฌๅบฆ", + "title-width-reset-tooltip": "้‡่จญๆจ™้กŒๅฏฌๅบฆ" + }, + "tray": { + "chevron-color-description": "ๅฐ‡ไธป้กŒ้ก่‰ฒๅฅ—็”จ่‡ณๆŠฝๅฑœ็š„็ฎญ้ ญๅœ–็คบใ€‚", + "chevron-color-label": "็ฎญ้ ญ้ก่‰ฒ", + "colorize-icons-description": "ๅฐ‡็ณป็ตฑๅคพๅœ–็คบๅฅ—็”จไธป้กŒ็”จ่‰ฒ", + "colorize-icons-label": "็‚บๅœ–็คบไธŠ่‰ฒ", + "drawer-enabled-description": "็•ถๅ•Ÿ็”จๆ™‚, ๆฒ’ๆœ‰้‡˜้ธ็š„็ณป็ตฑๅŒฃๅœ–็คบๅฐ‡ๆœƒ้กฏ็คบๅœจๆŠฝๅฑœ้ขๆฟไธญ
ๅฆ‚ๆžœๅœ็”จๅ‰‡ๆ‰€ๆœ‰็š„ๅœ–็คบ้ƒฝๆœƒๅœจๅทฅๅ…ทๅˆ—่ฃก้กฏ็คบ", + "drawer-enabled-label": "ๅ•Ÿ็”จๆŠฝๅฑœ", + "hide-passive-description": "็•ถๅ•Ÿ็”จๆ™‚, ๅœจ \"่ขซๅ‹•\" ็‹€ๆ…‹ไธ‹็š„็ณป็ตฑๅŒฃๅœ–็คบๅฐ‡ๆœƒ่ขซ้šฑ่—", + "hide-passive-label": "้šฑ่—่ขซๅ‹•้ …็›ฎ" + }, + "volume": { + "display-mode-description": "้ธๆ“‡ไฝ ๆƒณๅฆ‚ไฝ•้กฏ็คบ้€™้ …ๆ•ธๅ€ผ" + }, + "workspace": { + "character-count-description": "้™ๅˆถๅทฅไฝœๅ€ๆ‰€่จญๅฎš็š„ๅๅญ—่ฆ้กฏ็คบๅนพๅ€‹ๅญ—ๅ…ƒ (1-10)", + "character-count-label": "ๅญ—ๅ…ƒไธŠ้™", + "empty-color-description": "่จญๅฎš็ฉบ็™ฝ Workspace ็š„่ƒŒๆ™ฏ้ก่‰ฒใ€‚", + "empty-color-label": "็ฉบ็™ฝๅทฅไฝœๅ€้ก่‰ฒ", + "enable-scrollwheel-description": "ๅˆฉ็”จๆป‘้ผ ๆปพ่ผชๅœจๅทฅไฝœๅ€้–“ๅˆ‡ๆ›", + "enable-scrollwheel-label": "ๆฒ่ผชๅˆ‡ๆ›ๅทฅไฝœๅ€", + "focused-color-description": "่จญๅฎš็„ฆ้ปž Workspace ็š„่ƒŒๆ™ฏ้ก่‰ฒใ€‚", + "focused-color-label": "่š็„ฆ็š„ๅทฅไฝœๅ€้ก่‰ฒ", + "follow-focused-screen-description": "้กฏ็คบๆญฃๅœจ่š็„ฆ็š„่žขๅน•ๆ‰€ๆ“ๆœ‰็š„ๅทฅไฝœๅ€, ่€Œไธๆ˜ฏๅทฅๅ…ทๅˆ—ๆ‰€ๅœจ็š„่žขๅน•ๆ“ๆœ‰็š„ๅทฅไฝœๅ€", + "follow-focused-screen-label": "่ทŸ้šจ็„ฆ้ปžๆ‰€ๅœจ็š„่žขๅน•", + "font-weight-description": "่จญๅฎšๅทฅไฝœๅ€ๅ…งๆ–‡ๅญ—็š„่ฆ–่ฆบๆฌŠ้‡ใ€‚", + "font-weight-label": "ๅญ—้ซ”็ฒ—็ดฐ", + "grouped-border-opacity-description": "่จญๅฎšๅทฅไฝœๅ€้‚Šๆก†็š„ไธ้€ๆ˜Žๅบฆ", + "grouped-border-opacity-label": "้‚Šๆก†ไธ้€ๆ˜Žๅบฆ", + "hide-unoccupied-description": "ๅฆ‚ๆžœๅทฅไฝœๅ€ๆฒ’ๆœ‰ไปปไฝ•่ฆ–็ช—ๅ‰‡ไธ่ฆ้กฏ็คบ", + "hide-unoccupied-label": "ๆฒ’ๆœ‰ไฝ”็”จๆ™‚้šฑ่—", + "label-mode-description": "้ธๆ“‡ๅทฅไฝœๅ€ๆจ™็ฑค่ฉฒๅฆ‚ไฝ•้กฏ็คบ", + "label-mode-label": "ๆจ™็ฑคๆจฃๅผ", + "occupied-color-description": "่จญๅฎšๅทฒไฝ”็”จWorkspace็š„่ƒŒๆ™ฏ้ก่‰ฒใ€‚", + "occupied-color-label": "ๅทฒไฝ”็”จๅทฅไฝœๅ€้ก่‰ฒ", + "pill-size-description": "่ชฟๆ•ดๅทฅไฝœๅ€ๆŒ‡็คบๅ™จ็š„ๅคงๅฐ (50%-100%)ใ€‚", + "pill-size-label": "่† ๅ›Šๅคงๅฐ", + "reverse-scrolling-description": "ๆฒๅ‹•ๆ™‚ๅ่ฝ‰ๅทฅไฝœๅ€ๅˆ‡ๆ›ๆ–นๅ‘ใ€‚", + "reverse-scrolling-label": "ๅ่ฝ‰ๆฒๅ‹•", + "show-applications-description": "้กฏ็คบๅ„ๅ€‹ๅทฅไฝœๅ€็š„็จ‹ๅผๅœ–็คบ", + "show-applications-hover-description": "็•ถๆป‘้ผ ๆ‡ธๅœๅœจๅทฅไฝœๅ€ไธŠๆ™‚๏ผŒๅœจๆฏๅ€‹ๅทฅไฝœๅ€ๅ…ง้กฏ็คบๆ‡‰็”จ็จ‹ๅผๅœ–็คบใ€‚", + "show-applications-hover-label": "ๆป‘้ผ ๆ‡ธๅœๆ™‚้กฏ็คบๆ‡‰็”จ็จ‹ๅผ", + "show-applications-label": "้กฏ็คบๆ‡‰็”จ็จ‹ๅผ", + "show-badge-description": "ๅœจ็พค็ต„ๆจกๅผไธ‹้กฏ็คบๅทฅไฝœๅ€็ทจ่™Ÿๅพฝ็ซ ใ€‚", + "show-badge-label": "้กฏ็คบๅทฅไฝœๅ€ๆจ™่จ˜", + "show-labels-only-when-occupied-description": "ๅชๅœจๅทฅไฝœๅ€ๆœ‰่ฆ–็ช—ๆ™‚้กฏ็คบๅทฅไฝœๅ€ๆจ™็ฑค", + "show-labels-only-when-occupied-label": "ไฝ”็”จๆ™‚้กฏ็คบๆจ™็ฑค", + "unfocused-icons-opacity-description": "่จญๅฎš้ž็„ฆ้ปžๆ‡‰็”จ็จ‹ๅผๅœ–็คบ็š„ไธ้€ๆ˜Žๅบฆใ€‚", + "unfocused-icons-opacity-label": "้ž็„ฆ้ปžๅœ–็คบ็š„ไธ้€ๆ˜Žๅบฆ" + } + }, + "battery": { + "all-batteries": "ๆ‰€ๆœ‰้›ปๆฑ ๏ผˆ็ต„ๅˆ๏ผ‰", + "battery-health": "้›ปๆฑ ๅฅๅบทๅบฆ", + "battery-level": "้›ปๆฑ ้›ป้‡", + "capacity-level": "ๅฎน้‡๏ผš{level}", + "charging-rate": "ๅ……้›ป้€Ÿ็އ: {rate} W", + "discharging-rate": "ๆ”พ้›ป้€Ÿ็އ: {rate} W", + "health": "ๅฅๅบทๅบฆ: {percent}%", + "inhibit-idle-description": "ไฟๆŒ็ณป็ตฑๅ–š้†’", + "no-battery-detected": "ๆฒ’ๆœ‰ๅตๆธฌๅˆฐ้›ปๆฑ ", + "plugged-in": "ๅทฒๆ’้›ป", + "power-profile": "้›ปๆบ้…็ฝฎ", + "time-left": "ๅ‰ฉ้ค˜ๆ™‚้–“: {time}", + "time-until-full": "้‚„ๆœ‰ๅคšไน…ๅ……ๆปฟ: {time}" + }, + "bluetooth": { + "panel": { + "available-devices": "ๅฏ็”จ่ฃ็ฝฎ", + "blocked": "ๅทฒๅฐ้Ž–", + "connected-devices": "ๅทฒ้€ฃๆŽฅ็š„่ฃ็ฝฎ", + "device-address": "่ฃ็ฝฎไฝๅ€", + "disabled": "่—็‰™ๅทฒ้—œ้–‰", + "discoverable": "ๅฏ่ขซๅตๆธฌ", + "enable-message": "ๅ•Ÿ็”จ่—่Šฝไพ†ๆชข่ฆ–ๅฏ็”จ่ฃ็ฝฎ", + "known-devices": "ๅทฒ็Ÿฅ่ฃ็ฝฎ", + "no-devices": "ๆ‰พไธๅˆฐๅฏ็”จ่ฃ็ฝฎ", + "paired-devices": "ๅทฒ้…ๅฐ่ฃ็ฝฎ", + "pairing-mode": "็ขบ่ชไฝ ็š„่ฃ็ฝฎๅทฒ็ถ“ๅˆ‡่‡ณ้…ๅฐๆจกๅผ", + "scanning": "ๆญฃๅœจๆœๅฐ‹่ฃ็ฝฎ...", + "signal-text-excellent": "่จŠ่™Ÿ: ๅ„ช็ง€", + "signal-text-fair": "่จŠ่™Ÿ: ่‰ฏๅฅฝ", + "signal-text-good": "่จŠ่™Ÿ: ๅฐšๅฏ", + "signal-text-poor": "่จŠ่™Ÿ: ไธไฝณ", + "signal-text-unknown": "่จŠ่™Ÿ: ๆœช็Ÿฅ", + "signal-text-very-poor": "่จŠ่™Ÿ: ๆƒกๅŠฃ" + } + }, + "changelog": { + "error": { + "fetch-failed": "็„กๆณ•่ฎ€ๅ–ๆ›ดๆ–ฐๅ…งๅฎน, ่ซ‹็จๅพŒๅ†้‡่ฉฆ", + "rate-limit": "่ถ…้Ž GitHub API ็š„ๅญ˜ๅ–้‡้™ๅˆถ, ่ซ‹ๅœจๆ•ธๅˆ†้˜ๅพŒๅ†่ฉฆไธ€ๆฌก" + }, + "panel": { + "buttons-discord": "ๅŠ ๅ…ฅๆˆ‘ๅ€‘็š„ Discord", + "buttons-dismiss": "OK", + "buttons-feedback": "ๅปบ่ญฐ่ˆ‡ๅ›ž้ฅ‹", + "empty": "ๆšซๆ™‚ๆฒ’ๆœ‰ๅฏ็”จ็š„็™ผ่กŒ่ชชๆ˜Ž", + "highlight-title": "ๆ›ดๆ–ฐไบฎ้ปž", + "released": "{date} ็™ผๅธƒ", + "subtitle-fresh": "่ฌ่ฌไฝ ๅฎ‰่ฃ Noctalia! ้€™่ฃกๆไพ›้€™ๅ€‹็ต„ๅปบๆ‰€ๅŒ…ๅซ็š„ๅ…งๅฎน", + "subtitle-updated": "่‡ช {previousVersion} ็‰ˆๆ›ดๆ–ฐ", + "title": "{version} ็‰ˆๆ–ฐ้ฎฎไบ‹", + "version": "{version} ็‰ˆ", + "version-new-user": "ๅ…จๆ–ฐๅฎ‰่ฃ" + } + }, + "common": { + "actions": "ๆ“ไฝœ", + "active": "ๆดป่บ", + "add": "ๆ–ฐๅขž", + "appearance": "ๅค–่ง€", + "apply": "ๅฅ—็”จ", + "auto-connect": "่‡ชๅ‹•้€ฃ็ทš", + "automation": "่‡ชๅ‹•ๅŒ–", + "available": "ๅฏ็”จ", + "back": "่ฟ”ๅ›ž", + "battery": "้›ปๆฑ ", + "behavior": "่กŒ็‚บ", + "bluetooth": "่—็‰™", + "brightness": "ไบฎๅบฆ", + "browse": "็€่ฆฝ", + "calendar": "่กŒไบ‹ๆ›†", + "calendar-panel": "่กŒไบ‹ๆ›†้ขๆฟ", + "cancel": "ๅ–ๆถˆ", + "cards": "ๅก็‰‡", + "charging": "ๆญฃๅœจๅ……้›ป", + "clear": "ๆธ…็ฉบ", + "clipboard": "ๅ‰ช่ฒผ็ฐฟ", + "close": "้—œ้–‰", + "color-muted": "ๅทฒ้œ้Ÿณ", + "colors": "้ก่‰ฒ", + "command": "ๆŒ‡ไปค", + "confirm": "็ขบ่ช", + "connect": "้€ฃๆŽฅ", + "connected": "ๅทฒ้€ฃๆŽฅ", + "connecting": "ๆญฃๅœจ้€ฃ็ทš...", + "continue": "็นผ็บŒ", + "contributors": "่ฒข็ป่€…", + "copied-to-clipboard": "ๅทฒ่ค‡่ฃฝๅˆฐๅ‰ช่ฒผ็ฐฟ", + "countdown": "ๅ€’ๆ•ธ", + "customize": "่‡ช่จ‚", + "date": "ๆ—ฅๆœŸ", + "default": "้ ่จญ", + "delete": "ๅˆช้™ค", + "devices": "่ฃ็ฝฎ", + "disabled": "ๅทฒๅœ็”จ", + "discharging": "ๆ”พ้›ปไธญ", + "disconnect": "ๆ–ท็ทš", + "disconnected": "ๅทฒๆ–ท็ทš", + "disconnecting": "ๆญฃๅœจๆ–ท็ทš...", + "display-mode": "้กฏ็คบๆจกๅผ", + "documentation": "ๆ–‡ไปถ", + "download": "ไธ‹่ผ‰", + "duration": "ๆŒ็บŒๆ™‚้–“", + "dysfunctional": "ๅŠŸ่ƒฝๅคฑ่ชฟ", + "edit": "็ทจ่ผฏ", + "enabled": "ๅทฒๅ•Ÿ็”จ", + "error": "้Œฏ่ชค", + "ethernet": "ไน™ๅคช็ถฒ่ทฏ", + "events": "ไบ‹ไปถ", + "execute": "ๅŸท่กŒ", + "faithful": "ๅฟ ๅฏฆ", + "focus": "้—œๆณจ", + "font-weight-bold": "็ฒ—้ซ”", + "font-weight-light": "็ดฐ", + "font-weight-medium": "ไธญ", + "font-weight-regular": "ไธ€่ˆฌ", + "font-weight-semibold": "ๅŠ็ฒ—้ซ”", + "frequency": "้ ป็އ", + "gateway": "็ถฒ่ทฏ้–˜้“", + "general": "ไธ€่ˆฌ", + "height": "้ซ˜ๅบฆ", + "hibernate": "็ก็œ ", + "history": "ๆญทๅฒ", + "icon": "ๅœ–็คบ", + "idle": "้–’็ฝฎ", + "info": "ๅ…งๅฎน", + "input": "่ผธๅ…ฅ", + "install": "ๅฎ‰่ฃ", + "installed": "ๅทฒๅฎ‰่ฃ", + "interface": "ไป‹้ข", + "internet": "็ถฒ้š›็ถฒ่ทฏ", + "keybind": "ๆŒ‰้ต็ถๅฎš", + "language": "่ชž่จ€", + "loading": "ๆญฃๅœจ่ผ‰ๅ…ฅ...", + "local": "ๆœฌๆฉŸ", + "location": "ไฝ็ฝฎ", + "lock": "้Ž–ๅฎš", + "logout": "็™ปๅ‡บ", + "look": "ๆจฃๅผ", + "margins": "้‚Š่ท", + "media": "ๅช’้ซ”", + "media-player": "ๅช’้ซ”ๆ’ญๆ”พๅ™จ", + "memory": "่จ˜ๆ†ถ้ซ”", + "monitors": "้กฏ็คบๅ™จ", + "move-down": "ไธ‹็งป", + "move-up": "ๅ‘ไธŠ็งปๅ‹•", + "network": "็ถฒ่ทฏ", + "next": "ไธ‹ไธ€้ฆ–", + "night-light": "ๅคœ็‡ˆๆจกๅผ", + "no": "ๅฆ", + "no-results": "ๆฒ’ๆœ‰็ตๆžœ", + "no-summary": "ๆฒ’ๆœ‰ๆ‘˜่ฆ", + "none": "็„ก", + "not-found": "ๆ‰พไธๅˆฐ", + "notifications": "้€š็Ÿฅ", + "official": "ๅฎ˜ๆ–น", + "on-surface": "ๅœจ่กจ้ขไธŠ", + "output": "่ผธๅ‡บ", + "pair": "้…ๅฐ", + "paired": "ๅทฒ้…ๅฐ", + "pairing": "้…ๅฐไธญ...", + "panels": "้ขๆฟ", + "password": "ๅฏ†็ขผ", + "pause": "ๆšซๅœ", + "performance": "ๆ•ˆ่ƒฝ", + "pin": "้‡˜้ธ", + "play": "ๆ’ญๆ”พ", + "polling": "่ผช่ฉข", + "position": "ไฝ็ฝฎ", + "previous": "ไธŠไธ€้ฆ–", + "primary": "ไธป่ฆ็š„", + "random": "้šจๆฉŸ", + "reboot": "้‡ๆ–ฐๅ•Ÿๅ‹•", + "reboot-to-uefi": "้‡ๆ–ฐๅ•Ÿๅ‹•่‡ณ UEFI", + "record": "้–‹ๅง‹้Œ„่ฃฝ", + "refresh": "้‡ๆ–ฐๆ•ด็†", + "required": "(ๅฟ…่ฆ)", + "reset": "้‡่จญ", + "reset-to-default": "้‡่จญ็‚บ้ ่จญๅ€ผ", + "result-count": "{count} ็ญ†็ตๆžœ", + "result-count-plural": "{count} ็ญ†็ตๆžœ", + "resume": "็นผ็บŒ", + "save": "ๅ„ฒๅญ˜", + "scanning": "ๆŽƒๆไธญ...", + "screen-corners": "็•ซ้ข้‚Š่ง’", + "search": "ๆœๅฐ‹", + "second": "{count} ็ง’", + "second-plural": "{count} ็ง’", + "secondary": "่ผ”ๅŠฉ", + "security": "ๅฎ‰ๅ…จ", + "select": "้ธๅ–", + "select-color": "้ธๆ“‡้ก่‰ฒ", + "select-color-description": "ๅฅ—็”จไธป้กŒ้ก่‰ฒไปฅ็คบๅผท่ชฟใ€‚", + "select-icon-color": "้ธๆ“‡ๅœ–็คบ้ก่‰ฒ", + "settings": "่จญๅฎš", + "shortcuts": "ๅฟซๆท้ต", + "shutdown": "้—œๆฉŸ", + "signal": "่จŠ่™Ÿ", + "sound": "้Ÿณๆ•ˆ", + "sources": "ไพ†ๆบ", + "start": "้–‹ๅง‹", + "stop": "ๅœๆญข", + "supporters": "ๆ”ฏๆŒ่€…", + "suspend": "ๆšซๅœ", + "templates": "ๆจกๆฟ", + "tertiary": "็ฌฌไธ‰", + "test": "ๆธฌ่ฉฆ", + "thresholds": "้–€ๆชป", + "title": "ๆจ™้กŒ", + "toast": "ๆฐฃๆณก่จŠๆฏ", + "trusted": "ๅ—ไฟกไปป็š„", + "uninstall": "็งป้™ค", + "unknown": "ๆœช็Ÿฅ", + "unpair": "่งฃ้™ค้…ๅฐ", + "unpin": "่งฃ้™ค้‡˜้ธ", + "update": "ๆ›ดๆ–ฐ", + "upload": "ไธŠๅ‚ณ", + "username": "ไฝฟ็”จ่€…ๅ็จฑ", + "userspace-reboot": "้‡ๆ–ฐๅ•Ÿๅ‹•ไฝฟ็”จ่€…็ฉบ้–“", + "version": "็‰ˆๆœฌ", + "vibrant": "้ฎฎๆ˜Ž", + "visualizer": "่ฆ–่ฆบๆ•ˆๆžœ", + "volume": "้Ÿณ้‡", + "volumes": "้Ÿณ้‡", + "wallpaper": "ๆกŒๅธƒ", + "weather": "ๅคฉๆฐฃ", + "weather-loading": "ๆญฃๅœจ่ผ‰ๅ…ฅๅคฉๆฐฃ...", + "weather-no-location": "ๅœจ่จญๅฎšไธญ่จญๅฎšไฝ็ฝฎไปฅๆŸฅ็œ‹ๅคฉๆฐฃ", + "week": "้€ฑ", + "widgets": "ๅฐๅทฅๅ…ท", + "width": "ๅฏฌๅบฆ", + "wifi": "Wi-Fi", + "windows": "่ฆ–็ช—", + "yes": "ๆ˜ฏ" + }, + "control-center": { + "power-profile": { + "tooltip-action": "้›ปๆบ้…็ฝฎ", + "tooltip-disabled": "ๅฎ‰่ฃ power-profiles-daemon ๅฅ—ไปถไปฅไฝฟ็”จ้›ปๆบ้…็ฝฎ" + }, + "wifi": { + "label-disconnected": "Wi-Fi ๅทฒๆ–ท็ทš", + "label-ethernet": "ไน™ๅคช็ถฒ่ทฏ" + } + }, + "display-modes": { + "always-hide": "็ธฝๆ˜ฏ้šฑ่—", + "always-show": "็ธฝๆ˜ฏ้กฏ็คบ", + "force-open": "ๅผทๅˆถ้–‹ๅ•Ÿ", + "on-hover": "ๆธธๆจ™็งป้Žๆ™‚" + }, + "hide-modes": { + "auto-hide": "่‡ชๅ‹•้šฑ่—", + "hidden": "็ฉบ็™ฝๆ™‚้šฑ่—", + "idle": "้–’็ฝฎๆ™‚้šฑ่—", + "non-exclusive": "้ž็จไฝ”", + "transparent": "็ฉบ็™ฝๆ™‚่ฎŠ้€ๆ˜Ž", + "visible": "ๅง‹็ต‚ๅฏ่ฆ‹" + }, + "launcher": { + "categories": { + "all": "ๆ‰€ๆœ‰้ …็›ฎ", + "audiovideo": "้Ÿณ่จŠๅŠๅฝฑ็‰‡", + "chat": "้€š่จŠ่Šๅคฉ", + "development": "้–‹็™ผ", + "education": "ๆ•™่‚ฒ", + "emoji-activity": "ๆดปๅ‹•", + "emoji-animals": "ๅ‹•็‰ฉ", + "emoji-flags": "ๆ——ๅนŸ", + "emoji-food": "้ฃŸ็‰ฉๅŠ้ฃฒๆ–™", + "emoji-nature": "ๅคง่‡ช็„ถ", + "emoji-objects": "็‰ฉๅ“", + "emoji-people": "ไบบ็‰ฉๅŠ่‚ข้ซ”", + "emoji-recent": "ๆœ€่ฟ‘ไฝฟ็”จ", + "emoji-symbols": "็ฌฆ่™Ÿ", + "emoji-travel": "ๆ—…่กŒๅŠๅœฐ้ปž", + "game": "้Šๆˆฒ", + "graphics": "ๅœ–ๅƒ", + "misc": "ๅ…ถไป–", + "office": "่พฆๅ…ฌ", + "pinned": "้‡˜้ธ้ …็›ฎ", + "system": "็ณป็ตฑ", + "webbrowser": "็€่ฆฝๅ™จ" + }, + "providers": { + "applications": "ๆ‡‰็”จ็จ‹ๅผ", + "calculator": "่จˆ็ฎ—ๆฉŸ", + "calculator-deprecated": ">calc ๆŒ‡ไปคๅทฒ็ถ“ๆฃ„็”จ, ๅพˆๅฟซๅฐฑๆœƒ่ขซ็งป้™ค; ่ซ‹็›ดๆŽฅๅœจๆœๅฐ‹ๅˆ—่ผธๅ…ฅๆ•ธๅญธ็ฎ—ๅผ", + "calculator-description": "่จˆ็ฎ—ๆฉŸ โ€” ่จˆ็ฎ—ๆ•ธๅญธ็ฎ—ๅผ", + "calculator-enter-expression": "่ผธๅ…ฅไธ€ๅ€‹ๆ•ธๅญธ็ฎ—ๅผ", + "calculator-press-enter-to-copy": "ๆŒ‰ Enter ่ค‡่ฃฝ่จˆ็ฎ—็ตๆžœ", + "clipboard": "ๅ‰ช่ฒผ็ฐฟๆญทๅฒ", + "clipboard-clear-description": "ๆธ…้™คๆ‰€ๆœ‰ๅ‰ช่ฒผ็ฐฟๆญทๅฒ", + "clipboard-clear-description-full": "็งป้™คๅ‰ช่ฒผ็ฐฟๆญทๅฒ็š„ๆ‰€ๆœ‰้ …็›ฎ", + "clipboard-clear-history": "ๆธ…้™คๅ‰ช่ฒผ็ฐฟๆญทๅฒ", + "clipboard-delete": "ๅˆช้™คๅ‰ช่ฒผ็ฐฟ้ …็›ฎ", + "clipboard-history-disabled": "ๅœ็”จๅ‰ช่ฒผ็ฐฟๆญทๅฒ", + "clipboard-history-disabled-description": "ๅœจ่จญๅฎš่ฃกๅ•Ÿ็”จๅ‰ช่ฒผ็ฐฟๆญทๅฒๆˆ–่€…ๅฎ‰่ฃ clipphist ๅฅ—ไปถ", + "clipboard-loading": "ๅ‰ช่ฒผ็ฐฟๆญทๅฒ่ผ‰ๅ…ฅไธญ...", + "clipboard-search-description": "ๆœๅฐ‹ๅ‰ช่ฒผ็ฐฟๆญทๅฒ", + "command-description": "ๅŸท่กŒ shell ๆŒ‡ไปค", + "emoji": "ๆŒ‘้ธ่กจๆƒ…็ฌฆ่™Ÿ", + "emoji-loading": "่ผ‰ๅ…ฅ่กจๆƒ…็ฌฆ่™Ÿ...", + "emoji-loading-description": "่ซ‹็จๅ€™", + "emoji-no-recent": "้‚„ๆฒ’ๆœ‰ๆœ€่ฟ‘ไฝฟ็”จ็š„่กจๆƒ…็ฌฆ่™Ÿ", + "emoji-search-description": "ๆœๅฐ‹ๅ’Œ่ค‡่ฃฝ่กจๆƒ…็ฌฆ่™Ÿ", + "settings-search-description": "ๆœๅฐ‹ไธฆๅ‰ๅพ€่จญๅฎš", + "windows-search-description": "ๆœๅฐ‹ไธฆ่š็„ฆ้–‹ๅ•Ÿ็š„่ฆ–็ช—" + } + }, + "lock-screen": { + "authenticating": "้ฉ—่ญ‰ไธญ...", + "password": "่ซ‹่ผธๅ…ฅไฝ ็š„ๅฏ†็ขผ...", + "restart": "้‡ๆ–ฐๅ•Ÿๅ‹•", + "shut-down": "้—œๆฉŸ" + }, + "notifications": { + "panel": { + "click-to-collapse": "้ปžๆ“Šๆ‘บ็–Š", + "click-to-expand": "้ปžๆ“Šๅฑ•้–‹", + "description": "ๅฆ‚ๆžœๆœ‰ๆ”ถๅˆฐ้€š็Ÿฅๅฐฑๆœƒ้กฏ็คบๅœจ้€™่ฃก", + "no-notifications": "ๆฒ’ๆœ‰ไปปไฝ•้€š็Ÿฅ" + }, + "range": { + "earlier": "ๆ›ดๆ—ฉไปฅๅ‰", + "today": "ไปŠๅคฉ", + "yesterday": "ๆ˜จๅคฉ" + }, + "time": { + "diff-d": "ไธ€ๅคฉๅ‰", + "diff-dd": "{diff} ๅคฉๅ‰", + "diff-h": "ไธ€ๅฐๆ™‚ๅ‰", + "diff-hh": "{diff} ๅฐๆ™‚ๅ‰", + "diff-m": "ไธ€ๅˆ†้˜ๅ‰", + "diff-mm": "{diff} ๅˆ†้˜ๅ‰", + "now": "็พๅœจ" + } + }, + "options": { + "bar": { + "density-comfortable": "้ค˜่ฃ•", + "density-compact": "็ทŠๆนŠ", + "density-default": "้ ่จญ", + "density-mini": "่ฟทไฝ ", + "density-spacious": "ๅฏฌๆ•ž", + "display-mode-always-visible": "ๆฐธ้ ๅฏ่ฆ‹", + "type-floating": "ๆ‡ธๆตฎ", + "type-framed": "้‚Šๆก†", + "type-simple": "็ฐก็ด„" + }, + "control-center": { + "quick-settings-style-classic": "ๅคๅ…ธ", + "quick-settings-style-modern": "็พไปฃ" + }, + "frame-rates-fps": "{fps} FPS", + "launcher-density": { + "comfortable": "่ˆ’้ฉ", + "compact": "็ทŠๆนŠ", + "default": "้ ่จญ" + }, + "launcher-view-mode": { + "grid": "็ถฒๆ ผ", + "list": "ๅˆ—่กจ" + }, + "notification-density": { + "compact": "็ทŠๆนŠ", + "default": "้ ่จญ" + }, + "scrolling-modes": { + "always": "็ธฝๆ˜ฏๆฒๅ‹•", + "hover": "ๆธธๆจ™็งป้Žๆ™‚ๆฒๅ‹•", + "never": "ๅพžไธๆฒๅ‹•" + }, + "session-menu-grid-layout": { + "grid": "ๆ–นๆ ผ็‹€", + "single-row": "ๅ–ฎๅˆ—" + }, + "settings-panel-mode": { + "attached": "้ขๆฟไพ้™„ๅœจๅทฅๅ…ทๅˆ—", + "centered": "ๅฑ…ไธญ้ขๆฟ", + "window": "ๅˆ†้›ข่ฆ–็ช—" + }, + "visualizer-types": { + "linear": "็ทšๆข", + "mirrored": "้กๅƒ", + "wave": "ๆณขๅฝข" + }, + "visualizer-visibility": { + "with-background": "ๅชๅœจๆœ‰่ƒŒๆ™ฏๆ™‚" + }, + "workspace-labels": { + "index": "็ทจ่™Ÿ", + "index-and-name": "็ทจ่™ŸๅŠๅ็จฑ", + "name": "ๅ็จฑ" + } + }, + "panels": { + "about": { + "become-supporter": "ๆˆ็‚บๆ”ฏๆŒ่€…", + "changelog": "ๆชข่ฆ–ๆ›ดๆ–ฐๆ—ฅ่ชŒ", + "changelog-on-startup": "ๆ›ดๆ–ฐๆ™‚้กฏ็คบ่ฎŠๆ›ดๆ—ฅ่ชŒ", + "changelog-on-startup-desc": "Noctalia ๆ›ดๆ–ฐๆ™‚่‡ชๅ‹•้กฏ็คบๆ›ดๆ–ฐๆ—ฅ่ชŒใ€‚", + "contributors-description": "็‰นๅˆฅๆ„Ÿ่ฌๆˆ‘ๅ€‘้€™{count}ไฝ่ถ…่ฎš็š„่ฒข็ป่€…!!", + "contributors-description-plural": "็‰นๅˆฅๆ„Ÿ่ฌๆˆ‘ๅ€‘้€™{count}ไฝ่ถ…่ฎš็š„่ฒข็ป่€…!!", + "copy-info": "่ค‡่ฃฝ่ณ‡่จŠ", + "debug-disabled": "ๅต้Œฏๆจกๅผๅทฒๅœ็”จ", + "debug-enabled": "ๅต้Œฏๆจกๅผๅทฒๅ•Ÿ็”จ", + "info-copied": "่ณ‡่จŠๅทฒ่ค‡่ฃฝ่‡ณๅ‰ช่ฒผ็ฐฟ", + "noctalia-available": "ๅฏ็”จ:", + "noctalia-desc": "ไธ€ๅ€‹็‚บ Wayland ็ฒพๅฟƒๆ‰“้€ , ๅ…ผๅ…ท็ฐกๆฝ”ๅŠๆตๆšข็š„ๆกŒ้ขไป‹้ข, ไฝฟ็”จ Quickshell ๅปบ็ฝฎ", + "noctalia-git-commit": "Git commit:", + "noctalia-installed-version": "ๅฎ‰่ฃ็‰ˆๆœฌ:", + "noctalia-latest-version": "ๆœ€ๆ–ฐ็‰ˆๆœฌ:", + "noctalia-qs-version": "Noctalia QS ็‰ˆๆœฌ:", + "noctalia-title": "Noctalia Shell", + "privacy-policy": "้šฑ็งๆฌŠๆ”ฟ็ญ–", + "support": "ๆ”ฏๆŒๆˆ‘ๅ€‘", + "supporter-badge": "ๆ”ฏๆŒ่€…", + "supporters-desc": "้žๅธธๆ„Ÿ่ฌๆˆ‘ๅ€‘ๅ‡บ่‰ฒ็š„ๆ”ฏๆŒ่€…๏ผ", + "supporters-desc-plural": "้žๅธธๆ„Ÿ่ฌๆˆ‘ๅ€‘็š„{count}ไฝๅ‡บ่‰ฒๆ”ฏๆŒ่€…๏ผ", + "supporters-loading": "ๆญฃๅœจ่ผ‰ๅ…ฅๆ”ฏๆŒ่€…...", + "system-board": "ไธปๆฉŸๆฟ:", + "system-cpu": "CPU:", + "system-disk": "็ฃ็ขŸ:", + "system-gpu": "GPU:", + "system-host": "ไธปๆฉŸๅ็จฑ:", + "system-install-hint": "ๅฎ‰่ฃ fastfetch ๅฅ—ไปถๅณๅฏๆชข่ฆ–็ณป็ตฑ่ณ‡่จŠ", + "system-kernel": "ๆ ธๅฟƒ:", + "system-loading": "ๆญฃๅœจ่ผ‰ๅ…ฅ็ณป็ตฑ่ณ‡่จŠ...", + "system-memory": "่จ˜ๆ†ถ้ซ”:", + "system-monitor": "้กฏ็คบๅ™จ:", + "system-not-installed": "ๅฐšๆœชๅฎ‰่ฃ fastfetch", + "system-os": "ไฝœๆฅญ็ณป็ตฑ:", + "system-packages": "ๅฅ—ไปถๆ•ธ้‡:", + "system-product": "็”ขๅ“:", + "system-title": "็ณป็ตฑ่ณ‡่จŠ:", + "system-uptime": "้–‹ๆฉŸๆ™‚้–“:", + "system-wm": "่ฆ–็ช—็ฎก็†ๅ™จ:", + "telemetry-data-copied": "ๅทฒ่ค‡่ฃฝๅŒฟๅ่ณ‡่จŠ่‡ณๅ‰ช่ฒผ็ฐฟ", + "telemetry-desc": "ๆไพ›ๅŒฟๅๅŒ–็š„็ณป็ตฑ่ณ‡่จŠ (็•ซ้ข่งฃๆžๅบฆ, ๅˆๆˆๅ™จ, ็™ผ่กŒ็‰ˆ) ๅนซๅŠฉ Noctalia ๆ”น้€ฒ; ๅชๅœจๆกŒ้ขๅ•Ÿๅ‹•ๆ™‚ๅŸท่กŒ, ๆฒ’ๆœ‰้กๅค–่ฟฝ่นค; ๅ‚ณ้€็š„่ณ‡ๆ–™ๆœƒๅœจ30ๅคฉๅพŒ่‡ชๅ‹•ๅˆช้™ค", + "telemetry-enabled": "ๅ‚ณ้€ๅŒฟๅๅŒ–็š„็ณป็ตฑ่ณ‡่จŠ", + "telemetry-show-data": "ๆชข่ฆ–่ณ‡ๆ–™", + "telemetry-title": "้šฑ็ง", + "title": "้—œๆ–ผ", + "up-to-date": "ไฝ ๅทฒ็ถ“ๅœจๆœ€ๆ–ฐ็‰ˆไบ†!!", + "update-available": "ๆœ‰ๆ›ดๆ–ฐ็‰ˆๆœฌ", + "view-commit": "ๅœจ GitHub ไธŠๆชข่ฆ– commit" + }, + "audio": { + "devices-desc": "่จญๅฎš้Ÿณ่จŠ่ผธๅ…ฅๅŠ่ผธๅ‡บ่ฃ็ฝฎ", + "devices-input-device-description": "้ธๆ“‡ๆƒณ็”จ็š„้Ÿณ่จŠ่ผธๅ…ฅ่ฃ็ฝฎ", + "devices-input-device-label": "่ผธๅ…ฅ่ฃ็ฝฎ", + "devices-output-device-description": "้ธๆ“‡ๆƒณ็”จ็š„้Ÿณ่จŠ่ผธๅ‡บ่ฃ็ฝฎ", + "devices-output-device-label": "่ผธๅ‡บ่ฃ็ฝฎ", + "devices-title": "้Ÿณ่จŠ่ฃ็ฝฎ", + "external-mixer-description": "่ผธๅ…ฅๅ•Ÿ็”จๅค–้ƒจๆทท้Ÿณๅ™จๆ™‚ๆ‰€็”จ็š„ๆŒ‡ไปคๆˆ–ๆ˜ฏ็จ‹ๅผ่ทฏๅพ‘", + "external-mixer-label": "ๅค–้ƒจๆทท้Ÿณๅ™จๆŒ‡ไปค", + "external-mixer-placeholder": "pwvucontrol || pavucontrol", + "media-desc": "่จญๅฎšไฝ ๅๅฅฝๆˆ–ๆƒณๅฟฝ็•ฅ็š„ๅช’้ซ”ๆ’ญๆ”พ็จ‹ๅผ", + "media-excluded-player-description": "่ผธๅ…ฅไฝ ๆƒณ่ฆ็ณป็ตฑๅฟฝ็•ฅ็š„ๆ’ญๆ”พๅ™จ้—œ้ตๅญ—, ไธๅŒ้—œ้ตๅญ—้–“ๆ‡‰ๅˆ†่กŒๅ€้š”", + "media-excluded-player-label": "ๆŽ’้™ค็š„ๆ’ญๆ”พๅ™จ", + "media-excluded-player-placeholder": "่ผธๅ…ฅๆ’ญๆ”พๅ™จ้—œ้ตๅญ—ๅพŒ้ปž + ่™Ÿ", + "media-frame-rate-description": "้ ป็އ่ถŠ้ซ˜ๆ•ˆๆžœ่ถŠๆป‘้ †, ไฝ†ไนŸๆœƒ็”จๆ„ˆๅคš่ณ‡ๆบ", + "media-frame-rate-label": "ๅน€ๆ•ธ", + "media-primary-player-description": "่ผธๅ…ฅๅฏไปฅ่พจ่ญ˜ไฝ ็š„ๅธธ็”จๆ’ญๆ”พๅ™จ็š„้—œ้ตๅญ—", + "media-primary-player-label": "้ฆ–่ฆๆ’ญๆ”พๅ™จ", + "media-primary-player-placeholder": "ไพ‹ๅฆ‚: spotify, vlc, mpv", + "media-scrolling-speed-description": "ไปฅ็ง’็‚บๅ–ฎไฝ่จญๅฎšๆจ™้กŒๅพž้ ญๆฒๅ‹•ไธ€่ผช็š„ๆ™‚้–“", + "media-scrolling-speed-label": "ๆฒๅ‹•้€Ÿๅบฆ", + "media-scrolling-title-description": "ๅœจ็ขฐๅˆฐ้•ทๅช’้ซ”ๆจ™้กŒๆ™‚ๅ•Ÿ็”จๆŒ็บŒๆฒๅ‹•", + "media-scrolling-title-label": "ๆ›ฒ็›ฎๆฒๅ‹•", + "media-title": "ๅช’้ซ”ๆ’ญๆ”พๅ™จ", + "on-middle-clicked-description": "ๅœจ้ปžๆ“Šไธญ้ตๆ™‚่ฆๅŸท่กŒ็š„ๆŒ‡ไปค", + "panel-applications-empty": "ๆฒ’ๆœ‰ๆญฃๅœจๆ’ญๆ”พ้Ÿณ่จŠ็š„็จ‹ๅผ", + "spectrum-mirrored-description": "้กๅƒ้ ป่ญœ๏ผŒไฝฟไฝŽ้ ปไฝๆ–ผไธญๅคฎใ€‚้—œ้–‰ๆ™‚๏ผŒไฝŽ้ ปๅœจๅทฆๅด๏ผŒ้ซ˜้ ปๅœจๅณๅดใ€‚", + "spectrum-mirrored-label": "้กๅƒ้ ป่ญœ", + "title": "้Ÿณ่จŠ", + "visualizer-type-description": "้ธๆ“‡ๅช’้ซ”ๆ’ญๆ”พๆ™‚็š„่ฆ–่ฆบๆ•ˆๆžœๆจฃๅผ", + "visualizer-type-label": "่ฆ–่ฆบๆ•ˆๆžœๆจฃๅผ", + "volumes-desc": "่ชฟๆ•ด้Ÿณ่จŠๆŽงๅˆถๅŠ้Ÿณ้‡", + "volumes-feedback-sound-file-description": "ๆ›ดๆ”น้Ÿณ้‡ๆ™‚ๆ’ญๆ”พ็š„่ฒ้Ÿณๆช”ๆกˆ่ทฏๅพ‘ใ€‚", + "volumes-feedback-sound-file-label": "้Ÿณ้‡ๅ›ž้ฅ‹่ฒ้Ÿณๆช”ๆกˆ", + "volumes-feedback-sound-file-select-title": "้ธๆ“‡้Ÿณ้‡ๅ›ž้ฅ‹่ฒ้Ÿณๆช”ๆกˆ", + "volumes-input-volume-description": "้บฅๅ…‹้ขจ่ผธๅ…ฅ้Ÿณ้‡", + "volumes-mute-input-description": "ๅฐ‡้ ่จญ้Ÿณ่จŠ่ผธๅ…ฅ (้บฅๅ…‹้ขจ) ้œ้Ÿณ", + "volumes-mute-input-label": "่ผธๅ…ฅ้œ้Ÿณ", + "volumes-mute-output-description": "ๅฐ‡็ณป็ตฑ็š„ไธป่ฆ้Ÿณ่จŠ่ผธๅ‡บ้œ้Ÿณ", + "volumes-mute-output-label": "่ผธๅ‡บ้œ้Ÿณ", + "volumes-output-volume-description": "็ณป็ตฑๅ…จๅŸŸ้Ÿณ้‡", + "volumes-step-size-description": "ๅพฎ่ชฟ้Ÿณ้‡่ชฟๆ•ดไธ€ๆ ผ็š„ๅคงๅฐ (ๆปพ่ผชๅŠ้ต็›คๅฟซๆท้ต)", + "volumes-step-size-label": "้Ÿณ้‡ๆญฅ้€ฒๅคงๅฐ", + "volumes-volume-feedback-description": "่ชฟๆ•ด้Ÿณ้‡ๆ™‚ๆ’ญๆ”พๅ›ž้ฅ‹้Ÿณๆ•ˆ", + "volumes-volume-feedback-label": "ๆ’ญๆ”พ้Ÿณ้‡ๅ›ž้ฅ‹้Ÿณๆ•ˆ", + "volumes-volume-overdrive-description": "ๅขžๅผท้Ÿณ้‡่ชฟๆ•ด่ถ…้Ž100%, ไธๆ˜ฏๆ‰€ๆœ‰็กฌ้ซ”้ƒฝๆ”ฏๆด้€™ๅ€‹่จญๅฎš", + "volumes-volume-overdrive-label": "ๅ…่จฑ้Ÿณ้‡้Ž่ผ‰" + }, + "bar": { + "appearance-auto-hide-delay-description": "ๆป‘้ผ ้›ข้–‹ๅพŒBar้šฑ่—ๆ‰€้œ€ๆ™‚้–“", + "appearance-auto-hide-delay-label": "้šฑ่—ๅปถ้ฒ", + "appearance-auto-hide-exclusive-note": "ๆณจๆ„: ็•ถ่‡ชๅ‹•้šฑ่—่™•ๆ–ผๆดปๅ‹•็‹€ๆ…‹ๆ™‚๏ผŒๅฐˆๅฑฌๅ€ๅŸŸๆœƒ่‡ชๅ‹•็ฆ็”จใ€‚", + "appearance-auto-show-delay-description": "ๆป‘้ผ ้€ฒๅ…ฅ้‚Š็ทฃๆ™‚๏ผŒๆฌ„้กฏ็คบๅ‰็š„ๅปถ้ฒๆ™‚้–“ใ€‚", + "appearance-auto-show-delay-label": "้กฏ็คบๅปถ้ฒ", + "appearance-background-opacity-description": "่จญๅฎšๅทฅๅ…ทๅˆ—่ƒŒๆ™ฏ็š„ไธ้€ๆ˜Žๅบฆ", + "appearance-background-opacity-label": "ๅทฅๅ…ทๅˆ—่ƒŒๆ™ฏไธ้€ๆ˜Žๅบฆ", + "appearance-capsule-color-description": "็‚บๆฌ„่† ๅ›Š้ธๆ“‡ไธ€็จฎ้ก่‰ฒ๏ผŒๆˆ–ไธไฝฟ็”จไปปไฝ•้ก่‰ฒไปฅๆŽก็”จ้ ่จญ่กจ้ข้ก่‰ฒใ€‚", + "appearance-capsule-color-label": "่† ๅ›Š้ก่‰ฒ", + "appearance-capsule-opacity-description": "่จญๅฎš็•ถๅ•Ÿ็”จ้กฏ็คบๅฐๅฎนๅ™จๆ™‚ๅฐๅทฅๅ…ท็š„่ƒŒๆ™ฏไธ้€ๆ˜Žๅบฆ", + "appearance-capsule-opacity-label": "ๅฐๅฎนๅ™จไธ้€ๆ˜Žๅบฆ", + "appearance-content-padding-description": "่ชฟๆ•ดๅˆ—้‚Š็ทฃ่ˆ‡ๅฐๅทฅๅ…ทไน‹้–“็š„ๅ…ง้‚Š่ทใ€‚", + "appearance-content-padding-label": "ๅ…งๅฎนๅ…ง้‚Š่ท", + "appearance-density-description": "่ชฟๆ•ดๅทฅๅ…ทๅˆ—็š„ๅ…ง้ƒจ้–“่ทไพ†ๅšๅ‡บ็ทŠๆนŠๆˆ–ๅฏฌๆ•ž็š„ๅค–่ง€", + "appearance-density-label": "ๅทฅๅ…ทๅˆ—ๅฏ†ๅบฆ", + "appearance-desc": "่‡ช่จ‚ๅทฅๅ…ทๅˆ—็š„ๅค–่ง€ๅŠไฝ็ฝฎ", + "appearance-display-mode-description": "้ธๆ“‡ๅˆ—ไฝ•ๆ™‚ๅฏ่ฆ‹", + "appearance-enable-exclusion-zone-inset-description": "ๆธ›ๅฐ‘ๆŽ’้™คๅ€ๅŸŸ1ๅ€‹ๅฏฆ้ซ”ๅƒ็ด ๏ผŒไปฅไพฟ้ฝŠๅนณ็š„่ฆ–็ช—่ƒฝๅฎŒ็พŽๅœฐๅปถไผธๅˆฐๅทฅๅ…ทๅˆ—้‚Š็ทฃไธ‹ๆ–นใ€‚", + "appearance-enable-exclusion-zone-inset-label": "ๅ…งๅตŒๆŽ’้™คๅ€ๅŸŸ", + "appearance-floating-description": "ๅฐ‡ๅทฅๅ…ทๅˆ—ไปฅๆ‡ธๆตฎ็š„ๅœ“ๅผง้•ทๆข็‹€้กฏ็คบ", + "appearance-floating-label": "ๆ‡ธๆตฎๅทฅๅ…ทๅˆ—", + "appearance-font-scale-description": "่ชฟๆ•ดๅˆ—ไธญ้กฏ็คบ็š„ๆ–‡ๅญ—็š„ๅญ—้ซ”ๅคงๅฐๆฏ”ไพ‹ใ€‚", + "appearance-font-scale-label": "ๅญ—้ซ”็ธฎๆ”พ", + "appearance-frame-radius": "ๅ…งๅœ“่ง’", + "appearance-frame-settings-description": "่ชฟๆ•ด้‚Šๆก†็ฒ—็ดฐๅ’Œๅ…งๅœ“่ง’ๅŠๅพ‘", + "appearance-frame-settings-label": "้‚Šๆก†่จญๅฎš", + "appearance-frame-thickness": "็ฒ—็ดฐ", + "appearance-hide-on-overview-description": "็•ถๅœจๅˆๆˆๅ™จๆฆ‚่ฆฝๆ™‚, ้šฑ่—ๅทฅๅ…ทๅˆ—ไธฆ้—œ้–‰้ขๆฟ", + "appearance-hide-on-overview-label": "ๆฆ‚่ฆฝๆ™‚้šฑ่—ๅทฅๅ…ทๅˆ—", + "appearance-margins-description": "่ชฟๆ•ดๆ‡ธๆตฎๅทฅๅ…ทๅˆ—็š„้‚Š่ท", + "appearance-margins-horizontal": "ๆฐดๅนณ้‚Š่ท", + "appearance-margins-vertical": "ๅž‚็›ด้‚Š่ท", + "appearance-outer-corners-description": "ๅฐ‡ๅทฅๅ…ทๅˆ—ๅ‘ๅค–ๅปถไผธ้กฏ็คบ็•ซ้ขๅœ“่ง’", + "appearance-outer-corners-label": "ๅค–ๅดๅœ“่ง’", + "appearance-position-description": "้ธๆ“‡่ฆๆŠŠๅทฅๅ…ทๅˆ—ๆ”พๅœจ่žขๅน•ๅ“ช่ฃก", + "appearance-position-label": "ๅทฅๅ…ทๅˆ—ไฝ็ฝฎ", + "appearance-show-capsule-description": "้กฏ็คบๅฐๅทฅๅ…ท็š„ๅกซๅ……่ƒŒๆ™ฏ", + "appearance-show-capsule-label": "้กฏ็คบๅฐๅฎนๅ™จ", + "appearance-show-on-workspace-switch-description": "ๅˆ‡ๆ›ๅทฅไฝœๅ€ๆ™‚่‡ชๅ‹•็Ÿญๆšซ้กฏ็คบ็‹€ๆ…‹ๅˆ—ใ€‚", + "appearance-show-on-workspace-switch-label": "ๅทฅไฝœๅ€ๅˆ‡ๆ›ๆ™‚้กฏ็คบ็‹€ๆ…‹ๅˆ—", + "appearance-show-outline-description": "่ฎ“ๆฏๅ€‹ๅฐๅทฅๅ…ท้ƒฝ้กฏ็คบๅฏ่ฆ–้‚Šๆก†", + "appearance-show-outline-label": "้กฏ็คบๅฐๅทฅๅ…ทๅค–ๆก†", + "appearance-type-description": "้ธๆ“‡ๆฌ„็š„ๆจฃๅผ๏ผš็ฐก็ด„ใ€ๆ‡ธๆตฎๆˆ–้‚Šๆก†", + "appearance-type-label": "ๆฌ„้กžๅž‹", + "appearance-use-separate-opacity-description": "ๅ•Ÿ็”จๅพŒๅฏ้‡ๅฐๅทฅๅ…ทๅˆ—่ƒŒๆ™ฏไฝฟ็”จๅˆ†้–‹็š„ไธ้€ๆ˜Žๅบฆๆ•ธๅ€ผ", + "appearance-use-separate-opacity-label": "ๅˆ†้–‹่จญๅฎšๅทฅๅ…ทๅˆ—ไธ้€ๆ˜Žๅบฆ", + "appearance-widget-spacing-description": "่ชฟๆ•ดๅˆ—ไธญๆฏๅ€‹ๅฐๅทฅๅ…ทไน‹้–“็š„้–“่ทใ€‚", + "appearance-widget-spacing-label": "ๅฐๅทฅๅ…ท้–“่ท", + "behavior-middle-click-command-description": "ไธญ้ต้ปžๆ“Šๆ™‚ๅŸท่กŒ็š„ๆŒ‡ไปคใ€‚", + "behavior-middle-click-command-label": "ไธญ้ต้ปžๆ“ŠๆŒ‡ไปค", + "behavior-middle-click-command-placeholder": "niri msg action ๅˆ‡ๆ›ๆฆ‚่ฆฝ", + "behavior-middle-click-description": "้ธๆ“‡ๅœจๆฌ„็š„็ฉบ็™ฝๅ€ๅŸŸไธญ้ต้ปžๆ“Š็š„ๆ“ไฝœใ€‚", + "behavior-middle-click-follow-mouse-description": "ๅœจๆธธๆจ™ไฝ็ฝฎ้–‹ๅ•Ÿ้ธๅฎš็š„ไธญ้ต้ปžๆ“Š้ขๆฟใ€‚", + "behavior-middle-click-follow-mouse-label": "ไธญ้ต้ปžๆ“Š่ทŸ้šจๆป‘้ผ ", + "behavior-middle-click-label": "ๅˆ—ไธญ้ต้ปžๆ“Šๅ‹•ไฝœ", + "behavior-right-click-command-description": "ๅณ้ต้ปžๆ“Šๆ™‚ๅŸท่กŒ็š„ๅ‘ฝไปคใ€‚", + "behavior-right-click-command-label": "ๅณ้ต้ปžๆ“Šๅ‘ฝไปค", + "behavior-right-click-command-placeholder": "notify-send \"ๅณ้ต้ปžๆ“Š\"", + "behavior-right-click-description": "้ธๆ“‡ๅœจๅˆ—็š„็ฉบ็™ฝๅ€ๅŸŸๅณ้ต้ปžๆ“Šๆ™‚ๅŸท่กŒ็š„ๆ“ไฝœใ€‚", + "behavior-right-click-follow-mouse-description": "ๅœจๆธธๆจ™ไฝ็ฝฎ้–‹ๅ•Ÿ้ธๅฎš็š„ๅณ้ต้ขๆฟใ€‚", + "behavior-right-click-follow-mouse-label": "ๅณ้ต่ทŸ้šจๆป‘้ผ ", + "behavior-right-click-label": "ๆฌ„ๅณ้ต้ปžๆ“Šๆ“ไฝœ", + "behavior-wheel-wrap-description": "ๅ•Ÿ็”จๅพŒ๏ผŒๆฒๅ‹•ๆœƒๅพžๆœ€ๅพŒไธ€้ …็นผ็บŒๅˆฐ็ฌฌไธ€้ …ใ€‚", + "behavior-wheel-wrap-label": "ๅพช็’ฐ", + "behavior-workspace-scroll-description": "้ธๆ“‡ๆป‘้ผ ๆปพ่ผชๅœจๆฌ„็š„็ฉบ็™ฝๅ€ๅŸŸๅŸท่กŒไป€้บผๆ“ไฝœใ€‚", + "behavior-workspace-scroll-label": "ๅˆ—ๆป‘้ผ ๆปพ่ผชๅ‹•ไฝœ", + "behavior-workspace-scroll-option-content": "ๅ…งๅฎน", + "behavior-workspace-scroll-option-workspace": "ๅทฅไฝœๅ€", + "monitor-configure-widgets": "่จญๅฎšๅฐๅทฅๅ…ท", + "monitor-override-settings": "่ฆ†ๅฏซๅ…จๅŸŸ่จญๅฎš", + "monitor-override-settings-description": "ๅœจ้€™ๅ€‹้กฏ็คบๅ™จไธŠไฝฟ็”จ่‡ช่จ‚่จญๅฎš", + "monitor-reset-all": "ๅ…จ้ƒจ้‡่จญ", + "monitor-widgets-title": "ๅฐๅทฅๅ…ท่จญๅฎš - {monitor}", + "monitors-desc": "ๅชๅœจ็‰นๅฎš็š„้กฏ็คบๅ™จ้กฏ็คบๅทฅๅ…ทๅˆ—, ๅฆ‚ๆžœๅ…จ้ƒจๅ–ๆถˆๅ‹พ้ธๅ‰‡ๆœƒ้กฏ็คบๅœจๆ‰€ๆœ‰้กฏ็คบๅ™จ", + "monitors-desc-new": "่จญๅฎšๅœจๅ“ชไบ›่žขๅน•้กฏ็คบๅทฅๅ…ทๅˆ—, ไธฆ่‡ช่จ‚ๆฏๅ€‹่žขๅน•็š„่จญๅฎš", + "monitors-title": "ๅœจ้กฏ็คบๅ™จ้กฏ็คบ", + "title": "ๅทฅๅ…ทๅˆ—", + "tray-blacklist-description": "ๆ–ฐๅขž็ณป็ตฑๅŒฃ็š„ๆŽ’้™ค้ …็›ฎ, ๆ”ฏๆด่ฌ็”จๅญ—ๅ…ƒ (*)", + "tray-blacklist-label": "ๆŽ’้™คๅๅ–ฎ", + "tray-blacklist-placeholder": "ไพ‹ๅฆ‚: nm-applet, Fcitx*", + "tray-pin-application": "้‡˜้ธ็จ‹ๅผ", + "tray-unpin-application": "ๅ–ๆถˆ้‡˜้ธ็จ‹ๅผ", + "use-global-widgets": "ไฝฟ็”จๅ…จๅŸŸๅฐๅทฅๅ…ท", + "widgets-desc": "ๆ‹–ๆ›ณๅฐๅทฅๅ…ทๅฏไปฅ่ฎŠๆ›ดๆŽ’ๅˆ—้ †ๅบ, ๅฏๅˆฉ็”จๅณ้ต้ธๅ–ฎๅœจๅ€ๅกŠ้–“่ฎŠๅ‹•ๆˆ–็›ดๆŽฅ่งฃ้™คๅฎƒๅ€‘", + "widgets-title": "ๆŽ’ๅˆ—ๅฐๅทฅๅ…ท" + }, + "color-scheme": { + "color-source-use-wallpaper-colors-description": "ๅพžไฝ ็š„ๆกŒๅธƒ็”ข็”Ÿไธป้กŒ้…่‰ฒ, ่‡ชๅ‹•ๅพžๆŠฝๅ–็š„้ก่‰ฒๅปบ็ซ‹ไธ€ๅ€‹้€ฃ่ฒซ็š„ไธป้กŒ", + "color-source-use-wallpaper-colors-label": "ไฝฟ็”จๆกŒๅธƒ้…่‰ฒ", + "dark-mode-mode-description": "ๅ•Ÿ็”จ่‡ชๅ‹•ๅˆ‡ๆ›ๆทบ่‰ฒๅŠๆš—่‰ฒๆจกๅผ", + "dark-mode-mode-label": "ๆทฑ่‰ฒๆจกๅผ", + "dark-mode-mode-manual": "ๆ‰‹ๅ‹•ๆŽ’็จ‹", + "dark-mode-mode-off": "้—œ้–‰", + "dark-mode-switch-description": "ๅœจๅคœ้–“ๅˆ‡ๆ›ๅˆฐๆทฑ่‰ฒไธป้กŒ็œผ็›ๅฏไปฅ่ง€็œ‹็š„ๆ›ด่ผ•้ฌ†", + "delete-error-description": "ๅˆช้™ค {scheme} ้…่‰ฒๅคฑๆ•—", + "delete-error-title": "ๅˆช้™คๅคฑๆ•—", + "delete-success-description": "ๆˆๅŠŸๅˆช้™ค {scheme} ้…่‰ฒ", + "delete-success-title": "้…่‰ฒๅทฒๅˆช้™ค", + "download-button": "ไธ‹่ผ‰ๆ›ดๅคš้…่‰ฒ", + "download-downloading": "ไธ‹่ผ‰ไธญ...", + "download-empty": "ๆฒ’ๆœ‰ๅฏ็”จ็š„้…่‰ฒ", + "download-error-api-error": "API ้Œฏ่ชค: {status}", + "download-error-description": "ไธ‹่ผ‰ {scheme} ้…่‰ฒๅคฑๆ•—", + "download-error-download-failed": "ไธ‹่ผ‰ๅคฑๆ•—, ้Œฏ่ชค็ขผ: {code}", + "download-error-invalid-response": "็„กๆ•ˆ็š„ API ๅ›žๆ‡‰ๆ ผๅผ", + "download-error-no-files": "ๆ‰พไธๅˆฐไปปไฝ•ๅฏไฝœ็‚บ้…่‰ฒ็š„ๆช”ๆกˆ", + "download-error-parse-failed": "่งฃๆž API ๅ›žๆ‡‰ๆ™‚็™ผ็”Ÿ้Œฏ่ชค: {error}", + "download-error-rate-limit": "ๅทฒ่ถ…้Ž GitHub API ็š„ๅญ˜ๅ–้‡้™ๅˆถ", + "download-error-title": "ไธ‹่ผ‰ๅคฑๆ•—", + "download-fetching": "ๆญฃๅœจ็ฒๅพ—ๅฏ็”จ็š„้…่‰ฒ...", + "download-success-description": "ๆˆๅŠŸไธ‹่ผ‰ {scheme} ้…่‰ฒ", + "download-success-title": "้…่‰ฒๅทฒไธ‹่ผ‰", + "download-title": "ไธ‹่ผ‰้…่‰ฒ", + "method-description": { + "content": "ๅ…ทๆœ‰้ซ˜ไฟ็œŸ่‰ฒๅฝฉๆๅ–็š„ Material Design ๆ–นๆกˆ๏ผŒๅฏ่ˆ‡ไพ†ๆบๅ…งๅฎน็š„ๅฏฆ้š›่‰ฒๅฝฉ็ทŠๅฏ†ๅŒน้…ใ€‚", + "dysfunctional": "่ˆ‡ Faithful ้กžไผผ๏ผŒไฝ†้ธๆ“‡็ฌฌไบŒไธปๅฐŽ็š„้ก่‰ฒๅฎถๆ—ไฝœ็‚บไธป่ฆ้ก่‰ฒใ€‚", + "faithful": "ๅ˜—่ฉฆๅœจ็”Ÿๆˆๅ’Œ่ซง่ชฟ่‰ฒ็›ค็š„ๅŒๆ™‚๏ผŒ็›กๅฏ่ƒฝๆŽฅ่ฟ‘ไพ†ๆบ้ก่‰ฒใ€‚", + "fruit-salad": "Material Design ้…่‰ฒๆ–นๆกˆ๏ผŒๅฏ็”ข็”Ÿไธ€ๅ€‹ไฟ็šฎใ€ๅ……ๆปฟๆดปๅŠ›็š„่ชฟ่‰ฒ็›ค๏ผŒๅ…ทๆœ‰ๅคšๆจฃไธ”ๅคš่ฎŠ็š„่‰ฒ่ชฟใ€‚", + "monochrome": "ไฝฟ็”จๅ–ฎไธ€่‰ฒ่ชฟ็ฐ้šŽๅ’Œๆœ€ๅฐ‘่‰ฒๅฝฉๅ…งๅฎน็š„ Material Design ๆ–นๆกˆใ€‚", + "muted": "็”ข็”Ÿๆ›ดๆŸ”ๅ’Œใ€ไธ้ฃฝๅ’Œ็š„่‰ฒ่ชฟ๏ผŒ็‡Ÿ้€ ๅนณ้œ่€ŒไฝŽ่ชฟ็š„ๅค–่ง€ใ€‚", + "rainbow": "Material Design ๆ–นๆกˆ๏ผŒๅฏๅœจๆ•ดๅ€‹ๅ…‰่ญœไธญ็”ข็”Ÿ้ก่‰ฒ๏ผŒไปฅๅฏฆ็พๆœ€ๅคง็š„้ก่‰ฒๅคšๆจฃๆ€งใ€‚", + "tonal-spot": "Material Design ้ ่จญๆผ”็ฎ—ๆณ•ใ€‚ไฝฟ็”จไพ†ๆบ้ก่‰ฒ็š„่‰ฒ่ชฟ่ฎŠๅŒ–ๅปบ็ซ‹ๅนณ่กก็š„่ชฟ่‰ฒ็›คใ€‚", + "vibrant": "ๆœ€ๅคงๅŒ–่‰ฒๅฝฉ้ฃฝๅ’Œๅบฆ๏ผŒๆ‰“้€ ๅคง่†ฝ้†’็›ฎ็š„่ชฟ่‰ฒ็›ค๏ผŒๅ‘ˆ็พ็”Ÿๅ‹•่‰ฒ่ชฟใ€‚" + }, + "predefined-desc": "ๅพžไธ€ๆ‰น้ ๅ…ˆ่จญ่จˆๅฅฝ็š„้…่‰ฒไธญ้ธๆ“‡", + "predefined-generate-templates-label": "็‚บ้ ๅ…ˆ่จญ่จˆ็š„้…่‰ฒๅปบ็ซ‹ๆจกๆฟ", + "predefined-title": "้ ๅ…ˆ่จญ่จˆ็š„้…่‰ฒ", + "sync-gsettings-description": "ๅฐ‡็ณป็ตฑไธป้กŒ่ˆ‡ๆดปๅ‹•็š„ๆทบ่‰ฒๆˆ–ๆทฑ่‰ฒ่ฎŠ้ซ”ๅŒน้…ใ€‚", + "sync-gsettings-label": "ๅŒๆญฅ็ณป็ตฑไธป้กŒ", + "templates-desc": "ๅฅ—็”จ้ก่‰ฒ่‡ณๅค–้ƒจๆ‡‰็”จ็จ‹ๅผ", + "templates-filter-description": "้กฏ็คบ็‰นๅฎš้กžๅˆฅ็š„ๆจกๆฟ", + "templates-filter-label": "ๆŒ‰้กžๅˆฅ็ฏฉ้ธ", + "templates-misc-description": "ๅ‰ตๅปบไฝ ่‡ชๅทฑ็š„ๆจกๆฟ", + "templates-misc-label": "้€ฒ้šŽ", + "templates-misc-user-templates-description": "ๅชๅœจไฝ ็Ÿฅ้“่ฆๅšไป€้บผๆ™‚ๅ•Ÿ็”จ โ€” ่ซ‹ๅƒ้–ฑๆˆ‘ๅ€‘็š„็ทšไธŠๆ–‡ไปถ", + "templates-misc-user-templates-label": "ๅ•Ÿ็”จไฝฟ็”จ่€…ๆจกๆฟ", + "templates-none-detected": "ๆœชๅตๆธฌๅˆฐ", + "templates-write-path": "ๅฏซๅ…ฅ: {filepath}", + "title": "ไธป้กŒ้…่‰ฒ", + "wallpaper-method-description": "้ธๆ“‡ไฝ ๅ–œๆญก็š„้…่‰ฒ็”Ÿๆˆๆ–นๆณ•", + "wallpaper-method-label": "่ชฟ่‰ฒ็›ค็”ข็”Ÿๆ–นๆณ•", + "wallpaper-monitor-source-description": "้ธๅ–่ฆ็”จๆ–ผๆ“ทๅ–ๆกŒๅธƒ้ก่‰ฒ็š„่žขๅน•ใ€‚", + "wallpaper-monitor-source-label": "่‰ฒๅฝฉ็”ข็”Ÿไพ†ๆบ" + }, + "connections": { + "authentication-required": "้œ€่ฆ้ฉ—่ญ‰", + "bluetooth-auto-connect-description": "็•ถ Bluetooth ๅ•Ÿ็”จๆ™‚๏ผŒ่‡ชๅ‹•้€ฃๆŽฅๅˆฐๅ—ไฟกไปป็š„ๅทฒ้…ๅฐ่ฃ็ฝฎใ€‚", + "bluetooth-auto-connect-label": "่‡ชๅ‹•้€ฃๆŽฅๅทฒ้…ๅฐ่ฃ็ฝฎ", + "bluetooth-devices-unnamed": "ๆœชๅ‘ฝๅ่ฃ็ฝฎไธ้กฏ็คบใ€‚", + "bluetooth-discoverable": "็•ถๆญค่จญๅฎšๅˆ†้ ้–‹ๅ•Ÿๆ™‚๏ผŒๆญค่ฃ็ฝฎๅฏ่ขซ็™ผ็พ็‚บ {hostName}ใ€‚", + "bluetooth-rssi-polling-description": "้€้Ž bluetoothctl ๅฎšๆœŸๅ–ๆจฃๅทฒ้€ฃๆŽฅ่ฃ็ฝฎ็š„ RSSIใ€‚ๅฏ่ƒฝไธฆ้žๆ‰€ๆœ‰่ฃ็ฝฎ้ƒฝๅฏ็”จ๏ผ›ๅ•Ÿ็”จๆ™‚ไฝ”็”จๆœ€ๅฐ‘่ณ‡ๆบใ€‚", + "bluetooth-rssi-polling-interval-description": "้…็ฝฎๅทฒ้€ฃ็ทš่ฃ็ฝฎ็š„่จŠ่™Ÿๅผทๅบฆๆ›ดๆ–ฐ้ ป็އใ€‚", + "bluetooth-rssi-polling-interval-label": "่ผช่ฉข้–“้š”", + "bluetooth-rssi-polling-label": "่—็‰™่จŠ่™Ÿ่ผช่ฉข", + "disable-discoverability-description": "ๅฐ‡ๆ‚จ็š„่ฃ็ฝฎๅพž้™„่ฟ‘็š„ Bluetooth ่ฃ็ฝฎไธญ้šฑ่—ใ€‚", + "disable-discoverability-label": "ๅœ็”จ่ฃ็ฝฎๅฏ่ฆ‹ๆ€ง", + "hide-unnamed-devices-description": "้šฑ่—ๅƒ…้กฏ็คบ็‚บ Bluetooth ไฝๅ€็š„่ฃ็ฝฎใ€‚", + "hide-unnamed-devices-label": "้šฑ่—ๆœชๅ‘ฝๅ่ฃ็ฝฎ", + "pin-instructions": "่ซ‹่ผธๅ…ฅๆ‚จ่ฃ็ฝฎไธŠ้กฏ็คบ็š„PIN็ขผใ€‚", + "title": "้€ฃ็ทš", + "wifi-header-text": "ไฝฟ็”จ Wi-Fi ๆŸฅ็œ‹ๅฏ็”จ็ถฒ่ทฏไธฆ้€ฃๆŽฅๅˆฐ็ถฒ้š›็ถฒ่ทฏใ€‚" + }, + "control-center": { + "cards-desc": "่‡ช่จ‚ๆŽงๅˆถไธญๅฟƒๅ‡บ็พ็š„ๆŽงๅˆถ้ …็›ฎๅŠๆŽ’ๅบ", + "desc": "่จญๅฎšๆŽงๅˆถไธญๅฟƒ้ขๆฟ็š„ไฝ็ฝฎๅ’Œ่กŒ็‚บ", + "open-at-mouse-description": "็•ถๅณ้ต้ปžๆ“Šๆฌ„็š„็ฉบ็™ฝๅ€ๅŸŸๆ™‚๏ผŒๅœจๆธธๆจ™้™„่ฟ‘้–‹ๅ•ŸๆŽงๅˆถไธญๅฟƒใ€‚่‹ฅๅทฒๅœ็”จ๏ผŒๅ‰‡ๆ”น็”จไธŠๆ–น็š„ไฝ็ฝฎ่จญๅฎšใ€‚", + "open-at-mouse-label": "ๅณ้ต้ปžๆ“Šๆ™‚ๅœจๆธธๆจ™่™•้–‹ๅ•Ÿ", + "position-description": "้ธๆ“‡็•ถ้–‹ๅ•ŸๆŽงๅˆถไธญๅฟƒๆ™‚่ฉฒๅพžๅ“ช่ฃกๅ‡บ็พ", + "shortcuts-custom-button-command-description": "ๅœจๆŒ‰้ˆ•ๆŒ‰ไธ‹ๆ™‚่ฆๅŸท่กŒ็š„ๅ‘ฝไปค", + "shortcuts-custom-button-enable-on-state-logic-description": "ๅ•Ÿ็”จ็ฌฌไบŒๅ€‹ๅœ–็คบ่ˆ‡ใ€Œ็†ฑๅ€ใ€็‹€ๆ…‹๏ผŒไธฆๆ นๆ“šๆชขๆŸฅๅ‘ฝไปคๅˆคๆ–ทใ€‚", + "shortcuts-custom-button-enable-on-state-logic-label": "ๅ•Ÿ็”จ็‹€ๆ…‹้‚่ผฏ", + "shortcuts-custom-button-icon-description": "ๅพžๅœ–ๅบซไธญ้ธๆ“‡ไธ€ๅ€‹ๅœ–็คบใ€‚", + "shortcuts-custom-button-on-clicked-label": "ๅทฆ้ตๆŒ‡ไปค", + "shortcuts-custom-button-on-middle-clicked-description": "็•ถๆŒ‰้ˆ•่ขซไธญ้ต้ปžๆ“Šๆ™‚ๅŸท่กŒ็š„ๅ‘ฝไปคใ€‚", + "shortcuts-custom-button-on-state-command-description": "็”จไพ†ๆชขๆŸฅๆŒ‰้ˆ•ๆ˜ฏๅฆๆ‡‰่™•ๆ–ผใ€Œ้–‹ๅ•Ÿใ€็‹€ๆ…‹็š„ๆŒ‡ไปคใ€‚ๅ‚ณๅ›ž 0 ่กจ็คบๆ‡‰้–‹ๅ•Ÿ๏ผŒ้ž 0 ่กจ็คบๆ‡‰้—œ้–‰ใ€‚", + "shortcuts-custom-button-on-state-command-label": "็‹€ๆ…‹ๆชขๆŸฅๅ‘ฝไปค", + "shortcuts-custom-button-on-state-icon-description": "ๆŒ‰้ˆ•่™•ๆ–ผใ€Œ้–‹ๅ•“ใ€็‹€ๆ…‹ๆ™‚็š„ๅœ–็คบใ€‚", + "shortcuts-custom-button-on-state-icon-label": "้–‹ๅ•“็‹€ๆ…‹ๅœ–็คบ", + "shortcuts-custom-button-state-checks-add": "ๆ–ฐๅขž็‹€ๆ…‹็ขบ่ช", + "shortcuts-custom-button-state-checks-command": "็”จๆ–ผๆญค็‹€ๆ…‹ๆชขๆŸฅ็š„ๅŸท่กŒ็จ‹ๅผๅ‘ฝไปค", + "shortcuts-custom-button-state-checks-label": "็‹€ๆ…‹็ขบ่ช", + "shortcuts-custom-button-state-checks-remove": "็งป้™ค", + "shortcuts-custom-button-tooltip-description": "็•ถๆธธๆจ™็งป้ŽๆŒ‰้ˆ•ๆ™‚่ฆ้กฏ็คบ็š„ๆ็คบๆก†ๆ–‡ๅญ—", + "shortcuts-custom-button-tooltip-label": "ๆ็คบๆก†ๆ–‡ๅญ—", + "shortcuts-title": "ๅฟซๆทๅฐๅทฅๅ…ท", + "system-monitor-disk-path-description": "้ธๆ“‡ๆŽงๅˆถไธญๅฟƒ่ฃก็š„็ณป็ตฑ็›ฃ่ฆ–ๅ™จๅก็‰‡่ฉฒ็›ฃ่ฆ–ๅ“ชไธ€ๅ€‹็ฃ็ขŸๆŽ›่ผ‰้ปž", + "system-monitor-disk-path-label": "็ฃ็ขŸ็›ฃ่ฆ–่ทฏๅพ‘", + "title": "ๆŽงๅˆถไธญๅฟƒ" + }, + "desktop-widgets": { + "clock-enabled-description": "ๅœจๆกŒ้ขไธŠ้กฏ็คบๆ™‚้˜ๅฐๅทฅๅ…ท", + "clock-enabled-label": "ๅ•Ÿ็”จๆ™‚้˜ๅฐๅทฅๅ…ท", + "clock-format-label": "ๆ ผๅผ", + "clock-height-description": "ไปฅๅƒ็ด  (px) ่จญๅฎšๆ™‚้˜้ซ˜ๅบฆ", + "clock-height-label": "้ซ˜ๅบฆ", + "clock-minimal-mode-description": "ไฝฟ็”จ้กฏ็คบๆ™‚้–“ๅ’Œๆ—ฅๆœŸ็š„ๆฅต็ฐกๆ™‚้˜", + "clock-minimal-mode-label": "ๆฅต็ฐกๆจกๅผ", + "clock-rounded-corners-description": "ๅœจๅฐๅทฅๅ…ท่ƒŒๆ™ฏไฝฟ็”จๅœ“ๅผง็š„่ง’", + "clock-rounded-corners-label": "ๅœ“่ง’", + "clock-show-background-description": "้กฏ็คบๆ™‚้˜ๅฐๅทฅๅ…ท็š„ๅกซๅ……่ƒŒๆ™ฏ", + "clock-show-background-label": "้กฏ็คบ่ƒŒๆ™ฏ", + "clock-show-date-description": "ๅœจๆ™‚้–“ไธ‹ๆ–น้กฏ็คบ็พๅœจ็š„ๆ—ฅๆœŸ", + "clock-show-date-label": "้กฏ็คบๆ—ฅๆœŸ", + "clock-show-seconds-description": "ๅœจๆ™‚้–“้กฏ็คบ็พๅœจ็š„็ง’้˜", + "clock-show-seconds-label": "้กฏ็คบ็ง’", + "clock-style-analog": "้กžๆฏ”", + "clock-style-binary": "ไบŒ้€ฒไฝ", + "clock-style-description": "้ธๆ“‡ๆ™‚้˜้กฏ็คบ็š„ๆจฃๅผ", + "clock-style-digital": "ๆ•ธไฝ", + "clock-style-label": "ๆ™‚้˜ๆจฃๅผ", + "clock-style-minimal": "ๆฅต็ฐก", + "clock-width-description": "ไปฅๅƒ็ด  (px) ่จญๅฎšๆ™‚้˜ๅฏฌๅบฆ", + "cpu-intensive-note": "ๆจ™ๆœ‰ ! ็š„Widgetๆœƒไฝ”็”จๆ›ดๅคš CPU๏ผŒ่ซ‹่ฌนๆ…Žๅ•Ÿ็”จใ€‚", + "edit-mode-button-label": "้€ฒๅ…ฅ็ทจ่ผฏๆจกๅผ", + "edit-mode-controls-explanation": "้ปžๆ“Šๅทฆ้ตๅŠๆ‹–ๆ›ณ: ็งปๅ‹•ๅฐๅทฅๅ…ทๅŠ่ชฟๆ•ดๅคงๅฐ ้ปžๆ“Šๅณ้ต: ้–‹ๅ•Ÿๅณ้ต้ธๅ–ฎ", + "edit-mode-description": "ๅ•Ÿ็”จ็ทจ่ผฏๆจกๅผไปฅ็งปๅ‹•ๅฐๅทฅๅ…ท; ็•ถๅ•Ÿ็”จๆ™‚, ๅฐๅทฅๅ…ทๆœƒ้กฏ็คบไธ€ๅ€‹ๅฏๆ‹–ๆ›ณ็š„ๅค–ๆก†ไปฅไพ›็งปๅ‹•.", + "edit-mode-exit-button": "้›ข้–‹็ทจ่ผฏๆจกๅผ", + "edit-mode-grid-snap-label": "่ฒผ้ฝŠๆ ผ็ทš", + "edit-mode-grid-snap-scale-label": "ๆ ผ็ทšๅธ้™„ๆฏ”ไพ‹", + "edit-mode-label": "็ทจ่ผฏๆจกๅผ", + "enabled-description": "ๅ•Ÿ็”จๆˆ–ๅฎŒๅ…จๅœ็”จๆกŒ้ขๅฐๅทฅๅ…ท", + "enabled-label": "ๅ•Ÿ็”จๆกŒ้ขๅฐๅทฅๅ…ท", + "general-desc": "่จญๅฎš้กฏ็คบๅœจไฝ ๆกŒ้ขไธŠ็š„ๅฐๅทฅๅ…ท", + "general-title": "ๆกŒ้ขๅฐๅทฅๅ…ท", + "media-player-enabled-description": "ๅœจๆกŒ้ข้กฏ็คบไธ€ๅ€‹ๅช’้ซ”ๆ’ญๆ”พๅ™จๅฐๅทฅๅ…ท", + "media-player-enabled-label": "ๅ•Ÿ็”จๅช’้ซ”ๆ’ญๆ”พๅ™จๅฐๅทฅๅ…ท", + "media-player-rounded-corners-description": "ๅœจๅฐๅทฅๅ…ท็š„้‚Š็ทฃๅ•Ÿ็”จๅœ“่ง’", + "media-player-show-album-art-description": "้กฏ็คบๅฐˆ่ผฏๅฐ้ขๅŠๆญŒๆ›ฒ่ณ‡่จŠ (ๆ›ฒ็›ฎๅŠๆผ”ๅ‡บ่€…)", + "media-player-show-album-art-label": "้กฏ็คบๅฐˆ่ผฏๅฐ้ขๅŠๆ›ฒ็›ฎ", + "media-player-show-background-description": "้กฏ็คบๅกซๅ……่ƒŒๆ™ฏ", + "media-player-show-buttons-description": "้กฏ็คบๆŽงๅˆถๅช’้ซ”็š„ๆŒ‰้ˆ• (ๆ’ญๆ”พ/ๆšซๅœ, ไธŠไธ€้ฆ–, ไธ‹ไธ€้ฆ–)", + "media-player-show-buttons-label": "้กฏ็คบๆŒ‰้ˆ•", + "media-player-show-visualizer-description": "้กฏ็คบ่ฆ–่ฆบๆ•ˆๆžœ", + "media-player-visualizer-type-description": "้ธๆ“‡่ฆ–่ฆบๆ•ˆๆžœ็š„ๆจฃๅผ", + "overview-enabled-description": "็•ถๅˆๆˆๅ™จๆฆ‚่ฆฝ้–‹ๅ•Ÿๆ™‚๏ผŒ้กฏ็คบๆกŒ้ขๅฐๅทฅๅ…ทใ€‚", + "overview-enabled-label": "ๅœจๆฆ‚่ฆฝไธญ้กฏ็คบ", + "system-stat-layout-bottom": "ๅบ•้ƒจ", + "system-stat-layout-description": "้ธๆ“‡ๅœ–ไพ‹็›ธๅฐๆ–ผๅœ–่กจ็š„้กฏ็คบๆ–นๅผใ€‚", + "system-stat-layout-label": "ไฝˆๅฑ€", + "system-stat-layout-side": "้‚Š", + "system-stat-rounded-corners-description": "ๅฐๅทฅๅ…ท่ƒŒๆ™ฏไฝฟ็”จๅœ“่ง’ใ€‚", + "system-stat-rounded-corners-label": "ๅœ“่ง’", + "system-stat-show-background-description": "้กฏ็คบ็ณป็ตฑ็ตฑ่จˆๅฐๅทฅๅ…ท็š„่ƒŒๆ™ฏๅฎนๅ™จใ€‚", + "system-stat-show-background-label": "้กฏ็คบ่ƒŒๆ™ฏ", + "system-stat-stat-type-description": "้ธๆ“‡่ฆ้กฏ็คบ็š„็ณป็ตฑ็ตฑ่จˆ่ณ‡่จŠใ€‚", + "system-stat-stat-type-label": "็ตฑ่จˆ้กžๅž‹", + "title": "ๆกŒ้ขๅฐๅทฅๅ…ท", + "weather-enabled-description": "ๅœจๆกŒ้ข้กฏ็คบไธ€ๅ€‹ๅคฉๆฐฃๅฐๅทฅๅ…ท", + "weather-enabled-label": "ๅ•Ÿ็”จๅคฉๆฐฃๅฐๅทฅๅ…ท", + "weather-show-background-description": "้กฏ็คบๅคฉๆฐฃๅฐๅทฅๅ…ท็š„ๅกซๅ……่ƒŒๆ™ฏ" + }, + "display": { + "monitors-backlight-device-auto-option": "้ ่จญ", + "monitors-backlight-device-description": "็‚บๆญค่ผธๅ‡บ้ธๆ“‡ไธ€ๅ€‹่ƒŒๅ…‰่ฃ็ฝฎใ€‚", + "monitors-backlight-device-label": "่ƒŒๅ…‰่ฃ็ฝฎ", + "monitors-brightness-step-description": "ๅพฎ่ชฟไบฎๅบฆ่ชฟๆ•ดไธ€ๆ ผ็š„ๅคงๅฐ (ๆปพ่ผชๅŠ้ต็›คๅฟซๆท้ต)", + "monitors-brightness-step-label": "ไบฎๅบฆๆญฅ้€ฒๅคงๅฐ", + "monitors-brightness-unavailable-ddc-disabled": "็„กๆณ•ไฝฟ็”จไบฎๅบฆๆŽงๅˆถ, ๅ•Ÿ็”จ \"ๅค–้ƒจไบฎๅบฆ่ชฟๆ•ดๆ”ฏๆด\" ไพ†ๆŽงๅˆถ้€™ๅ€‹้กฏ็คบๅ™จ็š„ไบฎๅบฆ", + "monitors-brightness-unavailable-generic": "็„กๆณ•ๅœจ้€™ๅ€‹้กฏ็คบๅ™จไฝฟ็”จไบฎๅบฆๆŽงๅˆถ", + "monitors-desc": "ๆ›ฟๅ€‹ๅˆฅ็š„้กฏ็คบๅ™จ่ชฟๆ•ดไบฎๅบฆ", + "monitors-enforce-minimum-description": "่งฃๆฑบๆŸไบ›้กฏ็คบๅ™จๅœจ่จญ็ฝฎ 0% ไบฎๅบฆๆ™‚ๆœƒๅฎŒๅ…จ้—œ้–‰่ƒŒๅ…‰็š„ๅ•้กŒ", + "monitors-enforce-minimum-label": "ๅผทๅˆถๆœ€ๅฐ 1% ไบฎๅบฆ", + "monitors-external-brightness-description": "ๅ•Ÿ็”จ DDCUtil ๆ”ฏๆดไปฅๆ›ฟๅค–้ƒจ้กฏ็คบๅ™จไฝฟ็”จ DDC/CI ๅ”ๅฎšๆŽงๅˆถไบฎๅบฆ", + "monitors-external-brightness-label": "ๅค–้ƒจไบฎๅบฆ่ชฟๆ•ดๆ”ฏๆด", + "monitors-global-brightness-description": "ๅŒๆ™‚่ชฟๆ•ดๆ‰€ๆœ‰้กฏ็คบๅ™จ็š„ไบฎๅบฆใ€‚", + "monitors-global-brightness-label": "ๆ‰€ๆœ‰้กฏ็คบๅ™จ", + "monitors-title": "ๅ€‹ๅˆฅ้กฏ็คบๅ™จ่จญๅฎš", + "night-light-auto-schedule-description": "ๅŸบๆ–ผๅœจ {location} ๆ—ฅๅ‡บๆ—ฅ่ฝ็š„ๆ™‚้–“่จญๅฎš โ€” ๆŽจ่–ฆ", + "night-light-auto-schedule-label": "่‡ชๅ‹•ๆŽ’็จ‹", + "night-light-desc": "้™ไฝŽ่—ๅ…‰็…งๅฐ„ๅฏไปฅๅนซๅŠฉไฝ ็š„็ก็œ ๅŠ็œผ็›็–ฒๅ‹ž", + "night-light-enable-description": "ๅฅ—็”จไธ€ๅ€‹ๆš–่‰ฒๆฟพ้กไพ†้™ไฝŽ่—ๅ…‰็…งๅฐ„", + "night-light-enable-label": "ๅ•Ÿ็”จๅคœ็‡ˆๆจกๅผ", + "night-light-force-activation-description": "ๅฟฝ็•ฅๆŽ’็จ‹ไธฆ็ซ‹ๅˆปๅ•Ÿ็”จๅคœ็‡ˆๆจกๅผ", + "night-light-force-activation-label": "ๅผทๅˆถๅ•Ÿ็”จ", + "night-light-manual-schedule-description": "่‡ช่จ‚ๆ—ฅๅ‡บๅŠๆ—ฅ่ฝ็š„ๆ™‚้–“", + "night-light-manual-schedule-label": "ๆ‰‹ๅ‹•ๆŽ’็จ‹", + "night-light-manual-schedule-select-start": "้ธๆ“‡้–‹ๅง‹ๆ™‚้–“", + "night-light-manual-schedule-select-stop": "้ธๆ“‡็ตๆŸๆ™‚้–“", + "night-light-manual-schedule-sunrise": "ๆ—ฅๅ‡บๆ™‚้–“", + "night-light-manual-schedule-sunset": "ๆ—ฅ่ฝๆ™‚้–“", + "night-light-temperature-day": "ๆ—ฅ้–“", + "night-light-temperature-day-description": "ๆŽงๅˆถๆ—ฅ้–“็š„้กฏ็คบ่‰ฒๆบซ", + "night-light-temperature-description": "่จญๅฎšๆ—ฅ้–“ๅŠๅคœ้–“็š„้กฏ็คบ่‰ฒๆบซ", + "night-light-temperature-label": "่‰ฒๆบซ", + "night-light-temperature-night": "ๅคœ้–“", + "night-light-temperature-night-description": "ๆŽงๅˆถๅคœ้–“็š„้กฏ็คบ่‰ฒๆบซ", + "title": "้กฏ็คบๅ™จ" + }, + "dock": { + "appearance-background-opacity-description": "่ชฟๆ•ด Dock ็š„่ƒŒๆ™ฏไธ้€ๆ˜Žๅบฆ", + "appearance-border-radius-description": "่ชฟๆ•ด Dock ็š„้‚Šๆก†ๅœ“่ง’ใ€‚", + "appearance-border-radius-label": "้‚Šๆก†ๅœ“่ง’", + "appearance-colorize-icons-description": "ๅฐ‡ไธป้กŒ่‰ฒๅฝฉๅฅ—็”จ่‡ณ Dock ไธŠ็š„ๆ‡‰็”จ็จ‹ๅผๅœ–็คบ๏ผˆๅƒ…้ž็„ฆ้ปžๆ‡‰็”จ็จ‹ๅผ๏ผ‰ใ€‚", + "appearance-colorize-icons-label": "็‚บๅœ–็คบไธŠ่‰ฒ", + "appearance-dead-opacity-description": "่ชฟๆ•ดๆœชๅŸท่กŒไธญๆ‡‰็”จ็จ‹ๅผๅœ–็คบ็š„ไธ้€ๆ˜Žๅบฆใ€‚", + "appearance-dead-opacity-label": "้žๆดป่บๆ‡‰็”จ็จ‹ๅผไธ้€ๆ˜Žๅบฆ", + "appearance-desc": "่‡ช่จ‚ Dock ็š„ๅค–่ง€ๅŠ่กŒ็‚บ", + "appearance-display-auto-hide": "่‡ชๅ‹•้šฑ่—", + "appearance-display-description": "้ธๆ“‡ Dock ็š„่กŒ็‚บ", + "appearance-display-exclusive": "็จๅ ็ฉบ้–“", + "appearance-dock-indicator-description": "็•ถ Dock ้šฑ่—ๆ™‚๏ผŒ้กฏ็คบไธ€ๅ€‹ๅฐๆŒ‡็คบๅ™จใ€‚", + "appearance-dock-indicator-label": "Dock ๆŒ‡็คบๅ™จ", + "appearance-floating-distance-description": "่จญๅฎš Dock ่ˆ‡่žขๅน•้‚Š็ทฃ็›ธ้š”็š„่ท้›ข", + "appearance-floating-distance-label": "Dock ๆ‡ธๆตฎ้–“่ท", + "appearance-group-apps-description": "ๅฐ‡ๅŒไธ€ๆ‡‰็”จ็จ‹ๅผ็š„ๅคšๅ€‹่ฆ–็ช—ๅˆ†็ต„ๅˆฐไธ€ๅ€‹ Dock ้ …็›ฎไธญใ€‚", + "appearance-group-apps-label": "ๅˆ†็ต„็›ธๅŒๆ‡‰็”จ็จ‹ๅผ", + "appearance-group-click-action-cycle": "ๅˆ‡ๆ›่ฆ–็ช—", + "appearance-group-click-action-description": "้ธๆ“‡ๅทฆ้ต้ปžๆ“Šๅฐๅˆ†็ต„ๆ‡‰็”จ็จ‹ๅผๅŸท่กŒ็š„ๆ“ไฝœใ€‚", + "appearance-group-click-action-label": "ๅˆ†็ต„ๆ‡‰็”จ็จ‹ๅผ้ปžๆ“Šๅ‹•ไฝœ", + "appearance-group-click-action-list": "้–‹ๅ•Ÿ่ฆ–็ช—ๅˆ—่กจ", + "appearance-group-context-menu-mode-description": "้ธๆ“‡ๅˆ†็ต„ๆ‡‰็”จ็จ‹ๅผไธŠไธ‹ๆ–‡้ธๅ–ฎ็š„่กŒ็‚บๆ–นๅผใ€‚", + "appearance-group-context-menu-mode-extended": "ๆ“ดๅฑ•", + "appearance-group-context-menu-mode-label": "ๅˆ†็ต„ๆ‡‰็”จ็จ‹ๅผ้ธๅ–ฎๆจกๅผ", + "appearance-group-context-menu-mode-list": "่ฆ–็ช—ๅˆ—่กจ", + "appearance-group-indicator-style-description": "้ธๆ“‡ๅˆ†็ต„ๅŸท่กŒๆŒ‡็คบๅ™จๅฆ‚ไฝ•้กฏ็คบ็„ฆ้ปž่ฆ–็ช—็‹€ๆ…‹ใ€‚", + "appearance-group-indicator-style-dots": "้ปž", + "appearance-group-indicator-style-label": "ๅˆ†็ต„ๆŒ‡็คบๅ™จๆจฃๅผ", + "appearance-group-indicator-style-number": "ๆ•ธๅญ—", + "appearance-hide-show-speed-description": "่ชฟๆ•ด Dock ้šฑ่—ๅŠๆตฎๅ‡บ็š„ๅ‹•็•ซ้€Ÿๅบฆ", + "appearance-hide-show-speed-label": "้šฑ่—/ๆตฎๅ‡บ้€Ÿๅบฆ", + "appearance-icon-size-description": "่ชฟๆ•ด Dock ็š„ๆ•ด้ซ”ๅคงๅฐ", + "appearance-icon-size-label": "Dock ๅคงๅฐ", + "appearance-inactive-indicators-description": "็‚บๆ‰€ๆœ‰ๆ‡‰็”จ็จ‹ๅผ้กฏ็คบๆŒ‡็คบ่† ๅ›Š๏ผˆ่€Œไธๅƒ…ๆ˜ฏๆญฃๅœจ้‹ไฝœ็š„็จ‹ๅผ๏ผ‰ใ€‚", + "appearance-inactive-indicators-label": "้‹ไฝœๆŒ‡็คบๅ™จ", + "appearance-indicator-color-description": "้ธๆ“‡้šฑ่— Dock ๆŒ‡็คบๅ™จ็š„้ก่‰ฒใ€‚", + "appearance-indicator-color-label": "ๆŒ‡็คบๅ™จ้ก่‰ฒ", + "appearance-indicator-opacity-description": "่ชฟๆ•ด้šฑ่— Dock ๆŒ‡็คบๅ™จ็š„ไธ้€ๆ˜Žๅบฆใ€‚", + "appearance-indicator-opacity-label": "ๆŒ‡็คบๅ™จไธ้€ๆ˜Žๅบฆ", + "appearance-indicator-thickness-description": "ๅ•Ÿ็”จ่ผƒ็ฒ—็š„้šฑ่— Dock ๆŒ‡็คบๅ™จ๏ผˆ6px ่€Œ้ž 3px๏ผ‰ใ€‚", + "appearance-indicator-thickness-label": "่ผƒ็ฒ—็š„ๆŒ‡็คบๅ™จ", + "appearance-launcher-icon-description": "้ธๆ“‡็”จๆ–ผ Dock Launcher ็š„ๅœ–็คบใ€‚", + "appearance-launcher-icon-label": "ๅ•Ÿๅ‹•ๅ™จๅœ–็คบ", + "appearance-launcher-position-description": "้ธๆ“‡ๅ•Ÿๅ‹•ๅ™จๅœ–็คบๅœจๅœ้ ๆฌ„ไธญ็š„้กฏ็คบไฝ็ฝฎใ€‚", + "appearance-launcher-position-end": "็ตๆŸ", + "appearance-launcher-position-label": "ๅ•Ÿๅ‹•ๅ™จไฝ็ฝฎ", + "appearance-launcher-position-start": "้–‹ๅง‹", + "appearance-launcher-use-distro-logo-description": "ไฝฟ็”จไฝ ็š„็™ผ่กŒ็‰ˆๆจ™่ชŒไฝœ็‚บ Dock Launcher ๅœ–็คบใ€‚", + "appearance-launcher-use-distro-logo-label": "ไฝฟ็”จ็™ผ่กŒ็‰ˆๆจ™่ชŒ่€Œ้žๅœ–็คบ", + "appearance-pinned-static-description": "็ธฝๆ˜ฏๅฐ‡้‡˜้ธ็š„็จ‹ๅผๅœ–็คบๅ›บๅฎšๆ”พๅœจๅทฆ้‚Š", + "appearance-pinned-static-label": "ๅ›บๅฎšๆ”พ็ฝฎ้‡˜้ธ็จ‹ๅผ", + "appearance-position-description": "้ธๆ“‡ Dock ่ฉฒๆ”พๅœจ็•ซ้ข็š„ๅ“ช่ฃก", + "appearance-position-label": "ไฝ็ฝฎ", + "appearance-show-launcher-icon-description": "ๅœจ Dock ไธญ้กฏ็คบๆ‡‰็”จ็จ‹ๅผๅ•Ÿๅ‹•ๅ™จๅœ–็คบใ€‚", + "appearance-show-launcher-icon-label": "้กฏ็คบๆ‡‰็”จ็จ‹ๅผๅ•Ÿๅ‹•ๅ™จ", + "appearance-sit-on-frame-description": "ๅฐ‡ๅœ้ ๆฌ„ๅฐ้ฝŠๅœจๆก†ๆžถ้‚Šๆก†ๅ…ง๏ผŒ่€Œไธๆ˜ฏไฝๆ–ผ้ ‚้ƒจใ€‚", + "appearance-sit-on-frame-label": "ๅœ้ ๆฌ„ไฝๆ–ผๆก†ๆžถไธŠ", + "appearance-type-attached": "ๅœ้ ", + "appearance-type-description": "้ธๆ“‡ๆตฎๅ‹•่—ฅไธธๆจฃๅผๆˆ–้™„่‘—ๅœจ้‚Š็ทฃ็š„้œๆ…‹ๅˆ—ใ€‚", + "appearance-type-floating": "ๆตฎๅ‹•", + "appearance-type-label": "Dock ๆจฃๅผ", + "enabled-description": "้กฏ็คบๆˆ–ๅฎŒๅ…จ้šฑ่— Dock", + "enabled-label": "ๅ•Ÿ็”จ Dock", + "monitors-desc": "ๅชๅœจ็‰นๅฎš็š„้กฏ็คบๅ™จ้กฏ็คบ Dock, ๅฆ‚ๆžœๅ…จ้ƒจๅ–ๆถˆๅ‹พ้ธๅ‰‡ๆœƒ้กฏ็คบๅœจๆ‰€ๆœ‰้กฏ็คบๅ™จ", + "monitors-only-same-monitor-description": "ๅช้กฏ็คบ่ˆ‡ Dock ๅœจๅŒไธ€ๅ€‹้กฏ็คบๅ™จ็š„็จ‹ๅผ", + "monitors-only-same-monitor-label": "ๅชๅœจ็›ธๅŒ้กฏ็คบๅ™จ็š„็จ‹ๅผ", + "monitors-title": "ๅœจ้กฏ็คบๅ™จ้กฏ็คบ", + "title": "Dock" + }, + "general": { + "copy-settings": "่ค‡่ฃฝ่จญๅฎš", + "fonts-default-description": "ๅฅ—็”จๅœจๆ•ดๅ€‹ไป‹้ข็š„ไธป่ฆๅญ—ๅž‹", + "fonts-default-label": "้ ่จญๅญ—ๅž‹", + "fonts-default-placeholder": "้ธๆ“‡้ ่จญๅญ—ๅž‹", + "fonts-default-scale-description": "ๅขžๅคงๆˆ–็ธฎๅฐ้ ่จญ็š„ๆ–‡ๅญ—ๅคงๅฐ", + "fonts-default-scale-label": "้ ่จญๆ–‡ๅญ—ๅคงๅฐ", + "fonts-default-search-placeholder": "ๆœๅฐ‹ๅญ—ๅž‹...", + "fonts-desc": "้ธๆ“‡่ฆๅฅ—็”จๅœจๆ•ดๅ€‹ไป‹้ข็š„ๅญ—ๅž‹", + "fonts-monospace-description": "ๅฅ—็”จๅœจๆ•ธๅญ—ๅŠ็‹€ๆ…‹้กฏ็คบ็š„็ญ‰ๅฏฌๅญ—ๅž‹", + "fonts-monospace-label": "็ญ‰ๅฏฌๅญ—ๅž‹", + "fonts-monospace-placeholder": "้ธๆ“‡็ญ‰ๅฏฌๅญ—ๅž‹...", + "fonts-monospace-scale-description": "ๅขžๅคงๆˆ–็ธฎๅฐ็ญ‰ๅฏฌๆ–‡ๅญ—็š„ๅคงๅฐ", + "fonts-monospace-scale-label": "็ญ‰ๅฏฌๅญ—ๅž‹ๅคงๅฐ", + "fonts-monospace-search-placeholder": "ๆœๅฐ‹็ญ‰ๅฏฌๅญ—ๅž‹...", + "fonts-title": "ๅญ—ๅž‹", + "keybinds-conflict-description": "ๆญคๆŒ‰้ต็ต„ๅˆๅทฒๅˆ†้…็ตฆ {action}ใ€‚", + "keybinds-conflict-title": "ๆŒ‰้ต็ถๅฎš่ก็ช", + "keybinds-description": "้…็ฝฎ้ขๆฟๅ’Œๅ•Ÿๅ‹•ๅ™จ็š„ๅ…จๅŸŸๅฐŽ่ˆช้ตใ€‚", + "keybinds-down": "ไธ‹็งป", + "keybinds-enter": "็ขบ่ช / ๅ‹•ไฝœ", + "keybinds-escape": "้—œ้–‰ / ่ฟ”ๅ›ž", + "keybinds-left": "ๅ‘ๅทฆ็งปๅ‹•", + "keybinds-modifier-description": "ๅฟซๆท้ตๅฟ…้ ˆๅŒ…ๅซไฟฎ้ฃพ้ต (Ctrl ๆˆ– Alt)ใ€‚", + "keybinds-modifier-title": "้œ€่ฆไฟฎ้ฃพ้ต", + "keybinds-remove": "ๅˆช้™ค", + "keybinds-right": "ๅ‘ๅณ็งปๅ‹•", + "keybinds-title": "ๅฐŽ่ฆฝๆŒ‰้ต็ถๅฎš", + "keybinds-up": "ไธŠ็งป", + "language-desc": "้ธๆ“‡ไฝ ๅๅฅฝไฝฟ็”จ็š„่ชž่จ€", + "language-select-auto-detect": "่‡ชๅ‹•้ธๆ“‡", + "language-select-description": "้ธๆ“‡่ฆๅœจๆ‡‰็”จ็จ‹ๅผๆ‰€ไฝฟ็”จ็š„ไป‹้ข่ชž่จ€", + "language-select-label": "ไป‹้ข่ชž่จ€", + "launch-setup-wizard": "่จญๅฎš็ฒพ้ˆ", + "profile-desc": "็ทจ่ผฏไฝ ็š„ไฝฟ็”จ่€…่ณ‡่จŠๅŠ้ ญๅƒ", + "profile-picture-description": "ไฝ ็š„้ ญๅƒๆœƒๅœจๆ•ดๅ€‹ไป‹้ขไธญๅ‡บ็พ", + "profile-picture-label": "ๅ€‹ไบบ่ณ‡ๆ–™ๅœ–็‰‡", + "profile-select-avatar": "้ธๆ“‡ไฝœ็‚บ้ ญๅƒ็š„ๅœ–็‰‡", + "profile-title": "ๅ€‹ไบบๆช”ๆกˆ", + "profile-tooltip": "ๅ€‹ไบบ้ ญๅƒ", + "reverse-scrolling-description": "ๅ่ฝ‰่งฃ่ฎ€็š„ๆฒๅ‹•ๆ–นๅ‘", + "reverse-scrolling-label": "ๅ่ฝ‰ๆฒๅ‹•", + "screen-corners-desc": "่‡ช่จ‚็•ซ้ข้‚Š่ง’็š„ๅผงๅบฆๅŠ่ฆ–่ฆบๆ•ˆๆžœ", + "screen-corners-radius-description": "่ชฟๆ•ด็•ซ้ขๆ‰€ไฝฟ็”จ็š„้‚Š่ง’ๅŠๅพ‘", + "screen-corners-radius-label": "็•ซ้ข้‚Š่ง’ๅŠๅพ‘", + "screen-corners-radius-reset": "้‡่จญ็•ซ้ข้‚Š่ง’ๅŠๅพ‘", + "screen-corners-show-corners-description": "ๅœจ็•ซ้ขๅ‘จ้‚Š้กฏ็คบๅœ“่ง’", + "screen-corners-show-corners-label": "้กฏ็คบ็•ซ้ข้‚Š่ง’", + "screen-corners-solid-black-description": "ไฝฟ็”จ้ป‘่‰ฒ่€Œไธๆ˜ฏๅทฅๅ…ทๅˆ—่ƒŒๆ™ฏ่‰ฒ", + "screen-corners-solid-black-label": "้ป‘่‰ฒ้‚Š่ง’", + "screen-corners-title": "็•ซ้ข้‚Š่ง’", + "settings-copied": "ๅทฒ่ค‡่ฃฝ่จญๅฎš่‡ณๅ‰ช่ฒผ็ฐฟ", + "smooth-scrolling-description": "็‚บๅˆ—่กจๆฒๅ‹•ๆ–ฐๅขžๅ‹•็•ซ๏ผŒไปฅ็ฒๅพ—ๆ›ดๆตๆšข็š„ๆปพ่ผช้ซ”้ฉ—ใ€‚", + "smooth-scrolling-label": "ๅนณๆป‘ๆฒๅ‹•", + "tab-basics": "ๅŸบ็คŽ", + "tab-keybinds": "ๆŒ‰้ต็ถๅฎš" + }, + "hooks": { + "color-generation-description": "้ก่‰ฒ็”ŸๆˆๅฎŒๆˆๅพŒๅŸท่กŒ็š„ๅ‘ฝไปค๏ผˆไพ‹ๅฆ‚๏ผŒๅŸบๆ–ผๆกŒๅธƒ็š„ไธป้กŒ่จญๅฎšๅพŒ๏ผ‰ใ€‚", + "color-generation-label": "้ก่‰ฒๅทฒ็”Ÿๆˆ", + "color-generation-placeholder": "ไพ‹ๅฆ‚๏ผšnotify-send \"้ก่‰ฒ\" \"ๅทฒ็”Ÿๆˆ\"", + "info-command-info-description": "โ€ข ๆŒ‡ไปคๅฐ‡้€้Ž shell (sh -lc) ๅŸท่กŒ
โ€ข ๆŒ‡ไปคๆœƒๅœจ่ƒŒๆ™ฏ (detached ็‹€ๆ…‹) ๅŸท่กŒ
โ€ข ๆธฌ่ฉฆๆŒ‰้ˆ•ๆœƒไปฅ็•ถไธ‹ๅกซๅฏซ็š„ๅ…งๅฎนๅŸท่กŒ", + "info-parameters-description": "โ€ข Wallpaper hook: $1 = ๆกŒ้ข่ƒŒๆ™ฏ่ทฏๅพ‘, $2 = ่žขๅน•ๅ็จฑ, $3 = ไธป้กŒ (ๆทฑ่‰ฒ/ๆทบ่‰ฒ)
โ€ข Theme toggle hook: $1 = true/false (ๆทฑ่‰ฒๆจกๅผ็‹€ๆ…‹)
โ€ข Screen lock/unlock hooks: $1 = lock/unlock (่žขๅน•้Ž–ๅฎš็‹€ๆ…‹)
โ€ข Performance mode hooks: ็„กๅƒๆ•ธ
โ€ข Session hook: $1 = action (shutdown/reboot)", + "info-parameters-label": "ๅฏ็”จๅƒๆ•ธ", + "noctalia-started-description": "Noctalia ๅฎŒๆˆ่ผ‰ๅ…ฅๆ™‚่ฆๅŸท่กŒ็š„ๆŒ‡ไปคใ€‚", + "noctalia-started-label": "Noctalia ๅทฒๅ•Ÿๅ‹•", + "noctalia-started-placeholder": "ไพ‹ๅฆ‚: notify-send 'Noctalia ๅทฒ่ผ‰ๅ…ฅ'", + "performance-mode-disabled-description": "Noctalia ๆ•ˆ่ƒฝๆจกๅผๅœ็”จๆ™‚่ฆๅŸท่กŒ็š„ๆŒ‡ไปค", + "performance-mode-disabled-label": "ๆ•ˆ่ƒฝๆจกๅผๅœ็”จๆ™‚", + "performance-mode-disabled-placeholder": "ไพ‹ๅฆ‚: notify-send \"Performance\" \"Mode disabled\"", + "performance-mode-enabled-description": "Noctalia ๆ•ˆ่ƒฝๆจกๅผๅ•Ÿ็”จๆ™‚่ฆๅŸท่กŒ็š„ๆŒ‡ไปค", + "performance-mode-enabled-label": "ๆ•ˆ่ƒฝๆจกๅผๅ•Ÿ็”จๆ™‚", + "performance-mode-enabled-placeholder": "ไพ‹ๅฆ‚: notify-send \"Performance\" \"Mode enabled\"", + "screen-lock-description": "ๅœจ็•ซ้ข้Ž–ๅฎšๆ™‚่ฆๅŸท่กŒ็š„ๆŒ‡ไปค", + "screen-lock-label": "็•ซ้ข้Ž–ๅฎšๆ™‚", + "screen-lock-placeholder": "ไพ‹ๅฆ‚: notify-send \"Screen\" \"Locked\"", + "screen-unlock-description": "ๅœจ้Ž–ๅฎš็•ซ้ข่งฃ้Ž–ๆ™‚่ฆๅŸท่กŒ็š„ๆŒ‡ไปค", + "screen-unlock-label": "็•ซ้ข่งฃ้Ž–ๆ™‚", + "screen-unlock-placeholder": "ไพ‹ๅฆ‚: notify-send \"Screen\" \"Unlocked\"", + "session-description": "ๅœจ้—œๆฉŸๆˆ–้‡ๆ–ฐๅ•Ÿๅ‹•ๅ‰่ฆๅŸท่กŒ็š„ๆŒ‡ไปค, ๆŽฅๆ”ถๅˆฐ็š„ๅŸท่กŒๅ‹•ไฝœๆœƒ่ฆ–็‚บ $1 (shutdown/reboot)", + "session-label": "ๅทฅไฝœ้šŽๆฎต็ตๆŸๆ™‚", + "session-placeholder": "ไพ‹ๅฆ‚: notify-send \"Session\" \"$1\"", + "system-hooks-desc": "่จญๅฎš็•ถ็ณป็ตฑไบ‹ไปถ็™ผ็”Ÿๆ™‚่ฆๅŸท่กŒ็š„ๆŒ‡ไปค", + "system-hooks-enable-description": "ๅ•Ÿ็”จๆˆ–ๅœ็”จๆ‰€ๆœ‰็š„ hook ๆŒ‡ไปค", + "system-hooks-enable-label": "ๅ•Ÿ็”จ hook", + "system-hooks-title": "็ณป็ตฑ hook", + "theme-changed-description": "ๅœจไบฎ่‰ฒๅŠๆทฑ่‰ฒไธป้กŒๅˆ‡ๆ›ๆ™‚่ฆๅŸท่กŒ็š„ๆŒ‡ไปค", + "theme-changed-label": "ไธป้กŒๅˆ‡ๆ›ๆ™‚", + "theme-changed-placeholder": "ไพ‹ๅฆ‚: notify-send \"Theme\" \"Toggled\"", + "title": "Hooks", + "wallpaper-changed-description": "ๅœจๆกŒๅธƒๆ›ดๆ›ๆ™‚่ฆๅŸท่กŒ็š„ๆŒ‡ไปค", + "wallpaper-changed-label": "ๆกŒๅธƒๆ›ดๆ›ๆ™‚", + "wallpaper-changed-placeholder": "ไพ‹ๅฆ‚: notify-send \"Wallpaper\" \"Changed\"" + }, + "idle": { + "custom-add": "ๆ–ฐๅขžๆŒ‡ไปค", + "custom-description": "ๅœจไธ€ๆฎตๆ™‚้–“ไธๆดปๅ‹•ๅพŒๅŸท่กŒ shell ๅ‘ฝไปคใ€‚", + "custom-entry-command": "ๅ‘ฝไปค", + "custom-entry-delete": "ๅˆช้™ค", + "custom-entry-edit": "็ทจ่ผฏ่‡ช่จ‚ๅ‘ฝไปค", + "custom-entry-name": "ๅ็จฑ", + "custom-entry-name-placeholder": "ไพ‹ๅฆ‚๏ผš้—œ็‡ˆ", + "custom-entry-new": "ๆ–ฐๅขž่‡ช่จ‚ๅ‘ฝไปค", + "custom-entry-timeout": "้–’็ฝฎๆ™‚้–“", + "custom-entry-timeout-format": "{count} ็ง’", + "custom-entry-timeout-format-plural": "{count} ็ง’", + "custom-entry-unnamed": "ๆœชๅ‘ฝๅๅ‘ฝไปค", + "custom-label": "่‡ช่จ‚้–’็ฝฎๆŒ‡ไปค", + "enable-description": "ๅœจไธ€ๆฎตๆ™‚้–“ไธๆดปๅ‹•ๅพŒ๏ผŒ่‡ชๅ‹•้—œ้–‰่žขๅน•ใ€้Ž–ๅฎšๆˆ–ๆšซๅœใ€‚", + "enable-label": "ๅ•Ÿ็”จ้–’็ฝฎ็ฎก็†", + "fade-duration-description": "ๆฏๆฌกๆ“ไฝœ่งธ็™ผๅ‰๏ผŒ่žขๅน•ๆผธ่ฎŠ็‚บ้ป‘็š„ๅ‹•็•ซๆŒ็บŒๆ™‚้–“๏ผˆ็ง’๏ผ‰ใ€‚ไปปไฝ•ๆป‘้ผ ็งปๅ‹•้ƒฝๆœƒๅ–ๆถˆๆผธ่ฎŠใ€‚", + "fade-duration-label": "ๆทกๅ…ฅๆทกๅ‡บๆ™‚้•ท", + "lock-description": "้Ž–ๅฎš็•ซ้ขๅ•Ÿ็”จๅ‰็š„้–’็ฝฎ็ง’ๆ•ธใ€‚", + "lock-label": "้Ž–ๅฎš่žขๅน•", + "resume-command-label": "ๆขๅพฉๅ‘ฝไปค", + "screen-off-description": "้กฏ็คบๅ™จ้—œ้–‰ๅ‰็š„ไธๆดปๅ‹•็ง’ๆ•ธใ€‚", + "screen-off-label": "้—œ้–‰่žขๅน•", + "status-description": "็”ฑๆททๆˆๅ™จๅ›žๅ ฑ็š„้–’็ฝฎๆ™‚้–“ใ€‚", + "status-label": "้–’็ฝฎๆ™‚้–“", + "suspend-description": "็ณป็ตฑๆšซๅœๅ‰็š„้–’็ฝฎ็ง’ๆ•ธใ€‚", + "tab-behavior": "่กŒ็‚บ", + "tab-custom": "่‡ช่จ‚", + "timeouts-description": "่จญๅฎš็‚บ0ไปฅๅœ็”จไธ€ๅ€‹้šŽๆฎตใ€‚ไฟๆŒๅ–š้†’ๅ•Ÿ็”จๆ™‚๏ผŒ้€พๆ™‚ๆœƒๆšซๅœใ€‚", + "timeouts-label": "้€พๆ™‚", + "title": "้–’็ฝฎ", + "unavailable": "ๆญค Compositor ไธๆ”ฏๆดๅŽŸ็”Ÿ้–’็ฝฎ็›ฃๆŽงใ€‚" + }, + "indicator": { + "default-value": "้ ่จญๅ€ผ: {value}", + "system-default": "็ณป็ตฑ้ ่จญ" + }, + "launcher": { + "clipboard-desc": "ๅœจๅ•Ÿๅ‹•ๅ™จๅญ˜ๅ–ๅŠๆ•ด็†ไฝ ็š„ๅ‰ช่ฒผ็ฐฟๆญทๅฒ", + "execute-desc": "่จญๅฎšๆ‡‰็”จ็จ‹ๅผ่ฉฒๅฆ‚ไฝ•ๅ•Ÿๅ‹•", + "execute-title": "ๅŸท่กŒ", + "settings-annotation-tool-description": "้ปžๆ“Šๅ‰ช่ฒผ็ฐฟๆญทๅฒ่จ˜้Œ„ไธญ็š„่จป่จ˜ๆŒ‰้ˆ•ๆ™‚่ฆๅŸท่กŒ็š„ๅ‘ฝไปคใ€‚ๅœ–็‰‡ๅฐ‡ๆœƒ้€้Ž็ฎก็ทšๅ‚ณ้€่‡ณๆญคๅ‘ฝไปคใ€‚", + "settings-annotation-tool-label": "่จป่จ˜ๅทฅๅ…ท", + "settings-annotation-tool-placeholder": "ไพ‹ๅฆ‚: gradia', 'satty -f -'", + "settings-auto-paste-description": "่‡ชๅ‹•่ฒผไธŠ้ธๅ–็š„ๅ‰ช่ฒผ็ฐฟ้ …็›ฎ, ้œ€่ฆ wtype ๅฅ—ไปถ", + "settings-auto-paste-label": "่‡ชๅ‹•่ฒผไธŠ", + "settings-clip-chips-description": "้กฏ็คบไธ€ๅ€‹ๅˆ†้ ๅˆ—๏ผŒไปฅๆŒ‰้กžๅž‹๏ผˆๅœ–็‰‡ใ€้€ฃ็ตใ€ๆช”ๆกˆใ€็จ‹ๅผ็ขผ็ญ‰๏ผ‰็ฏฉ้ธๅ‰ช่ฒผ็ฐฟๆญทๅฒ่จ˜้Œ„ใ€‚", + "settings-clip-chips-label": "ๅ•Ÿ็”จ้กžๅˆฅๆจ™็ฑค", + "settings-clip-preview-description": "ไฝฟ็”จ >clip ๅ‘ฝไปคๆ™‚้กฏ็คบๅ‰ช่ฒผ็ฐฟๅ…งๅฎน็š„้ ่ฆฝใ€‚", + "settings-clip-preview-label": "ๅ•Ÿ็”จๅ‰ช่ฒผ็ฐฟ้ ่ฆฝ", + "settings-clip-smart-icons-description": "็‚บ้€ฃ็ตใ€ๆช”ๆกˆใ€้ก่‰ฒๅ’Œ็จ‹ๅผ็ขผ้กฏ็คบ็‰นๅฎšๅœ–็คบ๏ผŒ่€Œไธๆ˜ฏ้€š็”จ็š„ๅ‰ช่ฒผ็ฐฟๅœ–็คบใ€‚", + "settings-clip-smart-icons-label": "ๅ•Ÿ็”จๆ™บๆ…งๅœ–็คบ", + "settings-clip-wrap-text-description": "ๅฐ‡ๅ‰ช่ฒผ็ฐฟ็š„ๆ–‡ๅญ—ๆ›่กŒ้กฏ็คบ่€Œไธๆ˜ฏๆˆชๆމ", + "settings-clip-wrap-text-label": "ๅ‰ช่ฒผ็ฐฟๆ–‡ๅญ—ๆ›่กŒ", + "settings-clipboard-history-description": "ๅœจๅ•Ÿๅ‹•ๅ™จๅญ˜ๅ–ๅ…ˆๅ‰ๆ‰€่ค‡่ฃฝ็š„้ …็›ฎ", + "settings-clipboard-history-label": "ๅ•Ÿ็”จๅ‰ช่ฒผ็ฐฟๆญทๅฒ", + "settings-clipboard-watch-image-description": "ๅ‚ณ้ž็ตฆ wl-paste ไปฅ้€ฒ่กŒๅœ–ๅƒๆ›ดๆ”น็š„ๅฎŒๆ•ดๅ‘ฝไปคๅญ—ไธฒ๏ผˆ้œ€่ฆ้‡ๆ–ฐๅ•Ÿๅ‹•๏ผ‰ใ€‚", + "settings-clipboard-watch-image-label": "ๅœ–ๅƒ็›ฃๆŽงๅ‘ฝไปค", + "settings-clipboard-watch-text-description": "ๅ‚ณ้ž็ตฆ wl-paste ไปฅ้€ฒ่กŒๆ–‡ๅญ—่ฎŠๆ›ด็š„ๅฎŒๆ•ดๅ‘ฝไปคๅญ—ไธฒ๏ผˆ้œ€่ฆ้‡ๆ–ฐๅ•Ÿๅ‹•๏ผ‰ใ€‚", + "settings-clipboard-watch-text-label": "ๆ–‡ๅญ—็›ฃ็œ‹ๆŒ‡ไปค", + "settings-custom-launch-prefix-description": "ๅœจๅ‘ฝไปคๅ‰ๅŠ ไธŠ่‡ช่จ‚ๅ•Ÿๅ‹•ๅ™จๅ‰็ถด๏ผˆไพ‹ๅฆ‚: ็”จใ€Œrunappใ€ไพ†ๆ•ดๅˆ systemd๏ผ‰ใ€‚", + "settings-custom-launch-prefix-enabled-description": "ไฝฟ็”จ่‡ช่จ‚ๅ‰็ถด่€Œไธๆ˜ฏ้ ่จญ็š„ๆ–นๅผไพ†ๅ•Ÿๅ‹•ๆ‡‰็”จ็จ‹ๅผ", + "settings-custom-launch-prefix-enabled-label": "ๅ•Ÿ็”จ่‡ช่จ‚็š„็จ‹ๅผๅ•Ÿๅ‹•ๅ‰็ถด", + "settings-custom-launch-prefix-label": "่‡ช่จ‚ๅ•Ÿๅ‹•ๅ‰็ถด", + "settings-density-description": "่ชฟๆ•ดๆ‡‰็”จ็จ‹ๅผๅœ–็คบๅคงๅฐๅ’Œ Launcher ๅฏ†ๅบฆใ€‚", + "settings-density-label": "ๅฏ†ๅบฆ", + "settings-desc": "่‡ช่จ‚ๅ•Ÿๅ‹•ๅ™จ็š„ๅค–่ง€ๅŠ่กŒ็‚บ", + "settings-enable-session-search-description": "ๅœจ Launcher ไธญๆœๅฐ‹ๆ™‚้กฏ็คบๅทฅไฝœ้šŽๆฎตๅ‹•ไฝœ๏ผˆ้Ž–ๅฎšใ€้—œๆฉŸใ€้‡ๆ–ฐๅ•Ÿๅ‹•็ญ‰๏ผ‰ใ€‚", + "settings-enable-session-search-label": "ๅ•Ÿ็”จๅทฅไฝœ้šŽๆฎตๆœๅฐ‹", + "settings-enable-settings-search-description": "ๅœจๅ•Ÿๅ‹•ๅ™จไธญๆœๅฐ‹ๆ™‚้กฏ็คบ่จญๅฎš็ตๆžœใ€‚", + "settings-enable-settings-search-label": "ๅ•Ÿ็”จ่จญๅฎšๆœๅฐ‹", + "settings-enable-windows-search-description": "ๆœๅฐ‹ไธฆ่š็„ฆไฝœ็”จไธญ่ฆ–็ช—ใ€‚", + "settings-enable-windows-search-label": "ๅ•Ÿ็”จ่ฆ–็ช—ๆœๅฐ‹", + "settings-grid-view-description": "ๅฐ‡้ …็›ฎไปฅๆ–นๆ ผๆŽ’ๅˆ—่€Œไธๆ˜ฏๅˆ—่กจ้กฏ็คบ", + "settings-icon-mode-description": "ไฝฟ็”จ็ณป็ตฑๅŽŸ็”Ÿ็š„ๅœ–็คบ่€Œไธๆ˜ฏ่‡ช Tabler ๅœ–็คบๅŒ…ๅธถๅ…ฅ", + "settings-icon-mode-label": "ไฝฟ็”จๅŽŸ็”Ÿๅœ–็คบ", + "settings-ignore-mouse-input-description": "ๅฐๅ•Ÿๅ‹•ๅ™จๅœ็”จๆป‘้ผ ไบ’ๅ‹•ๅŠๆปพ่ผชๆฒๅ‹•", + "settings-ignore-mouse-input-label": "ๅฟฝ็•ฅๆป‘้ผ ่ผธๅ…ฅ", + "settings-overlay-layer-description": "ๅœจ่ฆ†่“‹ๅฑคไธŠ้กฏ็คบๅ•Ÿๅ‹•ๅ™จ๏ผŒไฝๆ–ผๅ…จ่žขๅน•่ฆ–็ช—ไน‹ไธŠใ€‚ๅ•Ÿ็”จๆ™‚๏ผŒๅ•Ÿๅ‹•ๅ™จไธๆœƒ้™„ๅŠ ๅˆฐๅˆ—ใ€‚", + "settings-overlay-layer-label": "ๅœจๅ…จ่žขๅน•ไธŠๆ–น้กฏ็คบ", + "settings-position-description": "้ธๆ“‡ๅ•Ÿๅ‹•ๅ™จ้ขๆฟ่ฆๅ‡บ็พ็š„ๅœฐๆ–น", + "settings-show-categories-description": "้กฏ็คบๅˆ†้กžๆจ™็ฑคไพ†็ฏฉ้ธๆ‡‰็”จ็จ‹ๅผ", + "settings-show-categories-label": "้กฏ็คบๅˆ†้กž", + "settings-show-icon-background-description": "ๅœจๅœ–็คบๅพŒ้ข้กฏ็คบไธ€ๅ€‹ๅœ“่ง’็Ÿฉๅฝข็‚บ่ƒŒๆ™ฏ", + "settings-show-icon-background-label": "้กฏ็คบๅœ–็คบ่ƒŒๆ™ฏ", + "settings-sort-by-usage-description": "็•ถๅ•Ÿ็”จๆ™‚, ๆœ€ๅธธไฝฟ็”จ็š„็จ‹ๅผๆœƒๅ‡บ็พๅœจๅˆ—่กจ็š„ไธŠ้ข", + "settings-sort-by-usage-label": "ๆŒ‰ๆœ€ๅธธไฝฟ็”จๆŽ’ๅบ", + "settings-terminal-command-description": "ๆ‰“้–‹็ต‚็ซฏๆฉŸ็š„ๆŒ‡ไปค, ไพ‹ๅฆ‚: 'kitty -e'ๆˆ– 'gnome-terminal --'", + "settings-terminal-command-label": "็ต‚็ซฏๆฉŸๆŒ‡ไปค", + "settings-use-app2unit-description": "ไฝฟ็”จๆ›ฟไปฃ็š„ๅ•Ÿๅ‹•ๆ–นๅผ๏ผŒไปฅๆ›ดๅฅฝๅœฐ็ฎก็†ๆ‡‰็”จ็จ‹ๅผ็จ‹ๅบไธฆ้ฟๅ…ๅ•้กŒใ€‚", + "settings-use-app2unit-label": "ไฝฟ็”จ App2Unit ๅ•Ÿๅ‹•ๆ‡‰็”จ็จ‹ๅผ", + "settings-view-mode-description": "้ธๆ“‡ๅ•Ÿๅ‹•ๅ™จ้ …็›ฎ็š„็‰ˆ้ข้…็ฝฎใ€‚", + "settings-view-mode-label": "ๆชข่ฆ–ๆจกๅผ", + "title": "ๅ•Ÿๅ‹•ๅ™จ" + }, + "location": { + "auto-locate-description": "ไฝฟ็”จๆ‚จ็š„ IP ไฝๅ€ๅฎšๆœŸ่‡ชๅ‹•ๅตๆธฌๆ‚จ็š„ไฝ็ฝฎใ€‚", + "auto-locate-label": "่‡ชๅ‹•ๅฎšไฝ", + "calendar-cards-desc": "ๆ•ด็†ๅŠๅ•Ÿ็”จ/ๅœ็”จๅœจ่กŒไบ‹ๆ›†้ขๆฟไฝฟ็”จ็š„ๅก็‰‡", + "calendar-cards-title": "่กŒไบ‹ๆ›†ๅก็‰‡", + "calendar-header-label": "่กŒไบ‹ๆ›†ๆŠฌ้ ญ", + "calendar-month-label": "่กŒไบ‹ๆ›†ๆœˆๆ›†", + "date-time-12hour-format-description": "ๅœจ้Ž–ๅฎš็•ซ้ขๅŠ่กŒไบ‹ๆ›†ไปฅ12ๅฐๆ™‚ๅˆถไพ†้กฏ็คบๆ™‚้–“, ๅทฅๅ…ทๅˆ—็š„ๆ™‚้˜ๆ˜ฏๅ€‹ๅˆฅ่จญๅฎš็š„", + "date-time-12hour-format-label": "ไฝฟ็”จ12ๅฐๆ™‚ๅˆถ", + "date-time-desc": "่‡ช่จ‚ๆ—ฅๆœŸ่ˆ‡ๆ™‚้–“่ฉฒๅฆ‚ไฝ•้กฏ็คบ", + "date-time-first-day-of-week-automatic": "่‡ชๅ‹• (ไฝฟ็”จ็ณป็ตฑๅ€ๅŸŸ่จญๅฎš)", + "date-time-first-day-of-week-description": "้ธๆ“‡่กŒไบ‹ๆ›†็š„ๆฏไธ€้€ฑ่‡ชๆ˜ŸๆœŸๅนพ้–‹ๅง‹", + "date-time-first-day-of-week-label": "ๆฏ้€ฑ็š„็ฌฌไธ€ๅคฉ", + "date-time-show-events-description": "ๅœจ่กŒไบ‹ๆ›†้ขๆฟ่ฃก้กฏ็คบๆดปๅ‹•", + "date-time-show-events-label": "้กฏ็คบ่กŒไบ‹ๆ›†ๆดปๅ‹•", + "date-time-title": "ๆ—ฅๆœŸ่ˆ‡ๆ™‚้–“", + "date-time-use-analog-description": "ๅœจๆ—ฅๆ›†่ฆ–็ช—ไธŠ้กฏ็คบ้กžๆฏ”ๆ™‚้˜ใ€‚", + "date-time-use-analog-label": "ไฝฟ็”จ้กžๆฏ”ๆ™‚้˜", + "date-time-week-numbers-description": "ๅœจ่กŒไบ‹ๆ›†้กฏ็คบไปŠๅนด็•ถ้€ฑ็š„้€ฑๆ•ธ (ไพ‹ๅฆ‚: ็ฌฌ38้€ฑ)", + "date-time-week-numbers-label": "้กฏ็คบ้€ฑๆ•ธ", + "geolocate-now-button": "็ซ‹ๅณๅฎšไฝ", + "location-desc": "่จญๅฎšไฝ ็š„ๆ‰€ๅœจๅœฐไปฅๅ–ๅพ—ๆบ–็ขบ็š„ๅคฉๆฐฃๅŠๅคœๅ…‰ๆจกๅผๆ™‚้–“", + "location-search-description": "ไพ‹ๅฆ‚: Taipei (่‹ฑๆ–‡)", + "location-search-label": "ๆœๅฐ‹ไฝ็ฝฎ", + "location-search-placeholder": "่ผธๅ…ฅไฝ็ฝฎๅ็จฑ", + "location-title": "ไฝ ็š„ๆ‰€ๅœจๅœฐ", + "weather-desc": "้ธๆ“‡ไฝ ๅๅฅฝ็š„ๆบซๅบฆๅ–ฎไฝ", + "weather-enabled-description": "็ฒๅ–ไธฆๅœจๆ•ดๅ€‹ไป‹้ขไธญ้กฏ็คบ็พๅœจๅคฉๆฐฃ; ็•ถๅœ็”จๆ™‚, ๆ‰€ๆœ‰็š„ๅคฉๆฐฃๅ…ƒ็ด ้ƒฝๆœƒ่ขซ้šฑ่—่€Œไธ”ไธๆœƒๅšๅ‡บ็ถฒ่ทฏ่ซ‹ๆฑ‚", + "weather-enabled-label": "ๅ•Ÿ็”จๅคฉๆฐฃ", + "weather-fahrenheit-description": "ไปฅ่ฏๆฐ (ยฐF) ่€Œไธๆ˜ฏๆ”ๆฐ (ยฐC) ้กฏ็คบๆบซๅบฆ", + "weather-fahrenheit-label": "ไปฅ่ฏๆฐๆบซๅบฆ (ยฐF) ้กฏ็คบ", + "weather-hide-city-description": "่‡ชๅ„ๅ€‹ไป‹้ขไธญ็š„ๅคฉๆฐฃๅ€ๅกŠ้šฑ่—ๅŸŽๅธ‚ๅ็จฑ", + "weather-hide-city-label": "้šฑ่—ๅŸŽๅธ‚ๅ็จฑ", + "weather-hide-timezone-description": "่‡ชๅ„ๅ€‹ไป‹้ขไธญ็š„ๅคฉๆฐฃๅ€ๅกŠ้šฑ่—ๆ‰€ๅœจๆ™‚ๅ€", + "weather-hide-timezone-label": "้šฑ่—ๆ™‚ๅ€", + "weather-show-effects-description": "ๅœจๆฐฃ่ฑกๅก็‰‡้กฏ็คบ้กๅค–็š„่ฆ–่ฆบๆ•ˆๆžœ (ๅƒๆ˜ฏไธ‹้›จ, ไธ‹้›ชๆˆ–้–ƒ้›ป)", + "weather-show-effects-label": "้กฏ็คบๅคฉๆฐฃ็‰นๆ•ˆ", + "weather-show-in-calendar-description": "ๅœจๆชข่ฆ–่กŒไบ‹ๆ›†ๆ™‚ไธ€ๅŒ้กฏ็คบๆฏๆ—ฅๅคฉๆฐฃ", + "weather-show-in-calendar-label": "ๅœจ่กŒไบ‹ๆ›†ไธญ้กฏ็คบๅคฉๆฐฃ", + "weather-talia-mascot-always-description": "ๅง‹็ต‚ไฝฟ็”จ Talia emojis ่€Œไธๆ˜ฏ tabler iconsใ€‚", + "weather-talia-mascot-always-label": "ๅง‹็ต‚้กฏ็คบTaliaๅคฉๆฐฃ่กจๆƒ…็ฌฆ่™Ÿ" + }, + "lock-screen": { + "allow-password-with-fprintd-description": "็•ถ fprintd๏ผˆๆŒ‡็ด‹่ช่ญ‰๏ผ‰่™•ๆ–ผๆดปๅ‹•็‹€ๆ…‹ๆ™‚๏ผŒๆญค้ธ้ …ๅ…่จฑๆ‚จไฝฟ็”จๅฏ†็ขผ่€Œ้žๆŒ‡็ด‹็™ปๅ…ฅใ€‚", + "allow-password-with-fprintd-label": "ๅ…่จฑไฝฟ็”จ fprintd ้€ฒ่กŒๅฏ†็ขผ็™ปๅ…ฅ", + "auto-start-auth-description": "่‡ชๅ‹•ๅ•Ÿๅ‹•ๆŒ‡็ด‹้ฉ—่ญ‰๏ผŒ็„ก้œ€ๆŒ‰้ตๆˆ–้ปžๆ“ŠๆŒ‰้ˆ•ใ€‚", + "auto-start-auth-label": "่‡ชๅ‹•ๅ•Ÿๅ‹•้ฉ—่ญ‰", + "clock-format-description": "ไฝฟ็”จๆ—ฅๆœŸ/ๆ™‚้–“่ชžๆณ•ไปค็‰Œ่‡ช่จ‚ๆ™‚้˜ๆ ผๅผใ€‚", + "clock-format-label": "ๆ™‚้˜ๆ ผๅผ", + "clock-style-analog": "้กžๆฏ”", + "clock-style-custom": "่‡ช่จ‚", + "clock-style-description": "้ธๆ“‡้Ž–ๅฎš็•ซ้ขๆ™‚้˜็š„่ฆ–่ฆบๆจฃๅผใ€‚", + "clock-style-digital": "ๆ•ธไฝ", + "clock-style-label": "ๆ™‚้˜ๆจฃๅผ", + "compact-lockscreen-description": "ๅช้กฏ็คบ็™ปๅ…ฅๆฌ„ไฝๅŠ้›ปๆบๆŽงๅˆถ, ้šฑ่—ๅคฉๆฐฃๅŠๅช’้ซ”ๅฐๅทฅๅ…ท", + "compact-lockscreen-label": "็ฒพ็ฐก้Ž–ๅฎš็•ซ้ข", + "enable-lockscreen-media-controls-description": "ๅœจ้Ž–ๅฎš็•ซ้ข้กฏ็คบไบ’ๅ‹•ๅผๅช’้ซ”ๆ’ญๆ”พๆŽงๅˆถ้ …ใ€‚", + "enable-lockscreen-media-controls-label": "้Ž–ๅฎš็•ซ้ขๅช’้ซ”ๆŽงๅˆถ้ …", + "lock-on-suspend-description": "ๅœจๆšซๅœ็ณป็ตฑๆ™‚่‡ชๅ‹•้Ž–ๅฎš็•ซ้ข", + "lock-on-suspend-label": "ๆšซๅœๆ™‚้Ž–ๅฎš", + "lock-screen-animations-description": "ๅ•Ÿ็”จๆˆ–ๅœ็”จ้Ž–ๅฎš็•ซ้ขๅ‹•็•ซใ€‚", + "lock-screen-animations-label": "้Ž–ๅฎš็•ซ้ขๅ‹•็•ซ", + "lock-screen-blur-strength-description": "ๅฐ้Ž–ๅฎš็•ซ้ขๆกŒๅธƒๅฅ—็”จๆจก็ณŠๆ•ˆๆžœใ€‚", + "lock-screen-blur-strength-label": "้Ž–ๅฎš่žขๅน•ๆจก็ณŠๅผทๅบฆ", + "lock-screen-tint-strength-description": "ๅฐ‡่‰ฒ่ชฟ็–ŠๅŠ ๅฅ—็”จๅˆฐ้Ž–ๅฎš็•ซ้ขๆกŒๅธƒใ€‚", + "lock-screen-tint-strength-label": "้Ž–ๅฎš็•ซ้ข่‘—่‰ฒๅผทๅบฆ", + "monitors-desc": "ๅœจ็‰นๅฎš้กฏ็คบๅ™จไธŠ้กฏ็คบ้Ž–ๅฎš็•ซ้ขใ€‚ๅฆ‚ๆžœๆœช้ธๆ“‡๏ผŒๅ‰‡้ ่จญ็‚บๆ‰€ๆœ‰้กฏ็คบๅ™จใ€‚", + "password-chars-description": "็”จๆ–ผ้šฑ่—ๅฏ†็ขผ็š„ๅฏๆ„›ๅœ–็คบใ€‚", + "password-chars-label": "้šจๆฉŸๅฏ†็ขผๅœ–็คบ", + "show-hibernate-description": "ๅœจ้›ปๆบๅŠๅทฅไฝœ้šŽๆฎต้ธๅ–ฎ้กฏ็คบ็ก็œ ", + "show-hibernate-label": "้กฏ็คบ็ก็œ ", + "show-session-buttons-description": "ๅ…่จฑๅœจ้Ž–ๅฎš็•ซ้ขๅญ˜ๅ–้›ปๆบๆŽงๅˆถ", + "show-session-buttons-label": "้›ปๆบๆŽงๅˆถ", + "title": "้Ž–ๅฎš็•ซ้ข" + }, + "network": { + "bluetooth-description": "ๅ•Ÿ็”จ่—็‰™็ฎก็†", + "bluetooth-rssi-polling-description": "้€้Ž bluetoothctl ๅฎšๆœŸๅ–ๆจฃ้€ฃๆŽฅ่ฃ็ฝฎ็š„ RSSI ๅ€ผ; ไธไธ€ๅฎšๆฏๅ€‹่ฃ็ฝฎ้ƒฝๅฏ็”จ; ๅ•Ÿ็”จๆ™‚ๆœƒไฝฟ็”จๆœ€ๅฐ‘่ณ‡ๆบ", + "bluetooth-rssi-polling-label": "ๅ›žๅ ฑ่—็‰™่จŠ่™Ÿ", + "desc": "็ฎก็† Wi-Fi ๅŠ่—็‰™้€ฃ็ทš", + "wifi-description": "็ฎก็†็„ก็ทš็ถฒ่ทฏ (้œ€่ฆ NetworkManager ๅฅ—ไปถ)" + }, + "notifications": { + "duration-critical-urgency-description": "่จญๅฎš้žๅธธๅฑๆ€ฅ็š„้€š็Ÿฅ่ฉฒ้กฏ็คบๅคšไน…", + "duration-critical-urgency-label": "้žๅธธๅฑๆ€ฅ", + "duration-desc": "่จญๅฎš้€š็Ÿฅ่ฉฒๅŸบๆ–ผๅฎƒๅ€‘็š„ๅ„ชๅ…ˆ็ดš้กฏ็คบๅคšไน…", + "duration-low-urgency-description": "่จญๅฎšไฝŽๆ€ฅ่ฟซๆ€ง็š„้€š็Ÿฅ่ฉฒ้กฏ็คบๅคšไน…", + "duration-low-urgency-label": "ไฝŽๆ€ฅ่ฟซๆ€ง", + "duration-normal-urgency-description": "่จญๅฎšๆ™ฎ้€šๆ€ฅ่ฟซ็š„้€š็Ÿฅ่ฉฒ้กฏ็คบๅคšไน…", + "duration-normal-urgency-label": "ๆ™ฎ้€šๆ€ฅ่ฟซ", + "duration-respect-expire-description": "ไฝฟ็”จ้€š็Ÿฅไธญ่จญๅฎš็š„ๅˆฐๆœŸ้€พๆ™‚ๆ™‚้–“ใ€‚", + "duration-respect-expire-label": "้ตๅพชๅˆฐๆœŸ้€พๆ™‚่จญๅฎš", + "duration-title": "้€š็Ÿฅ้กฏ็คบๆ™‚้•ท", + "history-clear-dismiss-description": "้—œ้–‰ๆ™‚ๅพžๆญทๅฒ่จ˜้Œ„ไธญๆธ…้™ค้€š็Ÿฅใ€‚", + "history-clear-dismiss-label": "้—œ้–‰ๆ™‚ๆธ…้™ค", + "history-critical-urgency-description": "ๅฐ‡้žๅธธๅฑๆ€ฅ็š„้€š็Ÿฅๅญ˜ๆ”พๅˆฐๆญทๅฒ้€š็Ÿฅ", + "history-critical-urgency-label": "ๅ„ฒๅญ˜้žๅธธๅฑๆ€ฅๅˆฐๆญทๅฒ้€š็Ÿฅ", + "history-desc": "ๆ นๆ“š้€š็Ÿฅ็š„็ทŠๆ€ฅ็จ‹ๅบฆ๏ผŒๆŽงๅˆถ่ฆๅฐ‡ๅ“ชไบ›้€š็Ÿฅๅ„ฒๅญ˜่‡ณ็ด€้Œ„ใ€‚", + "history-low-urgency-description": "ๅฐ‡ไฝŽๆ€ฅ่ฟซๆ€ง็š„้€š็Ÿฅๅญ˜ๆ”พๅˆฐๆญทๅฒ้€š็Ÿฅ", + "history-low-urgency-label": "ๅ„ฒๅญ˜ไฝŽๆ€ฅ่ฟซๆ€งๅˆฐๆญทๅฒ้€š็Ÿฅ", + "history-normal-urgency-description": "ๅฐ‡ๆ™ฎ้€šๆ€ฅ่ฟซ็š„้€š็Ÿฅๅญ˜ๆ”พๅˆฐๆญทๅฒ้€š็Ÿฅ", + "history-normal-urgency-label": "ๅ„ฒๅญ˜ๆ™ฎ้€šๆ€ฅ่ฟซๅˆฐๆญทๅฒ้€š็Ÿฅ", + "monitors-desc": "ๅชๅœจ็‰นๅฎš็š„้กฏ็คบๅ™จ้กฏ็คบ้€š็Ÿฅ, ๅฆ‚ๆžœๅ…จ้ƒจๅ–ๆถˆๅ‹พ้ธๅ‰‡ๆœƒ้กฏ็คบๅœจๆ‰€ๆœ‰้กฏ็คบๅ™จ", + "rules-action-block": "ๅ€ๅกŠ", + "rules-action-block-desc": "ๅฎŒๅ…จ่ทณ้Žใ€‚", + "rules-action-hide": "้šฑ่—", + "rules-action-hide-desc": "็„กๅฝˆ็ช—๏ผŒ็„ก่ฒ้Ÿณ๏ผŒๆ–ฐๅขž่‡ณๆญทๅฒ่จ˜้Œ„ใ€‚", + "rules-action-label": "ๅ‹•ไฝœ", + "rules-action-mute": "้œ้Ÿณ", + "rules-action-mute-desc": "็„ก่ฒ้Ÿณ๏ผŒไป้กฏ็คบๅฝˆๅ‡บ่ฆ–็ช—ไธฆ่จ˜้Œ„ๅœจๆญทๅฒไธญใ€‚", + "rules-add": "ๆ–ฐๅขž่ฆๅ‰‡", + "rules-delete": "ๅˆช้™ค่ฆๅ‰‡", + "rules-description": "ๅŒน้…ๆ‡‰็”จ็จ‹ๅผๅ็จฑๆˆ–ๅ…งๅฎนใ€‚่ฆๅ‰‡ๆœƒไพๅบๆชขๆŸฅ๏ผŒไธฆๅฅ—็”จ็ฌฌไธ€ๅ€‹ๅŒน้…้ …ใ€‚", + "rules-edit": "็ทจ่ผฏ่ฆๅ‰‡", + "rules-label": "็ฏฉ้ธ่ฆๅ‰‡", + "rules-pattern-label": "ๆจกๅผ", + "rules-pattern-placeholder": "firefox, discord, ๆˆ– /regex/", + "rules-tab": "่ฆๅ‰‡", + "settings-always-on-top-description": "ๅœจๅ…จ่žขๅน•่ฆ–็ช—่ˆ‡ๅ…ถไป–ๅฑค็ดšไธŠๆ–น้กฏ็คบ้€š็Ÿฅใ€‚", + "settings-background-opacity-description": "่ชฟๆ•ด้€š็Ÿฅ่ƒŒๆ™ฏ็š„ไธ้€ๆ˜Žๅบฆ", + "settings-density-description": "้ธๆ“‡้€š็Ÿฅๅกๅฏ†ๅบฆใ€‚", + "settings-density-label": "ๅฏ†ๅบฆ", + "settings-desc": "่จญๅฎš้€š็Ÿฅ็š„ๅค–่ง€ๅŠ่กŒ็‚บ", + "settings-do-not-disturb-description": "็•ถๅ•Ÿ็”จๆ™‚, ๅœๆญขๅฝˆๅ‡บๆ‰€ๆœ‰้€š็Ÿฅ", + "settings-enabled-description": "ๅ•Ÿ็”จๆˆ–ๅฎŒๅ…จๅœ็”จ้€š็Ÿฅ็š„ daemon, ้œ€่ฆ้‡ๆ–ฐๅ•Ÿๅ‹• Noctalia ไป‹้ข", + "settings-enabled-label": "ๅ•Ÿ็”จ้€š็Ÿฅ", + "settings-location-description": "้€š็Ÿฅ่ฉฒๅœจ็•ซ้ข็š„ๅ“ช่ฃกๅ‡บ็พ", + "settings-markdown-description": "ไฝฟ็”จ Markdown ๆ ผๅผๆธฒๆŸ“้€š็Ÿฅๅ…งๅฎนใ€‚", + "settings-markdown-label": "ๅ•Ÿ็”จ Markdown", + "sounds-desc": "่จญๅฎš้€š็Ÿฅๆ‰€ไฝฟ็”จ็š„้Ÿณๆ•ˆๅŠ้Ÿณ้‡", + "sounds-enabled-description": "ๅœจๆœ‰ๆ–ฐ้€š็Ÿฅๆ™‚ๅ•Ÿ็”จ้Ÿณๆ•ˆ", + "sounds-enabled-label": "ๅ•Ÿ็”จ้€š็Ÿฅ้Ÿณๆ•ˆ", + "sounds-excluded-apps-description": "้‡ๅฐ็‰นๅฎšๅ…ทๆœ‰ๅ…งๅปบ้Ÿณๆ•ˆ็š„ๆ‡‰็”จ็จ‹ๅผ๏ผŒ็•ฅ้Žๅœจๆญค่จญๅฎš็š„้€š็Ÿฅ้Ÿณๆ•ˆใ€‚", + "sounds-excluded-apps-label": "ๆŽ’้™ค็š„ๆ‡‰็”จ็จ‹ๅผ", + "sounds-excluded-apps-placeholder": "discord,firefox,chrome,chromium,edge", + "sounds-files-critical-description": "ไฝœ็‚บ้žๅธธๅฑๆ€ฅ็š„้€š็Ÿฅ้Ÿณๆ•ˆๆช”ๆกˆๆ‰€ๅœจ็š„่ทฏๅพ‘", + "sounds-files-critical-label": "้žๅธธๅฑๆ€ฅ็š„้Ÿณๆ•ˆ", + "sounds-files-critical-select-title": "้ธๆ“‡็›ธ็•ถๅฑๆ€ฅ้Ÿณๆ•ˆๆช”", + "sounds-files-desc": "็‚บไธๅŒ็š„้€š็Ÿฅ็ทŠๆ€ฅ็จ‹ๅบฆ็ญ‰็ดš่จญๅฎš่‡ช่จ‚้Ÿณๆ•ˆๆช”ๆกˆใ€‚", + "sounds-files-low-description": "ไฝœ็‚บไฝŽๆ€ฅ่ฟซๆ€ง็š„็š„้€š็Ÿฅ้Ÿณๆ•ˆๆช”ๆกˆๆ‰€ๅœจ็š„่ทฏๅพ‘", + "sounds-files-low-label": "ไฝŽๆ€ฅ่ฟซๆ€ง็š„้Ÿณๆ•ˆ", + "sounds-files-low-select-title": "้ธๆ“‡ไฝŽๆ€ฅ่ฟซๆ€ง้Ÿณๆ•ˆๆช”", + "sounds-files-normal-description": "ไฝœ็‚บๆ™ฎ้€šๆ€ฅ่ฟซ็š„้€š็Ÿฅ้Ÿณๆ•ˆๆช”ๆกˆๆ‰€ๅœจ็š„่ทฏๅพ‘", + "sounds-files-normal-label": "ๆ™ฎ้€šๆ€ฅ่ฟซ็š„้Ÿณๆ•ˆ", + "sounds-files-normal-select-title": "้ธๆ“‡ๆ™ฎ้€šๆ€ฅ่ฟซ้Ÿณๆ•ˆๆช”", + "sounds-files-placeholder": "่ผธๅ…ฅ้Ÿณๆ•ˆๆช”ๆ‰€ๅœจ็š„่ทฏๅพ‘", + "sounds-files-select-file": "้ธๆ“‡้Ÿณๆ•ˆๆช”", + "sounds-files-title": "้Ÿณๆ•ˆๆช”", + "sounds-files-unified-description": "ไฝœ็‚บ้€š็Ÿฅ้Ÿณๆ•ˆ็š„ๆช”ๆกˆๆ‰€ๅœจ็š„่ทฏๅพ‘", + "sounds-files-unified-label": "้€š็Ÿฅ้Ÿณๆ•ˆ", + "sounds-files-unified-select-title": "้ธๆ“‡้€š็Ÿฅ้Ÿณๆ•ˆๆช”", + "sounds-separate-description": "ๅˆ†ๅˆฅ็‚บไฝŽๆ€ฅ่ฟซๆ€ง, ๆ™ฎ้€šๆ€ฅ่ฟซๅŠ็›ธ็•ถๅฑๆ€ฅ้€š็Ÿฅไฝฟ็”จไธๅŒ็š„้Ÿณๆ•ˆๆช”", + "sounds-separate-label": "็‚บไธๅŒๅ„ชๅ…ˆ็ดšไฝฟ็”จไธๅŒ้Ÿณๆ•ˆ", + "sounds-title": "้Ÿณๆ•ˆ่จญๅฎš", + "sounds-unavailable-description": "ๅฎ‰่ฃ qt6 multimedia ๅฅ—ไปถไปฅๅ•Ÿ็”จ้€š็Ÿฅ้Ÿณๆ•ˆ", + "sounds-unavailable-label": "้€š็Ÿฅ้Ÿณๆ•ˆ็„กๆณ•ไฝฟ็”จ", + "sounds-volume-description": "่ชฟๆ•ด้€š็Ÿฅ้Ÿณๆ•ˆ็š„้Ÿณ้‡ๅคงๅฐ", + "sounds-volume-label": "้Ÿณๆ•ˆ้Ÿณ้‡", + "toast-battery-description": "็•ถ้›ปๆฑ ้›ป้‡ไฝŽๆ–ผๆญค็™พๅˆ†ๆฏ”ๆ™‚้กฏ็คบ่ญฆๅ‘Šใ€‚", + "toast-battery-label": "ไฝŽ้›ป้‡ๆ้†’", + "toast-desc": "่จญๅฎšๆฐฃๆณก่จŠๆฏ็š„ๅค–่ง€ๅŠ่กŒ็‚บ", + "toast-keyboard-description": "็•ถ้ต็›คไฝˆๅฑ€ๆ”น่ฎŠๆ™‚้กฏ็คบๆฐฃๆณก่จŠๆฏ", + "toast-keyboard-label": "้ต็›คไฝˆๅฑ€", + "toast-media-description": "ๅช’้ซ”ๆ’ญๆ”พ็‹€ๆ…‹่ฎŠๆ›ดๆ™‚้กฏ็คบๆฐฃๆณก่จŠๆฏ", + "toast-media-label": "ๅช’้ซ”" + }, + "osd": { + "always-on-top-description": "ๅฐ‡่žขๅน•้กฏ็คบๅœจๅ…จ่žขๅน•่ฆ–็ช—่ˆ‡ๅ…ถไป–ๅฑค็–Šไน‹ไธŠ", + "always-on-top-label": "็ธฝๆ˜ฏ็ฝฎ้ ‚", + "background-opacity-description": "ๆŽงๅˆถ่žขๅน•้กฏ็คบ่ƒŒๆ™ฏ็š„ไธ้€ๆ˜Žๅบฆ", + "background-opacity-label": "่ƒŒๆ™ฏไธ้€ๆ˜Žๅบฆ", + "description": "่จญๅฎš่žขๅน•้กฏ็คบ็š„ๆŒ‡็คบ้ …็›ฎ, ๅฆ‚้Ÿณ้‡ๅŠไบฎๅบฆ", + "duration-auto-hide-description": "่ชฟๆ•ด่žขๅน•้กฏ็คบๆถˆๅคฑ็š„ๆ™‚้–“", + "duration-auto-hide-label": "ๅœจๆ™‚้–“ๅพŒ้šฑ่—", + "duration-desc": "่จญๅฎš่žขๅน•้กฏ็คบๅœจ่‡ชๅ‹•ๆถˆๅคฑๅ‰่ฆ้กฏ็คบๅคšไน…", + "duration-title": "่‡ชๅ‹•้šฑ่—ๆ™‚้–“", + "enabled-description": "้Ÿณ้‡ๅŠไบฎๅบฆๆ”น่ฎŠๆ™‚ๅณๆ™‚้กฏ็คบ", + "enabled-label": "ๅ•Ÿ็”จ่žขๅน•้กฏ็คบ", + "events-desc": "้ธๆ“‡่ฆๅ“ชไบ›ไบ‹ไปถ่งธ็™ผ่žขๅน•้กฏ็คบ", + "general-desc": "่จญๅฎš่žขๅน•้กฏ็คบ็š„ๅค–่ง€ๅŠ่กŒ็‚บ", + "location-description": "่จญๅฎš่žขๅน•้กฏ็คบๅœจๅ“ช่ฃกๅ‡บ็พ", + "monitors-desc": "ๅชๅœจ็‰นๅฎš็š„้กฏ็คบๅ™จ้กฏ็คบOSD, ๅฆ‚ๆžœๅ…จ้ƒจๅ–ๆถˆๅ‹พ้ธๅ‰‡ๆœƒ้กฏ็คบๅœจๆ‰€ๆœ‰้กฏ็คบๅ™จ", + "title": "่žขๅน•้กฏ็คบ (OSD)", + "types-brightness-description": "็•ถไบฎๅบฆๆ”น่ฎŠๆ™‚็ง€ๅ‡บ่žขๅน•้กฏ็คบ", + "types-brightness-label": "ไบฎๅบฆ", + "types-custom-text-description": "้กฏ็คบๅพž IPC ๅ‚ณๅ…ฅ็š„่‡ช่จ‚ๆ–‡ๅญ—่จŠๆฏ", + "types-custom-text-label": "่‡ช่จ‚ๆ–‡ๅญ—", + "types-desc": "้ธๅ–ๆœƒ่งธ็™ผ่žขๅน•้กฏ็คบ็š„ไบ‹ไปถ", + "types-input-volume-description": "็•ถ้บฅๅ…‹้ขจๆ”น่ฎŠๆ™‚็ง€ๅ‡บ่žขๅน•้กฏ็คบ", + "types-input-volume-label": "่ผธๅ…ฅ้Ÿณ้‡", + "types-lockkey-description": "็•ถ Caps Lock, Num Lock, Scroll Lock ๅˆ‡ๆ›ๆ™‚็ง€ๅ‡บ่žขๅน•้กฏ็คบ", + "types-lockkey-label": "Lock ้ต", + "types-media-description": "ๅช’้ซ”ๆ’ญๆ”พ็‹€ๆ…‹่ฎŠๆ›ดๆ™‚็ง€ๅ‡บ่žขๅน•้กฏ็คบ (ๆ’ญๆ”พ, ๆšซๅœ, ่ทณ้Ž)", + "types-media-label": "ๅช’้ซ”ๆ’ญๆ”พ", + "types-title": "่žขๅน•้กฏ็คบ็š„่งธ็™ผไบ‹ไปถ", + "types-volume-description": "็•ถ่ผธๅ‡บ้Ÿณ้‡ๆ”น่ฎŠๆ™‚็ง€ๅ‡บ่žขๅน•้กฏ็คบ", + "types-volume-label": "่ผธๅ‡บ้Ÿณ้‡" + }, + "plugins": { + "auto-update": "่‡ชๅ‹•ๆ›ดๆ–ฐๅค–ๆŽ›็จ‹ๅผ", + "auto-update-description": "ๅœจ shell ๅ•Ÿๅ‹•ๆ™‚่‡ชๅ‹•ๆ›ดๆ–ฐๆ‰€ๆœ‰ๅค–ๆŽ›็จ‹ๅผใ€‚", + "available-description": "ๅพž่จญๅฎš็š„ไพ†ๆบๆชข่ฆ–ๅŠๅฎ‰่ฃๅค–ๆŽ›ๆจก็ต„", + "available-label": "ๅฏ็”จ็š„ๅค–ๆŽ›ๆจก็ต„", + "available-no-plugins-description": "ๆชขๆŸฅไฝ ็š„ๆจก็ต„ไพ†ๆบๆˆ–่€…้‡ๆ–ฐๆ•ด็†ๅˆ—่กจ", + "available-no-plugins-label": "ๆฒ’ๆœ‰ๅฏ็”จ็š„ๅค–ๆŽ›ๆจก็ต„", + "check-for-updates": "ๆชขๆŸฅๆ›ดๆ–ฐ", + "checking-for-updates": "ๆญฃๅœจๆชขๆŸฅๆ›ดๆ–ฐ...", + "collision-already-installed": "้€™ๅ€‹ๆจก็ต„ๅทฒ็ถ“ๅฎ‰่ฃ้Žไบ†", + "collision-custom-version-exists": "ๅทฒ็ถ“ๅฎ‰่ฃไบ†ไพ†่‡ช \"{source}\" ็š„่‡ช่จ‚็‰ˆๆœฌ", + "collision-official-version-exists": "ๅทฒ็ถ“ๅฎ‰่ฃไบ†้€™ๅ€‹ๆจก็ต„็š„ๅฎ˜ๆ–น็‰ˆๆœฌ", + "development-disable": "ๅœ็”จๆญคๆ’ไปถ็š„้–‹็™ผ่€…ๆจกๅผ", + "development-enable": "็‚บๆญคๅค–ๆŽ›็จ‹ๅผๅ•Ÿ็”จ้–‹็™ผๆจกๅผใ€‚", + "filter-downloaded": "ๅทฒไธ‹่ผ‰", + "filter-not-downloaded": "ๆœชไธ‹่ผ‰", + "filter-tags-description": "ไปฅๅˆ†้กžๆˆ–ไธ‹่ผ‰็‹€ๆ…‹็ฏฉ้ธๅค–ๆŽ›ๆจก็ต„", + "filter-tags-label": "ๆจ™็ฑค", + "hot-reload-description": "็•ถๆจก็ต„ไฝฟ็”จ็š„ๆช”ๆกˆ่ฎŠๅ‹•ๆ™‚่‡ชๅ‹•้‡ๆ–ฐ่ผ‰ๅ…ฅ, ๅœจๆจก็ต„้–‹็™ผๆ™‚็‰นๅˆฅๆœ‰็”จ", + "hot-reload-label": "็†ฑ้‡่ผ‰ (้–‹็™ผๆจกๅผ)", + "hot-reloaded": "้‡ๆ–ฐ่ผ‰ๅ…ฅๆจก็ต„: {name}", + "install-error": "ๅฎ‰่ฃๅคฑๆ•—: {error}", + "install-incompatible": "{plugin} ้œ€่ฆ Noctalia v{version} ๆˆ–ไปฅไธŠ", + "install-success": "ๆˆๅŠŸๅฎ‰่ฃ {plugin}", + "installed-description": "่จญๅฎšๅŠ็ฎก็†ๆ‰€ๆœ‰ๆœฌๆฉŸๅทฒๅฎ‰่ฃ็š„ๅค–ๆŽ›ๆจก็ต„", + "installed-label": "ๅทฒๅฎ‰่ฃ็š„ๅค–ๆŽ›ๆจก็ต„", + "installed-no-plugins-description": "ๅพž \"ๅฏ็”จ\"้ ้ขไพ†ๅฎ‰่ฃๅค–ๆŽ›ๆจก็ต„", + "installed-no-plugins-label": "ๆฒ’ๆœ‰ๅฎ‰่ฃๅค–ๆŽ›ๆจก็ต„", + "installing": "ๆญฃๅœจๅฎ‰่ฃ {plugin}...", + "notify-updates": "ๅค–ๆŽ›็จ‹ๅผๆ›ดๆ–ฐ้€š็Ÿฅ", + "notify-updates-description": "็•ถๆœ‰ๆ–ฐ็š„ๅค–ๆŽ›็จ‹ๅผๆ›ดๆ–ฐๅฏ็”จๆ™‚๏ผŒ้กฏ็คบ้€š็Ÿฅๅฝˆๅ‡บ่ฆ–็ช—ใ€‚", + "open-plugin-page": "ๆ‰“้–‹ๆจก็ต„้ ้ข", + "open-plugins-tab": "้–‹ๅ•Ÿๅค–ๆŽ›ๆจก็ต„่จญๅฎš", + "plugin-settings-title": "{plugin} ่จญๅฎš", + "refresh-refreshing": "ๆญฃๅœจ้‡ๆ–ฐๆ•ด็†ๆจก็ต„ๆธ…ๅ–ฎ...", + "refresh-tooltip": "้‡ๆ–ฐๆ•ด็†ๅฏ็”จ็š„ๅค–ๆŽ›ๆจก็ต„", + "settings-error-not-loaded": "ๆฒ’ๆœ‰่ผ‰ๅ…ฅๅค–ๆŽ›ๆจก็ต„", + "settings-saved": "ๅทฒๅ„ฒๅญ˜ๆจก็ต„่จญๅฎš", + "settings-tooltip": "ๅค–ๆŽ›ๆจก็ต„่จญๅฎš", + "source-custom": "่‡ช่จ‚ไพ†ๆบ", + "sources-add-custom": "ๆ–ฐๅขž่‡ช่จ‚ๅญ˜ๅ„ฒๅบซ", + "sources-add-dialog-description": "ๆ–ฐๅขžไธ€ๅ€‹ GitHub ๅ„ฒๅญ˜ๅบซไฝœ็‚บๆจก็ต„ไพ†ๆบ", + "sources-add-dialog-error": "ๆ–ฐๅขžๆจก็ต„ไพ†ๆบๅคฑๆ•—", + "sources-add-dialog-name": "ๅ„ฒๅญ˜ๅบซๅ็จฑ", + "sources-add-dialog-name-placeholder": "ๆˆ‘็š„่‡ช่จ‚ๅค–ๆŽ›ๆจก็ต„", + "sources-add-dialog-success": "ๆˆๅŠŸๆ–ฐๅขžๅค–ๆŽ›ๆจก็ต„ไพ†ๆบ", + "sources-add-dialog-title": "ๆ–ฐๅขžๅค–ๆŽ›ๆจก็ต„ไพ†ๆบ", + "sources-add-dialog-url": "ๅ„ฒๅญ˜ๅบซ URL", + "sources-description": "็ฎก็†ๅค–ๆŽ›ๅฅ—ไปถๅญ˜ๅ„ฒๅบซใ€‚", + "sources-placeholder": "ๆˆ‘็š„่ถ…้…ทๅ„ฒๅญ˜ๅบซ", + "sources-remove-tooltip": "็งป้™คๅค–ๆŽ›ๆจก็ต„ไพ†ๆบ", + "title": "ๅค–ๆŽ›ๆจก็ต„ (Plugins)", + "translations-reloaded": "ๅทฒ้‡ๆ–ฐ่ผ‰ๅ…ฅ็ฟป่ญฏ๏ผš{name}", + "uninstall-dialog-description": "ไฝ ็ขบๅฎšๆƒณ่ฆ็งป้™ค {plugin}? ้€™ๆจฃๆœƒ็งป้™คๆ‰€ๆœ‰ๆจก็ต„็š„่ณ‡ๆ–™", + "uninstall-dialog-title": "็งป้™คๅค–ๆŽ›ๆจก็ต„", + "uninstall-error": "็งป้™คๅคฑๆ•—: {error}", + "uninstall-success": "ๆˆๅŠŸ็งป้™ค {plugin}", + "uninstalling": "ๆญฃๅœจ็งป้™ค {plugin}...", + "update-all": "ๅฐ‡ๅ…จ้ƒจ {count} ๅ€‹้ƒฝๆ›ดๆ–ฐ", + "update-all-success": "ๅทฒๆˆๅŠŸๆ›ดๆ–ฐๆ‰€ๆœ‰็š„ๅค–ๆŽ›ๆจก็ต„", + "update-available": "ๆœ‰ๆ–ฐ็š„ๅค–ๆŽ›ๆจก็ต„ๆ›ดๆ–ฐ", + "update-available-plural": "ๆœ‰ๅค–ๆŽ›ๆจก็ต„ๅฏๆ›ดๆ–ฐ ({count}ๅ€‹)", + "update-error": "ๆ›ดๆ–ฐๆจก็ต„ๅคฑๆ•—: {plugin}: {error}", + "update-pending": "v{current} โ†’ v{new} (้œ€่ฆ Noctalia v{required})", + "update-success": "ๅทฒๆ›ดๆ–ฐ {plugin} ่‡ณ v{version}", + "update-version": "v{current} โ†’ v{new}", + "updating": "ๆญฃๅœจๆ›ดๆ–ฐ..." + }, + "region": { + "title": "ๅœฐๅ€" + }, + "session-menu": { + "countdown-duration-description": "่จญๅฎšๅœจๅŸท่กŒ้›ปๆบๆ“ไฝœๅ‰่ฆๅ€’ๆ•ธ่จˆๆ™‚ๅคšไน…", + "countdown-duration-label": "ๅ€’ๆ•ธ้•ทๅบฆ", + "enable-countdown-description": "ๅœจๅŸท่กŒ้›ปๆบๆ“ไฝœๅ‰ๅ…ˆ้กฏ็คบไธ€ๅ€‹ๅ€’ๆ•ธ่จˆๆ™‚ๅ™จ", + "enable-countdown-label": "ๅ•Ÿ็”จๅ€’ๆ•ธ่จˆๆ™‚", + "entries-desc": "่‡ช่จ‚่ฆๅ‡บ็พๅœจๅทฅไฝœ้šŽๆฎต้ธๅ–ฎไธญ็š„้ …็›ฎๅŠ้ †ๅบ", + "entries-title": "้›ปๆบๆ“ไฝœ", + "entry-settings-command-description": "่‡ช่จ‚ๅŸท่กŒ้€™ๅ€‹ๅ‹•ไฝœๆ‰€ๅŸท่กŒ็š„ๆŒ‡ไปค, ็•™็ฉบๅ‰‡ไฝฟ็”จ็ณป็ตฑ็š„้ ่จญๆŒ‡ไปค", + "entry-settings-command-placeholder": "ไพ‹ๅฆ‚: systemctl poweroff", + "entry-settings-default-command-lock": "ๅ…งๅปบ้Ž–ๅฎš็•ซ้ข (ๆฒ’ๆœ‰ๆŒ‡ไปค)", + "entry-settings-default-command-logout": "ๅ…งๅปบ็™ปๅ‡บ (ๆฒ’ๆœ‰ๆŒ‡ไปค)", + "entry-settings-default-info-description": "ๅฆ‚ๆžœๆฒ’ๆœ‰ๆŒ‡ๅฎš่‡ช่จ‚ๆŒ‡ไปคๅ‰‡ไฝฟ็”จ็ณป็ตฑ็š„้ ่จญๆŒ‡ไปค", + "entry-settings-default-info-label": "้ ่จญๆŒ‡ไปค", + "entry-settings-keybind-description": "็•ถๆœƒ่ฉฑ้ธๅ–ฎๆ‰“้–‹ๆ™‚๏ผŒๅˆ†้…ไธ€ๅ€‹ๆŒ‰้ต็ต„ๅˆไพ†่งธ็™ผๆญคๆ“ไฝœใ€‚", + "entry-settings-keybind-placeholder": "ๆŒ‰ไธ‹้Œ„่ฃฝไปฅๅˆ†้…ไธ€ๅ€‹้ต...", + "entry-settings-keybind-recording": "้Œ„่ฃฝไธญ... ๆŒ‰ไปปๆ„็ต„ๅˆ้ต", + "entry-settings-title": "่จญๅฎš{entry}", + "entry-settings-tooltip": "่จญๅฎšๆŒ‡ไปค", + "general-desc": "่จญๅฎšๅทฅไฝœ้šŽๆฎต้ธๅ–ฎ็š„ๅค–่ง€ๅ’Œ่กŒ็‚บ", + "large-buttons-layout-description": "้ธๆ“‡ๅทฅไฝœ้šŽๆฎต้ธๅ–ฎ็š„ๆŒ‰้ˆ•่ฉฒๅฆ‚ไฝ•้กฏ็คบ", + "large-buttons-layout-label": "ๅคงๆŒ‰้ˆ•ไฝˆๅฑ€", + "large-buttons-style-description": "ไปฅๆ–นๆ ผๆŽ’ๅˆ—็š„ๅคงๆŒ‰้ˆ•้กฏ็คบๅทฅไฝœ้šŽๆฎต้ธๅ–ฎ", + "large-buttons-style-label": "ไปฅๅคงๆŒ‰้ˆ•้กฏ็คบ", + "position-description": "้ธๆ“‡ๅทฅไฝœ้šŽๆฎต้ธๅ–ฎๅœจๅ“ช่ฃก้กฏ็คบ", + "show-header-description": "ๅœจๅทฅไฝœ้šŽๆฎต้ธๅ–ฎ้กฏ็คบๆจ™้กŒๅŠ้—œ้–‰ๆŒ‰้ˆ•", + "show-header-label": "้กฏ็คบๆจ™้กŒๅˆ—", + "show-keybinds-description": "ๅœจๅทฅไฝœ้šŽๆฎต้ธ้ …ไธŠ้กฏ็คบๅฟซๆท้ตๆ็คบใ€‚", + "show-keybinds-label": "้กฏ็คบๅฟซๆท้ต" + }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "็•ถ Noctalia ๆ•ˆ่ƒฝๆจกๅผๅ•Ÿ็”จๆ™‚ไฟๆŒๆกŒ้ขๅฐๅทฅๅ…ทๅฏ่ฆ‹ใ€‚", + "noctalia-performance-disable-desktop-widgets-label": "ๅœจๆ•ˆ่ƒฝๆจกๅผไธ‹ๅ•Ÿ็”จๆกŒ้ขๅฐๅทฅๅ…ท", + "noctalia-performance-disable-wallpaper-description": "ๅœจๅ•Ÿ็”จ Noctalia ๆ•ˆ่ƒฝๆจกๅผๆ™‚๏ผŒไฟๆŒๆกŒ้ขใ€ๆฆ‚่ฆฝๅ’Œ้Ž–ๅฎš็•ซ้ขๆกŒๅธƒๅฏ่ฆ‹ใ€‚", + "noctalia-performance-disable-wallpaper-label": "ๅœจๆ•ˆ่ƒฝๆจกๅผไธ‹ๅ•Ÿ็”จๆกŒๅธƒๆธฒๆŸ“", + "title": "็ณป็ตฑ" + }, + "system-monitor": { + "critical-color-label": "ๅฑๆ€ฅ้ก่‰ฒ", + "custom-highlight-colors-title-label": "่‡ช่จ‚็ชๅ‡บ่‰ฒ", + "disk-available-label": "ๅฏ็”จ็ฃ็ขŸ็ฉบ้–“", + "disk-section-label": "็ฃ็ขŸไฝฟ็”จ้‡", + "enable-dgpu-monitoring-description": "ๆณจๆ„: ้€™ๅ€‹้ธ้ …ๆœƒๅ–š้†’ไฝ ็š„ GPU (NVIDIA/AMD), ๅœจๆททๅˆ้กฏๅก็š„็ญ†้›ปไธŠๅฏ่ƒฝๆœƒๅšด้‡ๅฝฑ้Ÿฟ้›ปๆฑ ็บŒ่ˆชๅŠ›", + "enable-dgpu-monitoring-label": "ๅ•Ÿ็”จ็›ฃ่ฆ–็จ็ซ‹ GPU", + "external-monitor-description": "่ผธๅ…ฅ็•ถๅ•Ÿ็”จๅค–้ƒจ็ณป็ตฑ็›ฃ่ฆ–ๅ™จๆ™‚ๆƒณ่ฆๅŸท่กŒ็š„ๆŒ‡ไปคๆˆ–ๆ‡‰็”จ็จ‹ๅผ่ทฏๅพ‘", + "external-monitor-label": "ๅค–้ƒจ็ณป็ตฑ็›ฃ่ฆ–ๅ™จๆŒ‡ไปค", + "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "general-desc": "่จญๅฎš็ณป็ตฑ็›ฃ่ฆ–ๅ™จ็š„ๅค–่ง€ๅŠ่กŒ็‚บ", + "gpu-section-label": "GPU ๆบซๅบฆ", + "highlight-colors-section-label": "็ชๅ‡บ่‰ฒ", + "polling-interval-label": "่ผช่ฉข้–“้š”", + "polling-section-description": "่จญๅฎš่ฉฒๅคšไน…ๆ›ดๆ–ฐไธ€ๆฌก็ณป็ตฑ่จˆ้‡ๅ€ผ", + "polling-section-label": "่ผช่ฉข้–“้š”", + "threshold-critical": "ๅšด้‡", + "threshold-warning": "่ญฆๅ‘Š", + "thresholds-section-description": "่ชฟๆ•ดๆฏๅ€‹็ณป็ตฑ้‡ๆธฌๅ€ผ็š„่ญฆๅ‘Š/ๅฑๆ€ฅ้–€ๆชป", + "use-custom-highlight-colors-description": "็•ถๅœ็”จๆ™‚, ๅฐ‡ๆœƒไฝฟ็”จไธป้กŒ้ ่จญ็š„็ชๅ‡บ่‰ฒ", + "use-custom-highlight-colors-label": "ไฝฟ็”จ่‡ช่จ‚็š„็ชๅ‡บ่‰ฒ", + "warning-color-label": "่ญฆๅ‘Š้ก่‰ฒ" + }, + "user-interface": { + "allow-panels-without-bar-description": "็•ถๅ•Ÿ็”จๆ™‚, ้ขๆฟๅฏไปฅๅœจไปปไฝ•่žขๅน•ไธŠๆ‰“้–‹; ็•ถๅœ็”จๆ™‚้ขๆฟๅฐฑๅชๆœƒๅœจๆœ‰ๅทฅๅ…ทๅˆ—็š„่žขๅน•ไธŠๆ‰“้–‹, ไนŸๅฏไปฅๆธ›ๅฐ‘่จ˜ๆ†ถ้ซ”ไฝฟ็”จ้‡", + "allow-panels-without-bar-label": "ๅ…่จฑ้ขๆฟๅœจๆฒ’ๆœ‰ๅทฅๅ…ทๅˆ—็š„็•ซ้ข้กฏ็คบ", + "animation-disable-description": "ๅœ็”จๆ‰€ๆœ‰ๅ‹•็•ซไปฅ็ฒๅพ—ๆ›ดๅฟซ, ไธๆ‹–ๆณฅๅธถๆฐด็š„้ซ”้ฉ—", + "animation-disable-label": "ๅœ็”จไป‹้ขๅ‹•็•ซ", + "animation-speed-description": "่ชฟๆ•ดไป‹้ขๅ…จๅŸŸ็š„ๅ‹•็•ซ้€Ÿๅบฆ", + "animation-speed-label": "ๅ‹•็•ซ้€Ÿๅบฆ", + "animation-speed-reset": "้‡่จญๅ‹•็•ซ้€Ÿๅบฆ", + "appearance-desc": "่‡ช่จ‚่ฆ–่ฆบๅ…ƒ็ด , ๅฆ‚ๆ็คบๆก†, ้‚Šๆก†ๅŠ้™ฐๅฝฑ", + "blur-behind-description": "ไฝฟ็”จๅˆๆˆๅ™จๆจก็ณŠๅ”ๅฎš๏ผŒๆจก็ณŠๆฌ„ไฝๅ’Œ้ขๆฟๅพŒๆ–น็š„ๅ€ๅŸŸใ€‚", + "blur-behind-label": "่ƒŒๆ™ฏๆจก็ณŠ", + "box-border-description": "ๅœจๅ…งๅฎนๅ€ๅกŠ็š„ๅค–ๅœ้กฏ็คบๅค–ๆก†", + "box-border-label": "้ ้ขๅฎนๅ™จๅค–ๆก†", + "box-border-radius-description": "่ชฟๆ•ดไธป่ฆ็‰ˆ้ขๅ€ๅกŠ๏ผˆไพ‹ๅฆ‚ๅด้‚Šๆฌ„ใ€ๅก็‰‡่ˆ‡ๅ…งๅฎน้ขๆฟ๏ผ‰็š„ๅœ“่ง’ๅผงๅบฆใ€‚", + "box-border-radius-label": "้ ้ขๅฎนๅ™จๅŠๅพ‘", + "control-border-radius-description": "ๆŽงๅˆถไบ’ๅ‹•ๅ…ƒ็ด ๏ผˆๅŒ…ๆ‹ฌๆŒ‰้ˆ•ใ€ๅˆ‡ๆ›้–‹้—œ่ˆ‡ๆ–‡ๅญ—ๆฌ„ไฝ๏ผ‰็š„ๅœ“่ง’ๅผงๅบฆใ€‚", + "control-border-radius-label": "ๆŽงๅˆถ้ …ๅŠๅพ‘", + "desc": "่‡ช่จ‚ไป‹้ข้กฏ็คบ็š„่กŒ็‚บ, ๅค–่ง€ๅŠ้ขจๆ ผ", + "dim-desktop-description": "็•ถๅŠŸ่ƒฝ่กจๆˆ–้ขๆฟๆ‰“้–‹ๆ™‚่ชฟๆš—ๆกŒๅธƒ", + "dim-desktop-label": "่ชฟๆš—ๆกŒๅธƒ", + "dimmer-opacity-description": "่จญๅฎš่ชฟๆš—ๆกŒๅธƒๆ‰€ไฝฟ็”จ็š„ไธ้€ๆ˜Žๅบฆ", + "dimmer-opacity-label": "่ชฟๆš—ๆกŒ้ขไธ้€ๆ˜Žๅบฆ", + "panel-background-opacity-description": "่จญๅฎšๆ‰€ๆœ‰้ขๆฟ็š„่ƒŒๆ™ฏไธ้€ๆ˜Žๅบฆ (ๅทฅๅ…ทๅˆ—, ๅ•Ÿๅ‹•ๅ™จ, ่จญๅฎš็ญ‰...)", + "panel-background-opacity-label": "้ขๆฟ่ƒŒๆ™ฏไธ้€ๆ˜Žๅบฆ", + "panels-attached-to-bar-description": "ๅฐ‡้ขๆฟ้Ž–ๅฎšๅœจๅทฅๅ…ทๅˆ—ๅŠ็•ซ้ข้‚Š็ทฃ, ้…ๅˆๅ€’่ง’ๅฏๅšๅ‡บๆ›ดไธ€่‡ด็š„ๆ•ˆๆžœ", + "panels-attached-to-bar-label": "้ขๆฟ่ฒผ้ฝŠ้‚Š็ทฃ", + "panels-overlay-description": "็ขบไฟๅทฅๅ…ทๅˆ—ๅŠ้ขๆฟๅง‹็ต‚ๅฏ่ฆ‹, ็”š่‡ณ็–Š้Žๅ…จ่žขๅน•็š„็จ‹ๅผ", + "panels-overlay-label": "็ถญๆŒๅทฅๅ…ทๅˆ—ๅŠ้ขๆฟๅœจๆœ€ไธŠๅฑค", + "scaling-description": "่ชฟๆ•ด้™คไบ†ๅทฅๅ…ทๅˆ—ไปฅๅค–็š„ๆ•ด้ซ”ไป‹้ขๅคงๅฐ", + "scaling-label": "ไป‹้ขๆฏ”ไพ‹", + "scrollbar-always-visible-description": "็•ถๅ…งๅฎนๅฏๆฒๅ‹•ๆ™‚๏ผŒไฟๆŒๆฒ่ปธๅง‹็ต‚ๅฏ่ฆ‹๏ผŒ่€Œไธๆ˜ฏๅƒ…ๅœจๆ‡ธๅœๆ™‚้กฏ็คบใ€‚", + "scrollbar-always-visible-label": "ๆฐธ้ ้กฏ็คบๆฒ่ปธ", + "settings-panel-header": "่จญๅฎš้ขๆฟ", + "settings-panel-mode-description": "้ธๆ“‡ไป‹้ข่จญๅฎšๅ‡บ็พๆ‰€็”จ็š„ไฝˆๅฑ€ (ๅฏ่ƒฝ้œ€่ฆ้‡ๆ–ฐๆ‰“้–‹)", + "settings-panel-mode-label": "่จญๅฎš้ขๆฟ้กฏ็คบ", + "settings-panel-sidebar-card-style-description": "็”จๅœ“่ง’ๅกซๅ……่ƒŒๆ™ฏๅŒ…่ฃน่จญๅฎšๅด้‚Šๆฌ„ใ€‚", + "settings-panel-sidebar-card-style-label": "ๅด้‚Šๆฌ„ๅก็‰‡ๆจฃๅผ", + "shadows-description": "็‚บๅทฅๅ…ทๅˆ—ๅŠ้ขๆฟๅ•Ÿ็”จ้™ฐๅฝฑ", + "shadows-direction-description": "้ธๆ“‡้™ฐๅฝฑๆŠ•ๅฐ„็š„ไฝ็ฝฎใ€‚", + "shadows-direction-label": "้™ฐๅฝฑๆ–นๅ‘", + "shadows-label": "้™ฐๅฝฑ", + "title": "ไฝฟ็”จ่€…ไป‹้ข", + "tooltips-description": "ๅœจๆ•ดๅ€‹ไป‹้ขๅ•Ÿ็”จๆˆ–ๅœ็”จๆ็คบๆก†", + "tooltips-label": "้กฏ็คบๆ็คบๆก†", + "translucent-widgets-description": "ไฝฟ้ขๆฟๅ…ง็š„ๆŒ‰้ˆ•ใ€ๅˆ†้ ๅ’Œๅ…ถไป–ๅฐ้ƒจไปถๅŠ้€ๆ˜Žใ€‚", + "translucent-widgets-label": "ๅŠ้€ๆ˜Žๅฐๅทฅๅ…ท" + }, + "wallpaper": { + "automation-change-mode-alphabetical": "็…งๅญ—ๆฏๆŽ’ๅบ", + "automation-change-mode-description": "็•ถ่‡ชๅ‹•ๆ›ดๆ›ๆ™‚้ธๆ“‡ไปฅไป€้บผ่ฆๅ‰‡้ธๆ“‡ๆกŒๅธƒ", + "automation-change-mode-label": "ๆ›ดๆ›ๆจกๅผ", + "automation-custom-interval-description": "็”จ HH:MM ๆ ผๅผ่ผธๅ…ฅๆ™‚้–“ (ไพ‹ๅฆ‚: 01:30)", + "automation-custom-interval-label": "่‡ช่จ‚้–“ๆ ผ", + "automation-interval-description": "ๆกŒๅธƒ่ฉฒๅคšไน…่‡ชๅ‹•ๆ›ดๆ›", + "automation-interval-label": "ๆกŒๅธƒๆ›ดๆ›้–“ๆ ผ", + "automation-random-wallpaper-description": "ๅœจๅ›บๅฎš็š„้–“ๆ ผ้šจๆฉŸๆ›ดๆ›ๆกŒๅธƒ", + "automation-scheduled-change-description": "ๅœจๅ›บๅฎš็š„้–“ๆ ผ่‡ชๅ‹•ๆ›ดๆ›ๆกŒๅธƒ", + "automation-scheduled-change-label": "ๆŽ’็จ‹ๆ›ดๆ›", + "look-feel-edge-smoothness-description": "ๅœจ่ฝ‰ๅ ด้‚Š็ทฃๅฅ—็”จๆŸ”ๅ’Œ็š„็พฝๅŒ–ๆ•ˆๆžœใ€‚", + "look-feel-edge-smoothness-label": "ๆŸ”ๅŒ–่ฝ‰ๅ ด้‚Š็ทฃ", + "look-feel-fill-color-description": "ๆŒ‘้ธไธ€ๅ€‹ๆกŒๅธƒๅบ•ไธ‹ๅฏ่ƒฝๆœƒ้กฏ็คบ็š„ๅกซๅ……ๅฏฆ่‰ฒ", + "look-feel-fill-mode-description": "้ธๆ“‡ๅœ–ๅƒ่ฉฒไปฅไป€้บผๆ–นๅผ็ธฎๆ”พไพ†็ฌฆๅˆ้กฏ็คบๅ™จ็š„่งฃๆžๅบฆ", + "look-feel-fill-mode-label": "ๅกซๅ……ๆจกๅผ", + "look-feel-skip-startup-transition-description": "ๅœจ shell ๅ•Ÿๅ‹•ๆ™‚่ทณ้ŽๆกŒๅธƒๅ‹•็•ซใ€‚", + "look-feel-skip-startup-transition-label": "่ทณ้Žๅ•Ÿๅ‹•้Žๅ ดๅ‹•็•ซ", + "look-feel-title": "ๅค–่ง€ๅŠ้ขจๆ ผ", + "look-feel-transition-duration-description": "่จญๅฎš่ฝ‰ๅ ดๅ‹•็•ซไฝฟ็”จ็š„็ง’ๆ•ธ", + "look-feel-transition-duration-label": "่ฝ‰ๅ ด้•ทๅบฆ", + "look-feel-transition-type-description": "ๅˆ‡ๆ›ๆกŒๅธƒๆ™‚็š„่ฝ‰ๅ ดๆจฃๅผ", + "look-feel-transition-type-label": "่ฝ‰ๅ ดๆจฃๅผ", + "settings-clear-cache-button": "ๆธ…้™คๅฟซๅ–", + "settings-clear-cache-description": "ๆธ…้™คๅฟซๅ–ไธญๅทฒ่ชฟๆ•ดๅคงๅฐ็š„ๆกŒๅธƒไปฅ้‡‹ๆ”พ็ฃ็ขŸ็ฉบ้–“ใ€‚", + "settings-clear-cache-label": "ๆกŒ้ข่ƒŒๆ™ฏๅฟซๅ–", + "settings-clear-cache-toast": "่ƒŒๆ™ฏๅœ–็‰‡ๅฟซๅ–ๅทฒๆธ…้™ค", + "settings-desc": "ๆŽงๅˆถๆกŒๅธƒ็š„้กฏ็คบๅ’Œ็ฎก็†", + "settings-enable-management-description": "ไฝฟ็”จ Noctalia ็ฎก็†ๆกŒๅธƒ, ๅฆ‚ๆžœไฝ ๅๅฅฝไฝฟ็”จๅ…ถไป–็จ‹ๅผ่จญๅฎšๅ‰‡ๅ–ๆถˆ", + "settings-enable-management-label": "ๅ•Ÿ็”จๆกŒๅธƒ็ฎก็†", + "settings-enable-overview-description": "ๅœจ็ธฝ่ฆฝ็•ซ้ขๅฅ—็”จไธ€ๅ€‹ๆจก็ณŠ่ชฟๆš—้Ž็š„ๆกŒๅธƒ", + "settings-enable-overview-label": "ๅ•Ÿ็”จ็ธฝ่ฆฝๆกŒๅธƒ", + "settings-folder-description": "ไฝ ไธป่ฆๅญ˜ๆ”พๆกŒๅธƒ็š„ๆ–‡ไปถๅคพ่ทฏๅพ‘", + "settings-folder-label": "ๆกŒๅธƒๆ–‡ไปถๅคพ", + "settings-hide-wallpaper-filenames-tooltip-hide": "้šฑ่—ๆช”ๆกˆๅ็จฑ", + "settings-hide-wallpaper-filenames-tooltip-show": "้กฏ็คบๆช”ๆกˆๅ็จฑ", + "settings-monitor-specific-description": "ๆ›ฟๅ€‹ๅˆฅ้กฏ็คบๅ™จ่จญๅฎšไธๅŒ็š„ๆกŒๅธƒๆ–‡ไปถๅคพ", + "settings-monitor-specific-label": "็ตฆ็‰นๅฎš้กฏ็คบๅ™จๆ‰€ไฝฟ็”จ็š„ๆ–‡ไปถๅคพ", + "settings-monitor-specific-tooltip": "้กฏ็คบๅ™จๆกŒๅธƒๆ–‡ไปถๅคพ", + "settings-overview-blur-strength-description": "ๅฐ‡ๆจก็ณŠๅผทๅบฆๆ‡‰็”จๆ–ผๆฆ‚่ฆฝใ€‚", + "settings-overview-blur-strength-label": "ๆฆ‚่ฆฝๆจก็ณŠๅผทๅบฆ", + "settings-overview-tint-description": "ๅฐ‡่‘—่‰ฒๅผทๅบฆๅฅ—็”จ่‡ณๆฆ‚่ฆฝใ€‚", + "settings-overview-tint-label": "ๆฆ‚่ฆฝ่‘—่‰ฒๅผทๅบฆ", + "settings-recursive-search-description": "ไนŸๅŒๆ™‚ๆœๅฐ‹ๆกŒๅธƒ็›ฎ้Œ„ๅบ•ไธ‹็š„ๅญๆ–‡ไปถๅคพ", + "settings-recursive-search-label": "ๆœๅฐ‹ๅญๆ–‡ไปถๅคพ", + "settings-select-monitor-folder": "้ธๆ“‡้กฏ็คบๅ™จๆ‰€ไฝฟ็”จ็š„ๆกŒๅธƒๆ–‡ไปถๅคพ", + "settings-selector-description": "้ธๆ“‡ไฝ ็š„ๆกŒๅธƒ", + "settings-selector-position-description": "้ธๆ“‡ๆŒ‘้ธๆกŒๅธƒ้ขๆฟ่ฆๅœจๅ“ช่ฃกๅ‡บ็พ", + "settings-show-hidden-files-tooltip-hide": "้šฑ่—้šฑ่—ๆช”ๆกˆ", + "settings-show-hidden-files-tooltip-show": "้กฏ็คบ้šฑ่—ๆช”ๆกˆ", + "settings-title": "ๆกŒๅธƒ่จญๅฎš", + "settings-use-original-images-description": "่ทณ้Žๅœจ้กฏ็คบๅ‰่ชฟๆ•ดๆกŒๅธƒๅคงๅฐใ€‚็ฏ€็œ็ฃ็ขŸ็ฉบ้–“ไธฆๆธ›ๅฐ‘ๆกŒๅธƒๆ›ดๆ›ๆ™‚็š„ CPU ไฝฟ็”จ็އ๏ผŒไฝ†ๅฐๆ–ผ้žๅธธๅคง็š„ๅœ–ๅƒๅฏ่ƒฝๆœƒไฝ”็”จๆ›ดๅคš่จ˜ๆ†ถ้ซ”ใ€‚", + "settings-use-original-images-label": "ไฝฟ็”จๅŽŸๅง‹ๅœ–็‰‡", + "settings-view-mode-description": "้ธๆ“‡ๅฆ‚ไฝ•ๅพžๆ‚จ็š„็›ฎ้Œ„้กฏ็คบๆกŒๅธƒใ€‚", + "settings-view-mode-label": "ๆชข่ฆ–ๆจกๅผ", + "view-mode-browse": "็€่ฆฝ็›ฎ้Œ„", + "view-mode-cycle-tooltip": "ๆชข่ฆ–ๆจกๅผ๏ผš{mode} (้ปžๆ“Šไปฅ่ฎŠๆ›ด)", + "view-mode-recursive": "ๆ‰ๅนณๅŒ–็š„ๅญ็›ฎ้Œ„", + "view-mode-single": "ๆ น็›ฎ้Œ„" + } + }, + "placeholders": { + "add-new-keybind": "ๆ–ฐๅขžๆŒ‰้ต็ถๅฎš", + "command-example": "echo \"Hello World\"", + "enter-command": "่ผธๅ…ฅ่ฆๅŸท่กŒ็š„ๆŒ‡ไปค (็จ‹ๅผๆˆ–่‡ช่จ‚่…ณๆœฌ)", + "enter-ipc-identifier": "่ผธๅ…ฅ IPC ๅ‘ฝไปค็š„ๅ”ฏไธ€่ญ˜ๅˆฅ็ขผ", + "enter-path": "่ผธๅ…ฅ่ทฏๅพ‘...", + "enter-text-to-collapse": "ไพ‹ๅฆ‚: 'nothing is playing'. ไนŸๅฏ็”จๆญฃ่ฆ่กจ็คบๅผ /regex/ ๆŒ‡ๅฎšๆ ผๅผ", + "enter-tooltip": "่ผธๅ…ฅๆ็คบๆก†ๆ–‡ๅญ—", + "enter-width-pixels": "ไปฅๅƒ็ด  (px) ่ผธๅ…ฅๅฏฌๅบฆ", + "keybind-recording": "ๆญฃๅœจ้Œ„่ฃฝๆŒ‰้ต็ถๅฎš...", + "search": "ๅœจ้€™่ฃกๆœๅฐ‹...", + "search-icons": "ไพ‹ๅฆ‚: noctalia, niri, battery, cloud", + "search-launcher": "ๆœๅฐ‹ๆ‰€ๆœ‰้ …็›ฎ... ๆˆ–่ผธๅ…ฅ > ไฝฟ็”จๆŒ‡ไปค", + "search-wallhaven": "ๆœๅฐ‹ Wallhaven...", + "search-wallpapers": "่ผธๅ…ฅไพ†็ฏฉ้ธๆกŒๅธƒ..." + }, + "positions": { + "bottom": "ๅบ•้ƒจ", + "bottom-center": "ๅบ•้ƒจ็ฝฎไธญ", + "bottom-left": "ๅทฆไธ‹่ง’", + "bottom-right": "ๅณไธ‹่ง’", + "center": "ๅฑ…ไธญ", + "center-left": "ๅทฆ้‚Š็ฝฎไธญ", + "center-right": "ๅณ้‚Š็ฝฎไธญ", + "close-to-bar": "้ ่ฟ‘ๅทฅๅ…ทๅˆ—ๆŒ‰้ˆ•", + "follow-bar": "็ทŠ่ทŸๅทฅๅ…ทๅˆ—", + "left": "ๅทฆ้‚Š", + "right": "ๅณ้‚Š", + "top": "้ ‚้ƒจ", + "top-center": "้ ‚้ƒจ็ฝฎไธญ", + "top-left": "ๅทฆไธŠ่ง’", + "top-right": "ๅณไธŠ่ง’" + }, + "session-menu": { + "action-in-seconds": "ๅฐ‡ๅœจ{seconds}็ง’ๅพŒ{action}...", + "cancel-timer": "ๅ–ๆถˆ่จˆๆ™‚ๅ™จ", + "title": "ๅทฅไฝœ้šŽๆฎต้ธๅ–ฎ" + }, + "setup": { + "all-done": "ๅฎŒๆˆ!", + "appearance": { + "subheader": "้ธๆ“‡ๆทฑ่‰ฒๆจกๅผๅ’Œ้ก่‰ฒไพ†ๆบ๏ผˆๆกŒๅธƒๆˆ–้ ่จญ๏ผ‰ใ€‚" + }, + "customize": { + "header": "ๅฎข่ฃฝไฝ ็š„ไฝฟ็”จ้ซ”้ฉ—", + "subheader": "่ชฟๆ•ดๅทฅๅ…ทๅˆ—็š„ไฝ็ฝฎ, ๅฏ†ๅบฆ, ๆฏ”ไพ‹็ญ‰็ญ‰" + }, + "skip-setup": "่ทณ้Ž่จญๅฎš", + "telemetry-wizard-done": "ไบ†่งฃ!", + "telemetry-wizard-note": "็›กๅœจๆŽŒๆŽงโ€”โ€”ๆ‚จ้šจๆ™‚ๅฏๅœจ่จญๅฎšไธญๅ•Ÿ็”จๆˆ–ๅœ็”จๆญคๅŠŸ่ƒฝ", + "telemetry-wizard-subtitle": "ๆˆ‘ๅ€‘ๆ–ฐๅขžไบ†ๅŒฟๅๅˆ†ๆžๅŠŸ่ƒฝไปฅๅ”ๅŠฉๆ”นๅ–„ Noctalia", + "telemetry-wizard-title": "้šฑ็งๆฌŠๆ›ดๆ–ฐ", + "wallpaper": { + "choose-dir": "้ธๆ“‡ไธ€ๅ€‹ๅญ˜ๆ”พไฝ ๆกŒๅธƒ็š„็›ฎ้Œ„", + "dir-browse": "็€่ฆฝๆกŒๅธƒๆ–‡ไปถๅคพ", + "dir-description": "้ธๆ“‡ๆ”พ็ฝฎไฝ ๆกŒๅธƒ็š„ๆ–‡ไปถๅคพ", + "dir-label": "ๆกŒๅธƒ็›ฎ้Œ„", + "dir-select-title": "้ธๆ“‡ๆกŒๅธƒๆ–‡ไปถๅคพ", + "header": "้ธๆ“‡ไฝ ็š„ๆกŒๅธƒ", + "no-dir": "ๆฒ’ๆœ‰้ธๆ“‡ไปปไฝ•ๆกŒๅธƒ็›ฎ้Œ„", + "no-valid": "ๅœจ {dir} ๆ‰พไธๅˆฐไปปไฝ•ๅœ–ๅƒๆช”ๆกˆ", + "none-in-dir": "ๅœจ็›ฎ้Œ„่ฃกๆ‰พไธๅˆฐไปปไฝ•ๆกŒๅธƒ", + "preview-error": "่ผ‰ๅ…ฅๅœ–ๅƒๅคฑๆ•—", + "select-prompt": "ๅœจไธ‹้ข้ธๆ“‡ไธ€ๅ€‹ๆกŒๅธƒ", + "subheader": "็”จไธ€ๅ€‹ๆผ‚ไบฎ็š„่ƒŒๆ™ฏ็‡Ÿ้€ ๆฐ›ๅœ" + }, + "welcome": "ๆญก่ฟŽ", + "welcome-note": "ๅนพๅ€‹ๅŸบๆœฌ็š„่จญๅฎš่ฎ“ไฝ ็ฐกๅ–ฎไธŠๆ‰‹ โ€” ๆ‰€ๆœ‰็š„้ธ้ …ๅฏไปฅๅœจ่จญๅฎš่ฃก่ชฟๆ•ด", + "welcome-subtitle": "่ฎ“ไฝ ็š„ๆกŒ้ข็จไธ€็„กไบŒ", + "welcome-title": "ๆญก่ฟŽไพ†ๅˆฐ Noctalia๏ผ" + }, + "system": { + "cpu-temperature": "{temp}ยฐC", + "disk-usage": "{percent}%", + "location-display": "{name} ({coordinates})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", + "no-media-player-detected": "ๆฒ’ๆœ‰ๅตๆธฌๅˆฐๅช’้ซ”ๆ’ญๆ”พๅ™จ", + "scaling-percentage": "{percentage}%", + "signal-strength": "{signal}%", + "unknown-app": "ๆœช็Ÿฅ็š„็จ‹ๅผ", + "uptime": "้–‹ๆฉŸๆ™‚้–“: {uptime}", + "user-requested": "ไฝฟ็”จ่€…่ซ‹ๆฑ‚", + "welcome-back": "ๆญก่ฟŽๅ›žไพ†,", + "widget-settings-title": "{widget} ่จญๅฎš" + }, + "system-monitor": { + "core-usage": "ๆ ธๅฟƒ {id} ไฝฟ็”จ็އ", + "cpu-temp": "CPU ๆบซๅบฆ", + "cpu-usage": "CPU ไฝฟ็”จ็އ", + "disk": "็ฃ็ขŸๅทฒ็”จ", + "download-speed": "ไธ‹่ผ‰้€Ÿๅบฆ", + "gpu-temp": "GPU ๆบซๅบฆ", + "load-average": "ๅนณๅ‡่ฒ ่ผ‰", + "title": "็ณป็ตฑ็›ฃ่ฆ–ๅ™จ", + "upload-speed": "ไธŠๅ‚ณ้€Ÿๅบฆ" + }, + "toast": { + "airplane-mode": { + "description": "ๅœ็”จๆ‰€ๆœ‰็„ก็ทš้€š่จŠใ€‚", + "title": "้ฃ›่ˆชๆจกๅผ" + }, + "battery": { + "critical": "้›ป้‡ๅšด้‡ไธ่ถณ", + "critical-desc": "้›ป้‡ๅ‰ฉ้ค˜ {percent}% โ€” ่ซ‹็ซ‹ๅณ้€ฃๆŽฅๅ……้›ปๅ™จ", + "low": "้›ป้‡้ŽไฝŽ", + "low-desc": "้›ป้‡ๅ‰ฉ้ค˜ {percent}% โ€” ่ซ‹้€ฃๆŽฅๅ……้›ปๅ™จ" + }, + "bluetooth": { + "address-copied": "ๅทฒ่ค‡่ฃฝไฝๅ€่‡ณๅ‰ช่ฒผ็ฐฟ", + "auto-connect-disabled": "่‡ชๅ‹•้€ฃ็ทšๅทฒๅœ็”จ", + "auto-connect-enabled": "่‡ชๅ‹•้€ฃ็ทšๅทฒๅ•Ÿ็”จ", + "auto-connecting": "ๆญฃๅœจ้€ฃๆŽฅ {count} ๅ€‹่ฃ็ฝฎ...", + "confirm-code": "ๅœจๅฆไธ€ๅ€‹่ฃ็ฝฎไธŠ็ขบ่ช้…ๅฐ็ขผ: {value}", + "connect-failed": "่ฃ็ฝฎ้€ฃๆŽฅๅคฑๆ•—", + "disconnect-failed": "่ฃ็ฝฎไธญๆ–ท้€ฃๆŽฅๅคฑๆ•—", + "discoverable-change-failed": "ๅˆ‡ๆ›่ขซๅตๆธฌ็‹€ๆ…‹ๅคฑๆ•—", + "discoverable-disabled": "็พๅœจไธๆœƒ่ขซๅตๆธฌ", + "discoverable-enabled": "็พๅœจๅทฒๅฏ่ขซๅตๆธฌ", + "display-code": "ๅœจๅฆไธ€ๅ€‹่ฃ็ฝฎไธŠ่ผธๅ…ฅ้…ๅฐ็ขผ: {value}", + "forget-failed": "ๅฟ˜่จ˜่ฃ็ฝฎๅคฑๆ•—", + "pair-failed": "่ฃ็ฝฎ้…ๅฐๅคฑๆ•—", + "passkey-required": "้…ๅฐ็š„่ฃ็ฝฎ้œ€่ฆ Passkey", + "pincode-required": "้…ๅฐ็š„่ฃ็ฝฎ้œ€่ฆ PIN ็ขผ", + "state-change-failed": "่ฎŠๆ›ด่—็‰™็‹€ๆ…‹ๅคฑๆ•—" + }, + "clipboard": { + "long-text": "้•ทไธฒๆ–‡ๅญ—", + "unavailable": "็„กๆณ•ไฝฟ็”จๅ‰ช่ฒผ็ฐฟๆญทๅฒ", + "unavailable-desc": "ๅฐšๆœชๅฎ‰่ฃ cliphist ๅฅ—ไปถ, ่ฆไฝฟ็”จๅ‰ช่ฒผ็ฐฟๆญทๅฒ่ซ‹ๅฎ‰่ฃๅฅ—ไปถ" + }, + "custom-command-failed": { + "description": "ๆŒ‡ไปคๅคฑๆ•—: {command}\\n็ตๆŸไปฃ็ขผ: {code}", + "title": "่‡ช่จ‚ๆŒ‡ไปคๅคฑๆ•—" + }, + "do-not-disturb": { + "disabled": "ๅ‹ฟๆ“พๆจกๅผๅทฒๅœ็”จ", + "disabled-desc": "้กฏ็คบๆ‰€ๆœ‰้€š็Ÿฅ", + "enabled": "ๅ‹ฟๆ“พๆจกๅผๅทฒๅ•Ÿ็”จ", + "enabled-desc": "ไฝ ๆœƒๅœจๆญทๅฒ้€š็Ÿฅๆ‰พๅˆฐ้€™ไบ›้€š็Ÿฅ" + }, + "donation-opened": "ๆๆฌพ้ ้ขๅทฒๅœจๆ‚จ็š„็€่ฆฝๅ™จไธญ้–‹ๅ•Ÿ", + "internet-limited": "ๅทฒ้€ฃๆŽฅไฝ†ๆฒ’ๆœ‰็ถฒ้š›็ถฒ่ทฏ", + "keyboard-layout": { + "changed": "ๅˆ‡ๆ›้ต็›คไฝˆๅฑ€็‚บ {layout}", + "title": "้ต็›ค" + }, + "missing-control-center": { + "description": "ๆŽงๅˆถไธญๅฟƒๅฐๅทฅๅ…ทๅทฒ็ถ“ๅพžๅทฅๅ…ทๅˆ—็งป้™ค, ๅฆ‚ๆžœๆƒณ้‡ๆ–ฐ่‡ชๅทฅๅ…ทๅˆ—ๅญ˜ๅ–, ไฝ ๅฟ…้ ˆๆŠŠๅฐๅทฅๅ…ทๅŠ ๅ›žไพ†; ไฝ ไนŸๅฏไปฅๅœจๅทฅๅ…ทๅˆ—็›ดๆŽฅ้ปžๆ“Šๅณ้ตๆ‰“้–‹", + "label": "ๆœ€ๅพŒไธ€ๅ€‹ๆŽงๅˆถไธญๅฟƒๅฐๅทฅๅ…ทๅทฒ่งฃ้™ค" + }, + "night-light": { + "forced": "ๅผทๅˆถ้–‹ๅ•Ÿ", + "normal": "ไธ€่ˆฌๆจกๅผ", + "not-installed": "ๅฐšๆœชๅฎ‰่ฃ wlsunset ๅฅ—ไปถ" + }, + "noctalia-performance": { + "disabled": "ๆ•ˆ่ƒฝๆจกๅผๅทฒๅœ็”จ", + "enabled": "ๆ•ˆ่ƒฝๆจกๅผๅทฒๅ•Ÿ็”จ", + "label": "Noctalia ๆ•ˆ่ƒฝๆจกๅผ" + }, + "power-profile": { + "changed": "้›ปๆบ้…็ฝฎๅทฒ่ฎŠๆ›ด", + "profile-name": "{profile}" + }, + "theming-processor-failed": { + "desc-generic": "ๅœจ่™•็†ๆจกๆฟๆ™‚็™ผ็”Ÿ้Œฏ่ชค", + "title": "็ฏ„ๆœฌ่™•็†ๅคฑๆ•—" + }, + "vpn": { + "connected": "ๅทฒ้€ฃ่‡ณ '{name}'", + "disconnected": "ๅทฒ่‡ช '{name}' ๆ–ท้–‹้€ฃ็ทš" + }, + "wallpaper-colors": { + "disabled": "ๆกŒๅธƒ้…่‰ฒๅทฒๅœ็”จ", + "enabled": "ๆกŒๅธƒ้…่‰ฒๅทฒๅ•Ÿ็”จ", + "label": "ๆกŒๅธƒ้…่‰ฒ" + }, + "wifi": { + "connected": "ๅทฒ้€ฃไธŠ '{ssid}'", + "connection-failed": "้€ฃ็ทšๅคฑๆ•—", + "connection-timeout": "้€ฃ็ทš้€พๆ™‚", + "disconnected": "ๅทฒ่‡ช '{ssid}' ๆ–ท้–‹้€ฃ็ทš", + "incorrect-password": "ๅฏ†็ขผ้Œฏ่ชค", + "network-not-found": "ๆ‰พไธๅˆฐ็ถฒ่ทฏ" + } + }, + "tooltips": { + "add-widget": "ๆ–ฐๅขžๅฐๅทฅๅ…ท", + "auto-connect": "ๅˆ‡ๆ›ๆญค่ฃ็ฝฎ็š„่‡ชๅ‹•้€ฃ็ทš", + "bluetooth-auto-connect-off": "่‡ชๅ‹•้€ฃ็ทšๅทฒๅœ็”จ", + "bluetooth-auto-connect-on": "่‡ชๅ‹•้€ฃ็ทšๅทฒๅ•Ÿ็”จ", + "bluetooth-devices": "่—็‰™่ฃ็ฝฎ", + "brightness-at": "ไบฎๅบฆ: {brightness}%", + "click-to-start-recording": "่žขๅน•้Œ„ๅฝฑ (้–‹ๅง‹้Œ„่ฃฝ)", + "click-to-stop-recording": "่žขๅน•้Œ„ๅฝฑ (ๅœๆญข้Œ„่ฃฝ)", + "collapse": "ๆ”ถ่ตทๅด้‚Šๆฌ„", + "copy-address": "่ค‡่ฃฝไฝๅ€", + "delete-notification": "ๅˆช้™ค้€š็Ÿฅ", + "dismiss-notification": "้—œ้–‰้€š็Ÿฅ", + "do-not-disturb-enabled": "ๅ‹ฟๆ“พๆจกๅผ", + "expand": "ๅฑ•้–‹ๅด้‚Šๆฌ„", + "forget-network": "ๅฟ˜่จ˜็ถฒ่ทฏ่จญๅฎš", + "grid-view": "ๆ–นๆ ผๆชข่ฆ–", + "hidden-files-hide": "้šฑ่—ๆช”ๆกˆ", + "hide-unnamed-devices": "้šฑ่—ไธๅ…ทๅ่ฃ็ฝฎ", + "home": "ๅฎถ็›ฎ้Œ„", + "input-muted": "้–‹้—œ่ผธๅ…ฅ้œ้Ÿณ", + "keep-awake": "ไฟๆŒๅ–š้†’", + "keyboard-layout": "้ต็›คไฝˆๅฑ€: {layout}", + "list-view": "ๅˆ—่กจๆชข่ฆ–", + "manage-vpn": "VPN ้€ฃ็ทš", + "manage-wifi": "Wi-Fi", + "max-widgets-reached": "ๅทฒ้”ๅˆฐๅฐๅทฅๅ…ทๆ•ธ้‡ไธŠ้™", + "microphone-volume-at": "้บฅๅ…‹้ขจ้Ÿณ้‡: {volume}%", + "move-to-section": "็งป่‡ณ{section}", + "mute": "้œ้Ÿณ", + "next-media": "ไธ‹ไธ€้ฆ–", + "next-month": "ไธ‹ๅ€‹ๆœˆ", + "night-light-not-installed": "ๅคœ็‡ˆๆจกๅผ (็„กๆณ•ไฝฟ็”จ)", + "noctalia-performance-enabled": "Noctalia ๆ•ˆ่ƒฝๆจกๅผ", + "open-annotation-tool": "ไฝฟ็”จ่จป่จ˜ๅทฅๅ…ท้–‹ๅ•Ÿ", + "open-control-center": "ๆŽงๅˆถไธญๅฟƒ", + "open-notification-history-enable-dnd": "ๆญทๅฒ้€š็Ÿฅ", + "open-settings": "่จญๅฎš", + "open-tray-dropdown": "็ณป็ตฑๅŒฃ", + "output-muted": "้–‹้—œ่ผธๅ‡บ้œ้Ÿณ", + "power-profile": "้›ปๆบ้…็ฝฎ: {profile} ", + "previous-media": "ไธŠไธ€้ฆ–", + "previous-month": "ไธŠๅ€‹ๆœˆ", + "refresh-devices": "้‡ๆ–ฐๆ•ด็†่ฃ็ฝฎ", + "refresh-wallhaven": "้‡ๆ–ฐๆ•ด็† Wallhaven ็ตๆžœ", + "refresh-wallpaper-list": "้‡ๆ–ฐๆ•ด็†ๆกŒๅธƒๅˆ—่กจ", + "remove-widget": "่งฃ้™คๅฐๅทฅๅ…ท", + "search-close": "้—œ้–‰ๆœๅฐ‹", + "session-menu": "ๅทฅไฝœ้šŽๆฎต้ธๅ–ฎ", + "show-all-devices": "้กฏ็คบๆ‰€ๆœ‰่ฃ็ฝฎ", + "switch-to-dark-mode": "ๆทฑ่‰ฒๆจกๅผ", + "switch-to-light-mode": "ๆทบ่‰ฒๆจกๅผ", + "unmute": "่งฃ้™ค้œ้Ÿณ", + "up": "ไธŠๅฑค็›ฎ้Œ„", + "volume-at": "่ผธๅ‡บ้Ÿณ้‡: {volume}%", + "wallpaper-selector": "ๆŒ‘้ธๆกŒๅธƒ" + }, + "wallpaper": { + "browse": { + "empty-directory": "้€™ๅ€‹็›ฎ้Œ„ๆ˜ฏ็ฉบ็š„ใ€‚", + "go-root": "ๅ‰ๅพ€ๆกŒๅธƒๆ น็›ฎ้Œ„", + "go-up": "ๅ‰ๅพ€ไธŠๅฑค่ณ‡ๆ–™ๅคพ", + "go-up-hint": "ไฝฟ็”จ่ฟ”ๅ›žๆŒ‰้ˆ•ๅ‘ไธŠๅฐŽ่ˆชใ€‚" + }, + "configure-directory": "่จญๅฎšไฝ ็š„ๆกŒๅธƒ็›ฎ้Œ„", + "fill-modes": { + "crop": "่ฃๅˆ‡ (ๅกซๆปฟ)", + "fit": "่ชฟๆ•ด (ๆ”ถๅฎน)", + "repeat": "้‡่ค‡ (ๅนณ้‹ช)", + "stretch": "ๅปถๅฑ•" + }, + "no-match": "ๆฒ’ๆœ‰็›ธ้—œ็ตๆžœ", + "no-wallpaper": "ๆ‰พไธๅˆฐๆกŒๅธƒ", + "panel": { + "apikey-help": "้œ€่ฆ API key ไปฅๅญ˜ๅ–ๅทฅไฝœไธๅฎœ (NSFW) ็š„ๅ…งๅฎน", + "apikey-label": "API key", + "apikey-managed-by-env": "ๅทฒ้€้Ž NOCTALIA_WALLHAVEN_API_KEY ้€™ๅ€‹็’ฐๅขƒ่ฎŠๆ•ธ็ฎก็†", + "apikey-placeholder": "่ผธๅ…ฅไฝ ็š„ Wallhaven API Key", + "appearance-dark-tab": "ๆทฑ่‰ฒ", + "appearance-light-tab": "ๆทบ่‰ฒ", + "categories-anime": "ๅ‹•ๆผซ", + "categories-label": "ๅˆ†้กž", + "categories-people": "ไบบ็‰ฉ", + "color-extraction-disabled": "ไฝฟ็”จๆกŒๅธƒ้ก่‰ฒๆ“ทๅ–", + "color-extraction-enabled": "ไฝฟ็”จ้ ่จญ้…่‰ฒๆ–นๆกˆ", + "header-devices-apply-all-tooltip": "็‚บๆฏๅ€‹่žขๅน•้ธๆ“‡ๆกŒๅธƒ", + "header-devices-per-monitor-tooltip": "ๅฅ—็”จๅˆฐๆ‰€ๆœ‰้กฏ็คบๅ™จ", + "header-link-light-dark-tooltip": "ๅˆ‡ๆ›ไธป้กŒๆ™‚ไฟ็•™ๆกŒๅธƒ", + "header-separate-light-dark-tooltip": "้šจไธป้กŒ่‡ชๅ‹•ๅˆ‡ๆ›ๆกŒๅธƒ", + "header-sun-linked-tooltip": "ๅˆ†้›ขๆทบ่‰ฒๅ’Œๆทฑ่‰ฒๆกŒๅธƒ", + "header-sun-separate-tooltip": "ๆ˜Žๆš—ๆจกๅผไฝฟ็”จ็›ธๅŒๆกŒๅธƒ", + "order-asc": "ๅ‡ๅบ", + "order-desc": "้™ๅบ", + "order-label": "ๆŽ’ๅบ", + "purity-label": "ๅ…งๅฎน้Žๆฟพ", + "purity-nsfw": "ๅทฅไฝœไธๅฎœ (NSFW)", + "purity-sfw": "ๅฎ‰ๅ…จ้กŒๆ", + "purity-sketchy": "ๆ“ฆ้‚Š้กŒๆ", + "ratios-all-portrait": "ๅ…จ้ƒจ็›ดๅผ", + "ratios-all-wide": "ๆ‰€ๆœ‰ๅฏฌ่žขๅน•", + "ratios-any": "ไปปไฝ•", + "ratios-label": "้•ทๅฏฌๆฏ”", + "resolution-atleast": "่‡ณๅฐ‘ๆœ‰", + "resolution-exact": "็ฌฆๅˆ", + "resolution-label": "่งฃๆžๅบฆ", + "resolution-mode-label": "้ธ้ …", + "solid-color-tooltip": "ๅฏฆ่‰ฒ่ƒŒๆ™ฏ", + "sort-date-asc": "ๆŒ‰ๆœ€ๆ—ฉๆŽ’ๅบ", + "sort-date-desc": "ๆŒ‰ๆœ€ๆ–ฐๆŽ’ๅบ", + "sort-name-asc": "ๆŒ‰ๅ็จฑๆŽ’ๅบ (A-Z)", + "sort-name-desc": "ๆŒ‰ๅ็จฑๆŽ’ๅบ (Z-A)", + "sort-random": "้šจๆฉŸๆŽ’ๅบ", + "sorting-date-added": "ๆ–ฐๅขžๆ—ฅๆœŸ", + "sorting-favorites": "ๆœ€ๆ„›", + "sorting-label": "ๆŒ‰... ๆŽ’ๅบ", + "sorting-relevance": "็›ธ้—œๅบฆ", + "sorting-toplist": "Toplist", + "sorting-views": "่ง€็œ‹ๆ•ธ", + "source-label": "ไพ†ๆบ", + "source-wallhaven": "Wallhaven", + "title": "ๆŒ‘้ธๆกŒๅธƒ", + "wallhaven-settings-title": "Wallhaven ่จญๅฎš" + }, + "transitions": { + "disc": "ๅœ“็›ค", + "fade": "ๆทกๅ…ฅ", + "honeycomb": "่œ‚ๅทข", + "pixelate": "ๅƒ็ด ๅŒ–", + "stripes": "ๆข็ด‹", + "wipe": "ๆ“ฆ้™ค" + }, + "try-different-search": "่ฉฆ่‘—็”จไธๅŒ็š„ๆœๅฐ‹ๅญ—ไธฒ", + "wallhaven": { + "loading": "ๆญฃๅœจ่ผ‰ๅ…ฅๆกŒๅธƒ...", + "no-results": "ๆ‰พไธๅˆฐๆกŒๅธƒ, ่ฉฆ่‘—็”จไธๅŒ็š„ๆœๅฐ‹ๅญ—ไธฒ", + "page": "็ฌฌ {current} / {total} ้ ", + "page-prefix": "้ ้ข", + "page-suffix": "ๅ…ฑ {total} ๅ€‹" + } + }, + "weather": { + "clear-sky": "ๆ™ดๆœ—", + "drizzle": "ๆฏ›ๆฏ›้›จ", + "fog": "่ตท้œง", + "mainly-clear": "ๆ™ดๆ™‚ๅคš้›ฒ", + "overcast": "้™ฐๅคฉ", + "partly-cloudy": "ๅฑ€้ƒจๅคš้›ฒ", + "rain-showers": "้™ฃ้›จ", + "snow": "ไธ‹้›ช", + "thunderstorm": "้–ƒ้›ปๆšด้›จ" + }, + "widgets": { + "color-picker": { + "palette-description": "ๅพžๅ„็จฎ้ ๅ…ˆ่จญๅฎš็š„้ก่‰ฒไธญๆŒ‘้ธ", + "palette-label": "่ชฟ่‰ฒ็›ค", + "palette-theme-colors": "ๅฟซ้€Ÿๅญ˜ๅ–ไฝ ็š„ไธป้กŒๆ‰€ไฝฟ็”จ็š„้…่‰ฒ", + "title": "ๆŒ‘้ธ้ก่‰ฒ" + }, + "datetime-tokens": { + "ampm-lowercase": "ๅฐๅฏซ็š„ am/pm", + "ampm-uppercase": "ๅคงๅฏซ็š„ AM/PM", + "common-12hour-time-minutes": "ๅธถๅˆ†้˜็š„12ๅฐๆ™‚ๅˆถๆ™‚้–“", + "common-24hour-time-minutes": "ๅธถๅˆ†้˜็š„24ๅฐๆ™‚ๅˆถๆ™‚้–“", + "common-24hour-time-seconds": "ๅธถ็ง’้˜็š„24ๅฐๆ™‚ๅˆถๆ™‚้–“", + "common-european-date": "ๆญๆดฒๆ ผๅผๆ—ฅๆœŸ", + "common-iso-date": "ISO ๆ ผๅผๆ—ฅๆœŸ", + "common-us-date": "็พŽๅœ‹ๆ ผๅผๆ—ฅๆœŸ", + "common-weekday-date": "ๅธถๆ˜ŸๆœŸ็š„ๆ—ฅๆœŸ", + "common-weekday-month-day": "ๆ˜ŸๆœŸ, ๆœˆไปฝๅŠๆ—ฅ", + "day-abbreviated": "็ธฎๅฏซ็š„ๆœˆไปฝ", + "day-full": "ๅฎŒๆ•ด็š„ๆ˜ŸๆœŸ", + "day-leading-zero": "ๅธถ้›ถ็š„ๆ—ฅ (01-31)", + "day-no-leading-zero": "ไธๅธถ้›ถ็š„ๆ—ฅ (01-31)", + "hour-leading-zero": "ๅธถ้›ถ็š„ๅฐๆ™‚ (00-23) โ€” 24ๅฐๆ™‚ๅˆถ", + "hour-no-leading-zero": "ไธๅธถ้›ถ็š„ๅฐๆ™‚ (0-23) โ€” 24ๅฐๆ™‚ๅˆถ", + "minute-leading-zero": "ๅธถ้›ถ็š„ๅˆ†้˜ (00-59)", + "minute-no-leading-zero": "ไธๅธถ้›ถ็š„ๅˆ†้˜ (00-59)", + "month-abbreviated": "็ธฎๅฏซ็š„ๆœˆไปฝ", + "month-full": "ๅฎŒๆ•ด็š„ๆœˆไปฝ", + "month-number-leading-zero": "ไปฅๅธถ้›ถๆ•ธๅญ—่กจ่จ˜็š„ๆœˆไปฝ (01-12)", + "month-number-no-zero": "ไปฅไธๅธถ้›ถๆ•ธๅญ—่กจ่จ˜็š„ๆœˆไปฝ (1-12)", + "second-leading-zero": "ๅธถ้›ถ็š„็ง’้˜ (00-59)", + "second-no-leading-zero": "ไธๅธถ้›ถ็š„็ง’้˜ (0-59)", + "timezone-abbreviation": "ๆ™‚ๅ€็ธฎๅฏซ", + "year-four-digit": "ๅ››ไฝๆ•ธ็š„่ฅฟๆ›†ๅนดไปฝ", + "year-two-digit": "ไบŒไฝๆ•ธ็š„่ฅฟๆ›†ๅนดไปฝ (00-99)" + }, + "file-picker": { + "item": "้ …็›ฎ", + "items": "้ …็›ฎ", + "search-placeholder": "้ธๆ“‡ๆช”ๆกˆๅŠๆ–‡ไปถๅคพ...", + "select-current": "้ธๆ“‡ๆญค้ …", + "select-file": "้ธๆ“‡ๆช”ๆกˆ", + "select-folder": "้ธๆ“‡ๆ–‡ไปถๅคพ", + "selected": "ๅทฒ้ธๅ–:", + "title": "ๆช”ๆกˆ้ธๅ–ๅ™จ" + }, + "file-picker-title": "้ธๆ“‡ไธ€ๅ€‹ๆช”ๆกˆ", + "icon-picker": { + "title": "ๅœ–็คบ้ธๅ–ๅ™จ" + }, + "tooltip-placeholder": "ๆšซ็”จๆ–‡ๅญ—" + }, + "wifi": { + "enterprise": { + "anonymous-identity": "ๅŒฟๅ่บซไปฝ", + "ca-cert": "CA ๆ†‘่ญ‰", + "eap-method": "EAP ๆ–นๆณ•", + "password": "ไฝฟ็”จ่€…ๅฏ†็ขผ", + "phase2-auth": "็ฌฌไบŒ้šŽๆฎต่ช่ญ‰", + "username": "่บซๅˆ† / ไฝฟ็”จ่€…ๅ็จฑ" + }, + "panel": { + "action-required": "้œ€่ฆ้กๅค–ๆ“ไฝœ", + "add-hidden-network": "ๆ–ฐๅขž้šฑ่—็ถฒ่ทฏ", + "add-network": "ๆ–ฐๅขž็ถฒ่ทฏ", + "available-interfaces": "ๅฏ็”จ็š„ไป‹้ข", + "available-networks": "ๅฏ็”จ็š„็ถฒ่ทฏ", + "disabled": "Wi-Fi ๅทฒ้—œ้–‰", + "disconnecting": "ๆญฃๅœจๆ–ท็ทš...", + "dns": "DNS", + "enable-message": "ๅ•Ÿ็”จ Wi-Fi ไพ†ๆŸฅ็œ‹ๅฏ็”จ็ถฒ่ทฏ", + "enter-password": "่ผธๅ…ฅ Wi-Fi ๅฏ†็ขผ...", + "forget": "ๅฟ˜่จ˜", + "forget-network": "ๅฟ˜่จ˜้€™ๅ€‹็ถฒ่ทฏ่จญๅฎš", + "forgetting": "ๆญฃๅœจๅฟ˜่จ˜...", + "hidden-network": "้šฑ่—็ถฒ่ทฏ", + "interface": "็ถฒ่ทฏไป‹้ข", + "internet-connected": "ๅทฒ้€ฃๆŽฅ็ถฒ้š›็ถฒ่ทฏ", + "internet-limited": "ๆฒ’ๆœ‰็ถฒ้š›็ถฒ่ทฏ", + "internet-status": "ไธŠ็ทš็‹€ๆ…‹", + "ipv4": "IPv4", + "ipv6": "IPv6", + "known-networks": "ๅทฒ็Ÿฅ็š„็ถฒ่ทฏ", + "link-speed": "้€ฃ็ต้€Ÿๅบฆ", + "network-name-ssid": "็ถฒ่ทฏๅ็จฑ (SSID)", + "no-ethernet-devices": "ๆฒ’ๆœ‰ๅตๆธฌๅˆฐไปปไฝ•ไน™ๅคช็ถฒ่ทฏ่ฃ็ฝฎ", + "no-networks": "ๆ‰พไธๅˆฐ Wi-Fi ็ถฒ่ทฏ", + "saved": "ๅทฒๅ„ฒๅญ˜", + "scan-again": "ๅ†ๆฌกๆŽƒๆ", + "searching": "ๆญฃๅœจๆœๅฐ‹็ถฒ่ทฏ...", + "security-open": "้–‹ๅ•Ÿ", + "security-wep": "WEP", + "security-wpa": "WPA/WPA2/WPA3 ๅ€‹ไบบ", + "security-wpa-ent": "WPA ไผๆฅญ็‰ˆ", + "security-wpa2-ent": "WPA2 ไผๆฅญ็‰ˆ", + "security-wpa23": "WPA2/WPA3", + "security-wpa3": "WPA3", + "security-wpa3-ent": "WPA3 ไผๆฅญ็‰ˆ", + "show-password": "้กฏ็คบๅฏ†็ขผ", + "title": "Wiโ€‘Fi" + }, + "signal": { + "excellent": "ๅ„ช็ง€", + "fair": "่‰ฏๅฅฝ", + "good": "ๅฐšๅฏ", + "poor": "ๅทฎ", + "weak": "ๅผฑ" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/Wallpaper/noctalia.png b/arch/.config/quickshell/noctalia-shell/Assets/Wallpaper/noctalia.png new file mode 100644 index 0000000..809881b Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Assets/Wallpaper/noctalia.png differ diff --git a/arch/.config/quickshell/noctalia-shell/Assets/noctalia.svg b/arch/.config/quickshell/noctalia-shell/Assets/noctalia.svg new file mode 100644 index 0000000..1064622 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/noctalia.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/arch/.config/quickshell/noctalia-shell/Assets/settings-default.json b/arch/.config/quickshell/noctalia-shell/Assets/settings-default.json new file mode 100644 index 0000000..262d6a7 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/settings-default.json @@ -0,0 +1,563 @@ +{ + "settingsVersion": 0, + "bar": { + "barType": "simple", + "position": "top", + "monitors": [], + "density": "default", + "showOutline": false, + "showCapsule": true, + "capsuleOpacity": 1, + "capsuleColorKey": "none", + "widgetSpacing": 6, + "contentPadding": 2, + "fontScale": 1, + "enableExclusionZoneInset": true, + "backgroundOpacity": 0.93, + "useSeparateOpacity": false, + "marginVertical": 4, + "marginHorizontal": 4, + "frameThickness": 8, + "frameRadius": 12, + "outerCorners": true, + "hideOnOverview": false, + "displayMode": "always_visible", + "autoHideDelay": 500, + "autoShowDelay": 150, + "showOnWorkspaceSwitch": true, + "widgets": { + "left": [ + { + "id": "Launcher" + }, + { + "id": "Clock" + }, + { + "id": "SystemMonitor" + }, + { + "id": "ActiveWindow" + }, + { + "id": "MediaMini" + } + ], + "center": [ + { + "id": "Workspace" + } + ], + "right": [ + { + "id": "Tray" + }, + { + "id": "NotificationHistory" + }, + { + "id": "Battery" + }, + { + "id": "Volume" + }, + { + "id": "Brightness" + }, + { + "id": "ControlCenter" + } + ] + }, + "mouseWheelAction": "none", + "reverseScroll": false, + "mouseWheelWrap": true, + "middleClickAction": "none", + "middleClickFollowMouse": false, + "middleClickCommand": "", + "rightClickAction": "controlCenter", + "rightClickFollowMouse": true, + "rightClickCommand": "", + "screenOverrides": [] + }, + "general": { + "avatarImage": "", + "dimmerOpacity": 0.2, + "showScreenCorners": false, + "forceBlackScreenCorners": false, + "scaleRatio": 1, + "radiusRatio": 1, + "iRadiusRatio": 1, + "boxRadiusRatio": 1, + "screenRadiusRatio": 1, + "animationSpeed": 1, + "animationDisabled": false, + "compactLockScreen": false, + "lockScreenAnimations": false, + "lockOnSuspend": true, + "showSessionButtonsOnLockScreen": true, + "showHibernateOnLockScreen": false, + "enableLockScreenMediaControls": false, + "enableShadows": true, + "enableBlurBehind": true, + "shadowDirection": "bottom_right", + "shadowOffsetX": 2, + "shadowOffsetY": 3, + "language": "", + "allowPanelsOnScreenWithoutBar": true, + "showChangelogOnStartup": true, + "telemetryEnabled": false, + "enableLockScreenCountdown": true, + "lockScreenCountdownDuration": 10000, + "autoStartAuth": false, + "allowPasswordWithFprintd": false, + "clockStyle": "custom", + "clockFormat": "hh\\nmm", + "passwordChars": false, + "lockScreenMonitors": [], + "lockScreenBlur": 0, + "lockScreenTint": 0, + "keybinds": { + "keyUp": [ + "Up" + ], + "keyDown": [ + "Down" + ], + "keyLeft": [ + "Left" + ], + "keyRight": [ + "Right" + ], + "keyEnter": [ + "Return", + "Enter" + ], + "keyEscape": [ + "Esc" + ], + "keyRemove": [ + "Del" + ] + }, + "reverseScroll": false, + "smoothScrollEnabled": true + }, + "ui": { + "fontDefault": "", + "fontFixed": "", + "fontDefaultScale": 1, + "fontFixedScale": 1, + "tooltipsEnabled": true, + "scrollbarAlwaysVisible": true, + "boxBorderEnabled": false, + "panelBackgroundOpacity": 0.93, + "translucentWidgets": false, + "panelsAttachedToBar": true, + "settingsPanelMode": "attached", + "settingsPanelSideBarCardStyle": false + }, + "location": { + "name": "", + "weatherEnabled": true, + "weatherShowEffects": true, + "weatherTaliaMascotAlways": false, + "useFahrenheit": false, + "use12hourFormat": false, + "showWeekNumberInCalendar": false, + "showCalendarEvents": true, + "showCalendarWeather": true, + "analogClockInCalendar": false, + "firstDayOfWeek": -1, + "hideWeatherTimezone": false, + "hideWeatherCityName": false, + "autoLocate": true + }, + "calendar": { + "cards": [ + { + "enabled": true, + "id": "calendar-header-card" + }, + { + "enabled": true, + "id": "calendar-month-card" + }, + { + "enabled": true, + "id": "weather-card" + } + ] + }, + "wallpaper": { + "enabled": true, + "overviewEnabled": false, + "directory": "", + "monitorDirectories": [], + "enableMultiMonitorDirectories": false, + "showHiddenFiles": false, + "viewMode": "single", + "setWallpaperOnAllMonitors": true, + "linkLightAndDarkWallpapers": true, + "fillMode": "crop", + "fillColor": "#000000", + "useSolidColor": false, + "solidColor": "#1a1a2e", + "automationEnabled": false, + "wallpaperChangeMode": "random", + "randomIntervalSec": 300, + "transitionDuration": 1500, + "transitionType": [ + "fade", + "disc", + "stripes", + "wipe", + "pixelate", + "honeycomb" + ], + "skipStartupTransition": false, + "transitionEdgeSmoothness": 0.05, + "panelPosition": "follow_bar", + "hideWallpaperFilenames": false, + "useOriginalImages": false, + "overviewBlur": 0.4, + "overviewTint": 0.6, + "useWallhaven": false, + "wallhavenQuery": "", + "wallhavenSorting": "relevance", + "wallhavenOrder": "desc", + "wallhavenCategories": "111", + "wallhavenPurity": "100", + "wallhavenRatios": "", + "wallhavenApiKey": "", + "wallhavenResolutionMode": "atleast", + "wallhavenResolutionWidth": "", + "wallhavenResolutionHeight": "", + "sortOrder": "name", + "favorites": [] + }, + "appLauncher": { + "enableClipboardHistory": false, + "autoPasteClipboard": false, + "enableClipPreview": true, + "clipboardWrapText": true, + "enableClipboardSmartIcons": true, + "enableClipboardChips": true, + "clipboardWatchTextCommand": "wl-paste --type text --watch cliphist store", + "clipboardWatchImageCommand": "wl-paste --type image --watch cliphist store", + "position": "center", + "pinnedApps": [], + "sortByMostUsed": true, + "terminalCommand": "alacritty -e", + "customLaunchPrefixEnabled": false, + "customLaunchPrefix": "", + "viewMode": "list", + "showCategories": true, + "iconMode": "tabler", + "showIconBackground": false, + "enableSettingsSearch": true, + "enableWindowsSearch": true, + "enableSessionSearch": true, + "ignoreMouseInput": false, + "screenshotAnnotationTool": "", + "overviewLayer": false, + "density": "default" + }, + "controlCenter": { + "position": "close_to_bar_button", + "diskPath": "/", + "shortcuts": { + "left": [ + { + "id": "Network" + }, + { + "id": "Bluetooth" + }, + { + "id": "WallpaperSelector" + }, + { + "id": "NoctaliaPerformance" + } + ], + "right": [ + { + "id": "Notifications" + }, + { + "id": "PowerProfile" + }, + { + "id": "KeepAwake" + }, + { + "id": "NightLight" + } + ] + }, + "cards": [ + { + "enabled": true, + "id": "profile-card" + }, + { + "enabled": true, + "id": "shortcuts-card" + }, + { + "enabled": true, + "id": "audio-card" + }, + { + "enabled": false, + "id": "brightness-card" + }, + { + "enabled": true, + "id": "weather-card" + }, + { + "enabled": true, + "id": "media-sysmon-card" + } + ] + }, + "systemMonitor": { + "cpuWarningThreshold": 80, + "cpuCriticalThreshold": 90, + "tempWarningThreshold": 80, + "tempCriticalThreshold": 90, + "gpuWarningThreshold": 80, + "gpuCriticalThreshold": 90, + "memWarningThreshold": 80, + "memCriticalThreshold": 90, + "swapWarningThreshold": 80, + "swapCriticalThreshold": 90, + "diskWarningThreshold": 80, + "diskCriticalThreshold": 90, + "diskAvailWarningThreshold": 20, + "diskAvailCriticalThreshold": 10, + "batteryWarningThreshold": 20, + "batteryCriticalThreshold": 5, + "enableDgpuMonitoring": false, + "useCustomColors": false, + "warningColor": "", + "criticalColor": "", + "externalMonitor": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor" + }, + "noctaliaPerformance": { + "disableWallpaper": true, + "disableDesktopWidgets": true + }, + "dock": { + "enabled": true, + "position": "bottom", + "displayMode": "auto_hide", + "dockType": "floating", + "backgroundOpacity": 1, + "floatingRatio": 1, + "size": 1, + "onlySameOutput": true, + "monitors": [], + "pinnedApps": [], + "colorizeIcons": false, + "showLauncherIcon": false, + "launcherPosition": "end", + "launcherUseDistroLogo": false, + "launcherIcon": "", + "launcherIconColor": "none", + "pinnedStatic": false, + "inactiveIndicators": false, + "groupApps": false, + "groupContextMenuMode": "extended", + "groupClickAction": "cycle", + "groupIndicatorStyle": "dots", + "deadOpacity": 0.6, + "animationSpeed": 1, + "sitOnFrame": false, + "showDockIndicator": false, + "indicatorThickness": 3, + "indicatorColor": "primary", + "indicatorOpacity": 0.6 + }, + "network": { + "bluetoothRssiPollingEnabled": false, + "bluetoothRssiPollIntervalMs": 60000, + "networkPanelView": "wifi", + "wifiDetailsViewMode": "grid", + "bluetoothDetailsViewMode": "grid", + "bluetoothHideUnnamedDevices": false, + "disableDiscoverability": false, + "bluetoothAutoConnect": true + }, + "sessionMenu": { + "enableCountdown": true, + "countdownDuration": 10000, + "position": "center", + "showHeader": true, + "showKeybinds": true, + "largeButtonsStyle": true, + "largeButtonsLayout": "single-row", + "powerOptions": [ + { + "action": "lock", + "enabled": true, + "keybind": "1" + }, + { + "action": "suspend", + "enabled": true, + "keybind": "2" + }, + { + "action": "hibernate", + "enabled": true, + "keybind": "3" + }, + { + "action": "reboot", + "enabled": true, + "keybind": "4" + }, + { + "action": "logout", + "enabled": true, + "keybind": "5" + }, + { + "action": "shutdown", + "enabled": true, + "keybind": "6" + }, + { + "action": "rebootToUefi", + "enabled": true, + "keybind": "7" + } + ] + }, + "notifications": { + "enabled": true, + "enableMarkdown": false, + "density": "default", + "monitors": [], + "location": "top_right", + "overlayLayer": true, + "backgroundOpacity": 1, + "respectExpireTimeout": false, + "lowUrgencyDuration": 3, + "normalUrgencyDuration": 8, + "criticalUrgencyDuration": 15, + "clearDismissed": true, + "saveToHistory": { + "low": true, + "normal": true, + "critical": true + }, + "sounds": { + "enabled": false, + "volume": 0.5, + "separateSounds": false, + "criticalSoundFile": "", + "normalSoundFile": "", + "lowSoundFile": "", + "excludedApps": "discord,firefox,chrome,chromium,edge" + }, + "enableMediaToast": false, + "enableKeyboardLayoutToast": true, + "enableBatteryToast": true + }, + "osd": { + "enabled": true, + "location": "top_right", + "autoHideMs": 2000, + "overlayLayer": true, + "backgroundOpacity": 1, + "enabledTypes": [ + 0, + 1, + 2 + ], + "monitors": [] + }, + "audio": { + "volumeStep": 5, + "volumeOverdrive": false, + "spectrumFrameRate": 30, + "visualizerType": "linear", + "spectrumMirrored": true, + "mprisBlacklist": [], + "preferredPlayer": "", + "volumeFeedback": false, + "volumeFeedbackSoundFile": "" + }, + "brightness": { + "brightnessStep": 5, + "enforceMinimum": true, + "enableDdcSupport": false, + "backlightDeviceMappings": [] + }, + "colorSchemes": { + "useWallpaperColors": false, + "predefinedScheme": "Noctalia (default)", + "darkMode": true, + "schedulingMode": "off", + "manualSunrise": "06:30", + "manualSunset": "18:30", + "generationMethod": "tonal-spot", + "monitorForColors": "", + "syncGsettings": true + }, + "templates": { + "activeTemplates": [], + "enableUserTheming": false + }, + "nightLight": { + "enabled": false, + "forced": false, + "autoSchedule": true, + "nightTemp": "4000", + "dayTemp": "6500", + "manualSunrise": "06:30", + "manualSunset": "18:30" + }, + "hooks": { + "enabled": false, + "wallpaperChange": "", + "darkModeChange": "", + "screenLock": "", + "screenUnlock": "", + "performanceModeEnabled": "", + "performanceModeDisabled": "", + "startup": "", + "session": "", + "colorGeneration": "" + }, + "plugins": { + "autoUpdate": false, + "notifyUpdates": true + }, + "idle": { + "enabled": false, + "screenOffTimeout": 600, + "lockTimeout": 660, + "suspendTimeout": 1800, + "fadeDuration": 5, + "screenOffCommand": "", + "lockCommand": "", + "suspendCommand": "", + "resumeScreenOffCommand": "", + "resumeLockCommand": "", + "resumeSuspendCommand": "", + "customCommands": "[]" + }, + "desktopWidgets": { + "enabled": false, + "overviewEnabled": true, + "gridSnap": false, + "gridSnapScale": false, + "monitorWidgets": [] + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Assets/settings-search-index.json b/arch/.config/quickshell/noctalia-shell/Assets/settings-search-index.json new file mode 100644 index 0000000..7a05103 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/settings-search-index.json @@ -0,0 +1,2728 @@ +[ + { + "labelKey": "panels.about.changelog-on-startup", + "descriptionKey": "panels.about.changelog-on-startup-desc", + "widget": "NToggle", + "tab": 21, + "tabLabel": "panels.about.title", + "subTab": 0, + "subTabLabel": "common.info" + }, + { + "labelKey": "panels.about.telemetry-enabled", + "descriptionKey": "panels.about.telemetry-desc", + "widget": "NToggle", + "tab": 21, + "tabLabel": "panels.about.title", + "subTab": 0, + "subTabLabel": "common.info" + }, + { + "labelKey": "panels.audio.devices-output-device-label", + "descriptionKey": "panels.audio.devices-output-device-description", + "widget": "NLabel", + "tab": 14, + "tabLabel": "panels.audio.title", + "subTab": 1, + "subTabLabel": "common.devices" + }, + { + "labelKey": "panels.audio.devices-input-device-label", + "descriptionKey": "panels.audio.devices-input-device-description", + "widget": "NLabel", + "tab": 14, + "tabLabel": "panels.audio.title", + "subTab": 1, + "subTabLabel": "common.devices" + }, + { + "labelKey": "panels.audio.media-primary-player-label", + "descriptionKey": "panels.audio.media-primary-player-description", + "widget": "NTextInput", + "tab": 14, + "tabLabel": "panels.audio.title", + "subTab": 2, + "subTabLabel": "common.media" + }, + { + "labelKey": "panels.audio.media-excluded-player-label", + "descriptionKey": "panels.audio.media-excluded-player-description", + "widget": "NTextInputButton", + "tab": 14, + "tabLabel": "panels.audio.title", + "subTab": 2, + "subTabLabel": "common.media" + }, + { + "labelKey": "panels.audio.visualizer-type-label", + "descriptionKey": "panels.audio.visualizer-type-description", + "widget": "NComboBox", + "tab": 14, + "tabLabel": "panels.audio.title", + "subTab": 3, + "subTabLabel": "common.visualizer" + }, + { + "labelKey": "panels.audio.spectrum-mirrored-label", + "descriptionKey": "panels.audio.spectrum-mirrored-description", + "widget": "NToggle", + "tab": 14, + "tabLabel": "panels.audio.title", + "subTab": 3, + "subTabLabel": "common.visualizer" + }, + { + "labelKey": "panels.audio.media-frame-rate-label", + "descriptionKey": "panels.audio.media-frame-rate-description", + "widget": "NComboBox", + "tab": 14, + "tabLabel": "panels.audio.title", + "subTab": 3, + "subTabLabel": "common.visualizer" + }, + { + "labelKey": "panels.osd.types-volume-label", + "descriptionKey": "panels.audio.volumes-output-volume-description", + "widget": "NValueSlider", + "tab": 14, + "tabLabel": "panels.audio.title", + "subTab": 0, + "subTabLabel": "common.volumes" + }, + { + "labelKey": "panels.audio.volumes-mute-output-label", + "descriptionKey": "panels.audio.volumes-mute-output-description", + "widget": "NToggle", + "tab": 14, + "tabLabel": "panels.audio.title", + "subTab": 0, + "subTabLabel": "common.volumes" + }, + { + "labelKey": "panels.audio.volumes-volume-feedback-label", + "descriptionKey": "panels.audio.volumes-volume-feedback-description", + "widget": "NToggle", + "tab": 14, + "tabLabel": "panels.audio.title", + "subTab": 0, + "subTabLabel": "common.volumes" + }, + { + "labelKey": "panels.audio.volumes-feedback-sound-file-label", + "descriptionKey": "panels.audio.volumes-feedback-sound-file-description", + "widget": "NLabel", + "tab": 14, + "tabLabel": "panels.audio.title", + "subTab": 0, + "subTabLabel": "common.volumes" + }, + { + "labelKey": "panels.osd.types-input-volume-label", + "descriptionKey": "panels.audio.volumes-input-volume-description", + "widget": "NValueSlider", + "tab": 14, + "tabLabel": "panels.audio.title", + "subTab": 0, + "subTabLabel": "common.volumes" + }, + { + "labelKey": "panels.audio.volumes-mute-input-label", + "descriptionKey": "panels.audio.volumes-mute-input-description", + "widget": "NToggle", + "tab": 14, + "tabLabel": "panels.audio.title", + "subTab": 0, + "subTabLabel": "common.volumes" + }, + { + "labelKey": "panels.audio.volumes-step-size-label", + "descriptionKey": "panels.audio.volumes-step-size-description", + "widget": "NSpinBox", + "tab": 14, + "tabLabel": "panels.audio.title", + "subTab": 0, + "subTabLabel": "common.volumes" + }, + { + "labelKey": "panels.audio.volumes-volume-overdrive-label", + "descriptionKey": "panels.audio.volumes-volume-overdrive-description", + "widget": "NToggle", + "tab": 14, + "tabLabel": "panels.audio.title", + "subTab": 0, + "subTabLabel": "common.volumes" + }, + { + "labelKey": "panels.bar.appearance-position-label", + "descriptionKey": "panels.bar.appearance-position-description", + "widget": "NComboBox", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.bar.appearance-density-label", + "descriptionKey": "panels.bar.appearance-density-description", + "widget": "NComboBox", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.bar.appearance-type-label", + "descriptionKey": "panels.bar.appearance-type-description", + "widget": "NComboBox", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "common.display-mode", + "descriptionKey": "panels.bar.appearance-display-mode-description", + "widget": "NComboBox", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.bar.appearance-use-separate-opacity-label", + "descriptionKey": "panels.bar.appearance-use-separate-opacity-description", + "widget": "NToggle", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.bar.appearance-background-opacity-label", + "descriptionKey": "panels.bar.appearance-background-opacity-description", + "widget": "NValueSlider", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.bar.useSeparateOpacity" + ] + }, + { + "labelKey": "panels.bar.appearance-font-scale-label", + "descriptionKey": "panels.bar.appearance-font-scale-description", + "widget": "NValueSlider", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.bar.appearance-widget-spacing-label", + "descriptionKey": "panels.bar.appearance-widget-spacing-description", + "widget": "NValueSlider", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.bar.appearance-content-padding-label", + "descriptionKey": "panels.bar.appearance-content-padding-description", + "widget": "NValueSlider", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.bar.appearance-show-outline-label", + "descriptionKey": "panels.bar.appearance-show-outline-description", + "widget": "NToggle", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.bar.appearance-show-capsule-label", + "descriptionKey": "panels.bar.appearance-show-capsule-description", + "widget": "NToggle", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.bar.appearance-capsule-color-label", + "descriptionKey": "panels.bar.appearance-capsule-color-description", + "widget": "NColorChoice", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.bar.showCapsule" + ] + }, + { + "labelKey": "panels.bar.appearance-capsule-opacity-label", + "descriptionKey": "panels.bar.appearance-capsule-opacity-description", + "widget": "NValueSlider", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.bar.showCapsule" + ] + }, + { + "labelKey": "panels.bar.appearance-enable-exclusion-zone-inset-label", + "descriptionKey": "panels.bar.appearance-enable-exclusion-zone-inset-description", + "widget": "NToggle", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.bar.appearance-hide-on-overview-label", + "descriptionKey": "panels.bar.appearance-hide-on-overview-description", + "widget": "NToggle", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "CompositorService.isNiri" + ] + }, + { + "labelKey": "panels.bar.appearance-outer-corners-label", + "descriptionKey": "panels.bar.appearance-outer-corners-description", + "widget": "NToggle", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.bar.barType === \"simple\"" + ] + }, + { + "labelKey": "panels.bar.appearance-frame-settings-label", + "descriptionKey": "panels.bar.appearance-frame-settings-description", + "widget": "NLabel", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.bar.barType === \"framed\"" + ] + }, + { + "labelKey": "panels.bar.appearance-frame-thickness", + "descriptionKey": null, + "widget": "NValueSlider", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.bar.barType === \"framed\"" + ] + }, + { + "labelKey": "panels.bar.appearance-frame-radius", + "descriptionKey": null, + "widget": "NValueSlider", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.bar.barType === \"framed\"" + ] + }, + { + "labelKey": "panels.bar.appearance-margins-vertical", + "descriptionKey": "panels.bar.appearance-margins-description", + "widget": "NSpinBox", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.bar.barType === \"floating\"" + ] + }, + { + "labelKey": "panels.bar.appearance-margins-horizontal", + "descriptionKey": "panels.bar.appearance-margins-description", + "widget": "NSpinBox", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.bar.barType === \"floating\"" + ] + }, + { + "labelKey": "panels.bar.appearance-auto-hide-delay-label", + "descriptionKey": "panels.bar.appearance-auto-hide-delay-description", + "widget": "NValueSlider", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.bar.displayMode === \"auto_hide\"" + ] + }, + { + "labelKey": "panels.bar.appearance-auto-show-delay-label", + "descriptionKey": "panels.bar.appearance-auto-show-delay-description", + "widget": "NValueSlider", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.bar.displayMode === \"auto_hide\"" + ] + }, + { + "labelKey": "panels.bar.appearance-show-on-workspace-switch-label", + "descriptionKey": "panels.bar.appearance-show-on-workspace-switch-description", + "widget": "NToggle", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.bar.displayMode === \"auto_hide\"" + ] + }, + { + "labelKey": "panels.bar.behavior-workspace-scroll-label", + "descriptionKey": "panels.bar.behavior-workspace-scroll-description", + "widget": "NComboBox", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 2, + "subTabLabel": "common.behavior" + }, + { + "labelKey": "panels.general.reverse-scrolling-label", + "descriptionKey": "panels.general.reverse-scrolling-description", + "widget": "NToggle", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 2, + "subTabLabel": "common.behavior", + "visibleWhen": [ + "Settings.data.bar.mouseWheelAction !== \"none\"" + ] + }, + { + "labelKey": "panels.bar.behavior-wheel-wrap-label", + "descriptionKey": "panels.bar.behavior-wheel-wrap-description", + "widget": "NToggle", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 2, + "subTabLabel": "common.behavior", + "visibleWhen": [ + "Settings.data.bar.mouseWheelAction === \"workspace\"" + ] + }, + { + "labelKey": "panels.bar.behavior-middle-click-label", + "descriptionKey": "panels.bar.behavior-middle-click-description", + "widget": "NComboBox", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 2, + "subTabLabel": "common.behavior" + }, + { + "labelKey": "panels.bar.behavior-middle-click-command-label", + "descriptionKey": "panels.bar.behavior-middle-click-command-description", + "widget": "NTextInput", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 2, + "subTabLabel": "common.behavior", + "visibleWhen": [ + "Settings.data.bar.middleClickAction === \"command\"" + ] + }, + { + "labelKey": "panels.bar.behavior-middle-click-follow-mouse-label", + "descriptionKey": "panels.bar.behavior-middle-click-follow-mouse-description", + "widget": "NToggle", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 2, + "subTabLabel": "common.behavior", + "visibleWhen": [ + "Settings.data.bar.middleClickAction !== \"none\" && Settings.data.bar.middleClickAction !== \"command\" && !(Settings.data.bar.middleClickAction === \"settings\" && Settings.data.ui.settingsPanelMode === \"window\")" + ] + }, + { + "labelKey": "panels.bar.behavior-right-click-label", + "descriptionKey": "panels.bar.behavior-right-click-description", + "widget": "NComboBox", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 2, + "subTabLabel": "common.behavior" + }, + { + "labelKey": "panels.bar.behavior-right-click-command-label", + "descriptionKey": "panels.bar.behavior-right-click-command-description", + "widget": "NTextInput", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 2, + "subTabLabel": "common.behavior", + "visibleWhen": [ + "Settings.data.bar.rightClickAction === \"command\"" + ] + }, + { + "labelKey": "panels.bar.behavior-right-click-follow-mouse-label", + "descriptionKey": "panels.bar.behavior-right-click-follow-mouse-description", + "widget": "NToggle", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 2, + "subTabLabel": "common.behavior", + "visibleWhen": [ + "Settings.data.bar.rightClickAction !== \"none\" && Settings.data.bar.rightClickAction !== \"command\" && !(Settings.data.bar.rightClickAction === \"settings\" && Settings.data.ui.settingsPanelMode === \"window\")" + ] + }, + { + "labelKey": "panels.bar.monitor-override-settings", + "descriptionKey": "panels.bar.monitor-override-settings-description", + "widget": "NToggle", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 3, + "subTabLabel": "common.monitors" + }, + { + "labelKey": "tooltips.switch-to-dark-mode", + "descriptionKey": "panels.color-scheme.dark-mode-switch-description", + "widget": "NToggle", + "tab": 2, + "tabLabel": "panels.color-scheme.title", + "subTab": 0, + "subTabLabel": "common.colors" + }, + { + "labelKey": "panels.color-scheme.sync-gsettings-label", + "descriptionKey": "panels.color-scheme.sync-gsettings-description", + "widget": "NToggle", + "tab": 2, + "tabLabel": "panels.color-scheme.title", + "subTab": 0, + "subTabLabel": "common.colors" + }, + { + "labelKey": "panels.color-scheme.dark-mode-mode-label", + "descriptionKey": "panels.color-scheme.dark-mode-mode-description", + "widget": "NComboBox", + "tab": 2, + "tabLabel": "panels.color-scheme.title", + "subTab": 0, + "subTabLabel": "common.colors" + }, + { + "labelKey": "panels.display.night-light-manual-schedule-label", + "descriptionKey": "panels.display.night-light-manual-schedule-description", + "widget": "NLabel", + "tab": 2, + "tabLabel": "panels.color-scheme.title", + "subTab": 0, + "subTabLabel": "common.colors", + "visibleWhen": [ + "Settings.data.colorSchemes.schedulingMode === \"manual\"" + ] + }, + { + "labelKey": "panels.color-scheme.color-source-use-wallpaper-colors-label", + "descriptionKey": "panels.color-scheme.color-source-use-wallpaper-colors-description", + "widget": "NToggle", + "tab": 2, + "tabLabel": "panels.color-scheme.title", + "subTab": 0, + "subTabLabel": "common.colors" + }, + { + "labelKey": "panels.color-scheme.wallpaper-monitor-source-label", + "descriptionKey": "panels.color-scheme.wallpaper-monitor-source-description", + "widget": "NComboBox", + "tab": 2, + "tabLabel": "panels.color-scheme.title", + "subTab": 0, + "subTabLabel": "common.colors" + }, + { + "labelKey": "panels.color-scheme.wallpaper-method-label", + "descriptionKey": "panels.color-scheme.wallpaper-method-description", + "widget": "NComboBox", + "tab": 2, + "tabLabel": "panels.color-scheme.title", + "subTab": 0, + "subTabLabel": "common.colors" + }, + { + "labelKey": "panels.color-scheme.templates-misc-user-templates-label", + "descriptionKey": "panels.color-scheme.templates-misc-user-templates-description", + "widget": "NCheckbox", + "tab": 2, + "tabLabel": "panels.color-scheme.title", + "subTab": 1, + "subTabLabel": "common.templates" + }, + { + "labelKey": "common.bluetooth", + "descriptionKey": null, + "widget": "NToggle", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 1, + "subTabLabel": "common.bluetooth" + }, + { + "labelKey": "bluetooth.panel.connected-devices", + "descriptionKey": null, + "widget": "NLabel", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 1, + "subTabLabel": "common.bluetooth" + }, + { + "labelKey": "bluetooth.panel.paired-devices", + "descriptionKey": null, + "widget": "NLabel", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 1, + "subTabLabel": "common.bluetooth" + }, + { + "labelKey": "bluetooth.panel.available-devices", + "descriptionKey": null, + "widget": "NLabel", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 1, + "subTabLabel": "common.bluetooth" + }, + { + "labelKey": "panels.connections.bluetooth-auto-connect-label", + "descriptionKey": "panels.connections.bluetooth-auto-connect-description", + "widget": "NToggle", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 1, + "subTabLabel": "common.bluetooth" + }, + { + "labelKey": "panels.connections.hide-unnamed-devices-label", + "descriptionKey": "panels.connections.hide-unnamed-devices-description", + "widget": "NToggle", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 1, + "subTabLabel": "common.bluetooth" + }, + { + "labelKey": "panels.connections.disable-discoverability-label", + "descriptionKey": "panels.connections.disable-discoverability-description", + "widget": "NToggle", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 1, + "subTabLabel": "common.bluetooth" + }, + { + "labelKey": "panels.connections.bluetooth-rssi-polling-label", + "descriptionKey": "panels.connections.bluetooth-rssi-polling-description", + "widget": "NToggle", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 1, + "subTabLabel": "common.bluetooth" + }, + { + "labelKey": "panels.connections.bluetooth-rssi-polling-interval-label", + "descriptionKey": "panels.connections.bluetooth-rssi-polling-interval-description", + "widget": "NSpinBox", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 1, + "subTabLabel": "common.bluetooth", + "visibleWhen": [ + "Settings.data.network.bluetoothRssiPollingEnabled" + ] + }, + { + "labelKey": "common.auto-connect", + "descriptionKey": null, + "widget": "NCheckbox", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 1, + "subTabLabel": "common.bluetooth", + "visibleWhen": [ + "Settings.data.network.bluetoothAutoConnect" + ] + }, + { + "labelKey": "common.wifi", + "descriptionKey": null, + "widget": "NToggle", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 0, + "subTabLabel": "common.wifi" + }, + { + "labelKey": "common.connected", + "descriptionKey": null, + "widget": "NLabel", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 0, + "subTabLabel": "common.wifi" + }, + { + "labelKey": "wifi.panel.known-networks", + "descriptionKey": null, + "widget": "NLabel", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 0, + "subTabLabel": "common.wifi" + }, + { + "labelKey": "wifi.panel.available-networks", + "descriptionKey": null, + "widget": "NLabel", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 0, + "subTabLabel": "common.wifi" + }, + { + "labelKey": "toast.airplane-mode.title", + "descriptionKey": "toast.airplane-mode.description", + "widget": "NToggle", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 0, + "subTabLabel": "common.wifi" + }, + { + "labelKey": "wifi.panel.network-name-ssid", + "descriptionKey": null, + "widget": "NTextInput", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 0, + "subTabLabel": "common.wifi", + "visibleWhen": [ + "false" + ] + }, + { + "labelKey": "wifi.enterprise.eap-method", + "descriptionKey": null, + "widget": "NComboBox", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 0, + "subTabLabel": "common.wifi", + "visibleWhen": [ + "false" + ] + }, + { + "labelKey": "wifi.enterprise.phase2-auth", + "descriptionKey": null, + "widget": "NComboBox", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 0, + "subTabLabel": "common.wifi", + "visibleWhen": [ + "false" + ] + }, + { + "labelKey": "wifi.enterprise.anonymous-identity", + "descriptionKey": null, + "widget": "NTextInput", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 0, + "subTabLabel": "common.wifi", + "visibleWhen": [ + "false" + ] + }, + { + "labelKey": "wifi.enterprise.ca-cert", + "descriptionKey": null, + "widget": "NTextInput", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 0, + "subTabLabel": "common.wifi", + "visibleWhen": [ + "false" + ] + }, + { + "labelKey": "wifi.enterprise.username", + "descriptionKey": null, + "widget": "NTextInput", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 0, + "subTabLabel": "common.wifi", + "visibleWhen": [ + "false" + ] + }, + { + "labelKey": "common.password", + "descriptionKey": null, + "widget": "NTextInput", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 0, + "subTabLabel": "common.wifi", + "visibleWhen": [ + "false" + ] + }, + { + "labelKey": "wifi.panel.show-password", + "descriptionKey": null, + "widget": "NCheckbox", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 0, + "subTabLabel": "common.wifi", + "visibleWhen": [ + "false" + ] + }, + { + "labelKey": "wifi.panel.hidden-network", + "descriptionKey": null, + "widget": "NCheckbox", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 0, + "subTabLabel": "common.wifi", + "visibleWhen": [ + "false" + ] + }, + { + "labelKey": "common.position", + "descriptionKey": "panels.control-center.position-description", + "widget": "NComboBox", + "tab": 7, + "tabLabel": "panels.control-center.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.control-center.system-monitor-disk-path-label", + "descriptionKey": "panels.control-center.system-monitor-disk-path-description", + "widget": "NComboBox", + "tab": 7, + "tabLabel": "panels.control-center.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.desktop-widgets.enabled-label", + "descriptionKey": "panels.desktop-widgets.enabled-description", + "widget": "NToggle", + "tab": 6, + "tabLabel": "panels.desktop-widgets.title", + "subTab": null + }, + { + "labelKey": "panels.desktop-widgets.overview-enabled-label", + "descriptionKey": "panels.desktop-widgets.overview-enabled-description", + "widget": "NToggle", + "tab": 6, + "tabLabel": "panels.desktop-widgets.title", + "subTab": null + }, + { + "labelKey": "panels.display.monitors-backlight-device-label", + "descriptionKey": "panels.display.monitors-backlight-device-description", + "widget": "NComboBox", + "tab": 15, + "tabLabel": "panels.display.title", + "subTab": 0, + "subTabLabel": "common.brightness" + }, + { + "labelKey": "panels.display.monitors-brightness-step-label", + "descriptionKey": "panels.display.monitors-brightness-step-description", + "widget": "NSpinBox", + "tab": 15, + "tabLabel": "panels.display.title", + "subTab": 0, + "subTabLabel": "common.brightness" + }, + { + "labelKey": "panels.display.monitors-enforce-minimum-label", + "descriptionKey": "panels.display.monitors-enforce-minimum-description", + "widget": "NToggle", + "tab": 15, + "tabLabel": "panels.display.title", + "subTab": 0, + "subTabLabel": "common.brightness" + }, + { + "labelKey": "panels.display.monitors-external-brightness-label", + "descriptionKey": "panels.display.monitors-external-brightness-description", + "widget": "NToggle", + "tab": 15, + "tabLabel": "panels.display.title", + "subTab": 0, + "subTabLabel": "common.brightness" + }, + { + "labelKey": "panels.display.night-light-enable-label", + "descriptionKey": "panels.display.night-light-enable-description", + "widget": "NToggle", + "tab": 15, + "tabLabel": "panels.display.title", + "subTab": 1, + "subTabLabel": "common.night-light" + }, + { + "labelKey": "panels.display.night-light-temperature-night", + "descriptionKey": "panels.display.night-light-temperature-night-description", + "widget": "NLabel", + "tab": 15, + "tabLabel": "panels.display.title", + "subTab": 1, + "subTabLabel": "common.night-light" + }, + { + "labelKey": "panels.display.night-light-temperature-day", + "descriptionKey": "panels.display.night-light-temperature-day-description", + "widget": "NLabel", + "tab": 15, + "tabLabel": "panels.display.title", + "subTab": 1, + "subTabLabel": "common.night-light" + }, + { + "labelKey": "panels.display.night-light-auto-schedule-label", + "descriptionKey": "panels.display.night-light-auto-schedule-description", + "widget": "NToggle", + "tab": 15, + "tabLabel": "panels.display.title", + "subTab": 1, + "subTabLabel": "common.night-light" + }, + { + "labelKey": "panels.display.night-light-force-activation-label", + "descriptionKey": "panels.display.night-light-force-activation-description", + "widget": "NToggle", + "tab": 15, + "tabLabel": "panels.display.title", + "subTab": 1, + "subTabLabel": "common.night-light" + }, + { + "labelKey": "panels.dock.enabled-label", + "descriptionKey": "panels.dock.enabled-description", + "widget": "NToggle", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.dock.appearance-position-label", + "descriptionKey": "panels.dock.appearance-position-description", + "widget": "NComboBox", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.dock.appearance-type-label", + "descriptionKey": "panels.dock.appearance-type-description", + "widget": "NComboBox", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.display.title", + "descriptionKey": "panels.dock.appearance-display-description", + "widget": "NComboBox", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.dock.dockType === \"floating\"" + ] + }, + { + "labelKey": "panels.dock.appearance-sit-on-frame-label", + "descriptionKey": "panels.dock.appearance-sit-on-frame-description", + "widget": "NToggle", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.dock.dockType === \"attached\" && Settings.data.bar.barType === \"framed\"" + ] + }, + { + "labelKey": "panels.dock.appearance-dock-indicator-label", + "descriptionKey": "panels.dock.appearance-dock-indicator-description", + "widget": "NToggle", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.dock.appearance-indicator-thickness-label", + "descriptionKey": "panels.dock.appearance-indicator-thickness-description", + "widget": "NToggle", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.dock.showDockIndicator" + ] + }, + { + "labelKey": "panels.dock.appearance-indicator-color-label", + "descriptionKey": "panels.dock.appearance-indicator-color-description", + "widget": "NColorChoice", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.dock.showDockIndicator" + ] + }, + { + "labelKey": "panels.dock.appearance-indicator-opacity-label", + "descriptionKey": "panels.dock.appearance-indicator-opacity-description", + "widget": "NValueSlider", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.dock.showDockIndicator" + ] + }, + { + "labelKey": "panels.osd.background-opacity-label", + "descriptionKey": "panels.dock.appearance-background-opacity-description", + "widget": "NValueSlider", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.dock.appearance-dead-opacity-label", + "descriptionKey": "panels.dock.appearance-dead-opacity-description", + "widget": "NValueSlider", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.dock.appearance-floating-distance-label", + "descriptionKey": "panels.dock.appearance-floating-distance-description", + "widget": "NValueSlider", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.dock.dockType === \"floating\"" + ] + }, + { + "labelKey": "panels.dock.appearance-icon-size-label", + "descriptionKey": "panels.dock.appearance-icon-size-description", + "widget": "NValueSlider", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.dock.appearance-hide-show-speed-label", + "descriptionKey": "panels.dock.appearance-hide-show-speed-description", + "widget": "NValueSlider", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.dock.dockType === \"floating\" && Settings.data.dock.displayMode === \"auto_hide\"" + ] + }, + { + "labelKey": "panels.dock.appearance-inactive-indicators-label", + "descriptionKey": "panels.dock.appearance-inactive-indicators-description", + "widget": "NToggle", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.dock.appearance-pinned-static-label", + "descriptionKey": "panels.dock.appearance-pinned-static-description", + "widget": "NToggle", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.dock.appearance-group-apps-label", + "descriptionKey": "panels.dock.appearance-group-apps-description", + "widget": "NToggle", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.dock.appearance-group-click-action-label", + "descriptionKey": "panels.dock.appearance-group-click-action-description", + "widget": "NComboBox", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.dock.groupApps" + ] + }, + { + "labelKey": "panels.dock.appearance-group-context-menu-mode-label", + "descriptionKey": "panels.dock.appearance-group-context-menu-mode-description", + "widget": "NComboBox", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.dock.groupApps" + ] + }, + { + "labelKey": "panels.dock.appearance-group-indicator-style-label", + "descriptionKey": "panels.dock.appearance-group-indicator-style-description", + "widget": "NComboBox", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.dock.groupApps" + ] + }, + { + "labelKey": "panels.dock.monitors-only-same-monitor-label", + "descriptionKey": "panels.dock.monitors-only-same-monitor-description", + "widget": "NToggle", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.dock.appearance-colorize-icons-label", + "descriptionKey": "panels.dock.appearance-colorize-icons-description", + "widget": "NToggle", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.dock.appearance-show-launcher-icon-label", + "descriptionKey": "panels.dock.appearance-show-launcher-icon-description", + "widget": "NToggle", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.dock.appearance-launcher-position-label", + "descriptionKey": "panels.dock.appearance-launcher-position-description", + "widget": "NComboBox", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.dock.showLauncherIcon" + ] + }, + { + "labelKey": "panels.dock.appearance-launcher-use-distro-logo-label", + "descriptionKey": "panels.dock.appearance-launcher-use-distro-logo-description", + "widget": "NToggle", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.dock.showLauncherIcon" + ] + }, + { + "labelKey": "panels.dock.appearance-launcher-icon-label", + "descriptionKey": "panels.dock.appearance-launcher-icon-description", + "widget": "NLabel", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.dock.showLauncherIcon" + ] + }, + { + "labelKey": "common.select-icon-color", + "descriptionKey": null, + "widget": "NColorChoice", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.dock.showLauncherIcon" + ] + }, + { + "labelKey": "panels.general.profile-picture-label", + "descriptionKey": "panels.general.profile-picture-description", + "widget": "NTextInputButton", + "tab": 0, + "tabLabel": "common.general", + "subTab": null + }, + { + "labelKey": "panels.general.fonts-default-label", + "descriptionKey": "panels.general.fonts-default-description", + "widget": "NSearchableComboBox", + "tab": 0, + "tabLabel": "common.general", + "subTab": null + }, + { + "labelKey": "panels.general.fonts-monospace-label", + "descriptionKey": "panels.general.fonts-monospace-description", + "widget": "NSearchableComboBox", + "tab": 0, + "tabLabel": "common.general", + "subTab": null + }, + { + "labelKey": "panels.general.fonts-default-scale-label", + "descriptionKey": "panels.general.fonts-default-scale-description", + "widget": "NValueSlider", + "tab": 0, + "tabLabel": "common.general", + "subTab": null + }, + { + "labelKey": "panels.general.fonts-monospace-scale-label", + "descriptionKey": "panels.general.fonts-monospace-scale-description", + "widget": "NValueSlider", + "tab": 0, + "tabLabel": "common.general", + "subTab": null + }, + { + "labelKey": "panels.general.smooth-scrolling-label", + "descriptionKey": "panels.general.smooth-scrolling-description", + "widget": "NToggle", + "tab": 0, + "tabLabel": "common.general", + "subTab": null + }, + { + "labelKey": "panels.general.keybinds-title", + "descriptionKey": "panels.general.keybinds-description", + "widget": "NLabel", + "tab": 0, + "tabLabel": "common.general", + "subTab": null + }, + { + "labelKey": "panels.hooks.system-hooks-enable-label", + "descriptionKey": "panels.hooks.system-hooks-enable-description", + "widget": "NToggle", + "tab": 20, + "tabLabel": "panels.hooks.title", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.hooks.info-parameters-label", + "descriptionKey": "panels.hooks.info-parameters-description", + "widget": "NLabel", + "tab": 20, + "tabLabel": "panels.hooks.title", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.hooks.noctalia-started-label", + "descriptionKey": "panels.hooks.noctalia-started-description", + "widget": "HookRow", + "tab": 20, + "tabLabel": "panels.hooks.title", + "subTab": 1, + "subTabLabel": "panels.hooks.title" + }, + { + "labelKey": "panels.hooks.wallpaper-changed-label", + "descriptionKey": "panels.hooks.wallpaper-changed-description", + "widget": "HookRow", + "tab": 20, + "tabLabel": "panels.hooks.title", + "subTab": 1, + "subTabLabel": "panels.hooks.title" + }, + { + "labelKey": "panels.hooks.color-generation-label", + "descriptionKey": "panels.hooks.color-generation-description", + "widget": "HookRow", + "tab": 20, + "tabLabel": "panels.hooks.title", + "subTab": 1, + "subTabLabel": "panels.hooks.title" + }, + { + "labelKey": "panels.hooks.theme-changed-label", + "descriptionKey": "panels.hooks.theme-changed-description", + "widget": "HookRow", + "tab": 20, + "tabLabel": "panels.hooks.title", + "subTab": 1, + "subTabLabel": "panels.hooks.title" + }, + { + "labelKey": "panels.hooks.screen-lock-label", + "descriptionKey": "panels.hooks.screen-lock-description", + "widget": "HookRow", + "tab": 20, + "tabLabel": "panels.hooks.title", + "subTab": 1, + "subTabLabel": "panels.hooks.title" + }, + { + "labelKey": "panels.hooks.screen-unlock-label", + "descriptionKey": "panels.hooks.screen-unlock-description", + "widget": "HookRow", + "tab": 20, + "tabLabel": "panels.hooks.title", + "subTab": 1, + "subTabLabel": "panels.hooks.title" + }, + { + "labelKey": "panels.hooks.performance-mode-enabled-label", + "descriptionKey": "panels.hooks.performance-mode-enabled-description", + "widget": "HookRow", + "tab": 20, + "tabLabel": "panels.hooks.title", + "subTab": 1, + "subTabLabel": "panels.hooks.title" + }, + { + "labelKey": "panels.hooks.performance-mode-disabled-label", + "descriptionKey": "panels.hooks.performance-mode-disabled-description", + "widget": "HookRow", + "tab": 20, + "tabLabel": "panels.hooks.title", + "subTab": 1, + "subTabLabel": "panels.hooks.title" + }, + { + "labelKey": "panels.hooks.session-label", + "descriptionKey": "panels.hooks.session-description", + "widget": "HookRow", + "tab": 20, + "tabLabel": "panels.hooks.title", + "subTab": 1, + "subTabLabel": "panels.hooks.title" + }, + { + "labelKey": "panels.idle.enable-label", + "descriptionKey": "panels.idle.enable-description", + "widget": "NToggle", + "tab": 13, + "tabLabel": "panels.idle.title", + "subTab": 0, + "subTabLabel": "panels.idle.tab-behavior" + }, + { + "labelKey": "panels.idle.status-label", + "descriptionKey": "panels.idle.status-description", + "widget": "NLabel", + "tab": 13, + "tabLabel": "panels.idle.title", + "subTab": 0, + "subTabLabel": "panels.idle.tab-behavior", + "visibleWhen": [ + "IdleService.nativeIdleMonitorAvailable" + ] + }, + { + "labelKey": "panels.idle.timeouts-label", + "descriptionKey": "panels.idle.timeouts-description", + "widget": "NLabel", + "tab": 13, + "tabLabel": "panels.idle.title", + "subTab": 0, + "subTabLabel": "panels.idle.tab-behavior" + }, + { + "labelKey": "panels.idle.fade-duration-label", + "descriptionKey": "panels.idle.fade-duration-description", + "widget": "NSpinBox", + "tab": 13, + "tabLabel": "panels.idle.title", + "subTab": 0, + "subTabLabel": "panels.idle.tab-behavior" + }, + { + "labelKey": "panels.idle.custom-label", + "descriptionKey": "panels.idle.custom-description", + "widget": "NLabel", + "tab": 13, + "tabLabel": "panels.idle.title", + "subTab": 1, + "subTabLabel": "panels.idle.tab-custom" + }, + { + "labelKey": "panels.launcher.settings-clipboard-history-label", + "descriptionKey": "panels.launcher.settings-clipboard-history-description", + "widget": "NToggle", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 1, + "subTabLabel": "common.clipboard" + }, + { + "labelKey": "panels.launcher.settings-clip-preview-label", + "descriptionKey": "panels.launcher.settings-clip-preview-description", + "widget": "NToggle", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 1, + "subTabLabel": "common.clipboard" + }, + { + "labelKey": "panels.launcher.settings-clip-wrap-text-label", + "descriptionKey": "panels.launcher.settings-clip-wrap-text-description", + "widget": "NToggle", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 1, + "subTabLabel": "common.clipboard" + }, + { + "labelKey": "panels.launcher.settings-auto-paste-label", + "descriptionKey": "panels.launcher.settings-auto-paste-description", + "widget": "NToggle", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 1, + "subTabLabel": "common.clipboard" + }, + { + "labelKey": "panels.launcher.settings-clip-smart-icons-label", + "descriptionKey": "panels.launcher.settings-clip-smart-icons-description", + "widget": "NToggle", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 1, + "subTabLabel": "common.clipboard" + }, + { + "labelKey": "panels.launcher.settings-clip-chips-label", + "descriptionKey": "panels.launcher.settings-clip-chips-description", + "widget": "NToggle", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 1, + "subTabLabel": "common.clipboard" + }, + { + "labelKey": "panels.launcher.settings-clipboard-watch-text-label", + "descriptionKey": "panels.launcher.settings-clipboard-watch-text-description", + "widget": "NTextInput", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 1, + "subTabLabel": "common.clipboard", + "visibleWhen": [ + "Settings.data.appLauncher.enableClipboardHistory" + ] + }, + { + "labelKey": "panels.launcher.settings-clipboard-watch-image-label", + "descriptionKey": "panels.launcher.settings-clipboard-watch-image-description", + "widget": "NTextInput", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 1, + "subTabLabel": "common.clipboard", + "visibleWhen": [ + "Settings.data.appLauncher.enableClipboardHistory" + ] + }, + { + "labelKey": "panels.launcher.settings-terminal-command-label", + "descriptionKey": "panels.launcher.settings-terminal-command-description", + "widget": "NTextInput", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 2, + "subTabLabel": "common.execute" + }, + { + "labelKey": "panels.launcher.settings-custom-launch-prefix-enabled-label", + "descriptionKey": "panels.launcher.settings-custom-launch-prefix-enabled-description", + "widget": "NToggle", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 2, + "subTabLabel": "common.execute" + }, + { + "labelKey": "panels.launcher.settings-custom-launch-prefix-label", + "descriptionKey": "panels.launcher.settings-custom-launch-prefix-description", + "widget": "NTextInput", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 2, + "subTabLabel": "common.execute", + "visibleWhen": [ + "Settings.data.appLauncher.customLaunchPrefixEnabled" + ] + }, + { + "labelKey": "panels.launcher.settings-annotation-tool-label", + "descriptionKey": "panels.launcher.settings-annotation-tool-description", + "widget": "NTextInput", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 2, + "subTabLabel": "common.execute" + }, + { + "labelKey": "panels.launcher.settings-overlay-layer-label", + "descriptionKey": "panels.launcher.settings-overlay-layer-description", + "widget": "NToggle", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.launcher.settings-view-mode-label", + "descriptionKey": "panels.launcher.settings-view-mode-description", + "widget": "NComboBox", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.launcher.settings-density-label", + "descriptionKey": "panels.launcher.settings-density-description", + "widget": "NComboBox", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.launcher.settings-show-categories-label", + "descriptionKey": "panels.launcher.settings-show-categories-description", + "widget": "NToggle", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.launcher.settings-icon-mode-label", + "descriptionKey": "panels.launcher.settings-icon-mode-description", + "widget": "NToggle", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.launcher.settings-show-icon-background-label", + "descriptionKey": "panels.launcher.settings-show-icon-background-description", + "widget": "NToggle", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.launcher.settings-sort-by-usage-label", + "descriptionKey": "panels.launcher.settings-sort-by-usage-description", + "widget": "NToggle", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.launcher.settings-enable-settings-search-label", + "descriptionKey": "panels.launcher.settings-enable-settings-search-description", + "widget": "NToggle", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.launcher.settings-enable-windows-search-label", + "descriptionKey": "panels.launcher.settings-enable-windows-search-description", + "widget": "NToggle", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.launcher.settings-enable-session-search-label", + "descriptionKey": "panels.launcher.settings-enable-session-search-description", + "widget": "NToggle", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.launcher.settings-ignore-mouse-input-label", + "descriptionKey": "panels.launcher.settings-ignore-mouse-input-description", + "widget": "NToggle", + "tab": 8, + "tabLabel": "panels.launcher.title", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.lock-screen.clock-style-label", + "descriptionKey": "panels.lock-screen.clock-style-description", + "widget": "NComboBox", + "tab": 11, + "tabLabel": "panels.lock-screen.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.lock-screen.clock-format-label", + "descriptionKey": "panels.lock-screen.clock-format-description", + "widget": "NTextInput", + "tab": 11, + "tabLabel": "panels.lock-screen.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.general.clockStyle === \"custom\"" + ] + }, + { + "labelKey": "panels.lock-screen.password-chars-label", + "descriptionKey": "panels.lock-screen.password-chars-description", + "widget": "NToggle", + "tab": 11, + "tabLabel": "panels.lock-screen.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.lock-screen.compact-lockscreen-label", + "descriptionKey": "panels.lock-screen.compact-lockscreen-description", + "widget": "NToggle", + "tab": 11, + "tabLabel": "panels.lock-screen.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.lock-screen.enable-lockscreen-media-controls-label", + "descriptionKey": "panels.lock-screen.enable-lockscreen-media-controls-description", + "widget": "NToggle", + "tab": 11, + "tabLabel": "panels.lock-screen.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "!Settings.data.general.compactLockScreen" + ] + }, + { + "labelKey": "panels.lock-screen.lock-screen-animations-label", + "descriptionKey": "panels.lock-screen.lock-screen-animations-description", + "widget": "NToggle", + "tab": 11, + "tabLabel": "panels.lock-screen.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.lock-screen.lock-screen-blur-strength-label", + "descriptionKey": "panels.lock-screen.lock-screen-blur-strength-description", + "widget": "NValueSlider", + "tab": 11, + "tabLabel": "panels.lock-screen.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.lock-screen.lock-screen-tint-strength-label", + "descriptionKey": "panels.lock-screen.lock-screen-tint-strength-description", + "widget": "NValueSlider", + "tab": 11, + "tabLabel": "panels.lock-screen.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.lock-screen.lock-on-suspend-label", + "descriptionKey": "panels.lock-screen.lock-on-suspend-description", + "widget": "NToggle", + "tab": 11, + "tabLabel": "panels.lock-screen.title", + "subTab": 1, + "subTabLabel": "common.behavior" + }, + { + "labelKey": "panels.lock-screen.auto-start-auth-label", + "descriptionKey": "panels.lock-screen.auto-start-auth-description", + "widget": "NToggle", + "tab": 11, + "tabLabel": "panels.lock-screen.title", + "subTab": 1, + "subTabLabel": "common.behavior" + }, + { + "labelKey": "panels.lock-screen.allow-password-with-fprintd-label", + "descriptionKey": "panels.lock-screen.allow-password-with-fprintd-description", + "widget": "NToggle", + "tab": 11, + "tabLabel": "panels.lock-screen.title", + "subTab": 1, + "subTabLabel": "common.behavior" + }, + { + "labelKey": "panels.lock-screen.show-session-buttons-label", + "descriptionKey": "panels.lock-screen.show-session-buttons-description", + "widget": "NToggle", + "tab": 11, + "tabLabel": "panels.lock-screen.title", + "subTab": 1, + "subTabLabel": "common.behavior" + }, + { + "labelKey": "panels.lock-screen.show-hibernate-label", + "descriptionKey": "panels.lock-screen.show-hibernate-description", + "widget": "NToggle", + "tab": 11, + "tabLabel": "panels.lock-screen.title", + "subTab": 1, + "subTabLabel": "common.behavior", + "visibleWhen": [ + "Settings.data.general.showSessionButtonsOnLockScreen" + ] + }, + { + "labelKey": "panels.session-menu.enable-countdown-label", + "descriptionKey": "panels.session-menu.enable-countdown-description", + "widget": "NToggle", + "tab": 11, + "tabLabel": "panels.lock-screen.title", + "subTab": 1, + "subTabLabel": "common.behavior", + "visibleWhen": [ + "Settings.data.general.showSessionButtonsOnLockScreen" + ] + }, + { + "labelKey": "panels.session-menu.countdown-duration-label", + "descriptionKey": "panels.session-menu.countdown-duration-description", + "widget": "NValueSlider", + "tab": 11, + "tabLabel": "panels.lock-screen.title", + "subTab": 1, + "subTabLabel": "common.behavior", + "visibleWhen": [ + "Settings.data.general.showSessionButtonsOnLockScreen && Settings.data.general.enableLockScreenCountdown" + ] + }, + { + "labelKey": "panels.notifications.duration-respect-expire-label", + "descriptionKey": "panels.notifications.duration-respect-expire-description", + "widget": "NToggle", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 1, + "subTabLabel": "common.duration" + }, + { + "labelKey": "panels.notifications.duration-low-urgency-label", + "descriptionKey": "panels.notifications.duration-low-urgency-description", + "widget": "NValueSlider", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 1, + "subTabLabel": "common.duration" + }, + { + "labelKey": "panels.notifications.duration-normal-urgency-label", + "descriptionKey": "panels.notifications.duration-normal-urgency-description", + "widget": "NValueSlider", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 1, + "subTabLabel": "common.duration" + }, + { + "labelKey": "panels.notifications.duration-critical-urgency-label", + "descriptionKey": "panels.notifications.duration-critical-urgency-description", + "widget": "NValueSlider", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 1, + "subTabLabel": "common.duration" + }, + { + "labelKey": "panels.notifications.settings-enabled-label", + "descriptionKey": "panels.notifications.settings-enabled-description", + "widget": "NToggle", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.notifications.settings-density-label", + "descriptionKey": "panels.notifications.settings-density-description", + "widget": "NComboBox", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "tooltips.do-not-disturb-enabled", + "descriptionKey": "panels.notifications.settings-do-not-disturb-description", + "widget": "NToggle", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.osd.always-on-top-label", + "descriptionKey": "panels.notifications.settings-always-on-top-description", + "widget": "NToggle", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.notifications.history-clear-dismiss-label", + "descriptionKey": "panels.notifications.history-clear-dismiss-description", + "widget": "NToggle", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 2, + "subTabLabel": "common.history" + }, + { + "labelKey": "panels.notifications.settings-markdown-label", + "descriptionKey": "panels.notifications.settings-markdown-description", + "widget": "NToggle", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 2, + "subTabLabel": "common.history" + }, + { + "labelKey": "panels.notifications.history-low-urgency-label", + "descriptionKey": "panels.notifications.history-low-urgency-description", + "widget": "NToggle", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 2, + "subTabLabel": "common.history" + }, + { + "labelKey": "panels.notifications.history-normal-urgency-label", + "descriptionKey": "panels.notifications.history-normal-urgency-description", + "widget": "NToggle", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 2, + "subTabLabel": "common.history" + }, + { + "labelKey": "panels.notifications.history-critical-urgency-label", + "descriptionKey": "panels.notifications.history-critical-urgency-description", + "widget": "NToggle", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 2, + "subTabLabel": "common.history" + }, + { + "labelKey": "panels.notifications.rules-label", + "descriptionKey": "panels.notifications.rules-description", + "widget": "NLabel", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 5, + "subTabLabel": "panels.notifications.rules-tab" + }, + { + "labelKey": "panels.notifications.sounds-unavailable-label", + "descriptionKey": "panels.notifications.sounds-unavailable-description", + "widget": "NLabel", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 3, + "subTabLabel": "common.sound", + "visibleWhen": [ + "!SoundService.multimediaAvailable" + ] + }, + { + "labelKey": "panels.notifications.sounds-enabled-label", + "descriptionKey": "panels.notifications.sounds-enabled-description", + "widget": "NToggle", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 3, + "subTabLabel": "common.sound" + }, + { + "labelKey": "panels.notifications.sounds-volume-label", + "descriptionKey": "panels.notifications.sounds-volume-description", + "widget": "NValueSlider", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 3, + "subTabLabel": "common.sound" + }, + { + "labelKey": "panels.notifications.sounds-separate-label", + "descriptionKey": "panels.notifications.sounds-separate-description", + "widget": "NToggle", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 3, + "subTabLabel": "common.sound" + }, + { + "labelKey": "panels.notifications.sounds-files-unified-label", + "descriptionKey": "panels.notifications.sounds-files-unified-description", + "widget": "NLabel", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 3, + "subTabLabel": "common.sound", + "visibleWhen": [ + "!(Settings.data.notifications?.sounds?.separateSounds ?? false)" + ] + }, + { + "labelKey": "panels.notifications.sounds-files-low-label", + "descriptionKey": "panels.notifications.sounds-files-low-description", + "widget": "NLabel", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 3, + "subTabLabel": "common.sound", + "visibleWhen": [ + "SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false) && (Settings.data.notifications?.sounds?.separateSounds ?? false)" + ] + }, + { + "labelKey": "panels.notifications.sounds-files-normal-label", + "descriptionKey": "panels.notifications.sounds-files-normal-description", + "widget": "NLabel", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 3, + "subTabLabel": "common.sound", + "visibleWhen": [ + "SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false) && (Settings.data.notifications?.sounds?.separateSounds ?? false)" + ] + }, + { + "labelKey": "panels.notifications.sounds-files-critical-label", + "descriptionKey": "panels.notifications.sounds-files-critical-description", + "widget": "NLabel", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 3, + "subTabLabel": "common.sound", + "visibleWhen": [ + "SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false) && (Settings.data.notifications?.sounds?.separateSounds ?? false)" + ] + }, + { + "labelKey": "panels.notifications.sounds-excluded-apps-label", + "descriptionKey": "panels.notifications.sounds-excluded-apps-description", + "widget": "NLabel", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 3, + "subTabLabel": "common.sound" + }, + { + "labelKey": "panels.notifications.toast-media-label", + "descriptionKey": "panels.notifications.toast-media-description", + "widget": "NCheckbox", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 4, + "subTabLabel": "common.toast" + }, + { + "labelKey": "panels.notifications.toast-keyboard-label", + "descriptionKey": "panels.notifications.toast-keyboard-description", + "widget": "NCheckbox", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 4, + "subTabLabel": "common.toast" + }, + { + "labelKey": "panels.notifications.toast-battery-label", + "descriptionKey": "panels.notifications.toast-battery-description", + "widget": "NCheckbox", + "tab": 9, + "tabLabel": "common.notifications", + "subTab": 4, + "subTabLabel": "common.toast" + }, + { + "labelKey": "panels.osd.enabled-label", + "descriptionKey": "panels.osd.enabled-description", + "widget": "NToggle", + "tab": 10, + "tabLabel": "panels.osd.title", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.osd.duration-auto-hide-label", + "descriptionKey": "panels.osd.duration-auto-hide-description", + "widget": "NValueSlider", + "tab": 10, + "tabLabel": "panels.osd.title", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.plugins.available-no-plugins-label", + "descriptionKey": "panels.plugins.available-no-plugins-description", + "widget": "NLabel", + "tab": 19, + "tabLabel": "panels.plugins.title", + "subTab": 1, + "subTabLabel": "common.available" + }, + { + "labelKey": "panels.plugins.auto-update", + "descriptionKey": "panels.plugins.auto-update-description", + "widget": "NToggle", + "tab": 19, + "tabLabel": "panels.plugins.title", + "subTab": 0, + "subTabLabel": "common.installed" + }, + { + "labelKey": "panels.plugins.notify-updates", + "descriptionKey": "panels.plugins.notify-updates-description", + "widget": "NToggle", + "tab": 19, + "tabLabel": "panels.plugins.title", + "subTab": 0, + "subTabLabel": "common.installed" + }, + { + "labelKey": "panels.plugins.installed-no-plugins-label", + "descriptionKey": "panels.plugins.installed-no-plugins-description", + "widget": "NLabel", + "tab": 19, + "tabLabel": "panels.plugins.title", + "subTab": 0, + "subTabLabel": "common.installed" + }, + { + "labelKey": "panels.plugins.sources-add-dialog-name", + "descriptionKey": null, + "widget": "NTextInput", + "tab": 19, + "tabLabel": "panels.plugins.title", + "subTab": 2, + "subTabLabel": "common.sources" + }, + { + "labelKey": "panels.plugins.sources-add-dialog-url", + "descriptionKey": null, + "widget": "NTextInput", + "tab": 19, + "tabLabel": "panels.plugins.title", + "subTab": 2, + "subTabLabel": "common.sources" + }, + { + "labelKey": "panels.location.date-time-use-analog-label", + "descriptionKey": "panels.location.date-time-use-analog-description", + "widget": "NToggle", + "tab": 17, + "tabLabel": "panels.region.title", + "subTab": 2, + "subTabLabel": "common.calendar-panel" + }, + { + "labelKey": "panels.location.date-time-week-numbers-label", + "descriptionKey": "panels.location.date-time-week-numbers-description", + "widget": "NToggle", + "tab": 17, + "tabLabel": "panels.region.title", + "subTab": 2, + "subTabLabel": "common.calendar-panel" + }, + { + "labelKey": "panels.location.date-time-show-events-label", + "descriptionKey": "panels.location.date-time-show-events-description", + "widget": "NToggle", + "tab": 17, + "tabLabel": "panels.region.title", + "subTab": 2, + "subTabLabel": "common.calendar-panel" + }, + { + "labelKey": "panels.location.date-time-12hour-format-label", + "descriptionKey": "panels.location.date-time-12hour-format-description", + "widget": "NToggle", + "tab": 17, + "tabLabel": "panels.region.title", + "subTab": 1, + "subTabLabel": "common.date" + }, + { + "labelKey": "panels.location.date-time-first-day-of-week-label", + "descriptionKey": "panels.location.date-time-first-day-of-week-description", + "widget": "NComboBox", + "tab": 17, + "tabLabel": "panels.region.title", + "subTab": 1, + "subTabLabel": "common.date" + }, + { + "labelKey": "panels.general.language-select-label", + "descriptionKey": "panels.general.language-select-description", + "widget": "NComboBox", + "tab": 17, + "tabLabel": "panels.region.title", + "subTab": 0, + "subTabLabel": "common.location" + }, + { + "labelKey": "panels.location.auto-locate-label", + "descriptionKey": "panels.location.auto-locate-description", + "widget": "NToggle", + "tab": 17, + "tabLabel": "panels.region.title", + "subTab": 0, + "subTabLabel": "common.location" + }, + { + "labelKey": "panels.location.location-search-label", + "descriptionKey": "panels.location.location-search-description", + "widget": "NTextInput", + "tab": 17, + "tabLabel": "panels.region.title", + "subTab": 0, + "subTabLabel": "common.location", + "visibleWhen": [ + "!Settings.data.location.autoLocate" + ] + }, + { + "labelKey": "panels.location.weather-enabled-label", + "descriptionKey": "panels.location.weather-enabled-description", + "widget": "NToggle", + "tab": 17, + "tabLabel": "panels.region.title", + "subTab": 0, + "subTabLabel": "common.location" + }, + { + "labelKey": "panels.location.weather-fahrenheit-label", + "descriptionKey": "panels.location.weather-fahrenheit-description", + "widget": "NToggle", + "tab": 17, + "tabLabel": "panels.region.title", + "subTab": 0, + "subTabLabel": "common.location" + }, + { + "labelKey": "panels.location.weather-show-effects-label", + "descriptionKey": "panels.location.weather-show-effects-description", + "widget": "NToggle", + "tab": 17, + "tabLabel": "panels.region.title", + "subTab": 0, + "subTabLabel": "common.location" + }, + { + "labelKey": "panels.location.weather-talia-mascot-always-label", + "descriptionKey": "panels.location.weather-talia-mascot-always-description", + "widget": "NToggle", + "tab": 17, + "tabLabel": "panels.region.title", + "subTab": 0, + "subTabLabel": "common.location" + }, + { + "labelKey": "panels.location.weather-hide-city-label", + "descriptionKey": "panels.location.weather-hide-city-description", + "widget": "NToggle", + "tab": 17, + "tabLabel": "panels.region.title", + "subTab": 0, + "subTabLabel": "common.location" + }, + { + "labelKey": "panels.location.weather-hide-timezone-label", + "descriptionKey": "panels.location.weather-hide-timezone-description", + "widget": "NToggle", + "tab": 17, + "tabLabel": "panels.region.title", + "subTab": 0, + "subTabLabel": "common.location" + }, + { + "labelKey": "panels.session-menu.large-buttons-style-label", + "descriptionKey": "panels.session-menu.large-buttons-style-description", + "widget": "NToggle", + "tab": 12, + "tabLabel": "session-menu.title", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.session-menu.large-buttons-layout-label", + "descriptionKey": "panels.session-menu.large-buttons-layout-description", + "widget": "NComboBox", + "tab": 12, + "tabLabel": "session-menu.title", + "subTab": 0, + "subTabLabel": "common.general", + "visibleWhen": [ + "Settings.data.sessionMenu.largeButtonsStyle" + ] + }, + { + "labelKey": "panels.session-menu.show-header-label", + "descriptionKey": "panels.session-menu.show-header-description", + "widget": "NToggle", + "tab": 12, + "tabLabel": "session-menu.title", + "subTab": 0, + "subTabLabel": "common.general", + "visibleWhen": [ + "!Settings.data.sessionMenu.largeButtonsStyle" + ] + }, + { + "labelKey": "panels.session-menu.show-keybinds-label", + "descriptionKey": "panels.session-menu.show-keybinds-description", + "widget": "NToggle", + "tab": 12, + "tabLabel": "session-menu.title", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.system-monitor.enable-dgpu-monitoring-label", + "descriptionKey": "panels.system-monitor.enable-dgpu-monitoring-description", + "widget": "NToggle", + "tab": 18, + "tabLabel": "panels.system.title", + "subTab": 0, + "subTabLabel": "system-monitor.title" + }, + { + "labelKey": "panels.system-monitor.use-custom-highlight-colors-label", + "descriptionKey": "panels.system-monitor.use-custom-highlight-colors-description", + "widget": "NToggle", + "tab": 18, + "tabLabel": "panels.system.title", + "subTab": 0, + "subTabLabel": "system-monitor.title" + }, + { + "labelKey": "panels.system-monitor.external-monitor-label", + "descriptionKey": "panels.system-monitor.external-monitor-description", + "widget": "NTextInput", + "tab": 18, + "tabLabel": "panels.system.title", + "subTab": 0, + "subTabLabel": "system-monitor.title" + }, + { + "labelKey": "panels.system.noctalia-performance-disable-wallpaper-label", + "descriptionKey": "panels.system.noctalia-performance-disable-wallpaper-description", + "widget": "NToggle", + "tab": 18, + "tabLabel": "panels.system.title", + "subTab": 2, + "subTabLabel": "common.performance" + }, + { + "labelKey": "panels.system.noctalia-performance-disable-desktop-widgets-label", + "descriptionKey": "panels.system.noctalia-performance-disable-desktop-widgets-description", + "widget": "NToggle", + "tab": 18, + "tabLabel": "panels.system.title", + "subTab": 2, + "subTabLabel": "common.performance" + }, + { + "labelKey": "panels.user-interface.tooltips-label", + "descriptionKey": "panels.user-interface.tooltips-description", + "widget": "NToggle", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.user-interface.box-border-label", + "descriptionKey": "panels.user-interface.box-border-description", + "widget": "NToggle", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.user-interface.scrollbar-always-visible-label", + "descriptionKey": "panels.user-interface.scrollbar-always-visible-description", + "widget": "NToggle", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.user-interface.shadows-label", + "descriptionKey": "panels.user-interface.shadows-description", + "widget": "NToggle", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.user-interface.blur-behind-label", + "descriptionKey": "panels.user-interface.blur-behind-description", + "widget": "NToggle", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.user-interface.translucent-widgets-label", + "descriptionKey": "panels.user-interface.translucent-widgets-description", + "widget": "NToggle", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.user-interface.shadows-direction-label", + "descriptionKey": "panels.user-interface.shadows-direction-description", + "widget": "NComboBox", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "Settings.data.general.enableShadows" + ] + }, + { + "labelKey": "panels.user-interface.scaling-label", + "descriptionKey": "panels.user-interface.scaling-description", + "widget": "NValueSlider", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.user-interface.box-border-radius-label", + "descriptionKey": "panels.user-interface.box-border-radius-description", + "widget": "NValueSlider", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.user-interface.control-border-radius-label", + "descriptionKey": "panels.user-interface.control-border-radius-description", + "widget": "NValueSlider", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.user-interface.animation-disable-label", + "descriptionKey": "panels.user-interface.animation-disable-description", + "widget": "NToggle", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.user-interface.animation-speed-label", + "descriptionKey": "panels.user-interface.animation-speed-description", + "widget": "NValueSlider", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 0, + "subTabLabel": "common.appearance", + "visibleWhen": [ + "!Settings.data.general.animationDisabled" + ] + }, + { + "labelKey": "panels.user-interface.panels-attached-to-bar-label", + "descriptionKey": "panels.user-interface.panels-attached-to-bar-description", + "widget": "NToggle", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 1, + "subTabLabel": "common.panels" + }, + { + "labelKey": "panels.user-interface.allow-panels-without-bar-label", + "descriptionKey": "panels.user-interface.allow-panels-without-bar-description", + "widget": "NToggle", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 1, + "subTabLabel": "common.panels", + "visibleWhen": [ + "(Quickshell.screens.length > 1)" + ] + }, + { + "labelKey": "panels.user-interface.panel-background-opacity-label", + "descriptionKey": "panels.user-interface.panel-background-opacity-description", + "widget": "NValueSlider", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 1, + "subTabLabel": "common.panels" + }, + { + "labelKey": "panels.user-interface.dimmer-opacity-label", + "descriptionKey": "panels.user-interface.dimmer-opacity-description", + "widget": "NValueSlider", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 1, + "subTabLabel": "common.panels" + }, + { + "labelKey": "panels.user-interface.settings-panel-mode-label", + "descriptionKey": "panels.user-interface.settings-panel-mode-description", + "widget": "NComboBox", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 1, + "subTabLabel": "common.panels" + }, + { + "labelKey": "panels.user-interface.settings-panel-sidebar-card-style-label", + "descriptionKey": "panels.user-interface.settings-panel-sidebar-card-style-description", + "widget": "NToggle", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 1, + "subTabLabel": "common.panels" + }, + { + "labelKey": "panels.general.screen-corners-show-corners-label", + "descriptionKey": "panels.general.screen-corners-show-corners-description", + "widget": "NToggle", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 2, + "subTabLabel": "common.screen-corners" + }, + { + "labelKey": "panels.general.screen-corners-solid-black-label", + "descriptionKey": "panels.general.screen-corners-solid-black-description", + "widget": "NToggle", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 2, + "subTabLabel": "common.screen-corners" + }, + { + "labelKey": "panels.general.screen-corners-radius-label", + "descriptionKey": "panels.general.screen-corners-radius-description", + "widget": "NValueSlider", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 2, + "subTabLabel": "common.screen-corners" + }, + { + "labelKey": "panels.wallpaper.automation-scheduled-change-label", + "descriptionKey": "panels.wallpaper.automation-scheduled-change-description", + "widget": "NToggle", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 2, + "subTabLabel": "common.automation" + }, + { + "labelKey": "panels.wallpaper.automation-change-mode-label", + "descriptionKey": "panels.wallpaper.automation-change-mode-description", + "widget": "NComboBox", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 2, + "subTabLabel": "common.automation" + }, + { + "labelKey": "panels.wallpaper.automation-interval-label", + "descriptionKey": "panels.wallpaper.automation-interval-description", + "widget": "NSpinBox", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 2, + "subTabLabel": "common.automation" + }, + { + "labelKey": "panels.wallpaper.settings-enable-management-label", + "descriptionKey": "panels.wallpaper.settings-enable-management-description", + "widget": "NToggle", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "tooltips.wallpaper-selector", + "descriptionKey": "panels.wallpaper.settings-selector-description", + "widget": "NLabel", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.wallpaper.settings-view-mode-label", + "descriptionKey": "panels.wallpaper.settings-view-mode-description", + "widget": "NComboBox", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.wallpaper.settings-folder-label", + "descriptionKey": "panels.wallpaper.settings-folder-description", + "widget": "NTextInputButton", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.wallpaper.settings-monitor-specific-label", + "descriptionKey": "panels.wallpaper.settings-monitor-specific-description", + "widget": "NToggle", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.wallpaper.settings-use-original-images-label", + "descriptionKey": "panels.wallpaper.settings-use-original-images-description", + "widget": "NToggle", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.wallpaper.settings-clear-cache-label", + "descriptionKey": "panels.wallpaper.settings-clear-cache-description", + "widget": "NLabel", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 0, + "subTabLabel": "common.general" + }, + { + "labelKey": "panels.wallpaper.settings-enable-overview-label", + "descriptionKey": "panels.wallpaper.settings-enable-overview-description", + "widget": "NToggle", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 0, + "subTabLabel": "common.general", + "visibleWhen": [ + "CompositorService.isNiri" + ] + }, + { + "labelKey": "panels.wallpaper.settings-overview-blur-strength-label", + "descriptionKey": "panels.wallpaper.settings-overview-blur-strength-description", + "widget": "NValueSlider", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 0, + "subTabLabel": "common.general", + "visibleWhen": [ + "CompositorService.isNiri", + "Settings.data.wallpaper.overviewEnabled" + ] + }, + { + "labelKey": "panels.wallpaper.settings-overview-tint-label", + "descriptionKey": "panels.wallpaper.settings-overview-tint-description", + "widget": "NValueSlider", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 0, + "subTabLabel": "common.general", + "visibleWhen": [ + "CompositorService.isNiri", + "Settings.data.wallpaper.overviewEnabled" + ] + }, + { + "labelKey": "panels.wallpaper.look-feel-fill-mode-label", + "descriptionKey": "panels.wallpaper.look-feel-fill-mode-description", + "widget": "NComboBox", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 1, + "subTabLabel": "common.look" + }, + { + "labelKey": "bar.audio-visualizer.color-name-label", + "descriptionKey": "panels.wallpaper.look-feel-fill-color-description", + "widget": "NLabel", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 1, + "subTabLabel": "common.look" + }, + { + "labelKey": "panels.wallpaper.look-feel-transition-type-label", + "descriptionKey": "panels.wallpaper.look-feel-transition-type-description", + "widget": "NLabel", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 1, + "subTabLabel": "common.look" + }, + { + "labelKey": "panels.wallpaper.look-feel-skip-startup-transition-label", + "descriptionKey": "panels.wallpaper.look-feel-skip-startup-transition-description", + "widget": "NToggle", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 1, + "subTabLabel": "common.look" + }, + { + "labelKey": "panels.wallpaper.look-feel-transition-duration-label", + "descriptionKey": "panels.wallpaper.look-feel-transition-duration-description", + "widget": "NValueSlider", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 1, + "subTabLabel": "common.look" + }, + { + "labelKey": "panels.wallpaper.look-feel-edge-smoothness-label", + "descriptionKey": "panels.wallpaper.look-feel-edge-smoothness-description", + "widget": "NValueSlider", + "tab": 3, + "tabLabel": "common.wallpaper", + "subTab": 1, + "subTabLabel": "common.look" + } +] \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Assets/settings-widgets-default.json b/arch/.config/quickshell/noctalia-shell/Assets/settings-widgets-default.json new file mode 100644 index 0000000..df518b5 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Assets/settings-widgets-default.json @@ -0,0 +1,295 @@ +{ + "bar": { + "ActiveWindow": { + "showText": true, + "showIcon": true, + "hideMode": "hidden", + "scrollingMode": "hover", + "maxWidth": 145, + "useFixedWidth": false, + "colorizeIcons": false, + "textColor": "none" + }, + "AudioVisualizer": { + "width": 200, + "colorName": "primary", + "hideWhenIdle": false + }, + "Battery": { + "displayMode": "graphic-clean", + "deviceNativePath": "__default__", + "showPowerProfiles": false, + "showNoctaliaPerformance": false, + "hideIfNotDetected": true, + "hideIfIdle": false + }, + "Bluetooth": { + "displayMode": "onhover", + "iconColor": "none", + "textColor": "none" + }, + "Brightness": { + "displayMode": "onhover", + "iconColor": "none", + "textColor": "none", + "applyToAllMonitors": false + }, + "Clock": { + "clockColor": "none", + "useCustomFont": false, + "customFont": "", + "formatHorizontal": "HH:mm ddd, MMM dd", + "formatVertical": "HH mm - dd MM", + "tooltipFormat": "HH:mm ddd, MMM dd" + }, + "ControlCenter": { + "useDistroLogo": false, + "icon": "noctalia", + "customIconPath": "", + "colorizeDistroLogo": false, + "colorizeSystemIcon": "none", + "enableColorization": false + }, + "CustomButton": { + "icon": "heart", + "showIcon": true, + "showExecTooltip": true, + "showTextTooltip": true, + "generalTooltipText": "", + "hideMode": "alwaysExpanded", + "leftClickExec": "", + "leftClickUpdateText": false, + "rightClickExec": "", + "rightClickUpdateText": false, + "middleClickExec": "", + "middleClickUpdateText": false, + "textCommand": "", + "textStream": false, + "textIntervalMs": 3000, + "textCollapse": "", + "parseJson": false, + "wheelExec": "", + "wheelUpExec": "", + "wheelDownExec": "", + "wheelMode": "unified", + "wheelUpdateText": false, + "wheelUpUpdateText": false, + "wheelDownUpdateText": false, + "maxTextLength": { + "horizontal": 10, + "vertical": 10 + }, + "enableColorization": false, + "colorizeSystemIcon": "none", + "ipcIdentifier": "" + }, + "DarkMode": { + "iconColor": "none" + }, + "KeepAwake": { + "iconColor": "none", + "textColor": "none" + }, + "KeyboardLayout": { + "displayMode": "onhover", + "showIcon": true, + "iconColor": "none", + "textColor": "none" + }, + "LockKeys": { + "showCapsLock": true, + "showNumLock": true, + "showScrollLock": true, + "capsLockIcon": "letter-c", + "numLockIcon": "letter-n", + "scrollLockIcon": "letter-s", + "hideWhenOff": false + }, + "Launcher": { + "useDistroLogo": false, + "icon": "rocket", + "customIconPath": "", + "colorizeSystemIcon": "none", + "enableColorization": false, + "iconColor": "none" + }, + "MediaMini": { + "hideMode": "hidden", + "scrollingMode": "hover", + "maxWidth": 145, + "useFixedWidth": false, + "hideWhenIdle": false, + "showAlbumArt": true, + "showArtistFirst": true, + "showVisualizer": false, + "showProgressRing": true, + "visualizerType": "linear", + "textColor": "none", + "compactMode": false, + "panelShowAlbumArt": true + }, + "Microphone": { + "displayMode": "onhover", + "middleClickCommand": "pwvucontrol || pavucontrol", + "iconColor": "none", + "textColor": "none" + }, + "NotificationHistory": { + "showUnreadBadge": true, + "hideWhenZero": false, + "hideWhenZeroUnread": false, + "unreadBadgeColor": "primary", + "iconColor": "none" + }, + "SessionMenu": { + "iconColor": "error" + }, + "Settings": { + "iconColor": "none" + }, + "Spacer": { + "width": 20 + }, + "SystemMonitor": { + "compactMode": true, + "iconColor": "none", + "textColor": "none", + "useMonospaceFont": true, + "usePadding": false, + "showCpuUsage": true, + "showCpuCores": false, + "showCpuFreq": false, + "showCpuTemp": true, + "showGpuTemp": false, + "showLoadAverage": false, + "showMemoryUsage": true, + "showMemoryAsPercent": false, + "showSwapUsage": false, + "showNetworkStats": false, + "showDiskUsage": false, + "showDiskUsageAsPercent": false, + "showDiskAvailable": false, + "diskPath": "/" + }, + "Taskbar": { + "onlySameOutput": true, + "onlyActiveWorkspaces": true, + "hideMode": "hidden", + "colorizeIcons": false, + "showTitle": false, + "titleWidth": 120, + "showPinnedApps": true, + "smartWidth": true, + "maxTaskbarWidth": 40, + "iconScale": 0.8 + }, + "Tray": { + "blacklist": [], + "colorizeIcons": false, + "chevronColor": "none", + "pinned": [], + "drawerEnabled": true, + "hidePassive": false + }, + "VPN": { + "displayMode": "onhover", + "iconColor": "none", + "textColor": "none" + }, + "Network": { + "displayMode": "onhover", + "iconColor": "none", + "textColor": "none" + }, + "NightLight": { + "iconColor": "none" + }, + "NoctaliaPerformance": { + "iconColor": "none" + }, + "PowerProfile": { + "iconColor": "none" + }, + "Workspace": { + "labelMode": "index", + "followFocusedScreen": false, + "hideUnoccupied": false, + "characterCount": 2, + "showApplications": false, + "showApplicationsHover": false, + "showLabelsOnlyWhenOccupied": true, + "colorizeIcons": false, + "unfocusedIconsOpacity": 1, + "groupedBorderOpacity": 1, + "enableScrollWheel": true, + "iconScale": 0.8, + "focusedColor": "primary", + "occupiedColor": "secondary", + "emptyColor": "secondary", + "showBadge": true, + "pillSize": 0.6, + "fontWeight": "bold" + }, + "Volume": { + "displayMode": "onhover", + "middleClickCommand": "pwvucontrol || pavucontrol", + "iconColor": "none", + "textColor": "none" + }, + "WallpaperSelector": { + "iconColor": "none" + } + }, + "controlCenter": { + "CustomButton": { + "icon": "heart", + "onClicked": "", + "onRightClicked": "", + "onMiddleClicked": "", + "stateChecksJson": "[]", + "generalTooltipText": "", + "enableOnStateLogic": false, + "showExecTooltip": true + } + }, + "desktop": { + "Clock": { + "showBackground": true, + "roundedCorners": true, + "clockStyle": "digital", + "clockColor": "none", + "useCustomFont": false, + "customFont": "", + "format": "HH:mm\\nd MMMM yyyy" + }, + "MediaPlayer": { + "showBackground": true, + "roundedCorners": true, + "visualizerType": "linear", + "hideMode": "visible", + "showButtons": true, + "showAlbumArt": true, + "showVisualizer": true + }, + "Weather": { + "showBackground": true, + "roundedCorners": true + }, + "SystemStat": { + "showBackground": true, + "roundedCorners": true, + "statType": "CPU", + "diskPath": "/", + "layout": "bottom" + }, + "AudioVisualizer": { + "showBackground": true, + "roundedCorners": true, + "width": 320, + "height": 72, + "visualizerType": "linear", + "hideWhenIdle": false, + "colorName": "primary" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/CREDITS.md b/arch/.config/quickshell/noctalia-shell/CREDITS.md new file mode 100644 index 0000000..daaa0f2 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/CREDITS.md @@ -0,0 +1,48 @@ +# Credits + +Noctalia Shell is made possible by the incredible work of many open-source projects and contributors. + +## Design & Branding + +- **MrDowntempo** - Creator of the Noctalia Owl and moon logo +- **[SaberJ2X](https://www.reddit.com/user/SaberJ64/)** - Creator of Talia, the Noctalia mascot + +## Core Framework + +- **[Quickshell](https://github.com/outfoxxed/quickshell)** - The Qt/QML-based Wayland shell framework that powers Noctalia + +## Runtime Dependencies + +### System Integration +- **[brightnessctl](https://github.com/Hummer12007/brightnessctl)** - Screen brightness control +- **[wlsunset](https://sr.ht/~kennylevinsen/wlsunset/)** - Night light and blue light filter support +- **[wl-clipboard](https://github.com/bugaevc/wl-clipboard)** - Wayland clipboard utilities +- **[ddcutil](https://www.ddcutil.com/)** - External display brightness control +- **[power-profiles-daemon](https://gitlab.freedesktop.org/upower/power-profiles-daemon)** - Power profile management + +### Media & Audio +- **[gpu-screen-recorder](https://git.dec05eba.com/gpu-screen-recorder/about/)** - Hardware-accelerated screen recording +- **[Cava](https://github.com/karlstav/cava)** - Audio visualizer component + +### Utilities +- **[cliphist](https://github.com/sentriz/cliphist)** - Clipboard history support + +## Icons +- **[Tabler Icons](https://tabler.io/icons)** - Icon set used throughout the shell +- **[Riyan Resdian on Noun Project](https://thenounproject.com/creator/yaicon/)** - Plug icon + +## Audio Assets +- **[Universfield on Pixabay](https://pixabay.com/users/universfield-28281460/)** - Notification sound effect +- **[DrNI on Freesound](https://freesound.org/people/DrNI/sounds/34562/)** - Timer's alarm sound effect +- **[Lucas McCallister on Freesound](http://www.freesound.org/samplesViewSingle.php?id=67091)** - Volume change feedback sound effect + + +## Special Thanks +- The **Wayland** community for building the future of Linux desktop graphics +- The **Niri**, **Hyprland**, **Sway**, **Labwc**, and **MangoWC** teams for their excellent Wayland compositors +- All the contributors and users who have helped make Noctalia better + +## License +Noctalia Shell is licensed under the MIT License. See [LICENSE](LICENSE) for details. + +Each dependency listed above is governed by its own respective license. Please refer to their individual projects for licensing information. diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Color.qml b/arch/.config/quickshell/noctalia-shell/Commons/Color.qml new file mode 100644 index 0000000..6e940f5 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Color.qml @@ -0,0 +1,473 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons + +/* +Noctalia is not strictly a Material Design project, it supports both some predefined +color schemes and dynamic color generation from the wallpaper. + +We ultimately decided to use a restricted set of colors that follows the +Material Design 3 naming convention. + +NOTE: All color names are prefixed with 'm' (e.g., mPrimary) to prevent QML from +misinterpreting them as signals (e.g., the 'onPrimary' property name). +*/ +Singleton { + id: root + + property bool reloadColors: false + + // Suppress transition animations until the first colors.json load completes + property bool skipTransition: true + + // Flag indicating theme colors are currently transitioning (for widgets to disable their own animations) + property bool isTransitioning: false + + // Timer to reset isTransitioning after animation completes + Timer { + id: transitionTimer + interval: Style.animationSlowest + 50 // Small buffer after animation + onTriggered: root.isTransitioning = false + } + + // --- Key Colors: These are the main accent colors that define your app's style + property color mPrimary: defaultColors.mPrimary + property color mOnPrimary: defaultColors.mOnPrimary + property color mSecondary: defaultColors.mSecondary + property color mOnSecondary: defaultColors.mOnSecondary + property color mTertiary: defaultColors.mTertiary + property color mOnTertiary: defaultColors.mOnTertiary + + // --- Utility Colors: These colors serve specific, universal purposes like indicating errors + property color mError: defaultColors.mError + property color mOnError: defaultColors.mOnError + + // --- Surface and Variant Colors: These provide additional options for surfaces and their contents, creating visual hierarchy + property color mSurface: defaultColors.mSurface + property color mOnSurface: defaultColors.mOnSurface + + property color mSurfaceVariant: defaultColors.mSurfaceVariant + property color mOnSurfaceVariant: defaultColors.mOnSurfaceVariant + + property color mOutline: defaultColors.mOutline + property color mShadow: defaultColors.mShadow + + property color mHover: defaultColors.mHover + property color mOnHover: defaultColors.mOnHover + + // --- Color transition animations --- + Behavior on mPrimary { + enabled: !root.skipTransition + ColorAnimation { + duration: Style.animationSlowest + easing.type: Easing.OutCubic + } + } + Behavior on mOnPrimary { + enabled: !root.skipTransition + ColorAnimation { + duration: Style.animationSlowest + easing.type: Easing.OutCubic + } + } + Behavior on mSecondary { + enabled: !root.skipTransition + ColorAnimation { + duration: Style.animationSlowest + easing.type: Easing.OutCubic + } + } + Behavior on mOnSecondary { + enabled: !root.skipTransition + ColorAnimation { + duration: Style.animationSlowest + easing.type: Easing.OutCubic + } + } + Behavior on mTertiary { + enabled: !root.skipTransition + ColorAnimation { + duration: Style.animationSlowest + easing.type: Easing.OutCubic + } + } + Behavior on mOnTertiary { + enabled: !root.skipTransition + ColorAnimation { + duration: Style.animationSlowest + easing.type: Easing.OutCubic + } + } + Behavior on mError { + enabled: !root.skipTransition + ColorAnimation { + duration: Style.animationSlowest + easing.type: Easing.OutCubic + } + } + Behavior on mOnError { + enabled: !root.skipTransition + ColorAnimation { + duration: Style.animationSlowest + easing.type: Easing.OutCubic + } + } + Behavior on mSurface { + enabled: !root.skipTransition + ColorAnimation { + duration: Style.animationSlowest + easing.type: Easing.OutCubic + } + } + Behavior on mOnSurface { + enabled: !root.skipTransition + ColorAnimation { + duration: Style.animationSlowest + easing.type: Easing.OutCubic + } + } + Behavior on mSurfaceVariant { + enabled: !root.skipTransition + ColorAnimation { + duration: Style.animationSlowest + easing.type: Easing.OutCubic + } + } + Behavior on mOnSurfaceVariant { + enabled: !root.skipTransition + ColorAnimation { + duration: Style.animationSlowest + easing.type: Easing.OutCubic + } + } + Behavior on mOutline { + enabled: !root.skipTransition + ColorAnimation { + duration: Style.animationSlowest + easing.type: Easing.OutCubic + } + } + Behavior on mShadow { + enabled: !root.skipTransition + ColorAnimation { + duration: Style.animationSlowest + easing.type: Easing.OutCubic + } + } + Behavior on mHover { + enabled: !root.skipTransition + ColorAnimation { + duration: Style.animationSlowest + easing.type: Easing.OutCubic + } + } + Behavior on mOnHover { + enabled: !root.skipTransition + ColorAnimation { + duration: Style.animationSlowest + easing.type: Easing.OutCubic + } + } + + // Helper to start transition and update a color + function startTransition() { + root.isTransitioning = true; + transitionTimer.restart(); + } + + // Update colors when customColorsData changes (imperative assignment enables Behavior animations) + Connections { + target: customColorsData + function onMPrimaryChanged() { + if (!root.skipTransition) { + startTransition(); + } + root.mPrimary = customColorsData.mPrimary; + } + function onMOnPrimaryChanged() { + if (!root.skipTransition) { + startTransition(); + } + root.mOnPrimary = customColorsData.mOnPrimary; + } + function onMSecondaryChanged() { + if (!root.skipTransition) { + startTransition(); + } + root.mSecondary = customColorsData.mSecondary; + } + function onMOnSecondaryChanged() { + if (!root.skipTransition) { + startTransition(); + } + root.mOnSecondary = customColorsData.mOnSecondary; + } + function onMTertiaryChanged() { + if (!root.skipTransition) { + startTransition(); + } + root.mTertiary = customColorsData.mTertiary; + } + function onMOnTertiaryChanged() { + if (!root.skipTransition) { + startTransition(); + } + root.mOnTertiary = customColorsData.mOnTertiary; + } + function onMErrorChanged() { + if (!root.skipTransition) { + startTransition(); + } + root.mError = customColorsData.mError; + } + function onMOnErrorChanged() { + if (!root.skipTransition) { + startTransition(); + } + root.mOnError = customColorsData.mOnError; + } + function onMSurfaceChanged() { + if (!root.skipTransition) { + startTransition(); + } + root.mSurface = customColorsData.mSurface; + } + function onMOnSurfaceChanged() { + if (!root.skipTransition) { + startTransition(); + } + root.mOnSurface = customColorsData.mOnSurface; + } + function onMSurfaceVariantChanged() { + if (!root.skipTransition) { + startTransition(); + } + root.mSurfaceVariant = customColorsData.mSurfaceVariant; + } + function onMOnSurfaceVariantChanged() { + if (!root.skipTransition) { + startTransition(); + } + root.mOnSurfaceVariant = customColorsData.mOnSurfaceVariant; + } + function onMOutlineChanged() { + if (!root.skipTransition) { + startTransition(); + } + root.mOutline = customColorsData.mOutline; + } + function onMShadowChanged() { + if (!root.skipTransition) { + startTransition(); + } + root.mShadow = customColorsData.mShadow; + } + function onMHoverChanged() { + if (!root.skipTransition) { + startTransition(); + } + root.mHover = customColorsData.mHover; + } + function onMOnHoverChanged() { + if (!root.skipTransition) { + startTransition(); + } + root.mOnHover = customColorsData.mOnHover; + } + } + + function resolveColorKey(key) { + switch (key) { + case "primary": + return root.mPrimary; + case "secondary": + return root.mSecondary; + case "tertiary": + return root.mTertiary; + case "error": + return root.mError; + default: + return root.mOnSurface; + } + } + + function resolveOnColorKey(key) { + switch (key) { + case "primary": + return root.mOnPrimary; + case "secondary": + return root.mOnSecondary; + case "tertiary": + return root.mOnTertiary; + case "error": + return root.mOnError; + default: + return root.mSurface; + } + } + + function resolveColorKeyOptional(key) { + switch (key) { + case "primary": + return root.mPrimary; + case "secondary": + return root.mSecondary; + case "tertiary": + return root.mTertiary; + case "error": + return root.mError; + default: + return "transparent"; + } + } + + // Adaptive opacity calculation: automatically makes light mode more transparent + function adaptiveOpacity(baseOpacity) { + return Settings.data.colorSchemes.darkMode ? baseOpacity : Math.pow(baseOpacity, 1.5); + } + + function smartAlpha(baseColor, minAlpha = 0.4) { + if (!Settings.data.ui.translucentWidgets) + return baseColor; + + let alpha = Math.max(adaptiveOpacity(Settings.data.ui.panelBackgroundOpacity), minAlpha); + + // Combine with the base color's existing alpha + let resultAlpha = Math.max(0, baseColor.a - (1.0 - alpha)); + return Qt.alpha(baseColor, resultAlpha); + } + + readonly property var colorKeyModel: [ + { + "key": "none", + "name": I18n.tr("common.none") + }, + { + "key": "primary", + "name": I18n.tr("common.primary") + }, + { + "key": "secondary", + "name": I18n.tr("common.secondary") + }, + { + "key": "tertiary", + "name": I18n.tr("common.tertiary") + }, + { + "key": "error", + "name": I18n.tr("common.error") + } + ] + + // -------------------------------- + // Default colors: Noctalia (default) dark โ€” must match Assets/ColorScheme/Noctalia-default + QtObject { + id: defaultColors + + readonly property color mPrimary: "#fff59b" + readonly property color mOnPrimary: "#0e0e43" + + readonly property color mSecondary: "#a9aefe" + readonly property color mOnSecondary: "#0e0e43" + + readonly property color mTertiary: "#9BFECE" + readonly property color mOnTertiary: "#0e0e43" + + readonly property color mError: "#FD4663" + readonly property color mOnError: "#0e0e43" + + readonly property color mSurface: "#070722" + readonly property color mOnSurface: "#f3edf7" + + readonly property color mSurfaceVariant: "#11112d" + readonly property color mOnSurfaceVariant: "#7c80b4" + + readonly property color mOutline: "#21215F" + readonly property color mShadow: "#070722" + + readonly property color mHover: "#9BFECE" + readonly property color mOnHover: "#0e0e43" + } + + // ---------------------------------------------------------------- + // FileView to load custom colors data from colors.json + FileView { + id: customColorsFile + path: Settings.directoriesCreated ? (Settings.configDir + "colors.json") : undefined + printErrors: false + watchChanges: true + onFileChanged: { + Logger.d("Color", "Reloading colors from disk"); + reloadColors = true; + reload(); + } + onAdapterUpdated: { + Logger.d("Color", "Writing colors to disk"); + writeAdapter(); + } + + onLoaded: { + if (root.skipTransition) { + Qt.callLater(function () { + root.skipTransition = false; + }); + } + } + + // Trigger initial load when path changes from empty to actual path + onPathChanged: { + if (path !== undefined) { + reload(); + } + } + onLoadFailed: function (error) { + if (reloadColors) { + reloadColors = false; + return; + } + + if (root.skipTransition) { + Qt.callLater(function () { + root.skipTransition = false; + }); + } + + // Error code 2 = ENOENT (No such file or directory) + if (error === 2 || error.toString().includes("No such file")) { + // File doesn't exist, create it with default values + writeAdapter(); + } + } + JsonAdapter { + id: customColorsData + + property color mPrimary: defaultColors.mPrimary + property color mOnPrimary: defaultColors.mOnPrimary + + property color mSecondary: defaultColors.mSecondary + property color mOnSecondary: defaultColors.mOnSecondary + + property color mTertiary: defaultColors.mTertiary + property color mOnTertiary: defaultColors.mOnTertiary + + property color mError: defaultColors.mError + property color mOnError: defaultColors.mOnError + + property color mSurface: defaultColors.mSurface + property color mOnSurface: defaultColors.mOnSurface + + property color mSurfaceVariant: defaultColors.mSurfaceVariant + property color mOnSurfaceVariant: defaultColors.mOnSurfaceVariant + + property color mOutline: defaultColors.mOutline + property color mShadow: defaultColors.mShadow + + property color mHover: defaultColors.mHover + property color mOnHover: defaultColors.mOnHover + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/FuzzySort.qml b/arch/.config/quickshell/noctalia-shell/Commons/FuzzySort.qml new file mode 100644 index 0000000..d729e2a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/FuzzySort.qml @@ -0,0 +1,883 @@ +pragma Singleton + +import QtQuick +import Quickshell + +Singleton { + id: root + + // Public API + function go(search, targets, options) { + return _go(search, targets, options); + } + + function single(search, target) { + return _single(search, target); + } + + function highlight(result, open, close) { + if (open === undefined) + open = ''; + if (close === undefined) + close = ''; + return _highlight(result, open, close); + } + + function prepare(target) { + return _prepare(target); + } + + function cleanup() { + return _cleanup(); + } + + // Internal implementation + readonly property var _INFINITY: Infinity + readonly property var _NEGATIVE_INFINITY: -Infinity + readonly property var _NULL: null + property var _noResults: { + let r = []; + r.total = 0; + return r; + } + property var _noTarget: _prepare('') + + property var _preparedCache: new Map() + property var _preparedSearchCache: new Map() + + property var _matchesSimple: [] + property var _matchesStrict: [] + property var _nextBeginningIndexesChanges: [] + property var _keysSpacesBestScores: [] + property var _allowPartialMatchScores: [] + property var _tmpTargets: [] + property var _tmpResults: [] + property var _q: _fastpriorityqueue() + + function _fastpriorityqueue() { + var e = [], o = 0, a = {}; + var v = function (r) { + for (var a = 0, vc = e[a], c = 1; c < o; ) { + var s = c + 1; + a = c; + if (s < o && e[s]._score < e[c]._score) + a = s; + e[a - 1 >> 1] = e[a]; + c = 1 + (a << 1); + } + for (var f = a - 1 >> 1; a > 0 && vc._score < e[f]._score; f = (a = f) - 1 >> 1) + e[a] = e[f]; + e[a] = vc; + }; + a.add = function (r) { + var ac = o; + e[o++] = r; + for (var vc = ac - 1 >> 1; ac > 0 && r._score < e[vc]._score; vc = (ac = vc) - 1 >> 1) + e[ac] = e[vc]; + e[ac] = r; + }; + a.poll = function () { + if (o !== 0) { + var ac = e[0]; + e[0] = e[--o]; + v(); + return ac; + } + }; + a.peek = function () { + if (o !== 0) + return e[0]; + }; + a.replaceTop = function (r) { + e[0] = r; + v(); + }; + return a; + } + + function _createResult() { + return { + target: '', + obj: _NULL, + _score: _NEGATIVE_INFINITY, + _indexes: [], + _targetLower: '', + _targetLowerCodes: _NULL, + _nextBeginningIndexes: _NULL, + _bitflags: 0, + get indexes() { + return this._indexes.slice(0, this._indexes.len).sort((a, b) => a - b); + }, + set indexes(idx) { + this._indexes = idx; + }, + highlight: function (open, close) { + return root._highlight(this, open, close); + }, + get score() { + return root._normalizeScore(this._score); + }, + set score(s) { + this._score = root._denormalizeScore(s); + } + }; + } + + function _createKeysResult(len) { + var arr = new Array(len); + arr._score = _NEGATIVE_INFINITY; + arr.obj = _NULL; + Object.defineProperty(arr, 'score', { + get: function () { + return root._normalizeScore(this._score); + }, + set: function (s) { + this._score = root._denormalizeScore(s); + } + }); + return arr; + } + + function _new_result(target, options) { + var result = _createResult(); + result.target = target; + result.obj = options.obj ?? _NULL; + result._score = options._score ?? _NEGATIVE_INFINITY; + result._indexes = options._indexes ?? []; + result._targetLower = options._targetLower ?? ''; + result._targetLowerCodes = options._targetLowerCodes ?? _NULL; + result._nextBeginningIndexes = options._nextBeginningIndexes ?? _NULL; + result._bitflags = options._bitflags ?? 0; + return result; + } + + function _normalizeScore(score) { + if (score === _NEGATIVE_INFINITY) + return 0; + if (score > 1) + return score; + return Math.E ** (((-score + 1) ** 0.04307 - 1) * -2); + } + + function _denormalizeScore(normalizedScore) { + if (normalizedScore === 0) + return _NEGATIVE_INFINITY; + if (normalizedScore > 1) + return normalizedScore; + return 1 - Math.pow((Math.log(normalizedScore) / -2 + 1), 1 / 0.04307); + } + + function _remove_accents(str) { + return str.replace(/\p{Script=Latin}+/gu, match => match.normalize('NFD')).replace(/[\u0300-\u036f]/g, ''); + } + + function _prepareLowerInfo(str) { + str = _remove_accents(str); + var strLen = str.length; + var lower = str.toLowerCase(); + var lowerCodes = []; + var bitflags = 0; + var containsSpace = false; + + for (var i = 0; i < strLen; ++i) { + var lowerCode = lowerCodes[i] = lower.charCodeAt(i); + if (lowerCode === 32) { + containsSpace = true; + continue; + } + var bit = lowerCode >= 97 && lowerCode <= 122 ? lowerCode - 97 : lowerCode >= 48 && lowerCode <= 57 ? 26 : lowerCode <= 127 ? 30 : 31; + bitflags |= 1 << bit; + } + return { + lowerCodes: lowerCodes, + bitflags: bitflags, + containsSpace: containsSpace, + _lower: lower + }; + } + + function _prepareBeginningIndexes(target) { + var targetLen = target.length; + var beginningIndexes = []; + var beginningIndexesLen = 0; + var wasUpper = false; + var wasAlphanum = false; + for (var i = 0; i < targetLen; ++i) { + var targetCode = target.charCodeAt(i); + var isUpper = targetCode >= 65 && targetCode <= 90; + var isAlphanum = isUpper || targetCode >= 97 && targetCode <= 122 || targetCode >= 48 && targetCode <= 57; + var isBeginning = isUpper && !wasUpper || !wasAlphanum || !isAlphanum; + wasUpper = isUpper; + wasAlphanum = isAlphanum; + if (isBeginning) + beginningIndexes[beginningIndexesLen++] = i; + } + return beginningIndexes; + } + + function _prepareNextBeginningIndexes(target) { + target = _remove_accents(target); + var targetLen = target.length; + var beginningIndexes = _prepareBeginningIndexes(target); + var nextBeginningIndexes = []; + var lastIsBeginning = beginningIndexes[0]; + var lastIsBeginningI = 0; + for (var i = 0; i < targetLen; ++i) { + if (lastIsBeginning > i) { + nextBeginningIndexes[i] = lastIsBeginning; + } else { + lastIsBeginning = beginningIndexes[++lastIsBeginningI]; + nextBeginningIndexes[i] = lastIsBeginning === undefined ? targetLen : lastIsBeginning; + } + } + return nextBeginningIndexes; + } + + function _prepareSearch(search) { + if (typeof search === 'number') + search = '' + search; + else if (typeof search !== 'string') + search = ''; + search = search.trim(); + var info = _prepareLowerInfo(search); + + var spaceSearches = []; + if (info.containsSpace) { + var searches = search.split(/\s+/); + searches = [...new Set(searches)]; + for (var i = 0; i < searches.length; i++) { + if (searches[i] === '') + continue; + var _info = _prepareLowerInfo(searches[i]); + spaceSearches.push({ + lowerCodes: _info.lowerCodes, + _lower: searches[i].toLowerCase(), + containsSpace: false + }); + } + } + return { + lowerCodes: info.lowerCodes, + _lower: info._lower, + containsSpace: info.containsSpace, + bitflags: info.bitflags, + spaceSearches: spaceSearches + }; + } + + function _prepare(target) { + if (typeof target === 'number') + target = '' + target; + else if (typeof target !== 'string') + target = ''; + var info = _prepareLowerInfo(target); + return _new_result(target, { + _targetLower: info._lower, + _targetLowerCodes: info.lowerCodes, + _bitflags: info.bitflags + }); + } + + function _cleanup() { + _preparedCache.clear(); + _preparedSearchCache.clear(); + } + + function _isPrepared(x) { + return typeof x === 'object' && typeof x._bitflags === 'number'; + } + + function _getPrepared(target) { + if (target.length > 999) + return _prepare(target); + var targetPrepared = _preparedCache.get(target); + if (targetPrepared !== undefined) + return targetPrepared; + targetPrepared = _prepare(target); + _preparedCache.set(target, targetPrepared); + return targetPrepared; + } + + function _getPreparedSearch(search) { + if (search.length > 999) + return _prepareSearch(search); + var searchPrepared = _preparedSearchCache.get(search); + if (searchPrepared !== undefined) + return searchPrepared; + searchPrepared = _prepareSearch(search); + _preparedSearchCache.set(search, searchPrepared); + return searchPrepared; + } + + function _getValue(obj, prop) { + var tmp = obj[prop]; + if (tmp !== undefined) + return tmp; + if (typeof prop === 'function') + return prop(obj); + var segs = prop; + if (!Array.isArray(prop)) + segs = prop.split('.'); + var len = segs.length; + var i = -1; + while (obj && (++i < len)) + obj = obj[segs[i]]; + return obj; + } + + function _single(search, target) { + if (!search || !target) + return _NULL; + var preparedSearch = _getPreparedSearch(search); + if (!_isPrepared(target)) + target = _getPrepared(target); + var searchBitflags = preparedSearch.bitflags; + if ((searchBitflags & target._bitflags) !== searchBitflags) + return _NULL; + return _algorithm(preparedSearch, target); + } + + function _highlight(result, open, close) { + if (open === undefined) + open = ''; + if (close === undefined) + close = ''; + var callback = typeof open === 'function' ? open : undefined; + + var target = result.target; + var targetLen = target.length; + var indexes = result.indexes; + var highlighted = ''; + var matchI = 0; + var indexesI = 0; + var opened = false; + var parts = []; + + for (var i = 0; i < targetLen; ++i) { + var ch = target[i]; + if (indexes[indexesI] === i) { + ++indexesI; + if (!opened) { + opened = true; + if (callback) { + parts.push(highlighted); + highlighted = ''; + } else { + highlighted += open; + } + } + if (indexesI === indexes.length) { + if (callback) { + highlighted += ch; + parts.push(callback(highlighted, matchI++)); + highlighted = ''; + parts.push(target.substr(i + 1)); + } else { + highlighted += ch + close + target.substr(i + 1); + } + break; + } + } else { + if (opened) { + opened = false; + if (callback) { + parts.push(callback(highlighted, matchI++)); + highlighted = ''; + } else { + highlighted += close; + } + } + } + highlighted += ch; + } + return callback ? parts : highlighted; + } + + function _all(targets, options) { + var results = []; + results.total = targets.length; + var limit = options?.limit || _INFINITY; + + if (options?.key) { + for (var i = 0; i < targets.length; i++) { + var obj = targets[i]; + var target = _getValue(obj, options.key); + if (target == _NULL) + continue; + if (!_isPrepared(target)) + target = _getPrepared(target); + var result = _new_result(target.target, { + _score: target._score, + obj: obj + }); + results.push(result); + if (results.length >= limit) + return results; + } + } else if (options?.keys) { + for (var i = 0; i < targets.length; i++) { + var obj = targets[i]; + var objResults = _createKeysResult(options.keys.length); + for (var keyI = options.keys.length - 1; keyI >= 0; --keyI) { + var target = _getValue(obj, options.keys[keyI]); + if (!target) { + objResults[keyI] = _noTarget; + continue; + } + if (!_isPrepared(target)) + target = _getPrepared(target); + target._score = _NEGATIVE_INFINITY; + target._indexes.len = 0; + objResults[keyI] = target; + } + objResults.obj = obj; + objResults._score = _NEGATIVE_INFINITY; + results.push(objResults); + if (results.length >= limit) + return results; + } + } else { + for (var i = 0; i < targets.length; i++) { + var target = targets[i]; + if (target == _NULL) + continue; + if (!_isPrepared(target)) + target = _getPrepared(target); + target._score = _NEGATIVE_INFINITY; + target._indexes.len = 0; + results.push(target); + if (results.length >= limit) + return results; + } + } + return results; + } + + function _algorithm(preparedSearch, prepared, allowSpaces, allowPartialMatch) { + if (allowSpaces === undefined) + allowSpaces = false; + if (allowPartialMatch === undefined) + allowPartialMatch = false; + + if (allowSpaces === false && preparedSearch.containsSpace) + return _algorithmSpaces(preparedSearch, prepared, allowPartialMatch); + + var searchLower = preparedSearch._lower; + var searchLowerCodes = preparedSearch.lowerCodes; + var searchLowerCode = searchLowerCodes[0]; + var targetLowerCodes = prepared._targetLowerCodes; + var searchLen = searchLowerCodes.length; + var targetLen = targetLowerCodes.length; + var searchI = 0; + var targetI = 0; + var matchesSimpleLen = 0; + + for (; ; ) { + var isMatch = searchLowerCode === targetLowerCodes[targetI]; + if (isMatch) { + _matchesSimple[matchesSimpleLen++] = targetI; + ++searchI; + if (searchI === searchLen) + break; + searchLowerCode = searchLowerCodes[searchI]; + } + ++targetI; + if (targetI >= targetLen) + return _NULL; + } + + searchI = 0; + var successStrict = false; + var matchesStrictLen = 0; + + var nextBeginningIndexes = prepared._nextBeginningIndexes; + if (nextBeginningIndexes === _NULL) + nextBeginningIndexes = prepared._nextBeginningIndexes = _prepareNextBeginningIndexes(prepared.target); + targetI = _matchesSimple[0] === 0 ? 0 : nextBeginningIndexes[_matchesSimple[0] - 1]; + + var backtrackCount = 0; + if (targetI !== targetLen) + for (; ; ) { + if (targetI >= targetLen) { + if (searchI <= 0) + break; + ++backtrackCount; + if (backtrackCount > 200) + break; + --searchI; + var lastMatch = _matchesStrict[--matchesStrictLen]; + targetI = nextBeginningIndexes[lastMatch]; + } else { + var isMatch = searchLowerCodes[searchI] === targetLowerCodes[targetI]; + if (isMatch) { + _matchesStrict[matchesStrictLen++] = targetI; + ++searchI; + if (searchI === searchLen) { + successStrict = true; + break; + } + ++targetI; + } else { + targetI = nextBeginningIndexes[targetI]; + } + } + } + + var substringIndex = searchLen <= 1 ? -1 : prepared._targetLower.indexOf(searchLower, _matchesSimple[0]); + var isSubstring = !!~substringIndex; + var isSubstringBeginning = !isSubstring ? false : substringIndex === 0 || prepared._nextBeginningIndexes[substringIndex - 1] === substringIndex; + + if (isSubstring && !isSubstringBeginning) { + for (var i = 0; i < nextBeginningIndexes.length; i = nextBeginningIndexes[i]) { + if (i <= substringIndex) + continue; + for (var s = 0; s < searchLen; s++) + if (searchLowerCodes[s] !== prepared._targetLowerCodes[i + s]) + break; + if (s === searchLen) { + substringIndex = i; + isSubstringBeginning = true; + break; + } + } + } + + var calculateScore = function (matches) { + var score = 0; + var extraMatchGroupCount = 0; + for (var i = 1; i < searchLen; ++i) { + if (matches[i] - matches[i - 1] !== 1) { + score -= matches[i]; + ++extraMatchGroupCount; + } + } + var unmatchedDistance = matches[searchLen - 1] - matches[0] - (searchLen - 1); + score -= (12 + unmatchedDistance) * extraMatchGroupCount; + if (matches[0] !== 0) + score -= matches[0] * matches[0] * 0.2; + if (!successStrict) { + score *= 1000; + } else { + var uniqueBeginningIndexes = 1; + for (var i = nextBeginningIndexes[0]; i < targetLen; i = nextBeginningIndexes[i]) + ++uniqueBeginningIndexes; + if (uniqueBeginningIndexes > 24) + score *= (uniqueBeginningIndexes - 24) * 10; + } + score -= (targetLen - searchLen) / 2; + if (isSubstring) + score /= 1 + searchLen * searchLen * 1; + if (isSubstringBeginning) + score /= 1 + searchLen * searchLen * 1; + score -= (targetLen - searchLen) / 2; + return score; + }; + + var matchesBest, score; + if (!successStrict) { + if (isSubstring) + for (var i = 0; i < searchLen; ++i) + _matchesSimple[i] = substringIndex + i; + matchesBest = _matchesSimple; + score = calculateScore(matchesBest); + } else { + if (isSubstringBeginning) { + for (var i = 0; i < searchLen; ++i) + _matchesSimple[i] = substringIndex + i; + matchesBest = _matchesSimple; + score = calculateScore(_matchesSimple); + } else { + matchesBest = _matchesStrict; + score = calculateScore(_matchesStrict); + } + } + + prepared._score = score; + for (var i = 0; i < searchLen; ++i) + prepared._indexes[i] = matchesBest[i]; + prepared._indexes.len = searchLen; + + var result = _createResult(); + result.target = prepared.target; + result._score = prepared._score; + result._indexes = prepared._indexes; + return result; + } + + function _algorithmSpaces(preparedSearch, target, allowPartialMatch) { + var seen_indexes = new Set(); + var score = 0; + var result = _NULL; + + var first_seen_index_last_search = 0; + var searches = preparedSearch.spaceSearches; + var searchesLen = searches.length; + var changeslen = 0; + + var resetNextBeginningIndexes = function () { + for (let i = changeslen - 1; i >= 0; i--) + target._nextBeginningIndexes[_nextBeginningIndexesChanges[i * 2 + 0]] = _nextBeginningIndexesChanges[i * 2 + 1]; + }; + + var hasAtLeast1Match = false; + for (var i = 0; i < searchesLen; ++i) { + _allowPartialMatchScores[i] = _NEGATIVE_INFINITY; + var search = searches[i]; + result = _algorithm(search, target); + + if (allowPartialMatch) { + if (result === _NULL) + continue; + hasAtLeast1Match = true; + } else { + if (result === _NULL) { + resetNextBeginningIndexes(); + return _NULL; + } + } + + var isTheLastSearch = i === searchesLen - 1; + if (!isTheLastSearch) { + var indexes = result._indexes; + var indexesIsConsecutiveSubstring = true; + for (let j = 0; j < indexes.len - 1; j++) { + if (indexes[j + 1] - indexes[j] !== 1) { + indexesIsConsecutiveSubstring = false; + break; + } + } + + if (indexesIsConsecutiveSubstring) { + var newBeginningIndex = indexes[indexes.len - 1] + 1; + var toReplace = target._nextBeginningIndexes[newBeginningIndex - 1]; + for (let j = newBeginningIndex - 1; j >= 0; j--) { + if (toReplace !== target._nextBeginningIndexes[j]) + break; + target._nextBeginningIndexes[j] = newBeginningIndex; + _nextBeginningIndexesChanges[changeslen * 2 + 0] = j; + _nextBeginningIndexesChanges[changeslen * 2 + 1] = toReplace; + changeslen++; + } + } + } + + score += result._score / searchesLen; + _allowPartialMatchScores[i] = result._score / searchesLen; + + if (result._indexes[0] < first_seen_index_last_search) { + score -= (first_seen_index_last_search - result._indexes[0]) * 2; + } + first_seen_index_last_search = result._indexes[0]; + + for (var j = 0; j < result._indexes.len; ++j) + seen_indexes.add(result._indexes[j]); + } + + if (allowPartialMatch && !hasAtLeast1Match) + return _NULL; + + resetNextBeginningIndexes(); + + var allowSpacesResult = _algorithm(preparedSearch, target, true); + if (allowSpacesResult !== _NULL && allowSpacesResult._score > score) { + if (allowPartialMatch) { + for (var i = 0; i < searchesLen; ++i) { + _allowPartialMatchScores[i] = allowSpacesResult._score / searchesLen; + } + } + return allowSpacesResult; + } + + if (allowPartialMatch) + result = target; + result._score = score; + + var idx = 0; + for (let index of seen_indexes) + result._indexes[idx++] = index; + result._indexes.len = idx; + + return result; + } + + function _go(search, targets, options) { + if (!search) + return options?.all ? _all(targets, options) : _noResults; + + var preparedSearch = _getPreparedSearch(search); + var searchBitflags = preparedSearch.bitflags; + var containsSpace = preparedSearch.containsSpace; + + var threshold = _denormalizeScore(options?.threshold ?? 0.35); + var limit = options?.limit || _INFINITY; + + var resultsLen = 0; + var limitedCount = 0; + var targetsLen = targets.length; + + function push_result(result) { + if (resultsLen < limit) { + _q.add(result); + ++resultsLen; + } else { + ++limitedCount; + if (result._score > _q.peek()._score) + _q.replaceTop(result); + } + } + + if (options?.key) { + var key = options.key; + for (var i = 0; i < targetsLen; ++i) { + var obj = targets[i]; + var target = _getValue(obj, key); + if (!target) + continue; + if (!_isPrepared(target)) + target = _getPrepared(target); + if ((searchBitflags & target._bitflags) !== searchBitflags) + continue; + var result = _algorithm(preparedSearch, target); + if (result === _NULL) + continue; + if (result._score < threshold) + continue; + result.obj = obj; + push_result(result); + } + } else if (options?.keys) { + var keys = options.keys; + var keysLen = keys.length; + + outer: for (var i = 0; i < targetsLen; ++i) { + var obj = targets[i]; + var keysBitflags = 0; + for (var keyI = 0; keyI < keysLen; ++keyI) { + var key = keys[keyI]; + var target = _getValue(obj, key); + if (!target) { + _tmpTargets[keyI] = _noTarget; + continue; + } + if (!_isPrepared(target)) + target = _getPrepared(target); + _tmpTargets[keyI] = target; + keysBitflags |= target._bitflags; + } + + if ((searchBitflags & keysBitflags) !== searchBitflags) + continue; + + if (containsSpace) + for (let j = 0; j < preparedSearch.spaceSearches.length; j++) + _keysSpacesBestScores[j] = _NEGATIVE_INFINITY; + + for (var keyI = 0; keyI < keysLen; ++keyI) { + target = _tmpTargets[keyI]; + if (target === _noTarget) { + _tmpResults[keyI] = _noTarget; + continue; + } + + _tmpResults[keyI] = _algorithm(preparedSearch, target, false, containsSpace); + if (_tmpResults[keyI] === _NULL) { + _tmpResults[keyI] = _noTarget; + continue; + } + + if (containsSpace) + for (let j = 0; j < preparedSearch.spaceSearches.length; j++) { + if (_allowPartialMatchScores[j] > -1000) { + if (_keysSpacesBestScores[j] > _NEGATIVE_INFINITY) { + var tmp = (_keysSpacesBestScores[j] + _allowPartialMatchScores[j]) / 4; + if (tmp > _keysSpacesBestScores[j]) + _keysSpacesBestScores[j] = tmp; + } + } + if (_allowPartialMatchScores[j] > _keysSpacesBestScores[j]) + _keysSpacesBestScores[j] = _allowPartialMatchScores[j]; + } + } + + if (containsSpace) { + for (let j = 0; j < preparedSearch.spaceSearches.length; j++) + if (_keysSpacesBestScores[j] === _NEGATIVE_INFINITY) + continue outer; + } else { + var hasAtLeast1Match = false; + for (let j = 0; j < keysLen; j++) + if (_tmpResults[j]._score !== _NEGATIVE_INFINITY) { + hasAtLeast1Match = true; + break; + } + if (!hasAtLeast1Match) + continue; + } + + var objResults = _createKeysResult(keysLen); + for (let j = 0; j < keysLen; j++) + objResults[j] = _tmpResults[j]; + + var score; + if (containsSpace) { + score = 0; + for (let j = 0; j < preparedSearch.spaceSearches.length; j++) + score += _keysSpacesBestScores[j]; + } else { + score = _NEGATIVE_INFINITY; + for (let j = 0; j < keysLen; j++) { + var res = objResults[j]; + if (res._score > -1000) { + if (score > _NEGATIVE_INFINITY) { + var tmp = (score + res._score) / 4; + if (tmp > score) + score = tmp; + } + } + if (res._score > score) + score = res._score; + } + } + + objResults.obj = obj; + objResults._score = score; + + if (options?.scoreFn) { + score = options.scoreFn(objResults); + if (!score) + continue; + score = _denormalizeScore(score); + objResults._score = score; + } + + if (score < threshold) + continue; + push_result(objResults); + } + } else { + for (var i = 0; i < targetsLen; ++i) { + var target = targets[i]; + if (!target) + continue; + if (!_isPrepared(target)) + target = _getPrepared(target); + if ((searchBitflags & target._bitflags) !== searchBitflags) + continue; + var result = _algorithm(preparedSearch, target); + if (result === _NULL) + continue; + if (result._score < threshold) + continue; + push_result(result); + } + } + + if (resultsLen === 0) + return _noResults; + var results = new Array(resultsLen); + for (var i = resultsLen - 1; i >= 0; --i) + results[i] = _q.poll(); + results.total = resultsLen + limitedCount; + return results; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/I18n.qml b/arch/.config/quickshell/noctalia-shell/Commons/I18n.qml new file mode 100644 index 0000000..8248adc --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/I18n.qml @@ -0,0 +1,373 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons + +Singleton { + id: root + + property bool isLoaded: false + property string langCode: "" + property var locale: Qt.locale() + property string systemDetectedLangCode: "" + property string fullLocaleCode: "" // Preserves regional locale variants + property var translations: ({}) + property var fallbackTranslations: ({}) + + // Static list of available translations โ€” update when adding/removing translation files + property var availableLanguages: ["en", "en-GB", "cs", "de", "es", "fr", "hu", "it", "ja", "ko-KR", "ku", "nl", "nn-HN", "nn-NO", "pl", "pt", "ru", "sv", "tr", "uk-UA", "vi", "zh-CN", "zh-TW"] + + // Default date format per language (used by lock screen, etc.) + readonly property var dateFormats: ({ + "de": "dddd, d. MMMM", + "en": "dddd, MMMM d", + "es": "dddd, d 'de' MMMM", + "fr": "dddd d MMMM", + "hu": "dddd, MMMM d.", + "it": "dddd d MMMM", + "ja": "yyyyๅนดMๆœˆdๆ—ฅ dddd", + "ko": "yyyy๋…„ M์›” d์ผ dddd", + "ku": "dddd, dรช MMMM", + "nl": "dddd d MMMM", + "nn": "dddd d. MMMM", + "pl": "dddd, d MMMM", + "pt": "dddd, d 'de' MMMM", + "ru": "dddd, d MMMM", + "sv": "dddd d MMMM", + "tr": "dddd, d MMMM", + "uk": "dddd, d MMMM", + "vi": "dddd, d MMMM", + "zh": "yyyyๅนดMๆœˆdๆ—ฅ dddd" + }) + + // Signals for reactive updates + signal languageChanged(string newLanguage) + signal translationsLoaded + + // FileView to load translation files + property FileView translationFile: FileView { + id: fileView + printErrors: false + watchChanges: true + onFileChanged: reload() + onLoaded: { + try { + var data = JSON.parse(text()); + root.translations = data; + Logger.i("I18n", `Loaded translations for "${root.langCode}"`); + + root.isLoaded = true; + root.translationsLoaded(); + + // Load English fallback for non-English languages (only after main file succeeds) + if (root.langCode !== "en") { + fallbackFileView.path = `file://${Quickshell.shellDir}/Assets/Translations/en.json`; + } + } catch (e) { + Logger.e("I18n", `Failed to parse translation file: ${e}`); + setLanguage("en"); + } + } + onLoadFailed: function (error) { + if (root.langCode === "en") { + Logger.e("I18n", `Failed to load English translation file: ${error}`); + // English also failed - still emit signal to unblock startup + root.isLoaded = true; + root.translationsLoaded(); + return; + } + + // Qt.callLater is needed because FileView doesn't re-trigger when path + // is changed inside its own onLoadFailed handler + var strippedCode = stripScript(root.langCode); + if (strippedCode !== root.langCode) { + Logger.d("I18n", `Translation file for "${root.langCode}" not found, trying "${strippedCode}"`); + root.langCode = strippedCode; + Qt.callLater(loadTranslations); + } else { + // Try language-only code (e.g. "zh-CN" โ†’ "zh") + var shortCode = root.langCode.substring(0, 2); + if (shortCode !== root.langCode) { + Logger.d("I18n", `Translation file for "${root.langCode}" not found, trying "${shortCode}"`); + root.langCode = shortCode; + Qt.callLater(loadTranslations); + } else { + Logger.w("I18n", `Translation file for "${root.langCode}" not found, falling back to English`); + root.langCode = "en"; + root.fullLocaleCode = "en"; + root.locale = Qt.locale("en"); + Qt.callLater(loadTranslations); + } + } + } + } + + // FileView to load fallback translation files + property FileView fallbackTranslationFile: FileView { + id: fallbackFileView + watchChanges: true + onFileChanged: reload() + onLoaded: { + try { + var data = JSON.parse(text()); + root.fallbackTranslations = data; + Logger.d("I18n", `Loaded english fallback translations`); + } catch (e) { + Logger.e("I18n", `Failed to parse fallback translation file: ${e}`); + } + } + onLoadFailed: function (error) { + Logger.e("I18n", `Failed to load fallback translation file: ${error}`); + } + } + + // Correct language when settings finish loading from disk (or user changes it) + Connections { + target: Settings.data.general + function onLanguageChanged() { + var userLang = Settings.data.general.language; + if (userLang !== "" && userLang !== root.langCode && availableLanguages.includes(userLang)) { + Logger.i("I18n", `Applying user language preference: "${userLang}"`); + setLanguage(userLang); + } else if (userLang === "" && root.systemDetectedLangCode !== "" && root.systemDetectedLangCode !== root.langCode) { + Logger.i("I18n", `Language reset to default, reverting to system language: "${root.systemDetectedLangCode}"`); + setLanguage(root.systemDetectedLangCode); + } + } + } + + Component.onCompleted: { + Logger.i("I18n", "Service started"); + + var lang = determineFastLanguage(); + langCode = lang.code; + fullLocaleCode = lang.fullLocale; + locale = Qt.locale(lang.fullLocale); + systemDetectedLangCode = lang.code; + Logger.i("I18n", `Loading "${lang.code}" (locale: "${lang.fullLocale}")`); + loadTranslations(); + } + + // Strip 4-letter script subtag from a BCP 47 tag (e.g. "fr-Latn-FR" โ†’ "fr-FR") + function stripScript(tag) { + var parts = tag.split("-"); + var result = []; + for (var i = 0; i < parts.length; i++) { + if (parts[i].length === 4 && /^[A-Za-z]{4}$/.test(parts[i])) { + continue; + } + result.push(parts[i]); + } + return result.join("-"); + } + + // Determine the best language match against availableLanguages + function determineFastLanguage() { + // User preference from Settings (defaults to "" if not yet loaded from disk) + var userLang = Settings.data.general.language; + if (userLang !== "" && availableLanguages.includes(userLang)) { + return { + code: userLang, + fullLocale: userLang + }; + } + + // Match system locale against available translations + for (var i = 0; i < Qt.locale().uiLanguages.length; i++) { + var fullLang = Qt.locale().uiLanguages[i]; + + // Exact match (e.g. "zh-CN") + if (availableLanguages.includes(fullLang)) { + return { + code: fullLang, + fullLocale: fullLang + }; + } + + // Script-stripped match (e.g. "zh-Hans-CN" โ†’ "zh-CN") + var stripped = stripScript(fullLang); + if (stripped !== fullLang && availableLanguages.includes(stripped)) { + return { + code: stripped, + fullLocale: fullLang + }; + } + + // Language-only match (e.g. "fr-FR" โ†’ "fr") + var short_ = fullLang.substring(0, 2); + if (availableLanguages.includes(short_)) { + return { + code: short_, + fullLocale: fullLang + }; + } + } + + return { + code: "en", + fullLocale: "en" + }; + } + + function dateFormat() { + var lang = langCode.split("-")[0]; + return dateFormats[lang] || "dddd, d MMMM"; + } + + // ------------------------------------------- + function setLanguage(newLangCode, fullLocale) { + if (typeof fullLocale === "undefined") { + fullLocale = newLangCode; + } + + if (newLangCode !== langCode && availableLanguages.includes(newLangCode)) { + langCode = newLangCode; + fullLocaleCode = fullLocale; + locale = Qt.locale(fullLocale); + Logger.i("I18n", `Language set to "${langCode}" with locale "${fullLocale}"`); + languageChanged(langCode); + loadTranslations(); + } else if (!availableLanguages.includes(newLangCode)) { + Logger.w("I18n", `Language "${newLangCode}" is not available`); + } + } + + // ------------------------------------------- + function loadTranslations() { + if (langCode === "") + return; + const filePath = `file://${Quickshell.shellDir}/Assets/Translations/${langCode}.json`; + fileView.path = filePath; + isLoaded = false; + } + + // ------------------------------------------- + // Check if a translation exists + function hasTranslation(key) { + if (!isLoaded) + return false; + + const keys = key.split("."); + var value = translations; + + for (var i = 0; i < keys.length; i++) { + if (value && typeof value === "object" && keys[i] in value) { + value = value[keys[i]]; + } else { + return false; + } + } + + return typeof value === "string"; + } + + // ------------------------------------------- + // Get all translation keys (useful for debugging) + function getAllKeys(obj, prefix) { + if (typeof obj === "undefined") + obj = translations; + if (typeof prefix === "undefined") + prefix = ""; + + var keys = []; + for (var key in (obj || {})) { + const value = obj[key]; + const fullKey = prefix ? `${prefix}.${key}` : key; + if (typeof value === "object" && value !== null) { + keys = keys.concat(getAllKeys(value, fullKey)); + } else if (typeof value === "string") { + keys.push(fullKey); + } + } + return keys; + } + + // ------------------------------------------- + // Reload translations (useful for development) + function reload() { + Logger.d("I18n", "Reloading translations"); + loadTranslations(); + } + + // ------------------------------------------- + // Main translation function + function tr(key, interpolations) { + if (typeof interpolations === "undefined") + interpolations = {}; + + if (!isLoaded) { + //Logger.d("I18n", "Translations not loaded yet") + return key; + } + + // Navigate nested keys (e.g., "menu.file.open") + const keys = key.split("."); + + // Look-up translation in the active language + var value = translations; + var notFound = false; + for (var i = 0; i < keys.length; i++) { + if (value && typeof value === "object" && keys[i] in value) { + value = value[keys[i]]; + } else { + Logger.d("I18n", `Translation key "${key}" not found at part "${keys[i]}"`); + Logger.d("I18n", `Available keys: ${Object.keys(value || {}).join(", ")}`); + notFound = true; + break; + } + } + + // Fallback to english if not found + if (notFound && availableLanguages.includes("en") && langCode !== "en") { + value = fallbackTranslations; + for (var i = 0; i < keys.length; i++) { + if (value && typeof value === "object" && keys[i] in value) { + value = value[keys[i]]; + } else { + // Indicate this key does not even exists in the english fallback + return `!!${key}!!`; + } + } + } else if (notFound) { + // No fallback available + return `!!${key}!!`; + } + + if (typeof value !== "string") { + Logger.d("I18n", `Translation key "${key}" is not a string`); + return key; + } + + // Handle interpolations (e.g., "Hello {name}!") + var result = value; + for (var placeholder in interpolations) { + const regex = new RegExp(`\\{${placeholder}\\}`, 'g'); + result = result.replace(regex, interpolations[placeholder]); + } + + return result; + } + + // ------------------------------------------- + // Plural translation function + function trp(key, count, interpolations) { + if (typeof interpolations === "undefined") { + interpolations = {}; + } + + // Use key for singular, key-plural for plural + const realKey = count === 1 ? key : `${key}-plural`; + + // Merge interpolations with count + var finalInterpolations = { + "count": count + }; + for (var prop in interpolations) { + finalInterpolations[prop] = interpolations[prop]; + } + + return tr(realKey, finalInterpolations); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Icons.qml b/arch/.config/quickshell/noctalia-shell/Commons/Icons.qml new file mode 100644 index 0000000..1e8752d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Icons.qml @@ -0,0 +1,85 @@ +pragma Singleton + +import QtQuick +import QtQuick.Controls +import Quickshell +import qs.Commons + +Singleton { + id: root + + // Expose the font family name for easy access + readonly property string fontFamily: currentFontLoader ? currentFontLoader.name : "" + readonly property string defaultIcon: IconsTabler.defaultIcon + readonly property var icons: IconsTabler.icons + readonly property var aliases: IconsTabler.aliases + readonly property string fontPath: "/Assets/Fonts/tabler/noctalia-tabler-icons.ttf" + + // Current active font loader + property FontLoader currentFontLoader: null + property int fontVersion: 0 + + // Create a unique cache-busting path + readonly property string cacheBustingPath: Quickshell.shellDir + fontPath + "?v=" + fontVersion + "&t=" + Date.now() + + // Signal emitted when font is reloaded + signal fontReloaded + + Component.onCompleted: { + Logger.i("Icons", "Service started"); + loadFontWithCacheBusting(); + } + + Connections { + target: Quickshell + function onReloadCompleted() { + Logger.d("Icons", "Quickshell reload completed - forcing font reload"); + reloadFont(); + } + } + + // --------------------------------------- + function get(iconName) { + // Check in aliases first + if (aliases[iconName] !== undefined) { + iconName = aliases[iconName]; + } + + // Find the appropriate codepoint + return icons[iconName]; + } + + function loadFontWithCacheBusting() { + Logger.d("Icons", "Loading font with cache busting"); + + // Destroy old loader first + if (currentFontLoader) { + currentFontLoader.destroy(); + currentFontLoader = null; + } + + // Create new loader with cache-busting URL + currentFontLoader = Qt.createQmlObject(` + import QtQuick + FontLoader { + source: "${cacheBustingPath}" + } + `, root, "dynamicFontLoader_" + fontVersion); + + // Connect to the new loader's status changes + currentFontLoader.statusChanged.connect(function () { + if (currentFontLoader.status === FontLoader.Ready) { + Logger.d("Icons", "Font loaded successfully:", currentFontLoader.name, "(version " + fontVersion + ")"); + fontReloaded(); + } else if (currentFontLoader.status === FontLoader.Error) { + Logger.e("Icons", "Font failed to load (version " + fontVersion + ")"); + } + }); + } + + function reloadFont() { + Logger.d("Icons", "Forcing font reload..."); + fontVersion++; + loadFontWithCacheBusting(); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/IconsTabler.qml b/arch/.config/quickshell/noctalia-shell/Commons/IconsTabler.qml new file mode 100644 index 0000000..5efc5f5 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/IconsTabler.qml @@ -0,0 +1,6204 @@ +pragma Singleton + +import QtQuick +import Quickshell + +Singleton { + id: root + + readonly property string defaultIcon: "skull" + + readonly property var aliases: { + "close": "x", + "check": "check", + "settings": "settings", + "refresh": "refresh", + "add": "plus", + "trash": "trash", + "menu": "menu-2", + "person": "user", + "folder-open": "folder-open", + "download": "download", + "toast-notice": "circle-check", + "toast-warning": "alert-circle", + "toast-error": "circle-x", + "question-mark": "question-mark", + "search": "search", + "warning": "exclamation-circle", + "stop": "player-stop-filled", + "busy": "hourglass-empty", + "media-pause": "player-pause-filled", + "media-play": "player-play-filled", + "media-prev": "player-skip-back-filled", + "media-next": "player-skip-forward-filled", + "download-speed": "download", + "upload-speed": "upload", + "cpu-intensive": "alert-octagon", + "cpu-usage": "brand-speedtest", + "cpu-temperature": "flame", + "gpu-temperature": "device-desktop", + "memory": "cpu", + "performance": "gauge", + "balanced": "scale", + "powersaver": "leaf", + "storage": "database", + "keyboard": "keyboard", + "shutdown": "power", + "lock": "lock", + "lock-pause": "lock-pause", + "logout": "logout", + "reboot": "refresh", + "suspend": "player-pause", + "hibernate": "zzz", + "nightlight-on": "moon", + "nightlight-off": "moon-off", + "nightlight-forced": "moon-stars", + "bell": "bell", + "bell-off": "bell-off", + "keep-awake-on": "mug", + "keep-awake-off": "mug-off", + "disc": "disc-filled", + "eye": "eye", + "pin": "pin", + "unpin": "pinned-off", + "image": "photo", + "dark-mode": "contrast-filled", + "camera-video": "video", + "wallpaper-selector": "library-photo", + "color-picker": "color-picker", + "chevron-left": "chevron-left", + "chevron-right": "chevron-right", + "chevron-up": "chevron-up", + "chevron-down": "chevron-down", + "caret-up": "caret-up-filled", + "caret-down": "caret-down-filled", + "caret-left": "caret-left-filled", + "caret-right": "caret-right-filled", + "star": "star", + "star-off": "star-off", + "battery-exclamation": "battery-exclamation", + "battery-charging": "battery-charging", + "battery-charging-2": "battery-charging-2", + "battery-4": "battery-4", + "battery-3": "battery-3", + "battery-2": "battery-2", + "battery-1": "battery-1", + "battery": "battery", + "battery-off": "battery-off", + "wifi-0": "wifi-0", + "wifi-1": "wifi-1", + "wifi-2": "wifi-2", + "wifi-off": "wifi-off", + "wifi": "wifi", + "microphone": "microphone", + "microphone-mute": "microphone-off", + "volume-mute": "volume-off", + "volume-x": "volume-3", + "volume-zero": "volume-3", + "volume-low": "volume-2", + "volume-high": "volume", + "weather-sun": "sun", + "weather-moon": "moon", + "weather-moon-stars": "moon-stars", + "weather-cloud-off": "cloud-off", + "weather-cloud": "cloud", + "weather-cloud-haze": "cloud-fog", + "weather-cloud-lightning": "cloud-bolt", + "weather-cloud-rain": "cloud-rain", + "weather-cloud-snow": "cloud-snow", + "weather-cloud-sun": "cloud-sun", + "brightness-low": "brightness-down-filled", + "brightness-high": "brightness-up-filled", + "settings-general": "adjustments-horizontal", + "settings-bar": "crop-16-9", + "settings-user-interface": "layout-board", + "settings-control-center": "adjustments-horizontal", + "settings-dock": "layout-bottombar", + "settings-launcher": "rocket", + "settings-audio": "device-speaker", + "settings-display": "device-desktop", + "settings-network": "circles-relation", + "settings-brightness": "brightness-up", + "settings-location": "world-pin", + "settings-color-scheme": "palette", + "settings-wallpaper": "paint", + "settings-wallpaper-selector": "library-photo", + "settings-hooks": "link", + "settings-notifications": "bell", + "settings-osd": "picture-in-picture", + "settings-about": "info-square-rounded", + "settings-idle": "moon", + "settings-lock-screen": "lock", + "settings-session-menu": "power", + "settings-system-monitor": "activity", + "bluetooth": "bluetooth", + "bt-device-generic": "bluetooth", + "bt-device-gamepad": "device-gamepad-2", + "bt-device-microphone": "microphone", + "bt-device-headset": "headset", + "bt-device-earbuds": "device-airpods", + "bt-device-headphones": "headphones", + "bt-device-mouse": "mouse-2", + "bt-device-keyboard": "bluetooth", + "bt-device-phone": "device-mobile", + "bt-device-watch": "device-watch", + "bt-device-speaker": "device-speaker", + "bt-device-tv": "device-tv", + "antenna-bars-1": "antenna-bars-1", + "antenna-bars-2": "antenna-bars-2", + "antenna-bars-3": "antenna-bars-3", + "antenna-bars-4": "antenna-bars-4", + "antenna-bars-5": "antenna-bars-5", + "antenna-bars-off": "antenna-bars-off", + "noctalia": "noctalia", + "hyprland": "hyprland", + "filepicker-folder": "folder", + "filepicker-refresh": "refresh", + "filepicker-close": "x", + "filepicker-arrow-left": "arrow-left", + "filepicker-arrow-up": "arrow-up", + "filepicker-home": "home", + "filepicker-layout-grid": "layout-grid", + "filepicker-list": "list", + "filepicker-search": "search", + "filepicker-x": "x", + "filepicker-photo": "photo", + "filepicker-check": "check", + "filepicker-file-text": "file-text", + "filepicker-video": "video", + "filepicker-music": "music", + "filepicker-archive": "archive", + "filepicker-table": "table", + "filepicker-presentation": "presentation", + "filepicker-code": "code", + "filepicker-settings": "settings", + "filepicker-file": "file", + "filepicker-text": "file-text", + "filepicker-eye": "eye", + "filepicker-eye-off": "eye-off", + "filepicker-folder-current": "checks", + "plugin": "plug-connected", + "info": "file-description", + "official-plugin": "shield-filled" + } + + // Fonts Codepoints - do not change! + // Some icons have been disabled because Qt's text rendering engine recognizes + // some ranges as special Unicode characters at a very low level. + // ex: fe00-fe2f + readonly property var icons: { + "123": "\u{f554}", + "360": "\u{f62f}", + "12-hours": "\u{fc53}", + "24-hours": "\u{f5e7}", + "2fa": "\u{eca0}", + "360-view": "\u{f566}", + "3d-cube-sphere": "\u{ecd7}", + "3d-cube-sphere-off": "\u{f3b5}", + "3d-rotate": "\u{f020}", + "a-b": "\u{ec36}", + "a-b-2": "\u{f25f}", + "a-b-off": "\u{f0a6}", + "abacus": "\u{f05c}", + "abacus-off": "\u{f3b6}", + "abc": "\u{f567}", + "access-point": "\u{ed1b}", + "access-point-off": "\u{ed1a}", + "accessible": "\u{eba9}", + "accessible-filled": "\u{f6ea}", + "accessible-off": "\u{f0a7}", + "activity": "\u{ed23}", + "activity-heartbeat": "\u{f0db}", + "ad": "\u{ea02}", + "ad-2": "\u{ef1f}", + "ad-circle": "\u{f79e}", + "ad-circle-filled": "\u{f7d3}", + "ad-circle-off": "\u{f79d}", + "ad-filled": "\u{f6eb}", + "ad-off": "\u{f3b7}", + "address-book": "\u{f021}", + "address-book-off": "\u{f3b8}", + "adjustments": "\u{ea03}", + "adjustments-alt": "\u{ec37}", + "adjustments-bolt": "\u{f7fb}", + "adjustments-cancel": "\u{f7fc}", + "adjustments-check": "\u{f7fd}", + "adjustments-code": "\u{f7fe}", + "adjustments-cog": "\u{f7ff}", + "adjustments-dollar": "\u{f800}", + "adjustments-down": "\u{f801}", + "adjustments-exclamation": "\u{f802}", + "adjustments-filled": "\u{f6ec}", + "adjustments-heart": "\u{f803}", + "adjustments-horizontal": "\u{ec38}", + "adjustments-minus": "\u{f804}", + "adjustments-off": "\u{f0a8}", + "adjustments-pause": "\u{f805}", + "adjustments-pin": "\u{f806}", + "adjustments-plus": "\u{f807}", + "adjustments-question": "\u{f808}", + "adjustments-search": "\u{f809}", + "adjustments-share": "\u{f80a}", + "adjustments-spark": "\u{ffbe}", + "adjustments-star": "\u{f80b}", + "adjustments-up": "\u{f80c}", + "adjustments-x": "\u{f80d}", + "aerial-lift": "\u{edfe}", + "aerial-lift-filled": "\u{10101}", + "affiliate": "\u{edff}", + "affiliate-filled": "\u{f6ed}", + "ai": "\u{fee7}", + "air-balloon": "\u{f4a6}", + "air-balloon-filled": "\u{10100}", + "air-conditioning": "\u{f3a2}", + "air-conditioning-disabled": "\u{f542}", + "air-traffic-control": "\u{fb01}", + "alarm": "\u{ea04}", + "alarm-average": "\u{fc9e}", + "alarm-filled": "\u{f709}", + "alarm-minus": "\u{f630}", + "alarm-minus-filled": "\u{f70a}", + "alarm-off": "\u{f0a9}", + "alarm-plus": "\u{f631}", + "alarm-plus-filled": "\u{f70b}", + "alarm-smoke": "\u{100b6}", + "alarm-snooze": "\u{f632}", + "alarm-snooze-filled": "\u{f70c}", + "album": "\u{f022}", + "album-off": "\u{f3b9}", + "alert-circle": "\u{ea05}", + "alert-circle-filled": "\u{f6ee}", + "alert-circle-off": "\u{fc65}", + "alert-hexagon": "\u{f80e}", + "alert-hexagon-filled": "\u{fa34}", + "alert-hexagon-off": "\u{fc66}", + "alert-octagon": "\u{ecc6}", + "alert-octagon-filled": "\u{f6ef}", + "alert-small": "\u{f80f}", + "alert-small-off": "\u{fc67}", + "alert-square": "\u{f811}", + "alert-square-filled": "\u{fa35}", + "alert-square-rounded": "\u{f810}", + "alert-square-rounded-filled": "\u{fa36}", + "alert-square-rounded-off": "\u{fc68}", + "alert-triangle": "\u{ea06}", + "alert-triangle-filled": "\u{f6f0}", + "alert-triangle-off": "\u{fc69}", + "alien": "\u{ebde}", + "alien-filled": "\u{f70d}", + "align-box-bottom-center": "\u{f530}", + "align-box-bottom-center-filled": "\u{f70e}", + "align-box-bottom-left": "\u{f531}", + "align-box-bottom-left-filled": "\u{f70f}", + "align-box-bottom-right": "\u{f532}", + "align-box-bottom-right-filled": "\u{f710}", + "align-box-center-bottom": "\u{facb}", + "align-box-center-middle": "\u{f79f}", + "align-box-center-middle-filled": "\u{f7d4}", + "align-box-center-stretch": "\u{facc}", + "align-box-center-top": "\u{facd}", + "align-box-left-bottom": "\u{f533}", + "align-box-left-bottom-filled": "\u{f711}", + "align-box-left-middle": "\u{f534}", + "align-box-left-middle-filled": "\u{f712}", + "align-box-left-stretch": "\u{face}", + "align-box-left-top": "\u{f535}", + "align-box-left-top-filled": "\u{f713}", + "align-box-right-bottom": "\u{f536}", + "align-box-right-bottom-filled": "\u{f714}", + "align-box-right-middle": "\u{f537}", + "align-box-right-middle-filled": "\u{f7d5}", + "align-box-right-stretch": "\u{facf}", + "align-box-right-top": "\u{f538}", + "align-box-right-top-filled": "\u{f715}", + "align-box-top-center": "\u{f539}", + "align-box-top-center-filled": "\u{f716}", + "align-box-top-left": "\u{f53a}", + "align-box-top-left-filled": "\u{f717}", + "align-box-top-right": "\u{f53b}", + "align-box-top-right-filled": "\u{f718}", + "align-center": "\u{ea07}", + "align-justified": "\u{ea08}", + "align-left": "\u{ea09}", + "align-left-2": "\u{ff00}", + "align-right": "\u{ea0a}", + //"align-right-2": "\u{feff}", + "alpha": "\u{f543}", + "alphabet-arabic": "\u{ff2f}", + "alphabet-bangla": "\u{ff2e}", + "alphabet-cyrillic": "\u{f1df}", + "alphabet-greek": "\u{f1e0}", + "alphabet-hebrew": "\u{ff2d}", + "alphabet-korean": "\u{ff2c}", + "alphabet-latin": "\u{f1e1}", + "alphabet-thai": "\u{ff2b}", + "alt": "\u{fc54}", + "ambulance": "\u{ebf5}", + "ampersand": "\u{f229}", + "analyze": "\u{f3a3}", + "analyze-filled": "\u{f719}", + "analyze-off": "\u{f3ba}", + "anchor": "\u{eb76}", + "anchor-off": "\u{f0f7}", + "angle": "\u{ef20}", + "ankh": "\u{f1cd}", + "antenna": "\u{f094}", + "antenna-bars-1": "\u{ecc7}", + "antenna-bars-2": "\u{ecc8}", + "antenna-bars-3": "\u{ecc9}", + "antenna-bars-4": "\u{ecca}", + "antenna-bars-5": "\u{eccb}", + "antenna-bars-off": "\u{f0aa}", + "antenna-off": "\u{f3bb}", + "aperture": "\u{eb58}", + "aperture-off": "\u{f3bc}", + "api": "\u{effd}", + "api-app": "\u{effc}", + "api-app-off": "\u{f0ab}", + "api-off": "\u{f0f8}", + "app-window": "\u{efe6}", + "app-window-filled": "\u{f71a}", + "apple": "\u{ef21}", + "apple-filled": "\u{10017}", + "apps": "\u{ebb6}", + "apps-filled": "\u{f6f1}", + "apps-off": "\u{f0ac}", + "archery-arrow": "\u{fc55}", + "archive": "\u{ea0b}", + "archive-filled": "\u{fa82}", + "archive-off": "\u{f0ad}", + "armchair": "\u{ef9e}", + "armchair-2": "\u{efe7}", + "armchair-2-off": "\u{f3bd}", + "armchair-off": "\u{f3be}", + "arrow-autofit-content": "\u{ef31}", + "arrow-autofit-content-filled": "\u{f6f2}", + "arrow-autofit-down": "\u{ef32}", + "arrow-autofit-down-filled": "\u{10113}", + "arrow-autofit-height": "\u{ef33}", + "arrow-autofit-height-filled": "\u{10112}", + "arrow-autofit-left": "\u{ef34}", + "arrow-autofit-left-filled": "\u{10111}", + "arrow-autofit-right": "\u{ef35}", + "arrow-autofit-right-filled": "\u{10110}", + "arrow-autofit-up": "\u{ef36}", + "arrow-autofit-up-filled": "\u{1010f}", + "arrow-autofit-width": "\u{ef37}", + "arrow-autofit-width-filled": "\u{1010e}", + "arrow-back": "\u{ea0c}", + "arrow-back-up": "\u{eb77}", + "arrow-back-up-double": "\u{f9ec}", + "arrow-badge-down": "\u{f60b}", + "arrow-badge-down-filled": "\u{f7d6}", + "arrow-badge-left": "\u{f60c}", + "arrow-badge-left-filled": "\u{f7d7}", + "arrow-badge-right": "\u{f60d}", + "arrow-badge-right-filled": "\u{f7d8}", + "arrow-badge-up": "\u{f60e}", + "arrow-badge-up-filled": "\u{f7d9}", + "arrow-bar-both": "\u{fadd}", + "arrow-bar-down": "\u{ea0d}", + "arrow-bar-left": "\u{ea0e}", + "arrow-bar-right": "\u{ea0f}", + "arrow-bar-to-down": "\u{ec88}", + "arrow-bar-to-down-dashed": "\u{10164}", + "arrow-bar-to-left": "\u{ec89}", + "arrow-bar-to-left-dashed": "\u{10163}", + "arrow-bar-to-right": "\u{ec8a}", + "arrow-bar-to-right-dashed": "\u{10162}", + "arrow-bar-to-up": "\u{ec8b}", + "arrow-bar-to-up-dashed": "\u{10161}", + "arrow-bar-up": "\u{ea10}", + "arrow-bear-left": "\u{f045}", + "arrow-bear-left-2": "\u{f044}", + "arrow-bear-right": "\u{f047}", + "arrow-bear-right-2": "\u{f046}", + "arrow-big-down": "\u{edda}", + "arrow-big-down-filled": "\u{f6c6}", + "arrow-big-down-line": "\u{efe8}", + "arrow-big-down-line-filled": "\u{f6c7}", + "arrow-big-down-lines": "\u{efe9}", + "arrow-big-down-lines-filled": "\u{f6c8}", + "arrow-big-left": "\u{eddb}", + "arrow-big-left-filled": "\u{f6c9}", + "arrow-big-left-line": "\u{efea}", + "arrow-big-left-line-filled": "\u{f6ca}", + "arrow-big-left-lines": "\u{efeb}", + "arrow-big-left-lines-filled": "\u{f6cb}", + "arrow-big-right": "\u{eddc}", + "arrow-big-right-filled": "\u{f6cc}", + "arrow-big-right-line": "\u{efec}", + "arrow-big-right-line-filled": "\u{f6cd}", + "arrow-big-right-lines": "\u{efed}", + "arrow-big-right-lines-filled": "\u{f6ce}", + "arrow-big-up": "\u{eddd}", + "arrow-big-up-filled": "\u{f6cf}", + "arrow-big-up-line": "\u{efee}", + "arrow-big-up-line-filled": "\u{f6d0}", + "arrow-big-up-lines": "\u{efef}", + "arrow-big-up-lines-filled": "\u{f6d1}", + "arrow-bounce": "\u{f3a4}", + "arrow-capsule": "\u{fade}", + "arrow-curve-left": "\u{f048}", + "arrow-curve-right": "\u{f049}", + "arrow-down": "\u{ea16}", + "arrow-down-bar": "\u{ed98}", + "arrow-down-circle": "\u{ea11}", + "arrow-down-circle-filled": "\u{1003b}", + "arrow-down-dashed": "\u{1006a}", + "arrow-down-from-arc": "\u{fd86}", + "arrow-down-left": "\u{ea13}", + "arrow-down-left-circle": "\u{ea12}", + "arrow-down-rhombus": "\u{f61d}", + "arrow-down-rhombus-filled": "\u{1003a}", + "arrow-down-right": "\u{ea15}", + "arrow-down-right-circle": "\u{ea14}", + "arrow-down-square": "\u{ed9a}", + "arrow-down-square-filled": "\u{10039}", + "arrow-down-tail": "\u{ed9b}", + "arrow-down-to-arc": "\u{fd87}", + "arrow-elbow-left": "\u{f9ed}", + "arrow-elbow-right": "\u{f9ee}", + "arrow-fork": "\u{f04a}", + "arrow-forward": "\u{ea17}", + "arrow-forward-up": "\u{eb78}", + "arrow-forward-up-double": "\u{f9ef}", + "arrow-guide": "\u{f22a}", + "arrow-guide-filled": "\u{10038}", + "arrow-iteration": "\u{f578}", + "arrow-left": "\u{ea19}", + "arrow-left-bar": "\u{ed9c}", + "arrow-left-circle": "\u{ea18}", + "arrow-left-circle-filled": "\u{10037}", + "arrow-left-dashed": "\u{10069}", + "arrow-left-from-arc": "\u{fd88}", + "arrow-left-rhombus": "\u{f61e}", + "arrow-left-rhombus-filled": "\u{10036}", + "arrow-left-right": "\u{f04b}", + "arrow-left-square": "\u{ed9d}", + "arrow-left-square-filled": "\u{10035}", + "arrow-left-tail": "\u{ed9e}", + "arrow-left-to-arc": "\u{fd89}", + "arrow-loop-left": "\u{ed9f}", + "arrow-loop-left-2": "\u{f04c}", + "arrow-loop-right": "\u{eda0}", + "arrow-loop-right-2": "\u{f04d}", + "arrow-merge": "\u{f04e}", + "arrow-merge-alt-left": "\u{fc9f}", + "arrow-merge-alt-right": "\u{fca0}", + "arrow-merge-both": "\u{f23b}", + "arrow-merge-left": "\u{f23c}", + "arrow-merge-right": "\u{f23d}", + "arrow-move-down": "\u{f2ba}", + "arrow-move-down-filled": "\u{10034}", + "arrow-move-left": "\u{f2bb}", + "arrow-move-left-filled": "\u{10033}", + "arrow-move-right": "\u{f2bc}", + "arrow-move-right-filled": "\u{10032}", + "arrow-move-up": "\u{f2bd}", + "arrow-move-up-filled": "\u{10031}", + "arrow-narrow-down": "\u{ea1a}", + "arrow-narrow-down-dashed": "\u{10068}", + "arrow-narrow-left": "\u{ea1b}", + "arrow-narrow-left-dashed": "\u{10067}", + "arrow-narrow-right": "\u{ea1c}", + "arrow-narrow-right-dashed": "\u{10066}", + "arrow-narrow-up": "\u{ea1d}", + "arrow-narrow-up-dashed": "\u{10065}", + "arrow-ramp-left": "\u{ed3c}", + "arrow-ramp-left-2": "\u{f04f}", + "arrow-ramp-left-3": "\u{f050}", + "arrow-ramp-right": "\u{ed3d}", + "arrow-ramp-right-2": "\u{f051}", + "arrow-ramp-right-3": "\u{f052}", + "arrow-right": "\u{ea1f}", + "arrow-right-bar": "\u{eda1}", + "arrow-right-circle": "\u{ea1e}", + "arrow-right-circle-filled": "\u{10030}", + "arrow-right-dashed": "\u{10064}", + "arrow-right-from-arc": "\u{fd8a}", + "arrow-right-rhombus": "\u{f61f}", + "arrow-right-rhombus-filled": "\u{1002f}", + "arrow-right-square": "\u{eda2}", + "arrow-right-square-filled": "\u{1002e}", + "arrow-right-tail": "\u{eda3}", + "arrow-right-to-arc": "\u{fd8b}", + "arrow-rotary-first-left": "\u{f053}", + "arrow-rotary-first-right": "\u{f054}", + "arrow-rotary-last-left": "\u{f055}", + "arrow-rotary-last-right": "\u{f056}", + "arrow-rotary-left": "\u{f057}", + "arrow-rotary-right": "\u{f058}", + "arrow-rotary-straight": "\u{f059}", + "arrow-roundabout-left": "\u{f22b}", + "arrow-roundabout-right": "\u{f22c}", + "arrow-sharp-turn-left": "\u{f05a}", + "arrow-sharp-turn-right": "\u{f05b}", + "arrow-up": "\u{ea25}", + "arrow-up-bar": "\u{eda4}", + "arrow-up-circle": "\u{ea20}", + "arrow-up-circle-filled": "\u{1002d}", + "arrow-up-dashed": "\u{10063}", + "arrow-up-from-arc": "\u{fd8c}", + "arrow-up-left": "\u{ea22}", + "arrow-up-left-circle": "\u{ea21}", + "arrow-up-rhombus": "\u{f620}", + "arrow-up-rhombus-filled": "\u{1002c}", + "arrow-up-right": "\u{ea24}", + "arrow-up-right-circle": "\u{ea23}", + "arrow-up-square": "\u{eda6}", + "arrow-up-square-filled": "\u{1002b}", + "arrow-up-tail": "\u{eda7}", + "arrow-up-to-arc": "\u{fd8d}", + "arrow-wave-left-down": "\u{eda8}", + "arrow-wave-left-up": "\u{eda9}", + "arrow-wave-right-down": "\u{edaa}", + "arrow-wave-right-up": "\u{edab}", + "arrow-zig-zag": "\u{f4a7}", + "arrows-cross": "\u{effe}", + "arrows-diagonal": "\u{ea27}", + "arrows-diagonal-2": "\u{ea26}", + "arrows-diagonal-minimize": "\u{ef39}", + "arrows-diagonal-minimize-2": "\u{ef38}", + "arrows-diff": "\u{f296}", + "arrows-double-ne-sw": "\u{edde}", + "arrows-double-nw-se": "\u{eddf}", + "arrows-double-se-nw": "\u{ede0}", + "arrows-double-sw-ne": "\u{ede1}", + "arrows-down": "\u{edad}", + "arrows-down-up": "\u{edac}", + "arrows-exchange": "\u{f1f4}", + "arrows-exchange-2": "\u{f1f3}", + "arrows-horizontal": "\u{eb59}", + "arrows-join": "\u{edaf}", + "arrows-join-2": "\u{edae}", + "arrows-left": "\u{edb1}", + "arrows-left-down": "\u{ee00}", + "arrows-left-right": "\u{edb0}", + "arrows-maximize": "\u{ea28}", + "arrows-minimize": "\u{ea29}", + "arrows-move": "\u{f22f}", + "arrows-move-horizontal": "\u{f22d}", + "arrows-move-vertical": "\u{f22e}", + "arrows-random": "\u{f095}", + "arrows-right": "\u{edb3}", + "arrows-right-down": "\u{ee01}", + "arrows-right-left": "\u{edb2}", + "arrows-shuffle": "\u{f000}", + "arrows-shuffle-2": "\u{efff}", + "arrows-sort": "\u{eb5a}", + "arrows-split": "\u{edb5}", + "arrows-split-2": "\u{edb4}", + "arrows-transfer-down": "\u{f2cc}", + "arrows-transfer-up": "\u{f2cd}", + "arrows-transfer-up-down": "\u{ffac}", + "arrows-up": "\u{edb7}", + "arrows-up-down": "\u{edb6}", + "arrows-up-left": "\u{ee02}", + "arrows-up-right": "\u{ee03}", + "arrows-vertical": "\u{eb5b}", + "artboard": "\u{ea2a}", + "artboard-filled": "\u{fa83}", + "artboard-off": "\u{f0ae}", + "article": "\u{f1e2}", + "article-filled": "\u{f7da}", + "article-off": "\u{f3bf}", + "aspect-ratio": "\u{ed30}", + "aspect-ratio-filled": "\u{f7db}", + "aspect-ratio-off": "\u{f0af}", + "assembly": "\u{f24d}", + "assembly-filled": "\u{fe9e}", + "assembly-off": "\u{f3c0}", + "asset": "\u{f1ce}", + "asset-filled": "\u{fe9d}", + "asterisk": "\u{efd5}", + "asterisk-simple": "\u{efd4}", + "at": "\u{ea2b}", + "at-off": "\u{f0b0}", + "atom": "\u{eb79}", + "atom-2": "\u{ebdf}", + "atom-2-filled": "\u{f71b}", + "atom-off": "\u{f0f9}", + "augmented-reality": "\u{f023}", + "augmented-reality-2": "\u{f37e}", + "augmented-reality-off": "\u{f3c1}", + "auth-2fa": "\u{eca0}", + "automatic-gearbox": "\u{fc89}", + "automatic-gearbox-filled": "\u{1002a}", + "automation": "\u{fef8}", + "avocado": "\u{fd8e}", + "award": "\u{ea2c}", + "award-filled": "\u{f71c}", + "award-off": "\u{f0fa}", + "axe": "\u{ef9f}", + "axis-x": "\u{ef45}", + "axis-y": "\u{ef46}", + "baby-bottle": "\u{f5d2}", + "baby-carriage": "\u{f05d}", + "baby-carriage-filled": "\u{fe9c}", + "background": "\u{fd2c}", + "backhoe": "\u{ed86}", + "backpack": "\u{ef47}", + "backpack-off": "\u{f3c2}", + "backslash": "\u{fab9}", + "backspace": "\u{ea2d}", + "backspace-filled": "\u{f7dc}", + "badge": "\u{efc2}", + "badge-2k": "\u{100b5}", + "badge-3d": "\u{f555}", + "badge-3d-filled": "\u{fe9b}", + "badge-3k": "\u{100b4}", + "badge-4k": "\u{f556}", + "badge-4k-filled": "\u{fe9a}", + "badge-5k": "\u{100b3}", + "badge-8k": "\u{f557}", + "badge-8k-filled": "\u{fe99}", + "badge-ad": "\u{f558}", + "badge-ad-filled": "\u{fe98}", + "badge-ad-off": "\u{fd8f}", + "badge-ar": "\u{f559}", + "badge-ar-filled": "\u{fe97}", + "badge-cc": "\u{f55a}", + "badge-cc-filled": "\u{fe96}", + "badge-filled": "\u{f667}", + "badge-hd": "\u{f55b}", + "badge-hd-filled": "\u{fe95}", + "badge-off": "\u{f0fb}", + "badge-sd": "\u{f55c}", + "badge-sd-filled": "\u{fe94}", + "badge-tm": "\u{f55d}", + "badge-tm-filled": "\u{fe93}", + "badge-vo": "\u{f55e}", + "badge-vo-filled": "\u{fe92}", + "badge-vr": "\u{f55f}", + "badge-vr-filled": "\u{fe91}", + "badge-wc": "\u{f560}", + "badge-wc-filled": "\u{fe90}", + "badges": "\u{efc3}", + "badges-filled": "\u{f7dd}", + "badges-off": "\u{f0fc}", + "baguette": "\u{f3a5}", + "ball-american-football": "\u{ee04}", + "ball-american-football-off": "\u{f3c3}", + "ball-baseball": "\u{efa0}", + "ball-basketball": "\u{ec28}", + "ball-bowling": "\u{ec29}", + "ball-football": "\u{ee06}", + "ball-football-off": "\u{ee05}", + "ball-tennis": "\u{ec2a}", + "ball-volleyball": "\u{ec2b}", + "balloon": "\u{ef3a}", + "balloon-filled": "\u{fa84}", + "balloon-off": "\u{f0fd}", + "ballpen": "\u{f06e}", + "ballpen-filled": "\u{fa85}", + "ballpen-off": "\u{f0b1}", + "ban": "\u{ea2e}", + "bandage": "\u{eb7a}", + "bandage-filled": "\u{f7de}", + "bandage-off": "\u{f3c4}", + "barbell": "\u{eff0}", + "barbell-filled": "\u{fe8f}", + "barbell-off": "\u{f0b2}", + "barcode": "\u{ebc6}", + "barcode-off": "\u{f0b3}", + "barrel": "\u{f0b4}", + "barrel-off": "\u{f0fe}", + "barrier-block": "\u{f00e}", + "barrier-block-filled": "\u{fe8e}", + "barrier-block-off": "\u{f0b5}", + "baseline": "\u{f024}", + "baseline-density-large": "\u{f9f0}", + "baseline-density-medium": "\u{f9f1}", + "baseline-density-small": "\u{f9f2}", + "basket": "\u{ebe1}", + "basket-bolt": "\u{fb43}", + "basket-cancel": "\u{fb44}", + "basket-check": "\u{fb45}", + "basket-code": "\u{fb46}", + "basket-cog": "\u{fb47}", + "basket-discount": "\u{fb48}", + "basket-dollar": "\u{fb49}", + "basket-down": "\u{fb4a}", + "basket-exclamation": "\u{fb4b}", + "basket-filled": "\u{f7df}", + "basket-heart": "\u{fb4c}", + "basket-minus": "\u{fb4d}", + "basket-off": "\u{f0b6}", + "basket-pause": "\u{fb4e}", + "basket-pin": "\u{fb4f}", + "basket-plus": "\u{fb50}", + "basket-question": "\u{fb51}", + "basket-search": "\u{fb52}", + "basket-share": "\u{fb53}", + "basket-star": "\u{fb54}", + "basket-up": "\u{fb55}", + "basket-x": "\u{fb56}", + "bat": "\u{f284}", + "bath": "\u{ef48}", + "bath-filled": "\u{f71d}", + "bath-off": "\u{f0ff}", + "battery": "\u{ea34}", + "battery-1": "\u{ea2f}", + "battery-1-filled": "\u{f71e}", + "battery-2": "\u{ea30}", + "battery-2-filled": "\u{f71f}", + "battery-3": "\u{ea31}", + "battery-3-filled": "\u{f720}", + "battery-4": "\u{ea32}", + "battery-4-filled": "\u{f721}", + "battery-automotive": "\u{ee07}", + "battery-automotive-filled": "\u{10029}", + "battery-charging": "\u{ea33}", + "battery-charging-2": "\u{ef3b}", + "battery-eco": "\u{ef3c}", + "battery-exclamation": "\u{ff1d}", + "battery-filled": "\u{f668}", + "battery-off": "\u{ed1c}", + "battery-spark": "\u{ffbd}", + "battery-vertical": "\u{ff13}", + "battery-vertical-1": "\u{ff1c}", + "battery-vertical-1-filled": "\u{10028}", + "battery-vertical-2": "\u{ff1b}", + "battery-vertical-2-filled": "\u{10027}", + "battery-vertical-3": "\u{ff1a}", + "battery-vertical-3-filled": "\u{10026}", + "battery-vertical-4": "\u{ff19}", + "battery-vertical-4-filled": "\u{10025}", + "battery-vertical-charging": "\u{ff17}", + "battery-vertical-charging-2": "\u{ff18}", + "battery-vertical-eco": "\u{ff16}", + "battery-vertical-exclamation": "\u{ff15}", + "battery-vertical-filled": "\u{10024}", + "battery-vertical-off": "\u{ff14}", + "beach": "\u{ef3d}", + "beach-off": "\u{f0b7}", + "bed": "\u{eb5c}", + "bed-filled": "\u{f7e0}", + "bed-flat": "\u{fca1}", + "bed-flat-filled": "\u{fe8d}", + "bed-off": "\u{f100}", + "beer": "\u{efa1}", + "beer-filled": "\u{f7e1}", + "beer-off": "\u{f101}", + "bell": "\u{ea35}", + "bell-bolt": "\u{f812}", + "bell-cancel": "\u{f813}", + "bell-check": "\u{f814}", + "bell-code": "\u{f815}", + "bell-cog": "\u{f816}", + "bell-dollar": "\u{f817}", + "bell-down": "\u{f818}", + "bell-exclamation": "\u{f819}", + "bell-filled": "\u{f669}", + "bell-heart": "\u{f81a}", + "bell-minus": "\u{ede2}", + "bell-minus-filled": "\u{f722}", + "bell-off": "\u{ece9}", + "bell-pause": "\u{f81b}", + "bell-pin": "\u{f81c}", + "bell-plus": "\u{ede3}", + "bell-plus-filled": "\u{f723}", + "bell-question": "\u{f81d}", + "bell-ringing": "\u{ed07}", + "bell-ringing-2": "\u{ede4}", + "bell-ringing-2-filled": "\u{f724}", + "bell-ringing-filled": "\u{f725}", + "bell-school": "\u{f05e}", + "bell-search": "\u{f81e}", + "bell-share": "\u{f81f}", + "bell-star": "\u{f820}", + "bell-up": "\u{f821}", + "bell-x": "\u{ede5}", + "bell-x-filled": "\u{f726}", + "bell-z": "\u{eff1}", + "bell-z-filled": "\u{f727}", + "beta": "\u{f544}", + "bible": "\u{efc4}", + "bike": "\u{ea36}", + "bike-filled": "\u{10023}", + "bike-off": "\u{f0b8}", + "binary": "\u{ee08}", + "binary-off": "\u{f3c5}", + "binary-tree": "\u{f5d4}", + "binary-tree-2": "\u{f5d3}", + "binary-tree-2-filled": "\u{ff65}", + "binary-tree-filled": "\u{ff64}", + "binoculars": "\u{fefe}", + "binoculars-filled": "\u{ff0b}", + "biohazard": "\u{ecb8}", + "biohazard-filled": "\u{fe8c}", + "biohazard-off": "\u{f0b9}", + "blade": "\u{f4bd}", + "blade-filled": "\u{f7e2}", + "bleach": "\u{f2f3}", + "bleach-chlorine": "\u{f2f0}", + "bleach-no-chlorine": "\u{f2f1}", + "bleach-off": "\u{f2f2}", + "blend-mode": "\u{feb0}", + "blender": "\u{fca2}", + "blender-filled": "\u{10022}", + "blob": "\u{feaf}", + "blob-filled": "\u{feb1}", + "blockquote": "\u{ee09}", + "blocks": "\u{100b2}", + "bluetooth": "\u{ea37}", + "bluetooth-connected": "\u{ecea}", + "bluetooth-off": "\u{eceb}", + "bluetooth-x": "\u{f081}", + "blur": "\u{ef8c}", + "blur-off": "\u{f3c6}", + "bmp": "\u{f3a6}", + "body-scan": "\u{fca3}", + "bold": "\u{eb7b}", + "bold-off": "\u{f0ba}", + "bolt": "\u{ea38}", + "bolt-filled": "\u{10021}", + "bolt-off": "\u{ecec}", + "bomb": "\u{f59c}", + "bomb-filled": "\u{fa86}", + "bone": "\u{edb8}", + "bone-filled": "\u{fe8b}", + "bone-off": "\u{f0bb}", + "bong": "\u{f3a7}", + "bong-filled": "\u{10020}", + "bong-off": "\u{f3c7}", + "book": "\u{ea39}", + "book-2": "\u{efc5}", + "book-download": "\u{f070}", + "book-filled": "\u{fa87}", + "book-off": "\u{f0bc}", + "book-upload": "\u{f071}", + "bookmark": "\u{ea3a}", + "bookmark-ai": "\u{fc8a}", + "bookmark-edit": "\u{fa5e}", + "bookmark-filled": "\u{fa88}", + "bookmark-minus": "\u{fa5f}", + "bookmark-off": "\u{eced}", + "bookmark-plus": "\u{fa60}", + "bookmark-question": "\u{fa61}", + "bookmarks": "\u{ed08}", + "bookmarks-filled": "\u{fb1f}", + "bookmarks-off": "\u{f0bd}", + "books": "\u{eff2}", + "books-off": "\u{f0be}", + "boom": "\u{fdbe}", + "boom-filled": "\u{fe8a}", + "border-all": "\u{ea3b}", + "border-bottom": "\u{ea3c}", + "border-bottom-plus": "\u{fdbd}", + "border-corner-ios": "\u{fd98}", + "border-corner-pill": "\u{fd62}", + "border-corner-rounded": "\u{fd63}", + "border-corner-square": "\u{fd64}", + "border-corners": "\u{f7a0}", + "border-horizontal": "\u{ea3d}", + "border-inner": "\u{ea3e}", + "border-left": "\u{ea3f}", + "border-left-plus": "\u{fdbc}", + "border-none": "\u{ea40}", + "border-outer": "\u{ea41}", + "border-radius": "\u{eb7c}", + "border-right": "\u{ea42}", + "border-right-plus": "\u{fdbb}", + "border-sides": "\u{f7a1}", + "border-style": "\u{ee0a}", + "border-style-2": "\u{ef22}", + "border-top": "\u{ea43}", + "border-top-plus": "\u{fdba}", + "border-vertical": "\u{ea44}", + "bottle": "\u{ef0b}", + "bottle-filled": "\u{fa89}", + "bottle-off": "\u{f3c8}", + "bounce-left": "\u{f59d}", + "bounce-left-filled": "\u{fb20}", + "bounce-right": "\u{f59e}", + "bounce-right-filled": "\u{fb21}", + "bow": "\u{f096}", + "bow-filled": "\u{fe89}", + "bowl": "\u{f4fa}", + "bowl-chopsticks": "\u{fd90}", + "bowl-chopsticks-filled": "\u{fe88}", + "bowl-filled": "\u{fb22}", + "bowl-spoon": "\u{fd91}", + "bowl-spoon-filled": "\u{fe87}", + "bowling": "\u{100b1}", + "box": "\u{ea45}", + "box-align-bottom": "\u{f2a8}", + "box-align-bottom-filled": "\u{fa8a}", + "box-align-bottom-left": "\u{f2ce}", + "box-align-bottom-left-filled": "\u{fa8b}", + "box-align-bottom-right": "\u{f2cf}", + "box-align-bottom-right-filled": "\u{fa8c}", + "box-align-left": "\u{f2a9}", + "box-align-left-filled": "\u{fa8d}", + "box-align-right": "\u{f2aa}", + "box-align-right-filled": "\u{fa8e}", + "box-align-top": "\u{f2ab}", + "box-align-top-filled": "\u{fa8f}", + "box-align-top-left": "\u{f2d0}", + "box-align-top-left-filled": "\u{fa90}", + "box-align-top-right": "\u{f2d1}", + "box-align-top-right-filled": "\u{fa91}", + "box-margin": "\u{ee0b}", + "box-model": "\u{ee0c}", + "box-model-2": "\u{ef23}", + "box-model-2-off": "\u{f3c9}", + "box-model-off": "\u{f3ca}", + "box-multiple": "\u{ee17}", + "box-multiple-0": "\u{ee0d}", + "box-multiple-1": "\u{ee0e}", + "box-multiple-2": "\u{ee0f}", + "box-multiple-3": "\u{ee10}", + "box-multiple-4": "\u{ee11}", + "box-multiple-5": "\u{ee12}", + "box-multiple-6": "\u{ee13}", + "box-multiple-7": "\u{ee14}", + "box-multiple-8": "\u{ee15}", + "box-multiple-9": "\u{ee16}", + "box-multiple-filled": "\u{1001f}", + "box-off": "\u{f102}", + "box-padding": "\u{ee18}", + "box-seam": "\u{eaff}", + "braces": "\u{ebcc}", + "braces-off": "\u{f0bf}", + "brackets": "\u{ebcd}", + "brackets-angle": "\u{fcb2}", + "brackets-angle-off": "\u{fcb1}", + "brackets-contain": "\u{f1e5}", + "brackets-contain-end": "\u{f1e3}", + "brackets-contain-start": "\u{f1e4}", + "brackets-off": "\u{f0c0}", + "braille": "\u{f545}", + "brain": "\u{f59f}", + "brand-4chan": "\u{f494}", + "brand-abstract": "\u{f495}", + "brand-adobe": "\u{f0dc}", + "brand-adobe-after-effect": "\u{ff2a}", + "brand-adobe-illustrator": "\u{ff29}", + "brand-adobe-indesign": "\u{ff28}", + "brand-adobe-photoshop": "\u{ff27}", + "brand-adobe-premier": "\u{ff26}", + "brand-adobe-xd": "\u{ff25}", + "brand-adonis-js": "\u{f496}", + "brand-airbnb": "\u{ed68}", + "brand-airtable": "\u{ef6a}", + "brand-algolia": "\u{f390}", + "brand-alipay": "\u{f7a2}", + "brand-alpine-js": "\u{f324}", + "brand-amazon": "\u{f230}", + "brand-amd": "\u{f653}", + "brand-amie": "\u{ffab}", + "brand-amigo": "\u{f5f9}", + "brand-among-us": "\u{f205}", + "brand-android": "\u{ec16}", + "brand-angular": "\u{ef6b}", + "brand-angular-filled": "\u{10095}", + "brand-ansible": "\u{fa70}", + "brand-ao3": "\u{f5e8}", + "brand-appgallery": "\u{f231}", + "brand-apple": "\u{ec17}", + "brand-apple-arcade": "\u{ed69}", + "brand-apple-filled": "\u{fd74}", + "brand-apple-news": "\u{ff24}", + "brand-apple-podcast": "\u{f1e6}", + "brand-appstore": "\u{ed24}", + "brand-arc": "\u{feae}", + "brand-asana": "\u{edc5}", + "brand-astro": "\u{fdb9}", + "brand-auth0": "\u{fcb3}", + "brand-aws": "\u{fa4c}", + "brand-azure": "\u{fa4d}", + "brand-backbone": "\u{f325}", + "brand-badoo": "\u{f206}", + "brand-baidu": "\u{f5e9}", + "brand-bandcamp": "\u{f207}", + "brand-bandlab": "\u{f5fa}", + "brand-beats": "\u{f208}", + "brand-bebo": "\u{ffaa}", + "brand-behance": "\u{ec6e}", + "brand-bilibili": "\u{f6d2}", + "brand-binance": "\u{f5a0}", + "brand-bing": "\u{edc6}", + "brand-bitbucket": "\u{edc7}", + "brand-bitbucket-filled": "\u{100cb}", + "brand-blackberry": "\u{f568}", + "brand-blender": "\u{f326}", + "brand-blogger": "\u{f35a}", + "brand-bluesky": "\u{fd75}", + "brand-booking": "\u{edc8}", + "brand-bootstrap": "\u{ef3e}", + "brand-bulma": "\u{f327}", + "brand-bumble": "\u{f5fb}", + "brand-bunpo": "\u{f4cf}", + "brand-c-sharp": "\u{f003}", + "brand-cake": "\u{f7a3}", + "brand-cakephp": "\u{f7af}", + "brand-campaignmonitor": "\u{f328}", + "brand-carbon": "\u{f348}", + "brand-cashapp": "\u{f391}", + "brand-chrome": "\u{ec18}", + "brand-cinema-4d": "\u{fa71}", + "brand-citymapper": "\u{f5fc}", + "brand-cloudflare": "\u{fa4e}", + "brand-codecov": "\u{f329}", + "brand-codepen": "\u{ec6f}", + "brand-codesandbox": "\u{ed6a}", + "brand-cohost": "\u{f5d5}", + "brand-coinbase": "\u{f209}", + "brand-comedy-central": "\u{f217}", + "brand-coreos": "\u{f5fd}", + "brand-couchdb": "\u{f60f}", + "brand-couchsurfing": "\u{f392}", + "brand-cpp": "\u{f5fe}", + "brand-craft": "\u{fa72}", + "brand-crunchbase": "\u{f7e3}", + "brand-css3": "\u{ed6b}", + "brand-ctemplar": "\u{f4d0}", + "brand-cucumber": "\u{ef6c}", + "brand-cupra": "\u{f4d1}", + "brand-cypress": "\u{f333}", + "brand-d3": "\u{f24e}", + "brand-databricks": "\u{fc41}", + "brand-days-counter": "\u{f4d2}", + "brand-dcos": "\u{f32a}", + "brand-debian": "\u{ef57}", + "brand-deezer": "\u{f78b}", + "brand-deliveroo": "\u{f4d3}", + "brand-deno": "\u{f24f}", + "brand-denodo": "\u{f610}", + "brand-deviantart": "\u{ecfb}", + "brand-digg": "\u{fa73}", + "brand-dingtalk": "\u{f5ea}", + "brand-discord": "\u{ece3}", + "brand-discord-filled": "\u{f7e4}", + "brand-disney": "\u{f20a}", + "brand-disqus": "\u{edc9}", + "brand-django": "\u{f349}", + "brand-docker": "\u{edca}", + "brand-doctrine": "\u{ef6d}", + "brand-dolby-digital": "\u{f4d4}", + "brand-douban": "\u{f5ff}", + "brand-dribbble": "\u{ec19}", + "brand-dribbble-filled": "\u{f7e5}", + "brand-dropbox": "\u{1018a}", + "brand-drops": "\u{f4d5}", + "brand-drupal": "\u{f393}", + "brand-edge": "\u{ecfc}", + "brand-elastic": "\u{f611}", + "brand-electronic-arts": "\u{fa74}", + "brand-ember": "\u{f497}", + "brand-envato": "\u{f394}", + "brand-etsy": "\u{f654}", + "brand-evernote": "\u{f600}", + "brand-facebook": "\u{ec1a}", + "brand-facebook-filled": "\u{f7e6}", + "brand-feedly": "\u{fa75}", + "brand-figma": "\u{ec93}", + "brand-filezilla": "\u{fa76}", + "brand-finder": "\u{f218}", + "brand-firebase": "\u{ef6e}", + "brand-firefox": "\u{ecfd}", + "brand-fiverr": "\u{f7a4}", + "brand-flickr": "\u{ecfe}", + "brand-flightradar24": "\u{f4d6}", + "brand-flipboard": "\u{f20b}", + "brand-flutter": "\u{f395}", + "brand-fortnite": "\u{f260}", + "brand-foursquare": "\u{ecff}", + "brand-framer": "\u{ec1b}", + "brand-framer-motion": "\u{f78c}", + "brand-funimation": "\u{f655}", + "brand-gatsby": "\u{f396}", + "brand-git": "\u{ef6f}", + "brand-github": "\u{ec1c}", + "brand-github-copilot": "\u{f4a8}", + "brand-github-filled": "\u{f7e7}", + "brand-gitlab": "\u{ec1d}", + "brand-gmail": "\u{efa2}", + "brand-golang": "\u{f78d}", + "brand-google": "\u{ec1f}", + "brand-google-analytics": "\u{edcb}", + "brand-google-big-query": "\u{f612}", + "brand-google-drive": "\u{ec1e}", + "brand-google-filled": "\u{fd1a}", + "brand-google-fit": "\u{f297}", + "brand-google-home": "\u{f601}", + "brand-google-maps": "\u{fa4f}", + "brand-google-one": "\u{f232}", + "brand-google-photos": "\u{f20c}", + "brand-google-play": "\u{ed25}", + "brand-google-podcasts": "\u{f656}", + "brand-grammarly": "\u{f32b}", + "brand-graphql": "\u{f32c}", + "brand-gravatar": "\u{edcc}", + "brand-grindr": "\u{f20d}", + "brand-guardian": "\u{f4fb}", + "brand-gumroad": "\u{f5d6}", + "brand-hackerrank": "\u{ff23}", + "brand-hbo": "\u{f657}", + "brand-headlessui": "\u{f32d}", + "brand-hexo": "\u{fa50}", + "brand-hipchat": "\u{edcd}", + "brand-html5": "\u{ed6c}", + "brand-inertia": "\u{f34a}", + "brand-instagram": "\u{ec20}", + "brand-instagram-filled": "\u{10094}", + "brand-intercom": "\u{f1cf}", + "brand-itch": "\u{fa22}", + "brand-javascript": "\u{ef0c}", + "brand-juejin": "\u{f7b0}", + "brand-kako-talk": "\u{fd2d}", + "brand-kbin": "\u{fad0}", + "brand-kick": "\u{fa23}", + "brand-kick-filled": "\u{10093}", + "brand-kickstarter": "\u{edce}", + "brand-kotlin": "\u{ed6d}", + "brand-laravel": "\u{f34b}", + "brand-lastfm": "\u{f001}", + "brand-leetcode": "\u{fa51}", + "brand-letterboxd": "\u{fa24}", + "brand-line": "\u{f7e8}", + "brand-linkedin": "\u{ec8c}", + "brand-linkedin-filled": "\u{10092}", + "brand-linktree": "\u{f1e7}", + "brand-linqpad": "\u{f562}", + "brand-livewire": "\u{fd76}", + "brand-loom": "\u{ef70}", + "brand-mailgun": "\u{f32e}", + "brand-mantine": "\u{f32f}", + "brand-mastercard": "\u{ef49}", + "brand-mastodon": "\u{f250}", + "brand-matrix": "\u{f5eb}", + "brand-mcdonalds": "\u{f251}", + "brand-medium": "\u{ec70}", + "brand-meetup": "\u{fc6a}", + "brand-mercedes": "\u{f072}", + "brand-messenger": "\u{ec71}", + "brand-messenger-filled": "\u{100a7}", + "brand-meta": "\u{efb0}", + "brand-metabrainz": "\u{ff12}", + "brand-minecraft": "\u{faef}", + "brand-miniprogram": "\u{f602}", + "brand-mixpanel": "\u{f397}", + "brand-monday": "\u{f219}", + "brand-mongodb": "\u{f613}", + "brand-my-oppo": "\u{f4d7}", + "brand-mysql": "\u{f614}", + "brand-national-geographic": "\u{f603}", + "brand-nem": "\u{f5a1}", + "brand-netbeans": "\u{ef71}", + "brand-netease-music": "\u{f604}", + "brand-netflix": "\u{edcf}", + "brand-nexo": "\u{f5a2}", + "brand-nextcloud": "\u{f4d8}", + "brand-nextjs": "\u{f0dd}", + "brand-nodejs": "\u{fae0}", + "brand-nord-vpn": "\u{f37f}", + "brand-notion": "\u{ef7b}", + "brand-npm": "\u{f569}", + "brand-nuxt": "\u{f0de}", + "brand-nytimes": "\u{ef8d}", + "brand-oauth": "\u{fa52}", + "brand-office": "\u{f398}", + "brand-ok-ru": "\u{f399}", + "brand-onedrive": "\u{f5d7}", + "brand-onlyfans": "\u{f605}", + "brand-open-source": "\u{edd0}", + "brand-open-source-filled": "\u{10091}", + "brand-openai": "\u{f78e}", + "brand-openvpn": "\u{f39a}", + "brand-opera": "\u{ec21}", + "brand-opera-filled": "\u{10090}", + "brand-pagekit": "\u{edd1}", + "brand-parsinta": "\u{fc42}", + "brand-patreon": "\u{edd2}", + "brand-patreon-filled": "\u{fcff}", + "brand-paypal": "\u{ec22}", + "brand-paypal-filled": "\u{f7e9}", + "brand-paypay": "\u{f5ec}", + "brand-peanut": "\u{f39b}", + "brand-pepsi": "\u{f261}", + "brand-php": "\u{ef72}", + "brand-picsart": "\u{f4d9}", + "brand-pinterest": "\u{ec8d}", + "brand-pinterest-filled": "\u{1008f}", + "brand-planetscale": "\u{f78f}", + "brand-pnpm": "\u{fd77}", + "brand-pocket": "\u{ed00}", + "brand-polymer": "\u{f498}", + "brand-powershell": "\u{f5ed}", + "brand-printables": "\u{fd1b}", + "brand-prisma": "\u{f499}", + "brand-producthunt": "\u{edd3}", + "brand-pushbullet": "\u{f330}", + "brand-pushover": "\u{f20e}", + "brand-python": "\u{ed01}", + "brand-qq": "\u{f606}", + "brand-radix-ui": "\u{f790}", + "brand-react": "\u{f34c}", + "brand-react-native": "\u{ef73}", + "brand-reason": "\u{f49a}", + "brand-reddit": "\u{ec8e}", + "brand-redhat": "\u{f331}", + "brand-redux": "\u{f3a8}", + "brand-revolut": "\u{f4da}", + "brand-rumble": "\u{fad1}", + "brand-rust": "\u{fa53}", + "brand-safari": "\u{ec23}", + "brand-samsungpass": "\u{f4db}", + "brand-sass": "\u{edd4}", + "brand-sentry": "\u{edd5}", + "brand-sharik": "\u{f4dc}", + "brand-shazam": "\u{edd6}", + "brand-shopee": "\u{f252}", + "brand-sketch": "\u{ec24}", + "brand-sketch-filled": "\u{1008e}", + "brand-skype": "\u{ed02}", + "brand-slack": "\u{ec72}", + "brand-snapchat": "\u{ec25}", + "brand-snapchat-filled": "\u{1008d}", + "brand-snapseed": "\u{f253}", + "brand-snowflake": "\u{f615}", + "brand-socket-io": "\u{f49b}", + "brand-solidjs": "\u{f5ee}", + "brand-soundcloud": "\u{ed6e}", + "brand-spacehey": "\u{f4fc}", + "brand-speedtest": "\u{fa77}", + "brand-spotify": "\u{ed03}", + "brand-spotify-filled": "\u{fe86}", + "brand-stackoverflow": "\u{ef58}", + "brand-stackshare": "\u{f607}", + "brand-steam": "\u{ed6f}", + "brand-steam-filled": "\u{1008c}", + "brand-stocktwits": "\u{fd78}", + "brand-storj": "\u{fa54}", + "brand-storybook": "\u{f332}", + "brand-storytel": "\u{f608}", + "brand-strava": "\u{f254}", + "brand-stripe": "\u{edd7}", + "brand-stripe-filled": "\u{1008b}", + "brand-sublime-text": "\u{ef74}", + "brand-sugarizer": "\u{f7a5}", + "brand-supabase": "\u{f6d3}", + "brand-superhuman": "\u{f50c}", + "brand-supernova": "\u{f49c}", + "brand-surfshark": "\u{f255}", + "brand-svelte": "\u{f0df}", + "brand-swift": "\u{fa55}", + "brand-symfony": "\u{f616}", + "brand-tabler": "\u{ec8f}", + "brand-tabler-filled": "\u{1008a}", + "brand-tailwind": "\u{eca1}", + "brand-taobao": "\u{f5ef}", + "brand-teams": "\u{fadf}", + "brand-ted": "\u{f658}", + "brand-telegram": "\u{ec26}", + "brand-terraform": "\u{fa56}", + "brand-tesla": "\u{10099}", + "brand-tether": "\u{f5a3}", + "brand-thingiverse": "\u{fd1c}", + "brand-threads": "\u{fb02}", + "brand-threejs": "\u{f5f0}", + "brand-tidal": "\u{ed70}", + "brand-tiktok": "\u{ec73}", + "brand-tiktok-filled": "\u{f7ea}", + "brand-tinder": "\u{ed71}", + "brand-tinder-filled": "\u{10089}", + "brand-topbuzz": "\u{f50d}", + "brand-torchain": "\u{f5a4}", + "brand-toyota": "\u{f262}", + "brand-trello": "\u{f39d}", + "brand-tripadvisor": "\u{f002}", + "brand-tumblr": "\u{ed04}", + "brand-tumblr-filled": "\u{10088}", + "brand-twilio": "\u{f617}", + "brand-twitch": "\u{ed05}", + "brand-twitter": "\u{ec27}", + "brand-twitter-filled": "\u{f7eb}", + "brand-typescript": "\u{f5f1}", + "brand-uber": "\u{ef75}", + "brand-ubuntu": "\u{ef59}", + "brand-unity": "\u{f49d}", + "brand-unsplash": "\u{edd8}", + "brand-upwork": "\u{f39e}", + "brand-valorant": "\u{f39f}", + "brand-vercel": "\u{ef24}", + "brand-vercel-filled": "\u{10087}", + "brand-vimeo": "\u{ed06}", + "brand-vimeo-filled": "\u{10086}", + "brand-vinted": "\u{f20f}", + "brand-visa": "\u{f380}", + "brand-visual-studio": "\u{ef76}", + "brand-vite": "\u{f5f2}", + "brand-vivaldi": "\u{f210}", + "brand-vk": "\u{ed72}", + "brand-vlc": "\u{fa78}", + "brand-volkswagen": "\u{f50e}", + "brand-vsco": "\u{f334}", + "brand-vscode": "\u{f3a0}", + "brand-vue": "\u{f0e0}", + "brand-walmart": "\u{f211}", + "brand-waze": "\u{f5d8}", + "brand-webflow": "\u{f2d2}", + "brand-wechat": "\u{f5f3}", + "brand-weibo": "\u{f609}", + "brand-weibo-filled": "\u{100a6}", + "brand-whatsapp": "\u{ec74}", + "brand-whatsapp-filled": "\u{10085}", + "brand-wikipedia": "\u{fa79}", + "brand-windows": "\u{ecd8}", + "brand-windows-filled": "\u{10084}", + "brand-windy": "\u{f4dd}", + "brand-wish": "\u{f212}", + "brand-wix": "\u{f3a1}", + "brand-wordpress": "\u{f2d3}", + "brand-x": "\u{fc0f}", + "brand-x-filled": "\u{fc21}", + "brand-xamarin": "\u{fa7a}", + "brand-xbox": "\u{f298}", + "brand-xdeep": "\u{fc10}", + "brand-xing": "\u{f21a}", + "brand-yahoo": "\u{ed73}", + "brand-yandex": "\u{fae1}", + "brand-yarn": "\u{fd79}", + "brand-yatse": "\u{f213}", + "brand-ycombinator": "\u{edd9}", + "brand-youtube": "\u{ec90}", + "brand-youtube-filled": "\u{fc22}", + "brand-youtube-kids": "\u{f214}", + "brand-zalando": "\u{f49e}", + "brand-zapier": "\u{f49f}", + "brand-zeit": "\u{f335}", + "brand-zhihu": "\u{f60a}", + "brand-zoom": "\u{f215}", + "brand-zulip": "\u{f4de}", + "brand-zwift": "\u{f216}", + "bread": "\u{efa3}", + "bread-filled": "\u{fe85}", + "bread-off": "\u{f3cb}", + "briefcase": "\u{ea46}", + "briefcase-2": "\u{fb03}", + "briefcase-2-filled": "\u{fe84}", + "briefcase-filled": "\u{fd00}", + "briefcase-off": "\u{f3cc}", + "brightness": "\u{eb7f}", + "brightness-2": "\u{ee19}", + "brightness-auto": "\u{fd99}", + "brightness-auto-filled": "\u{fe83}", + "brightness-down": "\u{eb7d}", + "brightness-down-filled": "\u{fb23}", + "brightness-filled": "\u{fe82}", + "brightness-half": "\u{ee1a}", + "brightness-off": "\u{f3cd}", + "brightness-up": "\u{eb7e}", + "brightness-up-filled": "\u{fb24}", + "broadcast": "\u{f1e9}", + "broadcast-off": "\u{f1e8}", + "browser": "\u{ebb7}", + "browser-check": "\u{efd6}", + "browser-maximize": "\u{100b0}", + "browser-minus": "\u{100af}", + "browser-off": "\u{f0c1}", + "browser-plus": "\u{efd7}", + "browser-share": "\u{100ae}", + "browser-x": "\u{efd8}", + "brush": "\u{ebb8}", + "brush-off": "\u{f0c2}", + "bubble": "\u{feba}", + "bubble-filled": "\u{fec3}", + "bubble-minus": "\u{febe}", + "bubble-plus": "\u{febd}", + "bubble-tea": "\u{ff51}", + "bubble-tea-2": "\u{ff52}", + "bubble-text": "\u{febc}", + "bubble-text-filled": "\u{100a5}", + "bubble-x": "\u{febb}", + "bucket": "\u{ea47}", + "bucket-droplet": "\u{f56a}", + "bucket-off": "\u{f103}", + "bug": "\u{ea48}", + "bug-filled": "\u{fd01}", + "bug-off": "\u{f0c3}", + "building": "\u{ea4f}", + "building-airport": "\u{ffa9}", + "building-arch": "\u{ea49}", + "building-bank": "\u{ebe2}", + "building-bridge": "\u{ea4b}", + "building-bridge-2": "\u{ea4a}", + "building-bridge-2-filled": "\u{10189}", + "building-broadcast-tower": "\u{f4be}", + "building-broadcast-tower-filled": "\u{fe81}", + "building-burj-al-arab": "\u{ff50}", + "building-carousel": "\u{ed87}", + "building-castle": "\u{ed88}", + "building-church": "\u{ea4c}", + "building-circus": "\u{f4bf}", + "building-cog": "\u{10062}", + "building-community": "\u{ebf6}", + "building-cottage": "\u{ee1b}", + "building-estate": "\u{f5a5}", + "building-factory": "\u{ee1c}", + "building-factory-2": "\u{f082}", + "building-fortress": "\u{ed89}", + "building-hospital": "\u{ea4d}", + "building-lighthouse": "\u{ed8a}", + "building-minus": "\u{10061}", + "building-monument": "\u{ed26}", + "building-mosque": "\u{fa57}", + "building-off": "\u{fefd}", + "building-pavilion": "\u{ebf7}", + "building-plus": "\u{10060}", + "building-skyscraper": "\u{ec39}", + "building-stadium": "\u{f641}", + "building-store": "\u{ea4e}", + "building-tunnel": "\u{f5a6}", + "building-warehouse": "\u{ebe3}", + "building-wind-turbine": "\u{f4c0}", + "buildings": "\u{ff40}", + "bulb": "\u{ea51}", + "bulb-filled": "\u{f66a}", + "bulb-off": "\u{ea50}", + "bulldozer": "\u{ee1d}", + "burger": "\u{fcb4}", + "bus": "\u{ebe4}", + "bus-filled": "\u{100ff}", + "bus-off": "\u{f3ce}", + "bus-stop": "\u{f2d4}", + "businessplan": "\u{ee1e}", + "butterfly": "\u{efd9}", + "butterfly-filled": "\u{10016}", + "cactus": "\u{f21b}", + "cactus-filled": "\u{fb25}", + "cactus-off": "\u{f3cf}", + "cake": "\u{f00f}", + "cake-off": "\u{f104}", + "cake-roll": "\u{100bd}", + "calculator": "\u{eb80}", + "calculator-filled": "\u{fb26}", + "calculator-off": "\u{f0c4}", + "calendar": "\u{ea53}", + "calendar-bolt": "\u{f822}", + "calendar-cancel": "\u{f823}", + "calendar-check": "\u{f824}", + "calendar-clock": "\u{fd2e}", + "calendar-code": "\u{f825}", + "calendar-cog": "\u{f826}", + "calendar-dollar": "\u{f827}", + "calendar-dot": "\u{fd3e}", + "calendar-down": "\u{f828}", + "calendar-due": "\u{f621}", + "calendar-event": "\u{ea52}", + "calendar-event-filled": "\u{100b9}", + "calendar-exclamation": "\u{f829}", + "calendar-filled": "\u{fb27}", + "calendar-heart": "\u{f82a}", + "calendar-minus": "\u{ebb9}", + "calendar-month": "\u{fd2f}", + "calendar-month-filled": "\u{100b8}", + "calendar-off": "\u{ee1f}", + "calendar-pause": "\u{f82b}", + "calendar-pin": "\u{f82c}", + "calendar-plus": "\u{ebba}", + "calendar-question": "\u{f82d}", + "calendar-repeat": "\u{fad2}", + "calendar-sad": "\u{fd1d}", + "calendar-search": "\u{f82e}", + "calendar-share": "\u{f82f}", + "calendar-smile": "\u{fd1e}", + "calendar-star": "\u{f830}", + "calendar-stats": "\u{ee20}", + "calendar-time": "\u{ee21}", + "calendar-up": "\u{f831}", + "calendar-user": "\u{fd1f}", + "calendar-week": "\u{fd30}", + "calendar-week-filled": "\u{100b7}", + "calendar-x": "\u{f832}", + "camera": "\u{ea54}", + "camera-ai": "\u{ffa8}", + "camera-bitcoin": "\u{ffa7}", + "camera-bolt": "\u{f833}", + "camera-cancel": "\u{f834}", + "camera-check": "\u{f835}", + "camera-code": "\u{f836}", + "camera-cog": "\u{f837}", + "camera-dollar": "\u{f838}", + "camera-down": "\u{f839}", + "camera-exclamation": "\u{f83a}", + "camera-filled": "\u{fa37}", + "camera-heart": "\u{f83b}", + "camera-minus": "\u{ec3a}", + "camera-moon": "\u{ffa6}", + "camera-off": "\u{ecee}", + "camera-pause": "\u{f83c}", + "camera-pin": "\u{f83d}", + "camera-plus": "\u{ec3b}", + "camera-question": "\u{f83e}", + "camera-rotate": "\u{ee22}", + "camera-search": "\u{f83f}", + "camera-selfie": "\u{ee23}", + "camera-share": "\u{f840}", + "camera-spark": "\u{ffbc}", + "camera-star": "\u{f841}", + "camera-up": "\u{f842}", + "camera-x": "\u{f843}", + "camper": "\u{fa25}", + "campfire": "\u{f5a7}", + "campfire-filled": "\u{fb28}", + "cancel": "\u{ff11}", + "candle": "\u{efc6}", + "candle-filled": "\u{fc23}", + "candy": "\u{ef0d}", + "candy-off": "\u{f0c5}", + "cane": "\u{f50f}", + "cannabis": "\u{f4c1}", + "cannabis-filled": "\u{10015}", + "cap-projecting": "\u{ff22}", + "cap-rounded": "\u{ff21}", + "cap-straight": "\u{ff20}", + "capsule": "\u{fae3}", + "capsule-filled": "\u{fc24}", + "capsule-horizontal": "\u{fae2}", + "capsule-horizontal-filled": "\u{fc25}", + "capture": "\u{ec3c}", + "capture-filled": "\u{fb29}", + "capture-off": "\u{f0c6}", + "car": "\u{ebbb}", + "car-4wd": "\u{fdb8}", + "car-4wd-filled": "\u{1001e}", + "car-crane": "\u{ef25}", + "car-crane-filled": "\u{100fe}", + "car-crash": "\u{efa4}", + "car-fan": "\u{fdb3}", + "car-fan-1": "\u{fdb7}", + "car-fan-2": "\u{fdb6}", + "car-fan-3": "\u{fdb5}", + "car-fan-auto": "\u{fdb4}", + "car-fan-filled": "\u{1001d}", + "car-filled": "\u{1004c}", + "car-garage": "\u{fc77}", + "car-off": "\u{f0c7}", + "car-suv": "\u{fc8b}", + "car-suv-filled": "\u{1004d}", + "car-turbine": "\u{f4fd}", + "carambola": "\u{feb9}", + "carambola-filled": "\u{10014}", + "caravan": "\u{ec7c}", + "caravan-filled": "\u{100fd}", + "cardboards": "\u{ed74}", + "cardboards-filled": "\u{1001c}", + "cardboards-off": "\u{f0c8}", + "cards": "\u{f510}", + "cards-filled": "\u{fc26}", + "caret-down": "\u{eb5d}", + "caret-down-filled": "\u{fb2a}", + "caret-left": "\u{eb5e}", + "caret-left-filled": "\u{fb2b}", + "caret-left-right": "\u{fc43}", + "caret-left-right-filled": "\u{fd02}", + "caret-right": "\u{eb5f}", + "caret-right-filled": "\u{fb2c}", + "caret-up": "\u{eb60}", + "caret-up-down": "\u{fc44}", + "caret-up-down-filled": "\u{fd03}", + "caret-up-filled": "\u{fb2d}", + "carousel-horizontal": "\u{f659}", + "carousel-horizontal-filled": "\u{fa92}", + "carousel-vertical": "\u{f65a}", + "carousel-vertical-filled": "\u{fa93}", + "carrot": "\u{f21c}", + "carrot-off": "\u{f3d0}", + "cash": "\u{ea55}", + "cash-banknote": "\u{ee25}", + "cash-banknote-edit": "\u{10149}", + "cash-banknote-filled": "\u{fe80}", + "cash-banknote-heart": "\u{10148}", + "cash-banknote-minus": "\u{10147}", + "cash-banknote-move": "\u{10145}", + "cash-banknote-move-back": "\u{10146}", + "cash-banknote-off": "\u{ee24}", + "cash-banknote-plus": "\u{10144}", + "cash-edit": "\u{10143}", + "cash-heart": "\u{10142}", + "cash-minus": "\u{10141}", + "cash-move": "\u{1013f}", + "cash-move-back": "\u{10140}", + "cash-off": "\u{f105}", + "cash-plus": "\u{1013e}", + "cash-register": "\u{fee6}", + "cast": "\u{ea56}", + "cast-off": "\u{f0c9}", + "cat": "\u{f65b}", + "category": "\u{f1f6}", + "category-2": "\u{f1f5}", + "category-filled": "\u{fb2e}", + "category-minus": "\u{fd20}", + "category-plus": "\u{fd21}", + "ce": "\u{ed75}", + "ce-off": "\u{f0ca}", + "cell": "\u{f05f}", + "cell-signal-1": "\u{f083}", + "cell-signal-2": "\u{f084}", + "cell-signal-3": "\u{f085}", + "cell-signal-4": "\u{f086}", + "cell-signal-5": "\u{f087}", + "cell-signal-off": "\u{f088}", + "certificate": "\u{ed76}", + "certificate-2": "\u{f073}", + "certificate-2-off": "\u{f0cb}", + "certificate-off": "\u{f0cc}", + "chair-director": "\u{f2d5}", + "chalkboard": "\u{f34d}", + "chalkboard-off": "\u{f3d1}", + "chalkboard-teacher": "\u{10160}", + "charging-pile": "\u{ee26}", + "charging-pile-filled": "\u{1001b}", + "chart-arcs": "\u{ee28}", + "chart-arcs-3": "\u{ee27}", + "chart-area": "\u{ea58}", + "chart-area-filled": "\u{f66b}", + "chart-area-line": "\u{ea57}", + "chart-area-line-filled": "\u{f66c}", + "chart-arrows": "\u{ee2a}", + "chart-arrows-vertical": "\u{ee29}", + "chart-bar": "\u{ea59}", + "chart-bar-off": "\u{f3d2}", + "chart-bar-popular": "\u{fef7}", + "chart-bubble": "\u{ec75}", + "chart-bubble-filled": "\u{f66d}", + "chart-candle": "\u{ea5a}", + "chart-candle-filled": "\u{f66e}", + "chart-circles": "\u{ee2b}", + "chart-cohort": "\u{fef6}", + "chart-column": "\u{ffa5}", + "chart-covariate": "\u{ffa4}", + "chart-donut": "\u{ea5b}", + "chart-donut-2": "\u{ee2c}", + "chart-donut-3": "\u{ee2d}", + "chart-donut-4": "\u{ee2e}", + "chart-donut-filled": "\u{f66f}", + "chart-dots": "\u{ee2f}", + "chart-dots-2": "\u{f097}", + "chart-dots-2-filled": "\u{100dd}", + "chart-dots-3": "\u{f098}", + "chart-dots-3-filled": "\u{100dc}", + "chart-dots-filled": "\u{fd04}", + "chart-funnel": "\u{fef5}", + "chart-funnel-filled": "\u{100db}", + "chart-grid-dots": "\u{f4c2}", + "chart-grid-dots-filled": "\u{fd05}", + "chart-histogram": "\u{f65c}", + "chart-infographic": "\u{ee30}", + "chart-line": "\u{ea5c}", + "chart-pie": "\u{ea5d}", + "chart-pie-2": "\u{ee31}", + "chart-pie-2-filled": "\u{100da}", + "chart-pie-3": "\u{ee32}", + "chart-pie-3-filled": "\u{100d9}", + "chart-pie-4": "\u{ee33}", + "chart-pie-4-filled": "\u{100d8}", + "chart-pie-filled": "\u{f670}", + "chart-pie-off": "\u{f3d3}", + "chart-ppf": "\u{f618}", + "chart-radar": "\u{ed77}", + "chart-sankey": "\u{f619}", + "chart-scatter": "\u{fd93}", + "chart-scatter-3d": "\u{fd92}", + "chart-treemap": "\u{f381}", + "check": "\u{ea5e}", + "checkbox": "\u{eba6}", + "checklist": "\u{f074}", + "checks": "\u{ebaa}", + "checkup-list": "\u{ef5a}", + "cheese": "\u{ef26}", + "chef-hat": "\u{f21d}", + "chef-hat-filled": "\u{100d7}", + "chef-hat-off": "\u{f3d4}", + "cherry": "\u{f511}", + "cherry-filled": "\u{f728}", + "chess": "\u{f382}", + "chess-bishop": "\u{f56b}", + "chess-bishop-filled": "\u{f729}", + "chess-filled": "\u{f72a}", + "chess-king": "\u{f56c}", + "chess-king-filled": "\u{f72b}", + "chess-knight": "\u{f56d}", + "chess-knight-filled": "\u{f72c}", + "chess-queen": "\u{f56e}", + "chess-queen-filled": "\u{f72d}", + "chess-rook": "\u{f56f}", + "chess-rook-filled": "\u{f72e}", + "chevron-compact-down": "\u{faf0}", + "chevron-compact-left": "\u{faf1}", + "chevron-compact-right": "\u{faf2}", + "chevron-compact-up": "\u{faf3}", + "chevron-down": "\u{ea5f}", + "chevron-down-left": "\u{ed09}", + "chevron-down-right": "\u{ed0a}", + "chevron-left": "\u{ea60}", + "chevron-left-pipe": "\u{fae4}", + "chevron-right": "\u{ea61}", + "chevron-right-pipe": "\u{fae5}", + "chevron-up": "\u{ea62}", + "chevron-up-left": "\u{ed0b}", + "chevron-up-right": "\u{ed0c}", + "chevrons-down": "\u{ea63}", + "chevrons-down-left": "\u{ed0d}", + "chevrons-down-right": "\u{ed0e}", + "chevrons-left": "\u{ea64}", + "chevrons-right": "\u{ea65}", + "chevrons-up": "\u{ea66}", + "chevrons-up-left": "\u{ed0f}", + "chevrons-up-right": "\u{ed10}", + "chisel": "\u{f383}", + "christmas-ball": "\u{fd31}", + "christmas-tree": "\u{ed78}", + "christmas-tree-filled": "\u{1001a}", + "christmas-tree-off": "\u{f3d5}", + "circle": "\u{ea6b}", + "circle-0": "\u{ee34}", + "circle-1": "\u{ee35}", + "circle-2": "\u{ee36}", + "circle-3": "\u{ee37}", + "circle-4": "\u{ee38}", + "circle-5": "\u{ee39}", + "circle-6": "\u{ee3a}", + "circle-7": "\u{ee3b}", + "circle-8": "\u{ee3c}", + "circle-9": "\u{ee3d}", + "circle-arrow-down": "\u{f6f9}", + "circle-arrow-down-filled": "\u{f6f4}", + "circle-arrow-down-left": "\u{f6f6}", + "circle-arrow-down-left-filled": "\u{f6f5}", + "circle-arrow-down-right": "\u{f6f8}", + "circle-arrow-down-right-filled": "\u{f6f7}", + "circle-arrow-left": "\u{f6fb}", + "circle-arrow-left-filled": "\u{f6fa}", + "circle-arrow-right": "\u{f6fd}", + "circle-arrow-right-filled": "\u{f6fc}", + "circle-arrow-up": "\u{f703}", + "circle-arrow-up-filled": "\u{f6fe}", + "circle-arrow-up-left": "\u{f700}", + "circle-arrow-up-left-filled": "\u{f6ff}", + "circle-arrow-up-right": "\u{f702}", + "circle-arrow-up-right-filled": "\u{f701}", + "circle-caret-down": "\u{f4a9}", + "circle-caret-down-filled": "\u{100d6}", + "circle-caret-left": "\u{f4aa}", + "circle-caret-left-filled": "\u{100d5}", + "circle-caret-right": "\u{f4ab}", + "circle-caret-right-filled": "\u{100d4}", + "circle-caret-up": "\u{f4ac}", + "circle-caret-up-filled": "\u{100d3}", + "circle-check": "\u{ea67}", + "circle-check-filled": "\u{f704}", + "circle-chevron-down": "\u{f622}", + "circle-chevron-down-filled": "\u{100d2}", + "circle-chevron-left": "\u{f623}", + "circle-chevron-left-filled": "\u{100d1}", + "circle-chevron-right": "\u{f624}", + "circle-chevron-right-filled": "\u{100d0}", + "circle-chevron-up": "\u{f625}", + "circle-chevron-up-filled": "\u{100cf}", + "circle-chevrons-down": "\u{f642}", + "circle-chevrons-down-filled": "\u{100ef}", + "circle-chevrons-left": "\u{f643}", + "circle-chevrons-left-filled": "\u{100ee}", + "circle-chevrons-right": "\u{f644}", + "circle-chevrons-right-filled": "\u{100ed}", + "circle-chevrons-up": "\u{f645}", + "circle-chevrons-up-filled": "\u{100ec}", + "circle-dashed": "\u{ed27}", + "circle-dashed-check": "\u{feb8}", + "circle-dashed-letter-a": "\u{ff9a}", + "circle-dashed-letter-b": "\u{ff99}", + "circle-dashed-letter-c": "\u{ff98}", + "circle-dashed-letter-d": "\u{ff97}", + "circle-dashed-letter-e": "\u{ff96}", + "circle-dashed-letter-f": "\u{ff95}", + "circle-dashed-letter-g": "\u{ff94}", + "circle-dashed-letter-h": "\u{ff93}", + "circle-dashed-letter-i": "\u{ff92}", + "circle-dashed-letter-j": "\u{ff91}", + "circle-dashed-letter-k": "\u{ff90}", + "circle-dashed-letter-l": "\u{ff8f}", + "circle-dashed-letter-letter-v": "\u{ff84}", + "circle-dashed-letter-m": "\u{ff8d}", + "circle-dashed-letter-n": "\u{ff8c}", + "circle-dashed-letter-o": "\u{ff8b}", + "circle-dashed-letter-p": "\u{ff8a}", + "circle-dashed-letter-q": "\u{ff89}", + "circle-dashed-letter-r": "\u{ff88}", + "circle-dashed-letter-s": "\u{ff87}", + "circle-dashed-letter-t": "\u{ff86}", + "circle-dashed-letter-u": "\u{ff85}", + "circle-dashed-letter-v": "\u{ff84}", + "circle-dashed-letter-w": "\u{ff83}", + "circle-dashed-letter-x": "\u{ff82}", + "circle-dashed-letter-y": "\u{ff81}", + "circle-dashed-letter-z": "\u{ff80}", + "circle-dashed-minus": "\u{feb7}", + "circle-dashed-number-0": "\u{fc6b}", + "circle-dashed-number-1": "\u{fc6c}", + "circle-dashed-number-2": "\u{fc6d}", + "circle-dashed-number-3": "\u{fc6e}", + "circle-dashed-number-4": "\u{fc6f}", + "circle-dashed-number-5": "\u{fc70}", + "circle-dashed-number-6": "\u{fc71}", + "circle-dashed-number-7": "\u{fc72}", + "circle-dashed-number-8": "\u{fc73}", + "circle-dashed-number-9": "\u{fc74}", + "circle-dashed-percentage": "\u{fd7a}", + "circle-dashed-plus": "\u{feb6}", + "circle-dashed-x": "\u{fc75}", + "circle-dot": "\u{efb1}", + "circle-dot-filled": "\u{f705}", + "circle-dotted": "\u{ed28}", + "circle-dotted-letter-a": "\u{ff7f}", + "circle-dotted-letter-b": "\u{ff7e}", + "circle-dotted-letter-c": "\u{ff7d}", + "circle-dotted-letter-d": "\u{ff7c}", + "circle-dotted-letter-e": "\u{ff7b}", + "circle-dotted-letter-f": "\u{ff7a}", + "circle-dotted-letter-g": "\u{ff79}", + "circle-dotted-letter-h": "\u{ff78}", + "circle-dotted-letter-i": "\u{ff77}", + "circle-dotted-letter-j": "\u{ff76}", + "circle-dotted-letter-k": "\u{ff75}", + "circle-dotted-letter-l": "\u{ff74}", + "circle-dotted-letter-m": "\u{ff73}", + "circle-dotted-letter-n": "\u{ff72}", + "circle-dotted-letter-o": "\u{ff71}", + "circle-dotted-letter-p": "\u{ff70}", + "circle-dotted-letter-q": "\u{ff6f}", + "circle-dotted-letter-r": "\u{ff6e}", + "circle-dotted-letter-s": "\u{ff6d}", + "circle-dotted-letter-t": "\u{ff6c}", + "circle-dotted-letter-u": "\u{ff6b}", + "circle-dotted-letter-v": "\u{ff6a}", + "circle-dotted-letter-w": "\u{ff69}", + "circle-dotted-letter-x": "\u{ff68}", + "circle-dotted-letter-y": "\u{ff67}", + "circle-dotted-letter-z": "\u{ff66}", + "circle-filled": "\u{f671}", + "circle-half": "\u{ee3f}", + "circle-half-2": "\u{eff3}", + "circle-half-vertical": "\u{ee3e}", + "circle-key": "\u{f633}", + "circle-key-filled": "\u{f706}", + "circle-letter-a": "\u{f441}", + "circle-letter-a-filled": "\u{fe7f}", + "circle-letter-b": "\u{f442}", + "circle-letter-b-filled": "\u{fe7e}", + "circle-letter-c": "\u{f443}", + "circle-letter-c-filled": "\u{fe7d}", + "circle-letter-d": "\u{f444}", + "circle-letter-d-filled": "\u{fe7c}", + "circle-letter-e": "\u{f445}", + "circle-letter-e-filled": "\u{fe7b}", + "circle-letter-f": "\u{f446}", + "circle-letter-f-filled": "\u{fe7a}", + "circle-letter-g": "\u{f447}", + "circle-letter-g-filled": "\u{fe79}", + "circle-letter-h": "\u{f448}", + "circle-letter-h-filled": "\u{fe78}", + "circle-letter-i": "\u{f449}", + "circle-letter-i-filled": "\u{fe77}", + "circle-letter-j": "\u{f44a}", + "circle-letter-j-filled": "\u{fe76}", + "circle-letter-k": "\u{f44b}", + "circle-letter-k-filled": "\u{fe75}", + "circle-letter-l": "\u{f44c}", + "circle-letter-l-filled": "\u{fe74}", + "circle-letter-m": "\u{f44d}", + "circle-letter-m-filled": "\u{fe73}", + "circle-letter-n": "\u{f44e}", + "circle-letter-n-filled": "\u{fe72}", + "circle-letter-o": "\u{f44f}", + "circle-letter-o-filled": "\u{fe71}", + "circle-letter-p": "\u{f450}", + "circle-letter-p-filled": "\u{fe70}", + "circle-letter-q": "\u{f451}", + "circle-letter-q-filled": "\u{fe6f}", + "circle-letter-r": "\u{f452}", + "circle-letter-r-filled": "\u{fe6e}", + "circle-letter-s": "\u{f453}", + "circle-letter-s-filled": "\u{fe6d}", + "circle-letter-t": "\u{f454}", + "circle-letter-t-filled": "\u{fe6c}", + "circle-letter-u": "\u{f455}", + "circle-letter-u-filled": "\u{fe6b}", + "circle-letter-v": "\u{f4ad}", + "circle-letter-v-filled": "\u{fe6a}", + "circle-letter-w": "\u{f456}", + "circle-letter-w-filled": "\u{fe69}", + "circle-letter-x": "\u{f4ae}", + "circle-letter-x-filled": "\u{fe68}", + "circle-letter-y": "\u{f457}", + "circle-letter-y-filled": "\u{fe67}", + "circle-letter-z": "\u{f458}", + "circle-letter-z-filled": "\u{fe66}", + "circle-minus": "\u{ea68}", + "circle-minus-2": "\u{fc8c}", + "circle-number-0": "\u{ee34}", + "circle-number-0-filled": "\u{f72f}", + "circle-number-1": "\u{ee35}", + "circle-number-1-filled": "\u{f730}", + "circle-number-2": "\u{ee36}", + "circle-number-2-filled": "\u{f731}", + "circle-number-3": "\u{ee37}", + "circle-number-3-filled": "\u{f732}", + "circle-number-4": "\u{ee38}", + "circle-number-4-filled": "\u{f733}", + "circle-number-5": "\u{ee39}", + "circle-number-5-filled": "\u{f734}", + "circle-number-6": "\u{ee3a}", + "circle-number-6-filled": "\u{f735}", + "circle-number-7": "\u{ee3b}", + "circle-number-7-filled": "\u{f736}", + "circle-number-8": "\u{ee3c}", + "circle-number-8-filled": "\u{f737}", + "circle-number-9": "\u{ee3d}", + "circle-number-9-filled": "\u{f738}", + "circle-off": "\u{ee40}", + "circle-percentage": "\u{fd7b}", + "circle-percentage-filled": "\u{fed5}", + "circle-plus": "\u{ea69}", + "circle-plus-2": "\u{fc8d}", + "circle-plus-filled": "\u{fef9}", + "circle-rectangle": "\u{f010}", + "circle-rectangle-filled": "\u{ff63}", + "circle-rectangle-off": "\u{f0cd}", + "circle-square": "\u{ece4}", + "circle-triangle": "\u{f011}", + "circle-x": "\u{ea6a}", + "circle-x-filled": "\u{f739}", + "circles": "\u{ece5}", + "circles-filled": "\u{f672}", + "circles-relation": "\u{f4c3}", + "circuit-ammeter": "\u{f271}", + "circuit-battery": "\u{f272}", + "circuit-bulb": "\u{f273}", + "circuit-capacitor": "\u{f275}", + "circuit-capacitor-polarized": "\u{f274}", + "circuit-cell": "\u{f277}", + "circuit-cell-plus": "\u{f276}", + "circuit-changeover": "\u{f278}", + "circuit-diode": "\u{f27a}", + "circuit-diode-zener": "\u{f279}", + "circuit-ground": "\u{f27c}", + "circuit-ground-digital": "\u{f27b}", + "circuit-inductor": "\u{f27d}", + "circuit-motor": "\u{f27e}", + "circuit-pushbutton": "\u{f27f}", + "circuit-resistor": "\u{f280}", + "circuit-switch-closed": "\u{f281}", + "circuit-switch-open": "\u{f282}", + "circuit-voltmeter": "\u{f283}", + "clear-all": "\u{ee41}", + "clear-formatting": "\u{ebe5}", + "click": "\u{ebbc}", + "cliff-jumping": "\u{fefc}", + "clipboard": "\u{ea6f}", + "clipboard-check": "\u{ea6c}", + "clipboard-check-filled": "\u{100ce}", + "clipboard-copy": "\u{f299}", + "clipboard-data": "\u{f563}", + "clipboard-data-filled": "\u{100eb}", + "clipboard-filled": "\u{100cc}", + "clipboard-heart": "\u{f34e}", + "clipboard-list": "\u{ea6d}", + "clipboard-list-filled": "\u{100ea}", + "clipboard-off": "\u{f0ce}", + "clipboard-plus": "\u{efb2}", + "clipboard-plus-filled": "\u{10176}", + "clipboard-search": "\u{10098}", + "clipboard-smile": "\u{fd9a}", + "clipboard-smile-filled": "\u{10175}", + "clipboard-text": "\u{f089}", + "clipboard-text-filled": "\u{100e9}", + "clipboard-typography": "\u{f34f}", + "clipboard-typography-filled": "\u{100e8}", + "clipboard-x": "\u{ea6e}", + "clipboard-x-filled": "\u{100cd}", + "clock": "\u{ea70}", + "clock-12": "\u{fc56}", + "clock-2": "\u{f099}", + "clock-24": "\u{fc57}", + "clock-bitcoin": "\u{ff3f}", + "clock-bolt": "\u{f844}", + "clock-cancel": "\u{f546}", + "clock-check": "\u{f7c1}", + "clock-code": "\u{f845}", + "clock-cog": "\u{f7c2}", + "clock-dollar": "\u{f846}", + "clock-down": "\u{f7c3}", + "clock-edit": "\u{f547}", + "clock-exclamation": "\u{f847}", + "clock-filled": "\u{f73a}", + "clock-heart": "\u{f7c4}", + "clock-hour-1": "\u{f313}", + "clock-hour-1-filled": "\u{fe65}", + "clock-hour-10": "\u{f314}", + "clock-hour-10-filled": "\u{fe64}", + "clock-hour-11": "\u{f315}", + "clock-hour-11-filled": "\u{fe63}", + "clock-hour-12": "\u{f316}", + "clock-hour-12-filled": "\u{fe62}", + "clock-hour-2": "\u{f317}", + "clock-hour-2-filled": "\u{fe61}", + "clock-hour-3": "\u{f318}", + "clock-hour-3-filled": "\u{fe60}", + "clock-hour-4": "\u{f319}", + "clock-hour-4-filled": "\u{fe5f}", + "clock-hour-5": "\u{f31a}", + "clock-hour-5-filled": "\u{fe5e}", + "clock-hour-6": "\u{f31b}", + "clock-hour-6-filled": "\u{fe5d}", + "clock-hour-7": "\u{f31c}", + "clock-hour-7-filled": "\u{fe5c}", + "clock-hour-8": "\u{f31d}", + "clock-hour-8-filled": "\u{fe5b}", + "clock-hour-9": "\u{f31e}", + "clock-hour-9-filled": "\u{fe5a}", + "clock-minus": "\u{f848}", + "clock-off": "\u{f0cf}", + "clock-pause": "\u{f548}", + "clock-pin": "\u{f849}", + "clock-play": "\u{f549}", + "clock-plus": "\u{f7c5}", + "clock-question": "\u{f7c6}", + "clock-record": "\u{f54a}", + "clock-search": "\u{f7c7}", + "clock-share": "\u{f84a}", + "clock-shield": "\u{f7c8}", + "clock-star": "\u{f7c9}", + "clock-stop": "\u{f54b}", + "clock-up": "\u{f7ca}", + "clock-x": "\u{f7cb}", + "clothes-rack": "\u{f285}", + "clothes-rack-off": "\u{f3d6}", + "cloud": "\u{ea76}", + "cloud-bitcoin": "\u{ff3e}", + "cloud-bolt": "\u{f84b}", + "cloud-cancel": "\u{f84c}", + "cloud-check": "\u{f84d}", + "cloud-code": "\u{f84e}", + "cloud-cog": "\u{f84f}", + "cloud-computing": "\u{f1d0}", + "cloud-computing-filled": "\u{1010d}", + "cloud-data-connection": "\u{f1d1}", + "cloud-data-connection-filled": "\u{1010c}", + "cloud-dollar": "\u{f850}", + "cloud-down": "\u{f851}", + "cloud-download": "\u{ea71}", + "cloud-exclamation": "\u{f852}", + "cloud-filled": "\u{f673}", + "cloud-fog": "\u{ecd9}", + "cloud-heart": "\u{f853}", + "cloud-lock": "\u{efdb}", + "cloud-lock-open": "\u{efda}", + "cloud-minus": "\u{f854}", + "cloud-network": "\u{fc78}", + "cloud-off": "\u{ed3e}", + "cloud-pause": "\u{f855}", + "cloud-pin": "\u{f856}", + "cloud-plus": "\u{f857}", + "cloud-question": "\u{f858}", + "cloud-rain": "\u{ea72}", + "cloud-search": "\u{f859}", + "cloud-share": "\u{f85a}", + "cloud-snow": "\u{ea73}", + "cloud-star": "\u{f85b}", + "cloud-storm": "\u{ea74}", + "cloud-sun": "\u{ec6d}", + "cloud-up": "\u{f85c}", + "cloud-upload": "\u{ea75}", + "cloud-x": "\u{f85d}", + "clover": "\u{f1ea}", + "clover-2": "\u{f21e}", + "clover-filled": "\u{10013}", + "clubs": "\u{eff4}", + "clubs-filled": "\u{f674}", + "code": "\u{ea77}", + "code-asterisk": "\u{f312}", + "code-asterix": "\u{f312}", + "code-circle": "\u{f4ff}", + "code-circle-2": "\u{f4fe}", + "code-circle-2-filled": "\u{fed4}", + "code-circle-filled": "\u{fed3}", + "code-dots": "\u{f61a}", + "code-minus": "\u{ee42}", + "code-off": "\u{f0d0}", + "code-plus": "\u{ee43}", + "code-variable": "\u{100ab}", + "code-variable-minus": "\u{100ad}", + "code-variable-plus": "\u{100ac}", + "coffee": "\u{ef0e}", + "coffee-off": "\u{f106}", + "coffin": "\u{f579}", + "coin": "\u{eb82}", + "coin-bitcoin": "\u{f2be}", + "coin-bitcoin-filled": "\u{fd06}", + "coin-euro": "\u{f2bf}", + "coin-euro-filled": "\u{fd07}", + "coin-filled": "\u{fd08}", + "coin-monero": "\u{f4a0}", + "coin-monero-filled": "\u{fd09}", + "coin-off": "\u{f0d1}", + "coin-pound": "\u{f2c0}", + "coin-pound-filled": "\u{fd0a}", + "coin-rupee": "\u{f2c1}", + "coin-rupee-filled": "\u{fd0b}", + "coin-taka": "\u{fd0d}", + "coin-taka-filled": "\u{fd0c}", + "coin-yen": "\u{f2c2}", + "coin-yen-filled": "\u{fd0e}", + "coin-yuan": "\u{f2c3}", + "coin-yuan-filled": "\u{fd0f}", + "coins": "\u{f65d}", + "color-filter": "\u{f5a8}", + "color-picker": "\u{ebe6}", + "color-picker-off": "\u{f0d2}", + "color-swatch": "\u{eb61}", + "color-swatch-off": "\u{f0d3}", + "column-insert-left": "\u{ee44}", + "column-insert-right": "\u{ee45}", + "column-remove": "\u{faf4}", + "columns": "\u{eb83}", + "columns-1": "\u{f6d4}", + "columns-1-filled": "\u{10188}", + "columns-2": "\u{f6d5}", + "columns-2-filled": "\u{10187}", + "columns-3": "\u{f6d6}", + "columns-3-filled": "\u{10186}", + "columns-off": "\u{f0d4}", + "comet": "\u{ec76}", + "command": "\u{ea78}", + "command-off": "\u{f3d7}", + "compass": "\u{ea79}", + "compass-filled": "\u{fd10}", + "compass-off": "\u{f0d5}", + "components": "\u{efa5}", + "components-off": "\u{f0d6}", + "cone": "\u{efdd}", + "cone-2": "\u{efdc}", + "cone-2-filled": "\u{fe59}", + "cone-filled": "\u{fe58}", + "cone-off": "\u{f3d8}", + "cone-plus": "\u{fa94}", + "confetti": "\u{ee46}", + "confetti-filled": "\u{10185}", + "confetti-off": "\u{f3d9}", + "confucius": "\u{f58a}", + "congruent-to": "\u{ffa3}", + "container": "\u{ee47}", + "container-filled": "\u{10184}", + "container-off": "\u{f107}", + "contract": "\u{fefb}", + "contrast": "\u{ec4e}", + "contrast-2": "\u{efc7}", + "contrast-2-filled": "\u{fe57}", + "contrast-2-off": "\u{f3da}", + "contrast-filled": "\u{fe56}", + "contrast-off": "\u{f3db}", + "cooker": "\u{f57a}", + "cookie": "\u{fdb1}", + "cookie-filled": "\u{fe54}", + "cookie-man": "\u{fdb2}", + "cookie-man-filled": "\u{fe55}", + "cookie-off": "\u{f0d7}", + "copy": "\u{ea7a}", + "copy-check": "\u{fdb0}", + "copy-check-filled": "\u{fe53}", + "copy-minus": "\u{fdaf}", + "copy-minus-filled": "\u{fe52}", + "copy-off": "\u{f0d8}", + "copy-plus": "\u{fdae}", + "copy-plus-filled": "\u{fe51}", + "copy-x": "\u{fdad}", + "copy-x-filled": "\u{fe50}", + "copyleft": "\u{ec3d}", + "copyleft-filled": "\u{f73b}", + "copyleft-off": "\u{f0d9}", + "copyright": "\u{ea7b}", + "copyright-filled": "\u{f73c}", + "copyright-off": "\u{f0da}", + "corner-down-left": "\u{ea7c}", + "corner-down-left-double": "\u{ee48}", + "corner-down-right": "\u{ea7d}", + "corner-down-right-double": "\u{ee49}", + "corner-left-down": "\u{ea7e}", + "corner-left-down-double": "\u{ee4a}", + "corner-left-up": "\u{ea7f}", + "corner-left-up-double": "\u{ee4b}", + "corner-right-down": "\u{ea80}", + "corner-right-down-double": "\u{ee4c}", + "corner-right-up": "\u{ea81}", + "corner-right-up-double": "\u{ee4d}", + "corner-up-left": "\u{ea82}", + "corner-up-left-double": "\u{ee4e}", + "corner-up-right": "\u{ea83}", + "corner-up-right-double": "\u{ee4f}", + "cpu": "\u{ef8e}", + "cpu-2": "\u{f075}", + "cpu-off": "\u{f108}", + "crane": "\u{ef27}", + "crane-off": "\u{f109}", + "creative-commons": "\u{efb3}", + "creative-commons-by": "\u{f21f}", + "creative-commons-nc": "\u{f220}", + "creative-commons-nd": "\u{f221}", + "creative-commons-off": "\u{f10a}", + "creative-commons-sa": "\u{f222}", + "creative-commons-zero": "\u{f223}", + "credit-card": "\u{ea84}", + "credit-card-filled": "\u{fd11}", + "credit-card-off": "\u{ed11}", + "credit-card-pay": "\u{fd32}", + "credit-card-refund": "\u{fd33}", + "cricket": "\u{f09a}", + "crop": "\u{ea85}", + "crop-1-1": "\u{fd50}", + "crop-1-1-filled": "\u{fe4f}", + "crop-16-9": "\u{fd51}", + "crop-16-9-filled": "\u{fe4e}", + "crop-3-2": "\u{fd52}", + "crop-3-2-filled": "\u{fe4d}", + "crop-5-4": "\u{fd53}", + "crop-5-4-filled": "\u{fe4c}", + "crop-7-5": "\u{fd54}", + "crop-7-5-filled": "\u{fe4b}", + "crop-landscape": "\u{fd55}", + "crop-landscape-filled": "\u{fe4a}", + "crop-portrait": "\u{fd56}", + "crop-portrait-filled": "\u{fe49}", + "cross": "\u{ef8f}", + "cross-filled": "\u{f675}", + "cross-off": "\u{f10b}", + "crosshair": "\u{ec3e}", + "crown": "\u{ed12}", + "crown-off": "\u{ee50}", + "crutches": "\u{ef5b}", + "crutches-off": "\u{f10c}", + "crystal-ball": "\u{f57b}", + "csv": "\u{f791}", + "cube": "\u{fa97}", + "cube-3d-sphere": "\u{ecd7}", + "cube-3d-sphere-off": "\u{f3b5}", + "cube-off": "\u{fa95}", + "cube-plus": "\u{fa96}", + "cube-send": "\u{f61b}", + "cube-spark": "\u{ffbb}", + "cube-unfolded": "\u{f61c}", + "cup": "\u{ef28}", + "cup-off": "\u{f10d}", + "curling": "\u{efc8}", + "curly-loop": "\u{ecda}", + "currency": "\u{efa6}", + "currency-afghani": "\u{f65e}", + "currency-bahraini": "\u{ee51}", + "currency-baht": "\u{f08a}", + "currency-bitcoin": "\u{ebab}", + "currency-cent": "\u{ee53}", + "currency-dinar": "\u{ee54}", + "currency-dirham": "\u{ee55}", + "currency-dogecoin": "\u{ef4b}", + "currency-dollar": "\u{eb84}", + "currency-dollar-australian": "\u{ee56}", + "currency-dollar-brunei": "\u{f36c}", + "currency-dollar-canadian": "\u{ee57}", + "currency-dollar-guyanese": "\u{f36d}", + "currency-dollar-off": "\u{f3dc}", + "currency-dollar-singapore": "\u{ee58}", + "currency-dollar-zimbabwean": "\u{f36e}", + "currency-dong": "\u{f36f}", + "currency-dram": "\u{f370}", + "currency-ethereum": "\u{ee59}", + "currency-euro": "\u{eb85}", + "currency-euro-off": "\u{f3dd}", + "currency-florin": "\u{faf5}", + "currency-forint": "\u{ee5a}", + "currency-frank": "\u{ee5b}", + "currency-guarani": "\u{f371}", + "currency-hryvnia": "\u{f372}", + "currency-iranian-rial": "\u{fa58}", + "currency-kip": "\u{f373}", + "currency-krone-czech": "\u{ee5c}", + "currency-krone-danish": "\u{ee5d}", + "currency-krone-swedish": "\u{ee5e}", + "currency-lari": "\u{f374}", + "currency-leu": "\u{ee5f}", + "currency-lira": "\u{ee60}", + "currency-litecoin": "\u{ee61}", + "currency-lyd": "\u{f375}", + "currency-manat": "\u{f376}", + "currency-monero": "\u{f377}", + "currency-naira": "\u{ee62}", + "currency-nano": "\u{f7a6}", + "currency-off": "\u{f3de}", + "currency-paanga": "\u{f378}", + "currency-peso": "\u{f65f}", + "currency-pound": "\u{ebac}", + "currency-pound-off": "\u{f3df}", + "currency-quetzal": "\u{f379}", + "currency-real": "\u{ee63}", + "currency-renminbi": "\u{ee64}", + "currency-ripple": "\u{ee65}", + "currency-riyal": "\u{ee66}", + "currency-rubel": "\u{ee67}", + "currency-rufiyaa": "\u{f37a}", + "currency-rupee": "\u{ebad}", + "currency-rupee-nepalese": "\u{f37b}", + "currency-shekel": "\u{ee68}", + "currency-solana": "\u{f4a1}", + "currency-som": "\u{f37c}", + "currency-taka": "\u{ee69}", + "currency-tenge": "\u{f37d}", + "currency-tugrik": "\u{ee6a}", + "currency-won": "\u{ee6b}", + "currency-xrp": "\u{fd34}", + "currency-yen": "\u{ebae}", + "currency-yen-off": "\u{f3e0}", + "currency-yuan": "\u{f29a}", + "currency-zloty": "\u{ee6c}", + "current-location": "\u{ecef}", + "current-location-filled": "\u{10125}", + "current-location-off": "\u{f10e}", + "cursor-off": "\u{f10f}", + "cursor-text": "\u{ee6d}", + "cut": "\u{ea86}", + "cylinder": "\u{f54c}", + "cylinder-off": "\u{fa98}", + "cylinder-plus": "\u{fa99}", + "dashboard": "\u{ea87}", + "dashboard-filled": "\u{10019}", + "dashboard-off": "\u{f3e1}", + "database": "\u{ea88}", + "database-cog": "\u{fa10}", + "database-dollar": "\u{fa11}", + "database-edit": "\u{fa12}", + "database-exclamation": "\u{fa13}", + "database-export": "\u{ee6e}", + "database-heart": "\u{fa14}", + "database-import": "\u{ee6f}", + "database-leak": "\u{fa15}", + "database-minus": "\u{fa16}", + "database-off": "\u{ee70}", + "database-plus": "\u{fa17}", + "database-search": "\u{fa18}", + "database-share": "\u{fa19}", + "database-smile": "\u{fd9b}", + "database-star": "\u{fa1a}", + "database-x": "\u{fa1b}", + "decimal": "\u{fa26}", + "deer": "\u{f4c5}", + "delta": "\u{f53c}", + "dental": "\u{f025}", + "dental-broken": "\u{f286}", + "dental-off": "\u{f110}", + "deselect": "\u{f9f3}", + "desk": "\u{fd35}", + "details": "\u{ee71}", + "details-off": "\u{f3e2}", + "device-airpods": "\u{f5a9}", + "device-airpods-case": "\u{f646}", + "device-airtag": "\u{fae6}", + "device-analytics": "\u{ee72}", + "device-audio-tape": "\u{ee73}", + "device-camera-phone": "\u{f233}", + "device-cctv": "\u{ee74}", + "device-cctv-filled": "\u{1004b}", + "device-cctv-off": "\u{f3e3}", + "device-computer-camera": "\u{ee76}", + "device-computer-camera-off": "\u{ee75}", + "device-desktop": "\u{ea89}", + "device-desktop-analytics": "\u{ee77}", + "device-desktop-bolt": "\u{f85e}", + "device-desktop-cancel": "\u{f85f}", + "device-desktop-check": "\u{f860}", + "device-desktop-code": "\u{f861}", + "device-desktop-cog": "\u{f862}", + "device-desktop-dollar": "\u{f863}", + "device-desktop-down": "\u{f864}", + "device-desktop-exclamation": "\u{f865}", + "device-desktop-filled": "\u{1004a}", + "device-desktop-heart": "\u{f866}", + "device-desktop-minus": "\u{f867}", + "device-desktop-off": "\u{ee78}", + "device-desktop-pause": "\u{f868}", + "device-desktop-pin": "\u{f869}", + "device-desktop-plus": "\u{f86a}", + "device-desktop-question": "\u{f86b}", + "device-desktop-search": "\u{f86c}", + "device-desktop-share": "\u{f86d}", + "device-desktop-star": "\u{f86e}", + "device-desktop-up": "\u{f86f}", + "device-desktop-x": "\u{f870}", + "device-floppy": "\u{eb62}", + "device-gamepad": "\u{eb63}", + "device-gamepad-2": "\u{f1d2}", + "device-gamepad-3": "\u{fc58}", + "device-gamepad-3-filled": "\u{10049}", + "device-heart-monitor": "\u{f060}", + "device-heart-monitor-filled": "\u{fa38}", + "device-imac": "\u{f7a7}", + "device-imac-bolt": "\u{f871}", + "device-imac-cancel": "\u{f872}", + "device-imac-check": "\u{f873}", + "device-imac-code": "\u{f874}", + "device-imac-cog": "\u{f875}", + "device-imac-dollar": "\u{f876}", + "device-imac-down": "\u{f877}", + "device-imac-exclamation": "\u{f878}", + "device-imac-filled": "\u{10048}", + "device-imac-heart": "\u{f879}", + "device-imac-minus": "\u{f87a}", + "device-imac-off": "\u{f87b}", + "device-imac-pause": "\u{f87c}", + "device-imac-pin": "\u{f87d}", + "device-imac-plus": "\u{f87e}", + "device-imac-question": "\u{f87f}", + "device-imac-search": "\u{f880}", + "device-imac-share": "\u{f881}", + "device-imac-star": "\u{f882}", + "device-imac-up": "\u{f883}", + "device-imac-x": "\u{f884}", + "device-ipad": "\u{f648}", + "device-ipad-bolt": "\u{f885}", + "device-ipad-cancel": "\u{f886}", + "device-ipad-check": "\u{f887}", + "device-ipad-code": "\u{f888}", + "device-ipad-cog": "\u{f889}", + "device-ipad-dollar": "\u{f88a}", + "device-ipad-down": "\u{f88b}", + "device-ipad-exclamation": "\u{f88c}", + "device-ipad-filled": "\u{10047}", + "device-ipad-heart": "\u{f88d}", + "device-ipad-horizontal": "\u{f647}", + "device-ipad-horizontal-bolt": "\u{f88e}", + "device-ipad-horizontal-cancel": "\u{f88f}", + "device-ipad-horizontal-check": "\u{f890}", + "device-ipad-horizontal-code": "\u{f891}", + "device-ipad-horizontal-cog": "\u{f892}", + "device-ipad-horizontal-dollar": "\u{f893}", + "device-ipad-horizontal-down": "\u{f894}", + "device-ipad-horizontal-exclamation": "\u{f895}", + "device-ipad-horizontal-heart": "\u{f896}", + "device-ipad-horizontal-minus": "\u{f897}", + "device-ipad-horizontal-off": "\u{f898}", + "device-ipad-horizontal-pause": "\u{f899}", + "device-ipad-horizontal-pin": "\u{f89a}", + "device-ipad-horizontal-plus": "\u{f89b}", + "device-ipad-horizontal-question": "\u{f89c}", + "device-ipad-horizontal-search": "\u{f89d}", + "device-ipad-horizontal-share": "\u{f89e}", + "device-ipad-horizontal-star": "\u{f89f}", + "device-ipad-horizontal-up": "\u{f8a0}", + "device-ipad-horizontal-x": "\u{f8a1}", + "device-ipad-minus": "\u{f8a2}", + "device-ipad-off": "\u{f8a3}", + "device-ipad-pause": "\u{f8a4}", + "device-ipad-pin": "\u{f8a5}", + "device-ipad-plus": "\u{f8a6}", + "device-ipad-question": "\u{f8a7}", + "device-ipad-search": "\u{f8a8}", + "device-ipad-share": "\u{f8a9}", + "device-ipad-star": "\u{f8aa}", + "device-ipad-up": "\u{f8ab}", + "device-ipad-x": "\u{f8ac}", + "device-landline-phone": "\u{f649}", + "device-laptop": "\u{eb64}", + "device-laptop-off": "\u{f061}", + "device-mobile": "\u{ea8a}", + "device-mobile-bolt": "\u{f8ad}", + "device-mobile-cancel": "\u{f8ae}", + "device-mobile-charging": "\u{f224}", + "device-mobile-check": "\u{f8af}", + "device-mobile-code": "\u{f8b0}", + "device-mobile-cog": "\u{f8b1}", + "device-mobile-dollar": "\u{f8b2}", + "device-mobile-down": "\u{f8b3}", + "device-mobile-exclamation": "\u{f8b4}", + "device-mobile-filled": "\u{fa39}", + "device-mobile-heart": "\u{f8b5}", + "device-mobile-message": "\u{ee79}", + "device-mobile-minus": "\u{f8b6}", + "device-mobile-off": "\u{f062}", + "device-mobile-pause": "\u{f8b7}", + "device-mobile-pin": "\u{f8b8}", + "device-mobile-plus": "\u{f8b9}", + "device-mobile-question": "\u{f8ba}", + "device-mobile-rotated": "\u{ecdb}", + "device-mobile-search": "\u{f8bb}", + "device-mobile-share": "\u{f8bc}", + "device-mobile-star": "\u{f8bd}", + "device-mobile-up": "\u{f8be}", + "device-mobile-vibration": "\u{eb86}", + "device-mobile-x": "\u{f8bf}", + "device-nintendo": "\u{f026}", + "device-nintendo-off": "\u{f111}", + "device-projector": "\u{fc11}", + "device-remote": "\u{f792}", + "device-remote-filled": "\u{10046}", + "device-sd-card": "\u{f384}", + "device-sim": "\u{f4b2}", + "device-sim-1": "\u{f4af}", + "device-sim-2": "\u{f4b0}", + "device-sim-3": "\u{f4b1}", + "device-speaker": "\u{ea8b}", + "device-speaker-filled": "\u{10045}", + "device-speaker-off": "\u{f112}", + "device-tablet": "\u{ea8c}", + "device-tablet-bolt": "\u{f8c0}", + "device-tablet-cancel": "\u{f8c1}", + "device-tablet-check": "\u{f8c2}", + "device-tablet-code": "\u{f8c3}", + "device-tablet-cog": "\u{f8c4}", + "device-tablet-dollar": "\u{f8c5}", + "device-tablet-down": "\u{f8c6}", + "device-tablet-exclamation": "\u{f8c7}", + "device-tablet-filled": "\u{fa3a}", + "device-tablet-heart": "\u{f8c8}", + "device-tablet-minus": "\u{f8c9}", + "device-tablet-off": "\u{f063}", + "device-tablet-pause": "\u{f8ca}", + "device-tablet-pin": "\u{f8cb}", + "device-tablet-plus": "\u{f8cc}", + "device-tablet-question": "\u{f8cd}", + "device-tablet-search": "\u{f8ce}", + "device-tablet-share": "\u{f8cf}", + "device-tablet-star": "\u{f8d0}", + "device-tablet-up": "\u{f8d1}", + "device-tablet-x": "\u{f8d2}", + "device-tv": "\u{ea8d}", + "device-tv-filled": "\u{10043}", + "device-tv-off": "\u{f064}", + "device-tv-old": "\u{f1d3}", + "device-tv-old-filled": "\u{10044}", + "device-unknown": "\u{fef4}", + "device-unknown-filled": "\u{10018}", + "device-usb": "\u{fc59}", + "device-usb-filled": "\u{10042}", + "device-vision-pro": "\u{fae7}", + "device-vision-pro-filled": "\u{10041}", + "device-watch": "\u{ebf9}", + "device-watch-bolt": "\u{f8d3}", + "device-watch-cancel": "\u{f8d4}", + "device-watch-check": "\u{f8d5}", + "device-watch-code": "\u{f8d6}", + "device-watch-cog": "\u{f8d7}", + "device-watch-dollar": "\u{f8d8}", + "device-watch-down": "\u{f8d9}", + "device-watch-exclamation": "\u{f8da}", + "device-watch-filled": "\u{10040}", + "device-watch-heart": "\u{f8db}", + "device-watch-minus": "\u{f8dc}", + "device-watch-off": "\u{f065}", + "device-watch-pause": "\u{f8dd}", + "device-watch-pin": "\u{f8de}", + "device-watch-plus": "\u{f8df}", + "device-watch-question": "\u{f8e0}", + "device-watch-search": "\u{f8e1}", + "device-watch-share": "\u{f8e2}", + "device-watch-star": "\u{f8e3}", + "device-watch-stats": "\u{ef7d}", + "device-watch-stats-2": "\u{ef7c}", + "device-watch-up": "\u{f8e4}", + "device-watch-x": "\u{f8e5}", + "devices": "\u{eb87}", + "devices-2": "\u{ed29}", + "devices-bolt": "\u{f8e6}", + "devices-cancel": "\u{f8e7}", + "devices-check": "\u{f8e8}", + "devices-code": "\u{f8e9}", + "devices-cog": "\u{f8ea}", + "devices-dollar": "\u{f8eb}", + "devices-down": "\u{f8ec}", + "devices-exclamation": "\u{f8ed}", + "devices-heart": "\u{f8ee}", + "devices-minus": "\u{f8ef}", + "devices-off": "\u{f3e4}", + "devices-pause": "\u{f8f0}", + "devices-pc": "\u{ee7a}", + "devices-pc-off": "\u{f113}", + "devices-pin": "\u{f8f1}", + "devices-plus": "\u{f8f2}", + "devices-question": "\u{f8f3}", + "devices-search": "\u{f8f4}", + "devices-share": "\u{f8f5}", + "devices-star": "\u{f8f6}", + "devices-up": "\u{f8f7}", + "devices-x": "\u{f8f8}", + "diabolo": "\u{fa9c}", + "diabolo-off": "\u{fa9a}", + "diabolo-plus": "\u{fa9b}", + "dialpad": "\u{f067}", + "dialpad-filled": "\u{fa3b}", + "dialpad-off": "\u{f114}", + "diamond": "\u{eb65}", + "diamond-filled": "\u{f73d}", + "diamond-off": "\u{f115}", + "diamonds": "\u{eff5}", + "diamonds-filled": "\u{f676}", + "diaper": "\u{ffa2}", + "dice": "\u{eb66}", + "dice-1": "\u{f08b}", + "dice-1-filled": "\u{f73e}", + "dice-2": "\u{f08c}", + "dice-2-filled": "\u{f73f}", + "dice-3": "\u{f08d}", + "dice-3-filled": "\u{f740}", + "dice-4": "\u{f08e}", + "dice-4-filled": "\u{f741}", + "dice-5": "\u{f08f}", + "dice-5-filled": "\u{f742}", + "dice-6": "\u{f090}", + "dice-6-filled": "\u{f743}", + "dice-filled": "\u{f744}", + "dimensions": "\u{ee7b}", + "direction": "\u{ebfb}", + "direction-arrows": "\u{fd36}", + "direction-arrows-filled": "\u{100ca}", + "direction-horizontal": "\u{ebfa}", + "direction-sign": "\u{f1f7}", + "direction-sign-filled": "\u{f745}", + "direction-sign-off": "\u{f3e5}", + "directions": "\u{ea8e}", + "directions-filled": "\u{1003f}", + "directions-off": "\u{f116}", + "disabled": "\u{ea8f}", + "disabled-2": "\u{ebaf}", + "disabled-off": "\u{f117}", + "disc": "\u{ea90}", + "disc-filled": "\u{1003e}", + "disc-golf": "\u{f385}", + "disc-off": "\u{f118}", + "discount": "\u{ebbd}", + "discount-2": "\u{ee7c}", + "discount-2-off": "\u{f3e6}", + "discount-check": "\u{f1f8}", + "discount-check-filled": "\u{f746}", + "discount-filled": "\u{1003d}", + "discount-off": "\u{f3e7}", + "divide": "\u{ed5c}", + "dna": "\u{ee7d}", + "dna-2": "\u{ef5c}", + "dna-2-off": "\u{f119}", + "dna-off": "\u{f11a}", + "dog": "\u{f660}", + "dog-bowl": "\u{ef29}", + "door": "\u{ef4e}", + "door-enter": "\u{ef4c}", + "door-exit": "\u{ef4d}", + "door-off": "\u{f11b}", + "dots": "\u{ea95}", + "dots-circle-horizontal": "\u{ea91}", + "dots-diagonal": "\u{ea93}", + "dots-diagonal-2": "\u{ea92}", + "dots-vertical": "\u{ea94}", + "download": "\u{ea96}", + "download-off": "\u{f11c}", + "drag-drop": "\u{eb89}", + "drag-drop-2": "\u{eb88}", + "drone": "\u{ed79}", + "drone-off": "\u{ee7e}", + "drop-circle": "\u{efde}", + "drop-circle-filled": "\u{10137}", + "droplet": "\u{ea97}", + "droplet-bolt": "\u{f8f9}", + "droplet-cancel": "\u{f8fa}", + "droplet-check": "\u{f8fb}", + "droplet-code": "\u{f8fc}", + "droplet-cog": "\u{f8fd}", + "droplet-dollar": "\u{f8fe}", + "droplet-down": "\u{f8ff}", + "droplet-exclamation": "\u{f900}", + "droplet-filled": "\u{ee80}", + "droplet-half": "\u{ee82}", + "droplet-half-2": "\u{ee81}", + "droplet-half-2-filled": "\u{fb6c}", + "droplet-half-filled": "\u{f6c5}", + "droplet-heart": "\u{f901}", + "droplet-minus": "\u{f902}", + "droplet-off": "\u{ee83}", + "droplet-pause": "\u{f903}", + "droplet-pin": "\u{f904}", + "droplet-plus": "\u{f905}", + "droplet-question": "\u{f906}", + "droplet-search": "\u{f907}", + "droplet-share": "\u{f908}", + "droplet-star": "\u{f909}", + "droplet-up": "\u{f90a}", + "droplet-x": "\u{f90b}", + "droplets": "\u{fc12}", + "droplets-filled": "\u{100c9}", + "dual-screen": "\u{fa59}", + "dual-screen-filled": "\u{10136}", + "dumpling": "\u{feb5}", + "dumpling-filled": "\u{10135}", + "e-passport": "\u{f4df}", + "ear": "\u{ebce}", + "ear-off": "\u{ee84}", + "ear-scan": "\u{fd57}", + "ease-in": "\u{f573}", + "ease-in-control-point": "\u{f570}", + "ease-in-control-point-filled": "\u{10174}", + "ease-in-out": "\u{f572}", + "ease-in-out-control-points": "\u{f571}", + "ease-in-out-control-points-filled": "\u{10173}", + "ease-out": "\u{f575}", + "ease-out-control-point": "\u{f574}", + "ease-out-control-point-filled": "\u{10172}", + "edit": "\u{ea98}", + "edit-circle": "\u{ee85}", + "edit-circle-off": "\u{f11d}", + "edit-off": "\u{f11e}", + "egg": "\u{eb8a}", + "egg-cracked": "\u{f2d6}", + "egg-cracked-filled": "\u{10012}", + "egg-filled": "\u{f678}", + "egg-fried": "\u{f386}", + "egg-fried-filled": "\u{10134}", + "egg-off": "\u{f11f}", + "eggs": "\u{f500}", + "elevator": "\u{efdf}", + "elevator-filled": "\u{1003c}", + "elevator-off": "\u{f3e8}", + "emergency-bed": "\u{ef5d}", + "empathize": "\u{f29b}", + "empathize-off": "\u{f3e9}", + "emphasis": "\u{ebcf}", + "engine": "\u{ef7e}", + "engine-filled": "\u{100fc}", + "engine-off": "\u{f120}", + "equal": "\u{ee87}", + "equal-double": "\u{f4e1}", + "equal-not": "\u{ee86}", + "eraser": "\u{eb8b}", + "eraser-off": "\u{f121}", + "error-404": "\u{f027}", + "error-404-off": "\u{f122}", + "escalator": "\u{fb06}", + "escalator-down": "\u{fb04}", + "escalator-down-filled": "\u{10133}", + "escalator-filled": "\u{10131}", + "escalator-up": "\u{fb05}", + "escalator-up-filled": "\u{10132}", + "ethernet": "\u{eccc}", + "ethernet-off": "\u{eccd}", + "ethernet-exclamation": "\u{ecce}", + "ethernet-question": "\u{eccf}", + "exchange": "\u{ebe7}", + "exchange-filled": "\u{10130}", + "exchange-off": "\u{f123}", + "exclamation-circle": "\u{f634}", + "exclamation-circle-filled": "\u{ff62}", + "exclamation-mark": "\u{efb4}", + "exclamation-mark-off": "\u{f124}", + "explicit": "\u{f256}", + "explicit-filled": "\u{1012f}", + "explicit-off": "\u{f3ea}", + "exposure": "\u{eb8c}", + "exposure-0": "\u{f29c}", + "exposure-filled": "\u{10124}", + "exposure-minus-1": "\u{f29d}", + "exposure-minus-2": "\u{f29e}", + "exposure-off": "\u{f3eb}", + "exposure-plus-1": "\u{f29f}", + "exposure-plus-2": "\u{f2a0}", + "external-link": "\u{ea99}", + "external-link-off": "\u{f125}", + "eye": "\u{ea9a}", + "eye-bitcoin": "\u{ff3d}", + "eye-bolt": "\u{fb6d}", + "eye-cancel": "\u{fb6e}", + "eye-check": "\u{ee88}", + "eye-closed": "\u{f7ec}", + "eye-code": "\u{fb6f}", + "eye-cog": "\u{f7ed}", + "eye-discount": "\u{fb70}", + "eye-dollar": "\u{fb71}", + "eye-dotted": "\u{fead}", + "eye-down": "\u{fb72}", + "eye-edit": "\u{f7ee}", + "eye-exclamation": "\u{f7ef}", + "eye-filled": "\u{f679}", + "eye-heart": "\u{f7f0}", + "eye-minus": "\u{fb73}", + "eye-off": "\u{ecf0}", + "eye-pause": "\u{fb74}", + "eye-pin": "\u{fb75}", + "eye-plus": "\u{fb76}", + "eye-question": "\u{fb77}", + "eye-search": "\u{fb78}", + "eye-share": "\u{fb79}", + "eye-spark": "\u{ffba}", + "eye-star": "\u{fb7a}", + "eye-table": "\u{ef5e}", + "eye-table-filled": "\u{10123}", + "eye-up": "\u{fb7b}", + "eye-x": "\u{f7f1}", + "eyeglass": "\u{ee8a}", + "eyeglass-2": "\u{ee89}", + "eyeglass-2-filled": "\u{10122}", + "eyeglass-filled": "\u{100c8}", + "eyeglass-off": "\u{f126}", + "face-id": "\u{ea9b}", + "face-id-error": "\u{efa7}", + "face-mask": "\u{efb5}", + "face-mask-filled": "\u{10121}", + "face-mask-off": "\u{f127}", + "fall": "\u{ecb9}", + "favicon": "\u{fd65}", + "favicon-filled": "\u{10071}", + "feather": "\u{ee8b}", + "feather-filled": "\u{10011}", + "feather-off": "\u{f128}", + "fence": "\u{ef2a}", + "fence-filled": "\u{10120}", + "fence-off": "\u{f129}", + "ferry": "\u{10074}", + "ferry-filled": "\u{100fb}", + "fidget-spinner": "\u{f068}", + "fidget-spinner-filled": "\u{1011f}", + "file": "\u{eaa4}", + "file-3d": "\u{f032}", + "file-ai": "\u{ffa1}", + "file-alert": "\u{ede6}", + "file-analytics": "\u{ede7}", + "file-analytics-filled": "\u{10171}", + "file-arrow-left": "\u{f033}", + "file-arrow-right": "\u{f034}", + "file-barcode": "\u{f035}", + "file-bitcoin": "\u{ffa0}", + "file-broken": "\u{f501}", + "file-certificate": "\u{ed4d}", + "file-chart": "\u{f036}", + "file-check": "\u{ea9c}", + "file-check-filled": "\u{1012e}", + "file-code": "\u{ebd0}", + "file-code-2": "\u{ede8}", + "file-code-2-filled": "\u{1012d}", + "file-code-filled": "\u{10170}", + "file-cv": "\u{fa5a}", + "file-cv-filled": "\u{1012c}", + "file-database": "\u{f037}", + "file-delta": "\u{f53d}", + "file-delta-filled": "\u{1012b}", + "file-description": "\u{f028}", + "file-description-filled": "\u{1011e}", + "file-diff": "\u{ecf1}", + "file-diff-filled": "\u{1016f}", + "file-digit": "\u{efa8}", + "file-digit-filled": "\u{1011d}", + "file-dislike": "\u{ed2a}", + "file-dollar": "\u{efe0}", + "file-dots": "\u{f038}", + "file-dots-filled": "\u{1016e}", + "file-download": "\u{ea9d}", + "file-download-filled": "\u{1012a}", + "file-euro": "\u{efe1}", + "file-excel": "\u{fef3}", + "file-export": "\u{ede9}", + "file-filled": "\u{f747}", + "file-function": "\u{f53e}", + "file-function-filled": "\u{1016d}", + "file-horizontal": "\u{ebb0}", + "file-horizontal-filled": "\u{1011c}", + "file-import": "\u{edea}", + "file-infinity": "\u{f502}", + "file-info": "\u{edec}", + "file-info-filled": "\u{1016c}", + "file-invoice": "\u{eb67}", + "file-invoice-filled": "\u{1011b}", + "file-isr": "\u{feac}", + "file-lambda": "\u{f53f}", + "file-lambda-filled": "\u{10129}", + "file-like": "\u{ed2b}", + "file-minus": "\u{ea9e}", + "file-minus-filled": "\u{1011a}", + "file-music": "\u{ea9f}", + "file-neutral": "\u{fd22}", + "file-neutral-filled": "\u{10119}", + "file-off": "\u{ecf2}", + "file-orientation": "\u{f2a1}", + "file-pencil": "\u{f039}", + "file-percent": "\u{f540}", + "file-percent-filled": "\u{10128}", + "file-phone": "\u{ecdc}", + "file-phone-filled": "\u{10127}", + "file-plus": "\u{eaa0}", + "file-power": "\u{f03a}", + "file-power-filled": "\u{10118}", + "file-report": "\u{eded}", + "file-rss": "\u{f03b}", + "file-rss-filled": "\u{10126}", + "file-sad": "\u{fd23}", + "file-sad-filled": "\u{10117}", + "file-scissors": "\u{f03c}", + "file-search": "\u{ed5d}", + "file-settings": "\u{f029}", + "file-shredder": "\u{eaa1}", + "file-signal": "\u{f03d}", + "file-smile": "\u{fd24}", + "file-smile-filled": "\u{10116}", + "file-spark": "\u{ffb9}", + "file-spreadsheet": "\u{f03e}", + "file-stack": "\u{f503}", + "file-star": "\u{f03f}", + "file-star-filled": "\u{10115}", + "file-symlink": "\u{ed53}", + "file-text": "\u{eaa2}", + "file-text-ai": "\u{fa27}", + "file-text-filled": "\u{10114}", + "file-text-shield": "\u{100f2}", + "file-text-spark": "\u{ffb8}", + "file-time": "\u{f040}", + "file-type-bmp": "\u{fb07}", + "file-type-css": "\u{fb08}", + "file-type-csv": "\u{fb09}", + "file-type-doc": "\u{fb0a}", + "file-type-docx": "\u{fb0b}", + "file-type-html": "\u{fb0c}", + "file-type-jpg": "\u{fb0d}", + "file-type-js": "\u{fb0e}", + "file-type-jsx": "\u{fb0f}", + "file-type-pdf": "\u{fb10}", + "file-type-php": "\u{fb11}", + "file-type-png": "\u{fb12}", + "file-type-ppt": "\u{fb13}", + "file-type-rs": "\u{fb14}", + "file-type-sql": "\u{fb15}", + "file-type-svg": "\u{fb16}", + "file-type-ts": "\u{fb17}", + "file-type-tsx": "\u{fb18}", + "file-type-txt": "\u{fb19}", + "file-type-vue": "\u{fb1a}", + "file-type-xls": "\u{fb1b}", + "file-type-xml": "\u{fb1c}", + "file-type-zip": "\u{fb1d}", + "file-typography": "\u{f041}", + "file-typography-filled": "\u{1016b}", + "file-unknown": "\u{f042}", + "file-upload": "\u{ec91}", + "file-vector": "\u{f043}", + "file-word": "\u{fef2}", + "file-x": "\u{eaa3}", + "file-x-filled": "\u{f748}", + "file-zip": "\u{ed4e}", + "files": "\u{edef}", + "files-off": "\u{edee}", + "filter": "\u{eaa5}", + "filter-2": "\u{1014b}", + "filter-2-bolt": "\u{1015f}", + "filter-2-cancel": "\u{1015e}", + "filter-2-check": "\u{1015d}", + "filter-2-code": "\u{1015c}", + "filter-2-cog": "\u{1015b}", + "filter-2-discount": "\u{1015a}", + "filter-2-dollar": "\u{10159}", + "filter-2-down": "\u{10158}", + "filter-2-edit": "\u{10157}", + "filter-2-exclamation": "\u{10156}", + "filter-2-minus": "\u{10155}", + "filter-2-pause": "\u{10154}", + "filter-2-pin": "\u{10153}", + "filter-2-plus": "\u{10152}", + "filter-2-question": "\u{10151}", + "filter-2-search": "\u{10150}", + "filter-2-share": "\u{1014f}", + "filter-2-spark": "\u{1014e}", + "filter-2-up": "\u{1014d}", + "filter-2-x": "\u{1014c}", + "filter-bolt": "\u{fb7c}", + "filter-cancel": "\u{fb7d}", + "filter-check": "\u{fb7e}", + "filter-code": "\u{fb7f}", + "filter-cog": "\u{f9fe}", + "filter-discount": "\u{fb80}", + "filter-dollar": "\u{f9ff}", + "filter-down": "\u{fb81}", + "filter-edit": "\u{fa00}", + "filter-exclamation": "\u{fb82}", + "filter-filled": "\u{fc27}", + "filter-heart": "\u{fb83}", + "filter-minus": "\u{fa01}", + "filter-off": "\u{ed2c}", + "filter-pause": "\u{fb84}", + "filter-pin": "\u{fb85}", + "filter-plus": "\u{fa02}", + "filter-question": "\u{fb86}", + "filter-search": "\u{fb87}", + "filter-share": "\u{fb88}", + "filter-spark": "\u{1014a}", + "filter-star": "\u{fa03}", + "filter-up": "\u{fb89}", + "filter-x": "\u{fa04}", + "filters": "\u{f793}", + "filters-filled": "\u{100c7}", + "fingerprint": "\u{ebd1}", + "fingerprint-off": "\u{f12a}", + "fingerprint-scan": "\u{fcb5}", + "fire-extinguisher": "\u{faf6}", + "fire-hydrant": "\u{f3a9}", + "fire-hydrant-off": "\u{f3ec}", + "firetruck": "\u{ebe8}", + "first-aid-kit": "\u{ef5f}", + "first-aid-kit-off": "\u{f3ed}", + "fish": "\u{ef2b}", + "fish-bone": "\u{f287}", + "fish-bone-filled": "\u{1010b}", + "fish-christianity": "\u{f58b}", + "fish-hook": "\u{f1f9}", + "fish-hook-off": "\u{f3ee}", + "fish-off": "\u{f12b}", + "flag": "\u{eaa6}", + "flag-2": "\u{ee8c}", + "flag-2-filled": "\u{f707}", + "flag-2-off": "\u{f12c}", + "flag-3": "\u{ee8d}", + "flag-3-filled": "\u{f708}", + "flag-bitcoin": "\u{ff3c}", + "flag-bolt": "\u{fb8a}", + "flag-cancel": "\u{fb8b}", + "flag-check": "\u{fb8c}", + "flag-code": "\u{fb8d}", + "flag-cog": "\u{fb8e}", + "flag-discount": "\u{fb8f}", + "flag-dollar": "\u{fb90}", + "flag-down": "\u{fb91}", + "flag-exclamation": "\u{fb92}", + "flag-filled": "\u{f67a}", + "flag-heart": "\u{fb93}", + "flag-minus": "\u{fb94}", + "flag-off": "\u{f12d}", + "flag-pause": "\u{fb95}", + "flag-pin": "\u{fb96}", + "flag-plus": "\u{fb97}", + "flag-question": "\u{fb98}", + "flag-search": "\u{fb99}", + "flag-share": "\u{fb9a}", + "flag-spark": "\u{ffb7}", + "flag-star": "\u{fb9b}", + "flag-up": "\u{fb9c}", + "flag-x": "\u{fb9d}", + "flame": "\u{ec2c}", + "flame-filled": "\u{100c6}", + "flame-off": "\u{f12e}", + "flare": "\u{ee8e}", + "flare-filled": "\u{100c5}", + "flask": "\u{ebd2}", + "flask-2": "\u{ef60}", + "flask-2-filled": "\u{fd12}", + "flask-2-off": "\u{f12f}", + "flask-filled": "\u{fd13}", + "flask-off": "\u{f130}", + "flip-flops": "\u{f564}", + "flip-horizontal": "\u{eaa7}", + "flip-vertical": "\u{eaa8}", + "float-center": "\u{ebb1}", + "float-left": "\u{ebb2}", + "float-none": "\u{ed13}", + "float-right": "\u{ebb3}", + "flower": "\u{eff6}", + "flower-filled": "\u{10010}", + "flower-off": "\u{f131}", + "focus": "\u{eb8d}", + "focus-2": "\u{ebd3}", + "focus-auto": "\u{fa62}", + "focus-centered": "\u{f02a}", + "fold": "\u{ed56}", + "fold-down": "\u{ed54}", + "fold-up": "\u{ed55}", + "folder": "\u{eaad}", + "folder-bolt": "\u{f90c}", + "folder-cancel": "\u{f90d}", + "folder-check": "\u{f90e}", + "folder-code": "\u{f90f}", + "folder-cog": "\u{f910}", + "folder-dollar": "\u{f911}", + "folder-down": "\u{f912}", + "folder-exclamation": "\u{f913}", + "folder-filled": "\u{f749}", + "folder-heart": "\u{f914}", + "folder-minus": "\u{eaaa}", + "folder-off": "\u{ed14}", + "folder-open": "\u{faf7}", + "folder-pause": "\u{f915}", + "folder-pin": "\u{f916}", + "folder-plus": "\u{eaab}", + "folder-question": "\u{f917}", + "folder-root": "\u{fd43}", + "folder-search": "\u{f918}", + "folder-share": "\u{f919}", + "folder-star": "\u{f91a}", + "folder-symlink": "\u{f91b}", + "folder-up": "\u{f91c}", + "folder-x": "\u{eaac}", + "folders": "\u{eaae}", + "folders-filled": "\u{100c4}", + "folders-off": "\u{f133}", + "forbid": "\u{ebd5}", + "forbid-2": "\u{ebd4}", + "forbid-2-filled": "\u{fc28}", + "forbid-filled": "\u{fc29}", + "forklift": "\u{ebe9}", + "forms": "\u{ee8f}", + "fountain": "\u{f09b}", + "fountain-filled": "\u{fc2a}", + "fountain-off": "\u{f134}", + "frame": "\u{eaaf}", + "frame-off": "\u{f135}", + "free-rights": "\u{efb6}", + "freeze-column": "\u{fa63}", + "freeze-row": "\u{fa65}", + "freeze-row-column": "\u{fa64}", + "fridge": "\u{f1fa}", + "fridge-off": "\u{f3ef}", + "friends": "\u{eab0}", + "friends-off": "\u{f136}", + "frustum": "\u{fa9f}", + //"frustum-off": "\u{fa9d}", + "frustum-plus": "\u{fa9e}", + "function": "\u{f225}", + "function-filled": "\u{fc2b}", + "function-off": "\u{f3f0}", + "galaxy": "\u{fcb6}", + "garden-cart": "\u{f23e}", + "garden-cart-filled": "\u{100c3}", + "garden-cart-off": "\u{f3f1}", + "gas-station": "\u{ec7d}", + "gas-station-filled": "\u{100fa}", + "gas-station-off": "\u{f137}", + "gauge": "\u{eab1}", + "gauge-filled": "\u{fc2c}", + "gauge-off": "\u{f138}", + "gavel": "\u{ef90}", + "gender-agender": "\u{f0e1}", + "gender-androgyne": "\u{f0e2}", + "gender-bigender": "\u{f0e3}", + "gender-demiboy": "\u{f0e4}", + "gender-demigirl": "\u{f0e5}", + "gender-epicene": "\u{f0e6}", + "gender-female": "\u{f0e7}", + "gender-femme": "\u{f0e8}", + "gender-genderfluid": "\u{f0e9}", + "gender-genderless": "\u{f0ea}", + "gender-genderqueer": "\u{f0eb}", + "gender-hermaphrodite": "\u{f0ec}", + "gender-intergender": "\u{f0ed}", + "gender-male": "\u{f0ee}", + "gender-neutrois": "\u{f0ef}", + "gender-third": "\u{f0f0}", + "gender-transgender": "\u{f0f1}", + "gender-trasvesti": "\u{f0f2}", + "geometry": "\u{ee90}", + "ghost": "\u{eb8e}", + "ghost-2": "\u{f57c}", + "ghost-2-filled": "\u{f74a}", + "ghost-3": "\u{fc13}", + "ghost-3-filled": "\u{100a4}", + "ghost-filled": "\u{f74b}", + "ghost-off": "\u{f3f2}", + "gif": "\u{f257}", + "gift": "\u{eb68}", + "gift-card": "\u{f3aa}", + "gift-card-filled": "\u{fc2d}", + "gift-filled": "\u{fd14}", + "gift-off": "\u{f3f3}", + "git-branch": "\u{eab2}", + "git-branch-deleted": "\u{f57d}", + "git-cherry-pick": "\u{f57e}", + "git-commit": "\u{eab3}", + "git-compare": "\u{eab4}", + "git-fork": "\u{eb8f}", + "git-merge": "\u{eab5}", + "git-pull-request": "\u{eab6}", + "git-pull-request-closed": "\u{ef7f}", + "git-pull-request-draft": "\u{efb7}", + "gizmo": "\u{f02b}", + "glass": "\u{eab8}", + "glass-champagne": "\u{fd9c}", + "glass-cocktail": "\u{fd9d}", + "glass-filled": "\u{1000f}", + "glass-full": "\u{eab7}", + "glass-full-filled": "\u{fc2e}", + "glass-gin": "\u{fd9e}", + "glass-off": "\u{ee91}", + "globe": "\u{eab9}", + "globe-filled": "\u{fc2f}", + "globe-off": "\u{f139}", + "go-game": "\u{f512}", + "golf": "\u{ed8c}", + "golf-filled": "\u{100a3}", + "golf-off": "\u{f13a}", + "gps": "\u{ed7a}", + "gps-filled": "\u{fe48}", + "gradienter": "\u{f3ab}", + "grain": "\u{ee92}", + "graph": "\u{f288}", + "graph-filled": "\u{fd15}", + "graph-off": "\u{f3f4}", + "grave": "\u{f580}", + "grave-2": "\u{f57f}", + "grid-3x3": "\u{fca4}", + "grid-4x4": "\u{fca5}", + "grid-dots": "\u{eaba}", + "grid-goldenratio": "\u{fca6}", + "grid-pattern": "\u{efc9}", + "grid-pattern-filled": "\u{100c2}", + "grid-scan": "\u{fca7}", + "grill": "\u{efa9}", + "grill-fork": "\u{f35b}", + "grill-off": "\u{f3f5}", + "grill-spatula": "\u{f35c}", + "grip-horizontal": "\u{ec00}", + "grip-vertical": "\u{ec01}", + "growth": "\u{ee93}", + "guitar-pick": "\u{f4c6}", + "guitar-pick-filled": "\u{f67b}", + "gymnastics": "\u{fd44}", + "h-1": "\u{ec94}", + "h-2": "\u{ec95}", + "h-3": "\u{ec96}", + "h-4": "\u{ec97}", + "h-5": "\u{ec98}", + "h-6": "\u{ec99}", + "hammer": "\u{ef91}", + "hammer-off": "\u{f13c}", + "hand-click": "\u{ef4f}", + "hand-click-off": "\u{100f1}", + "hand-finger": "\u{ee94}", + "hand-finger-down": "\u{ff4f}", + "hand-finger-left": "\u{ff4e}", + "hand-finger-off": "\u{f13d}", + "hand-finger-right": "\u{ff4d}", + "hand-grab": "\u{f091}", + "hand-little-finger": "\u{ee95}", + "hand-love-you": "\u{ee97}", + "hand-middle-finger": "\u{ec2d}", + "hand-move": "\u{ef50}", + "hand-off": "\u{ed15}", + "hand-ring-finger": "\u{ee96}", + "hand-rock": "\u{ee97}", + "hand-sanitizer": "\u{f5f4}", + "hand-stop": "\u{ec2e}", + "hand-three-fingers": "\u{ee98}", + "hand-two-fingers": "\u{ee99}", + "hanger": "\u{ee9a}", + "hanger-2": "\u{f09c}", + "hanger-2-filled": "\u{ff61}", + "hanger-off": "\u{f13e}", + "hash": "\u{eabc}", + "haze": "\u{efaa}", + "haze-moon": "\u{faf8}", + "hdr": "\u{fa7b}", + "heading": "\u{ee9b}", + "heading-off": "\u{f13f}", + "headphones": "\u{eabd}", + "headphones-filled": "\u{fa3c}", + "headphones-off": "\u{ed1d}", + "headset": "\u{eb90}", + "headset-off": "\u{f3f6}", + "health-recognition": "\u{f1fb}", + "heart": "\u{eabe}", + "heart-bitcoin": "\u{ff3b}", + "heart-bolt": "\u{fb9e}", + "heart-broken": "\u{ecba}", + "heart-broken-filled": "\u{1016a}", + "heart-cancel": "\u{fb9f}", + "heart-check": "\u{fba0}", + "heart-code": "\u{fba1}", + "heart-cog": "\u{fba2}", + "heart-discount": "\u{fba3}", + "heart-dollar": "\u{fba4}", + "heart-down": "\u{fba5}", + "heart-exclamation": "\u{fba6}", + "heart-filled": "\u{f67c}", + "heart-handshake": "\u{f0f3}", + "heart-minus": "\u{f140}", + "heart-off": "\u{f141}", + "heart-pause": "\u{fba7}", + "heart-pin": "\u{fba8}", + "heart-plus": "\u{f142}", + "heart-question": "\u{fba9}", + "heart-rate-monitor": "\u{ef61}", + "heart-search": "\u{fbaa}", + "heart-share": "\u{fbab}", + "heart-spark": "\u{ffb6}", + "heart-star": "\u{fbac}", + "heart-up": "\u{fbad}", + "heart-x": "\u{fbae}", + "heartbeat": "\u{ef92}", + "hearts": "\u{f387}", + "hearts-off": "\u{f3f7}", + "helicopter": "\u{ed8e}", + "helicopter-filled": "\u{100f9}", + "helicopter-landing": "\u{ed8d}", + "helicopter-landing-filled": "\u{100c1}", + "helmet": "\u{efca}", + "helmet-off": "\u{f143}", + "help": "\u{eabf}", + "help-circle": "\u{f91d}", + "help-circle-filled": "\u{fa3d}", + "help-hexagon": "\u{f7a8}", + "help-hexagon-filled": "\u{fa3e}", + "help-octagon": "\u{f7a9}", + "help-octagon-filled": "\u{fa3f}", + "help-off": "\u{f3f8}", + "help-small": "\u{f91e}", + "help-square": "\u{f920}", + "help-square-filled": "\u{fa40}", + "help-square-rounded": "\u{f91f}", + "help-square-rounded-filled": "\u{fa41}", + "help-triangle": "\u{f921}", + "help-triangle-filled": "\u{fa42}", + "hemisphere": "\u{faa2}", + "hemisphere-off": "\u{faa0}", + "hemisphere-plus": "\u{faa1}", + "hexagon": "\u{ec02}", + "hexagon-0": "\u{f459}", + "hexagon-1": "\u{f45a}", + "hexagon-2": "\u{f45b}", + "hexagon-3": "\u{f45c}", + "hexagon-3d": "\u{f4c7}", + "hexagon-4": "\u{f45d}", + "hexagon-5": "\u{f45e}", + "hexagon-6": "\u{f45f}", + "hexagon-7": "\u{f460}", + "hexagon-8": "\u{f461}", + "hexagon-9": "\u{f462}", + "hexagon-filled": "\u{f67d}", + "hexagon-letter-a": "\u{f463}", + "hexagon-letter-a-filled": "\u{fe47}", + "hexagon-letter-b": "\u{f464}", + "hexagon-letter-b-filled": "\u{fe46}", + "hexagon-letter-c": "\u{f465}", + "hexagon-letter-c-filled": "\u{fe45}", + "hexagon-letter-d": "\u{f466}", + "hexagon-letter-d-filled": "\u{fe44}", + "hexagon-letter-e": "\u{f467}", + "hexagon-letter-e-filled": "\u{fe43}", + "hexagon-letter-f": "\u{f468}", + "hexagon-letter-f-filled": "\u{fe42}", + "hexagon-letter-g": "\u{f469}", + "hexagon-letter-g-filled": "\u{fe41}", + "hexagon-letter-h": "\u{f46a}", + "hexagon-letter-h-filled": "\u{fe40}", + "hexagon-letter-i": "\u{f46b}", + "hexagon-letter-i-filled": "\u{fe3f}", + "hexagon-letter-j": "\u{f46c}", + "hexagon-letter-j-filled": "\u{fe3e}", + "hexagon-letter-k": "\u{f46d}", + "hexagon-letter-k-filled": "\u{fe3d}", + "hexagon-letter-l": "\u{f46e}", + "hexagon-letter-l-filled": "\u{fe3c}", + "hexagon-letter-m": "\u{f46f}", + "hexagon-letter-m-filled": "\u{fe3b}", + "hexagon-letter-n": "\u{f470}", + "hexagon-letter-n-filled": "\u{fe3a}", + "hexagon-letter-o": "\u{f471}", + "hexagon-letter-o-filled": "\u{fe39}", + "hexagon-letter-p": "\u{f472}", + "hexagon-letter-p-filled": "\u{fe38}", + "hexagon-letter-q": "\u{f473}", + "hexagon-letter-q-filled": "\u{fe37}", + "hexagon-letter-r": "\u{f474}", + "hexagon-letter-r-filled": "\u{fe36}", + "hexagon-letter-s": "\u{f475}", + "hexagon-letter-s-filled": "\u{fe35}", + "hexagon-letter-t": "\u{f476}", + "hexagon-letter-t-filled": "\u{fe34}", + "hexagon-letter-u": "\u{f477}", + "hexagon-letter-u-filled": "\u{fe33}", + "hexagon-letter-v": "\u{f4b3}", + "hexagon-letter-v-filled": "\u{fe32}", + "hexagon-letter-w": "\u{f478}", + "hexagon-letter-w-filled": "\u{fe31}", + "hexagon-letter-x": "\u{f479}", + "hexagon-letter-x-filled": "\u{fe30}", + "hexagon-letter-y": "\u{f47a}", + //"hexagon-letter-y-filled": "\u{fe2f}", + "hexagon-letter-z": "\u{f47b}", + //"hexagon-letter-z-filled": "\u{fe2e}", + "hexagon-minus": "\u{fc8f}", + "hexagon-minus-2": "\u{fc8e}", + //"hexagon-minus-filled": "\u{fe2d}", + "hexagon-number-0": "\u{f459}", + "hexagon-number-0-filled": "\u{f74c}", + "hexagon-number-1": "\u{f45a}", + "hexagon-number-1-filled": "\u{f74d}", + "hexagon-number-2": "\u{f45b}", + "hexagon-number-2-filled": "\u{f74e}", + "hexagon-number-3": "\u{f45c}", + "hexagon-number-3-filled": "\u{f74f}", + "hexagon-number-4": "\u{f45d}", + "hexagon-number-4-filled": "\u{f750}", + "hexagon-number-5": "\u{f45e}", + "hexagon-number-5-filled": "\u{f751}", + "hexagon-number-6": "\u{f45f}", + "hexagon-number-6-filled": "\u{f752}", + "hexagon-number-7": "\u{f460}", + "hexagon-number-7-filled": "\u{f753}", + "hexagon-number-8": "\u{f461}", + "hexagon-number-8-filled": "\u{f754}", + "hexagon-number-9": "\u{f462}", + "hexagon-number-9-filled": "\u{f755}", + "hexagon-off": "\u{ee9c}", + "hexagon-plus": "\u{fc45}", + "hexagon-plus-2": "\u{fc90}", + //"hexagon-plus-filled": "\u{fe2c}", + "hexagonal-prism": "\u{faa5}", + "hexagonal-prism-off": "\u{faa3}", + "hexagonal-prism-plus": "\u{faa4}", + "hexagonal-pyramid": "\u{faa8}", + "hexagonal-pyramid-off": "\u{faa6}", + "hexagonal-pyramid-plus": "\u{faa7}", + "hexagons": "\u{f09d}", + "hexagons-off": "\u{f3f9}", + "hierarchy": "\u{ee9e}", + "hierarchy-2": "\u{ee9d}", + "hierarchy-3": "\u{f289}", + "hierarchy-off": "\u{f3fa}", + "highlight": "\u{ef3f}", + "highlight-off": "\u{f144}", + "history": "\u{ebea}", + "history-off": "\u{f3fb}", + "history-toggle": "\u{f1fc}", + "home": "\u{eac1}", + "home-2": "\u{eac0}", + "home-bitcoin": "\u{ff3a}", + "home-bolt": "\u{f336}", + "home-cancel": "\u{f350}", + "home-check": "\u{f337}", + "home-cog": "\u{f338}", + "home-dollar": "\u{f339}", + "home-dot": "\u{f33a}", + "home-down": "\u{f33b}", + "home-eco": "\u{f351}", + "home-edit": "\u{f352}", + "home-exclamation": "\u{f33c}", + //"home-filled": "\u{fe2b}", + "home-hand": "\u{f504}", + "home-heart": "\u{f353}", + "home-infinity": "\u{f505}", + "home-link": "\u{f354}", + "home-minus": "\u{f33d}", + "home-move": "\u{f33e}", + "home-off": "\u{f145}", + "home-plus": "\u{f33f}", + "home-question": "\u{f340}", + "home-ribbon": "\u{f355}", + "home-search": "\u{f341}", + "home-share": "\u{f342}", + "home-shield": "\u{f343}", + "home-signal": "\u{f356}", + "home-spark": "\u{ffb5}", + "home-star": "\u{f344}", + "home-stats": "\u{f345}", + "home-up": "\u{f346}", + "home-x": "\u{f347}", + "horse": "\u{fc46}", + "horse-toy": "\u{f28a}", + "horseshoe": "\u{fcb7}", + "hospital": "\u{fd59}", + "hospital-circle": "\u{fd58}", + "hospital-circle-filled": "\u{fed2}", + "hotel-service": "\u{ef80}", + "hourglass": "\u{ef93}", + "hourglass-empty": "\u{f146}", + "hourglass-filled": "\u{f756}", + "hourglass-high": "\u{f092}", + "hourglass-low": "\u{f093}", + "hourglass-off": "\u{f147}", + "hours-12": "\u{fc53}", + "hours-24": "\u{f5e7}", + "html": "\u{f7b1}", + "http-connect": "\u{fa28}", + "http-connect-off": "\u{100e7}", + "http-delete": "\u{fa29}", + "http-delete-off": "\u{100e6}", + "http-get": "\u{fa2a}", + "http-get-off": "\u{100e5}", + "http-head": "\u{fa2b}", + "http-head-off": "\u{100e4}", + "http-options": "\u{fa2c}", + "http-options-off": "\u{100e3}", + "http-patch": "\u{fa2d}", + "http-patch-off": "\u{100e2}", + "http-post": "\u{fa2e}", + "http-post-off": "\u{100e1}", + "http-put": "\u{fa2f}", + "http-put-off": "\u{100e0}", + "http-que": "\u{fa5b}", + "http-que-off": "\u{100df}", + "http-trace": "\u{fa30}", + "http-trace-off": "\u{100de}", + "hyprland": "\u{ec6a}", + "ice-cream": "\u{eac2}", + "ice-cream-2": "\u{ee9f}", + "ice-cream-off": "\u{f148}", + "ice-skating": "\u{efcb}", + "icons": "\u{f1d4}", + "icons-filled": "\u{10070}", + "icons-off": "\u{f3fc}", + "id": "\u{eac3}", + "id-badge": "\u{eff7}", + "id-badge-2": "\u{f076}", + "id-badge-off": "\u{f3fd}", + "id-off": "\u{f149}", + "ikosaedr": "\u{fec6}", + "image-in-picture": "\u{fd9f}", + "inbox": "\u{eac4}", + "inbox-off": "\u{f14a}", + "indent-decrease": "\u{eb91}", + "indent-increase": "\u{eb92}", + "infinity": "\u{eb69}", + "infinity-off": "\u{f3fe}", + "info-circle": "\u{eac5}", + "info-circle-filled": "\u{f6d8}", + "info-hexagon": "\u{f7aa}", + "info-hexagon-filled": "\u{fa43}", + "info-octagon": "\u{f7ab}", + "info-octagon-filled": "\u{fa44}", + "info-small": "\u{f922}", + "info-square": "\u{eac6}", + "info-square-filled": "\u{fa45}", + "info-square-rounded": "\u{f635}", + "info-square-rounded-filled": "\u{f6d9}", + "info-triangle": "\u{f923}", + "info-triangle-filled": "\u{fa46}", + "inner-shadow-bottom": "\u{f520}", + "inner-shadow-bottom-filled": "\u{f757}", + "inner-shadow-bottom-left": "\u{f51e}", + "inner-shadow-bottom-left-filled": "\u{f758}", + "inner-shadow-bottom-right": "\u{f51f}", + "inner-shadow-bottom-right-filled": "\u{f759}", + "inner-shadow-left": "\u{f521}", + "inner-shadow-left-filled": "\u{f75a}", + "inner-shadow-right": "\u{f522}", + "inner-shadow-right-filled": "\u{f75b}", + "inner-shadow-top": "\u{f525}", + "inner-shadow-top-filled": "\u{f75c}", + "inner-shadow-top-left": "\u{f523}", + "inner-shadow-top-left-filled": "\u{f75d}", + "inner-shadow-top-right": "\u{f524}", + "inner-shadow-top-right-filled": "\u{f75e}", + "input-ai": "\u{fc5a}", + "input-check": "\u{fc5b}", + "input-search": "\u{f2a2}", + "input-spark": "\u{ffb4}", + "input-x": "\u{fc5c}", + "invoice": "\u{feab}", + "ironing": "\u{fa7c}", + "ironing-1": "\u{f2f4}", + "ironing-1-filled": "\u{1006f}", + "ironing-2": "\u{f2f5}", + "ironing-2-filled": "\u{1006e}", + "ironing-3": "\u{f2f6}", + "ironing-3-filled": "\u{1006d}", + //"ironing-filled": "\u{fe2a}", + "ironing-off": "\u{f2f7}", + "ironing-steam": "\u{f2f9}", + "ironing-steam-filled": "\u{1006c}", + "ironing-steam-off": "\u{f2f8}", + "irregular-polyhedron": "\u{faab}", + "irregular-polyhedron-off": "\u{faa9}", + "irregular-polyhedron-plus": "\u{faaa}", + "italic": "\u{eb93}", + "jacket": "\u{f661}", + "jetpack": "\u{f581}", + //"jetpack-filled": "\u{fe29}", + "jewish-star": "\u{f3ff}", + "jewish-star-filled": "\u{f67e}", + "join-bevel": "\u{ff4c}", + "join-round": "\u{ff4b}", + "join-straight": "\u{ff4a}", + "joker": "\u{1005f}", + "jpg": "\u{f3ac}", + "json": "\u{f7b2}", + "jump-rope": "\u{ed8f}", + "karate": "\u{ed32}", + "kayak": "\u{f1d6}", + "kering": "\u{efb8}", + "kerning": "\u{efb8}", + "key": "\u{eac7}", + //"key-filled": "\u{fe28}", + "key-off": "\u{f14b}", + "keyboard": "\u{ebd6}", + "keyboard-filled": "\u{100a2}", + "keyboard-hide": "\u{ec7e}", + "keyboard-off": "\u{eea0}", + "keyboard-show": "\u{ec7f}", + "keyframe": "\u{f576}", + "keyframe-align-center": "\u{f582}", + "keyframe-align-center-filled": "\u{fc30}", + "keyframe-align-horizontal": "\u{f583}", + "keyframe-align-horizontal-filled": "\u{fc31}", + "keyframe-align-vertical": "\u{f584}", + "keyframe-align-vertical-filled": "\u{fc32}", + "keyframe-filled": "\u{fc33}", + "keyframes": "\u{f585}", + "keyframes-filled": "\u{fc34}", + "label": "\u{ff38}", + "label-filled": "\u{ff41}", + "label-important": "\u{ff49}", + "label-important-filled": "\u{ff60}", + "label-off": "\u{ff39}", + "ladder": "\u{efe2}", + "ladder-off": "\u{f14c}", + "ladle": "\u{fc14}", + "lambda": "\u{f541}", + "lamp": "\u{efab}", + "lamp-2": "\u{f09e}", + "lamp-off": "\u{f14d}", + "lane": "\u{faf9}", + "language": "\u{ebbe}", + "language-hiragana": "\u{ef77}", + "language-katakana": "\u{ef78}", + "language-off": "\u{f14e}", + "lasso": "\u{efac}", + "lasso-off": "\u{f14f}", + "lasso-polygon": "\u{f388}", + "lasso-polygon-filled": "\u{ff5f}", + "laurel-wreath": "\u{ff45}", + "laurel-wreath-1": "\u{ff48}", + "laurel-wreath-1-filled": "\u{10169}", + "laurel-wreath-2": "\u{ff47}", + "laurel-wreath-2-filled": "\u{10168}", + "laurel-wreath-3": "\u{ff46}", + "laurel-wreath-3-filled": "\u{10167}", + "laurel-wreath-filled": "\u{100c0}", + "layers-difference": "\u{eac8}", + "layers-intersect": "\u{eac9}", + "layers-intersect-2": "\u{eff8}", + "layers-linked": "\u{eea1}", + "layers-off": "\u{f150}", + "layers-selected": "\u{fea9}", + "layers-selected-bottom": "\u{feaa}", + "layers-subtract": "\u{eaca}", + "layers-union": "\u{eacb}", + "layout": "\u{eadb}", + "layout-2": "\u{eacc}", + //"layout-2-filled": "\u{fe27}", + //"layout-align-bottom": "\u{eacd}", + //"layout-align-bottom-filled": "\u{fe26}", + //"layout-align-center": "\u{eace}", + //"layout-align-center-filled": "\u{fe25}", + "layout-align-left": "\u{eacf}", + //"layout-align-left-filled": "\u{fe24}", + "layout-align-middle": "\u{ead0}", + //"layout-align-middle-filled": "\u{fe23}", + "layout-align-right": "\u{ead1}", + //"layout-align-right-filled": "\u{fe22}", + "layout-align-top": "\u{ead2}", + //"layout-align-top-filled": "\u{fe21}", + "layout-board": "\u{ef95}", + "layout-board-filled": "\u{10182}", + "layout-board-split": "\u{ef94}", + "layout-board-split-filled": "\u{10183}", + "layout-bottombar": "\u{ead3}", + "layout-bottombar-collapse": "\u{f28b}", + "layout-bottombar-collapse-filled": "\u{fc35}", + "layout-bottombar-expand": "\u{f28c}", + "layout-bottombar-expand-filled": "\u{fc36}", + "layout-bottombar-filled": "\u{fc37}", + "layout-bottombar-inactive": "\u{fd45}", + "layout-cards": "\u{ec13}", + //"layout-cards-filled": "\u{fe20}", + "layout-collage": "\u{f389}", + "layout-columns": "\u{ead4}", + "layout-dashboard": "\u{f02c}", + "layout-dashboard-filled": "\u{fe1f}", + "layout-distribute-horizontal": "\u{ead5}", + "layout-distribute-horizontal-filled": "\u{fe1e}", + "layout-distribute-vertical": "\u{ead6}", + "layout-distribute-vertical-filled": "\u{fe1d}", + "layout-filled": "\u{fe17}", + "layout-grid": "\u{edba}", + "layout-grid-add": "\u{edb9}", + "layout-grid-filled": "\u{fe1c}", + "layout-grid-remove": "\u{fa7d}", + "layout-kanban": "\u{ec3f}", + "layout-kanban-filled": "\u{fe1b}", + "layout-list": "\u{ec14}", + "layout-list-filled": "\u{fe1a}", + "layout-navbar": "\u{ead7}", + "layout-navbar-collapse": "\u{f28d}", + "layout-navbar-collapse-filled": "\u{fc38}", + "layout-navbar-expand": "\u{f28e}", + "layout-navbar-expand-filled": "\u{fc39}", + "layout-navbar-filled": "\u{fc3a}", + "layout-navbar-inactive": "\u{fd46}", + "layout-off": "\u{f151}", + "layout-rows": "\u{ead8}", + "layout-sidebar": "\u{eada}", + "layout-sidebar-filled": "\u{fe18}", + "layout-sidebar-inactive": "\u{fd47}", + "layout-sidebar-left-collapse": "\u{f004}", + "layout-sidebar-left-collapse-filled": "\u{fc3b}", + "layout-sidebar-left-expand": "\u{f005}", + "layout-sidebar-left-expand-filled": "\u{fc3c}", + "layout-sidebar-right": "\u{ead9}", + "layout-sidebar-right-collapse": "\u{f006}", + "layout-sidebar-right-collapse-filled": "\u{fc3d}", + "layout-sidebar-right-expand": "\u{f007}", + "layout-sidebar-right-expand-filled": "\u{fc3e}", + "layout-sidebar-right-filled": "\u{fe19}", + "layout-sidebar-right-inactive": "\u{fd48}", + "leaf": "\u{ed4f}", + "leaf-2": "\u{ff44}", + "leaf-off": "\u{f400}", + "lego": "\u{eadc}", + "lego-filled": "\u{fe16}", + "lego-off": "\u{f401}", + "lemon": "\u{ef10}", + "lemon-2": "\u{ef81}", + "lemon-2-filled": "\u{100bf}", + "letter-a": "\u{ec50}", + "letter-a-small": "\u{fcc7}", + "letter-b": "\u{ec51}", + "letter-b-small": "\u{fcc8}", + "letter-c": "\u{ec52}", + "letter-c-small": "\u{fcc9}", + "letter-case": "\u{eea5}", + "letter-case-lower": "\u{eea2}", + "letter-case-toggle": "\u{eea3}", + "letter-case-upper": "\u{eea4}", + "letter-d": "\u{ec53}", + "letter-d-small": "\u{fcca}", + "letter-e": "\u{ec54}", + "letter-e-small": "\u{fccb}", + "letter-f": "\u{ec55}", + "letter-f-small": "\u{fccc}", + "letter-g": "\u{ec56}", + "letter-g-small": "\u{fccd}", + "letter-h": "\u{ec57}", + "letter-h-small": "\u{fcce}", + "letter-i": "\u{ec58}", + "letter-i-small": "\u{fccf}", + "letter-j": "\u{ec59}", + "letter-j-small": "\u{fcd0}", + "letter-k": "\u{ec5a}", + "letter-k-small": "\u{fcd1}", + "letter-l": "\u{ec5b}", + "letter-l-small": "\u{fcd2}", + "letter-m": "\u{ec5c}", + "letter-m-small": "\u{fcd3}", + "letter-n": "\u{ec5d}", + "letter-n-small": "\u{fcd4}", + "letter-o": "\u{ec5e}", + "letter-o-small": "\u{fcd5}", + "letter-p": "\u{ec5f}", + "letter-p-small": "\u{fcd6}", + "letter-q": "\u{ec60}", + "letter-q-small": "\u{fcd7}", + "letter-r": "\u{ec61}", + "letter-r-small": "\u{fcd8}", + "letter-s": "\u{ec62}", + "letter-s-small": "\u{fcd9}", + "letter-spacing": "\u{eea6}", + "letter-t": "\u{ec63}", + "letter-t-small": "\u{fcda}", + "letter-u": "\u{ec64}", + "letter-u-small": "\u{fcdb}", + "letter-v": "\u{ec65}", + "letter-v-small": "\u{fcdc}", + "letter-w": "\u{ec66}", + "letter-w-small": "\u{fcdd}", + "letter-x": "\u{ec67}", + "letter-x-small": "\u{fcde}", + "letter-y": "\u{ec68}", + "letter-y-small": "\u{fcdf}", + "letter-z": "\u{ec69}", + "letter-z-small": "\u{fce0}", + "library": "\u{fd4c}", + "library-filled": "\u{10180}", + "library-minus": "\u{fd49}", + "library-photo": "\u{fd4a}", + "library-plus": "\u{fd4b}", + "library-plus-filled": "\u{10181}", + "license": "\u{ebc0}", + "license-off": "\u{f153}", + "lifebuoy": "\u{eadd}", + "lifebuoy-filled": "\u{100be}", + "lifebuoy-off": "\u{f154}", + "lighter": "\u{f794}", + "line": "\u{ec40}", + "line-dashed": "\u{eea7}", + "line-dotted": "\u{eea8}", + "line-height": "\u{eb94}", + "line-scan": "\u{fcb8}", + "link": "\u{eade}", + "link-minus": "\u{fd16}", + "link-off": "\u{f402}", + "link-plus": "\u{fd17}", + "list": "\u{eb6b}", + "list-check": "\u{eb6a}", + "list-details": "\u{ef40}", + "list-letters": "\u{fc47}", + "list-numbers": "\u{ef11}", + "list-search": "\u{eea9}", + "list-tree": "\u{fafa}", + "live-photo": "\u{eadf}", + "live-photo-filled": "\u{fed1}", + "live-photo-off": "\u{f403}", + "live-view": "\u{ec6b}", + "live-view-filled": "\u{100a1}", + "load-balancer": "\u{fa5c}", + "loader": "\u{eca3}", + "loader-2": "\u{f226}", + "loader-3": "\u{f513}", + "loader-quarter": "\u{eca2}", + "location": "\u{eae0}", + "location-bolt": "\u{fbaf}", + "location-broken": "\u{f2c4}", + "location-cancel": "\u{fbb0}", + "location-check": "\u{fbb1}", + "location-code": "\u{fbb2}", + "location-cog": "\u{fbb3}", + "location-discount": "\u{fbb4}", + "location-dollar": "\u{fbb5}", + "location-down": "\u{fbb6}", + "location-exclamation": "\u{fbb7}", + "location-filled": "\u{f67f}", + "location-heart": "\u{fbb8}", + "location-minus": "\u{fbb9}", + "location-off": "\u{f155}", + "location-pause": "\u{fbba}", + "location-pin": "\u{fbbb}", + "location-plus": "\u{fbbc}", + "location-question": "\u{fbbd}", + "location-search": "\u{fbbe}", + "location-share": "\u{fbbf}", + "location-star": "\u{fbc0}", + "location-up": "\u{fbc1}", + "location-x": "\u{fbc2}", + "lock": "\u{eae2}", + "lock-access": "\u{eeaa}", + "lock-access-off": "\u{f404}", + "lock-bitcoin": "\u{ff37}", + "lock-bolt": "\u{f924}", + "lock-cancel": "\u{f925}", + "lock-check": "\u{f926}", + "lock-code": "\u{f927}", + "lock-cog": "\u{f928}", + "lock-dollar": "\u{f929}", + "lock-down": "\u{f92a}", + "lock-exclamation": "\u{f92b}", + "lock-filled": "\u{fe15}", + "lock-heart": "\u{f92c}", + "lock-minus": "\u{f92d}", + "lock-off": "\u{ed1e}", + "lock-open": "\u{eae1}", + "lock-open-2": "\u{fea8}", + "lock-open-off": "\u{f156}", + "lock-password": "\u{ff9f}", + "lock-pause": "\u{f92e}", + "lock-pin": "\u{f92f}", + "lock-plus": "\u{f930}", + "lock-question": "\u{f931}", + "lock-search": "\u{f932}", + "lock-share": "\u{f933}", + "lock-square": "\u{ef51}", + "lock-square-rounded": "\u{f636}", + "lock-square-rounded-filled": "\u{f6da}", + "lock-star": "\u{f934}", + "lock-up": "\u{f935}", + "lock-x": "\u{f936}", + "logic-and": "\u{f240}", + "logic-buffer": "\u{f241}", + "logic-nand": "\u{f242}", + "logic-nor": "\u{f243}", + "logic-not": "\u{f244}", + "logic-or": "\u{f245}", + "logic-xnor": "\u{f246}", + "logic-xor": "\u{f247}", + "login": "\u{eba7}", + "login-2": "\u{fc76}", + "logout": "\u{eba8}", + "logout-2": "\u{fa7e}", + "logs": "\u{fea7}", + "lollipop": "\u{efcc}", + "lollipop-off": "\u{f157}", + "luggage": "\u{efad}", + "luggage-off": "\u{f158}", + "lungs": "\u{ef62}", + "lungs-filled": "\u{fe14}", + "lungs-off": "\u{f405}", + "macro": "\u{eeab}", + "macro-filled": "\u{fe13}", + "macro-off": "\u{f406}", + "magnet": "\u{eae3}", + "magnet-filled": "\u{fe12}", + "magnet-off": "\u{f159}", + "magnetic": "\u{fcb9}", + "mail": "\u{eae5}", + "mail-ai": "\u{fa31}", + "mail-bitcoin": "\u{ff36}", + "mail-bolt": "\u{f937}", + "mail-cancel": "\u{f938}", + "mail-check": "\u{f939}", + "mail-code": "\u{f93a}", + "mail-cog": "\u{f93b}", + "mail-dollar": "\u{f93c}", + "mail-down": "\u{f93d}", + "mail-exclamation": "\u{f93e}", + "mail-fast": "\u{f069}", + "mail-filled": "\u{fa47}", + "mail-forward": "\u{eeac}", + "mail-heart": "\u{f93f}", + "mail-minus": "\u{f940}", + "mail-off": "\u{f15a}", + "mail-opened": "\u{eae4}", + "mail-opened-filled": "\u{fa48}", + "mail-pause": "\u{f941}", + "mail-pin": "\u{f942}", + "mail-plus": "\u{f943}", + "mail-question": "\u{f944}", + "mail-search": "\u{f945}", + "mail-share": "\u{f946}", + "mail-spark": "\u{ffb3}", + "mail-star": "\u{f947}", + "mail-up": "\u{f948}", + "mail-x": "\u{f949}", + "mailbox": "\u{eead}", + "mailbox-off": "\u{f15b}", + "man": "\u{eae6}", + "man-filled": "\u{fe11}", + "manual-gearbox": "\u{ed7b}", + "manual-gearbox-filled": "\u{fe10}", + "map": "\u{eae9}", + "map-2": "\u{eae7}", + "map-bolt": "\u{fbc3}", + "map-cancel": "\u{fbc4}", + "map-check": "\u{fbc5}", + "map-code": "\u{fbc6}", + "map-cog": "\u{fbc7}", + "map-discount": "\u{fbc8}", + "map-dollar": "\u{fbc9}", + "map-down": "\u{fbca}", + "map-east": "\u{fc5d}", + "map-exclamation": "\u{fbcb}", + "map-heart": "\u{fbcc}", + "map-minus": "\u{fbcd}", + "map-north": "\u{fc5e}", + "map-off": "\u{f15c}", + "map-pause": "\u{fbce}", + "map-pin": "\u{eae8}", + "map-pin-2": "\u{fc48}", + "map-pin-bolt": "\u{f94a}", + "map-pin-cancel": "\u{f94b}", + "map-pin-check": "\u{f94c}", + "map-pin-code": "\u{f94d}", + "map-pin-cog": "\u{f94e}", + "map-pin-dollar": "\u{f94f}", + "map-pin-down": "\u{f950}", + "map-pin-exclamation": "\u{f951}", + "map-pin-filled": "\u{f680}", + "map-pin-heart": "\u{f952}", + "map-pin-minus": "\u{f953}", + "map-pin-off": "\u{ecf3}", + "map-pin-pause": "\u{f954}", + "map-pin-pin": "\u{f955}", + "map-pin-plus": "\u{f956}", + "map-pin-question": "\u{f957}", + "map-pin-search": "\u{f958}", + "map-pin-share": "\u{f795}", + "map-pin-star": "\u{f959}", + "map-pin-up": "\u{f95a}", + "map-pin-x": "\u{f95b}", + "map-pins": "\u{ed5e}", + "map-plus": "\u{fbcf}", + "map-question": "\u{fbd0}", + "map-route": "\u{fc79}", + "map-search": "\u{ef82}", + "map-share": "\u{fbd1}", + "map-south": "\u{fc5f}", + "map-star": "\u{fbd2}", + "map-up": "\u{fbd3}", + "map-west": "\u{fc60}", + "map-x": "\u{fbd4}", + "markdown": "\u{ec41}", + "markdown-off": "\u{f407}", + "marquee": "\u{ec77}", + "marquee-2": "\u{eeae}", + "marquee-off": "\u{f15d}", + "mars": "\u{ec80}", + "mask": "\u{eeb0}", + "mask-off": "\u{eeaf}", + "masks-theater": "\u{f263}", + "masks-theater-off": "\u{f408}", + "massage": "\u{eeb1}", + "matchstick": "\u{f577}", + "math": "\u{ebeb}", + "math-1-divide-2": "\u{f4e2}", + "math-1-divide-3": "\u{f4e3}", + "math-avg": "\u{f0f4}", + "math-cos": "\u{ff1f}", + "math-ctg": "\u{ff35}", + "math-equal-greater": "\u{f4e4}", + "math-equal-lower": "\u{f4e5}", + "math-function": "\u{eeb2}", + "math-function-off": "\u{f15e}", + "math-function-y": "\u{f4e6}", + "math-greater": "\u{f4e7}", + "math-integral": "\u{f4e9}", + "math-integral-x": "\u{f4e8}", + "math-integrals": "\u{f4ea}", + "math-lower": "\u{f4eb}", + "math-max": "\u{f0f5}", + "math-max-min": "\u{fda0}", + "math-min": "\u{f0f6}", + "math-not": "\u{f4ec}", + "math-off": "\u{f409}", + "math-pi": "\u{f4ee}", + "math-pi-divide-2": "\u{f4ed}", + "math-sec": "\u{ff34}", + "math-sin": "\u{ff1e}", + "math-symbols": "\u{eeb3}", + "math-tg": "\u{ff33}", + "math-x-divide-2": "\u{f4ef}", + "math-x-divide-y": "\u{f4f1}", + "math-x-divide-y-2": "\u{f4f0}", + "math-x-floor-divide-y": "\u{10073}", + "math-x-minus-x": "\u{f4f2}", + "math-x-minus-y": "\u{f4f3}", + "math-x-plus-x": "\u{f4f4}", + "math-x-plus-y": "\u{f4f5}", + "math-xy": "\u{f4f6}", + "math-y-minus-y": "\u{f4f7}", + "math-y-plus-y": "\u{f4f8}", + "matrix": "\u{100bc}", + "maximize": "\u{eaea}", + "maximize-off": "\u{f15f}", + "meat": "\u{ef12}", + "meat-off": "\u{f40a}", + "medal": "\u{ec78}", + "medal-2": "\u{efcd}", + "medical-cross": "\u{ec2f}", + "medical-cross-circle": "\u{fae8}", + "medical-cross-filled": "\u{f681}", + "medical-cross-off": "\u{f160}", + "medicine-syrup": "\u{ef63}", + "meeple": "\u{f514}", + "meeple-filled": "\u{100a0}", + "melon": "\u{fc7a}", + "melon-filled": "\u{1000e}", + "menorah": "\u{f58c}", + "menu": "\u{eaeb}", + "menu-2": "\u{ec42}", + "menu-3": "\u{ff43}", + "menu-4": "\u{ff42}", + "menu-deep": "\u{fafb}", + "menu-order": "\u{f5f5}", + "message": "\u{eaef}", + "message-2": "\u{eaec}", + "message-2-bolt": "\u{f95c}", + "message-2-cancel": "\u{f95d}", + "message-2-check": "\u{f95e}", + "message-2-code": "\u{f012}", + "message-2-cog": "\u{f95f}", + "message-2-dollar": "\u{f960}", + "message-2-down": "\u{f961}", + "message-2-exclamation": "\u{f962}", + "message-2-filled": "\u{1009f}", + "message-2-heart": "\u{f963}", + "message-2-minus": "\u{f964}", + "message-2-off": "\u{f40b}", + "message-2-pause": "\u{f965}", + "message-2-pin": "\u{f966}", + "message-2-plus": "\u{f967}", + "message-2-question": "\u{f968}", + "message-2-search": "\u{f969}", + "message-2-share": "\u{f077}", + "message-2-star": "\u{f96a}", + "message-2-up": "\u{f96b}", + "message-2-x": "\u{f96c}", + "message-bolt": "\u{f96d}", + "message-cancel": "\u{f96e}", + "message-chatbot": "\u{f38a}", + "message-chatbot-filled": "\u{fed0}", + "message-check": "\u{f96f}", + "message-circle": "\u{eaed}", + "message-circle-2": "\u{eaed}", + "message-circle-2-filled": "\u{fecf}", + "message-circle-bolt": "\u{f970}", + "message-circle-cancel": "\u{f971}", + "message-circle-check": "\u{f972}", + "message-circle-code": "\u{f973}", + "message-circle-cog": "\u{f974}", + "message-circle-dollar": "\u{f975}", + "message-circle-down": "\u{f976}", + "message-circle-exclamation": "\u{f977}", + "message-circle-filled": "\u{fecf}", + "message-circle-heart": "\u{f978}", + "message-circle-minus": "\u{f979}", + "message-circle-off": "\u{ed40}", + "message-circle-pause": "\u{f97a}", + "message-circle-pin": "\u{f97b}", + "message-circle-plus": "\u{f97c}", + "message-circle-question": "\u{f97d}", + "message-circle-search": "\u{f97e}", + "message-circle-share": "\u{f97f}", + "message-circle-star": "\u{f980}", + "message-circle-up": "\u{f981}", + "message-circle-user": "\u{fec5}", + "message-circle-x": "\u{f982}", + "message-code": "\u{f013}", + "message-cog": "\u{f983}", + "message-dollar": "\u{f984}", + "message-dots": "\u{eaee}", + "message-down": "\u{f985}", + "message-exclamation": "\u{f986}", + "message-filled": "\u{fecd}", + "message-forward": "\u{f28f}", + "message-heart": "\u{f987}", + "message-language": "\u{efae}", + "message-minus": "\u{f988}", + "message-off": "\u{ed41}", + "message-pause": "\u{f989}", + "message-pin": "\u{f98a}", + "message-plus": "\u{ec9a}", + "message-question": "\u{f98b}", + "message-reply": "\u{fd4d}", + "message-report": "\u{ec9b}", + "message-report-filled": "\u{fece}", + "message-search": "\u{f98c}", + "message-share": "\u{f078}", + "message-star": "\u{f98d}", + "message-up": "\u{f98e}", + "message-user": "\u{fec4}", + "message-x": "\u{f98f}", + "messages": "\u{eb6c}", + "messages-off": "\u{ed42}", + "meteor": "\u{f1fd}", + "meteor-filled": "\u{1000d}", + "meteor-off": "\u{f40c}", + "meter-cube": "\u{fd7c}", + "meter-square": "\u{fd7d}", + "metronome": "\u{fd25}", + "michelin-bib-gourmand": "\u{fae9}", + "michelin-star": "\u{faeb}", + "michelin-star-filled": "\u{1000c}", + "michelin-star-green": "\u{faea}", + "mickey": "\u{f2a3}", + "mickey-filled": "\u{f683}", + "microphone": "\u{eaf0}", + "microphone-2": "\u{ef2c}", + "microphone-2-off": "\u{f40d}", + //"microphone-filled": "\u{fe0f}", + "microphone-off": "\u{ed16}", + "microscope": "\u{ef64}", + "microscope-filled": "\u{10166}", + "microscope-off": "\u{f40e}", + "microwave": "\u{f248}", + //"microwave-filled": "\u{fe0e}", + "microwave-off": "\u{f264}", + "military-award": "\u{f079}", + "military-rank": "\u{efcf}", + "military-rank-filled": "\u{ff5e}", + "milk": "\u{ef13}", + "milk-filled": "\u{1000b}", + "milk-off": "\u{f40f}", + "milkshake": "\u{f4c8}", + "minimize": "\u{eaf1}", + "minus": "\u{eaf2}", + "minus-vertical": "\u{eeb4}", + "mist": "\u{ec30}", + "mist-off": "\u{f410}", + "mobiledata": "\u{f9f5}", + "mobiledata-off": "\u{f9f4}", + "moneybag": "\u{f506}", + "moneybag-edit": "\u{1013d}", + "moneybag-heart": "\u{1013c}", + "moneybag-minus": "\u{1013b}", + "moneybag-move": "\u{10139}", + "moneybag-move-back": "\u{1013a}", + "moneybag-plus": "\u{10138}", + "monkeybar": "\u{feb4}", + "mood-angry": "\u{f2de}", + "mood-angry-filled": "\u{ff0a}", + "mood-annoyed": "\u{f2e0}", + "mood-annoyed-2": "\u{f2df}", + "mood-bitcoin": "\u{ff32}", + "mood-boy": "\u{ed2d}", + "mood-check": "\u{f7b3}", + "mood-cog": "\u{f7b4}", + "mood-confuzed": "\u{eaf3}", + "mood-confuzed-filled": "\u{f7f2}", + "mood-crazy-happy": "\u{ed90}", + "mood-crazy-happy-filled": "\u{ff09}", + "mood-cry": "\u{ecbb}", + "mood-dollar": "\u{f7b5}", + "mood-edit": "\u{fa05}", + "mood-empty": "\u{eeb5}", + "mood-empty-filled": "\u{f7f3}", + "mood-happy": "\u{eaf4}", + "mood-happy-filled": "\u{f7f4}", + "mood-heart": "\u{f7b6}", + "mood-kid": "\u{ec03}", + "mood-kid-filled": "\u{f7f5}", + "mood-look-down": "\u{fd37}", + "mood-look-left": "\u{f2c5}", + "mood-look-right": "\u{f2c6}", + "mood-look-up": "\u{fd38}", + "mood-minus": "\u{f7b7}", + "mood-nerd": "\u{f2e1}", + "mood-nervous": "\u{ef96}", + "mood-neutral": "\u{eaf5}", + "mood-neutral-filled": "\u{f7f6}", + "mood-off": "\u{f161}", + "mood-pin": "\u{f7b8}", + "mood-plus": "\u{f7b9}", + "mood-puzzled": "\u{fd39}", + "mood-sad": "\u{eaf6}", + "mood-sad-2": "\u{f2e2}", + "mood-sad-dizzy": "\u{f2e3}", + "mood-sad-filled": "\u{f7f7}", + "mood-sad-squint": "\u{f2e4}", + "mood-search": "\u{f7ba}", + "mood-share": "\u{fa06}", + "mood-sick": "\u{f2e5}", + "mood-silence": "\u{f2e6}", + "mood-sing": "\u{f2c7}", + "mood-smile": "\u{eaf7}", + "mood-smile-beam": "\u{f2e7}", + "mood-smile-dizzy": "\u{f2e8}", + "mood-smile-filled": "\u{f7f8}", + "mood-spark": "\u{ffb2}", + "mood-suprised": "\u{ec04}", + "mood-surprised": "\u{ec04}", + "mood-tongue": "\u{eb95}", + "mood-tongue-wink": "\u{f2ea}", + "mood-tongue-wink-2": "\u{f2e9}", + "mood-unamused": "\u{f2eb}", + "mood-up": "\u{f7bb}", + "mood-wink": "\u{f2ed}", + "mood-wink-2": "\u{f2ec}", + "mood-wrrr": "\u{f2ee}", + "mood-wrrr-filled": "\u{ff08}", + "mood-x": "\u{f7bc}", + "mood-xd": "\u{f2ef}", + "moon": "\u{eaf8}", + "moon-2": "\u{ece6}", + "moon-filled": "\u{f684}", + "moon-off": "\u{f162}", + "moon-stars": "\u{ece7}", + "moped": "\u{ecbc}", + "motorbike": "\u{eeb6}", + "motorbike-filled": "\u{100f8}", + "mountain": "\u{ef97}", + "mountain-filled": "\u{1000a}", + "mountain-off": "\u{f411}", + "mouse": "\u{eaf9}", + "mouse-2": "\u{f1d7}", + "mouse-filled": "\u{fb2f}", + "mouse-off": "\u{f163}", + "moustache": "\u{f4c9}", + "movie": "\u{eafa}", + "movie-off": "\u{f164}", + "mug": "\u{eafb}", + "mug-filled": "\u{10009}", + "mug-off": "\u{f165}", + "multiplier-0-5x": "\u{ef41}", + "multiplier-1-5x": "\u{ef42}", + "multiplier-1x": "\u{ef43}", + "multiplier-2x": "\u{ef44}", + "mushroom": "\u{ef14}", + "mushroom-filled": "\u{f7f9}", + "mushroom-off": "\u{f412}", + "music": "\u{eafc}", + "music-bolt": "\u{fbd5}", + "music-cancel": "\u{fbd6}", + "music-check": "\u{fbd7}", + "music-code": "\u{fbd8}", + "music-cog": "\u{fbd9}", + "music-discount": "\u{fbda}", + "music-dollar": "\u{fbdb}", + "music-down": "\u{fbdc}", + "music-exclamation": "\u{fbdd}", + "music-heart": "\u{fbde}", + "music-minus": "\u{fbdf}", + "music-off": "\u{f166}", + "music-pause": "\u{fbe0}", + "music-pin": "\u{fbe1}", + "music-plus": "\u{fbe2}", + "music-question": "\u{fbe3}", + "music-search": "\u{fbe4}", + "music-share": "\u{fbe5}", + "music-star": "\u{fbe6}", + "music-up": "\u{fbe7}", + "music-x": "\u{fbe8}", + "navigation": "\u{f2c8}", + "navigation-bolt": "\u{fbe9}", + "navigation-cancel": "\u{fbea}", + "navigation-check": "\u{fbeb}", + "navigation-code": "\u{fbec}", + "navigation-cog": "\u{fbed}", + "navigation-discount": "\u{fbee}", + "navigation-dollar": "\u{fbef}", + "navigation-down": "\u{fbf0}", + "navigation-east": "\u{fcba}", + "navigation-exclamation": "\u{fbf1}", + "navigation-filled": "\u{f685}", + "navigation-heart": "\u{fbf2}", + "navigation-minus": "\u{fbf3}", + "navigation-north": "\u{fcbb}", + "navigation-off": "\u{f413}", + "navigation-pause": "\u{fbf4}", + "navigation-pin": "\u{fbf5}", + "navigation-plus": "\u{fbf6}", + "navigation-question": "\u{fbf7}", + "navigation-search": "\u{fbf8}", + "navigation-share": "\u{fbf9}", + "navigation-south": "\u{fcbc}", + "navigation-star": "\u{fbfa}", + "navigation-top": "\u{faec}", + "navigation-up": "\u{fbfb}", + "navigation-west": "\u{fcbd}", + "navigation-x": "\u{fbfc}", + "needle": "\u{f508}", + "needle-thread": "\u{f507}", + "network": "\u{f09f}", + "network-off": "\u{f414}", + "new-section": "\u{ebc1}", + "news": "\u{eafd}", + "news-off": "\u{f167}", + "nfc": "\u{eeb7}", + "nfc-off": "\u{f168}", + "niri": "\u{ec32}", + "noctalia": "\u{ec33}", + "no-copyright": "\u{efb9}", + "no-creative-commons": "\u{efba}", + "no-derivatives": "\u{efbb}", + "north-star": "\u{f014}", + "note": "\u{eb6d}", + "note-off": "\u{f169}", + "notebook": "\u{eb96}", + "notebook-off": "\u{f415}", + "notes": "\u{eb6e}", + "notes-off": "\u{f16a}", + "notification": "\u{eafe}", + "notification-off": "\u{f16b}", + "number": "\u{f1fe}", + "number-0": "\u{edf0}", + "number-0-small": "\u{fce1}", + "number-1": "\u{edf1}", + "number-1-small": "\u{fce2}", + "number-10": "\u{1005e}", + "number-10-small": "\u{fce3}", + "number-100-small": "\u{10005}", + "number-11": "\u{1005d}", + "number-11-small": "\u{fce4}", + "number-12-small": "\u{fce5}", + "number-123": "\u{f554}", + "number-13-small": "\u{fce6}", + "number-14-small": "\u{fce7}", + "number-15-small": "\u{fce8}", + "number-16-small": "\u{fce9}", + "number-17-small": "\u{fcea}", + "number-18-small": "\u{fceb}", + "number-19-small": "\u{fcec}", + "number-2": "\u{edf2}", + "number-2-small": "\u{fced}", + "number-20-small": "\u{fcee}", + "number-21-small": "\u{fcef}", + "number-22-small": "\u{fcf0}", + "number-23-small": "\u{fcf1}", + "number-24-small": "\u{fcf2}", + "number-25-small": "\u{fcf3}", + "number-26-small": "\u{fcf4}", + "number-27-small": "\u{fcf5}", + "number-28-small": "\u{fcf6}", + "number-29-small": "\u{fcf7}", + "number-3": "\u{edf3}", + "number-3-small": "\u{fcf8}", + "number-30-small": "\u{10004}", + "number-31-small": "\u{10003}", + "number-32-small": "\u{10002}", + "number-33-small": "\u{10001}", + "number-34-small": "\u{10000}", + "number-35-small": "\u{ffff}", + "number-36-small": "\u{fffe}", + "number-37-small": "\u{fffd}", + "number-38-small": "\u{fffc}", + "number-39-small": "\u{fffb}", + "number-4": "\u{edf4}", + "number-4-small": "\u{fcf9}", + "number-40-small": "\u{fffa}", + "number-41-small": "\u{fff9}", + //"number-42-small": "\u{fff8}", + //"number-43-small": "\u{fff7}", + //"number-44-small": "\u{fff6}", + //"number-45-small": "\u{fff5}", + //"number-46-small": "\u{fff4}", + //"number-47-small": "\u{fff3}", + //"number-48-small": "\u{fff2}", + //"number-49-small": "\u{fff1}", + "number-5": "\u{edf5}", + "number-5-small": "\u{fcfa}", + //"number-50-small": "\u{fff0}", + "number-51-small": "\u{ffef}", + "number-52-small": "\u{ffee}", + "number-53-small": "\u{ffed}", + "number-54-small": "\u{ffec}", + "number-55-small": "\u{ffeb}", + "number-56-small": "\u{ffea}", + "number-57-small": "\u{ffe9}", + "number-58-small": "\u{ffe8}", + "number-59-small": "\u{ffe7}", + "number-6": "\u{edf6}", + "number-6-small": "\u{fcfb}", + "number-60-small": "\u{ffe6}", + "number-61-small": "\u{ffe5}", + "number-62-small": "\u{ffe4}", + "number-63-small": "\u{ffe3}", + "number-64-small": "\u{ffe2}", + "number-65-small": "\u{ffe1}", + "number-66-small": "\u{ffe0}", + "number-67-small": "\u{ffdf}", + "number-68-small": "\u{ffde}", + "number-69-small": "\u{ffdd}", + "number-7": "\u{edf7}", + "number-7-small": "\u{fcfc}", + "number-70-small": "\u{ffdc}", + "number-71-small": "\u{ffdb}", + "number-72-small": "\u{ffda}", + "number-73-small": "\u{ffd9}", + "number-74-small": "\u{ffd8}", + "number-75-small": "\u{ffd7}", + "number-76-small": "\u{ffd6}", + "number-77-small": "\u{ffd5}", + "number-78-small": "\u{ffd4}", + "number-79-small": "\u{ffd3}", + "number-8": "\u{edf8}", + "number-8-small": "\u{fcfd}", + "number-80-small": "\u{ffd2}", + "number-81-small": "\u{ffd1}", + "number-82-small": "\u{ffd0}", + "number-83-small": "\u{ffcf}", + "number-84-small": "\u{ffce}", + "number-85-small": "\u{ffcd}", + "number-86-small": "\u{ffcc}", + "number-87-small": "\u{ffcb}", + "number-88-small": "\u{ffca}", + "number-89-small": "\u{ffc9}", + "number-9": "\u{edf9}", + "number-9-small": "\u{fcfe}", + "number-90-small": "\u{ffc8}", + "number-91-small": "\u{ffc7}", + "number-92-small": "\u{ffc6}", + "number-93-small": "\u{ffc5}", + "number-94-small": "\u{ffc4}", + "number-95-small": "\u{ffc3}", + "number-96-small": "\u{ffc2}", + "number-97-small": "\u{ffc1}", + "number-98-small": "\u{ffc0}", + "number-99-small": "\u{ffbf}", + "numbers": "\u{f015}", + "nurse": "\u{ef65}", + "nurse-filled": "\u{1009e}", + "nut": "\u{fc61}", + "object-scan": "\u{fef1}", + "octagon": "\u{ecbd}", + "octagon-filled": "\u{f686}", + "octagon-minus": "\u{fc92}", + "octagon-minus-2": "\u{fc91}", + "octagon-minus-filled": "\u{1017f}", + "octagon-off": "\u{eeb8}", + "octagon-plus": "\u{fc94}", + "octagon-plus-2": "\u{fc93}", + "octagon-plus-filled": "\u{1017e}", + "octahedron": "\u{faae}", + "octahedron-off": "\u{faac}", + "octahedron-plus": "\u{faad}", + "old": "\u{eeb9}", + "olympics": "\u{eeba}", + "olympics-off": "\u{f416}", + "om": "\u{f58d}", + "omega": "\u{eb97}", + "outbound": "\u{f249}", + "outlet": "\u{ebd7}", + "oval": "\u{f02e}", + "oval-filled": "\u{f687}", + "oval-vertical": "\u{f02d}", + "oval-vertical-filled": "\u{f688}", + "overline": "\u{eebb}", + "package": "\u{eaff}", + "package-export": "\u{f07a}", + "package-import": "\u{f07b}", + "package-off": "\u{f16c}", + "packages": "\u{f2c9}", + "pacman": "\u{eebc}", + "page-break": "\u{ec81}", + "paint": "\u{eb00}", + "paint-filled": "\u{f75f}", + "paint-off": "\u{f16d}", + "palette": "\u{eb01}", + "palette-filled": "\u{1009d}", + "palette-off": "\u{f16e}", + "panorama-horizontal": "\u{ed33}", + "panorama-horizontal-filled": "\u{fecc}", + "panorama-horizontal-off": "\u{f417}", + "panorama-vertical": "\u{ed34}", + "panorama-vertical-filled": "\u{fecb}", + "panorama-vertical-off": "\u{f418}", + "paper-bag": "\u{f02f}", + "paper-bag-off": "\u{f16f}", + "paperclip": "\u{eb02}", + "parachute": "\u{ed7c}", + "parachute-off": "\u{f170}", + "parentheses": "\u{ebd8}", + "parentheses-off": "\u{f171}", + "parking": "\u{eb03}", + "parking-circle": "\u{fd5a}", + "parking-circle-filled": "\u{feca}", + "parking-off": "\u{f172}", + "password": "\u{f4ca}", + "password-fingerprint": "\u{fc7b}", + "password-mobile-phone": "\u{fc7c}", + "password-user": "\u{fc7d}", + "paw": "\u{eff9}", + "paw-filled": "\u{f689}", + "paw-off": "\u{f419}", + "paywall": "\u{fd7e}", + "pdf": "\u{f7ac}", + "peace": "\u{ecbe}", + "pencil": "\u{eb04}", + "pencil-bolt": "\u{fbfd}", + "pencil-cancel": "\u{fbfe}", + "pencil-check": "\u{fbff}", + "pencil-code": "\u{fc00}", + "pencil-cog": "\u{fc01}", + "pencil-discount": "\u{fc02}", + "pencil-dollar": "\u{fc03}", + "pencil-down": "\u{fc04}", + "pencil-exclamation": "\u{fc05}", + "pencil-heart": "\u{fc06}", + "pencil-minus": "\u{f1eb}", + "pencil-off": "\u{f173}", + "pencil-pause": "\u{fc07}", + "pencil-pin": "\u{fc08}", + "pencil-plus": "\u{f1ec}", + "pencil-question": "\u{fc09}", + "pencil-search": "\u{fc0a}", + "pencil-share": "\u{fc0b}", + "pencil-star": "\u{fc0c}", + "pencil-up": "\u{fc0d}", + "pencil-x": "\u{fc0e}", + "pennant": "\u{ed7d}", + "pennant-2": "\u{f06a}", + "pennant-2-filled": "\u{f68a}", + "pennant-filled": "\u{f68b}", + "pennant-off": "\u{f174}", + "pentagon": "\u{efe3}", + "pentagon-filled": "\u{f68c}", + "pentagon-minus": "\u{feb3}", + "pentagon-number-0": "\u{fc7e}", + "pentagon-number-1": "\u{fc7f}", + "pentagon-number-2": "\u{fc80}", + "pentagon-number-3": "\u{fc81}", + "pentagon-number-4": "\u{fc82}", + "pentagon-number-5": "\u{fc83}", + "pentagon-number-6": "\u{fc84}", + "pentagon-number-7": "\u{fc85}", + "pentagon-number-8": "\u{fc86}", + "pentagon-number-9": "\u{fc87}", + "pentagon-off": "\u{f41a}", + "pentagon-plus": "\u{fc49}", + "pentagon-x": "\u{fc88}", + "pentagram": "\u{f586}", + "pepper": "\u{ef15}", + "pepper-off": "\u{f175}", + "percentage": "\u{ecf4}", + "percentage-0": "\u{fee5}", + "percentage-10": "\u{fee4}", + "percentage-100": "\u{fee3}", + "percentage-20": "\u{fee2}", + "percentage-25": "\u{fee1}", + "percentage-30": "\u{fee0}", + "percentage-33": "\u{fedf}", + "percentage-40": "\u{fede}", + "percentage-50": "\u{fedd}", + "percentage-60": "\u{fedc}", + "percentage-66": "\u{fedb}", + "percentage-70": "\u{feda}", + "percentage-75": "\u{fed9}", + "percentage-80": "\u{fed8}", + "percentage-90": "\u{fed7}", + "perfume": "\u{f509}", + "perspective": "\u{eebd}", + "perspective-off": "\u{f176}", + "phone": "\u{eb09}", + "phone-call": "\u{eb05}", + "phone-calling": "\u{ec43}", + "phone-check": "\u{ec05}", + "phone-done": "\u{ff9e}", + "phone-end": "\u{ff9d}", + "phone-filled": "\u{fa49}", + "phone-incoming": "\u{eb06}", + "phone-off": "\u{ecf5}", + "phone-outgoing": "\u{eb07}", + "phone-pause": "\u{eb08}", + "phone-plus": "\u{ec06}", + "phone-ringing": "\u{ff9c}", + "phone-spark": "\u{ffb1}", + "phone-x": "\u{ec07}", + "photo": "\u{eb0a}", + "photo-ai": "\u{fa32}", + "photo-bitcoin": "\u{ff31}", + "photo-bolt": "\u{f990}", + "photo-cancel": "\u{f35d}", + "photo-check": "\u{f35e}", + "photo-circle": "\u{fc4a}", + "photo-circle-minus": "\u{fc62}", + "photo-circle-plus": "\u{fc63}", + "photo-code": "\u{f991}", + "photo-cog": "\u{f992}", + "photo-dollar": "\u{f993}", + "photo-down": "\u{f35f}", + "photo-edit": "\u{f360}", + "photo-exclamation": "\u{f994}", + "photo-filled": "\u{fa4a}", + "photo-heart": "\u{f361}", + "photo-hexagon": "\u{fc4b}", + "photo-minus": "\u{f362}", + "photo-off": "\u{ecf6}", + "photo-pause": "\u{f995}", + "photo-pentagon": "\u{fc4c}", + "photo-pin": "\u{f996}", + "photo-plus": "\u{f363}", + "photo-question": "\u{f997}", + "photo-scan": "\u{fca8}", + "photo-search": "\u{f364}", + "photo-sensor": "\u{f798}", + "photo-sensor-2": "\u{f796}", + "photo-sensor-3": "\u{f797}", + "photo-share": "\u{f998}", + "photo-shield": "\u{f365}", + "photo-spark": "\u{ffb0}", + "photo-square-rounded": "\u{fc4d}", + "photo-star": "\u{f366}", + "photo-up": "\u{f38b}", + "photo-video": "\u{fc95}", + "photo-x": "\u{f367}", + "physotherapist": "\u{eebe}", + "piano": "\u{fad3}", + "pick": "\u{fafc}", + "picnic-table": "\u{fed6}", + "picture-in-picture": "\u{ed35}", + "picture-in-picture-filled": "\u{fec1}", + "picture-in-picture-off": "\u{ed43}", + "picture-in-picture-on": "\u{ed44}", + "picture-in-picture-top": "\u{efe4}", + "picture-in-picture-top-filled": "\u{fec2}", + "pig": "\u{ef52}", + "pig-filled": "\u{1010a}", + "pig-money": "\u{f38c}", + "pig-off": "\u{f177}", + "pilcrow": "\u{f5f6}", + "pilcrow-left": "\u{fd7f}", + "pilcrow-right": "\u{fd80}", + "pill": "\u{ec44}", + "pill-filled": "\u{ff07}", + "pill-off": "\u{f178}", + "pills": "\u{ef66}", + "pin": "\u{ec9c}", + "pin-end": "\u{fd5b}", + "pin-filled": "\u{f68d}", + "pin-invoke": "\u{fd5c}", + "ping-pong": "\u{f38d}", + "pinned": "\u{ed60}", + "pinned-filled": "\u{f68e}", + "pinned-off": "\u{ed5f}", + "pizza": "\u{edbb}", + "pizza-filled": "\u{10008}", + "pizza-off": "\u{f179}", + "placeholder": "\u{f626}", + "plane": "\u{eb6f}", + "plane-arrival": "\u{eb99}", + "plane-departure": "\u{eb9a}", + "plane-inflight": "\u{ef98}", + "plane-off": "\u{f17a}", + "plane-tilt": "\u{f1ed}", + "planet": "\u{ec08}", + "planet-off": "\u{f17b}", + "plant": "\u{ed50}", + "plant-2": "\u{ed7e}", + "plant-2-off": "\u{f17c}", + "plant-off": "\u{f17d}", + "play-basketball": "\u{fa66}", + "play-card": "\u{eebf}", + "play-card-1": "\u{1005c}", + "play-card-1-filled": "\u{10083}", + "play-card-10": "\u{1005b}", + "play-card-10-filled": "\u{10082}", + "play-card-2": "\u{1005a}", + "play-card-2-filled": "\u{10081}", + "play-card-3": "\u{10059}", + "play-card-3-filled": "\u{10080}", + "play-card-4": "\u{10058}", + "play-card-4-filled": "\u{1007f}", + "play-card-5": "\u{10057}", + "play-card-5-filled": "\u{1007e}", + "play-card-6": "\u{10056}", + "play-card-6-filled": "\u{1007d}", + "play-card-7": "\u{10055}", + "play-card-7-filled": "\u{1007c}", + "play-card-8": "\u{10054}", + "play-card-8-filled": "\u{1007b}", + "play-card-9": "\u{10053}", + "play-card-9-filled": "\u{1007a}", + "play-card-a": "\u{10052}", + "play-card-a-filled": "\u{10079}", + "play-card-j": "\u{10051}", + "play-card-j-filled": "\u{10078}", + "play-card-k": "\u{10050}", + "play-card-k-filled": "\u{10077}", + "play-card-off": "\u{f17e}", + "play-card-q": "\u{1004f}", + "play-card-q-filled": "\u{10076}", + "play-card-star": "\u{1004e}", + "play-card-star-filled": "\u{10075}", + "play-football": "\u{fa67}", + "play-handball": "\u{fa68}", + "play-volleyball": "\u{fa69}", + "player-eject": "\u{efbc}", + "player-eject-filled": "\u{f68f}", + "player-pause": "\u{ed45}", + "player-pause-filled": "\u{f690}", + "player-play": "\u{ed46}", + "player-play-filled": "\u{f691}", + "player-record": "\u{ed47}", + "player-record-filled": "\u{f692}", + "player-skip-back": "\u{ed48}", + "player-skip-back-filled": "\u{f693}", + "player-skip-forward": "\u{ed49}", + "player-skip-forward-filled": "\u{f694}", + "player-stop": "\u{ed4a}", + "player-stop-filled": "\u{f695}", + "player-track-next": "\u{ed4b}", + "player-track-next-filled": "\u{f696}", + "player-track-prev": "\u{ed4c}", + "player-track-prev-filled": "\u{f697}", + "playlist": "\u{eec0}", + "playlist-add": "\u{f008}", + "playlist-off": "\u{f17f}", + "playlist-x": "\u{f009}", + "playstation-circle": "\u{f2ad}", + "playstation-square": "\u{f2ae}", + "playstation-triangle": "\u{f2af}", + "playstation-x": "\u{f2b0}", + "plug": "\u{ebd9}", + "plug-filled": "\u{f6b3}", + "plug-connected": "\u{f00a}", + "plug-connected-x": "\u{f0a0}", + "plug-off": "\u{f180}", + "plug-x": "\u{f0a1}", + "plus": "\u{eb0b}", + "plus-equal": "\u{f7ad}", + "plus-minus": "\u{f7ae}", + "png": "\u{f3ad}", + "podium": "\u{f1d8}", + "podium-off": "\u{f41b}", + "point": "\u{eb0c}", + "point-filled": "\u{f698}", + "point-off": "\u{f181}", + "pointer": "\u{f265}", + "pointer-bolt": "\u{f999}", + "pointer-cancel": "\u{f99a}", + "pointer-check": "\u{f99b}", + "pointer-code": "\u{f99c}", + "pointer-cog": "\u{f99d}", + "pointer-dollar": "\u{f99e}", + "pointer-down": "\u{f99f}", + "pointer-exclamation": "\u{f9a0}", + "pointer-filled": "\u{fb30}", + "pointer-heart": "\u{f9a1}", + "pointer-minus": "\u{f9a2}", + "pointer-off": "\u{f9a3}", + "pointer-pause": "\u{f9a4}", + "pointer-pin": "\u{f9a5}", + "pointer-plus": "\u{f9a6}", + "pointer-question": "\u{f9a7}", + "pointer-search": "\u{f9a8}", + "pointer-share": "\u{f9a9}", + "pointer-star": "\u{f9aa}", + "pointer-up": "\u{f9ab}", + "pointer-x": "\u{f9ac}", + "pokeball": "\u{eec1}", + "pokeball-off": "\u{f41c}", + "poker-chip": "\u{f515}", + "polaroid": "\u{eec2}", + "polaroid-filled": "\u{fa4b}", + "polygon": "\u{efd0}", + "polygon-off": "\u{f182}", + "poo": "\u{f258}", + "poo-filled": "\u{fec9}", + "pool": "\u{ed91}", + "pool-off": "\u{f41d}", + "power": "\u{eb0d}", + "pray": "\u{ecbf}", + "premium-rights": "\u{efbd}", + "prescription": "\u{ef99}", + "presentation": "\u{eb70}", + "presentation-analytics": "\u{eec3}", + "presentation-analytics-filled": "\u{ff5d}", + "presentation-filled": "\u{ff5c}", + "presentation-off": "\u{f183}", + "printer": "\u{eb0e}", + "printer-off": "\u{f184}", + "prism": "\u{fab1}", + "prism-light": "\u{fea6}", + "prism-off": "\u{faaf}", + "prism-plus": "\u{fab0}", + "prison": "\u{ef79}", + "progress": "\u{fa0d}", + "progress-alert": "\u{fa07}", + "progress-bolt": "\u{fa08}", + "progress-check": "\u{fa09}", + "progress-down": "\u{fa0a}", + "progress-help": "\u{fa0b}", + "progress-x": "\u{fa0c}", + "prompt": "\u{eb0f}", + "prong": "\u{fda1}", + "propeller": "\u{eec4}", + "propeller-off": "\u{f185}", + "protocol": "\u{fd81}", + "pumpkin-scary": "\u{f587}", + "puzzle": "\u{eb10}", + "puzzle-2": "\u{ef83}", + "puzzle-filled": "\u{f699}", + "puzzle-off": "\u{f186}", + "pyramid": "\u{eec5}", + "pyramid-off": "\u{f187}", + "pyramid-plus": "\u{fab2}", + "qrcode": "\u{eb11}", + "qrcode-off": "\u{f41e}", + "question-mark": "\u{ec9d}", + "quote": "\u{efbe}", + "quote-filled": "\u{1009c}", + "quote-off": "\u{f188}", + //"quotes": "\u{fb1e}", + "radar": "\u{f017}", + "radar-2": "\u{f016}", + //"radar-filled": "\u{fe0d}", + "radar-off": "\u{f41f}", + "radio": "\u{ef2d}", + "radio-off": "\u{f420}", + "radioactive": "\u{ecc0}", + "radioactive-filled": "\u{f760}", + "radioactive-off": "\u{f189}", + "radius-bottom-left": "\u{eec6}", + "radius-bottom-right": "\u{eec7}", + "radius-top-left": "\u{eec8}", + "radius-top-right": "\u{eec9}", + "rainbow": "\u{edbc}", + "rainbow-off": "\u{f18a}", + "rating-12-plus": "\u{f266}", + "rating-14-plus": "\u{f267}", + "rating-16-plus": "\u{f268}", + "rating-18-plus": "\u{f269}", + "rating-21-plus": "\u{f26a}", + "razor": "\u{f4b5}", + "razor-electric": "\u{f4b4}", + "receipt": "\u{edfd}", + "receipt-2": "\u{edfa}", + "receipt-bitcoin": "\u{fd66}", + "receipt-dollar": "\u{fd67}", + "receipt-dollar-filled": "\u{1017d}", + "receipt-euro": "\u{fd68}", + "receipt-euro-filled": "\u{1017c}", + "receipt-filled": "\u{ff06}", + "receipt-off": "\u{edfb}", + "receipt-pound": "\u{fd69}", + "receipt-pound-filled": "\u{1017b}", + "receipt-refund": "\u{edfc}", + "receipt-rupee": "\u{fd82}", + "receipt-rupee-filled": "\u{1017a}", + "receipt-tax": "\u{edbd}", + "receipt-yen": "\u{fd6a}", + "receipt-yen-filled": "\u{10179}", + "receipt-yuan": "\u{fd6b}", + "receipt-yuan-filled": "\u{10178}", + "recharging": "\u{eeca}", + "record-mail": "\u{eb12}", + "record-mail-off": "\u{f18b}", + "rectangle": "\u{ed37}", + "rectangle-filled": "\u{f69a}", + "rectangle-rounded-bottom": "\u{faed}", + "rectangle-rounded-top": "\u{faee}", + "rectangle-vertical": "\u{ed36}", + "rectangle-vertical-filled": "\u{f69b}", + "rectangular-prism": "\u{fab5}", + "rectangular-prism-off": "\u{fab3}", + "rectangular-prism-plus": "\u{fab4}", + "recycle": "\u{eb9b}", + "recycle-off": "\u{f18c}", + "refresh": "\u{eb13}", + "refresh-alert": "\u{ed57}", + "refresh-dot": "\u{efbf}", + "refresh-off": "\u{f18d}", + "regex": "\u{f31f}", + "regex-off": "\u{f421}", + "registered": "\u{eb14}", + "relation-many-to-many": "\u{ed7f}", + //"relation-many-to-many-filled": "\u{fe0c}", + "relation-one-to-many": "\u{ed80}", + //"relation-one-to-many-filled": "\u{fe0b}", + "relation-one-to-one": "\u{ed81}", + //"relation-one-to-one-filled": "\u{fe0a}", + "reload": "\u{f3ae}", + "reorder": "\u{fc15}", + "repeat": "\u{eb72}", + "repeat-off": "\u{f18e}", + "repeat-once": "\u{eb71}", + "replace": "\u{ebc7}", + "replace-filled": "\u{f69c}", + "replace-off": "\u{f422}", + "replace-user": "\u{100f0}", + "report": "\u{eece}", + "report-analytics": "\u{eecb}", + "report-medical": "\u{eecc}", + "report-money": "\u{eecd}", + "report-off": "\u{f18f}", + "report-search": "\u{ef84}", + "reserved-line": "\u{f9f6}", + "resize": "\u{eecf}", + "restore": "\u{fafd}", + "rewind-backward-10": "\u{faba}", + "rewind-backward-15": "\u{fabb}", + "rewind-backward-20": "\u{fabc}", + "rewind-backward-30": "\u{fabd}", + "rewind-backward-40": "\u{fabe}", + "rewind-backward-5": "\u{fabf}", + "rewind-backward-50": "\u{fac0}", + "rewind-backward-60": "\u{fac1}", + "rewind-forward-10": "\u{fac2}", + "rewind-forward-15": "\u{fac3}", + "rewind-forward-20": "\u{fac4}", + "rewind-forward-30": "\u{fac5}", + "rewind-forward-40": "\u{fac6}", + "rewind-forward-5": "\u{fac7}", + "rewind-forward-50": "\u{fac8}", + "rewind-forward-60": "\u{fac9}", + "ribbon-health": "\u{f58e}", + "rings": "\u{fa6a}", + "ripple": "\u{ed82}", + "ripple-off": "\u{f190}", + "road": "\u{f018}", + "road-off": "\u{f191}", + "road-sign": "\u{ecdd}", + "robot": "\u{f00b}", + "robot-face": "\u{fcbe}", + "robot-off": "\u{f192}", + "rocket": "\u{ec45}", + "rocket-off": "\u{f193}", + "roller-skating": "\u{efd1}", + "rollercoaster": "\u{f0a2}", + "rollercoaster-filled": "\u{100f7}", + "rollercoaster-off": "\u{f423}", + "rosette": "\u{f599}", + "rosette-discount": "\u{ee7c}", + "rosette-discount-check": "\u{f1f8}", + "rosette-discount-check-filled": "\u{f746}", + "rosette-discount-check-off": "\u{ff10}", + "rosette-discount-filled": "\u{ff05}", + "rosette-discount-off": "\u{f3e6}", + "rosette-filled": "\u{f69d}", + "rosette-number-0": "\u{f58f}", + "rosette-number-1": "\u{f590}", + "rosette-number-2": "\u{f591}", + "rosette-number-3": "\u{f592}", + "rosette-number-4": "\u{f593}", + "rosette-number-5": "\u{f594}", + "rosette-number-6": "\u{f595}", + "rosette-number-7": "\u{f596}", + "rosette-number-8": "\u{f597}", + "rosette-number-9": "\u{f598}", + "rotate": "\u{eb16}", + "rotate-2": "\u{ebb4}", + "rotate-360": "\u{ef85}", + "rotate-3d": "\u{f020}", + "rotate-clockwise": "\u{eb15}", + "rotate-clockwise-2": "\u{ebb5}", + "rotate-dot": "\u{efe5}", + "rotate-rectangle": "\u{ec15}", + "route": "\u{eb17}", + "route-2": "\u{f4b6}", + "route-alt-left": "\u{fca9}", + "route-alt-right": "\u{fcaa}", + "route-off": "\u{f194}", + "route-scan": "\u{fcbf}", + "route-square": "\u{fcac}", + "route-square-2": "\u{fcab}", + "route-x": "\u{fcae}", + "route-x-2": "\u{fcad}", + "router": "\u{eb18}", + "router-off": "\u{f424}", + "row-insert-bottom": "\u{eed0}", + "row-insert-top": "\u{eed1}", + "row-remove": "\u{fafe}", + "rss": "\u{eb19}", + "rubber-stamp": "\u{f5ab}", + "rubber-stamp-off": "\u{f5aa}", + "ruler": "\u{eb1a}", + "ruler-2": "\u{eed2}", + "ruler-2-off": "\u{f195}", + "ruler-3": "\u{f290}", + "ruler-measure": "\u{f291}", + "ruler-measure-2": "\u{ff0f}", + "ruler-off": "\u{f196}", + "run": "\u{ec82}", + "rv-truck": "\u{fcc0}", + "s-turn-down": "\u{f516}", + "s-turn-left": "\u{f517}", + "s-turn-right": "\u{f518}", + "s-turn-up": "\u{f519}", + "sailboat": "\u{ec83}", + "sailboat-2": "\u{f5f7}", + "sailboat-off": "\u{f425}", + "salad": "\u{f50a}", + "salad-filled": "\u{10007}", + "salt": "\u{ef16}", + "sandbox": "\u{fd6c}", + "satellite": "\u{eed3}", + "satellite-off": "\u{f197}", + "sausage": "\u{ef17}", + "scale": "\u{ebc2}", + "scale-off": "\u{f198}", + "scale-outline": "\u{ef53}", + "scale-outline-off": "\u{f199}", + "scan": "\u{ebc8}", + "scan-eye": "\u{f1ff}", + "scan-position": "\u{fdac}", + "schema": "\u{f200}", + "schema-off": "\u{f426}", + "school": "\u{ecf7}", + "school-bell": "\u{f64a}", + "school-off": "\u{f19a}", + "scissors": "\u{eb1b}", + "scissors-off": "\u{f19b}", + "scooter": "\u{ec6c}", + "scooter-electric": "\u{ecc1}", + "scoreboard": "\u{fa6b}", + "screen-share": "\u{ed18}", + "screen-share-off": "\u{ed17}", + "screenshot": "\u{f201}", + "scribble": "\u{f0a3}", + "scribble-off": "\u{f427}", + "script": "\u{f2da}", + "script-minus": "\u{f2d7}", + "script-plus": "\u{f2d8}", + "script-x": "\u{f2d9}", + "scuba-diving": "\u{fd4e}", + "scuba-diving-tank": "\u{fefa}", + "scuba-diving-tank-filled": "\u{ff04}", + "scuba-mask": "\u{eed4}", + "scuba-mask-off": "\u{f428}", + "sdk": "\u{f3af}", + "search": "\u{eb1c}", + "search-off": "\u{f19c}", + "section": "\u{eed5}", + //"section-filled": "\u{fe09}", + "section-sign": "\u{f019}", + "seeding": "\u{ed51}", + "seeding-filled": "\u{10006}", + "seeding-off": "\u{f19d}", + "seedling": "\u{ed51}", + "seedling-filled": "\u{10006}", + "seedling-off": "\u{f19d}", + "select": "\u{ec9e}", + "select-all": "\u{f9f7}", + "selector": "\u{eb1d}", + "send": "\u{eb1e}", + "send-2": "\u{fd5d}", + "send-off": "\u{f429}", + "seo": "\u{f26b}", + "separator": "\u{ebda}", + "separator-horizontal": "\u{ec79}", + "separator-vertical": "\u{ec7a}", + "server": "\u{eb1f}", + "server-2": "\u{f07c}", + "server-bolt": "\u{f320}", + "server-cog": "\u{f321}", + "server-off": "\u{f19e}", + "server-spark": "\u{ffaf}", + "servicemark": "\u{ec09}", + "settings": "\u{eb20}", + "settings-2": "\u{f5ac}", + "settings-automation": "\u{eed6}", + "settings-bolt": "\u{f9ad}", + "settings-cancel": "\u{f9ae}", + "settings-check": "\u{f9af}", + "settings-code": "\u{f9b0}", + "settings-cog": "\u{f9b1}", + "settings-dollar": "\u{f9b2}", + "settings-down": "\u{f9b3}", + "settings-exclamation": "\u{f9b4}", + "settings-filled": "\u{f69e}", + "settings-heart": "\u{f9b5}", + "settings-minus": "\u{f9b6}", + "settings-off": "\u{f19f}", + "settings-pause": "\u{f9b7}", + "settings-pin": "\u{f9b8}", + "settings-plus": "\u{f9b9}", + "settings-question": "\u{f9ba}", + "settings-search": "\u{f9bb}", + "settings-share": "\u{f9bc}", + "settings-spark": "\u{ffae}", + "settings-star": "\u{f9bd}", + "settings-up": "\u{f9be}", + "settings-x": "\u{f9bf}", + "shadow": "\u{eed8}", + "shadow-off": "\u{eed7}", + "shape": "\u{eb9c}", + "shape-2": "\u{eed9}", + "shape-3": "\u{eeda}", + "shape-off": "\u{f1a0}", + "share": "\u{eb21}", + "share-2": "\u{f799}", + "share-3": "\u{f7bd}", + "share-off": "\u{f1a1}", + "shareplay": "\u{fea5}", + "shi-jumping": "\u{fa6c}", + "shield": "\u{eb24}", + "shield-bolt": "\u{f9c0}", + "shield-cancel": "\u{f9c1}", + "shield-check": "\u{eb22}", + "shield-check-filled": "\u{f761}", + "shield-checkered": "\u{ef9a}", + "shield-checkered-filled": "\u{f762}", + "shield-chevron": "\u{ef9b}", + "shield-code": "\u{f9c2}", + "shield-cog": "\u{f9c3}", + "shield-dollar": "\u{f9c4}", + "shield-down": "\u{f9c5}", + "shield-exclamation": "\u{f9c6}", + "shield-filled": "\u{f69f}", + "shield-half": "\u{f358}", + "shield-half-filled": "\u{f357}", + "shield-heart": "\u{f9c7}", + "shield-lock": "\u{ed58}", + "shield-lock-filled": "\u{f763}", + "shield-minus": "\u{f9c8}", + "shield-off": "\u{ecf8}", + "shield-pause": "\u{f9c9}", + "shield-pin": "\u{f9ca}", + "shield-plus": "\u{f9cb}", + "shield-question": "\u{f9cc}", + "shield-search": "\u{f9cd}", + "shield-share": "\u{f9ce}", + "shield-star": "\u{f9cf}", + "shield-up": "\u{f9d0}", + "shield-x": "\u{eb23}", + "ship": "\u{ec84}", + "ship-off": "\u{f42a}", + "shirt": "\u{ec0a}", + "shirt-filled": "\u{f6a0}", + "shirt-off": "\u{f1a2}", + "shirt-sport": "\u{f26c}", + "shoe": "\u{efd2}", + "shoe-off": "\u{f1a4}", + "shopping-bag": "\u{f5f8}", + "shopping-bag-check": "\u{fc16}", + "shopping-bag-discount": "\u{fc17}", + "shopping-bag-edit": "\u{fc18}", + "shopping-bag-exclamation": "\u{fc19}", + "shopping-bag-heart": "\u{fda2}", + "shopping-bag-minus": "\u{fc1a}", + "shopping-bag-plus": "\u{fc1b}", + "shopping-bag-search": "\u{fc1c}", + "shopping-bag-x": "\u{fc1d}", + "shopping-cart": "\u{eb25}", + "shopping-cart-bolt": "\u{fb57}", + "shopping-cart-cancel": "\u{fb58}", + "shopping-cart-check": "\u{fb59}", + "shopping-cart-code": "\u{fb5a}", + "shopping-cart-cog": "\u{fb5b}", + "shopping-cart-copy": "\u{fb5c}", + "shopping-cart-discount": "\u{fb5d}", + "shopping-cart-dollar": "\u{fb5e}", + "shopping-cart-down": "\u{fb5f}", + "shopping-cart-exclamation": "\u{fb60}", + "shopping-cart-filled": "\u{fc3f}", + "shopping-cart-heart": "\u{fb61}", + "shopping-cart-minus": "\u{fb62}", + "shopping-cart-off": "\u{eedc}", + "shopping-cart-pause": "\u{fb63}", + "shopping-cart-pin": "\u{fb64}", + "shopping-cart-plus": "\u{fb65}", + "shopping-cart-question": "\u{fb66}", + "shopping-cart-search": "\u{fb67}", + "shopping-cart-share": "\u{fb68}", + "shopping-cart-star": "\u{fb69}", + "shopping-cart-up": "\u{fb6a}", + "shopping-cart-x": "\u{fb6b}", + "shovel": "\u{f1d9}", + "shovel-pitchforks": "\u{fd3a}", + "shredder": "\u{eedf}", + "sign-left": "\u{f06b}", + "sign-left-filled": "\u{f6a1}", + "sign-right": "\u{f06c}", + "sign-right-filled": "\u{f6a2}", + "signal-2g": "\u{f79a}", + "signal-3g": "\u{f1ee}", + "signal-4g": "\u{f1ef}", + "signal-4g-plus": "\u{f259}", + "signal-5g": "\u{f1f0}", + "signal-6g": "\u{f9f8}", + "signal-e": "\u{f9f9}", + "signal-g": "\u{f9fa}", + "signal-h": "\u{f9fc}", + "signal-h-plus": "\u{f9fb}", + "signal-lte": "\u{f9fd}", + "signature": "\u{eee0}", + "signature-off": "\u{f1a5}", + "sitemap": "\u{eb9d}", + "sitemap-filled": "\u{1006b}", + "sitemap-off": "\u{f1a6}", + "skateboard": "\u{ecc2}", + "skateboard-off": "\u{f42b}", + "skateboarding": "\u{faca}", + "skew-x": "\u{fd3b}", + "skew-y": "\u{fd3c}", + "ski-jumping": "\u{fa6c}", + "skull": "\u{f292}", + "slash": "\u{f4f9}", + "slashes": "\u{f588}", + "sleigh": "\u{ef9c}", + "slice": "\u{ebdb}", + "slideshow": "\u{ebc9}", + "smart-home": "\u{ecde}", + "smart-home-off": "\u{f1a7}", + "smoking": "\u{ecc4}", + "smoking-no": "\u{ecc3}", + "snowboarding": "\u{fd4f}", + "snowflake": "\u{ec0b}", + "snowflake-off": "\u{f1a8}", + "snowman": "\u{f26d}", + "soccer-field": "\u{ed92}", + "social": "\u{ebec}", + "social-off": "\u{f1a9}", + "sock": "\u{eee1}", + "sofa": "\u{efaf}", + "sofa-off": "\u{f42c}", + "solar-electricity": "\u{fcc1}", + "solar-panel": "\u{f7bf}", + "solar-panel-2": "\u{f7be}", + "sort-0-9": "\u{f54d}", + "sort-9-0": "\u{f54e}", + "sort-a-z": "\u{f54f}", + "sort-ascending": "\u{eb26}", + "sort-ascending-2": "\u{eee2}", + "sort-ascending-2-filled": "\u{ff5b}", + "sort-ascending-letters": "\u{ef18}", + "sort-ascending-numbers": "\u{ef19}", + "sort-ascending-shapes": "\u{fd94}", + "sort-ascending-shapes-filled": "\u{ff5a}", + "sort-ascending-small-big": "\u{fd95}", + "sort-deacending-small-big": "\u{fd96}", + "sort-descending": "\u{eb27}", + "sort-descending-2": "\u{eee3}", + "sort-descending-2-filled": "\u{ff59}", + "sort-descending-letters": "\u{ef1a}", + "sort-descending-numbers": "\u{ef1b}", + "sort-descending-shapes": "\u{fd97}", + "sort-descending-shapes-filled": "\u{ff58}", + "sort-descending-small-big": "\u{fd96}", + "sort-z-a": "\u{f550}", + "sos": "\u{f24a}", + "soup": "\u{ef2e}", + //"soup-filled": "\u{fe08}", + "soup-off": "\u{f42d}", + "source-code": "\u{f4a2}", + "space": "\u{ec0c}", + "space-off": "\u{f1aa}", + "spaces": "\u{fea4}", + "spacing-horizontal": "\u{ef54}", + "spacing-vertical": "\u{ef55}", + "spade": "\u{effa}", + "spade-filled": "\u{f6a3}", + "sparkles": "\u{f6d7}", + "speakerphone": "\u{ed61}", + "speedboat": "\u{ed93}", + "speedboat-filled": "\u{100f6}", + "sphere": "\u{fab8}", + "sphere-off": "\u{fab6}", + "sphere-plus": "\u{fab7}", + "spider": "\u{f293}", + "spider-filled": "\u{10109}", + "spiral": "\u{f294}", + "spiral-off": "\u{f42e}", + "sport-billard": "\u{eee4}", + "spray": "\u{f50b}", + "spy": "\u{f227}", + "spy-off": "\u{f42f}", + "sql": "\u{f7c0}", + "square": "\u{eb2c}", + "square-0": "\u{eee5}", + "square-1": "\u{eee6}", + "square-2": "\u{eee7}", + "square-3": "\u{eee8}", + "square-4": "\u{eee9}", + "square-5": "\u{eeea}", + "square-6": "\u{eeeb}", + "square-7": "\u{eeec}", + "square-8": "\u{eeed}", + "square-9": "\u{eeee}", + "square-arrow-down": "\u{f4b7}", + "square-arrow-down-filled": "\u{fb31}", + "square-arrow-left": "\u{f4b8}", + "square-arrow-left-filled": "\u{fb32}", + "square-arrow-right": "\u{f4b9}", + "square-arrow-right-filled": "\u{fb33}", + "square-arrow-up": "\u{f4ba}", + "square-arrow-up-filled": "\u{fb34}", + "square-asterisk": "\u{f01a}", + "square-asterisk-filled": "\u{fb35}", + "square-check": "\u{eb28}", + "square-check-filled": "\u{f76d}", + "square-chevron-down": "\u{f627}", + "square-chevron-down-filled": "\u{fb36}", + "square-chevron-left": "\u{f628}", + "square-chevron-left-filled": "\u{fb37}", + "square-chevron-right": "\u{f629}", + "square-chevron-right-filled": "\u{fb38}", + "square-chevron-up": "\u{f62a}", + "square-chevron-up-filled": "\u{fb39}", + "square-chevrons-down": "\u{f64b}", + "square-chevrons-down-filled": "\u{fb3a}", + "square-chevrons-left": "\u{f64c}", + "square-chevrons-left-filled": "\u{fb3b}", + "square-chevrons-right": "\u{f64d}", + "square-chevrons-right-filled": "\u{fb3c}", + "square-chevrons-up": "\u{f64e}", + "square-chevrons-up-filled": "\u{fb3d}", + "square-dashed": "\u{100bb}", + "square-dot": "\u{ed59}", + "square-dot-filled": "\u{fb3e}", + "square-f0": "\u{f526}", + "square-f0-filled": "\u{f76e}", + "square-f1": "\u{f527}", + "square-f1-filled": "\u{f76f}", + "square-f2": "\u{f528}", + "square-f2-filled": "\u{f770}", + "square-f3": "\u{f529}", + "square-f3-filled": "\u{f771}", + "square-f4": "\u{f52a}", + "square-f4-filled": "\u{f772}", + "square-f5": "\u{f52b}", + "square-f5-filled": "\u{f773}", + "square-f6": "\u{f52c}", + "square-f6-filled": "\u{f774}", + "square-f7": "\u{f52d}", + "square-f7-filled": "\u{f775}", + "square-f8": "\u{f52e}", + "square-f8-filled": "\u{f776}", + "square-f9": "\u{f52f}", + "square-f9-filled": "\u{f777}", + "square-filled": "\u{fc40}", + "square-forbid": "\u{ed5b}", + "square-forbid-2": "\u{ed5a}", + "square-half": "\u{effb}", + "square-key": "\u{f638}", + "square-letter-a": "\u{f47c}", + //"square-letter-a-filled": "\u{fe07}", + "square-letter-b": "\u{f47d}", + //"square-letter-b-filled": "\u{fe06}", + "square-letter-c": "\u{f47e}", + //"square-letter-c-filled": "\u{fe05}", + "square-letter-d": "\u{f47f}", + //"square-letter-d-filled": "\u{fe04}", + "square-letter-e": "\u{f480}", + //"square-letter-e-filled": "\u{fe03}", + "square-letter-f": "\u{f481}", + //"square-letter-f-filled": "\u{fe02}", + "square-letter-g": "\u{f482}", + //"square-letter-g-filled": "\u{fe01}", + "square-letter-h": "\u{f483}", + //"square-letter-h-filled": "\u{fe00}", + "square-letter-i": "\u{f484}", + "square-letter-i-filled": "\u{fdff}", + "square-letter-j": "\u{f485}", + "square-letter-j-filled": "\u{fdfe}", + "square-letter-k": "\u{f486}", + "square-letter-k-filled": "\u{fdfd}", + "square-letter-l": "\u{f487}", + "square-letter-l-filled": "\u{fdfc}", + "square-letter-m": "\u{f488}", + "square-letter-m-filled": "\u{fdfb}", + "square-letter-n": "\u{f489}", + "square-letter-n-filled": "\u{fdfa}", + "square-letter-o": "\u{f48a}", + "square-letter-o-filled": "\u{fdf9}", + "square-letter-p": "\u{f48b}", + "square-letter-p-filled": "\u{fdf8}", + "square-letter-q": "\u{f48c}", + "square-letter-q-filled": "\u{fdf7}", + "square-letter-r": "\u{f48d}", + "square-letter-r-filled": "\u{fdf6}", + "square-letter-s": "\u{f48e}", + "square-letter-s-filled": "\u{fdf5}", + "square-letter-t": "\u{f48f}", + "square-letter-t-filled": "\u{fdf4}", + "square-letter-u": "\u{f490}", + "square-letter-u-filled": "\u{fdf3}", + "square-letter-v": "\u{f4bb}", + "square-letter-v-filled": "\u{fdf2}", + "square-letter-w": "\u{f491}", + "square-letter-w-filled": "\u{fdf1}", + "square-letter-x": "\u{f4bc}", + "square-letter-x-filled": "\u{fdf0}", + "square-letter-y": "\u{f492}", + "square-letter-y-filled": "\u{fdef}", + "square-letter-z": "\u{f493}", + "square-letter-z-filled": "\u{fdee}", + "square-minus": "\u{eb29}", + "square-minus-filled": "\u{fb3f}", + "square-number-0": "\u{eee5}", + "square-number-0-filled": "\u{f764}", + "square-number-1": "\u{eee6}", + "square-number-1-filled": "\u{f765}", + "square-number-2": "\u{eee7}", + "square-number-2-filled": "\u{f7fa}", + "square-number-3": "\u{eee8}", + "square-number-3-filled": "\u{f766}", + "square-number-4": "\u{eee9}", + "square-number-4-filled": "\u{f767}", + "square-number-5": "\u{eeea}", + "square-number-5-filled": "\u{f768}", + "square-number-6": "\u{eeeb}", + "square-number-6-filled": "\u{f769}", + "square-number-7": "\u{eeec}", + "square-number-7-filled": "\u{f76a}", + "square-number-8": "\u{eeed}", + "square-number-8-filled": "\u{f76b}", + "square-number-9": "\u{eeee}", + "square-number-9-filled": "\u{f76c}", + "square-off": "\u{eeef}", + "square-percentage": "\u{fd83}", + "square-plus": "\u{eb2a}", + "square-plus-2": "\u{fc96}", + "square-root": "\u{eef1}", + "square-root-2": "\u{eef0}", + "square-rotated": "\u{ecdf}", + "square-rotated-filled": "\u{f6a4}", + "square-rotated-forbid": "\u{f01c}", + "square-rotated-forbid-2": "\u{f01b}", + "square-rotated-off": "\u{eef2}", + "square-rounded": "\u{f59a}", + "square-rounded-arrow-down": "\u{f639}", + "square-rounded-arrow-down-filled": "\u{f6db}", + "square-rounded-arrow-left": "\u{f63a}", + "square-rounded-arrow-left-filled": "\u{f6dc}", + "square-rounded-arrow-right": "\u{f63b}", + "square-rounded-arrow-right-filled": "\u{f6dd}", + "square-rounded-arrow-up": "\u{f63c}", + "square-rounded-arrow-up-filled": "\u{f6de}", + "square-rounded-check": "\u{f63d}", + "square-rounded-check-filled": "\u{f6df}", + "square-rounded-chevron-down": "\u{f62b}", + "square-rounded-chevron-down-filled": "\u{f6e0}", + "square-rounded-chevron-left": "\u{f62c}", + "square-rounded-chevron-left-filled": "\u{f6e1}", + "square-rounded-chevron-right": "\u{f62d}", + "square-rounded-chevron-right-filled": "\u{f6e2}", + "square-rounded-chevron-up": "\u{f62e}", + "square-rounded-chevron-up-filled": "\u{f6e3}", + "square-rounded-chevrons-down": "\u{f64f}", + "square-rounded-chevrons-down-filled": "\u{f6e4}", + "square-rounded-chevrons-left": "\u{f650}", + "square-rounded-chevrons-left-filled": "\u{f6e5}", + "square-rounded-chevrons-right": "\u{f651}", + "square-rounded-chevrons-right-filled": "\u{f6e6}", + "square-rounded-chevrons-up": "\u{f652}", + "square-rounded-chevrons-up-filled": "\u{f6e7}", + "square-rounded-filled": "\u{f6a5}", + "square-rounded-letter-a": "\u{f5ae}", + "square-rounded-letter-a-filled": "\u{fded}", + "square-rounded-letter-b": "\u{f5af}", + "square-rounded-letter-b-filled": "\u{fdec}", + "square-rounded-letter-c": "\u{f5b0}", + "square-rounded-letter-c-filled": "\u{fdeb}", + "square-rounded-letter-d": "\u{f5b1}", + "square-rounded-letter-d-filled": "\u{fdea}", + "square-rounded-letter-e": "\u{f5b2}", + "square-rounded-letter-e-filled": "\u{fde9}", + "square-rounded-letter-f": "\u{f5b3}", + "square-rounded-letter-f-filled": "\u{fde8}", + "square-rounded-letter-g": "\u{f5b4}", + "square-rounded-letter-g-filled": "\u{fde7}", + "square-rounded-letter-h": "\u{f5b5}", + "square-rounded-letter-h-filled": "\u{fde6}", + "square-rounded-letter-i": "\u{f5b6}", + "square-rounded-letter-i-filled": "\u{fde5}", + "square-rounded-letter-j": "\u{f5b7}", + "square-rounded-letter-j-filled": "\u{fde4}", + "square-rounded-letter-k": "\u{f5b8}", + "square-rounded-letter-k-filled": "\u{fde3}", + "square-rounded-letter-l": "\u{f5b9}", + "square-rounded-letter-l-filled": "\u{fde2}", + "square-rounded-letter-m": "\u{f5ba}", + "square-rounded-letter-m-filled": "\u{fde1}", + "square-rounded-letter-n": "\u{f5bb}", + "square-rounded-letter-n-filled": "\u{fde0}", + "square-rounded-letter-o": "\u{f5bc}", + "square-rounded-letter-o-filled": "\u{fddf}", + "square-rounded-letter-p": "\u{f5bd}", + "square-rounded-letter-p-filled": "\u{fdde}", + "square-rounded-letter-q": "\u{f5be}", + "square-rounded-letter-q-filled": "\u{fddd}", + "square-rounded-letter-r": "\u{f5bf}", + "square-rounded-letter-r-filled": "\u{fddc}", + "square-rounded-letter-s": "\u{f5c0}", + "square-rounded-letter-s-filled": "\u{fddb}", + "square-rounded-letter-t": "\u{f5c1}", + "square-rounded-letter-t-filled": "\u{fdda}", + "square-rounded-letter-u": "\u{f5c2}", + "square-rounded-letter-u-filled": "\u{fdd9}", + "square-rounded-letter-v": "\u{f5c3}", + "square-rounded-letter-v-filled": "\u{fdd8}", + "square-rounded-letter-w": "\u{f5c4}", + "square-rounded-letter-w-filled": "\u{fdd7}", + "square-rounded-letter-x": "\u{f5c5}", + "square-rounded-letter-x-filled": "\u{fdd6}", + "square-rounded-letter-y": "\u{f5c6}", + "square-rounded-letter-y-filled": "\u{fdd5}", + "square-rounded-letter-z": "\u{f5c7}", + "square-rounded-letter-z-filled": "\u{fdd4}", + "square-rounded-minus": "\u{f63e}", + "square-rounded-minus-2": "\u{fc97}", + "square-rounded-minus-filled": "\u{fb40}", + "square-rounded-number-0": "\u{f5c8}", + "square-rounded-number-0-filled": "\u{f778}", + "square-rounded-number-1": "\u{f5c9}", + "square-rounded-number-1-filled": "\u{f779}", + "square-rounded-number-2": "\u{f5ca}", + "square-rounded-number-2-filled": "\u{f77a}", + "square-rounded-number-3": "\u{f5cb}", + "square-rounded-number-3-filled": "\u{f77b}", + "square-rounded-number-4": "\u{f5cc}", + "square-rounded-number-4-filled": "\u{f77c}", + "square-rounded-number-5": "\u{f5cd}", + "square-rounded-number-5-filled": "\u{f77d}", + "square-rounded-number-6": "\u{f5ce}", + "square-rounded-number-6-filled": "\u{f77e}", + "square-rounded-number-7": "\u{f5cf}", + "square-rounded-number-7-filled": "\u{f77f}", + "square-rounded-number-8": "\u{f5d0}", + "square-rounded-number-8-filled": "\u{f780}", + "square-rounded-number-9": "\u{f5d1}", + "square-rounded-number-9-filled": "\u{f781}", + "square-rounded-percentage": "\u{fd84}", + "square-rounded-plus": "\u{f63f}", + "square-rounded-plus-2": "\u{fc98}", + "square-rounded-plus-filled": "\u{f6e8}", + "square-rounded-x": "\u{f640}", + "square-rounded-x-filled": "\u{f6e9}", + "square-toggle": "\u{eef4}", + "square-toggle-horizontal": "\u{eef3}", + "square-x": "\u{eb2b}", + "square-x-filled": "\u{fb41}", + "squares": "\u{eef6}", + "squares-diagonal": "\u{eef5}", + "squares-filled": "\u{fe9f}", + "squares-selected": "\u{fea3}", + "stack": "\u{eb2d}", + "stack-2": "\u{eef7}", + "stack-2-filled": "\u{fdd3}", + "stack-3": "\u{ef9d}", + "stack-3-filled": "\u{fdd2}", + "stack-back": "\u{fd26}", + "stack-backward": "\u{fd27}", + "stack-filled": "\u{fdd1}", + "stack-forward": "\u{fd28}", + "stack-front": "\u{fd29}", + "stack-middle": "\u{fd2a}", + "stack-pop": "\u{f234}", + "stack-push": "\u{f235}", + "stairs": "\u{eca6}", + "stairs-down": "\u{eca4}", + "stairs-up": "\u{eca5}", + "star": "\u{eb2e}", + "star-filled": "\u{f6a6}", + "star-half": "\u{ed19}", + "star-half-filled": "\u{f6a7}", + "star-off": "\u{ed62}", + "stars": "\u{ed38}", + "stars-filled": "\u{f6a8}", + "stars-off": "\u{f430}", + "status-change": "\u{f3b0}", + "steam": "\u{f24b}", + "steering-wheel": "\u{ec7b}", + "steering-wheel-filled": "\u{ff03}", + "steering-wheel-off": "\u{f431}", + "step-into": "\u{ece0}", + "step-out": "\u{ece1}", + "stereo-glasses": "\u{f4cb}", + "stethoscope": "\u{edbe}", + "stethoscope-off": "\u{f432}", + "sticker": "\u{eb2f}", + "sticker-2": "\u{fd3d}", + "stopwatch": "\u{ff9b}", + "storm": "\u{f24c}", + "storm-off": "\u{f433}", + "stretching": "\u{f2db}", + "stretching-2": "\u{fa6d}", + "strikethrough": "\u{eb9e}", + "submarine": "\u{ed94}", + "subscript": "\u{eb9f}", + "subtask": "\u{ec9f}", + "sum": "\u{eb73}", + "sum-off": "\u{f1ab}", + "sun": "\u{eb30}", + "sun-electricity": "\u{fcc2}", + "sun-filled": "\u{f6a9}", + "sun-high": "\u{f236}", + "sun-high-filled": "\u{10108}", + "sun-low": "\u{f237}", + "sun-low-filled": "\u{10107}", + "sun-moon": "\u{f4a3}", + "sun-off": "\u{ed63}", + "sun-wind": "\u{f238}", + "sunglasses": "\u{f239}", + "sunglasses-filled": "\u{fec8}", + "sunrise": "\u{ef1c}", + "sunrise-filled": "\u{10106}", + "sunset": "\u{ec31}", + "sunset-2": "\u{f23a}", + "sunset-2-filled": "\u{10105}", + "sunset-filled": "\u{10104}", + "superscript": "\u{eba0}", + "svg": "\u{f25a}", + "swimming": "\u{ec92}", + "swipe": "\u{f551}", + "swipe-down": "\u{fd5e}", + "swipe-down-filled": "\u{ff57}", + "swipe-left": "\u{fd5f}", + "swipe-left-filled": "\u{ff56}", + "swipe-right": "\u{fd60}", + "swipe-right-filled": "\u{ff55}", + "swipe-up": "\u{fd61}", + "swipe-up-filled": "\u{ff54}", + "switch": "\u{eb33}", + "switch-2": "\u{edbf}", + "switch-3": "\u{edc0}", + "switch-horizontal": "\u{eb31}", + "switch-vertical": "\u{eb32}", + "sword": "\u{f030}", + "sword-off": "\u{f434}", + "swords": "\u{f132}", + "table": "\u{eba1}", + "table-alias": "\u{f25b}", + "table-column": "\u{faff}", + "table-dashed": "\u{100ba}", + "table-down": "\u{fa1c}", + "table-export": "\u{eef8}", + "table-filled": "\u{f782}", + "table-heart": "\u{fa1d}", + "table-import": "\u{eef9}", + "table-minus": "\u{fa1e}", + "table-off": "\u{eefa}", + "table-options": "\u{f25c}", + "table-plus": "\u{fa1f}", + "table-row": "\u{fb00}", + "table-share": "\u{fa20}", + "table-shortcut": "\u{f25d}", + "table-spark": "\u{ffad}", + "tag": "\u{10096}", + "tag-filled": "\u{ff02}", + "tag-minus": "\u{eb34}", + "tag-off": "\u{efc0}", + "tag-plus": "\u{10097}", + "tag-starred": "\u{fc99}", + "tags": "\u{ef86}", + "tags-filled": "\u{ff01}", + "tags-off": "\u{efc1}", + "tallymark-1": "\u{ec46}", + "tallymark-2": "\u{ec47}", + "tallymark-3": "\u{ec48}", + "tallymark-4": "\u{ec49}", + "tallymarks": "\u{ec4a}", + "tank": "\u{ed95}", + "target": "\u{eb35}", + "target-arrow": "\u{f51a}", + "target-off": "\u{f1ad}", + "tax": "\u{feee}", + "tax-euro": "\u{fef0}", + "tax-pound": "\u{feef}", + "teapot": "\u{f552}", + "telescope": "\u{f07d}", + "telescope-off": "\u{f1ae}", + "temperature": "\u{eb38}", + "temperature-celsius": "\u{eb36}", + "temperature-fahrenheit": "\u{eb37}", + "temperature-minus": "\u{ebed}", + "temperature-minus-filled": "\u{10103}", + "temperature-off": "\u{f1af}", + "temperature-plus": "\u{ebee}", + "temperature-plus-filled": "\u{10102}", + "temperature-snow": "\u{fda3}", + "temperature-sun": "\u{fda4}", + "template": "\u{eb39}", + "template-filled": "\u{10177}", + "template-off": "\u{f1b0}", + "tent": "\u{eefb}", + "tent-off": "\u{f435}", + "terminal": "\u{ebdc}", + "terminal-2": "\u{ebef}", + "test-pipe": "\u{eb3a}", + "test-pipe-2": "\u{f0a4}", + "test-pipe-2-filled": "\u{ff53}", + "test-pipe-off": "\u{f1b1}", + "tex": "\u{f4e0}", + "text-caption": "\u{f4a4}", + "text-color": "\u{f2dc}", + "text-decrease": "\u{f202}", + "text-direction-ltr": "\u{eefc}", + "text-direction-rtl": "\u{eefd}", + "text-grammar": "\u{fd6d}", + "text-increase": "\u{f203}", + "text-orientation": "\u{f2a4}", + "text-plus": "\u{f2a5}", + "text-recognition": "\u{f204}", + "text-resize": "\u{ef87}", + "text-scan-2": "\u{fcc3}", + "text-size": "\u{f2b1}", + "text-spellcheck": "\u{f2a6}", + "text-wrap": "\u{ebdd}", + "text-wrap-column": "\u{feb2}", + "text-wrap-disabled": "\u{eca7}", + "texture": "\u{f51b}", + "theater": "\u{f79b}", + "thermometer": "\u{ef67}", + "thumb-down": "\u{eb3b}", + "thumb-down-filled": "\u{f6aa}", + "thumb-down-off": "\u{f436}", + "thumb-up": "\u{eb3c}", + "thumb-up-filled": "\u{f6ab}", + "thumb-up-off": "\u{f437}", + "tic-tac": "\u{f51c}", + "ticket": "\u{eb3d}", + "ticket-off": "\u{f1b2}", + "tie": "\u{f07e}", + "tilde": "\u{f4a5}", + "tilt-shift": "\u{eefe}", + "tilt-shift-filled": "\u{fec7}", + "tilt-shift-off": "\u{f1b3}", + "time-duration-0": "\u{fad4}", + "time-duration-10": "\u{fad5}", + "time-duration-15": "\u{fad6}", + "time-duration-30": "\u{fad7}", + "time-duration-45": "\u{fad8}", + "time-duration-5": "\u{fad9}", + "time-duration-60": "\u{fada}", + "time-duration-90": "\u{fadb}", + "time-duration-off": "\u{fadc}", + "timeline": "\u{f031}", + "timeline-event": "\u{f553}", + "timeline-event-exclamation": "\u{f662}", + "timeline-event-filled": "\u{fd18}", + "timeline-event-minus": "\u{f663}", + "timeline-event-plus": "\u{f664}", + "timeline-event-text": "\u{f665}", + "timeline-event-x": "\u{f666}", + "timezone": "\u{feed}", + "tip-jar": "\u{feea}", + "tip-jar-euro": "\u{feec}", + "tip-jar-pound": "\u{feeb}", + "tir": "\u{ebf0}", + "toggle-left": "\u{eb3e}", + "toggle-left-filled": "\u{fec0}", + "toggle-right": "\u{eb3f}", + "toggle-right-filled": "\u{febf}", + "toilet-paper": "\u{efd3}", + "toilet-paper-off": "\u{f1b4}", + "toml": "\u{fa5d}", + "tool": "\u{eb40}", + "tools": "\u{ebca}", + "tools-kitchen": "\u{ed64}", + "tools-kitchen-2": "\u{eeff}", + "tools-kitchen-2-off": "\u{f1b5}", + "tools-kitchen-3": "\u{fd2b}", + "tools-kitchen-off": "\u{f1b6}", + "tools-off": "\u{f1b7}", + "tooltip": "\u{f2dd}", + "topology-bus": "\u{f5d9}", + "topology-complex": "\u{f5da}", + "topology-full": "\u{f5dc}", + "topology-full-hierarchy": "\u{f5db}", + "topology-ring": "\u{f5df}", + "topology-ring-2": "\u{f5dd}", + "topology-ring-3": "\u{f5de}", + "topology-star": "\u{f5e5}", + "topology-star-2": "\u{f5e0}", + "topology-star-3": "\u{f5e1}", + "topology-star-ring": "\u{f5e4}", + "topology-star-ring-2": "\u{f5e2}", + "topology-star-ring-3": "\u{f5e3}", + "torii": "\u{f59b}", + "tornado": "\u{ece2}", + "tournament": "\u{ecd0}", + "tower": "\u{f2cb}", + "tower-off": "\u{f2ca}", + "track": "\u{ef00}", + "tractor": "\u{ec0d}", + "trademark": "\u{ec0e}", + "traffic-cone": "\u{ec0f}", + "traffic-cone-off": "\u{f1b8}", + "traffic-lights": "\u{ed39}", + "traffic-lights-off": "\u{f1b9}", + "train": "\u{ed96}", + "train-filled": "\u{100f5}", + "transaction-bitcoin": "\u{fd6e}", + "transaction-dollar": "\u{fd6f}", + "transaction-euro": "\u{fd70}", + "transaction-pound": "\u{fd71}", + "transaction-rupee": "\u{fd85}", + "transaction-yen": "\u{fd72}", + "transaction-yuan": "\u{fd73}", + "transfer": "\u{fc1f}", + "transfer-in": "\u{ef2f}", + "transfer-out": "\u{ef30}", + "transfer-vertical": "\u{fc1e}", + "transform": "\u{f38e}", + "transform-filled": "\u{f6ac}", + "transform-point": "\u{fda9}", + "transform-point-bottom-left": "\u{fda5}", + "transform-point-bottom-right": "\u{fda6}", + "transform-point-top-left": "\u{fda7}", + "transform-point-top-right": "\u{fda8}", + "transition-bottom": "\u{f2b2}", + "transition-bottom-filled": "\u{fdd0}", + "transition-left": "\u{f2b3}", + "transition-left-filled": "\u{fdcf}", + "transition-right": "\u{f2b4}", + "transition-right-filled": "\u{fdce}", + "transition-top": "\u{f2b5}", + "transition-top-filled": "\u{fdcd}", + "trash": "\u{eb41}", + "trash-filled": "\u{f783}", + "trash-off": "\u{ed65}", + "trash-x": "\u{ef88}", + "trash-x-filled": "\u{f784}", + "treadmill": "\u{fa6e}", + "tree": "\u{ef01}", + "trees": "\u{ec10}", + "trekking": "\u{f5ad}", + "trending-down": "\u{eb42}", + "trending-down-2": "\u{edc1}", + "trending-down-3": "\u{edc2}", + "trending-up": "\u{eb43}", + "trending-up-2": "\u{edc3}", + "trending-up-3": "\u{edc4}", + "triangle": "\u{eb44}", + "triangle-filled": "\u{f6ad}", + "triangle-inverted": "\u{f01d}", + "triangle-inverted-filled": "\u{f6ae}", + "triangle-minus": "\u{fc9b}", + "triangle-minus-2": "\u{fc9a}", + "triangle-off": "\u{ef02}", + "triangle-plus": "\u{fc9d}", + "triangle-plus-2": "\u{fc9c}", + "triangle-square-circle": "\u{ece8}", + "triangle-square-circle-filled": "\u{fb42}", + "triangles": "\u{f0a5}", + "trident": "\u{ecc5}", + "trolley": "\u{f4cc}", + "trolley-filled": "\u{100f4}", + "trophy": "\u{eb45}", + "trophy-filled": "\u{f6af}", + "trophy-off": "\u{f438}", + "trowel": "\u{f368}", + "truck": "\u{ebc4}", + "truck-delivery": "\u{ec4b}", + "truck-filled": "\u{100f3}", + "truck-loading": "\u{f1da}", + "truck-off": "\u{ef03}", + "truck-return": "\u{ec4c}", + "txt": "\u{f3b1}", + "typeface": "\u{fdab}", + "typography": "\u{ebc5}", + "typography-off": "\u{f1ba}", + "u-turn-left": "\u{fea2}", + "u-turn-right": "\u{fea1}", + "ufo": "\u{f26f}", + "ufo-filled": "\u{10165}", + "ufo-off": "\u{f26e}", + "uhd": "\u{100aa}", + "umbrella": "\u{ebf1}", + "umbrella-2": "\u{ff0e}", + "umbrella-closed": "\u{ff0c}", + "umbrella-closed-2": "\u{ff0d}", + "umbrella-filled": "\u{f6b0}", + "umbrella-off": "\u{f1bb}", + "underline": "\u{eba2}", + "universe": "\u{fcc4}", + "unlink": "\u{eb46}", + "upload": "\u{eb47}", + "urgent": "\u{eb48}", + "usb": "\u{f00c}", + "user": "\u{eb4d}", + "user-bitcoin": "\u{ff30}", + "user-bolt": "\u{f9d1}", + "user-cancel": "\u{f9d2}", + "user-check": "\u{eb49}", + "user-circle": "\u{ef68}", + "user-code": "\u{f9d3}", + "user-cog": "\u{f9d4}", + "user-dollar": "\u{f9d5}", + "user-down": "\u{f9d6}", + "user-edit": "\u{f7cc}", + "user-exclamation": "\u{ec12}", + "user-filled": "\u{fd19}", + "user-heart": "\u{f7cd}", + "user-hexagon": "\u{fc4e}", + "user-minus": "\u{eb4a}", + "user-off": "\u{ecf9}", + "user-pause": "\u{f9d7}", + "user-pentagon": "\u{fc4f}", + "user-pin": "\u{f7ce}", + "user-plus": "\u{eb4b}", + "user-question": "\u{f7cf}", + "user-scan": "\u{fcaf}", + "user-screen": "\u{fea0}", + "user-search": "\u{ef89}", + "user-share": "\u{f9d8}", + "user-shield": "\u{f7d0}", + "user-square": "\u{fc51}", + "user-square-rounded": "\u{fc50}", + "user-star": "\u{f7d1}", + "user-up": "\u{f7d2}", + "user-x": "\u{eb4c}", + "users": "\u{ebf2}", + "users-group": "\u{fa21}", + "users-minus": "\u{fa0e}", + "users-plus": "\u{fa0f}", + "uv-index": "\u{f3b2}", + "ux-circle": "\u{f369}", + "vaccine": "\u{ef04}", + "vaccine-bottle": "\u{ef69}", + "vaccine-bottle-off": "\u{f439}", + "vaccine-off": "\u{f1bc}", + "vacuum-cleaner": "\u{f5e6}", + "variable": "\u{ef05}", + "variable-minus": "\u{f36a}", + "variable-off": "\u{f1bd}", + "variable-plus": "\u{f36b}", + "vector": "\u{eca9}", + "vector-bezier": "\u{ef1d}", + "vector-bezier-2": "\u{f1a3}", + "vector-bezier-arc": "\u{f4cd}", + "vector-bezier-circle": "\u{f4ce}", + "vector-off": "\u{f1be}", + "vector-spline": "\u{f565}", + "vector-triangle": "\u{eca8}", + "vector-triangle-off": "\u{f1bf}", + "venus": "\u{ec86}", + "versions": "\u{ed52}", + "versions-filled": "\u{f6b1}", + "versions-off": "\u{f1c0}", + "video": "\u{ed22}", + "video-filled": "\u{1009b}", + "video-minus": "\u{ed1f}", + "video-off": "\u{ed20}", + "video-plus": "\u{ed21}", + "view-360": "\u{ed84}", + "view-360-arrow": "\u{f62f}", + "view-360-number": "\u{f566}", + "view-360-off": "\u{f1c1}", + "viewfinder": "\u{eb4e}", + "viewfinder-off": "\u{f1c2}", + "viewport-narrow": "\u{ebf3}", + "viewport-short": "\u{fee9}", + "viewport-tall": "\u{fee8}", + "viewport-wide": "\u{ebf4}", + "vinyl": "\u{f00d}", + "vip": "\u{f3b3}", + "vip-off": "\u{f43a}", + "virus": "\u{eb74}", + "virus-off": "\u{ed66}", + "virus-search": "\u{ed67}", + "vocabulary": "\u{ef1e}", + "vocabulary-off": "\u{f43b}", + "volcano": "\u{f79c}", + "volume": "\u{eb51}", + "volume-2": "\u{eb4f}", + "volume-3": "\u{eb50}", + "volume-off": "\u{f1c3}", + "vs": "\u{fc52}", + "walk": "\u{ec87}", + "wall": "\u{ef7a}", + "wall-off": "\u{f43c}", + "wallet": "\u{eb75}", + "wallet-off": "\u{f1c4}", + "wallpaper": "\u{ef56}", + "wallpaper-off": "\u{f1c5}", + "wand": "\u{ebcb}", + "wand-off": "\u{f1c6}", + "wash": "\u{f311}", + "wash-dry": "\u{f304}", + "wash-dry-1": "\u{f2fa}", + "wash-dry-2": "\u{f2fb}", + "wash-dry-3": "\u{f2fc}", + "wash-dry-a": "\u{f2fd}", + "wash-dry-dip": "\u{f2fe}", + "wash-dry-f": "\u{f2ff}", + "wash-dry-flat": "\u{fa7f}", + "wash-dry-hang": "\u{f300}", + "wash-dry-off": "\u{f301}", + "wash-dry-p": "\u{f302}", + "wash-dry-shade": "\u{f303}", + "wash-dry-w": "\u{f322}", + "wash-dryclean": "\u{f305}", + "wash-dryclean-off": "\u{f323}", + "wash-eco": "\u{fa80}", + "wash-gentle": "\u{f306}", + "wash-hand": "\u{fa81}", + "wash-machine": "\u{f25e}", + "wash-off": "\u{f307}", + "wash-press": "\u{f308}", + "wash-temperature-1": "\u{f309}", + "wash-temperature-2": "\u{f30a}", + "wash-temperature-3": "\u{f30b}", + "wash-temperature-4": "\u{f30c}", + "wash-temperature-5": "\u{f30d}", + "wash-temperature-6": "\u{f30e}", + "wash-tumble-dry": "\u{f30f}", + "wash-tumble-off": "\u{f310}", + "waterpolo": "\u{fa6f}", + "wave-saw-tool": "\u{ecd3}", + "wave-sine": "\u{ecd4}", + "wave-square": "\u{ecd5}", + "waves-electricity": "\u{fcc5}", + "webhook": "\u{f01e}", + "webhook-off": "\u{f43d}", + "weight": "\u{f589}", + "wheat": "\u{100a8}", + "wheat-off": "\u{100a9}", + "wheel": "\u{fc64}", + "wheelchair": "\u{f1db}", + "wheelchair-off": "\u{f43e}", + "whirl": "\u{f51d}", + "wifi": "\u{eb52}", + "wifi-0": "\u{eba3}", + "wifi-1": "\u{eba4}", + "wifi-2": "\u{eba5}", + "wifi-3": "\u{ebfc}", + "wifi-off": "\u{ecfa}", + "wifi-exclamation": "\u{ebfd}", + "wifi-question": "\u{ebfe}", + "wind": "\u{ec34}", + "wind-electricity": "\u{fcc6}", + "wind-off": "\u{f1c7}", + "windmill": "\u{ed85}", + "windmill-filled": "\u{f6b2}", + "windmill-off": "\u{f1c8}", + "window": "\u{ef06}", + "window-maximize": "\u{f1f1}", + "window-minimize": "\u{f1f2}", + "window-off": "\u{f1c9}", + "windsock": "\u{f06d}", + "windsock-filled": "\u{1009a}", + "wiper": "\u{ecab}", + "wiper-wash": "\u{ecaa}", + "woman": "\u{eb53}", + "woman-filled": "\u{fdcc}", + "wood": "\u{f359}", + "world": "\u{eb54}", + "world-bolt": "\u{f9d9}", + "world-cancel": "\u{f9da}", + "world-check": "\u{f9db}", + "world-code": "\u{f9dc}", + "world-cog": "\u{f9dd}", + "world-dollar": "\u{f9de}", + "world-down": "\u{f9df}", + "world-download": "\u{ef8a}", + "world-exclamation": "\u{f9e0}", + "world-heart": "\u{f9e1}", + "world-latitude": "\u{ed2e}", + "world-longitude": "\u{ed2f}", + "world-minus": "\u{f9e2}", + "world-off": "\u{f1ca}", + "world-pause": "\u{f9e3}", + "world-pin": "\u{f9e4}", + "world-plus": "\u{f9e5}", + "world-question": "\u{f9e6}", + "world-search": "\u{f9e7}", + "world-share": "\u{f9e8}", + "world-star": "\u{f9e9}", + "world-up": "\u{f9ea}", + "world-upload": "\u{ef8b}", + "world-www": "\u{f38f}", + "world-x": "\u{f9eb}", + "wrecking-ball": "\u{ed97}", + "writing": "\u{ef08}", + "writing-off": "\u{f1cb}", + "writing-sign": "\u{ef07}", + "writing-sign-off": "\u{f1cc}", + "x": "\u{eb55}", + "x-power-y": "\u{10072}", + "xbox-a": "\u{f2b6}", + "xbox-a-filled": "\u{fdcb}", + "xbox-b": "\u{f2b7}", + "xbox-b-filled": "\u{fdca}", + "xbox-x": "\u{f2b8}", + "xbox-x-filled": "\u{fdc9}", + "xbox-y": "\u{f2b9}", + "xbox-y-filled": "\u{fdc8}", + "xd": "\u{fa33}", + "xxx": "\u{fc20}", + "yin-yang": "\u{ec35}", + "yin-yang-filled": "\u{f785}", + "yoga": "\u{f01f}", + "zeppelin": "\u{f270}", + "zeppelin-filled": "\u{fdc7}", + "zeppelin-off": "\u{f43f}", + "zip": "\u{f3b4}", + "zodiac-aquarius": "\u{ecac}", + "zodiac-aries": "\u{ecad}", + "zodiac-cancer": "\u{ecae}", + "zodiac-capricorn": "\u{ecaf}", + "zodiac-gemini": "\u{ecb0}", + "zodiac-leo": "\u{ecb1}", + "zodiac-libra": "\u{ecb2}", + "zodiac-pisces": "\u{ecb3}", + "zodiac-sagittarius": "\u{ecb4}", + "zodiac-scorpio": "\u{ecb5}", + "zodiac-taurus": "\u{ecb6}", + "zodiac-virgo": "\u{ecb7}", + "zoom": "\u{fdaa}", + "zoom-cancel": "\u{ec4d}", + "zoom-cancel-filled": "\u{fdc6}", + "zoom-check": "\u{ef09}", + "zoom-check-filled": "\u{f786}", + "zoom-code": "\u{f07f}", + "zoom-code-filled": "\u{fdc5}", + "zoom-exclamation": "\u{f080}", + "zoom-exclamation-filled": "\u{fdc4}", + "zoom-filled": "\u{f787}", + "zoom-in": "\u{eb56}", + "zoom-in-area": "\u{f1dc}", + "zoom-in-area-filled": "\u{f788}", + "zoom-in-filled": "\u{f789}", + "zoom-money": "\u{ef0a}", + "zoom-money-filled": "\u{fdc3}", + "zoom-out": "\u{eb57}", + "zoom-out-area": "\u{f1dd}", + "zoom-out-area-filled": "\u{fdc2}", + "zoom-out-filled": "\u{f78a}", + "zoom-pan": "\u{f1de}", + "zoom-pan-filled": "\u{fdc1}", + "zoom-question": "\u{edeb}", + "zoom-question-filled": "\u{fdc0}", + "zoom-replace": "\u{f2a7}", + "zoom-reset": "\u{f295}", + "zoom-scan": "\u{fcb0}", + "zoom-scan-filled": "\u{fdbf}", + "zzz": "\u{f228}", + "zzz-off": "\u{f440}" + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Keybinds.qml b/arch/.config/quickshell/noctalia-shell/Commons/Keybinds.qml new file mode 100644 index 0000000..1bebe3e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Keybinds.qml @@ -0,0 +1,184 @@ +pragma Singleton + +import QtQuick + +QtObject { + function getKeybindString(event) { + let keyStr = ""; + if (event.modifiers & Qt.ControlModifier) + keyStr += "Ctrl+"; + if (event.modifiers & Qt.AltModifier) + keyStr += "Alt+"; + if (event.modifiers & Qt.ShiftModifier) + keyStr += "Shift+"; + + let keyName = ""; + let rawText = event.text; + + if (event.key >= Qt.Key_A && event.key <= Qt.Key_Z || event.key >= Qt.Key_0 && event.key <= Qt.Key_9) { + keyName = String.fromCharCode(event.key); + } else if (event.key >= Qt.Key_F1 && event.key <= Qt.Key_F12) { + keyName = "F" + (event.key - Qt.Key_F1 + 1); + } else if (rawText && rawText.length > 0 && rawText.charCodeAt(0) > 31 && rawText.charCodeAt(0) !== 127) { + keyName = rawText.toUpperCase(); + + if (event.modifiers & Qt.ShiftModifier) { + const shiftMap = { + "!": "1", + "\"": "2", + "ยง": "3", + "$": "4", + "%": "5", + "&": "6", + "/": "7", + "(": "8", + ")": "9", + "=": "0", + "@": "2", + "#": "3", + "^": "6", + "*": "8" + }; + if (shiftMap[keyName]) { + keyName = shiftMap[keyName]; + } + } + } else { + switch (event.key) { + case Qt.Key_Escape: + keyName = "Esc"; + break; + case Qt.Key_Space: + keyName = "Space"; + break; + case Qt.Key_Return: + keyName = "Return"; + break; + case Qt.Key_Enter: + keyName = "Enter"; + break; + case Qt.Key_Tab: + keyName = "Tab"; + break; + case Qt.Key_Backspace: + keyName = "Backspace"; + break; + case Qt.Key_Delete: + keyName = "Del"; + break; + case Qt.Key_Insert: + keyName = "Ins"; + break; + case Qt.Key_Home: + keyName = "Home"; + break; + case Qt.Key_End: + keyName = "End"; + break; + case Qt.Key_PageUp: + keyName = "PgUp"; + break; + case Qt.Key_PageDown: + keyName = "PgDn"; + break; + case Qt.Key_Left: + keyName = "Left"; + break; + case Qt.Key_Right: + keyName = "Right"; + break; + case Qt.Key_Up: + keyName = "Up"; + break; + case Qt.Key_Down: + keyName = "Down"; + break; + } + } + + if (!keyName) + return ""; + return keyStr + keyName; + } + + function checkKey(event, settingName, settings) { + // Accept both simplified names ("remove") and full property names ("keyRemove") + // so callers are less error-prone. + var normalized = String(settingName || ""); + if (!normalized) + return false; + + var propName = normalized.startsWith("key") ? normalized : ("key" + normalized.charAt(0).toUpperCase() + normalized.slice(1)); + var boundKeys = settings.data.general.keybinds[propName]; + if (!boundKeys || boundKeys.length === 0) + return false; + var eventString = getKeybindString(event); + for (var i = 0; i < boundKeys.length; i++) { + if (boundKeys[i] === eventString) + return true; + } + return false; + } + + /** + * Check if a keybind string conflicts with any other existing keybinds. + * @param {string} keyStr - The keybind string to check (e.g., "Ctrl+A"). + * @param {string} currentPath - The settings path of the keybind being edited (to skip checking itself). + * @param {object} data - The settings data object (from Settings.data). + * @returns {string|null} - The name of the conflicting action, or null if no conflict. + */ + function getKeybindConflict(keyStr, currentPath, data) { + if (!keyStr || !data) + return null; + + const searchKey = String(keyStr).trim().toLowerCase(); + + // 1. Check navigation keybinds + const navKeybinds = data.general ? data.general.keybinds : null; + const navMap = { + "keyUp": "Navigation: Up", + "keyDown": "Navigation: Down", + "keyLeft": "Navigation: Left", + "keyRight": "Navigation: Right", + "keyEnter": "Navigation: Enter", + "keyEscape": "Navigation: Escape" + }; + + if (navKeybinds) { + for (const prop in navMap) { + const fullPath = "general.keybinds." + prop; + if (fullPath === currentPath) + continue; + + const boundKeys = navKeybinds[prop]; + if (boundKeys && boundKeys.length !== undefined) { + for (let i = 0; i < boundKeys.length; i++) { + if (String(boundKeys[i]).trim().toLowerCase() === searchKey) { + return navMap[prop]; + } + } + } + } + } + + // 2. Check session menu power options + const sessionMenu = data.sessionMenu; + if (sessionMenu && sessionMenu.powerOptions) { + const powerOptions = sessionMenu.powerOptions; + for (let i = 0; i < powerOptions.length; i++) { + const entry = powerOptions[i]; + const fullPath = "sessionMenu.powerOptions[" + i + "].keybind"; + if (fullPath === currentPath) + continue; + + if (entry.keybind && String(entry.keybind).trim().toLowerCase() === searchKey) { + // Capitalize action name + const actionName = entry.action ? entry.action.charAt(0).toUpperCase() + entry.action.slice(1) : "Unknown"; + return "Session Menu: " + actionName; + } + } + } + + return null; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Logger.qml b/arch/.config/quickshell/noctalia-shell/Commons/Logger.qml new file mode 100644 index 0000000..4fb4b32 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Logger.qml @@ -0,0 +1,69 @@ +pragma Singleton + +import Quickshell +import qs.Commons + +Singleton { + id: root + + function _formatMessage(...args) { + var t = Time.getFormattedTimestamp(); + if (args.length > 1) { + const maxLength = 14; + var module = args.shift().substring(0, maxLength).padStart(maxLength, " "); + return `\x1b[36m[${t}]\x1b[0m \x1b[35m${module}\x1b[0m ` + args.join(" "); + } else { + return `[\x1b[36m[${t}]\x1b[0m ` + args.join(" "); + } + } + + function _getStackTrace() { + try { + throw new Error("Stack trace"); + } catch (e) { + return e.stack; + } + } + + // Debug log (only when Settings.isDebug is true) + function d(...args) { + if (Settings?.isDebug) { + var msg = _formatMessage(...args); + console.debug(msg); + } + } + + // Info log (always visible) + function i(...args) { + var msg = _formatMessage(...args); + console.info(msg); + } + + // Warning log (always visible) + function w(...args) { + var msg = _formatMessage(...args); + console.warn(msg); + } + + // Error log (always visible) + function e(...args) { + var msg = _formatMessage(...args); + console.error(msg); + } + + function callStack() { + var stack = _getStackTrace(); + Logger.i("Debug", "--------------------------"); + Logger.i("Debug", "Current call stack"); + // Split the stack into lines and log each one + var stackLines = stack.split('\n'); + for (var i = 0; i < stackLines.length; i++) { + var line = stackLines[i].trim(); // Remove leading/trailing whitespace + if (line.length > 0) { + // Only log non-empty lines + Logger.i("Debug", `- ${line}`); + } + } + Logger.i("Debug", "--------------------------"); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration27.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration27.qml new file mode 100644 index 0000000..644218d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration27.qml @@ -0,0 +1,23 @@ +import QtQuick + +QtObject { + id: root + + // Migrate from version < 27 to version 27 + // Converts settingsPanelAttachToBar boolean to settingsPanelMode string + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v27"); + + // Check rawJson for old property (adapter doesn't expose removed properties) + if (rawJson?.ui?.settingsPanelAttachToBar !== undefined) { + if (rawJson.ui.settingsPanelAttachToBar === true) { + adapter.ui.settingsPanelMode = "attached"; + } else { + adapter.ui.settingsPanelMode = "centered"; + } + logger.i("Settings", "Migrated settingsPanelAttachToBar to settingsPanelMode: " + adapter.ui.settingsPanelMode); + } + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration28.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration28.qml new file mode 100644 index 0000000..d3ef7b7 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration28.qml @@ -0,0 +1,37 @@ +import QtQuick + +QtObject { + id: root + + // Migrate TaskbarGrouped widgets to Workspace with showApplications: true + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v28"); + + // Check bar widgets in all sections + const sections = ["left", "center", "right"]; + + for (const section of sections) { + if (!rawJson?.bar?.widgets?.[section]) + continue; + + const widgets = rawJson.bar.widgets[section]; + for (let i = 0; i < widgets.length; i++) { + if (widgets[i].id === "TaskbarGrouped") { + // Convert TaskbarGrouped to Workspace with showApplications + const oldWidget = widgets[i]; + adapter.bar.widgets[section][i] = { + id: "Workspace", + showApplications: true, + labelMode: oldWidget.labelMode || "index", + hideUnoccupied: oldWidget.hideUnoccupied || false, + showLabelsOnlyWhenOccupied: oldWidget.showLabelsOnlyWhenOccupied ?? true, + colorizeIcons: oldWidget.colorizeIcons || false + }; + logger.i("Settings", "Migrated TaskbarGrouped to Workspace in " + section + " section"); + } + } + } + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration32.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration32.qml new file mode 100644 index 0000000..34ca759 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration32.qml @@ -0,0 +1,18 @@ +import QtQuick + +QtObject { + id: root + + // Migrate systemMonitor.enableNvidiaGpu to systemMonitor.enableDgpuMonitoring + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v32"); + + // Check rawJson for old property (adapter doesn't expose removed properties) + if (rawJson?.systemMonitor?.enableNvidiaGpu !== undefined) { + adapter.systemMonitor.enableDgpuMonitoring = rawJson.systemMonitor.enableNvidiaGpu; + logger.i("Settings", "Migrated systemMonitor.enableNvidiaGpu to enableDgpuMonitoring: " + adapter.systemMonitor.enableDgpuMonitoring); + } + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration33.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration33.qml new file mode 100644 index 0000000..f012fa1 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration33.qml @@ -0,0 +1,26 @@ +import QtQuick + +QtObject { + id: root + + // Migrate wallpaper.randomEnabled to wallpaper.wallpaperChangeMode + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v33"); + + if (rawJson?.wallpaper?.randomEnabled !== undefined) { + // We'll keep randomEnabled for backward compatibility but set wallpaperChangeMode + if (rawJson.wallpaper.randomEnabled === true) { + adapter.wallpaper.wallpaperChangeMode = "random"; + logger.i("Settings", "Migrated wallpaper.randomEnabled=true to wallpaperChangeMode=random"); + } else { + adapter.wallpaper.wallpaperChangeMode = "random"; + logger.i("Settings", "Set wallpaperChangeMode=random (randomEnabled was false)"); + } + } else { + adapter.wallpaper.wallpaperChangeMode = "random"; + logger.i("Settings", "Set default wallpaperChangeMode=random"); + } + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration35.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration35.qml new file mode 100644 index 0000000..3721782 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration35.qml @@ -0,0 +1,20 @@ +import QtQuick + +QtObject { + id: root + + // Migrate bar.transparent to bar.backgroundOpacity + bar.useSeparateOpacity + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v35"); + + if (rawJson?.bar?.transparent !== undefined) { + if (rawJson.bar.transparent === true) { + adapter.bar.backgroundOpacity = 0; + adapter.bar.useSeparateOpacity = true; + logger.i("Settings", "Migrated bar.transparent=true to backgroundOpacity=0, useSeparateOpacity=true"); + } + } + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration36.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration36.qml new file mode 100644 index 0000000..563b2c7 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration36.qml @@ -0,0 +1,29 @@ +import QtQuick +import Quickshell +import qs.Commons + +QtObject { + id: root + + // Clear legacy emoji usage cache to adopt new format + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v36"); + + const usagePath = Settings.cacheDir + "emoji_usage.json"; + + if (!usagePath.endsWith("emoji_usage.json")) { + logger.w("Settings", "Skipping emoji usage cleanup due to unexpected path: " + usagePath); + return true; + } + + try { + // Ensure dir exists then remove the file + Quickshell.execDetached(["sh", "-c", `mkdir -p "${Settings.cacheDir}" && rm -f -- "${usagePath}"`]); + logger.i("Settings", "Cleared legacy emoji usage file at: " + usagePath); + } catch (e) { + logger.w("Settings", "Failed to clear emoji usage cache: " + e); + } + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration37.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration37.qml new file mode 100644 index 0000000..26d64ce --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration37.qml @@ -0,0 +1,79 @@ +import QtQuick + +QtObject { + id: root + + // Rename WiFi widgets/shortcuts to Network + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v37 (WiFi โ†’ Network)"); + + let changed = false; + + function migrateArray(contextName, rawArr, adapterArr, setItem) { + if (!adapterArr) + return; + const len = adapterArr.length; + for (let i = 0; i < len; i++) { + // Prefer raw item if present, otherwise use adapter item + const item = rawArr && rawArr[i] !== undefined ? rawArr[i] : adapterArr[i]; + + if (item === undefined || item === null) + continue; + + // Case 1: simple string entries + if (typeof item === "string") { + if (item === "WiFi") { + setItem(i, "Network"); + changed = true; + logger.i("Settings", `Migrated ${contextName}[${i}] from WiFi to Network (string entry)`); + } + continue; + } + + // Case 2: object entries with id + let id = item.id !== undefined ? item.id : (item.widgetId !== undefined ? item.widgetId : undefined); + if (id === "WiFi") { + var newObj = {}; + for (var key in item) + newObj[key] = item[key]; + if (newObj.id !== undefined) + newObj.id = "Network"; + if (newObj.widgetId !== undefined && newObj.id === undefined) + newObj.widgetId = "Network"; // fallback if older schema used widgetId + setItem(i, newObj); + changed = true; + logger.i("Settings", `Migrated ${contextName}[${i}] from WiFi to Network (object entry)`); + } + } + } + + // --- Bar widgets: left/center/right + const sections = ["left", "center", "right"]; + for (const section of sections) { + const rawArr = rawJson?.bar?.widgets?.[section]; + const adapterArr = adapter?.bar?.widgets?.[section]; + migrateArray(`bar.widgets.${section}`, rawArr, adapterArr, function (i, v) { + adapter.bar.widgets[section][i] = v; + }); + } + + // --- Control Center shortcuts: left/right + const rawLeft = rawJson?.controlCenter?.shortcuts?.left; + const adLeft = adapter?.controlCenter?.shortcuts?.left; + migrateArray("controlCenter.shortcuts.left", rawLeft, adLeft, function (i, v) { + adapter.controlCenter.shortcuts.left[i] = v; + }); + + const rawRight = rawJson?.controlCenter?.shortcuts?.right; + const adRight = adapter?.controlCenter?.shortcuts?.right; + migrateArray("controlCenter.shortcuts.right", rawRight, adRight, function (i, v) { + adapter.controlCenter.shortcuts.right[i] = v; + }); + + if (!changed) { + logger.i("Settings", "No WiFi widget IDs found to migrate; leaving settings unchanged"); + } + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration38.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration38.qml new file mode 100644 index 0000000..22dce4b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration38.qml @@ -0,0 +1,27 @@ +import QtQuick +import qs.Commons + +QtObject { + function migrate(adapter, logger, rawJson) { + logger.i("Migration38", "Migrating bar margins from percentages to integers"); + + // Use rawJson to read original values, adapter to write new values (following migration patterns) + const rawVertical = rawJson?.bar?.marginVertical; + const rawHorizontal = rawJson?.bar?.marginHorizontal; + + // Only migrate if values exist and are percentages (<= 1.0) + if (rawVertical !== undefined && typeof rawVertical === 'number' && rawVertical <= 1.0) { + const marginXL = 18; // Standard value of Style.marginXL + adapter.bar.marginVertical = Math.round(rawVertical * marginXL); + logger.d("Migration38", "Converted marginVertical from " + rawVertical + " to " + adapter.bar.marginVertical + "px"); + } + + if (rawHorizontal !== undefined && typeof rawHorizontal === 'number' && rawHorizontal <= 1.0) { + const marginXL = 18; // Standard value of Style.marginXL + adapter.bar.marginHorizontal = Math.round(rawHorizontal * marginXL); + logger.d("Migration38", "Converted marginHorizontal from " + rawHorizontal + " to " + adapter.bar.marginHorizontal + "px"); + } + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration40.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration40.qml new file mode 100644 index 0000000..7cb2399 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration40.qml @@ -0,0 +1,43 @@ +import QtQuick + +QtObject { + id: root + + // List of all template IDs that existed as booleans in the old format + readonly property var templateIds: ["gtk", "qt", "kcolorscheme", "alacritty", "kitty", "ghostty", "foot", "wezterm", "fuzzel", "discord", "pywalfox", "vicinae", "walker", "code", "spicetify", "telegram", "cava", "yazi", "emacs", "niri", "hyprland", "mango", "zed", "helix", "zenBrowser"] + + function migrate(adapter, logger, rawJson) { + logger.i("Migration39", "Migrating templates from boolean format to activeTemplates array"); + + // Check if old format exists (any boolean template property) + const oldTemplates = rawJson?.templates; + if (!oldTemplates) { + logger.d("Migration39", "No templates section found, skipping migration"); + return true; + } + + // Check if already migrated (has activeTemplates array) + if (Array.isArray(oldTemplates.activeTemplates)) { + logger.d("Migration39", "Already has activeTemplates array, skipping migration"); + return true; + } + + // Build the new activeTemplates array from old boolean values + const activeTemplates = []; + for (const templateId of templateIds) { + if (oldTemplates[templateId] === true) { + activeTemplates.push({ + "id": templateId, + "enabled": true + }); + logger.d("Migration40", "Migrated enabled template: " + templateId); + } + } + + // Write the new format + adapter.templates.activeTemplates = activeTemplates; + logger.i("Migration40", "Migrated " + activeTemplates.length + " templates to new array format"); + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration42.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration42.qml new file mode 100644 index 0000000..64358c8 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration42.qml @@ -0,0 +1,28 @@ +import QtQuick + +QtObject { + id: root + + function migrate(adapter, logger, rawJson) { + logger.i("Migration42", "Migrating randomEnabled to automationEnabled"); + + const wallpaper = rawJson?.wallpaper; + if (!wallpaper) { + logger.d("Migration42", "No wallpaper section found, skipping migration"); + return true; + } + + // Check if already migrated (has automationEnabled) + if (wallpaper.automationEnabled !== undefined) { + logger.d("Migration42", "Already has automationEnabled, skipping migration"); + return true; + } + + // Migrate randomEnabled to automationEnabled + const oldValue = wallpaper.randomEnabled ?? false; + adapter.wallpaper.automationEnabled = oldValue; + logger.i("Migration42", "Migrated randomEnabled=" + oldValue + " to automationEnabled=" + oldValue); + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration43.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration43.qml new file mode 100644 index 0000000..93daf31 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration43.qml @@ -0,0 +1,29 @@ +import QtQuick + +QtObject { + id: root + + function migrate(adapter, logger, rawJson) { + logger.i("Migration43", "Migrating recursiveSearch to viewMode"); + + const wallpaper = rawJson?.wallpaper; + if (!wallpaper) { + logger.d("Migration43", "No wallpaper section found, skipping migration"); + return true; + } + + // Check if already migrated (has viewMode) + if (wallpaper.viewMode !== undefined) { + logger.d("Migration43", "Already has viewMode, skipping migration"); + return true; + } + + // Migrate recursiveSearch to viewMode + const oldValue = wallpaper.recursiveSearch ?? false; + const newValue = oldValue ? "recursive" : "single"; + adapter.wallpaper.viewMode = newValue; + logger.i("Migration43", "Migrated recursiveSearch=" + oldValue + " to viewMode=" + newValue); + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration44.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration44.qml new file mode 100644 index 0000000..a8623b7 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration44.qml @@ -0,0 +1,35 @@ +import QtQuick +import Quickshell +import qs.Commons + +QtObject { + id: root + + function migrate(adapter, logger, rawJson) { + logger.i("Migration44", "Updating PAM pam/password.conf"); + + const configDir = Settings.configDir; + const pamConfigDir = configDir + "pam"; + const pamConfigFile = pamConfigDir + "/password.conf"; + const pamConfigDirEsc = pamConfigDir.replace(/'/g, "'\\''"); + const pamConfigFileEsc = pamConfigFile.replace(/'/g, "'\\''"); + + // Ensure directory exists + Quickshell.execDetached(["mkdir", "-p", pamConfigDir]); + + // Generate the PAM config file content (updated version) + var configContent = "auth sufficient pam_fprintd.so timeout=-1\n"; + configContent += "auth sufficient /run/current-system/sw/lib/security/pam_fprintd.so timeout=-1 # for NixOS\n"; + configContent += "auth required pam_unix.so\n"; + + // Write the config file using heredoc + var script = `cat > '${pamConfigFileEsc}' << 'EOF'\n`; + script += configContent; + script += "EOF\n"; + Quickshell.execDetached(["sh", "-c", script]); + + logger.d("Migration44", "PAM config file updated at:", pamConfigFile); + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration45.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration45.qml new file mode 100644 index 0000000..25c0f8d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration45.qml @@ -0,0 +1,18 @@ +import QtQuick + +QtObject { + /** + * Migration 45: Migrate 'floating' bar setting to 'barType' + */ + function migrate(adapter, logger, rawJson) { + logger.i("Migration45", "Migrating bar settings..."); + + if (rawJson?.bar?.floating) { + adapter.bar.barType = "floating"; + } else { + adapter.bar.barType = "simple"; + } + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration46.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration46.qml new file mode 100644 index 0000000..06a5aac --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration46.qml @@ -0,0 +1,21 @@ +import QtQuick +import Quickshell +import qs.Commons + +QtObject { + id: root + + function migrate(adapter, logger, rawJson) { + logger.i("Migration46", "Removing legacy PAM configuration file"); + + const configDir = Settings.configDir; + const pamConfigDir = configDir + "pam"; + // Remove the entire pam directory if it exists + const script = `rm -rf '${pamConfigDir}'`; + Quickshell.execDetached(["sh", "-c", script]); + + logger.d("Migration46", "Cleaned up legacy PAM config"); + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration47.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration47.qml new file mode 100644 index 0000000..9eef2c0 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration47.qml @@ -0,0 +1,18 @@ +import QtQuick +import Quickshell +import qs.Commons + +QtObject { + id: root + + function migrate(adapter, logger, rawJson) { + logger.i("Migration47", "Removing network_stats.json cache"); + + const networkStatsFile = Settings.cacheDir + "network_stats.json"; + Quickshell.execDetached(["rm", "-f", networkStatsFile]); + + logger.d("Migration47", "Removed network_stats.json"); + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration48.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration48.qml new file mode 100644 index 0000000..a775e95 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration48.qml @@ -0,0 +1,22 @@ +import QtQuick + +QtObject { + id: root + + // Migrate battery thresholds from notifications to systemMonitor + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v48"); + + if (rawJson?.notifications?.batteryWarningThreshold !== undefined) { + adapter.systemMonitor.batteryWarningThreshold = rawJson.notifications.batteryWarningThreshold; + logger.i("Settings", "Migrated notifications.batteryWarningThreshold to systemMonitor: " + adapter.systemMonitor.batteryWarningThreshold); + } + + if (rawJson?.notifications?.batteryCriticalThreshold !== undefined) { + adapter.systemMonitor.batteryCriticalThreshold = rawJson.notifications.batteryCriticalThreshold; + logger.i("Settings", "Migrated notifications.batteryCriticalThreshold to systemMonitor: " + adapter.systemMonitor.batteryCriticalThreshold); + } + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration49.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration49.qml new file mode 100644 index 0000000..d65ed19 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration49.qml @@ -0,0 +1,17 @@ +import QtQuick +import Quickshell +import qs.Commons + +QtObject { + id: root + + // Remove old launcher_app_usage.json (usage tracking moved to ShellState) + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v49"); + + Quickshell.execDetached(["rm", "-f", Settings.cacheDir + "launcher_app_usage.json"]); + logger.i("Settings", "Removed old launcher_app_usage.json"); + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration50.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration50.qml new file mode 100644 index 0000000..398b266 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration50.qml @@ -0,0 +1,24 @@ +import QtQuick + +QtObject { + id: root + + // Migrate keybinds from single strings to arrays of strings + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v50"); + + const keybinds = rawJson?.general?.keybinds; + if (!keybinds) + return true; + + const keys = ["keyUp", "keyDown", "keyLeft", "keyRight", "keyEnter", "keyEscape"]; + for (const key of keys) { + if (keybinds[key] !== undefined && typeof keybinds[key] === "string") { + adapter.general.keybinds[key] = [keybinds[key]]; + logger.i("Settings", "Migrated keybinds." + key + " from string to array: [" + keybinds[key] + "]"); + } + } + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration53.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration53.qml new file mode 100644 index 0000000..cf2382d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration53.qml @@ -0,0 +1,39 @@ +import QtQuick + +QtObject { + id: root + + // Add default keybinds (1-6) to session menu power options if none are defined + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v53"); + + const powerOptions = rawJson?.sessionMenu?.powerOptions; + if (!powerOptions || !Array.isArray(powerOptions)) + return true; + + // Check if any power option has a keybind defined + const hasAnyKeybind = powerOptions.some(opt => opt.keybind && opt.keybind !== ""); + if (hasAnyKeybind) + return true; + + // No keybinds defined โ€” apply defaults matching the action order + const defaultKeybinds = { + "lock": "1", + "suspend": "2", + "hibernate": "3", + "reboot": "4", + "logout": "5", + "shutdown": "6" + }; + + for (let i = 0; i < powerOptions.length; i++) { + const action = powerOptions[i].action; + if (defaultKeybinds[action]) { + adapter.sessionMenu.powerOptions[i].keybind = defaultKeybinds[action]; + logger.i("Settings", "Set keybind '" + defaultKeybinds[action] + "' for session menu action: " + action); + } + } + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration54.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration54.qml new file mode 100644 index 0000000..367ab12 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration54.qml @@ -0,0 +1,28 @@ +import QtQuick + +QtObject { + id: root + + // Add numpad Enter as a second default keybind for keyEnter + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v54"); + + const keybinds = rawJson?.general?.keybinds; + if (!keybinds) + return true; + + const keyEnter = keybinds.keyEnter; + if (!keyEnter || !Array.isArray(keyEnter)) + return true; + + // Only add "Enter" if the first entry is "Return" and "Enter" isn't already present + if (keyEnter[0] === "Return" && !keyEnter.includes("Enter")) { + var updated = Array.from(keyEnter); + updated.splice(1, 0, "Enter"); + adapter.general.keybinds.keyEnter = updated; + logger.i("Settings", "Added 'Enter' (numpad) as second default keybind for keyEnter"); + } + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration55.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration55.qml new file mode 100644 index 0000000..0e08276 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration55.qml @@ -0,0 +1,22 @@ +import QtQuick + +QtObject { + id: root + + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v55"); + + // Check if the old setting exists + if (rawJson.controlCenter && rawJson.controlCenter.openAtMouseOnBarRightClick !== undefined) { + if (!rawJson.bar) + rawJson.bar = {}; + + rawJson.bar.rightClickFollowMouse = rawJson.controlCenter.openAtMouseOnBarRightClick; + delete rawJson.controlCenter.openAtMouseOnBarRightClick; + + logger.i("Settings", "Successfully moved openAtMouseOnBarRightClick to bar.rightClickFollowMouse"); + } + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration56.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration56.qml new file mode 100644 index 0000000..95dec53 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration56.qml @@ -0,0 +1,21 @@ +import QtQuick +import Quickshell +import qs.Commons + +QtObject { + id: root + + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v56 (Color Scheme Migration)"); + + const scriptPath = Quickshell.shellDir + "/Scripts/python/src/theming/migrate-colorschemes.py"; + const configDir = Settings.configDir; + + logger.i("Settings", `Running color scheme migration script: ${scriptPath} with configDir: ${configDir}`); + + // Run the migration script detached + Quickshell.execDetached(["python3", scriptPath, configDir]); + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration57.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration57.qml new file mode 100644 index 0000000..a8e3444 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration57.qml @@ -0,0 +1,16 @@ +import QtQuick + +QtObject { + id: root + + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v57 (cavaFrameRate -> spectrumFrameRate)"); + + if (rawJson && rawJson.audio && rawJson.audio.cavaFrameRate !== undefined) { + adapter.audio.spectrumFrameRate = rawJson.audio.cavaFrameRate; + logger.i("Settings", "Migrated cavaFrameRate:", rawJson.audio.cavaFrameRate, "-> spectrumFrameRate"); + } + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration58.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration58.qml new file mode 100644 index 0000000..32dd42f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration58.qml @@ -0,0 +1,14 @@ +import QtQuick + +QtObject { + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v58 (dock.dockType: static -> attached)"); + + if (rawJson && rawJson.dock && rawJson.dock.dockType === "static") { + adapter.dock.dockType = "attached"; + logger.i("Settings", "Migrated dock.dockType: static -> attached"); + } + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration59.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration59.qml new file mode 100644 index 0000000..89d8bba --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/Migration59.qml @@ -0,0 +1,25 @@ +import QtQuick + +QtObject { + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v59 (wallpaper.transitionType: string -> array)"); + + if (rawJson && rawJson.wallpaper && typeof rawJson.wallpaper.transitionType === "string") { + var oldValue = rawJson.wallpaper.transitionType; + var newValue; + + if (oldValue === "random") { + newValue = ["fade", "disc", "stripes", "wipe", "pixelate", "honeycomb"]; + } else if (oldValue === "none") { + newValue = []; + } else { + newValue = [oldValue]; + } + + adapter.wallpaper.transitionType = newValue; + logger.i("Settings", "Migrated wallpaper.transitionType:", oldValue, "->", JSON.stringify(newValue)); + } + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Migrations/MigrationRegistry.qml b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/MigrationRegistry.qml new file mode 100644 index 0000000..80a9571 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Migrations/MigrationRegistry.qml @@ -0,0 +1,63 @@ +pragma Singleton + +import QtQuick + +QtObject { + id: root + + // Map of version number to migration component + readonly property var migrations: ({ + 27: migration27Component, + 28: migration28Component, + 32: migration32Component, + 33: migration33Component, + 35: migration35Component, + 36: migration36Component, + 37: migration37Component, + 38: migration38Component, + 40: migration40Component, + 42: migration42Component, + 43: migration43Component, + 44: migration44Component, + 45: migration45Component, + 46: migration46Component, + 47: migration47Component, + 48: migration48Component, + 49: migration49Component, + 50: migration50Component, + 53: migration53Component, + 54: migration54Component, + 55: migration55Component, + 56: migration56Component, + 57: migration57Component, + 58: migration58Component, + 59: migration59Component + }) + + // Migration components + property Component migration27Component: Migration27 {} + property Component migration28Component: Migration28 {} + property Component migration32Component: Migration32 {} + property Component migration33Component: Migration33 {} + property Component migration35Component: Migration35 {} + property Component migration36Component: Migration36 {} + property Component migration37Component: Migration37 {} + property Component migration38Component: Migration38 {} + property Component migration40Component: Migration40 {} + property Component migration42Component: Migration42 {} + property Component migration43Component: Migration43 {} + property Component migration44Component: Migration44 {} + property Component migration45Component: Migration45 {} + property Component migration46Component: Migration46 {} + property Component migration47Component: Migration47 {} + property Component migration48Component: Migration48 {} + property Component migration49Component: Migration49 {} + property Component migration50Component: Migration50 {} + property Component migration53Component: Migration53 {} + property Component migration54Component: Migration54 {} + property Component migration55Component: Migration55 {} + property Component migration56Component: Migration56 {} + property Component migration57Component: Migration57 {} + property Component migration58Component: Migration58 {} + property Component migration59Component: Migration59 {} +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Settings.qml b/arch/.config/quickshell/noctalia-shell/Commons/Settings.qml new file mode 100644 index 0000000..d4deed9 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Settings.qml @@ -0,0 +1,1267 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import "../Helpers/QtObj2JS.js" as QtObj2JS +import qs.Commons +import qs.Commons.Migrations +import qs.Modules.OSD +import qs.Services.Noctalia +import qs.Services.UI + +Singleton { + id: root + + property bool isLoaded: false + property bool reloadSettings: false + property bool directoriesCreated: false + property bool shouldOpenSetupWizard: false + property bool isFreshInstall: false + + /* + Shell directories. + - Default config directory: ~/.config/noctalia + - Default cache directory: ~/.cache/noctalia + */ + readonly property alias data: adapter // Used to access via Settings.data.xxx.yyy + readonly property int settingsVersion: 59 + property bool isDebug: Quickshell.env("NOCTALIA_DEBUG") === "1" + readonly property string shellName: "noctalia" + readonly property string configDir: ensureTrailingSlash(Quickshell.env("NOCTALIA_CONFIG_DIR") || (Quickshell.env("XDG_CONFIG_HOME") || Quickshell.env("HOME") + "/.config") + "/" + shellName + "/") + readonly property string cacheDir: ensureTrailingSlash(Quickshell.env("NOCTALIA_CACHE_DIR") || (Quickshell.env("XDG_CACHE_HOME") || Quickshell.env("HOME") + "/.cache") + "/" + shellName + "/") + + readonly property string settingsFile: Quickshell.env("NOCTALIA_SETTINGS_FILE") || (configDir + "settings.json") + readonly property string defaultAvatar: Quickshell.env("HOME") + "/.face" + readonly property string defaultVideosDirectory: Quickshell.env("HOME") + "/Videos" + readonly property string defaultWallpapersDirectory: Quickshell.env("HOME") + "/Pictures/Wallpapers" + + signal settingsLoaded + signal settingsSaved + signal settingsReloaded + + // ----------------------------------------------------- + // ----------------------------------------------------- + // Ensure directories exist before FileView tries to read files + Component.onCompleted: { + // ensure settings dir exists + Quickshell.execDetached(["mkdir", "-p", configDir]); + Quickshell.execDetached(["mkdir", "-p", cacheDir]); + + // Mark directories as created and trigger file loading + directoriesCreated = true; + + // This should only be activated once when the settings structure has changed + // Then it should be commented out again, regular users don't need to generate + // default settings on every start + if (isDebug) { + generateDefaultSettings(); + generateWidgetDefaultSettings(); + } + + // Patch-in the local default, resolved to user's home + adapter.general.avatarImage = defaultAvatar; + adapter.wallpaper.directory = defaultWallpapersDirectory; + adapter.ui.fontDefault = Qt.application.font.family; + adapter.ui.fontFixed = "monospace"; + + // Set the adapter to the settingsFileView to trigger the real settings load + settingsFileView.adapter = adapter; + } + + // Don't write settings to disk immediately + // This avoid excessive IO when a variable changes rapidly (ex: sliders) + Timer { + id: saveTimer + running: false + interval: 500 + onTriggered: { + root.saveImmediate(); + } + } + + FileView { + id: settingsFileView + path: directoriesCreated ? settingsFile : undefined + printErrors: false + watchChanges: true + onAdapterUpdated: saveTimer.start() + + onFileChanged: { + reloadSettings = true; + reload(); + } + + // Trigger initial load when path changes from empty to actual path + onPathChanged: { + if (path !== undefined) { + reload(); + } + } + onLoaded: function () { + if (!isLoaded) { + Logger.i("Settings", "Settings loaded"); + + // Load raw JSON for migrations (adapter doesn't expose removed properties) + var rawJson = null; + try { + rawJson = JSON.parse(settingsFileView.text()); + } catch (e) { + Logger.w("Settings", "Could not parse raw JSON for migrations"); + } + + // Run versioned migrations immediately, don't move it in upgradeSettings + runVersionedMigrations(rawJson); + + // Finally, update our local settings version + adapter.settingsVersion = settingsVersion; + + // Emit the signal + root.isLoaded = true; + root.settingsLoaded(); + + upgradeSettings(); + } else { + Logger.d("Settings", "Settings reloaded from external file change"); + root.settingsReloaded(); + } + } + onLoadFailed: function (error) { + if (reloadSettings) { + reloadSettings = false; + return; + } + if (error.toString().includes("No such file") || error === 2) { + // File doesn't exist, create it with default values + root.isFreshInstall = true; + writeAdapter(); + + // We started without settings, we should open the setupWizard + root.shouldOpenSetupWizard = true; + } + } + } + + // FileView to load default settings for comparison + FileView { + id: defaultSettingsFileView + path: Quickshell.shellDir + "/Assets/settings-default.json" + printErrors: false + watchChanges: false + } + + // Cached default settings object + property var _defaultSettings: null + + // Load default settings when file is loaded + Connections { + target: defaultSettingsFileView + function onLoaded() { + try { + root._defaultSettings = JSON.parse(defaultSettingsFileView.text()); + } catch (e) { + Logger.w("Settings", "Failed to parse default settings file: " + e); + root._defaultSettings = null; + } + } + } + + JsonAdapter { + id: adapter + + property int settingsVersion: 0 + + // bar + property JsonObject bar: JsonObject { + property string barType: "simple" // "simple", "floating", "framed" + property string position: "top" // "top", "bottom", "left", or "right" + property list monitors: [] // holds bar visibility per monitor + property string density: "default" // "compact", "default", "comfortable" + property bool showOutline: false + property bool showCapsule: true + property real capsuleOpacity: 1.0 + property string capsuleColorKey: "none" + property int widgetSpacing: 6 + property int contentPadding: 2 + property real fontScale: 1.0 + property bool enableExclusionZoneInset: true + + // Bar background opacity settings + property real backgroundOpacity: 0.93 + property bool useSeparateOpacity: false + + // Floating bar settings + property int marginVertical: 4 + property int marginHorizontal: 4 + + // Framed bar settings + property int frameThickness: 8 + property int frameRadius: 12 + + // Bar outer corners (inverted/concave corners at bar edges when not floating) + property bool outerCorners: true + + // Hide bar/panels when compositor overview is active + property bool hideOnOverview: false + + // Auto-hide settings + property string displayMode: "always_visible" + property int autoHideDelay: 500 // ms before hiding after mouse leaves + property int autoShowDelay: 150 // ms before showing when mouse enters + property bool showOnWorkspaceSwitch: true // show bar briefly on workspace switch + + // Widget configuration for modular bar system + property JsonObject widgets + widgets: JsonObject { + property list left: [ + { + "id": "Launcher" + }, + { + "id": "Clock" + }, + { + "id": "SystemMonitor" + }, + { + "id": "ActiveWindow" + }, + { + "id": "MediaMini" + } + ] + property list center: [ + { + "id": "Workspace" + } + ] + property list right: [ + { + "id": "Tray" + }, + { + "id": "NotificationHistory" + }, + { + "id": "Battery" + }, + { + "id": "Volume" + }, + { + "id": "Brightness" + }, + { + "id": "ControlCenter" + } + ] + } + property string mouseWheelAction: "none" + property bool reverseScroll: false + property bool mouseWheelWrap: true + property string middleClickAction: "none" + property bool middleClickFollowMouse: false + property string middleClickCommand: "" + property string rightClickAction: "controlCenter" + property bool rightClickFollowMouse: true + property string rightClickCommand: "" + // Per-screen overrides for position and widgets + // Format: [{ "name": "HDMI-1", "position": "left" }, { "name": "DP-1", "position": "bottom", "widgets": {...} }] + property list screenOverrides: [] + } + + // general + property JsonObject general: JsonObject { + property string avatarImage: "" + property real dimmerOpacity: 0.2 + property bool showScreenCorners: false + property bool forceBlackScreenCorners: false + property real scaleRatio: 1.0 + property real radiusRatio: 1.0 + property real iRadiusRatio: 1.0 + property real boxRadiusRatio: 1.0 + property real screenRadiusRatio: 1.0 + property real animationSpeed: 1.0 + property bool animationDisabled: false + property bool compactLockScreen: false + property bool lockScreenAnimations: false + property bool lockOnSuspend: true + property bool showSessionButtonsOnLockScreen: true + property bool showHibernateOnLockScreen: false + property bool enableLockScreenMediaControls: false + property bool enableShadows: true + property bool enableBlurBehind: true + property string shadowDirection: "bottom_right" + property int shadowOffsetX: 2 + property int shadowOffsetY: 3 + property string language: "" + property bool allowPanelsOnScreenWithoutBar: true + property bool showChangelogOnStartup: true + property bool telemetryEnabled: false + property bool enableLockScreenCountdown: true + property int lockScreenCountdownDuration: 10000 + property bool autoStartAuth: false + property bool allowPasswordWithFprintd: false + property string clockStyle: "custom" + property string clockFormat: "hh\\nmm" + property bool passwordChars: false + property list lockScreenMonitors: [] // holds lock screen visibility per monitor + property real lockScreenBlur: 0.0 + property real lockScreenTint: 0.0 + property JsonObject keybinds: JsonObject { + property list keyUp: ["Up"] + property list keyDown: ["Down"] + property list keyLeft: ["Left"] + property list keyRight: ["Right"] + property list keyEnter: ["Return", "Enter"] + property list keyEscape: ["Esc"] + property list keyRemove: ["Del"] + } + property bool reverseScroll: false + property bool smoothScrollEnabled: true + } + + // ui + property JsonObject ui: JsonObject { + property string fontDefault: "" + property string fontFixed: "" + property real fontDefaultScale: 1.0 + property real fontFixedScale: 1.0 + property bool tooltipsEnabled: true + property bool scrollbarAlwaysVisible: true + property bool boxBorderEnabled: false + property real panelBackgroundOpacity: 0.93 + property bool translucentWidgets: false + property bool panelsAttachedToBar: true + property string settingsPanelMode: "attached" // "centered", "attached", "window" + property bool settingsPanelSideBarCardStyle: false + } + + // location + property JsonObject location: JsonObject { + property string name: "" + property bool weatherEnabled: true + property bool weatherShowEffects: true + property bool weatherTaliaMascotAlways: false + property bool useFahrenheit: false + property bool use12hourFormat: false + property bool showWeekNumberInCalendar: false + property bool showCalendarEvents: true + property bool showCalendarWeather: true + property bool analogClockInCalendar: false + property int firstDayOfWeek: -1 // -1 = auto (use locale), 0 = Sunday, 1 = Monday, 6 = Saturday + property bool hideWeatherTimezone: false + property bool hideWeatherCityName: false + property bool autoLocate: false + } + + // calendar + property JsonObject calendar: JsonObject { + property list cards: [ + { + "id": "calendar-header-card", + "enabled": true + }, + { + "id": "calendar-month-card", + "enabled": true + }, + { + "id": "weather-card", + "enabled": true + } + ] + } + + // wallpaper + property JsonObject wallpaper: JsonObject { + property bool enabled: true + property bool overviewEnabled: false + property string directory: "" + property list monitorDirectories: [] + property bool enableMultiMonitorDirectories: false + property bool showHiddenFiles: false + property string viewMode: "single" // "single" | "recursive" | "browse" + property bool setWallpaperOnAllMonitors: true + property bool linkLightAndDarkWallpapers: true + property string fillMode: "crop" + property color fillColor: "#000000" + property bool useSolidColor: false + property color solidColor: "#1a1a2e" + property bool automationEnabled: false + property string wallpaperChangeMode: "random" // "random" or "alphabetical" + property int randomIntervalSec: 300 // 5 min + property int transitionDuration: 1500 // 1500 ms + property list transitionType: ["fade", "disc", "stripes", "wipe", "pixelate", "honeycomb"] + property bool skipStartupTransition: false + property real transitionEdgeSmoothness: 0.05 + property string panelPosition: "follow_bar" + property bool hideWallpaperFilenames: false + property bool useOriginalImages: false + property real overviewBlur: 0.4 + property real overviewTint: 0.6 + // Wallhaven settings + property bool useWallhaven: false + property string wallhavenQuery: "" + property string wallhavenSorting: "relevance" + property string wallhavenOrder: "desc" + property string wallhavenCategories: "111" // general,anime,people + property string wallhavenPurity: "100" // sfw only + property string wallhavenRatios: "" + property string wallhavenApiKey: "" + property string wallhavenResolutionMode: "atleast" // "atleast" or "exact" + property string wallhavenResolutionWidth: "" + + property string wallhavenResolutionHeight: "" + property string sortOrder: "name" // "name", "name_desc", "date", "date_desc", "random" + property list favorites: [] + // Format: [{ "path": "...", "appearance": "light"|"dark", "colorScheme": "...", "darkMode": bool, "useWallpaperColors": bool, "generationMethod": "...", "paletteColors": [...] }] + // Legacy entries omit "appearance" and use darkMode to infer light vs dark slot. + } + + // applauncher + property JsonObject appLauncher: JsonObject { + property bool enableClipboardHistory: false + property bool autoPasteClipboard: false + property bool enableClipPreview: true + property bool clipboardWrapText: true + property bool enableClipboardSmartIcons: true + property bool enableClipboardChips: true + property string clipboardWatchTextCommand: "wl-paste --type text --watch cliphist store" + property string clipboardWatchImageCommand: "wl-paste --type image --watch cliphist store" + property string position: "center" // Position: center, top_left, top_right, bottom_left, bottom_right, bottom_center, top_center + property list pinnedApps: [] + property bool sortByMostUsed: true + property string terminalCommand: "alacritty -e" + property bool customLaunchPrefixEnabled: false + property string customLaunchPrefix: "" + // View mode: "list" or "grid" + property string viewMode: "list" + property bool showCategories: true + // Icon mode: "tabler" or "native" + property string iconMode: "tabler" + property bool showIconBackground: false + property bool enableSettingsSearch: true + property bool enableWindowsSearch: true + property bool enableSessionSearch: true + property bool ignoreMouseInput: false + property string screenshotAnnotationTool: "" + property bool overviewLayer: false + property string density: "default" // "compact", "default", "comfortable" + } + + // control center + property JsonObject controlCenter: JsonObject { + // Position: close_to_bar_button, center, top_left, top_right, bottom_left, bottom_right, bottom_center, top_center + property string position: "close_to_bar_button" + property string diskPath: "/" + property JsonObject shortcuts + shortcuts: JsonObject { + property list left: [ + { + "id": "Network" + }, + { + "id": "Bluetooth" + }, + { + "id": "WallpaperSelector" + }, + { + "id": "NoctaliaPerformance" + } + ] + property list right: [ + { + "id": "Notifications" + }, + { + "id": "PowerProfile" + }, + { + "id": "KeepAwake" + }, + { + "id": "NightLight" + } + ] + } + property list cards: [ + { + "id": "profile-card", + "enabled": true + }, + { + "id": "shortcuts-card", + "enabled": true + }, + { + "id": "audio-card", + "enabled": true + }, + { + "id": "brightness-card", + "enabled": false + }, + { + "id": "weather-card", + "enabled": true + }, + { + "id": "media-sysmon-card", + "enabled": true + } + ] + } + + // system monitor + property JsonObject systemMonitor: JsonObject { + property int cpuWarningThreshold: 80 + property int cpuCriticalThreshold: 90 + property int tempWarningThreshold: 80 + property int tempCriticalThreshold: 90 + property int gpuWarningThreshold: 80 + property int gpuCriticalThreshold: 90 + property int memWarningThreshold: 80 + property int memCriticalThreshold: 90 + property int swapWarningThreshold: 80 + property int swapCriticalThreshold: 90 + property int diskWarningThreshold: 80 + property int diskCriticalThreshold: 90 + property int diskAvailWarningThreshold: 20 + property int diskAvailCriticalThreshold: 10 + property int batteryWarningThreshold: 20 + property int batteryCriticalThreshold: 5 + property bool enableDgpuMonitoring: false // Opt-in: reading dGPU sysfs/nvidia-smi wakes it from D3cold, draining battery + property bool useCustomColors: false + property string warningColor: "" + property string criticalColor: "" + property string externalMonitor: "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor" + } + + // performance + property JsonObject noctaliaPerformance: JsonObject { + property bool disableWallpaper: true + property bool disableDesktopWidgets: true + } + + // dock + property JsonObject dock: JsonObject { + property bool enabled: true + property string position: "bottom" // "top", "bottom", "left", "right" + property string displayMode: "auto_hide" // "always_visible", "auto_hide", "exclusive" + property string dockType: "floating" // "floating", "attached" + property real backgroundOpacity: 1.0 + property real floatingRatio: 1.0 + property real size: 1 + property bool onlySameOutput: true + property list monitors: [] // holds dock visibility per monitor + property list pinnedApps: [] // Desktop entry IDs pinned to the dock (e.g., "org.kde.konsole", "firefox.desktop") + property bool colorizeIcons: false + property bool showLauncherIcon: false + property string launcherPosition: "end" // "start", "end" + property bool launcherUseDistroLogo: false + property string launcherIcon: "" + property string launcherIconColor: "none" + property bool pinnedStatic: false + property bool inactiveIndicators: false + property bool groupApps: false + property string groupContextMenuMode: "extended" // "list", "extended" + property string groupClickAction: "cycle" // "cycle", "list" + property string groupIndicatorStyle: "dots" // "number", "dots" + property double deadOpacity: 0.6 + property real animationSpeed: 1.0 // Speed multiplier for hide/show animations (0.1 = slowest, 2.0 = fastest) + property bool sitOnFrame: false + property bool showDockIndicator: false + property int indicatorThickness: 3 + property string indicatorColor: "primary" + property real indicatorOpacity: 0.6 + } + + // network + property JsonObject network: JsonObject { + property bool bluetoothRssiPollingEnabled: false // Opt-in Bluetooth RSSI polling (uses bluetoothctl) + property int bluetoothRssiPollIntervalMs: 60000 // Polling interval in milliseconds for RSSI queries + property string networkPanelView: "wifi" + property string wifiDetailsViewMode: "grid" // "grid" or "list" + property string bluetoothDetailsViewMode: "grid" // "grid" or "list" + property bool bluetoothHideUnnamedDevices: false + property bool disableDiscoverability: false + property bool bluetoothAutoConnect: true + } + + // session menu + property JsonObject sessionMenu: JsonObject { + property bool enableCountdown: true + property int countdownDuration: 10000 + property string position: "center" + property bool showHeader: true + property bool showKeybinds: true + property bool largeButtonsStyle: true + property string largeButtonsLayout: "single-row" + property list powerOptions: [ + { + "action": "lock", + "enabled": true, + "keybind": "1" + }, + { + "action": "suspend", + "enabled": true, + "keybind": "2" + }, + { + "action": "hibernate", + "enabled": true, + "keybind": "3" + }, + { + "action": "reboot", + "enabled": true, + "keybind": "4" + }, + { + "action": "logout", + "enabled": true, + "keybind": "5" + }, + { + "action": "shutdown", + "enabled": true, + "keybind": "6" + }, + { + "action": "rebootToUefi", + "enabled": true, + "keybind": "7" + } + ] + } + + // notifications + property JsonObject notifications: JsonObject { + property bool enabled: true + property bool enableMarkdown: false + property string density: "default" // "default", "compact" + property list monitors: [] // holds notifications visibility per monitor + property string location: "top_right" + property bool overlayLayer: true + property real backgroundOpacity: 1.0 + property bool respectExpireTimeout: false + property int lowUrgencyDuration: 3 + property int normalUrgencyDuration: 8 + property int criticalUrgencyDuration: 15 + property bool clearDismissed: true + property JsonObject saveToHistory: JsonObject { + property bool low: true + property bool normal: true + property bool critical: true + } + property JsonObject sounds: JsonObject { + property bool enabled: false + property real volume: 0.5 + property bool separateSounds: false + property string criticalSoundFile: "" + property string normalSoundFile: "" + property string lowSoundFile: "" + property string excludedApps: "discord,firefox,chrome,chromium,edge" + } + property bool enableMediaToast: false + property bool enableKeyboardLayoutToast: true + property bool enableBatteryToast: true + } + + // on-screen display + property JsonObject osd: JsonObject { + property bool enabled: true + property string location: "top_right" + property int autoHideMs: 2000 + property bool overlayLayer: true + property real backgroundOpacity: 1.0 + property list enabledTypes: [OSD.Type.Volume, OSD.Type.InputVolume, OSD.Type.Brightness] + property list monitors: [] // holds osd visibility per monitor + } + + // audio + property JsonObject audio: JsonObject { + property int volumeStep: 5 + property bool volumeOverdrive: false + property int spectrumFrameRate: 30 + property string visualizerType: "linear" + property bool spectrumMirrored: true + property list mprisBlacklist: [] + property string preferredPlayer: "" + property bool volumeFeedback: false + property string volumeFeedbackSoundFile: "" + } + + // brightness + property JsonObject brightness: JsonObject { + property int brightnessStep: 5 + property bool enforceMinimum: true + property bool enableDdcSupport: false + property list backlightDeviceMappings: [] + // Format: [{ "output": "eDP-1", "device": "/sys/class/backlight/intel_backlight" }] + } + + property JsonObject colorSchemes: JsonObject { + property bool useWallpaperColors: false + property string predefinedScheme: "Noctalia (default)" + property bool darkMode: true + property string schedulingMode: "off" + property string manualSunrise: "06:30" + property string manualSunset: "18:30" + property string generationMethod: "tonal-spot" + property string monitorForColors: "" + property bool syncGsettings: true + } + + // templates toggles + property JsonObject templates: JsonObject { + property list activeTemplates: [] + // Format: [{ "id": "gtk", "enabled": true }, { "id": "qt", "enabled": true }, ...] + property bool enableUserTheming: false + } + + // night light + property JsonObject nightLight: JsonObject { + property bool enabled: false + property bool forced: false + property bool autoSchedule: true + property string nightTemp: "4000" + property string dayTemp: "6500" + property string manualSunrise: "06:30" + property string manualSunset: "18:30" + } + + // hooks + property JsonObject hooks: JsonObject { + property bool enabled: false + property string wallpaperChange: "" + property string darkModeChange: "" + property string screenLock: "" + property string screenUnlock: "" + property string performanceModeEnabled: "" + property string performanceModeDisabled: "" + property string startup: "" + property string session: "" + property string colorGeneration: "" + } + + // plugins + property JsonObject plugins: JsonObject { + property bool autoUpdate: false + property bool notifyUpdates: true + } + + // idle management + property JsonObject idle: JsonObject { + property bool enabled: false + property int screenOffTimeout: 600 // seconds, 0 = disabled + property int lockTimeout: 660 // seconds, 0 = disabled + property int suspendTimeout: 1800 // seconds, 0 = disabled + property int fadeDuration: 5 // seconds of fade-to-black before action fires + property string screenOffCommand: "" + property string lockCommand: "" + property string suspendCommand: "" + property string resumeScreenOffCommand: "" + property string resumeLockCommand: "" + property string resumeSuspendCommand: "" + property string customCommands: "[]" // JSON array of {timeout, command, resumeCommand} + } + + // desktop widgets + property JsonObject desktopWidgets: JsonObject { + property bool enabled: false + property bool overviewEnabled: true + property bool gridSnap: false + property bool gridSnapScale: false + property list monitorWidgets: [] + // Format: [{ "name": "DP-1", "widgets": [...] }, { "name": "HDMI-1", "widgets": [...] }] + } + } + + // ----------------------------------------------------- + // Preprocess paths by adding trailing "/" + function ensureTrailingSlash(path) { + return path.endsWith("/") ? path : path + "/"; + } + + // ----------------------------------------------------- + // Preprocess paths by expanding "~" to user's home directory + function preprocessPath(path) { + if (typeof path !== "string" || path === "") { + return path; + } + + // Expand "~" to user's home directory + if (path.startsWith("~/")) { + return Quickshell.env("HOME") + path.substring(1); + } else if (path === "~") { + return Quickshell.env("HOME"); + } + + return path; + } + + // ----------------------------------------------------- + // Get default value for a setting path (e.g., "general.scaleRatio" or "bar.position") + // Returns undefined if not found + function getDefaultValue(path) { + if (!root._defaultSettings) { + return undefined; + } + + var parts = path.split("."); + var current = root._defaultSettings; + + for (var i = 0; i < parts.length; i++) { + if (current === undefined || current === null) { + return undefined; + } + current = current[parts[i]]; + } + + return current; + } + + // ----------------------------------------------------- + // Compare current value with default value + // Returns true if values differ, false if they match or default is not found + function isValueChanged(path, currentValue) { + var defaultValue = getDefaultValue(path); + if (defaultValue === undefined) { + return false; // Can't compare if default not found + } + + // Deep comparison for objects and arrays + if (typeof currentValue === "object" && typeof defaultValue === "object") { + return JSON.stringify(currentValue) !== JSON.stringify(defaultValue); + } + + // Simple comparison for primitives + return currentValue !== defaultValue; + } + + // ----------------------------------------------------- + // Format default value for tooltip display + // Returns a human-readable string representation of the default value + function formatDefaultValueForTooltip(path) { + var defaultValue = getDefaultValue(path); + if (defaultValue === undefined) { + return ""; + } + + // Format based on type + if (typeof defaultValue === "boolean") { + return defaultValue ? "true" : "false"; + } else if (typeof defaultValue === "number") { + return defaultValue.toString(); + } else if (typeof defaultValue === "string") { + return defaultValue === "" ? "(empty)" : defaultValue; + } else if (Array.isArray(defaultValue)) { + return defaultValue.length === 0 ? "(empty)" : "[" + defaultValue.length + " items]"; + } else if (typeof defaultValue === "object") { + return "(object)"; + } + + return String(defaultValue); + } + + // ----------------------------------------------------- + // Helper to find a screen override entry by name in the array + // Format: [{ "name": "HDMI-A-1", "position": "left" }, ...] + // Note: QML's list is not a true JS array, so we check for .length instead of Array.isArray() + function _findScreenOverride(screenName) { + var overrides = data.bar.screenOverrides; + if (!screenName || !overrides || overrides.length === undefined) { + return null; + } + for (var i = 0; i < overrides.length; i++) { + if (overrides[i] && overrides[i].name === screenName) { + return overrides[i]; + } + } + return null; + } + + // Helper to find index of a screen override entry + function _findScreenOverrideIndex(screenName) { + var overrides = data.bar.screenOverrides; + if (!screenName || !overrides || overrides.length === undefined) { + return -1; + } + for (var i = 0; i < overrides.length; i++) { + if (overrides[i] && overrides[i].name === screenName) { + return i; + } + } + return -1; + } + + // ----------------------------------------------------- + // Check if a screen's overrides are enabled + // Returns true if enabled flag is true or undefined (backward compat) + // Returns false only if enabled is explicitly false + function isScreenOverrideEnabled(screenName) { + var override = _findScreenOverride(screenName); + if (!override) { + return false; + } + return override.enabled !== false; + } + + // ----------------------------------------------------- + // Get effective bar position for a screen (with inheritance) + // If the screen has a position override and overrides are enabled, use it; otherwise use global default + function getBarPositionForScreen(screenName) { + var override = _findScreenOverride(screenName); + if (override && override.enabled !== false && override.position !== undefined) { + return override.position; + } + return data.bar.position || "top"; + } + + // ----------------------------------------------------- + // Get effective bar widgets for a screen (with inheritance) + // If the screen has widget overrides and overrides are enabled, use them; otherwise use global defaults + function getBarWidgetsForScreen(screenName) { + var override = _findScreenOverride(screenName); + if (override && override.enabled !== false && override.widgets !== undefined) { + return override.widgets; + } + return data.bar.widgets; + } + + // ----------------------------------------------------- + // Get effective bar density for a screen (with inheritance) + // If the screen has a density override and overrides are enabled, use it; otherwise use global default + function getBarDensityForScreen(screenName) { + var override = _findScreenOverride(screenName); + if (override && override.enabled !== false && override.density !== undefined) { + return override.density; + } + return data.bar.density || "default"; + } + + // ----------------------------------------------------- + // Get effective bar display mode for a screen (with inheritance) + // If the screen has a displayMode override and overrides are enabled, use it; otherwise use global default + function getBarDisplayModeForScreen(screenName) { + var override = _findScreenOverride(screenName); + if (override && override.enabled !== false && override.displayMode !== undefined) { + return override.displayMode; + } + return data.bar.displayMode || "always_visible"; + } + + // ----------------------------------------------------- + // Check if a screen has any overrides, optionally for a specific property + function hasScreenOverride(screenName, property) { + var override = _findScreenOverride(screenName); + if (!override) { + return false; + } + if (property) { + return override[property] !== undefined; + } + // Check if screen has any override property (besides "name") + var keys = Object.keys(override); + return keys.length > 1 || (keys.length === 1 && keys[0] !== "name"); + } + + // ----------------------------------------------------- + // Get the screen override entry directly (for in-place modifications) + // Returns the actual entry object from the array, not a copy + function getScreenOverrideEntry(screenName) { + return _findScreenOverride(screenName); + } + + // ----------------------------------------------------- + // Set a per-screen override + function setScreenOverride(screenName, property, value) { + if (!screenName) + return; + + var overrides = JSON.parse(JSON.stringify(data.bar.screenOverrides || [])); + if (overrides.length === undefined) { + overrides = []; + } + + var index = -1; + for (var i = 0; i < overrides.length; i++) { + if (overrides[i] && overrides[i].name === screenName) { + index = i; + break; + } + } + + if (index === -1) { + // Create new entry + var newEntry = { + "name": screenName + }; + newEntry[property] = value; + overrides.push(newEntry); + } else { + // Update existing entry + overrides[index][property] = value; + } + data.bar.screenOverrides = overrides; + } + + // ----------------------------------------------------- + // Clear a per-screen override (revert to global default) + // If property is null, clears all overrides for that screen + function clearScreenOverride(screenName, property) { + if (!screenName) + return; + + var overrides = data.bar.screenOverrides; + if (!overrides || overrides.length === undefined) { + return; + } + + overrides = JSON.parse(JSON.stringify(overrides)); + + var index = -1; + for (var i = 0; i < overrides.length; i++) { + if (overrides[i] && overrides[i].name === screenName) { + index = i; + break; + } + } + + if (index === -1) { + return; + } + + if (property) { + delete overrides[index][property]; + // Remove screen entry if only "name" remains + var keys = Object.keys(overrides[index]); + if (keys.length <= 1 && (keys.length === 0 || keys[0] === "name")) { + overrides.splice(index, 1); + } + } else { + overrides.splice(index, 1); + } + data.bar.screenOverrides = overrides; + } + + // ----------------------------------------------------- + // Public function to trigger immediate settings saving + function saveImmediate() { + settingsFileView.writeAdapter(); + root.settingsSaved(); // Emit signal after saving + } + + // ----------------------------------------------------- + // Generate default settings: for reference only, not used by the shell + function generateDefaultSettings() { + try { + Logger.d("Settings", "Generating settings-default.json"); + + // Prepare a clean JSON + var plainAdapter = QtObj2JS.qtObjectToPlainObject(adapter); + var jsonData = JSON.stringify(plainAdapter, null, 2); + + var defaultPath = Quickshell.shellDir + "/Assets/settings-default.json"; + + Quickshell.execDetached(["sh", "-c", `cat > "${defaultPath}" << 'NOCTALIA_EOF'\n${jsonData}\nNOCTALIA_EOF`]); + } catch (error) { + Logger.e("Settings", "Failed to generate default settings file: " + error); + } + } + + // ----------------------------------------------------- + // Generate default widget settings: for reference only, not used by the shell + function generateWidgetDefaultSettings() { + try { + Logger.d("Settings", "Generating settings-widgets-default.json"); + + var output = { + "bar": QtObj2JS.qtObjectToPlainObject(BarWidgetRegistry.widgetMetadata), + "controlCenter": QtObj2JS.qtObjectToPlainObject(ControlCenterWidgetRegistry.widgetMetadata), + "desktop": QtObj2JS.qtObjectToPlainObject(DesktopWidgetRegistry.widgetMetadata) + }; + var jsonData = JSON.stringify(output, null, 2); + + var defaultPath = Quickshell.shellDir + "/Assets/settings-widgets-default.json"; + + Quickshell.execDetached(["sh", "-c", `cat > "${defaultPath}" << 'NOCTALIA_EOF'\n${jsonData}\nNOCTALIA_EOF`]); + } catch (error) { + Logger.e("Settings", "Failed to generate widget default settings file: " + error); + } + } + + // ----------------------------------------------------- + // Run versioned migrations using MigrationRegistry + // rawJson is the parsed JSON file content (before adapter filtering) + function runVersionedMigrations(rawJson) { + // Skip migrations on fresh installs (no prior settings file) + if (!rawJson || root.isFreshInstall) { + Logger.i("Settings", "Fresh install detected, skipping migrations"); + return; + } + + const currentVersion = adapter.settingsVersion; + const migrations = MigrationRegistry.migrations; + + Logger.i("Settings", "adapter.settingsVersion:", adapter.settingsVersion); + + // Get all migration versions and sort them + const versions = Object.keys(migrations).map(v => parseInt(v)).sort((a, b) => a - b); + + // Run migrations in order for versions newer than current + for (var i = 0; i < versions.length; i++) { + const version = versions[i]; + + if (currentVersion < version) { + // Create migration instance and run it + const migrationComponent = migrations[version]; + const migration = migrationComponent.createObject(root); + + if (migration && typeof migration.migrate === "function") { + const success = migration.migrate(adapter, Logger, rawJson); + if (!success) { + Logger.e("Settings", "Migration to v" + version + " failed"); + } + } else { + Logger.e("Settings", "Invalid migration for v" + version); + } + + // Clean up migration instance + if (migration) { + migration.destroy(); + } + } + } + } + + // ----------------------------------------------------- + // If the settings structure has changed, ensure + // backward compatibility by upgrading the settings + function upgradeSettings() { + // Wait for PluginService to finish loading plugins first + // This prevents deleting plugin widgets during reload before plugins are registered + if (!PluginService.initialized || !PluginService.pluginsFullyLoaded) { + Logger.d("Settings", "Plugins not fully loaded yet, deferring upgrade"); + Qt.callLater(upgradeSettings); + return; + } + + // Wait for BarWidgetRegistry to be ready + if (!BarWidgetRegistry.widgets || Object.keys(BarWidgetRegistry.widgets).length === 0) { + Logger.d("Settings", "BarWidgetRegistry not ready, deferring upgrade"); + Qt.callLater(upgradeSettings); + return; + } + + // ----------------- + const sections = ["left", "center", "right"]; + + // 1. remove any non existing bar widget type + var removedWidget = false; + for (var s = 0; s < sections.length; s++) { + const sectionName = sections[s]; + const widgets = adapter.bar.widgets[sectionName]; + // Iterate backward through the widgets array, so it does not break when removing a widget + for (var i = widgets.length - 1; i >= 0; i--) { + var widget = widgets[i]; + if (!BarWidgetRegistry.hasWidget(widget.id)) { + Logger.w(`Settings`, `!!! Deleted invalid bar widget ${widget.id} !!!`); + widgets.splice(i, 1); + removedWidget = true; + } + } + } + + // ----------------- + // 2. remove any non existing control center widget type + const ccSections = ["left", "right"]; + for (var s = 0; s < ccSections.length; s++) { + const sectionName = ccSections[s]; + const shortcuts = adapter.controlCenter.shortcuts[sectionName]; + for (var i = shortcuts.length - 1; i >= 0; i--) { + var shortcut = shortcuts[i]; + if (!ControlCenterWidgetRegistry.hasWidget(shortcut.id)) { + Logger.w(`Settings`, `!!! Deleted invalid control center widget ${shortcut.id} !!!`); + shortcuts.splice(i, 1); + removedWidget = true; + } + } + } + + // ----------------- + // 3. remove any non existing desktop widget type + const monitorWidgets = adapter.desktopWidgets.monitorWidgets; + for (var m = 0; m < monitorWidgets.length; m++) { + const monitor = monitorWidgets[m]; + if (!monitor.widgets) + continue; + for (var i = monitor.widgets.length - 1; i >= 0; i--) { + var desktopWidget = monitor.widgets[i]; + if (!DesktopWidgetRegistry.hasWidget(desktopWidget.id)) { + Logger.w(`Settings`, `!!! Deleted invalid desktop widget ${desktopWidget.id} !!!`); + monitor.widgets.splice(i, 1); + removedWidget = true; + } + } + } + + // ----------------- + // 4. upgrade user widget settings + for (var s = 0; s < sections.length; s++) { + const sectionName = sections[s]; + for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) { + var widget = adapter.bar.widgets[sectionName][i]; + + // Check if widget registry supports user settings, if it does not, then there is nothing to do + if (BarWidgetRegistry.widgetMetadata[widget.id] === undefined) { + continue; + } + + if (upgradeWidget(widget)) { + Logger.d("Settings", `Upgraded ${widget.id} widget:`, JSON.stringify(widget)); + } + } + } + } + + // ----------------------------------------------------- + // Function to clean up deprecated user/custom bar widgets settings + function upgradeWidget(widget) { + // Backup the widget definition before altering + const widgetBefore = JSON.stringify(widget); + + // Get all existing custom settings keys + const keys = Object.keys(BarWidgetRegistry.widgetMetadata[widget.id]); + + // Delete deprecated user settings from the wiget + for (const k of Object.keys(widget)) { + if (k === "id") { + continue; + } + if (!keys.includes(k)) { + delete widget[k]; + } + } + + // Inject missing default setting (metaData) from BarWidgetRegistry + for (var i = 0; i < keys.length; i++) { + const k = keys[i]; + if (k === "id") { + continue; + } + + if (widget[k] === undefined) { + widget[k] = BarWidgetRegistry.widgetMetadata[widget.id][k]; + } + } + + // Compare settings, to detect if something has been upgraded + const widgetAfter = JSON.stringify(widget); + return (widgetAfter !== widgetBefore); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/ShellState.qml b/arch/.config/quickshell/noctalia-shell/Commons/ShellState.qml new file mode 100644 index 0000000..187443b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/ShellState.qml @@ -0,0 +1,287 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import "../Helpers/QtObj2JS.js" as QtObj2JS +import qs.Services.Power +import qs.Services.System +import qs.Services.UI + +// Centralized shell state management for small cache files +Singleton { + id: root + + property string stateFile: "" + property bool isLoaded: false + + // State properties for different services + readonly property alias data: adapter + + // Signals for state changes + signal displayStateChanged + signal notificationsStateChanged + signal changelogStateChanged + signal colorSchemesListChanged + + Component.onCompleted: { + // Setup state file path (needs Settings to be available) + Qt.callLater(() => { + if (typeof Settings !== 'undefined' && Settings.cacheDir) { + stateFile = Settings.cacheDir + "shell-state.json"; + stateFileView.path = stateFile; + } + }); + } + + // FileView for shell state + FileView { + id: stateFileView + printErrors: false + watchChanges: false + + adapter: JsonAdapter { + id: adapter + + // CompositorService: display scales + property var display: ({}) + + // NotificationService: notification state + property var notificationsState: ({ + lastSeenTs: 0 + }) + + // UpdateService: changelog state + property var changelogState: ({ + lastSeenVersion: "" + }) + + // SchemeDownloader: color schemes list + property var colorSchemesList: ({ + schemes: [], + timestamp: 0 + }) + + // UI state: settings panel, etc. + property var ui: ({ + settingsSidebarExpanded: true + }) + + // Telemetry state + property var telemetry: ({ + instanceId: "" + }) + + // Launcher app usage counts + property var launcherUsage: ({}) + } + + onLoaded: { + root.isLoaded = true; + Logger.d("ShellState", "Loaded state file"); + } + + onLoadFailed: error => { + if (error === 2) { + // File doesn't exist, will be created on first write + root.isLoaded = true; + Logger.d("ShellState", "State file doesn't exist, will create on first write"); + } else { + Logger.e("ShellState", "Failed to load state file:", error); + root.isLoaded = true; + } + } + } + + // Launcher usage + function getLauncherUsageCount(key) { + const m = adapter.launcherUsage; + if (!m) + return 0; + const v = m[key]; + return typeof v === 'number' && isFinite(v) ? v : 0; + } + + function recordLauncherUsage(key) { + let counts = Object.assign({}, adapter.launcherUsage || {}); + counts[key] = getLauncherUsageCount(key) + 1; + adapter.launcherUsage = counts; + save(); + } + + // Migrate usage from one key to another, merging counts in a single save + function migrateLauncherUsage(fromKey, toKey) { + let counts = Object.assign({}, adapter.launcherUsage || {}); + const fromCount = typeof counts[fromKey] === 'number' && isFinite(counts[fromKey]) ? counts[fromKey] : 0; + const toCount = typeof counts[toKey] === 'number' && isFinite(counts[toKey]) ? counts[toKey] : 0; + counts[toKey] = toCount + fromCount; + delete counts[fromKey]; + adapter.launcherUsage = counts; + save(); + } + + // Debounced save timer + Timer { + id: saveTimer + interval: 500 + onTriggered: performSave() + } + + property bool saveQueued: false + + function save() { + saveQueued = true; + saveTimer.restart(); + } + + function performSave() { + if (!saveQueued || !stateFile) { + return; + } + + saveQueued = false; + + try { + // Ensure cache directory exists + Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir]); + + Qt.callLater(() => { + try { + stateFileView.writeAdapter(); + Logger.d("ShellState", "Saved state file"); + } catch (writeError) { + Logger.e("ShellState", "Failed to write state file:", writeError); + } + }); + } catch (error) { + Logger.e("ShellState", "Failed to save state:", error); + } + } + + // Convenience functions for each service + + // Display state (CompositorService) + function setDisplay(displayData) { + adapter.display = displayData; + save(); + displayStateChanged(); + } + + function getDisplay() { + return adapter.display || {}; + } + + // Notifications state (NotificationService) + function setNotificationsState(stateData) { + adapter.notificationsState = stateData; + save(); + notificationsStateChanged(); + } + + function getNotificationsState() { + return adapter.notificationsState || { + lastSeenTs: 0 + }; + } + + // Changelog state (UpdateService) + function setChangelogState(stateData) { + adapter.changelogState = stateData; + save(); + changelogStateChanged(); + } + + function getChangelogState() { + return adapter.changelogState || { + lastSeenVersion: "" + }; + } + + // Color schemes list (SchemeDownloader) + function setColorSchemesList(listData) { + adapter.colorSchemesList = listData; + save(); + colorSchemesListChanged(); + } + + function getColorSchemesList() { + return adapter.colorSchemesList || { + schemes: [], + timestamp: 0 + }; + } + + // UI state + function setUiState(stateData) { + adapter.ui = stateData; + save(); + } + + function getUiState() { + return adapter.ui || { + settingsSidebarExpanded: true + }; + } + + function setSettingsSidebarExpanded(expanded) { + let uiState = getUiState(); + uiState.settingsSidebarExpanded = expanded; + setUiState(uiState); + } + + function getSettingsSidebarExpanded() { + return getUiState().settingsSidebarExpanded !== false; // default to true + } + + // Telemetry state + function setTelemetryState(stateData) { + adapter.telemetry = stateData; + save(); + } + + function getTelemetryState() { + return adapter.telemetry || { + instanceId: "" + }; + } + + function getTelemetryInstanceId() { + return getTelemetryState().instanceId || ""; + } + + function setTelemetryInstanceId(instanceId) { + let state = getTelemetryState(); + state.instanceId = instanceId; + setTelemetryState(state); + } + + // ----------------------------------------------------- + function buildStateSnapshot() { + try { + const settingsData = QtObj2JS.qtObjectToPlainObject(Settings.data); + const shellStateData = ShellState?.data ? QtObj2JS.qtObjectToPlainObject(ShellState.data) || {} : {}; + + return { + settings: settingsData, + state: { + doNotDisturb: NotificationService.doNotDisturb, + noctaliaPerformanceMode: PowerProfileService.noctaliaPerformanceMode, + barVisible: BarService.isVisible, + openedPanel: PanelService.openedPanel?.objectName || "", + lockScreenActive: PanelService.lockScreen?.active || false, + wallpapers: WallpaperService.getWallpapersEffectiveMap(), + desktopWidgetsEditMode: DesktopWidgetRegistry.editMode || false, + // ------------- + display: shellStateData.display || {}, + notificationsState: shellStateData.notificationsState || {}, + changelogState: shellStateData.changelogState || {}, + colorSchemesList: shellStateData.colorSchemesList || {}, + ui: shellStateData.ui || {} + } + }; + } catch (error) { + Logger.e("Settings", "Failed to build state snapshot:", error); + return null; + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Style.qml b/arch/.config/quickshell/noctalia-shell/Commons/Style.qml new file mode 100644 index 0000000..1910401 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Style.qml @@ -0,0 +1,247 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Services.Power + +Singleton { + id: root + + // Font size + readonly property real fontSizeXXS: 8 + readonly property real fontSizeXS: 9 + readonly property real fontSizeS: 10 + readonly property real fontSizeM: 11 + readonly property real fontSizeL: 13 + readonly property real fontSizeXL: 16 + readonly property real fontSizeXXL: 18 + readonly property real fontSizeXXXL: 24 + + // Font weight + readonly property int fontWeightRegular: 400 + readonly property int fontWeightMedium: 500 + readonly property int fontWeightSemiBold: 600 + readonly property int fontWeightBold: 700 + + // Container Radii: major layout sections (sidebars, cards, content panels) + readonly property int radiusXXXS: Math.round(3 * Settings.data.general.radiusRatio) + readonly property int radiusXXS: Math.round(4 * Settings.data.general.radiusRatio) + readonly property int radiusXS: Math.round(8 * Settings.data.general.radiusRatio) + readonly property int radiusS: Math.round(12 * Settings.data.general.radiusRatio) + readonly property int radiusM: Math.round(16 * Settings.data.general.radiusRatio) + readonly property int radiusL: Math.round(20 * Settings.data.general.radiusRatio) + + // Input radii: interactive elements (buttons, toggles, text fields) + readonly property int iRadiusXXXS: Math.round(3 * Settings.data.general.iRadiusRatio) + readonly property int iRadiusXXS: Math.round(4 * Settings.data.general.iRadiusRatio) + readonly property int iRadiusXS: Math.round(8 * Settings.data.general.iRadiusRatio) + readonly property int iRadiusS: Math.round(12 * Settings.data.general.iRadiusRatio) + readonly property int iRadiusM: Math.round(16 * Settings.data.general.iRadiusRatio) + readonly property int iRadiusL: Math.round(20 * Settings.data.general.iRadiusRatio) + + readonly property int screenRadius: Math.round(20 * Settings.data.general.screenRadiusRatio) + + // Border + readonly property int borderS: Math.max(1, Math.round(1 * uiScaleRatio)) + readonly property int borderM: Math.max(1, Math.round(2 * uiScaleRatio)) + readonly property int borderL: Math.max(1, Math.round(3 * uiScaleRatio)) + + // Margins (for margins and spacing) + readonly property int marginXXXS: Math.round(1 * uiScaleRatio) + readonly property int marginXXS: Math.round(2 * uiScaleRatio) + readonly property int marginXS: Math.round(4 * uiScaleRatio) + readonly property int marginS: Math.round(6 * uiScaleRatio) + readonly property int marginM: Math.round(9 * uiScaleRatio) + readonly property int marginL: Math.round(13 * uiScaleRatio) + readonly property int marginXL: Math.round(18 * uiScaleRatio) + + // Double margins, for proper container sizing only (e.g. height: id.implicitHeight + Style.margin2M) + readonly property int margin2XXXS: marginXXXS * 2 + readonly property int margin2XXS: marginXXS * 2 + readonly property int margin2XS: marginXS * 2 + readonly property int margin2S: marginS * 2 + readonly property int margin2M: marginM * 2 + readonly property int margin2L: marginL * 2 + readonly property int margin2XL: marginXL * 2 + + // Opacity + readonly property real opacityNone: 0.0 + readonly property real opacityLight: 0.25 + readonly property real opacityMedium: 0.5 + readonly property real opacityHeavy: 0.75 + readonly property real opacityAlmost: 0.95 + readonly property real opacityFull: 1.0 + + // Shadows + readonly property real shadowOpacity: 0.85 + readonly property real shadowBlur: 1.0 + readonly property int shadowBlurMax: 22 + readonly property real shadowHorizontalOffset: Settings.data.general.shadowOffsetX + readonly property real shadowVerticalOffset: Settings.data.general.shadowOffsetY + + // Animation duration (ms) + readonly property int animationFaster: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(75 / Settings.data.general.animationSpeed) + readonly property int animationFast: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(150 / Settings.data.general.animationSpeed) + readonly property int animationNormal: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(300 / Settings.data.general.animationSpeed) + readonly property int animationSlow: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(450 / Settings.data.general.animationSpeed) + readonly property int animationSlowest: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(750 / Settings.data.general.animationSpeed) + + // Delays + readonly property int tooltipDelay: 300 + readonly property int tooltipDelayLong: 1200 + readonly property int pillDelay: 500 + + // Widgets base size + readonly property real baseWidgetSize: 33 + readonly property real sliderWidth: 200 + + readonly property real uiScaleRatio: Settings.data.general.scaleRatio + + // Bar Height + readonly property real barHeight: { + let h; + switch (Settings.data.bar.density) { + case "mini": + h = (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 23 : 21; + break; + case "compact": + h = (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 27 : 25; + break; + case "comfortable": + h = (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 39 : 37; + break; + case "spacious": + h = (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 49 : 47; + break; + default: + case "default": + h = (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 33 : 31; + } + return toOdd(h); + } + + // Capsule Height + // Note: capsule must always be smaller than barHeight to account for border rendering + // Qt Quick Rectangle borders are drawn centered on edges (half inside, half outside) + readonly property real capsuleHeight: { + let h; + switch (Settings.data.bar.density) { + case "mini": + h = Math.round(barHeight * 0.90); + break; + case "compact": + h = Math.round(barHeight * 0.85); + break; + case "comfortable": + h = Math.round(barHeight * 0.75); + break; + case "spacious": + h = Math.round(barHeight * 0.65); + break; + default: + h = Math.round(barHeight * 0.82); + break; + } + return toOdd(h); + } + + // The base/default font size for all texts in the bar + readonly property real _barBaseFontSize: Math.max(1, (Style.barHeight / Style.capsuleHeight) * Style.fontSizeXXS) + readonly property real barFontSize: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? _barBaseFontSize * 0.9 * Settings.data.bar.fontScale : _barBaseFontSize * Settings.data.bar.fontScale + + readonly property color capsuleColor: Settings.data.bar.showCapsule ? Qt.alpha(Settings.data.bar.capsuleColorKey !== "none" ? Color.resolveColorKey(Settings.data.bar.capsuleColorKey) : Color.mSurfaceVariant, Settings.data.bar.capsuleOpacity) : "transparent" + + readonly property color capsuleBorderColor: Settings.data.bar.showOutline ? Color.mPrimary : "transparent" + readonly property int capsuleBorderWidth: Settings.data.bar.showOutline ? Style.borderS : 0 + + readonly property color boxBorderColor: Settings.data.ui.boxBorderEnabled ? Color.mOutline : "transparent" + + // Pixel-perfect utility for centering content without subpixel positioning + function pixelAlignCenter(containerSize, contentSize) { + return Math.round((containerSize - contentSize) / 2); + } + + // Ensures a number is always odd (rounds down to nearest odd) + function toOdd(n) { + return Math.floor(n / 2) * 2 + 1; + } + + // Ensures a number is always even (rounds down to nearest even) + function toEven(n) { + return Math.floor(n / 2) * 2; + } + + // Get bar height for a specific density and orientation + function getBarHeightForDensity(density, isVertical) { + let h; + switch (density) { + case "mini": + h = isVertical ? 23 : 21; + break; + case "compact": + h = isVertical ? 27 : 25; + break; + case "comfortable": + h = isVertical ? 39 : 37; + break; + case "spacious": + h = isVertical ? 49 : 47; + break; + default: + case "default": + h = isVertical ? 33 : 31; + } + return toOdd(h); + } + + // Get capsule height for a specific density and bar height + function getCapsuleHeightForDensity(density, barHeight) { + let h; + switch (density) { + case "mini": + h = Math.round(barHeight * 0.90); + break; + case "compact": + h = Math.round(barHeight * 0.85); + break; + case "comfortable": + h = Math.round(barHeight * 0.75); + break; + case "spacious": + h = Math.round(barHeight * 0.65); + break; + default: + h = Math.round(barHeight * 0.82); + break; + } + return toOdd(h); + } + + // Get bar font size for a specific bar height, capsule height, and orientation + function getBarFontSizeForDensity(barHeight, capsuleHeight, isVertical) { + const baseFontSize = Math.max(1, (barHeight / capsuleHeight) * Style.fontSizeXXS); + return isVertical ? baseFontSize * 0.9 * Settings.data.bar.fontScale : baseFontSize * Settings.data.bar.fontScale; + } + + // Convenience functions for per-screen bar sizing + function getBarHeightForScreen(screenName) { + var density = Settings.getBarDensityForScreen(screenName); + var position = Settings.getBarPositionForScreen(screenName); + var isVertical = position === "left" || position === "right"; + return getBarHeightForDensity(density, isVertical); + } + + function getCapsuleHeightForScreen(screenName) { + var barHeight = getBarHeightForScreen(screenName); + var density = Settings.getBarDensityForScreen(screenName); + return getCapsuleHeightForDensity(density, barHeight); + } + + function getBarFontSizeForScreen(screenName) { + var barHeight = getBarHeightForScreen(screenName); + var capsuleHeight = getCapsuleHeightForScreen(screenName); + var position = Settings.getBarPositionForScreen(screenName); + var isVertical = position === "left" || position === "right"; + return getBarFontSizeForDensity(barHeight, capsuleHeight, isVertical); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/ThemeIcons.qml b/arch/.config/quickshell/noctalia-shell/Commons/ThemeIcons.qml new file mode 100644 index 0000000..f4dc67d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/ThemeIcons.qml @@ -0,0 +1,289 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Commons + +Singleton { + id: root + + property real scoreThreshold: 0.2 + + // Manual overrides for tricky apps + property var substitutions: ({ + "code-url-handler": "visual-studio-code", + "Code": "visual-studio-code", + "gnome-tweaks": "org.gnome.tweaks", + "pavucontrol-qt": "pavucontrol", + "wps": "wps-office2019-kprometheus", + "wpsoffice": "wps-office2019-kprometheus", + "footclient": "foot" + }) + + // Dynamic fixups + property var regexSubstitutions: [ + { + "regex": /^steam_app_(\d+)$/, + "replace": "steam_icon_$1" + }, + { + "regex": /Minecraft.*/, + "replace": "minecraft-launcher" + }, + { + "regex": /.*polkit.*/, + "replace": "system-lock-screen" + }, + { + "regex": /gcr.prompter/, + "replace": "system-lock-screen" + } + ] + + property list entryList: [] + property var preppedNames: [] + property var preppedIcons: [] + property var preppedIds: [] + + Component.onCompleted: refreshEntries() + + Connections { + target: DesktopEntries.applications + function onValuesChanged() { + refreshEntries(); + } + } + + function refreshEntries() { + if (typeof DesktopEntries === 'undefined') + return; + + const values = Array.from(DesktopEntries.applications.values); + if (values) { + entryList = values.sort((a, b) => a.name.localeCompare(b.name)); + updatePreppedData(); + } + } + + function updatePreppedData() { + if (typeof FuzzySort === 'undefined') + return; + + const list = Array.from(entryList); + preppedNames = list.map(a => ({ + name: FuzzySort.prepare(`${a.name} `), + entry: a + })); + preppedIcons = list.map(a => ({ + name: FuzzySort.prepare(`${a.icon} `), + entry: a + })); + preppedIds = list.map(a => ({ + name: FuzzySort.prepare(`${a.id} `), + entry: a + })); + } + + function iconForAppId(appId, fallbackName) { + const fallback = fallbackName || "application-x-executable"; + if (!appId) + return iconFromName(fallback, fallback); + + const entry = findAppEntry(appId); + if (entry) { + return iconFromName(entry.icon, fallback); + } + + return iconFromName(appId, fallback); + } + + // Robust lookup strategy + function findAppEntry(str) { + if (!str || str.length === 0) + return null; + + let result = null; + + if (result = checkHeuristic(str)) + return result; + if (result = checkSubstitutions(str)) + return result; + if (result = checkRegex(str)) + return result; + if (result = checkSimpleTransforms(str)) + return result; + if (result = checkFuzzySearch(str)) + return result; + if (result = checkCleanMatch(str)) + return result; + + return null; + } + + function iconFromName(iconName, fallbackName) { + const fallback = fallbackName || "application-x-executable"; + try { + if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) { + const p = Quickshell.iconPath(iconName, fallback); + if (p && p !== "") + return p; + } + } catch (e) {} + + try { + return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : ""; + } catch (e2) { + return ""; + } + } + + function distroLogoPath() { + try { + return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : ""; + } catch (e) { + return ""; + } + } + + // --- Lookup Helpers --- + + function checkHeuristic(str) { + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) { + const entry = DesktopEntries.heuristicLookup(str); + if (entry) + return entry; + } + return null; + } + + function checkSubstitutions(str) { + let effectiveStr = substitutions[str]; + if (!effectiveStr) + effectiveStr = substitutions[str.toLowerCase()]; + + if (effectiveStr && effectiveStr !== str) { + return findAppEntry(effectiveStr); + } + return null; + } + + function checkRegex(str) { + for (let i = 0; i < regexSubstitutions.length; i++) { + const sub = regexSubstitutions[i]; + const replaced = str.replace(sub.regex, sub.replace); + if (replaced !== str) { + return findAppEntry(replaced); + } + } + return null; + } + + function checkSimpleTransforms(str) { + if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId) + return null; + + const lower = str.toLowerCase(); + + const variants = [str, lower, getFromReverseDomain(str), getFromReverseDomain(str)?.toLowerCase(), normalizeWithHyphens(str), str.replace(/_/g, '-').toLowerCase(), str.replace(/-/g, '_').toLowerCase()]; + + for (let i = 0; i < variants.length; i++) { + const variant = variants[i]; + if (variant) { + const entry = DesktopEntries.byId(variant); + if (entry) + return entry; + } + } + return null; + } + + function checkFuzzySearch(str) { + if (typeof FuzzySort === 'undefined') + return null; + + // Check filenames (IDs) first + if (preppedIds.length > 0) { + let results = fuzzyQuery(str, preppedIds); + if (results.length === 0) { + const underscored = str.replace(/-/g, '_').toLowerCase(); + if (underscored !== str) + results = fuzzyQuery(underscored, preppedIds); + } + if (results.length > 0) + return results[0]; + } + + // Then icons + if (preppedIcons.length > 0) { + const results = fuzzyQuery(str, preppedIcons); + if (results.length > 0) + return results[0]; + } + + // Then names + if (preppedNames.length > 0) { + const results = fuzzyQuery(str, preppedNames); + if (results.length > 0) + return results[0]; + } + + return null; + } + + function checkCleanMatch(str) { + if (!str || str.length <= 3) + return null; + if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId) + return null; + + // Aggressive fallback: strip all separators + const cleanStr = str.toLowerCase().replace(/[\.\-_]/g, ''); + const list = Array.from(entryList); + + for (let i = 0; i < list.length; i++) { + const entry = list[i]; + const cleanId = (entry.id || "").toLowerCase().replace(/[\.\-_]/g, ''); + if (cleanId.includes(cleanStr) || cleanStr.includes(cleanId)) { + return entry; + } + } + return null; + } + + function fuzzyQuery(search, preppedData) { + if (!search || !preppedData || preppedData.length === 0) + return []; + return FuzzySort.go(search, preppedData, { + all: true, + key: "name" + }).map(r => r.obj.entry); + } + + function iconExists(iconName) { + if (!iconName || iconName.length === 0) + return false; + if (iconName.startsWith("/")) + return true; + + const path = Quickshell.iconPath(iconName, true); + return path && path.length > 0 && !path.includes("image-missing"); + } + + function getFromReverseDomain(str) { + if (!str) + return ""; + return str.split('.').slice(-1)[0]; + } + + function normalizeWithHyphens(str) { + if (!str) + return ""; + return str.toLowerCase().replace(/\s+/g, "-"); + } + + // Deprecated shim + function guessIcon(str) { + const entry = findAppEntry(str); + return entry ? entry.icon : "image-missing"; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Commons/Time.qml b/arch/.config/quickshell/noctalia-shell/Commons/Time.qml new file mode 100644 index 0000000..69a2469 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Commons/Time.qml @@ -0,0 +1,232 @@ +pragma Singleton +import QtQuick + +import Quickshell +import qs.Commons +import qs.Services.System + +Singleton { + id: root + + // Current date + property var now: new Date() + + // Unix timestamp of the last update + property real _lastUpdateTs: Date.now() + + // Signal emitted when a significant time jump is detected (e.g. system resume) + signal resumed + + // Returns a Unix Timestamp (in seconds) + readonly property int timestamp: { + return Math.floor(root.now / 1000); + } + + // Timer state (for countdown/stopwatch) + property bool timerRunning: false + property bool timerStopwatchMode: false + property int timerRemainingSeconds: 0 + property int timerTotalSeconds: 0 + property int timerElapsedSeconds: 0 + property bool timerSoundPlaying: false + property int timerStartTimestamp: 0 // Unix timestamp when timer was started + property int timerPausedAt: 0 // Value when paused (for resuming) + + Timer { + id: updateTimer + interval: 1000 + repeat: true + running: true + triggeredOnStart: false + onTriggered: { + var newTime = new Date(); + var currentTs = newTime.getTime(); + + // Detect time jump (e.g. system resume) - threshold: 5 seconds + if (currentTs - root._lastUpdateTs > 5000) { + Logger.i("Time", "Time jump detected (" + Math.round((currentTs - root._lastUpdateTs) / 1000) + "s) - likely system resume"); + root.resumed(); + } + root._lastUpdateTs = currentTs; + + root.now = newTime; + + // Update timer if running + if (root.timerRunning && root.timerStartTimestamp > 0) { + const elapsedSinceStart = root.timestamp - root.timerStartTimestamp; + + if (root.timerStopwatchMode) { + root.timerElapsedSeconds = root.timerPausedAt + elapsedSinceStart; + } else { + root.timerRemainingSeconds = root.timerTotalSeconds - elapsedSinceStart; + if (root.timerRemainingSeconds <= 0) { + root.timerOnFinished(); + } + } + } + + // Adjust next interval to sync with the start of the next second + var msIntoSecond = newTime.getMilliseconds(); + if (msIntoSecond > 100) { + // If we're more than 100ms into the second, adjust for next time + updateTimer.interval = 1000 - msIntoSecond + 10; // +10ms buffer + updateTimer.restart(); + } else { + updateTimer.interval = 1000; + } + } + } + + Component.onCompleted: { + // Start by syncing to the next second boundary + var now = new Date(); + var msUntilNextSecond = 1000 - now.getMilliseconds(); + updateTimer.interval = msUntilNextSecond + 10; // +10ms buffer + updateTimer.restart(); + } + + // Formats a Date object into a YYYYMMDD-HHMMSS string. + function getFormattedTimestamp(date) { + if (!date) { + date = new Date(); + } + const year = date.getFullYear(); + + // getMonth() is zero-based, so we add 1 + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + const seconds = String(date.getSeconds()).padStart(2, '0'); + + return `${year}${month}${day}-${hours}${minutes}${seconds}`; + } + + // Format an easy to read approximate duration ex: 4h 32m + // Used to display the time remaining on the Battery widget, computer uptime, etc.. + function formatVagueHumanReadableDuration(totalSeconds) { + if (typeof totalSeconds !== 'number' || totalSeconds < 0) { + return '0s'; + } + + // Floor the input to handle decimal seconds + totalSeconds = Math.floor(totalSeconds); + + const days = Math.floor(totalSeconds / 86400); + const hours = Math.floor((totalSeconds % 86400) / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = totalSeconds % 60; + + const parts = []; + if (days) + parts.push(`${days}d`); + if (hours) + parts.push(`${hours}h`); + if (minutes) + parts.push(`${minutes}m`); + + // Only show seconds if no hours and no minutes + if (!hours && !minutes) { + parts.push(`${seconds}s`); + } + + return parts.join(' '); + } + + // Format a date into + function formatRelativeTime(date) { + if (!date) + return ""; + const diff = Date.now() - date.getTime(); + if (diff < 60000) + return I18n.tr("notifications.time.now"); + if (diff < 120000) + return I18n.tr("notifications.time.diff-m"); + if (diff < 3600000) + return I18n.tr("notifications.time.diff-mm", { + "diff": Math.floor(diff / 60000) + }); + if (diff < 7200000) + return I18n.tr("notifications.time.diff-h"); + if (diff < 86400000) + return I18n.tr("notifications.time.diff-hh", { + "diff": Math.floor(diff / 3600000) + }); + if (diff < 172800000) + return I18n.tr("notifications.time.diff-d"); + return I18n.tr("notifications.time.diff-dd", { + "diff": Math.floor(diff / 86400000) + }); + } + + // Timer functions + function timerStart() { + if (root.timerStopwatchMode) { + root.timerRunning = true; + root.timerStartTimestamp = root.timestamp; + root.timerPausedAt = root.timerElapsedSeconds; + } else { + if (root.timerRemainingSeconds <= 0) { + return; + } + root.timerRunning = true; + root.timerTotalSeconds = root.timerRemainingSeconds; + root.timerStartTimestamp = root.timestamp; + root.timerPausedAt = 0; + } + } + + function timerPause() { + if (root.timerRunning) { + // Calculate and set remainingSeconds BEFORE changing timerRunning + // This ensures the UI sees the correct value when it reacts to timerRunning changing + if (root.timerStopwatchMode) { + root.timerPausedAt = root.timerElapsedSeconds; + } else { + // Calculate remaining seconds at the exact moment of pause + // Use current time directly to avoid stale timestamp issues + const currentTimestamp = Math.floor(Date.now() / 1000); + const elapsedSinceStart = currentTimestamp - root.timerStartTimestamp; + const currentRemaining = root.timerTotalSeconds - elapsedSinceStart; + root.timerPausedAt = Math.max(0, currentRemaining); + + // CRITICAL: Update timerRemainingSeconds to the paused value BEFORE changing timerRunning + // This ensures UI sees correct value when it reacts to timerRunning change + root.timerRemainingSeconds = root.timerPausedAt; + } + } + root.timerRunning = false; + root.timerStartTimestamp = 0; + // Stop any repeating notification sound when pausing + SoundService.stopSound("alarm-beep.wav"); + root.timerSoundPlaying = false; + } + + function timerReset() { + root.timerRunning = false; + root.timerStartTimestamp = 0; + if (root.timerStopwatchMode) { + root.timerElapsedSeconds = 0; + root.timerPausedAt = 0; + } else { + root.timerRemainingSeconds = 0; + root.timerTotalSeconds = 0; + root.timerPausedAt = 0; + } + // Stop any repeating notification sound + SoundService.stopSound("alarm-beep.wav"); + root.timerSoundPlaying = false; + } + + function timerOnFinished() { + root.timerRunning = false; + root.timerRemainingSeconds = 0; + // Play notification sound with repeat at lower volume + root.timerSoundPlaying = true; + SoundService.playSound("alarm-beep.wav", { + repeat: true, + volume: 0.3 + }); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Helpers/AdvancedMath.js b/arch/.config/quickshell/noctalia-shell/Helpers/AdvancedMath.js new file mode 100644 index 0000000..d7bc367 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Helpers/AdvancedMath.js @@ -0,0 +1,161 @@ +// AdvancedMath.js - Lightweight math library for Noctalia Calculator +// Provides advanced mathematical functions beyond basic arithmetic + +// Helper function to convert degrees to radians +function toRadians(degrees) { + return degrees * (Math.PI / 180); +} + +// Helper function to convert radians to degrees +function toDegrees(radians) { + return radians * (180 / Math.PI); +} + +// Constants +var constants = { + PI: Math.PI, + E: Math.E, + LN2: Math.LN2, + LN10: Math.LN10, + LOG2E: Math.LOG2E, + LOG10E: Math.LOG10E, + SQRT1_2: Math.SQRT1_2, + SQRT2: Math.SQRT2 +}; + +// Safe evaluation function that handles advanced math +function evaluate(expression) { + try { + // Fixes decimal arithmetic + var cleanExpr = expression.replace(/\s+/g, '').toLowerCase(); + + // Allows numbers (including decimals), basic operators, and explicitly permitted math terms only + var safeRegex = /^(\d*\.?\d+|[+\-*/()^%,]|sin|cos|tan|asin|acos|atan|atan2|sinh|cosh|tanh|asinh|acosh|atanh|log|ln|exp|pow|sqrt|cbrt|abs|floor|ceil|round|trunc|min|max|random|pi|e|sind|cosd|tand)+$/; + + if (!safeRegex.test(cleanExpr)) { + throw new Error("Invalid characters or unauthorized functions in expression"); + } + + // Replace mathematical constants (Original Structure) + var processed = cleanExpr + .replace(/\bpi\b/gi, Math.PI) + .replace(/\be\b/gi, Math.E); + + // Replace function calls with Math object equivalents + processed = processed + // Trigonometric functions + .replace(/\bsin\s*\(/g, 'Math.sin(') + .replace(/\bcos\s*\(/g, 'Math.cos(') + .replace(/\btan\s*\(/g, 'Math.tan(') + .replace(/\basin\s*\(/g, 'Math.asin(') + .replace(/\bacos\s*\(/g, 'Math.acos(') + .replace(/\batan\s*\(/g, 'Math.atan(') + .replace(/\batan2\s*\(/g, 'Math.atan2(') + + // Hyperbolic functions + .replace(/\bsinh\s*\(/g, 'Math.sinh(') + .replace(/\bcosh\s*\(/g, 'Math.cosh(') + .replace(/\btanh\s*\(/g, 'Math.tanh(') + .replace(/\basinh\s*\(/g, 'Math.asinh(') + .replace(/\bacosh\s*\(/g, 'Math.acosh(') + .replace(/\batanh\s*\(/g, 'Math.atanh(') + + // Logarithmic and exponential functions + .replace(/\blog\s*\(/g, 'Math.log10(') + .replace(/\bln\s*\(/g, 'Math.log(') + .replace(/\bexp\s*\(/g, 'Math.exp(') + .replace(/\bpow\s*\(/g, 'Math.pow(') + + // Root functions + .replace(/\bsqrt\s*\(/g, 'Math.sqrt(') + .replace(/\bcbrt\s*\(/g, 'Math.cbrt(') + + // Rounding and absolute + .replace(/\babs\s*\(/g, 'Math.abs(') + .replace(/\bfloor\s*\(/g, 'Math.floor(') + .replace(/\bceil\s*\(/g, 'Math.ceil(') + .replace(/\bround\s*\(/g, 'Math.round(') + .replace(/\btrunc\s*\(/g, 'Math.trunc(') + + // Min/Max + .replace(/\bmin\s*\(/g, 'Math.min(') + .replace(/\bmax\s*\(/g, 'Math.max(') + + // Random + .replace(/\brandom\s*\(\s*\)/g, 'Math.random()'); + + // Handle degree versions of trig functions + processed = processed + .replace(/\bsind\s*\(/g, '(function(x) { return Math.sin(' + (Math.PI / 180) + ' * x); })(') + .replace(/\bcosd\s*\(/g, '(function(x) { return Math.cos(' + (Math.PI / 180) + ' * x); })(') + .replace(/\btand\s*\(/g, '(function(x) { return Math.tan(' + (Math.PI / 180) + ' * x); })('); + + // Handle ^ for exponentiation: convert 2^3 to Math.pow(2,3) + processed = processed.replace(/([\d.]+|\))\^([\d.]+|\([^)]*\))/g, 'Math.pow($1,$2)'); + + // Replacing eval() with a scoped function constructor + // This is safe because the strict whitelist guarantees only math reaches this point + var result = new Function('return ' + processed)(); + + if (!isFinite(result) || isNaN(result)) { + throw new Error("Invalid result"); + } + + return result; + } catch (error) { + throw new Error("Evaluation failed: " + error.message); + } +} + +// Format result for display +function formatResult(result) { + if (Number.isInteger(result)) { + return result.toString(); + } + + // Handle very large or very small numbers + if (Math.abs(result) >= 1e15 || (Math.abs(result) < 1e-6 && result !== 0)) { + return result.toExponential(6); + } + + // Normal decimal formatting + return parseFloat(result.toFixed(10)).toString(); +} + +// Get list of available functions for help +function getAvailableFunctions() { + return [ + // Basic arithmetic: +, -, *, /, %, ^, () + + // Trigonometric functions + "sin(x), cos(x), tan(x) - trigonometric functions (radians)", + "sind(x), cosd(x), tand(x) - trigonometric functions (degrees)", + "asin(x), acos(x), atan(x) - inverse trigonometric", + "atan2(y, x) - two-argument arctangent", + + // Hyperbolic functions + "sinh(x), cosh(x), tanh(x) - hyperbolic functions", + "asinh(x), acosh(x), atanh(x) - inverse hyperbolic", + + // Logarithmic and exponential + "log(x) - base 10 logarithm", + "ln(x) - natural logarithm", + "exp(x) - e^x", + "pow(x, y) - x^y", + + // Root functions + "sqrt(x) - square root", + "cbrt(x) - cube root", + + // Rounding and absolute + "abs(x) - absolute value", + "floor(x), ceil(x), round(x), trunc(x)", + + // Min/Max/Random + "min(a, b, ...), max(a, b, ...)", + "random() - random number 0-1", + + // Constants + "pi, e - mathematical constants" + ]; +} diff --git a/arch/.config/quickshell/noctalia-shell/Helpers/BluetoothUtils.js b/arch/.config/quickshell/noctalia-shell/Helpers/BluetoothUtils.js new file mode 100644 index 0000000..879b275 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Helpers/BluetoothUtils.js @@ -0,0 +1,127 @@ +.pragma library + +// Address helpers +var macFromDevice = (dev) => { + if (!dev) return ""; + if (dev.address && dev.address.length > 0) return dev.address; + if (dev.nativePath && dev.nativePath.indexOf("/dev_") !== -1) + return dev.nativePath.split("dev_")[1].split("_").join(":"); + return ""; +}; + +var deviceKey = (dev) => { + if (!dev) return ""; + if (dev.address && dev.address.length > 0) return dev.address.toUpperCase(); + if (dev.nativePath && dev.nativePath.length > 0) return dev.nativePath; + if (dev.devicePath && dev.devicePath.length > 0) return dev.devicePath; + return (dev.name || dev.deviceName || "") + "|" + (dev.icon || ""); +}; + +var dedupeDevices = (list) => { + if (!list || list.length === 0) return []; + var seen = ({}); + var out = []; + for (var i = 0; i < list.length; ++i) { + var d = list[i]; + if (!d) continue; + var k = deviceKey(d); + if (k && !seen[k]) { seen[k] = true; out.push(d); } + } + return out; +}; + +// RSSI parsing +var parseRssiOutput = (text) => { + try { + text = text || ""; + var mParen = text.match(/\(\s*(-?\d+)\s*(?:d?b?m?)?\s*\)/i); + if (mParen && mParen.length > 1) return Number(mParen[1]); + var mDec = text.match(/RSSI:\s*(-?\d+)/i); + if (mDec && mDec.length > 1) return Number(mDec[1]); + var mHex = text.match(/RSSI:\s*0x([0-9a-fA-F]+)/i); + if (mHex && mHex.length > 1) { + var v = parseInt(mHex[1], 16); + if (v >= 0x80000000) v = v - 0x100000000; // 32-bit two's complement + else if (v >= 0x8000) v = v - 0x10000; // 16-bit + else if (v >= 0x80) v = v - 0x100; // 8-bit + return v; + } + } catch (e) {} + return null; +}; + +var dbmToPercent = (dbm) => { + if (dbm === null || dbm === undefined || isNaN(dbm)) return null; + // Clamp simple linear map roughly from -100..0 dBm to 0..100% + var pct = Math.round((Number(dbm) + 100) * 2); + if (isNaN(pct)) return null; + return Math.max(0, Math.min(100, pct)); +}; + +// Signal helpers +var signalPercent = (device, cache, _version) => { + if (!device) return null; + try { + var addr = macFromDevice(device); + if (addr && cache && cache[addr] !== undefined) { + var cached = Number(cache[addr]) | 0; + return Math.max(0, Math.min(100, cached)); + } + } catch (e) {} + var s = device && device.signalStrength; + if (s === undefined || s <= 0) return null; + var p = Number(s) | 0; + return Math.max(0, Math.min(100, p)); +}; + +var signalIcon = (p) => { + if (p === null) return "antenna-bars-off"; + if (p >= 80) return "antenna-bars-5"; + if (p >= 60) return "antenna-bars-4"; + if (p >= 40) return "antenna-bars-3"; + if (p >= 20) return "antenna-bars-2"; + return "antenna-bars-1"; +}; + +// Icon mapping +var deviceIcon = (name, icon) => { + var s1 = String(name || "").toLowerCase(); + var s2 = String(icon || "").toLowerCase(); + + // Prefer icon-based hints for display devices first to avoid "audio" catching TVs + var displayHints = ["display", "tv", "monitor", "projector", "screen", "chromecast", "cast"]; + for (var dh = 0; dh < displayHints.length; dh++) { + if (s2.indexOf(displayHints[dh]) !== -1) return "bt-device-tv"; + } + + var tests = [ + [["controller", "gamepad"], "bt-device-gamepad"], + [["microphone"], "bt-device-microphone"], + [["pod", "bud", "minor"], "bt-device-earbuds"], + [["headset", "arctis", "major"], "bt-device-headset"], + [["headphone"], "bt-device-headphones"], + [["mouse"], "bt-device-mouse"], + [["keyboard"], "bt-device-keyboard"], + [["watch"], "bt-device-watch"], + [["display", "tv", "monitor", "projector", "screen", "chromecast", "cast"], "bt-device-tv"], + [["speaker", "audio", "sound"], "bt-device-speaker"], + [["phone", "iphone", "android", "samsung"], "bt-device-phone"] + ]; + for (var i = 0; i < tests.length; i++) { + var keys = tests[i][0]; + var out = tests[i][1]; + for (var j = 0; j < keys.length; j++) { + var k = keys[j]; + if (s1.indexOf(k) !== -1 || s2.indexOf(k) !== -1) return out; + } + } + return "bt-device-generic"; +}; + +// Battery percent helper +var batteryPercent = (device) => { + if (!device || !device.batteryAvailable || device.battery === undefined) return null; + var val = Math.round(Number(device.battery) * 100); + if (isNaN(val)) return null; + return Math.max(0, Math.min(100, val)); +}; diff --git a/arch/.config/quickshell/noctalia-shell/Helpers/ColorList.js b/arch/.config/quickshell/noctalia-shell/Helpers/ColorList.js new file mode 100644 index 0000000..3893214 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Helpers/ColorList.js @@ -0,0 +1,187 @@ +var colors = [ + // --- REDS --- + { name: "MistyRose", color: "mistyrose" }, + { name: "LightPink", color: "lightpink" }, + { name: "Pink", color: "pink" }, + { name: "PaleVioletRed", color: "palevioletred" }, + { name: "Pink 500", color: "#E91E63" }, // Material + { name: "HotPink", color: "hotpink" }, + { name: "DeepPink", color: "deeppink" }, + { name: "MediumVioletRed", color: "mediumvioletred" }, + { name: "LightSalmon", color: "lightsalmon" }, + { name: "Salmon", color: "salmon" }, + { name: "DarkSalmon", color: "darksalmon" }, + { name: "LightCoral", color: "lightcoral" }, + { name: "IndianRed", color: "indianred" }, + { name: "Alizarin", color: "#E74C3C" }, // Flat UI + { name: "Red 500", color: "#F44336" }, // Material + { name: "Crimson", color: "crimson" }, + { name: "Red", color: "red" }, + { name: "FireBrick", color: "firebrick" }, + { name: "DarkRed", color: "darkred" }, + { name: "Maroon", color: "maroon" }, + { name: "Brown", color: "brown" }, + + // --- ORANGES & BROWNS --- + { name: "Coral", color: "coral" }, + { name: "Tomato", color: "tomato" }, + { name: "OrangeRed", color: "orangered" }, + { name: "Deep Orange 500", color: "#FF5722" }, // Material + { name: "DarkOrange", color: "darkorange" }, + { name: "Carrot", color: "#E67E22" }, // Flat UI + { name: "Orange 500", color: "#FF9800" }, // Material + { name: "Orange", color: "orange" }, + { name: "SandyBrown", color: "sandybrown" }, + { name: "Peru", color: "peru" }, + { name: "Chocolate", color: "chocolate" }, + { name: "SaddleBrown", color: "saddlebrown" }, + { name: "Sienna", color: "sienna" }, + { name: "Brown 500", color: "#795548" }, // Material + + // --- YELLOWS, BEIGES & GOLDS --- + { name: "LightYellow", color: "lightyellow" }, + { name: "LemonChiffon", color: "lemonchiffon" }, + { name: "LightGoldenrodYellow", color: "lightgoldenrodyellow" }, + { name: "PapayaWhip", color: "papayawhip" }, + { name: "Moccasin", color: "moccasin" }, + { name: "PeachPuff", color: "peachpuff" }, + { name: "NavajoWhite", color: "navajowhite" }, + { name: "Wheat", color: "wheat" }, + { name: "BurlyWood", color: "burlywood" }, + { name: "Tan", color: "tan" }, + { name: "Bisque", color: "bisque" }, + { name: "BlanchedAlmond", color: "blanchedalmond" }, + { name: "Cornsilk", color: "cornsilk" }, + { name: "PaleGoldenrod", color: "palegoldenrod" }, + { name: "Khaki", color: "khaki" }, + { name: "DarkKhaki", color: "darkkhaki" }, + { name: "Goldenrod", color: "goldenrod" }, + { name: "DarkGoldenrod", color: "darkgoldenrod" }, + { name: "Sun Flower", color: "#F1C40F" }, // Flat UI + { name: "Yellow 500", color: "#FFEB3B" }, // Material + { name: "Yellow", color: "yellow" }, + { name: "Gold", color: "gold" }, + { name: "Amber 500", color: "#FFC107" }, // Material + + // --- GREENS --- + { name: "GreenYellow", color: "greenyellow" }, + { name: "Chartreuse", color: "chartreuse" }, + { name: "LawnGreen", color: "lawngreen" }, + { name: "Lime 500", color: "#CDDC39" }, // Material + { name: "Lime", color: "lime" }, + { name: "LimeGreen", color: "limegreen" }, + { name: "PaleGreen", color: "palegreen" }, + { name: "LightGreen", color: "lightgreen" }, + { name: "Light Green 500", color: "#8BC34A" }, // Material + { name: "MediumSpringGreen", color: "mediumspringgreen" }, + { name: "SpringGreen", color: "springgreen" }, + { name: "Emerald", color: "#2ECC71" }, // Flat UI + { name: "Green 500", color: "#4CAF50" }, // Material + { name: "MediumSeaGreen", color: "mediumseagreen" }, + { name: "SeaGreen", color: "seagreen" }, + { name: "ForestGreen", color: "forestgreen" }, + { name: "Green", color: "green" }, + { name: "DarkGreen", color: "darkgreen" }, + { name: "YellowGreen", color: "yellowgreen" }, + { name: "OliveDrab", color: "olivedrab" }, + { name: "Olive", color: "olive" }, + { name: "DarkOliveGreen", color: "darkolivegreen" }, + + // --- TEALS & CYANS --- + { name: "MediumAquamarine", color: "mediumaquamarine" }, + { name: "DarkSeaGreen", color: "darkseagreen" }, + { name: "LightSeaGreen", color: "lightseagreen" }, + { name: "DarkCyan", color: "darkcyan" }, + { name: "Teal", color: "teal" }, + { name: "Turquoise", color: "#1ABC9C" }, // Flat UI + { name: "LightCyan", color: "lightcyan" }, + { name: "PaleTurquoise", color: "paleturquoise" }, + { name: "Aquamarine", color: "aquamarine" }, + { name: "Turquoise", color: "turquoise" }, + { name: "MediumTurquoise", color: "mediumturquoise" }, + { name: "DarkTurquoise", color: "darkturquoise" }, + { name: "Aqua", color: "aqua" }, + { name: "Cyan", color: "cyan" }, + { name: "Cyan 500", color: "#00BCD4" }, // Material + { name: "CadetBlue", color: "cadetblue" }, + { name: "Teal 500", color: "#009688" }, // Material + { name: "DarkSlateGray", color: "darkslategray" }, + + // --- BLUES --- + { name: "PowderBlue", color: "powderblue" }, + { name: "LightBlue", color: "lightblue" }, + { name: "SkyBlue", color: "skyblue" }, + { name: "LightSkyBlue", color: "lightskyblue" }, + { name: "Light Blue 500", color: "#03A9F4" }, // Material + { name: "DeepSkyBlue", color: "deepskyblue" }, + { name: "DodgerBlue", color: "dodgerblue" }, + { name: "CornflowerBlue", color: "cornflowerblue" }, + { name: "Peter River", color: "#3498DB" }, // Flat UI + { name: "Blue 500", color: "#2196F3" }, // Material + { name: "SteelBlue", color: "steelblue" }, + { name: "LightSteelBlue", color: "lightsteelblue" }, + { name: "RoyalBlue", color: "royalblue" }, + { name: "Blue", color: "blue" }, + { name: "MediumBlue", color: "mediumblue" }, + { name: "Belize Hole", color: "#2980B9" }, // Flat UI + { name: "DarkBlue", color: "darkblue" }, + { name: "Navy", color: "navy" }, + { name: "MidnightBlue", color: "midnightblue" }, + { name: "Midnight Blue", color: "#2C3E50" }, // Flat UI (Same name, different color) + { name: "Indigo 500", color: "#3F51B5" }, // Material + { name: "DarkSlateBlue", color: "darkslateblue" }, + { name: "MediumSlateBlue", color: "mediumslateblue" }, + { name: "SlateBlue", color: "slateblue" }, + + // --- PURPLES & MAGENTAS --- + { name: "Lavender", color: "lavender" }, + { name: "Thistle", color: "thistle" }, + { name: "Plum", color: "plum" }, + { name: "Violet", color: "violet" }, + { name: "Orchid", color: "orchid" }, + { name: "Fuchsia", color: "fuchsia" }, + { name: "Magenta", color: "magenta" }, + { name: "MediumOrchid", color: "mediumorchid" }, + { name: "MediumPurple", color: "mediumpurple" }, + { name: "Amethyst", color: "#9B59B6" }, // Flat UI + { name: "Purple 500", color: "#9C27B0" }, // Material + { name: "BlueViolet", color: "blueviolet" }, + { name: "DarkViolet", color: "darkviolet" }, + { name: "DarkOrchid", color: "darkorchid" }, + { name: "DarkMagenta", color: "darkmagenta" }, + { name: "Purple", color: "purple" }, + { name: "Deep Purple 500", color: "#673AB7" }, // Material + { name: "Indigo", color: "indigo" }, + + // --- NEUTRALS --- + { name: "White", color: "white" }, + { name: "Snow", color: "snow" }, + { name: "HoneyDew", color: "honeydew" }, + { name: "MintCream", color: "mintcream" }, + { name: "Azure", color: "azure" }, + { name: "AliceBlue", color: "aliceblue" }, + { name: "GhostWhite", color: "ghostwhite" }, + { name: "WhiteSmoke", color: "whitesmoke" }, + { name: "Seashell", color: "seashell" }, + { name: "Beige", color: "beige" }, + { name: "OldLace", color: "oldlace" }, + { name: "FloralWhite", color: "floralwhite" }, + { name: "Ivory", color: "ivory" }, + { name: "AntiqueWhite", color: "antiquewhite" }, + { name: "Linen", color: "linen" }, + { name: "LavenderBlush", color: "lavenderblush" }, + { name: "Gainsboro", color: "gainsboro" }, + { name: "LightGray", color: "lightgray" }, + { name: "Silver", color: "silver" }, + { name: "DarkGray", color: "darkgray" }, + { name: "Gray", color: "gray" }, + { name: "Grey 500", color: "#9E9E9E" }, // Material + { name: "Concrete", color: "#95A5A6" }, // Flat UI + { name: "DimGray", color: "dimgray" }, + { name: "LightSlateGray", color: "lightslategray" }, + { name: "SlateGray", color: "slategray" }, + { name: "Asbestos", color: "#7F8C8D" }, // Flat UI + { name: "Blue Grey 500", color: "#607D8B" }, // Material + { name: "Wet Asphalt", color: "#34495E" }, // Flat UI + { name: "Black", color: "black" } + ] diff --git a/arch/.config/quickshell/noctalia-shell/Helpers/ColorsConvert.js b/arch/.config/quickshell/noctalia-shell/Helpers/ColorsConvert.js new file mode 100644 index 0000000..0e5eb7a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Helpers/ColorsConvert.js @@ -0,0 +1,270 @@ +// Convert hex color to HSL +function hexToHSL(hex) { + const rgb = hexToRgb(hex); + if (!rgb) return null; + return rgbToHsl(rgb.r, rgb.g, rgb.b); +} + +// Convert HSL to hex color +function hslToHex(h, s, l) { + const rgb = hslToRgb(h, s, l); + return rgbToHex(rgb.r, rgb.g, rgb.b); +} + +// Convert hex color to RGB +function hexToRgb(hex) { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } : { r: 0, g: 0, b: 0 }; +} + +// Convert RGB to hex color +function rgbToHex(r, g, b) { + return "#" + [r, g, b].map(x => { + const hex = Math.round(Math.max(0, Math.min(255, x))).toString(16); + return hex.length === 1 ? "0" + hex : hex; + }).join(""); +} + +// Convert RGB to HSL +function rgbToHsl(r, g, b) { + r /= 255; + g /= 255; + b /= 255; + + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + let h, s, l = (max + min) / 2; + + if (max === min) { + h = s = 0; + } else { + const d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + + switch (max) { + case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break; + case g: h = ((b - r) / d + 2) / 6; break; + case b: h = ((r - g) / d + 4) / 6; break; + } + } + + return { h: h * 360, s: s * 100, l: l * 100 }; +} + +// Convert HSL to RGB +function hslToRgb(h, s, l) { + h /= 360; + s /= 100; + l /= 100; + + let r, g, b; + + if (s === 0) { + r = g = b = l; + } else { + const hue2rgb = (p, q, t) => { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1/6) return p + (q - p) * 6 * t; + if (t < 1/2) return q; + if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + }; + + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) }; +} + +// Convert RGB to HSV +function rgbToHsv(r, g, b) { + r /= 255; + g /= 255; + b /= 255; + var max = Math.max(r, g, b), min = Math.min(r, g, b); + var h, s, v = max; + var d = max - min; + s = max === 0 ? 0 : d / max; + if (max === min) { + h = 0; + } else { + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; + } + h /= 6; + } + return { h: h * 360, s: s * 100, v: v * 100 }; +} + +// Convert HSV to RGB +function hsvToRgb(h, s, v) { + h /= 360; + s /= 100; + v /= 100; + + var r, g, b; + var i = Math.floor(h * 6); + var f = h * 6 - i; + var p = v * (1 - s); + var q = v * (1 - f * s); + var t = v * (1 - (1 - f) * s); + + switch (i % 6) { + case 0: + r = v; + g = t; + b = p; + break; + case 1: + r = q; + g = v; + b = p; + break; + case 2: + r = p; + g = v; + b = t; + break; + case 3: + r = p; + g = q; + b = v; + break; + case 4: + r = t; + g = p; + b = v; + break; + case 5: + r = v; + g = p; + b = q; + break; + } + + return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) }; +} + +// Calculate relative luminance (WCAG standard) +function getLuminance(hex) { + const rgb = hexToRgb(hex); + const [r, g, b] = [rgb.r, rgb.g, rgb.b].map(val => { + val /= 255; + return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4); + }); + return 0.2126 * r + 0.7152 * g + 0.0722 * b; +} + +// Calculate contrast ratio between two colors +function getContrastRatio(hex1, hex2) { + const lum1 = getLuminance(hex1); + const lum2 = getLuminance(hex2); + const brightest = Math.max(lum1, lum2); + const darkest = Math.min(lum1, lum2); + return (brightest + 0.05) / (darkest + 0.05); +} + +// Check if a color is considered "light" +function isLightColor(hex) { + return getLuminance(hex) > 0.5; +} + +// Adjust color lightness +function adjustLightness(hex, amount) { + const hsl = hexToHSL(hex); + hsl.l = Math.max(0, Math.min(100, hsl.l + amount)); + return hslToHex(hsl.h, hsl.s, hsl.l); +} + +// Adjust color saturation +function adjustSaturation(hex, amount) { + const hsl = hexToHSL(hex); + hsl.s = Math.max(0, Math.min(100, hsl.s + amount)); + return hslToHex(hsl.h, hsl.s, hsl.l); +} + +// Adjust both lightness and saturation +function adjustLightnessAndSaturation(hex, lightnessAmount, saturationAmount) { + const hsl = hexToHSL(hex); + hsl.l = Math.max(0, Math.min(100, hsl.l + lightnessAmount)); + hsl.s = Math.max(0, Math.min(100, hsl.s + saturationAmount)); + return hslToHex(hsl.h, hsl.s, hsl.l); +} + +// Generate "on" color with proper contrast (for text/icons) +function generateOnColor(baseColor, isDarkMode) { + const isBaseLight = isLightColor(baseColor); + + // If base is light, we need dark text; if base is dark, we need light text + if (isBaseLight) { + // Try darker variants + let testColor = "#000000"; + if (getContrastRatio(baseColor, testColor) >= 4.5) { + return testColor; + } + // Fallback to dark gray + return "#1c1b1f"; + } else { + // Try lighter variants + let testColor = "#ffffff"; + if (getContrastRatio(baseColor, testColor) >= 4.5) { + return testColor; + } + // Fallback to light gray + return "#e6e1e5"; + } +} + +// Generate container color (lighter in light mode, darker in dark mode) +function generateContainerColor(baseColor, isDarkMode) { + const rgb = hexToRgb(baseColor); + const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b); + + if (isDarkMode) { + // In dark mode, containers are darker and more saturated + hsl.l = Math.max(10, Math.min(30, hsl.l - 20)); + hsl.s = Math.min(100, hsl.s + 10); + } else { + // In light mode, containers are lighter and less saturated + hsl.l = Math.min(90, Math.max(75, hsl.l + 30)); + hsl.s = Math.max(0, hsl.s - 10); + } + + const newRgb = hslToRgb(hsl.h, hsl.s, hsl.l); + return rgbToHex(newRgb.r, newRgb.g, newRgb.b); +} + +// Generate surface variant colors +function generateSurfaceVariant(backgroundColor, step, isDarkMode) { + const rgb = hexToRgb(backgroundColor); + const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b); + + if (isDarkMode) { + // In dark mode, variants get progressively lighter + hsl.l = Math.min(100, hsl.l + (step * 3)); + } else { + // In light mode, variants get progressively darker + hsl.l = Math.max(0, hsl.l - (step * 2)); + } + + const newRgb = hslToRgb(hsl.h, hsl.s, hsl.l); + return rgbToHex(newRgb.r, newRgb.g, newRgb.b); +} diff --git a/arch/.config/quickshell/noctalia-shell/Helpers/Debug.js b/arch/.config/quickshell/noctalia-shell/Helpers/Debug.js new file mode 100644 index 0000000..90dc27e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Helpers/Debug.js @@ -0,0 +1,25 @@ +// Helper function to stringify even if there are circular refs. +function stringify(obj, replacer, spaces, cycleReplacer) { + return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces) +} + +function serializer(replacer, cycleReplacer) { + var stack = [], keys = [] + + if (cycleReplacer == null) cycleReplacer = function(key, value) { + if (stack[0] === value) return "[Circular ~]" + return "[Circular ~." + keys.slice(0, stack.indexOf(value)).join(".") + "]" + } + + return function(key, value) { + if (stack.length > 0) { + var thisPos = stack.indexOf(this) + ~thisPos ? stack.splice(thisPos + 1) : stack.push(this) + ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key) + if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value) + } + else stack.push(value) + + return replacer == null ? value : replacer.call(this, key, value) + } +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Helpers/QtObj2JS.js b/arch/.config/quickshell/noctalia-shell/Helpers/QtObj2JS.js new file mode 100644 index 0000000..75a248f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Helpers/QtObj2JS.js @@ -0,0 +1,114 @@ +// ----------------------------------------------------- +// Helper function to convert Qt objects to plain JavaScript objects +// Only used when generating settings-default.json +function qtObjectToPlainObject(obj) { + if (obj === null || obj === undefined) { + return obj; + } + + // Handle primitive types + if (typeof obj !== "object") { + return obj; + } + + // Handle native JavaScript arrays + if (Array.isArray(obj)) { + return obj.map((item) => qtObjectToPlainObject(item)); + } + + // Detect QML arrays FIRST (before color detection) + // QML arrays have a numeric length property and indexed properties + if (typeof obj.length === "number" && obj.length >= 0) { + // Check if it has indexed properties - be more flexible about detection + var hasIndexedProps = true; + var hasNumericKeys = false; + + // Check if we have at least some numeric properties + for (var i = 0; i < obj.length; i++) { + if (obj.hasOwnProperty(i) || obj[i] !== undefined) { + hasNumericKeys = true; + break; + } + } + + // If we have length > 0 and some numeric keys, treat as array + if (obj.length > 0 && hasNumericKeys) { + var arr = []; + for (var i = 0; i < obj.length; i++) { + // Use direct property access, handle undefined gracefully + var item = obj[i]; + if (item !== undefined) { + arr.push(qtObjectToPlainObject(item)); + } + } + return arr; // Return here to avoid processing as object + } + + // Handle empty arrays (length = 0) + if (obj.length === 0) { + return []; + } + } + + // Detect and convert QML color objects to hex strings + if ( + typeof obj.r === "number" && + typeof obj.g === "number" && + typeof obj.b === "number" && + typeof obj.a === "number" && + typeof obj.valid === "boolean" + ) { + // This looks like a QML color object + try { + // Try to get the string representation (should be hex like "#000000") + if (typeof obj.toString === "function") { + return obj.toString(); + } else { + // Fallback: convert RGBA to hex manually + var r = Math.round(obj.r * 255); + var g = Math.round(obj.g * 255); + var b = Math.round(obj.b * 255); + var hex = + "#" + + r.toString(16).padStart(2, "0") + + g.toString(16).padStart(2, "0") + + b.toString(16).padStart(2, "0"); + return hex; + } + } catch (e) { + // If conversion fails, fall through to regular object handling + } + } + + // Handle regular objects + var plainObj = {}; + + // Get all property names, but filter out Qt-specific ones + var propertyNames = Object.getOwnPropertyNames(obj); + + for (var i = 0; i < propertyNames.length; i++) { + var propName = propertyNames[i]; + + // Skip Qt-specific properties, functions, and array-like properties + if ( + propName === "objectName" || + propName === "objectNameChanged" || + propName === "length" || // Skip length property + /^\d+$/.test(propName) || // Skip numeric keys (0, 1, 2, etc.) + propName.endsWith("Changed") || + typeof obj[propName] === "function" + ) { + continue; + } + + try { + var value = obj[propName]; + plainObj[propName] = qtObjectToPlainObject(value); + } catch (e) { + // Skip properties that can't be accessed + continue; + } + } + + return plainObj; +} diff --git a/arch/.config/quickshell/noctalia-shell/Helpers/sha256.js b/arch/.config/quickshell/noctalia-shell/Helpers/sha256.js new file mode 100644 index 0000000..39f255d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Helpers/sha256.js @@ -0,0 +1,192 @@ +function sha256(message) { + // SHA-256 constants + const K = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + ]; + + // Initial hash values + let h0 = 0x6a09e667; + let h1 = 0xbb67ae85; + let h2 = 0x3c6ef372; + let h3 = 0xa54ff53a; + let h4 = 0x510e527f; + let h5 = 0x9b05688c; + let h6 = 0x1f83d9ab; + let h7 = 0x5be0cd19; + + // Convert string to UTF-8 bytes manually + const msgBytes = stringToUtf8Bytes(message); + const msgLength = msgBytes.length; + const bitLength = msgLength * 8; + + // Calculate padding + // Message + 1 bit (0x80) + padding zeros + 8 bytes for length = multiple of 64 bytes + const totalBitsNeeded = bitLength + 1 + 64; // message bits + padding bit + 64-bit length + const totalBytesNeeded = Math.ceil(totalBitsNeeded / 8); + const paddedLength = Math.ceil(totalBytesNeeded / 64) * 64; // Round up to multiple of 64 + + const paddedMsg = new Array(paddedLength).fill(0); + + // Copy original message + for (let i = 0; i < msgLength; i++) { + paddedMsg[i] = msgBytes[i]; + } + + // Add padding bit (0x80 = 10000000 in binary) + paddedMsg[msgLength] = 0x80; + + // Add length as 64-bit big-endian integer at the end + // JavaScript numbers are not precise enough for 64-bit integers, so we handle high/low separately + const highBits = Math.floor(bitLength / 0x100000000); + const lowBits = bitLength % 0x100000000; + + // Write 64-bit length in big-endian format + paddedMsg[paddedLength - 8] = (highBits >>> 24) & 0xFF; + paddedMsg[paddedLength - 7] = (highBits >>> 16) & 0xFF; + paddedMsg[paddedLength - 6] = (highBits >>> 8) & 0xFF; + paddedMsg[paddedLength - 5] = highBits & 0xFF; + paddedMsg[paddedLength - 4] = (lowBits >>> 24) & 0xFF; + paddedMsg[paddedLength - 3] = (lowBits >>> 16) & 0xFF; + paddedMsg[paddedLength - 2] = (lowBits >>> 8) & 0xFF; + paddedMsg[paddedLength - 1] = lowBits & 0xFF; + + // Process message in 512-bit (64-byte) chunks + for (let chunk = 0; chunk < paddedLength; chunk += 64) { + const w = new Array(64); + + // Break chunk into sixteen 32-bit big-endian words + for (let i = 0; i < 16; i++) { + const offset = chunk + i * 4; + w[i] = (paddedMsg[offset] << 24) | + (paddedMsg[offset + 1] << 16) | + (paddedMsg[offset + 2] << 8) | + paddedMsg[offset + 3]; + // Ensure unsigned 32-bit + w[i] = w[i] >>> 0; + } + + // Extend the sixteen 32-bit words into sixty-four 32-bit words + for (let i = 16; i < 64; i++) { + const s0 = rightRotate(w[i - 15], 7) ^ rightRotate(w[i - 15], 18) ^ (w[i - 15] >>> 3); + const s1 = rightRotate(w[i - 2], 17) ^ rightRotate(w[i - 2], 19) ^ (w[i - 2] >>> 10); + w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0; + } + + // Initialize working variables for this chunk + let a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7; + + // Main loop + for (let i = 0; i < 64; i++) { + const S1 = rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25); + const ch = (e & f) ^ (~e & g); + const temp1 = (h + S1 + ch + K[i] + w[i]) >>> 0; + const S0 = rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22); + const maj = (a & b) ^ (a & c) ^ (b & c); + const temp2 = (S0 + maj) >>> 0; + + h = g; + g = f; + f = e; + e = (d + temp1) >>> 0; + d = c; + c = b; + b = a; + a = (temp1 + temp2) >>> 0; + } + + // Add this chunk's hash to result so far + h0 = (h0 + a) >>> 0; + h1 = (h1 + b) >>> 0; + h2 = (h2 + c) >>> 0; + h3 = (h3 + d) >>> 0; + h4 = (h4 + e) >>> 0; + h5 = (h5 + f) >>> 0; + h6 = (h6 + g) >>> 0; + h7 = (h7 + h) >>> 0; + } + + // Produce the final hash value as a hex string + return [h0, h1, h2, h3, h4, h5, h6, h7] + .map(h => h.toString(16).padStart(8, '0')) + .join(''); +} + +function stringToUtf8Bytes(str) { + const bytes = []; + for (let i = 0; i < str.length; i++) { + let code = str.charCodeAt(i); + + if (code < 0x80) { + // 1-byte character (ASCII) + bytes.push(code); + } else if (code < 0x800) { + // 2-byte character + bytes.push(0xC0 | (code >> 6)); + bytes.push(0x80 | (code & 0x3F)); + } else if (code < 0xD800 || code > 0xDFFF) { + // 3-byte character (not surrogate) + bytes.push(0xE0 | (code >> 12)); + bytes.push(0x80 | ((code >> 6) & 0x3F)); + bytes.push(0x80 | (code & 0x3F)); + } else { + // 4-byte character (surrogate pair) + i++; // Move to next character + const code2 = str.charCodeAt(i); + const codePoint = 0x10000 + (((code & 0x3FF) << 10) | (code2 & 0x3FF)); + bytes.push(0xF0 | (codePoint >> 18)); + bytes.push(0x80 | ((codePoint >> 12) & 0x3F)); + bytes.push(0x80 | ((codePoint >> 6) & 0x3F)); + bytes.push(0x80 | (codePoint & 0x3F)); + } + } + return bytes; +} + +function rightRotate(value, amount) { + return ((value >>> amount) | (value << (32 - amount))) >>> 0; +} + +// Test function to verify implementation +// function testSHA256() { +// const tests = [ +// { +// input: "", +// expected: +// "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", +// }, +// { +// input: "Hello World", +// expected: +// "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e", +// }, +// { +// input: "abc", +// expected: +// "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", +// }, +// { +// input: "The quick brown fox jumps over the lazy dog", +// expected: +// "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592", +// }, +// ]; + +// console.log("Running SHA-256 tests:"); +// tests.forEach((test, i) => { +// const result = Crypto.sha256(test.input); +// const passed = result === test.expected; +// console.log(`Test ${i + 1}: ${passed ? "PASS" : "FAIL"}`); +// if (!passed) { +// console.log(` Input: "${test.input}"`); +// console.log(` Expected: ${test.expected}`); +// console.log(` Got: ${result}`); +// } +// }); +// } diff --git a/arch/.config/quickshell/noctalia-shell/LICENSE b/arch/.config/quickshell/noctalia-shell/LICENSE new file mode 100644 index 0000000..2bdaed9 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 noctalia-dev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Background/Background.qml b/arch/.config/quickshell/noctalia-shell/Modules/Background/Background.qml new file mode 100644 index 0000000..517de91 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Background/Background.qml @@ -0,0 +1,801 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services.Compositor +import qs.Services.Power +import qs.Services.UI + +Variants { + id: backgroundVariants + model: Quickshell.screens + + delegate: Loader { + + required property ShellScreen modelData + + active: modelData && Settings.data.wallpaper.enabled && (!PowerProfileService.noctaliaPerformanceMode || !Settings.data.noctaliaPerformance.disableWallpaper) + + sourceComponent: PanelWindow { + id: root + + // Internal state management + property string transitionType: "fade" + property real transitionProgress: 0 + property bool isStartupTransition: true + property bool wallpaperReady: false + + visible: wallpaperReady + + readonly property real edgeSmoothness: Settings.data.wallpaper.transitionEdgeSmoothness + readonly property var allTransitions: WallpaperService.allTransitions + readonly property bool transitioning: transitionAnimation.running + + // Wipe direction: 0=left, 1=right, 2=up, 3=down + property real wipeDirection: 0 + + // Disc + property real discCenterX: 0.5 + property real discCenterY: 0.5 + + // Stripe + property real stripesCount: 16 + property real stripesAngle: 0 + + // Pixelate + property real pixelateMaxBlockSize: 64.0 + + // Honeycomb + property real honeycombCellSize: 0.04 + property real honeycombCenterX: 0.5 + property real honeycombCenterY: 0.5 + + // Used to debounce wallpaper changes + property string futureWallpaper: "" + // Track the original wallpaper path being transitioned to (before caching) + property string transitioningToOriginalPath: "" + + // Fillmode default is "crop" + property real fillMode: WallpaperService.getFillModeUniform() + property vector4d fillColor: Qt.vector4d(Settings.data.wallpaper.fillColor.r, Settings.data.wallpaper.fillColor.g, Settings.data.wallpaper.fillColor.b, 1.0) + + // Solid color mode - track whether current/next are solid colors + property bool isSolid1: false + property bool isSolid2: false + property color _solidColor1: Settings.data.wallpaper.solidColor + property color _solidColor2: Settings.data.wallpaper.solidColor + property vector4d solidColor1: Qt.vector4d(_solidColor1.r, _solidColor1.g, _solidColor1.b, 1.0) + property vector4d solidColor2: Qt.vector4d(_solidColor2.r, _solidColor2.g, _solidColor2.b, 1.0) + + Component.onCompleted: setWallpaperInitial() + + Component.onDestruction: { + transitionAnimation.stop(); + startupTransitionTimer.stop(); + debounceTimer.stop(); + shaderLoader.active = false; + currentWallpaper.source = ""; + nextWallpaper.source = ""; + } + + Connections { + target: Settings.data.wallpaper + function onFillModeChanged() { + fillMode = WallpaperService.getFillModeUniform(); + } + } + + // External state management + Connections { + target: WallpaperService + function onWallpaperChanged(screenName, path) { + if (screenName === modelData.name) { + requestPreprocessedWallpaper(path); + } + } + } + + Connections { + target: CompositorService + function onDisplayScalesChanged() { + if (!WallpaperService.isInitialized) { + return; + } + + const currentPath = WallpaperService.getWallpaper(modelData.name); + if (!currentPath || WallpaperService.isSolidColorPath(currentPath)) { + return; + } + + if (isStartupTransition) { + // During startup, just ensure the correct cache exists without visual changes + const compositorScale = CompositorService.getDisplayScale(modelData.name); + const targetWidth = Math.round(modelData.width * compositorScale); + const targetHeight = Math.round(modelData.height * compositorScale); + ImageCacheService.getLarge(currentPath, targetWidth, targetHeight, function (cachedPath, success) { + WallpaperService.wallpaperProcessingComplete(modelData.name, currentPath, success ? cachedPath : ""); + }); + return; + } + + requestPreprocessedWallpaper(currentPath); + } + } + + color: "transparent" + screen: modelData + WlrLayershell.layer: WlrLayer.Background + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.namespace: "noctalia-wallpaper-" + (screen?.name || "unknown") + + anchors { + bottom: true + top: true + right: true + left: true + } + + Timer { + id: debounceTimer + interval: 333 + running: false + repeat: false + onTriggered: changeWallpaper() + } + + // Delay startup transition to ensure the compositor has mapped the window + Timer { + id: startupTransitionTimer + interval: 100 + running: false + repeat: false + onTriggered: _executeStartupTransition() + } + + Image { + id: currentWallpaper + + source: "" + smooth: true + mipmap: false + visible: false + cache: true // Cached so Overview can share the same texture + asynchronous: true + onStatusChanged: { + if (status === Image.Error) { + Logger.w("Current wallpaper failed to load:", source); + } else if (status === Image.Ready && !wallpaperReady) { + wallpaperReady = true; + } + } + } + + Image { + id: nextWallpaper + + property bool pendingTransition: false + + source: "" + smooth: true + mipmap: false + visible: false + cache: false // Not cached - temporary during transitions + asynchronous: true + onStatusChanged: { + if (status === Image.Error) { + Logger.w("Next wallpaper failed to load:", source); + pendingTransition = false; + } else if (status === Image.Ready) { + if (!wallpaperReady) { + wallpaperReady = true; + } + if (pendingTransition) { + pendingTransition = false; + currentWallpaper.asynchronous = false; + transitionAnimation.start(); + } + } + } + } + + // Dynamic shader loader - only loads the active transition shader + Loader { + id: shaderLoader + anchors.fill: parent + active: true + + sourceComponent: { + switch (transitionType) { + case "wipe": + return wipeShaderComponent; + case "disc": + return discShaderComponent; + case "stripes": + return stripesShaderComponent; + case "pixelate": + return pixelateShaderComponent; + case "honeycomb": + return honeycombShaderComponent; + case "fade": + case "none": + default: + return fadeShaderComponent; + } + } + } + + // Fade or None transition shader component + Component { + id: fadeShaderComponent + ShaderEffect { + anchors.fill: parent + + property variant source1: currentWallpaper + property variant source2: nextWallpaper.status === Image.Ready ? nextWallpaper : currentWallpaper.status === Image.Ready ? nextWallpaper : currentWallpaper + property real progress: root.transitionProgress + + // Fill mode properties + property real fillMode: root.fillMode + property vector4d fillColor: root.fillColor + property real imageWidth1: source1.sourceSize.width + property real imageHeight1: source1.sourceSize.height + property real imageWidth2: source2.sourceSize.width + property real imageHeight2: source2.sourceSize.height + property real screenWidth: width + property real screenHeight: height + + // Solid color mode + property real isSolid1: root.isSolid1 ? 1.0 : 0.0 + property real isSolid2: root.isSolid2 ? 1.0 : 0.0 + property vector4d solidColor1: root.solidColor1 + property vector4d solidColor2: root.solidColor2 + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wp_fade.frag.qsb") + } + } + + // Wipe transition shader component + Component { + id: wipeShaderComponent + ShaderEffect { + anchors.fill: parent + + property variant source1: currentWallpaper + property variant source2: nextWallpaper.status === Image.Ready ? nextWallpaper : currentWallpaper + property real progress: root.transitionProgress + property real smoothness: root.edgeSmoothness + property real direction: root.wipeDirection + + // Fill mode properties + property real fillMode: root.fillMode + property vector4d fillColor: root.fillColor + property real imageWidth1: source1.sourceSize.width + property real imageHeight1: source1.sourceSize.height + property real imageWidth2: source2.sourceSize.width + property real imageHeight2: source2.sourceSize.height + property real screenWidth: width + property real screenHeight: height + + // Solid color mode + property real isSolid1: root.isSolid1 ? 1.0 : 0.0 + property real isSolid2: root.isSolid2 ? 1.0 : 0.0 + property vector4d solidColor1: root.solidColor1 + property vector4d solidColor2: root.solidColor2 + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wp_wipe.frag.qsb") + } + } + + // Disc reveal transition shader component + Component { + id: discShaderComponent + ShaderEffect { + anchors.fill: parent + + property variant source1: currentWallpaper + property variant source2: nextWallpaper.status === Image.Ready ? nextWallpaper : currentWallpaper + property real progress: root.transitionProgress + property real smoothness: root.edgeSmoothness + property real aspectRatio: root.width / root.height + property real centerX: root.discCenterX + property real centerY: root.discCenterY + + // Fill mode properties + property real fillMode: root.fillMode + property vector4d fillColor: root.fillColor + property real imageWidth1: source1.sourceSize.width + property real imageHeight1: source1.sourceSize.height + property real imageWidth2: source2.sourceSize.width + property real imageHeight2: source2.sourceSize.height + property real screenWidth: width + property real screenHeight: height + + // Solid color mode + property real isSolid1: root.isSolid1 ? 1.0 : 0.0 + property real isSolid2: root.isSolid2 ? 1.0 : 0.0 + property vector4d solidColor1: root.solidColor1 + property vector4d solidColor2: root.solidColor2 + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wp_disc.frag.qsb") + } + } + + // Diagonal stripes transition shader component + Component { + id: stripesShaderComponent + ShaderEffect { + anchors.fill: parent + + property variant source1: currentWallpaper + property variant source2: nextWallpaper.status === Image.Ready ? nextWallpaper : currentWallpaper + property real progress: root.transitionProgress + property real smoothness: root.edgeSmoothness + property real aspectRatio: root.width / root.height + property real stripeCount: root.stripesCount + property real angle: root.stripesAngle + + // Fill mode properties + property real fillMode: root.fillMode + property vector4d fillColor: root.fillColor + property real imageWidth1: source1.sourceSize.width + property real imageHeight1: source1.sourceSize.height + property real imageWidth2: source2.sourceSize.width + property real imageHeight2: source2.sourceSize.height + property real screenWidth: width + property real screenHeight: height + + // Solid color mode + property real isSolid1: root.isSolid1 ? 1.0 : 0.0 + property real isSolid2: root.isSolid2 ? 1.0 : 0.0 + property vector4d solidColor1: root.solidColor1 + property vector4d solidColor2: root.solidColor2 + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wp_stripes.frag.qsb") + } + } + + // Pixelate transition shader component + Component { + id: pixelateShaderComponent + ShaderEffect { + anchors.fill: parent + + property variant source1: currentWallpaper + property variant source2: nextWallpaper.status === Image.Ready ? nextWallpaper : currentWallpaper + property real progress: root.transitionProgress + property real maxBlockSize: root.pixelateMaxBlockSize + + // Fill mode properties + property real fillMode: root.fillMode + property vector4d fillColor: root.fillColor + property real imageWidth1: source1.sourceSize.width + property real imageHeight1: source1.sourceSize.height + property real imageWidth2: source2.sourceSize.width + property real imageHeight2: source2.sourceSize.height + property real screenWidth: width + property real screenHeight: height + + // Solid color mode + property real isSolid1: root.isSolid1 ? 1.0 : 0.0 + property real isSolid2: root.isSolid2 ? 1.0 : 0.0 + property vector4d solidColor1: root.solidColor1 + property vector4d solidColor2: root.solidColor2 + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wp_pixelate.frag.qsb") + } + } + + // Honeycomb transition shader component + Component { + id: honeycombShaderComponent + ShaderEffect { + anchors.fill: parent + + property variant source1: currentWallpaper + property variant source2: nextWallpaper.status === Image.Ready ? nextWallpaper : currentWallpaper + property real progress: root.transitionProgress + property real cellSize: root.honeycombCellSize + property real centerX: root.honeycombCenterX + property real centerY: root.honeycombCenterY + property real aspectRatio: root.width / root.height + + // Fill mode properties + property real fillMode: root.fillMode + property vector4d fillColor: root.fillColor + property real imageWidth1: source1.sourceSize.width + property real imageHeight1: source1.sourceSize.height + property real imageWidth2: source2.sourceSize.width + property real imageHeight2: source2.sourceSize.height + property real screenWidth: width + property real screenHeight: height + + // Solid color mode + property real isSolid1: root.isSolid1 ? 1.0 : 0.0 + property real isSolid2: root.isSolid2 ? 1.0 : 0.0 + property vector4d solidColor1: root.solidColor1 + property vector4d solidColor2: root.solidColor2 + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wp_honeycomb.frag.qsb") + } + } + + // Animation for the transition progress + NumberAnimation { + id: transitionAnimation + target: root + property: "transitionProgress" + from: 0.0 + to: 1.0 + duration: Settings.data.wallpaper.transitionDuration + easing.type: Easing.InOutCubic + onFinished: { + // Mark startup complete now that the animation has finished, + // so displayScalesChanged doesn't trigger a duplicate transition. + if (isStartupTransition) { + isStartupTransition = false; + } + + // Clear the tracking of what we're transitioning to + transitioningToOriginalPath = ""; + + // Transfer solid color state from next to current + isSolid1 = isSolid2; + _solidColor1 = _solidColor2; + + // Assign new image to current BEFORE clearing to prevent flicker + const tempSource = nextWallpaper.source; + currentWallpaper.source = tempSource; + transitionProgress = 0.0; + + // Now clear nextWallpaper after currentWallpaper has the new source + // Force complete cleanup to free texture memory + Qt.callLater(() => { + nextWallpaper.source = ""; + isSolid2 = false; + Qt.callLater(() => { + currentWallpaper.asynchronous = true; + }); + }); + } + } + + // Normalize a path (string or QUrl) to a plain string for comparison. + // QML Image.source is a url type; comparing url === string can return + // false even for identical paths. This converts both sides to strings. + function _pathStr(p) { + var s = p.toString(); + // QUrl.toString() may add a file:// prefix for local paths + if (s.startsWith("file://")) { + return s.substring(7); + } + return s; + } + + // ------------------------------------------------------ + function setWallpaperInitial() { + // On startup, defer assigning wallpaper until the services are ready + if (!WallpaperService || !WallpaperService.isInitialized) { + Qt.callLater(setWallpaperInitial); + return; + } + if (!ImageCacheService || !ImageCacheService.initialized) { + Qt.callLater(setWallpaperInitial); + return; + } + + // Check if we're in solid color mode + if (Settings.data.wallpaper.useSolidColor) { + var solidPath = WallpaperService.createSolidColorPath(Settings.data.wallpaper.solidColor.toString()); + futureWallpaper = solidPath; + performStartupTransition(); + WallpaperService.wallpaperProcessingComplete(modelData.name, solidPath, ""); + return; + } + + const wallpaperPath = WallpaperService.getWallpaper(modelData.name); + + // Check if the path is a solid color + if (WallpaperService.isSolidColorPath(wallpaperPath)) { + futureWallpaper = wallpaperPath; + performStartupTransition(); + WallpaperService.wallpaperProcessingComplete(modelData.name, wallpaperPath, ""); + return; + } + + const compositorScale = CompositorService.getDisplayScale(modelData.name); + const targetWidth = Math.round(modelData.width * compositorScale); + const targetHeight = Math.round(modelData.height * compositorScale); + + ImageCacheService.getLarge(wallpaperPath, targetWidth, targetHeight, function (cachedPath, success) { + if (success) { + futureWallpaper = cachedPath; + } else { + // Fallback to original + futureWallpaper = wallpaperPath; + } + performStartupTransition(); + // Pass cached path for blur optimization (already resized) + WallpaperService.wallpaperProcessingComplete(modelData.name, wallpaperPath, success ? cachedPath : ""); + }); + } + + // ------------------------------------------------------ + function requestPreprocessedWallpaper(originalPath) { + // If we're already transitioning to this exact wallpaper, skip the request + if (transitioning && originalPath === transitioningToOriginalPath) { + return; + } + + // Store the original path we're working towards + transitioningToOriginalPath = originalPath; + + // Handle solid color paths - no preprocessing needed + if (WallpaperService.isSolidColorPath(originalPath)) { + futureWallpaper = originalPath; + debounceTimer.restart(); + WallpaperService.wallpaperProcessingComplete(modelData.name, originalPath, ""); + return; + } + + const compositorScale = CompositorService.getDisplayScale(modelData.name); + const targetWidth = Math.round(modelData.width * compositorScale); + const targetHeight = Math.round(modelData.height * compositorScale); + + ImageCacheService.getLarge(originalPath, targetWidth, targetHeight, function (cachedPath, success) { + // Ignore stale callback if we've moved on to a different wallpaper + if (originalPath !== transitioningToOriginalPath) { + return; + } + if (success) { + futureWallpaper = cachedPath; + } else { + futureWallpaper = originalPath; + } + + // Skip transition if the resolved path matches what's already displayed + if (_pathStr(futureWallpaper) === _pathStr(currentWallpaper.source)) { + transitioningToOriginalPath = ""; + WallpaperService.wallpaperProcessingComplete(modelData.name, originalPath, success ? cachedPath : ""); + return; + } + + debounceTimer.restart(); + // Pass cached path for blur optimization (already resized) + WallpaperService.wallpaperProcessingComplete(modelData.name, originalPath, success ? cachedPath : ""); + }); + } + + // ------------------------------------------------------ + function setWallpaperImmediate(source) { + transitionAnimation.stop(); + transitionProgress = 0.0; + + // Check if this is a solid color + var isSolidSource = WallpaperService.isSolidColorPath(source); + isSolid1 = isSolidSource; + isSolid2 = false; + + if (isSolidSource) { + var colorStr = WallpaperService.getSolidColor(source); + _solidColor1 = colorStr; + // Clear image sources for memory efficiency + currentWallpaper.source = ""; + nextWallpaper.source = ""; + if (!wallpaperReady) { + wallpaperReady = true; + } + return; + } + + // Clear nextWallpaper completely to free texture memory + nextWallpaper.source = ""; + nextWallpaper.sourceSize = undefined; + + currentWallpaper.source = ""; + + Qt.callLater(() => { + currentWallpaper.source = source; + }); + } + + // ------------------------------------------------------ + function setWallpaperWithTransition(source) { + // Check if this is a solid color transition + var isSolidSource = WallpaperService.isSolidColorPath(source); + + // For solid colors, check if we're already showing the same color + if (isSolidSource && isSolid1) { + var newColor = WallpaperService.getSolidColor(source); + if (newColor === _solidColor1.toString()) { + return; + } + } + + // For images, check if source matches (use _pathStr to normalize url vs string) + if (!isSolidSource && _pathStr(source) === _pathStr(currentWallpaper.source)) { + return; + } + + // If we're already transitioning to this same wallpaper, skip + if (transitioning && source === nextWallpaper.source) { + return; + } + + if (transitioning) { + // We are interrupting a transition - handle cleanup properly + transitionAnimation.stop(); + transitionProgress = 0; + + // Transfer next state to current + isSolid1 = isSolid2; + _solidColor1 = _solidColor2; + const newCurrentSource = nextWallpaper.source; + currentWallpaper.source = newCurrentSource; + + // Now clear nextWallpaper after current has the new source + Qt.callLater(() => { + nextWallpaper.source = ""; + isSolid2 = false; + + // Now set the next wallpaper after a brief delay + Qt.callLater(() => { + _startTransitionTo(source, isSolidSource); + }); + }); + return; + } + + _startTransitionTo(source, isSolidSource); + } + + // Helper to start transition to a new source + function _startTransitionTo(source, isSolidSource) { + isSolid2 = isSolidSource; + + if (isSolidSource) { + var colorStr = WallpaperService.getSolidColor(source); + _solidColor2 = colorStr; + // No image to load, start transition immediately + nextWallpaper.source = ""; + if (!wallpaperReady) { + wallpaperReady = true; + } + currentWallpaper.asynchronous = false; + transitionAnimation.start(); + } else { + nextWallpaper.source = source; + if (nextWallpaper.status === Image.Ready) { + if (!wallpaperReady) { + wallpaperReady = true; + } + currentWallpaper.asynchronous = false; + transitionAnimation.start(); + } else { + nextWallpaper.pendingTransition = true; + } + } + } + + // ------------------------------------------------------ + // Main method that actually trigger the wallpaper change + function changeWallpaper() { + // Pick a transition from the user's selected list + var selected = Settings.data.wallpaper.transitionType; + if (!selected || selected.length === 0) { + transitionType = "none"; + } else if (selected.length === 1) { + transitionType = selected[0]; + } else { + var index = Math.floor(Math.random() * selected.length); + transitionType = selected[index]; + } + + // Ensure the transition type really exists + if (transitionType !== "none" && !allTransitions.includes(transitionType)) { + transitionType = "fade"; + } + + //Logger.i("Background", "New wallpaper: ", futureWallpaper, "On:", modelData.name, "Transition:", transitionType) + switch (transitionType) { + case "none": + setWallpaperImmediate(futureWallpaper); + break; + case "wipe": + wipeDirection = Math.random() * 4; + setWallpaperWithTransition(futureWallpaper); + break; + case "disc": + discCenterX = Math.random(); + discCenterY = Math.random(); + setWallpaperWithTransition(futureWallpaper); + break; + case "stripes": + stripesCount = Math.round(Math.random() * 20 + 4); + stripesAngle = Math.random() * 360; + setWallpaperWithTransition(futureWallpaper); + break; + case "pixelate": + pixelateMaxBlockSize = Math.round(Math.random() * 80 + 32); + setWallpaperWithTransition(futureWallpaper); + break; + case "honeycomb": + honeycombCellSize = Math.random() * 0.04 + 0.02; + honeycombCenterX = Math.random(); + honeycombCenterY = Math.random(); + setWallpaperWithTransition(futureWallpaper); + break; + default: + setWallpaperWithTransition(futureWallpaper); + break; + } + } + + // ------------------------------------------------------ + // Dedicated function for startup animation + // Sets up transition params, then defers the actual animation + // to allow the compositor time to map the window. + function performStartupTransition() { + if (Settings.data.wallpaper.skipStartupTransition) { + setWallpaperImmediate(futureWallpaper); + isStartupTransition = false; + return; + } + + // Pick a transition from the user's selected list + var selected = Settings.data.wallpaper.transitionType; + if (!selected || selected.length === 0) { + transitionType = "none"; + } else if (selected.length === 1) { + transitionType = selected[0]; + } else { + var index = Math.floor(Math.random() * selected.length); + transitionType = selected[index]; + } + + // Ensure the transition type really exists + if (transitionType !== "none" && !allTransitions.includes(transitionType)) { + transitionType = "fade"; + } + + // Pre-compute per-type params so the shader is ready + switch (transitionType) { + case "wipe": + wipeDirection = Math.random() * 4; + break; + case "disc": + // Force center origin for elegant startup animation + discCenterX = 0.5; + discCenterY = 0.5; + break; + case "stripes": + stripesCount = Math.round(Math.random() * 20 + 4); + stripesAngle = Math.random() * 360; + break; + case "pixelate": + pixelateMaxBlockSize = 64.0; + break; + case "honeycomb": + honeycombCellSize = 0.04; + honeycombCenterX = 0.5; + honeycombCenterY = 0.5; + break; + } + + // Defer the actual transition start so the compositor can map the window + startupTransitionTimer.start(); + } + + // Actually kick off the startup transition after the delay + function _executeStartupTransition() { + if (transitionType === "none") { + setWallpaperImmediate(futureWallpaper); + isStartupTransition = false; + } else { + // isStartupTransition stays true until transitionAnimation.onFinished + // to prevent displayScalesChanged from triggering a duplicate transition. + setWallpaperWithTransition(futureWallpaper); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Background/FadeOverlay.qml b/arch/.config/quickshell/noctalia-shell/Modules/Background/FadeOverlay.qml new file mode 100644 index 0000000..2274716 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Background/FadeOverlay.qml @@ -0,0 +1,52 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services.Power + +/** +* IdleFadeOverlay โ€” full-screen fade-to-black shown before each idle action. +* +* A single Loader wraps a Variants so per-screen windows only exist while +* a fade is in progress, keeping VRAM usage at zero at rest. +* +* Any mouse movement cancels the fade and unloads the windows immediately. +*/ +Item { + id: root + + Loader { + active: IdleService.fadePending !== "" + asynchronous: false + + sourceComponent: Variants { + model: Quickshell.screens + delegate: PanelWindow { + id: overlay + required property ShellScreen modelData + screen: modelData + + color: Qt.rgba(0, 0, 0, 0) + + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.namespace: "noctalia-fade-overlay" + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.anchors { + top: true + bottom: true + left: true + right: true + } + + ColorAnimation on color { + running: true + from: Qt.rgba(0, 0, 0, 0) + to: Qt.rgba(0, 0, 0, 1) + duration: IdleService.fadeDuration * 1000 + easing.type: Easing.InQuad + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Background/Overview.qml b/arch/.config/quickshell/noctalia-shell/Modules/Background/Overview.qml new file mode 100644 index 0000000..579fe0d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Background/Overview.qml @@ -0,0 +1,121 @@ +import QtQuick +import QtQuick.Effects +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services.Compositor +import qs.Services.Power +import qs.Services.UI + +Loader { + active: CompositorService.isNiri && Settings.data.wallpaper.enabled && Settings.data.wallpaper.overviewEnabled && (!PowerProfileService.noctaliaPerformanceMode || !Settings.data.noctaliaPerformance.disableWallpaper) + + sourceComponent: Variants { + model: Quickshell.screens + + delegate: PanelWindow { + id: panelWindow + + required property ShellScreen modelData + property string wallpaper: "" + property string preprocessedWallpaper: "" // Pre-resized wallpaper from Background.qml + property bool isSolidColor: Settings.data.wallpaper.useSolidColor + property color solidColor: Settings.data.wallpaper.solidColor + property color tintColor: Settings.data.colorSchemes.darkMode ? Color.mSurface : Color.mOnSurface + + visible: wallpaper !== "" || isSolidColor + + Component.onCompleted: { + if (modelData) { + Logger.d("Overview", "Loading overview for Niri on", modelData.name); + } + } + + Component.onDestruction: { + bgImage.source = ""; + } + + // External state management - wait for wallpaper processing to complete + // Reuses the same cached image as Background.qml for memory efficiency + GPU blur + Connections { + target: WallpaperService + function onWallpaperProcessingComplete(screenName, path, cachedPath) { + if (screenName === modelData.name) { + preprocessedWallpaper = cachedPath || ""; + wallpaper = path; + } + } + } + + // Handle wallpaper changes (solid color detection) + onWallpaperChanged: { + if (!wallpaper) + return; + + // Check if this is a solid color path + if (WallpaperService.isSolidColorPath(wallpaper)) { + isSolidColor = true; + var colorStr = WallpaperService.getSolidColor(wallpaper); + solidColor = colorStr; + return; + } + + isSolidColor = false; + } + + color: "transparent" + screen: modelData + WlrLayershell.layer: WlrLayer.Background + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.namespace: "noctalia-overview-" + (screen?.name || "unknown") + + anchors { + top: true + bottom: true + right: true + left: true + } + + // Solid color background + Rectangle { + anchors.fill: parent + visible: isSolidColor + color: solidColor + + Rectangle { + anchors.fill: parent + color: tintColor + opacity: Settings.data.wallpaper.overviewTint + } + } + + // Image background with GPU-based blur + Image { + id: bgImage + anchors.fill: parent + visible: !isSolidColor + fillMode: Image.PreserveAspectCrop + source: preprocessedWallpaper || wallpaper + smooth: true + mipmap: false + cache: true // Shares texture with Background's currentWallpaper + asynchronous: true + + layer.enabled: Settings.data.wallpaper.overviewBlur > 0 && !PowerProfileService.noctaliaPerformanceMode + layer.smooth: false + layer.effect: MultiEffect { + blurEnabled: true + blur: Settings.data.wallpaper.overviewBlur + blurMax: 48 + } + + // Tint overlay + Rectangle { + anchors.fill: parent + color: tintColor + opacity: Settings.data.wallpaper.overviewTint + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Bar.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Bar.qml new file mode 100644 index 0000000..51eac97 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Bar.qml @@ -0,0 +1,727 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.UPower +import Quickshell.Wayland +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Modules.Notification +import qs.Modules.Panels.Settings +import qs.Services.Compositor +import qs.Services.Media +import qs.Services.UI +import qs.Widgets + +// Bar Component +Item { + id: root + + // This property will be set by MainScreen + property ShellScreen screen: null + + // Filter widgets to only include those that exist in the registry + // This prevents errors when plugins are missing or widgets are being cleaned up + function filterValidWidgets(widgets: list): list { + if (!widgets) + return []; + return widgets.filter(function (w) { + return w && w.id && BarWidgetRegistry.hasWidget(w.id); + }); + } + + // Hot corner: trigger click on first widget in a section + function triggerFirstWidgetInSection(sectionName: string) { + var widgets = BarService.getWidgetsBySection(sectionName, screen?.name); + for (var i = 0; i < widgets.length; i++) { + var widget = widgets[i]; + if (widget && widget.visible && widget.widgetId !== "Spacer") { + if (typeof widget.clicked === "function") { + widget.clicked(); + } + return; + } + } + } + + // Hot corner: trigger click on last widget in a section + function triggerLastWidgetInSection(sectionName: string) { + var widgets = BarService.getWidgetsBySection(sectionName, screen?.name); + for (var i = widgets.length - 1; i >= 0; i--) { + var widget = widgets[i]; + if (widget && widget.visible && widget.widgetId !== "Spacer") { + if (typeof widget.clicked === "function") { + widget.clicked(); + } + return; + } + } + } + + // Expose bar region for click-through mask + readonly property var barRegion: barContentLoader.item?.children[0] || null + + // Expose the actual bar Item for unified background system + readonly property var barItem: barRegion + + // Bar positioning properties (per-screen) + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" + readonly property bool barFloating: Settings.data.bar.barType === "floating" + + // Bar density (per-screen) + readonly property string barDensity: Settings.getBarDensityForScreen(screen?.name) + + // Bar sizing based on per-screen density + readonly property real barHeight: Style.getBarHeightForDensity(barDensity, barIsVertical) + readonly property real capsuleHeight: Style.getCapsuleHeightForDensity(barDensity, barHeight) + readonly property real barFontSize: Style.getBarFontSizeForDensity(barHeight, capsuleHeight, barIsVertical) + + // Bar widgets (per-screen) - initial configuration + // Note: Updates are handled via Connections to BarService.widgetsRevisionChanged + readonly property var barWidgets: Settings.getBarWidgetsForScreen(screen?.name) + + // Stable ListModels for each section - prevents Repeater recreation on settings changes + property ListModel leftWidgetsModel: ListModel {} + property ListModel centerWidgetsModel: ListModel {} + property ListModel rightWidgetsModel: ListModel {} + + // Guard: set when Bar is destroyed; prevents Qt.callLater callbacks from running + // during/after teardown (avoids SIGSEGV in QV4::Object::insertMember when rapid + // workspace switch causes load/unload overlap with async widget incubation) + property bool _destroyed: false + Component.onDestruction: root._destroyed = true + + // Sync a ListModel with widget data, preserving delegates when only settings change + function syncWidgetModel(model, newWidgets) { + var validWidgets = filterValidWidgets(newWidgets); + + // Build list of current IDs in model + var currentIds = []; + for (var i = 0; i < model.count; i++) { + currentIds.push(model.get(i).id); + } + + // Build list of new IDs + var newIds = validWidgets.map(w => w.id); + + // Check if structure changed (different IDs or order) + var structureChanged = currentIds.length !== newIds.length; + if (!structureChanged) { + for (var i = 0; i < currentIds.length; i++) { + if (currentIds[i] !== newIds[i]) { + structureChanged = true; + break; + } + } + } + + Logger.d("Bar", "syncWidgetModel:", currentIds.join("|"), "โ†’", newIds.join("|"), "changed:", structureChanged); + + if (structureChanged) { + // Rebuild model - IDs changed + model.clear(); + for (var i = 0; i < validWidgets.length; i++) { + model.append(validWidgets[i]); + } + } + // If structure didn't change, delegates are preserved and will read fresh settings + } + + // Sync models when widget revision changes + // Note: We use Connections instead of onBarWidgetsChanged because getBarWidgetsForScreen + // returns the same object reference (Settings.data.bar.widgets) even when content changes, + // so QML won't detect the change via property binding. + Connections { + target: BarService + function onWidgetsRevisionChanged() { + Logger.d("Bar", "onWidgetsRevisionChanged, revision:", BarService.widgetsRevision, "screen:", root.screen?.name); + Qt.callLater(root._syncFromRevision); + } + } + + function _syncFromRevision() { + if (root._destroyed) + return; + var widgets = Settings.getBarWidgetsForScreen(screen?.name); + if (widgets) { + syncWidgetModel(leftWidgetsModel, widgets.left); + syncWidgetModel(centerWidgetsModel, widgets.center); + syncWidgetModel(rightWidgetsModel, widgets.right); + } + } + + // Initialize models โ€” deferred to next event-loop tick via Qt.callLater to avoid + // re-entrant incubation: Component.onCompleted fires during QQmlObjectCreator::finalize, + // and ListModel.append synchronously creates Repeater delegates whose own finalization + // can corrupt the V4 heap (SIGSEGV in QV4::Object::insertMember). + Component.onCompleted: { + Logger.d("Bar", "Bar Component.onCompleted for screen:", screen?.name); + Qt.callLater(root._initModels); + } + + function _initModels() { + if (root._destroyed) + return; + var widgets = Settings.getBarWidgetsForScreen(screen?.name); + if (widgets) { + syncWidgetModel(leftWidgetsModel, widgets.left); + syncWidgetModel(centerWidgetsModel, widgets.center); + syncWidgetModel(rightWidgetsModel, widgets.right); + } + } + + // Fill the parent (the Loader) + anchors.fill: parent + + // Register bar when screen becomes available + onScreenChanged: { + if (screen && screen.name) { + Logger.d("Bar", "Bar screen set to:", screen.name); + Logger.d("Bar", " Position:", barPosition, "Floating:", barFloating); + BarService.registerBar(screen.name); + } + } + + // Wait for screen to be set before loading bar content + Loader { + id: barContentLoader + anchors.fill: parent + active: { + if (root.screen === null || root.screen === undefined) { + return false; + } + + var monitors = Settings.data.bar.monitors || []; + var result = monitors.length === 0 || monitors.includes(root.screen.name); + return result; + } + + sourceComponent: Item { + anchors.fill: parent + + // Bar container - Content + Item { + id: bar + + // Wheel scroll handling (empty bar area) + property int barWheelAccumulatedDelta: 0 + property bool barWheelCooldown: false + readonly property string barWheelAction: { + return Settings.data.bar.mouseWheelAction || "none"; + } + readonly property string barRightClickAction: Settings.data.bar.rightClickAction || "controlCenter" + + // Position and size the bar content based on orientation + x: (root.barPosition === "right") ? (parent.width - root.barHeight) : 0 + y: (root.barPosition === "bottom") ? (parent.height - root.barHeight) : 0 + width: root.barIsVertical ? root.barHeight : parent.width + height: root.barIsVertical ? parent.height : root.barHeight + + // Corner states for new unified background system + // State -1: No radius (flat/square corner) + // State 0: Normal (inner curve) + // State 1: Horizontal inversion (outer curve on X-axis) + // State 2: Vertical inversion (outer curve on Y-axis) + readonly property int topLeftCornerState: { + // Floating bar: always simple rounded corners + if (barFloating) + return 0; + // Top bar: top corners against screen edge = no radius + if (barPosition === "top") + return -1; + // Left bar: top-left against screen edge = no radius + if (barPosition === "left") + return -1; + // Bottom/Right bar with outerCorners: inverted corner + if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "right")) { + return barIsVertical ? 1 : 2; // horizontal invert for vertical bars, vertical invert for horizontal + } + // No outerCorners = square + return -1; + } + + readonly property int topRightCornerState: { + // Floating bar: always simple rounded corners + if (barFloating) + return 0; + // Top bar: top corners against screen edge = no radius + if (barPosition === "top") + return -1; + // Right bar: top-right against screen edge = no radius + if (barPosition === "right") + return -1; + // Bottom/Left bar with outerCorners: inverted corner + if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "left")) { + return barIsVertical ? 1 : 2; + } + // No outerCorners = square + return -1; + } + + readonly property int bottomLeftCornerState: { + // Floating bar: always simple rounded corners + if (barFloating) + return 0; + // Bottom bar: bottom corners against screen edge = no radius + if (barPosition === "bottom") + return -1; + // Left bar: bottom-left against screen edge = no radius + if (barPosition === "left") + return -1; + // Top/Right bar with outerCorners: inverted corner + if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "right")) { + return barIsVertical ? 1 : 2; + } + // No outerCorners = square + return -1; + } + + readonly property int bottomRightCornerState: { + // Floating bar: always simple rounded corners + if (barFloating) + return 0; + // Bottom bar: bottom corners against screen edge = no radius + if (barPosition === "bottom") + return -1; + // Right bar: bottom-right against screen edge = no radius + if (barPosition === "right") + return -1; + // Top/Left bar with outerCorners: inverted corner + if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "left")) { + return barIsVertical ? 1 : 2; + } + // No outerCorners = square + return -1; + } + + function isPointOverWidget(xPos, yPos) { + var widgets = BarService.getAllWidgetInstances(null, screen.name); + for (var i = 0; i < widgets.length; i++) { + var widget = widgets[i]; + if (!widget || !widget.visible || widget.widgetId === "Spacer") { + continue; + } + var localPos = mapToItem(widget, xPos, yPos); + + if (root.barIsVertical) { + if (localPos.y >= -Style.marginS && localPos.y <= widget.height + Style.marginS) { + return true; + } + } else { + if (localPos.x >= -Style.marginS && localPos.x <= widget.width + Style.marginS) { + return true; + } + } + } + return false; + } + + function switchWorkspaceByOffset(offset) { + if (!root.screen || CompositorService.workspaces.count === 0) + return; + + var screenName = root.screen.name.toLowerCase(); + var candidates = []; + for (var i = 0; i < CompositorService.workspaces.count; i++) { + var ws = CompositorService.workspaces.get(i); + var matchesScreen = CompositorService.globalWorkspaces || (ws.output && ws.output.toLowerCase() === screenName); + if (matchesScreen) + candidates.push(ws); + } + + if (candidates.length <= 1) + return; + + var current = -1; + for (var j = 0; j < candidates.length; j++) { + if (candidates[j].isFocused) { + current = j; + break; + } + } + if (current < 0) + current = 0; + + var next = current + offset; + if (Settings.data.bar.mouseWheelWrap) { + next = next % candidates.length; + if (next < 0) + next = candidates.length - 1; + } else { + if (next < 0 || next >= candidates.length) + return; + } + + if (next === current) + return; + CompositorService.switchToWorkspace(candidates[next]); + } + + function handleEmptyBarClick(action, followMouse, command, mouse) { + if (action === "none") + return; + if (action === "controlCenter") { + var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen); + controlCenterPanel?.toggle(null, followMouse ? mapToItem(null, mouse.x, mouse.y) : "ControlCenter"); + mouse.accepted = true; + } else if (action === "settings") { + var settingsPanel = PanelService.getPanel("settingsPanel", screen); + settingsPanel?.toggle(null, followMouse ? mapToItem(null, mouse.x, mouse.y) : null); + mouse.accepted = true; + } else if (action === "launcherPanel") { + var launcherPanel = PanelService.getPanel("launcherPanel", screen); + launcherPanel?.toggle(null, followMouse ? mapToItem(null, mouse.x, mouse.y) : null); + mouse.accepted = true; + } else if (action === "command") { + runCustomCommand(command); + mouse.accepted = true; + } + } + + function runCustomCommand(command) { + if (!command || command.trim() === "") + return; + + const processString = "import QtQuick; import Quickshell.Io; Process { command: [\"sh\", \"-lc\", \"\"] }"; + + try { + const processObj = Qt.createQmlObject(processString, root, "BarCommandProcess_" + Date.now()); + processObj.command = ["sh", "-lc", command]; + + processObj.exited.connect(function (exitCode) { + if (exitCode !== 0) { + ToastService.showError(I18n.tr("toast.custom-command-failed.title"), I18n.tr("toast.custom-command-failed.description", { + command: command, + code: exitCode + })); + } + processObj.destroy(); + }); + + processObj.running = true; + } catch (e) { + Logger.e("Bar", "Failed to start custom command:", e); + ToastService.showError(I18n.tr("toast.custom-command-failed.title"), I18n.tr("toast.custom-command-failed.description", { + command: command, + code: "start_error" + })); + } + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton | Qt.MiddleButton + enabled: bar.barRightClickAction !== "none" || Settings.data.bar.middleClickAction !== "none" + hoverEnabled: false + preventStealing: true + onClicked: mouse => { + if (mouse.button === Qt.RightButton) { + if (bar.isPointOverWidget(mouse.x, mouse.y)) + return; + bar.handleEmptyBarClick(bar.barRightClickAction, Settings.data.bar.rightClickFollowMouse, Settings.data.bar.rightClickCommand, mouse); + return; + } + if (mouse.button === Qt.MiddleButton) { + if (bar.isPointOverWidget(mouse.x, mouse.y)) + return; + bar.handleEmptyBarClick(Settings.data.bar.middleClickAction || "none", Settings.data.bar.middleClickFollowMouse, Settings.data.bar.middleClickCommand, mouse); + return; + } + } + } + + // Debounce timer for wheel interactions + Timer { + id: barWheelDebounce + interval: 150 + repeat: false + onTriggered: { + bar.barWheelCooldown = false; + bar.barWheelAccumulatedDelta = 0; + } + } + + // Scroll on empty bar area action + WheelHandler { + id: barWheelHandler + target: bar + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + enabled: bar.barWheelAction !== "none" + + onWheel: function (event) { + if (bar.isPointOverWidget(event.x, event.y)) + return; + + var dy = event.angleDelta.y; + var dx = event.angleDelta.x; + var useDy = Math.abs(dy) >= Math.abs(dx); + var delta = useDy ? dy : dx; + var step = 120; + + if (bar.barWheelAction === "volume") { + if (Settings.data.bar.reverseScroll) + delta *= -1; + + bar.barWheelAccumulatedDelta += delta; + if (bar.barWheelAccumulatedDelta >= step) { + AudioService.increaseVolume(); + bar.barWheelAccumulatedDelta = 0; + event.accepted = true; + } else if (bar.barWheelAccumulatedDelta <= -step) { + AudioService.decreaseVolume(); + bar.barWheelAccumulatedDelta = 0; + event.accepted = true; + } + return; + } + + if (bar.barWheelCooldown) + return; + + bar.barWheelAccumulatedDelta += delta; + if (Math.abs(bar.barWheelAccumulatedDelta) >= step) { + var direction = bar.barWheelAccumulatedDelta > 0 ? -1 : 1; + if (Settings.data.bar.reverseScroll) + direction *= -1; + if (bar.barWheelAction === "workspace") { + bar.switchWorkspaceByOffset(direction); + } else if (bar.barWheelAction === "content") { + CompositorService.scrollWorkspaceContent(direction); + } + bar.barWheelCooldown = true; + barWheelDebounce.restart(); + bar.barWheelAccumulatedDelta = 0; + event.accepted = true; + } + } + } + + Loader { + anchors.fill: parent + sourceComponent: root.barIsVertical ? verticalBarComponent : horizontalBarComponent + } + } + } + } + + // For vertical bars + Component { + id: verticalBarComponent + Item { + anchors.fill: parent + clip: true + + // Top edge hot corner - triggers first widget in left (top) section + MouseArea { + width: parent.width + height: Style.marginS + x: 0 + y: 0 + onClicked: root.triggerFirstWidgetInSection("left") + } + + // Bottom edge hot corner - triggers last widget in right (bottom) section + MouseArea { + width: parent.width + height: Style.marginS + x: 0 + anchors.bottom: parent.bottom + onClicked: root.triggerLastWidgetInSection("right") + } + + // Calculate margin to center widgets vertically within the bar height + readonly property real verticalBarMargin: Math.round((root.barHeight - root.capsuleHeight) / 2) + + // Top section (left widgets) + ColumnLayout { + x: Style.pixelAlignCenter(parent.width, width) + anchors.top: parent.top + anchors.topMargin: verticalBarMargin + Settings.data.bar.contentPadding + spacing: Settings.data.bar.widgetSpacing + + Repeater { + model: root.leftWidgetsModel + delegate: BarWidgetLoader { + required property var model + required property int index + + widgetId: model.id || "" + widgetScreen: root.screen + widgetProps: ({ + "widgetId": model.id, + "section": "left", + "sectionWidgetIndex": index, + "sectionWidgetsCount": root.leftWidgetsModel.count + }) + Layout.alignment: Qt.AlignHCenter + } + } + } + + // Center section (center widgets) + ColumnLayout { + x: Style.pixelAlignCenter(parent.width, width) + anchors.verticalCenter: parent.verticalCenter + spacing: Settings.data.bar.widgetSpacing + + Repeater { + model: root.centerWidgetsModel + delegate: BarWidgetLoader { + required property var model + required property int index + + widgetId: model.id || "" + widgetScreen: root.screen + widgetProps: ({ + "widgetId": model.id, + "section": "center", + "sectionWidgetIndex": index, + "sectionWidgetsCount": root.centerWidgetsModel.count + }) + Layout.alignment: Qt.AlignHCenter + } + } + } + + // Bottom section (right widgets) + ColumnLayout { + x: Style.pixelAlignCenter(parent.width, width) + anchors.bottom: parent.bottom + anchors.bottomMargin: verticalBarMargin + Settings.data.bar.contentPadding + spacing: Settings.data.bar.widgetSpacing + + Repeater { + model: root.rightWidgetsModel + delegate: BarWidgetLoader { + required property var model + required property int index + + widgetId: model.id || "" + widgetScreen: root.screen + widgetProps: ({ + "widgetId": model.id, + "section": "right", + "sectionWidgetIndex": index, + "sectionWidgetsCount": root.rightWidgetsModel.count + }) + Layout.alignment: Qt.AlignHCenter + } + } + } + } + } + + // For horizontal bars + Component { + id: horizontalBarComponent + Item { + anchors.fill: parent + clip: true + + // Left edge hot corner - triggers first widget in left section + MouseArea { + width: Style.marginS + height: parent.height + x: 0 + y: 0 + onClicked: root.triggerFirstWidgetInSection("left") + } + + // Right edge hot corner - triggers last widget in right section + MouseArea { + width: Style.marginS + height: parent.height + anchors.right: parent.right + y: 0 + onClicked: root.triggerLastWidgetInSection("right") + } + + // Calculate margin to center widgets horizontally within the bar height + readonly property real horizontalBarMargin: Math.round((root.barHeight - root.capsuleHeight) / 2) + + // Left Section + RowLayout { + id: leftSection + objectName: "leftSection" + anchors.left: parent.left + anchors.leftMargin: horizontalBarMargin + Settings.data.bar.contentPadding + y: Style.pixelAlignCenter(parent.height, height) + spacing: Settings.data.bar.widgetSpacing + + Repeater { + model: root.leftWidgetsModel + delegate: BarWidgetLoader { + required property var model + required property int index + + widgetId: model.id || "" + widgetScreen: root.screen + widgetProps: ({ + "widgetId": model.id, + "section": "left", + "sectionWidgetIndex": index, + "sectionWidgetsCount": root.leftWidgetsModel.count + }) + Layout.alignment: Qt.AlignVCenter + } + } + } + + // Center Section + RowLayout { + id: centerSection + objectName: "centerSection" + anchors.horizontalCenter: parent.horizontalCenter + y: Style.pixelAlignCenter(parent.height, height) + spacing: Settings.data.bar.widgetSpacing + + Repeater { + model: root.centerWidgetsModel + delegate: BarWidgetLoader { + required property var model + required property int index + + widgetId: model.id || "" + widgetScreen: root.screen + widgetProps: ({ + "widgetId": model.id, + "section": "center", + "sectionWidgetIndex": index, + "sectionWidgetsCount": root.centerWidgetsModel.count + }) + Layout.alignment: Qt.AlignVCenter + } + } + } + + // Right Section + RowLayout { + id: rightSection + objectName: "rightSection" + anchors.right: parent.right + anchors.rightMargin: horizontalBarMargin + Settings.data.bar.contentPadding + y: Style.pixelAlignCenter(parent.height, height) + spacing: Settings.data.bar.widgetSpacing + + Repeater { + model: root.rightWidgetsModel + delegate: BarWidgetLoader { + required property var model + required property int index + + widgetId: model.id || "" + widgetScreen: root.screen + widgetProps: ({ + "widgetId": model.id, + "section": "right", + "sectionWidgetIndex": index, + "sectionWidgetsCount": root.rightWidgetsModel.count + }) + Layout.alignment: Qt.AlignVCenter + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/BarExclusionZone.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/BarExclusionZone.qml new file mode 100644 index 0000000..c2e7b18 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/BarExclusionZone.qml @@ -0,0 +1,72 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland +import qs.Commons + +/** +* BarExclusionZone - Invisible PanelWindow that reserves exclusive space for the bar +* +* This is a minimal window that works with the compositor to reserve space, +* while the actual bar UI is rendered in NFullScreenWindow. +*/ +PanelWindow { + id: root + + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" + readonly property bool barFloating: Settings.data.bar.barType === "floating" + readonly property real barMarginH: barFloating ? Settings.data.bar.marginHorizontal : 0 + readonly property real barMarginV: barFloating ? Settings.data.bar.marginVertical : 0 + readonly property real barHeight: Style.getBarHeightForScreen(screen?.name) + + // Invisible - just reserves space + color: "transparent" + + mask: Region {} + + // Wayland layer shell configuration + WlrLayershell.layer: WlrLayer.Top + WlrLayershell.namespace: "noctalia-bar-exclusion-" + (screen?.name || "unknown") + WlrLayershell.exclusionMode: ExclusionMode.Auto + + // Anchor based on bar position + anchors { + top: barPosition === "top" + bottom: barPosition === "bottom" + left: barPosition === "left" || barPosition === "top" || barPosition === "bottom" + right: barPosition === "right" || barPosition === "top" || barPosition === "bottom" + } + + // Size based on bar orientation + // When floating, only reserve space for the bar + margin on the anchored edge + implicitWidth: { + if (barIsVertical) { + // Vertical bar: reserve bar height + margin on the anchored edge only + if (barFloating) { + // For left bar, reserve left margin; for right bar, reserve right margin + return barHeight + barMarginH; + } + return barHeight; + } + return 0; // Auto-width when left/right anchors are true + } + + implicitHeight: { + if (!barIsVertical) { + // Horizontal bar: reserve bar height + margin on the anchored edge only + if (barFloating) { + // For top bar, reserve top margin; for bottom bar, reserve bottom margin + return barHeight + barMarginV; + } + return barHeight; + } + return 0; // Auto-height when top/bottom anchors are true + } + + Component.onCompleted: { + Logger.d("BarExclusionZone", "Created for screen:", screen?.name); + Logger.d("BarExclusionZone", " Position:", barPosition, "Floating:", barFloating); + Logger.d("BarExclusionZone", " Anchors - top:", anchors.top, "bottom:", anchors.bottom, "left:", anchors.left, "right:", anchors.right); + Logger.d("BarExclusionZone", " Size:", width, "x", height, "implicitWidth:", implicitWidth, "implicitHeight:", implicitHeight); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/BarPill.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/BarPill.qml new file mode 100644 index 0000000..c988487 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/BarPill.qml @@ -0,0 +1,128 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import qs.Commons +import qs.Widgets + +Item { + id: root + + required property ShellScreen screen + + property string icon: "" + property string text: "" + property string suffix: "" + property var tooltipText + property bool autoHide: false + property bool forceOpen: false + property bool forceClose: false + property bool oppositeDirection: false + property bool hovered: false + property bool rotateText: false + property color customBackgroundColor: "transparent" + property color customTextIconColor: "transparent" + property color customIconColor: "transparent" + property color customTextColor: "transparent" + + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right" + + signal shown + signal hidden + signal entered + signal exited + signal clicked + signal rightClicked + signal middleClicked + signal wheel(int delta) + + // Size based on content for the content dimension, fill parent for the extended dimension + // Horizontal bars: width = content, height = fill parent (for extended click area) + // Vertical bars: width = fill parent, height = content + width: isVerticalBar ? parent.width : (pillLoader.item ? pillLoader.item.implicitWidth : 0) + height: isVerticalBar ? (pillLoader.item ? pillLoader.item.implicitHeight : 0) : parent.height + implicitWidth: pillLoader.item ? pillLoader.item.implicitWidth : 0 + implicitHeight: pillLoader.item ? pillLoader.item.implicitHeight : 0 + + // Loader fills BarPill so child components can extend to full bar dimension + Loader { + id: pillLoader + anchors.fill: parent + sourceComponent: isVerticalBar ? verticalPillComponent : horizontalPillComponent + + Component { + id: verticalPillComponent + BarPillVertical { + screen: root.screen + icon: root.icon + text: root.text + suffix: root.suffix + tooltipText: root.tooltipText + autoHide: root.autoHide + forceOpen: root.forceOpen + forceClose: root.forceClose + oppositeDirection: root.oppositeDirection + hovered: root.hovered + rotateText: root.rotateText + customBackgroundColor: root.customBackgroundColor + customTextIconColor: root.customTextIconColor + customIconColor: root.customIconColor + customTextColor: root.customTextColor + onShown: root.shown() + onHidden: root.hidden() + onEntered: root.entered() + onExited: root.exited() + onClicked: root.clicked() + onRightClicked: root.rightClicked() + onMiddleClicked: root.middleClicked() + onWheel: delta => root.wheel(delta) + } + } + + Component { + id: horizontalPillComponent + BarPillHorizontal { + screen: root.screen + icon: root.icon + text: root.text + suffix: root.suffix + tooltipText: root.tooltipText + autoHide: root.autoHide + forceOpen: root.forceOpen + forceClose: root.forceClose + oppositeDirection: root.oppositeDirection + hovered: root.hovered + customBackgroundColor: root.customBackgroundColor + customTextIconColor: root.customTextIconColor + customIconColor: root.customIconColor + customTextColor: root.customTextColor + onShown: root.shown() + onHidden: root.hidden() + onEntered: root.entered() + onExited: root.exited() + onClicked: root.clicked() + onRightClicked: root.rightClicked() + onMiddleClicked: root.middleClicked() + onWheel: delta => root.wheel(delta) + } + } + } + + function show() { + if (pillLoader.item && pillLoader.item.show) { + pillLoader.item.show(); + } + } + + function hide() { + if (pillLoader.item && pillLoader.item.hide) { + pillLoader.item.hide(); + } + } + + function showDelayed() { + if (pillLoader.item && pillLoader.item.showDelayed) { + pillLoader.item.showDelayed(); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/BarPillHorizontal.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/BarPillHorizontal.qml new file mode 100644 index 0000000..2076618 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/BarPillHorizontal.qml @@ -0,0 +1,349 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import qs.Commons +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + required property ShellScreen screen + + property string icon: "" + property string text: "" + property string suffix: "" + property var tooltipText + property bool autoHide: false + property bool forceOpen: false + property bool forceClose: false + property bool oppositeDirection: false + property bool hovered: false + property color customBackgroundColor: "transparent" + property color customTextIconColor: "transparent" + property color customIconColor: "transparent" + property color customTextColor: "transparent" + + readonly property bool collapseToIcon: forceClose && !forceOpen + + // Effective shown state (true if hovered/animated open or forced) + readonly property bool revealed: !forceClose && (forceOpen || showPill) + readonly property bool hasIcon: root.icon !== "" + + signal shown + signal hidden + signal entered + signal exited + signal clicked + signal rightClicked + signal middleClicked + signal wheel(int delta) + + // Internal state + property bool showPill: false + property bool shouldAnimateHide: false + + readonly property int pillHeight: Style.getCapsuleHeightForScreen(screen?.name) + readonly property real barFontSize: Style.getBarFontSizeForScreen(screen?.name) + readonly property int pillPaddingHorizontal: Math.round(pillHeight * 0.2) + readonly property int pillOverlap: Math.round(pillHeight * 0.5) + readonly property int pillMaxWidth: Math.max(1, Math.round(textItem.implicitWidth + pillPaddingHorizontal * 2 + pillOverlap)) + + // Always prioritize hover color, then the custom one and finally the fallback color + readonly property color bgColor: hovered ? Color.mHover : (customBackgroundColor.a > 0) ? customBackgroundColor : Style.capsuleColor + readonly property color fgColor: hovered ? Color.mOnHover : (customTextIconColor.a > 0) ? customTextIconColor : Color.mOnSurface + readonly property color iconFgColor: hovered ? Color.mOnHover : (customIconColor.a > 0) ? customIconColor : (customTextIconColor.a > 0) ? customTextIconColor : Color.mOnSurface + readonly property color textFgColor: hovered ? Color.mOnHover : (customTextColor.a > 0) ? customTextColor : (customTextIconColor.a > 0) ? customTextIconColor : Color.mOnSurface + + readonly property real iconSize: Style.toOdd(pillHeight * 0.48) + + // Content width calculation (for implicit sizing) + readonly property real contentWidth: { + if (collapseToIcon) { + return hasIcon ? pillHeight : 0; + } + var overlap = hasIcon ? pillOverlap : 0; + var baseWidth = hasIcon ? pillHeight : 0; + return baseWidth + Math.max(0, pill.width - overlap); + } + + // Fill parent to extend click area to full bar height + // Visual content is centered vertically within + anchors.fill: parent + implicitWidth: contentWidth + implicitHeight: pillHeight + + Connections { + target: root + function onTooltipTextChanged() { + if (hovered) { + TooltipService.updateText(root.tooltipText); + } + } + } + + // Unified background for the entire pill area to avoid overlapping opacity + Rectangle { + id: pillBackground + width: collapseToIcon ? pillHeight : root.width + height: pillHeight + radius: Style.radiusM + color: root.bgColor + anchors.verticalCenter: parent.verticalCenter + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + } + + Rectangle { + id: pill + + width: revealed ? pillMaxWidth : 1 + height: pillHeight + + x: { + if (!hasIcon) + return 0; + return oppositeDirection ? (iconCircle.x + iconCircle.width / 2) : (iconCircle.x + iconCircle.width / 2) - width; + } + + opacity: revealed ? Style.opacityFull : Style.opacityNone + color: "transparent" // Make pill background transparent to avoid double opacity + + topLeftRadius: oppositeDirection ? 0 : Style.radiusM + bottomLeftRadius: oppositeDirection ? 0 : Style.radiusM + topRightRadius: oppositeDirection ? Style.radiusM : 0 + bottomRightRadius: oppositeDirection ? Style.radiusM : 0 + anchors.verticalCenter: parent.verticalCenter + + NText { + id: textItem + anchors.verticalCenter: parent.verticalCenter + x: { + if (!hasIcon) + return (parent.width - width) / 2; + + // Better text horizontal centering + var centerX = (parent.width - width) / 2; + var offset = oppositeDirection ? Style.marginXS : -Style.marginXS; + if (forceOpen) { + // If its force open, the icon disc background is the same color as the bg pill move text slightly + offset += oppositeDirection ? -Style.marginXXS : Style.marginXXS; + } + return centerX + offset; + } + text: root.text + root.suffix + family: Settings.data.ui.fontFixed + pointSize: root.barFontSize + applyUiScale: false + color: root.textFgColor + visible: revealed + } + + Behavior on width { + enabled: showAnim.running || hideAnim.running + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + Behavior on opacity { + enabled: showAnim.running || hideAnim.running + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + Rectangle { + id: iconCircle + width: hasIcon ? pillHeight : 0 + height: pillHeight + radius: Math.min(Style.radiusL, width / 2) + color: "transparent" // Make icon background transparent to avoid double opacity + anchors.verticalCenter: parent.verticalCenter + + x: oppositeDirection ? 0 : (parent.width - width) + + NIcon { + icon: root.icon + pointSize: iconSize + applyUiScale: false + color: root.iconFgColor + // Center horizontally + x: (iconCircle.width - width) / 2 + // Center vertically accounting for font metrics + y: (iconCircle.height - height) / 2 + (height - contentHeight) / 2 + } + } + + ParallelAnimation { + id: showAnim + running: false + NumberAnimation { + target: pill + property: "width" + from: 1 + to: pillMaxWidth + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + NumberAnimation { + target: pill + property: "opacity" + from: 0 + to: 1 + duration: Style.animationFast + easing.type: Easing.OutCubic + } + onStarted: { + showPill = true; + } + onStopped: { + delayedHideAnim.start(); + root.shown(); + } + } + + SequentialAnimation { + id: delayedHideAnim + running: false + PauseAnimation { + duration: 2500 + } + ScriptAction { + script: if (shouldAnimateHide) { + hideAnim.start(); + } + } + } + + ParallelAnimation { + id: hideAnim + running: false + NumberAnimation { + target: pill + property: "width" + from: pillMaxWidth + to: 1 + duration: Style.animationNormal + easing.type: Easing.InCubic + } + NumberAnimation { + target: pill + property: "opacity" + from: 1 + to: 0 + duration: Style.animationFast + easing.type: Easing.InCubic + } + onStopped: { + showPill = false; + shouldAnimateHide = false; + root.hidden(); + } + } + + Timer { + id: showTimer + interval: Style.pillDelay + onTriggered: { + if (!showPill) { + showAnim.start(); + } + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + cursorShape: root.clicked ? Qt.PointingHandCursor : Qt.ArrowCursor + onEntered: { + hovered = true; + root.entered(); + TooltipService.show(root, root.tooltipText, BarService.getTooltipDirection(root.screen?.name), (forceOpen || forceClose) ? Style.tooltipDelay : Style.tooltipDelayLong); + if (forceClose) { + return; + } + if (!forceOpen) { + showDelayed(); + } + } + onExited: { + hovered = false; + root.exited(); + if (!forceOpen && !forceClose) { + hide(); + } + TooltipService.hide(); + } + onClicked: mouse => { + TooltipService.hide(); + if (mouse.button === Qt.LeftButton) { + root.clicked(); + } else if (mouse.button === Qt.RightButton) { + root.rightClicked(); + } else if (mouse.button === Qt.MiddleButton) { + root.middleClicked(); + } + } + onWheel: wheel => root.wheel(wheel.angleDelta.y) + } + + function show() { + if (collapseToIcon || root.text.trim().length === 0) + return; + if (!showPill) { + shouldAnimateHide = autoHide; + showAnim.start(); + } else { + hideAnim.stop(); + delayedHideAnim.restart(); + } + } + + function hide() { + if (collapseToIcon) + return; + if (forceOpen) { + return; + } + if (showPill) { + hideAnim.start(); + } + showTimer.stop(); + } + + function showDelayed() { + if (collapseToIcon || root.text.trim().length === 0) + return; + if (!showPill) { + shouldAnimateHide = autoHide; + showTimer.start(); + } else { + hideAnim.stop(); + delayedHideAnim.restart(); + } + } + + onForceOpenChanged: { + if (forceOpen) { + // Immediately lock open without animations + showAnim.stop(); + hideAnim.stop(); + delayedHideAnim.stop(); + showPill = true; + } else { + hide(); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/BarPillVertical.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/BarPillVertical.qml new file mode 100644 index 0000000..caf30af --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/BarPillVertical.qml @@ -0,0 +1,386 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import qs.Commons +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + required property ShellScreen screen + + property string icon: "" + property string text: "" + property string suffix: "" + property var tooltipText + property bool autoHide: false + property bool forceOpen: false + property bool forceClose: false + property bool oppositeDirection: false + property bool hovered: false + property bool rotateText: false + property color customBackgroundColor: "transparent" + property color customTextIconColor: "transparent" + property color customIconColor: "transparent" + property color customTextColor: "transparent" + + readonly property bool collapseToIcon: forceClose && !forceOpen + + signal shown + signal hidden + signal entered + signal exited + signal clicked + signal rightClicked + signal middleClicked + signal wheel(int delta) + + // Internal state + property bool showPill: false + property bool shouldAnimateHide: false + + // Sizing logic for vertical bars + readonly property int buttonSize: Style.getCapsuleHeightForScreen(screen?.name) + readonly property real barFontSize: Style.getBarFontSizeForScreen(screen?.name) + readonly property int pillHeight: buttonSize + readonly property int pillOverlap: Math.round(buttonSize * 0.5) + readonly property int maxPillWidth: rotateText ? Math.max(buttonSize, Math.round(textItem.implicitHeight + Style.margin2M)) : buttonSize + readonly property int maxPillHeight: rotateText ? Math.max(1, Math.round(textItem.implicitWidth + Style.margin2M + Math.round(iconCircle.height / 4))) : Math.max(1, Math.round(textItem.implicitHeight + Style.margin2M)) + + // Determine pill direction based on section position + readonly property bool openDownward: oppositeDirection + readonly property bool openUpward: !oppositeDirection + + // Effective shown state (true if animated open or forced, but not if force closed) + readonly property bool revealed: !forceClose && (forceOpen || showPill) + readonly property bool hasIcon: root.icon !== "" + + // Always prioritize hover color, then the custom one and finally the fallback color + readonly property color bgColor: hovered ? Color.mHover : (customBackgroundColor.a > 0) ? customBackgroundColor : Style.capsuleColor + readonly property color fgColor: hovered ? Color.mOnHover : (customTextIconColor.a > 0) ? customTextIconColor : Color.mOnSurface + readonly property color iconFgColor: hovered ? Color.mOnHover : (customIconColor.a > 0) ? customIconColor : (customTextIconColor.a > 0) ? customTextIconColor : Color.mOnSurface + readonly property color textFgColor: hovered ? Color.mOnHover : (customTextColor.a > 0) ? customTextColor : (customTextIconColor.a > 0) ? customTextIconColor : Color.mOnSurface + + readonly property real iconSize: Style.toOdd(pillHeight * 0.48) + + // Content height calculation (for implicit sizing) + readonly property real contentHeight: { + if (collapseToIcon) { + return hasIcon ? buttonSize : 0; + } + var overlap = hasIcon ? pillOverlap : 0; + var baseHeight = hasIcon ? buttonSize : 0; + return baseHeight + Math.max(0, pill.height - overlap); + } + + // Fill parent width to extend horizontal click area + // Keep content-based height for visual layout + anchors.left: parent ? parent.left : undefined + anchors.right: parent ? parent.right : undefined + anchors.verticalCenter: parent ? parent.verticalCenter : undefined + height: contentHeight + implicitWidth: buttonSize + implicitHeight: contentHeight + + Connections { + target: root + function onTooltipTextChanged() { + if (hovered) { + TooltipService.updateText(root.tooltipText); + } + } + } + + // Unified background for the entire pill area to avoid overlapping opacity + Rectangle { + id: pillBackground + width: buttonSize + height: root.contentHeight + radius: Style.radiusM + color: root.bgColor + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + } + + Rectangle { + id: pill + + width: revealed ? maxPillWidth : 1 + height: revealed ? maxPillHeight : 1 + + // Position based on direction - center the pill relative to the icon + x: 0 + y: { + if (!hasIcon) + return 0; + return openUpward ? (iconCircle.y + iconCircle.height / 2 - height) : (iconCircle.y + iconCircle.height / 2); + } + + opacity: revealed ? Style.opacityFull : Style.opacityNone + color: "transparent" // Make pill background transparent to avoid double opacity + + // Radius logic for vertical expansion - rounded on the side that connects to icon + topLeftRadius: openUpward ? Style.radiusM : 0 + bottomLeftRadius: openDownward ? Style.radiusM : 0 + topRightRadius: openUpward ? Style.radiusM : 0 + bottomRightRadius: openDownward ? Style.radiusM : 0 + + anchors.horizontalCenter: parent.horizontalCenter + + NText { + id: textItem + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: hasIcon ? (openDownward ? Style.marginXXS : -Style.marginXXS) : 0 + rotation: rotateText ? -90 : 0 + text: root.text + root.suffix + family: Settings.data.ui.fontFixed + pointSize: root.barFontSize + applyUiScale: false + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: root.textFgColor + visible: revealed + + function getVerticalCenterOffset() { + // A small, symmetrical offset to push the text slightly away from the icon's edge. + return openDownward ? Style.marginXS : -Style.marginXS; + } + } + Behavior on width { + enabled: showAnim.running || hideAnim.running + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + Behavior on height { + enabled: showAnim.running || hideAnim.running + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + Behavior on opacity { + enabled: showAnim.running || hideAnim.running + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + Rectangle { + id: iconCircle + width: buttonSize + height: buttonSize + radius: Math.min(Style.radiusL, width / 2) + color: "transparent" // Make icon background transparent to avoid double opacity + + // Icon positioning based on direction + x: 0 + y: openUpward ? (root.contentHeight - height) : 0 + anchors.horizontalCenter: parent.horizontalCenter + + NIcon { + icon: root.icon + pointSize: iconSize + applyUiScale: false + color: root.iconFgColor + // Center horizontally + x: (iconCircle.width - width) / 2 + // Center vertically accounting for font metrics + y: (iconCircle.height - height) / 2 + (height - contentHeight) / 2 + } + } + + ParallelAnimation { + id: showAnim + running: false + NumberAnimation { + target: pill + property: "width" + from: 1 + to: maxPillWidth + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + NumberAnimation { + target: pill + property: "height" + from: 1 + to: maxPillHeight + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + NumberAnimation { + target: pill + property: "opacity" + from: 0 + to: 1 + duration: Style.animationFast + easing.type: Easing.OutCubic + } + onStarted: { + showPill = true; + } + onStopped: { + delayedHideAnim.start(); + root.shown(); + } + } + + SequentialAnimation { + id: delayedHideAnim + running: false + PauseAnimation { + duration: 2500 + } + ScriptAction { + script: if (shouldAnimateHide) { + hideAnim.start(); + } + } + } + + ParallelAnimation { + id: hideAnim + running: false + NumberAnimation { + target: pill + property: "width" + from: maxPillWidth + to: 1 + duration: Style.animationNormal + easing.type: Easing.InCubic + } + NumberAnimation { + target: pill + property: "height" + from: maxPillHeight + to: 1 + duration: Style.animationNormal + easing.type: Easing.InCubic + } + NumberAnimation { + target: pill + property: "opacity" + from: 1 + to: 0 + duration: Style.animationFast + easing.type: Easing.InCubic + } + onStopped: { + showPill = false; + shouldAnimateHide = false; + root.hidden(); + } + } + + Timer { + id: showTimer + interval: Style.pillDelay + onTriggered: { + if (!showPill) { + showAnim.start(); + } + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + cursorShape: root.clicked ? Qt.PointingHandCursor : Qt.ArrowCursor + onEntered: { + hovered = true; + root.entered(); + TooltipService.show(root, root.tooltipText, BarService.getTooltipDirection(root.screen?.name), (forceOpen || forceClose) ? Style.tooltipDelay : Style.tooltipDelayLong); + if (forceClose) { + return; + } + if (!forceOpen) { + showDelayed(); + } + } + onExited: { + hovered = false; + root.exited(); + if (!forceOpen && !forceClose) { + hide(); + } + TooltipService.hide(); + } + onClicked: mouse => { + TooltipService.hide(); + if (mouse.button === Qt.LeftButton) { + root.clicked(); + } else if (mouse.button === Qt.RightButton) { + root.rightClicked(); + } else if (mouse.button === Qt.MiddleButton) { + root.middleClicked(); + } + } + onWheel: wheel => root.wheel(wheel.angleDelta.y) + } + + function show() { + if (collapseToIcon || root.text.trim().length === 0) + return; + if (!showPill) { + shouldAnimateHide = autoHide; + showAnim.start(); + } else { + hideAnim.stop(); + delayedHideAnim.restart(); + } + } + + function hide() { + if (collapseToIcon) + return; + if (forceOpen) { + return; + } + if (showPill) { + hideAnim.start(); + } + showTimer.stop(); + } + + function showDelayed() { + if (collapseToIcon || root.text.trim().length === 0) + return; + if (!showPill) { + shouldAnimateHide = autoHide; + showTimer.start(); + } else { + hideAnim.stop(); + delayedHideAnim.restart(); + } + } + + onForceOpenChanged: { + if (forceOpen) { + // Immediately lock open without animations + showAnim.stop(); + hideAnim.stop(); + delayedHideAnim.stop(); + showPill = true; + } else { + hide(); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/BarWidgetLoader.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/BarWidgetLoader.qml new file mode 100644 index 0000000..b369467 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/BarWidgetLoader.qml @@ -0,0 +1,191 @@ +import QtQuick +import Quickshell +import qs.Commons +import qs.Services.Noctalia +import qs.Services.UI + +Item { + id: root + + required property string widgetId + required property var widgetScreen + required property var widgetProps + + // Extract section info from widgetProps + readonly property string section: widgetProps ? (widgetProps.section || "") : "" + readonly property int sectionIndex: widgetProps ? (widgetProps.sectionWidgetIndex || 0) : 0 + + // Store registration key at registration time so unregistration always uses the correct key, + // even if binding properties (section, sectionIndex) have changed by destruction time + property string _regScreen: "" + property string _regSection: "" + property string _regWidgetId: "" + property int _regIndex: -1 + + function _unregister() { + if (_regScreen !== "") { + BarService.unregisterWidget(_regScreen, _regSection, _regWidgetId, _regIndex); + _regScreen = ""; + } + } + + // Bar orientation and height for extended click areas + readonly property string barPosition: Settings.getBarPositionForScreen(widgetScreen?.name) + readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right" + readonly property real barHeight: Style.getBarHeightForScreen(widgetScreen?.name) + + // Request full bar dimension from layout to extend click areas above/below widgets + // For horizontal bars: full bar height, widget's content width + // For vertical bars: full bar width, widget's content height + implicitWidth: isVerticalBar ? barHeight : getImplicitSize(loader.item, "implicitWidth") + implicitHeight: isVerticalBar ? getImplicitSize(loader.item, "implicitHeight") : barHeight + + // Remove layout space left by hidden widgets + visible: loader.item ? ((loader.item.opacity > 0.0) || (loader.item.hasOwnProperty("hideMode") && loader.item.hideMode === "transparent")) : false + + function getImplicitSize(item, prop) { + return (item && item.visible) ? Math.round(item[prop]) : 0; + } + + // Only load if widget exists in registry + function checkWidgetExists(): bool { + return root.widgetId !== "" && BarWidgetRegistry.hasWidget(root.widgetId); + } + + // Force reload counter - incremented when plugin widget registry changes + property int reloadCounter: 0 + + // Listen for plugin widget registry changes to force reload + Connections { + target: BarWidgetRegistry + enabled: BarWidgetRegistry.isPluginWidget(root.widgetId) + + function onPluginWidgetRegistryUpdated() { + if (BarWidgetRegistry.hasWidget(root.widgetId)) { + root.reloadCounter++; + // Plugin widgets use setSource, so also trigger reload directly + if (root._isPlugin && loader.active) + root._loadWidget(); + Logger.d("BarWidgetLoader", "Plugin widget registry updated, reloading:", root.widgetId); + } + } + } + + readonly property bool _isPlugin: BarWidgetRegistry.isPluginWidget(widgetId) + + // Build initial properties that must be available during Component.onCompleted. + // This prevents registration-key mismatches in widgets that build IDs from + // screen.name, section, or sectionWidgetIndex. + // All standard bar widget props are passed for both core and plugin widgets. + // Plugins that don't define some of these properties will get harmless warnings + // but still load correctly โ€” and plugins that DO define them (e.g. for unique + // SpectrumService keys with multiple instances) get correct values from the start. + function _initialProps() { + return { + "screen": widgetScreen, + "widgetId": widgetProps.widgetId || "", + "section": widgetProps.section || "", + "sectionWidgetIndex": widgetProps.sectionWidgetIndex || 0, + "sectionWidgetsCount": widgetProps.sectionWidgetsCount || 0 + }; + } + + // Core widget URLs: file names match widget IDs exactly + readonly property string _barWidgetsDir: Quickshell.shellDir + "/Modules/Bar/Widgets/" + + function _loadWidget() { + if (!BarWidgetRegistry.hasWidget(root.widgetId)) + return; + + var props = _initialProps(); + + if (_isPlugin) { + var comp = BarWidgetRegistry.getWidget(root.widgetId); + if (!comp) + return; + var pluginId = root.widgetId.replace("plugin:", ""); + var api = PluginService.getPluginAPI(pluginId); + if (api) + props.pluginApi = api; + loader.setSource(comp.url, props); + } else { + loader.setSource(_barWidgetsDir + root.widgetId + ".qml", props); + } + } + + Loader { + id: loader + anchors.fill: parent + asynchronous: true + active: root.checkWidgetExists() && (root.reloadCounter >= 0) + + // All widgets use setSource() so that screen and widget properties + // are set as initial properties, available during Component.onCompleted. + Component.onCompleted: root._loadWidget() + + onActiveChanged: { + if (active) + root._loadWidget(); + } + + // Unregister when the loaded item is destroyed (Loader deactivated or sourceComponent changed) + onItemChanged: { + if (!item) { + root._unregister(); + } + } + + onLoaded: { + if (!item) + return; + + Logger.d("BarWidgetLoader", "Loading widget", widgetId, "on screen:", widgetScreen.name); + + // Extend widget to fill full bar dimension for extended click areas + // For horizontal bars: widget fills bar height (content width preserved) + // For vertical bars: widget fills bar width (content height preserved) + if (root.isVerticalBar) { + item.width = Qt.binding(function () { + return root.barHeight; + }); + } else { + item.height = Qt.binding(function () { + return root.barHeight; + }); + } + + // Apply remaining widget properties (screen is already set as initial prop) + for (var prop in widgetProps) { + if (item.hasOwnProperty(prop)) { + item[prop] = widgetProps[prop]; + } + } + + // Unregister any previous registration before registering the new instance + root._unregister(); + + // Register and store the key for reliable unregistration + BarService.registerWidget(widgetScreen.name, section, widgetId, sectionIndex, item); + root._regScreen = widgetScreen.name; + root._regSection = section; + root._regWidgetId = widgetId; + root._regIndex = sectionIndex; + + // Call custom onLoaded if it exists + if (item.hasOwnProperty("onLoaded")) { + item.onLoaded(); + } + } + + Component.onDestruction: { + root._unregister(); + } + } + + // Error handling + Component.onCompleted: { + if (!BarWidgetRegistry.hasWidget(widgetId)) { + Logger.w("BarWidgetLoader", "Widget not found in registry:", widgetId); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/TrayMenu.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/TrayMenu.qml new file mode 100644 index 0000000..ab11985 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/TrayMenu.qml @@ -0,0 +1,638 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.UI +import qs.Widgets + +PopupWindow { + id: root + + property ShellScreen screen + + property var trayItem: null + property var anchorItem: null + property real anchorX + property real anchorY + property bool isSubMenu: false + property string widgetSection: "" + property int widgetIndex: -1 + + // Derive menu from trayItem (only used for non-submenus) + readonly property QsMenuHandle menu: isSubMenu ? null : (trayItem ? trayItem.menu : null) + + // Compute if current tray item is pinned + readonly property bool isPinned: { + if (!trayItem || widgetSection === "" || widgetIndex < 0) + return false; + var widgets = Settings.getBarWidgetsForScreen(root.screen?.name)[widgetSection]; + if (!widgets || widgetIndex >= widgets.length) + return false; + var widgetSettings = widgets[widgetIndex]; + if (!widgetSettings || widgetSettings.id !== "Tray") + return false; + var pinnedList = widgetSettings.pinned || []; + const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || ""; + for (var i = 0; i < pinnedList.length; i++) { + if (pinnedList[i] === itemName) + return true; + } + return false; + } + + readonly property int menuWidth: 220 + + implicitWidth: menuWidth + + // Use the content height of the Flickable for implicit height + implicitHeight: Math.min(screen?.height * 0.9, flickable.contentHeight + Style.margin2S) + + // When implicitHeight changes (menu content loads), force anchor recalculation + onImplicitHeightChanged: { + if (visible && anchorItem) { + Qt.callLater(() => { + anchor.updateAnchor(); + }); + } + } + + visible: false + color: "transparent" + anchor.item: anchorItem + anchor.rect.x: { + if (anchorItem && screen) { + let baseX = anchorX; + + // Calculate position relative to current screen + let menuScreenX; + if (isSubMenu && anchorItem.Window && anchorItem.Window.window) { + const posInPopup = anchorItem.mapToItem(null, 0, 0); + const parentWindow = anchorItem.Window.window; + const windowXOnScreen = parentWindow.x - screen.x; + menuScreenX = windowXOnScreen + posInPopup.x + baseX; + } else { + const anchorGlobalPos = anchorItem.mapToItem(null, 0, 0); + const anchorScreenX = anchorGlobalPos.x; + menuScreenX = anchorScreenX + baseX; + } + + const menuRight = menuScreenX + implicitWidth; + const screenRight = screen.width; + const menuLeft = menuScreenX; + + // Only adjust if menu would clip off screen boundaries + // Don't adjust if the positioning is intentional (e.g., negative offset for right bar) + if (menuRight > screenRight && menuLeft < screenRight) { + // Clipping on right edge - shift left + const overflow = menuRight - screenRight; + return baseX - overflow - Style.marginS; + } else if (menuLeft < 0 && menuRight > 0) { + // Clipping on left edge - shift right + return baseX - menuLeft + Style.marginS; + } + + return baseX; + } + return anchorX; + } + anchor.rect.y: { + if (anchorItem && screen) { + const barPosition = Settings.getBarPositionForScreen(root.screen?.name); + + let baseY = anchorY; + + // Only apply bottom bar special positioning if: + // 1. Not a submenu + // 2. Bar is at bottom + // 3. anchorY is not already negative (if negative, it's pre-calculated from drawer) + const shouldApplyBottomBarLogic = !isSubMenu && barPosition === "bottom" && anchorY >= 0; + + if (shouldApplyBottomBarLogic) { + // For bottom bar from the bar itself, position menu above the anchor with margin + baseY = -(implicitHeight + Style.marginS); + } else if (barPosition === "top" && !isSubMenu && anchorY >= 0) { + // For top bar: position menu below bar with margin + const barHeight = Style.getBarHeightForScreen(root.screen?.name); + baseY = barHeight + Style.marginS; + } + + // Use a robust way to get screen coordinates + const posInWindow = anchorItem.mapToItem(null, 0, 0); + const parentWindow = anchorItem.Window.window; + + // Calculate screen-relative Y of the window + let windowYOnScreen = (parentWindow && screen) ? (parentWindow.y - screen.y) : 0; + + // If window reported 0 but bar is at bottom, assume it's at screen bottom + if (windowYOnScreen === 0 && barPosition === "bottom" && screen) { + windowYOnScreen = screen.height - (parentWindow ? parentWindow.height : Style.getBarHeightForScreen(screen.name)); + } + + // Calculate the screen Y of the menu top + // Use a small guess for height if implicitHeight is 0 to avoid covering the bar on the first frame + const effectiveHeight = implicitHeight > 0 ? implicitHeight : 200; + const effectiveBaseY = shouldApplyBottomBarLogic ? -(effectiveHeight + Style.marginS) : baseY; + + const menuScreenY = windowYOnScreen + posInWindow.y + effectiveBaseY; + const menuBottom = menuScreenY + (implicitHeight > 0 ? implicitHeight : effectiveHeight); + const screenHeight = screen ? screen.height : 1080; + + // Adjust the final baseY (the actual value returned to anchor.rect.y) + let finalBaseY = shouldApplyBottomBarLogic ? -(implicitHeight + Style.marginS) : baseY; + + // Adjust if menu would clip off the bottom + if (menuBottom > screenHeight) { + const overflow = menuBottom - screenHeight; + finalBaseY -= (overflow + Style.marginS); + } + + // Adjust if menu would clip off the top + // menuScreenY < 0 means it's above the screen edge + if (menuScreenY < 0) { + finalBaseY -= (menuScreenY - Style.marginS); + } + + return finalBaseY; + } + + // Fallback if no anchor/screen + if (isSubMenu) { + return anchorY; + } + return anchorY + (Settings.getBarPositionForScreen(root.screen?.name) === "bottom" ? -implicitHeight : Style.getBarHeightForScreen(root.screen?.name)); + } + + function showAt(item, x, y) { + if (!item) { + Logger.w("TrayMenu", "anchorItem is undefined, won't show menu."); + return; + } + + if (!opener.children || opener.children.values.length === 0) { + //Logger.w("TrayMenu", "Menu not ready, delaying show") + Qt.callLater(() => showAt(item, x, y)); + return; + } + + anchorItem = item; + anchorX = x; + anchorY = y; + + visible = true; + forceActiveFocus(); + + // Force update after showing. + Qt.callLater(() => { + root.anchor.updateAnchor(); + }); + } + + function hideMenu() { + visible = false; + + // Clean up all submenus recursively + for (var i = 0; i < columnLayout.children.length; i++) { + const child = columnLayout.children[i]; + if (child?.subMenu) { + child.subMenu.hideMenu(); + child.subMenu.destroy(); + child.subMenu = null; + } + } + } + + Item { + anchors.fill: parent + Keys.onEscapePressed: root.hideMenu() + } + + QsMenuOpener { + id: opener + menu: root.menu + } + + Rectangle { + anchors.fill: parent + color: Color.mSurface + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS) + radius: Style.radiusM + + // Fade-in animation + opacity: root.visible ? 1.0 : 0.0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutQuad + } + } + } + + Flickable { + id: flickable + anchors.fill: parent + anchors.margins: Style.marginS + contentHeight: columnLayout.implicitHeight + interactive: true + + // Fade-in animation + opacity: root.visible ? 1.0 : 0.0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutQuad + } + } + + // Use a ColumnLayout to handle menu item arrangement + ColumnLayout { + id: columnLayout + width: flickable.width + spacing: 0 + + Repeater { + model: opener.children ? [...opener.children.values] : [] + + delegate: Rectangle { + id: entry + required property var modelData + + Layout.preferredWidth: parent.width + Layout.preferredHeight: { + if (modelData?.isSeparator) { + return 8; + } else { + // Calculate based on text content + const textHeight = text.contentHeight || (Style.fontSizeS * 1.2); + return Math.max(28, textHeight + Style.margin2S); + } + } + + color: "transparent" + property var subMenu: null + + NDivider { + anchors.centerIn: parent + width: parent.width - Style.margin2M + visible: modelData?.isSeparator ?? false + } + + Rectangle { + id: innerRect + anchors.fill: parent + color: mouseArea.containsMouse ? Color.mHover : "transparent" + radius: Style.radiusS + visible: !(modelData?.isSeparator ?? false) + + RowLayout { + anchors.fill: parent + anchors.leftMargin: Style.marginM + anchors.rightMargin: Style.marginM + spacing: Style.marginS + + // Indicator Container + Item { + visible: (modelData?.buttonType ?? QsMenuButtonType.None) !== QsMenuButtonType.None + + implicitWidth: Math.round(Style.baseWidgetSize * 0.5) + implicitHeight: Math.round(Style.baseWidgetSize * 0.5) + Layout.alignment: Qt.AlignVCenter + + // Helper properties + readonly property int type: modelData?.buttonType ?? QsMenuButtonType.None + readonly property bool isRadio: type === QsMenuButtonType.RadioButton + readonly property bool isChecked: modelData?.checkState === Qt.Checked || (modelData?.checked ?? false) + + // Color Logic + readonly property color activeColor: mouseArea.containsMouse ? Color.mOnHover : Color.mPrimary + readonly property color checkMarkColor: mouseArea.containsMouse ? Color.mHover : Color.mOnPrimary + readonly property color borderColor: isChecked ? activeColor : (mouseArea.containsMouse ? Color.mOnHover : Color.mOnSurface) + + // Checkbox Visuals + Rectangle { + visible: !parent.isRadio + anchors.centerIn: parent + width: Math.round(Style.baseWidgetSize * 0.5) + height: Math.round(Style.baseWidgetSize * 0.5) + radius: Style.iRadiusXS + color: "transparent" // Transparent to match RadioButton style + border.color: parent.borderColor + border.width: Style.borderM + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + + NIcon { + visible: parent.parent.isChecked + anchors.centerIn: parent + anchors.horizontalCenterOffset: -1 + icon: "check" + color: parent.parent.activeColor + pointSize: Math.max(Style.fontSizeXXS, parent.width * 0.6) + } + } + + // RadioButton Visuals + Rectangle { + visible: parent.isRadio + anchors.centerIn: parent + width: Style.toOdd(Style.baseWidgetSize * 0.5) + height: Style.toOdd(Style.baseWidgetSize * 0.5) + radius: width / 2 + color: "transparent" + border.color: parent.borderColor + border.width: Style.borderM // Slightly thicker for radio look + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + + Rectangle { + visible: parent.parent.isChecked + anchors.centerIn: parent + width: Style.toOdd(parent.width * 0.5) + height: Style.toOdd(parent.height * 0.5) + radius: width / 2 + color: parent.parent.activeColor + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + } + } + + NText { + id: text + Layout.fillWidth: true + color: (modelData?.enabled ?? true) ? (mouseArea.containsMouse ? Color.mOnHover : Color.mOnSurface) : Color.mOnSurfaceVariant + text: modelData?.text !== "" ? modelData?.text.replace(/[\n\r]+/g, ' ') : "..." + pointSize: Style.fontSizeS + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + } + + Image { + Layout.preferredWidth: Style.marginL + Layout.preferredHeight: Style.marginL + source: modelData?.icon ?? "" + visible: (modelData?.icon ?? "") !== "" + fillMode: Image.PreserveAspectFit + } + + NIcon { + icon: modelData?.hasChildren ? "menu" : "" + pointSize: Style.fontSizeS + applyUiScale: false + verticalAlignment: Text.AlignVCenter + visible: modelData?.hasChildren ?? false + color: (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface) + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + enabled: (modelData?.enabled ?? true) && !(modelData?.isSeparator ?? false) && root.visible + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onClicked: mouse => { + if (modelData && !modelData.isSeparator) { + if (modelData.hasChildren) { + // Click on items with children toggles submenu + if (entry.subMenu) { + // Close existing submenu + entry.subMenu.hideMenu(); + entry.subMenu.destroy(); + entry.subMenu = null; + } else { + // Close any other open submenus first + for (var i = 0; i < columnLayout.children.length; i++) { + const sibling = columnLayout.children[i]; + if (sibling !== entry && sibling.subMenu) { + sibling.subMenu.hideMenu(); + sibling.subMenu.destroy(); + sibling.subMenu = null; + } + } + + // Determine submenu opening direction + let openLeft = false; + const barPosition = Settings.getBarPositionForScreen(root.screen?.name); + const globalPos = entry.mapToItem(null, 0, 0); + + if (barPosition === "right") { + openLeft = true; + } else if (barPosition === "left") { + openLeft = false; + } else { + openLeft = (root.widgetSection === "right"); + } + + // Open new submenu + entry.subMenu = Qt.createComponent("TrayMenu.qml").createObject(root, { + "menu": modelData, + "isSubMenu": true, + "screen": root.screen + }); + + if (entry.subMenu) { + const overlap = 60; + entry.subMenu.anchorItem = entry; + entry.subMenu.anchorX = openLeft ? -overlap : overlap; + entry.subMenu.anchorY = 0; + entry.subMenu.visible = true; + // Force anchor update with new position + Qt.callLater(() => { + entry.subMenu.anchor.updateAnchor(); + }); + } + } + } else { + // Click on regular items triggers them + modelData.triggered(); + root.hideMenu(); + + // Close the drawer if it's open + if (root.screen) { + const panel = PanelService.getPanel("trayDrawerPanel", root.screen); + if (panel && panel.visible) { + panel.close(); + } + } + } + } + } + } + } + + Component.onDestruction: { + if (subMenu) { + subMenu.destroy(); + subMenu = null; + } + } + } + } + + // PIN / UNPIN + Rectangle { + visible: { + if (widgetSection === "" || widgetIndex < 0) + return false; + var widgets = Settings.getBarWidgetsForScreen(root.screen?.name)[widgetSection]; + if (!widgets || widgetIndex >= widgets.length) + return false; + var widgetSettings = widgets[widgetIndex]; + if (!widgetSettings) + return false; + return widgetSettings.drawerEnabled ?? false; + } + Layout.preferredWidth: parent.width + Layout.preferredHeight: 28 + color: pinUnpinMouseArea.containsMouse ? Qt.alpha(Color.mPrimary, 0.2) : Qt.alpha(Color.mPrimary, 0.08) + radius: Style.radiusS + border.color: Qt.alpha(Color.mPrimary, pinUnpinMouseArea.containsMouse ? 0.4 : 0.2) + border.width: Style.borderS + + RowLayout { + anchors.fill: parent + anchors.leftMargin: Style.marginM + anchors.rightMargin: Style.marginM + spacing: Style.marginS + + NIcon { + icon: root.isPinned ? "unpin" : "pin" + pointSize: Style.fontSizeS + applyUiScale: false + verticalAlignment: Text.AlignVCenter + color: Color.mPrimary + } + + NText { + Layout.fillWidth: true + color: Color.mPrimary + text: root.isPinned ? I18n.tr("panels.bar.tray-unpin-application") : I18n.tr("panels.bar.tray-pin-application") + pointSize: Style.fontSizeS + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + } + + MouseArea { + id: pinUnpinMouseArea + anchors.fill: parent + hoverEnabled: true + + onClicked: { + if (root.isPinned) { + root.removeFromPinned(); + } else { + root.addToPinned(); + } + } + } + } + } + } + + function addToPinned() { + if (!trayItem || widgetSection === "" || widgetIndex < 0) { + Logger.w("TrayMenu", "Cannot pin: missing tray item or widget info"); + return; + } + const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || ""; + if (!itemName) { + Logger.w("TrayMenu", "Cannot pin: tray item has no name"); + return; + } + var screenName = root.screen?.name || ""; + var widgets = Settings.getBarWidgetsForScreen(screenName)[widgetSection]; + if (!widgets || widgetIndex >= widgets.length) { + Logger.w("TrayMenu", "Cannot pin: invalid widget index"); + return; + } + var widgetSettings = widgets[widgetIndex]; + if (!widgetSettings || widgetSettings.id !== "Tray") { + Logger.w("TrayMenu", "Cannot pin: widget is not a Tray widget"); + return; + } + var pinnedList = widgetSettings.pinned || []; + var newPinned = pinnedList.slice(); + newPinned.push(itemName); + var newSettings = Object.assign({}, widgetSettings); + newSettings.pinned = newPinned; + widgets[widgetIndex] = newSettings; + + // Write to the correct location: screen override or global + if (Settings.hasScreenOverride(screenName, "widgets")) { + var overrideWidgets = Settings.getBarWidgetsForScreen(screenName); + overrideWidgets[widgetSection] = widgets; + Settings.setScreenOverride(screenName, "widgets", overrideWidgets); + } else { + Settings.data.bar.widgets[widgetSection] = widgets; + } + Settings.saveImmediate(); + + // Close drawer when pinning (drawer needs to resize) + if (screen) { + const panel = PanelService.getPanel("trayDrawerPanel", screen); + if (panel) + panel.close(); + } + } + + function removeFromPinned() { + if (!trayItem || widgetSection === "" || widgetIndex < 0) { + Logger.w("TrayMenu", "Cannot unpin: missing tray item or widget info"); + return; + } + const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || ""; + if (!itemName) { + Logger.w("TrayMenu", "Cannot unpin: tray item has no name"); + return; + } + var screenName = root.screen?.name || ""; + var widgets = Settings.getBarWidgetsForScreen(screenName)[widgetSection]; + if (!widgets || widgetIndex >= widgets.length) { + Logger.w("TrayMenu", "Cannot unpin: invalid widget index"); + return; + } + var widgetSettings = widgets[widgetIndex]; + if (!widgetSettings || widgetSettings.id !== "Tray") { + Logger.w("TrayMenu", "Cannot unpin: widget is not a Tray widget"); + return; + } + var pinnedList = widgetSettings.pinned || []; + var newPinned = []; + for (var i = 0; i < pinnedList.length; i++) { + if (pinnedList[i] !== itemName) { + newPinned.push(pinnedList[i]); + } + } + var newSettings = Object.assign({}, widgetSettings); + newSettings.pinned = newPinned; + widgets[widgetIndex] = newSettings; + + // Write to the correct location: screen override or global + if (Settings.hasScreenOverride(screenName, "widgets")) { + var overrideWidgets = Settings.getBarWidgetsForScreen(screenName); + overrideWidgets[widgetSection] = widgets; + Settings.setScreenOverride(screenName, "widgets", overrideWidgets); + } else { + Settings.data.bar.widgets[widgetSection] = widgets; + } + Settings.saveImmediate(); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/WorkspacePill.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/WorkspacePill.qml new file mode 100644 index 0000000..149879e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Extras/WorkspacePill.qml @@ -0,0 +1,228 @@ +import QtQuick +import qs.Commons +import qs.Services.Compositor +import qs.Widgets + +Item { + id: pillContainer + + required property var workspace + required property bool isVertical + + // These must be provided by the parent Workspace widget + required property real baseDimensionRatio + required property real capsuleHeight + required property real barHeight + required property string labelMode + required property int fontWeight + required property int characterCount + required property real textRatio + required property bool showLabelsOnlyWhenOccupied + required property string focusedColor + required property string occupiedColor + required property string emptyColor + required property real masterProgress + required property bool effectsActive + required property color effectColor + required property var getWorkspaceWidth + required property var getWorkspaceHeight + + // Fixed dimension (cross-axis) for visual pill + readonly property real fixedDimension: Style.toOdd(capsuleHeight * baseDimensionRatio) + + // Animated pill dimensions (for visual pill, not container) + property real pillWidth: isVertical ? fixedDimension : getWorkspaceWidth(workspace, false) + property real pillHeight: isVertical ? getWorkspaceHeight(workspace, false) : fixedDimension + + // Container uses full barHeight on cross-axis for larger click area + width: isVertical ? barHeight : getWorkspaceWidth(workspace, false) + height: isVertical ? getWorkspaceHeight(workspace, false) : barHeight + + states: [ + State { + name: "active" + when: workspace.isActive + PropertyChanges { + target: pillContainer + width: isVertical ? barHeight : getWorkspaceWidth(workspace, true) + height: isVertical ? getWorkspaceHeight(workspace, true) : barHeight + pillWidth: isVertical ? fixedDimension : getWorkspaceWidth(workspace, true) + pillHeight: isVertical ? getWorkspaceHeight(workspace, true) : fixedDimension + } + } + ] + + transitions: [ + Transition { + from: "inactive" + to: "active" + NumberAnimation { + properties: isVertical ? "height,pillHeight" : "width,pillWidth" + duration: Style.animationNormal + easing.type: Easing.OutBack + } + }, + Transition { + from: "active" + to: "inactive" + NumberAnimation { + properties: isVertical ? "height,pillHeight" : "width,pillWidth" + duration: Style.animationNormal + easing.type: Easing.OutBack + } + } + ] + + Rectangle { + id: pill + width: pillContainer.pillWidth + height: pillContainer.pillHeight + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, height) + radius: Style.radiusM + z: 0 + + color: { + if (pillMouseArea.containsMouse) + return Color.mHover; + if (workspace.isFocused) + return Color.resolveColorKey(focusedColor); + if (workspace.isUrgent) + return Color.mError; + if (workspace.isOccupied) + return Color.resolveColorKey(occupiedColor); + return Qt.alpha(Color.resolveColorKey(emptyColor), 0.3); + } + + Loader { + active: (labelMode !== "none") && (!showLabelsOnlyWhenOccupied || workspace.isOccupied || workspace.isFocused) + anchors.fill: parent + sourceComponent: Component { + NText { + text: { + if (workspace.name && workspace.name.length > 0) { + if (labelMode === "name") { + return workspace.name.substring(0, characterCount); + } + if (labelMode === "index+name") { + // Vertical mode: compact format (no space, first char only) + // Horizontal mode: full format (space, more chars) + if (isVertical) { + return workspace.idx.toString() + workspace.name.substring(0, 1); + } + return workspace.idx.toString() + " " + workspace.name.substring(0, characterCount); + } + } + return workspace.idx.toString(); + } + family: Settings.data.ui.fontFixed + // Size based on the fixed dimension (cross-axis) of the visual pill + pointSize: (isVertical ? pillContainer.pillWidth : pillContainer.pillHeight) * textRatio + applyUiScale: false + font.capitalization: Font.AllUppercase + font.weight: fontWeight + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.Wrap + color: { + if (pillMouseArea.containsMouse) + return Color.mOnHover; + if (workspace.isFocused) + return Color.resolveOnColorKey(focusedColor); + if (workspace.isUrgent) + return Color.mOnError; + if (workspace.isOccupied) + return Color.resolveOnColorKey(occupiedColor); + return Color.resolveOnColorKey(emptyColor); + } + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + } + } + } + + // Material 3-inspired smooth animations + Behavior on scale { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutBack + } + } + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.InOutCubic + } + } + Behavior on radius { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutBack + } + } + } + + Behavior on width { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutBack + } + } + Behavior on height { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutBack + } + } + Behavior on pillWidth { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutBack + } + } + Behavior on pillHeight { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutBack + } + } + + // Full-height click area + MouseArea { + id: pillMouseArea + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + onClicked: { + CompositorService.switchToWorkspace(workspace); + } + } + + // Burst effect overlay for focused pill + Rectangle { + id: pillBurst + anchors.centerIn: pill + width: pillContainer.pillWidth + 18 * masterProgress * scale + height: pillContainer.pillHeight + 18 * masterProgress * scale + radius: width / 2 + color: "transparent" + border.color: effectColor + border.width: Math.max(1, Math.round((2 + 6 * (1.0 - masterProgress)))) + opacity: effectsActive && workspace.isFocused ? (1.0 - masterProgress) * 0.7 : 0 + visible: effectsActive && workspace.isFocused + z: 1 + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/ActiveWindow.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/ActiveWindow.qml new file mode 100644 index 0000000..12f197d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/ActiveWindow.qml @@ -0,0 +1,385 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import Quickshell.Widgets +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Services.Compositor +import qs.Services.UI +import qs.Widgets + +Item { + id: root + Layout.preferredHeight: isVerticalBar ? -1 : Style.getBarHeightForScreen(screenName) + Layout.preferredWidth: isVerticalBar ? Style.getBarHeightForScreen(screenName) : -1 + Layout.fillHeight: false + Layout.fillWidth: false + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] || {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length && widgets[sectionWidgetIndex]) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + // Widget settings - matching MediaMini pattern + readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : (widgetMetadata.showIcon || false) + readonly property bool showText: (widgetSettings.showText !== undefined) ? widgetSettings.showText : (widgetMetadata.showText || false) + readonly property string hideMode: (widgetSettings.hideMode !== undefined) ? widgetSettings.hideMode : (widgetMetadata.hideMode || "hidden") + readonly property string scrollingMode: (widgetSettings.scrollingMode !== undefined) ? widgetSettings.scrollingMode : (widgetMetadata.scrollingMode || "hover") + + // Maximum widget width with user settings support + readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth || 0, screen ? screen.width * 0.06 : 0) + readonly property bool useFixedWidth: (widgetSettings.useFixedWidth !== undefined) ? widgetSettings.useFixedWidth : (widgetMetadata.useFixedWidth || false) + readonly property string textColorKey: (widgetSettings.textColor !== undefined) ? widgetSettings.textColor : widgetMetadata.textColor + readonly property color textColor: Color.resolveColorKey(textColorKey) + + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right" + readonly property real barHeight: Style.getBarHeightForScreen(screenName) + readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName) + readonly property real barFontSize: Style.getBarFontSizeForScreen(screenName) + readonly property bool hasFocusedWindow: CompositorService.getFocusedWindow() !== null + readonly property string windowTitle: CompositorService.getFocusedWindowTitle() || "No active window" + readonly property string fallbackIcon: "user-desktop" + + readonly property int iconSize: Style.toOdd(capsuleHeight * 0.75) + readonly property int verticalSize: Style.toOdd(capsuleHeight * 0.85) + + // For horizontal bars, height is always barHeight (no animation needed) + // For vertical bars, collapse to 0 when hidden + implicitHeight: isVerticalBar ? (((!hasFocusedWindow) && hideMode === "hidden") ? 0 : verticalSize) : barHeight + implicitWidth: isVerticalBar ? (((!hasFocusedWindow) && hideMode === "hidden") ? 0 : verticalSize) : (((!hasFocusedWindow) && hideMode === "hidden") ? 0 : dynamicWidth) + + // "visible": Always Visible, "hidden": Hide When Empty, "transparent": Transparent When Empty + visible: (hideMode !== "hidden" || hasFocusedWindow) || opacity > 0 + opacity: ((hideMode !== "hidden" || hasFocusedWindow) && (hideMode !== "transparent" || hasFocusedWindow)) ? 1.0 : 0.0 + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + + Behavior on implicitWidth { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutCubic + } + } + + Behavior on implicitHeight { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutCubic + } + } + + function calculateContentWidth() { + // Calculate the actual content width based on visible elements + var contentWidth = 0; + var margins = Style.margin2S; // Left and right margins + + // Icon width (if visible) + if (showIcon) { + contentWidth += iconSize; + if (showText) { + contentWidth += Style.marginS; // Spacing after icon + } + } + + // Text width (use the measured width) + if (showText) { + contentWidth += titleContainer.measuredWidth; + // Additional small margin for text + contentWidth += Style.margin2XXS; + } + + // Add container margins + contentWidth += margins; + + return Math.ceil(contentWidth); + } + + // Dynamic width: adapt to content but respect maximum width setting + readonly property real dynamicWidth: { + // If using fixed width mode, always use maxWidth + if (useFixedWidth) { + return maxWidth; + } + // Otherwise, adapt to content + if (!hasFocusedWindow) { + return Math.min(calculateContentWidth(), maxWidth); + } + // Use content width but don't exceed user-set maximum width + return Math.min(calculateContentWidth(), maxWidth); + } + + function getAppIcon() { + try { + // Try CompositorService first + const focusedWindow = CompositorService.getFocusedWindow(); + if (focusedWindow && focusedWindow.appId) { + try { + const idValue = focusedWindow.appId; + const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue); + const iconResult = ThemeIcons.iconForAppId(normalizedId.toLowerCase()); + if (iconResult && iconResult !== "") { + return iconResult; + } + } catch (iconError) { + Logger.w("ActiveWindow", "Error getting icon from CompositorService:", iconError); + } + } + + if (CompositorService.isHyprland) { + // Fallback to ToplevelManager + if (ToplevelManager && ToplevelManager.activeToplevel) { + try { + const activeToplevel = ToplevelManager.activeToplevel; + if (activeToplevel.appId) { + const idValue2 = activeToplevel.appId; + const normalizedId2 = (typeof idValue2 === 'string') ? idValue2 : String(idValue2); + const iconResult2 = ThemeIcons.iconForAppId(normalizedId2.toLowerCase()); + if (iconResult2 && iconResult2 !== "") { + return iconResult2; + } + } + } catch (fallbackError) { + Logger.w("ActiveWindow", "Error getting icon from ToplevelManager:", fallbackError); + } + } + } + + return ThemeIcons.iconFromName(fallbackIcon); + } catch (e) { + Logger.w("ActiveWindow", "Error in getAppIcon:", e); + return ThemeIcons.iconFromName(fallbackIcon); + } + } + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + Rectangle { + id: windowActiveRect + visible: root.visible + x: isVerticalBar ? Style.pixelAlignCenter(parent.width, width) : 0 + y: isVerticalBar ? 0 : Style.pixelAlignCenter(parent.height, height) + width: isVerticalBar ? ((!hasFocusedWindow) && hideMode === "hidden" ? 0 : verticalSize) : ((!hasFocusedWindow) && (hideMode === "hidden") ? 0 : dynamicWidth) + height: isVerticalBar ? ((!hasFocusedWindow) && hideMode === "hidden" ? 0 : verticalSize) : capsuleHeight + radius: Style.radiusM + color: Style.capsuleColor + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + // Smooth width transition + Behavior on width { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutCubic + } + } + + Item { + id: mainContainer + anchors.fill: parent + anchors.leftMargin: isVerticalBar ? 0 : Style.marginS + anchors.rightMargin: isVerticalBar ? 0 : Style.marginS + + // Horizontal layout for top/bottom bars + RowLayout { + id: rowLayout + anchors.verticalCenter: parent.verticalCenter + spacing: Style.marginS + visible: !isVerticalBar + z: 1 + + // Window icon + Item { + Layout.preferredWidth: iconSize + Layout.preferredHeight: iconSize + Layout.alignment: Qt.AlignVCenter + visible: showIcon + + IconImage { + id: windowIcon + anchors.fill: parent + source: getAppIcon() + asynchronous: true + smooth: true + visible: source !== "" + + // Apply dock shader to active window icon (always themed) + layer.enabled: widgetSettings.colorizeIcons !== false + layer.effect: ShaderEffect { + property color targetColor: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mSurfaceVariant + property real colorizeMode: 0.0 // Dock mode (grayscale) + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } + } + } + + NScrollText { + id: titleContainer + text: windowTitle + Layout.alignment: Qt.AlignVCenter + Layout.preferredHeight: root.capsuleHeight + fadeRoundLeftCorners: !showIcon + visible: showText + + maxWidth: { + // Calculate available width based on other elements + var iconWidth = (showIcon && windowIcon.visible ? (iconSize + Style.marginS) : 0); + var totalMargins = Style.margin2XXS; + var availableWidth = mainContainer.width - iconWidth - totalMargins; + return Math.max(20, availableWidth); + } + scrollMode: { + if (scrollingMode === "always") + return NScrollText.ScrollMode.Always; + if (scrollingMode === "hover") + return NScrollText.ScrollMode.Hover; + return NScrollText.ScrollMode.Never; + } + forcedHover: mainMouseArea.containsMouse + fadeExtent: 0.1 + fadeCornerRadius: Style.radiusM + + NText { + text: windowTitle + pointSize: barFontSize + applyUiScale: false + font.weight: Style.fontWeightMedium + color: root.textColor + } + } + } + + // Vertical layout for left/right bars - icon only + Item { + id: verticalLayout + width: parent.width - Style.margin2M + height: parent.height - Style.margin2M + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, height) + visible: isVerticalBar + z: 1 + + // Window icon + Item { + id: verticalIconContainer + width: root.iconSize + height: width + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, height) + visible: windowTitle !== "" + + IconImage { + id: windowIconVertical + anchors.fill: parent + source: getAppIcon() + asynchronous: true + smooth: true + visible: source !== "" + + // Apply dock shader to active window icon (always themed) + layer.enabled: widgetSettings.colorizeIcons !== false + layer.effect: ShaderEffect { + property color targetColor: Color.mOnSurface + property real colorizeMode: 0.0 // Dock mode (grayscale) + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } + } + } + } + + // Mouse area moved to root + } + } + + // Mouse area for hover detection + MouseArea { + id: mainMouseArea + anchors.fill: parent + + // Extend click area to screen edge if widget is at the start/end + anchors.leftMargin: (!isVerticalBar && section === "left" && sectionWidgetIndex === 0) ? -Style.marginS : 0 + anchors.rightMargin: (!isVerticalBar && section === "right" && sectionWidgetIndex === sectionWidgetsCount - 1) ? -Style.marginS : 0 + anchors.topMargin: (isVerticalBar && section === "left" && sectionWidgetIndex === 0) ? -Style.marginM : 0 + anchors.bottomMargin: (isVerticalBar && section === "right" && sectionWidgetIndex === sectionWidgetsCount - 1) ? -Style.marginM : 0 + + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton + onEntered: { + if ((windowTitle !== "") && isVerticalBar || (scrollingMode === "never")) { + TooltipService.show(root, windowTitle, BarService.getTooltipDirection(root.screen?.name)); + } + } + onExited: { + TooltipService.hide(); + } + onClicked: mouse => { + if (mouse.button === Qt.RightButton) { + PanelService.showContextMenu(contextMenu, root, screen); + } + } + } + + Connections { + target: CompositorService + function onActiveWindowChanged() { + try { + windowIcon.source = Qt.binding(getAppIcon); + windowIconVertical.source = Qt.binding(getAppIcon); + } catch (e) { + Logger.w("ActiveWindow", "Error in onActiveWindowChanged:", e); + } + } + function onWindowListChanged() { + try { + windowIcon.source = Qt.binding(getAppIcon); + windowIconVertical.source = Qt.binding(getAppIcon); + } catch (e) { + Logger.w("ActiveWindow", "Error in onWindowListChanged:", e); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/AudioVisualizer.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/AudioVisualizer.qml new file mode 100644 index 0000000..f13701d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/AudioVisualizer.qml @@ -0,0 +1,216 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Services.Media +import qs.Services.UI +import qs.Widgets +import qs.Widgets.AudioSpectrum + +Item { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right" + readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName) + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + // Resolve settings: try user settings or defaults from BarWidgetRegistry + readonly property int visualizerWidth: widgetSettings.width !== undefined ? widgetSettings.width : widgetMetadata.width + readonly property bool hideWhenIdle: widgetSettings.hideWhenIdle !== undefined ? widgetSettings.hideWhenIdle : widgetMetadata.hideWhenIdle + readonly property string colorName: widgetSettings.colorName !== undefined ? widgetSettings.colorName : widgetMetadata.colorName + + readonly property color fillColor: Color.resolveColorKey(colorName) + + readonly property bool shouldShow: (currentVisualizerType !== "" && currentVisualizerType !== "none") && (!hideWhenIdle || MediaService.isPlaying) + + // Register/unregister with SpectrumService based on visibility (use screenName โ€” screen can be null after DPMS/output changes) + readonly property string spectrumComponentId: "bar:audiovisualizer:" + screenName + ":" + root.section + ":" + root.sectionWidgetIndex + + onShouldShowChanged: { + if (root.shouldShow) { + SpectrumService.registerComponent(root.spectrumComponentId); + } else { + SpectrumService.unregisterComponent(root.spectrumComponentId); + } + } + + Component.onDestruction: { + if (root.shouldShow) { + SpectrumService.unregisterComponent(root.spectrumComponentId); + } + } + + // Content dimensions for implicit sizing + readonly property real contentWidth: !shouldShow ? 0 : isVerticalBar ? capsuleHeight : visualizerWidth + readonly property real contentHeight: !shouldShow ? 0 : isVerticalBar ? visualizerWidth : capsuleHeight + + implicitWidth: contentWidth + implicitHeight: contentHeight + visible: shouldShow + opacity: shouldShow ? 1.0 : 0.0 + + Behavior on implicitWidth { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutCubic + } + } + Behavior on implicitHeight { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutCubic + } + } + + // Store visualizer type to force re-evaluation + readonly property string currentVisualizerType: Settings.data.audio.visualizerType + + // Visual capsule centered in parent + Rectangle { + id: background + width: root.contentWidth + height: root.contentHeight + anchors.centerIn: parent + radius: Style.radiusS + color: Style.capsuleColor + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + // When visualizer type or playback changes, shouldShow updates automatically + // The Loader dynamically loads the appropriate visualizer based on settings + Loader { + id: visualizerLoader + anchors.fill: parent + anchors.margins: Style.marginS + active: shouldShow + asynchronous: true + + sourceComponent: { + switch (currentVisualizerType) { + case "linear": + return linearComponent; + case "mirrored": + return mirroredComponent; + case "wave": + return waveComponent; + default: + return null; + } + } + } + } + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.cycle-visualizer"), + "action": "cycle-visualizer", + "icon": "chart-column" + }, + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + if (screen) { + PanelService.closeContextMenu(screen); + } + + if (action === "cycle-visualizer") { + const types = ["linear", "mirrored", "wave"]; + const currentIndex = types.indexOf(currentVisualizerType); + const nextIndex = (currentIndex + 1) % types.length; + Settings.data.audio.visualizerType = types[nextIndex]; + } else if (action === "widget-settings" && screen) { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + // Click to cycle through visualizer types + MouseArea { + id: mouseArea + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onClicked: mouse => { + if (mouse.button === Qt.RightButton) { + if (screen) { + PanelService.showContextMenu(contextMenu, root, screen); + } + } else { + const types = ["linear", "mirrored", "wave"]; + const currentIndex = types.indexOf(currentVisualizerType); + const nextIndex = (currentIndex + 1) % types.length; + Settings.data.audio.visualizerType = types[nextIndex]; + } + } + } + + Component { + id: linearComponent + NLinearSpectrum { + anchors.fill: parent + values: SpectrumService.values + fillColor: root.fillColor + showMinimumSignal: true + vertical: root.isVerticalBar + barPosition: root.barPosition + mirrored: Settings.data.audio.spectrumMirrored + } + } + + Component { + id: mirroredComponent + NMirroredSpectrum { + anchors.fill: parent + values: SpectrumService.values + fillColor: root.fillColor + showMinimumSignal: true + vertical: root.isVerticalBar + mirrored: Settings.data.audio.spectrumMirrored + } + } + + Component { + id: waveComponent + NWaveSpectrum { + anchors.fill: parent + values: SpectrumService.values + fillColor: root.fillColor + showMinimumSignal: true + vertical: root.isVerticalBar + mirrored: Settings.data.audio.spectrumMirrored + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Battery.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Battery.qml new file mode 100644 index 0000000..9e87cf8 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Battery.qml @@ -0,0 +1,263 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.UPower +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Services.Hardware +import qs.Services.Networking +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" + readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName) + + readonly property string displayMode: widgetSettings.displayMode !== undefined ? widgetSettings.displayMode : widgetMetadata.displayMode + readonly property bool useGraphicMode: displayMode === "graphic" || displayMode === "graphic-clean" + + readonly property bool hideIfNotDetected: widgetSettings.hideIfNotDetected !== undefined ? widgetSettings.hideIfNotDetected : widgetMetadata.hideIfNotDetected + readonly property bool hideIfIdle: widgetSettings.hideIfIdle !== undefined ? widgetSettings.hideIfIdle : widgetMetadata.hideIfIdle + + // Check if selected device is actually present/connected + readonly property bool isReady: BatteryService.isDeviceReady(selectedDevice) + readonly property bool isPresent: BatteryService.isDevicePresent(selectedDevice) + readonly property real percent: isReady ? BatteryService.getPercentage(selectedDevice) : -1 + readonly property bool isCharging: isReady ? BatteryService.isCharging(selectedDevice) : false + readonly property bool isPluggedIn: isReady ? BatteryService.isPluggedIn(selectedDevice) : false + readonly property bool isLowBattery: isReady ? BatteryService.isLowBattery(selectedDevice) : false + readonly property bool isCriticalBattery: isReady ? BatteryService.isCriticalBattery(selectedDevice) : false + + // Visibility: show if hideIfNotDetected is false, or if battery is ready + readonly property bool shouldShow: !hideIfNotDetected || (isReady && (hideIfIdle ? !isPluggedIn : true)) + readonly property string deviceNativePath: widgetSettings.deviceNativePath !== undefined ? widgetSettings.deviceNativePath : widgetMetadata.deviceNativePath + readonly property var selectedDevice: BatteryService.isDevicePresent(BatteryService.findDevice(deviceNativePath)) ? BatteryService.findDevice(deviceNativePath) : null + + readonly property var tooltipContent: { + if (!isReady || !isPresent) { + return I18n.tr("battery.no-battery-detected"); + } + + let rows = []; + const isInternal = selectedDevice.isLaptopBattery; + if (isInternal) { + // Show charge percentage + rows.push([I18n.tr("battery.battery-level"), `${percent}%`]); + + let timeText = BatteryService.getTimeRemainingText(selectedDevice); + if (timeText) { + const colonIdx = timeText.indexOf(":"); + if (colonIdx >= 0) { + rows.push([timeText.substring(0, colonIdx).trim(), timeText.substring(colonIdx + 1).trim()]); + } else { + rows.push([timeText, ""]); + } + } + + let rateText = BatteryService.getRateText(selectedDevice); + if (!isPluggedIn && rateText) { + const colonIdx = rateText.indexOf(":"); + if (colonIdx >= 0) { + rows.push([rateText.substring(0, colonIdx).trim(), rateText.substring(colonIdx + 1).trim()]); + } else { + rows.push([rateText, ""]); + } + } + + // Show battery health if supported (check actual battery, not DisplayDevice) + let healthDevice = selectedDevice.healthSupported ? selectedDevice : (BatteryService.laptopBatteries.length > 0 ? BatteryService.laptopBatteries[0] : null); + if (healthDevice && healthDevice.healthSupported) { + rows.push([I18n.tr("battery.battery-health"), `${Math.round(healthDevice.healthPercentage)}%`]); + } + } else if (selectedDevice) { + // External / Peripheral Device (Phone, Keyboard, Mouse, Gamepad, Headphone etc.) + let name = BatteryService.getDeviceName(selectedDevice); + rows.push([name, `${percent}%`]); + } + + // If we are showing the main laptop battery, append external devices + if (isInternal) { + var external = BatteryService.bluetoothBatteries; + if (external.length > 0) { + if (rows.length > 0) { + rows.push(["---", "---"]); // Separator + } + for (var j = 0; j < external.length; j++) { + var dev = external[j]; + var dName = BatteryService.getDeviceName(dev); + var dPct = BatteryService.getPercentage(dev); + rows.push([dName, `${dPct}%`]); + } + } + } + + return rows; + } + + visible: shouldShow + opacity: shouldShow ? 1.0 : 0.0 + + implicitWidth: useGraphicMode ? capsule.width : pill.width + implicitHeight: useGraphicMode ? capsule.height : pill.height + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + // ==================== GRAPHIC MODE ==================== + + // Capsule background (graphic mode only) + Rectangle { + id: capsule + visible: root.useGraphicMode + anchors.centerIn: nBattery + width: root.isBarVertical ? root.capsuleHeight : nBattery.width + Style.margin2S + height: root.isBarVertical ? nBattery.height + Style.margin2S : root.capsuleHeight + radius: Math.min(Style.radiusL, width / 2) + color: graphicMouseArea.containsMouse ? Color.mHover : Style.capsuleColor + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + } + + NBattery { + id: nBattery + visible: root.useGraphicMode + anchors.centerIn: parent + baseSize: (Style.getBarHeightForScreen(root.screenName) / root.capsuleHeight) * Style.fontSizeXXS + showPercentageText: root.displayMode !== "graphic-clean" + vertical: root.isBarVertical + percentage: root.percent + ready: root.isReady + charging: root.isCharging + pluggedIn: root.isPluggedIn + low: root.isLowBattery + critical: root.isCriticalBattery + baseColor: graphicMouseArea.containsMouse ? Color.mOnHover : Color.mOnSurface + textColor: graphicMouseArea.containsMouse ? Color.mHover : Color.mSurface + } + + MouseArea { + id: graphicMouseArea + visible: root.useGraphicMode + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + cursorShape: Qt.PointingHandCursor + onEntered: { + if (!getBatteryPanel()?.isPanelOpen && root.tooltipContent) { + TooltipService.show(root, root.tooltipContent, BarService.getTooltipDirection(root.screen?.name)); + tooltipRefreshTimer.start(); + } + } + onExited: { + tooltipRefreshTimer.stop(); + TooltipService.hide(); + } + onClicked: mouse => { + TooltipService.hide(); + if (mouse.button === Qt.RightButton) { + PanelService.showContextMenu(contextMenu, nBattery, screen); + } else { + toggleBatteryPanel(); + } + } + } + + Timer { + id: tooltipRefreshTimer + interval: 1000 + repeat: true + onTriggered: { + if (graphicMouseArea.containsMouse) { + TooltipService.updateText(root.tooltipContent); + } + } + } + + // ==================== ICON MODE ==================== + + BarPill { + id: pill + visible: !root.useGraphicMode + screen: root.screen + oppositeDirection: BarService.getPillDirection(root) + icon: BatteryService.getIcon(root.percent, root.isCharging, root.isPluggedIn, root.isReady) + text: root.isReady ? root.percent : "-" + suffix: "%" + autoHide: false + forceOpen: root.isReady && root.displayMode === "icon-always" + forceClose: root.displayMode === "icon-only" || !root.isReady + customBackgroundColor: root.isCharging ? Color.mPrimary : ((root.isLowBattery || root.isCriticalBattery) ? Color.mError : "transparent") + customTextIconColor: root.isCharging ? Color.mOnPrimary : ((root.isLowBattery || root.isCriticalBattery) ? Color.mOnError : "transparent") + tooltipText: !getBatteryPanel()?.isPanelOpen ? root.tooltipContent : "" + onClicked: toggleBatteryPanel() + onRightClicked: PanelService.showContextMenu(contextMenu, pill, screen) + } + + // ==================== SHARED ==================== + + function getBatteryPanel() { + var panel = PanelService.getPanel("batteryPanel", screen); + if (panel) { + panel.panelID = { + showPowerProfiles: widgetSettings.showPowerProfiles !== undefined ? widgetSettings.showPowerProfiles : widgetMetadata.showPowerProfiles, + showNoctaliaPerformance: widgetSettings.showNoctaliaPerformance !== undefined ? widgetSettings.showNoctaliaPerformance : widgetMetadata.showNoctaliaPerformance + }; + } + return panel; + } + + function toggleBatteryPanel() { + getBatteryPanel()?.toggle(root); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Bluetooth.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Bluetooth.qml new file mode 100644 index 0000000..1b24d51 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Bluetooth.qml @@ -0,0 +1,122 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Modules.Panels.Settings // For SettingsPanel +import qs.Services.Networking +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" + readonly property string displayMode: widgetSettings.displayMode !== undefined ? widgetSettings.displayMode : widgetMetadata.displayMode + readonly property string iconColorKey: widgetSettings.iconColor !== undefined ? widgetSettings.iconColor : widgetMetadata.iconColor + readonly property string textColorKey: widgetSettings.textColor !== undefined ? widgetSettings.textColor : widgetMetadata.textColor + + implicitWidth: pill.width + implicitHeight: pill.height + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": BluetoothService.enabled ? I18n.tr("actions.disable-bluetooth") : I18n.tr("actions.enable-bluetooth"), + "action": "toggle-bluetooth", + "icon": BluetoothService.enabled ? "bluetooth-off" : "bluetooth", + "enabled": !NetworkService.airplaneModeEnabled && BluetoothService.bluetoothAvailable + }, + { + "label": I18n.tr("common.bluetooth") + " " + I18n.tr("tooltips.open-settings"), + "action": "bluetooth-settings", + "icon": "settings" + }, + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "toggle-bluetooth") { + BluetoothService.setBluetoothEnabled(!BluetoothService.enabled); + } else if (action === "bluetooth-settings") { + SettingsPanelService.openToTab(SettingsPanel.Tab.Connections, 1, screen); + } else if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + BarPill { + id: pill + + screen: root.screen + oppositeDirection: BarService.getPillDirection(root) + customIconColor: Color.resolveColorKeyOptional(root.iconColorKey) + customTextColor: Color.resolveColorKeyOptional(root.textColorKey) + icon: !BluetoothService.enabled ? "bluetooth-off" : ((BluetoothService.connectedDevices && BluetoothService.connectedDevices.length > 0) ? "bluetooth-connected" : "bluetooth") + text: { + if (BluetoothService.connectedDevices && BluetoothService.connectedDevices.length > 0) { + const firstDevice = BluetoothService.connectedDevices[0]; + return firstDevice.name || firstDevice.deviceName; + } + return ""; + } + suffix: { + if (BluetoothService.connectedDevices && BluetoothService.connectedDevices.length > 1) { + return ` + ${BluetoothService.connectedDevices.length - 1}`; + } + return ""; + } + autoHide: false + forceOpen: !isBarVertical && root.displayMode === "alwaysShow" + forceClose: isBarVertical || root.displayMode === "alwaysHide" || text === "" + onClicked: { + var p = PanelService.getPanel("bluetoothPanel", screen); + if (p) + p.toggle(this); + } + onRightClicked: { + PanelService.showContextMenu(contextMenu, pill, screen); + } + tooltipText: { + if (PanelService.getPanel("bluetoothPanel", screen)?.isPanelOpen) { + return ""; + } + if (pill.text !== "") { + return pill.text; + } + return I18n.tr("tooltips.bluetooth-devices"); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Brightness.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Brightness.qml new file mode 100644 index 0000000..b82e0b4 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Brightness.qml @@ -0,0 +1,211 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Modules.Panels.Settings +import qs.Services.Hardware +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" + readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode + readonly property string iconColorKey: widgetSettings.iconColor !== undefined ? widgetSettings.iconColor : widgetMetadata.iconColor + readonly property string textColorKey: widgetSettings.textColor !== undefined ? widgetSettings.textColor : widgetMetadata.textColor + readonly property bool applyToAllMonitors: widgetSettings.applyToAllMonitors !== undefined ? widgetSettings.applyToAllMonitors : (Settings.data.brightness.syncAllMonitors !== undefined ? Settings.data.brightness.syncAllMonitors : widgetMetadata.applyToAllMonitors) + readonly property bool reverseScroll: Settings.data.general.reverseScroll + + // Used to avoid opening the pill on Quickshell startup + property bool firstBrightnessReceived: false + + implicitWidth: pill.width + implicitHeight: pill.height + + // Track the brightness monitor reactively via declarative binding so it + // updates atomically when monitors change, avoiding a transient undefined + // state that occurs when Monitor QtObjects are destroyed before the + // imperative updateMonitor() call would run. + property var brightnessMonitor: { + var _ = BrightnessService.monitors; // reactive dependency + var __ = BrightnessService.ddcMonitors; // reactive dependency + if (!screen) + return null; + return BrightnessService.getMonitorForScreen(screen) ?? null; + } + + function getControllableMonitorCount() { + var monitors = BrightnessService.monitors || []; + var count = 0; + for (var i = 0; i < monitors.length; i++) { + if (monitors[i] && monitors[i].brightnessControlAvailable) + count++; + } + return count; + } + + visible: brightnessMonitor !== null + opacity: brightnessMonitor !== null ? 1.0 : 0.0 + + function getIcon() { + var monitor = brightnessMonitor; + if (!monitor || !monitor.brightnessControlAvailable || isNaN(monitor.brightness)) + return "sun-off"; + var brightness = monitor.brightness; + if (brightness <= 0.001) + return "sun-off"; + return brightness <= 0.5 ? "brightness-low" : "brightness-high"; + } + + // Connection used to open the pill when brightness changes + Connections { + target: brightnessMonitor + ignoreUnknownSignals: true + function onBrightnessUpdated() { + // Ignore if this is the first time we receive an update. + // Most likely service just kicked off. + if (!firstBrightnessReceived) { + firstBrightnessReceived = true; + return; + } + + pill.show(); + hideTimerAfterChange.restart(); + } + } + + Timer { + id: hideTimerAfterChange + interval: 2500 + running: false + repeat: false + onTriggered: pill.hide() + } + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.open-display-settings"), + "action": "open-display-settings", + "icon": "sun" + }, + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "open-display-settings") { + var settingsPanel = PanelService.getPanel("settingsPanel", screen); + settingsPanel.requestedTab = SettingsPanel.Tab.Display; + settingsPanel.open(); + } else if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + BarPill { + id: pill + + screen: root.screen + oppositeDirection: BarService.getPillDirection(root) + customIconColor: Color.resolveColorKeyOptional(root.iconColorKey) + customTextColor: Color.resolveColorKeyOptional(root.textColorKey) + icon: getIcon() + autoHide: false // Important to be false so we can hover as long as we want + text: { + var monitor = brightnessMonitor; + if (!monitor || !monitor.brightnessControlAvailable || isNaN(monitor.brightness)) + return ""; + return Math.round(monitor.brightness * 100); + } + suffix: text.length > 0 ? "%" : "-" + forceOpen: displayMode === "alwaysShow" + forceClose: displayMode === "alwaysHide" + tooltipText: { + var monitor = brightnessMonitor; + var panel = PanelService.getPanel("brightnessPanel", screen); + if (panel?.isPanelOpen || !monitor || !monitor.brightnessControlAvailable || isNaN(monitor.brightness)) + return ""; + return I18n.tr("tooltips.brightness-at", { + "brightness": Math.round(monitor.brightness * 100) + }); + } + + onWheel: function (angle) { + var monitor = brightnessMonitor; + if (!monitor || !monitor.brightnessControlAvailable) + return; + + if (root.reverseScroll) + angle *= -1; + + if (angle === 0) + return; + + var shouldApplyToAll = root.applyToAllMonitors && root.getControllableMonitorCount() > 1; + if (shouldApplyToAll) { + var direction = angle > 0 ? 1 : -1; + var baseValue = !isNaN(monitor.queuedBrightness) ? monitor.queuedBrightness : monitor.brightness; + var step = monitor.stepSize; + var minValue = monitor.minBrightnessValue; + + if (direction > 0 && Settings.data.brightness.enforceMinimum && baseValue < minValue) { + baseValue = Math.max(step, minValue); + } else { + baseValue = baseValue + direction * step; + } + + var targetValue = Math.max(minValue, Math.min(1, baseValue)); + + BrightnessService.monitors.forEach(function (m) { + if (m && m.brightnessControlAvailable) { + m.setBrightnessDebounced(targetValue); + } + }); + } else if (angle > 0) { + monitor.increaseBrightness(); + } else if (angle < 0) { + monitor.decreaseBrightness(); + } + } + + onClicked: PanelService.getPanel("brightnessPanel", screen)?.toggle(this) + + onRightClicked: { + PanelService.showContextMenu(contextMenu, pill, screen); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Clock.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Clock.qml new file mode 100644 index 0000000..7dbafa6 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Clock.qml @@ -0,0 +1,219 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" + readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName) + readonly property real barFontSize: Style.getBarFontSizeForScreen(screenName) + readonly property var now: Time.now + + // Resolve settings: try user settings or defaults from BarWidgetRegistry + readonly property string clockColor: widgetSettings.clockColor !== undefined ? widgetSettings.clockColor : widgetMetadata.clockColor + readonly property bool useCustomFont: widgetSettings.useCustomFont !== undefined ? widgetSettings.useCustomFont : widgetMetadata.useCustomFont + readonly property string customFont: widgetSettings.customFont !== undefined ? widgetSettings.customFont : widgetMetadata.customFont + readonly property string formatHorizontal: widgetSettings.formatHorizontal !== undefined ? widgetSettings.formatHorizontal : widgetMetadata.formatHorizontal + readonly property string formatVertical: widgetSettings.formatVertical !== undefined ? widgetSettings.formatVertical : widgetMetadata.formatVertical + readonly property string tooltipFormat: widgetSettings.tooltipFormat !== undefined ? widgetSettings.tooltipFormat : widgetMetadata.tooltipFormat + + readonly property color textColor: Color.resolveColorKey(clockColor) + + // Content dimensions for implicit sizing + readonly property real contentWidth: isBarVertical ? capsuleHeight : Math.round((isBarVertical ? verticalLoader.implicitWidth : horizontalLoader.implicitWidth) + Style.margin2M) + readonly property real contentHeight: isBarVertical ? Math.round(verticalLoader.implicitHeight + Style.margin2S) : capsuleHeight + + // Size: use implicit width/height + // BarWidgetLoader sets explicit width/height to extend click area + implicitWidth: contentWidth + implicitHeight: contentHeight + + // Visual clock capsule - stays at content size, centered in parent + Rectangle { + id: visualClock + width: root.contentWidth + height: root.contentHeight + anchors.centerIn: parent + + radius: Style.radiusL + color: Style.capsuleColor + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + Item { + id: clockContainer + anchors.centerIn: parent + + // Horizontal + Loader { + id: horizontalLoader + active: !isBarVertical + anchors.centerIn: parent + sourceComponent: ColumnLayout { + anchors.centerIn: parent + spacing: Settings.data.bar.showCapsule ? -5 : -3 + Repeater { + id: repeater + model: I18n.locale.toString(now, formatHorizontal.trim()).split("\\n") + NText { + visible: text !== "" + text: modelData + family: useCustomFont && customFont ? customFont : Settings.data.ui.fontDefault + Binding on pointSize { + value: { + if (repeater.model.length == 1) { + // Single line: Full size + return barFontSize; + } else if (repeater.model.length == 2) { + // Two lines: First line is bigger than the second + return (index == 0) ? Math.round(barFontSize * 0.9) : Math.round(barFontSize * 0.75); + } else { + // More than two lines: Make it small! + return Math.round(barFontSize * 0.75); + } + } + } + applyUiScale: false + color: textColor + wrapMode: Text.WordWrap + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + features: ({ + "tnum": 1 + }) + } + } + } + } + + // Vertical + Loader { + id: verticalLoader + active: isBarVertical + anchors.centerIn: parent // Now this works without layout conflicts + sourceComponent: ColumnLayout { + anchors.centerIn: parent + spacing: -2 + Repeater { + model: I18n.locale.toString(now, formatVertical.trim()).split(" ") + delegate: NText { + visible: text !== "" + text: modelData + family: useCustomFont && customFont ? customFont : Settings.data.ui.fontDefault + pointSize: barFontSize + applyUiScale: false + color: textColor + wrapMode: Text.WordWrap + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + features: ({ + "tnum": 1 + }) + } + } + } + } + } + } + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.open-calendar"), + "action": "open-calendar", + "icon": "calendar" + }, + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + // Close the context menu + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "open-calendar") { + PanelService.getPanel("clockPanel", screen)?.toggle(root); + } else if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + // Build tooltip text with formatted time/date + function buildTooltipText() { + if (tooltipFormat && tooltipFormat.trim() !== "") { + return I18n.locale.toString(now, tooltipFormat.trim()); + } + // Fallback to default if no format is set + return I18n.tr("common.calendar"); // Defaults to "Calendar" + } + + MouseArea { + id: clockMouseArea + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + onEntered: { + if (!PanelService.getPanel("clockPanel", screen)?.isPanelOpen) { + TooltipService.show(root, buildTooltipText(), BarService.getTooltipDirection(root.screen?.name)); + tooltipRefreshTimer.start(); + } + } + onExited: { + tooltipRefreshTimer.stop(); + TooltipService.hide(); + } + onClicked: mouse => { + TooltipService.hide(); + if (mouse.button === Qt.RightButton) { + PanelService.showContextMenu(contextMenu, root, screen); + } else { + PanelService.getPanel("clockPanel", screen)?.toggle(this); + } + } + } + + Timer { + id: tooltipRefreshTimer + interval: 1000 + repeat: true + onTriggered: { + if (clockMouseArea.containsMouse && !PanelService.getPanel("clockPanel", screen)?.isPanelOpen) { + TooltipService.updateText(buildTooltipText()); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/ControlCenter.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/ControlCenter.qml new file mode 100644 index 0000000..0621bcf --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/ControlCenter.qml @@ -0,0 +1,144 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import Quickshell +import Quickshell.Widgets +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Modules.Panels.Settings +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +NIconButton { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string customIcon: widgetSettings.icon !== undefined ? widgetSettings.icon : widgetMetadata.icon + readonly property bool useDistroLogo: widgetSettings.useDistroLogo !== undefined ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo + readonly property string customIconPath: widgetSettings.customIconPath !== undefined ? widgetSettings.customIconPath : widgetMetadata.customIconPath + readonly property bool enableColorization: widgetSettings.enableColorization !== undefined ? widgetSettings.enableColorization : widgetMetadata.enableColorization + readonly property string colorizeSystemIcon: widgetSettings.colorizeSystemIcon !== undefined ? widgetSettings.colorizeSystemIcon : widgetMetadata.colorizeSystemIcon + + readonly property color iconColor: { + if (!enableColorization) + return Color.mOnSurface; + return Color.resolveColorKey(colorizeSystemIcon); + } + + // If we have a custom path and not using distro logo, use the theme icon. + // If using distro logo, don't use theme icon. + icon: (customIconPath === "" && !useDistroLogo) ? customIcon : "" + tooltipText: { + if (!screen || PanelService.getPanel("controlCenterPanel", screen)?.isPanelOpen) { + return ""; + } else { + return I18n.tr("tooltips.open-control-center"); + } + } + tooltipDirection: BarService.getTooltipDirection(screen?.name) + baseSize: Style.getCapsuleHeightForScreen(screen?.name) + applyUiScale: false + customRadius: Style.radiusL + colorBg: Style.capsuleColor + colorFg: iconColor + colorBgHover: Color.mHover + colorFgHover: Color.mOnHover + colorBorder: Style.capsuleBorderColor + colorBorderHover: Style.capsuleBorderColor + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.open-launcher"), + "action": "open-launcher", + "icon": "search" + }, + { + "label": I18n.tr("actions.open-settings"), + "action": "open-settings", + "icon": "adjustments" + }, + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "open-launcher") { + PanelService.toggleLauncher(screen); + } else if (action === "open-settings") { + var panel = PanelService.getPanel("settingsPanel", screen); + panel.requestedTab = SettingsPanel.Tab.General; + panel.toggle(); + } else if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + onClicked: { + var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen); + if (Settings.data.controlCenter.position === "close_to_bar_button") { + // Will open the panel next to the bar button. + controlCenterPanel?.toggle(this); + } else { + controlCenterPanel?.toggle(); + } + } + onRightClicked: { + PanelService.showContextMenu(contextMenu, root, screen); + } + onMiddleClicked: PanelService.toggleLauncher(screen) + + IconImage { + id: customOrDistroLogo + anchors.centerIn: parent + width: root.buttonSize * 0.8 + height: width + source: { + if (useDistroLogo) + return HostService.osLogo; + if (customIconPath !== "") + return customIconPath.startsWith("file://") ? customIconPath : "file://" + customIconPath; + return ""; + } + visible: source !== "" + smooth: true + asynchronous: true + layer.enabled: (enableColorization) && (useDistroLogo || customIconPath !== "") + layer.effect: ShaderEffect { + property color targetColor: !hovering ? iconColor : Color.mOnHover + property real colorizeMode: 2.0 + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/CustomButton.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/CustomButton.qml new file mode 100644 index 0000000..ed5796c --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/CustomButton.qml @@ -0,0 +1,676 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Modules.Panels.Settings +import qs.Services.Control +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right" + + readonly property string customIcon: widgetSettings.icon || widgetMetadata.icon + readonly property string leftClickExec: widgetSettings.leftClickExec || widgetMetadata.leftClickExec + readonly property bool leftClickUpdateText: widgetSettings.leftClickUpdateText ?? widgetMetadata.leftClickUpdateText + readonly property string rightClickExec: widgetSettings.rightClickExec || widgetMetadata.rightClickExec + readonly property bool rightClickUpdateText: widgetSettings.rightClickUpdateText ?? widgetMetadata.rightClickUpdateText + readonly property string middleClickExec: widgetSettings.middleClickExec || widgetMetadata.middleClickExec + readonly property bool middleClickUpdateText: widgetSettings.middleClickUpdateText ?? widgetMetadata.middleClickUpdateText + readonly property string ipcIdentifier: widgetSettings.ipcIdentifier !== undefined ? widgetSettings.ipcIdentifier : (widgetMetadata.ipcIdentifier || "") + readonly property string wheelExec: widgetSettings.wheelExec || widgetMetadata.wheelExec + readonly property string wheelUpExec: widgetSettings.wheelUpExec || widgetMetadata.wheelUpExec + readonly property string wheelDownExec: widgetSettings.wheelDownExec || widgetMetadata.wheelDownExec + readonly property string wheelMode: widgetSettings.wheelMode || widgetMetadata.wheelMode + readonly property bool wheelUpdateText: widgetSettings.wheelUpdateText ?? widgetMetadata.wheelUpdateText + readonly property bool wheelUpUpdateText: widgetSettings.wheelUpUpdateText ?? widgetMetadata.wheelUpUpdateText + readonly property bool wheelDownUpdateText: widgetSettings.wheelDownUpdateText ?? widgetMetadata.wheelDownUpdateText + readonly property string textCommand: widgetSettings.textCommand !== undefined ? widgetSettings.textCommand : (widgetMetadata.textCommand || "") + readonly property bool textStream: widgetSettings.textStream !== undefined ? widgetSettings.textStream : (widgetMetadata.textStream || false) + readonly property int textIntervalMs: widgetSettings.textIntervalMs !== undefined ? widgetSettings.textIntervalMs : (widgetMetadata.textIntervalMs || 3000) + readonly property string textCollapse: widgetSettings.textCollapse !== undefined ? widgetSettings.textCollapse : (widgetMetadata.textCollapse || "") + readonly property bool parseJson: widgetSettings.parseJson !== undefined ? widgetSettings.parseJson : (widgetMetadata.parseJson || false) + readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec || (wheelMode === "unified" && wheelExec) || (wheelMode === "separate" && (wheelUpExec || wheelDownExec))) + readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : true + readonly property bool showExecTooltip: widgetSettings.showExecTooltip !== undefined ? widgetSettings.showExecTooltip : (widgetMetadata.showExecTooltip !== undefined ? widgetMetadata.showExecTooltip : true) + readonly property bool showTextTooltip: widgetSettings.showTextTooltip !== undefined ? widgetSettings.showTextTooltip : (widgetMetadata.showTextTooltip !== undefined ? widgetMetadata.showTextTooltip : true) + readonly property string generalTooltipText: widgetSettings.generalTooltipText !== undefined ? widgetSettings.generalTooltipText : (widgetMetadata.generalTooltipText || "") + readonly property bool _hasCustomTooltip: generalTooltipText.trim() !== "" + readonly property string hideMode: widgetSettings.hideMode || "alwaysExpanded" + readonly property bool hasOutput: _dynamicText !== "" + readonly property bool shouldForceOpen: textStream && (hideMode === "alwaysExpanded" || hideMode === "maxTransparent") + + readonly property bool _useNewHideLogic: textCommand && textCommand.length > 0 && textStream + readonly property bool _useTextCommandLogic: textCommand && textCommand.length > 0 && textStream + + readonly property bool _pillVisible: { + if (!_useTextCommandLogic) { + return true; + } + if (hideMode === "alwaysExpanded" || hideMode === "maxTransparent") { + return true; + } + var hasActualIcon = (_dynamicIcon !== "" || customIcon !== ""); + return hasOutput || (showIcon && hasActualIcon); + } + + readonly property real _pillOpacity: { + if (!_useTextCommandLogic) { + return 1.0; + } + if (hideMode === "maxTransparent" && !hasOutput) { + return 0.0; + } + return 1.0; + } + + readonly property bool _pillForceOpen: { + if (!_useTextCommandLogic) { + return _dynamicText !== "" || (textStream && currentMaxTextLength > 0); + } + if (currentMaxTextLength <= 0) { + return false; + } + + if (hideMode === "alwaysExpanded" || hideMode === "maxTransparent") { + return true; + } + return hasOutput; + } + + readonly property string _pillIcon: { + if (!_useTextCommandLogic) { + if (textCommand && textCommand.length > 0 && showIcon) { + return _dynamicIcon !== "" ? _dynamicIcon : customIcon; + } else if (!(textCommand && textCommand.length > 0)) { + return _dynamicIcon !== "" ? _dynamicIcon : customIcon; + } else { + return ""; + } + } + if (!showIcon) + return ""; + var actualIcon = _dynamicIcon !== "" ? _dynamicIcon : customIcon; + if (hideMode === "expandWithOutput" && actualIcon === "") { + return "question-mark"; + } + return actualIcon; + } + + readonly property string _pillText: { + if (!_useTextCommandLogic) { + return (!isVerticalBar || currentMaxTextLength > 0) ? _dynamicText : ""; + } + if (currentMaxTextLength <= 0) { + return ""; + } + if (hasOutput) { + return _dynamicText; + } + if (hideMode === "expandWithOutput") { + return ""; + } + return " ".repeat(currentMaxTextLength); + } + + readonly property bool enableColorization: widgetSettings.enableColorization || false + readonly property string colorizeSystemIcon: { + if (widgetSettings.colorizeSystemIcon !== undefined) + return widgetSettings.colorizeSystemIcon; + return widgetMetadata.colorizeSystemIcon !== undefined ? widgetMetadata.colorizeSystemIcon : "none"; + } + + readonly property bool isColorizing: enableColorization && colorizeSystemIcon !== "none" + + // Get color value from color name (returns null for invalid names) + function _getColorValue(colorName, forHover) { + const baseColor = (function () { + switch (colorName) { + case "primary": + return Color.mPrimary; + case "secondary": + return Color.mSecondary; + case "tertiary": + return Color.mTertiary; + case "error": + return Color.mError; + default: + return null; + } + })(); + return baseColor !== null ? (forHover ? Qt.darker(baseColor, 1.2) : baseColor) : null; + } + + // Resolve icon color with priority: dynamic > static > default + function _resolveIconColor(dynamicColorName, staticColorName, isHover) { + if (dynamicColorName && dynamicColorName !== "") { + if (dynamicColorName === "none") { + return isHover ? Color.mOnHover : Color.mOnSurface; + } + const color = _getColorValue(dynamicColorName, isHover); + if (color !== null) + return color; + } + + if (staticColorName && staticColorName !== "" && isColorizing) { + const color = _getColorValue(staticColorName, isHover); + if (color !== null) + return color; + } + + return isHover ? Color.mOnHover : Color.mOnSurface; + } + + readonly property color iconColor: _resolveIconColor(_dynamicColor, colorizeSystemIcon, false) + readonly property color iconHoverColor: _resolveIconColor(_dynamicColor, colorizeSystemIcon, true) + + implicitWidth: pill.width + implicitHeight: pill.height + + BarPill { + id: pill + + visible: _pillVisible + opacity: _pillOpacity + screen: root.screen + oppositeDirection: BarService.getPillDirection(root) + icon: _pillIcon + text: _pillText + rotateText: isVerticalBar && currentMaxTextLength > 0 + autoHide: false + forceOpen: _pillForceOpen + forceClose: !_pillForceOpen + customTextIconColor: iconColor + + // Helper function to build tooltip content + function _buildTooltipContent() { + var lines = []; + + // Add custom tooltip if set + if (_hasCustomTooltip) { + lines.push(generalTooltipText); + } + + // Add command details if enabled and available + if (showExecTooltip && hasExec) { + if (leftClickExec !== "") { + lines.push(I18n.tr("bar.custom-button.left-click-label") + `: ${leftClickExec}`); + } else if (!leftClickUpdateText) { + lines.push(I18n.tr("bar.custom-button.left-click-label") + ": " + I18n.tr("actions.widget-settings")); + } + + if (rightClickExec !== "") { + lines.push(I18n.tr("bar.custom-button.right-click-label") + `: ${rightClickExec}`); + } else if (!rightClickUpdateText) { + lines.push(I18n.tr("bar.custom-button.right-click-label") + ": " + I18n.tr("actions.widget-settings")); + } + + if (middleClickExec !== "") { + lines.push(I18n.tr("bar.custom-button.middle-click-label") + `: ${middleClickExec}`); + } else if (!middleClickUpdateText) { + lines.push(I18n.tr("bar.custom-button.middle-click-label") + ": " + I18n.tr("actions.widget-settings")); + } + + if (wheelMode === "unified" && wheelExec !== "") { + lines.push(I18n.tr("bar.custom-button.wheel-label") + `: ${wheelExec}`); + } else if (wheelMode === "separate") { + if (wheelUpExec !== "") { + lines.push(I18n.tr("bar.custom-button.wheel-up") + `: ${wheelUpExec}`); + } + if (wheelDownExec !== "") { + lines.push(I18n.tr("bar.custom-button.wheel-down") + `Wheel down: ${wheelDownExec}`); + } + } + } + + // Add dynamic text tooltip if enabled and available + if (showTextTooltip && _dynamicTooltip !== "") { + lines.push(_dynamicTooltip); + } + + return lines; + } + + tooltipText: { + var lines = _buildTooltipContent(); + + // If no custom tooltip and both switches are off, show default tooltip + if (!_hasCustomTooltip && !showExecTooltip && !showTextTooltip) { + return I18n.tr("bar.custom-button.default-tooltip"); + } + + // If there's content, join with newlines + if (lines.length > 0) { + return lines.join("\n"); + } + + // Fallback (shouldn't reach here normally) + return I18n.tr("bar.custom-button.default-tooltip"); + } + + onClicked: root.clicked() + onRightClicked: root.rightClicked() + onMiddleClicked: root.middleClicked() + onWheel: delta => root.wheeled(delta) + } + + // Internal state for dynamic text + property string _dynamicText: "" + property string _dynamicIcon: "" + property string _dynamicTooltip: "" + property string _dynamicColor: "" + + // Maximum length for text display before scrolling (different values for horizontal and vertical) + readonly property var maxTextLength: { + "horizontal": ((widgetSettings && widgetSettings.maxTextLength && widgetSettings.maxTextLength.horizontal !== undefined) ? widgetSettings.maxTextLength.horizontal : ((widgetMetadata && widgetMetadata.maxTextLength && widgetMetadata.maxTextLength.horizontal !== undefined) ? widgetMetadata.maxTextLength.horizontal : 10)), + "vertical": ((widgetSettings && widgetSettings.maxTextLength && widgetSettings.maxTextLength.vertical !== undefined) ? widgetSettings.maxTextLength.vertical : ((widgetMetadata && widgetMetadata.maxTextLength && widgetMetadata.maxTextLength.vertical !== undefined) ? widgetMetadata.maxTextLength.vertical : 10)) + } + readonly property int _staticDuration: 6 // How many cycles to stay static at start/end + + // Encapsulated state for scrolling text implementation + property var _scrollState: { + "originalText": "", + "needsScrolling": false, + "offset": 0, + "phase": 0 // 0=static start, 1=scrolling, 2=static end + , + "phaseCounter": 0 + } + + // Current max text length based on bar orientation + readonly property int currentMaxTextLength: isVerticalBar ? maxTextLength.vertical : maxTextLength.horizontal + + // Periodically run the text command (if set) + Timer { + id: refreshTimer + interval: Math.max(250, textIntervalMs) + repeat: true + running: (!isVerticalBar || currentMaxTextLength > 0) && !textStream && textCommand && textCommand.length > 0 + triggeredOnStart: true + onTriggered: root.runTextCommand() + } + + // Restart exited text stream commands after a delay + Timer { + id: restartTimer + interval: 1000 + running: (!isVerticalBar || currentMaxTextLength > 0) && textStream && !textProc.running + onTriggered: root.runTextCommand() + } + + // Timer for scrolling text display + Timer { + id: scrollTimer + interval: 300 + repeat: true + running: false + onTriggered: { + if (_scrollState.needsScrolling && _scrollState.originalText.length > currentMaxTextLength) { + // Traditional marquee with pause at beginning and end + if (_scrollState.phase === 0) { + // Static at beginning + _dynamicText = _scrollState.originalText.substring(0, Math.min(currentMaxTextLength, _scrollState.originalText.length)); + _scrollState.phaseCounter++; + if (_scrollState.phaseCounter >= _staticDuration) { + _scrollState.phaseCounter = 0; + _scrollState.phase = 1; // Move to scrolling + } + } else if (_scrollState.phase === 1) { + // Scrolling + _scrollState.offset++; + var start = _scrollState.offset; + var end = start + currentMaxTextLength; + + if (start >= _scrollState.originalText.length - currentMaxTextLength) { + // Reached or passed the end, ensure we show the last part + var textEnd = _scrollState.originalText.length; + var textStart = Math.max(0, textEnd - currentMaxTextLength); + _dynamicText = _scrollState.originalText.substring(textStart, textEnd); + _scrollState.phase = 2; // Move to static end phase + _scrollState.phaseCounter = 0; + } else { + _dynamicText = _scrollState.originalText.substring(start, end); + } + } else if (_scrollState.phase === 2) { + // Static at end + // Ensure end text is displayed correctly + var textEnd = _scrollState.originalText.length; + var textStart = Math.max(0, textEnd - currentMaxTextLength); + _dynamicText = _scrollState.originalText.substring(textStart, textEnd); + _scrollState.phaseCounter++; + if (_scrollState.phaseCounter >= _staticDuration) { + // Do NOT loop back to start, just stop scrolling + scrollTimer.stop(); + } + } + } else { + scrollTimer.stop(); + } + } + } + + SplitParser { + id: textStdoutSplit + onRead: line => root.parseDynamicContent(line) + } + + StdioCollector { + id: textStdoutCollect + onStreamFinished: () => root.parseDynamicContent(this.text) + } + + Process { + id: textProc + stdout: textStream ? textStdoutSplit : textStdoutCollect + stderr: StdioCollector {} + onExited: (exitCode, exitStatus) => { + if (textStream) { + Logger.w("CustomButton", `Streaming text command exited (code: ${exitCode}), restarting...`); + return; + } + } + } + + function parseDynamicContent(content) { + var contentStr = String(content || "").trim(); + + if (parseJson && contentStr) { + // Handle multi-line JSON by filtering empty lines and joining + var jsonStr = contentStr; + if (contentStr.includes('\n')) { + const lines = contentStr.split('\n').filter(line => line.trim() !== ''); + jsonStr = lines.join(''); + } + + try { + const parsed = JSON.parse(jsonStr); + const text = parsed.text || ""; + const icon = parsed.icon || ""; + let tooltip = parsed.tooltip || ""; + const color = parsed.color || ""; + + // Validate color value + const validColors = ["primary", "secondary", "tertiary", "error", "none"]; + const validColor = (color && validColors.includes(color)) ? color : ""; + + if (checkCollapse(text)) { + _scrollState.originalText = ""; + _dynamicText = ""; + _dynamicIcon = ""; + _dynamicTooltip = ""; + _dynamicColor = ""; + _scrollState.needsScrolling = false; + _scrollState.phase = 0; + _scrollState.phaseCounter = 0; + return; + } + + _scrollState.originalText = text; + _scrollState.needsScrolling = text.length > currentMaxTextLength && currentMaxTextLength > 0; + if (_scrollState.needsScrolling) { + _dynamicText = text.substring(0, currentMaxTextLength); + _scrollState.phase = 0; + _scrollState.phaseCounter = 0; + _scrollState.offset = 0; + scrollTimer.start(); + } else { + _dynamicText = text; + scrollTimer.stop(); + } + _dynamicIcon = icon; + _dynamicColor = validColor; + + _dynamicTooltip = toHtml(tooltip); + _scrollState.offset = 0; + return; + } catch (e) { + Logger.w("CustomButton", `Failed to parse JSON. Content: "${contentStr}"`); + } + } + + if (checkCollapse(contentStr)) { + _scrollState.originalText = ""; + _dynamicText = ""; + _dynamicIcon = ""; + _dynamicTooltip = ""; + _dynamicColor = ""; + _scrollState.needsScrolling = false; + _scrollState.phase = 0; + _scrollState.phaseCounter = 0; + return; + } + + _scrollState.originalText = contentStr; + _scrollState.needsScrolling = contentStr.length > currentMaxTextLength && currentMaxTextLength > 0; + if (_scrollState.needsScrolling) { + _dynamicText = contentStr.substring(0, currentMaxTextLength); + _scrollState.phase = 0; + _scrollState.phaseCounter = 0; + _scrollState.offset = 0; + scrollTimer.start(); + } else { + _dynamicText = contentStr; + scrollTimer.stop(); + } + _dynamicIcon = ""; + _dynamicColor = ""; + _dynamicTooltip = toHtml(contentStr); + _scrollState.offset = 0; + } + + function checkCollapse(text) { + if (!textCollapse || textCollapse.length === 0) { + return false; + } + + if (textCollapse.startsWith("/") && textCollapse.endsWith("/") && textCollapse.length > 1) { + // Treat as regex + var pattern = textCollapse.substring(1, textCollapse.length - 1); + try { + var regex = new RegExp(pattern); + return regex.test(text); + } catch (e) { + Logger.w("CustomButton", `Invalid regex for textCollapse: ${textCollapse} - ${e.message}`); + return (textCollapse === text); // Fallback to exact match on invalid regex + } + } else { + // Treat as plain string + return (textCollapse === text); + } + } + + function clicked() { + if (leftClickExec) { + Quickshell.execDetached(["sh", "-lc", leftClickExec]); + Logger.i("CustomButton", `Executing command: ${leftClickExec}`); + } else if (!leftClickUpdateText) { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + if (!textStream && leftClickUpdateText) { + runTextCommand(); + } + } + + function rightClicked() { + if (rightClickExec) { + Quickshell.execDetached(["sh", "-lc", rightClickExec]); + Logger.i("CustomButton", `Executing command: ${rightClickExec}`); + } else if (!rightClickUpdateText) { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + if (!textStream && rightClickUpdateText) { + runTextCommand(); + } + } + + function middleClicked() { + if (middleClickExec) { + Quickshell.execDetached(["sh", "-lc", middleClickExec]); + Logger.i("CustomButton", `Executing command: ${middleClickExec}`); + } else if (!middleClickUpdateText) { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + if (!textStream && middleClickUpdateText) { + runTextCommand(); + } + } + + function toHtml(str) { + const htmlTagRegex = /<\/?[a-zA-Z][^>]*>/g; + const placeholders = []; + let i = 0; + const protectedStr = str.replace(htmlTagRegex, tag => { + placeholders.push(tag); + return `___HTML_TAG_${i++}___`; + }); + + let escaped = protectedStr.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\r\n|\r|\n/g, "
"); + + escaped = escaped.replace(/___HTML_TAG_(\d+)___/g, (_, index) => placeholders[Number(index)]); + + return escaped; + } + + function runTextCommand() { + if (!textCommand || textCommand.length === 0) + return; + if (textProc.running) + return; + textProc.command = ["sh", "-lc", textCommand]; + textProc.running = true; + } + + function wheeled(delta) { + if (wheelMode === "unified" && wheelExec) { + let normalizedDelta = delta > 0 ? 1 : -1; + + let command = wheelExec.replace(/\$delta([+\-*/]\d+)?/g, function (match, operation) { + if (operation) { + try { + let operator = operation.charAt(0); + let operand = parseInt(operation.substring(1)); + + let result; + switch (operator) { + case '+': + result = normalizedDelta + operand; + break; + case '-': + result = normalizedDelta - operand; + break; + case '*': + result = normalizedDelta * operand; + break; + case '/': + result = Math.floor(normalizedDelta / operand); + break; + default: + result = normalizedDelta; + } + + return result.toString(); + } catch (e) { + Logger.w("CustomButton", `Error evaluating expression: ${match}, using normalized value ${normalizedDelta}`); + return normalizedDelta.toString(); + } + } else { + return normalizedDelta.toString(); + } + }); + + Quickshell.execDetached(["sh", "-lc", command]); + Logger.i("CustomButton", `Executing command: ${command}`); + } else if (wheelMode === "separate") { + if ((delta > 0 && wheelUpExec) || (delta < 0 && wheelDownExec)) { + let commandExec = delta > 0 ? wheelUpExec : wheelDownExec; + let normalizedDelta = delta > 0 ? 1 : -1; + + let command = commandExec.replace(/\$delta([+\-*/]\d+)?/g, function (match, operation) { + if (operation) { + try { + let operator = operation.charAt(0); + let operand = parseInt(operation.substring(1)); + + let result; + switch (operator) { + case '+': + result = normalizedDelta + operand; + break; + case '-': + result = normalizedDelta - operand; + break; + case '*': + result = normalizedDelta * operand; + break; + case '/': + result = Math.floor(normalizedDelta / operand); + break; + default: + result = normalizedDelta; + } + + return result.toString(); + } catch (e) { + Logger.w("CustomButton", `Error evaluating expression: ${match}, using normalized value ${normalizedDelta}`); + return normalizedDelta.toString(); + } + } else { + return normalizedDelta.toString(); + } + }); + + Quickshell.execDetached(["sh", "-lc", command]); + Logger.i("CustomButton", `Executing command: ${command}`); + } + } + + if (!textStream) { + if (wheelMode === "unified" && wheelUpdateText) { + runTextCommand(); + } else if (wheelMode === "separate") { + if ((delta > 0 && wheelUpUpdateText) || (delta < 0 && wheelDownUpdateText)) { + runTextCommand(); + } + } + } + } + + // Register IPC button in onLoaded (called by BarWidgetLoader after properties are set). + // Component.onCompleted is too early โ€” widgetSettings depends on section/index/screen + // which are set by BarWidgetLoader.onLoaded after the component is created. + function onLoaded() { + if (ipcIdentifier && ipcIdentifier.trim() !== "") + CustomButtonIPCService.registerButton(root); + } + + Component.onDestruction: { + if (ipcIdentifier && ipcIdentifier.trim() !== "") + CustomButtonIPCService.unregisterButton(root); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/DarkMode.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/DarkMode.qml new file mode 100644 index 0000000..11f8015 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/DarkMode.qml @@ -0,0 +1,68 @@ +import Quickshell +import qs.Commons +import qs.Services.UI +import qs.Widgets + +NIconButton { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string iconColorKey: widgetSettings.iconColor !== undefined ? widgetSettings.iconColor : widgetMetadata.iconColor + + icon: "dark-mode" + tooltipText: Settings.data.colorSchemes.darkMode ? I18n.tr("tooltips.switch-to-light-mode") : I18n.tr("tooltips.switch-to-dark-mode") + tooltipDirection: BarService.getTooltipDirection(screen?.name) + baseSize: Style.getCapsuleHeightForScreen(screen?.name) + applyUiScale: false + customRadius: Style.radiusL + colorBg: Style.capsuleColor + colorFg: Color.resolveColorKey(iconColorKey) + onClicked: Settings.data.colorSchemes.darkMode = !Settings.data.colorSchemes.darkMode + + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + onRightClicked: { + PanelService.showContextMenu(contextMenu, root, screen); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/KeepAwake.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/KeepAwake.qml new file mode 100644 index 0000000..03fe09e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/KeepAwake.qml @@ -0,0 +1,96 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Services.Power +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" + readonly property string iconColorKey: widgetSettings.iconColor !== undefined ? widgetSettings.iconColor : widgetMetadata.iconColor + readonly property string textColorKey: widgetSettings.textColor !== undefined ? widgetSettings.textColor : widgetMetadata.textColor + + implicitWidth: pill.width + implicitHeight: pill.height + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + BarPill { + id: pill + + screen: root.screen + text: IdleInhibitorService.timeout == null ? "" : Time.formatVagueHumanReadableDuration(IdleInhibitorService.timeout) + oppositeDirection: BarService.getPillDirection(root) + customIconColor: Color.resolveColorKeyOptional(root.iconColorKey) + customTextColor: Color.resolveColorKeyOptional(root.textColorKey) + icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off" + tooltipText: IdleInhibitorService.isInhibited ? I18n.tr("tooltips.keep-awake") : I18n.tr("tooltips.keep-awake") + onClicked: IdleInhibitorService.manualToggle() + onRightClicked: { + PanelService.showContextMenu(contextMenu, pill, screen); + } + forceOpen: IdleInhibitorService.timeout !== null + forceClose: IdleInhibitorService.timeout == null + onWheel: function (delta) { + var sign = delta > 0 ? 1 : -1; + // the offset makes scrolling down feel symmetrical to scrolling up + var timeout = IdleInhibitorService.timeout - (delta < 0 ? 60 : 0); + if (timeout == null || timeout < 600) { + delta = 60; // <= 10m, increment at 1m interval + } else if (timeout >= 600 && timeout < 1800) { + delta = 300; // >= 10m, increment at 5m interval + } else if (timeout >= 1800 && timeout < 3600) { + delta = 600; // >= 30m, increment at 10m interval + } else if (timeout >= 3600) { + delta = 1800; // > 1h, increment at 30m interval + } + + IdleInhibitorService.changeTimeout(delta * sign); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/KeyboardLayout.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/KeyboardLayout.qml new file mode 100644 index 0000000..b9b0b48 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/KeyboardLayout.qml @@ -0,0 +1,92 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Services.Compositor +import qs.Services.Keyboard +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" + + readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode + readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon + readonly property string iconColorKey: widgetSettings.iconColor !== undefined ? widgetSettings.iconColor : widgetMetadata.iconColor + readonly property string textColorKey: widgetSettings.textColor !== undefined ? widgetSettings.textColor : widgetMetadata.textColor + + // Use the shared service for keyboard layout + property string currentLayout: KeyboardLayoutService.currentLayout + + implicitWidth: pill.width + implicitHeight: pill.height + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + BarPill { + id: pill + anchors.verticalCenter: parent.verticalCenter + screen: root.screen + oppositeDirection: BarService.getPillDirection(root) + customIconColor: Color.resolveColorKeyOptional(root.iconColorKey) + customTextColor: Color.resolveColorKeyOptional(root.textColorKey) + icon: root.showIcon ? "keyboard" : "" + autoHide: false // Important to be false so we can hover as long as we want + text: isBarVertical ? currentLayout.substring(0, 3).toUpperCase() : currentLayout + tooltipText: KeyboardLayoutService.fullLayoutName + // When icon is disabled, always show the layout text + forceOpen: !root.showIcon || root.displayMode === "forceOpen" + forceClose: root.showIcon && root.displayMode === "alwaysHide" + onClicked: CompositorService.cycleKeyboardLayout() + onRightClicked: { + PanelService.showContextMenu(contextMenu, pill, screen); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Launcher.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Launcher.qml new file mode 100644 index 0000000..b12fb98 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Launcher.qml @@ -0,0 +1,119 @@ +import QtQuick +import Quickshell +import Quickshell.Widgets +import qs.Commons +import qs.Modules.Panels.Settings +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +NIconButton { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string customIcon: widgetSettings.icon || (widgetMetadata ? widgetMetadata.icon : "rocket") + readonly property bool useDistroLogo: widgetSettings.useDistroLogo !== undefined ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo + readonly property string customIconPath: widgetSettings.customIconPath !== undefined ? widgetSettings.customIconPath : widgetMetadata.customIconPath + readonly property bool enableColorization: widgetSettings.enableColorization !== undefined ? widgetSettings.enableColorization : widgetMetadata.enableColorization + readonly property string colorizeSystemIcon: widgetSettings.colorizeSystemIcon !== undefined ? widgetSettings.colorizeSystemIcon : widgetMetadata.colorizeSystemIcon + + readonly property color iconColor: { + if (!enableColorization) + return Color.mOnSurface; + return Color.resolveColorKey(colorizeSystemIcon); + } + + // If we have a custom path or are using distro logo, don't show the theme icon. + icon: (customIconPath === "" && !useDistroLogo) ? customIcon : "" + tooltipText: I18n.tr("actions.open-launcher") + tooltipDirection: BarService.getTooltipDirection(screenName) + baseSize: Style.getCapsuleHeightForScreen(screenName) + applyUiScale: false + customRadius: Style.radiusL + colorBg: Style.capsuleColor + colorFg: iconColor + colorBgHover: Color.mHover + colorFgHover: Color.mOnHover + colorBorder: Style.capsuleBorderColor + colorBorderHover: Style.capsuleBorderColor + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.launcher-settings"), + "action": "launcher-settings", + "icon": "adjustments" + }, + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + } + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "launcher-settings") { + var panel = PanelService.getPanel("settingsPanel", screen); + panel.requestedTab = SettingsPanel.Tab.Launcher; + panel.toggle(); + } else if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + onClicked: PanelService.toggleLauncher(screen) + onMiddleClicked: PanelService.toggleLauncher(screen) + onRightClicked: { + PanelService.showContextMenu(contextMenu, root, screen); + } + + IconImage { + id: customOrDistroLogo + anchors.centerIn: parent + width: root.buttonSize * 0.8 + height: width + source: { + if (useDistroLogo) + return HostService.osLogo; + if (customIconPath !== "") + return customIconPath.startsWith("file://") ? customIconPath : "file://" + customIconPath; + return ""; + } + visible: source !== "" + smooth: true + asynchronous: true + layer.enabled: (enableColorization) && (useDistroLogo || customIconPath !== "") + layer.effect: ShaderEffect { + property color targetColor: !hovering ? iconColor : Color.mOnHover + property real colorizeMode: 2.0 + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/LockKeys.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/LockKeys.qml new file mode 100644 index 0000000..f903a4b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/LockKeys.qml @@ -0,0 +1,165 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Modules.Panels.Settings +import qs.Services.Keyboard +import qs.Services.UI +import qs.Widgets + +//test +Item { + id: root + + property ShellScreen screen + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + // Settings + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isVertical: barPosition === "left" || barPosition === "right" + readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName) + + // Content dimensions for implicit sizing + readonly property real contentWidth: isVertical ? capsuleHeight : Math.round(layout.implicitWidth + Style.margin2M) + readonly property real contentHeight: isVertical ? Math.round(layout.implicitHeight + Style.margin2M) : capsuleHeight + + readonly property bool hideWhenOff: (widgetSettings.hideWhenOff !== undefined) ? widgetSettings.hideWhenOff : (widgetMetadata.hideWhenOff !== undefined ? widgetMetadata.hideWhenOff : false) + + readonly property string capsIcon: widgetSettings.capsLockIcon !== undefined ? widgetSettings.capsLockIcon : widgetMetadata.capsLockIcon + readonly property string numIcon: widgetSettings.numLockIcon !== undefined ? widgetSettings.numLockIcon : widgetMetadata.numLockIcon + readonly property string scrollIcon: widgetSettings.scrollLockIcon !== undefined ? widgetSettings.scrollLockIcon : widgetMetadata.scrollLockIcon + readonly property bool showCaps: (widgetSettings.showCapsLock !== undefined) ? widgetSettings.showCapsLock : widgetMetadata.showCapsLock + readonly property bool showNum: (widgetSettings.showNumLock !== undefined) ? widgetSettings.showNumLock : widgetMetadata.showNumLock + readonly property bool showScroll: (widgetSettings.showScrollLock !== undefined) ? widgetSettings.showScrollLock : widgetMetadata.showScrollLock + + visible: !root.hideWhenOff || (root.showCaps && LockKeysService.capsLockOn) || (root.showNum && LockKeysService.numLockOn) || (root.showScroll && LockKeysService.scrollLockOn) + + Component.onCompleted: LockKeysService.registerComponent("bar-lockkeys:" + (screen?.name || "unknown")) + Component.onDestruction: LockKeysService.unregisterComponent("bar-lockkeys:" + (screen?.name || "unknown")) + + implicitHeight: contentHeight + implicitWidth: contentWidth + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + // Visual capsule centered in parent + Rectangle { + id: visualCapsule + anchors.centerIn: parent + color: Style.capsuleColor + width: root.contentWidth + height: root.contentHeight + radius: Style.radiusM + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + Item { + id: layout + + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + implicitHeight: rowLayout.visible ? rowLayout.implicitHeight : colLayout.implicitHeight + implicitWidth: rowLayout.visible ? rowLayout.implicitWidth : colLayout.implicitWidth + + RowLayout { + id: rowLayout + + spacing: 0 + visible: !root.isVertical + + NIcon { + color: LockKeysService.capsLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3) + icon: root.capsIcon + visible: root.showCaps && (!root.hideWhenOff || LockKeysService.capsLockOn) + } + + NIcon { + color: LockKeysService.numLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3) + icon: root.numIcon + visible: root.showNum && (!root.hideWhenOff || LockKeysService.numLockOn) + } + + NIcon { + color: LockKeysService.scrollLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3) + icon: root.scrollIcon + visible: root.showScroll && (!root.hideWhenOff || LockKeysService.scrollLockOn) + } + } + + ColumnLayout { + id: colLayout + + spacing: 0 + visible: root.isVertical + + NIcon { + color: LockKeysService.capsLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3) + icon: root.capsIcon + visible: root.showCaps && (!root.hideWhenOff || LockKeysService.capsLockOn) + } + + NIcon { + color: LockKeysService.numLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3) + icon: root.numIcon + visible: root.showNum && (!root.hideWhenOff || LockKeysService.numLockOn) + } + + NIcon { + color: LockKeysService.scrollLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3) + icon: root.scrollIcon + visible: root.showScroll && (!root.hideWhenOff || LockKeysService.scrollLockOn) + } + } + } + } + + // MouseArea at root level for extended click area + MouseArea { + acceptedButtons: Qt.RightButton + anchors.fill: parent + + onClicked: mouse => { + if (mouse.button === Qt.RightButton) { + PanelService.showContextMenu(contextMenu, root, screen); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/MediaMini.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/MediaMini.qml new file mode 100644 index 0000000..d7b6b74 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/MediaMini.qml @@ -0,0 +1,510 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Services.Media +import qs.Services.UI +import qs.Widgets +import qs.Widgets.AudioSpectrum + +Item { + id: root + + property ShellScreen screen + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + // Settings + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + // Bar orientation (per-screen) + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isVertical: barPosition === "left" || barPosition === "right" + readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName) + readonly property real barFontSize: Style.getBarFontSizeForScreen(screenName) + + // Widget settings + readonly property string hideMode: widgetSettings.hideMode !== undefined ? widgetSettings.hideMode : widgetMetadata.hideMode + readonly property bool hideWhenIdle: widgetSettings.hideWhenIdle !== undefined ? widgetSettings.hideWhenIdle : widgetMetadata.hideWhenIdle + readonly property bool showAlbumArt: widgetSettings.showAlbumArt !== undefined ? widgetSettings.showAlbumArt : widgetMetadata.showAlbumArt + readonly property bool showArtistFirst: widgetSettings.showArtistFirst !== undefined ? widgetSettings.showArtistFirst : widgetMetadata.showArtistFirst + readonly property bool showVisualizer: widgetSettings.showVisualizer !== undefined ? widgetSettings.showVisualizer : widgetMetadata.showVisualizer + readonly property string visualizerType: widgetSettings.visualizerType !== undefined ? widgetSettings.visualizerType : widgetMetadata.visualizerType + readonly property string scrollingMode: widgetSettings.scrollingMode !== undefined ? widgetSettings.scrollingMode : widgetMetadata.scrollingMode + readonly property bool showProgressRing: widgetSettings.showProgressRing !== undefined ? widgetSettings.showProgressRing : widgetMetadata.showProgressRing + readonly property bool useFixedWidth: widgetSettings.useFixedWidth !== undefined ? widgetSettings.useFixedWidth : widgetMetadata.useFixedWidth + readonly property real maxWidth: widgetSettings.maxWidth !== undefined ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen ? screen.width * 0.06 : 0) + readonly property string textColorKey: widgetSettings.textColor !== undefined ? widgetSettings.textColor : widgetMetadata.textColor + readonly property color textColor: Color.resolveColorKey(textColorKey) + + // Dimensions + readonly property int artSize: Style.toOdd(capsuleHeight * 0.75) + readonly property int iconSize: Style.toOdd(capsuleHeight * 0.75) + readonly property int verticalSize: Style.toOdd(capsuleHeight * 0.85) + readonly property int progressWidth: 2 + + // State + readonly property bool hasPlayer: MediaService.currentPlayer !== null + readonly property bool shouldHideIdle: (hideMode === "idle" || hideWhenIdle) && !MediaService.isPlaying + readonly property bool shouldHideEmpty: !hasPlayer && hideMode === "hidden" + readonly property bool isHidden: shouldHideIdle || shouldHideEmpty + + // Title + readonly property string title: { + if (!hasPlayer) + return I18n.tr("bar.media-mini.no-active-player"); + var artist = MediaService.trackArtist; + var track = MediaService.trackTitle; + return showArtistFirst ? (artist ? `${artist} - ${track}` : track) : (artist ? `${track} - ${artist}` : track); + } + + // SpectrumService registration for visualizer + readonly property string spectrumComponentId: "bar:mediamini:" + root.screen?.name + ":" + root.section + ":" + root.sectionWidgetIndex + readonly property bool needsSpectrum: root.showVisualizer && root.visualizerType !== "" && root.visualizerType !== "none" && !root.isHidden + + Layout.preferredHeight: isVertical ? -1 : Style.getBarHeightForScreen(screenName) + Layout.preferredWidth: isVertical ? Style.getBarHeightForScreen(screenName) : -1 + Layout.fillHeight: false + Layout.fillWidth: false + + onNeedsSpectrumChanged: { + if (root.needsSpectrum) { + SpectrumService.registerComponent(root.spectrumComponentId); + } else { + SpectrumService.unregisterComponent(root.spectrumComponentId); + } + } + + Component.onCompleted: { + if (root.needsSpectrum) { + SpectrumService.registerComponent(root.spectrumComponentId); + } + } + + Component.onDestruction: { + SpectrumService.unregisterComponent(root.spectrumComponentId); + } + + // Layout + // For horizontal bars, height is always capsuleHeight (no animation needed to prevent jitter) + // For vertical bars, collapse to 0 when hidden + implicitWidth: isVertical ? (isHidden ? 0 : verticalSize) : (isHidden ? 0 : contentWidth) + implicitHeight: isVertical ? (isHidden ? 0 : verticalSize) : capsuleHeight + visible: !shouldHideIdle && (hideMode !== "hidden" || opacity > 0) + opacity: isHidden ? 0.0 : ((hideMode === "transparent" && !hasPlayer) ? 0.0 : 1.0) + + property real mainContentWidth: 0 + readonly property real contentWidth: { + if (useFixedWidth) + return maxWidth; + + // Calculate icon/art width (must match RowLayout visibility) + var iconWidth = 0; + if (!hasPlayer) { + iconWidth = iconSize; + } else if (showAlbumArt || showProgressRing) { + iconWidth = artSize; + } + + var margins = isVertical ? 0 : Style.margin2S; + + // Add spacing and text width + var textWidth = 0; + if (titleContainer.measuredWidth > 0) { + if (iconWidth > 0) + margins += Style.marginS; + textWidth = titleContainer.measuredWidth + Style.margin2XXS; + } + + var total = iconWidth + textWidth + margins; + + // calculate the width of all elements except the scrolling text + mainContentWidth = total - textWidth; + + return hasPlayer ? Math.min(total, maxWidth) : total; + } + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutCubic + } + } + Behavior on implicitWidth { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutCubic + } + } + Behavior on implicitHeight { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutCubic + } + } + + // Context menu + NPopupContextMenu { + id: contextMenu + model: { + var items = []; + if (hasPlayer && MediaService.canPlay) { + items.push({ + "label": MediaService.isPlaying ? I18n.tr("common.pause") : I18n.tr("common.play"), + "action": "play-pause", + "icon": MediaService.isPlaying ? "media-pause" : "media-play" + }); + } + if (hasPlayer && MediaService.canGoPrevious) { + items.push({ + "label": I18n.tr("common.previous"), + "action": "previous", + "icon": "media-prev" + }); + } + if (hasPlayer && MediaService.canGoNext) { + items.push({ + "label": I18n.tr("common.next"), + "action": "next", + "icon": "media-next" + }); + } + + // Append available players (like in Control Center) so user can switch from the bar + var players = MediaService.getAvailablePlayers ? MediaService.getAvailablePlayers() : []; + if (players && players.length > 1) { + for (var i = 0; i < players.length; i++) { + var isCurrent = (i === MediaService.selectedPlayerIndex); + items.push({ + "label": players[i].identity, + "action": "player-" + i, + "icon": isCurrent ? "check" : "disc", + "enabled": true, + "visible": true + }); + } + } + + items.push({ + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }); + return items; + } + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "play-pause") + MediaService.playPause(); + else if (action === "previous") + MediaService.previous(); + else if (action === "next") + MediaService.next(); + else if (action && action.indexOf("player-") === 0) { + var idx = parseInt(action.split("-")[1]); + if (!isNaN(idx)) { + MediaService.switchToPlayer(idx); + } + } else if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + // Main container - stays at content size, pixel-perfect centered in parent + Rectangle { + id: container + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, height) + width: Style.toOdd(isVertical ? (isHidden ? 0 : verticalSize) : (isHidden ? 0 : contentWidth)) + height: Style.toOdd(isVertical ? (isHidden ? 0 : verticalSize) : capsuleHeight) + radius: Style.radiusM + color: Style.capsuleColor + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + Behavior on width { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutCubic + } + } + Behavior on height { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutCubic + } + } + + Item { + anchors.fill: parent + anchors.leftMargin: isVertical ? 0 : Style.marginS + anchors.rightMargin: isVertical ? 0 : Style.marginS + + // Visualizer + Loader { + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, height) + width: Style.toOdd(parent.width) + height: Style.toOdd(parent.height) + active: showVisualizer + z: 0 + sourceComponent: { + if (!showVisualizer) + return null; + if (visualizerType === "linear") + return linearSpectrum; + if (visualizerType === "mirrored") + return mirroredSpectrum; + if (visualizerType === "wave") + return waveSpectrum; + return null; + } + } + + // Horizontal layout + RowLayout { + anchors.fill: parent + anchors.verticalCenter: parent.verticalCenter + spacing: Style.marginS + visible: !isVertical + z: 1 + + // Album art / Progress ring + Item { + visible: hasPlayer && (showAlbumArt || showProgressRing) + Layout.preferredWidth: visible ? artSize : 0 + Layout.preferredHeight: visible ? artSize : 0 + Layout.alignment: Qt.AlignVCenter + + ProgressRing { + id: progressRing + anchors.fill: parent + visible: showProgressRing + progress: MediaService.trackLength > 0 ? MediaService.currentPosition / MediaService.trackLength : 0 + lineWidth: root.progressWidth + } + + NImageRounded { + visible: showAlbumArt && hasPlayer + anchors.fill: parent + anchors.margins: showProgressRing ? root.progressWidth * 2 : 0 + radius: width / 2 + imagePath: MediaService.trackArtUrl + borderWidth: 0 + imageFillMode: Image.PreserveAspectCrop + } + } + + // Scrolling title + NScrollText { + id: titleContainer + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + Layout.preferredHeight: capsuleHeight + fadeRoundLeftCorners: !(showAlbumArt || showProgressRing) + + text: title + + scrollMode: { + if (scrollingMode === "always") + return NScrollText.ScrollMode.Always; + if (scrollingMode === "hover") + return NScrollText.ScrollMode.Hover; + return NScrollText.ScrollMode.Never; + } + cursorShape: hasPlayer ? Qt.PointingHandCursor : Qt.ArrowCursor + maxWidth: root.maxWidth - root.mainContentWidth + forcedHover: mainMouseArea.containsMouse + fadeExtent: 0.1 + fadeCornerRadius: Style.radiusM + + NText { + color: hasPlayer ? root.textColor : Color.mOnSurfaceVariant + pointSize: barFontSize + elide: Text.ElideNone + } + } + } + + // Vertical layout + Item { + id: verticalLayout + visible: isVertical + width: Style.toOdd(verticalSize) + height: Style.toOdd(width) + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, height) + z: 1 + + ProgressRing { + anchors.fill: parent + visible: showProgressRing + progress: MediaService.trackLength > 0 ? MediaService.currentPosition / MediaService.trackLength : 0 + lineWidth: root.progressWidth + } + + NImageRounded { + visible: showAlbumArt && hasPlayer + anchors.fill: parent + anchors.margins: showProgressRing ? root.progressWidth * 2 : 0 + radius: width / 2 + imagePath: MediaService.trackArtUrl + borderWidth: 0 + imageFillMode: Image.PreserveAspectCrop + } + } + + // Mouse interaction moved to root + } + } + + // Mouse interaction + MouseArea { + id: mainMouseArea + anchors.fill: parent + + // Extend click area to screen edge if widget is at the start/end + anchors.leftMargin: (!isVertical && section === "left" && sectionWidgetIndex === 0) ? -Style.marginS : 0 + anchors.rightMargin: (!isVertical && section === "right" && sectionWidgetIndex === sectionWidgetsCount - 1) ? -Style.marginS : 0 + anchors.topMargin: (isVertical && section === "left" && sectionWidgetIndex === 0) ? -Style.marginM : 0 + anchors.bottomMargin: (isVertical && section === "right" && sectionWidgetIndex === sectionWidgetsCount - 1) ? -Style.marginM : 0 + + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton | Qt.ForwardButton | Qt.BackButton + + onClicked: mouse => { + TooltipService.hide(); + if (mouse.button === Qt.LeftButton) { + PanelService.getPanel("mediaPlayerPanel", screen)?.toggle(container); + } else if (mouse.button === Qt.RightButton) { + PanelService.showContextMenu(contextMenu, container, screen); + } else if (mouse.button === Qt.MiddleButton && hasPlayer) { + MediaService.playPause(); + } else if (mouse.button === Qt.ForwardButton && hasPlayer) { + MediaService.next(); + } else if (mouse.button === Qt.BackButton && hasPlayer) { + MediaService.previous(); + } + } + + onEntered: { + if (!root || !screen) { + return; + } + var scrollMode = scrollingMode; + if ((isVertical || scrollMode === "never")) { + var panel = PanelService.getPanel("mediaPlayerPanel", screen); + if (panel && !panel.isPanelOpen) { + TooltipService.show(root, title, BarService.getTooltipDirection(root.screen?.name)); + } + } + } + onExited: TooltipService.hide() + } + + // Components + Component { + id: linearSpectrum + NLinearSpectrum { + width: parent.width - Style.marginS + height: 20 + values: SpectrumService.values + fillColor: Color.mPrimary + opacity: 0.4 + barPosition: root.barPosition + mirrored: Settings.data.audio.spectrumMirrored + } + } + + Component { + id: mirroredSpectrum + NMirroredSpectrum { + width: parent.width - Style.marginS + height: parent.height - Style.marginS + values: SpectrumService.values + fillColor: Color.mPrimary + opacity: 0.4 + mirrored: Settings.data.audio.spectrumMirrored + } + } + + Component { + id: waveSpectrum + NWaveSpectrum { + width: parent.width - Style.marginS + height: parent.height - Style.marginS + values: SpectrumService.values + fillColor: Color.mPrimary + opacity: 0.4 + mirrored: Settings.data.audio.spectrumMirrored + } + } + + // Progress Ring Component + component ProgressRing: Canvas { + property real progress: 0 + property real lineWidth: 2 + + function repaint() { + if (this.visible && this.opacity > 0) + requestPaint(); + } + + onProgressChanged: repaint() + Component.onCompleted: repaint() + + Connections { + target: Color + function onMPrimaryChanged() { + repaint(); + } + } + + onPaint: { + if (width <= 0 || height <= 0) + return; + + var ctx = getContext("2d"); + var centerX = width / 2; + var centerY = height / 2; + var radius = Math.min(width, height) / 2 - lineWidth; + + ctx.reset(); + + // Background + ctx.beginPath(); + ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI); + ctx.lineWidth = lineWidth; + ctx.strokeStyle = Qt.alpha(Color.mOnSurface, 0.4); + ctx.stroke(); + + // Progress + ctx.beginPath(); + ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI); + ctx.lineWidth = lineWidth; + ctx.strokeStyle = Color.mPrimary; + ctx.lineCap = "round"; + ctx.stroke(); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Microphone.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Microphone.qml new file mode 100644 index 0000000..dab422d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Microphone.qml @@ -0,0 +1,185 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import Quickshell.Io +import Quickshell.Services.Pipewire +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Modules.Panels.Settings +import qs.Services.Media +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" + readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode + readonly property string middleClickCommand: (widgetSettings.middleClickCommand !== undefined) ? widgetSettings.middleClickCommand : widgetMetadata.middleClickCommand + readonly property string iconColorKey: widgetSettings.iconColor !== undefined ? widgetSettings.iconColor : widgetMetadata.iconColor + readonly property string textColorKey: widgetSettings.textColor !== undefined ? widgetSettings.textColor : widgetMetadata.textColor + + // Used to avoid opening the pill on Quickshell startup + property bool firstInputVolumeReceived: false + property int wheelAccumulator: 0 + + implicitWidth: pill.width + implicitHeight: pill.height + + // Connection used to open the pill when input volume changes + Connections { + target: AudioService.source?.audio ? AudioService.source?.audio : null + function onVolumeChanged() { + // Logger.i("Bar:Microphone", "onInputVolumeChanged") + if (!firstInputVolumeReceived) { + // Ignore the first volume change + firstInputVolumeReceived = true; + } else { + // If a tooltip is visible while we show the pill + // hide it so it doesn't overlap the volume slider. + TooltipService.hide(); + pill.show(); + externalHideTimer.restart(); + } + } + } + + // Connection used to open the pill when input mute state changes + Connections { + target: AudioService.source?.audio ? AudioService.source?.audio : null + function onMutedChanged() { + // Logger.i("Bar:Microphone", "onInputMutedChanged") + if (!firstInputVolumeReceived) { + // Ignore the first mute change + firstInputVolumeReceived = true; + } else { + TooltipService.hide(); + pill.show(); + externalHideTimer.restart(); + } + } + } + + Timer { + id: externalHideTimer + running: false + interval: 1500 + onTriggered: { + pill.hide(); + } + } + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.toggle-mute"), + "action": "toggle-mute", + "icon": AudioService.inputMuted ? "microphone-off" : "microphone" + }, + { + "label": I18n.tr("actions.run-custom-command"), + "action": "custom-command", + "icon": "adjustments" + }, + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "toggle-mute") { + AudioService.setInputMuted(!AudioService.inputMuted); + } else if (action === "custom-command") { + Quickshell.execDetached(["sh", "-c", middleClickCommand]); + } else if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + BarPill { + id: pill + + screen: root.screen + oppositeDirection: BarService.getPillDirection(root) + customIconColor: Color.resolveColorKeyOptional(root.iconColorKey) + customTextColor: Color.resolveColorKeyOptional(root.textColorKey) + icon: AudioService.getInputIcon() + autoHide: false // Important to be false so we can hover as long as we want + text: { + const maxVolume = Settings.data.audio.volumeOverdrive ? 1.5 : 1.0; + const displayVolume = Math.min(maxVolume, AudioService.inputVolume); + return Math.round(displayVolume * 100); + } + suffix: "%" + forceOpen: displayMode === "alwaysShow" + forceClose: displayMode === "alwaysHide" + tooltipText: { + if (PanelService.getPanel("audioPanel", screen)?.isPanelOpen) { + return ""; + } else { + const nick = AudioService.source?.nickname ?? ""; + const volumeText = I18n.tr("tooltips.microphone-volume-at", { + "volume": (() => { + const maxVolume = Settings.data.audio.volumeOverdrive ? 1.5 : 1.0; + const displayVolume = Math.min(maxVolume, AudioService.inputVolume); + return Math.round(displayVolume * 100); + })() + }); + return nick ? volumeText + "\n" + nick : volumeText; + } + } + + onWheel: function (delta) { + // As soon as we start scrolling to adjust volume, hide the tooltip + TooltipService.hide(); + + wheelAccumulator += delta; + if (wheelAccumulator >= 120) { + wheelAccumulator = 0; + AudioService.setInputVolume(AudioService.inputVolume + AudioService.stepVolume); + } else if (wheelAccumulator <= -120) { + wheelAccumulator = 0; + AudioService.setInputVolume(AudioService.inputVolume - AudioService.stepVolume); + } + } + onClicked: { + PanelService.getPanel("audioPanel", screen)?.toggle(this); + } + onRightClicked: { + PanelService.showContextMenu(contextMenu, pill, screen); + } + onMiddleClicked: { + Quickshell.execDetached(["sh", "-c", middleClickCommand]); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Network.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Network.qml new file mode 100644 index 0000000..e7687ab --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Network.qml @@ -0,0 +1,105 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Modules.Panels.Settings // For SettingsPanel +import qs.Services.Networking +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" + readonly property string displayMode: widgetSettings.displayMode !== undefined ? widgetSettings.displayMode : widgetMetadata.displayMode + readonly property string iconColorKey: widgetSettings.iconColor !== undefined ? widgetSettings.iconColor : widgetMetadata.iconColor + readonly property string textColorKey: widgetSettings.textColor !== undefined ? widgetSettings.textColor : widgetMetadata.textColor + + implicitWidth: pill.width + implicitHeight: pill.height + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": NetworkService.wifiEnabled ? I18n.tr("actions.disable-wifi") : I18n.tr("actions.enable-wifi"), + "action": "toggle-wifi", + "icon": NetworkService.wifiEnabled ? "wifi-off" : "wifi", + "enabled": !NetworkService.airplaneModeEnabled && NetworkService.wifiAvailable + }, + { + "label": I18n.tr("common.wifi") + " " + I18n.tr("tooltips.open-settings"), + "action": "wifi-settings", + "icon": "settings" + }, + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "toggle-wifi") { + NetworkService.setWifiEnabled(!NetworkService.wifiEnabled); + } else if (action === "wifi-settings") { + SettingsPanelService.openToTab(SettingsPanel.Tab.Connections, 0, screen); + } else if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + BarPill { + id: pill + screen: root.screen + oppositeDirection: BarService.getPillDirection(root) + customIconColor: Color.resolveColorKeyOptional(root.iconColorKey) + customTextColor: Color.resolveColorKeyOptional(root.textColorKey) + icon: NetworkService.getIcon() + text: NetworkService.getStatusText(false) + autoHide: false + forceOpen: !isBarVertical && root.displayMode === "alwaysShow" + forceClose: isBarVertical || root.displayMode === "alwaysHide" || text === "" + onClicked: { + var panel = PanelService.getPanel("networkPanel", screen); + panel?.toggle(this); + } + onRightClicked: { + PanelService.showContextMenu(contextMenu, pill, screen); + } + tooltipText: { + if (PanelService.getPanel("networkPanel", screen)?.isPanelOpen) { + return ""; + } + return NetworkService.getStatusText(true); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/NightLight.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/NightLight.qml new file mode 100644 index 0000000..8c5fe5b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/NightLight.qml @@ -0,0 +1,90 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Modules.Panels.Settings +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +NIconButton { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string iconColorKey: widgetSettings.iconColor !== undefined ? widgetSettings.iconColor : widgetMetadata.iconColor + + baseSize: Style.getCapsuleHeightForScreen(screen?.name) + applyUiScale: false + customRadius: Style.radiusL + colorBg: Settings.data.nightLight.enabled ? Color.mPrimary : Style.capsuleColor + colorFg: Settings.data.nightLight.enabled ? Color.mOnPrimary : Color.resolveColorKey(iconColorKey) + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + icon: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? "nightlight-forced" : "nightlight-on") : "nightlight-off" + tooltipText: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? I18n.tr("common.night-light") : I18n.tr("common.night-light")) : I18n.tr("common.night-light") + tooltipDirection: BarService.getTooltipDirection(screen?.name) + onClicked: { + // Check if wlsunset is available before enabling night light + if (!ProgramCheckerService.wlsunsetAvailable) { + ToastService.showWarning(I18n.tr("common.night-light"), I18n.tr("toast.night-light.not-installed")); + return; + } + + if (!Settings.data.nightLight.enabled) { + Settings.data.nightLight.enabled = true; + Settings.data.nightLight.forced = false; + } else if (Settings.data.nightLight.enabled && !Settings.data.nightLight.forced) { + Settings.data.nightLight.forced = true; + } else { + Settings.data.nightLight.enabled = false; + Settings.data.nightLight.forced = false; + } + } + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + onRightClicked: { + PanelService.showContextMenu(contextMenu, root, screen); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/NoctaliaPerformance.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/NoctaliaPerformance.qml new file mode 100644 index 0000000..ef70b3f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/NoctaliaPerformance.qml @@ -0,0 +1,74 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Modules.Panels.Settings +import qs.Services.Power +import qs.Services.UI +import qs.Widgets + +NIconButton { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string iconColorKey: widgetSettings.iconColor !== undefined ? widgetSettings.iconColor : widgetMetadata.iconColor + + baseSize: Style.getCapsuleHeightForScreen(screen?.name) + applyUiScale: false + customRadius: Style.radiusL + colorBg: PowerProfileService.noctaliaPerformanceMode ? Color.mPrimary : Style.capsuleColor + colorFg: PowerProfileService.noctaliaPerformanceMode ? Color.mOnPrimary : Color.resolveColorKey(iconColorKey) + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + icon: PowerProfileService.noctaliaPerformanceMode ? "rocket" : "rocket-off" + tooltipText: PowerProfileService.noctaliaPerformanceMode ? I18n.tr("tooltips.noctalia-performance-enabled") : I18n.tr("tooltips.noctalia-performance-enabled") + tooltipDirection: BarService.getTooltipDirection(screen?.name) + onClicked: PowerProfileService.toggleNoctaliaPerformance() + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + onRightClicked: { + PanelService.showContextMenu(contextMenu, root, screen); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/NotificationHistory.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/NotificationHistory.qml new file mode 100644 index 0000000..13596e5 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/NotificationHistory.qml @@ -0,0 +1,139 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +NIconButton { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + readonly property bool showUnreadBadge: widgetSettings.showUnreadBadge !== undefined ? widgetSettings.showUnreadBadge : widgetMetadata.showUnreadBadge + readonly property bool hideWhenZero: widgetSettings.hideWhenZero !== undefined ? widgetSettings.hideWhenZero : widgetMetadata.hideWhenZero + readonly property bool hideWhenZeroUnread: widgetSettings.hideWhenZeroUnread !== undefined ? widgetSettings.hideWhenZeroUnread : widgetMetadata.hideWhenZeroUnread + readonly property string unreadBadgeColor: widgetSettings.unreadBadgeColor !== undefined ? widgetSettings.unreadBadgeColor : widgetMetadata.unreadBadgeColor + readonly property string iconColorKey: widgetSettings.iconColor !== undefined ? widgetSettings.iconColor : widgetMetadata.iconColor + + readonly property color badgeColor: Color.resolveColorKey(unreadBadgeColor) + + function computeUnreadCount() { + var since = NotificationService.lastSeenTs; + var count = 0; + var model = NotificationService.historyModel; + for (var i = 0; i < model.count; i++) { + var item = model.get(i); + var ts = item.timestamp instanceof Date ? item.timestamp.getTime() : item.timestamp; + if (ts > since) + count++; + } + return count; + } + + readonly property int count: computeUnreadCount() + + baseSize: Style.getCapsuleHeightForScreen(screen?.name) + applyUiScale: false + customRadius: Style.radiusL + icon: NotificationService.doNotDisturb ? "bell-off" : "bell" + tooltipText: { + if (PanelService.getPanel("notificationHistoryPanel", screen)?.isPanelOpen) { + return ""; + } else { + return I18n.tr("tooltips.open-notification-history-enable-dnd"); + } + } + tooltipDirection: BarService.getTooltipDirection(screen?.name) + colorBg: Style.capsuleColor + colorFg: Color.resolveColorKey(iconColorKey) + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + visible: !((hideWhenZero && NotificationService.historyModel.count === 0) || (hideWhenZeroUnread && count === 0)) + opacity: !((hideWhenZero && NotificationService.historyModel.count === 0) || (hideWhenZeroUnread && count === 0)) ? 1.0 : 0.0 + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": NotificationService.doNotDisturb ? I18n.tr("actions.disable-dnd") : I18n.tr("actions.enable-dnd"), + "action": "toggle-dnd", + "icon": NotificationService.doNotDisturb ? "bell" : "bell-off" + }, + { + "label": I18n.tr("actions.clear-history"), + "action": "clear-history", + "icon": "trash" + }, + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "toggle-dnd") { + NotificationService.doNotDisturb = !NotificationService.doNotDisturb; + } else if (action === "clear-history") { + NotificationService.clearHistory(); + } else if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + onClicked: { + var panel = PanelService.getPanel("notificationHistoryPanel", screen); + panel?.toggle(this); + } + + onRightClicked: { + PanelService.showContextMenu(contextMenu, root, screen); + } + + Loader { + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenterOffset: parent.baseSize / 4 + anchors.verticalCenterOffset: -parent.baseSize / 4 + z: 2 + active: showUnreadBadge + sourceComponent: Rectangle { + id: badge + height: 7 + width: height + radius: Style.radiusXS + color: root.hovering ? Color.mOnHover : (root.badgeColor || Color.mError) + border.color: Color.mSurface + border.width: Style.borderS + visible: count > 0 + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/PowerProfile.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/PowerProfile.qml new file mode 100644 index 0000000..9ec9742 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/PowerProfile.qml @@ -0,0 +1,74 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.UPower +import qs.Commons +import qs.Services.Power +import qs.Services.UI +import qs.Widgets + +NIconButton { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string iconColorKey: widgetSettings.iconColor !== undefined ? widgetSettings.iconColor : widgetMetadata.iconColor + + baseSize: Style.getCapsuleHeightForScreen(screen?.name) + applyUiScale: false + customRadius: Style.radiusL + visible: PowerProfileService.available + icon: PowerProfileService.getIcon() + tooltipText: I18n.tr("tooltips.power-profile", { + "profile": PowerProfileService.getName() + }) + tooltipDirection: BarService.getTooltipDirection(screen?.name) + colorBg: (PowerProfileService.profile === PowerProfile.Balanced) ? Style.capsuleColor : Color.mPrimary + colorFg: (PowerProfileService.profile === PowerProfile.Balanced) ? Color.resolveColorKey(iconColorKey) : Color.mOnPrimary + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + onClicked: PowerProfileService.cycleProfile() + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + onRightClicked: { + PanelService.showContextMenu(contextMenu, root, screen); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/SessionMenu.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/SessionMenu.qml new file mode 100644 index 0000000..ba862bc --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/SessionMenu.qml @@ -0,0 +1,77 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Services.UI +import qs.Widgets + +NIconButton { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string iconColorKey: (widgetSettings.iconColor !== undefined) ? widgetSettings.iconColor : widgetMetadata.iconColor + + baseSize: Style.getCapsuleHeightForScreen(screenName) + applyUiScale: false + customRadius: Style.radiusL + icon: "power" + tooltipText: { + if (PanelService.getPanel("sessionMenuPanel", screen)?.isPanelOpen) + return ""; + else + return I18n.tr("tooltips.session-menu"); + } + tooltipDirection: BarService.getTooltipDirection(screenName) + colorBg: Style.capsuleColor + colorFg: Color.resolveColorKey(iconColorKey) + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + onClicked: PanelService.getPanel("sessionMenuPanel", screen)?.toggle() + onRightClicked: { + PanelService.showContextMenu(contextMenu, root, screen); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Settings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Settings.qml new file mode 100644 index 0000000..a7cecbf --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Settings.qml @@ -0,0 +1,86 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import qs.Commons +import qs.Services.UI +import qs.Widgets + +NIconButton { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string valueIconColor: widgetSettings.iconColor !== undefined ? widgetSettings.iconColor : widgetMetadata.iconColor + + readonly property color iconColor: Color.resolveColorKey(valueIconColor) + + icon: "settings" + tooltipText: { + if (PanelService.getPanel("settingsPanel", screen)?.isPanelOpen) { + return ""; + } else { + return I18n.tr("tooltips.open-settings"); + } + } + tooltipDirection: BarService.getTooltipDirection(screen?.name) + baseSize: Style.getCapsuleHeightForScreen(screen?.name) + applyUiScale: false + customRadius: Style.radiusL + colorBg: Style.capsuleColor + colorFg: iconColor + colorBgHover: Color.mHover + colorFgHover: Color.mOnHover + colorBorder: Style.capsuleBorderColor + colorBorderHover: Style.capsuleBorderColor + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + onClicked: { + if (Settings.data.ui.settingsPanelMode === "attached") { + PanelService.getPanel("settingsPanel", screen)?.toggle(this); + } else { + PanelService.getPanel("settingsPanel", screen)?.toggle(); + } + } + onRightClicked: { + PanelService.showContextMenu(contextMenu, root, screen); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Spacer.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Spacer.qml new file mode 100644 index 0000000..7fe6c16 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Spacer.qml @@ -0,0 +1,41 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" + readonly property real barHeight: Style.getBarHeightForScreen(screenName) + readonly property int spacerSize: widgetSettings.width !== undefined ? widgetSettings.width : widgetMetadata.width + + implicitWidth: isBarVertical ? barHeight : spacerSize + implicitHeight: isBarVertical ? spacerSize : barHeight + width: implicitWidth + height: implicitHeight +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/SystemMonitor.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/SystemMonitor.qml new file mode 100644 index 0000000..7bdfa63 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/SystemMonitor.qml @@ -0,0 +1,967 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Modules.Panels.Settings +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isVertical: barPosition === "left" || barPosition === "right" + readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName) + readonly property real barFontSize: Style.getBarFontSizeForScreen(screenName) + + readonly property bool compactMode: widgetSettings.compactMode !== undefined ? widgetSettings.compactMode : widgetMetadata.compactMode + readonly property string iconColorKey: widgetSettings.iconColor !== undefined ? widgetSettings.iconColor : widgetMetadata.iconColor + readonly property string textColorKey: widgetSettings.textColor !== undefined ? widgetSettings.textColor : widgetMetadata.textColor + + readonly property bool useMonospaceFont: widgetSettings.useMonospaceFont !== undefined ? widgetSettings.useMonospaceFont : widgetMetadata.useMonospaceFont + readonly property bool usePadding: !compactMode && !isVertical && useMonospaceFont && ((widgetSettings.usePadding !== undefined) ? widgetSettings.usePadding : widgetMetadata.usePadding) + + readonly property bool showCpuUsage: (widgetSettings.showCpuUsage !== undefined) ? widgetSettings.showCpuUsage : widgetMetadata.showCpuUsage + readonly property bool showCpuCores: (widgetSettings.showCpuCores !== undefined) ? widgetSettings.showCpuCores : widgetMetadata.showCpuCores + readonly property bool showCpuFreq: (widgetSettings.showCpuFreq !== undefined) ? widgetSettings.showCpuFreq : widgetMetadata.showCpuFreq + readonly property bool showCpuTemp: (widgetSettings.showCpuTemp !== undefined) ? widgetSettings.showCpuTemp : widgetMetadata.showCpuTemp + readonly property bool showGpuTemp: (widgetSettings.showGpuTemp !== undefined) ? widgetSettings.showGpuTemp : widgetMetadata.showGpuTemp + readonly property bool showMemoryUsage: (widgetSettings.showMemoryUsage !== undefined) ? widgetSettings.showMemoryUsage : widgetMetadata.showMemoryUsage + readonly property bool showMemoryAsPercent: (widgetSettings.showMemoryAsPercent !== undefined) ? widgetSettings.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent + readonly property bool showSwapUsage: (widgetSettings.showSwapUsage !== undefined) ? widgetSettings.showSwapUsage : widgetMetadata.showSwapUsage + readonly property bool showNetworkStats: (widgetSettings.showNetworkStats !== undefined) ? widgetSettings.showNetworkStats : widgetMetadata.showNetworkStats + readonly property bool showDiskUsage: (widgetSettings.showDiskUsage !== undefined) ? widgetSettings.showDiskUsage : widgetMetadata.showDiskUsage + readonly property bool showDiskUsageAsPercent: (widgetSettings.showDiskUsageAsPercent !== undefined) ? widgetSettings.showDiskUsageAsPercent : widgetMetadata.showDiskUsageAsPercent + readonly property bool showDiskAvailable: (widgetSettings.showDiskAvailable !== undefined) ? widgetSettings.showDiskAvailable : widgetMetadata.showDiskAvailable + readonly property bool showLoadAverage: (widgetSettings.showLoadAverage !== undefined) ? widgetSettings.showLoadAverage : widgetMetadata.showLoadAverage + readonly property string diskPath: (widgetSettings.diskPath !== undefined) ? widgetSettings.diskPath : widgetMetadata.diskPath + readonly property string fontFamily: useMonospaceFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault + + readonly property int paddingPercent: usePadding ? String("100%").length : 0 + readonly property int paddingTemp: usePadding ? String("999ยฐ").length : 0 + readonly property int paddingCpuFreq: usePadding ? String("9.9").length : 0 + readonly property int paddingSpeed: usePadding ? String("9999G").length : 0 + + readonly property real iconSize: Style.toOdd(capsuleHeight * 0.48) + readonly property real miniGaugeWidth: Math.max(3, Style.toOdd(root.iconSize * 0.25)) + + // Content dimensions for implicit sizing + readonly property real contentWidth: isVertical ? capsuleHeight : Math.round(mainGrid.implicitWidth + Style.margin2M) + readonly property real contentHeight: isVertical ? Math.round(mainGrid.implicitHeight + Style.margin2M) : capsuleHeight + + readonly property color iconColor: Color.resolveColorKey(iconColorKey) + readonly property color textColor: Color.resolveColorKey(textColorKey) + + // Size: use implicit width/height + // BarWidgetLoader sets explicit width/height to extend click area + implicitWidth: contentWidth + implicitHeight: contentHeight + + Component.onCompleted: SystemStatService.registerComponent("bar-sysmon:" + (screen?.name || "unknown")) + Component.onDestruction: SystemStatService.unregisterComponent("bar-sysmon:" + (screen?.name || "unknown")) + + function openExternalMonitor() { + Quickshell.execDetached(["sh", "-c", Settings.data.systemMonitor.externalMonitor]); + } + + // Build comprehensive tooltip text with all stats + function buildTooltipContent() { + let rows = []; + + // CPU + rows.push([I18n.tr("system-monitor.cpu-usage"), `${Math.round(SystemStatService.cpuUsage)}% (${SystemStatService.cpuFreq.replace(/[^0-9.]/g, "")} GHz)`]); + if (showCpuCores) { + SystemStatService.coresUsage.forEach((usage, core) => rows.push([" " + I18n.tr("system-monitor.core-usage", { + "id": core + }), `${Math.round(usage)}%`])); + } + + if (SystemStatService.cpuTemp > 0) { + rows.push([I18n.tr("system-monitor.cpu-temp"), `${Math.round(SystemStatService.cpuTemp)}ยฐC`]); + } + + // GPU (if available) + if (SystemStatService.gpuAvailable) { + rows.push([I18n.tr("system-monitor.gpu-temp"), `${Math.round(SystemStatService.gpuTemp)}ยฐC`]); + } + + // Load Average + if (SystemStatService.loadAvg1 >= 0) { + rows.push([I18n.tr("system-monitor.load-average"), `${SystemStatService.loadAvg1.toFixed(2)} ยท ${SystemStatService.loadAvg5.toFixed(2)} ยท ${SystemStatService.loadAvg15.toFixed(2)}`]); + } + + // Memory + rows.push([I18n.tr("common.memory"), `${Math.round(SystemStatService.memPercent)}% (${(SystemStatService.memGb).toFixed(1)} GiB)`]); + + // Swap (if available) + if (SystemStatService.swapTotalGb > 0) { + rows.push([I18n.tr("bar.system-monitor.swap-usage-label"), `${Math.round(SystemStatService.swapPercent)}% (${(SystemStatService.swapGb).toFixed(1)} GiB)`]); + } + + // Network + rows.push([I18n.tr("system-monitor.download-speed"), `${SystemStatService.formatSpeed(SystemStatService.rxSpeed).replace(/([0-9.]+)([A-Za-z]+)/, "$1 $2")}` + "/s"]); + rows.push([I18n.tr("system-monitor.upload-speed"), `${SystemStatService.formatSpeed(SystemStatService.txSpeed).replace(/([0-9.]+)([A-Za-z]+)/, "$1 $2")}` + "/s"]); + + // Disk + const diskPercent = SystemStatService.diskPercents[diskPath]; + if (diskPercent !== undefined) { + const usedGb = SystemStatService.diskUsedGb[diskPath] || 0; + const sizeGb = SystemStatService.diskSizeGb[diskPath] || 0; + const availGb = SystemStatService.diskAvailableGb[diskPath] || 0; + rows.push([I18n.tr("system-monitor.disk"), `${diskPercent}% (${usedGb.toFixed(1)} / ${sizeGb.toFixed(1)} GB)`]); + rows.push([I18n.tr("common.available"), `${availGb.toFixed(1)} GB`]); + } + + return rows; + } + + // Visibility-aware warning/critical states (delegates to service) + readonly property bool cpuWarning: showCpuUsage && SystemStatService.cpuWarning + readonly property bool cpuCritical: showCpuUsage && SystemStatService.cpuCritical + readonly property bool tempWarning: showCpuTemp && SystemStatService.tempWarning + readonly property bool tempCritical: showCpuTemp && SystemStatService.tempCritical + readonly property bool gpuWarning: showGpuTemp && SystemStatService.gpuWarning + readonly property bool gpuCritical: showGpuTemp && SystemStatService.gpuCritical + readonly property bool memWarning: showMemoryUsage && SystemStatService.memWarning + readonly property bool memCritical: showMemoryUsage && SystemStatService.memCritical + readonly property bool swapWarning: showSwapUsage && SystemStatService.swapWarning + readonly property bool swapCritical: showSwapUsage && SystemStatService.swapCritical + readonly property bool diskWarning: showDiskUsage && SystemStatService.isDiskWarning(diskPath) + readonly property bool diskCritical: showDiskUsage && SystemStatService.isDiskCritical(diskPath) + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("system-monitor.title"), + "action": "sysmon-settings", + "icon": "settings" + }, + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "sysmon-settings") { + let monitorCmd = Settings.data.systemMonitor.externalMonitor; + if (monitorCmd && monitorCmd.trim() !== "") { + openExternalMonitor(); + } else { + SettingsPanelService.openToTab(SettingsPanel.Tab.System, 0, screen); + } + } else if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + // Visual capsule centered in parent + Rectangle { + id: visualCapsule + width: root.contentWidth + height: root.contentHeight + anchors.centerIn: parent + radius: Style.radiusM + color: Style.capsuleColor + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + // Mini gauge component for compact mode, vertical gauge that fills from bottom + Component { + id: miniGaugeComponent + + NLinearGauge { + ratio: 0 + orientation: Qt.Vertical + fillColor: Color.mPrimary + width: miniGaugeWidth + height: iconSize + } + } + + GridLayout { + id: mainGrid + anchors.centerIn: parent + flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight + rows: isVertical ? -1 : 1 + columns: isVertical ? 1 : -1 + rowSpacing: isVertical ? (compactMode ? Style.marginL : Style.marginXL) : 0 + columnSpacing: isVertical ? 0 : Style.marginM + + // CPU Usage Component + Item { + id: cpuUsageContainer + implicitWidth: cpuUsageContent.implicitWidth + implicitHeight: cpuUsageContent.implicitHeight + Layout.preferredWidth: isVertical ? root.width : implicitWidth + Layout.preferredHeight: compactMode ? implicitHeight : capsuleHeight + Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter + visible: showCpuUsage + + GridLayout { + id: cpuUsageContent + anchors.centerIn: parent + + property bool verticalDisplay: isVertical && (!compactMode || showCpuCores) + flow: verticalDisplay ? GridLayout.TopToBottom : GridLayout.LeftToRight + rows: verticalDisplay ? -1 : 1 + columns: verticalDisplay ? 1 : -1 + rowSpacing: compactMode ? 3 : Style.marginXS + columnSpacing: compactMode ? 3 : Style.marginXS + + Item { + Layout.preferredWidth: iconSize + Layout.preferredHeight: (compactMode || isVertical) ? iconSize : capsuleHeight + Layout.alignment: Qt.AlignCenter + Layout.row: (isVertical && !compactMode) ? 1 : 0 + Layout.column: 0 + + NIcon { + icon: "cpu-usage" + pointSize: iconSize + applyUiScale: false + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, contentHeight) + color: (cpuWarning || cpuCritical) ? SystemStatService.cpuColor : root.iconColor + } + } + + // Text mode + NText { + visible: !compactMode + text: `${Math.round(SystemStatService.cpuUsage)}%`.padStart(paddingPercent, " ") + family: fontFamily + pointSize: barFontSize + applyUiScale: false + Layout.alignment: Qt.AlignCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: (cpuWarning || cpuCritical) ? SystemStatService.cpuColor : root.textColor + Layout.row: isVertical ? 0 : 0 + Layout.column: isVertical ? 0 : 1 + } + + // Compact mode general cpu + Loader { + active: compactMode && !showCpuCores + visible: compactMode && !showCpuCores + sourceComponent: miniGaugeComponent + Layout.alignment: Qt.AlignCenter + Layout.row: 0 + Layout.column: 1 + + onLoaded: { + item.ratio = Qt.binding(() => SystemStatService.cpuUsage / 100); + item.fillColor = Qt.binding(() => SystemStatService.cpuColor); + } + } + + // Compact mode for cores + Repeater { + model: (compactMode && showCpuCores) ? SystemStatService.coresUsage : [] + + delegate: NLinearGauge { + required property var modelData + width: isVertical ? iconSize : miniGaugeWidth + height: isVertical ? miniGaugeWidth : iconSize + orientation: isVertical ? Qt.Horizontal : Qt.Vertical + ratio: modelData / 100 + fillColor: SystemStatService.getCoreUsageColor(modelData) + } + } + } + } + + // CPU Frequency Component + Item { + id: cpuFreqContainer + implicitWidth: cpuFreqContent.implicitWidth + implicitHeight: cpuFreqContent.implicitHeight + Layout.preferredWidth: isVertical ? root.width : implicitWidth + Layout.preferredHeight: compactMode ? implicitHeight : capsuleHeight + Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter + visible: showCpuFreq && (!isVertical || compactMode) + + GridLayout { + id: cpuFreqContent + anchors.centerIn: parent + flow: (isVertical && !compactMode) ? GridLayout.TopToBottom : GridLayout.LeftToRight + rows: (isVertical && !compactMode) ? 2 : 1 + columns: (isVertical && !compactMode) ? 1 : 2 + rowSpacing: Style.marginXXS + columnSpacing: compactMode ? 3 : Style.marginXS + + Item { + Layout.preferredWidth: iconSize + Layout.preferredHeight: (compactMode || isVertical) ? iconSize : capsuleHeight + Layout.alignment: Qt.AlignCenter + Layout.row: (isVertical && !compactMode) ? 1 : 0 + Layout.column: 0 + + NIcon { + icon: "cpu-usage" + pointSize: iconSize + applyUiScale: false + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, contentHeight) + color: root.iconColor + } + } + + // Text mode + NText { + visible: !compactMode + text: SystemStatService.cpuFreq.replace("Hz", "").replace(" ", "").padStart(paddingCpuFreq, " ") + family: fontFamily + pointSize: barFontSize + applyUiScale: false + Layout.alignment: Qt.AlignCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: root.textColor + Layout.row: isVertical ? 0 : 0 + Layout.column: isVertical ? 0 : 1 + } + + // Compact mode + Loader { + active: compactMode + visible: compactMode + sourceComponent: miniGaugeComponent + Layout.alignment: Qt.AlignCenter + Layout.row: 0 + Layout.column: 1 + + onLoaded: { + item.ratio = Qt.binding(() => SystemStatService.cpuFreqRatio); + } + } + } + } + + // CPU Temperature Component + Item { + id: cpuTempContainer + implicitWidth: cpuTempContent.implicitWidth + implicitHeight: cpuTempContent.implicitHeight + Layout.preferredWidth: isVertical ? root.width : implicitWidth + Layout.preferredHeight: compactMode ? implicitHeight : capsuleHeight + Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter + visible: showCpuTemp + + GridLayout { + id: cpuTempContent + anchors.centerIn: parent + flow: (isVertical && !compactMode) ? GridLayout.TopToBottom : GridLayout.LeftToRight + rows: (isVertical && !compactMode) ? 2 : 1 + columns: (isVertical && !compactMode) ? 1 : 2 + rowSpacing: Style.marginXXS + columnSpacing: compactMode ? 3 : Style.marginXS + + Item { + Layout.preferredWidth: iconSize + Layout.preferredHeight: (compactMode || isVertical) ? iconSize : capsuleHeight + Layout.alignment: Qt.AlignCenter + Layout.row: (isVertical && !compactMode) ? 1 : 0 + Layout.column: 0 + + NIcon { + icon: "cpu-temperature" + pointSize: iconSize + applyUiScale: false + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, contentHeight) + color: (tempWarning || tempCritical) ? SystemStatService.tempColor : root.iconColor + } + } + + // Text mode + NText { + visible: !compactMode + text: `${Math.round(SystemStatService.cpuTemp)}ยฐ`.padStart(paddingTemp, " ") + family: fontFamily + pointSize: barFontSize + applyUiScale: false + Layout.alignment: Qt.AlignCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: (tempWarning || tempCritical) ? SystemStatService.tempColor : root.textColor + Layout.row: isVertical ? 0 : 0 + Layout.column: isVertical ? 0 : 1 + } + + // Compact mode, mini gauge (to the right of icon) + Loader { + active: compactMode + visible: compactMode + sourceComponent: miniGaugeComponent + Layout.alignment: Qt.AlignCenter + Layout.row: 0 + Layout.column: 1 + + onLoaded: { + item.ratio = Qt.binding(() => SystemStatService.cpuTemp / 100); + item.fillColor = Qt.binding(() => SystemStatService.tempColor); + } + } + } + } + + // GPU Temperature Component + Item { + id: gpuTempContainer + implicitWidth: gpuTempContent.implicitWidth + implicitHeight: gpuTempContent.implicitHeight + Layout.preferredWidth: isVertical ? root.width : implicitWidth + Layout.preferredHeight: compactMode ? implicitHeight : capsuleHeight + Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter + visible: showGpuTemp && SystemStatService.gpuAvailable + + GridLayout { + id: gpuTempContent + anchors.centerIn: parent + flow: (isVertical && !compactMode) ? GridLayout.TopToBottom : GridLayout.LeftToRight + rows: (isVertical && !compactMode) ? 2 : 1 + columns: (isVertical && !compactMode) ? 1 : 2 + rowSpacing: Style.marginXXS + columnSpacing: compactMode ? 3 : Style.marginXS + + Item { + Layout.preferredWidth: iconSize + Layout.preferredHeight: (compactMode || isVertical) ? iconSize : capsuleHeight + Layout.alignment: Qt.AlignCenter + Layout.row: (isVertical && !compactMode) ? 1 : 0 + Layout.column: 0 + + NIcon { + icon: "gpu-temperature" + pointSize: iconSize + applyUiScale: false + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, contentHeight) + color: (gpuWarning || gpuCritical) ? SystemStatService.gpuColor : root.iconColor + } + } + + // Text mode + NText { + visible: !compactMode + text: `${Math.round(SystemStatService.gpuTemp)}ยฐ`.padStart(paddingTemp, " ") + family: fontFamily + pointSize: barFontSize + applyUiScale: false + Layout.alignment: Qt.AlignCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: (gpuWarning || gpuCritical) ? SystemStatService.gpuColor : root.textColor + Layout.row: isVertical ? 0 : 0 + Layout.column: isVertical ? 0 : 1 + } + + // Compact mode + Loader { + active: compactMode + visible: compactMode + sourceComponent: miniGaugeComponent + Layout.alignment: Qt.AlignCenter + Layout.row: 0 + Layout.column: 1 + + onLoaded: { + item.ratio = Qt.binding(() => SystemStatService.gpuTemp / 100); + item.fillColor = Qt.binding(() => SystemStatService.gpuColor); + } + } + } + } + + // Load Average Component + Item { + id: loadAvgContainer + implicitWidth: loadAvgContent.implicitWidth + implicitHeight: loadAvgContent.implicitHeight + Layout.preferredWidth: isVertical ? root.width : implicitWidth + Layout.preferredHeight: compactMode ? implicitHeight : capsuleHeight + Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter + visible: showLoadAverage && SystemStatService.nproc > 0 && SystemStatService.loadAvg1 > 0 + + GridLayout { + id: loadAvgContent + anchors.centerIn: parent + flow: (isVertical && !compactMode) ? GridLayout.TopToBottom : GridLayout.LeftToRight + rows: (isVertical && !compactMode) ? 2 : 1 + columns: (isVertical && !compactMode) ? 1 : 2 + rowSpacing: Style.marginXXS + columnSpacing: compactMode ? 3 : Style.marginXS + + Item { + Layout.preferredWidth: iconSize + Layout.preferredHeight: (compactMode || isVertical) ? iconSize : capsuleHeight + Layout.alignment: Qt.AlignCenter + Layout.row: (isVertical && !compactMode) ? 1 : 0 + Layout.column: 0 + + NIcon { + icon: "weight" + pointSize: iconSize + applyUiScale: false + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, contentHeight) + color: root.iconColor + } + } + + // Text mode + NText { + visible: !compactMode + text: `${SystemStatService.loadAvg1.toFixed(1)}`.padStart(usePadding ? `${SystemStatService.nproc.toFixed(1)}`.length : 0, " ") + family: fontFamily + pointSize: barFontSize + applyUiScale: false + Layout.alignment: Qt.AlignCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: root.textColor + Layout.row: isVertical ? 0 : 0 + Layout.column: isVertical ? 0 : 1 + } + + // Compact mode + Loader { + active: compactMode + visible: compactMode + sourceComponent: miniGaugeComponent + Layout.alignment: Qt.AlignCenter + Layout.row: 0 + Layout.column: 1 + + onLoaded: { + item.ratio = Qt.binding(() => Math.min(1, SystemStatService.loadAvg1 / SystemStatService.nproc)); + } + } + } + } + + // Memory Usage Component + Item { + id: memoryContainer + implicitWidth: memoryContent.implicitWidth + implicitHeight: memoryContent.implicitHeight + Layout.preferredWidth: isVertical ? root.width : implicitWidth + Layout.preferredHeight: compactMode ? implicitHeight : capsuleHeight + Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter + visible: showMemoryUsage + + GridLayout { + id: memoryContent + anchors.centerIn: parent + flow: (isVertical && !compactMode) ? GridLayout.TopToBottom : GridLayout.LeftToRight + rows: (isVertical && !compactMode) ? 2 : 1 + columns: (isVertical && !compactMode) ? 1 : 2 + rowSpacing: Style.marginXXS + columnSpacing: compactMode ? 3 : Style.marginXS + + Item { + Layout.preferredWidth: iconSize + Layout.preferredHeight: (compactMode || isVertical) ? iconSize : capsuleHeight + Layout.alignment: Qt.AlignCenter + Layout.row: (isVertical && !compactMode) ? 1 : 0 + Layout.column: 0 + + NIcon { + icon: "memory" + pointSize: iconSize + applyUiScale: false + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, contentHeight) + color: (memWarning || memCritical) ? SystemStatService.memColor : root.iconColor + } + } + + // Text mode + NText { + visible: !compactMode + text: SystemStatService.formatRamDisplay({ + percent: showMemoryAsPercent, + padding: usePadding + }) + family: fontFamily + pointSize: barFontSize + applyUiScale: false + Layout.alignment: Qt.AlignCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: (memWarning || memCritical) ? SystemStatService.memColor : root.textColor + Layout.row: isVertical ? 0 : 0 + Layout.column: isVertical ? 0 : 1 + } + + // Compact mode + Loader { + active: compactMode + visible: compactMode + sourceComponent: miniGaugeComponent + Layout.alignment: Qt.AlignCenter + Layout.row: 0 + Layout.column: 1 + + onLoaded: { + item.ratio = Qt.binding(() => SystemStatService.memPercent / 100); + item.fillColor = Qt.binding(() => SystemStatService.memColor); + } + } + } + } + + // Swap Usage Component + Item { + id: swapContainer + implicitWidth: swapContent.implicitWidth + implicitHeight: swapContent.implicitHeight + Layout.preferredWidth: isVertical ? root.width : implicitWidth + Layout.preferredHeight: compactMode ? implicitHeight : capsuleHeight + Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter + visible: showSwapUsage && SystemStatService.swapTotalGb > 0 + + GridLayout { + id: swapContent + anchors.centerIn: parent + flow: (isVertical && !compactMode) ? GridLayout.TopToBottom : GridLayout.LeftToRight + rows: (isVertical && !compactMode) ? 2 : 1 + columns: (isVertical && !compactMode) ? 1 : 2 + rowSpacing: Style.marginXXS + columnSpacing: compactMode ? 3 : Style.marginXS + + Item { + Layout.preferredWidth: iconSize + Layout.preferredHeight: (compactMode || isVertical) ? iconSize : capsuleHeight + Layout.alignment: Qt.AlignCenter + Layout.row: (isVertical && !compactMode) ? 1 : 0 + Layout.column: 0 + + NIcon { + icon: "exchange" + pointSize: iconSize + applyUiScale: false + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, contentHeight) + color: (swapWarning || swapCritical) ? SystemStatService.swapColor : root.iconColor + } + } + + // Text mode + NText { + visible: !compactMode + text: SystemStatService.formatRamDisplay({ + swap: true, + percent: true, + padding: usePadding + }) + family: fontFamily + pointSize: barFontSize + applyUiScale: false + Layout.alignment: Qt.AlignCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: (swapWarning || swapCritical) ? SystemStatService.swapColor : root.textColor + Layout.row: isVertical ? 0 : 0 + Layout.column: isVertical ? 0 : 1 + } + + // Compact mode + Loader { + active: compactMode + visible: compactMode + sourceComponent: miniGaugeComponent + Layout.alignment: Qt.AlignCenter + Layout.row: 0 + Layout.column: 1 + + onLoaded: { + item.ratio = Qt.binding(() => SystemStatService.swapPercent / 100); + item.fillColor = Qt.binding(() => SystemStatService.swapColor); + } + } + } + } + + // Network Download Speed Component + Item { + implicitWidth: downloadContent.implicitWidth + implicitHeight: downloadContent.implicitHeight + Layout.preferredWidth: isVertical ? root.width : implicitWidth + Layout.preferredHeight: compactMode ? implicitHeight : capsuleHeight + Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter + visible: showNetworkStats + + GridLayout { + id: downloadContent + anchors.centerIn: parent + flow: (isVertical && !compactMode) ? GridLayout.TopToBottom : GridLayout.LeftToRight + rows: (isVertical && !compactMode) ? 2 : 1 + columns: (isVertical && !compactMode) ? 1 : 2 + rowSpacing: Style.marginXXS + columnSpacing: compactMode ? 3 : Style.marginXS + + Item { + Layout.preferredWidth: iconSize + Layout.preferredHeight: (compactMode || isVertical) ? iconSize : capsuleHeight + Layout.alignment: Qt.AlignCenter + Layout.row: (isVertical && !compactMode) ? 1 : 0 + Layout.column: 0 + + NIcon { + icon: "download-speed" + pointSize: iconSize + applyUiScale: false + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, contentHeight) + color: root.iconColor + } + } + + // Text mode + NText { + visible: !compactMode + text: isVertical ? SystemStatService.formatCompactSpeed(SystemStatService.rxSpeed) : SystemStatService.formatSpeed(SystemStatService.rxSpeed).padStart(paddingSpeed, " ") + family: fontFamily + pointSize: barFontSize + applyUiScale: false + Layout.alignment: Qt.AlignCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: root.textColor + Layout.row: isVertical ? 0 : 0 + Layout.column: isVertical ? 0 : 1 + } + + // Compact mode + Loader { + active: compactMode + visible: compactMode + sourceComponent: miniGaugeComponent + Layout.alignment: Qt.AlignCenter + Layout.row: 0 + Layout.column: 1 + + onLoaded: { + item.ratio = Qt.binding(() => SystemStatService.rxRatio); + } + } + } + } + + // Network Upload Speed Component + Item { + implicitWidth: uploadContent.implicitWidth + implicitHeight: uploadContent.implicitHeight + Layout.preferredWidth: isVertical ? root.width : implicitWidth + Layout.preferredHeight: compactMode ? implicitHeight : capsuleHeight + Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter + visible: showNetworkStats + + GridLayout { + id: uploadContent + anchors.centerIn: parent + flow: (isVertical && !compactMode) ? GridLayout.TopToBottom : GridLayout.LeftToRight + rows: (isVertical && !compactMode) ? 2 : 1 + columns: (isVertical && !compactMode) ? 1 : 2 + rowSpacing: Style.marginXXS + columnSpacing: compactMode ? 3 : Style.marginXS + + Item { + Layout.preferredWidth: iconSize + Layout.preferredHeight: (compactMode || isVertical) ? iconSize : capsuleHeight + Layout.alignment: Qt.AlignCenter + Layout.row: (isVertical && !compactMode) ? 1 : 0 + Layout.column: 0 + + NIcon { + icon: "upload-speed" + pointSize: iconSize + applyUiScale: false + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, contentHeight) + color: root.iconColor + } + } + + // Text mode + NText { + visible: !compactMode + text: isVertical ? SystemStatService.formatCompactSpeed(SystemStatService.txSpeed) : SystemStatService.formatSpeed(SystemStatService.txSpeed).padStart(paddingSpeed, " ") + family: fontFamily + pointSize: barFontSize + applyUiScale: false + Layout.alignment: Qt.AlignCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: root.textColor + Layout.row: isVertical ? 0 : 0 + Layout.column: isVertical ? 0 : 1 + } + + // Compact mode + Loader { + active: compactMode + visible: compactMode + sourceComponent: miniGaugeComponent + Layout.alignment: Qt.AlignCenter + Layout.row: 0 + Layout.column: 1 + + onLoaded: { + item.ratio = Qt.binding(() => SystemStatService.txRatio); + } + } + } + } + + // Disk Usage Component (primary drive) + Item { + id: diskContainer + implicitWidth: diskContent.implicitWidth + implicitHeight: diskContent.implicitHeight + Layout.preferredWidth: isVertical ? root.width : implicitWidth + Layout.preferredHeight: compactMode ? implicitHeight : capsuleHeight + Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter + visible: showDiskUsage + + GridLayout { + id: diskContent + anchors.centerIn: parent + flow: (isVertical && !compactMode) ? GridLayout.TopToBottom : GridLayout.LeftToRight + rows: (isVertical && !compactMode) ? 2 : 1 + columns: (isVertical && !compactMode) ? 1 : 2 + rowSpacing: Style.marginXXS + columnSpacing: compactMode ? 3 : Style.marginXS + + Item { + Layout.preferredWidth: iconSize + Layout.preferredHeight: (compactMode || isVertical) ? iconSize : capsuleHeight + Layout.alignment: Qt.AlignCenter + Layout.row: (isVertical && !compactMode) ? 1 : 0 + Layout.column: 0 + + NIcon { + icon: "storage" + pointSize: iconSize + applyUiScale: false + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, contentHeight) + color: (diskWarning || diskCritical) ? SystemStatService.getDiskColor(diskPath) : root.iconColor + } + } + + // Text mode + NText { + visible: !compactMode + text: SystemStatService.formatDiskDisplay(diskPath, { + percent: showDiskUsageAsPercent, + available: showDiskAvailable, + padding: usePadding + }) + family: fontFamily + pointSize: barFontSize + applyUiScale: false + Layout.alignment: Qt.AlignCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: (diskWarning || diskCritical) ? SystemStatService.getDiskColor(diskPath) : root.textColor + Layout.row: isVertical ? 0 : 0 + Layout.column: isVertical ? 0 : 1 + } + + // Compact mode + Loader { + active: compactMode + visible: compactMode + sourceComponent: miniGaugeComponent + Layout.alignment: Qt.AlignCenter + Layout.row: 0 + Layout.column: 1 + + onLoaded: { + item.ratio = Qt.binding(() => (showDiskAvailable ? SystemStatService.diskAvailPercents[diskPath] : SystemStatService.diskPercents[diskPath] ?? 0) / 100); + item.fillColor = Qt.binding(() => SystemStatService.getDiskColor(diskPath, showDiskAvailable)); + } + } + } + } + } + } + + // MouseArea at root level for extended click area + MouseArea { + id: tooltipArea + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + hoverEnabled: true + onClicked: mouse => { + if (mouse.button === Qt.LeftButton) { + PanelService.getPanel("systemStatsPanel", screen)?.toggle(root); + TooltipService.hide(); + } else if (mouse.button === Qt.RightButton) { + TooltipService.hide(); + PanelService.showContextMenu(contextMenu, root, screen); + } else if (mouse.button === Qt.MiddleButton) { + TooltipService.hide(); + openExternalMonitor(); + } + } + onEntered: { + if (!PanelService.getPanel("systemStatsPanel", screen).isPanelOpen) { + TooltipService.show(root, buildTooltipContent(), BarService.getTooltipDirection(root.screen?.name)); + tooltipRefreshTimer.start(); + } + } + onExited: { + tooltipRefreshTimer.stop(); + TooltipService.hide(); + } + } + + Timer { + id: tooltipRefreshTimer + interval: 1000 + repeat: true + onTriggered: { + if (tooltipArea.containsMouse) { + TooltipService.updateText(buildTooltipContent()); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Taskbar.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Taskbar.qml new file mode 100644 index 0000000..339727e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Taskbar.qml @@ -0,0 +1,1026 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import Quickshell.Widgets +import qs.Commons +import qs.Services.Compositor +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right" + readonly property real barHeight: Style.getBarHeightForScreen(screenName) + readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName) + readonly property real barFontSize: Style.getBarFontSizeForScreen(screenName) + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + property bool hasWindow: false + readonly property string hideMode: (widgetSettings.hideMode !== undefined) ? widgetSettings.hideMode : widgetMetadata.hideMode + readonly property bool onlySameOutput: (widgetSettings.onlySameOutput !== undefined) ? widgetSettings.onlySameOutput : widgetMetadata.onlySameOutput + readonly property bool onlyActiveWorkspaces: (widgetSettings.onlyActiveWorkspaces !== undefined) ? widgetSettings.onlyActiveWorkspaces : widgetMetadata.onlyActiveWorkspaces + readonly property bool showTitle: isVerticalBar ? false : (widgetSettings.showTitle !== undefined) ? widgetSettings.showTitle : widgetMetadata.showTitle + readonly property bool smartWidth: (widgetSettings.smartWidth !== undefined) ? widgetSettings.smartWidth : widgetMetadata.smartWidth + readonly property int maxTaskbarWidthPercent: (widgetSettings.maxTaskbarWidth !== undefined) ? widgetSettings.maxTaskbarWidth : widgetMetadata.maxTaskbarWidth + readonly property real iconScale: (widgetSettings.iconScale !== undefined) ? widgetSettings.iconScale : widgetMetadata.iconScale + readonly property int itemSize: Style.toOdd(capsuleHeight * Math.max(0.1, iconScale)) + + // Maximum width for the taskbar widget to prevent overlapping with other widgets + readonly property real maxTaskbarWidth: { + if (!screen || isVerticalBar || !smartWidth || maxTaskbarWidthPercent <= 0) + return 0; + var barFloating = Settings.data.bar.barType === "floating"; + var barMarginH = barFloating ? Math.ceil(Settings.data.bar.marginHorizontal) : 0; + var availableWidth = screen.width - (barMarginH * 2); + return Math.round(availableWidth * (maxTaskbarWidthPercent / 100)); + } + + readonly property int titleWidth: { + // First, use user-defined title width if set + var calculatedWidth = (widgetSettings.titleWidth !== undefined) ? widgetSettings.titleWidth : widgetMetadata.titleWidth; + + // Second, shrink title width if it exceeds maxTaskbarWidth when smartWidth is enabled + if (smartWidth && combinedModel.length > 0) { + if (maxTaskbarWidth > 0) { + var entriesCount = combinedModel.length; + var maxWidthPerEntry = (maxTaskbarWidth / entriesCount) - itemSize - Style.marginS - Style.margin2M; + calculatedWidth = Math.min(calculatedWidth, maxWidthPerEntry); + } + + calculatedWidth = Math.max(Math.round(calculatedWidth), 20); + } + + return calculatedWidth; + } + readonly property bool showPinnedApps: (widgetSettings.showPinnedApps !== undefined) ? widgetSettings.showPinnedApps : widgetMetadata.showPinnedApps + + // Context menu state - store ID instead of object reference to avoid stale references + property string selectedWindowId: "" + property string selectedAppId: "" + + // Helper to get the current window object from ID + function getSelectedWindow() { + if (!selectedWindowId) + return null; + for (var i = 0; i < combinedModel.length; i++) { + // Using loose equality on purpose (==) + if (combinedModel[i].id == selectedWindowId && combinedModel[i].window) { + return combinedModel[i].window; + } + } + return null; + } + property int modelUpdateTrigger: 0 // Dummy property to force model re-evaluation + + // Hover state + property var hoveredWindowId: "" + // Combined model of running windows and pinned apps + property var combinedModel: [] + + // Wheel scroll handling + property int wheelAccumulatedDelta: 0 + property bool wheelCooldown: false + + // Drag and Drop state for visual feedback + property int dragSourceIndex: -1 + property int dragTargetIndex: -1 + + // Track the session order of apps (transient reordering) + property var sessionAppOrder: [] + + function getAppKey(appData) { + if (!appData) + return null; + // prefer window object identity for running apps to distinguish instances + if (appData.window) + return appData.window; + // fallback to appId for pinned-only apps + return appData.appId; + } + + function sortApps(apps) { + if (!sessionAppOrder || sessionAppOrder.length === 0) { + return apps; + } + + const sorted = []; + const remaining = [...apps]; + + // 1. Pick apps that are in the session order + for (let i = 0; i < sessionAppOrder.length; i++) { + const key = sessionAppOrder[i]; + const idx = remaining.findIndex(app => getAppKey(app) === key); + if (idx !== -1) { + sorted.push(remaining[idx]); + remaining.splice(idx, 1); + } + } + + // 2. Append any new/remaining apps + remaining.forEach(app => sorted.push(app)); + + return sorted; + } + + function reorderApps(fromIndex, toIndex) { + Logger.d("Taskbar", "Reordering apps from " + fromIndex + " to " + toIndex); + if (fromIndex === toIndex || fromIndex < 0 || toIndex < 0 || fromIndex >= combinedModel.length || toIndex >= combinedModel.length) + return; + + const list = [...combinedModel]; + const item = list.splice(fromIndex, 1)[0]; + list.splice(toIndex, 0, item); + + combinedModel = list; + sessionAppOrder = combinedModel.map(getAppKey); + savePinnedOrder(); + } + + function savePinnedOrder() { + const currentPinned = Settings.data.dock.pinnedApps || []; + const newPinned = []; + const seen = new Set(); + + // Extract pinned apps in their current visual order + combinedModel.forEach(app => { + if (app.appId && !seen.has(app.appId)) { + const isPinned = currentPinned.some(p => normalizeAppId(p) === normalizeAppId(app.appId)); + + if (isPinned) { + newPinned.push(app.appId); + seen.add(app.appId); + } + } + }); + + // Check if any pinned apps were missed (e.g. filtered out by workspace) + currentPinned.forEach(p => { + if (!seen.has(p)) { + newPinned.push(p); + seen.add(p); + } + }); + + if (JSON.stringify(currentPinned) !== JSON.stringify(newPinned)) { + Settings.data.dock.pinnedApps = newPinned; + } + } + + // Helper function to normalize app IDs for case-insensitive matching + function normalizeAppId(appId) { + if (!appId || typeof appId !== 'string') + return ""; + return appId.toLowerCase().trim(); + } + + // Helper function to check if an app ID matches a pinned app (case-insensitive) + function isAppIdPinned(appId, pinnedApps) { + if (!appId || !pinnedApps || pinnedApps.length === 0) + return false; + const normalizedId = normalizeAppId(appId); + // Direct match + if (pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedId)) + return true; + // Resolve via desktop entry lookup (handles StartupWMClass != .desktop filename) + const resolved = resolveToDesktopEntryId(appId); + if (resolved !== appId) { + const normalizedResolved = normalizeAppId(resolved); + return pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedResolved); + } + return false; + } + + // Desktop entry ID resolution cache (cleared when DesktopEntries change) + property var _desktopEntryIdCache: ({}) + + // Resolve a toplevel appId to its canonical .desktop entry ID via heuristic lookup. + function resolveToDesktopEntryId(appId) { + if (!appId) + return appId; + if (_desktopEntryIdCache.hasOwnProperty(appId)) + return _desktopEntryIdCache[appId]; + try { + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) { + const entry = DesktopEntries.heuristicLookup(appId); + if (entry && entry.id) { + _desktopEntryIdCache[appId] = entry.id; + return entry.id; + } + } + } catch (e) {} + _desktopEntryIdCache[appId] = appId; + return appId; + } + + // Helper function to get app name from desktop entry + function getAppNameFromDesktopEntry(appId) { + if (!appId) + return appId; + + try { + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) { + const entry = DesktopEntries.heuristicLookup(appId); + if (entry && entry.name) { + return entry.name; + } + } + + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.byId) { + const entry = DesktopEntries.byId(appId); + if (entry && entry.name) { + return entry.name; + } + } + } catch (e) + // Fall through to return original appId + {} + + // Return original appId if we can't find a desktop entry + return appId; + } + + // Helper function to get desktop entry ID from an app ID + function getDesktopEntryId(appId) { + if (!appId) + return appId; + + // Try to find the desktop entry using heuristic lookup + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) { + try { + const entry = DesktopEntries.heuristicLookup(appId); + if (entry && entry.id) { + return entry.id; + } + } catch (e) + // Fall through to return original appId + {} + } + + // Try direct lookup + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.byId) { + try { + const entry = DesktopEntries.byId(appId); + if (entry && entry.id) { + return entry.id; + } + } catch (e) + // Fall through to return original appId + {} + } + + // Return original appId if we can't find a desktop entry + return appId; + } + + // Helper function to check if an app is pinned + function isAppPinned(appId) { + if (!appId) + return false; + const pinnedApps = Settings.data.dock.pinnedApps || []; + const normalizedId = normalizeAppId(appId); + if (pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedId)) + return true; + const resolved = resolveToDesktopEntryId(appId); + if (resolved !== appId) { + const normalizedResolved = normalizeAppId(resolved); + return pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedResolved); + } + return false; + } + + // Helper function to toggle app pin/unpin + function toggleAppPin(appId) { + if (!appId) + return; + + // Get the desktop entry ID for consistent pinning + const desktopEntryId = getDesktopEntryId(appId); + const normalizedId = normalizeAppId(desktopEntryId); + + let pinnedApps = (Settings.data.dock.pinnedApps || []).slice(); // Create a copy + + // Find existing pinned app with case-insensitive matching + const existingIndex = pinnedApps.findIndex(pinnedId => normalizeAppId(pinnedId) === normalizedId); + const isPinned = existingIndex >= 0; + + if (isPinned) { + // Unpin: remove from array + pinnedApps.splice(existingIndex, 1); + } else { + // Pin: add desktop entry ID to array + pinnedApps.push(desktopEntryId); + } + + // Update the settings + Settings.data.dock.pinnedApps = pinnedApps; + } + + // Function to update the combined model + function updateCombinedModel() { + const runningWindows = []; + const pinnedApps = Settings.data.dock.pinnedApps || []; + const processedAppIds = new Set(); + + // First pass: Add all running windows + try { + const total = CompositorService.windows.count || 0; + const activeIds = CompositorService.getActiveWorkspaces().map(function (ws) { + return ws.id; + }); + + for (var i = 0; i < total; i++) { + var w = CompositorService.windows.get(i); + if (!w) + continue; + var passOutput = (!onlySameOutput) || (w.output == screen?.name); + var passWorkspace = (!onlyActiveWorkspaces) || (activeIds.includes(w.workspaceId)); + if (passOutput && passWorkspace) { + const isPinned = isAppIdPinned(w.appId, pinnedApps); + runningWindows.push({ + "id": w.id, + "type": isPinned ? "pinned-running" : "running", + "window": w, + "appId": w.appId, + "title": w.title || getAppNameFromDesktopEntry(w.appId) + }); + processedAppIds.add(normalizeAppId(w.appId)); + // Also track the resolved desktop entry ID so pinned app matching works + const resolvedId = resolveToDesktopEntryId(w.appId); + if (resolvedId !== w.appId) + processedAppIds.add(normalizeAppId(resolvedId)); + } + } + } catch (e) + // Ignore errors + {} + + // Second pass: Add non-running pinned apps (only if showPinnedApps is enabled) + if (showPinnedApps) { + pinnedApps.forEach(pinnedAppId => { + const normalizedPinnedId = normalizeAppId(pinnedAppId); + if (!processedAppIds.has(normalizedPinnedId)) { + const appName = getAppNameFromDesktopEntry(pinnedAppId); + runningWindows.push({ + "id": pinnedAppId, + "type": "pinned", + "window": null, + "appId": pinnedAppId, + "title": appName + }); + } + }); + } + + combinedModel = sortApps(runningWindows); + + // Sync session order if needed (e.g. first run or new apps added) + if (!sessionAppOrder || sessionAppOrder.length === 0 || sessionAppOrder.length !== combinedModel.length) { + sessionAppOrder = combinedModel.map(getAppKey); + } + updateHasWindow(); + } + + // Function to launch a pinned app + function launchPinnedApp(appId) { + if (!appId) + return; + + try { + const app = DesktopEntries.byId(appId); + + if (Settings.data.appLauncher.customLaunchPrefixEnabled && Settings.data.appLauncher.customLaunchPrefix) { + // Use custom launch prefix + const prefix = Settings.data.appLauncher.customLaunchPrefix.split(" "); + + if (app.runInTerminal) { + const terminal = Settings.data.appLauncher.terminalCommand.split(" "); + const command = prefix.concat(terminal.concat(app.command)); + Quickshell.execDetached(command); + } else { + const command = prefix.concat(app.command); + Quickshell.execDetached(command); + } + } else { + if (app.runInTerminal) { + Logger.d("Taskbar", "Executing terminal app manually: " + app.name); + const terminal = Settings.data.appLauncher.terminalCommand.split(" "); + const command = terminal.concat(app.command); + CompositorService.spawn(command); + } else if (app.command && app.command.length > 0) { + CompositorService.spawn(app.command); + } else if (app.execute) { + app.execute(); + } else { + Logger.w("Taskbar", `Could not launch: ${app.name}. No valid launch method.`); + } + } + } catch (e) { + Logger.e("Taskbar", "Failed to launch app: " + e); + } + } + + NPopupContextMenu { + id: contextMenu + model: { + // Reference modelUpdateTrigger to make binding reactive + const _ = root.modelUpdateTrigger; + + var items = []; + if (root.selectedWindowId) { + // Focus item (for running apps) + items.push({ + "label": I18n.tr("common.focus"), + "action": "focus", + "icon": "eye" + }); + + // Pin/Unpin item (always available when right-clicking an app) + const isPinned = root.isAppPinned(root.selectedAppId); + items.push({ + "label": !isPinned ? I18n.tr("common.pin") : I18n.tr("common.unpin"), + "action": "pin", + "icon": !isPinned ? "pin" : "unpin" + }); + + // Close item (for running apps) + items.push({ + "label": I18n.tr("common.close"), + "action": "close", + "icon": "x" + }); + + // Add desktop entry actions (like "New Window", "Private Window", etc.) + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.byId && root.selectedAppId) { + const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(root.selectedAppId) : DesktopEntries.byId(root.selectedAppId); + if (entry != null && entry.actions) { + entry.actions.forEach(function (action) { + items.push({ + "label": action.name, + "action": "desktop-action-" + action.name, + "icon": "chevron-right", + "desktopAction": action + }); + }); + } + } + } + items.push({ + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }); + return items; + } + onTriggered: (action, item) => { + contextMenu.close(); + PanelService.closeContextMenu(root.screen); + + // Look up the window fresh each time to avoid stale references + const selectedWindow = root.getSelectedWindow(); + + if (action === "focus" && selectedWindow) { + CompositorService.focusWindow(selectedWindow); + } else if (action === "pin" && root.selectedAppId) { + root.toggleAppPin(root.selectedAppId); + } else if (action === "close" && selectedWindow) { + CompositorService.closeWindow(selectedWindow); + } else if (action === "widget-settings") { + BarService.openWidgetSettings(root.screen, root.section, root.sectionWidgetIndex, root.widgetId, root.widgetSettings); + } else if (action.startsWith("desktop-action-") && item && item.desktopAction) { + if (item.desktopAction.command && item.desktopAction.command.length > 0) { + Quickshell.execDetached(item.desktopAction.command); + } else if (item.desktopAction.execute) { + item.desktopAction.execute(); + } + } + root.selectedWindowId = ""; + root.selectedAppId = ""; + } + } + + function updateHasWindow() { + // Check if we have any items in the combined model (windows or pinned apps) + hasWindow = combinedModel.length > 0; + } + + Connections { + target: CompositorService + function onActiveWindowChanged() { + updateCombinedModel(); + } + function onWindowListChanged() { + updateCombinedModel(); + } + function onWorkspaceChanged() { + updateCombinedModel(); + } + } + + Connections { + target: Settings.data.dock + function onPinnedAppsChanged() { + updateCombinedModel(); + } + } + + Component.onCompleted: { + updateCombinedModel(); + } + onScreenChanged: updateCombinedModel() + + // Debounce timer for wheel interactions + Timer { + id: wheelDebounce + interval: 150 + repeat: false + onTriggered: { + root.wheelCooldown = false; + root.wheelAccumulatedDelta = 0; + } + } + + // Scroll to switch between windows + WheelHandler { + id: wheelHandler + target: root + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onWheel: function (event) { + if (root.wheelCooldown || root.combinedModel.length === 0) + return; + var dy = event.angleDelta.y; + var dx = event.angleDelta.x; + var useDy = Math.abs(dy) >= Math.abs(dx); + var delta = useDy ? dy : dx; + root.wheelAccumulatedDelta += delta; + var step = 120; + if (Math.abs(root.wheelAccumulatedDelta) >= step) { + var direction = root.wheelAccumulatedDelta > 0 ? -1 : 1; + // Find the focused window or first running window + var currentIndex = -1; + for (var i = 0; i < root.combinedModel.length; i++) { + if (root.combinedModel[i].window && root.combinedModel[i].window.isFocused) { + currentIndex = i; + break; + } + } + if (currentIndex < 0) { + // No focused window, find first running window + for (var j = 0; j < root.combinedModel.length; j++) { + if (root.combinedModel[j].window) { + currentIndex = j; + break; + } + } + } + if (currentIndex >= 0) { + var nextIndex = (currentIndex + direction + root.combinedModel.length) % root.combinedModel.length; + var nextItem = root.combinedModel[nextIndex]; + if (nextItem && nextItem.window) { + try { + CompositorService.focusWindow(nextItem.window); + } catch (error) { + Logger.e("Taskbar", "Failed to focus window: " + error); + } + } + } + root.wheelCooldown = true; + wheelDebounce.restart(); + root.wheelAccumulatedDelta = 0; + event.accepted = true; + } + } + } + + // "visible": Always Visible, "hidden": Hide When Empty, "transparent": Transparent When Empty + visible: hideMode !== "hidden" || hasWindow + opacity: ((hideMode !== "hidden" && hideMode !== "transparent") || hasWindow) ? 1.0 : 0.0 + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + + // Content dimensions for implicit sizing + readonly property real contentWidth: { + if (!visible) + return 0; + if (isVerticalBar) + return barHeight; + + var calculatedWidth = showTitle ? taskbarLayout.implicitWidth : taskbarLayout.implicitWidth + Style.margin2M; + + // Apply maximum width constraint when smartWidth is enabled + if (smartWidth && maxTaskbarWidth > 0) { + return Math.min(calculatedWidth, maxTaskbarWidth); + } + + return Math.round(calculatedWidth); + } + readonly property real contentHeight: visible ? (isVerticalBar ? Math.round(taskbarLayout.implicitHeight + Style.margin2S) : capsuleHeight) : 0 + + implicitWidth: contentWidth + implicitHeight: contentHeight + + // Visual capsule centered in parent + Rectangle { + id: visualCapsule + width: root.contentWidth + height: root.contentHeight + anchors.centerIn: parent + radius: Style.radiusM + color: Style.capsuleColor + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + GridLayout { + id: taskbarLayout + + // Pixel-perfect centering + x: isVerticalBar ? Style.pixelAlignCenter(parent.width, width) : ((root.showTitle) ? Style.pixelAlignCenter(parent.width, width) : Style.marginM) + y: Style.pixelAlignCenter(parent.height, height) + + // Configure GridLayout to behave like RowLayout or ColumnLayout + rows: isVerticalBar ? -1 : 1 // -1 means unlimited + columns: isVerticalBar ? 1 : -1 // -1 means unlimited + + rowSpacing: isVerticalBar ? Style.marginXXS : 0 + columnSpacing: isVerticalBar ? 0 : Style.marginXXS + + Repeater { + model: root.combinedModel + delegate: Item { + id: taskbarItem + required property var modelData + required property int index + property ShellScreen screen: root.screen + + readonly property bool isRunning: modelData.window !== null + readonly property bool isPinned: modelData.type === "pinned" || modelData.type === "pinned-running" + readonly property bool isFocused: isRunning && modelData.window && modelData.window.isFocused + readonly property bool isPinnedRunning: isPinned && isRunning && !isFocused + readonly property bool isHovered: root.hoveredWindowId === modelData.id + + readonly property bool shouldShowTitle: root.showTitle && modelData.type !== "pinned" + readonly property real itemSpacing: Style.marginS + readonly property real contentWidth: shouldShowTitle ? root.itemSize + itemSpacing + root.titleWidth : root.itemSize + + readonly property string title: modelData.title || modelData.appId || "Unknown application" + readonly property color titleBgColor: (isHovered || isFocused) ? Color.mHover : Style.capsuleColor + readonly property color titleFgColor: (isHovered || isFocused) ? Color.mOnHover : Color.mOnSurface + + Layout.preferredWidth: root.isVerticalBar ? root.barHeight : (root.showTitle ? Math.round(contentWidth + Style.margin2M) : Math.round(contentWidth)) // Add margins for both pinned and running apps + Layout.preferredHeight: root.isVerticalBar ? root.itemSize : root.barHeight + Layout.alignment: Qt.AlignCenter + + // Ensure dragged item is on top + z: (root.dragSourceIndex === index) ? 1000 : 1 + + property int modelIndex: index + objectName: "taskbarAppItem" + + DropArea { + anchors.fill: parent + keys: ["taskbar-app"] + onEntered: function (drag) { + if (drag.source && drag.source.objectName === "taskbarAppItem") { + root.dragTargetIndex = taskbarItem.modelIndex; + } + } + onExited: function () { + if (root.dragTargetIndex === taskbarItem.modelIndex) { + root.dragTargetIndex = -1; + } + } + onDropped: function (drop) { + root.dragSourceIndex = -1; + root.dragTargetIndex = -1; + Logger.d("Taskbar", "Dropped! Source: " + (drop.source ? drop.source.objectName : "null") + " Index: " + (drop.source ? drop.source.modelIndex : "?") + " -> Target Index: " + taskbarItem.modelIndex); + if (drop.source && drop.source.objectName === "taskbarAppItem" && drop.source !== taskbarItem) { + root.reorderApps(drop.source.modelIndex, taskbarItem.modelIndex); + } else { + Logger.d("Taskbar", "Drop ignored. Source objectName: " + (drop.source ? drop.source.objectName : "null")); + } + } + } + + Item { + id: draggableContent + width: parent.width + height: parent.height + anchors.centerIn: dragging ? undefined : parent + + // Visual shifting logic + readonly property bool isDragged: root.dragSourceIndex === index + property real shiftOffset: 0 + + // Calculate shift based on drag state + // If I am NOT the dragged item, but I am in the path of the drag + Binding on shiftOffset { + value: { + if (root.dragSourceIndex !== -1 && root.dragTargetIndex !== -1 && !draggableContent.isDragged) { + if (root.dragSourceIndex < root.dragTargetIndex) { + // Dragging Right: Items between source and target shift Left + if (index > root.dragSourceIndex && index <= root.dragTargetIndex) { + return -1 * (root.isVerticalBar ? root.itemSize : draggableContent.width); // Simple approximation, could be refined + } + } else if (root.dragSourceIndex > root.dragTargetIndex) { + // Dragging Left: Items between target and source shift Right + if (index >= root.dragTargetIndex && index < root.dragSourceIndex) { + return (root.isVerticalBar ? root.itemSize : draggableContent.width); + } + } + } + return 0; + } + } + + transform: Translate { + x: !root.isVerticalBar ? draggableContent.shiftOffset : 0 + y: root.isVerticalBar ? draggableContent.shiftOffset : 0 + + Behavior on x { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutQuad + } + } + Behavior on y { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutQuad + } + } + } + + property bool dragging: taskbarMouseArea.drag.active + onDraggingChanged: { + if (dragging) { + root.dragSourceIndex = index; + } else { + // Don't reset immediately on release to allow drop to handle it, + // or use a timer if needed, but drop handler usually fires. + // However, if dropped outside, we need to reset. + // Let's reset if not handled by drop area quickly? + // Actually, drag.active becomes false on release. + // We might want to clear it if no drop happened. + if (root.dragSourceIndex === index) { + // Slight delay/check? For now, let DropArea handle reset on success. + // If cancelled (dropped nowhere), we should reset. + Qt.callLater(() => { + if (!taskbarMouseArea.drag.active && root.dragSourceIndex === index) { + root.dragSourceIndex = -1; + root.dragTargetIndex = -1; + } + }); + } + } + } + + Drag.active: dragging + Drag.source: taskbarItem + Drag.hotSpot.x: width / 2 + Drag.hotSpot.y: height / 2 + Drag.keys: ["taskbar-app"] + + z: dragging ? 1000 : 0 + scale: dragging ? 1.05 : 1.0 + Behavior on scale { + NumberAnimation { + duration: Style.animationFast + } + } + + Rectangle { + id: titleBackground + visible: shouldShowTitle + anchors.centerIn: parent + width: parent.width + height: root.capsuleHeight + color: titleBgColor + radius: Style.radiusM + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + } + + Rectangle { + anchors.centerIn: parent + width: taskbarItem.contentWidth + height: parent.height + color: "transparent" + + RowLayout { + id: itemLayout + anchors.fill: parent + spacing: taskbarItem.itemSpacing + + Item { + Layout.preferredWidth: root.itemSize + Layout.preferredHeight: root.itemSize + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + + IconImage { + id: appIcon + anchors.fill: parent + + source: ThemeIcons.iconForAppId(taskbarItem.modelData.appId) + smooth: true + asynchronous: true + + // Apply dock shader to all taskbar icons + layer.enabled: widgetSettings.colorizeIcons !== false + layer.effect: ShaderEffect { + property color targetColor: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mSurfaceVariant + property real colorizeMode: 0.0 // Dock mode (grayscale) + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } + } + + Rectangle { + id: iconBackground + visible: !shouldShowTitle + anchors.bottomMargin: -2 + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + width: Style.toOdd(root.itemSize * 0.25) + height: 4 + color: taskbarItem.isFocused ? Color.mPrimary : (taskbarItem.isHovered ? Color.mHover : "transparent") + radius: Math.min(Style.radiusXXS, width / 2) + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + } + + NText { + id: titleText + visible: shouldShowTitle + Layout.preferredWidth: root.titleWidth + Layout.preferredHeight: root.itemSize + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + Layout.fillWidth: false + + text: taskbarItem.title + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + + pointSize: barFontSize + color: titleFgColor + opacity: Style.opacityFull + } + } + } + } + + MouseArea { + id: taskbarMouseArea + objectName: "taskbarMouseArea" + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton + + drag.target: draggableContent + drag.axis: root.isVerticalBar ? Drag.YAxis : Drag.XAxis + preventStealing: true + + onPressed: { + // Constrain drag to roughly the taskbar area but allow some freedom + // Or just let it be free since we only care about drops + } + + onReleased: { + if (draggableContent.Drag.active) { + draggableContent.Drag.drop(); + } + } + + onClicked: mouse => { + if (!modelData) + return; + if (mouse.button === Qt.LeftButton) { + if (isRunning && modelData.window) { + // Running app - focus it + try { + CompositorService.focusWindow(modelData.window); + } catch (error) { + Logger.e("Taskbar", "Failed to activate toplevel: " + error); + } + } else if (isPinned) { + // Pinned app not running - launch it + root.launchPinnedApp(modelData.appId); + } + } else if (mouse.button === Qt.RightButton) { + TooltipService.hide(); + // Only show context menu for running apps + if (isRunning && modelData.window) { + root.selectedWindowId = modelData.id; + root.selectedAppId = modelData.appId; + root.openTaskbarContextMenu(taskbarItem); + } + } + } + onEntered: { + root.hoveredWindowId = taskbarItem.modelData.id; + TooltipService.show(taskbarItem, taskbarItem.title, BarService.getTooltipDirection(root.screen?.name)); + } + onExited: { + root.hoveredWindowId = ""; + TooltipService.hide(); + } + } + } + } + } + } + + function openTaskbarContextMenu(item) { + // Build menu model directly + var items = []; + if (root.selectedWindowId) { + // Focus item (for running apps) + items.push({ + "label": I18n.tr("common.focus"), + "action": "focus", + "icon": "eye" + }); + + // Pin/Unpin item + const isPinned = root.isAppPinned(root.selectedAppId); + items.push({ + "label": !isPinned ? I18n.tr("common.pin") : I18n.tr("common.unpin"), + "action": "pin", + "icon": !isPinned ? "pin" : "unpin" + }); + + // Close item + items.push({ + "label": I18n.tr("common.close"), + "action": "close", + "icon": "x" + }); + + // Add desktop entry actions (like "New Window", "Private Window", etc.) + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.byId && root.selectedAppId) { + const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(root.selectedAppId) : DesktopEntries.byId(root.selectedAppId); + if (entry != null && entry.actions) { + entry.actions.forEach(function (action) { + items.push({ + "label": action.name, + "action": "desktop-action-" + action.name, + "icon": "chevron-right", + "desktopAction": action + }); + }); + } + } + } + items.push({ + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }); + + // Set the model directly + contextMenu.model = items; + + // Anchor to root (stable) but center horizontally on the clicked item + PanelService.showContextMenu(contextMenu, root, screen, item); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Tray.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Tray.qml new file mode 100644 index 0000000..ef33111 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Tray.qml @@ -0,0 +1,580 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.SystemTray +import Quickshell.Widgets +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property ShellScreen screen + + // Trigger re-evaluation when window is registered + property int popupMenuUpdateTrigger: 0 + + // Get shared popup menu window from PanelService (reactive to trigger changes) + readonly property var popupMenuWindow: { + // Reference trigger to force re-evaluation + var popupMenuUpdateTriggerRef = popupMenuUpdateTrigger; + return PanelService.getPopupMenuWindow(screen); + } + + readonly property var trayMenu: popupMenuWindow ? popupMenuWindow.trayMenuLoader : null + + Connections { + target: PanelService + function onPopupMenuWindowRegistered(registeredScreen) { + if (registeredScreen === screen) { + root.popupMenuUpdateTrigger++; + } + } + } + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isVertical: barPosition === "left" || barPosition === "right" + readonly property real barHeight: Style.getBarHeightForScreen(screenName) + readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName) + readonly property bool density: Settings.data.bar.density + readonly property int iconSize: Style.toOdd(capsuleHeight * 0.65) + + property var blacklist: widgetSettings.blacklist || widgetMetadata.blacklist || [] // Read from settings + property var pinned: widgetSettings.pinned || widgetMetadata.pinned || [] // Pinned items (shown inline) + property bool drawerEnabled: widgetSettings.drawerEnabled !== undefined ? widgetSettings.drawerEnabled : (widgetMetadata.drawerEnabled !== undefined ? widgetMetadata.drawerEnabled : true) // Enable drawer panel + property bool hidePassive: widgetSettings.hidePassive !== undefined ? widgetSettings.hidePassive : true // Hide passive status items + readonly property string chevronColorKey: widgetSettings.chevronColor !== undefined ? widgetSettings.chevronColor : widgetMetadata.chevronColor + readonly property color chevronColor: Color.resolveColorKey(chevronColorKey) + property var filteredItems: [] // Items to show inline (pinned) + property var dropdownItems: [] // Items to show in drawer (unpinned) + property int hoveredItemIndex: -1 // Track hovered item for dot indicator + + Timer { + id: updateDebounceTimer + interval: 100 // milliseconds + running: false + repeat: false + onTriggered: _performFilteredItemsUpdate() + } + + readonly property var statusSignature: { + if (!SystemTray.items || !SystemTray.items.values) { + return ""; + } + var sig = ""; + var items = SystemTray.items.values; + for (var i = 0; i < items.length; i++) { + var item = items[i]; + if (item) { + // Direct property access creates reactive binding + var s = item.status; + sig += (item.id || i) + ":" + (s !== undefined ? s : -1); + } + } + // Trigger update when signature changes (status changed) + if (root.hidePassive) { + Qt.callLater(root.updateFilteredItems); + } + return sig; + } + Repeater { + id: statusConnectionsRepeater + model: SystemTray.items && SystemTray.items.values ? SystemTray.items.values : [] + + delegate: Item { + Connections { + target: modelData + enabled: modelData !== null && modelData !== undefined + function onStatusChanged() { + if (root.hidePassive) { + root.updateFilteredItems(); + } + } + } + } + } + + function _performFilteredItemsUpdate() { + // Force a fresh read of settings to ensure we have the latest blacklist + var currentSettings = {}; + if (section && sectionWidgetIndex >= 0 && screenName) { + var w = Settings.getBarWidgetsForScreen(screenName)[section]; + if (w && sectionWidgetIndex < w.length) { + currentSettings = w[sectionWidgetIndex]; + } + } + + // Update local properties with fresh data + if (currentSettings.blacklist !== undefined) + root.blacklist = currentSettings.blacklist; + if (currentSettings.pinned !== undefined) + root.pinned = currentSettings.pinned; + + let newItems = []; + if (SystemTray.items && SystemTray.items.values) { + const trayItems = SystemTray.items.values; + for (var i = 0; i < trayItems.length; i++) { + const item = trayItems[i]; + if (!item) { + continue; + } + + const title = item.tooltipTitle || item.name || item.id || ""; + + // Skip passive items if hidePassive is enabled + if (root.hidePassive && item.status !== undefined && (item.status === SystemTray.Passive || item.status === 0)) { + continue; + } + + // Check if blacklisted + let isBlacklisted = false; + if (root.blacklist && root.blacklist.length > 0) { + for (var j = 0; j < root.blacklist.length; j++) { + const rule = root.blacklist[j]; + if (wildCardMatch(title, rule)) { + isBlacklisted = true; + break; + } + } + } + + if (!isBlacklisted) { + newItems.push(item); + } + } + } + + // If drawer is disabled, show all items inline + if (!root.drawerEnabled) { + filteredItems = newItems; + dropdownItems = []; + } else { + // Build inline (pinned) and drawer (unpinned) lists + // If pinned list is empty, all items go to drawer (none inline) + // If pinned list has items, pinned items are inline, rest go to drawer + if (pinned && pinned.length > 0) { + let pinnedItems = []; + for (var k = 0; k < newItems.length; k++) { + const item2 = newItems[k]; + const title2 = item2.tooltipTitle || item2.name || item2.id || ""; + for (var m = 0; m < pinned.length; m++) { + const rule2 = pinned[m]; + if (wildCardMatch(title2, rule2)) { + pinnedItems.push(item2); + break; + } + } + } + filteredItems = pinnedItems; + + // Unpinned items go to drawer + let unpinnedItems = []; + for (var v = 0; v < newItems.length; v++) { + const cand = newItems[v]; + let isPinned = false; + for (var f = 0; f < filteredItems.length; f++) { + if (filteredItems[f] === cand) { + isPinned = true; + break; + } + } + if (!isPinned) + unpinnedItems.push(cand); + } + dropdownItems = unpinnedItems; + } else { + // No pinned items: all items go to drawer (none inline) + filteredItems = []; + dropdownItems = newItems; + } + } + } + + function updateFilteredItems() { + updateDebounceTimer.restart(); + } + + function wildCardMatch(str, rule) { + if (!str || !rule) { + return false; + } + + // First, convert '*' to a placeholder to preserve it, then escape other special regex characters + // Use a unique placeholder that won't appear in normal strings + const placeholder = '\uE000'; // Private use character + let processedRule = rule.replace(/\*/g, placeholder); + // Escape all special regex characters (but placeholder won't match this) + let escapedRule = processedRule.replace(/[.+?^${}()|[\]\\]/g, '\\$&'); + // Convert placeholder back to '.*' for wildcard matching + let pattern = escapedRule.replace(new RegExp(placeholder, 'g'), '.*'); + // Add ^ and $ to match the entire string + pattern = '^' + pattern + '$'; + + try { + const regex = new RegExp(pattern, 'i'); + // 'i' for case-insensitive + return regex.test(str); + } catch (e) { + Logger.w("Tray", "Invalid regex pattern for wildcard match:", rule, e.message); + return false; // If regex is invalid, it won't match + } + } + + function toggleDrawer(button) { + TooltipService.hideImmediately(); + + // Close the popup menu if it's open + if (popupMenuWindow && popupMenuWindow.visible) { + popupMenuWindow.close(); + } + + const panel = PanelService.getPanel("trayDrawerPanel", root.screen); + if (panel) { + panel.widgetSection = root.section; + panel.widgetIndex = root.sectionWidgetIndex; + panel.toggle(this); + } + } + + function onLoaded() { + // When the widget is fully initialized with its props set the screen for the trayMenu + if (trayMenu && trayMenu.item) { + trayMenu.item.screen = screen; + } + } + + Connections { + target: SystemTray.items + function onValuesChanged() { + root.updateFilteredItems(); + // Repeater will automatically update when items change + } + } + + Connections { + target: Settings + function onSettingsSaved() { + root.updateFilteredItems(); + } + } + + // Watch for hidePassive changes to update filtering immediately + onHidePassiveChanged: { + root.updateFilteredItems(); + } + + Component.onCompleted: { + root.updateFilteredItems(); // Initial update + } + + // Content dimensions for implicit sizing + readonly property int visibleItemCount: (root.drawerEnabled && dropdownItems.length > 0 ? 1 : 0) + filteredItems.length + readonly property real capsulePadding: 0 + readonly property real capsuleWidth: isVertical ? capsuleHeight : Math.round(trayFlow.implicitWidth + capsulePadding * 2) + readonly property real capsuleContentHeight: isVertical ? Math.round(trayFlow.implicitHeight + capsulePadding * 2) : capsuleHeight + + implicitWidth: isVertical ? barHeight : Math.round(trayFlow.implicitWidth + capsulePadding * 2) + implicitHeight: isVertical ? Math.round(trayFlow.implicitHeight + capsulePadding * 2) : barHeight + visible: filteredItems.length > 0 || dropdownItems.length > 0 + opacity: (filteredItems.length > 0 || dropdownItems.length > 0) ? 1.0 : 0.0 + + // Visual capsule centered in parent + Rectangle { + id: visualCapsule + width: capsuleWidth + height: capsuleContentHeight + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, height) + radius: Style.radiusM + color: Style.capsuleColor + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + } + + NPopupContextMenu { + id: chevronContextMenu + + model: [ + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + chevronContextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + Flow { + id: trayFlow + spacing: 0 + flow: isVertical ? Flow.TopToBottom : Flow.LeftToRight + + // Position centered in capsule + anchors.centerIn: visualCapsule + + // Drawer opener (before items if opposite direction) + NIconButton { + id: chevronIconBefore + visible: root.drawerEnabled && dropdownItems.length > 0 && BarService.getPillDirection(root) + width: isVertical ? barHeight : capsuleHeight + height: isVertical ? capsuleHeight : barHeight + tooltipText: { + if (PanelService.getPanel("trayDrawerPanel", root.screen)?.isPanelOpen) { + return ""; + } else { + return I18n.tr("tooltips.open-tray-dropdown"); + } + } + tooltipDirection: BarService.getTooltipDirection(root.screen?.name) + baseSize: capsuleHeight + applyUiScale: false + customRadius: Style.radiusL + colorBg: "transparent" + colorFg: root.chevronColor + colorBorder: "transparent" + colorBorderHover: "transparent" + icon: { + switch (barPosition) { + case "bottom": + return "caret-up"; + case "left": + return "caret-right"; + case "right": + return "caret-left"; + case "top": + default: + return "caret-down"; + } + } + onClicked: toggleDrawer(this) + onRightClicked: PanelService.showContextMenu(chevronContextMenu, this, screen) + } + + // Pinned items + Repeater { + id: repeater + model: root.filteredItems + + delegate: Item { + id: trayDelegate + required property var modelData + required property int index + width: isVertical ? barHeight : capsuleHeight + height: isVertical ? capsuleHeight : barHeight + visible: modelData + readonly property bool isHovered: root.hoveredItemIndex === index + + // Tooltip anchor representing the visual area (for proper tooltip positioning) + Item { + id: tooltipAnchor + width: capsuleHeight + height: capsuleHeight + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, height) + } + + IconImage { + id: trayIcon + width: iconSize + height: iconSize + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, height) + asynchronous: true + backer.fillMode: Image.PreserveAspectFit + + source: { + let icon = modelData?.icon || ""; + if (!icon) { + return ""; + } + + // Process icon path + if (icon.includes("?path=")) { + const chunks = icon.split("?path="); + const name = chunks[0]; + const path = chunks[1]; + const fileName = name.substring(name.lastIndexOf("/") + 1); + return `file://${path}/${fileName}`; + } + return icon; + } + opacity: status === Image.Ready ? 1 : 0 + + layer.enabled: widgetSettings.colorizeIcons !== false + layer.effect: ShaderEffect { + property color targetColor: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mSurfaceVariant + property real colorizeMode: 1.0 + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } + } + + Rectangle { + id: hoverIndicator + anchors.bottom: trayIcon.bottom + anchors.bottomMargin: -2 + anchors.horizontalCenter: trayIcon.horizontalCenter + width: Style.toOdd(iconSize * 0.25) + height: 4 + color: trayDelegate.isHovered ? Color.mHover : "transparent" + radius: Math.min(Style.radiusXXS, width / 2) + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + MouseArea { + id: itemMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + onContainsMouseChanged: { + if (containsMouse) { + if (popupMenuWindow) { + popupMenuWindow.close(); + } + root.hoveredItemIndex = trayDelegate.index; + TooltipService.show(tooltipAnchor, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection(root.screen?.name)); + } else if (root.hoveredItemIndex === trayDelegate.index) { + root.hoveredItemIndex = -1; + TooltipService.hide(tooltipAnchor); + } + } + onClicked: mouse => { + if (!modelData) { + return; + } + + if (mouse.button === Qt.LeftButton) { + // Close any open menu first + if (popupMenuWindow) { + popupMenuWindow.close(); + } + + if (!modelData.onlyMenu) { + modelData.activate(); + } + } else if (mouse.button === Qt.MiddleButton) { + // Close the menu if it was visible + if (popupMenuWindow && popupMenuWindow.visible) { + popupMenuWindow.close(); + return; + } + modelData.secondaryActivate && modelData.secondaryActivate(); + } else if (mouse.button === Qt.RightButton) { + TooltipService.hideImmediately(); + + // Close the menu if it was visible + if (popupMenuWindow && popupMenuWindow.visible) { + popupMenuWindow.close(); + return; + } + + // Close any opened panel + if ((PanelService.openedPanel !== null) && !PanelService.openedPanel.isClosing) { + PanelService.openedPanel.close(); + } + + if (modelData.hasMenu && modelData.menu && trayMenu && trayMenu.item) { + // Calculate menu position after ensuring menu is loaded + const calculateAndShow = () => { + // Position menu based on bar position, using tooltipAnchor for proper positioning + // Increased spacing for better alignment with other context menus + let menuX, menuY; + if (barPosition === "left") { + // For left bar: position menu to the right of the visual area + menuX = tooltipAnchor.width + Style.marginL; + menuY = 0; + } else if (barPosition === "right") { + // For right bar: position menu to the left of the visual area + menuX = -trayMenu.item.implicitWidth - Style.marginL; + menuY = 0; + } else { + // For horizontal bars: center horizontally and position below visual area + menuX = (tooltipAnchor.width / 2) - (trayMenu.item.implicitWidth / 2); + menuY = tooltipAnchor.height + Style.marginS; + } + + PanelService.showTrayMenu(root.screen, modelData, trayMenu.item, tooltipAnchor, menuX, menuY, root.section, root.sectionWidgetIndex); + }; + + // Use Qt.callLater to ensure menu dimensions are calculated + Qt.callLater(calculateAndShow); + } else { + Logger.d("Tray", "No menu available for", modelData.id, "or trayMenu not set"); + } + } + } + } + } + } + + // Drawer opener (after items if normal direction) + NIconButton { + id: chevronIconAfter + visible: root.drawerEnabled && dropdownItems.length > 0 && !BarService.getPillDirection(root) + width: isVertical ? barHeight : capsuleHeight + height: isVertical ? capsuleHeight : barHeight + tooltipText: I18n.tr("tooltips.open-tray-dropdown") + tooltipDirection: BarService.getTooltipDirection(root.screen?.name) + baseSize: capsuleHeight + applyUiScale: false + customRadius: Style.radiusL + colorBg: "transparent" + colorFg: root.chevronColor + colorBorder: "transparent" + colorBorderHover: "transparent" + icon: { + switch (barPosition) { + case "bottom": + return "caret-up"; + case "left": + return "caret-right"; + case "right": + return "caret-left"; + case "top": + default: + return "caret-down"; + } + } + onClicked: toggleDrawer(this) + onRightClicked: PanelService.showContextMenu(chevronContextMenu, this, screen) + } + } // closes Flow +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/VPN.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/VPN.qml new file mode 100644 index 0000000..eee5196 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/VPN.qml @@ -0,0 +1,139 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Services.Networking +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property ShellScreen screen + + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" + readonly property string displayMode: widgetSettings.displayMode !== undefined ? widgetSettings.displayMode : widgetMetadata.displayMode + readonly property string iconColorKey: widgetSettings.iconColor !== undefined ? widgetSettings.iconColor : widgetMetadata.iconColor + readonly property string textColorKey: widgetSettings.textColor !== undefined ? widgetSettings.textColor : widgetMetadata.textColor + + implicitWidth: pill.width + implicitHeight: pill.height + + NPopupContextMenu { + id: contextMenu + + model: { + const items = []; + const active = VPNService.activeConnections; + for (let i = 0; i < active.length; ++i) { + const conn = active[i]; + items.push({ + "label": I18n.tr("actions.disconnect-vpn", { + "name": conn.name + }), + "action": "disconnect:" + conn.uuid, + "icon": "shield-off" + }); + } + const inactive = VPNService.inactiveConnections; + for (let i = 0; i < inactive.length; ++i) { + const conn = inactive[i]; + items.push({ + "label": I18n.tr("actions.connect-vpn", { + "name": conn.name + }), + "action": "connect:" + conn.uuid, + "icon": "shield-lock" + }); + } + items.push({ + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }); + return items; + } + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (!action) { + return; + } + if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + return; + } + if (action.startsWith("connect:")) { + const uuid = action.substring("connect:".length); + VPNService.connect(uuid); + return; + } + if (action.startsWith("disconnect:")) { + const uuid = action.substring("disconnect:".length); + VPNService.disconnect(uuid); + } + } + } + + BarPill { + id: pill + + screen: root.screen + oppositeDirection: BarService.getPillDirection(root) + customIconColor: Color.resolveColorKeyOptional(root.iconColorKey) + customTextColor: Color.resolveColorKeyOptional(root.textColorKey) + icon: VPNService.hasActiveConnection ? "shield-lock" : "shield" + text: { + if (VPNService.activeConnections.length > 0) { + return VPNService.activeConnections[0].name; + } + if (VPNService.connectingUuid) { + const pending = VPNService.connections[VPNService.connectingUuid]; + if (pending) { + return pending.name; + } + } + return ""; + } + suffix: { + if (VPNService.activeConnections.length > 1) { + return ` + ${VPNService.activeConnections.length - 1}`; + } + return ""; + } + autoHide: false + forceOpen: !isBarVertical && root.displayMode === "alwaysShow" + forceClose: isBarVertical || root.displayMode === "alwaysHide" || !pill.text + onRightClicked: { + PanelService.showContextMenu(contextMenu, pill, screen); + } + tooltipText: { + if (pill.text !== "") { + return pill.text; + } + return I18n.tr("tooltips.manage-vpn"); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Volume.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Volume.qml new file mode 100644 index 0000000..03bd496 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Volume.qml @@ -0,0 +1,203 @@ +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Services.Pipewire +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Modules.Panels.Settings +import qs.Services.Media +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" + readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode + readonly property string middleClickCommand: (widgetSettings.middleClickCommand !== undefined) ? widgetSettings.middleClickCommand : widgetMetadata.middleClickCommand + readonly property string iconColorKey: widgetSettings.iconColor !== undefined ? widgetSettings.iconColor : widgetMetadata.iconColor + readonly property string textColorKey: widgetSettings.textColor !== undefined ? widgetSettings.textColor : widgetMetadata.textColor + readonly property bool reverseScroll: Settings.data.general.reverseScroll + + // Used to avoid opening the pill on Quickshell startup + property bool firstVolumeReceived: false + property int wheelAccumulator: 0 + + implicitWidth: pill.width + implicitHeight: pill.height + + // Connection used to open the pill when volume changes + Connections { + target: AudioService + function onVolumeChanged() { + // Logger.i("Bar:Volume", "onVolumeChanged") + if (!firstVolumeReceived) { + // Ignore the first volume change + firstVolumeReceived = true; + } else { + // Hide any tooltip while the pill is visible / being updated + TooltipService.hide(); + pill.show(); + externalHideTimer.restart(); + } + } + + function onMutedChanged() { + if (!firstVolumeReceived) { + firstVolumeReceived = true; + } else { + TooltipService.hide(); + pill.show(); + externalHideTimer.restart(); + } + } + + function onVolumeAtMaximum() { + if (!firstVolumeReceived) { + firstVolumeReceived = true; + } else { + // Hide any tooltip while the pill is visible / being updated + TooltipService.hide(); + pill.show(); + externalHideTimer.restart(); + } + } + + function onVolumeAtMinimum() { + if (!firstVolumeReceived) { + firstVolumeReceived = true; + } else { + // Hide any tooltip while the pill is visible / being updated + TooltipService.hide(); + pill.show(); + externalHideTimer.restart(); + } + } + } + + Timer { + id: externalHideTimer + running: false + interval: 1500 + onTriggered: { + pill.hide(); + } + } + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.toggle-mute"), + "action": "toggle-mute", + "icon": AudioService.muted ? "volume-off" : "volume" + }, + { + "label": I18n.tr("actions.run-custom-command"), + "action": "custom-command", + "icon": "adjustments" + }, + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "toggle-mute") { + AudioService.setOutputMuted(!AudioService.muted); + } else if (action === "custom-command") { + Quickshell.execDetached(["sh", "-c", middleClickCommand]); + } else if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + BarPill { + id: pill + + screen: root.screen + oppositeDirection: BarService.getPillDirection(root) + customIconColor: Color.resolveColorKeyOptional(root.iconColorKey) + customTextColor: Color.resolveColorKeyOptional(root.textColorKey) + icon: AudioService.getOutputIcon() + autoHide: false // Important to be false so we can hover as long as we want + text: { + const maxVolume = Settings.data.audio.volumeOverdrive ? 1.5 : 1.0; + const displayVolume = Math.min(maxVolume, AudioService.volume); + return Math.round(displayVolume * 100); + } + suffix: "%" + forceOpen: displayMode === "alwaysShow" + forceClose: displayMode === "alwaysHide" + tooltipText: { + if (PanelService.getPanel("audioPanel", screen)?.isPanelOpen) { + return ""; + } else { + const nick = AudioService.sink?.nickname ?? ""; + const volumeText = I18n.tr("tooltips.volume-at", { + "volume": (() => { + const maxVolume = Settings.data.audio.volumeOverdrive ? 1.5 : 1.0; + const displayVolume = Math.min(maxVolume, AudioService.volume); + return Math.round(displayVolume * 100); + })() + }); + return nick ? volumeText + "\n" + nick : volumeText; + } + } + + onWheel: function (delta) { + // Hide tooltip as soon as the user starts scrolling to adjust volume + TooltipService.hide(); + + if (root.reverseScroll) + delta *= -1; + + wheelAccumulator += delta; + if (wheelAccumulator >= 120) { + wheelAccumulator = 0; + AudioService.increaseVolume(); + } else if (wheelAccumulator <= -120) { + wheelAccumulator = 0; + AudioService.decreaseVolume(); + } + } + onClicked: { + PanelService.getPanel("audioPanel", screen)?.toggle(this); + } + onRightClicked: { + PanelService.showContextMenu(contextMenu, pill, screen); + } + onMiddleClicked: { + Quickshell.execDetached(["sh", "-c", middleClickCommand]); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/WallpaperSelector.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/WallpaperSelector.qml new file mode 100644 index 0000000..0c2c562 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/WallpaperSelector.qml @@ -0,0 +1,92 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Services.UI +import qs.Widgets + +NIconButton { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string iconColorKey: widgetSettings.iconColor !== undefined ? widgetSettings.iconColor : widgetMetadata.iconColor + + enabled: Settings.data.wallpaper.enabled + baseSize: Style.getCapsuleHeightForScreen(screen?.name) + applyUiScale: false + customRadius: Style.radiusL + icon: "wallpaper-selector" + tooltipText: { + if (PanelService.getPanel("wallpaperPanel", screen)?.isPanelOpen) { + return ""; + } else { + return I18n.tr("tooltips.wallpaper-selector"); + } + } + tooltipDirection: BarService.getTooltipDirection(screen?.name) + colorBg: Style.capsuleColor + colorFg: Color.resolveColorKey(iconColorKey) + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": I18n.tr("actions.random-wallpaper"), + "action": "random-wallpaper", + "icon": "dice" + }, + { + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }, + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "random-wallpaper") { + WallpaperService.setRandomWallpaper(); + } else if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } + } + } + + onClicked: { + var wallpaperPanel = PanelService.getPanel("wallpaperPanel", screen); + if (Settings.data.wallpaper.panelPosition === "follow_bar") { + wallpaperPanel?.toggle(this); + } else { + wallpaperPanel?.toggle(); + } + } + onRightClicked: { + PanelService.showContextMenu(contextMenu, root, screen); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Workspace.qml b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Workspace.qml new file mode 100644 index 0000000..c961036 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Bar/Widgets/Workspace.qml @@ -0,0 +1,1007 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import QtQuick.Window +import Quickshell +import Quickshell.Io +import Quickshell.Widgets +import qs.Commons +import qs.Modules.Bar.Extras +import qs.Services.Compositor +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property ShellScreen screen + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {} + // Explicit screenName property ensures reactive binding when screen changes + readonly property string screenName: screen ? screen.name : "" + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0 && screenName) { + var widgets = Settings.getBarWidgetsForScreen(screenName)[section]; + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex]; + } + } + return {}; + } + + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isVertical: barPosition === "left" || barPosition === "right" + readonly property real barHeight: Style.getBarHeightForScreen(screenName) + readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName) + readonly property real barFontSize: Style.getBarFontSizeForScreen(screenName) + + readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode + readonly property bool hasLabel: (labelMode !== "none") + readonly property string fontWeight: { + var fontWeightSetting = (widgetSettings.fontWeight !== undefined) ? widgetSettings.fontWeight : widgetMetadata.fontWeight; + + if (fontWeightSetting === "regular") + return Style.fontWeightRegular; + if (fontWeightSetting === "medium") + return Style.fontWeightMedium; + if (fontWeightSetting === "semibold") + return Style.fontWeightSemiBold; + if (fontWeightSetting === "bold") + return Style.fontWeightBold; + return Style.fontWeightBold; + } + readonly property bool hideUnoccupied: (widgetSettings.hideUnoccupied !== undefined) ? widgetSettings.hideUnoccupied : widgetMetadata.hideUnoccupied + readonly property bool followFocusedScreen: (widgetSettings.followFocusedScreen !== undefined) ? widgetSettings.followFocusedScreen : widgetMetadata.followFocusedScreen + readonly property int characterCount: { + const count = (widgetSettings.characterCount !== undefined) ? widgetSettings.characterCount : widgetMetadata.characterCount; + return isVertical ? Math.min(count, 2) : count; + } + + // Pill size setting (0.5-1.0 range) + readonly property real pillSize: (widgetSettings.pillSize !== undefined) ? widgetSettings.pillSize : widgetMetadata.pillSize + + // When no label the pills are smaller + readonly property real baseDimensionRatio: pillSize + + // Grouped mode (show applications) settings + readonly property bool showApplications: (widgetSettings.showApplications !== undefined) ? widgetSettings.showApplications : widgetMetadata.showApplications + readonly property bool showApplicationsHover: (widgetSettings.showApplicationsHover !== undefined) ? widgetSettings.showApplicationsHover : widgetMetadata.showApplicationsHover + readonly property bool showLabelsOnlyWhenOccupied: (widgetSettings.showLabelsOnlyWhenOccupied !== undefined) ? widgetSettings.showLabelsOnlyWhenOccupied : widgetMetadata.showLabelsOnlyWhenOccupied + readonly property bool colorizeIcons: (widgetSettings.colorizeIcons !== undefined) ? widgetSettings.colorizeIcons : widgetMetadata.colorizeIcons + readonly property real unfocusedIconsOpacity: (widgetSettings.unfocusedIconsOpacity !== undefined) ? widgetSettings.unfocusedIconsOpacity : widgetMetadata.unfocusedIconsOpacity + readonly property real groupedBorderOpacity: (widgetSettings.groupedBorderOpacity !== undefined) ? widgetSettings.groupedBorderOpacity : widgetMetadata.groupedBorderOpacity + readonly property bool enableScrollWheel: (widgetSettings.enableScrollWheel !== undefined) ? widgetSettings.enableScrollWheel : widgetMetadata.enableScrollWheel + readonly property bool reverseScroll: Settings.data.general.reverseScroll + readonly property real iconScale: (widgetSettings.iconScale !== undefined) ? widgetSettings.iconScale : widgetMetadata.iconScale + readonly property string focusedColor: (widgetSettings.focusedColor !== undefined) ? widgetSettings.focusedColor : widgetMetadata.focusedColor + readonly property string occupiedColor: (widgetSettings.occupiedColor !== undefined) ? widgetSettings.occupiedColor : widgetMetadata.occupiedColor + readonly property string emptyColor: (widgetSettings.emptyColor !== undefined) ? widgetSettings.emptyColor : widgetMetadata.emptyColor + readonly property bool showBadge: (widgetSettings.showBadge !== undefined) ? widgetSettings.showBadge : widgetMetadata.showBadge + + // Only for grouped mode / show apps + readonly property int baseItemSize: Style.toOdd(capsuleHeight * 0.8) + readonly property int iconSize: Style.toOdd(baseItemSize * iconScale) + readonly property real textRatio: 0.50 + + // Context menu state for grouped mode - store IDs instead of object references to avoid stale references + property string selectedWindowId: "" + property string selectedAppId: "" + + // Helper to get the current window object from ID + function getSelectedWindow() { + if (!selectedWindowId) + return null; + // Search directly in CompositorService to get the live window object + for (var i = 0; i < CompositorService.windows.count; i++) { + var win = CompositorService.windows.get(i); + // Using loose equality on purpose (==) + if (win && (win.id == selectedWindowId || win.address == selectedWindowId)) { + return win; + } + } + return null; + } + + property bool isDestroying: false + + // Revision counter to force icon re-evaluation + property int iconRevision: 0 + // Revision counter to force window list re-evaluation (for liveWindows binding in grouped mode) + property int windowRevision: 0 + + property ListModel localWorkspaces: ListModel {} + property int lastFocusedWorkspaceId: -1 + property real masterProgress: 0.0 + property bool effectsActive: false + property color effectColor: Color.mPrimary + + property int horizontalPadding: Style.marginS + property int spacingBetweenPills: Style.marginXS + + // Wheel scroll handling + property int wheelAccumulatedDelta: 0 + property bool wheelCooldown: false + + signal workspaceChanged(int workspaceId, color accentColor) + + property bool isHovered: false + + HoverHandler { + id: workspaceHoverHandler + enabled: showApplications && showApplicationsHover + onHoveredChanged: { + if (hovered) { + hoverEval.stop(); + isHovered = true; + } else { + hoverEval.restart(); + } + } + } + + Timer { + id: hoverEval + interval: 150 + repeat: false + onTriggered: { + isHovered = workspaceHoverHandler.hovered || contextMenu.visible; + } + } + + readonly property bool appVisible: showApplications && (!showApplicationsHover || root.isHovered) + + implicitWidth: appVisible ? (isVertical ? groupedGrid.implicitWidth : Math.round(groupedGrid.implicitWidth + horizontalPadding * hasLabel)) : (isVertical ? barHeight : computeWidth()) + implicitHeight: appVisible ? (isVertical ? Math.round(groupedGrid.implicitHeight + horizontalPadding * 0.6 * hasLabel) : barHeight) : (isVertical ? computeHeight() : barHeight) + + function getWorkspaceWidth(ws, activeOverride) { + const d = Math.round(capsuleHeight * root.baseDimensionRatio); + const isActive = activeOverride !== undefined ? activeOverride : ws.isActive; + const factor = isActive ? 2.2 : 1; + + // Don't calculate text width if labels are off + if (labelMode === "none") { + return Style.toOdd(d * factor); + } + + var displayText = ws.idx.toString(); + + if (ws.name && ws.name.length > 0) { + if (root.labelMode === "name") { + displayText = ws.name.substring(0, characterCount); + } else if (root.labelMode === "index+name") { + displayText = ws.idx.toString() + " " + ws.name.substring(0, characterCount); + } + } + + const textWidth = displayText.length * (d * 0.4); // Approximate width per character + const padding = d * 0.6; + return Style.toOdd(Math.max(d * factor, textWidth + padding)); + } + + function getWorkspaceHeight(ws, activeOverride) { + const d = Math.round(capsuleHeight * root.baseDimensionRatio); + const isActive = activeOverride !== undefined ? activeOverride : ws.isActive; + const factor = isActive ? 2.2 : 1; + return Style.toOdd(d * factor); + } + + function computeWidth() { + let total = 0; + for (var i = 0; i < localWorkspaces.count; i++) { + const ws = localWorkspaces.get(i); + total += getWorkspaceWidth(ws); + } + total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills; + total += horizontalPadding * 2; + return Style.toOdd(total); + } + + function computeHeight() { + let total = 0; + for (var i = 0; i < localWorkspaces.count; i++) { + const ws = localWorkspaces.get(i); + total += getWorkspaceHeight(ws); + } + total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills; + total += horizontalPadding * 2; + return Style.toOdd(total); + } + + function getFocusedLocalIndex() { + for (var i = 0; i < localWorkspaces.count; i++) { + if (localWorkspaces.get(i).isFocused === true) + return i; + } + return -1; + } + + function switchByOffset(offset) { + if (localWorkspaces.count <= 1) + return; + var current = getFocusedLocalIndex(); + if (current < 0) + current = 0; + var next = (current + offset) % localWorkspaces.count; + if (next < 0) + next = localWorkspaces.count - 1; + if (next === current) + return; + const ws = localWorkspaces.get(next); + if (ws && ws.idx !== undefined) + CompositorService.switchToWorkspace(ws); + } + + // Helper function to normalize app IDs for case-insensitive matching + function normalizeAppId(appId) { + if (!appId || typeof appId !== 'string') + return ""; + return appId.toLowerCase().trim(); + } + + // Helper function to check if an app is pinned + function isAppPinned(appId) { + if (!appId) + return false; + const pinnedApps = Settings.data.dock.pinnedApps || []; + const normalizedId = normalizeAppId(appId); + return pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedId); + } + + // Helper function to toggle app pin/unpin + function toggleAppPin(appId) { + if (!appId) + return; + + const normalizedId = normalizeAppId(appId); + let pinnedApps = (Settings.data.dock.pinnedApps || []).slice(); + + const existingIndex = pinnedApps.findIndex(pinnedId => normalizeAppId(pinnedId) === normalizedId); + const isPinned = existingIndex >= 0; + + if (isPinned) { + pinnedApps.splice(existingIndex, 1); + } else { + pinnedApps.push(appId); + } + + Settings.data.dock.pinnedApps = pinnedApps; + } + + // Deferred via Qt.callLater to avoid synchronous ListModel mutations during + // signal cascades. Qt.callLater deduplicates by function identity, so rapid + // calls from multiple signal handlers coalesce into a single refresh. + function scheduleRefresh() { + if (!root.isDestroying) + Qt.callLater(root.refreshWorkspaces); + } + + Component.onCompleted: scheduleRefresh() + + Component.onDestruction: { + root.isDestroying = true; + } + + onScreenChanged: scheduleRefresh() + onScreenNameChanged: scheduleRefresh() + onHideUnoccupiedChanged: scheduleRefresh() + onAppVisibleChanged: { + if (appVisible) { + scheduleRefresh(); + } + } + + Connections { + target: CompositorService + function onWorkspacesChanged() { + scheduleRefresh(); + } + function onWindowListChanged() { + if (appVisible || showLabelsOnlyWhenOccupied) { + root.windowRevision++; + scheduleRefresh(); + } + } + function onActiveWindowChanged() { + if (appVisible) { + root.windowRevision++; + scheduleRefresh(); + } + } + } + + // Refresh icons when DesktopEntries becomes available + Connections { + target: DesktopEntries.applications + function onValuesChanged() { + root.iconRevision++; + } + } + + function refreshWorkspaces() { + var targetList = []; + var focusedOutput = null; + if (followFocusedScreen) { + for (var i = 0; i < CompositorService.workspaces.count; i++) { + const ws = CompositorService.workspaces.get(i); + if (ws.isFocused) + focusedOutput = ws.output.toLowerCase(); + } + } + + if (screen !== null) { + const screenName = screen.name.toLowerCase(); + for (var i = 0; i < CompositorService.workspaces.count; i++) { + const ws = CompositorService.workspaces.get(i); + // For global workspaces (e.g., LabWC), show all workspaces on all screens + const matchesScreen = CompositorService.globalWorkspaces || (followFocusedScreen && ws.output.toLowerCase() == focusedOutput) || (!followFocusedScreen && ws.output.toLowerCase() == screenName); + + if (!matchesScreen) + continue; + if (hideUnoccupied && !ws.isOccupied && !ws.isFocused) + continue; + + // Create a plain JS object for the workspace data + var workspaceData = { + id: ws.id, + idx: ws.idx, + name: ws.name, + output: ws.output, + isFocused: ws.isFocused, + isActive: ws.isActive, + isUrgent: ws.isUrgent, + isOccupied: ws.isOccupied + }; + + if (ws.handle !== null && ws.handle !== undefined) { + workspaceData.handle = ws.handle; + } + + // Windows are fetched live via liveWindows property in grouped mode + // to avoid Qt 6.9 ListModel nested array serialization issues + + targetList.push(workspaceData); + } + } + + // In-place update to preserve delegates for animations + var i = 0; + while (i < localWorkspaces.count || i < targetList.length) { + if (i < localWorkspaces.count && i < targetList.length) { + var existing = localWorkspaces.get(i); + var target = targetList[i]; + if (existing.id === target.id) { + // Use set() to update all properties, including arrays like 'windows' + // This is more reliable than repeated setProperty calls for complex types + localWorkspaces.set(i, target); + i++; + } else { + // ID mismatch, remove existing and re-evaluate this index + localWorkspaces.remove(i); + } + } else if (i < localWorkspaces.count) { + // Excess items in local, remove them + localWorkspaces.remove(i); + } else { + // More items in target, append them + localWorkspaces.append(targetList[i]); + i++; + } + } + + updateWorkspaceFocus(); + } + + function triggerUnifiedWave() { + effectColor = Color.mPrimary; + masterAnimation.restart(); + } + + function updateWorkspaceFocus() { + for (var i = 0; i < localWorkspaces.count; i++) { + const ws = localWorkspaces.get(i); + if (ws.isFocused === true) { + if (root.lastFocusedWorkspaceId !== -1 && root.lastFocusedWorkspaceId !== ws.id) { + root.triggerUnifiedWave(); + } + root.lastFocusedWorkspaceId = ws.id; + root.workspaceChanged(ws.id, Color.mPrimary); + break; + } + } + } + + SequentialAnimation { + id: masterAnimation + PropertyAction { + target: root + property: "effectsActive" + value: true + } + NumberAnimation { + target: root + property: "masterProgress" + from: 0.0 + to: 1.0 + duration: Style.animationSlow * 2 + easing.type: Easing.OutQuint + } + PropertyAction { + target: root + property: "effectsActive" + value: false + } + PropertyAction { + target: root + property: "masterProgress" + value: 0.0 + } + } + + NPopupContextMenu { + id: contextMenu + + onVisibleChanged: { + if (visible) { + hoverEval.stop(); + root.isHovered = true; + } else { + hoverEval.restart(); + } + } + + model: { + var items = []; + if (root.selectedWindowId) { + // Focus item + items.push({ + "label": I18n.tr("common.focus"), + "action": "focus", + "icon": "eye" + }); + + // Pin/Unpin item + const isPinned = root.isAppPinned(root.selectedAppId); + items.push({ + "label": !isPinned ? I18n.tr("common.pin") : I18n.tr("common.unpin"), + "action": "pin", + "icon": !isPinned ? "pin" : "pinned-off" + }); + + // Close item + items.push({ + "label": I18n.tr("common.close"), + "action": "close", + "icon": "x" + }); + + // Add desktop entry actions + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.byId && root.selectedAppId) { + const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(root.selectedAppId) : DesktopEntries.byId(root.selectedAppId); + if (entry != null && entry.actions) { + entry.actions.forEach(function (action) { + items.push({ + "label": action.name, + "action": "desktop-action-" + action.name, + "icon": "chevron-right", + "desktopAction": action + }); + }); + } + } + } + items.push({ + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }); + return items; + } + + onTriggered: (action, item) => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + const selectedWindow = root.getSelectedWindow(); + + if (action === "focus" && selectedWindow) { + CompositorService.focusWindow(selectedWindow); + } else if (action === "pin" && selectedAppId) { + root.toggleAppPin(selectedAppId); + } else if (action === "close" && selectedWindow) { + CompositorService.closeWindow(selectedWindow); + } else if (action === "widget-settings") { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); + } else if (action.startsWith("desktop-action-") && item && item.desktopAction) { + if (item.desktopAction.command && item.desktopAction.command.length > 0) { + Quickshell.execDetached(item.desktopAction.command); + } else if (item.desktopAction.execute) { + item.desktopAction.execute(); + } + } + selectedWindowId = ""; + selectedAppId = ""; + } + } + + Rectangle { + id: workspaceBackground + visible: !appVisible + width: isVertical ? capsuleHeight : parent.width + height: isVertical ? parent.height : capsuleHeight + radius: Style.radiusM + color: Style.capsuleColor + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + x: isVertical ? Style.pixelAlignCenter(parent.width, width) : 0 + y: isVertical ? 0 : Style.pixelAlignCenter(parent.height, height) + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: mouse => { + if (mouse.button === Qt.RightButton) { + PanelService.showContextMenu(contextMenu, workspaceBackground, screen); + } + } + } + } + + // Debounce timer for wheel interactions + Timer { + id: wheelDebounce + interval: 150 + repeat: false + onTriggered: { + root.wheelCooldown = false; + root.wheelAccumulatedDelta = 0; + } + } + + // Scroll to switch workspaces + WheelHandler { + id: wheelHandler + target: root + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + enabled: root.enableScrollWheel + onWheel: function (event) { + if (root.wheelCooldown) + return; + // Prefer vertical delta, fall back to horizontal if needed + var dy = event.angleDelta.y; + var dx = event.angleDelta.x; + var useDy = Math.abs(dy) >= Math.abs(dx); + var delta = useDy ? dy : dx; + // One notch is typically 120 + root.wheelAccumulatedDelta += delta; + var step = 120; + if (Math.abs(root.wheelAccumulatedDelta) >= step) { + var direction = root.wheelAccumulatedDelta > 0 ? -1 : 1; + if (root.reverseScroll) + direction *= -1; + // For vertical layout, natural mapping: wheel up -> previous, down -> next (already handled by sign) + // For horizontal layout, same mapping using vertical wheel + root.switchByOffset(direction); + root.wheelCooldown = true; + wheelDebounce.restart(); + root.wheelAccumulatedDelta = 0; + event.accepted = true; + } + } + } + + // Horizontal layout for top/bottom bars + Row { + id: pillRow + spacing: spacingBetweenPills + x: horizontalPadding + y: 0 + visible: !isVertical && !appVisible + scale: visible ? 1.0 : 0.8 + Behavior on scale { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutBack + easing.overshoot: 1.2 + } + } + + Repeater { + id: workspaceRepeaterHorizontal + model: localWorkspaces + delegate: WorkspacePill { + required property var model + workspace: model + isVertical: false + baseDimensionRatio: root.baseDimensionRatio + capsuleHeight: root.capsuleHeight + barHeight: root.barHeight + labelMode: root.labelMode + fontWeight: root.fontWeight + characterCount: root.characterCount + textRatio: root.textRatio + showLabelsOnlyWhenOccupied: root.showLabelsOnlyWhenOccupied + focusedColor: root.focusedColor + occupiedColor: root.occupiedColor + emptyColor: root.emptyColor + masterProgress: root.masterProgress + effectsActive: root.effectsActive + effectColor: root.effectColor + getWorkspaceWidth: root.getWorkspaceWidth + getWorkspaceHeight: root.getWorkspaceHeight + } + } + } + + // Vertical layout for left/right bars + Column { + id: pillColumn + spacing: spacingBetweenPills + x: 0 + y: horizontalPadding + visible: isVertical && !appVisible + scale: visible ? 1.0 : 0.8 + Behavior on scale { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutBack + easing.overshoot: 1.2 + } + } + + Repeater { + id: workspaceRepeaterVertical + model: localWorkspaces + delegate: WorkspacePill { + required property var model + workspace: model + isVertical: true + baseDimensionRatio: root.baseDimensionRatio + capsuleHeight: root.capsuleHeight + barHeight: root.barHeight + labelMode: root.labelMode + fontWeight: root.fontWeight + characterCount: root.characterCount + textRatio: root.textRatio + showLabelsOnlyWhenOccupied: root.showLabelsOnlyWhenOccupied + focusedColor: root.focusedColor + occupiedColor: root.occupiedColor + emptyColor: root.emptyColor + masterProgress: root.masterProgress + effectsActive: root.effectsActive + effectColor: root.effectColor + getWorkspaceWidth: root.getWorkspaceWidth + getWorkspaceHeight: root.getWorkspaceHeight + } + } + } + + // ======================================== + // Grouped mode (appVisible = true) + // ======================================== + Component { + id: groupedWorkspaceDelegate + + Rectangle { + id: groupedContainer + + required property var model + property var workspaceModel: model + // Fetch windows directly from service to avoid Qt 6.9 ListModel nested array issues + property var liveWindows: [] + property bool hasWindows: liveWindows.length > 0 + + function updateWindows() { + var wsId = workspaceModel?.id; + if (wsId !== undefined && wsId !== null) { + liveWindows = CompositorService.getWindowsForWorkspace(wsId); + } else { + liveWindows = []; + } + } + + // Deferred to avoid re-entrant incubation: when localWorkspaces.append() + Component.onCompleted: Qt.callLater(updateWindows) + onWorkspaceModelChanged: Qt.callLater(updateWindows) + + Connections { + target: root + function onWindowRevisionChanged() { + Qt.callLater(groupedContainer.updateWindows); + } + } + + HoverHandler { + id: groupHoverHandler + } + + width: Style.toOdd((hasWindows ? groupedIconsFlow.implicitWidth : root.iconSize) + (root.isVertical ? (root.baseItemSize - root.iconSize + Style.marginXS) : Style.marginXL)) + height: Style.toOdd((hasWindows ? groupedIconsFlow.implicitHeight : root.iconSize) + (root.isVertical ? Style.marginL : (root.baseItemSize - root.iconSize + Style.marginXS))) + color: Style.capsuleColor + radius: Style.radiusS + border.color: Settings.data.bar.showOutline ? Style.capsuleBorderColor : Qt.alpha((workspaceModel.isFocused ? Color.mPrimary : (groupHoverHandler.hovered ? Color.mHover : Color.mOutline)), root.groupedBorderOpacity) + border.width: Style.borderS + + Behavior on width { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + Behavior on height { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + enabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton + preventStealing: true + onPressed: mouse => { + if (mouse.button === Qt.LeftButton) { + CompositorService.switchToWorkspace(groupedContainer.workspaceModel); + } + } + onReleased: mouse => { + if (mouse.button === Qt.RightButton) { + mouse.accepted = true; + TooltipService.hide(); + root.selectedWindowId = ""; + root.selectedAppId = ""; + openGroupedContextMenu(groupedContainer); + } + } + } + + Flow { + id: groupedIconsFlow + + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, height) + spacing: 2 + flow: root.isVertical ? Flow.TopToBottom : Flow.LeftToRight + + Repeater { + model: groupedContainer.liveWindows + + delegate: Item { + id: groupedTaskbarItem + + readonly property bool isFocused: modelData?.isFocused ?? false + + width: root.iconSize + height: root.iconSize + + HoverHandler { + id: windowHoverHandler + } + + IconImage { + id: groupedAppIcon + + width: parent.width + height: parent.height + + source: { + root.iconRevision; // Force re-evaluation when revision changes + return ThemeIcons.iconForAppId(modelData?.appId?.toLowerCase()); + } + smooth: true + asynchronous: true + opacity: groupedTaskbarItem.isFocused ? Style.opacityFull : unfocusedIconsOpacity + layer.enabled: root.colorizeIcons && !groupedTaskbarItem.isFocused + + Rectangle { + id: groupedFocusIndicator + visible: groupedTaskbarItem.isFocused || windowHoverHandler.hovered + anchors.bottomMargin: -2 + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + width: Style.toOdd(root.iconSize * 0.25) + height: 4 + color: groupedTaskbarItem.isFocused ? Color.mPrimary : Color.mHover + radius: Math.min(Style.radiusXXS, width / 2) + } + + layer.effect: ShaderEffect { + property color targetColor: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mSurfaceVariant + property real colorizeMode: 0 + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton + preventStealing: true + + onPressed: mouse => { + if (mouse.button === Qt.LeftButton && modelData) { + CompositorService.focusWindow(modelData); + } + } + + onReleased: mouse => { + if (mouse.button === Qt.RightButton && modelData) { + mouse.accepted = true; + TooltipService.hide(); + root.selectedWindowId = modelData.id || modelData.address || ""; + root.selectedAppId = modelData.appId; + openGroupedContextMenu(groupedTaskbarItem); + } + } + onEntered: { + if (!modelData) + return; + TooltipService.show(groupedTaskbarItem, modelData.title || modelData.appId || "Unknown app.", BarService.getTooltipDirection(root.screenName)); + } + onExited: { + TooltipService.hide(); + } + } + } + } + } + + Item { + id: groupedWorkspaceNumberContainer + + visible: root.labelMode !== "none" && root.showBadge && (!root.showLabelsOnlyWhenOccupied || groupedContainer.hasWindows || groupedContainer.workspaceModel.isFocused) + + anchors { + left: parent.left + top: parent.top + leftMargin: -Style.fontSizeXS * 0.55 + topMargin: -Style.fontSizeXS * 0.25 + } + + width: Math.max(groupedWorkspaceNumber.implicitWidth + Style.margin2XS, Style.fontSizeXXS * 2) + height: Math.max(groupedWorkspaceNumber.implicitHeight + Style.marginXS, Style.fontSizeXXS * 2) + + Rectangle { + id: groupedWorkspaceNumberBackground + + anchors.fill: parent + radius: Math.min(Style.radiusL, width / 2) + + color: { + if (groupedContainer.workspaceModel.isFocused) + return Color.resolveColorKey(root.focusedColor); + if (groupedContainer.workspaceModel.isUrgent) + return Color.mError; + if (groupedContainer.hasWindows) + return Color.resolveColorKey(root.occupiedColor); + + return Color.resolveColorKey(root.emptyColor); + } + + scale: groupedContainer.workspaceModel.isActive ? 1.0 : 0.8 + + Behavior on scale { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutBack + } + } + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutCubic + } + } + } + + // Burst effect overlay for focused workspace number + Rectangle { + id: groupedWorkspaceNumberBurst + anchors.centerIn: groupedWorkspaceNumberContainer + width: groupedWorkspaceNumberContainer.width + 12 * root.masterProgress + height: groupedWorkspaceNumberContainer.height + 12 * root.masterProgress + radius: width / 2 + color: "transparent" + border.color: root.effectColor + border.width: Math.max(1, Math.round((2 + 4 * (1.0 - root.masterProgress)))) + opacity: root.effectsActive && groupedContainer.workspaceModel.isFocused ? (1.0 - root.masterProgress) * 0.7 : 0 + visible: root.effectsActive && groupedContainer.workspaceModel.isFocused + z: 1 + } + + NText { + id: groupedWorkspaceNumber + + anchors.centerIn: parent + + text: { + if (groupedContainer.workspaceModel.name && groupedContainer.workspaceModel.name.length > 0) { + if (root.labelMode === "name") { + return groupedContainer.workspaceModel.name.substring(0, root.characterCount); + } + if (root.labelMode === "index+name") { + return (groupedContainer.workspaceModel.idx.toString() + groupedContainer.workspaceModel.name.substring(0, 1)); + } + } + return groupedContainer.workspaceModel.idx.toString(); + } + + family: Settings.data.ui.fontFixed + font { + pointSize: barFontSize * 0.75 + weight: fontWeight + capitalization: Font.AllUppercase + } + applyUiScale: false + + color: { + if (groupedContainer.workspaceModel.isFocused) + return Color.resolveOnColorKey(root.focusedColor); + if (groupedContainer.workspaceModel.isUrgent) + return Color.mOnError; + if (groupedContainer.hasWindows) + return Color.resolveOnColorKey(root.occupiedColor); + + return Color.resolveOnColorKey(root.emptyColor); + } + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.InOutCubic + } + } + } + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.InOutCubic + } + } + } + } + } + + Flow { + id: groupedGrid + visible: appVisible + scale: visible ? 1.0 : 0.8 + Behavior on scale { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + easing.overshoot: 1.2 + } + } + + x: root.isVertical ? Style.pixelAlignCenter(parent.width, width) : Math.round(horizontalPadding * root.hasLabel) + y: root.isVertical ? Math.round(horizontalPadding * 0.4 * root.hasLabel) : Style.pixelAlignCenter(parent.height, height) + + spacing: Style.marginS + flow: root.isVertical ? Flow.TopToBottom : Flow.LeftToRight + + Repeater { + model: appVisible ? localWorkspaces : null + delegate: groupedWorkspaceDelegate + } + } + + function openGroupedContextMenu(item) { + // Anchor to root (stable) but center horizontally on the clicked item + PanelService.showContextMenu(contextMenu, root, screen, item); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Cards/AudioCard.qml b/arch/.config/quickshell/noctalia-shell/Modules/Cards/AudioCard.qml new file mode 100644 index 0000000..ccc1876 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Cards/AudioCard.qml @@ -0,0 +1,276 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Media +import qs.Widgets + +// Audio controls card: output and input volume controls +NBox { + id: root + + property real localOutputVolume: 0 + property bool localOutputVolumeChanging: false + property int lastSinkId: -1 + + property real localInputVolume: 0 + property bool localInputVolumeChanging: false + property int lastSourceId: -1 + + readonly property bool outputVolumeGuard: outputVolumeSlider.sliderActive || localOutputVolumeChanging + readonly property bool inputVolumeGuard: inputVolumeSlider.sliderActive || localInputVolumeChanging + + Component.onCompleted: { + var vol = AudioService.volume; + localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0; + var inputVol = AudioService.inputVolume; + localInputVolume = (inputVol !== undefined && !isNaN(inputVol)) ? inputVol : 0; + if (AudioService.sink) { + lastSinkId = AudioService.sink.id; + } + if (AudioService.source) { + lastSourceId = AudioService.source.id; + } + } + + // Reset local volume when device changes - use current device's volume + Connections { + target: AudioService + function onSinkChanged() { + if (AudioService.sink) { + const newSinkId = AudioService.sink.id; + if (newSinkId !== lastSinkId) { + lastSinkId = newSinkId; + // Immediately set local volume to current device's volume + var vol = AudioService.volume; + localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0; + } + } else { + lastSinkId = -1; + localOutputVolume = 0; + } + } + } + + Connections { + target: AudioService + function onSourceChanged() { + if (AudioService.source) { + const newSourceId = AudioService.source.id; + if (newSourceId !== lastSourceId) { + lastSourceId = newSourceId; + // Immediately set local volume to current device's volume + var vol = AudioService.inputVolume; + localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0; + } + } else { + lastSourceId = -1; + localInputVolume = 0; + } + } + } + + // Timer to debounce volume changes + // Only sync if the device hasn't changed (check by comparing IDs) + Timer { + interval: 100 + running: true + repeat: true + onTriggered: { + // Only sync if sink hasn't changed + if (AudioService.sink && AudioService.sink.id === lastSinkId) { + if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) { + AudioService.setVolume(localOutputVolume); + } + } + // Only sync if source hasn't changed + if (AudioService.source && AudioService.source.id === lastSourceId) { + if (Math.abs(localInputVolume - AudioService.inputVolume) >= 0.01) { + AudioService.setInputVolume(localInputVolume); + } + } + } + } + + Connections { + target: AudioService + function onVolumeChanged() { + if (!outputVolumeGuard && !AudioService.isSettingOutputVolume && AudioService.sink && AudioService.sink.id === lastSinkId) { + var vol = AudioService.volume; + localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0; + } + } + } + + Connections { + target: AudioService + function onInputVolumeChanged() { + if (!inputVolumeGuard && !AudioService.isSettingInputVolume && AudioService.source && AudioService.source.id === lastSourceId) { + var vol = AudioService.inputVolume; + localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0; + } + } + } + + Connections { + target: outputVolumeSlider + function onSliderActiveChanged() { + if (!outputVolumeSlider.sliderActive && AudioService.sink && AudioService.sink.id === lastSinkId) { + var vol = AudioService.volume; + localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0; + } + } + } + + Connections { + target: inputVolumeSlider + function onSliderActiveChanged() { + if (!inputVolumeSlider.sliderActive && AudioService.source && AudioService.source.id === lastSourceId) { + var vol = AudioService.inputVolume; + localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0; + } + } + } + + RowLayout { + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + + // Output Volume Section + ColumnLayout { + spacing: Style.marginXXS + Layout.fillWidth: true + Layout.preferredWidth: 0 + opacity: AudioService.sink ? 1.0 : 0.5 + enabled: AudioService.sink + + // Output Volume Header + RowLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NIconButton { + icon: AudioService.muted ? "volume-off" : "volume-high" + baseSize: Style.baseWidgetSize * 0.5 + colorFg: AudioService.muted ? Color.mError : Color.mOnSurface + colorBg: "transparent" + colorBgHover: Color.mHover + colorFgHover: Color.mOnHover + onClicked: AudioService.setOutputMuted(!AudioService.muted) + } + + NText { + text: AudioService.sink ? AudioService.sink.description : "No output device" + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + elide: Text.ElideRight + Layout.fillWidth: true + Layout.preferredWidth: 0 + } + } + + // Output Volume Slider + NSlider { + id: outputVolumeSlider + Layout.fillWidth: true + from: 0 + to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0 + value: localOutputVolume + stepSize: 0.01 + heightRatio: 0.5 + onMoved: localOutputVolume = value + onPressedChanged: localOutputVolumeChanging = pressed + tooltipText: `${Math.round((outputVolumeGuard ? localOutputVolume : AudioService.volume) * 100)}%` + tooltipDirection: "bottom" + + // MouseArea to handle wheel events when hovering over the slider + MouseArea { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + propagateComposedEvents: true + + onWheel: wheel => { + if (outputVolumeSlider.enabled && AudioService.sink) { + const delta = wheel.angleDelta.y || wheel.angleDelta.x; + const step = Settings.data.audio.volumeStep / 100.0; // Convert percentage to 0-1 range + const increment = delta > 0 ? step : -step; + const maxVolume = Settings.data.audio.volumeOverdrive ? 1.5 : 1.0; + const newValue = Math.max(0, Math.min(maxVolume, localOutputVolume + increment)); + localOutputVolume = newValue; + } + } + } + } + } + + // Input Volume Section + ColumnLayout { + spacing: Style.marginXXS + Layout.fillWidth: true + Layout.preferredWidth: 0 + opacity: AudioService.source ? 1.0 : 0.5 + enabled: AudioService.source + + // Input Volume Header + RowLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NIconButton { + icon: AudioService.inputMuted ? "microphone-off" : "microphone" + baseSize: Style.baseWidgetSize * 0.5 + colorFg: AudioService.inputMuted ? Color.mError : Color.mOnSurface + colorBg: "transparent" + colorBgHover: Color.mHover + colorFgHover: Color.mOnHover + onClicked: AudioService.setInputMuted(!AudioService.inputMuted) + } + + NText { + text: AudioService.source ? AudioService.source.description : "No input device" + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + elide: Text.ElideRight + Layout.fillWidth: true + Layout.preferredWidth: 0 + } + } + + // Input Volume Slider + NSlider { + id: inputVolumeSlider + Layout.fillWidth: true + from: 0 + to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0 + value: localInputVolume + stepSize: 0.01 + heightRatio: 0.5 + onMoved: localInputVolume = value + onPressedChanged: localInputVolumeChanging = pressed + tooltipText: `${Math.round((inputVolumeGuard ? localInputVolume : AudioService.inputVolume) * 100)}%` + tooltipDirection: "bottom" + + // MouseArea to handle wheel events when hovering over the slider + MouseArea { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + propagateComposedEvents: true + + onWheel: wheel => { + if (inputVolumeSlider.enabled && AudioService.source) { + const delta = wheel.angleDelta.y || wheel.angleDelta.x; + const step = Settings.data.audio.volumeStep / 100.0; // Convert percentage to 0-1 range + const increment = delta > 0 ? step : -step; + const maxVolume = Settings.data.audio.volumeOverdrive ? 1.5 : 1.0; + const newValue = Math.max(0, Math.min(maxVolume, localInputVolume + increment)); + localInputVolume = newValue; + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Cards/BrightnessCard.qml b/arch/.config/quickshell/noctalia-shell/Modules/Cards/BrightnessCard.qml new file mode 100644 index 0000000..3254db6 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Cards/BrightnessCard.qml @@ -0,0 +1,155 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Hardware +import qs.Widgets + +// Brightness control card for the ControlCenter +NBox { + id: root + + Layout.fillWidth: true + clip: true + + // Get the primary monitor (first screen) + readonly property var brightnessMonitor: { + if (Quickshell.screens.length > 0) { + return BrightnessService.getMonitorForScreen(Quickshell.screens[0]); + } + return null; + } + + property real localBrightness: 0 + property bool localBrightnessChanging: false + + Component.onCompleted: { + if (brightnessMonitor) { + localBrightness = brightnessMonitor.brightness || 0; + } + } + + // Update local brightness when monitor changes + Connections { + target: BrightnessService + function onMonitorBrightnessChanged(monitor, newBrightness) { + if (monitor === brightnessMonitor && !localBrightnessChanging) { + localBrightness = newBrightness; + } + } + } + + // Update local brightness when monitor's brightness property changes + Connections { + target: brightnessMonitor + ignoreUnknownSignals: true + function onBrightnessUpdated() { + if (brightnessMonitor && !localBrightnessChanging) { + localBrightness = brightnessMonitor.brightness || 0; + } + } + } + + // Timer to debounce brightness changes - only runs when user is changing slider + Timer { + id: debounceTimer + interval: 100 + running: false + repeat: false + onTriggered: { + if (brightnessMonitor && Math.abs(localBrightness - brightnessMonitor.brightness) >= 0.01) { + brightnessMonitor.setBrightness(localBrightness); + } + } + } + + RowLayout { + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + + // Brightness Section + ColumnLayout { + spacing: Style.marginXXS + Layout.fillWidth: true + Layout.preferredWidth: 0 + opacity: brightnessMonitor && brightnessMonitor.brightnessControlAvailable ? 1.0 : 0.5 + enabled: brightnessMonitor && brightnessMonitor.brightnessControlAvailable + + // Brightness Header + RowLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NIconButton { + icon: { + if (!brightnessMonitor) + return "brightness-low"; + const brightness = brightnessMonitor.brightness || 0; + if (brightness <= 0.001) + return "sun-off"; + return brightness <= 0.5 ? "brightness-low" : "brightness-high"; + } + baseSize: Style.baseWidgetSize * 0.5 + colorFg: Color.mOnSurface + colorBg: "transparent" + colorBgHover: Color.mHover + colorFgHover: Color.mOnHover + } + + NText { + text: brightnessMonitor ? I18n.tr("common.brightness") : "No display" + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + elide: Text.ElideRight + Layout.fillWidth: true + Layout.preferredWidth: 0 + } + + NText { + text: brightnessMonitor ? Math.round(localBrightness * 100) + "%" : "N/A" + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + opacity: brightnessMonitor && brightnessMonitor.brightnessControlAvailable ? 1.0 : 0.5 + } + } + + // Brightness Slider + NSlider { + id: brightnessSlider + Layout.fillWidth: true + from: 0 + to: 1 + value: localBrightness + stepSize: 0.01 + heightRatio: 0.5 + onMoved: { + localBrightness = value; + debounceTimer.restart(); + } + onPressedChanged: localBrightnessChanging = pressed + tooltipText: `${Math.round(localBrightness * 100)}%` + tooltipDirection: "bottom" + + // MouseArea to handle wheel events when hovering over the slider + MouseArea { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + propagateComposedEvents: true + + onWheel: wheel => { + if (brightnessSlider.enabled && brightnessMonitor && brightnessMonitor.brightnessControlAvailable) { + const delta = wheel.angleDelta.y || wheel.angleDelta.x; + const step = Settings.data.brightness.brightnessStep / 100.0; // Convert percentage to 0-1 range + const increment = delta > 0 ? step : -step; + const newValue = Math.max(0, Math.min(1, localBrightness + increment)); + localBrightness = newValue; + debounceTimer.restart(); + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Cards/CalendarHeaderCard.qml b/arch/.config/quickshell/noctalia-shell/Modules/Cards/CalendarHeaderCard.qml new file mode 100644 index 0000000..23c5fbe --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Cards/CalendarHeaderCard.qml @@ -0,0 +1,134 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Location +import qs.Widgets + +// Calendar header with date, month/year, location, and clock +Rectangle { + id: root + Layout.fillWidth: true + Layout.minimumHeight: (60 * Style.uiScaleRatio) + Style.margin2M + Layout.preferredHeight: (60 * Style.uiScaleRatio) + Style.margin2M + implicitHeight: (60 * Style.uiScaleRatio) + Style.margin2M + radius: Style.radiusL + color: Color.mPrimary + + // Internal state + readonly property var now: Time.now + readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null) + + // Expose current month/year for potential synchronization with CalendarMonthCard + readonly property int currentMonth: now.getMonth() + readonly property int currentYear: now.getFullYear() + + ColumnLayout { + id: capsuleColumn + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.topMargin: Style.marginM + anchors.bottomMargin: Style.marginM + anchors.rightMargin: clockLoader.width + Style.margin2XL + anchors.leftMargin: Style.marginXL + spacing: 0 + + // Combined layout for date, month year, location and time-zone + RowLayout { + Layout.fillWidth: true + height: 60 * Style.uiScaleRatio + clip: true + spacing: Style.marginS + + // Today day number + NText { + Layout.preferredWidth: implicitWidth + elide: Text.ElideNone + clip: true + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + text: root.now.getDate() + pointSize: Style.fontSizeXXXL * 1.5 + font.weight: Style.fontWeightBold + color: Color.mOnPrimary + } + + // Month, year, location + ColumnLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + Layout.bottomMargin: Style.marginXXS + Layout.topMargin: -Style.marginXXS + spacing: -Style.marginXS + + RowLayout { + spacing: Style.marginS + + NText { + text: I18n.locale.monthName(root.currentMonth, Locale.LongFormat).toUpperCase() + pointSize: Style.fontSizeXL * 1.1 + font.weight: Style.fontWeightBold + color: Color.mOnPrimary + Layout.alignment: Qt.AlignBaseline + elide: Text.ElideRight + } + + NText { + text: `${root.currentYear}` + pointSize: Style.fontSizeM + font.weight: Style.fontWeightBold + color: Qt.alpha(Color.mOnPrimary, 0.7) + Layout.alignment: Qt.AlignBaseline + } + } + + RowLayout { + spacing: 0 + + NText { + text: { + if (!Settings.data.location.weatherEnabled) + return ""; + if (!LocationService.locationConfigured) + return I18n.tr("common.weather-no-location"); + if (!root.weatherReady) + return I18n.tr("common.weather-loading"); + if (Settings.data.location.hideWeatherCityName) + return ""; + const chunks = Settings.data.location.name.split(","); + return chunks[0]; + } + pointSize: Style.fontSizeM + color: Color.mOnPrimary + Layout.maximumWidth: 150 + elide: Text.ElideRight + } + + NText { + text: root.weatherReady && !Settings.data.location.hideWeatherTimezone ? `${Settings.data.location.hideWeatherCityName ? "" : " "}(${LocationService.data.weather.timezone_abbreviation})` : "" + pointSize: Style.fontSizeXS + color: Qt.alpha(Color.mOnPrimary, 0.7) + } + } + } + + // Spacer + Item { + Layout.fillWidth: true + } + } + } + + // Analog/Digital clock + NClock { + id: clockLoader + anchors.right: parent.right + anchors.rightMargin: Style.marginXL + anchors.verticalCenter: parent.verticalCenter + clockStyle: Settings.data.location.analogClockInCalendar ? "analog" : "digital" + progressColor: Color.mOnPrimary + Layout.alignment: Qt.AlignVCenter + now: root.now + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Cards/CalendarMonthCard.qml b/arch/.config/quickshell/noctalia-shell/Modules/Cards/CalendarMonthCard.qml new file mode 100644 index 0000000..6008a28 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Cards/CalendarMonthCard.qml @@ -0,0 +1,429 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Location +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +// Calendar month grid with navigation +NBox { + id: root + Layout.fillWidth: true + implicitHeight: calendarContent.implicitHeight + Style.margin2M + + // Internal state - independent from header + readonly property var now: Time.now + property int calendarMonth: now.getMonth() + property int calendarYear: now.getFullYear() + readonly property int firstDayOfWeek: Settings.data.location.firstDayOfWeek === -1 ? I18n.locale.firstDayOfWeek : Settings.data.location.firstDayOfWeek + + // Helper function to calculate ISO week number + function getISOWeekNumber(date) { + const target = new Date(date.valueOf()); + const dayNr = (date.getDay() + 6) % 7; + target.setDate(target.getDate() - dayNr + 3); + const firstThursday = new Date(target.getFullYear(), 0, 4); + const diff = target - firstThursday; + const oneWeek = 1000 * 60 * 60 * 24 * 7; + const weekNumber = 1 + Math.round(diff / oneWeek); + return weekNumber; + } + + // Helper function to check if an event is all-day + function isAllDayEvent(event) { + const duration = event.end - event.start; + const startDate = new Date(event.start * 1000); + const isAtMidnight = startDate.getHours() === 0 && startDate.getMinutes() === 0; + return duration === 86400 && isAtMidnight; + } + + // Navigation functions + function navigateToPreviousMonth() { + let newDate = new Date(root.calendarYear, root.calendarMonth - 1, 1); + root.calendarYear = newDate.getFullYear(); + root.calendarMonth = newDate.getMonth(); + const now = new Date(); + const monthStart = new Date(root.calendarYear, root.calendarMonth, 1); + const monthEnd = new Date(root.calendarYear, root.calendarMonth + 1, 0); + const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000))); + const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000))); + CalendarService.loadEvents(daysAhead + 30, daysBehind + 30); + } + + function navigateToNextMonth() { + let newDate = new Date(root.calendarYear, root.calendarMonth + 1, 1); + root.calendarYear = newDate.getFullYear(); + root.calendarMonth = newDate.getMonth(); + const now = new Date(); + const monthStart = new Date(root.calendarYear, root.calendarMonth, 1); + const monthEnd = new Date(root.calendarYear, root.calendarMonth + 1, 0); + const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000))); + const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000))); + CalendarService.loadEvents(daysAhead + 30, daysBehind + 30); + } + + // Wheel handler for month navigation + WheelHandler { + id: wheelHandler + target: root + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onWheel: function (event) { + if (event.angleDelta.y > 0) { + // Scroll up - go to previous month + root.navigateToPreviousMonth(); + event.accepted = true; + } else if (event.angleDelta.y < 0) { + // Scroll down - go to next month + root.navigateToNextMonth(); + event.accepted = true; + } + } + } + + ColumnLayout { + id: calendarContent + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginS + + // Navigation row + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + Item { + Layout.preferredWidth: Style.marginS + } + + NText { + text: I18n.locale.monthName(root.calendarMonth, Locale.LongFormat).toUpperCase() + " " + root.calendarYear + pointSize: Style.fontSizeM + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NDivider { + Layout.fillWidth: true + } + + NIconButton { + icon: "chevron-left" + onClicked: root.navigateToPreviousMonth() + } + + NIconButton { + icon: "calendar" + onClicked: { + root.calendarMonth = root.now.getMonth(); + root.calendarYear = root.now.getFullYear(); + CalendarService.loadEvents(); + } + } + + NIconButton { + icon: "chevron-right" + onClicked: root.navigateToNextMonth() + } + } + + // Day names header + RowLayout { + Layout.fillWidth: true + spacing: 0 + + Item { + visible: Settings.data.location.showWeekNumberInCalendar + Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0 + } + + GridLayout { + Layout.fillWidth: true + columns: 7 + rows: 1 + columnSpacing: 0 + rowSpacing: 0 + + Repeater { + model: 7 + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.fontSizeS * 2 + + NText { + anchors.centerIn: parent + text: { + let dayIndex = (root.firstDayOfWeek + index) % 7; + const dayName = I18n.locale.dayName(dayIndex, Locale.ShortFormat); + return dayName.substring(0, 2).toUpperCase(); + } + color: Color.mPrimary + pointSize: Style.fontSizeS + font.weight: Style.fontWeightBold + horizontalAlignment: Text.AlignHCenter + } + } + } + } + } + + // Calendar grid with week numbers + RowLayout { + Layout.fillWidth: true + spacing: 0 + + // Helper functions + function hasEventsOnDate(year, month, day) { + if (!CalendarService.available || CalendarService.events.length === 0) + return false; + const targetDate = new Date(year, month, day); + const targetStart = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000; + const targetEnd = targetStart + 86400; + return CalendarService.events.some(event => { + return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd); + }); + } + + function getEventsForDate(year, month, day) { + if (!CalendarService.available || CalendarService.events.length === 0) + return []; + const targetDate = new Date(year, month, day); + const targetStart = Math.floor(new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000); + const targetEnd = targetStart + 86400; + return CalendarService.events.filter(event => { + return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd); + }); + } + + function isMultiDayEvent(event) { + if (root.isAllDayEvent(event)) { + return false; + } + const startDate = new Date(event.start * 1000); + const endDate = new Date(event.end * 1000); + const startDateOnly = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate()); + const endDateOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate()); + return startDateOnly.getTime() !== endDateOnly.getTime(); + } + + function getEventColor(event, isToday) { + if (isMultiDayEvent(event)) { + return isToday ? Color.mOnSecondary : Color.mTertiary; + } else if (root.isAllDayEvent(event)) { + return isToday ? Color.mOnSecondary : Color.mSecondary; + } else { + return isToday ? Color.mOnSecondary : Color.mPrimary; + } + } + + // Week numbers column + ColumnLayout { + visible: Settings.data.location.showWeekNumberInCalendar + Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0 + Layout.alignment: Qt.AlignTop + spacing: Style.marginXXS + + property var weekNumbers: { + if (!grid.daysModel || grid.daysModel.length === 0) + return []; + const weeks = []; + const numWeeks = Math.ceil(grid.daysModel.length / 7); + for (var i = 0; i < numWeeks; i++) { + const dayIndex = i * 7; + if (dayIndex < grid.daysModel.length) { + const weekDay = grid.daysModel[dayIndex]; + const date = new Date(weekDay.year, weekDay.month, weekDay.day); + let thursday = new Date(date); + if (root.firstDayOfWeek === 0) { + thursday.setDate(date.getDate() + 4); + } else if (root.firstDayOfWeek === 1) { + thursday.setDate(date.getDate() + 3); + } else { + let daysToThursday = (4 - root.firstDayOfWeek + 7) % 7; + thursday.setDate(date.getDate() + daysToThursday); + } + weeks.push(root.getISOWeekNumber(thursday)); + } + } + return weeks; + } + + Repeater { + model: parent.weekNumbers + Item { + Layout.preferredWidth: Style.baseWidgetSize * 0.7 + Layout.preferredHeight: Style.baseWidgetSize * 0.9 + + NText { + anchors.centerIn: parent + color: Qt.alpha(Color.mPrimary, 0.7) + pointSize: Style.fontSizeXXS + text: modelData + } + } + } + } + + // Calendar grid + GridLayout { + id: grid + Layout.fillWidth: true + columns: 7 + columnSpacing: Style.marginXXS + rowSpacing: Style.marginXXS + + property int month: root.calendarMonth + property int year: root.calendarYear + + property var daysModel: { + const firstOfMonth = new Date(year, month, 1); + const lastOfMonth = new Date(year, month + 1, 0); + const daysInMonth = lastOfMonth.getDate(); + const firstDayOfWeek = root.firstDayOfWeek; + const firstOfMonthDayOfWeek = firstOfMonth.getDay(); + let daysBefore = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7; + const lastOfMonthDayOfWeek = lastOfMonth.getDay(); + const daysAfter = (firstDayOfWeek - lastOfMonthDayOfWeek - 1 + 7) % 7; + const days = []; + const today = new Date(); + + // Previous month days + const prevMonth = new Date(year, month, 0); + const prevMonthDays = prevMonth.getDate(); + for (var i = daysBefore - 1; i >= 0; i--) { + const day = prevMonthDays - i; + days.push({ + "day": day, + "month": month - 1, + "year": month === 0 ? year - 1 : year, + "today": false, + "currentMonth": false + }); + } + + // Current month days + for (var day = 1; day <= daysInMonth; day++) { + const date = new Date(year, month, day); + const isToday = date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate(); + days.push({ + "day": day, + "month": month, + "year": year, + "today": isToday, + "currentMonth": true + }); + } + + // Next month days + for (var i = 1; i <= daysAfter; i++) { + days.push({ + "day": i, + "month": month + 1, + "year": month === 11 ? year + 1 : year, + "today": false, + "currentMonth": false + }); + } + + return days; + } + + Repeater { + model: grid.daysModel + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.baseWidgetSize * 0.9 + + Rectangle { + width: Style.baseWidgetSize * 0.9 + height: Style.baseWidgetSize * 0.9 + anchors.centerIn: parent + radius: Style.radiusM + color: modelData.today ? Color.mSecondary : "transparent" + + NText { + anchors.centerIn: parent + text: modelData.day + color: { + if (modelData.today) + return Color.mOnSecondary; + if (modelData.currentMonth) + return Color.mOnSurface; + return Color.mOnSurfaceVariant; + } + opacity: modelData.currentMonth ? 1.0 : 0.4 + pointSize: Style.fontSizeM + font.weight: modelData.today ? Style.fontWeightBold : Style.fontWeightMedium + } + + // Event indicator dots + Row { + visible: Settings.data.location.showCalendarEvents && parent.parent.parent.parent.hasEventsOnDate(modelData.year, modelData.month, modelData.day) + spacing: 2 + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: Style.marginXS + + Repeater { + model: parent.parent.parent.parent.parent.getEventsForDate(modelData.year, modelData.month, modelData.day) + + Rectangle { + width: 4 + height: width + radius: Style.radiusXXS + color: parent.parent.parent.parent.parent.getEventColor(modelData, modelData.today) + } + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + enabled: Settings.data.location.showCalendarEvents + + onEntered: { + const events = parent.parent.parent.parent.getEventsForDate(modelData.year, modelData.month, modelData.day); + if (events.length > 0) { + const summaries = events.map(event => { + if (root.isAllDayEvent(event)) { + return event.summary; + } else { + const timeFormat = Settings.data.location.use12hourFormat ? "hh:mm AP" : "HH:mm"; + const start = new Date(event.start * 1000); + const startFormatted = I18n.locale.toString(start, timeFormat); + const end = new Date(event.end * 1000); + const endFormatted = I18n.locale.toString(end, timeFormat); + return `${startFormatted}-${endFormatted} ${event.summary}`; + } + }).join('\n'); + TooltipService.show(parent, summaries, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed); + } + } + + onClicked: { + const dateWithSlashes = `${(modelData.month + 1).toString().padStart(2, '0')}/${modelData.day.toString().padStart(2, '0')}/${modelData.year.toString().substring(2)}`; + if (ProgramCheckerService.gnomeCalendarAvailable) { + Quickshell.execDetached(["gnome-calendar", "--date", dateWithSlashes]); + } + } + + onExited: { + TooltipService.hide(); + } + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Cards/MediaCard.qml b/arch/.config/quickshell/noctalia-shell/Modules/Cards/MediaCard.qml new file mode 100644 index 0000000..ab604be --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Cards/MediaCard.qml @@ -0,0 +1,456 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Media +import qs.Services.UI +import qs.Widgets +import qs.Widgets.AudioSpectrum + +NBox { + id: root + + // Track whether we have an active media player + readonly property bool hasActivePlayer: MediaService.currentPlayer && MediaService.canPlay + + // SpectrumService registration for visualizer + readonly property bool needsSpectrum: Settings.data.audio.visualizerType !== "" && Settings.data.audio.visualizerType !== "none" + + onNeedsSpectrumChanged: { + if (root.needsSpectrum) { + SpectrumService.registerComponent("mediacard"); + } else { + SpectrumService.unregisterComponent("mediacard"); + } + } + + Component.onCompleted: { + if (root.needsSpectrum) { + SpectrumService.registerComponent("mediacard"); + } + updateCachedWallpaper(); + } + + Component.onDestruction: { + SpectrumService.unregisterComponent("mediacard"); + } + + property string wallpaper: WallpaperService.getWallpaper(screen.name) + property string cachedWallpaper: "" + + // External state management + Connections { + target: WallpaperService + function onWallpaperChanged(screenName, path) { + if (screenName === screen.name) { + wallpaper = path; + updateCachedWallpaper(); + } + } + } + + function updateCachedWallpaper() { + // Handle solid color mode - no wallpaper to cache + if (Settings.data.wallpaper.useSolidColor || WallpaperService.isSolidColorPath(wallpaper)) { + cachedWallpaper = ""; + return; + } + + if (!wallpaper) { + cachedWallpaper = ""; + return; + } + + if (!ImageCacheService.initialized) { + cachedWallpaper = wallpaper; + return; + } + + ImageCacheService.getThumbnail(wallpaper, function (cachedPath, success) { + if (!root) + return; + cachedWallpaper = success ? cachedPath : wallpaper; + }); + } + + // Wrapper - rounded rect clipper + Item { + anchors.fill: parent + layer.enabled: true + layer.smooth: true + layer.effect: MultiEffect { + maskEnabled: true + maskThresholdMin: 0.95 + maskSpreadAtMin: 0.15 + maskSource: ShaderEffectSource { + sourceItem: Rectangle { + width: root.width + height: root.height + radius: Style.radiusM + color: "white" + } + } + } + + // Solid color background (always present as base layer) + Rectangle { + anchors.fill: parent + color: Settings.data.wallpaper.useSolidColor ? Settings.data.wallpaper.solidColor : Color.mSurface + } + + // Background image that covers everything + Image { + id: bgImage + readonly property int dim: Math.round(256 * Style.uiScaleRatio) + anchors.fill: parent + visible: source.toString() !== "" + source: MediaService.trackArtUrl || (Settings.data.wallpaper.enabled && !Settings.data.wallpaper.useSolidColor ? root.cachedWallpaper : "") + sourceSize: Qt.size(dim, dim) + fillMode: Image.PreserveAspectCrop + layer.enabled: true + layer.smooth: true + layer.effect: MultiEffect { + blurEnabled: true + blurMax: 8 + blur: 0.33 + } + } + + // Dark overlay for readability + Rectangle { + anchors.fill: parent + color: Color.mSurface + opacity: 0.65 + radius: Style.radiusM + } + + // Border + Rectangle { + anchors.fill: parent + color: "transparent" + border.color: Style.boxBorderColor + border.width: Style.borderS + radius: Style.radiusM + } + + // Background visualizer on top of the artwork + Loader { + anchors.fill: parent + active: Settings.data.audio.visualizerType !== "" && Settings.data.audio.visualizerType !== "none" + + sourceComponent: { + switch (Settings.data.audio.visualizerType) { + case "linear": + return linearComponent; + case "mirrored": + return mirroredComponent; + case "wave": + return waveComponent; + default: + return null; + } + } + + Component { + id: linearComponent + NLinearSpectrum { + anchors.fill: parent + values: SpectrumService.values + fillColor: Color.mPrimary + opacity: 0.8 + mirrored: Settings.data.audio.spectrumMirrored + } + } + + Component { + id: mirroredComponent + NMirroredSpectrum { + anchors.fill: parent + values: SpectrumService.values + fillColor: Color.mPrimary + opacity: 0.8 + mirrored: Settings.data.audio.spectrumMirrored + } + } + + Component { + id: waveComponent + NWaveSpectrum { + anchors.fill: parent + values: SpectrumService.values + fillColor: Color.mPrimary + opacity: 0.8 + mirrored: Settings.data.audio.spectrumMirrored + } + } + } + } + + // Player selector + Rectangle { + id: playerSelectorButton + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: Style.marginXS + anchors.leftMargin: Style.marginM + anchors.rightMargin: Style.marginM + height: Style.baseWidgetSize + visible: MediaService.getAvailablePlayers().length > 1 + radius: Style.radiusM + color: "transparent" + + property var currentPlayer: MediaService.getAvailablePlayers()[MediaService.selectedPlayerIndex] + + RowLayout { + anchors.fill: parent + spacing: Style.marginS + + NIcon { + icon: "caret-down" + pointSize: Style.fontSizeXXL + color: Color.mOnSurfaceVariant + } + + NText { + text: playerSelectorButton.currentPlayer ? playerSelectorButton.currentPlayer.identity : "" + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + Layout.fillWidth: true + } + } + + MouseArea { + id: playerSelectorMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + onClicked: { + var menuItems = []; + var players = MediaService.getAvailablePlayers(); + for (var i = 0; i < players.length; i++) { + menuItems.push({ + "label": players[i].identity, + "action": i.toString(), + "icon": "disc", + "enabled": true, + "visible": true + }); + } + playerContextMenu.model = menuItems; + playerContextMenu.openAtItem(playerSelectorButton, playerSelectorButton.width - playerContextMenu.width, playerSelectorButton.height); + } + } + + NContextMenu { + id: playerContextMenu + parent: root + width: 200 + verticalPolicy: ScrollBar.AlwaysOff + + onTriggered: function (action) { + var index = parseInt(action); + if (!isNaN(index)) { + MediaService.switchToPlayer(index); + } + } + } + } + + // Content container that adjusts for player selector + Item { + anchors.fill: parent + anchors.topMargin: playerSelectorButton.visible ? (playerSelectorButton.height + Style.marginXS + Style.marginM) : Style.marginM + anchors.leftMargin: Style.marginM + anchors.rightMargin: Style.marginM + anchors.bottomMargin: Style.marginM + + // No media player detected - centered disc icon + NIcon { + anchors.centerIn: parent + visible: !root.hasActivePlayer && SpectrumService.isIdle + icon: "disc" + pointSize: Style.fontSizeXXXL * 3 + color: Color.mOnSurfaceVariant + opacity: 1.0 + } + + // MediaPlayer Main Content - use Loader for performance + Loader { + id: mainLoader + anchors.fill: parent + active: root.hasActivePlayer + + sourceComponent: Item { + Layout.fillWidth: true + Layout.fillHeight: true + + // Exceptionaly we put shadow on text and controls to ease readability + NDropShadow { + anchors.fill: main + source: main + autoPaddingEnabled: true + shadowBlur: 1.0 + shadowOpacity: 0.9 + shadowHorizontalOffset: 0 + shadowVerticalOffset: 0 + shadowColor: Settings.data.colorSchemes.darkMode ? "black" : "white" + } + + ColumnLayout { + id: main + anchors.fill: parent + spacing: Style.marginS + + // Spacer to push content down + Item { + Layout.preferredHeight: Style.marginM + } + + // Metadata + ColumnLayout { + id: metadata + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft + spacing: Style.marginXS + + NText { + visible: MediaService.trackTitle !== "" + text: MediaService.trackTitle + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + elide: Text.ElideRight + wrapMode: Text.Wrap + maximumLineCount: 2 + Layout.fillWidth: true + } + + NText { + visible: MediaService.trackArtist !== "" + text: MediaService.trackArtist + color: Color.mSecondary + pointSize: Style.fontSizeS + elide: Text.ElideRight + Layout.fillWidth: true + } + + NText { + visible: MediaService.trackAlbum !== "" + text: MediaService.trackAlbum + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeM + elide: Text.ElideRight + Layout.fillWidth: true + } + } + + // Progress slider + Item { + id: progressWrapper + visible: (MediaService.currentPlayer && MediaService.trackLength > 0) + Layout.fillWidth: true + height: Style.baseWidgetSize * 0.5 + + property real localSeekRatio: -1 + property real lastSentSeekRatio: -1 + property real seekEpsilon: 0.01 + property real progressRatio: { + if (!MediaService.currentPlayer || MediaService.trackLength <= 0) + return 0; + const r = MediaService.currentPosition / MediaService.trackLength; + if (isNaN(r) || !isFinite(r)) + return 0; + return Math.max(0, Math.min(1, r)); + } + property real effectiveRatio: (MediaService.isSeeking && localSeekRatio >= 0) ? Math.max(0, Math.min(1, localSeekRatio)) : progressRatio + + Timer { + id: seekDebounce + interval: 75 + repeat: false + onTriggered: { + if (MediaService.isSeeking && progressWrapper.localSeekRatio >= 0) { + const next = Math.max(0, Math.min(1, progressWrapper.localSeekRatio)); + if (progressWrapper.lastSentSeekRatio < 0 || Math.abs(next - progressWrapper.lastSentSeekRatio) >= progressWrapper.seekEpsilon) { + MediaService.seekByRatio(next); + progressWrapper.lastSentSeekRatio = next; + } + } + } + } + + NSlider { + id: progressSlider + anchors.fill: parent + from: 0 + to: 1 + stepSize: 0 + snapAlways: false + enabled: MediaService.trackLength > 0 && MediaService.canSeek + heightRatio: 0.6 + + onMoved: { + progressWrapper.localSeekRatio = value; + seekDebounce.restart(); + } + onPressedChanged: { + if (pressed) { + MediaService.isSeeking = true; + progressWrapper.localSeekRatio = value; + MediaService.seekByRatio(value); + progressWrapper.lastSentSeekRatio = value; + } else { + seekDebounce.stop(); + MediaService.seekByRatio(value); + MediaService.isSeeking = false; + progressWrapper.localSeekRatio = -1; + progressWrapper.lastSentSeekRatio = -1; + } + } + } + + Binding { + target: progressSlider + property: "value" + value: progressWrapper.progressRatio + when: !MediaService.isSeeking + } + } + + // Spacer to push media controls down + Item { + Layout.preferredHeight: Style.marginL + } + + // Media controls + RowLayout { + spacing: Style.marginS + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + + NIconButton { + icon: "media-prev" + visible: MediaService.canGoPrevious + onClicked: MediaService.canGoPrevious ? MediaService.previous() : {} + } + + NIconButton { + icon: MediaService.isPlaying ? "media-pause" : "media-play" + visible: (MediaService.canPlay || MediaService.canPause) + onClicked: (MediaService.canPlay || MediaService.canPause) ? MediaService.playPause() : {} + } + + NIconButton { + icon: "media-next" + visible: MediaService.canGoNext + onClicked: MediaService.canGoNext ? MediaService.next() : {} + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Cards/ProfileCard.qml b/arch/.config/quickshell/noctalia-shell/Modules/Cards/ProfileCard.qml new file mode 100644 index 0000000..2c49613 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Cards/ProfileCard.qml @@ -0,0 +1,123 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Widgets +import qs.Commons +import qs.Modules.Panels.Settings +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +// Header card with avatar, user and quick actions +NBox { + id: root + + property string uptimeText: "--" + + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.margins: Style.marginM + spacing: Style.marginM + + NImageRounded { + Layout.preferredWidth: Math.round(Style.baseWidgetSize * 1.25 * Style.uiScaleRatio) + Layout.preferredHeight: Math.round(Style.baseWidgetSize * 1.25 * Style.uiScaleRatio) + radius: Layout.preferredWidth / 2 + imagePath: Settings.preprocessPath(Settings.data.general.avatarImage) + fallbackIcon: "person" + borderColor: Color.mPrimary + borderWidth: Style.borderS * 1.5 + } + + ColumnLayout { + Layout.fillWidth: true + Layout.minimumWidth: 0 + spacing: Style.marginXXS + NText { + Layout.fillWidth: true + Layout.minimumWidth: 0 + text: HostService.displayName + font.weight: Style.fontWeightBold + } + NText { + Layout.fillWidth: true + Layout.minimumWidth: 0 + text: I18n.tr("system.uptime", { + "uptime": uptimeText + }) + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + + RowLayout { + spacing: Style.marginS + Layout.fillWidth: false + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + Item { + Layout.fillWidth: true + } + NIconButton { + icon: "settings" + tooltipText: I18n.tr("tooltips.open-settings") + onClicked: { + // Better close the control center in case the settings open in a separate window + PanelService.openedPanel?.close(); + + var panel = PanelService.getPanel("settingsPanel", screen); + panel.requestedTab = SettingsPanel.Tab.General; + panel.open(); + } + } + + NIconButton { + icon: "power" + tooltipText: I18n.tr("tooltips.session-menu") + onClicked: { + PanelService.getPanel("sessionMenuPanel", screen)?.open(); + PanelService.getPanel("controlCenterPanel", screen)?.close(); + } + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + onClicked: { + PanelService.getPanel("controlCenterPanel", screen)?.close(); + } + } + } + } + + // ---------------------------------- + // Uptime + Timer { + interval: 60000 + repeat: true + running: true + onTriggered: uptimeProcess.running = true + } + + Process { + id: uptimeProcess + command: ["cat", "/proc/uptime"] + running: true + + stdout: StdioCollector { + onStreamFinished: { + var uptimeSeconds = parseFloat(this.text.trim().split(' ')[0]); + uptimeText = Time.formatVagueHumanReadableDuration(uptimeSeconds); + uptimeProcess.running = false; + } + } + } + + function updateSystemInfo() { + uptimeProcess.running = true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Cards/ShortcutsCard.qml b/arch/.config/quickshell/noctalia-shell/Modules/Cards/ShortcutsCard.qml new file mode 100644 index 0000000..104569c --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Cards/ShortcutsCard.qml @@ -0,0 +1,92 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.Panels.ControlCenter +import qs.Widgets + +RowLayout { + Layout.fillWidth: true + spacing: Style.marginL + + NBox { + Layout.fillWidth: true + Layout.preferredHeight: root.shortcutsHeight + visible: Settings.data.controlCenter.shortcuts.left.length > 0 + + RowLayout { + id: leftContent + anchors.fill: parent + spacing: Style.marginS + + Item { + Layout.fillWidth: true + } + + Repeater { + model: Settings.data.controlCenter.shortcuts.left + delegate: ControlCenterWidgetLoader { + required property var modelData + required property int index + + Layout.fillWidth: false + widgetId: (modelData.id !== undefined ? modelData.id : "") + widgetScreen: root.screen + widgetProps: { + "widgetId": modelData.id, + "section": "quickSettings", + "sectionWidgetIndex": index, + "sectionWidgetsCount": Settings.data.controlCenter.shortcuts.left.length, + "widgetSettings": modelData + } + Layout.alignment: Qt.AlignVCenter + } + } + + Item { + Layout.fillWidth: true + } + } + } + + NBox { + Layout.fillWidth: true + Layout.preferredHeight: root.shortcutsHeight + visible: Settings.data.controlCenter.shortcuts.right.length > 0 + + RowLayout { + id: rightContent + anchors.fill: parent + spacing: Style.marginS + + Item { + Layout.fillWidth: true + } + + Repeater { + model: Settings.data.controlCenter.shortcuts.right + delegate: ControlCenterWidgetLoader { + required property var modelData + required property int index + + Layout.fillWidth: false + widgetId: (modelData.id !== undefined ? modelData.id : "") + widgetScreen: root.screen + widgetProps: { + "widgetId": modelData.id, + "section": "quickSettings", + "sectionWidgetIndex": index, + "sectionWidgetsCount": Settings.data.controlCenter.shortcuts.right.length, + "widgetSettings": modelData + } + Layout.alignment: Qt.AlignVCenter + } + } + + Item { + Layout.fillWidth: true + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Cards/SystemMonitorCard.qml b/arch/.config/quickshell/noctalia-shell/Modules/Cards/SystemMonitorCard.qml new file mode 100644 index 0000000..6ce5af6 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Cards/SystemMonitorCard.qml @@ -0,0 +1,125 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +// Unified system card: monitors CPU, temp, memory, disk +NBox { + id: root + + Component.onCompleted: SystemStatService.registerComponent("card-sysmonitor") + Component.onDestruction: SystemStatService.unregisterComponent("card-sysmonitor") + + readonly property string diskPath: Settings.data.controlCenter.diskPath || "/" + readonly property real contentScale: 0.95 * Style.uiScaleRatio + + Item { + id: content + anchors.fill: parent + anchors.margins: Style.marginS + + Column { + anchors.fill: parent + + Item { + width: parent.width + height: parent.height / 4 + + NCircleStat { + id: cpuUsageGauge + anchors.centerIn: parent + ratio: SystemStatService.cpuUsage / 100 + icon: "cpu-usage" + contentScale: root.contentScale + fillColor: SystemStatService.cpuColor + tooltipText: I18n.tr("system-monitor.cpu-usage") + `: ${Math.round(SystemStatService.cpuUsage)}%` + } + + Connections { + target: SystemStatService + function onCpuUsageChanged() { + if (TooltipService.activeTooltip && TooltipService.activeTooltip.targetItem === cpuUsageGauge) { + TooltipService.updateText(I18n.tr("system-monitor.cpu-usage") + `: ${Math.round(SystemStatService.cpuUsage)}%`); + } + } + } + } + + Item { + width: parent.width + height: parent.height / 4 + + NCircleStat { + id: cpuTempGauge + anchors.centerIn: parent + ratio: SystemStatService.cpuTemp / 100 + suffix: "ยฐC" + icon: "cpu-temperature" + contentScale: root.contentScale + fillColor: SystemStatService.tempColor + tooltipText: I18n.tr("system-monitor.cpu-temp") + `: ${Math.round(SystemStatService.cpuTemp)}ยฐC` + } + + Connections { + target: SystemStatService + function onCpuTempChanged() { + if (TooltipService.activeTooltip && TooltipService.activeTooltip.targetItem === cpuTempGauge) { + TooltipService.updateText(I18n.tr("system-monitor.cpu-temp") + `: ${Math.round(SystemStatService.cpuTemp)}ยฐC`); + } + } + } + } + + Item { + width: parent.width + height: parent.height / 4 + + NCircleStat { + id: memPercentGauge + anchors.centerIn: parent + ratio: SystemStatService.memPercent / 100 + icon: "memory" + contentScale: root.contentScale + fillColor: SystemStatService.memColor + tooltipText: I18n.tr("common.memory") + `: ${Math.round(SystemStatService.memPercent)}%` + } + + Connections { + target: SystemStatService + function onMemPercentChanged() { + if (TooltipService.activeTooltip && TooltipService.activeTooltip.targetItem === memPercentGauge) { + TooltipService.updateText(I18n.tr("common.memory") + `: ${Math.round(SystemStatService.memPercent)}%`); + } + } + } + } + + Item { + width: parent.width + height: parent.height / 4 + + NCircleStat { + id: diskPercentsGauge + anchors.centerIn: parent + ratio: (SystemStatService.diskPercents[root.diskPath] ?? 0) / 100 + icon: "storage" + contentScale: root.contentScale + fillColor: SystemStatService.getDiskColor(root.diskPath) + tooltipText: I18n.tr("system-monitor.disk") + `: ${SystemStatService.diskPercents[root.diskPath] || 0}%\n${root.diskPath}` + } + + Connections { + target: SystemStatService + function onDiskPercentsChanged() { + if (TooltipService.activeTooltip && TooltipService.activeTooltip.targetItem === diskPercentsGauge) { + TooltipService.updateText(I18n.tr("system-monitor.disk") + `: ${SystemStatService.diskPercents[root.diskPath] || 0}%\n${root.diskPath}`); + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Cards/WeatherCard.qml b/arch/.config/quickshell/noctalia-shell/Modules/Cards/WeatherCard.qml new file mode 100644 index 0000000..0dd5cb4 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Cards/WeatherCard.qml @@ -0,0 +1,279 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Location +import qs.Widgets + +// Weather overview card (placeholder data) +NBox { + id: root + + property int forecastDays: 6 + property bool showLocation: true + property bool showEffects: Settings.data.location.weatherShowEffects + readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null) + + // Test mode: set to "clear_day", "clear_night", "rain", "snow", "cloud" or "fog" + property string testEffects: "" + + // Weather condition detection + readonly property int currentWeatherCode: weatherReady ? LocationService.data.weather.current_weather.weathercode : 0 + readonly property bool isDayTime: weatherReady ? LocationService.data.weather.current_weather.is_day : true + readonly property bool isRaining: testEffects === "rain" || (testEffects === "" && ((currentWeatherCode >= 51 && currentWeatherCode <= 67) || (currentWeatherCode >= 80 && currentWeatherCode <= 82))) + readonly property bool isSnowing: testEffects === "snow" || (testEffects === "" && ((currentWeatherCode >= 71 && currentWeatherCode <= 77) || (currentWeatherCode >= 85 && currentWeatherCode <= 86))) + readonly property bool isCloudy: testEffects === "cloud" || (testEffects === "" && (currentWeatherCode === 3)) + readonly property bool isFoggy: testEffects === "fog" || (testEffects === "" && (currentWeatherCode >= 40 && currentWeatherCode <= 49)) + readonly property bool isClearDay: testEffects === "clear_day" || (testEffects === "" && (currentWeatherCode === 0 && isDayTime)) + readonly property bool isClearNight: testEffects === "clear_night" || (testEffects === "" && (currentWeatherCode === 0 && !isDayTime)) + + visible: Settings.data.location.weatherEnabled + implicitHeight: Math.max(100 * Style.uiScaleRatio, content.implicitHeight + Style.margin2XL) + + // Weather effect layer (rain/snow) + Loader { + id: weatherEffectLoader + anchors.fill: parent + active: root.showEffects && (root.isRaining || root.isSnowing || root.isCloudy || root.isFoggy || root.isClearDay || root.isClearNight) + + sourceComponent: Item { + anchors.fill: parent + + // Animated time for shaders + property real shaderTime: 0 + NumberAnimation on shaderTime { + loops: Animation.Infinite + from: 0 + to: root.isSnowing ? 900 : 3000 + duration: 300000 + } + + ShaderEffect { + id: weatherEffect + anchors.fill: parent + // Rain matches content margins, everything else fills the box + anchors.margins: root.isRaining ? Style.marginXL : root.border.width + + property var source: ShaderEffectSource { + sourceItem: content + hideSource: root.isRaining // Only hide for rain (distortion), show for snow + } + + // Limit update rate to avoid using too much processing power + property real time: parent.shaderTime - (parent.shaderTime % (1 / (root.isRaining ? 3 : root.isSnowing ? 6 : 2))) + property real itemWidth: weatherEffect.width + property real itemHeight: weatherEffect.height + property color bgColor: root.color + property real cornerRadius: root.isRaining ? 0 : (root.radius - root.border.width) + property real alternative: root.isFoggy + + fragmentShader: { + let shaderName; + if (root.isSnowing) + shaderName = "weather_snow"; + else if (root.isRaining) + shaderName = "weather_rain"; + else if (root.isCloudy || root.isFoggy) + shaderName = "weather_cloud"; + else if (root.isClearDay) + shaderName = "weather_sun"; + else if (root.isClearNight) + shaderName = "weather_stars"; + else + shaderName = ""; + + return Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/" + shaderName + ".frag.qsb"); + } + } + } + } + + ColumnLayout { + id: content + anchors.fill: parent + anchors.margins: Style.marginXL + spacing: Style.marginM + clip: true + + RowLayout { + visible: weatherReady + Layout.fillWidth: true + spacing: Style.marginS + + Item { + Layout.preferredWidth: Style.marginXXS + } + + RowLayout { + spacing: Style.marginL + Layout.fillWidth: true + + Item { + Layout.preferredWidth: mainWeatherIconSide + Layout.preferredHeight: mainWeatherIconSide + Layout.alignment: Qt.AlignVCenter + readonly property int mainWeatherIconSide: Math.round(Style.fontSizeXXXL * 1.75 * Style.uiScaleRatio * 1.6) + + NIcon { + visible: !LocationService.taliaWeatherMascotActive + anchors.centerIn: parent + icon: weatherReady ? LocationService.weatherSymbolFromCode(LocationService.data.weather.current_weather.weathercode) : "" + pointSize: Style.fontSizeXXXL * 1.75 + color: Color.mPrimary + } + Loader { + active: LocationService.taliaWeatherMascotActive + anchors.fill: parent + asynchronous: true + sourceComponent: Component { + Image { + anchors.fill: parent + fillMode: Image.PreserveAspectFit + smooth: true + mipmap: true + asynchronous: true + source: Qt.resolvedUrl(LocationService.taliaWeatherImageFromCode(currentWeatherCode)) + } + } + } + } + + ColumnLayout { + spacing: Style.marginXXS + NText { + text: { + // Ensure the name is not too long if one had to specify the country + const chunks = Settings.data.location.name.split(","); + return chunks[0]; + } + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + visible: showLocation && !Settings.data.location.hideWeatherCityName + } + + RowLayout { + NText { + text: { + if (!weatherReady) + return ""; + var temp = LocationService.data.weather.current_weather.temperature; + var suffix = "C"; + if (Settings.data.location.useFahrenheit) { + temp = LocationService.celsiusToFahrenheit(temp); + var suffix = "F"; + } + temp = Math.round(temp); + return `${temp}ยฐ${suffix}`; + } + pointSize: showLocation ? Style.fontSizeXL : Style.fontSizeXL * 1.6 + font.weight: Style.fontWeightBold + } + + NText { + text: weatherReady ? `(${LocationService.data.weather.timezone_abbreviation})` : "" + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + visible: LocationService.data.weather && showLocation && !Settings.data.location.hideWeatherTimezone + } + } + } + } + } + + NDivider { + visible: weatherReady + Layout.fillWidth: true + } + + RowLayout { + visible: weatherReady + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + spacing: Style.marginM + + Repeater { + model: weatherReady ? Math.min(root.forecastDays, LocationService.data.weather.daily.time.length) : 0 + delegate: ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXS + Item { + Layout.fillWidth: true + } + NText { + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + text: { + var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/")); + return I18n.locale.toString(weatherDate, "ddd"); + } + color: Color.mOnSurface + } + Item { + Layout.preferredWidth: forecastWeatherIconSide + Layout.preferredHeight: forecastWeatherIconSide + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + readonly property int forecastWeatherIconSide: Math.round(Style.fontSizeXXL * 1.6 * Style.uiScaleRatio * 1.6) + + NIcon { + visible: !LocationService.taliaWeatherMascotActive + anchors.centerIn: parent + icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index]) + pointSize: Style.fontSizeXXL * 1.6 + color: Color.mPrimary + } + Loader { + active: LocationService.taliaWeatherMascotActive + anchors.fill: parent + asynchronous: true + sourceComponent: Component { + Image { + anchors.fill: parent + fillMode: Image.PreserveAspectFit + smooth: true + mipmap: true + asynchronous: true + source: Qt.resolvedUrl(LocationService.taliaWeatherImageFromCode(LocationService.data.weather.daily.weathercode[index])) + } + } + } + } + NText { + Layout.alignment: Qt.AlignHCenter + text: { + var max = LocationService.data.weather.daily.temperature_2m_max[index]; + var min = LocationService.data.weather.daily.temperature_2m_min[index]; + if (Settings.data.location.useFahrenheit) { + max = LocationService.celsiusToFahrenheit(max); + min = LocationService.celsiusToFahrenheit(min); + } + max = Math.round(max); + min = Math.round(min); + return `${max}ยฐ/${min}ยฐ`; + } + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + } + } + } + + ColumnLayout { + visible: !weatherReady + Layout.alignment: Qt.AlignCenter + spacing: Style.marginS + + NBusyIndicator { + Layout.alignment: Qt.AlignCenter + visible: LocationService.locationConfigured + } + + NText { + visible: !LocationService.locationConfigured + Layout.alignment: Qt.AlignCenter + text: I18n.tr("common.weather-no-location") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/DesktopWidgets.qml b/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/DesktopWidgets.qml new file mode 100644 index 0000000..e137d1e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/DesktopWidgets.qml @@ -0,0 +1,547 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Modules.Panels.Settings +import qs.Services.Compositor +import qs.Services.Noctalia +import qs.Services.Power +import qs.Services.UI +import qs.Widgets + +Variants { + id: root + model: Quickshell.screens + + // Direct binding to registry's widgets property for reactivity + readonly property var registeredWidgets: DesktopWidgetRegistry.widgets + + // Force reload counter - incremented when plugin widget registry changes + property int pluginReloadCounter: 0 + + Connections { + target: DesktopWidgetRegistry + + function onPluginWidgetRegistryUpdated() { + root.pluginReloadCounter++; + Logger.d("DesktopWidgets", "Plugin widget registry updated, reload counter:", root.pluginReloadCounter); + } + } + + delegate: Loader { + id: screenLoader + required property ShellScreen modelData + + // Reactive property for widgets on this specific screen + // Returns a fresh array whenever Settings changes + property var screenWidgets: { + if (!modelData || !modelData.name) { + return []; + } + var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || []; + for (var i = 0; i < monitorWidgets.length; i++) { + if (monitorWidgets[i].name === modelData.name) { + return monitorWidgets[i].widgets || []; + } + } + return []; + } + + // Only create PanelWindow if enabled AND (screen has widgets OR in edit mode) + // During compositor overview, show widgets only when overviewEnabled is true. + active: modelData && Settings.data.desktopWidgets.enabled && (screenWidgets.length > 0 || DesktopWidgetRegistry.editMode) && (!CompositorService.overviewActive || Settings.data.desktopWidgets.overviewEnabled) && (!PowerProfileService.noctaliaPerformanceMode || !Settings.data.noctaliaPerformance.disableDesktopWidgets) && !PanelService.lockScreen?.active + + sourceComponent: PanelWindow { + id: window + color: "transparent" + screen: screenLoader.modelData + mask: DesktopWidgetRegistry.editMode ? null : widgetsMask + + // Dynamic mask: combine clickable regions for each loaded widget + property var _maskRegions: [] + + Component { + id: maskRegionComponent + Region {} + } + + Region { + id: widgetsMask + regions: window._maskRegions + } + + WlrLayershell.layer: WlrLayer.Bottom + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.namespace: "noctalia-desktop-widgets-" + (screen?.name || "unknown") + + anchors { + top: true + bottom: true + right: true + left: true + } + + Component.onCompleted: { + Logger.d("DesktopWidgets", "Created panel window for", screen?.name); + } + + // Add a new widget to the current screen + function addWidgetToCurrentScreen(widgetId) { + var monitorName = window.screen.name; + var newWidget = { + "id": widgetId + }; + + // Load default metadata if available + var metadata = DesktopWidgetRegistry.widgetMetadata[widgetId]; + if (metadata) { + Object.keys(metadata).forEach(function (key) { + newWidget[key] = metadata[key]; + }); + } + + // Place at screen center + newWidget.x = (window.screen.width / 2) - 100; + newWidget.y = (window.screen.height / 2) - 100; + newWidget.scale = 1.0; + + // Get current widgets and add new one + var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || []; + var newMonitorWidgets = monitorWidgets.slice(); + var found = false; + + for (var i = 0; i < newMonitorWidgets.length; i++) { + if (newMonitorWidgets[i].name === monitorName) { + var widgets = (newMonitorWidgets[i].widgets || []).slice(); + widgets.push(newWidget); + newMonitorWidgets[i] = { + "name": monitorName, + "widgets": widgets + }; + found = true; + break; + } + } + + if (!found) { + newMonitorWidgets.push({ + "name": monitorName, + "widgets": [newWidget] + }); + } + + Settings.data.desktopWidgets.monitorWidgets = newMonitorWidgets; + Logger.i("DesktopWidgets", "Added widget", widgetId, "to", monitorName); + } + + Item { + id: widgetsContainer + anchors.fill: parent + + // Visual grid overlay - shown when grid snap is enabled in edit mode + // Using Loader to properly unload Canvas when not needed + Loader { + id: gridOverlayLoader + active: DesktopWidgetRegistry.editMode && Settings.data.desktopWidgets.enabled && Settings.data.desktopWidgets.gridSnap + anchors.fill: parent + z: -1 // Behind widgets but above background + asynchronous: false + + sourceComponent: Canvas { + id: gridOverlay + anchors.fill: parent + opacity: 0.3 + + // Grid size calculated based on screen resolution - matches DraggableDesktopWidget + // Ensures grid lines pass through the screen center on both axes + readonly property int gridSize: { + if (!window.screen) + return 30; // Fallback + var baseSize = Math.round(window.screen.width * 0.015); + baseSize = Math.max(20, Math.min(60, baseSize)); + + // Calculate center coordinates + var centerX = window.screen.width / 2; + var centerY = window.screen.height / 2; + + // Find a grid size that divides evenly into both center coordinates + // This ensures a grid line crosses through the center on both axes + var bestSize = baseSize; + var bestDistance = Infinity; + + // Try values around baseSize to find one that divides evenly into both centers + for (var offset = -10; offset <= 10; offset++) { + var candidate = baseSize + offset; + if (candidate < 20 || candidate > 60) + continue; + + // Check if this size divides evenly into both center coordinates + var remainderX = centerX % candidate; + var remainderY = centerY % candidate; + + // If both remainders are 0, this is perfect - center is on grid lines + if (remainderX === 0 && remainderY === 0) { + return candidate; // Perfect match, use it immediately + } + + // Otherwise, find the closest to perfect alignment + var distance = Math.abs(remainderX) + Math.abs(remainderY); + if (distance < bestDistance) { + bestDistance = distance; + bestSize = candidate; + } + } + + // If we found a perfect match, it would have returned already + // Otherwise, try to find a divisor of both centerX and centerY + // that's close to our best size + var gcd = function (a, b) { + while (b !== 0) { + var temp = b; + b = a % b; + a = temp; + } + return a; + }; + + // Find common divisors of centerX and centerY + var centerGcd = gcd(Math.round(centerX), Math.round(centerY)); + if (centerGcd > 0) { + // Find a divisor of centerGcd that's close to bestSize + for (var divisor = Math.floor(centerGcd / 60); divisor <= Math.ceil(centerGcd / 20); divisor++) { + if (centerGcd % divisor !== 0) + continue; + var candidate = centerGcd / divisor; + if (candidate >= 20 && candidate <= 60) { + if (Math.abs(candidate - baseSize) < Math.abs(bestSize - baseSize)) { + bestSize = candidate; + } + } + } + } + + return bestSize; + } + + onPaint: { + const ctx = getContext("2d"); + ctx.reset(); + ctx.strokeStyle = Color.mPrimary; + ctx.lineWidth = 1; + + // Draw vertical lines + for (let x = 0; x <= width; x += gridSize) { + ctx.beginPath(); + ctx.moveTo(x, 0); + ctx.lineTo(x, height); + ctx.stroke(); + } + + // Draw horizontal lines + for (let y = 0; y <= height; y += gridSize) { + ctx.beginPath(); + ctx.moveTo(0, y); + ctx.lineTo(width, y); + ctx.stroke(); + } + } + + // Repaint when size changes + onWidthChanged: requestPaint() + onHeightChanged: requestPaint() + + Component.onCompleted: { + requestPaint(); + } + + Connections { + target: Settings.data.desktopWidgets + function onGridSnapChanged() { + if (gridOverlayLoader.active) { + gridOverlay.requestPaint(); + } + } + } + + Connections { + target: DesktopWidgetRegistry + function onEditModeChanged() { + if (gridOverlayLoader.active) { + gridOverlay.requestPaint(); + } + } + } + } + } + + // Load widgets dynamically from per-monitor array + Repeater { + model: screenLoader.screenWidgets + + delegate: Loader { + id: widgetLoader + // Bind to registeredWidgets and pluginReloadCounter to re-evaluate when plugins register/unregister + active: (modelData.id in root.registeredWidgets) && (root.pluginReloadCounter >= 0) + + required property var modelData + required property int index + property var _maskRegion: null + readonly property bool _isPlugin: DesktopWidgetRegistry.isPluginWidget(modelData.id) + + // All widgets use setSource() so that screen, widgetData, and + // widgetIndex are set as initial properties, available during + // Component.onCompleted. This prevents registration-key + // mismatches in widgets that build IDs from screen.name. + Component.onCompleted: _loadWidget() + + onActiveChanged: { + if (active) + _loadWidget(); + } + + function _loadWidget() { + var widgetId = modelData.id; + var comp = root.registeredWidgets[widgetId]; + if (!comp) + return; + + var props = { + "screen": window.screen, + "widgetData": modelData, + "widgetIndex": index + }; + + if (_isPlugin) { + var pluginId = widgetId.replace("plugin:", ""); + var api = PluginService.getPluginAPI(pluginId); + if (api) + props.pluginApi = api; + setSource(comp.url, props); + } else { + // Core widgets: use explicit URL (inline Component.url + // returns the registry file, not the widget file) + var url = DesktopWidgetRegistry.widgetUrls[widgetId]; + if (url) + setSource(url, props); + } + } + + onLoaded: { + if (item) { + item.parent = widgetsContainer; + + // Create mask region so this widget receives mouse input + _maskRegion = maskRegionComponent.createObject(window); + _maskRegion.item = item; + var newRegions = window._maskRegions.slice(); + newRegions.push(_maskRegion); + window._maskRegions = newRegions; + } + } + + // Clean up mask region when widget unloads + onItemChanged: { + if (!item && _maskRegion) { + var region = _maskRegion; + _maskRegion = null; + window._maskRegions = window._maskRegions.filter(function (r) { + return r !== region; + }); + region.destroy(); + } + } + } + } + + // Edit mode controls panel + Rectangle { + id: editModeControlsPanel + visible: DesktopWidgetRegistry.editMode && Settings.data.desktopWidgets.enabled + + readonly property string barPos: Settings.getBarPositionForScreen(window.screen?.name) + readonly property bool barFloating: Settings.data.bar.barType === "floating" + readonly property real barHeight: Style.getBarHeightForScreen(window.screen?.name) + + readonly property int barOffsetTop: { + if (barPos !== "top") + return Style.marginM; + const floatMarginV = barFloating ? Math.ceil(Settings.data.bar.marginVertical) : 0; + return barHeight + floatMarginV + Style.marginM; + } + readonly property int barOffsetRight: { + if (barPos !== "right") + return Style.marginM; + const floatMarginH = barFloating ? Math.ceil(Settings.data.bar.marginHorizontal) : 0; + return barHeight + floatMarginH + Style.marginM; + } + + // Internal state for drag tracking (session-only, resets on restart) + QtObject { + id: panelInternal + property bool isDragging: false + property real dragOffsetX: 0 + property real dragOffsetY: 0 + // Default position: top-right corner accounting for bar + property real baseX: widgetsContainer.width - editModeControlsPanel.width - editModeControlsPanel.barOffsetRight + property real baseY: editModeControlsPanel.barOffsetTop + } + + // Reset position when bar position changes + Connections { + target: Settings.data.bar + function onPositionChanged() { + panelInternal.baseX = widgetsContainer.width - editModeControlsPanel.width - editModeControlsPanel.barOffsetRight; + panelInternal.baseY = editModeControlsPanel.barOffsetTop; + } + } + + x: panelInternal.isDragging ? panelInternal.dragOffsetX : panelInternal.baseX + y: panelInternal.isDragging ? panelInternal.dragOffsetY : panelInternal.baseY + + width: controlsLayout.implicitWidth + Style.margin2XL + height: controlsLayout.implicitHeight + Style.margin2XL + + color: Qt.rgba(Color.mSurface.r, Color.mSurface.g, Color.mSurface.b, 0.85) + radius: Style.radiusL + border { + width: Style.borderS + color: Color.mOutline + } + z: 9999 + + // Drag area for relocating the panel + MouseArea { + id: dragArea + anchors.fill: parent + cursorShape: panelInternal.isDragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor + + property point pressPos: Qt.point(0, 0) + + onPressed: mouse => { + pressPos = mapToItem(widgetsContainer, mouse.x, mouse.y); + panelInternal.dragOffsetX = editModeControlsPanel.x; + panelInternal.dragOffsetY = editModeControlsPanel.y; + panelInternal.isDragging = true; + } + + onPositionChanged: mouse => { + if (panelInternal.isDragging && pressed) { + var currentPos = mapToItem(widgetsContainer, mouse.x, mouse.y); + var deltaX = currentPos.x - pressPos.x; + var deltaY = currentPos.y - pressPos.y; + + var newX = panelInternal.baseX + deltaX; + var newY = panelInternal.baseY + deltaY; + + // Boundary clamping + newX = Math.max(0, Math.min(newX, widgetsContainer.width - editModeControlsPanel.width)); + newY = Math.max(0, Math.min(newY, widgetsContainer.height - editModeControlsPanel.height)); + + panelInternal.dragOffsetX = newX; + panelInternal.dragOffsetY = newY; + } + } + + onReleased: { + if (panelInternal.isDragging) { + panelInternal.baseX = panelInternal.dragOffsetX; + panelInternal.baseY = panelInternal.dragOffsetY; + panelInternal.isDragging = false; + } + } + + onCanceled: { + panelInternal.isDragging = false; + } + } + + ColumnLayout { + id: controlsLayout + anchors { + fill: parent + margins: Style.marginXL + } + spacing: Style.marginL + + RowLayout { + Layout.alignment: Qt.AlignRight + spacing: Style.marginS + + NIconButton { + id: addWidgetButton + icon: "layout-grid-add" + tooltipText: I18n.tr("tooltips.add-widget") + onClicked: { + var popupMenuWindow = PanelService.getPopupMenuWindow(window.screen); + if (popupMenuWindow) { + // Build menu items from registry + var items = []; + var widgets = DesktopWidgetRegistry.widgets; + for (var id in widgets) { + items.push({ + action: id, + text: DesktopWidgetRegistry.getWidgetDisplayName(id), + icon: "layout-grid-add" + }); + } + var globalPos = addWidgetButton.mapToItem(null, 0, addWidgetButton.height + Style.marginS); + popupMenuWindow.showDynamicContextMenu(items, globalPos.x, globalPos.y, function (widgetId) { + addWidgetToCurrentScreen(widgetId); + return false; + }); + } + } + } + + NIconButton { + icon: "grid-3x3" + visible: Settings.data.desktopWidgets.gridSnap + tooltipText: I18n.tr("panels.desktop-widgets.edit-mode-grid-snap-scale-label") + colorBg: Settings.data.desktopWidgets.gridSnapScale ? Color.mPrimary : Color.mSurfaceVariant + colorFg: Settings.data.desktopWidgets.gridSnapScale ? Color.mOnPrimary : Color.mPrimary + onClicked: Settings.data.desktopWidgets.gridSnapScale = !Settings.data.desktopWidgets.gridSnapScale + } + + NIconButton { + icon: "grid-4x4" + tooltipText: I18n.tr("panels.desktop-widgets.edit-mode-grid-snap-label") + colorBg: Settings.data.desktopWidgets.gridSnap ? Color.mPrimary : Color.mSurfaceVariant + colorFg: Settings.data.desktopWidgets.gridSnap ? Color.mOnPrimary : Color.mPrimary + onClicked: Settings.data.desktopWidgets.gridSnap = !Settings.data.desktopWidgets.gridSnap + } + + NIconButton { + icon: "settings" + tooltipText: I18n.tr("actions.open-settings") + onClicked: { + SettingsPanelService.toggle(SettingsPanel.Tab.DesktopWidgets, -1, screenLoader.modelData); + } + } + + NButton { + text: I18n.tr("panels.desktop-widgets.edit-mode-exit-button") + icon: "logout" + outlined: false + fontSize: Style.fontSizeS + iconSize: Style.fontSizeM + onClicked: DesktopWidgetRegistry.editMode = false + } + } + + NText { + Layout.alignment: Qt.AlignRight + Layout.maximumWidth: 300 * Style.uiScaleRatio + text: I18n.tr("panels.desktop-widgets.edit-mode-controls-explanation") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + horizontalAlignment: Text.AlignRight + wrapMode: Text.WordWrap + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/DraggableDesktopWidget.qml b/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/DraggableDesktopWidget.qml new file mode 100644 index 0000000..c3c544c --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/DraggableDesktopWidget.qml @@ -0,0 +1,634 @@ +import QtQuick +import QtQuick.Effects +import Quickshell +import qs.Commons +import qs.Services.Noctalia +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property ShellScreen screen + property var widgetData: null + property int widgetIndex: -1 + + property real defaultX: 100 + property real defaultY: 100 + + default property alias content: contentContainer.data + + readonly property bool isDragging: internal.isDragging + readonly property bool isScaling: internal.isScaling + + // All Desktop widgets have these settings, but fallback just in case + readonly property var _metadata: widgetData?.id ? DesktopWidgetRegistry.widgetMetadata[widgetData.id] : null + property bool showBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : (_metadata?.showBackground ?? true) + property bool roundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : (_metadata?.roundedCorners ?? true) + + property real widgetScale: 1.0 + property real minScale: 0.5 + property real maxScale: 5.0 + + readonly property real scaleSensitivity: 0.0015 + readonly property real scaleUpdateThreshold: 0.015 + readonly property real cornerScaleSensitivity: 0.0003 // Much lower sensitivity for corner handles + + // Grid size ensures lines pass through screen center on both axes + readonly property int gridSize: { + if (!screen) + return 30; + var baseSize = Math.round(screen.width * 0.015); + baseSize = Math.max(20, Math.min(60, baseSize)); + + var centerX = screen.width / 2; + var centerY = screen.height / 2; + var bestSize = baseSize; + var bestDistance = Infinity; + + for (var offset = -10; offset <= 10; offset++) { + var candidate = baseSize + offset; + if (candidate < 20 || candidate > 60) + continue; + + var remainderX = centerX % candidate; + var remainderY = centerY % candidate; + + if (remainderX === 0 && remainderY === 0) { + return candidate; + } + + var distance = Math.abs(remainderX) + Math.abs(remainderY); + if (distance < bestDistance) { + bestDistance = distance; + bestSize = candidate; + } + } + + var gcd = function (a, b) { + while (b !== 0) { + var temp = b; + b = a % b; + a = temp; + } + return a; + }; + + var centerGcd = gcd(Math.round(centerX), Math.round(centerY)); + if (centerGcd > 0) { + for (var divisor = Math.floor(centerGcd / 60); divisor <= Math.ceil(centerGcd / 20); divisor++) { + if (centerGcd % divisor !== 0) + continue; + var candidate = centerGcd / divisor; + if (candidate >= 20 && candidate <= 60) { + if (Math.abs(candidate - baseSize) < Math.abs(bestSize - baseSize)) { + bestSize = candidate; + } + } + } + } + + return bestSize; + } + + QtObject { + id: internal + property bool isDragging: false + property bool isScaling: false + property real dragOffsetX: 0 + property real dragOffsetY: 0 + property real baseX: (root.widgetData && root.widgetData.x !== undefined) ? root.widgetData.x : root.defaultX + property real baseY: (root.widgetData && root.widgetData.y !== undefined) ? root.widgetData.y : root.defaultY + property real initialWidth: 0 + property real initialHeight: 0 + property point initialMousePos: Qt.point(0, 0) + property real initialScale: 1.0 + property real lastScale: 1.0 + // Locks operation type to prevent switching between drag/scale mid-operation + property string operationType: "" // "drag" or "scale" or "" + } + + function snapToGrid(coord) { + if (!Settings.data.desktopWidgets.gridSnap) { + return coord; + } + return Math.round(coord / root.gridSize) * root.gridSize; + } + + function snapScaleToGrid(scale) { + if (!Settings.data.desktopWidgets.gridSnap || !Settings.data.desktopWidgets.gridSnapScale) { + return scale; + } + + // Get widget's base width + var initialWidth = internal.initialWidth; + var initialScale = internal.initialScale; + if (initialWidth <= 0 || initialScale <= 0) { + return scale; + } + + // Since initialWidth = baseWidth * initialScale + var baseWidth = initialWidth / initialScale; + + // Snap the resulting width with the scale + var resultingWidth = baseWidth * scale; + var snappedWidth = root.snapToGrid(resultingWidth); + + // Check that the snappedWidth isn't smaller than one grid size + if (snappedWidth < root.gridSize) { + snappedWidth = root.gridSize; + } + + // Return the ratio of the snappedWidth and the baseWidth, which is the new snapped scale + var snappedScale = snappedWidth / baseWidth; + return Math.max(minScale, Math.min(maxScale, snappedScale)); + } + + function updateWidgetData(properties) { + if (widgetIndex < 0 || !screen || !screen.name) { + return; + } + DesktopWidgetRegistry.updateWidgetData(screen.name, widgetIndex, properties); + } + + function removeWidget() { + if (widgetIndex < 0 || !screen || !screen.name) { + return; + } + + var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || []; + var newMonitorWidgets = monitorWidgets.slice(); + + for (var i = 0; i < newMonitorWidgets.length; i++) { + if (newMonitorWidgets[i].name === screen.name) { + var widgets = (newMonitorWidgets[i].widgets || []).slice(); + if (widgetIndex >= 0 && widgetIndex < widgets.length) { + widgets.splice(widgetIndex, 1); + newMonitorWidgets[i] = Object.assign({}, newMonitorWidgets[i], { + "widgets": widgets + }); + Settings.data.desktopWidgets.monitorWidgets = newMonitorWidgets; + } + break; + } + } + } + + function raiseToTop() { + if (widgetIndex < 0 || !screen || !screen.name) { + return; + } + + var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || []; + var newMonitorWidgets = monitorWidgets.slice(); + + for (var i = 0; i < newMonitorWidgets.length; i++) { + if (newMonitorWidgets[i].name === screen.name) { + var widgets = (newMonitorWidgets[i].widgets || []).slice(); + if (widgetIndex < widgets.length && widgetIndex < widgets.length - 1) { + var widget = widgets.splice(widgetIndex, 1)[0]; + widgets.push(widget); + newMonitorWidgets[i] = Object.assign({}, newMonitorWidgets[i], { + "widgets": widgets + }); + Settings.data.desktopWidgets.monitorWidgets = newMonitorWidgets; + } + break; + } + } + } + + function lowerToBottom() { + if (widgetIndex < 0 || !screen || !screen.name) { + return; + } + + var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || []; + var newMonitorWidgets = monitorWidgets.slice(); + + for (var i = 0; i < newMonitorWidgets.length; i++) { + if (newMonitorWidgets[i].name === screen.name) { + var widgets = (newMonitorWidgets[i].widgets || []).slice(); + if (widgetIndex < widgets.length && widgetIndex > 0) { + var widget = widgets.splice(widgetIndex, 1)[0]; + widgets.unshift(widget); + newMonitorWidgets[i] = Object.assign({}, newMonitorWidgets[i], { + "widgets": widgets + }); + Settings.data.desktopWidgets.monitorWidgets = newMonitorWidgets; + } + break; + } + } + } + + function openWidgetSettings() { + DesktopWidgetRegistry.openWidgetSettings(screen, widgetIndex, widgetData.id, widgetData); + } + + function handleContextMenuAction(action) { + if (action === "widget-settings") { + // Don't close - openWidgetSettings will use the popup window for the dialog + root.openWidgetSettings(); + return true; // Signal that we're handling close ourselves + } else if (action === "reset") { + // Reset scale and position to defaults + root.widgetScale = 1.0; + internal.baseX = root.defaultX; + internal.baseY = root.defaultY; + root.updateWidgetData({ + "scale": 1.0, + "x": Math.round(root.defaultX), + "y": Math.round(root.defaultY) + }); + return false; + } else if (action === "raise-to-top") { + root.raiseToTop(); + return false; + } else if (action === "lower-to-bottom") { + root.lowerToBottom(); + return false; + } else if (action === "delete") { + root.removeWidget(); + return false; // Let caller close the popup + } + return false; + } + + x: Math.round(internal.isDragging ? internal.dragOffsetX : internal.baseX) + y: Math.round(internal.isDragging ? internal.dragOffsetY : internal.baseY) + + // Note: We no longer use transform-based scaling (scale property) + // Instead, child widgets multiply their dimensions by widgetScale + // This prevents blurry text at fractional scale values + + Component.onCompleted: { + // Initialize scale from widgetData when component is first created + if (widgetData && widgetData.scale !== undefined) { + widgetScale = widgetData.scale; + } + } + + onWidgetDataChanged: { + if (!internal.isDragging && !internal.isScaling) { + internal.baseX = (widgetData && widgetData.x !== undefined) ? widgetData.x : defaultX; + internal.baseY = (widgetData && widgetData.y !== undefined) ? widgetData.y : defaultY; + if (widgetData && widgetData.scale !== undefined) { + widgetScale = widgetData.scale; + } else if (widgetData) { + // If widgetData exists but scale is not set, default to 1.0 + widgetScale = 1.0; + } + } + } + + Rectangle { + id: decorationRect + anchors.fill: parent + anchors.margins: -outlineMargin + color: DesktopWidgetRegistry.editMode ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.1) : "transparent" + border.color: (DesktopWidgetRegistry.editMode || internal.isDragging) ? (internal.isDragging ? Color.mOutline : Color.mPrimary) : "transparent" + border.width: DesktopWidgetRegistry.editMode ? 3 : 0 + radius: Math.min(Math.round(Style.radiusL * root.widgetScale), Style.radiusL, width / 2, height / 2) + z: -1 + } + + Rectangle { + id: container + anchors.fill: parent + radius: root.roundedCorners ? Math.min(Math.round(Style.radiusL * root.widgetScale), Style.radiusL, width / 2, height / 2) : 0 + color: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity) + border { + width: 1 + color: Qt.alpha(Color.mOutline, 0.12) + } + clip: true + visible: root.showBackground + + layer.enabled: Settings.data.general.enableShadows && !internal.isDragging && root.showBackground + layer.effect: MultiEffect { + shadowEnabled: true + shadowBlur: Style.shadowBlur * 1.5 + shadowOpacity: Style.shadowOpacity * 0.6 + shadowColor: "black" + shadowHorizontalOffset: Settings.data.general.shadowOffsetX + shadowVerticalOffset: Settings.data.general.shadowOffsetY + blurMax: Style.shadowBlurMax + } + } + + Item { + id: contentContainer + anchors.fill: parent + z: 1 + clip: true + } + + // Context menu model and handler - menu is created dynamically in PopupMenuWindow + property var contextMenuModel: { + var hasSettings = false; + if (widgetData && widgetData.id) { + var widgetId = widgetData.id; + if (DesktopWidgetRegistry.isPluginWidget(widgetId)) { + var pluginId = widgetId.replace("plugin:", ""); + var manifest = PluginRegistry.getPluginManifest(pluginId); + hasSettings = manifest && manifest.entryPoints && (manifest.entryPoints.settings || manifest.entryPoints.desktopWidgetSettings); + } else { + hasSettings = DesktopWidgetRegistry.widgetSettingsMap[widgetId] !== undefined; + } + } + + var items = []; + if (hasSettings) { + items.push({ + "label": I18n.tr("actions.widget-settings"), + "action": "widget-settings", + "icon": "settings" + }); + } + items.push({ + "label": I18n.tr("common.reset"), + "action": "reset", + "icon": "restore" + }); + items.push({ + "label": I18n.tr("actions.raise-to-top"), + "action": "raise-to-top", + "icon": "stack-front" + }); + items.push({ + "label": I18n.tr("actions.lower-to-bottom"), + "action": "lower-to-bottom", + "icon": "stack-back" + }); + items.push({ + "label": I18n.tr("common.delete"), + "action": "delete", + "icon": "trash" + }); + return items; + } + + // Drag MouseArea - handles dragging (left-click) + MouseArea { + id: dragArea + anchors.fill: parent + z: 1000 + visible: DesktopWidgetRegistry.editMode + cursorShape: internal.isDragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor + hoverEnabled: true + acceptedButtons: Qt.LeftButton + + property point pressPos: Qt.point(0, 0) + + onPressed: mouse => { + // Prevent starting new operation if one is already in progress + if (internal.operationType !== "") { + return; + } + + pressPos = Qt.point(mouse.x, mouse.y); + internal.operationType = "drag"; + internal.dragOffsetX = root.x; + internal.dragOffsetY = root.y; + internal.isDragging = true; + } + + onPositionChanged: mouse => { + if (internal.isDragging && pressed && internal.operationType === "drag") { + var globalPressPos = mapToItem(root.parent, pressPos.x, pressPos.y); + var globalCurrentPos = mapToItem(root.parent, mouse.x, mouse.y); + + var deltaX = globalCurrentPos.x - globalPressPos.x; + var deltaY = globalCurrentPos.y - globalPressPos.y; + + var newX = internal.dragOffsetX + deltaX; + var newY = internal.dragOffsetY + deltaY; + + // Boundary clamping - allow widgets to go partially off-screen (75% can clip) + // This gives users more control over positioning while keeping widgets accessible + var scaledWidth = root.width; + var scaledHeight = root.height; + if (root.parent && scaledWidth > 0 && scaledHeight > 0) { + var minVisibleX = scaledWidth * 0.25; + var minVisibleY = scaledHeight * 0.25; + newX = Math.max(-scaledWidth + minVisibleX, Math.min(newX, root.parent.width - minVisibleX)); + newY = Math.max(-scaledHeight + minVisibleY, Math.min(newY, root.parent.height - minVisibleY)); + } + + if (Settings.data.desktopWidgets.gridSnap) { + newX = root.snapToGrid(newX); + newY = root.snapToGrid(newY); + // Re-clamp after snapping + if (root.parent && scaledWidth > 0 && scaledHeight > 0) { + var minVisibleX = scaledWidth * 0.25; + var minVisibleY = scaledHeight * 0.25; + newX = Math.max(-scaledWidth + minVisibleX, Math.min(newX, root.parent.width - minVisibleX)); + newY = Math.max(-scaledHeight + minVisibleY, Math.min(newY, root.parent.height - minVisibleY)); + } + } + + internal.dragOffsetX = newX; + internal.dragOffsetY = newY; + } + } + + onReleased: mouse => { + if (internal.isDragging && internal.operationType === "drag" && widgetIndex >= 0 && screen && screen.name) { + var roundedX = Math.round(internal.dragOffsetX); + var roundedY = Math.round(internal.dragOffsetY); + root.updateWidgetData({ + "x": roundedX, + "y": roundedY + }); + + internal.baseX = roundedX; + internal.baseY = roundedY; + internal.isDragging = false; + internal.operationType = ""; + } + } + + onCanceled: { + internal.isDragging = false; + internal.operationType = ""; + } + } + + // Right-click MouseArea for context menu + MouseArea { + id: contextMenuArea + anchors.fill: parent + z: 1001 + visible: DesktopWidgetRegistry.editMode + acceptedButtons: Qt.RightButton + hoverEnabled: true + + onPressed: mouse => { + if (mouse.button === Qt.RightButton) { + var popupMenuWindow = PanelService.getPopupMenuWindow(root.screen); + if (popupMenuWindow) { + // Map click position to screen coordinates + var globalPos = root.mapToItem(null, mouse.x, mouse.y); + // Use dynamic context menu (created in PopupMenuWindow's Top layer) + // This ensures input events work correctly for desktop widgets (Bottom layer) + popupMenuWindow.showDynamicContextMenu(root.contextMenuModel, globalPos.x, globalPos.y, root.handleContextMenuAction); + } + } + } + } + + // Corner handles for scaling - using Repeater to avoid code duplication + readonly property real cornerHandleSize: 8 * widgetScale + readonly property real outlineMargin: Style.marginS * widgetScale + readonly property color colorHandle: Color.mSecondary + + // Corner handle model: defines position, direction, cursor, and triangle points for each corner + // xMult/yMult: multipliers for position (0 = left/top edge, 1 = right/bottom edge) + // xDir/yDir: direction multiplier for scaling (-1 = left/up increases, 1 = right/down increases) + // cursor: resize cursor type (FDiag for TL-BR diagonal, BDiag for TR-BL diagonal) + // points: triangle vertices as [x, y] pairs normalized to cornerHandleSize + readonly property var cornerHandleModel: [ + { + xMult: 0, + yMult: 0, + xDir: -1, + yDir: -1, + cursor: Qt.SizeFDiagCursor, + points: [[0, 0], [1, 0], [0, 1]] + }, + { + xMult: 1, + yMult: 0, + xDir: 1, + yDir: -1, + cursor: Qt.SizeBDiagCursor, + points: [[1, 0], [1, 1], [0, 0]] + }, + { + xMult: 0, + yMult: 1, + xDir: -1, + yDir: 1, + cursor: Qt.SizeBDiagCursor, + points: [[0, 1], [0, 0], [1, 1]] + }, + { + xMult: 1, + yMult: 1, + xDir: 1, + yDir: 1, + cursor: Qt.SizeFDiagCursor, + points: [[1, 1], [1, 0], [0, 1]] + } + ] + + Repeater { + model: root.cornerHandleModel + + delegate: Canvas { + id: cornerHandle + required property var modelData + required property int index + + visible: DesktopWidgetRegistry.editMode && !internal.isDragging + // Position handles at corners of decoration rectangle (which extends by outlineMargin) + x: modelData.xMult === 0 ? -outlineMargin : (root.width + outlineMargin - cornerHandleSize) + y: modelData.yMult === 0 ? -outlineMargin : (root.height + outlineMargin - cornerHandleSize) + width: cornerHandleSize + height: cornerHandleSize + z: 2000 + + onPaint: { + var ctx = getContext("2d"); + ctx.reset(); + ctx.fillStyle = colorHandle; + ctx.beginPath(); + ctx.moveTo(modelData.points[0][0] * cornerHandleSize, modelData.points[0][1] * cornerHandleSize); + ctx.lineTo(modelData.points[1][0] * cornerHandleSize, modelData.points[1][1] * cornerHandleSize); + ctx.lineTo(modelData.points[2][0] * cornerHandleSize, modelData.points[2][1] * cornerHandleSize); + ctx.closePath(); + ctx.fill(); + } + + Component.onCompleted: requestPaint() + onVisibleChanged: if (visible) + requestPaint() + + Connections { + target: root + function onWidthChanged() { + if (cornerHandle.visible) + cornerHandle.requestPaint(); + } + function onHeightChanged() { + if (cornerHandle.visible) + cornerHandle.requestPaint(); + } + } + + MouseArea { + id: scaleMouseArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton + cursorShape: cornerHandle.modelData.cursor + property point pressPos: Qt.point(0, 0) + + onPressed: mouse => { + if (internal.operationType !== "") { + return; + } + pressPos = mapToItem(root.parent, mouse.x, mouse.y); + internal.operationType = "scale"; + internal.isScaling = true; + internal.initialScale = root.widgetScale; + internal.lastScale = root.widgetScale; + internal.initialWidth = root.width; + internal.initialHeight = root.height; + } + + onPositionChanged: mouse => { + if (internal.isScaling && pressed && internal.operationType === "scale") { + var currentPos = mapToItem(root.parent, mouse.x, mouse.y); + var deltaX = currentPos.x - pressPos.x; + var deltaY = currentPos.y - pressPos.y; + + // Project delta onto the diagonal direction for this corner + // xDir/yDir indicate which direction increases scale + var diagonalDelta = (deltaX * cornerHandle.modelData.xDir + deltaY * cornerHandle.modelData.yDir) / Math.sqrt(2); + + // Scale sensitivity: pixels of drag per 1.0 scale change + var sensitivity = 150; + var scaleDelta = diagonalDelta / sensitivity; + var newScale = Math.max(root.minScale, Math.min(root.maxScale, internal.initialScale + scaleDelta)); + + newScale = root.snapScaleToGrid(newScale); + + if (!isNaN(newScale) && newScale > 0) { + root.widgetScale = newScale; + internal.lastScale = newScale; + } + } + } + + onReleased: mouse => { + if (internal.isScaling && internal.operationType === "scale") { + root.updateWidgetData({ + "scale": root.widgetScale + }); + internal.isScaling = false; + internal.operationType = ""; + root.widgetScale = root.snapScaleToGrid(root.widgetScale); + internal.lastScale = root.widgetScale; + } + } + + onCanceled: { + internal.isScaling = false; + internal.operationType = ""; + internal.lastScale = root.widgetScale; + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/Widgets/DesktopAudioVisualizer.qml b/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/Widgets/DesktopAudioVisualizer.qml new file mode 100644 index 0000000..a462df8 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/Widgets/DesktopAudioVisualizer.qml @@ -0,0 +1,119 @@ +import QtQuick +import QtQuick.Effects +import Quickshell +import qs.Commons +import qs.Modules.DesktopWidgets +import qs.Services.Media +import qs.Services.UI +import qs.Widgets +import qs.Widgets.AudioSpectrum + +DraggableDesktopWidget { + id: root + + defaultY: 280 + + readonly property var widgetMetadata: DesktopWidgetRegistry.widgetMetadata["AudioVisualizer"] + + readonly property int visualizerWidth: (widgetData && widgetData.width !== undefined) ? widgetData.width : (widgetMetadata?.width ?? 320) + readonly property int visualizerHeight: (widgetData && widgetData.height !== undefined) ? widgetData.height : (widgetMetadata?.height ?? 72) + readonly property string visualizerType: (widgetData && widgetData.visualizerType !== undefined) ? widgetData.visualizerType : (widgetMetadata?.visualizerType ?? "linear") + readonly property bool hideWhenIdle: (widgetData && widgetData.hideWhenIdle !== undefined) ? widgetData.hideWhenIdle : (widgetMetadata?.hideWhenIdle ?? false) + readonly property string colorName: (widgetData && widgetData.colorName !== undefined) ? widgetData.colorName : (widgetMetadata?.colorName ?? "primary") + + readonly property color fillColor: Color.resolveColorKey(colorName) + + readonly property bool shouldShow: visualizerType !== "" && visualizerType !== "none" && (!hideWhenIdle || MediaService.isPlaying) + readonly property bool isHidden: !shouldShow + readonly property bool shouldRegisterSpectrum: shouldShow + + // Keep widget visible in edit mode so users can move/configure it + visible: !root.isHidden || DesktopWidgetRegistry.editMode + + readonly property string spectrumComponentId: "desktop:audiovisualizer:" + (root.screen ? root.screen.name : "unknown") + ":" + root.widgetIndex + + onShouldRegisterSpectrumChanged: { + if (root.shouldRegisterSpectrum) { + SpectrumService.registerComponent(root.spectrumComponentId); + } else { + SpectrumService.unregisterComponent(root.spectrumComponentId); + } + } + + Component.onCompleted: { + if (root.shouldRegisterSpectrum) { + SpectrumService.registerComponent(root.spectrumComponentId); + } + } + + Component.onDestruction: { + SpectrumService.unregisterComponent(root.spectrumComponentId); + } + + implicitWidth: Math.round(visualizerWidth * widgetScale) + implicitHeight: Math.round(visualizerHeight * widgetScale) + width: implicitWidth + height: implicitHeight + + Rectangle { + id: visualizerMask + anchors.fill: parent + color: "transparent" + radius: root.roundedCorners ? Math.min(Math.round(Style.radiusL * root.widgetScale), Style.radiusL, width / 2, height / 2) : 0 + clip: true + + Loader { + id: visualizerLoader + anchors.fill: parent + anchors.margins: root.showBackground ? Math.round(Style.marginXS * root.widgetScale) : 0 + active: root.shouldShow + asynchronous: true + + sourceComponent: { + switch (root.visualizerType) { + case "linear": + return linearComponent; + case "mirrored": + return mirroredComponent; + case "wave": + return waveComponent; + default: + return null; + } + } + } + } + + Component { + id: linearComponent + NLinearSpectrum { + anchors.fill: parent + values: SpectrumService.values + fillColor: root.fillColor + showMinimumSignal: true + mirrored: Settings.data.audio.spectrumMirrored + } + } + + Component { + id: mirroredComponent + NMirroredSpectrum { + anchors.fill: parent + values: SpectrumService.values + fillColor: root.fillColor + showMinimumSignal: true + mirrored: Settings.data.audio.spectrumMirrored + } + } + + Component { + id: waveComponent + NWaveSpectrum { + anchors.fill: parent + values: SpectrumService.values + fillColor: root.fillColor + showMinimumSignal: true + mirrored: Settings.data.audio.spectrumMirrored + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/Widgets/DesktopClock.qml b/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/Widgets/DesktopClock.qml new file mode 100644 index 0000000..e72d4f0 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/Widgets/DesktopClock.qml @@ -0,0 +1,81 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.DesktopWidgets +import qs.Services.UI +import qs.Widgets + +DraggableDesktopWidget { + id: root + + readonly property var now: Time.now + readonly property var widgetMetadata: DesktopWidgetRegistry.widgetMetadata["Clock"] + + readonly property color clockTextColor: Color.resolveColorKey(clockColor) + readonly property real fontSize: Math.round(Style.fontSizeXXXL * 2.5 * widgetScale) + readonly property real widgetOpacity: widgetData.opacity !== undefined ? widgetData.opacity : 1.0 + readonly property string clockStyle: widgetData.clockStyle !== undefined ? widgetData.clockStyle : widgetMetadata.clockStyle + readonly property string clockColor: widgetData.clockColor !== undefined ? widgetData.clockColor : widgetMetadata.clockColor + readonly property bool useCustomFont: widgetData.useCustomFont !== undefined ? widgetData.useCustomFont : widgetMetadata.useCustomFont + readonly property string customFont: widgetData.customFont !== undefined ? widgetData.customFont : widgetMetadata.customFont + readonly property string format: widgetData.format !== undefined ? widgetData.format : widgetMetadata.format + + readonly property real contentPadding: Math.round((clockStyle === "minimal" ? Style.marginL : Style.marginXL) * widgetScale) + implicitWidth: contentLoader.item ? Math.round((contentLoader.item.implicitWidth || contentLoader.item.width || 0) + contentPadding * 2) : 0 + implicitHeight: contentLoader.item ? Math.round((contentLoader.item.implicitHeight || contentLoader.item.height || 0) + contentPadding * 2) : 0 + width: implicitWidth + height: implicitHeight + + Component { + id: nclockComponent + NClock { + now: root.now + clockStyle: root.clockStyle + backgroundColor: "transparent" + clockColor: clockTextColor + progressColor: Color.mPrimary + opacity: root.widgetOpacity + height: Math.round(fontSize * 1.9) + width: height + hoursFontSize: fontSize * 0.6 + minutesFontSize: fontSize * 0.4 + scaleRatio: root.widgetScale + } + } + + Component { + id: minimalClockComponent + ColumnLayout { + spacing: -2 + opacity: root.widgetOpacity + + Repeater { + model: I18n.locale.toString(root.now, root.format.trim()).split("\\n") + delegate: NText { + visible: text !== "" + text: modelData + family: root.useCustomFont && root.customFont ? root.customFont : Settings.data.ui.fontDefault + pointSize: { + if (model.length == 1) { + return Math.round(Style.fontSizeXXL * root.widgetScale); + } else { + return Math.round((index == 0) ? Style.fontSizeXXL * root.widgetScale : Style.fontSizeM * root.widgetScale); + } + } + font.weight: Style.fontWeightBold + color: root.clockTextColor + wrapMode: Text.WordWrap + Layout.alignment: Qt.AlignHCenter + } + } + } + } + + Loader { + id: contentLoader + anchors.centerIn: parent + z: 2 + sourceComponent: clockStyle === "minimal" ? minimalClockComponent : nclockComponent + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/Widgets/DesktopMediaPlayer.qml b/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/Widgets/DesktopMediaPlayer.qml new file mode 100644 index 0000000..52b5cbb --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/Widgets/DesktopMediaPlayer.qml @@ -0,0 +1,311 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.DesktopWidgets +import qs.Services.Media +import qs.Services.UI +import qs.Widgets +import qs.Widgets.AudioSpectrum + +DraggableDesktopWidget { + id: root + + defaultY: 200 + + // Widget settings - check widgetData exists before accessing properties + readonly property string hideMode: (widgetData && widgetData.hideMode !== undefined) ? widgetData.hideMode : "visible" + readonly property bool showButtons: (widgetData && widgetData.showButtons !== undefined) ? widgetData.showButtons : true + readonly property bool showAlbumArt: (widgetData && widgetData.showAlbumArt !== undefined) ? widgetData.showAlbumArt : true + readonly property bool showVisualizer: (widgetData && widgetData.showVisualizer !== undefined) ? widgetData.showVisualizer : true + readonly property string visualizerType: (widgetData && widgetData.visualizerType && widgetData.visualizerType !== "") ? widgetData.visualizerType : "linear" + readonly property bool roundedCorners: (widgetData && widgetData.roundedCorners !== undefined) ? widgetData.roundedCorners : true + readonly property bool hasPlayer: MediaService.currentPlayer !== null + readonly property bool isPlaying: MediaService.isPlaying + readonly property bool hasActiveTrack: hasPlayer && (MediaService.trackTitle || MediaService.trackArtist) + + // State + // Hide when idle when playback is not active + readonly property bool shouldHideIdle: (hideMode === "idle") && !isPlaying + readonly property bool shouldHideEmpty: !hasPlayer && hideMode === "hidden" + readonly property bool isHidden: (shouldHideIdle || shouldHideEmpty) && !DesktopWidgetRegistry.editMode + visible: !isHidden + + // SpectrumService registration for visualizer + readonly property string spectrumComponentId: "desktopmediaplayer:" + (root.screen ? root.screen.name : "unknown") + readonly property bool needsSpectrum: root.shouldShowVisualizer && !root.isHidden + + onNeedsSpectrumChanged: { + if (root.needsSpectrum) { + SpectrumService.registerComponent(root.spectrumComponentId); + } else { + SpectrumService.unregisterComponent(root.spectrumComponentId); + } + } + + Component.onCompleted: { + if (root.needsSpectrum) { + SpectrumService.registerComponent(root.spectrumComponentId); + } + } + + Component.onDestruction: { + SpectrumService.unregisterComponent(root.spectrumComponentId); + } + + readonly property bool showPrev: hasPlayer && MediaService.canGoPrevious + readonly property bool showNext: hasPlayer && MediaService.canGoNext + readonly property int visibleButtonCount: root.showButtons ? (1 + (showPrev ? 1 : 0) + (showNext ? 1 : 0)) : 0 + + implicitWidth: Math.round(400 * widgetScale) + implicitHeight: Math.round(64 * widgetScale + Style.margin2M * widgetScale) + width: implicitWidth + height: implicitHeight + + // Visualizer visibility mode + readonly property bool shouldShowVisualizer: { + if (!root.showVisualizer) + return false; + if (root.visualizerType === "" || root.visualizerType === "none") + return false; + return true; + } + + // Visualizer overlay (visibility controlled by visualizerVisibility setting) + // Completely disabled during scaling to avoid expensive canvas redraws + Loader { + anchors.fill: parent + anchors.leftMargin: Math.round(Style.marginXS * widgetScale) + anchors.rightMargin: Math.round(Style.marginXS * widgetScale) + anchors.topMargin: Math.round(Style.marginXS * widgetScale) + anchors.bottomMargin: 0 + z: 0 + clip: true + active: needsSpectrum + layer.enabled: true + layer.smooth: true + layer.effect: MultiEffect { + maskEnabled: root.roundedCorners + maskSource: ShaderEffectSource { + sourceItem: Rectangle { + width: root.width - Math.round(Style.marginXS * widgetScale) * 2 + height: root.height - Math.round(Style.marginXS * widgetScale) + radius: root.roundedCorners ? Math.round(Math.max(0, (Style.radiusL - Style.marginXS) * widgetScale)) : 0 + color: "white" + } + } + } + + sourceComponent: { + switch (root.visualizerType) { + case "linear": + return linearComponent; + case "mirrored": + return mirroredComponent; + case "wave": + return waveComponent; + default: + return null; + } + } + + Component { + id: linearComponent + NLinearSpectrum { + anchors.fill: parent + values: SpectrumService.values + fillColor: Color.mPrimary + opacity: 0.5 + mirrored: Settings.data.audio.spectrumMirrored + } + } + + Component { + id: mirroredComponent + NMirroredSpectrum { + anchors.fill: parent + values: SpectrumService.values + fillColor: Color.mPrimary + opacity: 0.5 + mirrored: Settings.data.audio.spectrumMirrored + } + } + + Component { + id: waveComponent + NWaveSpectrum { + anchors.fill: parent + values: SpectrumService.values + fillColor: Color.mPrimary + opacity: 0.5 + mirrored: Settings.data.audio.spectrumMirrored + } + } + } + + // Drop shadow for text and controls readability over visualizer + // Disabled during scaling to avoid expensive recomputations + NDropShadow { + visible: !root.isScaling + anchors.fill: contentLayout + source: contentLayout + z: 1 + autoPaddingEnabled: true + shadowBlur: 1.0 + shadowOpacity: 0.9 + shadowHorizontalOffset: 0 + shadowVerticalOffset: 0 + } + + RowLayout { + id: contentLayout + states: [ + State { + when: root.showButtons + AnchorChanges { + target: contentLayout + anchors.horizontalCenter: undefined + anchors.verticalCenter: undefined + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + } + }, + State { + when: !root.showButtons + AnchorChanges { + target: contentLayout + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + anchors.top: undefined + anchors.bottom: undefined + anchors.left: undefined + anchors.right: undefined + } + } + ] + anchors.margins: Math.round(Style.marginM * widgetScale) + spacing: Math.round(Style.marginS * widgetScale) + z: 2 + + Item { + visible: root.showAlbumArt + Layout.preferredWidth: Math.round(48 * widgetScale) + Layout.preferredHeight: Math.round(48 * widgetScale) + Layout.alignment: Qt.AlignVCenter + + NImageRounded { + visible: hasPlayer + anchors.fill: parent + radius: Math.round(Style.radiusM * widgetScale) + imagePath: MediaService.trackArtUrl + imageFillMode: Image.PreserveAspectCrop + fallbackIcon: isPlaying ? "media-pause" : "media-play" + fallbackIconSize: Math.round(20 * widgetScale) + borderWidth: 0 + } + + NIcon { + visible: !hasPlayer + anchors.centerIn: parent + icon: "disc" + pointSize: Math.round(24 * widgetScale) + color: Color.mOnSurfaceVariant + } + } + + ColumnLayout { + visible: root.showAlbumArt + Layout.fillWidth: true + Layout.alignment: root.showButtons ? Qt.AlignVCenter : Qt.AlignCenter + spacing: 0 + + NText { + Layout.fillWidth: true + text: hasPlayer ? (MediaService.trackTitle || "Unknown Track") : "No media playing" + pointSize: Math.round(Style.fontSizeS * widgetScale) + font.weight: Style.fontWeightSemiBold + color: Color.mOnSurface + elide: Text.ElideRight + maximumLineCount: 1 + } + + NText { + visible: hasPlayer && MediaService.trackArtist + Layout.fillWidth: true + text: MediaService.trackArtist || "" + pointSize: Math.round(Style.fontSizeXS * widgetScale) + font.weight: Style.fontWeightRegular + color: Color.mSecondary + elide: Text.ElideRight + maximumLineCount: 1 + } + } + + RowLayout { + id: controlsRow + spacing: Math.round(Style.marginXS * widgetScale) + z: 10 + visible: root.showButtons + Layout.alignment: root.showAlbumArt ? Qt.AlignVCenter : Qt.AlignCenter + + NIconButton { + opacity: showPrev ? 1 : 0 + Behavior on opacity { + NumberAnimation { + duration: Style.animationSlow + easing.type: Easing.InOutQuad + } + } + baseSize: Math.round(32 * widgetScale) + icon: "media-prev" + enabled: hasPlayer && MediaService.canGoPrevious + colorBg: Color.mSurfaceVariant + colorFg: enabled ? Color.mPrimary : Color.mOnSurfaceVariant + customRadius: Math.round(Style.radiusS * widgetScale) + onClicked: { + if (enabled) + MediaService.previous(); + } + } + + NIconButton { + baseSize: Math.round(36 * widgetScale) + icon: isPlaying ? "media-pause" : "media-play" + enabled: hasPlayer && (MediaService.canPlay || MediaService.canPause) + colorBg: Color.mPrimary + colorFg: Color.mOnPrimary + colorBgHover: Qt.lighter(Color.mPrimary, 1.1) + colorFgHover: Color.mOnPrimary + customRadius: Math.round(Style.radiusS * widgetScale) + onClicked: { + if (enabled) { + MediaService.playPause(); + } + } + } + + NIconButton { + opacity: showNext ? 1 : 0 + Behavior on opacity { + NumberAnimation { + duration: Style.animationSlow + easing.type: Easing.InOutQuad + } + } + baseSize: Math.round(32 * widgetScale) + icon: "media-next" + enabled: hasPlayer && MediaService.canGoNext + colorBg: Color.mSurfaceVariant + colorFg: enabled ? Color.mPrimary : Color.mOnSurfaceVariant + customRadius: Math.round(Style.radiusS * widgetScale) + onClicked: { + if (enabled) + MediaService.next(); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/Widgets/DesktopSystemStat.qml b/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/Widgets/DesktopSystemStat.qml new file mode 100644 index 0000000..5d14aaf --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/Widgets/DesktopSystemStat.qml @@ -0,0 +1,305 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.DesktopWidgets +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +DraggableDesktopWidget { + id: root + + // Widget settings + readonly property var widgetMetadata: DesktopWidgetRegistry.widgetMetadata["SystemStat"] + readonly property string statType: (widgetData && widgetData.statType !== undefined) ? widgetData.statType : (widgetMetadata.statType !== undefined ? widgetMetadata.statType : "CPU") + readonly property string diskPath: (widgetData && widgetData.diskPath !== undefined) ? widgetData.diskPath : "/" + readonly property string layout: (widgetData && widgetData.layout !== undefined) ? widgetData.layout : (widgetMetadata.layout !== undefined ? widgetMetadata.layout : "side") + + // Fixed colors + readonly property color color: Color.mPrimary + readonly property color color2: Color.mSecondary + + // Legend items model - each item has: text, color, icon (optional), bold (optional), opacity (optional), elide (optional) + readonly property var legendItems: { + switch (root.statType) { + case "CPU": + return [ + { + icon: "cpu-usage", + text: Math.round(SystemStatService.cpuUsage) + "%", + color: root.color + }, + { + text: "cpu-usage", + text: SystemStatService.cpuFreq, + color: root.color, + opacity: 0.8 + }, + { + icon: "cpu-temperature", + text: SystemStatService.cpuTemp + "ยฐC", + color: root.color2 + } + ]; + case "GPU": + return [ + { + icon: "gpu-temperature", + text: Math.round(SystemStatService.gpuTemp) + "ยฐC", + color: root.color + } + ]; + case "Memory": + return [ + { + icon: "memory", + text: Math.round(SystemStatService.memPercent) + "%", + color: root.color + } + ]; + case "Disk": + var items = [ + { + icon: "storage", + text: Math.round(SystemStatService.diskPercents[root.diskPath] || 0) + "%", + color: root.color + } + ]; + if (root.diskPath !== "/") { + items.push({ + text: root.diskPath, + color: root.color, + opacity: 0.8, + elide: true + }); + } + return items; + case "Network": + return [ + { + icon: "download-speed", + text: SystemStatService.formatSpeed(SystemStatService.rxSpeed), + color: root.color + }, + { + icon: "upload-speed", + text: SystemStatService.formatSpeed(SystemStatService.txSpeed), + color: root.color2 + } + ]; + default: + return []; + } + } + + // History from service + readonly property var history: { + switch (root.statType) { + case "CPU": + return SystemStatService.cpuHistory; + case "GPU": + return SystemStatService.gpuTempHistory; + case "Memory": + return SystemStatService.memHistory; + case "Disk": + return SystemStatService.diskHistories[root.diskPath] || []; + case "Network": + return SystemStatService.rxSpeedHistory; + default: + return []; + } + } + + // Secondary history (CPU temp for CPU, Tx for Network) + readonly property var history2: { + switch (root.statType) { + case "CPU": + return SystemStatService.cpuTempHistory; + case "Network": + return SystemStatService.txSpeedHistory; + default: + return []; + } + } + + // Graph min/max values + readonly property real graphMinValue: root.statType === "GPU" ? Math.max(SystemStatService.gpuTempHistoryMin - 5, 0) : 0 + readonly property real graphMaxValue: { + switch (root.statType) { + case "CPU": + case "Memory": + case "Disk": + return 100; // Percentage-based stats use fixed 0-100 range + case "GPU": + return Math.max(SystemStatService.gpuTempHistoryMax + 5, 1); + case "Network": + return SystemStatService.rxMaxSpeed; + default: + return 100; + } + } + readonly property real graphMinValue2: { + switch (root.statType) { + case "CPU": + return Math.max(SystemStatService.cpuTempHistoryMin - 5, 0); + default: + return graphMinValue; + } + } + readonly property real graphMaxValue2: { + switch (root.statType) { + case "CPU": + return Math.max(SystemStatService.cpuTempHistoryMax + 5, 1); + case "Network": + return SystemStatService.txMaxSpeed; + default: + return graphMaxValue; + } + } + + Component.onCompleted: SystemStatService.registerComponent("desktop-sysstat:" + root.statType) + Component.onDestruction: SystemStatService.unregisterComponent("desktop-sysstat:" + root.statType) + + implicitWidth: Math.round(240 * widgetScale) + implicitHeight: Math.round(120 * widgetScale) + width: implicitWidth + height: implicitHeight + + // Update interval per stat type + readonly property int graphUpdateInterval: { + switch (root.statType) { + case "CPU": + return SystemStatService.cpuUsageIntervalMs; + case "GPU": + return SystemStatService.gpuIntervalMs; + case "Memory": + return SystemStatService.memIntervalMs; + case "Disk": + return SystemStatService.diskIntervalMs; + case "Network": + return SystemStatService.networkIntervalMs; + default: + return 1000; + } + } + + // Graph component (shared between layouts) + Component { + id: graphComponent + NGraph { + values: root.history + values2: root.history2 + minValue: root.graphMinValue + maxValue: root.graphMaxValue + minValue2: root.graphMinValue2 + maxValue2: root.graphMaxValue2 + color: root.color + color2: root.color2 + fill: true + updateInterval: root.graphUpdateInterval + strokeWidth: Math.max(1, root.widgetScale) + animateScale: root.statType === "Network" + } + } + + // Side layout: icon + legend on left, graph on right + RowLayout { + anchors.fill: parent + anchors.margins: Math.round(Style.marginM * widgetScale) + spacing: Math.round(Style.marginL * widgetScale) + visible: root.layout === "side" + + ColumnLayout { + Layout.alignment: Qt.AlignVCenter + Layout.fillHeight: true + Layout.preferredWidth: Math.round(64 * widgetScale) + spacing: Style.marginXS * root.widgetScale + + Repeater { + model: root.legendItems + delegate: RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: Math.round(Style.marginXXS * root.widgetScale) + + NIcon { + visible: !!modelData.icon + icon: modelData.icon || "" + color: modelData.color + pointSize: Style.fontSizeS * root.widgetScale + opacity: modelData.opacity !== undefined ? modelData.opacity : 1.0 + } + + NText { + text: modelData.text + color: modelData.color + pointSize: Style.fontSizeS * root.widgetScale + font.family: Settings.data.ui.fontFixed + font.weight: modelData.bold ? Style.fontWeightBold : Style.fontWeightRegular + opacity: modelData.opacity !== undefined ? modelData.opacity : 1.0 + elide: modelData.elide ? Text.ElideMiddle : Text.ElideNone + Layout.maximumWidth: modelData.elide ? Math.round(56 * root.widgetScale) : -1 + horizontalAlignment: Text.AlignHCenter + } + } + } + } + + Loader { + active: root.layout === "side" + Layout.fillWidth: true + Layout.fillHeight: true + sourceComponent: graphComponent + } + } + + // Bottom layout: full-width graph, horizontal legend at bottom + ColumnLayout { + anchors.fill: parent + anchors.margins: Math.round(Style.marginM * widgetScale) + spacing: Math.round(Style.marginS * widgetScale) + visible: root.layout === "bottom" + + Loader { + active: root.layout === "bottom" + Layout.fillWidth: true + Layout.fillHeight: true + sourceComponent: graphComponent + } + + RowLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + spacing: Math.round(Style.marginM * widgetScale) + + Repeater { + model: root.legendItems + delegate: RowLayout { + Layout.alignment: Qt.AlignVCenter + spacing: Math.round(Style.marginXXS * root.widgetScale) + + NIcon { + Layout.alignment: Qt.AlignVCenter + visible: !!modelData.icon + icon: modelData.icon || "" + color: modelData.color + pointSize: Style.fontSizeS * root.widgetScale + opacity: modelData.opacity !== undefined ? modelData.opacity : 1.0 + } + + NText { + Layout.alignment: Qt.AlignVCenter + text: modelData.text + color: modelData.color + pointSize: Style.fontSizeS * root.widgetScale + font.family: Settings.data.ui.fontFixed + font.weight: modelData.bold ? Style.fontWeightBold : Style.fontWeightRegular + opacity: modelData.opacity !== undefined ? modelData.opacity : 1.0 + elide: modelData.elide ? Text.ElideMiddle : Text.ElideNone + Layout.maximumWidth: modelData.elide ? Math.round(56 * root.widgetScale) : -1 + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/Widgets/DesktopWeather.qml b/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/Widgets/DesktopWeather.qml new file mode 100644 index 0000000..21a11eb --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/DesktopWidgets/Widgets/DesktopWeather.qml @@ -0,0 +1,146 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.DesktopWidgets +import qs.Services.Location +import qs.Widgets + +DraggableDesktopWidget { + id: root + + readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null) + readonly property int currentWeatherCode: weatherReady ? LocationService.data.weather.current_weather.weathercode : 0 + readonly property real currentTemp: { + if (!weatherReady) + return 0; + var temp = LocationService.data.weather.current_weather.temperature; + if (Settings.data.location.useFahrenheit) { + temp = LocationService.celsiusToFahrenheit(temp); + } + return Math.round(temp); + } + readonly property real todayMax: { + if (!weatherReady || !LocationService.data.weather.daily || LocationService.data.weather.daily.temperature_2m_max.length === 0) + return 0; + var temp = LocationService.data.weather.daily.temperature_2m_max[0]; + if (Settings.data.location.useFahrenheit) { + temp = LocationService.celsiusToFahrenheit(temp); + } + return Math.round(temp); + } + readonly property real todayMin: { + if (!weatherReady || !LocationService.data.weather.daily || LocationService.data.weather.daily.temperature_2m_min.length === 0) + return 0; + var temp = LocationService.data.weather.daily.temperature_2m_min[0]; + if (Settings.data.location.useFahrenheit) { + temp = LocationService.celsiusToFahrenheit(temp); + } + return Math.round(temp); + } + readonly property string tempUnit: Settings.data.location.useFahrenheit ? "F" : "C" + readonly property string locationName: { + const chunks = Settings.data.location.name.split(","); + return chunks[0]; + } + + implicitWidth: Math.round(Math.max(240 * widgetScale, contentLayout.implicitWidth + Style.margin2M * widgetScale)) + implicitHeight: Math.round(64 * widgetScale + Style.margin2M * widgetScale) + width: implicitWidth + height: implicitHeight + + RowLayout { + id: contentLayout + anchors.fill: parent + anchors.margins: Math.round(Style.marginM * widgetScale) + spacing: Math.round(Style.marginM * widgetScale) + z: 2 + + Item { + Layout.preferredWidth: Math.round(64 * widgetScale) + Layout.preferredHeight: Math.round(64 * widgetScale) + Layout.alignment: Qt.AlignVCenter + + NIcon { + visible: !LocationService.taliaWeatherMascotActive || !weatherReady + anchors.centerIn: parent + icon: weatherReady ? LocationService.weatherSymbolFromCode(currentWeatherCode) : (LocationService.locationConfigured ? "weather-cloud-off" : "map-pin-off") + pointSize: Math.round(Style.fontSizeXXXL * 2 * widgetScale) + color: weatherReady ? Color.mPrimary : Color.mOnSurfaceVariant + } + Loader { + active: LocationService.taliaWeatherMascotActive && weatherReady + anchors.fill: parent + asynchronous: true + sourceComponent: Component { + Image { + anchors.fill: parent + fillMode: Image.PreserveAspectFit + smooth: true + mipmap: true + asynchronous: true + source: Qt.resolvedUrl(LocationService.taliaWeatherImageFromCode(currentWeatherCode)) + } + } + } + } + + NText { + text: weatherReady ? `${currentTemp}ยฐ${tempUnit}` : "--" + pointSize: Math.round(Style.fontSizeXXXL * widgetScale) + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Math.round(Style.marginXXS * widgetScale) + Layout.alignment: Qt.AlignVCenter + + NText { + Layout.fillWidth: true + text: locationName || I18n.tr("common.weather-no-location") + pointSize: Math.round(Style.fontSizeS * widgetScale) + font.weight: Style.fontWeightRegular + color: Color.mOnSurfaceVariant + elide: Text.ElideRight + maximumLineCount: 1 + visible: !Settings.data.location.hideWeatherCityName + } + + RowLayout { + spacing: Math.round(Style.marginXS * widgetScale) + visible: weatherReady && todayMax > 0 && todayMin > 0 + + NText { + text: "H:" + pointSize: Math.round(Style.fontSizeXS * widgetScale) + color: Color.mOnSurfaceVariant + } + NText { + text: `${todayMax}ยฐ` + pointSize: Math.round(Style.fontSizeXS * widgetScale) + color: Color.mOnSurface + } + + NText { + text: "โ€ข" + pointSize: Math.round(Style.fontSizeXXS * widgetScale) + color: Color.mOnSurfaceVariant + opacity: 0.5 + } + + NText { + text: "L:" + pointSize: Math.round(Style.fontSizeXS * widgetScale) + color: Color.mOnSurfaceVariant + } + NText { + text: `${todayMin}ยฐ` + pointSize: Math.round(Style.fontSizeXS * widgetScale) + color: Color.mOnSurfaceVariant + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Dock/Dock.qml b/arch/.config/quickshell/noctalia-shell/Modules/Dock/Dock.qml new file mode 100644 index 0000000..73dec8d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Dock/Dock.qml @@ -0,0 +1,951 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import Quickshell.Widgets +import qs.Commons +import qs.Services.Compositor +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +Loader { + + active: Settings.data.dock.enabled + sourceComponent: Variants { + model: Quickshell.screens + + delegate: Item { + id: root + + required property ShellScreen modelData + + property bool barIsReady: modelData ? BarService.isBarReady(modelData.name) : false + + Connections { + target: BarService + function onBarReadyChanged(screenName) { + if (screenName === modelData.name) { + barIsReady = true; + } + } + } + + // Update dock apps when window list change + Connections { + target: CompositorService + function onWindowListChanged() { + updateDockApps(); + } + } + + // Update dock apps when toplevels change + Connections { + target: ToplevelManager ? ToplevelManager.toplevels : null + function onValuesChanged() { + updateDockApps(); + } + } + + // Update dock apps when pinned apps change + Connections { + target: Settings.data.dock + function onPinnedAppsChanged() { + updateDockApps(); + } + function onOnlySameOutputChanged() { + updateDockApps(); + } + function onGroupAppsChanged() { + updateDockApps(); + } + } + + // Initial update when component is ready + Component.onCompleted: { + if (ToplevelManager) { + updateDockApps(); + } + } + + // Refresh icons and names when DesktopEntries becomes available (or updates) + Connections { + target: DesktopEntries.applications + function onValuesChanged() { + root.iconRevision++; + root._desktopEntryIdCache = {}; + updateDockApps(); + } + } + + // Shared properties between peek and dock windows + readonly property string displayMode: Settings.data.dock.displayMode + readonly property bool autoHide: displayMode === "auto_hide" + readonly property bool exclusive: displayMode === "exclusive" + readonly property bool isAttachedMode: Settings.data.dock.dockType === "attached" + readonly property int hideDelay: 500 + readonly property int showDelay: 100 + readonly property int hideAnimationDuration: Math.max(0, Math.round(Style.animationFast / (Settings.data.dock.animationSpeed || 1.0))) + readonly property int showAnimationDuration: Math.max(0, Math.round(Style.animationFast / (Settings.data.dock.animationSpeed || 1.0))) + readonly property int peekThickness: 1 + readonly property int indicatorThickness: Settings.data.dock.indicatorThickness || 3 + readonly property string indicatorColorKey: Settings.data.dock.indicatorColor || "primary" + readonly property real indicatorOpacity: Settings.data.dock.indicatorOpacity !== undefined ? Settings.data.dock.indicatorOpacity : 0.6 + readonly property int iconSize: Math.round(12 + 24 * (Settings.data.dock.size ?? 1)) + readonly property int floatingMargin: Settings.data.dock.floatingRatio * Style.marginL + readonly property int maxWidth: modelData ? modelData.width * 0.8 : 1000 + readonly property int maxHeight: modelData ? modelData.height * 0.8 : 1000 + + // Dock position properties + readonly property string dockPosition: Settings.data.dock.position + readonly property bool isVertical: dockPosition === "left" || dockPosition === "right" + + // Bar detection and positioning properties + readonly property bool hasBar: modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0)) : false + readonly property bool barAtSameEdge: hasBar && Settings.getBarPositionForScreen(modelData?.name) === dockPosition + readonly property string barPosition: Settings.getBarPositionForScreen(modelData?.name) + readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" + readonly property bool barIsFramed: Settings.data.bar.barType === "framed" && hasBar + readonly property bool barFloating: Settings.data.bar.barType === "floating" + readonly property real barMarginH: barFloating ? Math.ceil(Settings.data.bar.marginHorizontal) : 0 + readonly property real barMarginV: barFloating ? Math.ceil(Settings.data.bar.marginVertical) : 0 + readonly property int barHeight: Style.getBarHeightForScreen(modelData?.name) + readonly property bool staticPanelOpen: { + if (!isAttachedMode) + return false; + var panel = getStaticDockPanel(); + if (panel && panel.isPanelOpen !== undefined) + return panel.isPanelOpen; + return false; + } + readonly property int peekEdgeLength: { + const edgeSize = isVertical ? Math.round(modelData?.height || maxHeight) : Math.round(modelData?.width || maxWidth); + const minLength = Math.max(1, Math.round(edgeSize * (Settings.data.dock.showDockIndicator ? 0.1 : 0.25))); + return Math.max(minLength, dockIndicatorLength); + } + readonly property int peekCenterOffsetX: { + if (isVertical) + return 0; + const edgeSize = Math.round(modelData?.width || maxWidth); + if (barIsVertical) { + if (barPosition === "left") { + const availableStart = (barIsFramed ? 0 : barMarginH) + barHeight; + const availableWidth = edgeSize - availableStart - (barIsFramed ? Settings.data.bar.frameThickness : 0); + return Math.max(0, Math.round(availableStart + (availableWidth - peekEdgeLength) / 2)); + } + if (barPosition === "right") { + const availableWidth = edgeSize - (barIsFramed ? 0 : barMarginH) - barHeight - (barIsFramed ? Settings.data.bar.frameThickness : 0); + return Math.max(0, Math.round((barIsFramed ? Settings.data.bar.frameThickness : 0) + (availableWidth - peekEdgeLength) / 2)); + } + } + return Math.max(0, Math.round((edgeSize - peekEdgeLength) / 2)); + } + readonly property int peekCenterOffsetY: { + if (!isVertical) + return 0; + const edgeSize = Math.round(modelData?.height || maxHeight); + if (!barIsVertical) { + if (barPosition === "top") { + const availableStart = (barIsFramed ? 0 : barMarginV) + barHeight; + const availableHeight = edgeSize - availableStart - (barIsFramed ? Settings.data.bar.frameThickness : 0); + return Math.max(0, Math.round(availableStart + (availableHeight - peekEdgeLength) / 2)); + } + if (barPosition === "bottom") { + const availableHeight = edgeSize - (barIsFramed ? 0 : barMarginV) - barHeight - (barIsFramed ? Settings.data.bar.frameThickness : 0); + return Math.max(0, Math.round((barIsFramed ? Settings.data.bar.frameThickness : 0) + (availableHeight - peekEdgeLength) / 2)); + } + } + return Math.max(0, Math.round((edgeSize - peekEdgeLength) / 2)); + } + readonly property bool showDockIndicator: { + if (!Settings.data.dock.showDockIndicator || (!autoHide && !isAttachedMode) || !hidden) + return false; + return !staticPanelOpen; + } + readonly property int dockItemCount: dockApps.length + (Settings.data.dock.showLauncherIcon ? 1 : 0) + readonly property bool indicatorVisible: showDockIndicator && dockIndicatorLength > 0 + readonly property int dockIndicatorLength: { + if (dockItemCount <= 0) + return 0; + const spacing = Style.marginS; + const layoutLength = (iconSize * dockItemCount) + (spacing * Math.max(0, dockItemCount - 1)); + const padded = layoutLength + Style.marginXL; + return Math.min(padded, isVertical ? maxHeight : maxWidth); + } + + // Shared state between windows + property bool dockHovered: false + property bool anyAppHovered: false + property bool menuHovered: false + property bool hidden: autoHide + property bool peekHovered: false + + // Separate property to control Loader - stays true during animations + property bool dockLoaded: !autoHide // Start loaded if autoHide is off + + // Track the currently open context menu + property var currentContextMenu: null + + // Combined model of running apps and pinned apps + property var dockApps: [] + property var groupCycleIndices: ({}) + + // Track the session order of apps (transient reordering) + property var sessionAppOrder: [] + + // Drag and Drop state for visual feedback + property int dragSourceIndex: -1 + property int dragTargetIndex: -1 + + // when dragging ended but the cursor is outside the dock area, restart the timer + onDragSourceIndexChanged: { + if (dragSourceIndex === -1) { + if (autoHide && !dockHovered && !anyAppHovered && !peekHovered && !menuHovered) { + hideTimer.restart(); + } + } + } + + // Revision counter to force icon re-evaluation + property int iconRevision: 0 + + // Function to close any open context menu + function closeAllContextMenus() { + if (currentContextMenu && currentContextMenu.visible) { + currentContextMenu.hide(); + } + } + + function getStaticDockPanel() { + return PanelService.getPanel("staticDockPanel", modelData, false); + } + + function getAppKey(appData) { + if (!appData) + return null; + + if (Settings.data.dock.groupApps) { + return appData.appId; + } + + // Use stable appId for pinned apps to maintain their slot regardless of running state + if (appData.type === "pinned" || appData.type === "pinned-running") { + return appData.appId; + } + + // prefer toplevel object identity for unpinned running apps to distinguish instances + if (appData.toplevel) + return appData.toplevel; + + // fallback to appId + return appData.appId; + } + + function sortDockApps(apps) { + if (!sessionAppOrder || sessionAppOrder.length === 0) { + return apps; + } + + const sorted = []; + const remaining = [...apps]; + + // Pick apps that are in the session order + for (let i = 0; i < sessionAppOrder.length; i++) { + const key = sessionAppOrder[i]; + + // Pick ALL matching apps (e.g. all instances of a pinned app) + while (true) { + const idx = remaining.findIndex(app => getAppKey(app) === key); + if (idx !== -1) { + sorted.push(remaining[idx]); + remaining.splice(idx, 1); + } else { + break; + } + } + } + + // Append any new/remaining apps + remaining.forEach(app => sorted.push(app)); + + return sorted; + } + + function reorderApps(fromIndex, toIndex) { + if (fromIndex === toIndex || fromIndex < 0 || toIndex < 0 || fromIndex >= dockApps.length || toIndex >= dockApps.length) + return; + + const list = [...dockApps]; + const item = list.splice(fromIndex, 1)[0]; + list.splice(toIndex, 0, item); + + dockApps = list; + sessionAppOrder = dockApps.map(getAppKey); + savePinnedOrder(); + } + + function savePinnedOrder() { + const currentPinned = Settings.data.dock.pinnedApps || []; + const newPinned = []; + const seen = new Set(); + + // Extract pinned apps in their current visual order + dockApps.forEach(app => { + if (app.appId && !seen.has(app.appId)) { + const isPinned = currentPinned.some(p => normalizeAppId(p) === normalizeAppId(app.appId)); + + if (isPinned) { + newPinned.push(app.appId); + seen.add(app.appId); + } + } + }); + + // Check if any pinned apps were missed (unlikely if dockApps is correct) + currentPinned.forEach(p => { + if (!seen.has(p)) { + newPinned.push(p); + seen.add(p); + } + }); + + if (JSON.stringify(currentPinned) !== JSON.stringify(newPinned)) { + Settings.data.dock.pinnedApps = newPinned; + } + } + + // Helper function to normalize app IDs for case-insensitive matching + function normalizeAppId(appId) { + if (!appId || typeof appId !== 'string') + return ""; + let id = appId.toLowerCase().trim(); + if (id.endsWith(".desktop")) + id = id.substring(0, id.length - 8); + return id; + } + + // Helper function to check if an app ID matches a pinned app (case-insensitive) + function isAppIdPinned(appId, pinnedApps) { + if (!appId || !pinnedApps || pinnedApps.length === 0) + return false; + const normalizedId = normalizeAppId(appId); + // Direct match + if (pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedId)) + return true; + // Resolve via desktop entry lookup (handles StartupWMClass != .desktop filename) + const resolved = resolveToDesktopEntryId(appId); + if (resolved !== appId) { + const normalizedResolved = normalizeAppId(resolved); + return pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedResolved); + } + return false; + } + + // Desktop entry ID resolution cache (cleared when DesktopEntries change) + property var _desktopEntryIdCache: ({}) + + // Resolve a toplevel appId to its canonical .desktop entry ID via heuristic lookup. + // This handles cases where the Wayland appId (e.g. "zen" from StartupWMClass) + // differs from the .desktop filename (e.g. "zen-browser-bin"). + function resolveToDesktopEntryId(appId) { + if (!appId) + return appId; + if (_desktopEntryIdCache.hasOwnProperty(appId)) + return _desktopEntryIdCache[appId]; + try { + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) { + const entry = DesktopEntries.heuristicLookup(appId); + if (entry && entry.id) { + _desktopEntryIdCache[appId] = entry.id; + return entry.id; + } + } + } catch (e) {} + _desktopEntryIdCache[appId] = appId; + return appId; + } + + // Helper function to get app name from desktop entry + function getAppNameFromDesktopEntry(appId) { + if (!appId) + return appId; + + try { + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) { + const entry = DesktopEntries.heuristicLookup(appId); + if (entry && entry.name) { + return entry.name; + } + } + + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.byId) { + const entry = DesktopEntries.byId(appId); + if (entry && entry.name) { + return entry.name; + } + } + } catch (e) + // Fall through to return original appId + {} + + // Return original appId if we can't find a desktop entry + return appId; + } + + function getToplevelsForEntry(appData) { + if (!appData) + return []; + + if (appData.toplevels && appData.toplevels.length > 0) { + return appData.toplevels.filter(toplevel => toplevel && (!Settings.data.dock.onlySameOutput || !toplevel.screens || toplevel.screens.includes(modelData))); + } + + if (!appData.toplevel) + return []; + + if (Settings.data.dock.onlySameOutput && appData.toplevel.screens && !appData.toplevel.screens.includes(modelData)) + return []; + + return [appData.toplevel]; + } + + function getPrimaryToplevelForEntry(appData) { + const toplevels = getToplevelsForEntry(appData); + if (toplevels.length === 0) + return null; + + if (ToplevelManager && ToplevelManager.activeToplevel && toplevels.includes(ToplevelManager.activeToplevel)) + return ToplevelManager.activeToplevel; + + return toplevels[0]; + } + + // Build grouped render model without mutating the raw toplevel list. + function buildGroupedDockApps(apps) { + if (!Settings.data.dock.groupApps) { + return apps.map(app => { + const entry = Object.assign({}, app); + entry.toplevels = getToplevelsForEntry(app); + return entry; + }); + } + + const grouped = []; + const groupedById = new Map(); + + apps.forEach(app => { + const appId = app.appId; + const toplevels = getToplevelsForEntry(app); + const existing = groupedById.get(appId); + + if (existing) { + toplevels.forEach(toplevel => { + if (!existing.toplevels.includes(toplevel)) { + existing.toplevels.push(toplevel); + } + }); + if (app.type === "pinned" || app.type === "pinned-running") { + existing.isPinned = true; + } + } else { + const entry = { + "type": app.type, + "appId": appId, + "title": app.title, + "toplevels": toplevels.slice(), + "isPinned": app.type === "pinned" || app.type === "pinned-running" + }; + grouped.push(entry); + groupedById.set(appId, entry); + } + }); + + grouped.forEach(entry => { + entry.toplevel = getPrimaryToplevelForEntry(entry); + if (entry.toplevels.length > 0 && entry.isPinned) { + entry.type = "pinned-running"; + } else if (entry.toplevels.length > 0) { + entry.type = "running"; + } else { + entry.type = "pinned"; + } + if (entry.toplevel && entry.toplevel.title && entry.toplevel.title.trim() !== "") { + entry.title = entry.toplevel.title; + } + }); + + return grouped; + } + + // Function to update the combined dock apps model + function updateDockApps() { + const runningApps = ToplevelManager ? (ToplevelManager.toplevels.values || []) : []; + const pinnedApps = Settings.data.dock.pinnedApps || []; + const combined = []; + const processedToplevels = new Set(); + const processedPinnedAppIds = new Set(); + + //push an app onto combined with the given appType + function pushApp(appType, toplevel, appId, title) { + // Use canonical ID for pinned apps to ensure key stability + const canonicalId = isAppIdPinned(appId, pinnedApps) ? (pinnedApps.find(p => normalizeAppId(p) === normalizeAppId(appId)) || appId) : appId; + + // For running apps, track by toplevel object to allow multiple instances + if (toplevel) { + if (processedToplevels.has(toplevel)) { + return; // Already processed this toplevel instance + } + if (Settings.data.dock.onlySameOutput && toplevel.screens && !toplevel.screens.includes(modelData)) { + return; // Filtered out by onlySameOutput setting + } + combined.push({ + "type": appType, + "toplevel": toplevel, + "toplevels": toplevel ? [toplevel] : [], + "appId": canonicalId, + "title": title + }); + processedToplevels.add(toplevel); + } else { + // For pinned apps that aren't running, track by appId to avoid duplicates + if (processedPinnedAppIds.has(canonicalId)) { + return; // Already processed this pinned app + } + combined.push({ + "type": appType, + "toplevel": toplevel, + "toplevels": [], + "appId": canonicalId, + "title": title + }); + processedPinnedAppIds.add(canonicalId); + } + } + + function pushRunning(first) { + runningApps.forEach(toplevel => { + if (toplevel) { + // Use robust matching to check if pinned + const isPinned = isAppIdPinned(toplevel.appId, pinnedApps); + if (!first && isPinned && processedToplevels.has(toplevel)) { + return; // Already added by pushPinned() + } + pushApp((first && isPinned) ? "pinned-running" : "running", toplevel, toplevel.appId, toplevel.title); + } + }); + } + + function pushPinned() { + pinnedApps.forEach(pinnedAppId => { + // Find all running instances of this pinned app using robust matching + // Also resolve toplevel appId via desktop entry lookup to handle + // StartupWMClass != .desktop filename (e.g. zen -> zen-browser-bin) + const normalizedPinned = normalizeAppId(pinnedAppId); + const matchingToplevels = runningApps.filter(app => { + if (!app) + return false; + if (normalizeAppId(app.appId) === normalizedPinned) + return true; + const resolved = resolveToDesktopEntryId(app.appId); + return resolved !== app.appId && normalizeAppId(resolved) === normalizedPinned; + }); + + if (matchingToplevels.length > 0) { + // Add all running instances as pinned-running + matchingToplevels.forEach(toplevel => { + pushApp("pinned-running", toplevel, pinnedAppId, toplevel.title); + }); + } else { + // App is pinned but not running - add once + pushApp("pinned", null, pinnedAppId, getAppNameFromDesktopEntry(pinnedAppId) || pinnedAppId); + } + }); + } + + //if pinnedStatic then push all pinned and then all remaining running apps + if (Settings.data.dock.pinnedStatic) { + pushPinned(); + pushRunning(false); + + //else add all running apps and then remaining pinned apps + } else { + pushRunning(true); + pushPinned(); + } + + const sortedApps = sortDockApps(combined); + dockApps = buildGroupedDockApps(sortedApps); + const cycleState = root.groupCycleIndices || {}; + const nextCycleState = {}; + dockApps.forEach(app => { + if (app && app.appId && cycleState[app.appId] !== undefined) { + nextCycleState[app.appId] = cycleState[app.appId]; + } + }); + root.groupCycleIndices = nextCycleState; + + // Sync session order if needed + // Instead of resetting everything when length changes, we reconcile the keys + if (!sessionAppOrder || sessionAppOrder.length === 0) { + sessionAppOrder = dockApps.map(getAppKey); + } else { + const currentKeys = new Set(dockApps.map(getAppKey)); + const existingKeys = new Set(); + const newOrder = []; + + // Keep existing keys that are still present + sessionAppOrder.forEach(key => { + if (currentKeys.has(key)) { + newOrder.push(key); + existingKeys.add(key); + } + }); + + // Add new keys at the end + dockApps.forEach(app => { + const key = getAppKey(app); + if (!existingKeys.has(key)) { + newOrder.push(key); + existingKeys.add(key); + } + }); + + if (JSON.stringify(newOrder) !== JSON.stringify(sessionAppOrder)) { + sessionAppOrder = newOrder; + } + } + } + + // Timer to unload dock after hide animation completes + Timer { + id: unloadTimer + interval: hideAnimationDuration + 50 // Add small buffer + onTriggered: { + if (hidden && autoHide) { + dockLoaded = false; + } + } + } + + property alias hideTimer: hideTimer + property alias showTimer: showTimer + property alias unloadTimer: unloadTimer + + // Timer for auto-hide delay + Timer { + id: hideTimer + interval: hideDelay + onTriggered: { + // do not hide if dragging + if (root.dragSourceIndex !== -1) { + return; + } + + // Force menuHovered to false if no menu is current or visible + if (!root.currentContextMenu || !root.currentContextMenu.visible) { + menuHovered = false; + } + if (autoHide && !dockHovered && !anyAppHovered && !peekHovered && !menuHovered) { + if (isAttachedMode) { + const panel = getStaticDockPanel(); + if (panel && (panel.menuHovered || (panel.currentContextMenu && panel.currentContextMenu.visible))) { + restart(); + return; + } + if (panel && (panel.isDockHovered || panel.dockHovered || panel.anyAppHovered)) { + restart(); + return; + } + if (panel) + panel.close(); + } else { + closeAllContextMenus(); + } + hidden = true; + unloadTimer.restart(); // Start unload timer when hiding + } else if (autoHide && !dockHovered && !peekHovered) { + // Restart timer if menu is closing (handles race condition) + restart(); + } + } + } + + // Timer for show delay + Timer { + id: showTimer + interval: showDelay + onTriggered: { + if (autoHide) { + if (!isAttachedMode) { + dockLoaded = true; // Load dock immediately + } + hidden = false; // Then trigger show animation + unloadTimer.stop(); // Cancel any pending unload + } + } + } + + // Watch for autoHide setting changes + onAutoHideChanged: { + if (!autoHide) { + hidden = false; + dockLoaded = true; + hideTimer.stop(); + showTimer.stop(); + unloadTimer.stop(); + } else { + hidden = true; + unloadTimer.restart(); // Schedule unload after animation + } + } + + // PEEK WINDOW โ€” only needed when dock can auto-hide or is in attached mode + Loader { + active: (autoHide || isAttachedMode) && (barIsReady || !hasBar) && modelData && (Settings.data.dock.monitors.length === 0 || Settings.data.dock.monitors.includes(modelData.name)) + + sourceComponent: PanelWindow { + id: peekWindow + + screen: modelData + // Dynamic anchors based on dock position + anchors.top: dockPosition === "top" || isVertical + anchors.bottom: dockPosition === "bottom" + anchors.left: dockPosition === "left" || !isVertical + anchors.right: dockPosition === "right" + focusable: false + color: "transparent" + + margins.top: peekCenterOffsetY + margins.left: peekCenterOffsetX + + WlrLayershell.namespace: "noctalia-dock-peek-" + (screen?.name || "unknown") + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.exclusionMode: ExclusionMode.Ignore + // Larger peek area when bar is at same edge, normal 1px otherwise + implicitHeight: isVertical ? peekEdgeLength : peekThickness + implicitWidth: isVertical ? peekThickness : peekEdgeLength + + MouseArea { + id: peekArea + anchors.fill: parent + hoverEnabled: true + + onEntered: { + peekHovered = true; + if (isAttachedMode) { + if (dockItemCount <= 0) + return; + const panel = getStaticDockPanel(); + if (panel && !panel.isPanelOpen) + panel.open(); + return; + } + if (hidden) { + showTimer.start(); + } + } + + onExited: { + peekHovered = false; + showTimer.stop(); + if (isAttachedMode) { + // Start hideTimer which checks panel.isDockHovered before closing + if (!dockHovered && !anyAppHovered && !menuHovered) { + hideTimer.restart(); + } + } else if (!hidden && !dockHovered && !anyAppHovered && !menuHovered) { + hideTimer.restart(); + } + } + } + } + } + + // DOCK INDICATOR WINDOW โ€” only needed when dock can auto-hide/attach and indicator is enabled + Loader { + active: (autoHide || isAttachedMode) && Settings.data.dock.showDockIndicator && (barIsReady || !hasBar) && modelData && (Settings.data.dock.monitors.length === 0 || Settings.data.dock.monitors.includes(modelData.name)) + + sourceComponent: PanelWindow { + id: dockIndicatorWindow + + screen: modelData + // Dynamic anchors based on dock position + anchors.top: dockPosition === "top" || isVertical + anchors.bottom: dockPosition === "bottom" + anchors.left: dockPosition === "left" || !isVertical + anchors.right: dockPosition === "right" + focusable: false + color: "transparent" + + margins.top: peekCenterOffsetY + margins.left: peekCenterOffsetX + + WlrLayershell.namespace: "noctalia-dock-indicator-" + (screen?.name || "unknown") + WlrLayershell.layer: WlrLayer.Top + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + implicitHeight: isVertical ? peekEdgeLength : indicatorThickness + implicitWidth: isVertical ? indicatorThickness : peekEdgeLength + + // Hide the window surface when indicator is not visible, so the compositor + // can skip compositing this layer-shell surface entirely (saves GPU on NVIDIA) + visible: indicatorRect.opacity > 0 || indicatorVisible + + Rectangle { + id: indicatorRect + anchors.fill: parent + radius: indicatorThickness + color: Qt.alpha(Color.resolveColorKey(indicatorColorKey), indicatorOpacity) + opacity: indicatorVisible ? 1 : 0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutQuad + } + } + } + } + } + + // Force dock reload when position changes to fix anchor/layout issues + // Force dock reload when position/mode changes to fix anchor/layout issues + property bool _reloading: false + function handleReload() { + if (!autoHide && dockLoaded && !_reloading) { + _reloading = true; + // Brief unload/reload cycle to reset layout + Qt.callLater(() => { + dockLoaded = false; + Qt.callLater(() => { + dockLoaded = true; + _reloading = false; + }); + }); + } + } + + onDockPositionChanged: handleReload() + onExclusiveChanged: handleReload() + + Loader { + id: dockWindowLoader + active: Settings.data.dock.enabled && !isAttachedMode && (barIsReady || !hasBar) && modelData && (Settings.data.dock.monitors.length === 0 || Settings.data.dock.monitors.includes(modelData.name)) && dockLoaded && ToplevelManager && (dockApps.length > 0) + + sourceComponent: PanelWindow { + id: dockWindow + + screen: modelData + + focusable: false + color: "transparent" + + WlrLayershell.namespace: "noctalia-dock-" + (screen?.name || "unknown") + WlrLayershell.exclusionMode: exclusive ? ExclusionMode.Auto : ExclusionMode.Ignore + + // Slide animation: content slides inside a fixed window, no margin animation. + // Only reserve extra space for sliding when auto-hide is enabled + property int slideDistance: autoHide ? ((isVertical ? dockContainerWrapper.contentWidth : dockContainerWrapper.contentHeight) + floatingMargin + 10) : 0 + property real slideOffset: hidden ? slideDistance : 0 + + Behavior on slideOffset { + NumberAnimation { + duration: hidden ? hideAnimationDuration : showAnimationDuration + easing.type: hidden ? Easing.InCubic : Easing.OutCubic + } + } + + // Signed slide: positive pushes content toward its edge (off-screen) + readonly property real slideX: dockPosition === "left" ? -slideOffset : dockPosition === "right" ? slideOffset : 0 + readonly property real slideY: dockPosition === "top" ? -slideOffset : dockPosition === "bottom" ? slideOffset : 0 + + // Blur behind dock โ€” offset by slide so it follows the content + BackgroundEffect.blurRegion: Settings.data.general.enableBlurBehind ? dockBlurRegion : null + Region { + id: dockBlurRegion + Region { + x: Math.round(dockContainerWrapper.x + dockContent.dockContainer.x + dockWindow.slideX) + y: Math.round(dockContainerWrapper.y + dockContent.dockContainer.y + dockWindow.slideY) + width: Math.round(dockContent.dockContainer.width) + height: Math.round(dockContent.dockContainer.height) + radius: Style.radiusL + } + } + + // Window sized to fit content + slide distance so content can slide off-edge. + // When auto-hide is disabled, slideDistance is 0 so the window (and thus + // the exclusion zone) matches the dock content size. + implicitWidth: dockContainerWrapper.width + (isVertical ? slideDistance : 0) + implicitHeight: dockContainerWrapper.height + (!isVertical ? slideDistance : 0) + + // Position based on dock setting + anchors.top: dockPosition === "top" + anchors.bottom: dockPosition === "bottom" + anchors.left: dockPosition === "left" + anchors.right: dockPosition === "right" + + // Static margins โ€” no animation, window stays put + margins.top: dockPosition === "top" ? (barAtSameEdge && !exclusive ? barHeight + (barFloating ? Settings.data.bar.marginVertical : 0) + floatingMargin : floatingMargin) : 0 + margins.bottom: dockPosition === "bottom" ? (barAtSameEdge && !exclusive ? barHeight + (barFloating ? Settings.data.bar.marginVertical : 0) + floatingMargin : floatingMargin) : 0 + margins.left: dockPosition === "left" ? (barAtSameEdge && !exclusive ? barHeight + (barFloating ? Settings.data.bar.marginHorizontal : 0) + floatingMargin : floatingMargin) : 0 + margins.right: dockPosition === "right" ? (barAtSameEdge && !exclusive ? barHeight + (barFloating ? Settings.data.bar.marginHorizontal : 0) + floatingMargin : floatingMargin) : 0 + + // Container wrapper for animations + Item { + id: dockContainerWrapper + + // Helper properties for orthogonal bar detection + readonly property string screenBarPosition: Settings.getBarPositionForScreen(modelData?.name) + readonly property bool barOnLeft: hasBar && screenBarPosition === "left" && !barFloating + readonly property bool barOnRight: hasBar && screenBarPosition === "right" && !barFloating + readonly property bool barOnTop: hasBar && screenBarPosition === "top" && !barFloating + readonly property bool barOnBottom: hasBar && screenBarPosition === "bottom" && !barFloating + + // Calculate padding needed to shift center to match exclusive mode + readonly property int extraTop: (isVertical && !exclusive && barOnTop) ? barHeight : 0 + readonly property int extraBottom: (isVertical && !exclusive && barOnBottom) ? barHeight : 0 + readonly property int extraLeft: (!isVertical && !exclusive && barOnLeft) ? barHeight : 0 + readonly property int extraRight: (!isVertical && !exclusive && barOnRight) ? barHeight : 0 + + // Expose content size for window sizing (before slide padding) + readonly property int contentWidth: dockContent.dockContainer.width + extraLeft + extraRight + 2 + readonly property int contentHeight: dockContent.dockContainer.height + extraTop + extraBottom + 2 + + // Add +2 buffer for fractional scaling issues + width: contentWidth + height: contentHeight + + anchors.horizontalCenter: isVertical ? undefined : parent.horizontalCenter + anchors.verticalCenter: isVertical ? parent.verticalCenter : undefined + + anchors.top: dockPosition === "top" ? parent.top : undefined + anchors.bottom: dockPosition === "bottom" ? parent.bottom : undefined + anchors.left: dockPosition === "left" ? parent.left : undefined + anchors.right: dockPosition === "right" ? parent.right : undefined + + // Slide content inside the fixed window + transform: Translate { + x: dockWindow.slideX + y: dockWindow.slideY + } + + // Enable layer caching to reduce GPU usage from continuous animations + layer.enabled: true + + DockContent { + id: dockContent + anchors.fill: parent + dockRoot: root + extraTop: dockContainerWrapper.extraTop + extraBottom: dockContainerWrapper.extraBottom + extraLeft: dockContainerWrapper.extraLeft + extraRight: dockContainerWrapper.extraRight + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Dock/DockContent.qml b/arch/.config/quickshell/noctalia-shell/Modules/Dock/DockContent.qml new file mode 100644 index 0000000..09b4953 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Dock/DockContent.qml @@ -0,0 +1,895 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import Quickshell.Widgets +import qs.Commons +import qs.Services.Compositor +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +Item { + required property var dockRoot + required property int extraTop + required property int extraBottom + required property int extraLeft + required property int extraRight + property alias dockContainer: dockContainer + readonly property bool isAttachedMode: Settings.data.dock.dockType === "attached" + readonly property string tooltipDirection: dockRoot.dockPosition === "left" ? "right" : (dockRoot.dockPosition === "right" ? "left" : (dockRoot.dockPosition === "top" ? "bottom" : "top")) + + Rectangle { + id: dockContainer + // For vertical dock, swap width and height logic + width: dockRoot.isVertical ? Math.round(dockRoot.iconSize * 1.5) : Math.min(dockLayout.implicitWidth + Style.marginXL, dockRoot.maxWidth) + height: dockRoot.isVertical ? Math.min(dockLayout.implicitHeight + Style.marginXL, dockRoot.maxHeight) : Math.round(dockRoot.iconSize * 1.5) + color: Qt.alpha(Color.mSurface, (isAttachedMode ? 0 : Color.adaptiveOpacity(Settings.data.dock.backgroundOpacity))) + + // Anchor based on padding to achieve centering shift + anchors.horizontalCenter: extraLeft > 0 || extraRight > 0 ? undefined : parent.horizontalCenter + anchors.right: extraLeft > 0 ? parent.right : undefined + anchors.left: extraRight > 0 ? parent.left : undefined + + anchors.verticalCenter: extraTop > 0 || extraBottom > 0 ? undefined : parent.verticalCenter + anchors.bottom: extraTop > 0 ? parent.bottom : undefined + anchors.top: extraBottom > 0 ? parent.top : undefined + + radius: Style.radiusL + border.width: Style.borderS + border.color: Qt.alpha(Color.mOutline, (isAttachedMode ? 0 : Color.adaptiveOpacity(Settings.data.dock.backgroundOpacity))) + + MouseArea { + id: dockMouseArea + anchors.fill: parent + hoverEnabled: true + + onEntered: { + dockRoot.dockHovered = true; + if (dockRoot.autoHide) { + dockRoot.showTimer.stop(); + dockRoot.hideTimer.stop(); + dockRoot.unloadTimer.stop(); // Cancel unload if hovering + dockRoot.hidden = false; // Make sure dock is visible + } + } + + onExited: { + dockRoot.dockHovered = false; + if (dockRoot.autoHide && !dockRoot.anyAppHovered && !dockRoot.peekHovered && !dockRoot.menuHovered && dockRoot.dragSourceIndex === -1) { + dockRoot.hideTimer.restart(); + } + } + + onClicked: { + // Close any open context menu when clicking on the dock background + dockRoot.closeAllContextMenus(); + } + } + + Flickable { + id: dock + // Use parent dimensions more directly to avoid clipping + width: dockRoot.isVertical ? parent.width : Math.min(dockLayout.implicitWidth, parent.width - Style.marginXL) + height: !dockRoot.isVertical ? parent.height : Math.min(dockLayout.implicitHeight, parent.height - Style.marginXL) + contentWidth: dockLayout.implicitWidth + contentHeight: dockLayout.implicitHeight + anchors.centerIn: parent + clip: true + + flickableDirection: dockRoot.isVertical ? Flickable.VerticalFlick : Flickable.HorizontalFlick + + // Keep interactive dependent on overflow + interactive: dockRoot.isVertical ? contentHeight > height : contentWidth > width + + // Centering margins + contentX: dockRoot.isVertical && contentWidth < width ? (contentWidth - width) / 2 : 0 + contentY: !dockRoot.isVertical && contentHeight < height ? (contentHeight - height) / 2 : 0 + + WheelHandler { + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onWheel: event => { + var delta = (event.angleDelta.y !== 0) ? event.angleDelta.y : event.angleDelta.x; + if (dockRoot.isVertical) { + dock.contentY = Math.max(-dock.topMargin, Math.min(dock.contentHeight - dock.height + dock.bottomMargin, dock.contentY - delta)); + } else { + // For horizontal dock, we want to scroll contentX with BOTH x and y wheels + var hDelta = (event.angleDelta.x !== 0) ? event.angleDelta.x : event.angleDelta.y; + dock.contentX = Math.max(-dock.leftMargin, Math.min(dock.contentWidth - dock.width + dock.rightMargin, dock.contentX - hDelta)); + } + event.accepted = true; + } + } + + ScrollBar.horizontal: ScrollBar { + visible: !dockRoot.isVertical && dock.interactive + policy: ScrollBar.AsNeeded + } + ScrollBar.vertical: ScrollBar { + visible: dockRoot.isVertical && dock.interactive + policy: ScrollBar.AsNeeded + } + + function getAppIcon(appData): string { + if (!appData || !appData.appId) + return ""; + return ThemeIcons.iconForAppId(appData.appId?.toLowerCase()); + } + + function getValidToplevels(appData) { + if (!appData || !ToplevelManager || !ToplevelManager.toplevels) + return []; + const source = appData.toplevels && appData.toplevels.length > 0 ? appData.toplevels : (appData.toplevel ? [appData.toplevel] : []); + const allToplevels = ToplevelManager.toplevels.values || []; + return source.filter(toplevel => toplevel && allToplevels.includes(toplevel)); + } + + function getPrimaryToplevel(appData) { + const toplevels = getValidToplevels(appData); + if (toplevels.length === 0) + return null; + if (ToplevelManager && ToplevelManager.activeToplevel && toplevels.includes(ToplevelManager.activeToplevel)) + return ToplevelManager.activeToplevel; + return toplevels[0]; + } + + function launchAppById(appId) { + if (!appId) + return; + + const app = ThemeIcons.findAppEntry(appId); + if (!app) { + Logger.w("Dock", `Could not find desktop entry for pinned app: ${appId}`); + return; + } + + if (Settings.data.appLauncher.customLaunchPrefixEnabled && Settings.data.appLauncher.customLaunchPrefix) { + const prefix = Settings.data.appLauncher.customLaunchPrefix.split(" "); + + if (app.runInTerminal) { + const terminal = Settings.data.appLauncher.terminalCommand.split(" "); + const command = prefix.concat(terminal.concat(app.command)); + Quickshell.execDetached(command); + } else { + const command = prefix.concat(app.command); + Quickshell.execDetached(command); + } + } else { + if (app.runInTerminal) { + Logger.d("Dock", "Executing terminal app manually: " + app.name); + const terminal = Settings.data.appLauncher.terminalCommand.split(" "); + const command = terminal.concat(app.command); + CompositorService.spawn(command); + } else if (app.command && app.command.length > 0) { + CompositorService.spawn(app.command); + } else if (app.execute) { + app.execute(); + } else { + Logger.w("Dock", `Could not launch: ${app.name}. No valid launch method.`); + } + } + } + + // Use GridLayout for flexible horizontal/vertical arrangement + GridLayout { + id: dockLayout + columns: dockRoot.isVertical ? 1 : -1 + rows: dockRoot.isVertical ? -1 : 1 + rowSpacing: Style.marginS + columnSpacing: Style.marginS + + // Ensure the layout takes its full implicit size + width: implicitWidth + height: implicitHeight + + Component { + id: launcherButtonComponent + + Item { + id: launcherButton + anchors.fill: parent + readonly property string screenName: dockRoot.modelData ? dockRoot.modelData.name : (dockRoot.screen ? dockRoot.screen.name : "") + readonly property var launcherWidgetSettings: { + const widgetsBySection = screenName ? Settings.getBarWidgetsForScreen(screenName) : Settings.data.bar.widgets; + if (!widgetsBySection) + return {}; + const sections = ["left", "center", "right"]; + for (let i = 0; i < sections.length; i++) { + const sectionWidgets = widgetsBySection[sections[i]] || []; + for (let j = 0; j < sectionWidgets.length; j++) { + const widget = sectionWidgets[j]; + if (widget && widget.id === "Launcher") + return widget; + } + } + return {}; + } + readonly property string launcherWidgetSection: { + const widgetsBySection = screenName ? Settings.getBarWidgetsForScreen(screenName) : Settings.data.bar.widgets; + if (!widgetsBySection) + return ""; + const sections = ["left", "center", "right"]; + for (let i = 0; i < sections.length; i++) { + const sectionWidgets = widgetsBySection[sections[i]] || []; + for (let j = 0; j < sectionWidgets.length; j++) { + const widget = sectionWidgets[j]; + if (widget && widget.id === "Launcher") + return sections[i]; + } + } + return ""; + } + readonly property int launcherWidgetIndex: { + const widgetsBySection = screenName ? Settings.getBarWidgetsForScreen(screenName) : Settings.data.bar.widgets; + if (!widgetsBySection) + return -1; + const sections = ["left", "center", "right"]; + for (let i = 0; i < sections.length; i++) { + const sectionWidgets = widgetsBySection[sections[i]] || []; + for (let j = 0; j < sectionWidgets.length; j++) { + const widget = sectionWidgets[j]; + if (widget && widget.id === "Launcher") + return j; + } + } + return -1; + } + readonly property var launcherMetadata: BarWidgetRegistry.widgetMetadata["Launcher"] + readonly property string launcherIcon: { + if (Settings.data.dock.launcherIcon !== undefined && Settings.data.dock.launcherIcon !== "") + return Settings.data.dock.launcherIcon; + if (launcherWidgetSettings.icon !== undefined && launcherWidgetSettings.icon !== "") + return launcherWidgetSettings.icon; + return (launcherMetadata && launcherMetadata.icon) ? launcherMetadata.icon : "search"; + } + readonly property string launcherIconColorKey: { + if (Settings.data.dock.launcherIconColor !== undefined) + return Settings.data.dock.launcherIconColor; + if (launcherWidgetSettings.iconColor !== undefined) + return launcherWidgetSettings.iconColor; + if (launcherMetadata && launcherMetadata.iconColor !== undefined) + return launcherMetadata.iconColor; + return "none"; + } + readonly property bool launcherUseDistroLogo: { + if (Settings.data.dock.launcherUseDistroLogo !== undefined) + return Settings.data.dock.launcherUseDistroLogo; + if (launcherWidgetSettings.useDistroLogo !== undefined) + return launcherWidgetSettings.useDistroLogo; + if (launcherMetadata && launcherMetadata.useDistroLogo !== undefined) + return launcherMetadata.useDistroLogo; + return false; + } + + Item { + id: launcherIconContainer + width: dockRoot.iconSize + height: dockRoot.iconSize + anchors.centerIn: parent + + scale: launcherMouseArea.containsMouse ? 1.15 : 1.0 + Behavior on scale { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutBack + easing.overshoot: 1.2 + } + } + + NIcon { + anchors.centerIn: parent + icon: launcherButton.launcherIcon + pointSize: dockRoot.iconSize * 0.7 + color: Color.resolveColorKey(launcherButton.launcherIconColorKey) + visible: !launcherButton.launcherUseDistroLogo + } + + IconImage { + anchors.centerIn: parent + width: dockRoot.iconSize * 0.8 + height: width + source: launcherButton.launcherUseDistroLogo ? HostService.osLogo : "" + visible: source !== "" + smooth: true + asynchronous: true + layer.enabled: visible + layer.effect: ShaderEffect { + property color targetColor: Color.resolveColorKey(launcherButton.launcherIconColorKey) + property real colorizeMode: 2.0 + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } + } + } + + MouseArea { + id: launcherMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton + + onEntered: { + dockRoot.anyAppHovered = true; + TooltipService.show(launcherButton, I18n.tr("actions.open-launcher"), tooltipDirection); + if (dockRoot.autoHide) { + dockRoot.showTimer.stop(); + dockRoot.hideTimer.stop(); + dockRoot.unloadTimer.stop(); + dockRoot.hidden = false; + } + } + + onExited: { + dockRoot.anyAppHovered = false; + TooltipService.hide(); + if (dockRoot.autoHide && !dockRoot.dockHovered && !dockRoot.peekHovered && !dockRoot.menuHovered && dockRoot.dragSourceIndex === -1) { + dockRoot.hideTimer.restart(); + } + } + + onClicked: mouse => { + const targetScreen = dockRoot.modelData || dockRoot.screen || null; + if (!targetScreen) { + return; + } + + if (mouse.button === Qt.RightButton) { + if (dockRoot.currentContextMenu === launcherContextMenu && launcherContextMenu.visible) { + dockRoot.closeAllContextMenus(); + return; + } + dockRoot.closeAllContextMenus(); + TooltipService.hideImmediately(); + launcherContextMenu.show(launcherButton, null, targetScreen); + return; + } + + if (mouse.button === Qt.LeftButton || mouse.button === Qt.MiddleButton) { + dockRoot.closeAllContextMenus(); + PanelService.toggleLauncher(targetScreen); + } + } + } + + DockMenu { + id: launcherContextMenu + dockPosition: dockRoot.dockPosition + menuMode: "launcher" + launcherWidgetSection: launcherButton.launcherWidgetSection + launcherWidgetIndex: launcherButton.launcherWidgetIndex + launcherWidgetSettings: launcherButton.launcherWidgetSettings + + onHoveredChanged: { + if (dockRoot.currentContextMenu === launcherContextMenu && launcherContextMenu.visible) { + dockRoot.menuHovered = hovered; + } else { + dockRoot.menuHovered = false; + } + } + + Connections { + target: launcherContextMenu + function onRequestClose() { + dockRoot.currentContextMenu = null; + dockRoot.hideTimer.stop(); + launcherContextMenu.hide(); + dockRoot.menuHovered = false; + dockRoot.anyAppHovered = false; + } + } + + onVisibleChanged: { + if (visible) { + dockRoot.currentContextMenu = launcherContextMenu; + } else if (dockRoot.currentContextMenu === launcherContextMenu) { + dockRoot.currentContextMenu = null; + dockRoot.hideTimer.stop(); + dockRoot.menuHovered = false; + if (dockRoot.autoHide && !dockRoot.dockHovered && !dockRoot.anyAppHovered && !dockRoot.peekHovered && !dockRoot.menuHovered) { + dockRoot.hideTimer.restart(); + } + } + } + } + } + } + + Loader { + id: launcherButtonStart + active: Settings.data.dock.showLauncherIcon && Settings.data.dock.launcherPosition === "start" + visible: active + sourceComponent: launcherButtonComponent + readonly property real indicatorMargin: Math.max(3, Math.round(dockRoot.iconSize * 0.18)) + Layout.preferredWidth: active ? (dockRoot.isVertical ? dockRoot.iconSize + indicatorMargin * 2 : dockRoot.iconSize) : 0 + Layout.preferredHeight: active ? (dockRoot.isVertical ? dockRoot.iconSize : dockRoot.iconSize + indicatorMargin * 2) : 0 + Layout.minimumWidth: active ? Layout.preferredWidth : 0 + Layout.minimumHeight: active ? Layout.preferredHeight : 0 + Layout.maximumWidth: active ? Layout.preferredWidth : 0 + Layout.maximumHeight: active ? Layout.preferredHeight : 0 + Layout.alignment: Qt.AlignCenter + } + + Repeater { + model: dockRoot.dockApps + + delegate: Item { + id: appButton + readonly property real indicatorMargin: Math.max(3, Math.round(dockRoot.iconSize * 0.18)) + Layout.preferredWidth: dockRoot.isVertical ? dockRoot.iconSize + indicatorMargin * 2 : dockRoot.iconSize + Layout.preferredHeight: dockRoot.isVertical ? dockRoot.iconSize : dockRoot.iconSize + indicatorMargin * 2 + Layout.alignment: Qt.AlignCenter + + property var toplevels: dock.getValidToplevels(modelData) + property bool isActive: ToplevelManager && ToplevelManager.activeToplevel && toplevels.includes(ToplevelManager.activeToplevel) + property bool hovered: appMouseArea.containsMouse + property string appId: modelData ? modelData.appId : "" + property int groupedCount: toplevels.length + property int focusedWindowIndex: { + if (!ToplevelManager || !ToplevelManager.activeToplevel) + return -1; + return toplevels.indexOf(ToplevelManager.activeToplevel); + } + property string groupedIndicatorText: focusedWindowIndex >= 0 ? (focusedWindowIndex + 1) + "/" + groupedCount : groupedCount.toString() + property string appTitle: { + if (!modelData) + return ""; + const primaryToplevel = dock.getPrimaryToplevel(modelData); + if (primaryToplevel) { + const toplevelTitle = primaryToplevel.title || ""; + // If title is "Loading..." or empty, use desktop entry name + if (!toplevelTitle || toplevelTitle === "Loading..." || toplevelTitle.trim() === "") { + return dockRoot.getAppNameFromDesktopEntry(modelData.appId) || modelData.appId; + } + return toplevelTitle; + } + // For pinned apps that aren't running, use the stored title + return modelData.title || modelData.appId || ""; + } + property bool isRunning: toplevels.length > 0 + readonly property bool baseIndicatorVisible: Settings.data.dock.inactiveIndicators ? isRunning : isActive + // Grouped indicators should be visible whenever grouped windows are running, even if none is focused. + readonly property bool showGroupedIndicator: Settings.data.dock.groupApps && groupedCount > 1 && isRunning + + // Store index for drag-and-drop + property int modelIndex: index + objectName: "dockAppButton" + + DropArea { + anchors.fill: parent + keys: ["dock-app"] + onEntered: function (drag) { + if (drag.source && drag.source.objectName === "dockAppButton") { + dockRoot.dragTargetIndex = appButton.modelIndex; + } + } + onExited: function () { + if (dockRoot.dragTargetIndex === appButton.modelIndex) { + dockRoot.dragTargetIndex = -1; + } + } + onDropped: function (drop) { + dockRoot.dragSourceIndex = -1; + dockRoot.dragTargetIndex = -1; + if (drop.source && drop.source.objectName === "dockAppButton" && drop.source !== appButton) { + dockRoot.reorderApps(drop.source.modelIndex, appButton.modelIndex); + } + } + } + + // Listen for the toplevel being closed + Connections { + target: modelData?.toplevel + function onClosed() { + Qt.callLater(dockRoot.updateDockApps); + } + } + + // Draggable container for the icon + Item { + id: iconContainer + width: dockRoot.iconSize + height: dockRoot.iconSize + + // When dragging, remove anchors so MouseArea can position it + anchors.centerIn: dragging ? undefined : parent + + property bool dragging: appMouseArea.drag.active + onDraggingChanged: { + if (dragging) { + dockRoot.dragSourceIndex = index; + } else { + // Reset if not handled by drop (e.g. dropped outside) + Qt.callLater(() => { + if (!appMouseArea.drag.active && dockRoot.dragSourceIndex === index) { + dockRoot.dragSourceIndex = -1; + dockRoot.dragTargetIndex = -1; + } + }); + } + } + + Drag.active: dragging + Drag.source: appButton + Drag.hotSpot.x: width / 2 + Drag.hotSpot.y: height / 2 + Drag.keys: ["dock-app"] + + z: (dockRoot.dragSourceIndex === index) ? 1000 : ((dragging ? 1000 : 0)) + scale: dragging ? 1.1 : (appButton.hovered ? 1.15 : 1.0) + Behavior on scale { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutBack + easing.overshoot: 1.2 + } + } + + // Visual shifting logic + readonly property bool isDragged: dockRoot.dragSourceIndex === index + property real shiftOffset: 0 + + Binding on shiftOffset { + value: { + if (dockRoot.dragSourceIndex !== -1 && dockRoot.dragTargetIndex !== -1 && !iconContainer.isDragged) { + if (dockRoot.dragSourceIndex < dockRoot.dragTargetIndex) { + // Dragging Forward: Items between source and target shift Backward + if (index > dockRoot.dragSourceIndex && index <= dockRoot.dragTargetIndex) { + return -1 * (dockRoot.isVertical ? dockRoot.iconSize + Style.marginS : dockRoot.iconSize + Style.marginS); + } + } else if (dockRoot.dragSourceIndex > dockRoot.dragTargetIndex) { + // Dragging Backward: Items between target and source shift Forward + if (index >= dockRoot.dragTargetIndex && index < dockRoot.dragSourceIndex) { + return (dockRoot.isVertical ? dockRoot.iconSize + Style.marginS : dockRoot.iconSize + Style.marginS); + } + } + } + return 0; + } + } + + transform: Translate { + x: !dockRoot.isVertical ? iconContainer.shiftOffset : 0 + y: dockRoot.isVertical ? iconContainer.shiftOffset : 0 + + Behavior on x { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutQuad + } + } + Behavior on y { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutQuad + } + } + } + + IconImage { + id: appIcon + anchors.fill: parent + source: { + dockRoot.iconRevision; // Force re-evaluation when revision changes + return dock.getAppIcon(modelData); + } + visible: source.toString() !== "" + smooth: true + asynchronous: true + + // Dim pinned apps that aren't running + opacity: appButton.isRunning ? 1.0 : Settings.data.dock.deadOpacity + + // Apply dock-specific colorization shader only to non-focused apps + layer.enabled: !appButton.isActive && Settings.data.dock.colorizeIcons + layer.effect: ShaderEffect { + property color targetColor: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mSurfaceVariant + property real colorizeMode: 0.0 // Dock mode (grayscale) + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutQuad + } + } + } + + // Fall back if no icon + NIcon { + anchors.centerIn: parent + visible: !appIcon.visible + icon: "question-mark" + pointSize: dockRoot.iconSize * 0.7 + color: appButton.isActive ? Color.mPrimary : Color.mOnSurfaceVariant + opacity: appButton.isRunning ? 1.0 : 0.6 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutQuad + } + } + } + } + + // Context menu popup + DockMenu { + id: contextMenu + dockPosition: dockRoot.dockPosition // Pass dock position for menu placement + onHoveredChanged: { + // Only update menuHovered if this menu is current and visible + if (dockRoot.currentContextMenu === contextMenu && contextMenu.visible) { + dockRoot.menuHovered = hovered; + } else { + dockRoot.menuHovered = false; + } + } + + Connections { + target: contextMenu + function onRequestClose() { + // Clear current menu immediately to prevent hover updates + dockRoot.currentContextMenu = null; + dockRoot.hideTimer.stop(); + contextMenu.hide(); + dockRoot.menuHovered = false; + dockRoot.anyAppHovered = false; + } + } + onAppClosed: dockRoot.updateDockApps // Force immediate dock update when app is closed + onVisibleChanged: { + if (visible) { + dockRoot.currentContextMenu = contextMenu; + } else if (dockRoot.currentContextMenu === contextMenu) { + dockRoot.currentContextMenu = null; + dockRoot.hideTimer.stop(); + dockRoot.menuHovered = false; + // Restart hide timer after menu closes + if (dockRoot.autoHide && !dockRoot.dockHovered && !dockRoot.anyAppHovered && !dockRoot.peekHovered && !dockRoot.menuHovered) { + dockRoot.hideTimer.restart(); + } + } + } + } + + MouseArea { + id: appMouseArea + objectName: "appMouseArea" + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton + + // Only allow left-click dragging via axis control + drag.target: iconContainer + drag.axis: (pressedButtons & Qt.LeftButton) ? (dockRoot.isVertical ? Drag.YAxis : Drag.XAxis) : Drag.None + + onPressed: { + var p1 = appButton.mapFromItem(dockContainer, 0, 0); + var p2 = appButton.mapFromItem(dockContainer, dockContainer.width, dockContainer.height); + drag.minimumX = p1.x; + drag.maximumX = p2.x - iconContainer.width; + drag.minimumY = p1.y; + drag.maximumY = p2.y - iconContainer.height; + } + + onReleased: { + if (iconContainer.Drag.active) { + iconContainer.Drag.drop(); + } + } + + onEntered: { + dockRoot.anyAppHovered = true; + const appName = appButton.appTitle || appButton.appId || "Unknown"; + const tooltipText = appName.length > 40 ? appName.substring(0, 37) + "..." : appName; + if (!contextMenu.visible) { + TooltipService.show(appButton, tooltipText, tooltipDirection); + } + if (dockRoot.autoHide) { + dockRoot.showTimer.stop(); + dockRoot.hideTimer.stop(); + dockRoot.unloadTimer.stop(); // Cancel unload if hovering app + dockRoot.hidden = false; // Make sure dock is visible + } + } + + onExited: { + dockRoot.anyAppHovered = false; + TooltipService.hide(); + // Clear menuHovered if no current menu or menu not visible + if (!dockRoot.currentContextMenu || !dockRoot.currentContextMenu.visible) { + dockRoot.menuHovered = false; + } + if (dockRoot.autoHide && !dockRoot.dockHovered && !dockRoot.peekHovered && !dockRoot.menuHovered && dockRoot.dragSourceIndex === -1) { + dockRoot.hideTimer.restart(); + } + } + + onClicked: mouse => { + if (mouse.button === Qt.RightButton) { + const targetScreen = dockRoot.modelData || dockRoot.screen || null; + // If right-clicking on the same app with an open context menu, close it + if (dockRoot.currentContextMenu === contextMenu && contextMenu.visible) { + dockRoot.closeAllContextMenus(); + return; + } + // Close any other existing context menu first + dockRoot.closeAllContextMenus(); + // Hide tooltip when showing context menu + TooltipService.hideImmediately(); + contextMenu.show(appButton, modelData, targetScreen); + return; + } + + // Close any existing context menu for non-right-click actions + dockRoot.closeAllContextMenus(); + + const runningToplevels = dock.getValidToplevels(modelData); + const primaryToplevel = dock.getPrimaryToplevel(modelData); + + if (mouse.button === Qt.MiddleButton) { + if (primaryToplevel && primaryToplevel.close) { + primaryToplevel.close(); + Qt.callLater(dockRoot.updateDockApps); + } + } else if (mouse.button === Qt.LeftButton) { + if (runningToplevels.length === 0) { + dock.launchAppById(modelData?.appId); + return; + } + + if (!Settings.data.dock.groupApps || runningToplevels.length <= 1) { + if (primaryToplevel && primaryToplevel.activate) { + primaryToplevel.activate(); + } + return; + } + + const clickAction = Settings.data.dock.groupClickAction || "cycle"; + if (clickAction === "list") { + const targetScreen = dockRoot.modelData || dockRoot.screen || null; + TooltipService.hideImmediately(); + // Left-click list should always open the grouped window list view. + contextMenu.show(appButton, modelData, targetScreen, "list"); + } else { + const appKey = modelData?.appId || ""; + const state = dockRoot.groupCycleIndices || {}; + const nextIndex = (state[appKey] || 0) % runningToplevels.length; + const nextToplevel = runningToplevels[nextIndex]; + if (nextToplevel && nextToplevel.activate) { + nextToplevel.activate(); + } + state[appKey] = (nextIndex + 1) % runningToplevels.length; + dockRoot.groupCycleIndices = Object.assign({}, state); + } + } + } + } + + // Active indicator - positioned at the edge of the delegate area + Rectangle { + visible: baseIndicatorVisible && !showGroupedIndicator + width: dockRoot.isVertical ? indicatorMargin * 0.6 : dockRoot.iconSize * 0.2 + height: dockRoot.isVertical ? dockRoot.iconSize * 0.2 : indicatorMargin * 0.6 + color: Color.mPrimary + radius: Style.radiusXS + + // Anchor to the edge facing the screen center + anchors.bottom: !dockRoot.isVertical && dockRoot.dockPosition === "bottom" ? parent.bottom : undefined + anchors.top: !dockRoot.isVertical && dockRoot.dockPosition === "top" ? parent.top : undefined + anchors.left: dockRoot.isVertical && dockRoot.dockPosition === "left" ? parent.left : undefined + anchors.right: dockRoot.isVertical && dockRoot.dockPosition === "right" ? parent.right : undefined + + anchors.horizontalCenter: dockRoot.isVertical ? undefined : parent.horizontalCenter + anchors.verticalCenter: dockRoot.isVertical ? parent.verticalCenter : undefined + + // Offset slightly from the edge + anchors.bottomMargin: !dockRoot.isVertical && dockRoot.dockPosition === "bottom" ? 2 : 0 + anchors.topMargin: !dockRoot.isVertical && dockRoot.dockPosition === "top" ? 2 : 0 + anchors.leftMargin: dockRoot.isVertical && dockRoot.dockPosition === "left" ? 2 : 0 + anchors.rightMargin: dockRoot.isVertical && dockRoot.dockPosition === "right" ? 2 : 0 + } + + Loader { + id: groupedIndicatorLoader + active: showGroupedIndicator + anchors.bottom: !dockRoot.isVertical && dockRoot.dockPosition === "bottom" ? parent.bottom : undefined + anchors.top: !dockRoot.isVertical && dockRoot.dockPosition === "top" ? parent.top : undefined + anchors.left: dockRoot.isVertical && dockRoot.dockPosition === "left" ? parent.left : undefined + anchors.right: dockRoot.isVertical && dockRoot.dockPosition === "right" ? parent.right : undefined + anchors.horizontalCenter: dockRoot.isVertical ? undefined : parent.horizontalCenter + anchors.verticalCenter: dockRoot.isVertical ? parent.verticalCenter : undefined + anchors.bottomMargin: !dockRoot.isVertical && dockRoot.dockPosition === "bottom" ? 1 : 0 + anchors.topMargin: !dockRoot.isVertical && dockRoot.dockPosition === "top" ? 1 : 0 + anchors.leftMargin: dockRoot.isVertical && dockRoot.dockPosition === "left" ? 1 : 0 + anchors.rightMargin: dockRoot.isVertical && dockRoot.dockPosition === "right" ? 1 : 0 + + sourceComponent: Settings.data.dock.groupIndicatorStyle === "dots" ? groupDotsIndicatorComponent : groupNumberIndicatorComponent + } + + Component { + id: groupNumberIndicatorComponent + Rectangle { + radius: Style.radiusS + color: Qt.alpha(Color.mSurface, 0.9) + border.color: Qt.alpha(Color.mOutline, 0.7) + border.width: Style.borderS + width: Math.max(14, numberLabel.implicitWidth + Style.marginXS) + height: Math.max(10, numberLabel.implicitHeight + 2) + + NText { + id: numberLabel + anchors.centerIn: parent + text: appButton.groupedIndicatorText + pointSize: Style.fontSizeXS + color: appButton.focusedWindowIndex >= 0 ? Color.mPrimary : Color.mOnSurfaceVariant + } + } + } + + Component { + id: groupDotsIndicatorComponent + Item { + readonly property int maxVisibleDots: 5 + readonly property int totalCount: Math.max(0, appButton.groupedCount) + readonly property int focusedIndex: appButton.focusedWindowIndex >= 0 ? appButton.focusedWindowIndex : 0 + readonly property int visibleCount: Math.min(totalCount, maxVisibleDots) + readonly property int dotSize: Math.max(2, Math.round(dockRoot.iconSize * 0.1)) + readonly property int dotSpacing: Math.max(1, Math.round(dotSize * 0.7)) + readonly property int pitch: dotSize + dotSpacing + readonly property int windowStart: { + if (totalCount <= maxVisibleDots) + return 0; + const centeredStart = focusedIndex - Math.floor(maxVisibleDots / 2); + const maxStart = totalCount - maxVisibleDots; + return Math.max(0, Math.min(maxStart, centeredStart)); + } + readonly property bool hasHiddenLeft: windowStart > 0 + readonly property bool hasHiddenRight: (windowStart + visibleCount) < totalCount + width: dockRoot.isVertical ? dotSize : (visibleCount * dotSize + Math.max(0, visibleCount - 1) * dotSpacing) + height: dockRoot.isVertical ? (visibleCount * dotSize + Math.max(0, visibleCount - 1) * dotSpacing) : dotSize + + Repeater { + model: parent.visibleCount + delegate: Rectangle { + readonly property int absoluteIndex: parent.windowStart + index + readonly property bool isFocusedDot: appButton.focusedWindowIndex >= 0 && absoluteIndex === appButton.focusedWindowIndex + readonly property bool isOverflowHint: (index === 0 && parent.hasHiddenLeft) || (index === parent.visibleCount - 1 && parent.hasHiddenRight) + width: isOverflowHint && !isFocusedDot ? Math.max(2, Math.round(parent.dotSize * 0.72)) : parent.dotSize + height: width + radius: width / 2 + x: dockRoot.isVertical ? Math.round((parent.dotSize - width) / 2) : (index * parent.pitch + Math.round((parent.dotSize - width) / 2)) + y: dockRoot.isVertical ? (index * parent.pitch + Math.round((parent.dotSize - width) / 2)) : Math.round((parent.dotSize - width) / 2) + color: isFocusedDot ? Color.mPrimary : Qt.alpha(Color.mOutline, 0.9) + opacity: isOverflowHint && !isFocusedDot ? 0.55 : 1.0 + } + } + } + } + } + } + + Loader { + id: launcherButtonEnd + active: Settings.data.dock.showLauncherIcon && Settings.data.dock.launcherPosition === "end" + visible: active + sourceComponent: launcherButtonComponent + readonly property real indicatorMargin: Math.max(3, Math.round(dockRoot.iconSize * 0.18)) + Layout.preferredWidth: active ? (dockRoot.isVertical ? dockRoot.iconSize + indicatorMargin * 2 : dockRoot.iconSize) : 0 + Layout.preferredHeight: active ? (dockRoot.isVertical ? dockRoot.iconSize : dockRoot.iconSize + indicatorMargin * 2) : 0 + Layout.minimumWidth: active ? Layout.preferredWidth : 0 + Layout.minimumHeight: active ? Layout.preferredHeight : 0 + Layout.maximumWidth: active ? Layout.preferredWidth : 0 + Layout.maximumHeight: active ? Layout.preferredHeight : 0 + Layout.alignment: Qt.AlignCenter + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Dock/DockMenu.qml b/arch/.config/quickshell/noctalia-shell/Modules/Dock/DockMenu.qml new file mode 100644 index 0000000..2146b34 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Dock/DockMenu.qml @@ -0,0 +1,804 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import Quickshell.Widgets +import qs.Commons +import qs.Modules.Panels.Settings +import qs.Services.UI +import qs.Widgets + +PopupWindow { + id: root + + property var toplevel: null + property var appData: null + property Item anchorItem: null + property ShellScreen targetScreen: null + + property string menuMode: "app" // "app" or "launcher" + property string launcherWidgetSection: "" + property int launcherWidgetIndex: -1 + property var launcherWidgetSettings: ({}) + + property bool hovered: menuHoverHandler.hovered + property var onAppClosed: null // Callback function for when an app is closed + property bool canAutoClose: false + + // Track which menu item is hovered + property int hoveredItem: -1 // -1: none, otherwise the index of the item in `items` + + property var items: [] + + signal requestClose + + property real menuContentWidth: 160 + property real menuMinWidth: 120 + property real menuMaxWidth: 360 + property real menuMaxHeight: Math.max(180, Math.min(420, Math.round((targetScreen ? targetScreen.height : 600) * 0.3))) + property int separatorCompactHeight: Style.borderS + Style.margin2S + property string forcedGroupMenuMode: "" + readonly property int separatorIndex: { + for (let i = 0; i < root.items.length; i++) { + if (root.items[i] && root.items[i].separator === true) + return i; + } + return -1; + } + readonly property bool splitExtendedLayout: separatorIndex >= 0 + readonly property var scrollItems: splitExtendedLayout ? root.items.slice(0, separatorIndex) : root.items + readonly property var fixedItems: splitExtendedLayout ? root.items.slice(separatorIndex + 1) : [] + readonly property real menuInnerHeight: Math.max(0, implicitHeight - Style.margin2M) + readonly property real fixedActionsHeight: listHeight(fixedItems) + readonly property real separatorBlockHeight: splitExtendedLayout ? separatorCompactHeight : 0 + readonly property real scrollAreaHeight: splitExtendedLayout ? Math.max(0, menuInnerHeight - fixedActionsHeight - separatorBlockHeight) : menuInnerHeight + readonly property bool listOverflowing: menuFlick && menuFlick.contentHeight > menuFlick.height + readonly property real menuBodyHeight: { + if (splitExtendedLayout) { + return listHeight(scrollItems) + separatorBlockHeight + fixedActionsHeight; + } + return listHeight(root.items); + } + + implicitWidth: menuContentWidth + Style.margin2M + implicitHeight: Math.min(menuBodyHeight + Style.margin2M, menuMaxHeight) + color: "transparent" + visible: false + + // Hidden text element for measuring text width + NText { + id: textMeasure + visible: false + pointSize: Style.fontSizeS + family: "Sans Serif" // Match your NText font if different + wrapMode: Text.NoWrap + elide: Text.ElideNone + } + + // Calculate the maximum width needed for all menu items + function calculateMenuWidth() { + let maxWidth = 0; // Start with 0, we'll apply minimum later + if (root.items && root.items.length > 0) { + for (let i = 0; i < root.items.length; i++) { + const item = root.items[i]; + if (item && item.text) { + // Calculate width: margins + icon (if present) + spacing + text width + let itemWidth = Style.margin2S; // left and right margins + + if (item.icon && item.icon !== "") { + itemWidth += Style.fontSizeL + Style.marginS; // icon + spacing + } + + // Measure actual text width + textMeasure.text = item.text; + const textWidth = textMeasure.contentWidth; + itemWidth += textWidth; + + if (itemWidth > maxWidth) { + maxWidth = itemWidth; + } + } + } + } + // Keep menu readable without allowing extremely wide labels. + menuContentWidth = Math.max(menuMinWidth, Math.min(menuMaxWidth, Math.ceil(maxWidth))); + } + + function getCurrentAppId() { + return appData?.appId || toplevel?.appId || ""; + } + + function getValidToplevels() { + if (!ToplevelManager || !ToplevelManager.toplevels) + return []; + const source = appData?.toplevels && appData.toplevels.length > 0 ? appData.toplevels : (toplevel ? [toplevel] : []); + const allToplevels = ToplevelManager.toplevels.values || []; + return source.filter(window => window && allToplevels.includes(window)); + } + + function getPrimaryToplevel() { + const windows = getValidToplevels(); + if (windows.length === 0) + return null; + if (ToplevelManager && ToplevelManager.activeToplevel && windows.includes(ToplevelManager.activeToplevel)) + return ToplevelManager.activeToplevel; + return windows[0]; + } + + function isItemActionable(index) { + if (index < 0 || index >= root.items.length) + return false; + const item = root.items[index]; + return item && typeof item.action === "function"; + } + + function rowHeightForItem(item) { + return item && item.separator === true ? 16 : 32; + } + + function listHeight(items) { + let total = 0; + if (!items) + return total; + for (let i = 0; i < items.length; i++) { + total += rowHeightForItem(items[i]); + } + return total; + } + + function initItems() { + if (menuMode === "launcher") { + root.items = [ + { + "icon": "adjustments", + "text": I18n.tr("actions.dock-settings"), + "action": function () { + handleDockSettings(); + } + }, + { + "icon": "adjustments", + "text": I18n.tr("actions.launcher-settings"), + "action": function () { + handleLauncherSettings(); + } + } + ]; + calculateMenuWidth(); + return; + } + + const windows = getValidToplevels(); + const primaryToplevel = getPrimaryToplevel(); + const appId = getCurrentAppId(); + const isRunning = windows.length > 0; + const isPinned = isAppPinned(appId); + const grouped = Settings.data.dock.groupApps && windows.length > 1; + const rawGroupMenuMode = forcedGroupMenuMode || Settings.data.dock.groupContextMenuMode || "extended"; + const menuModeForGroup = grouped ? ((rawGroupMenuMode === "list" || rawGroupMenuMode === "extended") ? rawGroupMenuMode : "extended") : "single"; + + var next = []; + + if (!grouped || menuModeForGroup === "single") { + if (isRunning) { + next.push({ + "icon": "eye", + "text": I18n.tr("common.focus"), + "action": function () { + handleFocus(primaryToplevel); + } + }); + } + + next.push({ + "icon": !isPinned ? "pin" : "unpin", + "text": !isPinned ? I18n.tr("common.pin") : I18n.tr("common.unpin"), + "action": function () { + handlePin(appId); + } + }); + + if (isRunning) { + next.push({ + "icon": "close", + "text": I18n.tr("common.close"), + "action": function () { + handleClose(primaryToplevel); + } + }); + } + } else { + windows.forEach((window, index) => { + const windowTitle = (window.title && window.title.trim() !== "") ? window.title : (appId || ("Window " + (index + 1))); + next.push({ + "icon": window === ToplevelManager?.activeToplevel ? "circle-filled" : "square-rounded", + "text": windowTitle, + "action": function () { + handleFocus(window); + } + }); + }); + + if (menuModeForGroup === "extended") { + next.push({ + "separator": true + }); + next.push({ + "icon": "eye", + "text": I18n.tr("common.focus"), + "action": function () { + handleFocus(primaryToplevel); + } + }); + next.push({ + "icon": !isPinned ? "pin" : "unpin", + "text": !isPinned ? I18n.tr("common.pin") : I18n.tr("common.unpin"), + "action": function () { + handlePin(appId); + } + }); + next.push({ + "icon": "close", + "text": I18n.tr("common.close") + " All", + "action": function () { + handleCloseAll(windows); + } + }); + } + } + + // Keep grouped list mode as a clean window switcher. + const canAddDesktopActions = !grouped || menuModeForGroup === "extended"; + + // Create a menu entry for each app-specific action defined in its .desktop file + if (canAddDesktopActions && typeof DesktopEntries !== 'undefined' && DesktopEntries.byId && appId) { + const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId); + if (entry != null) { + entry.actions.forEach(function (action) { + next.push({ + "icon": "chevron-right", + "text": action.name, + "action": function () { + if (action.command && action.command.length > 0) { + Quickshell.execDetached(action.command); + } else if (action.execute) { + action.execute(); + } + if (Settings.data.dock.dockType === "attached") { + const panel = PanelService.getPanel("staticDockPanel", root.screen, false); + if (panel) + panel.close(); + } + } + }); + }); + } + } + + root.items = next; + // Force width recalculation when items change + calculateMenuWidth(); + } + + // Helper function to normalize app IDs for case-insensitive matching + function normalizeAppId(appId) { + if (!appId || typeof appId !== 'string') + return ""; + return appId.toLowerCase().trim(); + } + + // Helper function to get desktop entry ID from an app ID + function getDesktopEntryId(appId) { + if (!appId) + return appId; + + // Try to find the desktop entry using heuristic lookup + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) { + try { + const entry = DesktopEntries.heuristicLookup(appId); + if (entry && entry.id) { + return entry.id; + } + } catch (e) + // Fall through to return original appId + {} + } + + // Try direct lookup + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.byId) { + try { + const entry = DesktopEntries.byId(appId); + if (entry && entry.id) { + return entry.id; + } + } catch (e) + // Fall through to return original appId + {} + } + + // Return original appId if we can't find a desktop entry + return appId; + } + + // Helper functions for pin/unpin functionality + function isAppPinned(appId) { + if (!appId) + return false; + const pinnedApps = Settings.data.dock.pinnedApps || []; + const normalizedId = normalizeAppId(appId); + return pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedId); + } + + function toggleAppPin(appId) { + if (!appId) + return; + + // Get the desktop entry ID for consistent pinning + const desktopEntryId = getDesktopEntryId(appId); + const normalizedId = normalizeAppId(desktopEntryId); + + let pinnedApps = (Settings.data.dock.pinnedApps || []).slice(); // Create a copy + + // Find existing pinned app with case-insensitive matching + const existingIndex = pinnedApps.findIndex(pinnedId => normalizeAppId(pinnedId) === normalizedId); + const isPinned = existingIndex >= 0; + + if (isPinned) { + // Unpin: remove from array + pinnedApps.splice(existingIndex, 1); + } else { + // Pin: add desktop entry ID to array + pinnedApps.push(desktopEntryId); + } + + // Update the settings + Settings.data.dock.pinnedApps = pinnedApps; + } + + // Dock position for context menu placement + property string dockPosition: "bottom" + + anchor.item: anchorItem + // Position menu on opposite side of dock with comfortable spacing + anchor.rect.x: { + if (!anchorItem) + return 0; + switch (dockPosition) { + case "left": + return anchorItem.width + Style.marginL; // Open to right of dock + case "right": + return -implicitWidth - Style.marginL; // Open to left of dock + default: + return (anchorItem.width - implicitWidth) / 2; // Center horizontally + } + } + anchor.rect.y: { + if (!anchorItem) + return 0; + switch (dockPosition) { + case "top": + return anchorItem.height + Style.marginL; // Open below dock + case "bottom": + return -implicitHeight - Style.marginL; // Open above dock (default) + case "left": + case "right": + return (anchorItem.height - implicitHeight) / 2; // Center vertically + default: + return -implicitHeight - Style.marginL; + } + } + + function show(item, toplevelData, screen, groupModeOverride) { + if (!item) { + return; + } + + // First hide completely + visible = false; + + // Then set up new data + anchorItem = item; + if (toplevelData && typeof toplevelData === "object" && (toplevelData.appId !== undefined || toplevelData.toplevels !== undefined)) { + appData = toplevelData; + toplevel = toplevelData.toplevel || null; + } else { + appData = toplevelData ? { + "appId": toplevelData.appId, + "toplevel": toplevelData, + "toplevels": toplevelData ? [toplevelData] : [] + } : null; + toplevel = toplevelData; + } + targetScreen = screen || null; + forcedGroupMenuMode = groupModeOverride || ""; + initItems(); + + visible = true; + canAutoClose = false; + gracePeriodTimer.restart(); + } + + // Helper function to determine which menu item is under the mouse + function getHoveredItem(mouseY) { + const startY = Style.marginM; + const localY = mouseY - startY; + + if (localY < 0) + return -1; + + function findIndexInList(items, relativeY, baseIndex) { + let offset = 0; + for (let i = 0; i < items.length; i++) { + const h = rowHeightForItem(items[i]); + if (relativeY >= offset && relativeY < offset + h) + return baseIndex + i; + offset += h; + } + return -1; + } + + if (splitExtendedLayout) { + if (localY < scrollAreaHeight) { + return findIndexInList(scrollItems, localY + (menuFlick ? menuFlick.contentY : 0), 0); + } + if (localY < scrollAreaHeight + separatorBlockHeight) { + return -1; + } + return findIndexInList(fixedItems, localY - scrollAreaHeight - separatorBlockHeight, separatorIndex + 1); + } else { + return findIndexInList(scrollItems, localY + (menuFlick ? menuFlick.contentY : 0), 0); + } + } + + function fixedItemGlobalIndex(localIndex) { + if (!splitExtendedLayout) + return localIndex; + return separatorIndex + 1 + localIndex; + } + + function isScrollableHovered(mouseY) { + const localY = mouseY - Style.marginM; + return localY >= 0 && localY < scrollAreaHeight; + } + + function onWheelScroll(deltaY) { + if (!menuFlick || menuFlick.contentHeight <= menuFlick.height) + return; + const nextY = menuFlick.contentY - deltaY; + menuFlick.contentY = Math.max(0, Math.min(nextY, menuFlick.contentHeight - menuFlick.height)); + } + + function resetMenuState() { + root.items.length = 0; + root.appData = null; + root.toplevel = null; + root.forcedGroupMenuMode = ""; + menuContentWidth = menuMinWidth; + hoveredItem = -1; + if (menuFlick) + menuFlick.contentY = 0; + } + + function hide() { + visible = false; + resetMenuState(); + } + + function hideWithoutReset() { + visible = false; + } + + function closeAndReset() { + hide(); + root.requestClose(); + } + + function handleFocus(targetToplevel) { + if (targetToplevel?.activate) { + targetToplevel.activate(); + } + closeAndReset(); + } + + function handlePin(appId) { + if (appId) { + root.toggleAppPin(appId); + } + Qt.callLater(() => closeAndReset()); + } + + function handleClose(targetToplevel) { + const isValidToplevel = targetToplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(targetToplevel); + + if (isValidToplevel && targetToplevel.close) { + targetToplevel.close(); + if (root.onAppClosed && typeof root.onAppClosed === "function") { + Qt.callLater(root.onAppClosed); + } + } + closeAndReset(); + } + + function handleCloseAll(windows) { + windows.forEach(window => { + if (window && ToplevelManager && ToplevelManager.toplevels.values.includes(window) && window.close) { + window.close(); + } + }); + if (root.onAppClosed && typeof root.onAppClosed === "function") { + Qt.callLater(root.onAppClosed); + } + closeAndReset(); + } + + function handleLauncherSettings() { + if (targetScreen) { + var panel = PanelService.getPanel("settingsPanel", targetScreen); + panel.requestedTab = SettingsPanel.Tab.Launcher; + panel.toggle(); + } + closeAndReset(); + } + + function handleDockSettings() { + if (targetScreen) { + var panel = PanelService.getPanel("settingsPanel", targetScreen); + panel.requestedTab = SettingsPanel.Tab.Dock; + panel.toggle(); + } + closeAndReset(); + } + + function handleLauncherWidgetSettings() { + if (targetScreen && launcherWidgetSection && launcherWidgetIndex >= 0) { + BarService.openWidgetSettings(targetScreen, launcherWidgetSection, launcherWidgetIndex, "Launcher", launcherWidgetSettings || {}); + } + closeAndReset(); + } + + // Short delay to ignore spurious events + Timer { + id: gracePeriodTimer + interval: 1500 + repeat: false + onTriggered: { + root.canAutoClose = true; + if (!menuHoverHandler.hovered) { + closeTimer.start(); + } + } + } + + Timer { + id: closeTimer + interval: 500 + repeat: false + running: false + onTriggered: { + root.hideWithoutReset(); + } + } + + Rectangle { + anchors.fill: parent + anchors.margins: border.width + color: Color.mSurfaceVariant + radius: Style.radiusS + border.color: Color.mOutline + border.width: Style.borderS + + HoverHandler { + id: menuHoverHandler + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onHoveredChanged: { + if (hovered) { + closeTimer.stop(); + } else { + root.hoveredItem = -1; + if (root.canAutoClose) { + closeTimer.start(); + } + } + } + } + + WheelHandler { + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onWheel: event => { + if (!root.isScrollableHovered(event.y)) + return; + const delta = event.pixelDelta.y !== 0 ? event.pixelDelta.y : event.angleDelta.y / 2; + root.onWheelScroll(delta); + event.accepted = true; + } + } + + Flickable { + id: menuFlick + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.leftMargin: Style.marginM + anchors.rightMargin: Style.marginM + anchors.topMargin: Style.marginM + height: root.scrollAreaHeight + clip: true + contentWidth: width + contentHeight: scrollColumn.height + flickableDirection: Flickable.VerticalFlick + boundsBehavior: Flickable.StopAtBounds + interactive: contentHeight > height + + ScrollBar.vertical: ScrollBar { + id: menuScrollBar + policy: ScrollBar.AsNeeded + visible: root.listOverflowing + interactive: true + hoverEnabled: true + } + + Column { + id: scrollColumn + width: menuFlick.width + spacing: 0 + + Repeater { + model: root.scrollItems + + Rectangle { + readonly property bool isSeparator: modelData && modelData.separator === true + width: scrollColumn.width + height: root.rowHeightForItem(modelData) + color: (!isSeparator && root.hoveredItem === index) ? Color.mHover : "transparent" + radius: Style.radiusXS + + Row { + id: rowLayout + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: Style.marginS + anchors.rightMargin: Style.marginS + spacing: Style.marginS + visible: !isSeparator + + NIcon { + icon: modelData.icon + pointSize: Style.fontSizeL + color: root.hoveredItem === index ? Color.mOnHover : Color.mOnSurfaceVariant + visible: icon !== "" + anchors.verticalCenter: parent.verticalCenter + } + + NText { + text: modelData.text + pointSize: Style.fontSizeS + color: root.hoveredItem === index ? Color.mOnHover : Color.mOnSurfaceVariant + anchors.verticalCenter: parent.verticalCenter + width: rowLayout.width - ((modelData.icon && modelData.icon !== "") ? (Style.fontSizeL + Style.marginS) : 0) + elide: Text.ElideRight + } + } + + MouseArea { + anchors.fill: parent + enabled: !parent.isSeparator && root.isItemActionable(index) + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton + + onEntered: { + root.hoveredItem = index; + } + + onExited: { + if (root.hoveredItem === index) { + root.hoveredItem = -1; + } + } + + onClicked: { + if (root.isItemActionable(index)) { + root.items[index].action.call(); + } + } + } + } + } + } + } + + Rectangle { + id: separator + visible: root.splitExtendedLayout + anchors.left: parent.left + anchors.right: parent.right + anchors.top: menuFlick.bottom + anchors.leftMargin: Style.marginS + anchors.rightMargin: Style.marginS + anchors.topMargin: Style.marginS + height: Style.borderS + color: Qt.alpha(Color.mOutline, 0.7) + radius: Style.radiusXS + } + + Column { + id: fixedColumn + visible: root.splitExtendedLayout && root.fixedItems.length > 0 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.leftMargin: Style.marginM + anchors.rightMargin: Style.marginM + anchors.topMargin: Style.marginS + anchors.bottomMargin: Style.marginM + anchors.top: separator.bottom + spacing: 0 + + Repeater { + model: root.fixedItems + + Rectangle { + id: fixedItemRect + readonly property int globalIndex: root.fixedItemGlobalIndex(index) + width: fixedColumn.width + height: root.rowHeightForItem(modelData) + color: root.hoveredItem === globalIndex ? Color.mHover : "transparent" + radius: Style.radiusXS + + Row { + id: fixedRowLayout + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: Style.marginS + anchors.rightMargin: Style.marginS + spacing: Style.marginS + + NIcon { + icon: modelData.icon + pointSize: Style.fontSizeL + color: root.hoveredItem === fixedItemRect.globalIndex ? Color.mOnHover : Color.mOnSurfaceVariant + visible: icon !== "" + anchors.verticalCenter: parent.verticalCenter + } + + NText { + text: modelData.text + pointSize: Style.fontSizeS + color: root.hoveredItem === fixedItemRect.globalIndex ? Color.mOnHover : Color.mOnSurfaceVariant + anchors.verticalCenter: parent.verticalCenter + width: fixedRowLayout.width - ((modelData.icon && modelData.icon !== "") ? (Style.fontSizeL + Style.marginS) : 0) + elide: Text.ElideRight + } + } + + MouseArea { + anchors.fill: parent + enabled: root.isItemActionable(parent.globalIndex) + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton + + onEntered: { + root.hoveredItem = parent.globalIndex; + } + + onExited: { + if (root.hoveredItem === parent.globalIndex) { + root.hoveredItem = -1; + } + } + + onClicked: { + if (root.isItemActionable(parent.globalIndex)) { + root.items[parent.globalIndex].action.call(); + } + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/LockScreen/LockContext.qml b/arch/.config/quickshell/noctalia-shell/Modules/LockScreen/LockContext.qml new file mode 100644 index 0000000..40a6b35 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/LockScreen/LockContext.qml @@ -0,0 +1,170 @@ +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Services.Pam +import qs.Commons +import qs.Services.System + +Scope { + id: root + signal unlocked + signal failed + + property string currentText: "" + property bool waitingForPassword: false + property bool unlockInProgress: false + property bool showFailure: false + property bool showInfo: false + property string errorMessage: "" + property string infoMessage: "" + + readonly property string pamConfigDirectory: "/etc/pam.d" + property string pamConfig: Quickshell.env("NOCTALIA_PAM_SERVICE") || "login" + property bool pamReady: false + + Component.onCompleted: { + if (Quickshell.env("NOCTALIA_PAM_SERVICE")) { + Logger.i("LockContext", "NOCTALIA_PAM_SERVICE is set, using system PAM config: /etc/pam.d/" + pamConfig); + pamReady = true; + } else { + Logger.i("LockContext", "Probing for best PAM service..."); + detectPamServiceProc.running = true; + } + } + + Process { + id: detectPamServiceProc + command: ["sh", "-c", " + if [ -f /etc/pam.d/login ]; then echo 'login'; exit 0; fi; + if [ -f /etc/pam.d/system-auth ]; then echo 'system-auth'; exit 0; fi; + if [ -f /etc/pam.d/common-auth ]; then echo 'common-auth'; exit 0; fi; + echo 'login'; + "] + stdout: StdioCollector { + onStreamFinished: { + const service = String(text || "").trim(); + if (service.length > 0) { + root.pamConfig = service; + Logger.i("LockContext", "Detected PAM service: " + service); + } else { + Logger.w("LockContext", "Failed to detect PAM service, defaulting to login"); + } + root.pamReady = true; + } + } + stderr: StdioCollector {} + } + + onPamReadyChanged: { + if (pamReady) { + if (Settings.data.general.autoStartAuth && currentText === "") { + pam.start(); + } + } + } + + onShowInfoChanged: { + if (showInfo) { + showFailure = false; + } + } + + onShowFailureChanged: { + if (showFailure) { + showInfo = false; + } + } + + onCurrentTextChanged: { + if (currentText !== "") { + showInfo = false; + showFailure = false; + if (!waitingForPassword) { + pam.abort(); + } + if (Settings.data.general.allowPasswordWithFprintd) { + occupyFingerprintSensorProc.running = true; + } + } else { + occupyFingerprintSensorProc.running = false; + if (pamReady && Settings.data.general.autoStartAuth) { + pam.start(); + } + } + } + + function tryUnlock() { + if (!pamReady) { + Logger.w("LockContext", "PAM not ready yet, ignoring unlock attempt"); + return; + } + + if (waitingForPassword) { + pam.respond(currentText); + unlockInProgress = true; + waitingForPassword = false; + showInfo = false; + return; + } + + Logger.i("LockContext", "Starting PAM authentication for user:", pam.user); + pam.start(); + } + + Process { + id: occupyFingerprintSensorProc + command: ["fprintd-verify"] + } + + PamContext { + id: pam + configDirectory: root.pamConfigDirectory + config: root.pamConfig + user: HostService.username + + onPamMessage: { + Logger.i("LockContext", "PAM message:", message, "isError:", messageIsError, "responseRequired:", responseRequired); + + if (this.responseRequired) { + Logger.i("LockContext", "Responding to PAM with password"); + if (root.currentText !== "") { + this.respond(root.currentText); + unlockInProgress = true; + } else { + root.waitingForPassword = true; + infoMessage = I18n.tr("lock-screen.password"); + showInfo = true; + } + } else if (messageIsError) { + errorMessage = message; + showFailure = true; + } else { + infoMessage = message; + showInfo = true; + } + } + + onCompleted: result => { + Logger.i("LockContext", "PAM completed with result:", result); + if (result === PamResult.Success) { + Logger.i("LockContext", "Authentication successful"); + root.unlocked(); + } else { + Logger.i("LockContext", "Authentication failed"); + root.currentText = ""; + errorMessage = I18n.tr("authentication.failed"); + showFailure = true; + root.failed(); + } + root.unlockInProgress = false; + } + + onError: { + Logger.i("LockContext", "PAM error:", error, "message:", message); + errorMessage = message || "Authentication error"; + showFailure = true; + root.unlockInProgress = false; + root.failed(); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/LockScreen/LockScreen.qml b/arch/.config/quickshell/noctalia-shell/Modules/LockScreen/LockScreen.qml new file mode 100644 index 0000000..88698b5 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/LockScreen/LockScreen.qml @@ -0,0 +1,387 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.Pam +import Quickshell.Wayland +import qs.Commons +import qs.Services.Compositor +import qs.Services.Hardware +import qs.Services.Keyboard +import qs.Services.Media +import qs.Services.UI +import qs.Widgets + +Loader { + id: root + active: false + + // Track if the visualizer should be shown (lockscreen active + media playing + non-compact mode) + readonly property bool needsSpectrum: root.active && !Settings.data.general.compactLockScreen && Settings.data.audio.visualizerType !== "" && Settings.data.audio.visualizerType !== "none" + + onActiveChanged: { + if (root.active && root.needsSpectrum) { + SpectrumService.registerComponent("lockscreen"); + } else { + SpectrumService.unregisterComponent("lockscreen"); + } + + if (root.active) { + LockKeysService.registerComponent("lockscreen"); + } else { + LockKeysService.unregisterComponent("lockscreen"); + } + } + + onNeedsSpectrumChanged: { + if (root.needsSpectrum) { + SpectrumService.registerComponent("lockscreen"); + } else { + SpectrumService.unregisterComponent("lockscreen"); + } + } + + Component.onCompleted: { + // Register with panel service + PanelService.lockScreen = this; + } + + Component.onDestruction: { + SpectrumService.unregisterComponent("lockscreen"); + LockKeysService.unregisterComponent("lockscreen"); + } + + Timer { + id: unloadAfterUnlockTimer + interval: 250 + repeat: false + onTriggered: root.active = false + } + + function scheduleUnloadAfterUnlock() { + unloadAfterUnlockTimer.start(); + } + + sourceComponent: Component { + Item { + id: lockContainer + + LockContext { + id: lockContext + onUnlocked: { + lockSession.locked = false; + root.scheduleUnloadAfterUnlock(); + lockContext.currentText = ""; + } + onFailed: { + lockContext.currentText = ""; + } + } + + // Whether any monitor from the user's lockScreenMonitors list is currently connected. + readonly property bool anyConfiguredMonitorConnected: { + const configured = Settings.data.general.lockScreenMonitors; + if (!configured || configured.length === 0) + return false; + return (Quickshell.screens || []).some(s => configured.includes(s.name)); + } + + WlSessionLock { + id: lockSession + locked: root.active + + WlSessionLockSurface { + id: lockSurface + + Loader { + anchors.fill: parent + active: true + sourceComponent: (!lockContainer.anyConfiguredMonitorConnected || Settings.data.general.lockScreenMonitors.includes(lockSurface.screen?.name)) ? fullLockScreenComponent : blackScreenComponent + } + + Component { + id: fullLockScreenComponent + + Item { + Item { + id: batteryIndicator + + property bool isReady: BatteryService.batteryReady + property real percent: BatteryService.batteryPercentage + property bool charging: BatteryService.batteryCharging + property bool pluggedIn: BatteryService.batteryPluggedIn + property bool batteryVisible: isReady + property string icon: BatteryService.batteryIcon + } + + Item { + id: keyboardLayout + property string currentLayout: KeyboardLayoutService.currentLayout + } + + // Background with wallpaper, gradient, and screen corners + LockScreenBackground { + id: backgroundComponent + screen: lockSurface.screen + } + + Item { + anchors.fill: parent + + // Mouse area to trigger focus on cursor movement (workaround for Hyprland focus issues) + MouseArea { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + onEntered: { + // Avoid repeatedly forcing focus on every mouse move. + // This can churn text-input surface state during monitor/suspend transitions. + if (passwordInput && !passwordInput.activeFocus) { + passwordInput.forceActiveFocus(); + } + } + } + + // Header with avatar, welcome, time, date + LockScreenHeader { + id: headerComponent + } + + // Info notification + Rectangle { + width: infoRowLayout.implicitWidth + Style.marginXL * 1.5 + height: 50 + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: (Settings.data.general.compactLockScreen ? 280 : 360) * Style.uiScaleRatio + radius: Style.radiusL + color: Color.mTertiary + visible: lockContext.showInfo && lockContext.infoMessage && !panelComponent.timerActive + opacity: visible ? 1.0 : 0.0 + + RowLayout { + id: infoRowLayout + anchors.centerIn: parent + spacing: Style.marginM + + NIcon { + icon: "circle-key" + pointSize: Style.fontSizeXL + color: Color.mOnTertiary + } + + NText { + text: lockContext.infoMessage + color: Color.mOnTertiary + pointSize: Style.fontSizeL + horizontalAlignment: Text.AlignHCenter + } + } + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + } + + // Error notification + Rectangle { + width: errorRowLayout.implicitWidth + Style.marginXL * 1.5 + height: 50 + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: (Settings.data.general.compactLockScreen ? 280 : 360) * Style.uiScaleRatio + radius: Style.radiusL + color: Color.mError + visible: lockContext.showFailure && lockContext.errorMessage && !panelComponent.timerActive + opacity: visible ? 1.0 : 0.0 + + RowLayout { + id: errorRowLayout + anchors.centerIn: parent + spacing: Style.marginM + + NIcon { + icon: "alert-circle" + pointSize: Style.fontSizeXL + color: Color.mOnError + } + + NText { + text: lockContext.errorMessage || "Authentication failed" + color: Color.mOnError + pointSize: Style.fontSizeL + horizontalAlignment: Text.AlignHCenter + } + } + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + } + + // Countdown notification + Rectangle { + width: countdownRowLayout.implicitWidth + Style.marginXL * 1.5 + height: 50 + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: (Settings.data.general.compactLockScreen ? 280 : 360) * Style.uiScaleRatio + radius: Style.radiusL + color: Color.mSurface + visible: panelComponent.timerActive + opacity: visible ? 1.0 : 0.0 + + RowLayout { + id: countdownRowLayout + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + + NIcon { + icon: "clock" + pointSize: Style.fontSizeXL + color: Color.mPrimary + } + + NText { + text: I18n.tr("session-menu.action-in-seconds", { + "action": I18n.tr("common." + panelComponent.pendingAction), + "seconds": Math.ceil(panelComponent.timeRemaining / 1000) + }) + color: Color.mOnSurface + pointSize: Style.fontSizeL + horizontalAlignment: Text.AlignHCenter + font.weight: Style.fontWeightBold + } + + Item { + Layout.fillWidth: true + } + + NIconButton { + icon: "x" + tooltipText: I18n.tr("session-menu.cancel-timer") + baseSize: 32 + colorBg: Qt.alpha(Color.mPrimary, 0.1) + colorFg: Color.mPrimary + colorBgHover: Color.mPrimary + onClicked: panelComponent.cancelTimer() + } + } + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + } + + // Hidden input that receives actual text + TextInput { + id: passwordInput + width: 0 + height: 0 + visible: false + enabled: !lockContext.unlockInProgress + echoMode: TextInput.Password + passwordMaskDelay: 0 + + // Bidirectional sync โ€” avoids a declarative binding which breaks on input + onTextChanged: { + if (lockContext.currentText !== text) + lockContext.currentText = text; + } + Connections { + target: lockContext + function onCurrentTextChanged() { + if (passwordInput.text !== lockContext.currentText) + passwordInput.text = lockContext.currentText; + } + } + + Keys.onPressed: function (event) { + if (Keybinds.checkKey(event, 'enter', Settings)) { + lockContext.tryUnlock(); + event.accepted = true; + } + if (Keybinds.checkKey(event, 'escape', Settings) && panelComponent.timerActive) { + panelComponent.cancelTimer(); + event.accepted = true; + } + } + + Component.onCompleted: forceActiveFocus() + } + + // Main panel with password, weather, media, session controls + LockScreenPanel { + id: panelComponent + lockControl: lockContext + batteryIndicator: batteryIndicator + keyboardLayout: keyboardLayout + passwordInput: passwordInput + } + } + } + } + + Component { + id: blackScreenComponent + + // Black surface for disabled monitors โ€” still captures keyboard for password entry + Rectangle { + anchors.fill: parent + color: "black" + + TextInput { + id: blackScreenPasswordInput + width: 0 + height: 0 + visible: false + enabled: !lockContext.unlockInProgress + echoMode: TextInput.Password + passwordMaskDelay: 0 + + // Bidirectional sync โ€” avoids a declarative binding which breaks on input + onTextChanged: { + if (lockContext.currentText !== text) + lockContext.currentText = text; + } + Connections { + target: lockContext + function onCurrentTextChanged() { + if (blackScreenPasswordInput.text !== lockContext.currentText) + blackScreenPasswordInput.text = lockContext.currentText; + } + } + + Keys.onPressed: function (event) { + if (Keybinds.checkKey(event, 'enter', Settings)) { + lockContext.tryUnlock(); + event.accepted = true; + } + } + + Component.onCompleted: forceActiveFocus() + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + onPositionChanged: blackScreenPasswordInput.forceActiveFocus() + } + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/LockScreen/LockScreenBackground.qml b/arch/.config/quickshell/noctalia-shell/Modules/LockScreen/LockScreenBackground.qml new file mode 100644 index 0000000..dde0839 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/LockScreen/LockScreenBackground.qml @@ -0,0 +1,303 @@ +import QtQuick +import QtQuick.Effects +import Quickshell +import qs.Commons +import qs.Services.Compositor +import qs.Services.Power +import qs.Services.UI + +Item { + id: root + anchors.fill: parent + + // Cached wallpaper path - exposed for parent components + property string resolvedWallpaperPath: "" + property color tintColor: Settings.data.colorSchemes.darkMode ? Color.mSurface : Color.mOnSurface + + required property var screen + + // Request preprocessed wallpaper when lock screen becomes active or dimensions change + Component.onCompleted: { + if (screen) { + Qt.callLater(requestCachedWallpaper); + } + } + + onWidthChanged: { + if (screen && width > 0 && height > 0) { + Qt.callLater(requestCachedWallpaper); + } + } + + onHeightChanged: { + if (screen && width > 0 && height > 0) { + Qt.callLater(requestCachedWallpaper); + } + } + + // Listen for wallpaper changes + Connections { + target: WallpaperService + function onWallpaperChanged(screenName, path) { + if (screen && screenName === screen.name) { + Qt.callLater(requestCachedWallpaper); + } + } + } + + // Listen for display scale changes + Connections { + target: CompositorService + function onDisplayScalesChanged() { + if (screen && width > 0 && height > 0) { + Qt.callLater(requestCachedWallpaper); + } + } + } + + function requestCachedWallpaper() { + if (!screen || width <= 0 || height <= 0) { + return; + } + + // Check for solid color mode first + if (Settings.data.wallpaper.useSolidColor) { + resolvedWallpaperPath = ""; + return; + } + + const originalPath = WallpaperService.getWallpaper(screen.name) || ""; + if (originalPath === "") { + resolvedWallpaperPath = ""; + return; + } + + // Handle solid color paths + if (WallpaperService.isSolidColorPath(originalPath)) { + resolvedWallpaperPath = ""; + return; + } + + if (!ImageCacheService || !ImageCacheService.initialized) { + // Fallback to original if services not ready + resolvedWallpaperPath = originalPath; + return; + } + + const compositorScale = CompositorService.getDisplayScale(screen.name); + const targetWidth = Math.round(width * compositorScale); + const targetHeight = Math.round(height * compositorScale); + if (targetWidth <= 0 || targetHeight <= 0) { + return; + } + + // Don't set resolvedWallpaperPath until cache is ready + // This prevents loading the original huge image + ImageCacheService.getLarge(originalPath, targetWidth, targetHeight, function (cachedPath, success) { + if (success) { + resolvedWallpaperPath = cachedPath; + } else { + // Only fall back to original if caching failed + resolvedWallpaperPath = originalPath; + } + }); + } + + // Background - solid color or black fallback + Rectangle { + anchors.fill: parent + color: Settings.data.wallpaper.useSolidColor ? Settings.data.wallpaper.solidColor : "#000000" + } + + Image { + id: lockBgImage + visible: source !== "" && Settings.data.wallpaper.enabled && !Settings.data.wallpaper.useSolidColor && (!PowerProfileService.noctaliaPerformanceMode || !Settings.data.noctaliaPerformance.disableWallpaper) + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + source: resolvedWallpaperPath + cache: false + smooth: true + mipmap: false + antialiasing: true + + layer.enabled: Settings.data.general.lockScreenBlur > 0 && !PowerProfileService.noctaliaPerformanceMode + layer.smooth: false + layer.effect: MultiEffect { + blurEnabled: true + blur: Settings.data.general.lockScreenBlur + blurMax: 48 + } + + // Tint overlay + Rectangle { + anchors.fill: parent + color: root.tintColor + opacity: Settings.data.general.lockScreenTint + } + } + + Rectangle { + visible: !Settings.data.wallpaper.useSolidColor + anchors.fill: parent + gradient: Gradient { + GradientStop { + position: 0.0 + color: Qt.alpha(Color.mShadow, 0.4) + } + GradientStop { + position: 0.3 + color: Qt.alpha(Color.mShadow, 0.2) + } + GradientStop { + position: 0.7 + color: Qt.alpha(Color.mShadow, 0.25) + } + GradientStop { + position: 1.0 + color: Qt.alpha(Color.mShadow, 0.5) + } + } + } + + // Screen corners for lock screen + Item { + anchors.fill: parent + visible: Settings.data.general.showScreenCorners + + property color cornerColor: Settings.data.general.forceBlackScreenCorners ? "black" : Color.mSurface + property real cornerRadius: Style.screenRadius + property real cornerSize: Style.screenRadius + + // Top-left concave corner + Canvas { + anchors.top: parent.top + anchors.left: parent.left + width: parent.cornerSize + height: parent.cornerSize + antialiasing: true + renderTarget: Canvas.FramebufferObject + smooth: false + + onPaint: { + const ctx = getContext("2d"); + if (!ctx) + return; + ctx.reset(); + ctx.clearRect(0, 0, width, height); + + ctx.fillStyle = parent.cornerColor; + ctx.fillRect(0, 0, width, height); + + ctx.globalCompositeOperation = "destination-out"; + ctx.fillStyle = "#ffffff"; + ctx.beginPath(); + ctx.arc(width, height, parent.cornerRadius, 0, 2 * Math.PI); + ctx.fill(); + } + + onWidthChanged: if (available) + requestPaint() + onHeightChanged: if (available) + requestPaint() + } + + // Top-right concave corner + Canvas { + anchors.top: parent.top + anchors.right: parent.right + width: parent.cornerSize + height: parent.cornerSize + antialiasing: true + renderTarget: Canvas.FramebufferObject + smooth: true + + onPaint: { + const ctx = getContext("2d"); + if (!ctx) + return; + ctx.reset(); + ctx.clearRect(0, 0, width, height); + + ctx.fillStyle = parent.cornerColor; + ctx.fillRect(0, 0, width, height); + + ctx.globalCompositeOperation = "destination-out"; + ctx.fillStyle = "#ffffff"; + ctx.beginPath(); + ctx.arc(0, height, parent.cornerRadius, 0, 2 * Math.PI); + ctx.fill(); + } + + onWidthChanged: if (available) + requestPaint() + onHeightChanged: if (available) + requestPaint() + } + + // Bottom-left concave corner + Canvas { + anchors.bottom: parent.bottom + anchors.left: parent.left + width: parent.cornerSize + height: parent.cornerSize + antialiasing: true + renderTarget: Canvas.FramebufferObject + smooth: true + + onPaint: { + const ctx = getContext("2d"); + if (!ctx) + return; + ctx.reset(); + ctx.clearRect(0, 0, width, height); + + ctx.fillStyle = parent.cornerColor; + ctx.fillRect(0, 0, width, height); + + ctx.globalCompositeOperation = "destination-out"; + ctx.fillStyle = "#ffffff"; + ctx.beginPath(); + ctx.arc(width, 0, parent.cornerRadius, 0, 2 * Math.PI); + ctx.fill(); + } + + onWidthChanged: if (available) + requestPaint() + onHeightChanged: if (available) + requestPaint() + } + + // Bottom-right concave corner + Canvas { + anchors.bottom: parent.bottom + anchors.right: parent.right + width: parent.cornerSize + height: parent.cornerSize + antialiasing: true + renderTarget: Canvas.FramebufferObject + smooth: true + + onPaint: { + const ctx = getContext("2d"); + if (!ctx) + return; + ctx.reset(); + ctx.clearRect(0, 0, width, height); + + ctx.fillStyle = parent.cornerColor; + ctx.fillRect(0, 0, width, height); + + ctx.globalCompositeOperation = "destination-out"; + ctx.fillStyle = "#ffffff"; + ctx.beginPath(); + ctx.arc(0, 0, parent.cornerRadius, 0, 2 * Math.PI); + ctx.fill(); + } + + onWidthChanged: if (available) + requestPaint() + onHeightChanged: if (available) + requestPaint() + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/LockScreen/LockScreenHeader.qml b/arch/.config/quickshell/noctalia-shell/Modules/LockScreen/LockScreenHeader.qml new file mode 100644 index 0000000..8792cc0 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/LockScreen/LockScreenHeader.qml @@ -0,0 +1,196 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.System +import qs.Widgets + +// Time, Date, and User Profile Container +Rectangle { + id: root + + readonly property bool animationsEnabled: Settings.data.general.lockScreenAnimations || false + + // Use timer-driven properties instead of Time.now to avoid per-frame repaints. + // Time.now updates every frame (~60+ Hz); these update only when needed. + property date currentTime: new Date() + property date currentDate: new Date() + + Timer { + interval: 1000 + running: true + repeat: true + onTriggered: root.currentTime = new Date() + } + + Timer { + interval: 60000 + running: true + repeat: true + onTriggered: root.currentDate = new Date() + } + + width: Math.max(500, contentRow.implicitWidth + 32) + height: Math.max(120, contentRow.implicitHeight + 32) + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 100 + radius: Style.radiusL + color: Color.mSurface + border.color: Qt.alpha(Color.mOutline, 0.2) + border.width: Style.borderS + + RowLayout { + id: contentRow + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.margin2XL + + // Left side: Avatar + Rectangle { + Layout.preferredWidth: 70 + Layout.preferredHeight: 70 + Layout.alignment: Qt.AlignVCenter + radius: width / 2 + color: "transparent" + + Rectangle { + anchors.fill: parent + radius: parent.radius + color: "transparent" + border.color: Qt.alpha(Color.mPrimary, 0.8) + border.width: Style.borderM + + SequentialAnimation on border.color { + loops: Animation.Infinite + running: root.animationsEnabled + ColorAnimation { + to: Qt.alpha(Color.mPrimary, 1.0) + duration: 2000 + easing.type: Easing.InOutQuad + } + ColorAnimation { + to: Qt.alpha(Color.mPrimary, 0.8) + duration: 2000 + easing.type: Easing.InOutQuad + } + } + } + + NImageRounded { + anchors.centerIn: parent + width: 66 + height: 66 + radius: width / 2 + imagePath: Settings.preprocessPath(Settings.data.general.avatarImage) + fallbackIcon: "person" + + SequentialAnimation on scale { + loops: Animation.Infinite + running: root.animationsEnabled + NumberAnimation { + to: 1.02 + duration: 4000 + easing.type: Easing.InOutQuad + } + NumberAnimation { + to: 1.0 + duration: 4000 + easing.type: Easing.InOutQuad + } + } + } + } + + // Center: User Info Column (left-aligned text) + ColumnLayout { + Layout.alignment: Qt.AlignVCenter + spacing: Style.marginXXS + + // Welcome back + Username on one line + NText { + text: I18n.tr("system.welcome-back") + " " + HostService.displayName + "!" + pointSize: Style.fontSizeXXL + color: Color.mOnSurface + horizontalAlignment: Text.AlignLeft + } + + // Date below + NText { + text: { + var dateString = I18n.locale.toString(root.currentDate, I18n.dateFormat()); + return dateString.charAt(0).toUpperCase() + dateString.slice(1); + } + pointSize: Style.fontSizeXL + color: Color.mOnSurfaceVariant + horizontalAlignment: Text.AlignLeft + } + } + + // Spacer to push time to the right + Item { + Layout.fillWidth: true + } + + // Clock + Item { + Layout.preferredWidth: Settings.data.general.clockStyle === "analog" ? 70 : (Settings.data.general.clockStyle === "custom" ? 90 : 70) + Layout.preferredHeight: Settings.data.general.clockStyle === "analog" ? 70 : (Settings.data.general.clockStyle === "custom" ? 90 : 70) + Layout.alignment: Qt.AlignVCenter + + // Analog Clock + NClock { + anchors.centerIn: parent + width: 70 + height: 70 + visible: Settings.data.general.clockStyle === "analog" + now: root.currentTime + clockStyle: "analog" + backgroundColor: "transparent" + clockColor: Color.mOnSurface + secondHandColor: Color.mPrimary + } + + // Digital Clock (Standard) + NClock { + anchors.centerIn: parent + width: 70 + height: 70 + visible: Settings.data.general.clockStyle === "digital" + now: root.currentTime + clockStyle: "digital" + showProgress: true + progressColor: Color.mPrimary + backgroundColor: "transparent" + clockColor: Color.mOnSurface + hoursFontSize: Style.fontSizeL + minutesFontSize: Style.fontSizeL + hoursFontWeight: Style.fontWeightBold + minutesFontWeight: Style.fontWeightBold + } + + // Custom Clock (Stacked) + ColumnLayout { + anchors.centerIn: parent + width: parent.width + visible: Settings.data.general.clockStyle === "custom" + spacing: -3 + + Repeater { + model: I18n.locale.toString(root.currentTime, (Settings.data.general.clockFormat || "hh\\nmm").replace(/\\n/g, "\n")).split("\n") + NText { + text: modelData + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + Layout.alignment: Qt.AlignHCenter + Layout.maximumWidth: parent.width + Layout.fillWidth: true + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/LockScreen/LockScreenPanel.qml b/arch/.config/quickshell/noctalia-shell/Modules/LockScreen/LockScreenPanel.qml new file mode 100644 index 0000000..ddd0678 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/LockScreen/LockScreenPanel.qml @@ -0,0 +1,1216 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Compositor +import qs.Services.Hardware +import qs.Services.Keyboard +import qs.Services.Location +import qs.Services.Media +import qs.Widgets +import qs.Widgets.AudioSpectrum + +Item { + id: root + anchors.fill: parent + + required property var lockControl + required property var batteryIndicator + required property var keyboardLayout + required property TextInput passwordInput + + // Whether to enable lock screen animations (smooth cursor blink). + // Defaults to false to reduce GPU usage. Set Settings.data.general.lockScreenAnimations = true to restore. + readonly property bool animationsEnabled: Settings.data.general.lockScreenAnimations || false + + Component.onCompleted: { + if (Settings.data.general.autoStartAuth) { + doUnlock(); + } + } + + function doUnlock() { + if (lockControl) { + lockControl.tryUnlock(); + } + } + + // Timer properties + readonly property int timerDuration: Settings.data.general.lockScreenCountdownDuration + property string pendingAction: "" + property bool timerActive: false + property int timeRemaining: 0 + readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null) + + // Timer management functions + function startTimer(action) { + // Check if global countdown is disabled + if (!Settings.data.general.enableLockScreenCountdown) { + executeAction(action); + return; + } + + if (timerActive && pendingAction === action) { + // Second click - execute immediately + executeAction(action); + return; + } + + pendingAction = action; + timeRemaining = timerDuration; + timerActive = true; + countdownTimer.start(); + } + + function cancelTimer() { + timerActive = false; + pendingAction = ""; + timeRemaining = 0; + countdownTimer.stop(); + } + + function executeAction(action) { + // Stop timer but don't reset other properties yet + countdownTimer.stop(); + + // Execute the action + switch (action) { + case "logout": + CompositorService.logout(); + break; + case "suspend": + CompositorService.suspend(); + break; + case "hibernate": + CompositorService.hibernate(); + break; + case "reboot": + CompositorService.reboot(); + break; + case "userspaceReboot": + CompositorService.userspaceReboot(); + break; + case "shutdown": + CompositorService.shutdown(); + break; + } + + // Reset timer state + cancelTimer(); + } + + // Countdown timer + Timer { + id: countdownTimer + interval: 100 + repeat: true + onTriggered: { + timeRemaining -= interval; + if (timeRemaining <= 0) { + executeAction(pendingAction); + } + } + } + + // Compact status indicators container (compact mode only) + Rectangle { + width: { + var hasBattery = batteryIndicator.isReady; + var hasKeyboard = keyboardLayout.currentLayout !== "Unknown"; + var hasCaps = LockKeysService.capsLockOn; + var hasCapsSlot = hasBattery || hasKeyboard || hasCaps; + + var visibleCount = 0; + if (hasBattery) + visibleCount++; + if (hasKeyboard) + visibleCount++; + if (hasCapsSlot) + visibleCount++; + + if (visibleCount >= 3) { + return 280; + } else if (visibleCount === 2) { + return 200; + } else if (visibleCount === 1) { + return 120; + } else { + return 0; + } + } + height: 40 + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 96 + (Settings.data.general.compactLockScreen ? 116 : 220) + topLeftRadius: Style.radiusL + topRightRadius: Style.radiusL + color: Color.mSurface + visible: Settings.data.general.compactLockScreen && (batteryIndicator.isReady || keyboardLayout.currentLayout !== "Unknown" || LockKeysService.capsLockOn) + + RowLayout { + id: compactStatusRow + anchors.centerIn: parent + spacing: Style.marginL + + // Battery indicator + RowLayout { + spacing: Style.marginS + visible: batteryIndicator.isReady + + NIcon { + icon: batteryIndicator.icon + pointSize: Style.fontSizeM + color: batteryIndicator.charging ? Color.mPrimary : Color.mOnSurfaceVariant + } + + NText { + text: Math.round(batteryIndicator.percent) + "%" + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeM + } + } + + // Keyboard layout indicator + RowLayout { + spacing: 6 + visible: keyboardLayout.currentLayout !== "Unknown" + + NIcon { + icon: "keyboard" + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + + NText { + text: keyboardLayout.currentLayout + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeM + elide: Text.ElideRight + } + } + + // Caps Lock indicator + RowLayout { + spacing: 6 + visible: batteryIndicator.isReady || keyboardLayout.currentLayout !== "Unknown" || LockKeysService.capsLockOn + + NIcon { + icon: "lock" + pointSize: Style.fontSizeM + color: LockKeysService.capsLockOn ? Color.mPrimary : Qt.alpha(Color.mOnSurfaceVariant, 0.5) + } + + NText { + text: I18n.tr("bar.lock-keys.show-caps-lock-label") + color: LockKeysService.capsLockOn ? Color.mOnSurfaceVariant : Qt.alpha(Color.mOnSurfaceVariant, 0.65) + pointSize: Style.fontSizeM + elide: Text.ElideRight + } + } + } + } + + // Bottom container with weather, password input and controls + Rectangle { + id: bottomContainer + + // Support for removing the session/power buttons at the bottom. + readonly property int deltaY: Settings.data.general.showSessionButtonsOnLockScreen ? 0 : (Settings.data.general.compactLockScreen ? 36 : 48) + 14 + + height: { + let calcHeight = Settings.data.general.compactLockScreen ? 120 : 220; + if (!Settings.data.general.showSessionButtonsOnLockScreen) { + calcHeight -= bottomContainer.deltaY; + } + return calcHeight; + } + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 100 + bottomContainer.deltaY + radius: Style.radiusL + color: Color.mSurface + + width: Settings.data.general.showHibernateOnLockScreen ? 860 : 810 + + ColumnLayout { + anchors.fill: parent + anchors.margins: 14 + spacing: Style.marginL + + // Top info row + RowLayout { + Layout.fillWidth: true + Layout.preferredHeight: 65 + spacing: Style.marginXL + visible: !Settings.data.general.compactLockScreen + + // Media widget with visualizer + Item { + Layout.preferredWidth: Style.marginM + visible: MediaService.currentPlayer && MediaService.canPlay + } + + Rectangle { + Layout.preferredWidth: 220 + // Expand to take remaining space when weather is hidden + Layout.fillWidth: !(Settings.data.location.weatherEnabled && LocationService.data.weather !== null) + Layout.preferredHeight: 50 + radius: Style.radiusL + color: "transparent" + clip: true + visible: MediaService.currentPlayer && MediaService.canPlay + + Loader { + anchors.fill: parent + anchors.margins: 4 + active: Settings.data.audio.visualizerType === "linear" + z: 0 + sourceComponent: NLinearSpectrum { + anchors.fill: parent + values: SpectrumService.values + fillColor: Color.mPrimary + opacity: 0.4 + mirrored: Settings.data.audio.spectrumMirrored + } + } + + Loader { + anchors.fill: parent + anchors.margins: 4 + active: Settings.data.audio.visualizerType === "mirrored" + z: 0 + sourceComponent: NMirroredSpectrum { + anchors.fill: parent + values: SpectrumService.values + fillColor: Color.mPrimary + opacity: 0.4 + mirrored: Settings.data.audio.spectrumMirrored + } + } + + Loader { + anchors.fill: parent + anchors.margins: 4 + active: Settings.data.audio.visualizerType === "wave" + z: 0 + sourceComponent: NWaveSpectrum { + anchors.fill: parent + values: SpectrumService.values + fillColor: Color.mPrimary + opacity: 0.4 + mirrored: Settings.data.audio.spectrumMirrored + } + } + + RowLayout { + anchors.fill: parent + anchors.margins: 8 + spacing: Style.marginM + z: 1 + + Rectangle { + Layout.preferredWidth: 34 + Layout.preferredHeight: 34 + radius: Math.min(Style.radiusL, width / 2) + color: "transparent" + clip: true + + NImageRounded { + anchors.fill: parent + anchors.margins: 2 + radius: Math.min(Style.radiusL, width / 2) + imagePath: MediaService.trackArtUrl + fallbackIcon: "disc" + fallbackIconSize: Style.fontSizeM + borderColor: Color.mOutline + borderWidth: Style.borderS + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXXS + + NText { + text: MediaService.trackTitle || "No media" + pointSize: Style.fontSizeM + color: Color.mOnSurface + Layout.fillWidth: true + elide: Text.ElideRight + } + + NText { + text: MediaService.trackArtist || "" + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + Layout.fillWidth: true + elide: Text.ElideRight + } + } + + // Media controls (when enabled) + RowLayout { + spacing: Style.marginXS + visible: Settings.data.general.enableLockScreenMediaControls + Layout.alignment: Qt.AlignHCenter + + Rectangle { + width: 28 + height: 28 + radius: Math.min(Style.radiusL, width / 2) + color: prevButtonArea.containsMouse ? Color.mPrimary : Qt.alpha(Color.mOnSurface, 0.1) + visible: MediaService.canGoPrevious + + NIcon { + anchors.centerIn: parent + icon: "media-prev" + pointSize: Style.fontSizeM + color: prevButtonArea.containsMouse ? Color.mOnPrimary : Color.mOnSurface + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + MouseArea { + id: prevButtonArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: MediaService.canGoPrevious ? MediaService.previous() : {} + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + Rectangle { + width: 32 + height: 32 + radius: Math.min(Style.radiusL, width / 2) + color: playPauseButtonArea.containsMouse ? Color.mPrimary : Qt.alpha(Color.mOnSurface, 0.15) + visible: MediaService.canPlay || MediaService.canPause + + NIcon { + anchors.centerIn: parent + icon: MediaService.isPlaying ? "media-pause" : "media-play" + pointSize: Style.fontSizeL + color: playPauseButtonArea.containsMouse ? Color.mOnPrimary : Color.mOnSurface + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + MouseArea { + id: playPauseButtonArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: (MediaService.canPlay || MediaService.canPause) ? MediaService.playPause() : {} + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + Rectangle { + width: 28 + height: 28 + radius: Math.min(Style.radiusL, width / 2) + color: nextButtonArea.containsMouse ? Color.mPrimary : Qt.alpha(Color.mOnSurface, 0.1) + visible: MediaService.canGoNext + + NIcon { + anchors.centerIn: parent + icon: "media-next" + pointSize: Style.fontSizeM + color: nextButtonArea.containsMouse ? Color.mOnPrimary : Color.mOnSurface + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + MouseArea { + id: nextButtonArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: MediaService.canGoNext ? MediaService.next() : {} + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + } + } + } + + Rectangle { + Layout.preferredWidth: 1 + Layout.fillHeight: true + Layout.rightMargin: 4 + color: Qt.alpha(Color.mOutline, 0.3) + visible: MediaService.currentPlayer && MediaService.canPlay + } + + Item { + Layout.preferredWidth: Style.marginM + visible: !(MediaService.currentPlayer && MediaService.canPlay) + } + + // Current weather + RowLayout { + visible: Settings.data.location.weatherEnabled && LocationService.data.weather !== null + Layout.preferredWidth: 180 + spacing: Style.marginM + + Item { + Layout.preferredWidth: lockMainWeatherIconSide + Layout.preferredHeight: lockMainWeatherIconSide + Layout.alignment: Qt.AlignVCenter + readonly property int lockMainWeatherIconSide: Math.round(Style.fontSizeXXXL * Style.uiScaleRatio * 1.6) + + NIcon { + visible: !LocationService.taliaWeatherMascotActive || !weatherReady + anchors.centerIn: parent + icon: weatherReady ? LocationService.weatherSymbolFromCode(LocationService.data.weather.current_weather.weathercode) : "weather-cloud-off" + pointSize: Style.fontSizeXXXL + color: Color.mPrimary + } + Loader { + active: LocationService.taliaWeatherMascotActive && weatherReady + anchors.fill: parent + asynchronous: true + sourceComponent: Component { + Image { + anchors.fill: parent + fillMode: Image.PreserveAspectFit + smooth: true + mipmap: true + asynchronous: true + source: Qt.resolvedUrl(LocationService.taliaWeatherImageFromCode(LocationService.data.weather.current_weather.weathercode)) + } + } + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXXS + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginL + + NText { + text: { + var temp = LocationService.data.weather.current_weather.temperature; + var suffix = "C"; + if (Settings.data.location.useFahrenheit) { + temp = LocationService.celsiusToFahrenheit(temp); + suffix = "F"; + } + temp = Math.round(temp); + return temp + "ยฐ" + suffix; + } + pointSize: Style.fontSizeXL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: { + var wind = LocationService.data.weather.current_weather.windspeed; + var unit = "km/h"; + if (Settings.data.location.useFahrenheit) { + wind = wind * 0.621371; // Convert km/h to mph + unit = "mph"; + } + wind = Math.round(wind); + return wind + " " + unit; + } + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NText { + text: Settings.data.location.name.split(",")[0] + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + visible: !Settings.data.location.hideWeatherCityName + } + + NText { + text: (LocationService.data.weather.current && LocationService.data.weather.current.relativehumidity_2m) ? LocationService.data.weather.current.relativehumidity_2m + "% humidity" : "" + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + } + } + } + + // Forecast + RowLayout { + visible: Settings.data.location.weatherEnabled && LocationService.data.weather !== null + Layout.preferredWidth: 260 + Layout.rightMargin: 8 + spacing: Style.marginXS + + Repeater { + model: MediaService.currentPlayer && MediaService.canPlay ? 2 : 4 + delegate: ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXXS + 1 + + NText { + text: { + var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/")); + return I18n.locale.toString(weatherDate, "ddd"); + } + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + } + + Item { + Layout.preferredWidth: lockForecastWeatherIconSide + Layout.preferredHeight: lockForecastWeatherIconSide + Layout.alignment: Qt.AlignHCenter + readonly property int lockForecastWeatherIconSide: Math.round(Style.fontSizeXL * Style.uiScaleRatio * 1.6) + + NIcon { + visible: !LocationService.taliaWeatherMascotActive + anchors.centerIn: parent + icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index]) + pointSize: Style.fontSizeXL + color: Color.mOnSurfaceVariant + } + Loader { + active: LocationService.taliaWeatherMascotActive + anchors.fill: parent + asynchronous: true + sourceComponent: Component { + Image { + anchors.fill: parent + fillMode: Image.PreserveAspectFit + smooth: true + mipmap: true + asynchronous: true + source: Qt.resolvedUrl(LocationService.taliaWeatherImageFromCode(LocationService.data.weather.daily.weathercode[index])) + } + } + } + } + + NText { + text: { + var max = LocationService.data.weather.daily.temperature_2m_max[index]; + var min = LocationService.data.weather.daily.temperature_2m_min[index]; + if (Settings.data.location.useFahrenheit) { + max = LocationService.celsiusToFahrenheit(max); + min = LocationService.celsiusToFahrenheit(min); + } + max = Math.round(max); + min = Math.round(min); + return max + "ยฐ/" + min + "ยฐ"; + } + pointSize: Style.fontSizeM + font.weight: Style.fontWeightMedium + color: Color.mOnSurfaceVariant + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + } + } + } + } + + Item { + Layout.fillWidth: batteryIndicator.isReady + } + + // Battery and Keyboard Layout (full mode only) + ColumnLayout { + Layout.alignment: (batteryIndicator.isReady) ? (Qt.AlignRight | Qt.AlignVCenter) : Qt.AlignVCenter + spacing: Style.marginM + visible: batteryIndicator.isReady || keyboardLayout.currentLayout !== "Unknown" || LockKeysService.capsLockOn + + // Battery + RowLayout { + spacing: Style.marginXS + visible: batteryIndicator.isReady + + NIcon { + icon: batteryIndicator.icon + pointSize: Style.fontSizeM + color: batteryIndicator.charging ? Color.mPrimary : Color.mOnSurfaceVariant + } + + NText { + text: Math.round(batteryIndicator.percent) + "%" + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeM + } + } + + // Keyboard Layout + RowLayout { + spacing: Style.marginXS + visible: keyboardLayout.currentLayout !== "Unknown" + + NIcon { + icon: "keyboard" + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + + NText { + text: keyboardLayout.currentLayout + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeM + elide: Text.ElideRight + } + } + + // Caps Lock + RowLayout { + spacing: Style.marginXS + visible: batteryIndicator.isReady || keyboardLayout.currentLayout !== "Unknown" || LockKeysService.capsLockOn + + NIcon { + icon: "lock" + pointSize: Style.fontSizeM + color: LockKeysService.capsLockOn ? Color.mPrimary : Qt.alpha(Color.mOnSurfaceVariant, 0.5) + } + + NText { + text: I18n.tr("bar.lock-keys.show-caps-lock-label") + color: LockKeysService.capsLockOn ? Color.mOnSurfaceVariant : Qt.alpha(Color.mOnSurfaceVariant, 0.65) + pointSize: Style.fontSizeM + elide: Text.ElideRight + } + } + } + + Item { + Layout.preferredWidth: Style.marginM + } + } + + // Password input + RowLayout { + Layout.fillWidth: true + spacing: 0 + + Item { + Layout.preferredWidth: Style.marginM + } + + Rectangle { + id: passwordInputContainer + Layout.fillWidth: true + Layout.preferredHeight: 48 + radius: Style.iRadiusL + color: Color.mSurface + border.color: passwordInput.activeFocus ? Color.mPrimary : Qt.alpha(Color.mOutline, 0.3) + border.width: passwordInput.activeFocus ? 2 : 1 + + property bool passwordVisible: false + + // Ctrl + A to highlight the portion + Shortcut { + sequence: StandardKey.SelectAll + enabled: passwordInput.activeFocus + onActivated: passwordInput.selectAll() + } + + // Esc to clear selection + Shortcut { + sequences: [StandardKey.Cancel] + enabled: passwordInput.activeFocus && passwordInput.selectionStart !== passwordInput.selectionEnd + onActivated: passwordInput.deselect() + } + + Row { + anchors.left: parent.left + anchors.leftMargin: 18 + anchors.verticalCenter: parent.verticalCenter + spacing: Style.marginL + + NIcon { + icon: "login-2" + pointSize: Style.fontSizeL + color: passwordInput.activeFocus ? Color.mPrimary : Color.mOnSurfaceVariant + anchors.verticalCenter: parent.verticalCenter + } + + Row { + spacing: 0 + + Rectangle { + width: 2 + height: 20 + color: Color.mPrimary + visible: passwordInput.activeFocus && passwordInput.text.length === 0 + anchors.verticalCenter: parent.verticalCenter + + // Smooth fade animation (when animations enabled) + SequentialAnimation on opacity { + loops: Animation.Infinite + running: root.animationsEnabled && passwordInput.activeFocus && passwordInput.text.length === 0 + NumberAnimation { + to: 0 + duration: 530 + } + NumberAnimation { + to: 1 + duration: 530 + } + } + + // Simple toggle (when animations disabled) โ€” no per-frame repaints + Timer { + interval: 530 + running: !root.animationsEnabled && passwordInput.activeFocus && passwordInput.text.length === 0 + repeat: true + onTriggered: parent.opacity = parent.opacity > 0.5 ? 0 : 1 + } + } + + // Host for dots / plain text and the caret (caret x follows passwordInput.cursorPosition) + Item { + id: passwordVisualHost + height: 20 + width: passwordInputContainer.passwordVisible ? Math.min(visiblePasswordPlainText.implicitWidth, 550) : Math.min(passwordDisplayContent.width, 550) + anchors.verticalCenter: parent.verticalCenter + + readonly property real caretVisualX: { + const len = passwordInput.text.length; + if (len <= 0) + return 0; + if (passwordInputContainer.passwordVisible) { + const adv = passwordCaretFontMetrics.advanceWidth(passwordInput.text.substring(0, passwordInput.cursorPosition)); + return Math.max(0, Math.min(adv, width)); + } + const w = passwordDisplayContent.width; + if (w <= 0) + return 0; + return Math.max(0, Math.min((passwordInput.cursorPosition / len) * w, width)); + } + + // Password dots display with selection support + Item { + width: Math.min(passwordDisplayContent.width, 550) + height: 20 + visible: passwordInput.text.length > 0 && !passwordInputContainer.passwordVisible + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + clip: true + + // Proportional selection highlight behind the dots + Rectangle { + id: selectionHighlight + visible: passwordInput.selectionStart !== passwordInput.selectionEnd && passwordInput.text.length > 0 + color: Qt.alpha(Color.mPrimary, 0.8) + height: parent.height + Style.marginS + anchors.verticalCenter: parent.verticalCenter + x: (passwordInput.selectionStart / passwordInput.text.length) * passwordDisplayContent.width + width: ((passwordInput.selectionEnd - passwordInput.selectionStart) / passwordInput.text.length) * passwordDisplayContent.width + } + + Row { + id: passwordDisplayContent + spacing: Style.marginXXXS + anchors.verticalCenter: parent.verticalCenter + + Repeater { + id: iconRepeater + model: ScriptModel { + values: Array(passwordInput.text.length) + } + + property list passwordChars: ["circle-filled", "pentagon-filled", "michelin-star-filled", "square-rounded-filled", "guitar-pick-filled", "blob-filled", "triangle-filled"] + + NIcon { + id: icon + + required property int index + // This will be called with index = -1 when the TextInput is deleted + // So we make sur index is positive to avoid warning on array accesses + property bool drawCustomChar: index >= 0 && Settings.data.general.passwordChars + // Flip color when this dot falls inside the active selection range + property bool isSelected: index >= 0 && passwordInput.selectionStart !== passwordInput.selectionEnd && index >= passwordInput.selectionStart && index < passwordInput.selectionEnd + + icon: drawCustomChar ? iconRepeater.passwordChars[index % iconRepeater.passwordChars.length] : "circle-filled" + pointSize: Style.fontSizeL + color: isSelected ? Color.mOnPrimary : Color.mPrimary + opacity: 1.0 + scale: animationsEnabled ? 0.5 : 1 + ParallelAnimation { + id: iconAnim + NumberAnimation { + target: icon + properties: "scale" + to: 1 + duration: Style.animationFast + easing.type: Easing.BezierSpline + easing.bezierCurve: Easing.OutInBounce + } + } + Component.onCompleted: { + if (animationsEnabled) { + iconAnim.start(); + } + } + } + } + } + + // Mouse area for click-to-position and drag-to-select + MouseArea { + anchors.fill: parent + cursorShape: Qt.IBeamCursor + + property int dragStartPos: 0 + property bool pendingSelectAll: false + + // Resets double-click state if user pauses too long between clicks + Timer { + id: doubleClickResetTimer + interval: 600 + onTriggered: parent.pendingSelectAll = false + } + + function charIndexFromX(mouseX) { + if (passwordInput.text.length === 0) + return 0; + var charWidth = passwordDisplayContent.width / passwordInput.text.length; + // floor so clicking anywhere on a dot selects that dot, not the next + return Math.max(0, Math.min(passwordInput.text.length - 1, Math.floor(mouseX / charWidth))); + } + + onPressed: function (mouse) { + doubleClickResetTimer.stop(); + passwordInput.forceActiveFocus(); + dragStartPos = charIndexFromX(mouse.x); + passwordInput.cursorPosition = dragStartPos; + } + + onPositionChanged: function (mouse) { + pendingSelectAll = false; + var curPos = charIndexFromX(mouse.x); + if (curPos <= dragStartPos) { + passwordInput.select(curPos, dragStartPos + 1); + } else { + passwordInput.select(dragStartPos, curPos + 1); + } + } + + onDoubleClicked: function (mouse) { + passwordInput.forceActiveFocus(); + if (pendingSelectAll) { + passwordInput.selectAll(); + pendingSelectAll = false; + } else { + var pos = charIndexFromX(mouse.x); + passwordInput.select(pos, Math.min(pos + 1, passwordInput.text.length)); + pendingSelectAll = true; + doubleClickResetTimer.restart(); + } + } + } + } + + NText { + id: visiblePasswordPlainText + text: passwordInput.text + color: Color.mPrimary + pointSize: Style.fontSizeM + visible: passwordInput.text.length > 0 && passwordInputContainer.passwordVisible + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + elide: Text.ElideRight + width: Math.min(implicitWidth, 550) + } + + FontMetrics { + id: passwordCaretFontMetrics + font: visiblePasswordPlainText.font + } + + Rectangle { + width: 2 + height: 20 + x: passwordVisualHost.caretVisualX + color: Color.mPrimary + // Hide the cursor when text is selected + visible: passwordInput.activeFocus && passwordInput.text.length > 0 && passwordInput.selectionStart === passwordInput.selectionEnd + anchors.verticalCenter: parent.verticalCenter + + // Smooth fade animation (when animations enabled) + SequentialAnimation on opacity { + loops: Animation.Infinite + running: root.animationsEnabled && passwordInput.activeFocus && passwordInput.text.length > 0 && passwordInput.selectionStart === passwordInput.selectionEnd + NumberAnimation { + to: 0 + duration: 530 + } + NumberAnimation { + to: 1 + duration: 530 + } + } + + // Simple toggle (when animations disabled) โ€” no per-frame repaints + Timer { + interval: 530 + running: !root.animationsEnabled && passwordInput.activeFocus && passwordInput.text.length > 0 && passwordInput.selectionStart === passwordInput.selectionEnd + repeat: true + onTriggered: parent.opacity = parent.opacity > 0.5 ? 0 : 1 + } + } + } + } + } + + // Eye button to toggle password visibility + Rectangle { + anchors.right: submitButton.left + anchors.rightMargin: 4 + anchors.verticalCenter: parent.verticalCenter + width: 36 + height: 36 + radius: Math.min(Style.iRadiusL, width / 2) + color: eyeButtonArea.containsMouse ? Color.mPrimary : "transparent" + visible: passwordInput.text.length > 0 + enabled: !lockContext || !lockContext.unlockInProgress + + NIcon { + anchors.centerIn: parent + icon: parent.parent.passwordVisible ? "eye-off" : "eye" + pointSize: Style.fontSizeM + color: eyeButtonArea.containsMouse ? Color.mOnPrimary : Color.mOnSurfaceVariant + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + MouseArea { + id: eyeButtonArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: parent.parent.passwordVisible = !parent.parent.passwordVisible + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + // Submit button + Rectangle { + id: submitButton + anchors.right: parent.right + anchors.rightMargin: 8 + anchors.verticalCenter: parent.verticalCenter + width: 36 + height: 36 + radius: Math.min(Style.iRadiusL, width / 2) + color: submitButtonArea.containsMouse ? Color.mPrimary : "transparent" + border.color: Color.mPrimary + border.width: Style.borderS + enabled: !lockContext || !lockContext.unlockInProgress + + NIcon { + anchors.centerIn: parent + icon: "arrow-forward" + pointSize: Style.fontSizeM + color: submitButtonArea.containsMouse ? Color.mOnPrimary : Color.mPrimary + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + MouseArea { + id: submitButtonArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: root.doUnlock() + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + Item { + Layout.preferredWidth: Style.marginM + } + } + + // Session control buttons + RowLayout { + id: sessionButtonRow + Layout.fillWidth: true + Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48 + Layout.alignment: Qt.AlignHCenter + spacing: Style.marginM + visible: Settings.data.general.showSessionButtonsOnLockScreen + + readonly property int buttonCount: Settings.data.general.showHibernateOnLockScreen ? 5 : 4 + readonly property real availableWidth: bottomContainer.width - 48 + readonly property real buttonWidth: (availableWidth - (buttonCount - 1) * spacing) / buttonCount + readonly property real buttonHeight: sessionButtonRow.height + + Item { + Layout.preferredWidth: sessionButtonRow.buttonWidth + Layout.preferredHeight: sessionButtonRow.buttonHeight + + NButton { + anchors.fill: parent + icon: "logout" + text: I18n.tr("common.logout") + outlined: true + backgroundColor: Color.mOnSurfaceVariant + textColor: Color.mOnPrimary + fontSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM + iconSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL + horizontalAlignment: Qt.AlignHCenter + buttonRadius: Style.radiusL + onClicked: startTimer("logout") + } + } + + Item { + Layout.preferredWidth: sessionButtonRow.buttonWidth + Layout.preferredHeight: sessionButtonRow.buttonHeight + + NButton { + anchors.fill: parent + icon: "suspend" + text: I18n.tr("common.suspend") + outlined: true + backgroundColor: Color.mOnSurfaceVariant + textColor: Color.mOnPrimary + fontSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM + iconSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL + horizontalAlignment: Qt.AlignHCenter + buttonRadius: Style.radiusL + onClicked: startTimer("suspend") + } + } + + Item { + Layout.preferredWidth: sessionButtonRow.buttonWidth + Layout.preferredHeight: sessionButtonRow.buttonHeight + visible: Settings.data.general.showHibernateOnLockScreen + + NButton { + anchors.fill: parent + icon: "hibernate" + text: I18n.tr("common.hibernate") + outlined: true + backgroundColor: Color.mOnSurfaceVariant + textColor: Color.mOnPrimary + fontSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM + iconSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL + horizontalAlignment: Qt.AlignHCenter + buttonRadius: Style.radiusL + onClicked: startTimer("hibernate") + } + } + + Item { + Layout.preferredWidth: sessionButtonRow.buttonWidth + Layout.preferredHeight: sessionButtonRow.buttonHeight + + NButton { + anchors.fill: parent + icon: "reboot" + text: I18n.tr("common.reboot") + outlined: true + backgroundColor: Color.mOnSurfaceVariant + textColor: Color.mOnPrimary + fontSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM + iconSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL + horizontalAlignment: Qt.AlignHCenter + buttonRadius: Style.radiusL + onClicked: startTimer("reboot") + } + } + + Item { + Layout.preferredWidth: sessionButtonRow.buttonWidth + Layout.preferredHeight: sessionButtonRow.buttonHeight + + NButton { + anchors.fill: parent + icon: "shutdown" + text: I18n.tr("common.shutdown") + outlined: true + backgroundColor: Color.mError + textColor: Color.mOnError + fontSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM + iconSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL + horizontalAlignment: Qt.AlignHCenter + buttonRadius: Style.radiusL + onClicked: startTimer("shutdown") + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/AllScreens.qml b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/AllScreens.qml new file mode 100644 index 0000000..2039dd0 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/AllScreens.qml @@ -0,0 +1,164 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland + +import qs.Commons +import qs.Modules.MainScreen +import qs.Services.UI + +// ------------------------------ +// MainScreen for each screen (manages bar + all panels) +// Wrapped in Loader to optimize memory - only loads when screen needs it +Variants { + model: Quickshell.screens + delegate: Item { + id: windowItem + required property ShellScreen modelData + + property bool shouldBeActive: { + if (!modelData || !modelData.name) { + return false; + } + + let shouldLoad = true; + if (!Settings.data.general.allowPanelsOnScreenWithoutBar) { + // Check if bar is configured for this screen + var monitors = Settings.data.bar.monitors || []; + shouldLoad = monitors.length === 0 || monitors.includes(modelData?.name); + } + + if (shouldLoad) { + Logger.d("AllScreens", "Screen activated: ", modelData?.name); + } + return shouldLoad; + } + + property bool windowLoaded: false + + // Main Screen loader - Bar and panels backgrounds + Loader { + id: windowLoader + active: parent.shouldBeActive + asynchronous: false + + property ShellScreen loaderScreen: modelData + + onLoaded: { + // Signal that window is loaded so exclusion zone can be created + parent.windowLoaded = true; + } + + sourceComponent: MainScreen { + screen: windowLoader.loaderScreen + } + } + + // Bar content in separate windows to prevent fullscreen redraws + // Note: Window stays alive when bar is hidden (visible=false) to avoid + // rapid Wayland surface destruction/creation that can crash compositors. + // Content is debounce-unloaded inside BarContentWindow. + Loader { + active: { + if (!parent.windowLoaded || !parent.shouldBeActive) + return false; + + // Check if bar is configured for this screen + var monitors = Settings.data.bar.monitors || []; + return monitors.length === 0 || monitors.includes(modelData?.name); + } + asynchronous: false + + sourceComponent: BarContentWindow { + screen: modelData + } + + onLoaded: { + Logger.d("AllScreens", "BarContentWindow created for", modelData?.name); + } + } + + // BarTriggerZone - thin invisible zone to reveal hidden bar + // Always loaded when auto-hide is enabled (it's just 1px, no performance impact) + Loader { + active: { + if (!parent.windowLoaded || !parent.shouldBeActive) + return false; + if (!BarService.effectivelyVisible) + return false; + if (Settings.getBarDisplayModeForScreen(modelData?.name) !== "auto_hide") + return false; + + // Check if bar is configured for this screen + var monitors = Settings.data.bar.monitors || []; + return monitors.length === 0 || monitors.includes(modelData?.name); + } + asynchronous: false + + sourceComponent: BarTriggerZone { + screen: modelData + } + + onLoaded: { + Logger.d("AllScreens", "BarTriggerZone created for", modelData?.name); + } + } + + // BarExclusionZone - created after MainScreen has fully loaded + // Note: Exclusion zone should NOT be affected by hideOnOverview setting. + // When bar is hidden during overview, the exclusion zone should remain to prevent + // windows from moving into the bar area. Auto-hide is handled by the component + // itself via ExclusionMode.Ignore/Auto. + Repeater { + model: Settings.data.bar.barType === "framed" ? ["top", "bottom", "left", "right"] : [Settings.getBarPositionForScreen(windowItem.modelData?.name)] + delegate: Loader { + active: { + if (!windowItem.windowLoaded || !windowItem.shouldBeActive) + return false; + + // Check if bar is configured for this screen + var monitors = Settings.data.bar.monitors || []; + return monitors.length === 0 || monitors.includes(windowItem.modelData?.name); + } + asynchronous: false + + sourceComponent: BarExclusionZone { + screen: windowItem.modelData + edge: modelData + } + + onLoaded: { + Logger.d("AllScreens", "BarExclusionZone (" + modelData + ") created for", windowItem.modelData?.name); + } + } + } + + // PopupMenuWindow - reusable popup window for both tray menus and context menus + // Stays alive when bar is hidden to avoid Wayland surface churn crashes. + // PopupMenuWindow manages its own visibility internally. + Loader { + active: { + // Desktop widgets edit mode needs popup window on ALL screens + if (DesktopWidgetRegistry.editMode && Settings.data.desktopWidgets.enabled) { + return true; + } + + // Normal bar-based condition + if (!parent.windowLoaded || !parent.shouldBeActive) + return false; + + // Check if bar is configured for this screen + var monitors = Settings.data.bar.monitors || []; + return monitors.length === 0 || monitors.includes(modelData?.name); + } + asynchronous: false + + sourceComponent: PopupMenuWindow { + screen: modelData + } + + onLoaded: { + Logger.d("AllScreens", "PopupMenuWindow created for", modelData?.name); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/Backgrounds/AllBackgrounds.qml b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/Backgrounds/AllBackgrounds.qml new file mode 100644 index 0000000..4041bf8 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/Backgrounds/AllBackgrounds.qml @@ -0,0 +1,182 @@ +import QtQuick +import QtQuick.Shapes +import qs.Commons +import qs.Services.UI +import qs.Widgets + +/** +* AllBackgrounds - Unified Shape container for all bar and panel backgrounds +* +* Unified shadow system. This component contains a single Shape +* with multiple ShapePath children (one for bar, one for each panel type). +* +* Benefits: +* - Single GPU-accelerated rendering pass for all backgrounds +* - Unified shadow system (one MultiEffect for everything) +*/ +Item { + id: root + + // Reference Bar + required property var bar + + // Reference to MainScreen (for panel access) + required property var windowRoot + + readonly property color panelBackgroundColor: Color.mSurface + + anchors.fill: parent + + // Unified background container + Item { + anchors.fill: parent + + // When not using separate bar opacity, use unified approach (original behavior) + Item { + anchors.fill: parent + visible: !Settings.data.bar.useSeparateOpacity + + // Enable layer caching to prevent continuous re-rendering + layer.enabled: true + opacity: Color.adaptiveOpacity(Settings.data.ui.panelBackgroundOpacity) + + Shape { + id: unifiedBackgroundsShape + anchors.fill: parent + preferredRendererType: Shape.CurveRenderer + asynchronous: true + enabled: false + + Component.onCompleted: { + Logger.d("AllBackgrounds", "AllBackgrounds initialized"); + } + + /** + * Bar + */ + BarBackground { + bar: root.bar + shapeContainer: unifiedBackgroundsShape + windowRoot: root.windowRoot + backgroundColor: panelBackgroundColor + } + + /** + * Panel Background Slots + * Only 2 slots needed: one for currently open/opening panel, one for closing panel + */ + + // Slot 0: Currently open/opening panel + PanelBackground { + assignedPanel: { + var p = PanelService.backgroundSlotAssignments[0]; + // Only render if this panel belongs to this screen + return (p && p.screen === root.windowRoot.screen) ? p : null; + } + shapeContainer: unifiedBackgroundsShape + defaultBackgroundColor: panelBackgroundColor + } + + // Slot 1: Closing panel (during transitions) + PanelBackground { + assignedPanel: { + var p = PanelService.backgroundSlotAssignments[1]; + // Only render if this panel belongs to this screen + return (p && p.screen === root.windowRoot.screen) ? p : null; + } + shapeContainer: unifiedBackgroundsShape + defaultBackgroundColor: panelBackgroundColor + } + } + + // Apply shadow to the unified backgrounds + NDropShadow { + anchors.fill: parent + source: unifiedBackgroundsShape + } + } + + // When using separate bar opacity, separate the rendering + Item { + anchors.fill: parent + visible: Settings.data.bar.useSeparateOpacity + + // Panel backgrounds with panel opacity + Item { + anchors.fill: parent + + layer.enabled: true + opacity: Color.adaptiveOpacity(Settings.data.ui.panelBackgroundOpacity) + + Shape { + id: panelBackgroundsShape + anchors.fill: parent + preferredRendererType: Shape.CurveRenderer + asynchronous: true + enabled: false + + /** + * Panel Background Slots + * Only 2 slots needed: one for currently open/opening panel, one for closing panel + */ + + // Slot 0: Currently open/opening panel + PanelBackground { + assignedPanel: { + var p = PanelService.backgroundSlotAssignments[0]; + // Only render if this panel belongs to this screen + return (p && p.screen === root.windowRoot.screen) ? p : null; + } + shapeContainer: panelBackgroundsShape + defaultBackgroundColor: panelBackgroundColor + } + + // Slot 1: Closing panel (during transitions) + PanelBackground { + assignedPanel: { + var p = PanelService.backgroundSlotAssignments[1]; + // Only render if this panel belongs to this screen + return (p && p.screen === root.windowRoot.screen) ? p : null; + } + shapeContainer: panelBackgroundsShape + defaultBackgroundColor: panelBackgroundColor + } + } + + // Apply shadow to the panel backgrounds + NDropShadow { + anchors.fill: parent + source: panelBackgroundsShape + } + } + + // Bar background with separate opacity + Item { + anchors.fill: parent + + layer.enabled: true + opacity: Settings.data.bar.backgroundOpacity + + Shape { + id: barBackgroundShape + anchors.fill: parent + preferredRendererType: Shape.CurveRenderer + asynchronous: true + enabled: false + + BarBackground { + bar: root.bar + shapeContainer: barBackgroundShape + windowRoot: root.windowRoot + backgroundColor: panelBackgroundColor + } + } + + NDropShadow { + anchors.fill: parent + source: barBackgroundShape + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/Backgrounds/BarBackground.qml b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/Backgrounds/BarBackground.qml new file mode 100644 index 0000000..18f5e25 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/Backgrounds/BarBackground.qml @@ -0,0 +1,283 @@ +import QtQuick +import QtQuick.Shapes +import qs.Commons +import qs.Modules.MainScreen.Backgrounds +import qs.Services.UI + +/** +* BarBackground - ShapePath component for rendering the bar background +* +* Unified shadow system. This component is a ShapePath that will be +* a child of the unified AllBackgrounds Shape container. +* +* Uses 4-state per-corner system for flexible corner rendering: +* - State -1: No radius (flat/square corner) +* - State 0: Normal (inner curve) +* - State 1: Horizontal inversion (outer curve on X-axis) +* - State 2: Vertical inversion (outer curve on Y-axis) +*/ +ShapePath { + id: root + + // Required reference to the bar component + required property var bar + + // Required reference to AllBackgrounds shapeContainer + required property var shapeContainer + + // Required reference to windowRoot for screen access + required property var windowRoot + + required property color backgroundColor + + // Check if bar should be visible on this screen + readonly property bool shouldShow: { + // Check global bar visibility (includes overview state) + if (!BarService.effectivelyVisible) + return false; + + // Check screen-specific configuration + var monitors = Settings.data.bar.monitors || []; + var screenName = windowRoot?.screen?.name || ""; + + // If no monitors specified, show on all screens + // If monitors specified, only show if this screen is in the list + return monitors.length === 0 || monitors.includes(screenName); + } + + // Corner radius (from Style) + readonly property real radius: Style.radiusL + + // Framed bar properties + readonly property bool isFramed: Settings.data.bar.barType === "framed" + readonly property real frameThickness: Settings.data.bar.frameThickness ?? 12 + readonly property real frameRadius: Settings.data.bar.frameRadius ?? 20 + + // Bar position - since bar's parent fills the screen and Shape also fills the screen, + // we can use bar.x and bar.y directly (they're already in screen coordinates) + readonly property point barMappedPos: bar ? Qt.point(bar.x, bar.y) : Qt.point(0, 0) + + // Effective dimensions - 0 when bar shouldn't show (similar to panel behavior) + readonly property real barWidth: (bar && shouldShow) ? bar.width : 0 + readonly property real barHeight: (bar && shouldShow) ? bar.height : 0 + + // Screen dimensions for frame + readonly property real screenWidth: windowRoot?.screen?.width || 0 + readonly property real screenHeight: windowRoot?.screen?.height || 0 + + // Inner hole dimensions for framed mode - always relative to screen + readonly property string barPosition: Settings.getBarPositionForScreen(windowRoot?.screen?.name) + readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" + readonly property real holeX: (barPosition === "left") ? barWidth : frameThickness + readonly property real holeY: (barPosition === "top") ? barHeight : frameThickness + readonly property real holeWidth: screenWidth - (barPosition === "left" || barPosition === "right" ? (barWidth + frameThickness) : (frameThickness * 2)) + readonly property real holeHeight: screenHeight - (barPosition === "top" || barPosition === "bottom" ? (barHeight + frameThickness) : (frameThickness * 2)) + + // Flatten corners if bar is too small (handle null bar) + readonly property bool shouldFlatten: bar ? ShapeCornerHelper.shouldFlatten(barWidth, barHeight, radius) : false + readonly property real effectiveRadius: shouldFlatten ? (bar ? ShapeCornerHelper.getFlattenedRadius(Math.min(barWidth, barHeight), radius) : 0) : radius + + // Minimum safe arc radius โ€” prevents zero-displacement zero-radius PathArcs + // that crash qTriangulate in CurveRenderer. 0.01px is sub-pixel and invisible. + readonly property real _minR: 0.01 + + // Helper function for getting corner radius based on state + function getCornerRadius(cornerState) { + // State -1 = flat corner โ€” use minimum safe radius instead of 0 + // to prevent degenerate PathArc (zero displacement + zero radius) + if (cornerState === -1) + return _minR; + // All other states use effectiveRadius (clamped to safe minimum) + return Math.max(_minR, effectiveRadius); + } + + // Per-corner multipliers and radii based on bar's corner states (handle null bar) + readonly property real tlMultX: bar ? ShapeCornerHelper.getMultX(bar.topLeftCornerState) : 1 + readonly property real tlMultY: bar ? ShapeCornerHelper.getMultY(bar.topLeftCornerState) : 1 + readonly property real tlRadius: bar ? getCornerRadius(bar.topLeftCornerState) : 0 + + readonly property real trMultX: bar ? ShapeCornerHelper.getMultX(bar.topRightCornerState) : 1 + readonly property real trMultY: bar ? ShapeCornerHelper.getMultY(bar.topRightCornerState) : 1 + readonly property real trRadius: bar ? getCornerRadius(bar.topRightCornerState) : 0 + + readonly property real brMultX: bar ? ShapeCornerHelper.getMultX(bar.bottomRightCornerState) : 1 + readonly property real brMultY: bar ? ShapeCornerHelper.getMultY(bar.bottomRightCornerState) : 1 + readonly property real brRadius: bar ? getCornerRadius(bar.bottomRightCornerState) : 0 + + readonly property real blMultX: bar ? ShapeCornerHelper.getMultX(bar.bottomLeftCornerState) : 1 + readonly property real blMultY: bar ? ShapeCornerHelper.getMultY(bar.bottomLeftCornerState) : 1 + readonly property real blRadius: bar ? getCornerRadius(bar.bottomLeftCornerState) : 0 + + // True when the bar path has valid, non-degenerate geometry to render. + // Mirrors PanelBackground.isRenderable โ€” prevents CurveRenderer crash on zero-area paths. + readonly property bool isRenderable: bar !== null && shouldShow && (isFramed ? (screenWidth > 0 && screenHeight > 0) : (barWidth > 0 && barHeight > 0)) + + // Edge overshoot: extend bar background beyond screen edges where both adjacent + // corners are flat (state -1) to prevent CurveRenderer antialiasing artifacts. + // Uses corner state checks instead of radius === 0 since flat corners now have _minR. + readonly property real screenEdgeOvershoot: 2 + readonly property real topEdgeOvs: (!isFramed && shouldShow && bar && bar.topLeftCornerState === -1 && bar.topRightCornerState === -1 && barMappedPos.y <= 0) ? -screenEdgeOvershoot : 0 + readonly property real bottomEdgeOvs: (!isFramed && shouldShow && bar && bar.bottomLeftCornerState === -1 && bar.bottomRightCornerState === -1 && (barMappedPos.y + barHeight) >= screenHeight) ? screenEdgeOvershoot : 0 + readonly property real leftEdgeOvs: (!isFramed && shouldShow && bar && bar.topLeftCornerState === -1 && bar.bottomLeftCornerState === -1 && barMappedPos.x <= 0) ? -screenEdgeOvershoot : 0 + readonly property real rightEdgeOvs: (!isFramed && shouldShow && bar && bar.topRightCornerState === -1 && bar.bottomRightCornerState === -1 && (barMappedPos.x + barWidth) >= screenWidth) ? screenEdgeOvershoot : 0 + + // Auto-hide opacity factor for background fade + property real opacityFactor: (bar && bar.isHidden) ? 0 : 1 + + Behavior on opacityFactor { + enabled: bar && bar.autoHide + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutQuad + } + } + + // ShapePath configuration + strokeWidth: -1 // No stroke, fill only + fillColor: isRenderable ? Qt.rgba(backgroundColor.r, backgroundColor.g, backgroundColor.b, backgroundColor.a * opacityFactor) : "transparent" + fillRule: isFramed ? ShapePath.OddEvenFill : ShapePath.WindingFill + + // Starting position โ€” falls back to off-screen when not renderable so that + // all subsequent path elements form a valid non-degenerate off-screen square. + // Each edge is split between PathLine and PathArc so no arc has zero displacement, + // preventing CurveRenderer triangulation crashes on degenerate arcs. + // For framed mode the outer path is a full-screen rectangle; _minR offsets at each + // corner prevent zero-displacement zero-radius arcs that crash qTriangulate. + startX: isRenderable ? (isFramed ? _minR : (barMappedPos.x + leftEdgeOvs + tlRadius * tlMultX)) : -0.75 + startY: isRenderable ? (isFramed ? 0 : (barMappedPos.y + topEdgeOvs)) : -1 + + // ========== PATH DEFINITION ========== + + // 1. Main Bar / Outer Screen Rectangle + // When !isRenderable all elements use fallback coordinates forming a valid 1ร—1 + // off-screen square with non-degenerate arcs so CurveRenderer never receives + // a zero-area, bare-moveto, or zero-displacement arc path. + PathLine { + x: root.isRenderable ? (root.isFramed ? (root.screenWidth - root._minR) : (root.barMappedPos.x + root.barWidth + root.rightEdgeOvs - root.trRadius * root.trMultX)) : 0 + y: root.isRenderable ? (root.isFramed ? 0 : (root.barMappedPos.y + root.topEdgeOvs)) : -1 + } + + // Top-right corner + PathArc { + x: root.isRenderable ? (root.isFramed ? root.screenWidth : (root.barMappedPos.x + root.barWidth + root.rightEdgeOvs)) : 0 + y: root.isRenderable ? (root.isFramed ? root._minR : (root.barMappedPos.y + root.topEdgeOvs + root.trRadius * root.trMultY)) : -0.75 + radiusX: root.isRenderable ? (root.isFramed ? root._minR : root.trRadius) : 0 + radiusY: root.isRenderable ? (root.isFramed ? root._minR : root.trRadius) : 0 + direction: ShapeCornerHelper.getArcDirection(root.trMultX, root.trMultY) + } + + PathLine { + x: root.isRenderable ? (root.isFramed ? root.screenWidth : (root.barMappedPos.x + root.barWidth + root.rightEdgeOvs)) : 0 + y: root.isRenderable ? (root.isFramed ? (root.screenHeight - root._minR) : (root.barMappedPos.y + root.barHeight + root.bottomEdgeOvs - root.brRadius * root.brMultY)) : 0 + } + + // Bottom-right corner + PathArc { + x: root.isRenderable ? (root.isFramed ? (root.screenWidth - root._minR) : (root.barMappedPos.x + root.barWidth + root.rightEdgeOvs - root.brRadius * root.brMultX)) : -0.25 + y: root.isRenderable ? (root.isFramed ? root.screenHeight : (root.barMappedPos.y + root.barHeight + root.bottomEdgeOvs)) : 0 + radiusX: root.isRenderable ? (root.isFramed ? root._minR : root.brRadius) : 0 + radiusY: root.isRenderable ? (root.isFramed ? root._minR : root.brRadius) : 0 + direction: ShapeCornerHelper.getArcDirection(root.brMultX, root.brMultY) + } + + PathLine { + x: root.isRenderable ? (root.isFramed ? root._minR : (root.barMappedPos.x + root.leftEdgeOvs + root.blRadius * root.blMultX)) : -1 + y: root.isRenderable ? (root.isFramed ? root.screenHeight : (root.barMappedPos.y + root.barHeight + root.bottomEdgeOvs)) : 0 + } + + // Bottom-left corner + PathArc { + x: root.isRenderable ? (root.isFramed ? 0 : (root.barMappedPos.x + root.leftEdgeOvs)) : -1 + y: root.isRenderable ? (root.isFramed ? (root.screenHeight - root._minR) : (root.barMappedPos.y + root.barHeight + root.bottomEdgeOvs - root.blRadius * root.blMultY)) : -0.25 + radiusX: root.isRenderable ? (root.isFramed ? root._minR : root.blRadius) : 0 + radiusY: root.isRenderable ? (root.isFramed ? root._minR : root.blRadius) : 0 + direction: ShapeCornerHelper.getArcDirection(root.blMultX, root.blMultY) + } + + PathLine { + x: root.isRenderable ? (root.isFramed ? 0 : (root.barMappedPos.x + root.leftEdgeOvs)) : -1 + y: root.isRenderable ? (root.isFramed ? root._minR : (root.barMappedPos.y + root.topEdgeOvs + root.tlRadius * root.tlMultY)) : -1 + } + + // Top-left corner (back to start) + PathArc { + x: root.isRenderable ? (root.isFramed ? root._minR : (root.barMappedPos.x + root.leftEdgeOvs + root.tlRadius * root.tlMultX)) : -0.75 + y: root.isRenderable ? (root.isFramed ? 0 : (root.barMappedPos.y + root.topEdgeOvs)) : -1 + radiusX: root.isRenderable ? (root.isFramed ? root._minR : root.tlRadius) : 0 + radiusY: root.isRenderable ? (root.isFramed ? root._minR : root.tlRadius) : 0 + direction: ShapeCornerHelper.getArcDirection(root.tlMultX, root.tlMultY) + } + + // 2. Inner Hole for Framed Mode (Clockwise) + // When !isFramed, draws a tiny 1x1 rectangle inside the bar as a non-degenerate WindingFill + // no-op to prevent a zero-area degenerate subpath crashing qTriangulate. + // Note: an exact duplicate of the outer path cannot be used here because Qt's CurveRenderer + // has issues with exactly coincident subpaths, causing the fill to not render on some systems. + // When !isRenderable, falls back to a valid 1ร—1 off-screen square at (-3,-3)โ†’(-2,-2). + readonly property real _nhX: barMappedPos.x + barWidth / 2 + readonly property real _nhY: barMappedPos.y + barHeight / 2 + PathMove { + x: root.isRenderable ? (root.isFramed ? (root.holeX + root.frameRadius) : (root._nhX + 0.25)) : -2.75 + y: root.isRenderable ? (root.isFramed ? root.holeY : root._nhY) : -3 + } + + // Top edge + PathLine { + x: root.isRenderable ? (root.isFramed ? (root.holeX + root.holeWidth - root.frameRadius) : (root._nhX + 1)) : -2 + y: root.isRenderable ? (root.isFramed ? root.holeY : root._nhY) : -3 + } + + // Top-right corner + PathArc { + x: root.isRenderable ? (root.isFramed ? (root.holeX + root.holeWidth) : (root._nhX + 1)) : -2 + y: root.isRenderable ? (root.isFramed ? (root.holeY + root.frameRadius) : (root._nhY + 0.25)) : -2.75 + radiusX: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0 + radiusY: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0 + direction: PathArc.Clockwise + } + + // Right edge + PathLine { + x: root.isRenderable ? (root.isFramed ? (root.holeX + root.holeWidth) : (root._nhX + 1)) : -2 + y: root.isRenderable ? (root.isFramed ? (root.holeY + root.holeHeight - root.frameRadius) : (root._nhY + 1)) : -2 + } + + // Bottom-right corner + PathArc { + x: root.isRenderable ? (root.isFramed ? (root.holeX + root.holeWidth - root.frameRadius) : (root._nhX + 0.75)) : -2.25 + y: root.isRenderable ? (root.isFramed ? (root.holeY + root.holeHeight) : (root._nhY + 1)) : -2 + radiusX: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0 + radiusY: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0 + direction: PathArc.Clockwise + } + + // Bottom edge + PathLine { + x: root.isRenderable ? (root.isFramed ? (root.holeX + root.frameRadius) : root._nhX) : -3 + y: root.isRenderable ? (root.isFramed ? (root.holeY + root.holeHeight) : (root._nhY + 1)) : -2 + } + + // Bottom-left corner + PathArc { + x: root.isRenderable ? (root.isFramed ? root.holeX : root._nhX) : -3 + y: root.isRenderable ? (root.isFramed ? (root.holeY + root.holeHeight - root.frameRadius) : (root._nhY + 0.75)) : -2.25 + radiusX: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0 + radiusY: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0 + direction: PathArc.Clockwise + } + + // Left edge + PathLine { + x: root.isRenderable ? (root.isFramed ? root.holeX : root._nhX) : -3 + y: root.isRenderable ? (root.isFramed ? (root.holeY + root.frameRadius) : root._nhY) : -3 + } + + // Top-left corner (back to start) + PathArc { + x: root.isRenderable ? (root.isFramed ? (root.holeX + root.frameRadius) : (root._nhX + 0.25)) : -2.75 + y: root.isRenderable ? (root.isFramed ? root.holeY : root._nhY) : -3 + radiusX: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0 + radiusY: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0 + direction: PathArc.Clockwise + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/Backgrounds/PanelBackground.qml b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/Backgrounds/PanelBackground.qml new file mode 100644 index 0000000..5842b10 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/Backgrounds/PanelBackground.qml @@ -0,0 +1,167 @@ +import QtQuick +import QtQuick.Shapes +import qs.Commons +import qs.Modules.MainScreen.Backgrounds + +/** +* PanelBackground - Dynamic ShapePath for rendering panel backgrounds +* +* Dynamically switches between panels based on which panel is currently +* assigned by PanelService. Only 2 instances are needed: one for the +* currently open panel and one for a closing panel during transitions. +* +* Uses 4-state per-corner system for flexible corner rendering: +* - State -1: No radius (flat/square corner) +* - State 0: Normal (inner curve) +* - State 1: Horizontal inversion (outer curve on X-axis) +* - State 2: Vertical inversion (outer curve on Y-axis) +*/ +ShapePath { + id: root + + // Dynamically assigned panel (null if slot is unused) + property var assignedPanel: null + + // Required reference to AllBackgrounds shapeContainer + required property var shapeContainer + + // Default background color (used if panel doesn't specify one) + property color defaultBackgroundColor: Color.mSurface + + // Corner radius (from Style) + readonly property real radius: Style.radiusL + + // Get panel's panelRegion (geometry placeholder) + readonly property var panelRegion: assignedPanel?.panelRegion ?? null + + // Get the actual panelBackground Item from panelRegion + // Only access panelItem if panelRegion exists and is visible + readonly property var panelBg: (panelRegion && panelRegion.visible) ? panelRegion.panelItem : null + + // Effective background color: use panel's if defined, else default + readonly property color effectiveBackgroundColor: { + if (!assignedPanel) + return "transparent"; + if (assignedPanel.panelBackgroundColor !== undefined) { + return assignedPanel.panelBackgroundColor; + } + return defaultBackgroundColor; + } + + // Panel position - panelBg is in screen coordinates already + readonly property real panelX: panelBg ? panelBg.x : 0 + readonly property real panelY: panelBg ? panelBg.y : 0 + readonly property real panelWidth: panelBg ? panelBg.width : 0 + readonly property real panelHeight: panelBg ? panelBg.height : 0 + readonly property bool isRenderable: assignedPanel && panelBg && panelWidth > 0 && panelHeight > 0 + + // Flatten corners if panel is too small + readonly property bool shouldFlatten: panelBg ? ShapeCornerHelper.shouldFlatten(panelWidth, panelHeight, radius) : false + readonly property real effectiveRadius: shouldFlatten ? ShapeCornerHelper.getFlattenedRadius(Math.min(panelWidth, panelHeight), radius) : radius + + // Minimum safe arc radius โ€” prevents zero-displacement zero-radius PathArcs + // that crash qTriangulate in CurveRenderer. 0.01px is sub-pixel and invisible. + readonly property real _minR: 0.01 + + // Helper function for getting corner radius based on state + function getCornerRadius(cornerState) { + // State -1 = flat corner โ€” use minimum safe radius instead of 0 + // to prevent degenerate PathArc (zero displacement + zero radius) + if (cornerState === -1) + return _minR; + // All other states use effectiveRadius (clamped to safe minimum) + return Math.max(_minR, effectiveRadius); + } + + // Per-corner multipliers and radii based on panelBg's corner states + readonly property real tlMultX: panelBg ? ShapeCornerHelper.getMultX(panelBg.topLeftCornerState) : 1 + readonly property real tlMultY: panelBg ? ShapeCornerHelper.getMultY(panelBg.topLeftCornerState) : 1 + readonly property real tlRadius: panelBg ? getCornerRadius(panelBg.topLeftCornerState) : 0 + + readonly property real trMultX: panelBg ? ShapeCornerHelper.getMultX(panelBg.topRightCornerState) : 1 + readonly property real trMultY: panelBg ? ShapeCornerHelper.getMultY(panelBg.topRightCornerState) : 1 + readonly property real trRadius: panelBg ? getCornerRadius(panelBg.topRightCornerState) : 0 + + readonly property real brMultX: panelBg ? ShapeCornerHelper.getMultX(panelBg.bottomRightCornerState) : 1 + readonly property real brMultY: panelBg ? ShapeCornerHelper.getMultY(panelBg.bottomRightCornerState) : 1 + readonly property real brRadius: panelBg ? getCornerRadius(panelBg.bottomRightCornerState) : 0 + + readonly property real blMultX: panelBg ? ShapeCornerHelper.getMultX(panelBg.bottomLeftCornerState) : 1 + readonly property real blMultY: panelBg ? ShapeCornerHelper.getMultY(panelBg.bottomLeftCornerState) : 1 + readonly property real blRadius: panelBg ? getCornerRadius(panelBg.bottomLeftCornerState) : 0 + + // ShapePath configuration + strokeWidth: -1 // No stroke, fill only + + // Start point - use tiny off-screen non-degenerate fallback when not renderable. + // Fallback forms a 1ร—1 off-screen square where each edge is split between a PathLine + // and a PathArc, ensuring no arc has zero displacement (which can crash qTriangulate). + startX: isRenderable ? (panelX + tlRadius * tlMultX) : -0.75 + startY: isRenderable ? panelY : -1 + + fillColor: isRenderable ? effectiveBackgroundColor : "transparent" + + // ========== PATH DEFINITION ========== + // Draws a rectangle with potentially inverted corners + // All coordinates are relative to startX/startY + + // Top edge (moving right) + PathLine { + relativeX: root.isRenderable ? (root.panelWidth - root.tlRadius * root.tlMultX - root.trRadius * root.trMultX) : 0.75 + relativeY: 0 + } + + // Top-right corner arc + PathArc { + relativeX: root.isRenderable ? (root.trRadius * root.trMultX) : 0 + relativeY: root.isRenderable ? (root.trRadius * root.trMultY) : 0.25 + radiusX: root.isRenderable ? root.trRadius : 0 + radiusY: root.isRenderable ? root.trRadius : 0 + direction: ShapeCornerHelper.getArcDirection(root.trMultX, root.trMultY) + } + + // Right edge (moving down) + PathLine { + relativeX: 0 + relativeY: root.isRenderable ? (root.panelHeight - root.trRadius * root.trMultY - root.brRadius * root.brMultY) : 0.75 + } + + // Bottom-right corner arc + PathArc { + relativeX: root.isRenderable ? (-root.brRadius * root.brMultX) : -0.25 + relativeY: root.isRenderable ? (root.brRadius * root.brMultY) : 0 + radiusX: root.isRenderable ? root.brRadius : 0 + radiusY: root.isRenderable ? root.brRadius : 0 + direction: ShapeCornerHelper.getArcDirection(root.brMultX, root.brMultY) + } + + // Bottom edge (moving left) + PathLine { + relativeX: root.isRenderable ? (-(root.panelWidth - root.brRadius * root.brMultX - root.blRadius * root.blMultX)) : -0.75 + relativeY: 0 + } + + // Bottom-left corner arc + PathArc { + relativeX: root.isRenderable ? (-root.blRadius * root.blMultX) : 0 + relativeY: root.isRenderable ? (-root.blRadius * root.blMultY) : -0.25 + radiusX: root.isRenderable ? root.blRadius : 0 + radiusY: root.isRenderable ? root.blRadius : 0 + direction: ShapeCornerHelper.getArcDirection(root.blMultX, root.blMultY) + } + + // Left edge (moving up) - closes the path back to start + PathLine { + relativeX: 0 + relativeY: root.isRenderable ? (-(root.panelHeight - root.blRadius * root.blMultY - root.tlRadius * root.tlMultY)) : -0.75 + } + + // Top-left corner arc (back to start) + PathArc { + relativeX: root.isRenderable ? (root.tlRadius * root.tlMultX) : 0.25 + relativeY: root.isRenderable ? (-root.tlRadius * root.tlMultY) : 0 + radiusX: root.isRenderable ? root.tlRadius : 0 + radiusY: root.isRenderable ? root.tlRadius : 0 + direction: ShapeCornerHelper.getArcDirection(root.tlMultX, root.tlMultY) + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/Backgrounds/ShapeCornerHelper.qml b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/Backgrounds/ShapeCornerHelper.qml new file mode 100644 index 0000000..f53cc67 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/Backgrounds/ShapeCornerHelper.qml @@ -0,0 +1,74 @@ +pragma Singleton + +import QtQuick +import QtQuick.Shapes +import Quickshell + +/** +* ShapeCornerHelper - Utility singleton for shape corner calculations +* +* Uses 4-state per-corner system for flexible corner rendering: +* - State -1: No radius (flat/square corner) +* - State 0: Normal (inner curve) +* - State 1: Horizontal inversion (outer curve on X-axis) +* - State 2: Vertical inversion (outer curve on Y-axis) +* +* The key technique: Using PathArc direction control (Clockwise vs Counterclockwise) +* combined with multipliers to create both inner and outer corner curves. +*/ +Singleton { + id: root + + /** + * Get X-axis multiplier for a corner state + * State 1 (horizontal invert) returns -1, others return 1 + */ + function getMultX(cornerState) { + return cornerState === 1 ? -1 : 1; + } + + /** + * Get Y-axis multiplier for a corner state + * State 2 (vertical invert) returns -1, others return 1 + */ + function getMultY(cornerState) { + return cornerState === 2 ? -1 : 1; + } + + /** + * Get PathArc direction for a corner based on its multipliers + * Uses XOR logic: if X inverted differs from Y inverted, use Counterclockwise + * This creates the outer curve effect for inverted corners + */ + function getArcDirection(multX, multY) { + return ((multX < 0) !== (multY < 0)) ? PathArc.Counterclockwise : PathArc.Clockwise; + } + + /** + * Convenience function to get arc direction directly from corner state + */ + function getArcDirectionFromState(cornerState) { + const multX = getMultX(cornerState); + const multY = getMultY(cornerState); + return getArcDirection(multX, multY); + } + + /** + * Get the "flattening" radius when shape dimensions are too small + * Prevents visual artifacts when radius exceeds dimensions + */ + function getFlattenedRadius(dimension, requestedRadius) { + if (dimension < requestedRadius * 2) { + return dimension / 2; + } + return requestedRadius; + } + + /** + * Check if a shape should use flattened corners + * Returns true if width or height is too small for the requested radius + */ + function shouldFlatten(width, height, radius) { + return width < radius * 2 || height < radius * 2; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/BarContentWindow.qml b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/BarContentWindow.qml new file mode 100644 index 0000000..32bcc95 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/BarContentWindow.qml @@ -0,0 +1,255 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Modules.Bar +import qs.Services.UI + +/** +* BarContentWindow - Separate transparent PanelWindow for bar content +* +* This window contains only the bar widgets (content), while the background +* is rendered in MainScreen's unified Shape system. This separation prevents +* fullscreen redraws when bar widgets redraw. +* +* This component should be instantiated once per screen by AllScreens.qml +*/ +PanelWindow { + id: barWindow + + // Note: screen property is inherited from PanelWindow and should be set by parent + color: "transparent" // Transparent - background is in MainScreen below + + // Window invisible when auto-hidden (blocks input) or toggled off via IPC. + // windowVisible stays true briefly after isHidden to allow fade-out animation. + property bool windowVisible: !isHidden + visible: contentLoaded && windowVisible && BarService.effectivelyVisible + + Component.onCompleted: { + Logger.d("BarContentWindow", "Bar content window created for screen:", barWindow.screen?.name); + if (!isHidden) + contentLoaded = true; + } + + // Wayland layer configuration + WlrLayershell.namespace: "noctalia-bar-content-" + (barWindow.screen?.name || "unknown") + WlrLayershell.layer: WlrLayer.Top + WlrLayershell.exclusionMode: ExclusionMode.Ignore // Don't reserve space - BarExclusionZone in MainScreen handles that + + // Position and size to match bar location (per-screen) + readonly property string barPosition: Settings.getBarPositionForScreen(barWindow.screen?.name) + readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" + readonly property bool isFramed: Settings.data.bar.barType === "framed" + readonly property real frameThickness: Settings.data.bar.frameThickness ?? 12 + readonly property bool barFloating: Settings.data.bar.barType === "floating" + readonly property real barMarginH: Math.ceil(barFloating ? Settings.data.bar.marginHorizontal : 0) + readonly property real barMarginV: Math.ceil(barFloating ? Settings.data.bar.marginVertical : 0) + readonly property real barHeight: Style.getBarHeightForScreen(barWindow.screen?.name) + + // Auto-hide properties + readonly property bool autoHide: Settings.getBarDisplayModeForScreen(barWindow.screen?.name) === "auto_hide" + readonly property int hideDelay: Settings.data.bar.autoHideDelay || 500 + readonly property int showDelay: Settings.data.bar.autoShowDelay || 100 + property bool isHidden: autoHide + + // Hover tracking + property bool barHovered: false + + // Check if any panel is open on this screen + readonly property bool panelOpen: PanelService.openedPanel !== null + + // Timer for delayed hide + Timer { + id: hideTimer + interval: barWindow.hideDelay + onTriggered: { + if (barWindow.autoHide && !barWindow.barHovered && !barWindow.panelOpen && !BarService.popupOpen) { + BarService.setScreenHidden(barWindow.screen?.name, true); + } + } + } + + // Timer for delayed show + Timer { + id: showTimer + interval: barWindow.showDelay + onTriggered: { + // Only show if still hovered (via trigger zone or bar itself) + if (barWindow.autoHide && BarService.isBarHovered(barWindow.screen?.name)) { + BarService.setScreenHidden(barWindow.screen?.name, false); + } + } + } + + // React to auto-hide state changes from BarService + Connections { + target: BarService + function onBarAutoHideStateChanged(screenName, hidden) { + Logger.d("BarContentWindow", "onBarAutoHideStateChanged:", screenName, hidden, "my screen:", barWindow.screen?.name); + if (screenName === barWindow.screen?.name) { + barWindow.isHidden = hidden; + } + } + function onBarHoverStateChanged(screenName, hovered) { + if (screenName === barWindow.screen?.name && barWindow.autoHide) { + if (hovered) { + hideTimer.stop(); + // If bar is already visible, no need to delay + if (!barWindow.isHidden) { + showTimer.stop(); + } else { + // Bar is hidden, use show delay + showTimer.restart(); + } + } else if (!barWindow.barHovered && !barWindow.panelOpen) { + showTimer.stop(); + hideTimer.restart(); + } + } + } + } + + // Don't hide when panel is open + onPanelOpenChanged: { + if (panelOpen && autoHide) { + hideTimer.stop(); + BarService.setScreenHidden(barWindow.screen?.name, false); + } else if (!panelOpen && autoHide && !barHovered) { + hideTimer.restart(); + } + } + + // React to popup menu closing + Connections { + target: BarService + function onPopupOpenChanged() { + if (!BarService.popupOpen && barWindow.autoHide && !barWindow.barHovered && !barWindow.panelOpen) { + hideTimer.restart(); + } + } + } + + // React to displayMode changes + onAutoHideChanged: { + if (!autoHide) { + // Show bar when auto-hide is disabled + hideTimer.stop(); + showTimer.stop(); + barWindow.isHidden = false; + } + // When auto-hide is enabled, don't immediately hide - wait for mouse to leave + } + + // Anchor to the bar's edge + anchors { + top: barPosition === "top" || barIsVertical + bottom: barPosition === "bottom" || barIsVertical + left: barPosition === "left" || !barIsVertical + right: barPosition === "right" || !barIsVertical + } + + // Content stays loaded once initialized โ€” never unloaded during auto-hide. + // Destroying and recreating widgets on every hide/show cycle caused nested + // QML incubation crashes (SIGSEGV in QV4::Object::insertMember) because + // async widget Loaders complete during incubateFor() and their onLoaded + // handlers trigger signal cascades mid-incubation. + // The bar is hidden via opacity + window visibility instead. + property bool contentLoaded: false + + // Delay window hide to allow fade-out animation to complete + Timer { + id: windowHideTimer + interval: Style.animationFast + onTriggered: { + if (barWindow.isHidden) + barWindow.windowVisible = false; + } + } + + onIsHiddenChanged: { + if (isHidden) { + // Delay window hide so fade-out is visible + windowHideTimer.restart(); + } else { + windowHideTimer.stop(); + windowVisible = true; + if (!contentLoaded) + contentLoaded = true; + } + } + + Connections { + target: BarService + function onEffectivelyVisibleChanged() { + if (BarService.effectivelyVisible && !barWindow.isHidden && !barWindow.contentLoaded) { + barWindow.contentLoaded = true; + } + } + } + + // Handle floating margins and framed mode offsets + margins { + top: (barPosition === "top") ? barMarginV : (isFramed ? frameThickness : barMarginV) + bottom: (barPosition === "bottom") ? barMarginV : (isFramed ? frameThickness : barMarginV) + left: (barPosition === "left") ? barMarginH : (isFramed ? frameThickness : barMarginH) + right: (barPosition === "right") ? barMarginH : (isFramed ? frameThickness : barMarginH) + } + + // Set a tight window size + implicitWidth: barIsVertical ? barHeight : barWindow.screen.width + implicitHeight: barIsVertical ? barWindow.screen.height : barHeight + + // Bar content loader - loaded once, stays active for lifetime + Loader { + id: barLoader + anchors.fill: parent + active: barWindow.contentLoaded + + sourceComponent: Item { + anchors.fill: parent + + // Fade animation + opacity: barWindow.isHidden ? 0 : 1 + + Behavior on opacity { + enabled: barWindow.autoHide + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutQuad + } + } + + Bar { + id: barContent + anchors.fill: parent + screen: barWindow.screen + + // Hover detection using HoverHandler (doesn't block child hover events) + HoverHandler { + id: hoverHandler + + onHoveredChanged: { + if (hovered) { + barWindow.barHovered = true; + BarService.setScreenHovered(barWindow.screen?.name, true); + if (barWindow.autoHide) { + hideTimer.stop(); + showTimer.restart(); + } + } else { + // Skip if already hidden (being destroyed) + if (barWindow.isHidden) + return; + barWindow.barHovered = false; + BarService.setScreenHovered(barWindow.screen?.name, false); + if (barWindow.autoHide && !barWindow.panelOpen) { + showTimer.stop(); + hideTimer.restart(); + } + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/BarExclusionZone.qml b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/BarExclusionZone.qml new file mode 100644 index 0000000..cad17da --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/BarExclusionZone.qml @@ -0,0 +1,74 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services.Compositor +import qs.Services.UI + +/** +* BarExclusionZone - Invisible PanelWindow that reserves exclusive space for the bar +* +* This is a minimal window that works with the compositor to reserve space, +* while the actual bar UI is rendered in MainScreen. +*/ +PanelWindow { + id: root + + // Edge to anchor to and thickness to reserve + property string edge: Settings.getBarPositionForScreen(screen?.name) + property real thickness: (edge === Settings.getBarPositionForScreen(screen?.name)) ? Style.getBarHeightForScreen(screen?.name) : (Settings.data.bar.frameThickness ?? 12) + + readonly property bool autoHide: Settings.getBarDisplayModeForScreen(screen?.name) === "auto_hide" + readonly property bool nonExclusive: Settings.getBarDisplayModeForScreen(screen?.name) === "non_exclusive" + readonly property bool barFloating: Settings.data.bar.barType === "floating" + readonly property real barMarginH: (barFloating && edge === Settings.getBarPositionForScreen(screen?.name)) ? Math.ceil(Settings.data.bar.marginHorizontal) : 0 + readonly property real barMarginV: (barFloating && edge === Settings.getBarPositionForScreen(screen?.name)) ? Math.ceil(Settings.data.bar.marginVertical) : 0 + // Allow users to enable a 1-physical-pixel inset for the exclusion zone so window borders can bleed under the bar + readonly property real bleedOffset: Settings.data.bar.enableExclusionZoneInset ? 1.0 : 0.0 + readonly property real bleedInset: { + const info = CompositorService.displayScales[screen?.name]; + const scale = (info && info.scale) ? info.scale : 1.0; + return bleedOffset / scale; + } + + // Invisible - just reserves space + color: "transparent" + + mask: Region {} + + // Wayland layer shell configuration + WlrLayershell.layer: WlrLayer.Top + WlrLayershell.namespace: "noctalia-bar-exclusion-" + edge + "-" + (screen?.name || "unknown") + // When auto-hide, non-exclusive mode is enabled, OR bar is explicitly hidden via IPC, don't reserve space + // Note: We check BarService.isVisible directly, NOT effectivelyVisible, because we want + // the exclusion zone to stay during overview (effectivelyVisible is false during overview + // when hideOnOverview is enabled, but isVisible remains true) + WlrLayershell.exclusionMode: (autoHide || nonExclusive || !BarService.isVisible) ? ExclusionMode.Ignore : ExclusionMode.Auto + + // Anchor based on specified edge + anchors { + top: edge === "top" + bottom: edge === "bottom" + left: edge === "left" || edge === "top" || edge === "bottom" + right: edge === "right" || edge === "top" || edge === "bottom" + } + + // Size based on orientation + implicitWidth: { + if (edge === "left" || edge === "right") { + return thickness + barMarginH - bleedInset; + } + return 0; // Auto-width when left/right anchors are true + } + + implicitHeight: { + if (edge === "top" || edge === "bottom") { + return thickness + barMarginV - bleedInset; + } + return 0; // Auto-height when top/bottom anchors are true + } + + Component.onCompleted: { + Logger.d("BarExclusionZone", "Created for screen:", screen?.name); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/BarTriggerZone.qml b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/BarTriggerZone.qml new file mode 100644 index 0000000..b7a9cd5 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/BarTriggerZone.qml @@ -0,0 +1,66 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services.UI + +/** +* BarTriggerZone - Thin invisible window at screen edge to reveal hidden bar +* +* This window is only active when the bar is in auto-hide mode and hidden. +* When the mouse enters this zone, it triggers the bar to show. +*/ +PanelWindow { + id: root + + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" + readonly property int triggerSize: 1 + + // Track if component is being destroyed to prevent signals during cleanup + property bool isBeingDestroyed: false + Component.onDestruction: isBeingDestroyed = true + + // Invisible trigger zone + color: "transparent" + focusable: false + + WlrLayershell.namespace: "noctalia-bar-trigger-" + (screen?.name || "unknown") + WlrLayershell.layer: WlrLayer.Top + WlrLayershell.exclusionMode: ExclusionMode.Ignore + + // Anchor to bar's edge + anchors { + top: barPosition === "top" || barIsVertical + bottom: barPosition === "bottom" || barIsVertical + left: barPosition === "left" || !barIsVertical + right: barPosition === "right" || !barIsVertical + } + + // Size based on orientation - thin strip at edge + implicitWidth: barIsVertical ? triggerSize : 0 + implicitHeight: !barIsVertical ? triggerSize : 0 + + MouseArea { + id: triggerArea + anchors.fill: parent + hoverEnabled: true + + onEntered: { + if (root.isBeingDestroyed) + return; + // Signal hover - BarContentWindow will handle the show delay + BarService.setScreenHovered(root.screen?.name, true); + } + + onExited: { + if (root.isBeingDestroyed) + return; + BarService.setScreenHovered(root.screen?.name, false); + } + } + + Component.onCompleted: { + Logger.d("BarTriggerZone", "Created for screen:", screen?.name); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/MainScreen.qml b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/MainScreen.qml new file mode 100644 index 0000000..4526a29 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/MainScreen.qml @@ -0,0 +1,690 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import Quickshell +import Quickshell.Wayland +import "Backgrounds" as Backgrounds + +import qs.Commons + +// All panels +import qs.Modules.Bar +import qs.Modules.Bar.Extras +import qs.Modules.Panels.Audio +import qs.Modules.Panels.Battery +import qs.Modules.Panels.Bluetooth +import qs.Modules.Panels.Brightness +import qs.Modules.Panels.Changelog +import qs.Modules.Panels.Clock +import qs.Modules.Panels.ControlCenter +import qs.Modules.Panels.Dock +import qs.Modules.Panels.Launcher +import qs.Modules.Panels.Media +import qs.Modules.Panels.Network +import qs.Modules.Panels.NotificationHistory +import qs.Modules.Panels.Plugins +import qs.Modules.Panels.SessionMenu +import qs.Modules.Panels.Settings +import qs.Modules.Panels.SetupWizard +import qs.Modules.Panels.SystemStats +import qs.Modules.Panels.Tray +import qs.Modules.Panels.Wallpaper +import qs.Services.Compositor +import qs.Services.Power +import qs.Services.UI + +/** +* MainScreen - Single PanelWindow per screen that manages all panels and the bar +*/ +PanelWindow { + id: root + + Component.onCompleted: { + Logger.d("MainScreen", "Initialized for screen:", screen?.name, "- Dimensions:", screen?.width, "x", screen?.height, "- Position:", screen?.x, ",", screen?.y); + } + + WlrLayershell.layer: WlrLayer.Top + WlrLayershell.namespace: "noctalia-background-" + (screen?.name || "unknown") + WlrLayershell.exclusionMode: ExclusionMode.Ignore // Don't reserve space - BarExclusionZone handles that + WlrLayershell.keyboardFocus: { + // No panel open anywhere: no keyboard focus needed + if (!root.isAnyPanelOpen) { + return WlrKeyboardFocus.None; + } + // Panel open on THIS screen: use panel's preferred focus mode + if (root.isPanelOpen) { + // Hyprland's Exclusive captures ALL input globally (including pointer), + // preventing click-to-close from working on other monitors. + // Workaround: briefly use Exclusive when panel opens (for text input focus), + // then switch to OnDemand (for click-to-close on other screens). + if (CompositorService.isHyprland) { + return PanelService.isInitializingKeyboard ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.OnDemand; + } + return PanelService.openedPanel.exclusiveKeyboard ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.OnDemand; + } + // Panel open on ANOTHER screen: OnDemand allows receiving pointer events for click-to-close + return WlrKeyboardFocus.OnDemand; + } + + anchors { + top: true + bottom: true + left: true + right: true + } + + // Desktop dimming when panels are open + property real dimmerOpacity: Settings.data.general.dimmerOpacity ?? 0.8 + property bool isPanelOpen: (PanelService.openedPanel !== null) && (PanelService.openedPanel.screen === screen) + property bool isPanelClosing: (PanelService.openedPanel !== null) && PanelService.openedPanel.isClosing + property bool isAnyPanelOpen: PanelService.openedPanel !== null + + color: { + if (dimmerOpacity > 0 && isPanelOpen && !isPanelClosing) { + return Qt.alpha(Color.mShadow, dimmerOpacity); + } + return "transparent"; + } + + Behavior on color { + enabled: !PanelService.closedImmediately + ColorAnimation { + duration: isPanelClosing ? Style.animationFaster : Style.animationNormal + easing.type: Easing.OutQuad + } + } + + // Reset closedImmediately flag after color change is applied + onColorChanged: { + if (PanelService.closedImmediately) { + PanelService.closedImmediately = false; + } + } + + // Check if bar should be visible on this screen + readonly property bool barShouldShow: { + // Check global bar visibility (includes overview state) + if (!BarService.effectivelyVisible) + return false; + + // Check screen-specific configuration + var monitors = Settings.data.bar.monitors || []; + var screenName = screen?.name || ""; + + // If no monitors specified, show on all screens + // If monitors specified, only show if this screen is in the list + return monitors.length === 0 || monitors.includes(screenName); + } + + // Make everything click-through except bar + mask: Region { + id: clickableMask + + // Cover entire window (everything is masked/click-through) + x: 0 + y: 0 + width: root.width + height: root.height + intersection: Intersection.Xor + + // Only include regions that are actually needed + // panelRegions is handled by PanelService, bar is local to this screen + regions: [barMaskRegion, backgroundMaskRegion] + + // Bar region - subtract bar area from mask (only if bar should be shown on this screen) + Region { + id: barMaskRegion + + readonly property bool isFramed: Settings.data.bar.barType === "framed" + readonly property real barThickness: Style.barHeight + readonly property real frameThickness: Settings.data.bar.frameThickness ?? 12 + readonly property string barPos: Settings.data.bar.position || "top" + + // Bar / Frame Mask + Region { + // Mode: Simple or Floating + x: barPlaceholder.x + y: barPlaceholder.y + width: (!barMaskRegion.isFramed && root.barShouldShow) ? barPlaceholder.width : 0 + height: (!barMaskRegion.isFramed && root.barShouldShow) ? barPlaceholder.height : 0 + intersection: Intersection.Subtract + } + + // Mode: Framed - 4 sides + Region { + // Top side + Region { + x: 0 + y: 0 + width: (barMaskRegion.isFramed && root.barShouldShow) ? root.width : 0 + height: (barMaskRegion.isFramed && root.barShouldShow) ? (barMaskRegion.barPos === "top" ? barMaskRegion.barThickness : barMaskRegion.frameThickness) : 0 + intersection: Intersection.Subtract + } + + // Bottom side + Region { + x: 0 + y: (barMaskRegion.isFramed && root.barShouldShow) ? (root.height - (barMaskRegion.barPos === "bottom" ? barMaskRegion.barThickness : barMaskRegion.frameThickness)) : 0 + width: (barMaskRegion.isFramed && root.barShouldShow) ? root.width : 0 + height: (barMaskRegion.isFramed && root.barShouldShow) ? (barMaskRegion.barPos === "bottom" ? barMaskRegion.barThickness : barMaskRegion.frameThickness) : 0 + intersection: Intersection.Subtract + } + + // Left side + Region { + x: 0 + y: 0 + width: (barMaskRegion.isFramed && root.barShouldShow) ? (barMaskRegion.barPos === "left" ? barMaskRegion.barThickness : barMaskRegion.frameThickness) : 0 + height: (barMaskRegion.isFramed && root.barShouldShow) ? root.height : 0 + intersection: Intersection.Subtract + } + + // Right side + Region { + x: (barMaskRegion.isFramed && root.barShouldShow) ? (root.width - (barMaskRegion.barPos === "right" ? barMaskRegion.barThickness : barMaskRegion.frameThickness)) : 0 + width: (barMaskRegion.isFramed && root.barShouldShow) ? (barMaskRegion.barPos === "right" ? barMaskRegion.barThickness : barMaskRegion.frameThickness) : 0 + height: (barMaskRegion.isFramed && root.barShouldShow) ? root.height : 0 + intersection: Intersection.Subtract + } + } + } + + // Background region for click-to-close - reactive sizing + // Uses isAnyPanelOpen so clicking on any screen's background closes the panel + Region { + id: backgroundMaskRegion + x: 0 + y: 0 + width: root.isAnyPanelOpen ? root.width : 0 + height: root.isAnyPanelOpen ? root.height : 0 + intersection: Intersection.Subtract + } + } + + // Blur behind the bar and open panels โ€” attached to PanelWindow (required by BackgroundEffect API) + BackgroundEffect.blurRegion: Settings.data.general.enableBlurBehind ? blurRegion : null + Region { + id: blurRegion + // โ”€โ”€ Non-framed bar (simple/floating): single rectangle with bar corner states โ”€โ”€ + Region { + x: (!barPlaceholder.isFramed && root.barShouldShow && !barPlaceholder.isHidden) ? barPlaceholder.x : 0 + y: (!barPlaceholder.isFramed && root.barShouldShow && !barPlaceholder.isHidden) ? barPlaceholder.y : 0 + width: (!barPlaceholder.isFramed && root.barShouldShow && !barPlaceholder.isHidden) ? barPlaceholder.width : 0 + height: (!barPlaceholder.isFramed && root.barShouldShow && !barPlaceholder.isHidden) ? barPlaceholder.height : 0 + radius: Style.radiusL + topLeftCorner: barPlaceholder.topLeftCornerState + topRightCorner: barPlaceholder.topRightCornerState + bottomLeftCorner: barPlaceholder.bottomLeftCornerState + bottomRightCorner: barPlaceholder.bottomRightCornerState + } + + // โ”€โ”€ Framed bar: full screen minus rounded hole โ”€โ”€ + Region { + x: 0 + y: 0 + width: (barPlaceholder.isFramed && root.barShouldShow && !barPlaceholder.isHidden) ? root.width : 0 + height: (barPlaceholder.isFramed && root.barShouldShow && !barPlaceholder.isHidden) ? root.height : 0 + + Region { + intersection: Intersection.Subtract + x: backgroundBlur.frameHoleX + y: backgroundBlur.frameHoleY + width: backgroundBlur.frameHoleX2 - backgroundBlur.frameHoleX + height: backgroundBlur.frameHoleY2 - backgroundBlur.frameHoleY + radius: backgroundBlur.frameR + } + } + + // โ”€โ”€ Panel blur regions โ”€โ”€ + // Opening panel + Region { + x: backgroundBlur.panelBg ? Math.round(backgroundBlur.panelBg.x) : 0 + y: backgroundBlur.panelBg ? Math.round(backgroundBlur.panelBg.y) : 0 + width: backgroundBlur.panelBg ? Math.round(backgroundBlur.panelBg.width) : 0 + height: backgroundBlur.panelBg ? Math.round(backgroundBlur.panelBg.height) : 0 + radius: Style.radiusL + topLeftCorner: backgroundBlur.panelBg ? backgroundBlur.panelBg.topLeftCornerState : CornerState.Normal + topRightCorner: backgroundBlur.panelBg ? backgroundBlur.panelBg.topRightCornerState : CornerState.Normal + bottomLeftCorner: backgroundBlur.panelBg ? backgroundBlur.panelBg.bottomLeftCornerState : CornerState.Normal + bottomRightCorner: backgroundBlur.panelBg ? backgroundBlur.panelBg.bottomRightCornerState : CornerState.Normal + } + + // Closing panel (coexists with opening panel during transition) + Region { + x: backgroundBlur.closingPanelBg ? Math.round(backgroundBlur.closingPanelBg.x) : 0 + y: backgroundBlur.closingPanelBg ? Math.round(backgroundBlur.closingPanelBg.y) : 0 + width: backgroundBlur.closingPanelBg ? Math.round(backgroundBlur.closingPanelBg.width) : 0 + height: backgroundBlur.closingPanelBg ? Math.round(backgroundBlur.closingPanelBg.height) : 0 + radius: Style.radiusL + topLeftCorner: backgroundBlur.closingPanelBg ? backgroundBlur.closingPanelBg.topLeftCornerState : CornerState.Normal + topRightCorner: backgroundBlur.closingPanelBg ? backgroundBlur.closingPanelBg.topRightCornerState : CornerState.Normal + bottomLeftCorner: backgroundBlur.closingPanelBg ? backgroundBlur.closingPanelBg.bottomLeftCornerState : CornerState.Normal + bottomRightCorner: backgroundBlur.closingPanelBg ? backgroundBlur.closingPanelBg.bottomRightCornerState : CornerState.Normal + } + } + + // -------------------------------------- + // Container for all UI elements + Item { + id: container + width: root.width + height: root.height + + // Unified backgrounds container / unified shadow system + // Renders all bar and panel backgrounds as ShapePaths within a single Shape + // This allows the shadow effect to apply to all backgrounds in one render pass + Backgrounds.AllBackgrounds { + id: unifiedBackgrounds + anchors.fill: parent + bar: barPlaceholder.barItem || null + windowRoot: root + z: 0 // Behind all content + } + + // Background MouseArea for closing panels when clicking outside + // Uses isAnyPanelOpen so clicking on any screen's background closes the panel + MouseArea { + anchors.fill: parent + enabled: root.isAnyPanelOpen + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + onClicked: mouse => { + if (PanelService.openedPanel) { + PanelService.openedPanel.close(); + } + } + z: 0 // Behind panels and bar + } + + // --------------------------------------- + // All panels always exist + // --------------------------------------- + AudioPanel { + id: audioPanel + objectName: "audioPanel-" + (root.screen?.name || "unknown") + screen: root.screen + } + + MediaPlayerPanel { + id: mediaPlayerPanel + objectName: "mediaPlayerPanel-" + (root.screen?.name || "unknown") + screen: root.screen + } + + BatteryPanel { + id: batteryPanel + objectName: "batteryPanel-" + (root.screen?.name || "unknown") + screen: root.screen + } + + BluetoothPanel { + id: bluetoothPanel + objectName: "bluetoothPanel-" + (root.screen?.name || "unknown") + screen: root.screen + } + + BrightnessPanel { + id: brightnessPanel + objectName: "brightnessPanel-" + (root.screen?.name || "unknown") + screen: root.screen + } + + ControlCenterPanel { + id: controlCenterPanel + objectName: "controlCenterPanel-" + (root.screen?.name || "unknown") + screen: root.screen + } + + ChangelogPanel { + id: changelogPanel + objectName: "changelogPanel-" + (root.screen?.name || "unknown") + screen: root.screen + } + + ClockPanel { + id: clockPanel + objectName: "clockPanel-" + (root.screen?.name || "unknown") + screen: root.screen + } + + Launcher { + id: launcherPanel + objectName: "launcherPanel-" + (root.screen?.name || "unknown") + screen: root.screen + } + + NotificationHistoryPanel { + id: notificationHistoryPanel + objectName: "notificationHistoryPanel-" + (root.screen?.name || "unknown") + screen: root.screen + } + + SessionMenu { + id: sessionMenuPanel + objectName: "sessionMenuPanel-" + (root.screen?.name || "unknown") + screen: root.screen + } + + SettingsPanel { + id: settingsPanel + objectName: "settingsPanel-" + (root.screen?.name || "unknown") + screen: root.screen + } + + SetupWizard { + id: setupWizardPanel + objectName: "setupWizardPanel-" + (root.screen?.name || "unknown") + screen: root.screen + } + + TrayDrawerPanel { + id: trayDrawerPanel + objectName: "trayDrawerPanel-" + (root.screen?.name || "unknown") + screen: root.screen + } + + WallpaperPanel { + id: wallpaperPanel + objectName: "wallpaperPanel-" + (root.screen?.name || "unknown") + screen: root.screen + } + + NetworkPanel { + id: networkPanel + objectName: "networkPanel-" + (root.screen?.name || "unknown") + screen: root.screen + } + + SystemStatsPanel { + id: systemStatsPanel + objectName: "systemStatsPanel-" + (root.screen?.name || "unknown") + screen: root.screen + } + + StaticDockPanel { + id: staticDockPanel + objectName: "staticDockPanel-" + (root.screen?.name || "unknown") + screen: root.screen + } + + // ---------------------------------------------- + // Plugin panel slots + // ---------------------------------------------- + PluginPanelSlot { + id: pluginPanel1 + objectName: "pluginPanel1-" + (root.screen?.name || "unknown") + screen: root.screen + slotNumber: 1 + } + + PluginPanelSlot { + id: pluginPanel2 + objectName: "pluginPanel2-" + (root.screen?.name || "unknown") + screen: root.screen + slotNumber: 2 + } + + // ---------------------------------------------- + // Bar background placeholder - just for background positioning (actual bar content is in BarContentWindow) + Item { + id: barPlaceholder + + // Expose self as barItem for AllBackgrounds compatibility + readonly property var barItem: barPlaceholder + + // Screen reference + property ShellScreen screen: root.screen + + // Bar background positioning properties (per-screen) + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" + readonly property bool isFramed: Settings.data.bar.barType === "framed" + readonly property real frameThickness: Settings.data.bar.frameThickness ?? 12 + readonly property bool barFloating: Settings.data.bar.barType === "floating" + readonly property real barMarginH: barFloating ? Math.floor(Settings.data.bar.marginHorizontal) : 0 + readonly property real barMarginV: barFloating ? Math.floor(Settings.data.bar.marginVertical) : 0 + readonly property real barHeight: Style.getBarHeightForScreen(screen?.name) + + // Auto-hide properties (read by AllBackgrounds for background fade) + readonly property bool autoHide: Settings.getBarDisplayModeForScreen(screen?.name) === "auto_hide" + property bool isHidden: autoHide + + Connections { + target: BarService + function onBarAutoHideStateChanged(screenName, hidden) { + if (screenName === barPlaceholder.screen?.name) { + barPlaceholder.isHidden = hidden; + } + } + } + + // Expose bar dimensions directly on this Item for BarBackground + // Use screen dimensions directly + x: { + if (barPosition === "right") + return (screen?.width ?? 0) - barHeight - barMarginH; + if (isFramed && !barIsVertical) + return frameThickness; + return barMarginH; + } + y: { + if (barPosition === "bottom") + return (screen?.height ?? 0) - barHeight - barMarginV; + if (isFramed && barIsVertical) + return frameThickness; + return barMarginV; + } + width: { + if (barIsVertical) { + return barHeight; + } + if (isFramed) + return (screen?.width ?? 0) - frameThickness * 2; + return (screen?.width ?? 0) - barMarginH * 2; + } + height: { + if (!barIsVertical) { + return barHeight; + } + if (isFramed) + return (screen?.height ?? 0) - frameThickness * 2; + return (screen?.height ?? 0) - barMarginV * 2; + } + + // Corner states (same as Bar.qml) + readonly property int topLeftCornerState: { + if (barFloating) + return 0; + if (barPosition === "top") + return -1; + if (barPosition === "left") + return -1; + if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "right")) { + return barIsVertical ? 1 : 2; + } + return -1; + } + + readonly property int topRightCornerState: { + if (barFloating) + return 0; + if (barPosition === "top") + return -1; + if (barPosition === "right") + return -1; + if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "left")) { + return barIsVertical ? 1 : 2; + } + return -1; + } + + readonly property int bottomLeftCornerState: { + if (barFloating) + return 0; + if (barPosition === "bottom") + return -1; + if (barPosition === "left") + return -1; + if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "right")) { + return barIsVertical ? 1 : 2; + } + return -1; + } + + readonly property int bottomRightCornerState: { + if (barFloating) + return 0; + if (barPosition === "bottom") + return -1; + if (barPosition === "right") + return -1; + if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "left")) { + return barIsVertical ? 1 : 2; + } + return -1; + } + } + + // Screen Corners + ScreenCorners {} + + // Blur behind the bar and open panels + // Helper object holding computed properties for blur regions + QtObject { + id: backgroundBlur + + // Panel background geometry (from the currently open panel on this screen) + readonly property var panelBg: { + var op = PanelService.openedPanel; + if (!op || op.screen !== root.screen || op.blurEnabled === false) + return null; + var region = op.panelRegion; + return (region && region.visible) ? region.panelItem : null; + } + + // Panel background geometry for the closing panel (may coexist with panelBg) + readonly property var closingPanelBg: { + var cp = PanelService.closingPanel; + if (!cp || cp.screen !== root.screen || cp.blurEnabled === false) + return null; + var region = cp.panelRegion; + return (region && region.visible) ? region.panelItem : null; + } + + // Framed bar: inner hole boundary (where the hole begins on each axis) + // These are the x/y coordinates of the 4 inner hole corners + readonly property real frameHoleX: barPlaceholder.barPosition === "left" ? barPlaceholder.barHeight : barPlaceholder.frameThickness + readonly property real frameHoleY: barPlaceholder.barPosition === "top" ? barPlaceholder.barHeight : barPlaceholder.frameThickness + readonly property real frameHoleX2: root.width - (barPlaceholder.barPosition === "right" ? barPlaceholder.barHeight : barPlaceholder.frameThickness) + readonly property real frameHoleY2: root.height - (barPlaceholder.barPosition === "bottom" ? barPlaceholder.barHeight : barPlaceholder.frameThickness) + readonly property real frameR: Settings.data.bar.frameRadius ?? 20 + } + + // Native idle inhibitor โ€” one per active MainScreen window. + // Multiple inhibitors bound to the same enabled state are harmless; + // having one per screen is more robust than picking a "primary" screen. + IdleInhibitor { + window: root + enabled: IdleInhibitorService.isInhibited + + Component.onCompleted: { + IdleInhibitorService.nativeInhibitorAvailable = true; + Logger.d("IdleInhibitor", "Native IdleInhibitor active on screen:", root.screen?.name); + } + } + } + + // Centralized Keyboard Shortcuts + + // These shortcuts delegate to the opened panel's handler functions + // Panels can implement: onEscapePressed, onTabPressed, onBackTabPressed, + // onUpPressed, onDownPressed, onReturnPressed, etc... + Instantiator { + model: Settings.data.general.keybinds.keyEscape || [] + Shortcut { + sequence: modelData + enabled: root.isPanelOpen && (PanelService.openedPanel.onEscapePressed !== undefined) && !PanelService.isKeybindRecording + onActivated: PanelService.openedPanel.onEscapePressed() + } + } + + Shortcut { + sequence: "Tab" + enabled: root.isPanelOpen && (PanelService.openedPanel.onTabPressed !== undefined) + onActivated: PanelService.openedPanel.onTabPressed() + } + + Shortcut { + sequence: "Backtab" + enabled: root.isPanelOpen && (PanelService.openedPanel.onBackTabPressed !== undefined) + onActivated: PanelService.openedPanel.onBackTabPressed() + } + + Instantiator { + model: Settings.data.general.keybinds.keyUp || [] + Shortcut { + sequence: modelData + enabled: root.isPanelOpen && (PanelService.openedPanel.onUpPressed !== undefined) && !PanelService.isKeybindRecording + onActivated: PanelService.openedPanel.onUpPressed() + } + } + + Instantiator { + model: Settings.data.general.keybinds.keyDown || [] + Shortcut { + sequence: modelData + enabled: root.isPanelOpen && (PanelService.openedPanel.onDownPressed !== undefined) && !PanelService.isKeybindRecording + onActivated: PanelService.openedPanel.onDownPressed() + } + } + + Instantiator { + model: Settings.data.general.keybinds.keyEnter || [] + Shortcut { + sequence: modelData + enabled: root.isPanelOpen && (PanelService.openedPanel.onEnterPressed !== undefined) && !PanelService.isKeybindRecording + onActivated: PanelService.openedPanel.onEnterPressed() + } + } + + Instantiator { + model: Settings.data.general.keybinds.keyLeft || [] + Shortcut { + sequence: modelData + enabled: root.isPanelOpen && (PanelService.openedPanel.onLeftPressed !== undefined) && !PanelService.isKeybindRecording + onActivated: PanelService.openedPanel.onLeftPressed() + } + } + + Instantiator { + model: Settings.data.general.keybinds.keyRight || [] + Shortcut { + sequence: modelData + enabled: root.isPanelOpen && (PanelService.openedPanel.onRightPressed !== undefined) && !PanelService.isKeybindRecording + onActivated: PanelService.openedPanel.onRightPressed() + } + } + + Shortcut { + sequence: "Home" + enabled: root.isPanelOpen && (PanelService.openedPanel.onHomePressed !== undefined) + onActivated: PanelService.openedPanel.onHomePressed() + } + + Shortcut { + sequence: "End" + enabled: root.isPanelOpen && (PanelService.openedPanel.onEndPressed !== undefined) + onActivated: PanelService.openedPanel.onEndPressed() + } + + Shortcut { + sequence: "PgUp" + enabled: root.isPanelOpen && (PanelService.openedPanel.onPageUpPressed !== undefined) + onActivated: PanelService.openedPanel.onPageUpPressed() + } + + Shortcut { + sequence: "PgDown" + enabled: root.isPanelOpen && (PanelService.openedPanel.onPageDownPressed !== undefined) + onActivated: PanelService.openedPanel.onPageDownPressed() + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/PopupMenuWindow.qml b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/PopupMenuWindow.qml new file mode 100644 index 0000000..eb034a4 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/PopupMenuWindow.qml @@ -0,0 +1,167 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services.Compositor +import qs.Services.UI +import qs.Widgets + +// Generic full-screen popup window for menus and context menus +// This is a top-level PanelWindow (sibling to MainScreen, not nested inside it) +// Provides click-outside-to-close functionality for any popup content +// Loads TrayMenu by default but can show context menus via showContextMenu() +PanelWindow { + id: root + + required property ShellScreen screen + property string windowType: "popupmenu" // Used for namespace and registration + + // Content item to display (set by the popup that uses this window) + property var contentItem: null + + // Expose the trayMenu Loader directly (for backward compatibility) + readonly property alias trayMenuLoader: trayMenuLoader + + // Dynamic context menu callback for items in other windows (e.g., desktop widgets) + property var dynamicMenuCallback: null + + anchors.top: true + anchors.left: true + anchors.right: true + anchors.bottom: true + visible: false + color: "transparent" + + // Use Top layer for proper event handling, but on labwc use Bottom + // to avoid stealing input from popups while still catching outside clicks. + // However, when a dialog is open, always use Top so dialogs appear above apps. + WlrLayershell.layer: (CompositorService.isLabwc && !hasDialog) ? WlrLayer.Bottom : WlrLayer.Top + WlrLayershell.keyboardFocus: hasDialog ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None + WlrLayershell.namespace: "noctalia-" + windowType + "-" + (screen?.name || "unknown") + WlrLayershell.exclusionMode: ExclusionMode.Ignore + + // Track if a dialog is currently open (needed for keyboard focus) + property bool hasDialog: false + + // Register with PanelService so widgets can find this window + Component.onCompleted: { + objectName = "popupMenuWindow-" + (screen?.name || "unknown"); + PanelService.registerPopupMenuWindow(screen, root); + } + + Component.onDestruction: { + PanelService.unregisterPopupMenuWindow(screen); + } + + // Load TrayMenu as the default content + Loader { + id: trayMenuLoader + source: Quickshell.shellDir + "/Modules/Bar/Extras/TrayMenu.qml" + onLoaded: { + if (item) { + item.screen = root.screen; + // Set the loaded item as default content + root.contentItem = item; + } + } + } + + // Dynamic context menu - created as child of this window (Top layer) so input works correctly + // Used for items in other windows like desktop widgets (bottom layer) + NPopupContextMenu { + id: dynamicMenu + visible: false + screen: root.screen + minWidth: 180 + + onTriggered: (action, item) => { + if (root.dynamicMenuCallback) { + // Callback returns true if it will handle closing (e.g., opening a dialog) + var handled = root.dynamicMenuCallback(action); + if (!handled) { + root.close(); + } + } else { + root.close(); + } + } + } + + function open() { + visible = true; + BarService.popupOpen = true; + } + + // Show a context menu (temporarily replaces TrayMenu as content) + function showContextMenu(menu) { + if (menu) { + contentItem = menu; + open(); + } + } + + // Show a dynamic context menu with model and callback at screen coordinates + // Used for items in other window layers (e.g., desktop widgets in bottom layer) + function showDynamicContextMenu(model, screenX, screenY, callback) { + dynamicMenu.model = model; + dynamicMenuCallback = callback; + + // Use the anchor point item for positioning at absolute coordinates + dynamicMenu.anchorItem = anchorPoint; + anchorPoint.x = screenX; + anchorPoint.y = screenY; + + contentItem = dynamicMenu; + dynamicMenu.visible = true; + open(); + } + + // Invisible anchor point for dynamic menu positioning + Item { + id: anchorPoint + width: 1 + height: 1 + } + + // Hide just the dynamic menu without closing the popup window + // Used when transitioning from context menu to a dialog + function hideDynamicMenu() { + dynamicMenu.visible = false; + } + + function close() { + visible = false; + BarService.popupOpen = false; + // Call close/hide method on current content + if (contentItem) { + if (typeof contentItem.hideMenu === "function") { + contentItem.hideMenu(); + } else if (typeof contentItem.close === "function") { + contentItem.close(); + } + } + // Hide dynamic menu + dynamicMenu.visible = false; + dynamicMenuCallback = null; + // Restore TrayMenu as default content + if (trayMenuLoader.item) { + contentItem = trayMenuLoader.item; + } + } + + // Full-screen click catcher - click anywhere outside content closes the window + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + onClicked: root.close() + } + + // Container for dialogs that need a full-screen Item parent (e.g., Qt Popup) + Item { + id: dialogContainer + anchors.fill: parent + } + + // Expose the dialog container for external use + readonly property alias dialogParent: dialogContainer +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/ScreenCorners.qml b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/ScreenCorners.qml new file mode 100644 index 0000000..4387ecf --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/ScreenCorners.qml @@ -0,0 +1,231 @@ +import QtQuick +import QtQuick.Shapes +import qs.Commons + +/** +* ScreenCorners - Shape component for rendering screen corners +* +* Renders concave corners at the screen edges to create a rounded screen effect. +* Self-contained Shape component (no shadows). +*/ +Item { + id: root + + anchors.fill: parent + + // Wrapper with layer caching to reduce GPU tessellation overhead + Item { + anchors.fill: parent + + // Cache the Shape to a texture to prevent continuous re-tessellation + layer.enabled: true + + Shape { + id: cornersShape + + anchors.fill: parent + preferredRendererType: Shape.CurveRenderer + asynchronous: true + enabled: false // Disable mouse input + visible: cornersPath.cornerRadius > 0 && width > 0 && height > 0 + + ShapePath { + id: cornersPath + + // Corner configuration + readonly property color cornerColor: Settings.data.general.forceBlackScreenCorners ? "black" : Color.mSurface + readonly property real cornerRadius: Style.screenRadius + readonly property real cornerSize: Style.screenRadius + + // Determine margins based on bar position + readonly property real topMargin: 0 + readonly property real bottomMargin: 0 + readonly property real leftMargin: 0 + readonly property real rightMargin: 0 + + // Screen dimensions + readonly property real screenWidth: cornersShape.width + readonly property real screenHeight: cornersShape.height + + // Only show screen corners if enabled and appropriate conditions are met + readonly property bool shouldShow: Settings.data.general.showScreenCorners + + // ShapePath configuration + strokeWidth: -1 // No stroke, fill only + fillColor: shouldShow ? cornerColor : "transparent" + + // Smooth color animation (disabled during theme transitions to sync with Color.qml) + Behavior on fillColor { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + } + } + + // ========== PATH DEFINITION ========== + // Draws 4 separate corner squares at screen edges + // Each corner square has a concave arc on the inner diagonal + + // ========== TOP-LEFT CORNER ========== + // Arc is at the bottom-right of this square (inner diagonal) + // Start at top-left screen corner + startX: leftMargin + startY: topMargin + + // Top edge (moving right) + PathLine { + relativeX: cornersPath.cornerSize + relativeY: 0 + } + + // Right edge (moving down toward arc) + PathLine { + relativeX: 0 + relativeY: cornersPath.cornerSize - cornersPath.cornerRadius + } + + // Concave arc (bottom-right corner of square, curving inward toward screen center) + PathArc { + relativeX: -cornersPath.cornerRadius + relativeY: cornersPath.cornerRadius + radiusX: cornersPath.cornerRadius + radiusY: cornersPath.cornerRadius + direction: PathArc.Counterclockwise + } + + // Bottom edge (moving left) + PathLine { + relativeX: -(cornersPath.cornerSize - cornersPath.cornerRadius) + relativeY: 0 + } + + // Left edge (moving up) - closes back to start + PathLine { + relativeX: 0 + relativeY: -cornersPath.cornerSize + } + + // ========== TOP-RIGHT CORNER ========== + // Arc is at the bottom-left of this square (inner diagonal) + PathMove { + x: cornersPath.screenWidth - cornersPath.rightMargin - cornersPath.cornerSize + y: cornersPath.topMargin + } + + // Top edge (moving right) + PathLine { + relativeX: cornersPath.cornerSize + relativeY: 0 + } + + // Right edge (moving down) + PathLine { + relativeX: 0 + relativeY: cornersPath.cornerSize + } + + // Bottom edge (moving left toward arc) + PathLine { + relativeX: -(cornersPath.cornerSize - cornersPath.cornerRadius) + relativeY: 0 + } + + // Concave arc (bottom-left corner of square, curving inward toward screen center) + PathArc { + relativeX: -cornersPath.cornerRadius + relativeY: -cornersPath.cornerRadius + radiusX: cornersPath.cornerRadius + radiusY: cornersPath.cornerRadius + direction: PathArc.Counterclockwise + } + + // Left edge (moving up) - closes back to start + PathLine { + relativeX: 0 + relativeY: -(cornersPath.cornerSize - cornersPath.cornerRadius) + } + + // ========== BOTTOM-LEFT CORNER ========== + // Arc is at the top-right of this square (inner diagonal) + PathMove { + x: cornersPath.leftMargin + y: cornersPath.screenHeight - cornersPath.bottomMargin - cornersPath.cornerSize + } + + // Top edge (moving right toward arc) + PathLine { + relativeX: cornersPath.cornerSize - cornersPath.cornerRadius + relativeY: 0 + } + + // Concave arc (top-right corner of square, curving inward toward screen center) + PathArc { + relativeX: cornersPath.cornerRadius + relativeY: cornersPath.cornerRadius + radiusX: cornersPath.cornerRadius + radiusY: cornersPath.cornerRadius + direction: PathArc.Counterclockwise + } + + // Right edge (moving down) + PathLine { + relativeX: 0 + relativeY: cornersPath.cornerSize - cornersPath.cornerRadius + } + + // Bottom edge (moving left) + PathLine { + relativeX: -cornersPath.cornerSize + relativeY: 0 + } + + // Left edge (moving up) - closes back to start + PathLine { + relativeX: 0 + relativeY: -cornersPath.cornerSize + } + + // ========== BOTTOM-RIGHT CORNER ========== + // Arc is at the top-left of this square (inner diagonal) + // Start at bottom-right of square (different from other corners!) + PathMove { + x: cornersPath.screenWidth - cornersPath.rightMargin + y: cornersPath.screenHeight - cornersPath.bottomMargin + } + + // Bottom edge (moving left) + PathLine { + relativeX: -cornersPath.cornerSize + relativeY: 0 + } + + // Left edge (moving up toward arc) + PathLine { + relativeX: 0 + relativeY: -(cornersPath.cornerSize - cornersPath.cornerRadius) + } + + // Concave arc (top-left corner of square, curving inward toward screen center) + PathArc { + relativeX: cornersPath.cornerRadius + relativeY: -cornersPath.cornerRadius + radiusX: cornersPath.cornerRadius + radiusY: cornersPath.cornerRadius + direction: PathArc.Counterclockwise + } + + // Top edge (moving right) + PathLine { + relativeX: cornersPath.cornerSize - cornersPath.cornerRadius + relativeY: 0 + } + + // Right edge (moving down) - closes back to start + PathLine { + relativeX: 0 + relativeY: cornersPath.cornerSize + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/SmartPanel.qml b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/SmartPanel.qml new file mode 100644 index 0000000..e3839d0 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/MainScreen/SmartPanel.qml @@ -0,0 +1,1363 @@ +import QtQuick +import Quickshell +import qs.Commons +import qs.Services.UI + +/** +* SmartPanel for use within MainScreen +*/ +Item { + id: root + + // Screen property provided by MainScreen + property ShellScreen screen: null + + // Panel content: Text, icons, etc... + property Component panelContent: null + + // PanelID for binding panels to widgets of the same type + property var panelID: null + + // Panel size properties + property real preferredWidth: 700 + property real preferredHeight: 900 + property real preferredWidthRatio + property real preferredHeightRatio + property color panelBackgroundColor: Color.mSurface + property color panelBorderColor: Color.mOutline + property var buttonItem: null + property bool forceAttachToBar: false + + // Anchoring properties + property bool panelAnchorHorizontalCenter: false + property bool panelAnchorVerticalCenter: false + property bool panelAnchorTop: false + property bool panelAnchorBottom: false + property bool panelAnchorLeft: false + property bool panelAnchorRight: false + + // Button position properties + property bool useButtonPosition: false + property point buttonPosition: Qt.point(0, 0) + property int buttonWidth: 0 + property int buttonHeight: 0 + + // Edge snapping: if panel is within this distance (in pixels) from a screen edge, snap + property real edgeSnapDistance: 50 + + // Track whether panel is open + property bool isPanelOpen: false + + // Track actual visibility (delayed until content is loaded and sized) + property bool isPanelVisible: false + + // Track size animation completion for sequential opacity animation + property bool sizeAnimationComplete: false + + // Derived state: track opening transition + readonly property bool isOpening: isPanelVisible && !isClosing && !sizeAnimationComplete + + // Track close animation state: fade opacity first, then shrink size + property bool isClosing: false + property bool opacityFadeComplete: false + property bool closeFinalized: false // Prevent double-finalization + + // Safety: Watchdog timers to prevent stuck states + property bool closeWatchdogActive: false + property bool openWatchdogActive: false + + // Cached animation direction - set when panel opens, doesn't change during animation + // These are computed once when opening and used for the entire open/close cycle + property bool cachedAnimateFromTop: false + property bool cachedAnimateFromBottom: false + property bool cachedAnimateFromLeft: false + property bool cachedAnimateFromRight: false + + readonly property bool animationsDisabled: Settings.data.general.animationDisabled + property bool cachedShouldAnimateWidth: false + property bool cachedShouldAnimateHeight: false + + // Whether blur should be applied behind this panel + property bool blurEnabled: true + + // Close with escape key + property bool closeWithEscape: true + + property bool exclusiveKeyboard: true + + // Keyboard event handler + // These are called from MainScreen's centralized shortcuts + // override these in specific panels to handle shortcuts + function onEscapePressed() { + if (closeWithEscape) + close(); + } + + // Expose panel region for background rendering + readonly property var panelRegion: panelContent.geometryPlaceholder + + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" + readonly property real barHeight: barShouldShow ? Style.getBarHeightForScreen(screen?.name) : 0 + readonly property bool hasBar: modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0)) : false + readonly property bool isFramed: Settings.data.bar.barType === "framed" && hasBar + readonly property real frameThickness: Settings.data.bar.frameThickness ?? 12 + readonly property bool barFloating: Settings.data.bar.barType === "floating" + readonly property real barMarginH: (barFloating && barShouldShow) ? Math.ceil(Settings.data.bar.marginHorizontal) : 0 + readonly property real barMarginV: (barFloating && barShouldShow) ? Math.ceil(Settings.data.bar.marginVertical) : 0 + readonly property real attachmentOverlap: 1 // Panel extends into bar area to fix hairline gap with fractional scaling + + // Check if bar should be visible on this screen + readonly property bool barShouldShow: { + if (!BarService.effectivelyVisible) + return false; + var monitors = Settings.data.bar.monitors || []; + var screenName = screen?.name || ""; + return monitors.length === 0 || monitors.includes(screenName); + } + + // Helper to detect if any anchor is explicitly set + readonly property bool hasExplicitHorizontalAnchor: panelAnchorHorizontalCenter || panelAnchorLeft || panelAnchorRight + readonly property bool hasExplicitVerticalAnchor: panelAnchorVerticalCenter || panelAnchorTop || panelAnchorBottom + + // Effective anchor properties (depend on allowAttach) + // These are true when allowAttach is enabled AND: + // 1. Explicitly anchored to that edge, OR + // 2. Using button position and bar is on that edge, OR + // 3. No explicit anchors and bar is on that edge (default centering behavior) + readonly property bool effectivePanelAnchorTop: panelContent.allowAttach && (panelAnchorTop || (useButtonPosition && barPosition === "top") || (!hasExplicitVerticalAnchor && barPosition === "top" && !barIsVertical)) + readonly property bool effectivePanelAnchorBottom: panelContent.allowAttach && (panelAnchorBottom || (useButtonPosition && barPosition === "bottom") || (!hasExplicitVerticalAnchor && barPosition === "bottom" && !barIsVertical)) + readonly property bool effectivePanelAnchorLeft: panelContent.allowAttach && (panelAnchorLeft || (useButtonPosition && barPosition === "left") || (!hasExplicitHorizontalAnchor && barPosition === "left" && barIsVertical)) + readonly property bool effectivePanelAnchorRight: panelContent.allowAttach && (panelAnchorRight || (useButtonPosition && barPosition === "right") || (!hasExplicitHorizontalAnchor && barPosition === "right" && barIsVertical)) + + signal opened + signal closed + + Connections { + target: Style + + function onUiScaleRatioChanged() { + if (root.isPanelOpen && root.isPanelVisible) { + root.setPosition(); + } + } + } + + // Panel visibility and sizing + visible: isPanelVisible + width: parent ? parent.width : 0 + height: parent ? parent.height : 0 + + // Panel control functions + function toggle(buttonItem, buttonName) { + if (!isPanelOpen) { + open(buttonItem, buttonName); + } else { + close(); + } + } + + function open(buttonItem, buttonName) { + // Reset immediate close flag to ensure animations work properly + PanelService.closedImmediately = false; + // Reset to default - fixes panel being stuck in one position + root.useButtonPosition = false; + + // Calculate the bar window's position on screen based on bar settings + // The BarContentWindow uses anchors + margins, so we need to compute its origin + var barWindowX = 0; + var barWindowY = 0; + var screenWidth = root.screen?.width || 0; + var screenHeight = root.screen?.height || 0; + + if (root.barPosition === "right") { + barWindowX = screenWidth - root.barMarginH - root.barHeight; + } else if (root.barPosition === "left") { + barWindowX = root.barMarginH; + } else if (root.isFramed) { + barWindowX = root.frameThickness; + } else { + // Horizontal floating bars: BarContentWindow has margins.left = barMarginH + barWindowX = root.barMarginH; + } + + if (root.barPosition === "bottom") { + barWindowY = screenHeight - root.barMarginV - root.barHeight; + } else if (root.barPosition === "top") { + barWindowY = root.barMarginV; + } else if (root.isFramed) { + barWindowY = root.frameThickness; + } else { + // Vertical floating bars: BarContentWindow has margins.top = barMarginV + barWindowY = root.barMarginV; + } + + if (!buttonItem && buttonName) { + // Check if buttonName is actually a point object (click coordinates) + if (typeof buttonName === "object" && buttonName.x !== undefined && buttonName.y !== undefined) { + root.buttonItem = null; + // Click coordinates are in BarContentWindow-local space, offset to screen space + root.buttonPosition = Qt.point(barWindowX + buttonName.x, barWindowY + buttonName.y); + root.buttonWidth = 0; + root.buttonHeight = 0; + root.useButtonPosition = true; + } else { + // buttonName is a widget name, look it up + buttonItem = BarService.lookupWidget(buttonName, screen.name); + } + } + + // Validate buttonItem is a valid QML Item with mapToItem function + if (buttonItem && typeof buttonItem.mapToItem === "function") { + try { + root.buttonItem = buttonItem; + // Map button position within its window (BarContentWindow-local coordinates) + var buttonLocal = buttonItem.mapToItem(null, 0, 0); + + root.buttonPosition = Qt.point(barWindowX + buttonLocal.x, barWindowY + buttonLocal.y); + root.buttonWidth = buttonItem.width; + root.buttonHeight = buttonItem.height; + root.useButtonPosition = true; + } catch (e) { + Logger.w("SmartPanel", "Failed to get button position, using default positioning:", e); + root.buttonItem = null; + root.useButtonPosition = false; + } + } else if (!root.useButtonPosition) { + // No valid button provided and no click position: reset button position mode + root.buttonItem = null; + } + + // Set isPanelOpen to trigger content loading, but don't show yet + isPanelOpen = true; + + // Notify PanelService + PanelService.willOpenPanel(root); + + // Position and visibility will be set by Loader.onLoaded + // This ensures no flicker from default size to content size + } + + function close() { + // Reset immediate close flag to ensure animations work properly + PanelService.closedImmediately = false; + + // Start close sequence: fade opacity first + isClosing = true; + sizeAnimationComplete = false; + closeFinalized = false; + + // Stop the open animation timer if it's still running + opacityTrigger.stop(); + openWatchdogActive = false; + openWatchdogTimer.stop(); + + // Start close watchdog timer + closeWatchdogActive = true; + closeWatchdogTimer.restart(); + + // If opacity is already 0 (closed during open animation before fade-in), + // skip directly to size animation + if (root.opacity === 0.0) { + opacityFadeComplete = true; + } else { + opacityFadeComplete = false; + } + + // Opacity will fade out, then size will shrink, then finalizeClose() will complete + Logger.d("SmartPanel", "Closing panel", objectName); + } + + function closeImmediately() { + // Close without any animation, useful for app launches to avoid focus issues + opacityTrigger.stop(); + openWatchdogActive = false; + openWatchdogTimer.stop(); + closeWatchdogActive = false; + closeWatchdogTimer.stop(); + + // Don't set opacity directly as it breaks the binding + root.isPanelVisible = false; + root.sizeAnimationComplete = false; + root.isClosing = false; + root.opacityFadeComplete = false; + root.closeFinalized = true; + root.isPanelOpen = false; + panelBackground.dimensionsInitialized = false; + + // Signal immediate close so MainScreen can skip dimmer animation + PanelService.closedImmediately = true; + PanelService.closedPanel(root); + closed(); + + // Flush pending double-buffered Wayland state (blur regions) that won't + // be committed otherwise โ€” after an app launch the compositor may stop + // sending frame callbacks, leaving the render loop idle. + Window.window?.flushWaylandState(); + + Logger.d("SmartPanel", "Panel closed immediately", objectName); + } + + function finalizeClose() { + // Prevent double-finalization + if (root.closeFinalized) { + Logger.w("SmartPanel", "finalizeClose called but already finalized - ignoring", objectName); + return; + } + + // Complete the close sequence after animations finish + root.closeFinalized = true; + root.closeWatchdogActive = false; + closeWatchdogTimer.stop(); + + root.isPanelVisible = false; + root.isPanelOpen = false; + root.isClosing = false; + root.opacityFadeComplete = false; + + // Reset dimensionsInitialized for next opening + panelBackground.dimensionsInitialized = false; + + PanelService.closedPanel(root); + closed(); + + // Flush pending double-buffered Wayland state (blur regions). + Window.window?.flushWaylandState(); + + Logger.d("SmartPanel", "Panel close finalized", objectName); + } + + function setPosition() { + // Don't calculate position if parent dimensions aren't available yet + // This prevents centering around (0,0) when width/height are still 0 + if (!root.width || !root.height) { + Logger.d("SmartPanel", "Skipping setPosition - dimensions not ready:", root.width, "x", root.height); + // Retry on next frame when dimensions should be available + Qt.callLater(setPosition); + return; + } + + // Effective screen margins (account for frame thickness) + var effMarginL = Style.marginL + (root.isFramed ? root.frameThickness : 0); + var effMarginR = Style.marginL + (root.isFramed ? root.frameThickness : 0); + var effMarginT = Style.marginL + (root.isFramed ? root.frameThickness : 0); + var effMarginB = Style.marginL + (root.isFramed ? root.frameThickness : 0); + + // Calculate panel dimensions first (needed for positioning) + var w; + // Priority 1: Content-driven size (dynamic) + if (contentLoader.item && contentLoader.item.contentPreferredWidth !== undefined) { + w = contentLoader.item.contentPreferredWidth; + } // Priority 2: Ratio-based size + else if (root.preferredWidthRatio !== undefined) { + w = Math.round(Math.max(root.width * root.preferredWidthRatio, root.preferredWidth)); + } // Priority 3: Static preferred width + else { + w = root.preferredWidth; + } + var panelWidth = Math.min(w, root.width - effMarginL - effMarginR); + // For floating bars, additionally clamp to available space accounting for corner insets + if (root.barFloating && !root.barIsVertical) { + var floatCornerInset = Style.radiusL * 2; + panelWidth = Math.min(panelWidth, root.width - 2 * (root.barMarginH + floatCornerInset)); + } + + var h; + // Priority 1: Content-driven size (dynamic) + if (contentLoader.item && contentLoader.item.contentPreferredHeight !== undefined) { + h = contentLoader.item.contentPreferredHeight; + } // Priority 2: Ratio-based size + else if (root.preferredHeightRatio !== undefined) { + h = Math.round(Math.max(root.height * root.preferredHeightRatio, root.preferredHeight)); + } // Priority 3: Static preferred height + else { + h = root.preferredHeight; + } + var panelHeight = Math.min(h, root.height - root.barHeight - effMarginT - effMarginB); + // For vertical floating bars, clamp panelHeight to available space accounting for corner insets + if (root.barFloating && root.barIsVertical) { + var floatCornerInset = Style.radiusL * 2; + panelHeight = Math.min(panelHeight, root.height - 2 * (root.barMarginV + floatCornerInset)); + } + + // Update panelBackground target size (will be animated) + panelBackground.targetWidth = panelWidth; + panelBackground.targetHeight = panelHeight; + + // Pre-compute bar edge positions with overlap (used multiple times below) + // For attached panels, we extend slightly into the bar area to prevent hairline gaps + var leftBarEdgeWithOverlap = root.barMarginH + root.barHeight - root.attachmentOverlap; + var rightBarEdgeWithOverlap = root.width - root.barMarginH - root.barHeight + root.attachmentOverlap; + var topBarEdgeWithOverlap = root.barMarginV + root.barHeight - root.attachmentOverlap; + var bottomBarEdgeWithOverlap = root.height - root.barMarginV - root.barHeight + root.attachmentOverlap; + + if (root.isFramed) { + if (root.barPosition === "left") + leftBarEdgeWithOverlap = root.barHeight - root.attachmentOverlap; + if (root.barPosition === "right") + rightBarEdgeWithOverlap = root.width - root.barHeight + root.attachmentOverlap; + if (root.barPosition === "top") + topBarEdgeWithOverlap = root.barHeight - root.attachmentOverlap; + if (root.barPosition === "bottom") + bottomBarEdgeWithOverlap = root.height - root.barHeight + root.attachmentOverlap; + } + + // Calculate position + var calculatedX; + var calculatedY; + + // ===== X POSITIONING ===== + if (root.useButtonPosition && root.width > 0 && panelWidth > 0) { + if (root.barIsVertical) { + // For vertical bars + if (panelContent.allowAttach) { + // Attached panels: align with bar edge (left or right side) + if (root.barPosition === "left") { + calculatedX = leftBarEdgeWithOverlap; + } else { + calculatedX = rightBarEdgeWithOverlap - panelWidth; + } + } else { + // Detached panels: center on button X position + var panelX = root.buttonPosition.x + root.buttonWidth / 2 - panelWidth / 2; + var minX = effMarginL; + var maxX = root.width - panelWidth - effMarginR; + + // Account for vertical bar taking up space + if (root.barPosition === "left") { + minX = (root.isFramed ? 0 : root.barMarginH) + root.barHeight + Style.marginL; + } else if (root.barPosition === "right") { + maxX = root.width - (root.isFramed ? 0 : root.barMarginH) - root.barHeight - panelWidth - Style.marginL; + } + + panelX = Math.max(minX, Math.min(panelX, maxX)); + calculatedX = panelX; + } + } else { + // For horizontal bars, center panel on button X position + var panelX = root.buttonPosition.x + root.buttonWidth / 2 - panelWidth / 2; + if (panelContent.allowAttach) { + var cornerInset = root.barFloating ? Style.radiusL * 2 : 0; + var barLeftEdge = (root.isFramed ? root.frameThickness - root.attachmentOverlap : root.barMarginH) + cornerInset; + var barRightEdge = root.width - (root.isFramed ? root.frameThickness - root.attachmentOverlap : root.barMarginH) - cornerInset; + panelX = Math.max(barLeftEdge, Math.min(panelX, barRightEdge - panelWidth)); + } else { + panelX = Math.max(effMarginL, Math.min(panelX, root.width - panelWidth - effMarginR)); + } + calculatedX = panelX; + } + } else { + // Standard anchor positioning + if (root.panelAnchorHorizontalCenter) { + if (root.barIsVertical) { + if (root.barPosition === "left") { + var availableStart = (root.isFramed ? 0 : root.barMarginH) + root.barHeight; + var availableWidth = root.width - availableStart - (root.isFramed ? root.frameThickness : 0); + calculatedX = availableStart + (availableWidth - panelWidth) / 2; + } else if (root.barPosition === "right") { + var availableWidth = root.width - (root.isFramed ? 0 : root.barMarginH) - root.barHeight - (root.isFramed ? root.frameThickness : 0); + calculatedX = (root.isFramed ? root.frameThickness : 0) + (availableWidth - panelWidth) / 2; + } else { + calculatedX = (root.width - panelWidth) / 2; + } + } else { + calculatedX = (root.width - panelWidth) / 2; + } + } else if (root.panelAnchorRight) { + // Use raw panelAnchorRight for positioning decision + if (root.effectivePanelAnchorRight) { + // Attached: snap to edge/bar + if (root.barIsVertical && root.barPosition === "right") { + calculatedX = rightBarEdgeWithOverlap - panelWidth; + } else { + var panelOnSameEdgeAsBar = (root.barPosition === "top" && root.effectivePanelAnchorTop) || (root.barPosition === "bottom" && root.effectivePanelAnchorBottom); + if (!root.barIsVertical && root.barFloating && panelOnSameEdgeAsBar) { + var rightCornerInset = Style.radiusL * 2; + calculatedX = root.width - root.barMarginH - rightCornerInset - panelWidth; + } else { + calculatedX = root.width - panelWidth - (root.isFramed ? root.frameThickness - root.attachmentOverlap : 0); + } + } + } else { + // Not attached: position at right with margin + calculatedX = root.width - panelWidth - effMarginR; + } + } else if (root.panelAnchorLeft) { + // Use raw panelAnchorLeft for positioning decision + if (root.effectivePanelAnchorLeft) { + // Attached: snap to edge/bar + if (root.barIsVertical && root.barPosition === "left") { + calculatedX = leftBarEdgeWithOverlap; + } else { + var panelOnSameEdgeAsBar = (root.barPosition === "top" && root.effectivePanelAnchorTop) || (root.barPosition === "bottom" && root.effectivePanelAnchorBottom); + if (!root.barIsVertical && root.barFloating && panelOnSameEdgeAsBar) { + var leftCornerInset = Style.radiusL * 2; + calculatedX = root.barMarginH + leftCornerInset; + } else { + calculatedX = (root.isFramed ? root.frameThickness - root.attachmentOverlap : 0); + } + } + } else { + // Not attached: position at left with margin + calculatedX = effMarginL; + } + } else { + // No explicit anchor: attach to bar if allowAttach, otherwise center + if (root.barIsVertical) { + if (panelContent.allowAttach) { + // Attach to the bar edge (with overlap into bar area) + if (root.barPosition === "left") { + calculatedX = leftBarEdgeWithOverlap; + } else { + calculatedX = rightBarEdgeWithOverlap - panelWidth; + } + } else { + // Not attached: center in available space + if (root.barPosition === "left") { + var availableStart = (root.isFramed ? 0 : root.barMarginH) + root.barHeight; + var availableWidth = root.width - availableStart - effMarginR; + calculatedX = availableStart + (availableWidth - panelWidth) / 2; + } else { + var availableWidth = root.width - (root.isFramed ? 0 : root.barMarginH) - root.barHeight - effMarginL; + calculatedX = effMarginL + (availableWidth - panelWidth) / 2; + } + } + } else { + if (panelContent.allowAttach) { + var cornerInset = Style.radiusL + (root.barFloating ? Style.radiusL : 0); + var barLeftEdge = (root.isFramed ? root.frameThickness - root.attachmentOverlap : root.barMarginH) + cornerInset; + var barRightEdge = root.width - (root.isFramed ? root.frameThickness - root.attachmentOverlap : root.barMarginH) - cornerInset; + var centeredX = (root.width - panelWidth) / 2; + calculatedX = Math.max(barLeftEdge, Math.min(centeredX, barRightEdge - panelWidth)); + } else { + calculatedX = (root.width - panelWidth) / 2; + } + } + } + } + + // Edge snapping for X + if (panelContent.allowAttach && !root.barFloating && root.width > 0 && panelWidth > 0) { + var leftEdgePos = root.barPosition === "left" ? leftBarEdgeWithOverlap : (root.isFramed ? root.frameThickness - root.attachmentOverlap : root.barMarginH); + var rightEdgePos = root.barPosition === "right" ? rightBarEdgeWithOverlap - panelWidth : root.width - (root.isFramed ? root.frameThickness - root.attachmentOverlap : root.barMarginH) - panelWidth; + + // Only snap to left edge if panel is actually meant to be at left (or no explicit anchor) + var shouldSnapToLeft = root.effectivePanelAnchorLeft || (!root.hasExplicitHorizontalAnchor && root.barPosition === "left"); + // Only snap to right edge if panel is actually meant to be at right (or no explicit anchor) + var shouldSnapToRight = root.effectivePanelAnchorRight || (!root.hasExplicitHorizontalAnchor && root.barPosition === "right"); + + if (shouldSnapToLeft && Math.abs(calculatedX - leftEdgePos) <= root.edgeSnapDistance) { + calculatedX = leftEdgePos; + } else if (shouldSnapToRight && Math.abs(calculatedX - rightEdgePos) <= root.edgeSnapDistance) { + calculatedX = rightEdgePos; + } + } + + // ===== Y POSITIONING ===== + if (root.useButtonPosition && root.height > 0 && panelHeight > 0) { + if (root.barPosition === "top") { + if (panelContent.allowAttach) { + calculatedY = topBarEdgeWithOverlap; + } else { + calculatedY = (root.isFramed ? 0 : root.barMarginV) + root.barHeight + Style.marginM; + } + } else if (root.barPosition === "bottom") { + if (panelContent.allowAttach) { + calculatedY = bottomBarEdgeWithOverlap - panelHeight; + } else { + calculatedY = root.height - (root.isFramed ? 0 : root.barMarginV) - root.barHeight - panelHeight - Style.marginM; + } + } else if (root.barIsVertical) { + var panelY = root.buttonPosition.y + root.buttonHeight / 2 - panelHeight / 2; + var extraPadding = (panelContent.allowAttach && root.barFloating) ? Style.radiusL : 0; + if (panelContent.allowAttach) { + var cornerInset = extraPadding + (root.barFloating ? Style.radiusL : 0); + var barTopEdge = (root.isFramed ? root.frameThickness - root.attachmentOverlap : root.barMarginV) + cornerInset; + var barBottomEdge = root.height - (root.isFramed ? root.frameThickness - root.attachmentOverlap : root.barMarginV) - cornerInset; + panelY = Math.max(barTopEdge, Math.min(panelY, barBottomEdge - panelHeight)); + } else { + panelY = Math.max(effMarginT + extraPadding, Math.min(panelY, root.height - panelHeight - effMarginB - extraPadding)); + } + calculatedY = panelY; + } + } else { + // Standard anchor positioning + var barOffset = !panelContent.allowAttach && (root.barPosition === "top" || root.barPosition === "bottom") ? (root.isFramed ? 0 : root.barMarginV) + root.barHeight + Style.marginM : 0; + + if (panelContent.allowAttach && !root.barIsVertical) { + // Attached to horizontal bar: position with overlap + if ((root.effectivePanelAnchorTop && root.barPosition === "top") || (!root.hasExplicitVerticalAnchor && root.barPosition === "top")) { + calculatedY = topBarEdgeWithOverlap; + } else if ((root.effectivePanelAnchorBottom && root.barPosition === "bottom") || (!root.hasExplicitVerticalAnchor && root.barPosition === "bottom")) { + calculatedY = bottomBarEdgeWithOverlap - panelHeight; + } + } + + if (calculatedY === undefined) { + if (root.panelAnchorVerticalCenter) { + if (!root.barIsVertical) { + if (root.barPosition === "top") { + var availableStart = (root.isFramed ? 0 : root.barMarginV) + root.barHeight; + var availableHeight = root.height - availableStart - (root.isFramed ? root.frameThickness : 0); + calculatedY = availableStart + (availableHeight - panelHeight) / 2; + } else if (root.barPosition === "bottom") { + var availableHeight = root.height - (root.isFramed ? 0 : root.barMarginV) - root.barHeight - (root.isFramed ? root.frameThickness : 0); + calculatedY = (root.isFramed ? root.frameThickness : 0) + (availableHeight - panelHeight) / 2; + } else { + calculatedY = (root.height - panelHeight) / 2; + } + } else { + calculatedY = (root.height - panelHeight) / 2; + } + } else if (root.panelAnchorTop) { + if (root.effectivePanelAnchorTop) { + calculatedY = root.barPosition === "top" ? topBarEdgeWithOverlap : (root.isFramed ? root.frameThickness - root.attachmentOverlap : 0); + } else { + var topBarOffset = (root.barPosition === "top") ? barOffset : 0; + calculatedY = topBarOffset + effMarginT; + } + } else if (root.panelAnchorBottom) { + if (root.effectivePanelAnchorBottom) { + calculatedY = root.barPosition === "bottom" ? bottomBarEdgeWithOverlap - panelHeight : root.height - panelHeight - (root.isFramed ? root.frameThickness - root.attachmentOverlap : 0); + } else { + var bottomBarOffset = (root.barPosition === "bottom") ? barOffset : 0; + calculatedY = root.height - panelHeight - bottomBarOffset - effMarginB; + } + } else { + // No explicit vertical anchor + if (root.barIsVertical) { + if (panelContent.allowAttach) { + var cornerInset = root.barFloating ? Style.radiusL * 2 : 0; + var barTopEdge = (root.isFramed ? root.frameThickness - root.attachmentOverlap : root.barMarginV) + cornerInset; + var barBottomEdge = root.height - (root.isFramed ? root.frameThickness - root.attachmentOverlap : root.barMarginV) - cornerInset; + var centeredY = (root.height - panelHeight) / 2; + calculatedY = Math.max(barTopEdge, Math.min(centeredY, barBottomEdge - panelHeight)); + } else { + calculatedY = (root.height - panelHeight) / 2; + } + } else { + // Horizontal bar, not attached + if (root.barPosition === "top") { + calculatedY = barOffset + effMarginT; + } else if (root.barPosition === "bottom") { + calculatedY = effMarginT; + } else { + calculatedY = effMarginT; + } + } + } + } + } + + // Edge snapping for Y + if (panelContent.allowAttach && !root.barFloating && root.height > 0 && panelHeight > 0) { + var topEdgePos = root.barPosition === "top" ? topBarEdgeWithOverlap : (root.isFramed ? root.frameThickness - root.attachmentOverlap : root.barMarginV); + var bottomEdgePos = root.barPosition === "bottom" ? bottomBarEdgeWithOverlap - panelHeight : root.height - (root.isFramed ? root.frameThickness - root.attachmentOverlap : root.barMarginV) - panelHeight; + + // Only snap to top edge if panel is actually meant to be at top (or no explicit anchor) + var shouldSnapToTop = root.effectivePanelAnchorTop || (!root.hasExplicitVerticalAnchor && root.barPosition === "top"); + // Only snap to bottom edge if panel is actually meant to be at bottom (or no explicit anchor) + var shouldSnapToBottom = root.effectivePanelAnchorBottom || (!root.hasExplicitVerticalAnchor && root.barPosition === "bottom"); + + if (shouldSnapToTop && Math.abs(calculatedY - topEdgePos) <= root.edgeSnapDistance) { + calculatedY = topEdgePos; + } else if (shouldSnapToBottom && Math.abs(calculatedY - bottomEdgePos) <= root.edgeSnapDistance) { + calculatedY = bottomEdgePos; + } + } + + // Apply calculated positions (set targets for animation) + panelBackground.targetX = calculatedX; + panelBackground.targetY = calculatedY; + + // Logger.d("SmartPanel", "Position calculated:", calculatedX, calculatedY); + // Logger.d("SmartPanel", " Panel size:", panelWidth, "x", panelHeight); + // Logger.d("SmartPanel", " Parent size:", root.width, "x", root.height); + } + + // Watch for changes in content-driven sizes and update position + Connections { + target: contentLoader.item + ignoreUnknownSignals: true + + function onContentPreferredWidthChanged() { + if (root.isPanelOpen && root.isPanelVisible) { + root.setPosition(); + } + } + + function onContentPreferredHeightChanged() { + if (root.isPanelOpen && root.isPanelVisible) { + root.setPosition(); + } + } + } + + // Opacity animation + // Opening: fade in after size animation reaches 75% + // Closing: fade out immediately + opacity: { + if (isClosing) + return 0.0; // Fade out when closing + if (isPanelVisible && sizeAnimationComplete) + return 1.0; // Fade in when opening + return 0.0; + } + + Behavior on opacity { + enabled: !PanelService.closedImmediately + NumberAnimation { + id: opacityAnimation + duration: root.isClosing ? Style.animationFaster : Style.animationFast + easing.type: Easing.OutQuad + + onRunningChanged: { + // Safety: If animation didn't run (zero duration), handle immediately + if (!running && duration === 0) { + if (root.isClosing && root.opacity === 0.0) { + root.opacityFadeComplete = true; + var shouldFinalizeNow = panelContent.geometryPlaceholder && !panelContent.geometryPlaceholder.shouldAnimateWidth && !panelContent.geometryPlaceholder.shouldAnimateHeight; + if (shouldFinalizeNow) { + // Logger.d("SmartPanel", "Zero-duration opacity + no size animation - finalizing", root.objectName); + Qt.callLater(root.finalizeClose); + } + } else if (root.isPanelVisible && root.opacity === 1.0) { + // Open completed with zero duration + root.openWatchdogActive = false; + openWatchdogTimer.stop(); + } + return; + } + + // When opacity fade completes during close, trigger size animation + if (!running && root.isClosing && root.opacity === 0.0) { + root.opacityFadeComplete = true; + // If no size animation will run (centered attached panels only), finalize immediately + // Detached panels (allowAttach === false) should always animate from top + var shouldFinalizeNow = panelContent.geometryPlaceholder && !panelContent.geometryPlaceholder.shouldAnimateWidth && !panelContent.geometryPlaceholder.shouldAnimateHeight; + if (shouldFinalizeNow) { + //Logger.d("SmartPanel", "No animation - finalizing immediately", root.objectName); + Qt.callLater(root.finalizeClose); + } else { + //Logger.d("SmartPanel", "Animation will run - waiting for size animation", root.objectName, "shouldAnimateHeight:", panelContent.geometryPlaceholder.shouldAnimateHeight, "shouldAnimateWidth:", panelContent.geometryPlaceholder.shouldAnimateWidth); + } + } // When opacity fade completes during open, stop watchdog + else if (!running && root.isPanelVisible && root.opacity === 1.0) { + root.openWatchdogActive = false; + openWatchdogTimer.stop(); + } + } + } + } + + // Timer to trigger opacity fade at 50% of size animation + Timer { + id: opacityTrigger + interval: Style.animationNormal * 0.5 + repeat: false + onTriggered: { + if (root.isPanelVisible) { + root.sizeAnimationComplete = true; + } + } + } + + // Watchdog timer for open sequence (safety mechanism) + Timer { + id: openWatchdogTimer + interval: Style.animationNormal * 3 // 3x normal animation time + repeat: false + onTriggered: { + if (root.openWatchdogActive) { + Logger.w("SmartPanel", "Open watchdog timeout - forcing panel visible state", root.objectName); + root.openWatchdogActive = false; + // Force completion of open sequence + if (root.isPanelOpen && !root.isPanelVisible) { + root.isPanelVisible = true; + root.sizeAnimationComplete = true; + } + } + } + } + + // Watchdog timer for close sequence (safety mechanism) + Timer { + id: closeWatchdogTimer + interval: Style.animationFast * 3 // 3x fast animation time + repeat: false + onTriggered: { + if (root.closeWatchdogActive && !root.closeFinalized) { + Logger.w("SmartPanel", "Close watchdog timeout - forcing panel close", root.objectName); + // Force finalization + Qt.callLater(root.finalizeClose); + } + } + } + + // ------------------------------------------------ + // Panel Content + Item { + id: panelContent + anchors.fill: parent + + // Screen-dependent attachment properties + // Allow panel content to override allowAttach (e.g., plugin panels) + readonly property bool allowAttach: { + if (contentLoader.item && contentLoader.item.allowAttach !== undefined) { + return contentLoader.item.allowAttach; + } + return Settings.data.ui.panelsAttachedToBar || root.forceAttachToBar; + } + readonly property bool allowAttachToBar: { + if (!(Settings.data.ui.panelsAttachedToBar || root.forceAttachToBar)) { + return false; + } + + // A panel can only be attached to a bar if there is a bar on that screen + var monitors = Settings.data.bar.monitors || []; + var result = monitors.length === 0 || monitors.includes(root.screen?.name || ""); + return result; + } + + // Edge detection - detect if panel is touching screen edges + readonly property bool touchingLeftEdge: allowAttach && panelBackground.x <= (isFramed ? frameThickness + 1 : 1) + readonly property bool touchingRightEdge: allowAttach && (panelBackground.x + panelBackground.width) >= (root.width - (isFramed ? frameThickness + 1 : 1)) + readonly property bool touchingTopEdge: allowAttach && panelBackground.y <= (isFramed ? frameThickness + 1 : 1) + readonly property bool touchingBottomEdge: allowAttach && (panelBackground.y + panelBackground.height) >= (root.height - (isFramed ? frameThickness + 1 : 1)) + + // Bar edge detection - detect if panel is touching bar edges (for cases where centered panels snap to bar due to height constraints) + readonly property bool touchingTopBar: allowAttachToBar && root.barPosition === "top" && !root.barIsVertical && Math.abs(panelBackground.y - ((isFramed ? 0 : root.barMarginV) + root.barHeight)) <= 1 + readonly property bool touchingBottomBar: allowAttachToBar && root.barPosition === "bottom" && !root.barIsVertical && Math.abs((panelBackground.y + panelBackground.height) - (root.height - (isFramed ? 0 : root.barMarginV) - root.barHeight)) <= 1 + readonly property bool touchingLeftBar: allowAttachToBar && root.barPosition === "left" && root.barIsVertical && Math.abs(panelBackground.x - ((isFramed ? 0 : root.barMarginH) + root.barHeight)) <= 1 + readonly property bool touchingRightBar: allowAttachToBar && root.barPosition === "right" && root.barIsVertical && Math.abs((panelBackground.x + panelBackground.width) - (root.width - (isFramed ? 0 : root.barMarginH) - root.barHeight)) <= 1 + + // Expose panelBackground for geometry placeholder + property alias geometryPlaceholder: panelBackground + + // The actual panel background - provides geometry for PanelBackground rendering + Item { + id: panelBackground + + // Expose self as panelItem for PanelBackground compatibility + readonly property var panelItem: panelBackground + + // Store target dimensions (Initialize to 0, set by setPosition()) + property real targetWidth: 0 + property real targetHeight: 0 + property real targetX: root.x + property real targetY: root.y + + // Track whether dimensions have been initialized (to prevent initial changes from animating) + property bool dimensionsInitialized: false + + property var bezierCurve: [0.05, 0, 0.133, 0.06, 0.166, 0.4, 0.208, 0.82, 0.25, 1, 1, 1] + + // Determine which edges the panel is closest to for animation direction + // Use target position (not animated position) to avoid binding loops + readonly property bool willTouchTopBar: { + if (!panelContent.allowAttachToBar || root.barPosition !== "top" || root.barIsVertical) + return false; + var targetTopBarY = (isFramed ? 0 : root.barMarginV) + root.barHeight; + return Math.abs(panelBackground.targetY - targetTopBarY) <= 1; + } + readonly property bool willTouchBottomBar: { + if (!panelContent.allowAttachToBar || root.barPosition !== "bottom" || root.barIsVertical) + return false; + var targetBottomBarY = root.height - (isFramed ? 0 : root.barMarginV) - root.barHeight - panelBackground.targetHeight; + return Math.abs(panelBackground.targetY - targetBottomBarY) <= 1; + } + readonly property bool willTouchLeftBar: { + if (!panelContent.allowAttachToBar || root.barPosition !== "left" || !root.barIsVertical) + return false; + var targetLeftBarX = (isFramed ? 0 : root.barMarginH) + root.barHeight; + return Math.abs(panelBackground.targetX - targetLeftBarX) <= 1; + } + readonly property bool willTouchRightBar: { + if (!panelContent.allowAttachToBar || root.barPosition !== "right" || !root.barIsVertical) + return false; + var targetRightBarX = root.width - (isFramed ? 0 : root.barMarginH) - root.barHeight - panelBackground.targetWidth; + return Math.abs(panelBackground.targetX - targetRightBarX) <= 1; + } + readonly property bool willTouchTopEdge: panelContent.allowAttach && panelBackground.targetY <= (isFramed ? frameThickness + 1 : 1) + readonly property bool willTouchBottomEdge: panelContent.allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - (isFramed ? frameThickness + 1 : 1)) + readonly property bool willTouchLeftEdge: panelContent.allowAttach && panelBackground.targetX <= (isFramed ? frameThickness + 1 : 1) + readonly property bool willTouchRightEdge: panelContent.allowAttach && (panelBackground.targetX + panelBackground.targetWidth) >= (root.width - (isFramed ? frameThickness + 1 : 1)) + + readonly property bool isActuallyAttachedToAnyEdge: { + return willTouchTopBar || willTouchBottomBar || willTouchLeftBar || willTouchRightBar || willTouchTopEdge || willTouchBottomEdge || willTouchLeftEdge || willTouchRightEdge; + } + + readonly property bool animateFromTop: { + // Non-attached panels always roll down from top + // Check both the setting and whether panel actually touches any edge + if (!panelContent.allowAttach || !isActuallyAttachedToAnyEdge) { + return true; + } + // When panel is opening, use effective anchors and calculated positions + if (!root.isPanelVisible) { + // Attached to horizontal bar at top + if (panelContent.allowAttachToBar && root.effectivePanelAnchorTop && !root.barIsVertical) { + return true; + } + // Attached to vertical bar (left/right) - don't animate from top + // Panels attach to bar if they have allowAttach (with or without explicit anchor) + var attachedToVerticalBar = panelContent.allowAttachToBar && root.barIsVertical && ((root.effectivePanelAnchorLeft && root.barPosition === "left") || (root.effectivePanelAnchorRight && root.barPosition === "right")); + if (attachedToVerticalBar) { + return false; + } + // Panel touching left/right/bottom screen edge - animate from that edge instead + var touchingNonTopEdge = (willTouchLeftEdge || willTouchRightEdge || willTouchBottomEdge) && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar; + if (touchingNonTopEdge) { + return false; + } + // Panel touching top screen edge (not bar) + if (willTouchTopEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar) { + return true; + } + // Panel anchored to left/right/bottom edge - animate from that edge instead + if (root.panelAnchorLeft || root.panelAnchorRight || root.panelAnchorBottom) { + return false; + } + // Attached to top edge + if (panelContent.allowAttach && root.panelAnchorTop) { + return true; + } + // Default: animate from top (for floating panels with no explicit anchors) + return true; + } + // Panel is visible - use calculated positions + if (willTouchTopBar) { + return true; + } + if (willTouchTopEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar) { + return true; + } + if (!isActuallyAttachedToAnyEdge) { + return true; + } + return false; + } + readonly property bool animateFromBottom: { + // Non-attached panels always roll down from top, not bottom + if (!panelContent.allowAttach || !isActuallyAttachedToAnyEdge) { + return false; + } + if (!root.isPanelVisible) { + // Attached to horizontal bar at bottom + if (panelContent.allowAttachToBar && root.effectivePanelAnchorBottom && !root.barIsVertical) { + return true; + } + // Attached to vertical bar (left/right) - don't animate from bottom + var attachedToVerticalBar = panelContent.allowAttachToBar && root.barIsVertical && ((root.effectivePanelAnchorLeft && root.barPosition === "left") || (root.effectivePanelAnchorRight && root.barPosition === "right")); + if (attachedToVerticalBar) { + return false; + } + // Panel touching bottom screen edge (not bar) + if (willTouchBottomEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar) { + return true; + } + // Panel anchored to top/left/right edge - don't animate from bottom + if (root.panelAnchorTop || root.panelAnchorLeft || root.panelAnchorRight) { + return false; + } + // Attached to bottom edge (when bar is vertical, panel can still be anchored to bottom) + if (panelContent.allowAttach && root.panelAnchorBottom) { + return true; + } + return false; + } + if (willTouchBottomBar) { + return true; + } + if (willTouchBottomEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar) { + return true; + } + return false; + } + readonly property bool animateFromLeft: { + // Non-attached panels always roll down from top, not left + if (!panelContent.allowAttach || !isActuallyAttachedToAnyEdge) { + return false; + } + if (!root.isPanelVisible) { + // Attached to vertical bar on left - must verify bar is actually on left + if (panelContent.allowAttachToBar && root.effectivePanelAnchorLeft && root.barIsVertical && root.barPosition === "left") { + return true; + } + // Panel touching left screen edge (not bar) + if (willTouchLeftEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar) { + return true; + } + // Panel anchored to left edge - animate from left + // Takes precedence over top/bottom when bar is vertical + if (root.panelAnchorLeft) { + return true; + } + return false; + } + if (willTouchTopBar || willTouchBottomBar) { + return false; + } + if (willTouchLeftBar) { + return true; + } + if (willTouchTopEdge || willTouchBottomEdge) { + return false; + } + if (willTouchLeftEdge && !willTouchLeftBar && !willTouchTopBar && !willTouchBottomBar && !willTouchRightBar) { + return true; + } + return false; + } + readonly property bool animateFromRight: { + // Non-attached panels always roll down from top, not right + if (!panelContent.allowAttach || !isActuallyAttachedToAnyEdge) { + return false; + } + if (!root.isPanelVisible) { + // Attached to vertical bar on right - must verify bar is actually on right + if (panelContent.allowAttachToBar && root.effectivePanelAnchorRight && root.barIsVertical && root.barPosition === "right") { + return true; + } + // Panel touching right screen edge (not bar) + if (willTouchRightEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar) { + return true; + } + // Panel anchored to right edge - animate from right + // Takes precedence over top/bottom when bar is vertical + if (root.panelAnchorRight) { + return true; + } + return false; + } + if (willTouchTopBar || willTouchBottomBar) { + return false; + } + if (willTouchRightBar) { + return true; + } + if (willTouchTopEdge || willTouchBottomEdge) { + return false; + } + if (willTouchRightEdge && !willTouchLeftBar && !willTouchTopBar && !willTouchBottomBar && !willTouchRightBar) { + return true; + } + return false; + } + + // Determine animation axis based on which edge is closest + // Priority: horizontal edges (top/bottom) take precedence over vertical edges (left/right) + // This prevents diagonal animations when panel is attached to a corner + // Use reactive values here - they're evaluated BEFORE isPanelVisible becomes true + readonly property bool shouldAnimateWidth: !shouldAnimateHeight && (animateFromLeft || animateFromRight) + readonly property bool shouldAnimateHeight: animateFromTop || animateFromBottom + + // Current animated width/height (referenced by x/y for right/bottom positioning) + readonly property real currentWidth: { + if (isClosing && opacityFadeComplete && shouldAnimateWidth) + return 0; + if (isClosing || isPanelVisible) + return targetWidth; + // If not animating width, start at target (no visual change) + // If animating width, start at 0 (will animate to target) + return shouldAnimateWidth ? 0 : targetWidth; + } + readonly property real currentHeight: { + if (isClosing && opacityFadeComplete && shouldAnimateHeight) + return 0; + if (isClosing || isPanelVisible) + return targetHeight; + // If not animating height, start at target (no visual change) + // If animating height, start at 0 (will animate to target) + return shouldAnimateHeight ? 0 : targetHeight; + } + + width: currentWidth + height: currentHeight + + x: { + // Offset x to make panel grow/shrink from the appropriate edge + // Use CACHED values to prevent recalculation during animation + if (root.cachedAnimateFromRight && root.cachedShouldAnimateWidth) { + // Keep the RIGHT edge fixed at its target position + var targetRightEdge = targetX + targetWidth; + return targetRightEdge - width; + } + return targetX; + } + y: { + // Offset y to make panel grow/shrink from the appropriate edge + // Use CACHED values to prevent recalculation during animation + if (root.cachedAnimateFromBottom && root.cachedShouldAnimateHeight) { + // Keep the BOTTOM edge fixed at its target position + var targetBottomEdge = targetY + targetHeight; + return targetBottomEdge - height; + } + return targetY; + } + + Behavior on width { + enabled: !PanelService.closedImmediately + NumberAnimation { + id: widthAnimation + // Use 0ms if dimensions not initialized to prevent initial changes from animating + // During opening: use 0ms if not animating width, otherwise use normal duration + // During closing: use 0ms if not animating width, otherwise use fast duration + // During normal content resizing: always use normal duration + duration: !panelBackground.dimensionsInitialized ? 0 : (root.isOpening && !panelBackground.shouldAnimateWidth) ? 0 : root.isOpening ? Style.animationNormal : (root.isClosing && !panelBackground.shouldAnimateWidth) ? 0 : root.isClosing ? Style.animationFast : Style.animationNormal + easing.type: Easing.BezierSpline + easing.bezierCurve: panelBackground.bezierCurve + + onRunningChanged: { + // Safety: Zero-duration animation handling + if (!running && duration === 0) { + if (root.isClosing && panelBackground.width === 0 && panelBackground.shouldAnimateWidth) { + Logger.d("SmartPanel", "Zero-duration width animation - finalizing", root.objectName); + Qt.callLater(root.finalizeClose); + } + return; + } + + // When width shrink completes during close, finalize + if (!running && root.isClosing && panelBackground.width === 0 && panelBackground.shouldAnimateWidth) { + Qt.callLater(root.finalizeClose); + } + } + } + } + + Behavior on height { + enabled: !PanelService.closedImmediately + NumberAnimation { + id: heightAnimation + // Use 0ms if dimensions not initialized to prevent initial changes from animating + // During opening: use 0ms if not animating height, otherwise use normal duration + // During closing: use 0ms if not animating height, otherwise use fast duration + // During normal content resizing: always use normal duration + duration: !panelBackground.dimensionsInitialized ? 0 : (root.isOpening && !panelBackground.shouldAnimateHeight) ? 0 : root.isOpening ? Style.animationNormal : (root.isClosing && !panelBackground.shouldAnimateHeight) ? 0 : root.isClosing ? Style.animationFast : Style.animationNormal + easing.type: Easing.BezierSpline + easing.bezierCurve: panelBackground.bezierCurve + + onRunningChanged: { + // Safety: Zero-duration animation handling + if (!running && duration === 0) { + if (root.isClosing && panelBackground.height === 0 && panelBackground.shouldAnimateHeight) { + Logger.d("SmartPanel", "Zero-duration height animation - finalizing", root.objectName); + Qt.callLater(root.finalizeClose); + } + return; + } + + // When height shrink completes during close, finalize + if (!running && root.isClosing && panelBackground.height === 0 && panelBackground.shouldAnimateHeight) { + Qt.callLater(root.finalizeClose); + } + } + } + } + + // Corner states for PanelBackground to read + // State -1: No radius (flat/square corner) + // State 0: Normal (inner curve) + // State 1: Horizontal inversion (outer curve on X-axis) + // State 2: Vertical inversion (outer curve on Y-axis) + + // Smart corner state calculation based on bar attachment and edge touching + property int topLeftCornerState: { + // If bar is not visible, don't show outer corners based on bar attachment + if (!root.barShouldShow) { + // Only check edge touching, not bar touching + var edgeInverted = panelContent.allowAttach && (panelContent.touchingLeftEdge || panelContent.touchingTopEdge); + if (edgeInverted) { + if (panelContent.touchingLeftEdge && panelContent.touchingTopEdge) + return 0; // Both edges: no inversion (normal rounded corner) + if (panelContent.touchingLeftEdge) + return 2; // Left edge: vertical inversion + if (panelContent.touchingTopEdge) + return 1; // Top edge: horizontal inversion + } + return 0; + } + + var barTouchInverted = panelContent.touchingTopBar || panelContent.touchingLeftBar; + // Invert if touching either edge that forms this corner (left OR top), regardless of bar position + var edgeInverted = panelContent.allowAttach && (panelContent.touchingLeftEdge || panelContent.touchingTopEdge); + + if (barTouchInverted || edgeInverted) { + // Determine inversion direction based on which edge is touched + if (panelContent.touchingLeftEdge && panelContent.touchingTopEdge) + return 0; // Both edges: no inversion (normal rounded corner) + if (panelContent.touchingLeftEdge) + return 2; // Left edge: vertical inversion + if (panelContent.touchingTopEdge) + return 1; // Top edge: horizontal inversion + return root.barIsVertical ? 2 : 1; + } + return 0; + } + + property int topRightCornerState: { + // If bar is not visible, don't show outer corners based on bar attachment + if (!root.barShouldShow) { + // Only check edge touching, not bar touching + var edgeInverted = panelContent.allowAttach && (panelContent.touchingRightEdge || panelContent.touchingTopEdge); + if (edgeInverted) { + if (panelContent.touchingRightEdge && panelContent.touchingTopEdge) + return 0; // Both edges: no inversion (normal rounded corner) + if (panelContent.touchingRightEdge) + return 2; // Right edge: vertical inversion + if (panelContent.touchingTopEdge) + return 1; // Top edge: horizontal inversion + } + return 0; + } + + var barTouchInverted = panelContent.touchingTopBar || panelContent.touchingRightBar; + // Invert if touching either edge that forms this corner (right OR top), regardless of bar position + var edgeInverted = panelContent.allowAttach && (panelContent.touchingRightEdge || panelContent.touchingTopEdge); + + if (barTouchInverted || edgeInverted) { + // Determine inversion direction based on which edge is touched + if (panelContent.touchingRightEdge && panelContent.touchingTopEdge) + return 0; // Both edges: no inversion (normal rounded corner) + if (panelContent.touchingRightEdge) + return 2; // Right edge: vertical inversion + if (panelContent.touchingTopEdge) + return 1; // Top edge: horizontal inversion + return root.barIsVertical ? 2 : 1; + } + return 0; + } + + property int bottomLeftCornerState: { + // If bar is not visible, don't show outer corners based on bar attachment + if (!root.barShouldShow) { + // Only check edge touching, not bar touching + var edgeInverted = panelContent.allowAttach && (panelContent.touchingLeftEdge || panelContent.touchingBottomEdge); + if (edgeInverted) { + if (panelContent.touchingLeftEdge && panelContent.touchingBottomEdge) + return 0; // Both edges: no inversion (normal rounded corner) + if (panelContent.touchingLeftEdge) + return 2; // Left edge: vertical inversion + if (panelContent.touchingBottomEdge) + return 1; // Bottom edge: horizontal inversion + } + return 0; + } + + var barTouchInverted = panelContent.touchingBottomBar || panelContent.touchingLeftBar; + // Invert if touching either edge that forms this corner (left OR bottom), regardless of bar position + var edgeInverted = panelContent.allowAttach && (panelContent.touchingLeftEdge || panelContent.touchingBottomEdge); + + if (barTouchInverted || edgeInverted) { + // Determine inversion direction based on which edge is touched + if (panelContent.touchingLeftEdge && panelContent.touchingBottomEdge) + return 0; // Both edges: no inversion (normal rounded corner) + if (panelContent.touchingLeftEdge) + return 2; // Left edge: vertical inversion + if (panelContent.touchingBottomEdge) + return 1; // Bottom edge: horizontal inversion + return root.barIsVertical ? 2 : 1; + } + return 0; + } + + property int bottomRightCornerState: { + // If bar is not visible, don't show outer corners based on bar attachment + if (!root.barShouldShow) { + // Only check edge touching, not bar touching + var edgeInverted = panelContent.allowAttach && (panelContent.touchingRightEdge || panelContent.touchingBottomEdge); + if (edgeInverted) { + if (panelContent.touchingRightEdge && panelContent.touchingBottomEdge) + return 0; // Both edges: no inversion (normal rounded corner) + if (panelContent.touchingRightEdge) + return 2; // Right edge: vertical inversion + if (panelContent.touchingBottomEdge) + return 1; // Bottom edge: horizontal inversion + } + return 0; + } + + var barTouchInverted = panelContent.touchingBottomBar || panelContent.touchingRightBar; + // Invert if touching either edge that forms this corner (right OR bottom), regardless of bar position + var edgeInverted = panelContent.allowAttach && (panelContent.touchingRightEdge || panelContent.touchingBottomEdge); + + if (barTouchInverted || edgeInverted) { + // Determine inversion direction based on which edge is touched + if (panelContent.touchingRightEdge && panelContent.touchingBottomEdge) + return 0; // Both edges: no inversion (normal rounded corner) + if (panelContent.touchingRightEdge) + return 2; // Right edge: vertical inversion + if (panelContent.touchingBottomEdge) + return 1; // Bottom edge: horizontal inversion + return root.barIsVertical ? 2 : 1; + } + return 0; + } + + // MouseArea to catch clicks on the panel and prevent them from reaching the background + // This prevents closing the panel when clicking inside it + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + z: -1 // Behind content, but on the panel background + onClicked: mouse => { + mouse.accepted = true; // Accept and ignore - prevents propagation to background + } + } + } + + // Panel top content: Text, icons, etc... + Loader { + id: contentLoader + active: isPanelOpen + x: panelBackground.x + y: panelBackground.y + width: panelBackground.width + height: panelBackground.height + sourceComponent: root.panelContent + + onLoaded: { + // Wait for contentPreferredWidth/Height to be available before making visible + Qt.callLater(function () { + // Calculate position with stable contentPreferredWidth/Height values + setPosition(); + + // Mark dimensions as initialized to enable animations + panelBackground.dimensionsInitialized = true; + + // Cache animation direction BEFORE isPanelVisible becomes true + // This locks in the direction for the entire open/close cycle + root.cachedAnimateFromTop = panelBackground.animateFromTop; + root.cachedAnimateFromBottom = panelBackground.animateFromBottom; + root.cachedAnimateFromLeft = panelBackground.animateFromLeft; + root.cachedAnimateFromRight = panelBackground.animateFromRight; + root.cachedShouldAnimateWidth = panelBackground.shouldAnimateWidth; + root.cachedShouldAnimateHeight = panelBackground.shouldAnimateHeight; + + // Make panel visible, now only the intended dimension will animate + root.isPanelVisible = true; + + if (root.animationsDisabled) { + // Skip delay when animations are disabled + root.sizeAnimationComplete = true; + } else { + opacityTrigger.start(); + } + + // Start open watchdog timer (skip when animations disabled - everything completes synchronously) + if (!root.animationsDisabled) { + root.openWatchdogActive = true; + openWatchdogTimer.start(); + } + + opened(); + }); + } + } + } + + Component.onCompleted: { + PanelService.registerPanel(root); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Notification/Notification.qml b/arch/.config/quickshell/noctalia-shell/Modules/Notification/Notification.qml new file mode 100644 index 0000000..a88d14b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Notification/Notification.qml @@ -0,0 +1,812 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.Notifications +import Quickshell.Wayland +import Quickshell.Widgets +import qs.Commons +import qs.Services.System +import qs.Widgets + +// Simple notification popup - displays multiple notifications +Variants { + + model: { + const screens = Quickshell.screens.filter(screen => Settings.data.notifications.monitors.includes(screen.name)); + // Empty list can mean two things : + // - No (visible) notification display activated in settings + // - One or more (not visible) displays are activated but unplugged + // In both cases we fallback to show notification on all screens + return screens.length === 0 ? Quickshell.screens : screens; + } + + delegate: Loader { + id: root + + required property ShellScreen modelData + + property ListModel notificationModel: NotificationService.popupModel + + // Deferred activation via Qt.callLater to avoid activating the Loader + // synchronously during ListModel.insert() (which would cause nested + // incubation with the inner Repeater). + property bool shouldBeActive: false + active: shouldBeActive || delayTimer.running + + // Keep loader active briefly after last notification to allow animations to complete + Timer { + id: delayTimer + interval: Style.animationSlow + 200 + repeat: false + } + + function activate() { + shouldBeActive = true; + } + + Connections { + target: notificationModel + function onCountChanged() { + if (notificationModel.count > 0) { + if (!root.shouldBeActive) { + Qt.callLater(root.activate); + } + } else if (root.shouldBeActive) { + root.shouldBeActive = false; + delayTimer.restart(); + } + } + } + + sourceComponent: PanelWindow { + id: notifWindow + screen: modelData + + WlrLayershell.namespace: "noctalia-notifications-" + (screen?.name || "unknown") + WlrLayershell.layer: (Settings.data.notifications?.overlayLayer) ? WlrLayer.Overlay : WlrLayer.Top + WlrLayershell.exclusionMode: ExclusionMode.Ignore + + color: "transparent" + + // Make shadow area click-through, only notification content is clickable + mask: Region { + x: 0 + y: 0 + width: notifWindow.width + height: notifWindow.height + intersection: Intersection.Xor + + Region { + // The clickable content area is inset by shadowPadding from all edges + x: notifWindow.shadowPadding + y: notifWindow.shadowPadding + width: notifWindow.notifWidth + height: Math.max(0, notifWindow.height - notifWindow.shadowPadding * 2) + intersection: Intersection.Subtract + } + } + + // Parse location setting + readonly property string location: Settings.data.notifications.location || "top_right" + readonly property bool isTop: location.startsWith("top") + readonly property bool isBottom: location.startsWith("bottom") + readonly property bool isLeft: location.endsWith("_left") + readonly property bool isRight: location.endsWith("_right") + readonly property bool isCentered: location === "top" || location === "bottom" + + readonly property string barPos: Settings.getBarPositionForScreen(notifWindow.screen?.name) + readonly property bool isFloating: Settings.data.bar.barType === "floating" + readonly property real barHeight: Style.getBarHeightForScreen(notifWindow.screen?.name) + + readonly property bool isFramed: Settings.data.bar.barType === "framed" + readonly property real frameThickness: Settings.data.bar.frameThickness ?? 8 + + readonly property bool isCompact: Settings.data.notifications.density === "compact" + readonly property int notifWidth: Math.round((isCompact ? 320 : 440) * Style.uiScaleRatio) + readonly property int shadowPadding: Style.shadowBlurMax + Style.marginL + + // Calculate bar and frame offsets for each edge separately + readonly property int barOffsetTop: { + if (barPos !== "top") + return isFramed ? frameThickness : 0; + const floatMarginV = isFloating ? Math.ceil(Settings.data.bar.marginVertical) : 0; + return barHeight + floatMarginV; + } + + readonly property int barOffsetBottom: { + if (barPos !== "bottom") + return isFramed ? frameThickness : 0; + const floatMarginV = isFloating ? Math.ceil(Settings.data.bar.marginVertical) : 0; + return barHeight + floatMarginV; + } + + readonly property int barOffsetLeft: { + if (barPos !== "left") + return isFramed ? frameThickness : 0; + const floatMarginH = isFloating ? Math.ceil(Settings.data.bar.marginHorizontal) : 0; + return barHeight + floatMarginH; + } + + readonly property int barOffsetRight: { + if (barPos !== "right") + return isFramed ? frameThickness : 0; + const floatMarginH = isFloating ? Math.ceil(Settings.data.bar.marginHorizontal) : 0; + return barHeight + floatMarginH; + } + + // Anchoring + anchors.top: isTop + anchors.bottom: isBottom + anchors.left: isLeft + anchors.right: isRight + + // Margins for PanelWindow - only apply bar offset for the specific edge where the bar is + margins.top: isTop ? barOffsetTop - shadowPadding + Style.marginM : 0 + margins.bottom: isBottom ? barOffsetBottom - shadowPadding : 0 + margins.left: isLeft ? barOffsetLeft - shadowPadding + Style.marginM : 0 + margins.right: isRight ? barOffsetRight - shadowPadding + Style.marginM : 0 + + implicitWidth: notifWidth + shadowPadding * 2 + implicitHeight: notificationStack.implicitHeight + Style.marginL + + property var animateConnection: null + + Component.onCompleted: { + animateConnection = function (notificationId) { + var delegate = null; + if (notificationRepeater) { + for (var i = 0; i < notificationRepeater.count; i++) { + var item = notificationRepeater.itemAt(i); + if (item?.notificationId === notificationId) { + delegate = item; + break; + } + } + } + + try { + if (delegate && typeof delegate.animateOut === "function" && !delegate.isRemoving) { + delegate.animateOut(); + } + } catch (e) { + // Service fallback if delegate is already invalid + NotificationService.dismissPopup(notificationId); + } + }; + + NotificationService.animateAndRemove.connect(animateConnection); + } + + Component.onDestruction: { + if (animateConnection) { + NotificationService.animateAndRemove.disconnect(animateConnection); + animateConnection = null; + } + } + + ColumnLayout { + id: notificationStack + + anchors { + top: parent.isTop ? parent.top : undefined + bottom: parent.isBottom ? parent.bottom : undefined + left: parent.isLeft ? parent.left : undefined + right: parent.isRight ? parent.right : undefined + horizontalCenter: parent.isCentered ? parent.horizontalCenter : undefined + } + + spacing: -notifWindow.shadowPadding * 2 + Style.marginM + + Behavior on implicitHeight { + enabled: !Settings.data.general.animationDisabled + SpringAnimation { + spring: 2.0 + damping: 0.4 + epsilon: 0.01 + mass: 0.8 + } + } + + Repeater { + id: notificationRepeater + model: notificationModel + + delegate: Item { + id: card + + property string notificationId: model.id + property var notificationData: model + property bool isHovered: false + property bool isRemoving: false + + readonly property int animationDelay: index * 100 + readonly property int slideDistance: 300 + + Layout.preferredWidth: notifWidth + notifWindow.shadowPadding * 2 + Layout.preferredHeight: (notifWindow.isCompact ? compactContent.implicitHeight : notificationContent.implicitHeight) + Style.margin2M + notifWindow.shadowPadding * 2 + Layout.maximumHeight: Layout.preferredHeight + + // Animation properties + property real scaleValue: 0.8 + property real opacityValue: 0.0 + property real slideOffset: 0 + property real swipeOffset: 0 + property real swipeOffsetY: 0 + property real pressGlobalX: 0 + property real pressGlobalY: 0 + property bool isSwiping: false + property bool suppressClick: false + readonly property bool useVerticalSwipe: notifWindow.location === "bottom" || notifWindow.location === "top" + readonly property real swipeStartThreshold: Math.round(18 * Style.uiScaleRatio) + readonly property real swipeDismissThreshold: Math.max(110, cardBackground.width * 0.32) + readonly property real verticalSwipeDismissThreshold: Math.max(70, cardBackground.height * 0.35) + + scale: scaleValue + opacity: opacityValue + transform: Translate { + x: card.swipeOffset + y: card.slideOffset + card.swipeOffsetY + } + + readonly property real slideInOffset: notifWindow.isTop ? -slideDistance : slideDistance + readonly property real slideOutOffset: slideInOffset + + function clampSwipeDelta(deltaX) { + if (notifWindow.isRight) + return Math.max(0, deltaX); + if (notifWindow.isLeft) + return Math.min(0, deltaX); + return deltaX; + } + + function clampVerticalSwipeDelta(deltaY) { + if (notifWindow.isBottom) + return Math.max(0, deltaY); + if (notifWindow.isTop) + return Math.min(0, deltaY); + return deltaY; + } + + // Animation setup + function triggerEntryAnimation() { + animInDelayTimer.stop(); + removalTimer.stop(); + resumeTimer.stop(); + isRemoving = false; + isHovered = false; + isSwiping = false; + swipeOffset = 0; + swipeOffsetY = 0; + if (Settings.data.general.animationDisabled) { + slideOffset = 0; + scaleValue = 1.0; + opacityValue = 1.0; + return; + } + + slideOffset = slideInOffset; + scaleValue = 0.8; + opacityValue = 0.0; + animInDelayTimer.interval = animationDelay; + animInDelayTimer.start(); + } + + Component.onCompleted: triggerEntryAnimation() + + onNotificationIdChanged: triggerEntryAnimation() + + Timer { + id: animInDelayTimer + interval: 0 + repeat: false + onTriggered: { + if (card.isRemoving) + return; + slideOffset = 0; + scaleValue = 1.0; + opacityValue = 1.0; + } + } + + function animateOut() { + if (isRemoving) + return; + animInDelayTimer.stop(); + resumeTimer.stop(); + isRemoving = true; + isSwiping = false; + swipeOffset = 0; + swipeOffsetY = 0; + if (!Settings.data.general.animationDisabled) { + slideOffset = slideOutOffset; + scaleValue = 0.8; + opacityValue = 0.0; + } + } + + function dismissBySwipe() { + if (isRemoving) + return; + animInDelayTimer.stop(); + resumeTimer.stop(); + isRemoving = true; + isSwiping = false; + if (!Settings.data.general.animationDisabled) { + if (useVerticalSwipe) { + swipeOffset = 0; + swipeOffsetY = swipeOffsetY >= 0 ? cardBackground.height + Style.marginXL : -cardBackground.height - Style.marginXL; + } else { + swipeOffset = swipeOffset >= 0 ? cardBackground.width + Style.marginXL : -cardBackground.width - Style.marginXL; + swipeOffsetY = 0; + } + scaleValue = 0.8; + opacityValue = 0.0; + } else { + swipeOffset = 0; + swipeOffsetY = 0; + } + } + + function runAction(actionId, isDismissed) { + if (!isDismissed) { + if (NotificationService.invokeActionAndSuppressClose(notificationId, actionId)) + card.animateOut(); + } else { + if (Settings.data.notifications.clearDismissed) + NotificationService.removeFromHistory(notificationId); + card.animateOut(); + } + } + + Timer { + id: removalTimer + interval: Style.animationSlow + repeat: false + onTriggered: { + NotificationService.dismissPopup(notificationId); + } + } + + onIsRemovingChanged: { + if (isRemoving) { + removalTimer.start(); + } + } + + Behavior on scale { + enabled: !Settings.data.general.animationDisabled + SpringAnimation { + spring: 3 + damping: 0.4 + epsilon: 0.01 + mass: 0.8 + } + } + + Behavior on opacity { + enabled: !Settings.data.general.animationDisabled + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + + Behavior on slideOffset { + enabled: !Settings.data.general.animationDisabled + SpringAnimation { + spring: 2.5 + damping: 0.3 + epsilon: 0.01 + mass: 0.6 + } + } + + Behavior on swipeOffset { + enabled: !Settings.data.general.animationDisabled && !card.isSwiping + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + Behavior on swipeOffsetY { + enabled: !Settings.data.general.animationDisabled && !card.isSwiping + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + // Sub item with the right dimensions, really usefull for the + // HoverHandler: card items are overlapping because of the + // negative spacing of notificationStack. + Item { + id: displayedCard + + anchors.fill: parent + anchors.margins: notifWindow.shadowPadding + + HoverHandler { + onHoveredChanged: { + isHovered = hovered; + if (isHovered) { + resumeTimer.stop(); + NotificationService.pauseTimeout(notificationId); + } else { + resumeTimer.start(); + } + } + } + + Timer { + id: resumeTimer + interval: 50 + repeat: false + onTriggered: { + if (!isHovered) { + NotificationService.resumeTimeout(notificationId); + } + } + } + + // Right-click to dismiss + MouseArea { + id: cardDragArea + anchors.fill: cardBackground + acceptedButtons: Qt.LeftButton | Qt.RightButton + hoverEnabled: true + onPressed: mouse => { + if (mouse.button === Qt.LeftButton) { + const globalPoint = cardDragArea.mapToGlobal(mouse.x, mouse.y); + card.pressGlobalX = globalPoint.x; + card.pressGlobalY = globalPoint.y; + card.isSwiping = false; + card.suppressClick = false; + } + } + onPositionChanged: mouse => { + if (!(mouse.buttons & Qt.LeftButton) || card.isRemoving) + return; + const globalPoint = cardDragArea.mapToGlobal(mouse.x, mouse.y); + const rawDeltaX = globalPoint.x - card.pressGlobalX; + const rawDeltaY = globalPoint.y - card.pressGlobalY; + const deltaX = card.clampSwipeDelta(rawDeltaX); + const deltaY = card.clampVerticalSwipeDelta(rawDeltaY); + if (!card.isSwiping) { + if (card.useVerticalSwipe) { + if (Math.abs(deltaY) < card.swipeStartThreshold) + return; + card.isSwiping = true; + } else { + if (Math.abs(deltaX) < card.swipeStartThreshold) + return; + card.isSwiping = true; + } + } + if (card.useVerticalSwipe) { + card.swipeOffset = 0; + card.swipeOffsetY = deltaY; + } else { + card.swipeOffset = deltaX; + card.swipeOffsetY = 0; + } + } + onReleased: mouse => { + if (mouse.button === Qt.RightButton) { + card.animateOut(); + if (Settings.data.notifications.clearDismissed) { + NotificationService.removeFromHistory(notificationId); + } + return; + } + + if (mouse.button !== Qt.LeftButton) + return; + + if (card.isSwiping) { + const dismissDistance = card.useVerticalSwipe ? Math.abs(card.swipeOffsetY) : Math.abs(card.swipeOffset); + const threshold = card.useVerticalSwipe ? card.verticalSwipeDismissThreshold : card.swipeDismissThreshold; + if (dismissDistance >= threshold) { + card.dismissBySwipe(); + if (Settings.data.notifications.clearDismissed) { + NotificationService.removeFromHistory(notificationId); + } + } else { + card.swipeOffset = 0; + card.swipeOffsetY = 0; + } + card.suppressClick = true; + card.isSwiping = false; + return; + } + + if (card.suppressClick) + return; + + var actions = model.actionsJson ? JSON.parse(model.actionsJson) : []; + var hasDefault = actions.some(function (a) { + return a.identifier === "default"; + }); + if (hasDefault && NotificationService.invokeActionAndSuppressClose(notificationId, "default")) { + card.animateOut(); + } else { + // Without a default action, or if invoking it fails, + // the best fallback is focusing the sender window by app identity. + NotificationService.focusSenderWindow(model.appName); + card.animateOut(); + } + } + onCanceled: { + card.isSwiping = false; + card.swipeOffset = 0; + card.swipeOffsetY = 0; + } + } + + // Background with border + Rectangle { + id: cardBackground + anchors.fill: parent + radius: Style.radiusL + border.color: Qt.alpha(Color.mOutline, Color.adaptiveOpacity(Settings.data.notifications.backgroundOpacity) || 1.0) + border.width: Style.borderS + color: Qt.alpha(Color.mSurface, Color.adaptiveOpacity(Settings.data.notifications.backgroundOpacity) || 1.0) + + // Progress bar + Rectangle { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + height: 2 + color: "transparent" + + Rectangle { + id: progressBar + readonly property real progressWidth: cardBackground.width - (2 * cardBackground.radius) + height: parent.height + x: cardBackground.radius + (progressWidth * (1 - model.progress)) / 2 + width: progressWidth * model.progress + + color: { + var baseColor = model.urgency === 2 ? Color.mError : model.urgency === 0 ? Color.mOnSurface : Color.mPrimary; + return Qt.alpha(baseColor, Color.adaptiveOpacity(Settings.data.notifications.backgroundOpacity) || 1.0); + } + + antialiasing: true + + Behavior on width { + enabled: !card.isRemoving + NumberAnimation { + duration: 100 + easing.type: Easing.Linear + } + } + + Behavior on x { + enabled: !card.isRemoving + NumberAnimation { + duration: 100 + easing.type: Easing.Linear + } + } + } + } + } + + NDropShadow { + anchors.fill: cardBackground + source: cardBackground + autoPaddingEnabled: true + } + + // Content + ColumnLayout { + id: notificationContent + visible: !notifWindow.isCompact + anchors.fill: cardBackground + anchors.margins: Style.marginM + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginL + Layout.leftMargin: Style.marginM + Layout.rightMargin: Style.marginM + Layout.topMargin: Style.marginM + Layout.bottomMargin: Style.marginM + + NImageRounded { + Layout.preferredWidth: Math.round(40 * Style.uiScaleRatio) + Layout.preferredHeight: Math.round(40 * Style.uiScaleRatio) + Layout.alignment: Qt.AlignVCenter + radius: Math.min(Style.radiusL, Layout.preferredWidth / 2) + imagePath: model.originalImage || "" + borderColor: "transparent" + borderWidth: 0 + fallbackIcon: "bell" + fallbackIconSize: 24 + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS + + // Header with urgency indicator + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + Rectangle { + Layout.preferredWidth: 6 + Layout.preferredHeight: 6 + Layout.alignment: Qt.AlignVCenter + radius: Style.radiusXS + color: model.urgency === 2 ? Color.mError : model.urgency === 0 ? Color.mOnSurface : Color.mPrimary + } + + NText { + text: model.appName || "Unknown App" + pointSize: Style.fontSizeXS + font.weight: Style.fontWeightBold + color: Color.mSecondary + } + + NText { + textFormat: Text.PlainText + text: " " + Time.formatRelativeTime(model.timestamp) + pointSize: Style.fontSizeXXS + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignBottom + } + + Item { + Layout.fillWidth: true + } + } + + NText { + text: model.summary || I18n.tr("common.no-summary") + pointSize: Style.fontSizeM + font.weight: Style.fontWeightMedium + color: Color.mOnSurface + textFormat: Text.StyledText + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + maximumLineCount: 3 + elide: Text.ElideRight + visible: text.length > 0 + Layout.fillWidth: true + Layout.rightMargin: Style.marginM + } + + NText { + text: model.body || "" + pointSize: Style.fontSizeM + color: Color.mOnSurface + textFormat: Text.StyledText + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + + maximumLineCount: 5 + elide: Text.ElideRight + visible: text.length > 0 + Layout.fillWidth: true + Layout.rightMargin: Style.marginXL + } + + // Actions + Flow { + Layout.fillWidth: true + spacing: Style.marginS + Layout.topMargin: Style.marginM + flow: Flow.LeftToRight + + property string parentNotificationId: notificationId + property var parsedActions: { + try { + return model.actionsJson ? JSON.parse(model.actionsJson) : []; + } catch (e) { + return []; + } + } + visible: parsedActions.length > 0 + + Repeater { + model: parent.parsedActions + + delegate: NButton { + property var actionData: modelData + + text: { + var actionText = actionData.text || "Open"; + if (actionText.includes(",")) { + return actionText.split(",")[1] || actionText; + } + return actionText; + } + fontSize: Style.fontSizeS + backgroundColor: Color.mPrimary + textColor: hovered ? Color.mOnHover : Color.mOnPrimary + hoverColor: Color.mHover + outlined: false + implicitHeight: 24 + onClicked: { + card.runAction(actionData.identifier, false); + } + } + } + } + } + } + } + + // Close button + NIconButton { + visible: !notifWindow.isCompact + icon: "close" + tooltipText: I18n.tr("tooltips.dismiss-notification") + baseSize: Style.baseWidgetSize * 0.6 + anchors.top: cardBackground.top + anchors.topMargin: Style.marginXL + anchors.right: cardBackground.right + anchors.rightMargin: Style.marginXL + + onClicked: { + card.runAction("", true); + } + } + + // Compact content + RowLayout { + id: compactContent + visible: notifWindow.isCompact + anchors.fill: cardBackground + anchors.margins: Style.marginM + spacing: Style.marginS + + NImageRounded { + Layout.preferredWidth: Math.round(24 * Style.uiScaleRatio) + Layout.preferredHeight: Math.round(24 * Style.uiScaleRatio) + Layout.alignment: Qt.AlignVCenter + radius: Style.radiusXS + imagePath: model.originalImage || "" + borderColor: "transparent" + borderWidth: 0 + fallbackIcon: "bell" + fallbackIconSize: 16 + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NText { + text: model.summary || I18n.tr("common.no-summary") + pointSize: Style.fontSizeM + font.weight: Style.fontWeightMedium + color: Color.mOnSurface + textFormat: Text.StyledText + maximumLineCount: 1 + elide: Text.ElideRight + Layout.fillWidth: true + } + + NText { + visible: model.body && model.body.length > 0 + Layout.fillWidth: true + text: model.body || "" + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + textFormat: Text.StyledText + wrapMode: Text.Wrap + maximumLineCount: 2 + elide: Text.ElideRight + } + } + } + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/OSD/OSD.qml b/arch/.config/quickshell/noctalia-shell/Modules/OSD/OSD.qml new file mode 100644 index 0000000..a64eee6 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/OSD/OSD.qml @@ -0,0 +1,868 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services.Hardware +import qs.Services.Keyboard +import qs.Services.Media +import qs.Services.UI +import qs.Widgets + +// Unified OSD component that displays volume, input volume, and brightness changes +Variants { + id: osd + + // Do not change the order or it will break settings. + enum Type { + Volume, + InputVolume, + Brightness, + LockKey + } + + model: Quickshell.screens.filter(screen => (Settings.data.osd.monitors.includes(screen.name) || Settings.data.osd.monitors.length === 0) && Settings.data.osd.enabled) + + delegate: Loader { + id: root + + required property ShellScreen modelData + + active: false + + // OSD State + property int currentOSDType: -1 // OSD.Type enum value, -1 means none + property bool startupComplete: false + property real currentBrightness: 0 + property bool suppressInputOSD: false + + // Lock Key States + property string lastLockKeyChanged: "" // "caps", "num", "scroll", or "" + + // Current values (computed properties) + readonly property real currentVolume: AudioService.volume + readonly property bool isMuted: AudioService.muted + readonly property real currentInputVolume: AudioService.inputVolume + readonly property bool isInputMuted: AudioService.inputMuted + readonly property real epsilon: 0.005 + + // LockKey OSD enabled state (reactive to settings) + readonly property bool lockKeyOSDEnabled: { + const enabledTypes = Settings.data.osd.enabledTypes || []; + if (enabledTypes.length === 0) + return false; + return enabledTypes.includes(OSD.Type.LockKey); + } + + readonly property var validIcons: { + const iconKeys = Object.keys(Icons.icons); + const aliasKeys = Object.keys(Icons.aliases); + return new Set([...iconKeys, ...aliasKeys]); + } + + function getIcon() { + switch (currentOSDType) { + case OSD.Type.Volume: + if (isMuted) + return "volume-mute"; + // Show volume-x icon when volume is effectively 0% (within rounding threshold) + if (currentVolume < root.epsilon) + return "volume-x"; + return currentVolume <= 0.5 ? "volume-low" : "volume-high"; + case OSD.Type.InputVolume: + return isInputMuted ? "microphone-off" : "microphone"; + case OSD.Type.Brightness: + // Show sun-off icon when brightness is effectively 0% (within rounding threshold) + if (currentBrightness < root.epsilon) + return "sun-off"; + return currentBrightness <= 0.5 ? "brightness-low" : "brightness-high"; + case OSD.Type.LockKey: + return "keyboard"; + default: + return ""; + } + } + + function getCurrentValue() { + switch (currentOSDType) { + case OSD.Type.Volume: + return isMuted ? 0 : currentVolume; + case OSD.Type.InputVolume: + return isInputMuted ? 0 : currentInputVolume; + case OSD.Type.Brightness: + return currentBrightness; + case OSD.Type.LockKey: + return 1.0; // Always show 100% when showing lock key status + + default: + return 0; + } + } + + function getMaxValue() { + if (currentOSDType === OSD.Type.Volume || currentOSDType === OSD.Type.InputVolume) { + return Settings.data.audio.volumeOverdrive ? 1.5 : 1.0; + } + return 1.0; + } + + function getDisplayPercentage() { + if (currentOSDType === OSD.Type.LockKey) { + // For lock keys, return the pre-determined status text + return lastLockKeyChanged; + } + + const value = getCurrentValue(); + const max = getMaxValue(); + if ((currentOSDType === OSD.Type.Volume || currentOSDType === OSD.Type.InputVolume) && Settings.data.audio.volumeOverdrive) { + const pct = Math.round(value * 100); + return pct + "%"; + } + const pct = Math.round(Math.min(max, value) * 100); + return pct + "%"; + } + + function getProgressColor() { + const isMutedState = (currentOSDType === OSD.Type.Volume && isMuted) || (currentOSDType === OSD.Type.InputVolume && isInputMuted); + if (isMutedState) { + return Color.mError; + } + // When volumeOverdrive is enabled, show error color if volume is above 100% + if ((currentOSDType === OSD.Type.Volume || currentOSDType === OSD.Type.InputVolume) && Settings.data.audio.volumeOverdrive) { + const value = getCurrentValue(); + if (value > 1.0) { + return Color.mError; + } + } + // For lock keys, use a different color to indicate the lock state + if (currentOSDType === OSD.Type.LockKey) { + // Check the specific lock key that was changed + if (lastLockKeyChanged.startsWith("CAPS")) { + return LockKeysService.capsLockOn ? Color.mPrimary : Color.mOnSurfaceVariant; + } else if (lastLockKeyChanged.startsWith("NUM")) { + return LockKeysService.numLockOn ? Color.mPrimary : Color.mOnSurfaceVariant; + } else if (lastLockKeyChanged.startsWith("SCROLL")) { + return LockKeysService.scrollLockOn ? Color.mPrimary : Color.mOnSurfaceVariant; + } + } + return Color.mPrimary; + } + + function getIconColor() { + const isMutedState = (currentOSDType === OSD.Type.Volume && isMuted) || (currentOSDType === OSD.Type.InputVolume && isInputMuted); + if (isMutedState) + return Color.mError; + + if (currentOSDType === OSD.Type.LockKey) { + // Check the specific lock key that was changed + if (lastLockKeyChanged.startsWith("CAPS")) { + return LockKeysService.capsLockOn ? Color.mPrimary : Color.mOnSurfaceVariant; + } else if (lastLockKeyChanged.startsWith("NUM")) { + return LockKeysService.numLockOn ? Color.mPrimary : Color.mOnSurfaceVariant; + } else if (lastLockKeyChanged.startsWith("SCROLL")) { + return LockKeysService.scrollLockOn ? Color.mPrimary : Color.mOnSurfaceVariant; + } + } + + return Color.mOnSurface; + } + + // Brightness Handling + function connectBrightnessMonitors() { + for (var i = 0; i < BrightnessService.monitors.length; i++) { + const monitor = BrightnessService.monitors[i]; + monitor.brightnessUpdated.disconnect(onBrightnessChanged); + monitor.brightnessUpdated.connect(onBrightnessChanged); + } + } + + function onBrightnessChanged(newBrightness) { + if (!root) + return; + + root.currentBrightness = newBrightness; + // Don't show OSD if brightness panel is open + var brightnessPanel = PanelService.getPanel("brightnessPanel", root.modelData); + var controlCenterPanel = PanelService.getPanel("controlCenterPanel", root.modelData); + + if (brightnessPanel && brightnessPanel.isPanelOpen) + return; + if (controlCenterPanel && controlCenterPanel.isPanelOpen) { + var cards = Settings.data.controlCenter.cards || []; + if (cards.some(c => c.enabled && c.id === "brightness-card")) + return; + } + showOSD(OSD.Type.Brightness); + } + + // Check if a specific OSD type is enabled + function isTypeEnabled(type) { + const enabledTypes = Settings.data.osd.enabledTypes || []; + // If enabledTypes is empty, no types are enabled (no OSD will be shown) + if (enabledTypes.length === 0) + return false; + return enabledTypes.includes(type); + } + + // OSD Display Control + function showOSD(type) { + // Ignore all OSD requests during startup period + if (!startupComplete) + return; + + // Check if this OSD type is enabled + if (!isTypeEnabled(type)) + return; + + // Suppress Audio OSD if Audio Panel or Control Center (with audio card) is open + if (type === OSD.Type.Volume || type === OSD.Type.InputVolume) { + var audioPanel = PanelService.getPanel("audioPanel", root.modelData); + if (audioPanel && audioPanel.isPanelOpen) + return; + var controlCenterPanel = PanelService.getPanel("controlCenterPanel", root.modelData); + if (controlCenterPanel && controlCenterPanel.isPanelOpen) { + var cards = Settings.data.controlCenter.cards || []; + if (cards.some(c => c.enabled && c.id === "audio-card")) + return; + } + } + + currentOSDType = type; + + if (!root.active) { + root.active = true; + } + + if (root.item) { + root.item.showOSD(); + } else { + Qt.callLater(() => { + if (root.item) + root.item.showOSD(); + }); + } + } + + function hideOSD() { + if (root.item?.osdItem) { + root.item.osdItem.hideImmediately(); + } else if (root.active) { + root.active = false; + } + } + + // AudioService monitoring + Connections { + target: AudioService + + function onVolumeChanged() { + showOSD(OSD.Type.Volume); + } + + function onVolumeAtMaximum() { + showOSD(OSD.Type.Volume); + } + + function onVolumeAtMinimum() { + showOSD(OSD.Type.Volume); + } + + function onMutedChanged() { + if (AudioService.consumeOutputOSDSuppression()) + return; + showOSD(OSD.Type.Volume); + } + + function onInputVolumeChanged() { + if (suppressInputOSD) + return; + if (AudioService.hasInput) + showOSD(OSD.Type.InputVolume); + } + + function onInputMutedChanged() { + if (suppressInputOSD) + return; + if (!AudioService.hasInput) + return; + if (AudioService.consumeInputOSDSuppression()) + return; + showOSD(OSD.Type.InputVolume); + } + + // Refresh OSD when device changes to ensure correct volume is displayed + function onSinkChanged() { + suppressInputOSD = true; + inputSuppressionTimer.restart(); + // If volume OSD is currently showing, refresh it to show new device's volume + if (root.currentOSDType === OSD.Type.Volume) { + Qt.callLater(() => { + showOSD(OSD.Type.Volume); + }); + } + } + + function onSourceChanged() { + // If input volume OSD is currently showing, refresh it to show new device's volume + if (root.currentOSDType === OSD.Type.InputVolume) { + Qt.callLater(() => { + showOSD(OSD.Type.InputVolume); + }); + } + } + } + + // Brightness monitoring + Connections { + target: BrightnessService + function onMonitorsChanged() { + connectBrightnessMonitors(); + } + } + + // Register/unregister LockKeysService polling based on whether LockKey OSD is enabled + onLockKeyOSDEnabledChanged: { + if (lockKeyOSDEnabled) { + LockKeysService.registerComponent("osd:" + (modelData?.name || "unknown")); + } else { + LockKeysService.unregisterComponent("osd:" + (modelData?.name || "unknown")); + } + } + Component.onCompleted: { + if (lockKeyOSDEnabled) { + LockKeysService.registerComponent("osd:" + (modelData?.name || "unknown")); + } + } + + // LockKeys monitoring with a cleaner approach + // Only connect when LockKey OSD is enabled to avoid starting the service unnecessarily + Connections { + target: root.lockKeyOSDEnabled ? LockKeysService : null + + function onCapsLockChanged(active) { + root.lastLockKeyChanged = active ? "CAPS ON" : "CAPS OFF"; + root.showOSD(OSD.Type.LockKey); + } + + function onNumLockChanged(active) { + root.lastLockKeyChanged = active ? "NUM ON" : "NUM OFF"; + root.showOSD(OSD.Type.LockKey); + } + + function onScrollLockChanged(active) { + root.lastLockKeyChanged = active ? "SCROLL ON" : "SCROLL OFF"; + root.showOSD(OSD.Type.LockKey); + } + } + + // Startup timer - connect brightness monitors and enable OSD after 2 seconds + Timer { + id: startupTimer + interval: 2000 + running: true + onTriggered: { + connectBrightnessMonitors(); + root.startupComplete = true; + } + } + + // Timer to reset the input volume OSD suppression + Timer { + id: inputSuppressionTimer + interval: 300 + repeat: false + onTriggered: root.suppressInputOSD = false + } + + Component.onDestruction: { + LockKeysService.unregisterComponent("osd:" + (modelData?.name || "unknown")); + if (typeof BrightnessService !== "undefined" && BrightnessService.monitors) { + for (var i = 0; i < BrightnessService.monitors.length; i++) { + try { + BrightnessService.monitors[i].brightnessUpdated.disconnect(onBrightnessChanged); + } catch (e) { + // Ignore errors if already disconnected or not connected + } + } + } + } + + // Visual Component + sourceComponent: PanelWindow { + id: panel + screen: modelData + + // Position configuration + readonly property string location: Settings.data.osd?.location || "top_right" + readonly property bool isTop: location === "top" || location.startsWith("top") + readonly property bool isBottom: location === "bottom" || location.startsWith("bottom") + readonly property bool isLeft: location.includes("_left") || location === "left" + readonly property bool isRight: location.includes("_right") || location === "right" + readonly property bool verticalMode: location === "left" || location === "right" + + // Hidden text element for measuring lock key text width + NText { + id: lockKeyTextMetrics + visible: false + text: root.getDisplayPercentage() + pointSize: Style.fontSizeS + family: Settings.data.ui.fontFixed + elide: Text.ElideNone + wrapMode: Text.NoWrap + } + + // Dimensions + readonly property bool isShortMode: root.currentOSDType === OSD.Type.LockKey + readonly property int longHWidth: Math.round(320 * Style.uiScaleRatio) + readonly property int longHHeight: Math.round(72 * Style.uiScaleRatio) + readonly property int shortHWidth: Math.round(180 * Style.uiScaleRatio) + readonly property int longVWidth: Math.round(80 * Style.uiScaleRatio) + readonly property int longVHeight: Math.round(280 * Style.uiScaleRatio) + readonly property int shortVHeight: Math.round(180 * Style.uiScaleRatio) + + // Dynamic width for horizontal lock keys based on text length + // Explicitly bind to contentWidth to ensure reactivity + readonly property int lockKeyHWidth: { + if (root.currentOSDType !== OSD.Type.LockKey || verticalMode) { + return shortHWidth; + } + const text = root.getDisplayPercentage(); + if (!text) { + return shortHWidth; + } + // Access contentWidth to create binding dependency + const textWidth = Math.ceil(lockKeyTextMetrics.contentWidth || 0); + if (textWidth === 0) { + // Fallback: estimate based on text length if measurement not ready + const fontSize = Style.fontSizeS * Settings.data.ui.fontFixedScale * Style.uiScaleRatio; + const estimatedWidth = text.length * fontSize * 0.6; + const iconWidth = Style.fontSizeXL * Style.uiScaleRatio; + const margins = Style.margin2L; + const spacing = Style.marginM; + const bgMargins = Style.margin2M * 1.5; + return Math.max(shortHWidth, Math.round((estimatedWidth + iconWidth + margins + spacing + bgMargins) * 1.1)); + } + const iconWidth = Style.fontSizeXL * Style.uiScaleRatio; + const margins = Style.margin2L; // Left and right content margins + const spacing = Style.marginM; // Spacing between icon and text + const bgMargins = Style.margin2M * 1.5; // Background margins + const totalWidth = textWidth + iconWidth + margins + spacing + bgMargins; + // Ensure minimum width and add some buffer + return Math.max(shortHWidth, Math.round(totalWidth * 1.1)); + } + + // Dynamic height for vertical lock keys based on text length + readonly property int lockKeyVHeight: { + if (root.currentOSDType !== OSD.Type.LockKey || !verticalMode) { + return shortVHeight; + } + const text = root.getDisplayPercentage(); + const charCount = text ? text.length : 0; + if (charCount === 0) { + return shortVHeight; + } + // Calculate height: font size * char count + margins + icon space + // Font size M (11pt) scaled, plus some spacing between chars + const fontSize = Style.fontSizeS * Settings.data.ui.fontFixedScale * Style.uiScaleRatio; + const charHeight = fontSize * 1.3; // Add 30% for line height (matches Layout.preferredHeight) + const textHeight = charCount * charHeight; + // Background margins (Style.marginM * 1.5 * 2 for top and bottom) + const bgMargins = Style.marginM * 1.5 * 2; + // Content margins (Style.margin2L for top and bottom) + const contentMargins = Style.margin2L; + // Icon size: fontSizeXL scaled, with extra space for icon rendering and padding + const iconSize = Style.fontSizeXL * Style.uiScaleRatio * 1.8; // Add 80% for icon rendering and padding + // Spacing between text and icon (Style.marginM for lock keys) + const textIconSpacing = Style.marginM; + // Add extra buffer to ensure everything fits comfortably + const buffer = Style.marginL; + const totalHeight = textHeight + bgMargins + contentMargins + iconSize + textIconSpacing + buffer; + // Ensure minimum height and add extra padding for safety + return Math.max(shortVHeight, Math.round(totalHeight * 1.1)); + } + + readonly property int barThickness: { + const base = Math.max(8, Math.round(8 * Style.uiScaleRatio)); + return base % 2 === 0 ? base : base + 1; + } + + anchors.top: isTop + anchors.bottom: isBottom + anchors.left: isLeft + anchors.right: isRight + + readonly property string screenBarPosition: Settings.getBarPositionForScreen(root.modelData?.name) + readonly property real barHeight: Style.getBarHeightForScreen(root.modelData?.name) + readonly property bool isFramed: Settings.data.bar.barType === "framed" + readonly property real frameThickness: Settings.data.bar.frameThickness ?? 8 + + function calculateMargin(isAnchored, position) { + if (!isAnchored) + return 0; + + let base = Style.marginM; + if (screenBarPosition === position) { + const isVertical = position === "top" || position === "bottom"; + const floatExtra = Math.ceil(Settings.data.bar.barType === "floating" ? (isVertical ? Settings.data.bar.marginVertical : Settings.data.bar.marginHorizontal) : 0); + return barHeight + base + floatExtra; + } + + if (isFramed) { + return base + frameThickness; + } + + return base; + } + + margins.top: calculateMargin(anchors.top, "top") + margins.bottom: calculateMargin(anchors.bottom, "bottom") + margins.left: calculateMargin(anchors.left, "left") + margins.right: calculateMargin(anchors.right, "right") + + implicitWidth: verticalMode ? longVWidth : (isShortMode ? lockKeyHWidth : longHWidth) + implicitHeight: verticalMode ? (isShortMode ? lockKeyVHeight : longVHeight) : longHHeight + color: "transparent" + + WlrLayershell.namespace: "noctalia-osd-" + (screen?.name || "unknown") + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + WlrLayershell.layer: Settings.data.osd?.overlayLayer ? WlrLayer.Overlay : WlrLayer.Top + WlrLayershell.exclusionMode: ExclusionMode.Ignore + + // Click-through โ€” OSD is display-only, no input needed + mask: Region {} + + Item { + id: osdItem + anchors.fill: parent + visible: false + opacity: 0 + scale: 0.85 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutQuad + } + } + + Behavior on scale { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutQuad + } + } + + Timer { + id: hideTimer + interval: Settings.data.osd.autoHideMs + onTriggered: osdItem.hide() + } + + Timer { + id: visibilityTimer + interval: Style.animationNormal + 50 + onTriggered: { + osdItem.visible = false; + root.currentOSDType = -1; + root.lastLockKeyChanged = ""; + root.active = false; + } + } + + Rectangle { + id: background + anchors.fill: parent + anchors.margins: Style.marginM * 1.5 + radius: Style.radiusL + color: Qt.alpha(Color.mSurface, Color.adaptiveOpacity(Settings.data.osd.backgroundOpacity) || 1.0) + border.color: Qt.alpha(Color.mOutline, Color.adaptiveOpacity(Settings.data.osd.backgroundOpacity) || 1.0) + border.width: { + const bw = Math.max(2, Style.borderM); + return bw % 2 === 0 ? bw : bw + 1; + } + } + + NDropShadow { + anchors.fill: background + source: background + autoPaddingEnabled: true + } + + Loader { + id: contentLoader + anchors.fill: background + anchors.margins: Style.marginM + active: true + sourceComponent: panel.verticalMode ? verticalContent : horizontalContent + } + + Component { + id: horizontalContent + RowLayout { + anchors.fill: parent + anchors.leftMargin: Style.marginL + anchors.rightMargin: Style.marginL + spacing: Style.marginM + + TextMetrics { + id: percentageMetrics + font.family: Settings.data.ui.fontFixed + font.pointSize: Style.fontSizeS * (Settings.data.ui.fontFixedScale * Style.uiScaleRatio) + text: "150%" + } + + // Common Icon for all types + NIcon { + icon: root.getIcon() + color: root.getIconColor() + pointSize: Style.fontSizeXL + Layout.alignment: Qt.AlignVCenter + + Behavior on color { + ColorAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutQuad + } + } + } + + // Lock Key Status Text (replaces progress bar) + NText { + visible: root.currentOSDType === OSD.Type.LockKey + text: root.getDisplayPercentage() + color: root.getProgressColor() + pointSize: Style.fontSizeS + elide: Text.ElideNone + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + Layout.alignment: Qt.AlignVCenter + } + + // Progress Bar for Volume/Brightness + Rectangle { + visible: root.currentOSDType !== OSD.Type.LockKey + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + height: panel.barThickness + radius: Math.min(Style.iRadiusL, panel.barThickness / 2) + color: Color.mSurfaceVariant + + Rectangle { + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + width: parent.width * Math.min(1.0, root.getCurrentValue() / root.getMaxValue()) + radius: parent.radius + color: root.getProgressColor() + + Behavior on width { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutQuad + } + } + Behavior on color { + ColorAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutQuad + } + } + } + } + + // Percentage Text for Volume/Brightness + NText { + visible: root.currentOSDType !== OSD.Type.LockKey + text: root.getDisplayPercentage() + color: Color.mOnSurface + pointSize: Style.fontSizeS + family: Settings.data.ui.fontFixed + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignVCenter + Layout.fillWidth: false + Layout.preferredWidth: Math.ceil(percentageMetrics.width) + Math.round(8 * Style.uiScaleRatio) + Layout.maximumWidth: Math.ceil(percentageMetrics.width) + Math.round(8 * Style.uiScaleRatio) + Layout.minimumWidth: Math.ceil(percentageMetrics.width) + } + } + } + + Component { + id: verticalContent + ColumnLayout { + anchors.fill: parent + anchors.topMargin: Style.marginL + anchors.bottomMargin: Style.marginL + spacing: root.currentOSDType === OSD.Type.LockKey ? Style.marginM : Style.marginS + clip: root.currentOSDType !== OSD.Type.LockKey + + ColumnLayout { + id: textVerticalLayout + visible: root.currentOSDType === OSD.Type.LockKey + Layout.fillWidth: true + Layout.fillHeight: false + Layout.alignment: Qt.AlignHCenter + spacing: 0 + + property var verticalTextChars: [] + + function updateVerticalTextChars() { + const text = root.getDisplayPercentage(); + const chars = []; + for (let i = 0; i < text.length; i++) { + chars.push(text[i]); + } + verticalTextChars = chars; + } + + Component.onCompleted: updateVerticalTextChars() + + Connections { + target: root + function onLastLockKeyChangedChanged() { + if (root.currentOSDType === OSD.Type.LockKey) { + textVerticalLayout.updateVerticalTextChars(); + } + } + function onCurrentOSDTypeChanged() { + if (root.currentOSDType === OSD.Type.LockKey) { + textVerticalLayout.updateVerticalTextChars(); + } + } + } + + Repeater { + model: textVerticalLayout.verticalTextChars + + NText { + text: modelData || "" + color: root.getProgressColor() + pointSize: Style.fontSizeS + family: Settings.data.ui.fontFixed + Layout.fillWidth: true + Layout.preferredHeight: { + const fontSize = Style.fontSizeS * Settings.data.ui.fontFixedScale * Style.uiScaleRatio; + return Math.round(fontSize * 1.3); + } + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + } + + NText { + visible: root.currentOSDType !== OSD.Type.LockKey + text: root.getDisplayPercentage() + color: Color.mOnSurface + pointSize: Style.fontSizeS + family: Settings.data.ui.fontFixed + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + Layout.preferredHeight: Math.round(20 * Style.uiScaleRatio) + } + + Item { + visible: root.currentOSDType !== OSD.Type.LockKey + Layout.fillWidth: true + Layout.fillHeight: root.currentOSDType !== OSD.Type.LockKey + + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.bottom: parent.bottom + width: panel.barThickness + radius: Math.min(Style.iRadiusL, panel.barThickness / 2) + color: Color.mSurfaceVariant + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: parent.height * Math.min(1.0, root.getCurrentValue() / root.getMaxValue()) + radius: parent.radius + color: root.getProgressColor() + + Behavior on height { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutQuad + } + } + Behavior on color { + ColorAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutQuad + } + } + } + } + } + + NIcon { + icon: root.getIcon() + color: root.getIconColor() + pointSize: root.currentOSDType === OSD.Type.LockKey ? Style.fontSizeXL : Style.fontSizeL + Layout.alignment: root.currentOSDType === OSD.Type.LockKey ? Qt.AlignHCenter : (Qt.AlignHCenter | Qt.AlignBottom) + Layout.preferredHeight: root.currentOSDType === OSD.Type.LockKey ? (Style.fontSizeXL * Style.uiScaleRatio * 1.5) : -1 + Layout.minimumHeight: root.currentOSDType === OSD.Type.LockKey ? (Style.fontSizeXL * Style.uiScaleRatio) : 0 + + Behavior on color { + ColorAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutQuad + } + } + } + } + } + + // Delay showing the OSD to allow the layout to settle after activation. + // Without this, the percentage text renders outside the box on first + // show. + Timer { + id: showDelayTimer + interval: 30 + onTriggered: { + osdItem.visible = true; + osdItem.opacity = 1; + osdItem.scale = 1.0; + hideTimer.start(); + } + } + + function show() { + hideTimer.stop(); + visibilityTimer.stop(); + showDelayTimer.start(); + } + + function hide() { + hideTimer.stop(); + visibilityTimer.stop(); + osdItem.opacity = 0; + osdItem.scale = 0.85; + visibilityTimer.start(); + } + + function hideImmediately() { + hideTimer.stop(); + visibilityTimer.stop(); + osdItem.opacity = 0; + osdItem.scale = 0.85; + osdItem.visible = false; + root.currentOSDType = -1; + root.active = false; + } + } + + function showOSD() { + osdItem.show(); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Audio/AudioPanel.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Audio/AudioPanel.qml new file mode 100644 index 0000000..31e2280 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Audio/AudioPanel.qml @@ -0,0 +1,791 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.Pipewire +import Quickshell.Widgets +import qs.Commons +import qs.Modules.MainScreen +import qs.Services.Media +import qs.Widgets + +SmartPanel { + id: root + + preferredWidth: Math.round(440 * Style.uiScaleRatio) + preferredHeight: Math.round(420 * Style.uiScaleRatio) + + panelContent: Item { + id: panelContent + + // Volume state (lazy-loaded with panelContent) + property real localOutputVolume: AudioService.volume || 0 + property bool localOutputVolumeChanging: false + property int lastSinkId: -1 + + property real localInputVolume: AudioService.inputVolume || 0 + property bool localInputVolumeChanging: false + property int lastSourceId: -1 + + readonly property bool outputVolumeGuard: outputVolumeSlider.sliderActive || localOutputVolumeChanging + readonly property bool inputVolumeGuard: inputVolumeSlider.sliderActive || localInputVolumeChanging + + // UI state (lazy-loaded with panelContent) + property int currentTabIndex: 0 + + Component.onCompleted: { + var vol = AudioService.volume; + localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0; + var inputVol = AudioService.inputVolume; + localInputVolume = (inputVol !== undefined && !isNaN(inputVol)) ? inputVol : 0; + if (AudioService.sink) { + lastSinkId = AudioService.sink.id; + } + if (AudioService.source) { + lastSourceId = AudioService.source.id; + } + } + + // Reset local volume when device changes - use current device's volume + Connections { + target: AudioService + function onSinkChanged() { + if (AudioService.sink) { + const newSinkId = AudioService.sink.id; + if (newSinkId !== panelContent.lastSinkId) { + panelContent.lastSinkId = newSinkId; + // Immediately set local volume to current device's volume + var vol = AudioService.volume; + panelContent.localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0; + } + } else { + panelContent.lastSinkId = -1; + panelContent.localOutputVolume = 0; + } + } + } + + Connections { + target: AudioService + function onSourceChanged() { + if (AudioService.source) { + const newSourceId = AudioService.source.id; + if (newSourceId !== panelContent.lastSourceId) { + panelContent.lastSourceId = newSourceId; + // Immediately set local volume to current device's volume + var vol = AudioService.inputVolume; + panelContent.localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0; + } + } else { + panelContent.lastSourceId = -1; + panelContent.localInputVolume = 0; + } + } + } + + // Connections to update local volumes when AudioService changes + Connections { + target: AudioService + function onVolumeChanged() { + if (!panelContent.outputVolumeGuard && !AudioService.isSettingOutputVolume && AudioService.sink && AudioService.sink.id === panelContent.lastSinkId) { + var vol = AudioService.volume; + panelContent.localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0; + } + } + } + + Connections { + target: AudioService + function onInputVolumeChanged() { + if (!panelContent.inputVolumeGuard && !AudioService.isSettingInputVolume && AudioService.source && AudioService.source.id === panelContent.lastSourceId) { + var vol = AudioService.inputVolume; + panelContent.localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0; + } + } + } + + Connections { + target: outputVolumeSlider + function onSliderActiveChanged() { + if (!outputVolumeSlider.sliderActive && AudioService.sink && AudioService.sink.id === panelContent.lastSinkId) { + var vol = AudioService.volume; + panelContent.localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0; + } + } + } + + Connections { + target: inputVolumeSlider + function onSliderActiveChanged() { + if (!inputVolumeSlider.sliderActive && AudioService.source && AudioService.source.id === panelContent.lastSourceId) { + var vol = AudioService.inputVolume; + panelContent.localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0; + } + } + } + + // Timer to debounce volume changes + // Only sync if the device hasn't changed (check by comparing IDs) + Timer { + interval: 100 + running: true + repeat: true + onTriggered: { + // Only sync if sink hasn't changed + if (AudioService.sink && AudioService.sink.id === panelContent.lastSinkId) { + if (Math.abs(panelContent.localOutputVolume - AudioService.volume) >= 0.01) { + AudioService.setVolume(panelContent.localOutputVolume); + } + } + // Only sync if source hasn't changed + if (AudioService.source && AudioService.source.id === panelContent.lastSourceId) { + if (Math.abs(panelContent.localInputVolume - AudioService.inputVolume) >= 0.01) { + AudioService.setInputVolume(panelContent.localInputVolume); + } + } + } + } + + // Find application streams that are actually playing audio (connected to default sink) + // Use linkGroups to find nodes connected to the default audio sink + // Note: We need to use link IDs since source/target properties require binding + readonly property var appStreams: AudioService.appStreams + + // Use implicitHeight from content + margins to avoid binding loops + property real contentPreferredHeight: mainColumn.implicitHeight + Style.margin2L + + ColumnLayout { + id: mainColumn + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + // HEADER + NBox { + Layout.fillWidth: true + implicitHeight: header.implicitHeight + Style.margin2M + + ColumnLayout { + id: header + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + + RowLayout { + NIcon { + icon: "settings-audio" + pointSize: Style.fontSizeXXL + color: Color.mPrimary + } + + NText { + text: I18n.tr("panels.audio.title") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: { + root.close(); + } + } + } + + NTabBar { + id: tabBar + Layout.fillWidth: true + margins: Style.marginS + currentIndex: panelContent.currentTabIndex + distributeEvenly: true + onCurrentIndexChanged: panelContent.currentTabIndex = currentIndex + + NTabButton { + text: I18n.tr("common.volumes") + tabIndex: 0 + checked: tabBar.currentIndex === 0 + } + + NTabButton { + text: I18n.tr("common.devices") + tabIndex: 1 + checked: tabBar.currentIndex === 1 + } + } + } + } + + // Content Stack + StackLayout { + Layout.fillWidth: true + Layout.fillHeight: true + currentIndex: panelContent.currentTabIndex + + // Applications Tab (Volume) + NScrollView { + id: volumeScrollView + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AsNeeded + contentWidth: availableWidth + reserveScrollbarSpace: false + gradientColor: Color.mSurface + + ColumnLayout { + spacing: Style.marginM + width: volumeScrollView.availableWidth + + // Output Volume + NBox { + Layout.fillWidth: true + Layout.preferredHeight: outputVolumeColumn.implicitHeight + Style.margin2M + + ColumnLayout { + id: outputVolumeColumn + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Style.marginM + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NText { + text: I18n.tr("common.output") + pointSize: Style.fontSizeM + color: Color.mPrimary + } + + NText { + text: AudioService.sink ? (" - " + (AudioService.sink.description || AudioService.sink.name || "")) : "" + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + elide: Text.ElideRight + Layout.fillWidth: true + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NValueSlider { + id: outputVolumeSlider + Layout.fillWidth: true + from: 0 + to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0 + value: localOutputVolume + stepSize: 0.01 + heightRatio: 0.5 + onMoved: function (value) { + localOutputVolume = value; + } + onPressedChanged: function (pressed) { + localOutputVolumeChanging = pressed; + } + } + + NText { + text: Math.round((panelContent.outputVolumeGuard ? localOutputVolume : AudioService.volume) * 100) + "%" + pointSize: Style.fontSizeM + family: Settings.data.ui.fontFixed + color: Color.mOnSurface + opacity: enabled ? 1.0 : 0.6 + Layout.alignment: Qt.AlignVCenter + Layout.preferredWidth: 45 * Style.uiScaleRatio + horizontalAlignment: Text.AlignRight + } + + NIconButton { + icon: AudioService.getOutputIcon() + tooltipText: I18n.tr("tooltips.output-muted") + baseSize: Style.baseWidgetSize * 0.7 + onClicked: { + AudioService.suppressOutputOSD(); + AudioService.setOutputMuted(!AudioService.muted); + } + } + } + } + } + + // Input Volume + NBox { + Layout.fillWidth: true + Layout.preferredHeight: inputVolumeColumn.implicitHeight + Style.margin2M + + ColumnLayout { + id: inputVolumeColumn + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Style.marginM + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NText { + text: I18n.tr("common.input") + pointSize: Style.fontSizeM + color: Color.mPrimary + } + + NText { + text: AudioService.source ? (" - " + (AudioService.source.description || AudioService.source.name || "")) : "" + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + elide: Text.ElideRight + Layout.fillWidth: true + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NValueSlider { + id: inputVolumeSlider + Layout.fillWidth: true + from: 0 + to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0 + value: localInputVolume + stepSize: 0.01 + heightRatio: 0.5 + onMoved: function (value) { + localInputVolume = value; + } + onPressedChanged: function (pressed) { + localInputVolumeChanging = pressed; + } + } + + NText { + text: Math.round((panelContent.inputVolumeGuard ? localInputVolume : AudioService.inputVolume) * 100) + "%" + pointSize: Style.fontSizeM + family: Settings.data.ui.fontFixed + color: Color.mOnSurface + opacity: enabled ? 1.0 : 0.6 + Layout.alignment: Qt.AlignVCenter + Layout.preferredWidth: 45 * Style.uiScaleRatio + horizontalAlignment: Text.AlignRight + } + + NIconButton { + icon: AudioService.getInputIcon() + tooltipText: I18n.tr("tooltips.input-muted") + baseSize: Style.baseWidgetSize * 0.7 + onClicked: { + AudioService.suppressInputOSD(); + AudioService.setInputMuted(!AudioService.inputMuted); + } + } + } + } + } + + // Bind all app stream nodes to access their audio properties + PwObjectTracker { + id: appStreamsTracker + objects: panelContent.appStreams + } + + Repeater { + model: panelContent.appStreams + + NBox { + id: appBox + required property PwNode modelData + Layout.fillWidth: true + Layout.preferredHeight: appRow.implicitHeight + Style.margin2M + visible: !isCaptureStream + + // Track individual node to ensure properties are bound + PwObjectTracker { + objects: modelData ? [modelData] : [] + } + + property PwNodeAudio nodeAudio: (modelData && modelData.audio) ? modelData.audio : null + property real appVolume: (nodeAudio && nodeAudio.volume !== undefined) ? nodeAudio.volume : 0.0 + property bool appMuted: (nodeAudio && nodeAudio.muted !== undefined) ? nodeAudio.muted : false + + // Check if this is a capture stream (after node is bound) + readonly property bool isCaptureStream: { + if (!modelData || !modelData.properties) + return false; + const props = modelData.properties; + // Exclude capture streams - check for stream.capture.sink property + if (props["stream.capture.sink"] !== undefined) { + return true; + } + const mediaClass = props["media.class"] || ""; + // Exclude Stream/Input (capture) but allow Stream/Output (playback) + if (mediaClass.includes("Capture") || mediaClass === "Stream/Input" || mediaClass === "Stream/Input/Audio") { + return true; + } + const mediaRole = props["media.role"] || ""; + if (mediaRole === "Capture") { + return true; + } + return false; + } + + // Helper function to validate if a fuzzy match is actually related + function isValidMatch(searchTerm, entry) { + if (!entry) + return false; + var search = searchTerm.toLowerCase(); + var id = (entry.id || "").toLowerCase(); + var name = (entry.name || "").toLowerCase(); + var icon = (entry.icon || "").toLowerCase(); + // Match is valid if search term appears in entry or entry appears in search + return id.includes(search) || name.includes(search) || icon.includes(search) || search.includes(id.split('.').pop()) || search.includes(name.replace(/\s+/g, '')); + } + + readonly property string appName: { + if (!modelData) + return "Unknown App"; + + var props = modelData.properties; + var desc = modelData.description || ""; + var name = modelData.name || ""; + + if (!props) { + if (desc) + return desc; + if (name) { + var nameParts = name.split(/[-_]/); + if (nameParts.length > 0 && nameParts[0]) + return nameParts[0].charAt(0).toUpperCase() + nameParts[0].slice(1); + return name; + } + return "Unknown App"; + } + + var binaryName = props["application.process.binary"] || ""; + + // Try binary name first (fixes Electron apps like vesktop) + if (binaryName) { + var binParts = binaryName.split("/"); + if (binParts.length > 0) { + var binName = binParts[binParts.length - 1].toLowerCase(); + var entry = ThemeIcons.findAppEntry(binName); + // Only use entry if it's actually related to binary name + if (entry && entry.name && isValidMatch(binName, entry)) + return entry.name; + } + } + + var computedAppName = props["application.name"] || ""; + var mediaName = props["media.name"] || ""; + var mediaTitle = props["media.title"] || ""; + var appId = props["application.id"] || ""; + + if (appId) { + var entry = ThemeIcons.findAppEntry(appId); + if (entry && entry.name && isValidMatch(appId, entry)) + return entry.name; + if (!computedAppName) { + var parts = appId.split("."); + if (parts.length > 0 && parts[0]) + computedAppName = parts[0].charAt(0).toUpperCase() + parts[0].slice(1); + } + } + + if (!computedAppName && binaryName) { + var binParts = binaryName.split("/"); + if (binParts.length > 0 && binParts[binParts.length - 1]) + computedAppName = binParts[binParts.length - 1].charAt(0).toUpperCase() + binParts[binParts.length - 1].slice(1); + } + + var result = computedAppName || mediaTitle || mediaName || binaryName || desc || name; + + if (!result || result === "" || result === "Unknown App") { + if (name) { + var nameParts = name.split(/[-_]/); + if (nameParts.length > 0 && nameParts[0]) + result = nameParts[0].charAt(0).toUpperCase() + nameParts[0].slice(1); + } + } + + return result || "Unknown App"; + } + + readonly property string appIcon: { + if (!modelData) + return ThemeIcons.iconFromName("application-x-executable", "application-x-executable"); + + var props = modelData.properties; + + if (!props) { + var name = modelData.name || ""; + if (name) { + var nameParts = name.split(/[-_]/); + if (nameParts.length > 0) { + var entry = ThemeIcons.findAppEntry(nameParts[0].toLowerCase()); + if (entry && entry.icon && isValidMatch(nameParts[0].toLowerCase(), entry)) + return ThemeIcons.iconFromName(entry.icon, "application-x-executable"); + } + } + return ThemeIcons.iconFromName("application-x-executable", "application-x-executable"); + } + + var binaryName = props["application.process.binary"] || ""; + if (binaryName) { + var binParts = binaryName.split("/"); + if (binParts.length > 0) { + var binName = binParts[binParts.length - 1].toLowerCase(); + var entry = ThemeIcons.findAppEntry(binName); + if (entry && entry.icon && isValidMatch(binName, entry)) + return ThemeIcons.iconFromName(entry.icon, ""); + } + } + + var iconName = props["application.icon-name"] || ""; + if (iconName && ThemeIcons.iconExists(iconName)) { + var iconPath = ThemeIcons.iconFromName(iconName, ""); + if (iconPath && iconPath !== "") + return iconPath; + } + + var appId = props["application.id"] || ""; + if (appId) { + var entry = ThemeIcons.findAppEntry(appId); + if (entry && entry.icon && isValidMatch(appId, entry)) + return ThemeIcons.iconFromName(entry.icon, ""); + } + + var appName = props["application.name"] || ""; + if (appName) { + var entry = ThemeIcons.findAppEntry(appName.toLowerCase()); + if (entry && entry.icon && isValidMatch(appName.toLowerCase(), entry)) + return ThemeIcons.iconFromName(entry.icon, ""); + } + + var name = modelData.name || ""; + if (name) { + var nameParts = name.split(/[-_]/); + if (nameParts.length > 0) { + var entry = ThemeIcons.findAppEntry(nameParts[0].toLowerCase()); + if (entry && entry.icon && isValidMatch(nameParts[0].toLowerCase(), entry)) + return ThemeIcons.iconFromName(entry.icon, ""); + } + } + + return ThemeIcons.iconFromName("application-x-executable", "application-x-executable"); + } + + RowLayout { + id: appRow + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + + // App Icon + IconImage { + id: appIconImage + Layout.preferredWidth: Style.baseWidgetSize + Layout.preferredHeight: Style.baseWidgetSize + source: appBox.appIcon + smooth: true + asynchronous: true + + // Fallback icon if image fails to load + NIcon { + anchors.fill: parent + icon: "apps" + pointSize: Style.fontSizeXL + color: Color.mPrimary + visible: appIconImage.status === Image.Error || appIconImage.status === Image.Null || appBox.appIcon === "" + } + } + + // App Name and Volume Slider + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NText { + text: appBox.appName || "Unknown App" + pointSize: Style.fontSizeM + color: Color.mOnSurface + elide: Text.ElideRight + Layout.fillWidth: true + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NValueSlider { + Layout.fillWidth: true + from: 0 + to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0 + value: (appBox.appVolume !== undefined) ? appBox.appVolume : 0.0 + stepSize: 0.01 + heightRatio: 0.5 + enabled: !!(appBox.nodeAudio && appBox.modelData && appBox.modelData.ready === true) + onMoved: function (value) { + if (appBox.nodeAudio && appBox.modelData && appBox.modelData.ready === true) { + appBox.nodeAudio.volume = value; + var key = AudioService.getAppKey(appBox.modelData); + if (key) + AudioService.setAppStreamVolume(key, value); + } + } + } + + NText { + text: Math.round((appBox.appVolume !== undefined ? appBox.appVolume : 0.0) * 100) + "%" + pointSize: Style.fontSizeM + family: Settings.data.ui.fontFixed + color: Color.mOnSurface + opacity: enabled ? 1.0 : 0.6 + Layout.alignment: Qt.AlignVCenter + Layout.preferredWidth: 45 * Style.uiScaleRatio + horizontalAlignment: Text.AlignRight + enabled: !!(appBox.nodeAudio && appBox.modelData && appBox.modelData.ready === true) + } + + // Mute Button + NIconButton { + icon: (appBox.appMuted === true) ? "volume-mute" : "volume-high" + tooltipText: (appBox.appMuted === true) ? I18n.tr("tooltips.unmute") : I18n.tr("tooltips.mute") + baseSize: Style.baseWidgetSize * 0.7 + enabled: !!(appBox.nodeAudio && appBox.modelData && appBox.modelData.ready === true) + onClicked: { + if (appBox.nodeAudio && appBox.modelData && appBox.modelData.ready === true) { + var newMuted = !appBox.appMuted; + appBox.nodeAudio.muted = newMuted; + var key = AudioService.getAppKey(appBox.modelData); + if (key) + AudioService.setAppStreamMuted(key, newMuted); + } + } + } + } + } + } + } + } + + // Empty state + NText { + visible: panelContent.appStreams.length === 0 + text: I18n.tr("panels.audio.panel-applications-empty") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + Layout.topMargin: Style.marginXL + } + } + } + + // Devices Tab + NScrollView { + id: devicesScrollView + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AsNeeded + contentWidth: availableWidth + reserveScrollbarSpace: false + gradientColor: Color.mSurface + + // AudioService Devices + ColumnLayout { + spacing: Style.marginM + width: devicesScrollView.availableWidth + + // ------------------------------- + // Output Devices + ButtonGroup { + id: sinks + } + + NBox { + Layout.fillWidth: true + Layout.preferredHeight: outputColumn.implicitHeight + Style.margin2M + + ColumnLayout { + id: outputColumn + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Style.marginM + spacing: Style.marginS + + NText { + text: I18n.tr("panels.audio.devices-output-device-label") + pointSize: Style.fontSizeL + color: Color.mPrimary + } + + Repeater { + model: AudioService.sinks + NRadioButton { + ButtonGroup.group: sinks + required property PwNode modelData + pointSize: Style.fontSizeS + text: modelData.description + checked: AudioService.sink?.id === modelData.id + onClicked: { + AudioService.setAudioSink(modelData); + localOutputVolume = AudioService.volume; + } + Layout.fillWidth: true + } + } + } + } + + // ------------------------------- + // Input Devices + ButtonGroup { + id: sources + } + + NBox { + Layout.fillWidth: true + Layout.preferredHeight: inputColumn.implicitHeight + Style.margin2M + + ColumnLayout { + id: inputColumn + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.margins: Style.marginM + spacing: Style.marginS + + NText { + text: I18n.tr("panels.audio.devices-input-device-label") + pointSize: Style.fontSizeL + color: Color.mPrimary + } + + Repeater { + model: AudioService.sources + NRadioButton { + ButtonGroup.group: sources + required property PwNode modelData + pointSize: Style.fontSizeS + text: modelData.description + checked: AudioService.source?.id === modelData.id + onClicked: AudioService.setAudioSource(modelData) + Layout.fillWidth: true + } + } + } + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Battery/BatteryPanel.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Battery/BatteryPanel.qml new file mode 100644 index 0000000..5bb03a5 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Battery/BatteryPanel.qml @@ -0,0 +1,401 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.UPower +import qs.Commons +import qs.Modules.MainScreen +import qs.Services.Hardware +import qs.Services.Networking +import qs.Services.Power +import qs.Services.UI +import qs.Widgets + +SmartPanel { + id: root + + preferredWidth: Math.round(440 * Style.uiScaleRatio) + preferredHeight: Math.round(460 * Style.uiScaleRatio) + + panelContent: Item { + id: panelContent + + property real contentPreferredHeight: mainLayout.implicitHeight + Style.margin2L + + property var batteryWidgetInstance: BarService.lookupWidget("Battery", screen ? screen.name : null) + readonly property var batteryWidgetSettings: batteryWidgetInstance ? batteryWidgetInstance.widgetSettings : null + readonly property var batteryWidgetMetadata: BarWidgetRegistry.widgetMetadata["Battery"] + readonly property bool powerProfileAvailable: PowerProfileService.available + readonly property var powerProfiles: [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance] + readonly property bool profilesAvailable: PowerProfileService.available + property int profileIndex: profileToIndex(PowerProfileService.profile) + readonly property bool showPowerProfiles: panelID ? panelID.showPowerProfiles : resolveWidgetSetting("showPowerProfiles", false) + readonly property bool showNoctaliaPerformance: panelID ? panelID.showNoctaliaPerformance : resolveWidgetSetting("showNoctaliaPerformance", false) + readonly property bool isLowBattery: BatteryService.isLowBattery + readonly property bool isCriticalBattery: BatteryService.isCriticalBattery + readonly property var primaryDevice: BatteryService.primaryDevice + + function profileToIndex(p) { + return powerProfiles.indexOf(p) ?? 1; + } + + function indexToProfile(idx) { + return powerProfiles[idx] ?? PowerProfile.Balanced; + } + + function setProfileByIndex(idx) { + var prof = indexToProfile(idx); + profileIndex = idx; + PowerProfileService.setProfile(prof); + } + + function resolveWidgetSetting(key, defaultValue) { + if (batteryWidgetSettings && batteryWidgetSettings[key] !== undefined) + return batteryWidgetSettings[key]; + if (batteryWidgetMetadata && batteryWidgetMetadata[key] !== undefined) + return batteryWidgetMetadata[key]; + return defaultValue; + } + + Connections { + target: PowerProfileService + function onProfileChanged() { + panelContent.profileIndex = panelContent.profileToIndex(PowerProfileService.profile); + } + } + + Connections { + target: BarService + function onActiveWidgetsChanged() { + panelContent.batteryWidgetInstance = BarService.lookupWidget("Battery", screen ? screen.name : null); + } + } + + ColumnLayout { + id: mainLayout + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + // HEADER + NBox { + Layout.fillWidth: true + implicitHeight: headerRow.implicitHeight + Style.margin2M + + RowLayout { + id: headerRow + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + + NIcon { + pointSize: Style.fontSizeXXL + color: (BatteryService.isCharging(primaryDevice) || BatteryService.isPluggedIn(primaryDevice)) ? Color.mPrimary : (BatteryService.isCriticalBattery(primaryDevice) || BatteryService.isLowBattery(primaryDevice)) ? Color.mError : Color.mOnSurface + icon: BatteryService.getIcon(BatteryService.getPercentage(primaryDevice), BatteryService.isCharging(primaryDevice), BatteryService.isPluggedIn(primaryDevice), BatteryService.isDeviceReady(primaryDevice)) + } + + ColumnLayout { + spacing: Style.marginXXS + Layout.fillWidth: true + + NText { + text: I18n.tr("common.battery") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + elide: Text.ElideRight + } + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: root.close() + } + } + } + + // Charge level + health/time + NBox { + Layout.fillWidth: true + implicitHeight: chargeLayout.implicitHeight + Style.margin2L + visible: BatteryService.laptopBatteries.length > 0 || BatteryService.bluetoothBatteries.length > 0 + + ColumnLayout { + id: chargeLayout + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginL + + // Laptop batteries section + Repeater { + model: BatteryService.laptopBatteries + delegate: ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS + + RowLayout { + Item { + id: batteryInfoItem + implicitWidth: batteryInfoRow.implicitWidth + implicitHeight: batteryInfoRow.implicitHeight + + RowLayout { + id: batteryInfoRow + anchors.fill: parent + + NIcon { + icon: BatteryService.getIcon(BatteryService.getPercentage(modelData), BatteryService.isCharging(modelData), BatteryService.isPluggedIn(modelData), BatteryService.isDeviceReady(modelData)) + color: (BatteryService.isCharging(modelData) || BatteryService.isPluggedIn(modelData)) ? Color.mPrimary : (BatteryService.isCriticalBattery(modelData) || BatteryService.isLowBattery(modelData)) ? Color.mError : Color.mOnSurface + } + + NText { + readonly property string dName: BatteryService.getDeviceName(modelData) + text: dName ? dName : I18n.tr("common.battery") + color: (BatteryService.isCharging(modelData) || BatteryService.isPluggedIn(modelData)) ? Color.mPrimary : (BatteryService.isCriticalBattery(modelData) || BatteryService.isLowBattery(modelData)) ? Color.mError : Color.mOnSurface + pointSize: Style.fontSizeS + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: { + if (modelData.healthSupported) { + TooltipService.show(batteryInfoItem, `${I18n.tr("battery.battery-health")}: ${Math.round(modelData.healthPercentage)}%`); + } + } + onExited: TooltipService.hide(batteryInfoItem) + } + } + + Item { + Layout.fillWidth: true + } + + NText { + text: BatteryService.getTimeRemainingText(modelData) + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + Rectangle { + Layout.fillWidth: true + height: Math.round(8 * Style.uiScaleRatio) + radius: Math.min(Style.radiusL, height / 2) + color: Color.mSurface + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + height: parent.height + radius: parent.radius + width: { + var p = BatteryService.getPercentage(modelData); + var ratio = Math.max(0, Math.min(1, p / 100)); + return parent.width * ratio; + } + color: Color.mPrimary + } + } + + NText { + Layout.preferredWidth: 40 * Style.uiScaleRatio + horizontalAlignment: Text.AlignRight + text: `${BatteryService.getPercentage(modelData)}%` + color: (BatteryService.isCharging(modelData) || BatteryService.isPluggedIn(modelData)) ? Color.mPrimary : (BatteryService.isCriticalBattery(modelData) || BatteryService.isLowBattery(modelData)) ? Color.mError : Color.mOnSurface + pointSize: Style.fontSizeS + font.weight: Style.fontWeightBold + } + } + } + } + } + } + + NDivider { + Layout.fillWidth: true + visible: BatteryService.laptopBatteries.length > 0 && BatteryService.bluetoothBatteries.length > 0 + } + + // Other devices (Bluetooth) section + Repeater { + model: BatteryService.bluetoothBatteries + delegate: ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NIcon { + icon: BluetoothService.getDeviceIcon(modelData) + color: (BatteryService.isCharging(modelData) || BatteryService.isPluggedIn(modelData)) ? Color.mPrimary : (BatteryService.isCriticalBattery(modelData) || BatteryService.isLowBattery(modelData)) ? Color.mError : Color.mOnSurface + } + + NText { + readonly property string dName: BatteryService.getDeviceName(modelData) + text: dName ? dName : I18n.tr("common.bluetooth") + color: (BatteryService.isCharging(modelData) || BatteryService.isPluggedIn(modelData)) ? Color.mPrimary : (BatteryService.isCriticalBattery(modelData) || BatteryService.isLowBattery(modelData)) ? Color.mError : Color.mOnSurface + pointSize: Style.fontSizeS + } + } + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + Rectangle { + Layout.fillWidth: true + height: Math.round(8 * Style.uiScaleRatio) + radius: Math.min(Style.radiusL, height / 2) + color: Color.mSurface + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + height: parent.height + radius: parent.radius + width: { + var p = BatteryService.getPercentage(modelData); + var ratio = Math.max(0, Math.min(1, p / 100)); + return parent.width * ratio; + } + color: Color.mPrimary + } + } + + NText { + Layout.preferredWidth: 40 * Style.uiScaleRatio + horizontalAlignment: Text.AlignRight + text: `${BatteryService.getPercentage(modelData)}%` + color: (BatteryService.isCharging(modelData) || BatteryService.isPluggedIn(modelData)) ? Color.mPrimary : (BatteryService.isCriticalBattery(modelData) || BatteryService.isLowBattery(modelData)) ? Color.mError : Color.mOnSurface + pointSize: Style.fontSizeS + font.weight: Style.fontWeightBold + } + } + } + } + } + } + + NBox { + Layout.fillWidth: true + height: controlsLayout.implicitHeight + Style.margin2L + visible: showPowerProfiles || showNoctaliaPerformance + + ColumnLayout { + id: controlsLayout + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + ColumnLayout { + visible: powerProfileAvailable && showPowerProfiles + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NText { + text: I18n.tr("battery.power-profile") + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + } + + NText { + text: PowerProfileService.getName(profileIndex) + color: Color.mOnSurfaceVariant + } + } + + NValueSlider { + Layout.fillWidth: true + from: 0 + to: 2 + stepSize: 1 + snapAlways: true + heightRatio: 0.5 + value: profileIndex + enabled: profilesAvailable + onPressedChanged: (pressed, v) => { + if (!pressed) { + setProfileByIndex(v); + } + } + onMoved: v => { + profileIndex = v; + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NIcon { + icon: "powersaver" + pointSize: Style.fontSizeS + color: PowerProfileService.getIcon() === "powersaver" ? Color.mPrimary : Color.mOnSurfaceVariant + } + + NIcon { + icon: "balanced" + pointSize: Style.fontSizeS + color: PowerProfileService.getIcon() === "balanced" ? Color.mPrimary : Color.mOnSurfaceVariant + Layout.fillWidth: true + } + + NIcon { + icon: "performance" + pointSize: Style.fontSizeS + color: PowerProfileService.getIcon() === "performance" ? Color.mPrimary : Color.mOnSurfaceVariant + } + } + } + + NDivider { + Layout.fillWidth: true + visible: showPowerProfiles && PowerProfileService.available && showNoctaliaPerformance + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + visible: showNoctaliaPerformance + + NText { + text: I18n.tr("toast.noctalia-performance.label") + pointSize: Style.fontSizeM + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + } + + NIcon { + icon: PowerProfileService.noctaliaPerformanceMode ? "rocket" : "rocket-off" + pointSize: Style.fontSizeL + color: PowerProfileService.noctaliaPerformanceMode ? Color.mPrimary : Color.mOnSurfaceVariant + } + + NToggle { + checked: PowerProfileService.noctaliaPerformanceMode + onToggled: checked => PowerProfileService.noctaliaPerformanceMode = checked + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Bluetooth/BluetoothPanel.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Bluetooth/BluetoothPanel.qml new file mode 100644 index 0000000..90a59ee --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Bluetooth/BluetoothPanel.qml @@ -0,0 +1,208 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Bluetooth +import "../Settings/Tabs/Connections" as BluetoothPrefs +import qs.Commons +import qs.Modules.MainScreen +import qs.Modules.Panels.Settings +import qs.Services.Hardware +import qs.Services.Networking +import qs.Services.UI +import qs.Widgets + +SmartPanel { + id: root + + preferredWidth: Math.round(440 * Style.uiScaleRatio) + preferredHeight: Math.round(500 * Style.uiScaleRatio) + + panelContent: Rectangle { + id: panelContent + color: "transparent" + + property real contentPreferredHeight: Math.min(root.preferredHeight, mainColumn.implicitHeight + Style.margin2L) + + ColumnLayout { + id: mainColumn + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + // Header + NBox { + Layout.fillWidth: true + Layout.preferredHeight: headerRow.implicitHeight + Style.margin2M + + RowLayout { + id: headerRow + anchors.fill: parent + anchors.margins: Style.marginM + + NIcon { + icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off" + pointSize: Style.fontSizeXXL + color: BluetoothService.enabled ? Color.mPrimary : Color.mOnSurfaceVariant + } + + NLabel { + label: I18n.tr("common.bluetooth") + Layout.fillWidth: true + } + + NToggle { + id: bluetoothSwitch + checked: BluetoothService.enabled + enabled: !NetworkService.airplaneModeEnabled && BluetoothService.bluetoothAvailable + onToggled: checked => BluetoothService.setBluetoothEnabled(checked) + baseSize: Style.baseWidgetSize * 0.65 + } + + NIconButton { + icon: Settings.data.network.bluetoothAutoConnect ? "bluetooth-connected" : "bluetooth" + tooltipText: Settings.data.network.bluetoothAutoConnect ? I18n.tr("tooltips.bluetooth-auto-connect-on") : I18n.tr("tooltips.bluetooth-auto-connect-off") + colorFg: Settings.data.network.bluetoothAutoConnect ? Color.mPrimary : Color.mOnSurfaceVariant + baseSize: Style.baseWidgetSize * 0.8 + onClicked: Settings.data.network.bluetoothAutoConnect = !Settings.data.network.bluetoothAutoConnect + } + + NIconButton { + icon: "settings" + tooltipText: I18n.tr("tooltips.open-settings") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: SettingsPanelService.openToTab(SettingsPanel.Tab.Connections, 1, screen) + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: { + root.close(); + } + } + } + } + + NScrollView { + id: bluetoothScrollView + Layout.fillWidth: true + Layout.fillHeight: true + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AsNeeded + reserveScrollbarSpace: false + gradientColor: Color.mSurface + + ColumnLayout { + id: devicesList + width: bluetoothScrollView.availableWidth + spacing: Style.marginM + + // Adapter not available of disabled + NBox { + id: disabledBox + visible: !BluetoothService.enabled + Layout.fillWidth: true + Layout.preferredHeight: disabledColumn.implicitHeight + Style.margin2M + + ColumnLayout { + id: disabledColumn + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginL + + Item { + Layout.fillHeight: true + } + + NIcon { + icon: "bluetooth-off" + pointSize: 48 + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: I18n.tr("bluetooth.panel.disabled") + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: I18n.tr("bluetooth.panel.enable-message") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + wrapMode: Text.WordWrap + } + + Item { + Layout.fillHeight: true + } + } + } + + // Empty state when no paired devices + NBox { + id: emptyBox + visible: { + if (!BluetoothService.enabled || !BluetoothService.devices) + return false; + // Pulling pairedDevices count from the source component + return (btSource.pairedDevices.length === 0 && btSource.connectedDevices.length === 0); + } + Layout.fillWidth: true + Layout.preferredHeight: emptyColumn.implicitHeight + Style.margin2M + + ColumnLayout { + id: emptyColumn + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginL + + Item { + Layout.fillHeight: true + } + + NIcon { + icon: "bluetooth" + pointSize: 48 + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: I18n.tr("bluetooth.panel.no-devices") + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + } + + NButton { + text: I18n.tr("common.settings") + icon: "settings" + Layout.alignment: Qt.AlignHCenter + onClicked: SettingsPanelService.openToTab(SettingsPanel.Tab.Connections, 1, screen) + } + + Item { + Layout.fillHeight: true + } + } + } + + // Pull connected/paired lists from BluetoothSubTab + BluetoothPrefs.BluetoothSubTab { + id: btSource + Layout.fillWidth: true + showOnlyLists: true + visible: !disabledBox.visible && !emptyBox.visible + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Brightness/BrightnessPanel.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Brightness/BrightnessPanel.qml new file mode 100644 index 0000000..4093345 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Brightness/BrightnessPanel.qml @@ -0,0 +1,285 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Modules.MainScreen +import qs.Services.Compositor +import qs.Services.Hardware +import qs.Services.UI +import qs.Widgets + +SmartPanel { + id: root + + preferredWidth: Math.round(440 * Style.uiScaleRatio) + preferredHeight: Math.round(420 * Style.uiScaleRatio) + + panelContent: Item { + id: panelContent + property real contentPreferredHeight: mainColumn.implicitHeight + Style.margin2L + + property var brightnessWidgetInstance: BarService.lookupWidget("Brightness", screen ? screen.name : null) + readonly property var brightnessWidgetSettings: brightnessWidgetInstance ? brightnessWidgetInstance.widgetSettings : null + readonly property var brightnessWidgetMetadata: BarWidgetRegistry.widgetMetadata["Brightness"] + + function resolveWidgetSetting(key, defaultValue) { + if (brightnessWidgetSettings && brightnessWidgetSettings[key] !== undefined) + return brightnessWidgetSettings[key]; + if (brightnessWidgetMetadata && brightnessWidgetMetadata[key] !== undefined) + return brightnessWidgetMetadata[key]; + return defaultValue; + } + + Connections { + target: BarService + function onActiveWidgetsChanged() { + panelContent.brightnessWidgetInstance = BarService.lookupWidget("Brightness", screen ? screen.name : null); + } + } + + property real globalBrightness: 0 + property bool globalBrightnessChanging: false + property int globalBrightnessCapableMonitors: 0 + + function getIcon(brightness) { + return brightness <= 0.5 ? "brightness-low" : "brightness-high"; + } + + function getControllableMonitors() { + var monitors = BrightnessService.monitors || []; + return monitors.filter(m => m && m.brightnessControlAvailable); + } + + function updateGlobalBrightness() { + var monitors = getControllableMonitors(); + panelContent.globalBrightnessCapableMonitors = monitors.length; + + if (panelContent.globalBrightnessChanging) + return; + + if (monitors.length === 0) { + panelContent.globalBrightness = 0; + return; + } + + var total = 0; + monitors.forEach(m => { + var brightnessValue = isNaN(m.brightness) ? 0 : m.brightness; + total += brightnessValue; + }); + panelContent.globalBrightness = total / monitors.length; + } + + function applyGlobalBrightness(value) { + var monitors = BrightnessService.monitors || []; + monitors.forEach(m => { + if (m && m.brightnessControlAvailable) { + m.setBrightness(value); + } + }); + } + + Component.onCompleted: updateGlobalBrightness() + + Connections { + target: BrightnessService + function onMonitorBrightnessChanged(monitor, newBrightness) { + panelContent.updateGlobalBrightness(); + } + function onMonitorsChanged() { + panelContent.updateGlobalBrightness(); + } + function onDdcMonitorsChanged() { + panelContent.updateGlobalBrightness(); + } + } + + ColumnLayout { + id: mainColumn + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + // HEADER + NBox { + Layout.fillWidth: true + implicitHeight: headerRow.implicitHeight + Style.margin2M + + RowLayout { + id: headerRow + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + + NIcon { + icon: "settings-display" + pointSize: Style.fontSizeXXL + color: Color.mPrimary + } + + NText { + text: I18n.tr("panels.display.title") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: { + root.close(); + } + } + } + } + + NScrollView { + id: brightnessScrollView + Layout.fillWidth: true + Layout.fillHeight: true + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AsNeeded + contentWidth: availableWidth + reserveScrollbarSpace: false + gradientColor: Color.mSurface + + // AudioService Devices + ColumnLayout { + spacing: Style.marginM + width: brightnessScrollView.availableWidth + + NBox { + Layout.fillWidth: true + visible: panelContent.globalBrightnessCapableMonitors > 1 && panelContent.resolveWidgetSetting("applyToAllMonitors", false) + implicitHeight: globalBrightnessContent.implicitHeight + (Style.marginXL) + + ColumnLayout { + id: globalBrightnessContent + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Style.marginM + spacing: Style.marginS + + NLabel { + label: I18n.tr("panels.display.monitors-global-brightness-label") + description: I18n.tr("panels.display.monitors-global-brightness-description") + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NIcon { + icon: panelContent.getIcon(panelContent.globalBrightness) + pointSize: Style.fontSizeXL + color: Color.mOnSurface + } + + NValueSlider { + id: globalBrightnessSlider + from: 0 + to: 1 + value: panelContent.globalBrightness + stepSize: 0.01 + enabled: panelContent.globalBrightnessCapableMonitors > 0 + onMoved: value => { + panelContent.globalBrightness = value; + panelContent.applyGlobalBrightness(value); + } + onPressedChanged: (pressed, value) => { + panelContent.globalBrightnessChanging = pressed; + panelContent.globalBrightness = value; + panelContent.applyGlobalBrightness(value); + } + Layout.fillWidth: true + text: "" + } + + NText { + text: panelContent.globalBrightnessCapableMonitors > 0 ? Math.round(panelContent.globalBrightness * 100) + "%" : "N/A" + Layout.preferredWidth: 55 + horizontalAlignment: Text.AlignRight + Layout.alignment: Qt.AlignVCenter + } + } + } + } + + Repeater { + model: Quickshell.screens || [] + delegate: NBox { + Layout.fillWidth: true + Layout.preferredHeight: outputColumn.implicitHeight + Style.margin2M + + property var brightnessMonitor: BrightnessService.getMonitorForScreen(modelData) + readonly property real compositorScale: { + const info = CompositorService.displayScales[modelData.name]; + return (info && info.scale) ? info.scale : 1.0; + } + + ColumnLayout { + id: outputColumn + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Style.marginM + spacing: Style.marginS + + NLabel { + label: modelData.name || "Unknown" + labelColor: Color.mPrimary + description: { + I18n.tr("system.monitor-description", { + "model": modelData.model, + "width": modelData.width * compositorScale, + "height": modelData.height * compositorScale, + "scale": compositorScale + }); + } + } + + RowLayout { + + Layout.fillWidth: true + spacing: Style.marginS + NIcon { + icon: getIcon(brightnessMonitor ? brightnessMonitor.brightness : 0) + pointSize: Style.fontSizeXL + color: Color.mOnSurface + } + + NValueSlider { + id: brightnessSlider + from: 0 + to: 1 + value: brightnessMonitor ? brightnessMonitor.brightness : 0.5 + stepSize: 0.01 + enabled: brightnessMonitor ? brightnessMonitor.brightnessControlAvailable : false + onMoved: value => { + if (brightnessMonitor && brightnessMonitor.brightnessControlAvailable) { + brightnessMonitor.setBrightness(value); + } + } + onPressedChanged: (pressed, value) => { + if (brightnessMonitor && brightnessMonitor.brightnessControlAvailable) { + brightnessMonitor.setBrightness(value); + } + } + Layout.fillWidth: true + text: brightnessMonitor ? Math.round(brightnessSlider.value * 100) + "%" : "N/A" + } + } + } + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Changelog/ChangelogPanel.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Changelog/ChangelogPanel.qml new file mode 100644 index 0000000..48337d1 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Changelog/ChangelogPanel.qml @@ -0,0 +1,202 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.MainScreen +import qs.Services.Noctalia +import qs.Services.UI +import qs.Widgets + +SmartPanel { + id: root + + preferredWidth: Math.round(820 * Style.uiScaleRatio) + preferredHeight: Math.round(620 * Style.uiScaleRatio) + panelAnchorHorizontalCenter: true + panelAnchorVerticalCenter: true + + panelContent: Rectangle { + id: panelContent + color: Color.mSurfaceVariant + radius: Style.radiusM + border.color: Color.mOutline + border.width: Style.borderS + + readonly property string currentVersion: UpdateService.currentVersion + readonly property string previousVersion: UpdateService.previousVersion + readonly property bool hasPreviousVersion: previousVersion && previousVersion.length > 0 + readonly property string releaseContent: UpdateService.releaseContent || "" + + function colorizeHeaders(text) { + if (!text) + return ""; + const color = Color.mPrimary; + return text.replace(/^# (.*)$/gm, `

$1


`).replace(/^## (.*)$/gm, `

$1


`); + } + readonly property string subtitleText: hasPreviousVersion ? I18n.tr("changelog.panel.subtitle-updated", { + "previousVersion": previousVersion + }) : I18n.tr("changelog.panel.subtitle-fresh") + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NIcon { + icon: "sparkles" + color: Color.mPrimary + pointSize: Style.fontSizeXXL + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NText { + text: I18n.tr("changelog.panel.title", { + "version": currentVersion || UpdateService.currentVersion + }) + pointSize: Style.fontSizeXL + font.weight: Style.fontWeightBold + color: Color.mPrimary + wrapMode: Text.WordWrap + } + + NText { + text: subtitleText + color: Color.mOnSurface + opacity: Style.opacityMedium + wrapMode: Text.WordWrap + } + } + + Item { + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + onClicked: root.close() + Layout.alignment: Qt.AlignTop | Qt.AlignRight + Layout.preferredHeight: Style.baseWidgetSize + Layout.preferredWidth: Style.baseWidgetSize + } + } + + Rectangle { + clip: true + Layout.fillWidth: true + color: Qt.alpha(Color.mPrimary, 0.08) + radius: Style.radiusS + border.color: Color.mPrimary + border.width: Style.borderS + + RowLayout { + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginS + + NText { + text: hasPreviousVersion ? previousVersion : I18n.tr("changelog.panel.version-new-user") + font.weight: Style.fontWeightSemiBold + color: Color.mPrimary + } + + NIcon { + icon: "arrow-right" + color: Color.mPrimary + } + + NText { + text: currentVersion || UpdateService.currentVersion + font.weight: Style.fontWeightSemiBold + color: Color.mPrimary + } + } + } + + NDivider { + Layout.fillWidth: true + } + + NScrollView { + id: changelogScrollView + Layout.fillWidth: true + Layout.fillHeight: true + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AsNeeded + padding: 0 + + ColumnLayout { + width: changelogScrollView.availableWidth + spacing: Style.marginM + + NText { + visible: UpdateService.fetchError !== "" + text: UpdateService.fetchError + color: Color.mError + wrapMode: Text.WordWrap + } + + NText { + Layout.fillWidth: true + visible: releaseContent.length > 0 + text: panelContent.colorizeHeaders(releaseContent) + wrapMode: Text.WordWrap + elide: Text.ElideNone + textFormat: Text.MarkdownText + color: Color.mOnSurface + } + + NText { + visible: releaseContent.length === 0 + text: I18n.tr("changelog.panel.empty") + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NButton { + Layout.fillWidth: true + icon: "brand-discord" + text: I18n.tr("changelog.panel.buttons-discord") + outlined: true + onClicked: UpdateService.openDiscord() + } + + NButton { + Layout.fillWidth: true + visible: UpdateService.feedbackUrl !== "" + icon: "forms" + text: I18n.tr("changelog.panel.buttons-feedback") + outlined: true + onClicked: UpdateService.openFeedbackForm() + } + + NButton { + Layout.fillWidth: true + icon: "check" + text: I18n.tr("changelog.panel.buttons-dismiss") + onClicked: root.close() + } + } + } + } + + onClosed: { + if (UpdateService && UpdateService.changelogCurrentVersion) { + UpdateService.markChangelogSeen(UpdateService.changelogCurrentVersion); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Clock/ClockPanel.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Clock/ClockPanel.qml new file mode 100644 index 0000000..5ab12db --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Clock/ClockPanel.qml @@ -0,0 +1,76 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Modules.Cards +import qs.Modules.MainScreen +import qs.Services.Location +import qs.Services.UI +import qs.Widgets + +SmartPanel { + id: root + + panelContent: Item { + id: panelContent + anchors.fill: parent + + readonly property real contentPreferredWidth: Math.round((Settings.data.location.showWeekNumberInCalendar ? 440 : 420) * Style.uiScaleRatio) + readonly property real contentPreferredHeight: content.implicitHeight + Style.margin2L + + ColumnLayout { + id: content + x: Style.marginL + y: Style.marginL + width: parent.width - Style.margin2L + spacing: Style.marginL + + // All clock panel cards + Repeater { + model: Settings.data.calendar.cards + Loader { + active: modelData.enabled && (modelData.id !== "weather-card" || Settings.data.location.weatherEnabled) + visible: active + Layout.fillWidth: true + sourceComponent: { + switch (modelData.id) { + case "calendar-header-card": + return calendarHeaderCard; + case "calendar-month-card": + return calendarMonthCard; + case "weather-card": + return weatherCard; + default: + return null; + } + } + } + } + } + } + + Component { + id: calendarHeaderCard + CalendarHeaderCard { + Layout.fillWidth: true + } + } + + Component { + id: calendarMonthCard + CalendarMonthCard { + Layout.fillWidth: true + } + } + + Component { + id: weatherCard + WeatherCard { + Layout.fillWidth: true + forecastDays: 5 + showLocation: false + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/ControlCenterPanel.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/ControlCenterPanel.qml new file mode 100644 index 0000000..e652e67 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/ControlCenterPanel.qml @@ -0,0 +1,191 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.Cards +import qs.Modules.MainScreen +import qs.Services.Media +import qs.Services.UI +import qs.Widgets + +SmartPanel { + id: root + + // Positioning + readonly property string controlCenterPosition: Settings.data.controlCenter.position + + // Check if there's a bar on this screen + readonly property bool hasBarOnScreen: { + var monitors = Settings.data.bar.monitors || []; + return monitors.length === 0 || monitors.includes(screen?.name); + } + + // When position is "close_to_bar_button" but there's no bar, fall back to center + readonly property bool shouldCenter: controlCenterPosition === "close_to_bar_button" && !hasBarOnScreen + + panelAnchorHorizontalCenter: shouldCenter || (controlCenterPosition !== "close_to_bar_button" && (controlCenterPosition.endsWith("_center") || controlCenterPosition === "center")) + panelAnchorVerticalCenter: shouldCenter || controlCenterPosition === "center" + panelAnchorLeft: !shouldCenter && controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.endsWith("_left") + panelAnchorRight: !shouldCenter && controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.endsWith("_right") + panelAnchorBottom: !shouldCenter && controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.startsWith("bottom_") + panelAnchorTop: !shouldCenter && controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.startsWith("top_") + + preferredWidth: Math.round(440 * Style.uiScaleRatio) + preferredHeight: { + var height = 0; + var count = 0; + for (var i = 0; i < Settings.data.controlCenter.cards.length; i++) { + const card = Settings.data.controlCenter.cards[i]; + if (!card.enabled) + continue; + const contributes = (card.id !== "weather-card" || Settings.data.location.weatherEnabled); + if (!contributes) + continue; + count++; + switch (card.id) { + case "profile-card": + height += profileHeight; + break; + case "shortcuts-card": + height += shortcutsHeight; + break; + case "audio-card": + height += audioHeight; + break; + case "brightness-card": + height += brightnessHeight; + break; + case "weather-card": + height += weatherHeight; + break; + case "media-sysmon-card": + height += mediaSysMonHeight; + break; + default: + break; + } + } + return height + (count + 1) * Style.marginL; + } + + readonly property int profileHeight: Math.round(64 * Style.uiScaleRatio) + readonly property int shortcutsHeight: Math.round(52 * Style.uiScaleRatio) + readonly property int audioHeight: Math.round(60 * Style.uiScaleRatio) + readonly property int brightnessHeight: Math.round(60 * Style.uiScaleRatio) + readonly property int mediaSysMonHeight: Math.round(260 * Style.uiScaleRatio) + + // We keep a dynamic weather height due to a more complex layout and font scaling + property int weatherHeight: Math.round(210 * Style.uiScaleRatio) + + onOpened: { + MediaService.autoSwitchingPaused = true; + } + + onClosed: { + MediaService.autoSwitchingPaused = false; + } + + panelContent: Item { + id: panelContent + + ColumnLayout { + id: layout + x: Style.marginL + y: Style.marginL + width: parent.width - Style.margin2L + spacing: Style.marginL + + Repeater { + model: Settings.data.controlCenter.cards + Loader { + active: modelData.enabled && (modelData.id !== "weather-card" || Settings.data.location.weatherEnabled) + visible: active + Layout.fillWidth: true + Layout.preferredHeight: { + switch (modelData.id) { + case "profile-card": + return profileHeight; + case "shortcuts-card": + return shortcutsHeight; + case "audio-card": + return audioHeight; + case "brightness-card": + return brightnessHeight; + case "weather-card": + return weatherHeight; + case "media-sysmon-card": + return mediaSysMonHeight; + default: + return 0; + } + } + sourceComponent: { + switch (modelData.id) { + case "profile-card": + return profileCard; + case "shortcuts-card": + return shortcutsCard; + case "audio-card": + return audioCard; + case "brightness-card": + return brightnessCard; + case "weather-card": + return weatherCard; + case "media-sysmon-card": + return mediaSysMonCard; + } + } + } + } + } + + Component { + id: profileCard + ProfileCard {} + } + + Component { + id: shortcutsCard + ShortcutsCard {} + } + + Component { + id: audioCard + AudioCard {} + } + + Component { + id: brightnessCard + BrightnessCard {} + } + + Component { + id: weatherCard + WeatherCard { + Component.onCompleted: { + root.weatherHeight = this.height; + } + } + } + + Component { + id: mediaSysMonCard + RowLayout { + spacing: Style.marginL + + // Media card + MediaCard { + Layout.fillWidth: true + Layout.fillHeight: true + } + + // System monitors combined in one card + SystemMonitorCard { + Layout.preferredWidth: Math.round(Style.baseWidgetSize * 2.625) + Layout.fillHeight: true + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/ControlCenterWidgetLoader.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/ControlCenterWidgetLoader.qml new file mode 100644 index 0000000..28f77d8 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/ControlCenterWidgetLoader.qml @@ -0,0 +1,114 @@ +import QtQuick +import Quickshell +import qs.Commons +import qs.Services.Noctalia +import qs.Services.UI + +Item { + id: root + + required property string widgetId + required property var widgetScreen + required property var widgetProps + + property string section: widgetProps && widgetProps.section || "" + property int sectionIndex: widgetProps && widgetProps.sectionWidgetIndex || 0 + + // Don't reserve space unless the loaded widget is really visible + implicitWidth: getImplicitSize(loader.item, "implicitWidth") + implicitHeight: getImplicitSize(loader.item, "implicitHeight") + + function getImplicitSize(item, prop) { + return (item && item.visible) ? item[prop] : 0; + } + + readonly property bool _isPlugin: ControlCenterWidgetRegistry.isPluginWidget(widgetId) + + function _loadPluginWidget() { + var comp = ControlCenterWidgetRegistry.getWidget(widgetId); + if (!comp) + return; + var pluginId = widgetId.substring(7); // Remove "plugin:" prefix + var api = PluginService.getPluginAPI(pluginId); + loader.setSource(comp.url, api ? { + "pluginApi": api + } : {}); + } + + Loader { + id: loader + anchors.fill: parent + asynchronous: false + + // Core widgets use sourceComponent; plugin widgets use setSource() + // so pluginApi is available from the first binding evaluation. + Component.onCompleted: { + if (root._isPlugin) { + root._loadPluginWidget(); + } else { + sourceComponent = Qt.binding(function () { + return ControlCenterWidgetRegistry.getWidget(widgetId); + }); + } + } + + onLoaded: { + if (!item) + return; + + // Apply properties to loaded widget + for (var prop in widgetProps) { + if (item.hasOwnProperty(prop)) { + item[prop] = widgetProps[prop]; + } + } + + // Set screen property + if (item.hasOwnProperty("screen")) { + item.screen = widgetScreen; + } + + // Call custom onLoaded if it exists + if (item.hasOwnProperty("onLoaded")) { + item.onLoaded(); + } + } + + Component.onDestruction: { + // Explicitly clear references + widgetProps = null; + } + } + + // Error handling + Component.onCompleted: { + if (!ControlCenterWidgetRegistry.hasWidget(widgetId)) { + Logger.w("ControlCenterWidgetLoader", "Widget not found in registry:", widgetId); + // Retry briefly in case the registry initializes after this component + retryTimer.start(); + } + } + + // Retry mechanism to cope with early evaluation before registry is ready + Timer { + id: retryTimer + interval: 150 + repeat: true + running: false + property int attempts: 0 + onTriggered: { + attempts += 1; + if (ControlCenterWidgetRegistry.hasWidget(widgetId)) { + loader.sourceComponent = ControlCenterWidgetRegistry.getWidget(widgetId); + stop(); + attempts = 0; + return; + } + if (attempts >= 20) { // ~3s max + stop(); + attempts = 0; + Logger.w("ControlCenterWidgetLoader", "Giving up waiting for widget:", widgetId); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/AirplaneMode.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/AirplaneMode.qml new file mode 100644 index 0000000..d22963b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/AirplaneMode.qml @@ -0,0 +1,18 @@ +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Networking +import qs.Services.UI +import qs.Widgets + +NIconButtonHot { + property ShellScreen screen + + icon: !NetworkService.airplaneModeEnabled ? "plane-off" : "plane" + hot: NetworkService.airplaneModeEnabled + tooltipText: I18n.tr("toast.airplane-mode.title") + onClicked: { + NetworkService.setAirplaneMode(!NetworkService.airplaneModeEnabled); + } + enabled: NetworkService.wifiAvailable && BluetoothService.bluetoothAvailable +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/Bluetooth.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/Bluetooth.qml new file mode 100644 index 0000000..b6ede84 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/Bluetooth.qml @@ -0,0 +1,23 @@ +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Networking +import qs.Services.UI +import qs.Widgets + +NIconButtonHot { + property ShellScreen screen + + icon: !BluetoothService.enabled ? "bluetooth-off" : ((BluetoothService.connectedDevices && BluetoothService.connectedDevices.length > 0) ? "bluetooth-connected" : "bluetooth") + tooltipText: I18n.tr("common.bluetooth") + onClicked: { + var p = PanelService.getPanel("bluetoothPanel", screen); + if (p) + p.toggle(this); + } + onRightClicked: { + if (!NetworkService.airplaneModeEnabled) { + BluetoothService.setBluetoothEnabled(!BluetoothService.enabled); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/CustomButton.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/CustomButton.qml new file mode 100644 index 0000000..f719ae4 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/CustomButton.qml @@ -0,0 +1,178 @@ +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Widgets + +Item { + id: root + + property string widgetId: "CustomButton" + property var widgetSettings: null + + property string onClickedCommand: "" + property string onRightClickedCommand: "" + property string onMiddleClickedCommand: "" + property string stateChecksJson: "[]" // Store state checks as JSON string + + property string generalTooltipText: "" + property bool enableOnStateLogic: false + property bool showExecTooltip: true + property bool _hasCustomTooltip: false + + property string _currentIcon: "heart" // Default icon + property bool _isHot: false + property var _parsedStateChecks: [] // Local array for parsed state checks + + property int _currentStateCheckIndex: -1 + property string _activeStateIcon: "" + + implicitWidth: button.implicitWidth + implicitHeight: button.implicitHeight + + Connections { + target: root + + function _updatePropertiesFromSettings() { + if (!widgetSettings) { + return; + } + + onClickedCommand = widgetSettings.onClicked || ""; + onRightClickedCommand = widgetSettings.onRightClicked || ""; + onMiddleClickedCommand = widgetSettings.onMiddleClicked || ""; + stateChecksJson = widgetSettings.stateChecksJson || "[]"; // Populate from widgetSettings + try { + _parsedStateChecks = JSON.parse(stateChecksJson); + } catch (e) { + Logger.e("CustomButton", "Failed to parse stateChecksJson:", e.message); + _parsedStateChecks = []; + } + generalTooltipText = widgetSettings.generalTooltipText || ""; + _hasCustomTooltip = !!(widgetSettings.generalTooltipText && widgetSettings.generalTooltipText.trim() !== ""); + enableOnStateLogic = widgetSettings.enableOnStateLogic || false; + showExecTooltip = widgetSettings.showExecTooltip !== undefined ? widgetSettings.showExecTooltip : true; + + updateState(); + } + + function onWidgetSettingsChanged() { + if (widgetSettings) { + _updatePropertiesFromSettings(); + } + } + } + + Process { + id: stateCheckProcessExecutor + running: false + command: _currentStateCheckIndex !== -1 && _parsedStateChecks.length > _currentStateCheckIndex ? ["sh", "-lc", _parsedStateChecks[_currentStateCheckIndex].command] : [] + onExited: function (exitCode, stdout, stderr) { + var currentCheckItem = _parsedStateChecks[_currentStateCheckIndex]; + var currentCommand = currentCheckItem.command; + if (exitCode === 0) { + // Command succeeded, this is the active state + _isHot = true; + _activeStateIcon = currentCheckItem.icon || widgetSettings.icon || "heart"; + } else { + // Command failed, try next one + _currentStateCheckIndex++; + _checkNextState(); + } + } + } + + Timer { + id: stateUpdateTimer + interval: 200 + running: false + repeat: false + onTriggered: { + if (enableOnStateLogic && _parsedStateChecks.length > 0) { + _currentStateCheckIndex = 0; + _checkNextState(); + } else { + _isHot = false; + _activeStateIcon = widgetSettings.icon || "heart"; + } + } + } + + function _checkNextState() { + if (_currentStateCheckIndex < _parsedStateChecks.length) { + var currentCheckItem = _parsedStateChecks[_currentStateCheckIndex]; + if (currentCheckItem && currentCheckItem.command) { + stateCheckProcessExecutor.running = true; + } else { + _currentStateCheckIndex++; + _checkNextState(); + } + } else { + // All checks failed + _isHot = false; + _activeStateIcon = widgetSettings.icon || "heart"; + } + } + + function updateState() { + if (!enableOnStateLogic || _parsedStateChecks.length === 0) { + _isHot = false; + _activeStateIcon = widgetSettings.icon || "heart"; + return; + } + stateUpdateTimer.restart(); + } + + function _buildTooltipText() { + // Build tooltip based on settings + let tooltip = ""; + + // If user set custom text, always show it + // If no custom text and showExecTooltip is off, show i18n default + if (_hasCustomTooltip) { + tooltip = generalTooltipText; + } else if (!showExecTooltip) { + tooltip = I18n.tr("bar.custom-button.default-tooltip"); + } + + // Add command details if enabled + if (showExecTooltip) { + if (onClickedCommand) { + tooltip += (tooltip ? "\n" : "") + I18n.tr("bar.custom-button.left-click-label") + `: ${onClickedCommand}`; + } + if (onRightClickedCommand) { + tooltip += (tooltip ? "\n" : "") + I18n.tr("bar.custom-button.right-click-label") + `: ${onRightClickedCommand}`; + } + if (onMiddleClickedCommand) { + tooltip += (tooltip ? "\n" : "") + I18n.tr("bar.custom-button.middle-click-label") + `: ${onMiddleClickedCommand}`; + } + } + + return tooltip; + } + + NIconButtonHot { + id: button + icon: _activeStateIcon + hot: _isHot + tooltipText: _buildTooltipText() + onClicked: { + if (onClickedCommand) { + Quickshell.execDetached(["sh", "-lc", onClickedCommand]); + updateState(); + } + } + onRightClicked: { + if (onRightClickedCommand) { + Quickshell.execDetached(["sh", "-lc", onRightClickedCommand]); + updateState(); + } + } + onMiddleClicked: { + if (onMiddleClickedCommand) { + Quickshell.execDetached(["sh", "-lc", onMiddleClickedCommand]); + updateState(); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/DarkMode.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/DarkMode.qml new file mode 100644 index 0000000..8ae4f41 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/DarkMode.qml @@ -0,0 +1,13 @@ +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Power +import qs.Widgets + +NIconButtonHot { + property ShellScreen screen + + icon: "dark-mode" + tooltipText: Settings.data.colorSchemes.darkMode ? I18n.tr("tooltips.switch-to-light-mode") : I18n.tr("tooltips.switch-to-dark-mode") + onClicked: Settings.data.colorSchemes.darkMode = !Settings.data.colorSchemes.darkMode +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/KeepAwake.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/KeepAwake.qml new file mode 100644 index 0000000..9778dcd --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/KeepAwake.qml @@ -0,0 +1,14 @@ +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Power +import qs.Widgets + +NIconButtonHot { + property ShellScreen screen + + icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off" + hot: IdleInhibitorService.isInhibited + tooltipText: I18n.tr("tooltips.keep-awake") + onClicked: IdleInhibitorService.manualToggle() +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/Network.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/Network.qml new file mode 100644 index 0000000..9fe91ee --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/Network.qml @@ -0,0 +1,21 @@ +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Networking +import qs.Services.UI +import qs.Widgets + +NIconButtonHot { + property ShellScreen screen + icon: NetworkService.getIcon() + tooltipText: NetworkService.getStatusText(true) + onClicked: { + var panel = PanelService.getPanel("networkPanel", screen); + panel?.toggle(this); + } + onRightClicked: { + if (!NetworkService.airplaneModeEnabled) { + NetworkService.setWifiEnabled(!NetworkService.wifiEnabled); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/NightLight.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/NightLight.qml new file mode 100644 index 0000000..fc0dc53 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/NightLight.qml @@ -0,0 +1,34 @@ +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.Panels.Settings +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +NIconButtonHot { + property ShellScreen screen + + enabled: ProgramCheckerService.wlsunsetAvailable + icon: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? "nightlight-forced" : "nightlight-on") : "nightlight-off" + hot: Settings.data.nightLight.enabled + tooltipText: I18n.tr("common.night-light") + + onClicked: { + if (!Settings.data.nightLight.enabled) { + Settings.data.nightLight.enabled = true; + Settings.data.nightLight.forced = false; + } else if (Settings.data.nightLight.enabled && !Settings.data.nightLight.forced) { + Settings.data.nightLight.forced = true; + } else { + Settings.data.nightLight.enabled = false; + Settings.data.nightLight.forced = false; + } + } + + onRightClicked: { + var settingsPanel = PanelService.getPanel("settingsPanel", screen); + settingsPanel.requestedTab = SettingsPanel.Tab.Display; + settingsPanel.open(); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/NoctaliaPerformance.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/NoctaliaPerformance.qml new file mode 100644 index 0000000..f47dae2 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/NoctaliaPerformance.qml @@ -0,0 +1,14 @@ +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Power +import qs.Widgets + +NIconButtonHot { + property ShellScreen screen + + icon: PowerProfileService.noctaliaPerformanceMode ? "rocket" : "rocket-off" + tooltipText: I18n.tr("tooltips.noctalia-performance-enabled") + hot: PowerProfileService.noctaliaPerformanceMode + onClicked: PowerProfileService.toggleNoctaliaPerformance() +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/Notifications.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/Notifications.qml new file mode 100644 index 0000000..ecefebd --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/Notifications.qml @@ -0,0 +1,16 @@ +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +NIconButtonHot { + property ShellScreen screen + + icon: NotificationService.doNotDisturb ? "bell-off" : "bell" + hot: NotificationService.doNotDisturb + tooltipText: I18n.tr("common.notifications") + onClicked: PanelService.getPanel("notificationHistoryPanel", screen)?.toggle(this) + onRightClicked: NotificationService.doNotDisturb = !NotificationService.doNotDisturb +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/PowerProfile.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/PowerProfile.qml new file mode 100644 index 0000000..42184f0 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/PowerProfile.qml @@ -0,0 +1,19 @@ +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.UPower +import qs.Commons +import qs.Services.Power +import qs.Widgets + +// Performance +NIconButtonHot { + property ShellScreen screen + + readonly property bool hasPP: PowerProfileService.available + + enabled: hasPP + icon: PowerProfileService.getIcon() + hot: !PowerProfileService.isDefault() + tooltipText: I18n.tr("control-center.power-profile.tooltip-action") + onClicked: PowerProfileService.cycleProfile() +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/WallpaperSelector.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/WallpaperSelector.qml new file mode 100644 index 0000000..184d024 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/ControlCenter/Widgets/WallpaperSelector.qml @@ -0,0 +1,15 @@ +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.UI +import qs.Widgets + +NIconButtonHot { + property ShellScreen screen + + enabled: Settings.data.wallpaper.enabled + icon: "wallpaper-selector" + tooltipText: I18n.tr("wallpaper.panel.title") + onClicked: PanelService.getPanel("wallpaperPanel", screen)?.toggle() + onRightClicked: WallpaperService.setRandomWallpaper() +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Dock/StaticDockPanel.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Dock/StaticDockPanel.qml new file mode 100644 index 0000000..7e221d2 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Dock/StaticDockPanel.qml @@ -0,0 +1,615 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Modules.Dock +import qs.Modules.MainScreen + +SmartPanel { + id: root + panelBackgroundColor: Qt.alpha(Color.mSurface, Settings.data.dock.backgroundOpacity) + + readonly property string dockPosition: Settings.data.dock.position + readonly property bool isVertical: dockPosition === "left" || dockPosition === "right" + readonly property bool hasBar: modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0)) : false + readonly property bool barAtSameEdge: hasBar && Settings.getBarPositionForScreen(modelData?.name) === dockPosition + readonly property bool isFramed: Settings.data.bar.barType === "framed" && hasBar + property bool isDockHovered: false + property bool panelHovered: false + readonly property int iconSize: Math.round(12 + 24 * (Settings.data.dock.size ?? 1)) + readonly property int maxWidth: screen ? screen.width * 0.8 : 1000 + readonly property int maxHeight: screen ? screen.height * 0.8 : 1000 + readonly property bool autoHide: false + readonly property int hideDelay: 1000 + readonly property int showDelay: 100 + + // Shared state with dock content + property bool dockHovered: false + property bool anyAppHovered: false + property bool menuHovered: false + property bool hidden: false + property bool peekHovered: false + + // Track the currently open context menu + property var currentContextMenu: null + + // Combined model of running apps and pinned apps + property var dockApps: [] + property var groupCycleIndices: ({}) + + // Track the session order of apps (transient reordering) + property var sessionAppOrder: [] + + // Drag and Drop state for visual feedback + property int dragSourceIndex: -1 + property int dragTargetIndex: -1 + + // Revision counter to force icon re-evaluation + property int iconRevision: 0 + + property alias hideTimer: hideTimer + property alias showTimer: showTimer + + onMenuHoveredChanged: { + if (!menuHovered && !panelHovered) { + hoverCloseTimer.restart(); + } + } + + onAnyAppHoveredChanged: { + if (anyAppHovered) { + hoverCloseTimer.stop(); + } else if (!panelHovered && !menuHovered && !dockHovered && !isDockHovered) { + hoverCloseTimer.restart(); + } + } + + onClosed: { + hoverCloseTimer.stop(); + isDockHovered = false; + } + + onOpened: { + // Don't auto-start close timer here โ€” the peek zone's onExited in Dock.qml + // starts hideTimer which checks panel.isDockHovered, giving the user time + // to move into the panel without it closing prematurely. + } + + panelAnchorTop: dockPosition === "top" + panelAnchorBottom: dockPosition === "bottom" + panelAnchorLeft: dockPosition === "left" + panelAnchorRight: dockPosition === "right" + panelAnchorHorizontalCenter: !isVertical + panelAnchorVerticalCenter: isVertical + + forceAttachToBar: hasBar + exclusiveKeyboard: false + + // when dragging ended but the cursor is outside the dock area, restart the timer + onDragSourceIndexChanged: { + if (dragSourceIndex === -1) { + if (autoHide && !dockHovered && !anyAppHovered && !peekHovered && !menuHovered) { + hideTimer.restart(); + } + } + } + + // Function to close any open context menu + function closeAllContextMenus() { + if (currentContextMenu && currentContextMenu.visible) { + currentContextMenu.hide(); + } + } + + function getAppKey(appData) { + if (!appData) + return null; + + if (Settings.data.dock.groupApps) { + return appData.appId; + } + + // Use stable appId for pinned apps to maintain their slot regardless of running state + if (appData.type === "pinned" || appData.type === "pinned-running") { + return appData.appId; + } + + // prefer toplevel object identity for unpinned running apps to distinguish instances + if (appData.toplevel) + return appData.toplevel; + + // fallback to appId + return appData.appId; + } + + function sortDockApps(apps) { + if (!sessionAppOrder || sessionAppOrder.length === 0) { + return apps; + } + + const sorted = []; + const remaining = [...apps]; + + // Pick apps that are in the session order + for (let i = 0; i < sessionAppOrder.length; i++) { + const key = sessionAppOrder[i]; + + // Pick ALL matching apps (e.g. all instances of a pinned app) + while (true) { + const idx = remaining.findIndex(app => getAppKey(app) === key); + if (idx !== -1) { + sorted.push(remaining[idx]); + remaining.splice(idx, 1); + } else { + break; + } + } + } + + // Append any new/remaining apps + remaining.forEach(app => sorted.push(app)); + + return sorted; + } + + function reorderApps(fromIndex, toIndex) { + if (fromIndex === toIndex || fromIndex < 0 || toIndex < 0 || fromIndex >= dockApps.length || toIndex >= dockApps.length) + return; + + const list = [...dockApps]; + const item = list.splice(fromIndex, 1)[0]; + list.splice(toIndex, 0, item); + + dockApps = list; + sessionAppOrder = dockApps.map(getAppKey); + savePinnedOrder(); + } + + function savePinnedOrder() { + const currentPinned = Settings.data.dock.pinnedApps || []; + const newPinned = []; + const seen = new Set(); + + // Extract pinned apps in their current visual order + dockApps.forEach(app => { + if (app.appId && !seen.has(app.appId)) { + const isPinned = currentPinned.some(p => normalizeAppId(p) === normalizeAppId(app.appId)); + + if (isPinned) { + newPinned.push(app.appId); + seen.add(app.appId); + } + } + }); + + // Check if any pinned apps were missed (unlikely if dockApps is correct) + currentPinned.forEach(p => { + if (!seen.has(p)) { + newPinned.push(p); + seen.add(p); + } + }); + + if (JSON.stringify(currentPinned) !== JSON.stringify(newPinned)) { + Settings.data.dock.pinnedApps = newPinned; + } + } + + // Helper function to normalize app IDs for case-insensitive matching + function normalizeAppId(appId) { + if (!appId || typeof appId !== 'string') + return ""; + let id = appId.toLowerCase().trim(); + if (id.endsWith(".desktop")) + id = id.substring(0, id.length - 8); + return id; + } + + // Helper function to check if an app ID matches a pinned app (case-insensitive) + function isAppIdPinned(appId, pinnedApps) { + if (!appId || !pinnedApps || pinnedApps.length === 0) + return false; + const normalizedId = normalizeAppId(appId); + // Direct match + if (pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedId)) + return true; + // Resolve via desktop entry lookup (handles StartupWMClass != .desktop filename) + const resolved = resolveToDesktopEntryId(appId); + if (resolved !== appId) { + const normalizedResolved = normalizeAppId(resolved); + return pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedResolved); + } + return false; + } + + // Desktop entry ID resolution cache (cleared when DesktopEntries change) + property var _desktopEntryIdCache: ({}) + + // Resolve a toplevel appId to its canonical .desktop entry ID via heuristic lookup. + function resolveToDesktopEntryId(appId) { + if (!appId) + return appId; + if (_desktopEntryIdCache.hasOwnProperty(appId)) + return _desktopEntryIdCache[appId]; + try { + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) { + const entry = DesktopEntries.heuristicLookup(appId); + if (entry && entry.id) { + _desktopEntryIdCache[appId] = entry.id; + return entry.id; + } + } + } catch (e) {} + _desktopEntryIdCache[appId] = appId; + return appId; + } + + // Helper function to get app name from desktop entry + function getAppNameFromDesktopEntry(appId) { + if (!appId) + return appId; + + try { + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) { + const entry = DesktopEntries.heuristicLookup(appId); + if (entry && entry.name) { + return entry.name; + } + } + + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.byId) { + const entry = DesktopEntries.byId(appId); + if (entry && entry.name) { + return entry.name; + } + } + } catch (e) + // Fall through to return original appId + {} + + // Return original appId if we can't find a desktop entry + return appId; + } + + function getToplevelsForEntry(appData) { + if (!appData) + return []; + + if (appData.toplevels && appData.toplevels.length > 0) { + return appData.toplevels.filter(toplevel => toplevel && (!Settings.data.dock.onlySameOutput || !toplevel.screens || toplevel.screens.includes(screen))); + } + + if (!appData.toplevel) + return []; + + if (Settings.data.dock.onlySameOutput && appData.toplevel.screens && !appData.toplevel.screens.includes(screen)) + return []; + + return [appData.toplevel]; + } + + function getPrimaryToplevelForEntry(appData) { + const toplevels = getToplevelsForEntry(appData); + if (toplevels.length === 0) + return null; + + if (ToplevelManager && ToplevelManager.activeToplevel && toplevels.includes(ToplevelManager.activeToplevel)) + return ToplevelManager.activeToplevel; + + return toplevels[0]; + } + + // Build grouped render model without mutating the raw toplevel list. + function buildGroupedDockApps(apps) { + if (!Settings.data.dock.groupApps) { + return apps.map(app => { + const entry = Object.assign({}, app); + entry.toplevels = getToplevelsForEntry(app); + return entry; + }); + } + + const grouped = []; + const groupedById = new Map(); + + apps.forEach(app => { + const appId = app.appId; + const toplevels = getToplevelsForEntry(app); + const existing = groupedById.get(appId); + + if (existing) { + toplevels.forEach(toplevel => { + if (!existing.toplevels.includes(toplevel)) { + existing.toplevels.push(toplevel); + } + }); + if (app.type === "pinned" || app.type === "pinned-running") { + existing.isPinned = true; + } + } else { + const entry = { + "type": app.type, + "appId": appId, + "title": app.title, + "toplevels": toplevels.slice(), + "isPinned": app.type === "pinned" || app.type === "pinned-running" + }; + grouped.push(entry); + groupedById.set(appId, entry); + } + }); + + grouped.forEach(entry => { + entry.toplevel = getPrimaryToplevelForEntry(entry); + if (entry.toplevels.length > 0 && entry.isPinned) { + entry.type = "pinned-running"; + } else if (entry.toplevels.length > 0) { + entry.type = "running"; + } else { + entry.type = "pinned"; + } + if (entry.toplevel && entry.toplevel.title && entry.toplevel.title.trim() !== "") { + entry.title = entry.toplevel.title; + } + }); + + return grouped; + } + + // Function to update the combined dock apps model + function updateDockApps() { + const runningApps = ToplevelManager ? (ToplevelManager.toplevels.values || []) : []; + const pinnedApps = Settings.data.dock.pinnedApps || []; + const combined = []; + const processedToplevels = new Set(); + const processedPinnedAppIds = new Set(); + + // push an app onto combined with the given appType + function pushApp(appType, toplevel, appId, title) { + // Use canonical ID for pinned apps to ensure key stability + const canonicalId = isAppIdPinned(appId, pinnedApps) ? (pinnedApps.find(p => normalizeAppId(p) === normalizeAppId(appId)) || appId) : appId; + + // For running apps, track by toplevel object to allow multiple instances + if (toplevel) { + if (processedToplevels.has(toplevel)) { + return; + } + if (Settings.data.dock.onlySameOutput && toplevel.screens && !toplevel.screens.includes(screen)) { + return; + } + combined.push({ + "type": appType, + "toplevel": toplevel, + "toplevels": toplevel ? [toplevel] : [], + "appId": canonicalId, + "title": title + }); + processedToplevels.add(toplevel); + } else { + // For pinned apps that aren't running, track by appId to avoid duplicates + if (processedPinnedAppIds.has(canonicalId)) { + return; + } + combined.push({ + "type": appType, + "toplevel": toplevel, + "toplevels": [], + "appId": canonicalId, + "title": title + }); + processedPinnedAppIds.add(canonicalId); + } + } + + function pushRunning(first) { + runningApps.forEach(toplevel => { + if (toplevel) { + // Use robust matching to check if pinned + const isPinned = isAppIdPinned(toplevel.appId, pinnedApps); + if (!first && isPinned && processedToplevels.has(toplevel)) { + return; // Already added by pushPinned() + } + pushApp((first && isPinned) ? "pinned-running" : "running", toplevel, toplevel.appId, toplevel.title); + } + }); + } + + function pushPinned() { + pinnedApps.forEach(pinnedAppId => { + // Find all running instances of this pinned app using robust matching + // Also resolve toplevel appId via desktop entry lookup to handle + // StartupWMClass != .desktop filename (e.g. zen -> zen-browser-bin) + const normalizedPinned = normalizeAppId(pinnedAppId); + const matchingToplevels = runningApps.filter(app => { + if (!app) + return false; + if (normalizeAppId(app.appId) === normalizedPinned) + return true; + const resolved = resolveToDesktopEntryId(app.appId); + return resolved !== app.appId && normalizeAppId(resolved) === normalizedPinned; + }); + + if (matchingToplevels.length > 0) { + // Add all running instances as pinned-running + matchingToplevels.forEach(toplevel => { + pushApp("pinned-running", toplevel, pinnedAppId, toplevel.title); + }); + } else { + // App is pinned but not running - add once + pushApp("pinned", null, pinnedAppId, getAppNameFromDesktopEntry(pinnedAppId) || pinnedAppId); + } + }); + } + + // if pinnedStatic then push all pinned and then all remaining running apps + if (Settings.data.dock.pinnedStatic) { + pushPinned(); + pushRunning(false); + + // else add all running apps and then remaining pinned apps + } else { + pushRunning(true); + pushPinned(); + } + + const sortedApps = sortDockApps(combined); + dockApps = buildGroupedDockApps(sortedApps); + const cycleState = root.groupCycleIndices || {}; + const nextCycleState = {}; + dockApps.forEach(app => { + if (app && app.appId && cycleState[app.appId] !== undefined) { + nextCycleState[app.appId] = cycleState[app.appId]; + } + }); + root.groupCycleIndices = nextCycleState; + + // Sync session order if needed + if (!sessionAppOrder || sessionAppOrder.length === 0) { + sessionAppOrder = dockApps.map(getAppKey); + } else { + const currentKeys = new Set(dockApps.map(getAppKey)); + const existingKeys = new Set(); + const newOrder = []; + + // Keep existing keys that are still present + sessionAppOrder.forEach(key => { + if (currentKeys.has(key)) { + newOrder.push(key); + existingKeys.add(key); + } + }); + + // Add new keys at the end + dockApps.forEach(app => { + const key = getAppKey(app); + if (!existingKeys.has(key)) { + newOrder.push(key); + existingKeys.add(key); + } + }); + + if (JSON.stringify(newOrder) !== JSON.stringify(sessionAppOrder)) { + sessionAppOrder = newOrder; + } + } + } + + // Update dock apps when toplevels change + Connections { + target: ToplevelManager ? ToplevelManager.toplevels : null + function onValuesChanged() { + updateDockApps(); + } + } + + // Update dock apps when pinned apps change + Connections { + target: Settings.data.dock + function onPinnedAppsChanged() { + updateDockApps(); + } + function onOnlySameOutputChanged() { + updateDockApps(); + } + function onGroupAppsChanged() { + updateDockApps(); + } + } + + // Initial update when component is ready + Component.onCompleted: { + if (ToplevelManager) { + updateDockApps(); + } + } + + // Refresh icons when DesktopEntries becomes available + Connections { + target: DesktopEntries.applications + function onValuesChanged() { + root.iconRevision++; + root._desktopEntryIdCache = {}; + } + } + + Timer { + id: hideTimer + interval: hideDelay + onTriggered: {} + } + + Timer { + id: showTimer + interval: showDelay + onTriggered: {} + } + + Timer { + id: hoverCloseTimer + interval: hideDelay + onTriggered: { + if (root.dockHovered || root.isDockHovered || root.anyAppHovered || root.menuHovered || (root.currentContextMenu && root.currentContextMenu.visible)) { + restart(); + return; + } + root.isDockHovered = false; + root.close(); + } + } + + panelContent: Item { + id: panelContent + + property bool allowAttach: true + property real frameThickness: isFramed && !barAtSameEdge && !Settings.data.dock.sitOnFrame ? Settings.data.bar.frameThickness : 0 + property real contentPreferredWidth: Math.round(dockContainerWrapper.width) - (isVertical ? frameThickness : 0) + property real contentPreferredHeight: Math.round(dockContainerWrapper.height) - (!isVertical ? frameThickness : 0) + + Item { + id: hoverArea + anchors.fill: dockContainerWrapper + anchors.margins: -frameThickness + + // Detect hover over dock area including frame thickness + HoverHandler { + id: dockHoverArea + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onHoveredChanged: { + root.panelHovered = hovered; + if (hovered) { + root.isDockHovered = true; + hoverCloseTimer.stop(); + } else { + root.isDockHovered = false; + if (root.menuHovered || (root.currentContextMenu && root.currentContextMenu.visible)) { + hoverCloseTimer.stop(); + } else { + hoverCloseTimer.restart(); + } + } + } + } + } + + Item { + id: dockContainerWrapper + readonly property real frameThickness: isFramed ? Settings.data.bar.frameThickness : 0 + width: dockContent.dockContainer.width + height: dockContent.dockContainer.height + anchors.top: root.dockPosition === "bottom" ? parent.top : undefined + anchors.bottom: root.dockPosition === "top" ? parent.bottom : undefined + anchors.left: root.dockPosition === "right" ? parent.left : undefined + anchors.right: root.dockPosition === "left" ? parent.right : undefined + + DockContent { + id: dockContent + anchors.fill: parent + dockRoot: root + extraTop: 0 + extraBottom: 0 + extraLeft: 0 + extraRight: 0 + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/ClipboardPreview.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/ClipboardPreview.qml new file mode 100644 index 0000000..9b7ebcb --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/ClipboardPreview.qml @@ -0,0 +1,195 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.Keyboard +import qs.Widgets + +Item { + id: previewPanel + + property var currentItem: null + property string fullContent: "" + property string imageDataUrl: "" + property bool loadingFullContent: false + property bool isImageContent: false + + implicitHeight: contentArea.implicitHeight + Style.margin2L + + function loadContent() { + if (!currentItem || !currentItem.clipboardId) + return; + + if (isImageContent) { + // For images, check cache first then decode + imageDataUrl = ClipboardService.getImageData(currentItem.clipboardId) || ""; + loadingFullContent = !imageDataUrl; + if (!imageDataUrl && currentItem.mime) { + ClipboardService.decodeToDataUrl(currentItem.clipboardId, currentItem.mime, null); + } + } else { + // For text, check sync cache first + const cached = ClipboardService.getContent(currentItem.clipboardId); + if (cached) { + fullContent = cached; + loadingFullContent = false; + } else { + // Show preview while loading full content + fullContent = currentItem.preview || ""; + loadingFullContent = true; + // Async decode as fallback + var requestedId = currentItem.clipboardId; + ClipboardService.decode(requestedId, function (content) { + if (!previewPanel || !previewPanel.currentItem) { + return; + } + if (previewPanel.currentItem.clipboardId === requestedId) { + var trimmed = content ? content.trim() : ""; + if (trimmed !== "") { + previewPanel.fullContent = trimmed; + } + previewPanel.loadingFullContent = false; + } + }); + } + } + } + + Connections { + target: previewPanel + function onCurrentItemChanged() { + fullContent = ""; + imageDataUrl = ""; + loadingFullContent = false; + isImageContent = currentItem && currentItem.isImage; + + if (currentItem && currentItem.clipboardId) { + loadContent(); + } + } + } + + readonly property int _rev: ClipboardService.revision + on_RevChanged: { + // When cache updates, try to load content if we're still showing loading or preview + if (currentItem && currentItem.clipboardId && !isImageContent && loadingFullContent) { + const cached = ClipboardService.getContent(currentItem.clipboardId); + if (cached) { + fullContent = cached; + loadingFullContent = false; + } + } + } + + Timer { + id: imageUpdateTimer + interval: 200 + running: currentItem && currentItem.isImage && imageDataUrl === "" + repeat: currentItem && currentItem.isImage && imageDataUrl === "" + + onTriggered: { + if (currentItem && currentItem.clipboardId) { + const newData = ClipboardService.getImageData(currentItem.clipboardId) || ""; + if (newData !== imageDataUrl) { + imageDataUrl = newData; + if (newData) { + loadingFullContent = false; + } + } + } + } + } + + Item { + id: contentArea + anchors.fill: parent + anchors.margins: Style.marginS + + BusyIndicator { + anchors.centerIn: parent + running: loadingFullContent + visible: loadingFullContent + width: Style.baseWidgetSize + height: width + } + + // Preview for image entry + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginS + spacing: Style.marginS + visible: isImageContent && !loadingFullContent && imageDataUrl !== "" + + NImageRounded { + id: previewImage + Layout.fillWidth: true + Layout.fillHeight: true + radius: Style.marginS + imagePath: imageDataUrl + imageFillMode: Image.PreserveAspectFit + } + + NDivider { + Layout.fillWidth: true + Layout.bottomMargin: Style.marginS + } + + NText { + Layout.fillWidth: true + text: { + const meta = ClipboardService.parseImageMeta(currentItem?.preview); + if (meta) + return `${meta.fmt} โ€ข ${meta.w}ร—${meta.h} โ€ข ${meta.size}`; + // Fallback to basic info + const format = (currentItem?.mime || "image").split("/")[1]?.toUpperCase() || "Image"; + return `${format} โ€ข ${previewImage.implicitWidth}ร—${previewImage.implicitHeight}`; + } + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + + // Preview for text entry + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginS + spacing: Style.marginS + visible: !isImageContent && !loadingFullContent + + NScrollView { + id: clipboardScrollView + Layout.fillWidth: true + Layout.fillHeight: true + horizontalPolicy: Settings.data.appLauncher.clipboardWrapText ? ScrollBar.AlwaysOff : ScrollBar.AsNeeded + + NText { + text: fullContent + width: Settings.data.appLauncher.clipboardWrapText ? clipboardScrollView.availableWidth : implicitWidth + wrapMode: Settings.data.appLauncher.clipboardWrapText ? Text.Wrap : Text.NoWrap + textFormat: Text.PlainText + font.pointSize: Style.fontSizeM + font.family: Settings.data.ui.fontFixed + color: Color.mOnSurface + } + } + + NDivider { + Layout.fillWidth: true + Layout.bottomMargin: Style.marginS + } + + NText { + Layout.fillWidth: true + visible: fullContent.length > 0 + text: { + const chars = fullContent.length; + const words = fullContent.split(/\s+/).filter(w => w.length > 0).length; + const lines = fullContent.split('\n').length; + return `${chars} chars, ${words} words, ${lines} lines`; + } + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Helpers/LauncherNavigation.js b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Helpers/LauncherNavigation.js new file mode 100644 index 0000000..00a1c5e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Helpers/LauncherNavigation.js @@ -0,0 +1,134 @@ +function selectNext(selectedIndex, resultsLength) { + if (resultsLength > 0 && selectedIndex < resultsLength - 1) + return selectedIndex + 1; + return selectedIndex; +} + +function selectPrevious(selectedIndex, resultsLength) { + if (resultsLength > 0 && selectedIndex > 0) + return selectedIndex - 1; + return selectedIndex; +} + +function selectNextWrapped(selectedIndex, resultsLength, allowWrap) { + if (resultsLength > 0) { + if (allowWrap) + return (selectedIndex + 1) % resultsLength; + return selectNext(selectedIndex, resultsLength); + } + return selectedIndex; +} + +function selectPreviousWrapped(selectedIndex, resultsLength, allowWrap) { + if (resultsLength > 0) { + if (allowWrap) + return (((selectedIndex - 1) % resultsLength) + resultsLength) % resultsLength; + return selectPrevious(selectedIndex, resultsLength); + } + return selectedIndex; +} + +function selectFirst() { + return 0; +} + +function selectLast(resultsLength) { + return resultsLength > 0 ? resultsLength - 1 : 0; +} + +function selectNextPage(selectedIndex, resultsLength, entryHeight) { + if (resultsLength > 0) { + var page = Math.max(1, Math.floor(600 / entryHeight)); + return Math.min(selectedIndex + page, resultsLength - 1); + } + return selectedIndex; +} + +function selectPreviousPage(selectedIndex, resultsLength, entryHeight) { + if (resultsLength > 0) { + var page = Math.max(1, Math.floor(600 / entryHeight)); + return Math.max(selectedIndex - page, 0); + } + return selectedIndex; +} + +function selectPreviousRow(selectedIndex, resultsLength, gridColumns) { + if (resultsLength <= 0 || gridColumns <= 0) + return selectedIndex; + + var currentRow = Math.floor(selectedIndex / gridColumns); + var currentCol = selectedIndex % gridColumns; + + if (currentRow > 0) { + var targetRow = currentRow - 1; + var itemsInTargetRow = Math.min(gridColumns, resultsLength - targetRow * gridColumns); + if (currentCol < itemsInTargetRow) + return targetRow * gridColumns + currentCol; + return targetRow * gridColumns + itemsInTargetRow - 1; + } + + // Wrap to last row, same column + var totalRows = Math.ceil(resultsLength / gridColumns); + var lastRow = totalRows - 1; + var itemsInLastRow = Math.min(gridColumns, resultsLength - lastRow * gridColumns); + if (currentCol < itemsInLastRow) + return lastRow * gridColumns + currentCol; + return resultsLength - 1; +} + +function selectNextRow(selectedIndex, resultsLength, gridColumns) { + if (resultsLength <= 0 || gridColumns <= 0) + return selectedIndex; + + var currentRow = Math.floor(selectedIndex / gridColumns); + var currentCol = selectedIndex % gridColumns; + var totalRows = Math.ceil(resultsLength / gridColumns); + + if (currentRow < totalRows - 1) { + var targetRow = currentRow + 1; + var targetIndex = targetRow * gridColumns + currentCol; + if (targetIndex < resultsLength) + return targetIndex; + var itemsInTargetRow = resultsLength - targetRow * gridColumns; + if (itemsInTargetRow > 0) + return targetRow * gridColumns + itemsInTargetRow - 1; + return Math.min(currentCol, resultsLength - 1); + } + + // Wrap to first row, same column + return Math.min(currentCol, resultsLength - 1); +} + +function selectPreviousColumn(selectedIndex, resultsLength, gridColumns) { + if (resultsLength <= 0) + return selectedIndex; + + var currentRow = Math.floor(selectedIndex / gridColumns); + var currentCol = selectedIndex % gridColumns; + + if (currentCol > 0) + return currentRow * gridColumns + (currentCol - 1); + if (currentRow > 0) + return (currentRow - 1) * gridColumns + (gridColumns - 1); + + var totalRows = Math.ceil(resultsLength / gridColumns); + var lastRowIndex = (totalRows - 1) * gridColumns + (gridColumns - 1); + return Math.min(lastRowIndex, resultsLength - 1); +} + +function selectNextColumn(selectedIndex, resultsLength, gridColumns) { + if (resultsLength <= 0) + return selectedIndex; + + var currentRow = Math.floor(selectedIndex / gridColumns); + var currentCol = selectedIndex % gridColumns; + var itemsInCurrentRow = Math.min(gridColumns, resultsLength - currentRow * gridColumns); + + if (currentCol < itemsInCurrentRow - 1) + return currentRow * gridColumns + (currentCol + 1); + + var totalRows = Math.ceil(resultsLength / gridColumns); + if (currentRow < totalRows - 1) + return (currentRow + 1) * gridColumns; + return 0; +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Launcher.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Launcher.qml new file mode 100644 index 0000000..6623ce3 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Launcher.qml @@ -0,0 +1,182 @@ +import QtQuick +import QtQuick.Controls +import Quickshell + +import qs.Commons +import qs.Modules.MainScreen +import qs.Services.UI +import qs.Widgets + +SmartPanel { + id: root + + // Disable when overlay mode is enabled (LauncherOverlayWindow handles it) + enabled: !Settings.data.appLauncher.overviewLayer + visible: !Settings.data.appLauncher.overviewLayer + + // Reference to core (set after panelContent loads) + property var launcherCoreRef: null + + // Expose core launcher for external access (e.g., IPC) + readonly property string searchText: launcherCoreRef ? launcherCoreRef.searchText : "" + readonly property int selectedIndex: launcherCoreRef ? launcherCoreRef.selectedIndex : 0 + readonly property var results: launcherCoreRef ? launcherCoreRef.results : [] + readonly property var activeProvider: launcherCoreRef ? launcherCoreRef.activeProvider : null + readonly property var currentProvider: launcherCoreRef ? launcherCoreRef.currentProvider : null + readonly property bool isGridView: launcherCoreRef ? launcherCoreRef.isGridView : false + readonly property int gridColumns: launcherCoreRef ? launcherCoreRef.gridColumns : 5 + + function setSearchText(text) { + if (launcherCoreRef) + launcherCoreRef.setSearchText(text); + } + + // Preview panel support + readonly property bool previewActive: { + if (!launcherCoreRef) + return false; + var provider = launcherCoreRef.activeProvider; + if (!provider || !provider.hasPreview) + return false; + if (!Settings.data.appLauncher.enableClipPreview) + return false; + return selectedIndex >= 0 && results && !!results[selectedIndex]; + } + readonly property int previewPanelWidth: Math.round(400 * Style.uiScaleRatio) + + // Panel sizing + readonly property int listPanelWidth: Math.round(500 * Style.uiScaleRatio) + readonly property int totalBaseWidth: listPanelWidth + Style.margin2L + + preferredWidth: totalBaseWidth + preferredHeight: Math.round(600 * Style.uiScaleRatio) + preferredWidthRatio: 0.25 + preferredHeightRatio: 0.5 + + // Positioning + readonly property string screenBarPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property string panelPosition: { + if (Settings.data.appLauncher.position === "follow_bar") { + if (screenBarPosition === "left" || screenBarPosition === "right") { + return `center_${screenBarPosition}`; + } else { + return `${screenBarPosition}_center`; + } + } else { + return Settings.data.appLauncher.position; + } + } + panelAnchorHorizontalCenter: !root.useButtonPosition && (panelPosition === "center" || panelPosition.endsWith("_center")) + panelAnchorVerticalCenter: !root.useButtonPosition && panelPosition === "center" + panelAnchorLeft: !root.useButtonPosition && panelPosition !== "center" && panelPosition.endsWith("_left") + panelAnchorRight: !root.useButtonPosition && panelPosition !== "center" && panelPosition.endsWith("_right") + panelAnchorBottom: !root.useButtonPosition && panelPosition.startsWith("bottom_") + panelAnchorTop: !root.useButtonPosition && panelPosition.startsWith("top_") + + panelContent: Rectangle { + id: ui + color: "transparent" + opacity: launcherCore.resultsReady ? 1.0 : 0.0 + + Component.onCompleted: root.launcherCoreRef = launcherCore + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCirc + } + } + + // Preview Panel (external) - uses provider's preview component + NDropShadow { + source: previewBox + anchors.fill: previewBox + autoPaddingEnabled: true + visible: previewBox.visible + z: previewBox.z - 1 + } + + NBox { + id: previewBox + visible: root.previewActive + width: root.previewPanelWidth + height: Math.round(400 * Style.uiScaleRatio) + forceOpaque: true // no blur for now + x: root.panelAnchorRight ? -(root.previewPanelWidth + Style.marginM) : ui.width + Style.marginM + y: { + var view = launcherCore.resultsView; + if (!view) + return Style.marginL; + var row = launcherCore.isGridView ? Math.floor(launcherCore.selectedIndex / launcherCore.gridColumns) : launcherCore.selectedIndex; + var gridCellSize = Math.floor((root.listPanelWidth - (2 * Style.marginXS) - ((launcherCore.targetGridColumns - 1) * Style.marginS)) / launcherCore.targetGridColumns); + var itemHeight = launcherCore.isGridView ? (gridCellSize + Style.marginXXS) : (launcherCore.entryHeight + (view.spacing || 0)); + var yPos = row * itemHeight - (view.contentY || 0); + var mapped = view.mapToItem(ui, 0, yPos); + return Math.max(Style.marginL, Math.min(mapped.y, ui.height - previewBox.height - Style.marginL)); + } + z: -1 + + opacity: visible ? 1.0 : 0.0 + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + Behavior on y { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + Loader { + id: previewLoader + anchors.fill: parent + active: root.previewActive + source: { + if (!active) + return ""; + var provider = launcherCore.activeProvider; + if (provider && provider.previewComponentPath) + return provider.previewComponentPath; + return ""; + } + + onLoaded: updatePreviewItem() + onItemChanged: updatePreviewItem() + + function updatePreviewItem() { + if (!item || launcherCore.selectedIndex < 0 || !launcherCore.results[launcherCore.selectedIndex]) + return; + var provider = launcherCore.activeProvider; + if (provider && provider.getPreviewData) { + item.currentItem = provider.getPreviewData(launcherCore.results[launcherCore.selectedIndex]); + } else { + item.currentItem = launcherCore.results[launcherCore.selectedIndex]; + } + } + } + } + + // Core launcher (state, providers, UI) + LauncherCore { + id: launcherCore + anchors.fill: parent + screen: root.screen + isOpen: root.isPanelOpen + onRequestClose: root.close() + // Defer so the signal emission completes before SmartPanel + // sets isPanelOpen=false and the contentLoader destroys us. + onRequestCloseImmediately: Qt.callLater(root.closeImmediately) + } + + // Update preview when selection changes + Connections { + target: launcherCore + function onSelectedIndexChanged() { + if (previewLoader.item) + previewLoader.updatePreviewItem(); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/LauncherCore.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/LauncherCore.qml new file mode 100644 index 0000000..fb8f7fc --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/LauncherCore.qml @@ -0,0 +1,909 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import "Helpers/LauncherNavigation.js" as LauncherNav + +import "Providers" +import qs.Commons +import qs.Services.Keyboard +import qs.Services.UI +import qs.Widgets + +// Core launcher logic and UI - shared between SmartPanel (Launcher.qml) and overlay (LauncherOverlayWindow.qml) +Rectangle { + id: root + color: "transparent" + + // External interface - set by parent + property var screen: null + property bool isOpen: false + signal requestClose + signal requestCloseImmediately + + function closeImmediately() { + requestCloseImmediately(); + } + + // Expose for preview panel positioning + readonly property var resultsView: resultsSwapView.item + + // State + property string searchText: "" + property int selectedIndex: 0 + property var results: [] + property var providers: [] + property var activeProvider: null + property bool resultsReady: false + property var pluginProviderInstances: ({}) + property bool ignoreMouseHover: true // Transient flag, should always be true on init + + // Global mouse tracking for movement detection across delegates + property real globalLastMouseX: 0 + property real globalLastMouseY: 0 + property bool globalMouseInitialized: false + property bool mouseTrackingReady: false // Delay tracking until panel is settled + + readonly property bool animationsDisabled: Settings.data.general.animationDisabled + + Timer { + id: mouseTrackingDelayTimer + interval: root.animationsDisabled ? 0 : (Style.animationNormal + 50) // Wait for panel animation to complete + safety margin + repeat: false + onTriggered: { + root.mouseTrackingReady = true; + root.globalMouseInitialized = false; // Reset so we get fresh initial position + } + } + + readonly property var defaultProvider: appsProvider + readonly property var currentProvider: activeProvider || defaultProvider + + readonly property string launcherDensity: (currentProvider && currentProvider.ignoreDensity === false) ? (Settings.data.appLauncher.density || "default") : "comfortable" + readonly property int effectiveIconSize: launcherDensity === "comfortable" ? 48 : (launcherDensity === "default" ? 36 : 24) + readonly property int badgeSize: Math.round(effectiveIconSize * Style.uiScaleRatio) + readonly property int entryHeight: Math.round(badgeSize + (launcherDensity === "compact" ? (Style.marginL + Style.marginXXS) : (Style.marginXL + Style.marginS))) + + readonly property bool providerShowsCategories: (currentProvider.showsCategories !== undefined ? currentProvider.showsCategories : true) && providerCategories.length > 0 + + readonly property var providerCategories: { + if (currentProvider.availableCategories && currentProvider.availableCategories.length > 0) { + return currentProvider.availableCategories; + } + return currentProvider.categories || []; + } + + readonly property bool showProviderCategories: { + if (!providerShowsCategories || providerCategories.length === 0) + return false; + if (currentProvider === defaultProvider) + return Settings.data.appLauncher.showCategories; + return true; + } + + readonly property bool providerHasDisplayString: results.length > 0 && !!results[0].displayString + + readonly property string providerSupportedLayouts: { + if (activeProvider && activeProvider.supportedLayouts) + return activeProvider.supportedLayouts; + if (results.length > 0 && results[0].provider && results[0].provider.supportedLayouts) + return results[0].provider.supportedLayouts; + if (defaultProvider && defaultProvider.supportedLayouts) + return defaultProvider.supportedLayouts; + return "both"; + } + + readonly property bool showLayoutToggle: !providerHasDisplayString && providerSupportedLayouts === "both" + + readonly property string layoutMode: { + if (searchText === ">") + return "list"; + if (providerSupportedLayouts === "grid") + return "grid"; + if (providerSupportedLayouts === "list") + return "list"; + if (providerSupportedLayouts === "single") + return "single"; + if (providerHasDisplayString) + return "grid"; + return Settings.data.appLauncher.viewMode; + } + + readonly property bool isGridView: layoutMode === "grid" + readonly property bool isSingleView: layoutMode === "single" + readonly property bool isCompactDensity: launcherDensity === "compact" + + readonly property int targetGridColumns: { + let base = 5; + if (launcherDensity === "comfortable") + base = 4; + else if (launcherDensity === "compact") + base = 6; + + if (!activeProvider || activeProvider === defaultProvider) + return base; + + if (activeProvider.preferredGridColumns) { + let multiplier = base / 5.0; + return Math.max(1, Math.round(activeProvider.preferredGridColumns * multiplier)); + } + + return base; + } + readonly property int listPanelWidth: Math.round(500 * Style.uiScaleRatio) + readonly property int gridContentWidth: listPanelWidth - (2 * Style.marginXS) + readonly property int gridCellSize: Math.floor((gridContentWidth - ((targetGridColumns - 1) * Style.marginS)) / targetGridColumns) + + readonly property int gridColumns: targetGridColumns + + // Check if current provider allows wrap navigation (default true) + readonly property bool allowWrapNavigation: { + var provider = activeProvider || currentProvider; + return provider && provider.wrapNavigation !== undefined ? provider.wrapNavigation : true; + } + + // Listen for plugin provider registry changes + Connections { + target: LauncherProviderRegistry + function onPluginProviderRegistryUpdated() { + root.syncPluginProviders(); + } + } + + // Lifecycle + onIsOpenChanged: { + if (isOpen) { + onOpened(); + } else { + onClosed(); + } + } + + onSearchTextChanged: { + if (isOpen) { + updateResults(); + } + } + + function onOpened() { + ignoreMouseHover = true; + globalMouseInitialized = false; + mouseTrackingReady = false; + mouseTrackingDelayTimer.restart(); + + // Show launcher immediately, results will populate asynchronously + resultsReady = true; + focusSearchInput(); + + Qt.callLater(() => { + syncPluginProviders(); + for (let provider of providers) { + if (provider.onOpened) + provider.onOpened(); + } + updateResults(); + }); + } + + function onClosed() { + searchText = ""; + ignoreMouseHover = true; + if (resultsSwapView) + resultsSwapView.resetVisuals(); + for (let provider of providers) { + if (provider.onClosed) + provider.onClosed(); + } + } + + function close() { + requestClose(); + } + + function applyCategorySelection(tabIndex, categories) { + const categoryList = categories || providerCategories; + if (!categoryList || tabIndex < 0 || tabIndex >= categoryList.length) + return false; + + currentProvider.selectCategory(categoryList[tabIndex]); + categoryTabs.currentIndex = tabIndex; + return true; + } + + function selectCategoryWithSlide(tabIndex) { + if (!showProviderCategories || !currentProvider || !currentProvider.selectCategory) + return; + + const cats = providerCategories; + if (!cats || tabIndex < 0 || tabIndex >= cats.length) + return; + + const currentIdx = cats.indexOf(currentProvider.selectedCategory); + if (tabIndex === currentIdx) + return; + + const canAnimate = !animationsDisabled && resultsSwapView.width > 0 && resultsSwapView.height > 0; + if (!canAnimate) { + applyCategorySelection(tabIndex, cats); + return; + } + + const direction = tabIndex > currentIdx ? 1 : -1; + resultsSwapView.swap(direction, () => applyCategorySelection(tabIndex, providerCategories)); + } + + // Public API + function setSearchText(text) { + searchText = text; + } + + function focusSearchInput() { + if (searchInput.inputItem) { + searchInput.inputItem.forceActiveFocus(); + } + } + + // Provider registration + function registerProvider(provider) { + providers.push(provider); + provider.launcher = root; + if (provider.init) + provider.init(); + } + + function syncPluginProviders() { + var registeredIds = LauncherProviderRegistry.getPluginProviders(); + var changed = false; + + // Remove providers that are no longer registered + for (var existingId in pluginProviderInstances) { + if (registeredIds.indexOf(existingId) === -1) { + var idx = providers.indexOf(pluginProviderInstances[existingId]); + if (idx >= 0) + providers.splice(idx, 1); + delete pluginProviderInstances[existingId]; + Logger.d("Launcher", "Removed plugin provider:", existingId); + changed = true; + } + } + + // Adopt persistent instances from the registry + for (var i = 0; i < registeredIds.length; i++) { + var providerId = registeredIds[i]; + if (!pluginProviderInstances[providerId]) { + var instance = LauncherProviderRegistry.getProviderInstance(providerId); + if (instance) { + pluginProviderInstances[providerId] = instance; + providers.push(instance); + instance.launcher = root; + Logger.d("Launcher", "Adopted plugin provider:", providerId); + changed = true; + } + } + } + + // Update results only if providers changed + if (changed && root.isOpen) { + updateResults(); + } + } + + // Search handling + function updateResults() { + results = []; + var newActiveProvider = null; + + // Check for command mode + if (searchText.startsWith(">")) { + for (let provider of providers) { + if (provider.handleCommand && provider.handleCommand(searchText)) { + newActiveProvider = provider; + results = provider.getResults(searchText); + break; + } + } + + // Show available commands if just ">" or filter commands if partial match + if (!newActiveProvider) { + let allCommands = []; + for (let provider of providers) { + if (provider.commands) + allCommands = allCommands.concat(provider.commands()); + } + if (searchText === ">") { + results = allCommands; + } else if (searchText.length > 1) { + const query = searchText.substring(1); + if (typeof FuzzySort !== 'undefined') { + const fuzzyResults = FuzzySort.go(query, allCommands, { + "keys": ["name"], + "limit": 50 + }); + results = fuzzyResults.map(result => result.obj); + } else { + const queryLower = query.toLowerCase(); + results = allCommands.filter(cmd => (cmd.name || "").toLowerCase().includes(queryLower)); + } + } + } + } else { + // Regular search - let providers contribute results + let allResults = []; + for (let provider of providers) { + if (provider.handleSearch) { + const providerResults = provider.getResults(searchText); + allResults = allResults.concat(providerResults); + } + } + + // Sort by _score (higher = better match), items without _score go first + if (searchText.trim() !== "") { + const boostByUsage = Settings.data.appLauncher.sortByMostUsed; + + allResults.sort((a, b) => { + let sa = a._score !== undefined ? a._score : 0; + let sb = b._score !== undefined ? b._score : 0; + + // Boost scores for frequently used items from tracked providers + // _score is normalized 0โ€“1, so boost is scaled to nudge, not overwhelm + if (boostByUsage) { + if (a.provider && a.provider.trackUsage && a.usageKey) { + sa += 0.1 * Math.log2(1 + ShellState.getLauncherUsageCount(a.usageKey)); + } + if (b.provider && b.provider.trackUsage && b.usageKey) { + sb += 0.1 * Math.log2(1 + ShellState.getLauncherUsageCount(b.usageKey)); + } + } + + return sb - sa; + }); + } + results = allResults; + } + + // Update activeProvider only after computing new state to avoid UI flicker + activeProvider = newActiveProvider; + selectedIndex = 0; + } + + // Navigation functions (delegated to LauncherNavigation.js) + function selectNext() { + selectedIndex = LauncherNav.selectNext(selectedIndex, results.length); + } + function selectPrevious() { + selectedIndex = LauncherNav.selectPrevious(selectedIndex, results.length); + } + function selectNextWrapped() { + selectedIndex = LauncherNav.selectNextWrapped(selectedIndex, results.length, allowWrapNavigation); + } + function selectPreviousWrapped() { + selectedIndex = LauncherNav.selectPreviousWrapped(selectedIndex, results.length, allowWrapNavigation); + } + function selectFirst() { + selectedIndex = LauncherNav.selectFirst(); + } + function selectLast() { + selectedIndex = LauncherNav.selectLast(results.length); + } + function selectNextPage() { + selectedIndex = LauncherNav.selectNextPage(selectedIndex, results.length, entryHeight); + } + function selectPreviousPage() { + selectedIndex = LauncherNav.selectPreviousPage(selectedIndex, results.length, entryHeight); + } + function selectPreviousRow() { + selectedIndex = LauncherNav.selectPreviousRow(selectedIndex, results.length, gridColumns); + } + function selectNextRow() { + selectedIndex = LauncherNav.selectNextRow(selectedIndex, results.length, gridColumns); + } + function selectPreviousColumn() { + selectedIndex = LauncherNav.selectPreviousColumn(selectedIndex, results.length, gridColumns); + } + function selectNextColumn() { + selectedIndex = LauncherNav.selectNextColumn(selectedIndex, results.length, gridColumns); + } + + function activate() { + if (results.length > 0 && results[selectedIndex]) { + const item = results[selectedIndex]; + const provider = item.provider || currentProvider; + + // Track usage for providers that opt in (cross-provider "most used" tracking) + if (Settings.data.appLauncher.sortByMostUsed && provider && provider.trackUsage && item.usageKey) { + ShellState.recordLauncherUsage(item.usageKey); + } + + // Check if auto-paste is enabled and provider/item supports it + if (Settings.data.appLauncher.autoPasteClipboard && provider && provider.supportsAutoPaste && item.autoPasteText) { + if (item.onAutoPaste) + item.onAutoPaste(); + closeImmediately(); + Qt.callLater(() => { + ClipboardService.pasteText(item.autoPasteText); + }); + return; + } + + if (item.onActivate) + item.onActivate(); + } + } + + function checkKey(event, settingName) { + return Keybinds.checkKey(event, settingName, Settings); + } + + // Keyboard handler + function handleKeyPress(event) { + if (checkKey(event, 'escape')) { + close(); + event.accepted = true; + return; + } + + if (checkKey(event, 'enter')) { + activate(); + event.accepted = true; + return; + } + + if (checkKey(event, 'up')) { + if (!isSingleView) { + isGridView ? selectPreviousRow() : selectPreviousWrapped(); + } + event.accepted = true; + return; + } + + if (checkKey(event, 'down')) { + if (!isSingleView) { + isGridView ? selectNextRow() : selectNextWrapped(); + } + event.accepted = true; + return; + } + + if (checkKey(event, 'left')) { + if (isGridView) { + selectPreviousColumn(); + event.accepted = true; + return; + } + } + + if (checkKey(event, 'right')) { + if (isGridView) { + selectNextColumn(); + event.accepted = true; + return; + } + } + + // Static bindings + switch (event.key) { + case Qt.Key_Tab: + if (showProviderCategories) { + var cats = providerCategories; + var idx = cats.indexOf(currentProvider.selectedCategory); + var nextIdx = (idx + 1) % cats.length; + selectCategoryWithSlide(nextIdx); + } else { + selectNextWrapped(); + } + event.accepted = true; + break; + case Qt.Key_Backtab: + if (showProviderCategories) { + var cats2 = providerCategories; + var idx2 = cats2.indexOf(currentProvider.selectedCategory); + var prevIdx = ((idx2 - 1) % cats2.length + cats2.length) % cats2.length; + selectCategoryWithSlide(prevIdx); + } else { + selectPreviousWrapped(); + } + event.accepted = true; + break; + case Qt.Key_Home: + selectFirst(); + event.accepted = true; + break; + case Qt.Key_End: + selectLast(); + event.accepted = true; + break; + case Qt.Key_PageUp: + selectPreviousPage(); + event.accepted = true; + break; + case Qt.Key_PageDown: + selectNextPage(); + event.accepted = true; + break; + case Qt.Key_Delete: + if (selectedIndex >= 0 && results && results[selectedIndex]) { + var item = results[selectedIndex]; + var provider = item.provider || currentProvider; + if (provider && provider.canDeleteItem && provider.canDeleteItem(item)) + provider.deleteItem(item); + } + event.accepted = true; + break; + } + } + + // ----------------------- + // Provider components + // ----------------------- + ApplicationsProvider { + id: appsProvider + Component.onCompleted: { + registerProvider(this); + Logger.d("Launcher", "Registered: ApplicationsProvider"); + } + } + + ClipboardProvider { + id: clipProvider + Component.onCompleted: { + if (Settings.data.appLauncher.enableClipboardHistory) { + registerProvider(this); + Logger.d("Launcher", "Registered: ClipboardProvider"); + } + } + } + + CommandProvider { + id: cmdProvider + Component.onCompleted: { + registerProvider(this); + Logger.d("Launcher", "Registered: CommandProvider"); + } + } + + EmojiProvider { + id: emojiProvider + Component.onCompleted: { + registerProvider(this); + Logger.d("Launcher", "Registered: EmojiProvider"); + } + } + + CalculatorProvider { + id: calcProvider + Component.onCompleted: { + registerProvider(this); + Logger.d("Launcher", "Registered: CalculatorProvider"); + } + } + + SettingsProvider { + id: settingsProvider + Component.onCompleted: { + registerProvider(this); + Logger.d("Launcher", "Registered: SettingsProvider"); + } + } + + SessionProvider { + id: sessionProvider + Component.onCompleted: { + registerProvider(this); + Logger.d("Launcher", "Registered: SessionProvider"); + } + } + + WindowsProvider { + id: windowsProvider + Component.onCompleted: { + registerProvider(this); + Logger.d("Launcher", "Registered: WindowsProvider"); + } + } + + // ==================== UI Content ==================== + + opacity: resultsReady ? 1.0 : 0.0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCirc + } + } + + HoverHandler { + id: globalHoverHandler + enabled: !Settings.data.appLauncher.ignoreMouseInput + + onPointChanged: { + if (!root.mouseTrackingReady) { + return; + } + + if (!root.globalMouseInitialized) { + root.globalLastMouseX = point.position.x; + root.globalLastMouseY = point.position.y; + root.globalMouseInitialized = true; + return; + } + + const deltaX = Math.abs(point.position.x - root.globalLastMouseX); + const deltaY = Math.abs(point.position.y - root.globalLastMouseY); + if (deltaX + deltaY >= 5) { + root.ignoreMouseHover = false; + root.globalLastMouseX = point.position.x; + root.globalLastMouseY = point.position.y; + } + } + } + + ColumnLayout { + anchors.fill: parent + anchors.topMargin: Style.marginL + anchors.bottomMargin: Style.marginL + spacing: Style.marginL + + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: Style.marginL + Layout.rightMargin: Style.marginL + spacing: Style.marginS + + NTextInput { + id: searchInput + Layout.fillWidth: true + radius: Style.iRadiusM + text: root.searchText + placeholderText: I18n.tr("placeholders.search-launcher") + fontSize: Style.fontSizeM + onTextChanged: root.searchText = text + + Component.onCompleted: { + if (searchInput.inputItem) { + searchInput.inputItem.forceActiveFocus(); + searchInput.inputItem.Keys.onPressed.connect(function (event) { + root.handleKeyPress(event); + }); + } + } + } + + NIconButton { + visible: root.showLayoutToggle + icon: Settings.data.appLauncher.viewMode === "grid" ? "layout-list" : "layout-grid" + tooltipText: Settings.data.appLauncher.viewMode === "grid" ? I18n.tr("tooltips.list-view") : I18n.tr("tooltips.grid-view") + customRadius: Style.iRadiusM + Layout.preferredWidth: searchInput.height + Layout.preferredHeight: searchInput.height + onClicked: Settings.data.appLauncher.viewMode = Settings.data.appLauncher.viewMode === "grid" ? "list" : "grid" + } + } + + // Unified category tabs (works with any provider that has categories) + NTabBar { + id: categoryTabs + visible: root.showProviderCategories + Layout.fillWidth: true + Layout.leftMargin: Style.marginL + Layout.rightMargin: Style.marginL + margins: 0 + border.color: Style.boxBorderColor + border.width: Style.borderS + + property int computedCurrentIndex: visible && root.providerCategories.length > 0 ? root.providerCategories.indexOf(root.currentProvider.selectedCategory) : 0 + currentIndex: computedCurrentIndex + + Repeater { + model: root.providerCategories + NTabButton { + required property string modelData + required property int index + icon: root.currentProvider.categoryIcons ? (root.currentProvider.categoryIcons[modelData] || "star") : "star" + tooltipText: root.currentProvider.getCategoryName ? root.currentProvider.getCategoryName(modelData) : modelData + tabIndex: index + checked: categoryTabs.currentIndex === index + onClicked: root.selectCategoryWithSlide(index) + } + } + } + + // Results view + NSlideSwapView { + id: resultsSwapView + Layout.fillWidth: true + Layout.leftMargin: Style.marginL + Layout.rightMargin: Style.marginL + Layout.fillHeight: true + animationsEnabled: !root.animationsDisabled + sourceComponent: root.isSingleView ? singleViewComponent : (root.isGridView ? gridViewComponent : listViewComponent) + } + + // -------------------------- + // LIST VIEW + Component { + id: listViewComponent + NListView { + id: resultsList + + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AlwaysOff + reserveScrollbarSpace: false + gradientColor: Settings.data.ui.panelBackgroundOpacity < 1 ? "transparent" : Color.mSurface + wheelScrollMultiplier: 4.0 + + width: parent.width + height: parent.height + spacing: Style.marginS + model: root.results + currentIndex: root.selectedIndex + cacheBuffer: resultsList.height * 2 + interactive: !Settings.data.appLauncher.ignoreMouseInput + onCurrentIndexChanged: { + cancelFlick(); + if (currentIndex >= 0) { + positionViewAtIndex(currentIndex, ListView.Contain); + } + } + onModelChanged: {} + + delegate: LauncherListDelegate { + launcher: root + } + } + } + + // -------------------------- + // SINGLE ITEM VIEW + Component { + id: singleViewComponent + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + NBox { + anchors.fill: parent + color: Color.mSurfaceVariant + forceOpaque: true + Layout.fillWidth: true + Layout.fillHeight: true + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginL + Layout.fillWidth: true + Layout.fillHeight: true + + Item { + Layout.alignment: Qt.AlignTop | Qt.AlignLeft + NText { + text: root.results.length > 0 ? root.results[0].name : "" + pointSize: Style.fontSizeL + font.weight: Font.Bold + color: Color.mPrimary + } + } + + NScrollView { + id: descriptionScrollView + Layout.alignment: Qt.AlignTop | Qt.AlignLeft + Layout.topMargin: Style.fontSizeL + Style.marginXL + Layout.fillWidth: true + Layout.fillHeight: true + horizontalPolicy: ScrollBar.AlwaysOff + reserveScrollbarSpace: false + + NText { + width: descriptionScrollView.availableWidth + text: root.results.length > 0 ? root.results[0].description : "" + pointSize: Style.fontSizeM + font.weight: Font.Bold + color: Color.mOnSurface + horizontalAlignment: Text.AlignHLeft + verticalAlignment: Text.AlignTop + wrapMode: Text.Wrap + markdownTextEnabled: true + } + } + } + } + } + } + + // // -------------------------- + // GRID VIEW + Component { + id: gridViewComponent + NGridView { + id: resultsGrid + + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AlwaysOff + reserveScrollbarSpace: false + gradientColor: Settings.data.ui.panelBackgroundOpacity < 1 ? "transparent" : Color.mSurface + wheelScrollMultiplier: 4.0 + trackedSelectionIndex: root.selectedIndex + + width: parent.width + height: parent.height + cellWidth: parent.width / root.targetGridColumns + cellHeight: { + var cellWidth = parent.width / root.targetGridColumns; + // Use provider's preferred ratio if available + if (root.currentProvider && root.currentProvider.preferredGridCellRatio) { + return cellWidth * root.currentProvider.preferredGridCellRatio; + } + return cellWidth; + } + leftMargin: 0 + rightMargin: 0 + topMargin: 0 + bottomMargin: 0 + model: root.results + cacheBuffer: resultsGrid.height * 2 + keyNavigationEnabled: false + focus: false + interactive: !Settings.data.appLauncher.ignoreMouseInput + + // Completely disable GridView key handling + Keys.enabled: false + + // Handle scrolling to show selected item when it changes + Connections { + target: root + enabled: root.isGridView + function onSelectedIndexChanged() { + if (!root.isGridView || root.selectedIndex < 0 || !resultsGrid) { + return; + } + + Qt.callLater(() => { + if (root.isGridView && resultsGrid && resultsGrid.cancelFlick) { + resultsGrid.cancelFlick(); + resultsGrid.positionViewAtIndex(root.selectedIndex, GridView.Contain); + } + }); + } + } + + delegate: LauncherGridDelegate { + launcher: root + } + } + } + + ColumnLayout { + Layout.leftMargin: Style.marginL + Layout.rightMargin: Style.marginL + + NDivider { + Layout.fillWidth: true + Layout.bottomMargin: Style.marginS + } + + NText { + Layout.fillWidth: true + text: { + if (root.results.length === 0) { + if (root.searchText) { + return I18n.tr("common.no-results"); + } + // Use provider's empty browsing message if available + var provider = root.currentProvider; + if (provider && provider.emptyBrowsingMessage) { + return provider.emptyBrowsingMessage; + } + return ""; + } + var prefix = root.activeProvider && root.activeProvider.name ? root.activeProvider.name + ": " : ""; + return prefix + I18n.trp("common.result-count", root.results.length); + } + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + horizontalAlignment: Text.AlignCenter + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/LauncherGridDelegate.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/LauncherGridDelegate.qml new file mode 100644 index 0000000..67317c9 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/LauncherGridDelegate.qml @@ -0,0 +1,272 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Widgets + +import qs.Commons +import qs.Widgets + +Item { + id: gridEntryContainer + + required property var modelData + required property int index + required property var launcher + + width: GridView.view.cellWidth + height: GridView.view.cellHeight + + property bool isSelected: (!launcher.ignoreMouseHover && mouseArea.containsMouse) || (index === launcher.selectedIndex) + + // Prepare item when it becomes visible (e.g., decode images) + Component.onCompleted: { + var provider = modelData.provider; + if (provider && provider.prepareItem) { + provider.prepareItem(modelData); + } + } + + NBox { + id: gridEntry + anchors.fill: parent + anchors.margins: Style.marginXXS + color: gridEntryContainer.isSelected ? Color.mHover : Color.mSurfaceVariant + forceOpaque: gridEntryContainer.isSelected + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCirc + } + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: launcher.isCompactDensity ? Style.marginXS : Style.marginS + anchors.bottomMargin: launcher.isCompactDensity ? Style.marginXS : Style.marginS + spacing: launcher.isCompactDensity ? 0 : Style.marginXXS + + // Icon badge or Image preview or Emoji + Item { + // Size image at 65% of cell dimensions. + Layout.preferredWidth: Math.round(gridEntry.width * 0.65) + Layout.preferredHeight: Math.round(gridEntry.height * 0.65) + Layout.alignment: Qt.AlignHCenter + + // Icon background + Rectangle { + anchors.fill: parent + radius: Style.radiusM + color: Color.mSurface + visible: Settings.data.appLauncher.showIconBackground && !modelData.isImage + } + + // Image preview - uses provider's getImageUrl if available + NImageRounded { + id: gridImagePreview + anchors.fill: parent + visible: !!modelData.isImage && !modelData.displayString + radius: Style.radiusM + + // Use provider's image revision for reactive updates + readonly property int _rev: modelData.provider && modelData.provider.imageRevision ? modelData.provider.imageRevision : 0 + + // Get image URL from provider + imagePath: { + _rev; + var provider = modelData.provider; + if (provider && provider.getImageUrl) { + return provider.getImageUrl(modelData); + } + return ""; + } + + Rectangle { + anchors.fill: parent + visible: parent.status === Image.Loading + color: Color.mSurfaceVariant + + BusyIndicator { + anchors.centerIn: parent + running: true + width: Style.baseWidgetSize * 0.5 + height: width + } + } + + onStatusChanged: status => { + if (status === Image.Error) { + gridIconLoader.visible = true; + gridImagePreview.visible = false; + } + } + } + + Loader { + id: gridIconLoader + anchors.fill: parent + anchors.margins: Style.marginXS + + visible: (!modelData.isImage && !modelData.displayString) || (!!modelData.isImage && gridImagePreview.status === Image.Error) + active: visible + + sourceComponent: Settings.data.appLauncher.iconMode === "tabler" && modelData.isTablerIcon ? gridTablerIconComponent : gridSystemIconComponent + + Component { + id: gridTablerIconComponent + NIcon { + icon: modelData.icon + pointSize: Style.fontSizeXXXL + visible: modelData.icon && !modelData.displayString + color: (gridEntryContainer.isSelected && !Settings.data.appLauncher.showIconBackground) ? Color.mOnHover : Color.mOnSurface + } + } + + Component { + id: gridSystemIconComponent + IconImage { + anchors.fill: parent + source: modelData.icon ? ThemeIcons.iconFromName(modelData.icon, "application-x-executable") : "" + visible: modelData.icon && source !== "" && !modelData.displayString + asynchronous: true + } + } + } + + // String display + NText { + id: gridStringDisplay + anchors.centerIn: parent + visible: !!modelData.displayString || (!gridImagePreview.visible && !gridIconLoader.visible) + text: modelData.displayString ? modelData.displayString : (modelData.name ? modelData.name.charAt(0).toUpperCase() : "?") + pointSize: { + if (modelData.displayString) { + // Use custom size if provided, otherwise default scaling + if (modelData.displayStringSize) { + return modelData.displayStringSize * Style.uiScaleRatio; + } + if (launcher.providerHasDisplayString) { + // Scale with cell width but cap at reasonable maximum + const cellBasedSize = gridEntry.width * 0.4; + const maxSize = Style.fontSizeXXXL * Style.uiScaleRatio; + return Math.min(cellBasedSize, maxSize); + } + return Style.fontSizeXXL * 2 * Style.uiScaleRatio; + } + // Scale font size relative to cell width for low res, but cap at maximum + const cellBasedSize = gridEntry.width * 0.25; + const baseSize = Style.fontSizeXL * Style.uiScaleRatio; + const maxSize = Style.fontSizeXXL * Style.uiScaleRatio; + return Math.min(Math.max(cellBasedSize, baseSize), maxSize); + } + font.weight: Style.fontWeightBold + color: modelData.displayString ? Color.mOnSurface : Color.mOnPrimary + } + + // Badge icon overlay (generic indicator for any provider) + Rectangle { + visible: !!modelData.badgeIcon + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.margins: 2 + width: height + height: Style.fontSizeM + Style.marginXS + color: Color.mSurfaceVariant + radius: Style.radiusXXS + NIcon { + anchors.centerIn: parent + icon: modelData.badgeIcon || "" + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } + + // Text content (hidden when hideLabel is true) + NText { + visible: !modelData.hideLabel + text: modelData.name || "Unknown" + pointSize: { + if (launcher.providerHasDisplayString && modelData.displayString) { + return Style.fontSizeS * Style.uiScaleRatio; + } + // Scale font size relative to cell width for low res, but cap at maximum + const cellBasedSize = gridEntry.width * 0.1; + const baseSize = Style.fontSizeXS * Style.uiScaleRatio; + const maxSize = Style.fontSizeS * Style.uiScaleRatio; + return Math.min(Math.max(cellBasedSize, baseSize), maxSize); + } + font.weight: Style.fontWeightSemiBold + color: gridEntryContainer.isSelected ? Color.mOnHover : Color.mOnSurface + elide: Text.ElideRight + Layout.fillWidth: true + Layout.maximumWidth: gridEntry.width - 8 + Layout.leftMargin: (launcher.providerHasDisplayString && modelData.displayString) ? Style.marginS : 0 + Layout.rightMargin: (launcher.providerHasDisplayString && modelData.displayString) ? Style.marginS : 0 + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.NoWrap + maximumLineCount: 1 + } + } + + // Action buttons (overlay in top-right corner) - dynamically populated from provider + Row { + visible: gridEntryContainer.isSelected && gridItemActions.length > 0 + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: Style.marginXS + z: 10 + spacing: Style.marginXXS + + property var gridItemActions: { + if (!gridEntryContainer.isSelected) + return []; + var provider = modelData.provider || launcher.currentProvider; + if (provider && provider.getItemActions) { + return provider.getItemActions(modelData); + } + return []; + } + + Repeater { + model: parent.gridItemActions + NIconButton { + required property var modelData + icon: modelData.icon + baseSize: Style.baseWidgetSize * 0.75 + tooltipText: modelData.tooltip + z: 11 + handleWheel: true + onClicked: { + if (modelData.action) { + modelData.action(); + } + } + } + } + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + z: -1 + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + enabled: !Settings.data.appLauncher.ignoreMouseInput + + onEntered: { + if (!launcher.ignoreMouseHover) { + launcher.selectedIndex = gridEntryContainer.index; + } + } + onClicked: mouse => { + if (mouse.button === Qt.LeftButton) { + launcher.selectedIndex = gridEntryContainer.index; + launcher.activate(); + mouse.accepted = true; + } + } + acceptedButtons: Qt.LeftButton + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/LauncherListDelegate.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/LauncherListDelegate.qml new file mode 100644 index 0000000..57cdeb5 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/LauncherListDelegate.qml @@ -0,0 +1,294 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Widgets + +import qs.Commons +import qs.Widgets + +NBox { + id: entry + + required property var modelData + required property int index + required property var launcher + + property bool isSelected: (!launcher.ignoreMouseHover && mouseArea.containsMouse) || (index === launcher.selectedIndex) + + width: ListView.view.width + implicitHeight: launcher.entryHeight + clip: true + color: entry.isSelected ? Color.mHover : Color.mSurfaceVariant + forceOpaque: entry.isSelected + + // Prepare item when it becomes visible (e.g., decode images) + Component.onCompleted: { + var provider = modelData.provider; + if (provider && provider.prepareItem) { + provider.prepareItem(modelData); + } + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCirc + } + } + + ColumnLayout { + id: contentLayout + anchors.fill: parent + anchors.margins: launcher.isCompactDensity ? Style.marginXS : Style.marginM + spacing: launcher.isCompactDensity ? Style.marginXS : Style.marginM + + // Top row - Main entry content with action buttons + RowLayout { + Layout.fillWidth: true + spacing: launcher.isCompactDensity ? Style.marginS : Style.marginM + + // Icon badge or Image preview or Emoji + Item { + visible: !modelData.hideIcon + Layout.preferredWidth: modelData.hideIcon ? 0 : launcher.badgeSize + Layout.preferredHeight: modelData.hideIcon ? 0 : launcher.badgeSize + + // Icon background + Rectangle { + anchors.fill: parent + radius: Style.radiusXS + color: Color.mSurface + visible: Settings.data.appLauncher.showIconBackground && !modelData.isImage + } + + // Image preview - uses provider's getImageUrl if available + NImageRounded { + id: imagePreview + anchors.fill: parent + visible: !!modelData.isImage && !modelData.displayString + radius: Style.radiusXS + borderColor: Color.mOnSurface + borderWidth: Style.borderM + imageFillMode: Image.PreserveAspectCrop + + // Use provider's image revision for reactive updates + readonly property int _rev: modelData.provider && modelData.provider.imageRevision ? modelData.provider.imageRevision : 0 + + // Get image URL from provider + imagePath: { + _rev; + var provider = modelData.provider; + if (provider && provider.getImageUrl) { + return provider.getImageUrl(modelData); + } + return ""; + } + + Rectangle { + anchors.fill: parent + visible: parent.status === Image.Loading + color: Color.mSurfaceVariant + + BusyIndicator { + anchors.centerIn: parent + running: true + width: Style.baseWidgetSize * 0.5 + height: width + } + } + + onStatusChanged: status => { + if (status === Image.Error) { + iconLoader.visible = true; + imagePreview.visible = false; + } + } + } + + // Color swatch - shown for clipboard color entries + Rectangle { + anchors.fill: parent + radius: Style.radiusXS + color: modelData.colorHex || "transparent" + visible: !!modelData.colorHex + border.color: Color.mOnSurface + border.width: Style.borderM + } + + Loader { + id: iconLoader + anchors.fill: parent + anchors.margins: Style.marginXS + + visible: (!modelData.isImage && !modelData.displayString && !modelData.colorHex) || (!!modelData.isImage && imagePreview.status === Image.Error) + active: visible + + sourceComponent: Component { + Loader { + anchors.fill: parent + sourceComponent: Settings.data.appLauncher.iconMode === "tabler" && modelData.isTablerIcon ? tablerIconComponent : systemIconComponent + } + } + + Component { + id: tablerIconComponent + NIcon { + icon: modelData.icon + pointSize: Style.fontSizeXXXL + visible: modelData.icon && !modelData.displayString + color: (entry.isSelected && !Settings.data.appLauncher.showIconBackground) ? Color.mOnHover : Color.mOnSurface + } + } + + Component { + id: systemIconComponent + IconImage { + anchors.fill: parent + source: modelData.icon ? ThemeIcons.iconFromName(modelData.icon, "application-x-executable") : "" + visible: modelData.icon && source !== "" && !modelData.displayString + asynchronous: true + } + } + } + + // String display - takes precedence when displayString is present + NText { + id: stringDisplay + anchors.centerIn: parent + visible: !!modelData.displayString || (!imagePreview.visible && !iconLoader.visible) + text: modelData.displayString ? modelData.displayString : (modelData.name ? modelData.name.charAt(0).toUpperCase() : "?") + pointSize: modelData.displayString ? (modelData.displayStringSize || Style.fontSizeXXXL) : Style.fontSizeXXL + font.weight: Style.fontWeightBold + color: modelData.displayString ? Color.mOnSurface : Color.mOnPrimary + } + + // Image type indicator overlay + Rectangle { + visible: !!modelData.isImage && imagePreview.visible + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.margins: 2 + width: formatLabel.width + Style.marginXS + height: formatLabel.height + Style.marginXXS + color: Color.mSurfaceVariant + radius: Style.radiusXXS + NText { + id: formatLabel + anchors.centerIn: parent + text: { + if (!modelData.isImage) + return ""; + const desc = modelData.description || ""; + const parts = desc.split(" \u2022 "); + return parts[0] || "IMG"; + } + pointSize: Style.fontSizeXXS + color: Color.mOnSurfaceVariant + } + } + + // Badge icon overlay (generic indicator for any provider) + Rectangle { + visible: !!modelData.badgeIcon + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.margins: 2 + width: height + height: Style.fontSizeM + Style.marginXS + color: Color.mSurfaceVariant + radius: Style.radiusXXS + NIcon { + anchors.centerIn: parent + icon: modelData.badgeIcon || "" + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } + + // Text content + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + NText { + text: modelData.name || "Unknown" + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: entry.isSelected ? Color.mOnHover : Color.mOnSurface + elide: Text.ElideRight + maximumLineCount: 1 + wrapMode: Text.Wrap + clip: true + Layout.fillWidth: true + } + + NText { + text: modelData.description || "" + pointSize: Style.fontSizeS + color: entry.isSelected ? Color.mOnHover : Color.mOnSurfaceVariant + elide: Text.ElideRight + maximumLineCount: 1 + Layout.fillWidth: true + visible: text !== "" && !launcher.isCompactDensity + } + } + + // Action buttons row - dynamically populated from provider + RowLayout { + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + spacing: Style.marginXS + visible: entry.isSelected && itemActions.length > 0 + + property var itemActions: { + if (!entry.isSelected) + return []; + var provider = modelData.provider || launcher.currentProvider; + if (provider && provider.getItemActions) { + return provider.getItemActions(modelData); + } + return []; + } + + Repeater { + model: parent.itemActions + NIconButton { + required property var modelData + icon: modelData.icon + baseSize: Style.baseWidgetSize * 0.75 + tooltipText: modelData.tooltip + z: 1 + handleWheel: true + onClicked: { + if (modelData.action) { + modelData.action(); + } + } + } + } + } + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + z: -1 + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + enabled: !Settings.data.appLauncher.ignoreMouseInput + onEntered: { + if (!launcher.ignoreMouseHover) { + launcher.selectedIndex = entry.index; + } + } + onClicked: mouse => { + if (mouse.button === Qt.LeftButton) { + launcher.selectedIndex = entry.index; + launcher.activate(); + mouse.accepted = true; + } + } + acceptedButtons: Qt.LeftButton + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/LauncherOverlayWindow.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/LauncherOverlayWindow.qml new file mode 100644 index 0000000..4769906 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/LauncherOverlayWindow.qml @@ -0,0 +1,426 @@ +import QtQuick +import QtQuick.Shapes +import Quickshell +import Quickshell.Wayland + +import qs.Commons +import qs.Modules.MainScreen.Backgrounds +import qs.Services.UI +import qs.Widgets + +// Standalone launcher window for Overlay layer mode. +// This window appears above fullscreen windows and does not attach to the bar. +Variants { + id: launcherVariants + + model: Quickshell.screens.filter(screen => Settings.data.appLauncher.overviewLayer) + + delegate: Loader { + id: windowLoader + + required property ShellScreen modelData + + active: PanelService.overlayLauncherOpen && PanelService.overlayLauncherScreen === modelData + + sourceComponent: PanelWindow { + id: launcherWindow + screen: windowLoader.modelData + + anchors { + top: true + bottom: true + left: true + right: true + } + + color: "transparent" + + WlrLayershell.namespace: "noctalia-launcher-overlay-" + (screen?.name || "unknown") + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.exclusionMode: ExclusionMode.Ignore + + BackgroundEffect.blurRegion: Settings.data.general.enableBlurBehind ? launcherBlurRegion : null + Region { + id: launcherBlurRegion + + Region { + x: Math.round(launcherPanel.x) + y: Math.round(launcherPanel.y) + width: Math.round(launcherPanel.width) + height: Math.round(launcherPanel.height) + radius: Style.radiusL + topLeftCorner: launcherPanel.topLeftCornerState + topRightCorner: launcherPanel.topRightCornerState + bottomLeftCorner: launcherPanel.bottomLeftCornerState + bottomRightCorner: launcherPanel.bottomRightCornerState + } + + Region { + x: Math.round(previewBox.visible ? previewBox.x : 0) + y: Math.round(previewBox.visible ? previewBox.y : 0) + width: Math.round(previewBox.visible ? previewBox.width : 0) + height: Math.round(previewBox.visible ? previewBox.height : 0) + radius: Style.radiusL + } + } + + // Positioning logic (respects settings but doesn't attach to bar) + readonly property string barPosition: Settings.data.bar.position + readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" + readonly property int barThickness: Math.round(Style.barHeight + Style.marginL) + + readonly property string panelPosition: { + var pos = Settings.data.appLauncher.position; + if (pos === "follow_bar") { + if (barIsVertical) { + return "center_" + barPosition; + } else { + return barPosition + "_center"; + } + } + return pos; + } + + // Preview panel support + readonly property int listPanelWidth: Math.round(500 * Style.uiScaleRatio) + readonly property int previewPanelWidth: Math.round(400 * Style.uiScaleRatio) + readonly property bool previewActive: { + if (!launcherCore) + return false; + var provider = launcherCore.activeProvider; + if (!provider || !provider.hasPreview) + return false; + if (!Settings.data.appLauncher.enableClipPreview) + return false; + return launcherCore.selectedIndex >= 0 && launcherCore.results && !!launcherCore.results[launcherCore.selectedIndex]; + } + + // Dimmer background (click to close) + Rectangle { + anchors.fill: parent + color: Qt.alpha(Color.mSurface, Settings.data.general.dimmerOpacity) + + MouseArea { + anchors.fill: parent + onClicked: PanelService.closeOverlayLauncher() + } + } + + // Shadow for launcher panel + NDropShadow { + source: launcherPanel + anchors.fill: launcherPanel + autoPaddingEnabled: true + } + + // Launcher panel with position-based anchoring + Item { + id: launcherPanel + width: Math.round(Math.max(parent.width * 0.25, launcherWindow.listPanelWidth + Style.margin2L * 2)) + height: Math.round(Math.max(parent.height * 0.5, 600 * Style.uiScaleRatio)) + clip: false + + // Entrance animation + opacity: 0 + transformOrigin: { + if (touchingTop && touchingLeft) + return Item.TopLeft; + if (touchingTop && touchingRight) + return Item.TopRight; + if (touchingBottom && touchingLeft) + return Item.BottomLeft; + if (touchingBottom && touchingRight) + return Item.BottomRight; + if (touchingTop) + return Item.Top; + if (touchingBottom) + return Item.Bottom; + if (touchingLeft) + return Item.Left; + if (touchingRight) + return Item.Right; + return Item.Center; + } + + Component.onCompleted: { + opacity = 1; + } + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + + // Horizontal positioning + anchors.horizontalCenter: (panelPosition === "center" || panelPosition.endsWith("_center")) ? parent.horizontalCenter : undefined + anchors.left: panelPosition.endsWith("_left") ? parent.left : undefined + anchors.right: panelPosition.endsWith("_right") ? parent.right : undefined + + // Vertical positioning + anchors.verticalCenter: (panelPosition === "center" || panelPosition.startsWith("center_")) ? parent.verticalCenter : undefined + anchors.top: panelPosition.startsWith("top_") ? parent.top : undefined + anchors.bottom: panelPosition.startsWith("bottom_") ? parent.bottom : undefined + + // Margins - only add bar clearance on the bar's edge + anchors.leftMargin: barPosition === "left" ? barThickness : 0 + anchors.rightMargin: barPosition === "right" ? barThickness : 0 + anchors.topMargin: barPosition === "top" ? barThickness : 0 + anchors.bottomMargin: barPosition === "bottom" ? barThickness : 0 + + // Edge detection - based on position setting and bar location + readonly property bool touchingLeft: panelPosition.endsWith("_left") && barPosition !== "left" + readonly property bool touchingRight: panelPosition.endsWith("_right") && barPosition !== "right" + readonly property bool touchingTop: panelPosition.startsWith("top_") && barPosition !== "top" + readonly property bool touchingBottom: panelPosition.startsWith("bottom_") && barPosition !== "bottom" + + // Corner states based on edge touching + // State 0: Normal rounded, State 1: Horizontal inversion, State 2: Vertical inversion + readonly property int topLeftCornerState: { + if (touchingLeft && touchingTop) + return 0; + if (touchingLeft) + return 2; + if (touchingTop) + return 1; + return 0; + } + readonly property int topRightCornerState: { + if (touchingRight && touchingTop) + return 0; + if (touchingRight) + return 2; + if (touchingTop) + return 1; + return 0; + } + readonly property int bottomLeftCornerState: { + if (touchingLeft && touchingBottom) + return 0; + if (touchingLeft) + return 2; + if (touchingBottom) + return 1; + return 0; + } + readonly property int bottomRightCornerState: { + if (touchingRight && touchingBottom) + return 0; + if (touchingRight) + return 2; + if (touchingBottom) + return 1; + return 0; + } + + // Background with inverted corners - extends beyond panel for inverted corners + Shape { + id: panelShape + // Extend shape to allow inverted corners to render outside panel bounds + x: -radius + y: -radius + width: launcherPanel.width + radius * 2 + height: launcherPanel.height + radius * 2 + visible: panelW > 0 && panelH > 0 + opacity: launcherPanel.opacity + layer.enabled: true + + readonly property real radius: Style.radiusL + + // Panel dimensions (for path calculations) + readonly property real panelW: launcherPanel.width + readonly property real panelH: launcherPanel.height + + // Helper functions for corner rendering + function getMultX(state) { + return state === 1 ? -1 : 1; + } + function getMultY(state) { + return state === 2 ? -1 : 1; + } + function getArcDir(multX, multY) { + return ((multX < 0) !== (multY < 0)) ? PathArc.Counterclockwise : PathArc.Clockwise; + } + + readonly property real tlMultX: getMultX(launcherPanel.topLeftCornerState) + readonly property real tlMultY: getMultY(launcherPanel.topLeftCornerState) + readonly property real trMultX: getMultX(launcherPanel.topRightCornerState) + readonly property real trMultY: getMultY(launcherPanel.topRightCornerState) + readonly property real blMultX: getMultX(launcherPanel.bottomLeftCornerState) + readonly property real blMultY: getMultY(launcherPanel.bottomLeftCornerState) + readonly property real brMultX: getMultX(launcherPanel.bottomRightCornerState) + readonly property real brMultY: getMultY(launcherPanel.bottomRightCornerState) + + ShapePath { + strokeWidth: -1 + fillColor: Qt.alpha(Color.mSurface, Color.adaptiveOpacity(Settings.data.ui.panelBackgroundOpacity)) + + // Offset by radius to account for Shape's extended bounds + startX: panelShape.radius + panelShape.radius * panelShape.tlMultX + startY: panelShape.radius + + // Top edge + PathLine { + relativeX: panelShape.panelW - panelShape.radius * panelShape.tlMultX - panelShape.radius * panelShape.trMultX + relativeY: 0 + } + // Top-right corner + PathArc { + relativeX: panelShape.radius * panelShape.trMultX + relativeY: panelShape.radius * panelShape.trMultY + radiusX: panelShape.radius + radiusY: panelShape.radius + direction: panelShape.getArcDir(panelShape.trMultX, panelShape.trMultY) + } + // Right edge + PathLine { + relativeX: 0 + relativeY: panelShape.panelH - panelShape.radius * panelShape.trMultY - panelShape.radius * panelShape.brMultY + } + // Bottom-right corner + PathArc { + relativeX: -panelShape.radius * panelShape.brMultX + relativeY: panelShape.radius * panelShape.brMultY + radiusX: panelShape.radius + radiusY: panelShape.radius + direction: panelShape.getArcDir(panelShape.brMultX, panelShape.brMultY) + } + // Bottom edge + PathLine { + relativeX: -(panelShape.panelW - panelShape.radius * panelShape.brMultX - panelShape.radius * panelShape.blMultX) + relativeY: 0 + } + // Bottom-left corner + PathArc { + relativeX: -panelShape.radius * panelShape.blMultX + relativeY: -panelShape.radius * panelShape.blMultY + radiusX: panelShape.radius + radiusY: panelShape.radius + direction: panelShape.getArcDir(panelShape.blMultX, panelShape.blMultY) + } + // Left edge + PathLine { + relativeX: 0 + relativeY: -(panelShape.panelH - panelShape.radius * panelShape.blMultY - panelShape.radius * panelShape.tlMultY) + } + // Top-left corner + PathArc { + relativeX: panelShape.radius * panelShape.tlMultX + relativeY: -panelShape.radius * panelShape.tlMultY + radiusX: panelShape.radius + radiusY: panelShape.radius + direction: panelShape.getArcDir(panelShape.tlMultX, panelShape.tlMultY) + } + } + } + + // Border + Rectangle { + anchors.fill: parent + color: "transparent" + radius: Style.radiusL + border.color: Style.boxBorderColor + border.width: Style.borderS + visible: !launcherPanel.touchingLeft && !launcherPanel.touchingRight && !launcherPanel.touchingTop && !launcherPanel.touchingBottom + } + + LauncherCore { + id: launcherCore + anchors.fill: parent + screen: windowLoader.modelData + isOpen: true + onRequestClose: PanelService.closeOverlayLauncher() + onRequestCloseImmediately: PanelService.closeOverlayLauncherImmediately() + + Component.onCompleted: PanelService.overlayLauncherCore = launcherCore + Component.onDestruction: PanelService.overlayLauncherCore = null + } + } + + // Preview Panel - positioned as sibling of launcherPanel to avoid shadow bleed + NDropShadow { + source: previewBox + anchors.fill: previewBox + autoPaddingEnabled: true + visible: previewBox.visible + z: previewBox.z - 1 + } + + NBox { + id: previewBox + visible: launcherWindow.previewActive + width: launcherWindow.previewPanelWidth + height: Math.round(400 * Style.uiScaleRatio) + forceOpaque: true + x: { + if (panelPosition.endsWith("_right")) + return launcherPanel.x - launcherWindow.previewPanelWidth - Style.marginM; + return launcherPanel.x + launcherPanel.width + Style.marginM; + } + y: { + var view = launcherCore.resultsView; + if (!view) + return launcherPanel.y + Style.marginL; + var row = launcherCore.isGridView ? Math.floor(launcherCore.selectedIndex / launcherCore.gridColumns) : launcherCore.selectedIndex; + var gridCellSize = Math.floor((launcherWindow.listPanelWidth - (2 * Style.marginXS) - ((launcherCore.targetGridColumns - 1) * Style.marginS)) / launcherCore.targetGridColumns); + var itemHeight = launcherCore.isGridView ? (gridCellSize + Style.marginXXS) : (launcherCore.entryHeight + (view.spacing || 0)); + var yPos = row * itemHeight - (view.contentY || 0); + var mapped = view.mapToItem(launcherWindow.contentItem, 0, yPos); + return Math.max(launcherPanel.y + Style.marginL, Math.min(mapped.y, launcherPanel.y + launcherPanel.height - previewBox.height - Style.marginL)); + } + + opacity: visible ? 1.0 : 0.0 + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + Behavior on y { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + Loader { + id: previewLoader + anchors.fill: parent + active: launcherWindow.previewActive + source: { + if (!active) + return ""; + var provider = launcherCore.activeProvider; + if (provider && provider.previewComponentPath) + return provider.previewComponentPath; + return ""; + } + + onLoaded: updatePreviewItem() + onItemChanged: updatePreviewItem() + + function updatePreviewItem() { + if (!item || launcherCore.selectedIndex < 0 || !launcherCore.results[launcherCore.selectedIndex]) + return; + var provider = launcherCore.activeProvider; + if (provider && provider.getPreviewData) { + item.currentItem = provider.getPreviewData(launcherCore.results[launcherCore.selectedIndex]); + } else { + item.currentItem = launcherCore.results[launcherCore.selectedIndex]; + } + } + } + } + + // Update preview when selection changes + Connections { + target: launcherCore + function onSelectedIndexChanged() { + if (previewLoader.item) + previewLoader.updatePreviewItem(); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/ApplicationsProvider.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/ApplicationsProvider.qml new file mode 100644 index 0000000..fac28e5 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/ApplicationsProvider.qml @@ -0,0 +1,654 @@ +import QtQuick +import Quickshell +import qs.Commons +import qs.Services.Compositor +import qs.Services.System + +Item { + id: root + + property var launcher: null + property string name: I18n.tr("launcher.providers.applications") + property bool handleSearch: true + property var entries: [] + property string supportedLayouts: "both" + property bool isDefaultProvider: true // This provider handles empty search + property bool ignoreDensity: false // Apps should scale with launcher density + property bool trackUsage: true // Track usage frequency for "most used" sorting + + // Category support + property string selectedCategory: "all" + property bool showsCategories: true // Default to showing categories + property var categories: ["all", "Pinned", "AudioVideo", "Chat", "Development", "Education", "Game", "Graphics", "Network", "Office", "System", "Misc", "WebBrowser"] + property var availableCategories: ["all"] // Reactive property for available categories + + property var categoryIcons: ({ + "all": "apps", + "Pinned": "pin", + "AudioVideo": "music", + "Chat": "message-circle", + "Development": "code", + "Education": "school" // Includes Science + , + "Game": "device-gamepad", + "Graphics": "brush", + "Network": "wifi", + "Office": "file-text", + "System": "device-desktop" // Includes Settings and Utility + , + "Misc": "dots", + "WebBrowser": "world" + }) + + function getCategoryName(category) { + const names = { + "all": I18n.tr("launcher.categories.all"), + "Pinned": I18n.tr("launcher.categories.pinned"), + "AudioVideo": I18n.tr("launcher.categories.audiovideo"), + "Chat": I18n.tr("launcher.categories.chat"), + "Development": I18n.tr("launcher.categories.development"), + "Education": I18n.tr("launcher.categories.education"), + "Game": I18n.tr("launcher.categories.game"), + "Graphics": I18n.tr("launcher.categories.graphics"), + "Network": I18n.tr("common.network"), + "Office": I18n.tr("launcher.categories.office"), + "System": I18n.tr("launcher.categories.system"), + "Misc": I18n.tr("launcher.categories.misc"), + "WebBrowser": I18n.tr("launcher.categories.webbrowser") + }; + return names[category] || category; + } + + function init() { + loadApplications(); + migrateLegacyUsageKeys(); + } + + function onOpened() { + // Just update available categories in case pinned apps changed + updateAvailableCategories(); + // Default to Pinned if there are pinned apps, otherwise all + if (availableCategories.includes("Pinned")) { + selectedCategory = "Pinned"; + } else { + selectedCategory = "all"; + } + // Set category mode initially (will be updated when getResults is called) + showsCategories = true; + } + + // Reload applications when desktop entries change on disk + Connections { + target: typeof DesktopEntries !== 'undefined' ? DesktopEntries.applications : null + function onValuesChanged() { + Logger.d("ApplicationsProvider", "Desktop entries changed, reloading applications"); + loadApplications(); + } + } + + function selectCategory(category) { + selectedCategory = category; + if (launcher) { + launcher.updateResults(); + } + } + + function getAppCategories(app) { + if (!app) + return []; + + const result = []; + + if (app.categories) { + if (Array.isArray(app.categories)) { + for (let cat of app.categories) { + if (cat && cat.trim && cat.trim() !== '') { + result.push(cat.trim()); + } else if (cat && typeof cat === 'string' && cat.trim() !== '') { + result.push(cat.trim()); + } + } + } else if (typeof app.categories === 'string') { + const cats = app.categories.split(';').filter(c => c && c.trim() !== ''); + for (let cat of cats) { + const trimmed = cat.trim(); + if (trimmed && !result.includes(trimmed)) { + result.push(trimmed); + } + } + } else if (app.categories.length !== undefined) { + try { + for (let i = 0; i < app.categories.length; i++) { + const cat = app.categories[i]; + if (cat && cat.trim && typeof cat.trim === 'function' && cat.trim() !== '') { + result.push(cat.trim()); + } else if (cat && typeof cat === 'string' && cat.trim() !== '') { + result.push(cat.trim()); + } + } + } catch (e) {} + } + } + + if (app.Categories) { + const cats = app.Categories.split(';').filter(c => c && c.trim() !== ''); + for (let cat of cats) { + const trimmed = cat.trim(); + if (trimmed && !result.includes(trimmed)) { + result.push(trimmed); + } + } + } + + return result; + } + + function getAppCategory(app) { + const appCategories = getAppCategories(app); + if (appCategories.length === 0) + return null; + + const priorityCategories = ["AudioVideo", "Chat", "WebBrowser", "Game", "Development", "Graphics", "Office", "Education", "System", "Network", "Misc"]; + + for (let cat of appCategories) { + if (cat === "AudioVideo" || cat === "Audio" || cat === "Video") { + return "AudioVideo"; + } + } + + if (appCategories.includes("Chat") || appCategories.includes("InstantMessaging")) { + return "Chat"; + } + + if (appCategories.includes("WebBrowser")) { + return "WebBrowser"; + } + + // Map Science to Education + if (appCategories.includes("Science")) { + return "Education"; + } + + // Map Settings to System + if (appCategories.includes("Settings")) { + return "System"; + } + + // Map Utility to System + if (appCategories.includes("Utility")) { + return "System"; + } + + for (let priorityCat of priorityCategories) { + if (appCategories.includes(priorityCat) && root.categories.includes(priorityCat)) { + return priorityCat; + } + } + + return "Misc"; + } + + // Helper function to normalize app IDs for case-insensitive matching + function normalizeAppId(appId) { + if (!appId || typeof appId !== 'string') + return ""; + return appId.toLowerCase().trim(); + } + + // Helper function to check if an app is pinned + function isAppPinned(app) { + if (!app) + return false; + const pinnedApps = Settings.data.appLauncher.pinnedApps || []; + const appId = getAppKey(app); + const normalizedId = normalizeAppId(appId); + return pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedId); + } + + function appMatchesCategory(app, category) { + // Check if app matches the selected category + if (category === "all") + return true; + + // Handle Pinned category separately + if (category === "Pinned") { + return isAppPinned(app); + } + + // Get the primary category for this app (first matching standard category) + const primaryCategory = getAppCategory(app); + + // If app has no matching standard category, don't show it in any category (only in "all") + if (!primaryCategory) + return false; + + // Map Audio/Video to AudioVideo + if (category === "AudioVideo") { + const appCategories = getAppCategories(app); + // Show if app has AudioVideo, Audio, or Video + return appCategories.includes("AudioVideo") || appCategories.includes("Audio") || appCategories.includes("Video"); + } + + // Map Science to Education + if (category === "Education") { + const appCategories = getAppCategories(app); + return appCategories.includes("Education") || appCategories.includes("Science"); + } + + // Map Settings and Utility to System + if (category === "System") { + const appCategories = getAppCategories(app); + return appCategories.includes("System") || appCategories.includes("Settings") || appCategories.includes("Utility"); + } + + // Only show app in its primary category to avoid overlap + // This ensures each app appears in exactly one category tab + return category === primaryCategory; + } + + function getAvailableCategories() { + const categorySet = new Set(); + let hasAudioVideo = false; + let hasEducation = false; + let hasSystem = false; + let hasPinned = false; + + // Check if there are any pinned apps + const pinnedApps = Settings.data.appLauncher.pinnedApps || []; + if (pinnedApps.length > 0) { + // Verify that at least one pinned app exists in entries + for (let app of entries) { + if (isAppPinned(app)) { + hasPinned = true; + break; + } + } + } + + for (let app of entries) { + const appCategories = getAppCategories(app); + const primaryCategory = getAppCategory(app); + + if (appCategories.includes("AudioVideo") || appCategories.includes("Audio") || appCategories.includes("Video")) { + hasAudioVideo = true; + } else if (appCategories.includes("Education") || appCategories.includes("Science")) { + hasEducation = true; + } else if (appCategories.includes("System") || appCategories.includes("Settings") || appCategories.includes("Utility")) { + hasSystem = true; + } else if (primaryCategory && root.categories.includes(primaryCategory)) { + categorySet.add(primaryCategory); + } + } + + const result = []; + + // Add Pinned category first if there are pinned apps + if (hasPinned) { + result.push("Pinned"); + } + + result.push("all"); + + if (hasAudioVideo) { + categorySet.add("AudioVideo"); + } + if (hasEducation) { + categorySet.add("Education"); + } + if (hasSystem) { + categorySet.add("System"); + } + + for (let cat of root.categories) { + if (cat !== "all" && cat !== "Pinned" && cat !== "Misc" && categorySet.has(cat)) { + result.push(cat); + } + } + + if (categorySet.has("Misc")) { + result.push("Misc"); + } + + if (result.length === 1) { + const fallback = root.categories.filter(c => c !== "Misc"); + fallback.push("Misc"); + return fallback; + } + + return result; + } + + function loadApplications() { + if (typeof DesktopEntries === 'undefined') { + Logger.w("ApplicationsProvider", "DesktopEntries service not available"); + return; + } + + const allApps = DesktopEntries.applications.values || []; + const seen = new Map(); // Map of appId -> exec command + + entries = allApps.filter(app => { + if (!app || app.noDisplay || app.hidden) + return false; + + const appId = app.id || app.name; + const execCmd = getExecutableName(app); + + // Check if we've seen this app ID before + if (seen.has(appId)) { + const previousExec = seen.get(appId); + + // If exec is different, it's a legitimate different entry - keep it + if (previousExec !== execCmd) { + Logger.d("ApplicationsProvider", `Keeping variant of ${appId}: ${execCmd} (differs from ${previousExec})`); + // Add with modified ID to make it unique + app.id = `${appId}_${execCmd}`; + seen.set(app.id, execCmd); + return true; + } + + // Same appId AND same exec = true duplicate, skip it + Logger.d("ApplicationsProvider", `Skipping duplicate: ${appId}`); + return false; + } + + seen.set(appId, execCmd); + return true; + }).map(app => { + app.executableName = getExecutableName(app); + return app; + }); + + Logger.d("ApplicationsProvider", `Loaded ${entries.length} applications`); + updateAvailableCategories(); + } + + function updateAvailableCategories() { + availableCategories = getAvailableCategories(); + } + + Connections { + target: Settings.data.appLauncher + function onPinnedAppsChanged() { + const wasViewingPinned = selectedCategory === "Pinned"; + updateAvailableCategories(); + + // If we were viewing Pinned category and it's no longer available, switch to "all" + if (wasViewingPinned && !availableCategories.includes("Pinned")) { + selectedCategory = "all"; + } + + // Update results if we're currently viewing the Pinned category + if (selectedCategory === "Pinned" && launcher) { + launcher.updateResults(); + } else if (wasViewingPinned && selectedCategory === "all" && launcher) { + // Also update results when switching to "all" + launcher.updateResults(); + } + } + } + + function getExecutableName(app) { + if (!app) + return ""; + + // Try to get executable name from command array + if (app.command && Array.isArray(app.command) && app.command.length > 0) { + const cmd = app.command[0]; + // Extract just the executable name from the full path + const parts = cmd.split('/'); + const executable = parts[parts.length - 1]; + // Remove any arguments or parameters + return executable.split(' ')[0]; + } + + // Try to get from exec property if available + if (app.exec) { + const parts = app.exec.split('/'); + const executable = parts[parts.length - 1]; + return executable.split(' ')[0]; + } + + // Fallback to app id (desktop file name without .desktop) + if (app.id) { + return app.id.replace('.desktop', ''); + } + + return ""; + } + + function getResults(query) { + if (!entries || entries.length === 0) + return []; + + // Set category mode based on whether there's a query + const isSearching = !!(query && query.trim() !== ""); + showsCategories = !isSearching; + + // Filter by category only when NOT searching + let filteredEntries = entries; + if (!isSearching && selectedCategory && selectedCategory !== "all") { + filteredEntries = entries.filter(app => appMatchesCategory(app, selectedCategory)); + } + + if (!query || query.trim() === "") { + // Return filtered apps, optionally sorted by usage + let sorted; + if (Settings.data.appLauncher.sortByMostUsed) { + sorted = filteredEntries.slice().sort((a, b) => { + // Pinned first + const aPinned = isAppPinned(a); + const bPinned = isAppPinned(b); + if (aPinned !== bPinned) + return aPinned ? -1 : 1; + + const ua = getUsageCount(a); + const ub = getUsageCount(b); + if (ub !== ua) + return ub - ua; + return (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase()); + }); + } else { + sorted = filteredEntries.slice().sort((a, b) => { + const aPinned = isAppPinned(a); + const bPinned = isAppPinned(b); + if (aPinned !== bPinned) + return aPinned ? -1 : 1; + return (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase()); + }); + } + return sorted.map(app => createResultEntry(app)); + } + + // Use fuzzy search if available, fallback to simple search + if (typeof FuzzySort !== 'undefined') { + const fuzzyResults = FuzzySort.go(query, filteredEntries, { + "keys": ["name", "comment", "genericName", "executableName"], + "limit": 20 + }); + + // Sort pinned first within fuzzy results while preserving fuzzysort order otherwise + const pinned = []; + const nonPinned = []; + for (const r of fuzzyResults) { + const app = r.obj; + if (isAppPinned(app)) + pinned.push(r); + else + nonPinned.push(r); + } + return pinned.concat(nonPinned).map(result => createResultEntry(result.obj, result.score)); + } else { + // Fallback to simple search + const searchTerm = query.toLowerCase(); + return filteredEntries.filter(app => { + const name = (app.name || "").toLowerCase(); + const comment = (app.comment || "").toLowerCase(); + const generic = (app.genericName || "").toLowerCase(); + const executable = getExecutableName(app).toLowerCase(); + return name.includes(searchTerm) || comment.includes(searchTerm) || generic.includes(searchTerm) || executable.includes(searchTerm); + }).sort((a, b) => { + // Prioritize name matches, then executable matches + const aName = a.name.toLowerCase(); + const bName = b.name.toLowerCase(); + const aExecutable = getExecutableName(a).toLowerCase(); + const bExecutable = getExecutableName(b).toLowerCase(); + const aStarts = aName.startsWith(searchTerm); + const bStarts = bName.startsWith(searchTerm); + const aExecStarts = aExecutable.startsWith(searchTerm); + const bExecStarts = bExecutable.startsWith(searchTerm); + + // Prioritize name matches first + if (aStarts && !bStarts) + return -1; + if (!aStarts && bStarts) + return 1; + + // Then prioritize executable matches + if (aExecStarts && !bExecStarts) + return -1; + if (!aExecStarts && bExecStarts) + return 1; + + return aName.localeCompare(bName); + }).slice(0, 20).map(app => createResultEntry(app)); + } + } + + function createResultEntry(app, score) { + return { + "appId": getAppKey(app), + "usageKey": getAppKey(app), + "name": app.name || "Unknown", + "description": app.genericName || app.comment || "", + "icon": app.icon || "application-x-executable", + "isImage": false, + "_score": (score !== undefined ? score : 0), + "provider": root, + "onActivate": function () { + // Close the launcher/SmartPanel immediately without any animations. + // Ensures we are not preventing the future focusing of the app + launcher.closeImmediately(); + + // Defer execution to next event loop iteration to ensure panel is fully closed + Qt.callLater(() => { + Logger.d("ApplicationsProvider", `Launching: ${app.name} (App ID: ${app.id || "unknown"})`); + + const execString = (app.exec !== undefined && app.exec !== null) ? String(app.exec) : ""; + const commandArgs = Array.isArray(app.command) ? app.command : (app.command && app.command.length !== undefined) ? Array.from(app.command) : []; + let hasQuotedArgs = execString.includes("\"") || execString.includes("'"); + let hasSpaceArgs = false; + if (!hasQuotedArgs) { + hasQuotedArgs = commandArgs.some(arg => { + const text = String(arg); + return text.includes("\"") || text.includes("'"); + }); + } + if (!hasSpaceArgs) { + hasSpaceArgs = commandArgs.some(arg => String(arg).includes(" ")); + } + if (app.execute && (hasQuotedArgs || hasSpaceArgs)) { + Logger.d("ApplicationsProvider", `Detected quoted/space arguments in Exec for ${app.name}, using app.execute()`); + app.execute(); + return; + } + + if (Settings.data.appLauncher.customLaunchPrefixEnabled && Settings.data.appLauncher.customLaunchPrefix) { + // Use custom launch prefix + const prefix = Settings.data.appLauncher.customLaunchPrefix.split(" "); + Logger.d("ApplicationsProvider", `Using custom launch prefix: ${Settings.data.appLauncher.customLaunchPrefix}`); + + if (app.runInTerminal) { + const terminal = Settings.data.appLauncher.terminalCommand.split(" "); + const command = prefix.concat(terminal.concat(app.command)); + Logger.d("ApplicationsProvider", `Executing command (with prefix and terminal): ${command.join(" ")}`); + Quickshell.execDetached(command); + } else { + const command = prefix.concat(app.command); + Logger.d("ApplicationsProvider", `Executing command (with prefix): ${command.join(" ")}`); + Quickshell.execDetached(command); + } + } else { + if (app.runInTerminal) { + Logger.d("ApplicationsProvider", "Executing terminal app manually: " + app.name); + const terminal = Settings.data.appLauncher.terminalCommand.split(" "); + const command = terminal.concat(app.command); + Logger.d("ApplicationsProvider", "Executing command (manual terminal): " + command.join(" ")); + CompositorService.spawn(command); + } else if (app.command && app.command.length > 0) { + Logger.d("ApplicationsProvider", "Executing command: " + app.command.join(" ")); + CompositorService.spawn(app.command); + } else if (app.execute) { + Logger.d("ApplicationsProvider", "Calling app.execute() for: " + app.name); + app.execute(); + } else { + Logger.w("ApplicationsProvider", `Could not launch: ${app.name}. No valid launch method.`); + } + } + }); + } + }; + } + + // ------------------------- + // Item actions for launcher delegate + function getItemActions(item) { + if (!item || !item.appId) + return []; + return [ + { + "icon": isAppPinned({ + "id": item.appId + }) ? "unpin" : "pin", + "tooltip": isAppPinned({ + "id": item.appId + }) ? I18n.tr("common.unpin") : I18n.tr("common.pin"), + "action": function () { + togglePin(item.appId); + } + } + ]; + } + + function togglePin(appId) { + if (!appId) + return; + const normalizedId = normalizeAppId(appId); + let arr = (Settings.data.appLauncher.pinnedApps || []).slice(); + const idx = arr.findIndex(pinnedId => normalizeAppId(pinnedId) === normalizedId); + if (idx >= 0) + arr.splice(idx, 1); + else + arr.push(appId); + Settings.data.appLauncher.pinnedApps = arr; + } + + // ------------------------- + // Usage tracking helpers + function getAppKey(app) { + if (app && app.id) + return String(app.id); + if (app && app.command && app.command.join) + return app.command.join(" "); + return String(app && app.name ? app.name : "unknown"); + } + + function getUsageCount(app) { + return ShellState.getLauncherUsageCount(getAppKey(app)); + } + + // Migrate legacy command-based usage keys to canonical app-id keys at startup + function migrateLegacyUsageKeys() { + for (let i = 0; i < entries.length; i++) { + const app = entries[i]; + if (app && app.id && app.command && app.command.join) { + const key = getAppKey(app); + const legacyKey = app.command.join(" "); + if (legacyKey !== key && ShellState.getLauncherUsageCount(legacyKey) > 0) { + ShellState.migrateLauncherUsage(legacyKey, key); + Logger.d("ApplicationsProvider", `Migrated usage: "${legacyKey}" โ†’ "${key}"`); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/CalculatorProvider.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/CalculatorProvider.qml new file mode 100644 index 0000000..18b73d1 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/CalculatorProvider.qml @@ -0,0 +1,77 @@ +import QtQuick +import Quickshell +import "../../../../Helpers/AdvancedMath.js" as AdvancedMath +import qs.Commons +import qs.Services.Keyboard +import qs.Services.UI + +Item { + id: root + + // Provider metadata + property string name: I18n.tr("launcher.providers.calculator") + property var launcher: null + property string iconMode: Settings.data.appLauncher.iconMode + property bool handleSearch: true // Contribute to regular search + property string supportedLayouts: "list" + + // Initialize provider + function init() { + Logger.d("CalculatorProvider", "Initialized"); + } + + // Get search results - evaluates math expressions inline + function getResults(query) { + if (!query) + return []; + + const trimmed = query.trim(); + if (!trimmed || !isMathExpression(trimmed)) + return []; + + try { + const result = AdvancedMath.evaluate(trimmed); + const formattedResult = AdvancedMath.formatResult(result); + return [ + { + "name": formattedResult, + "description": I18n.tr("launcher.providers.calculator-press-enter-to-copy"), + "icon": iconMode === "tabler" ? "calculator" : "accessories-calculator", + "isTablerIcon": true, + "isImage": false, + "provider": root, + "onActivate": function () { + // Copy result to clipboard via xclip + Quickshell.execDetached(["sh", "-c", "echo -n '" + formattedResult.replace(/'/g, "'\\''") + "' | wl-copy"]); + ToastService.showNotice(I18n.tr("common.copied-to-clipboard"), formattedResult); + if (launcher) + launcher.close(); + } + } + ]; + } catch (error) { + return []; + } + } + + // Check if a string is a valid math expression + function isMathExpression(expr) { + // Allow: digits, operators, parentheses, decimal points, whitespace, letters (for functions), commas + if (!/^[\d\s\+\-\*\/\(\)\.\%\^a-zA-Z,]+$/.test(expr)) + return false; + + // Must contain at least one operator OR a function call (letter followed by parenthesis) + if (!/[+\-*/%\^]/.test(expr) && !/[a-zA-Z]\s*\(/.test(expr)) + return false; + + // Reject if ends with an operator (incomplete expression) + if (/[+\-*/%\^]\s*$/.test(expr)) + return false; + + // Reject if it's just letters (would match app names) + if (/^[a-zA-Z\s]+$/.test(expr)) + return false; + + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/ClipboardProvider.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/ClipboardProvider.qml new file mode 100644 index 0000000..ddb8aa1 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/ClipboardProvider.qml @@ -0,0 +1,457 @@ +import QtQuick +import Quickshell +import qs.Commons +import qs.Services.Keyboard +import qs.Services.Noctalia + +Item { + id: root + + // Provider metadata + property string name: I18n.tr("launcher.providers.clipboard") + property var launcher: null + property string iconMode: Settings.data.appLauncher.iconMode + property string supportedLayouts: "list" // List view for clipboard content + property bool wrapNavigation: false // Don't wrap at end of list + + // Provider capabilities + property bool handleSearch: false // Don't handle regular search + + // Preview support + property bool hasPreview: Settings.data.appLauncher.enableClipPreview + property string previewComponentPath: "./ClipboardPreview.qml" + + // Image handling - expose revision for reactive updates in delegates + readonly property int imageRevision: ClipboardService.revision + + // Categories + property var availableCategories: Settings.data.appLauncher.enableClipboardChips ? ["All", "Images", "Links", "Files", "Code", "Colors"] : [] + property string selectedCategory: "All" + + function selectCategory(cat) { + if (selectedCategory !== cat) { + selectedCategory = cat; + if (launcher) { + launcher.updateResults(); + } + } + } + + property var categoryIcons: { + "All": iconMode === "tabler" ? "border-all" : "view-grid", + "Images": iconMode === "tabler" ? "photo" : "image", + "Links": iconMode === "tabler" ? "link" : "insert-link", + "Files": iconMode === "tabler" ? "file" : "text-x-generic", + "Code": iconMode === "tabler" ? "code" : "text-x-script", + "Colors": iconMode === "tabler" ? "palette" : "color-picker" + } + + // Internal state + property bool isWaitingForData: false + property bool gotResults: false + property string lastSearchText: "" + + // Listen for clipboard data updates + Connections { + target: ClipboardService + function onListCompleted() { + if (gotResults && (lastSearchText === searchText)) { + // Do not update results after the first fetch. + // This will avoid the list resetting every 2seconds when the service updates. + return; + } + // Refresh results if we're waiting for data or if clipboard plugin is active + if (isWaitingForData || (launcher && launcher.searchText.startsWith(">clip"))) { + isWaitingForData = false; + gotResults = true; + if (launcher) { + launcher.updateResults(); + } + } + } + function onActiveChanged() { + // When active state changes (e.g. dependency check completes), refresh results + if (ClipboardService.active && launcher && launcher.searchText.startsWith(">clip")) { + isWaitingForData = true; + gotResults = false; + ClipboardService.list(100); + } + } + } + + // Initialize provider + function init() { + Logger.d("ClipboardProvider", "Initialized"); + // Pre-load clipboard data if service is active + if (ClipboardService.active) { + ClipboardService.list(100); + } + } + + // Called when launcher opens + function onOpened() { + isWaitingForData = true; + gotResults = false; + lastSearchText = ""; + + // Refresh clipboard history when launcher opens + if (ClipboardService.active) { + ClipboardService.list(100); + } + } + + // Check if this provider handles the command + function handleCommand(searchText) { + return searchText.startsWith(">clip"); + } + + // Return available commands when user types ">" + function commands() { + return [ + { + "name": ">clip", + "description": I18n.tr("launcher.providers.clipboard-search-description"), + "icon": iconMode === "tabler" ? "clipboard" : "diodon", + "isTablerIcon": true, + "isImage": false, + "onActivate": function () { + launcher.setSearchText(">clip "); + } + }, + { + "name": ">clip clear", + "description": I18n.tr("launcher.providers.clipboard-clear-description"), + "icon": iconMode === "tabler" ? "trash" : "user-trash", + "isTablerIcon": true, + "isImage": false, + "onActivate": function () { + ClipboardService.wipeAll(); + launcher.close(); + } + } + ]; + } + + // Get search results + function getResults(searchText) { + if (!searchText.startsWith(">clip")) { + return []; + } + + lastSearchText = searchText; + const results = []; + const query = searchText.slice(5).trim(); + + // Check if clipboard service is not active + if (!ClipboardService.active) { + // If dependency check hasn't completed yet, show loading instead of disabled + if (!ClipboardService.dependencyChecked) { + return [ + { + "name": I18n.tr("launcher.providers.clipboard-loading"), + "description": I18n.tr("launcher.providers.emoji-loading-description"), + "icon": iconMode === "tabler" ? "refresh" : "view-refresh", + "isTablerIcon": true, + "isImage": false, + "onActivate": function () {} + } + ]; + } + return [ + { + "name": I18n.tr("launcher.providers.clipboard-history-disabled"), + "description": I18n.tr("launcher.providers.clipboard-history-disabled-description"), + "icon": iconMode === "tabler" ? "refresh" : "view-refresh", + "isTablerIcon": true, + "isImage": false, + "onActivate": function () {} + } + ]; + } + + // Special command: clear + if (query === "clear") { + return [ + { + "name": I18n.tr("launcher.providers.clipboard-clear-history"), + "description": I18n.tr("launcher.providers.clipboard-clear-description-full"), + "icon": iconMode === "tabler" ? "trash" : "user-trash", + "isTablerIcon": true, + "isImage": false, + "onActivate": function () { + ClipboardService.wipeAll(); + launcher.close(); + } + } + ]; + } + + // Show loading state if data is being loaded + if (ClipboardService.loading || isWaitingForData) { + return [ + { + "name": I18n.tr("launcher.providers.clipboard-loading"), + "description": I18n.tr("launcher.providers.emoji-loading-description"), + "icon": iconMode === "tabler" ? "refresh" : "view-refresh", + "isTablerIcon": true, + "isImage": false, + "onActivate": function () {} + } + ]; + } + + // Get clipboard items + const items = ClipboardService.items || []; + + // If no items and we haven't tried loading yet, trigger a load + if (items.count === 0 && !ClipboardService.loading) { + isWaitingForData = true; + ClipboardService.list(100); + return [ + { + "name": I18n.tr("launcher.providers.clipboard-loading"), + "description": I18n.tr("launcher.providers.emoji-loading-description"), + "icon": iconMode === "tabler" ? "refresh" : "view-refresh", + "isTablerIcon": true, + "isImage": false, + "onActivate": function () {} + } + ]; + } + + // Search clipboard items + const searchTerm = query.toLowerCase(); + + const now = Date.now() / 1000; + + const catMap = { + "Images": "image", + "Links": "link", + "Files": "file", + "Code": "code", + "Colors": "color" + }; + + // Filter and format results + items.forEach(function (item) { + // Category filter + if (Settings.data.appLauncher.enableClipboardChips && root.selectedCategory !== "All") { + if (item.contentType !== catMap[root.selectedCategory]) { + return; + } + } + + const preview = (item.preview || "").toLowerCase(); + + // Skip if search term doesn't match + if (searchTerm && preview.indexOf(searchTerm) === -1) { + return; + } + + const firstSeen = ClipboardService.firstSeenById[item.id] || now; + + // Format the result based on type + let entry; + if (item.isImage) { + entry = formatImageEntry(item, firstSeen); + } else { + entry = formatTextEntry(item, firstSeen); + } + + // Add activation handler + entry.onActivate = function () { + if (Settings.data.appLauncher.autoPasteClipboard) { + launcher.closeImmediately(); + Qt.callLater(() => { + ClipboardService.pasteFromClipboard(item.id, item.mime); + }); + } else { + ClipboardService.copyToClipboard(item.id); + launcher.close(); + } + }; + + results.push(entry); + }); + + // Show empty state if no results + if (results.length === 0) { + results.push({ + "name": searchTerm ? "No matching clipboard items" : "Clipboard is empty", + "description": searchTerm ? `No items containing "${query}"` : "Copy something to see it here", + "icon": iconMode === "tabler" ? "clipboard" : "text-x-generic", + "isTablerIcon": true, + "isImage": false, + "onActivate": function () {// Do nothing + } + }); + } + + //Logger.i("ClipboardPlugin", `Returning ${results.length} results for query: "${query}"`) + return results; + } + + function formatImageEntry(item, firstSeen) { + const meta = ClipboardService.parseImageMeta(item.preview); + const timeStr = Time.formatRelativeTime(new Date(firstSeen * 1000)); + let desc = meta ? `${meta.fmt} โ€ข ${meta.size}` : item.mime || "Image data"; + if (timeStr) + desc += ` โ€ข ${timeStr}`; + + return { + "name": meta ? `Image ${meta.w}ร—${meta.h}` : "Image", + "description": desc, + "icon": iconMode === "tabler" ? "photo" : "image", + "isTablerIcon": true, + "isImage": true, + "imageWidth": meta ? meta.w : 0, + "imageHeight": meta ? meta.h : 0, + "clipboardId": item.id, + "mime": item.mime, + "preview": item.preview, + "provider": root + }; + } + + function formatTextEntry(item, firstSeen) { + const preview = (item.preview || "").trim(); + const lines = preview.split('\n').filter(l => l.trim()); + + let title = lines[0] || "Empty text"; + if (title.length > 60) { + title = title.substring(0, 57) + "..."; + } + + let description = ""; + if (lines.length > 1) { + description = lines[1]; + if (description.length > 80) { + description = description.substring(0, 77) + "..."; + } + } else { + // Preview is truncated at ~100 chars, so we can't show exact count + if (preview.length >= 100) { + description = I18n.tr("toast.clipboard.long-text"); + } else { + const chars = preview.length; + const words = preview.split(/\s+/).length; + description = `${chars} characters, ${words} word${words !== 1 ? 's' : ''}`; + } + } + + const timeStr = Time.formatRelativeTime(new Date(firstSeen * 1000)); + if (timeStr) + description += ` โ€ข ${timeStr}`; + + let defaultIcon = iconMode === "tabler" ? "clipboard" : "text-x-generic"; + let colorHex = ""; + if (Settings.data.appLauncher.enableClipboardSmartIcons) { + if (item.contentType === "link") + defaultIcon = iconMode === "tabler" ? "link" : "insert-link"; + else if (item.contentType === "file") + defaultIcon = iconMode === "tabler" ? "file" : "text-x-generic"; + else if (item.contentType === "code") + defaultIcon = iconMode === "tabler" ? "code" : "text-x-script"; + else if (item.contentType === "color") { + defaultIcon = iconMode === "tabler" ? "palette" : "color-picker"; + colorHex = preview; + } + } + + return { + "name": title, + "description": description, + "icon": defaultIcon, + "isTablerIcon": true, + "isImage": false, + "clipboardId": item.id, + "preview": preview, + "contentType": item.contentType, + "colorHex": colorHex, + "provider": root + }; + } + + function getImageForItem(clipboardId) { + return ClipboardService.getImageData ? ClipboardService.getImageData(clipboardId) : null; + } + + // ------------------------- + // Item actions for launcher delegate + function getItemActions(item) { + if (!item || !item.clipboardId) + return []; + + var actions = []; + + // Annotation tool for images + if (item.isImage && Settings.data.appLauncher.screenshotAnnotationTool !== "") { + actions.push({ + "icon": "pencil", + "tooltip": I18n.tr("tooltips.open-annotation-tool"), + "action": function () { + var tool = Settings.data.appLauncher.screenshotAnnotationTool; + Quickshell.execDetached(["sh", "-c", "cliphist decode " + item.clipboardId + " | " + tool]); + if (launcher) + launcher.close(); + } + }); + } + + // Delete action + actions.push({ + "icon": "trash", + "tooltip": I18n.tr("launcher.providers.clipboard-delete"), + "action": function () { + deleteItem(item); + } + }); + + return actions; + } + + function canDeleteItem(item) { + return item && !!item.clipboardId; + } + + function deleteItem(item) { + if (!item || !item.clipboardId) + return; + + // Set provider state before deletion so refresh works + gotResults = false; + isWaitingForData = true; + lastSearchText = launcher ? launcher.searchText : ""; + + // Delete the item + ClipboardService.deleteById(String(item.clipboardId)); + } + + // Prepare item for display (handles image decoding) + function prepareItem(item) { + if (item && item.isImage && item.clipboardId) { + if (!ClipboardService.getImageData(item.clipboardId)) { + ClipboardService.decodeToDataUrl(item.clipboardId, item.mime, null); + } + } + } + + // Get image URL for item (used by delegates) + function getImageUrl(item) { + if (!item || !item.clipboardId) + return ""; + return ClipboardService.getImageData(item.clipboardId) || ""; + } + + // Get preview data for the preview panel + function getPreviewData(item) { + if (!item) + return null; + return { + "clipboardId": item.clipboardId, + "isImage": item.isImage, + "mime": item.mime, + "preview": item.preview + }; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/CommandProvider.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/CommandProvider.qml new file mode 100644 index 0000000..12be174 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/CommandProvider.qml @@ -0,0 +1,51 @@ +import QtQuick +import Quickshell +import qs.Commons + +Item { + property var launcher: null + property string name: I18n.tr("common.command") + property string iconMode: Settings.data.appLauncher.iconMode + + function handleCommand(query) { + return query.startsWith(">cmd"); + } + + function commands() { + return [ + { + "name": ">cmd", + "description": I18n.tr("launcher.providers.command-description"), + "icon": iconMode === "tabler" ? "terminal" : "utilities-terminal", + "isTablerIcon": true, + "isImage": false, + "onActivate": function () { + launcher.setSearchText(">cmd "); + } + } + ]; + } + + function getResults(query) { + if (!query.startsWith(">cmd")) + return []; + + let expression = query.substring(4).trim(); + return [ + { + "name": I18n.tr("common.command"), + "description": I18n.tr("launcher.providers.command-description"), + "icon": iconMode === "tabler" ? "terminal" : "utilities-terminal", + "isTablerIcon": true, + "isImage": false, + "onActivate": function () { + launcher.closeImmediately(); + Qt.callLater(() => { + Logger.d("CommandProvider", "Executing shell command: " + expression); + Quickshell.execDetached(["sh", "-c", expression]); + }); + } + } + ]; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/EmojiProvider.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/EmojiProvider.qml new file mode 100644 index 0000000..ae64ef9 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/EmojiProvider.qml @@ -0,0 +1,165 @@ +import QtQuick +import Quickshell +import qs.Commons +import qs.Services.Keyboard + +Item { + id: root + + // Provider metadata + property string name: I18n.tr("launcher.providers.emoji") + property var launcher: null + property string iconMode: Settings.data.appLauncher.iconMode + property bool handleSearch: false + property string supportedLayouts: "grid" // Only grid layout for emoji + property int preferredGridColumns: 7 // More columns for compact emoji display + property real preferredGridCellRatio: 1.15 // Slightly taller than wide to accommodate label + property bool supportsAutoPaste: true // Emoji can be auto-pasted + property bool ignoreDensity: false // Emoji should scale with launcher density + + property string selectedCategory: "recent" + property bool showsCategories: true // Default to showing categories + + // Empty state message for category view + readonly property string emptyBrowsingMessage: selectedCategory === "recent" ? I18n.tr("launcher.providers.emoji-no-recent") : "" + + property var categoryIcons: ({ + "all": "apps", + "recent": "clock", + "people": "user", + "animals": "paw", + "nature": "leaf", + "food": "apple", + "activity": "run", + "travel": "plane", + "objects": "home", + "symbols": "star", + "flags": "flag" + }) + + property var categories: ["all", "recent", "people", "animals", "nature", "food", "activity", "travel", "objects", "symbols", "flags"] + + function getCategoryName(category) { + const names = { + "all": I18n.tr("launcher.categories.all"), + "recent": I18n.tr("launcher.categories.emoji-recent"), + "people": I18n.tr("launcher.categories.emoji-people"), + "animals": I18n.tr("launcher.categories.emoji-animals"), + "nature": I18n.tr("launcher.categories.emoji-nature"), + "food": I18n.tr("launcher.categories.emoji-food"), + "activity": I18n.tr("launcher.categories.emoji-activity"), + "travel": I18n.tr("launcher.categories.emoji-travel"), + "objects": I18n.tr("launcher.categories.emoji-objects"), + "symbols": I18n.tr("launcher.categories.emoji-symbols"), + "flags": I18n.tr("launcher.categories.emoji-flags") + }; + return names[category] || category; + } + + // Force update results when emoji service loads + Connections { + target: EmojiService + function onLoadedChanged() { + if (EmojiService.loaded && root.launcher) { + root.launcher.updateResults(); + } + } + } + + // Initialize provider + function init() { + Logger.d("EmojiProvider", "Initialized"); + } + + function selectCategory(category) { + selectedCategory = category; + if (launcher) { + launcher.updateResults(); + } + } + + function onOpened() { + // Always reset to "recent" category when opening + selectedCategory = "recent"; + } + + // Check if this provider handles the command + function handleCommand(searchText) { + return searchText.startsWith(">emoji"); + } + + // Return available commands when user types ">" + function commands() { + return [ + { + "name": ">emoji", + "description": I18n.tr("launcher.providers.emoji-search-description"), + "icon": iconMode === "tabler" ? "mood-smile" : "face-smile", + "isTablerIcon": true, + "isImage": false, + "onActivate": function () { + launcher.setSearchText(">emoji "); + } + } + ]; + } + + // Get search results + function getResults(searchText) { + if (!searchText.startsWith(">emoji")) { + return []; + } + + if (!EmojiService.loaded) { + return [ + { + "name": I18n.tr("launcher.providers.emoji-loading"), + "description": I18n.tr("launcher.providers.emoji-loading-description"), + "icon": iconMode === "tabler" ? "refresh" : "view-refresh", + "isTablerIcon": true, + "isImage": false, + "onActivate": function () {} + } + ]; + } + + var query = searchText.slice(6).trim(); + var emojis = []; + + if (query !== "" || selectedCategory === "all") { + emojis = EmojiService.search(query); + } else { + emojis = EmojiService.getEmojisByCategory(selectedCategory); + } + return emojis.map(formatEmojiEntry); + } + + // Format an emoji entry for the results list + function formatEmojiEntry(emoji) { + let title = emoji.name; + let description = emoji.keywords.join(", "); + + if (emoji.category) { + description += " โ€ข Category: " + emoji.category; + } + + const emojiChar = emoji.emoji; + + return { + "name": title, + "description": description, + "icon": null, + "isImage": false, + "displayString": emojiChar, + "autoPasteText": emojiChar, + "provider": root, + "onAutoPaste": function () { + EmojiService.recordUsage(emojiChar); + }, + "onActivate": function () { + EmojiService.copy(emojiChar); + launcher.close(); + } + }; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/SessionProvider.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/SessionProvider.qml new file mode 100644 index 0000000..13a0f76 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/SessionProvider.qml @@ -0,0 +1,194 @@ +import QtQuick +import Quickshell +import qs.Commons +import qs.Services.Compositor +import qs.Services.UI + +Item { + id: root + + // Provider metadata + property string name: I18n.tr("tooltips.session-menu") + property var launcher: null + property bool handleSearch: Settings.data.appLauncher.enableSessionSearch + property string supportedLayouts: "list" + property string iconMode: Settings.data.appLauncher.iconMode + + // Session actions with search keywords + readonly property var sessionActions: [ + { + "action": "lock", + "labelKey": "common.lock", + "icon": iconMode === "tabler" ? "lock" : "system-lock-screen", + "keywords": ["lock", "screen", "secure"] + }, + { + "action": "suspend", + "labelKey": "common.suspend", + "icon": iconMode === "tabler" ? "suspend" : "system-suspend", + "keywords": ["suspend", "sleep", "standby"] + }, + { + "action": "hibernate", + "labelKey": "common.hibernate", + "icon": iconMode === "tabler" ? "hibernate" : "system-suspend-hibernate", + "keywords": ["hibernate", "disk"] + }, + { + "action": "reboot", + "labelKey": "common.reboot", + "icon": iconMode === "tabler" ? "reboot" : "system-reboot", + "keywords": ["reboot", "restart", "reload"] + }, + { + "action": "rebootToUefi", + "labelKey": "common.reboot-to-uefi", + "icon": iconMode === "tabler" ? "reboot" : "system-reboot", + "keywords": ["reboot", "uefi", "firmware", "bios"] + }, + { + "action": "userspaceReboot", + "labelKey": "common.userspace-reboot", + "icon": iconMode === "tabler" ? "rotate" : "system-reboot", + "keywords": ["reboot", "restart", "soft", "userspace"] + }, + { + "action": "logout", + "labelKey": "common.logout", + "icon": iconMode === "tabler" ? "logout" : "system-log-out", + "keywords": ["logout", "sign out", "exit", "leave"] + }, + { + "action": "shutdown", + "labelKey": "common.shutdown", + "icon": iconMode === "tabler" ? "shutdown" : "system-shutdown", + "keywords": ["shutdown", "power off", "turn off", "poweroff"] + } + ] + + function init() { + Logger.d("SessionProvider", "Initialized"); + } + + function getEnabledActions() { + var powerOptions = Settings.data.sessionMenu.powerOptions || []; + var enabledSet = {}; + + for (var i = 0; i < powerOptions.length; i++) { + if (powerOptions[i].enabled) { + enabledSet[powerOptions[i].action] = powerOptions[i]; + } + } + + var enabled = []; + for (var j = 0; j < sessionActions.length; j++) { + var action = sessionActions[j]; + if (enabledSet[action.action]) { + enabled.push({ + "action": action.action, + "labelKey": action.labelKey, + "icon": action.icon, + "keywords": action.keywords, + "command": enabledSet[action.action].command || "" + }); + } + } + + return enabled; + } + + function getResults(query) { + if (!query) + return []; + + var trimmed = query.trim(); + if (!trimmed || trimmed.length < 2) + return []; + + var enabledActions = getEnabledActions(); + if (enabledActions.length === 0) + return []; + + // Build searchable items with resolved translations + var items = []; + for (var i = 0; i < enabledActions.length; i++) { + var action = enabledActions[i]; + var label = I18n.tr(action.labelKey); + items.push({ + "action": action.action, + "icon": action.icon, + "label": label, + "command": action.command, + "searchText": [label.toLowerCase()].concat(action.keywords).join(" ") + }); + } + + var results = FuzzySort.go(trimmed, items, { + "keys": ["label", "searchText"], + "limit": 6 + }); + + var launcherItems = []; + for (var j = 0; j < results.length; j++) { + var entry = results[j].obj; + var score = results[j].score; + + launcherItems.push({ + "name": entry.label, + "description": I18n.tr("tooltips.session-menu"), + "icon": entry.icon, + "isTablerIcon": true, + "isImage": false, + "_score": score - 1, + "provider": root, + "onActivate": createActivateHandler(entry.action, entry.command) + }); + } + + return launcherItems; + } + + function createActivateHandler(action, command) { + return function () { + if (launcher) + launcher.close(); + + // Execute via Qt.callLater, but reference only singletons + // (root may be destroyed after launcher.close() unloads the panel) + Qt.callLater(() => { + switch (action) { + case "lock": + if (PanelService.lockScreen && !PanelService.lockScreen.active) { + PanelService.lockScreen.active = true; + } + break; + case "suspend": + if (Settings.data.general.lockOnSuspend) { + CompositorService.lockAndSuspend(); + } else { + CompositorService.suspend(); + } + break; + case "hibernate": + CompositorService.hibernate(); + break; + case "reboot": + CompositorService.reboot(); + break; + case "rebootToUefi": + CompositorService.rebootToUefi(); + break; + case "userspaceReboot": + CompositorService.userspaceReboot(); + break; + case "logout": + CompositorService.logout(); + break; + case "shutdown": + CompositorService.shutdown(); + break; + } + }); + }; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/SettingsProvider.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/SettingsProvider.qml new file mode 100644 index 0000000..8993d2a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/SettingsProvider.qml @@ -0,0 +1,161 @@ +import QtQuick +import qs.Commons +import qs.Services.UI + +Item { + id: root + + // Provider metadata + property string name: I18n.tr("common.settings") + property var launcher: null + property bool handleSearch: Settings.data.appLauncher.enableSettingsSearch + property string supportedLayouts: "list" + property string iconMode: Settings.data.appLauncher.iconMode + + function init() { + Logger.d("SettingsProvider", "Initialized"); + } + + // Check if this provider handles the command + function handleCommand(searchText) { + return searchText.startsWith(">settings"); + } + + // Return available commands when user types ">" + function commands() { + return [ + { + "name": ">settings", + "description": I18n.tr("launcher.providers.settings-search-description"), + "icon": iconMode === "tabler" ? "settings" : "preferences-system", + "isTablerIcon": true, + "isImage": false, + "onActivate": function () { + launcher.setSearchText(">settings "); + } + } + ]; + } + + function getResults(query) { + if (!query || SettingsSearchService.searchIndex.length === 0) + return []; + + var trimmed = query.trim(); + + // Handle command mode: ">settings" or ">settings " + var isCommandMode = trimmed.startsWith(">settings"); + if (isCommandMode) { + // Extract search term after ">settings " + var searchTerm = trimmed.substring(9).trim(); + // In command mode, show all settings if no search term + if (searchTerm.length === 0) { + return getAllSettings(); + } + trimmed = searchTerm; + } else { + // Regular search mode - require at least 2 chars + if (!trimmed || trimmed.length < 2) + return []; + } + + // Build searchable items with resolved translations, filtering out invisible entries + let items = []; + for (let j = 0; j < SettingsSearchService.searchIndex.length; j++) { + const entry = SettingsSearchService.searchIndex[j]; + if (!SettingsSearchService.isEntryVisible(entry)) + continue; + items.push({ + "labelKey": entry.labelKey, + "descriptionKey": entry.descriptionKey, + "widget": entry.widget, + "tab": entry.tab, + "tabLabel": entry.tabLabel, + "subTab": entry.subTab, + "subTabLabel": entry.subTabLabel || null, + "label": I18n.tr(entry.labelKey), + "description": entry.descriptionKey ? I18n.tr(entry.descriptionKey) : "", + "subTabName": entry.subTabLabel ? I18n.tr(entry.subTabLabel) : "" + }); + } + + const results = FuzzySort.go(trimmed, items, { + "keys": ["label", "subTabName", "description"], + "limit": 10, + "scoreFn": function (r) { + const labelScore = r[0].score; + const subTabScore = r[1].score * 1.5; + const descScore = r[2].score; + return Math.max(labelScore, subTabScore, descScore); + } + }); + + let launcherItems = []; + for (let i = 0; i < results.length; i++) { + const entry = results[i].obj; + const score = results[i].score; + const tabName = I18n.tr(entry.tabLabel); + const subTabName = entry.subTabName || ""; + const breadcrumb = subTabName ? (tabName + " โ€บ " + subTabName) : tabName; + + launcherItems.push({ + "name": entry.label, + "description": breadcrumb, + "icon": iconMode === "tabler" ? "settings" : "preferences-system", + "isTablerIcon": true, + "isImage": false, + "_score": score - 2, + "provider": root, + "onActivate": createActivateHandler(entry) + }); + } + + return launcherItems; + } + + function getAllSettings() { + var launcherItems = []; + + for (var j = 0; j < SettingsSearchService.searchIndex.length; j++) { + var entry = SettingsSearchService.searchIndex[j]; + if (!SettingsSearchService.isEntryVisible(entry)) + continue; + var label = I18n.tr(entry.labelKey); + var tabName = I18n.tr(entry.tabLabel); + var subTabName = entry.subTabLabel ? I18n.tr(entry.subTabLabel) : ""; + var breadcrumb = subTabName ? (tabName + " โ€บ " + subTabName) : tabName; + + launcherItems.push({ + "name": label, + "description": breadcrumb, + "icon": iconMode === "tabler" ? "settings" : "preferences-system", + "isTablerIcon": true, + "isImage": false, + "_score": 0, + "provider": root, + "onActivate": createActivateHandler({ + "labelKey": entry.labelKey, + "descriptionKey": entry.descriptionKey, + "widget": entry.widget, + "tab": entry.tab, + "tabLabel": entry.tabLabel, + "subTab": entry.subTab, + "subTabLabel": entry.subTabLabel || null + }) + }); + } + + return launcherItems; + } + + function createActivateHandler(entry) { + return function () { + if (launcher) + launcher.close(); + + Qt.callLater(() => { + SettingsPanelService.openToEntry(entry, launcher.screen); + }); + }; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/WindowsProvider.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/WindowsProvider.qml new file mode 100644 index 0000000..7627560 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Launcher/Providers/WindowsProvider.qml @@ -0,0 +1,159 @@ +import QtQuick +import Quickshell +import qs.Commons +import qs.Services.Compositor + +Item { + id: root + + property string name: I18n.tr("common.windows") + property var launcher: null + property bool handleSearch: Settings.data.appLauncher.enableWindowsSearch + property string supportedLayouts: "list" + property string iconMode: Settings.data.appLauncher.iconMode + + function init() { + Logger.d("WindowsProvider", "Initialized"); + } + + // Check if this provider handles the command + function handleCommand(searchText) { + return searchText.startsWith(">win"); + } + + // Return available commands when user types ">" + function commands() { + return [ + { + "name": ">win", + "description": I18n.tr("launcher.providers.windows-search-description"), + "icon": iconMode === "tabler" ? "app-window" : "preferences-system-windows", + "isTablerIcon": true, + "isImage": false, + "onActivate": function () { + launcher.setSearchText(">win "); + } + } + ]; + } + + function getResults(query) { + if (!query) + return []; + + var trimmed = query.trim(); + + // Handle command mode: ">win" or ">win " + var isCommandMode = trimmed.startsWith(">win"); + if (isCommandMode) { + // Extract search term after ">win " + var searchTerm = trimmed.substring(4).trim(); + // In command mode, show all windows if no search term + if (searchTerm.length === 0) { + return getAllWindows(); + } + trimmed = searchTerm; + } else { + // Regular search mode - require at least 2 chars + if (trimmed.length < 2) + return []; + } + + var items = []; + + // Collect all windows from CompositorService + for (var i = 0; i < CompositorService.windows.count; i++) { + var win = CompositorService.windows.get(i); + items.push({ + "id": win.id, + "title": win.title || "", + "appId": win.appId || "", + "workspaceId": win.workspaceId, + "isFocused": win.isFocused, + "searchText": (win.title + " " + win.appId).toLowerCase() + }); + } + + // Fuzzy search on title and appId + var results = FuzzySort.go(trimmed, items, { + "keys": ["title", "appId"], + "limit": 10 + }); + + // Map to launcher items + var launcherItems = []; + for (var j = 0; j < results.length; j++) { + var entry = results[j].obj; + var score = results[j].score; + + // Get icon name from DesktopEntry if available, otherwise use appId + var iconName = entry.appId; + var appEntry = ThemeIcons.findAppEntry(entry.appId); + if (appEntry && appEntry.icon) { + iconName = appEntry.icon; + } + + launcherItems.push({ + "name": entry.title || entry.appId, + "description": entry.appId, + "icon": iconName || "application-x-executable", + "isTablerIcon": false, + "badgeIcon": "app-window", + "_score": score, + "provider": root, + "windowId": entry.id, + "onActivate": createActivateHandler(entry) + }); + } + + return launcherItems; + } + + function getAllWindows() { + var launcherItems = []; + + for (var i = 0; i < CompositorService.windows.count; i++) { + var win = CompositorService.windows.get(i); + + var iconName = win.appId; + var appEntry = ThemeIcons.findAppEntry(win.appId); + if (appEntry && appEntry.icon) { + iconName = appEntry.icon; + } + + launcherItems.push({ + "name": win.title || win.appId, + "description": win.appId, + "icon": iconName || "application-x-executable", + "isTablerIcon": false, + "badgeIcon": "app-window", + "_score": 0, + "provider": root, + "windowId": win.id, + "onActivate": createActivateHandler({ + "id": win.id + }) + }); + } + + return launcherItems; + } + + function createActivateHandler(windowEntry) { + return function () { + if (launcher) + launcher.close(); + + Qt.callLater(() => { + // Find the actual window object to pass to focusWindow + for (var i = 0; i < CompositorService.windows.count; i++) { + var win = CompositorService.windows.get(i); + if (win.id === windowEntry.id) { + CompositorService.focusWindow(win); + break; + } + } + }); + }; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Media/MediaPlayerPanel.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Media/MediaPlayerPanel.qml new file mode 100644 index 0000000..400738f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Media/MediaPlayerPanel.qml @@ -0,0 +1,576 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.MainScreen +import qs.Services.Media +import qs.Services.UI +import qs.Widgets +import qs.Widgets.AudioSpectrum + +SmartPanel { + id: root + + preferredWidth: Math.round((root.isSideBySide ? 480 : 360) * Style.uiScaleRatio) + // Fallback only; SmartPanel uses panelContent.contentPreferredHeight when set. + preferredHeight: Math.round((root.compactMode ? 240 : 400) * Style.uiScaleRatio) + + property var mediaMiniSettings: { + const widget = BarService.lookupWidget("MediaMini", screen?.name); + return widget ? widget.widgetSettings : null; + } + + function refreshMediaMiniSettings() { + const widget = BarService.lookupWidget("MediaMini", screen?.name); + root.mediaMiniSettings = widget ? widget.widgetSettings : null; + } + + Connections { + target: BarService + function onActiveWidgetsChanged() { + root.refreshMediaMiniSettings(); + } + } + + Connections { + target: Settings + function onSettingsSaved() { + root.refreshMediaMiniSettings(); + } + } + + readonly property string visualizerType: (mediaMiniSettings && mediaMiniSettings.visualizerType !== undefined) ? mediaMiniSettings.visualizerType : "linear" + readonly property bool showArtistFirst: !!(mediaMiniSettings && mediaMiniSettings.showArtistFirst !== undefined ? mediaMiniSettings.showArtistFirst : true) + readonly property bool showAlbumArt: !!(mediaMiniSettings && mediaMiniSettings.panelShowAlbumArt !== undefined ? mediaMiniSettings.panelShowAlbumArt : true) + readonly property bool showVisualizer: !!(mediaMiniSettings && mediaMiniSettings.showVisualizer !== undefined ? mediaMiniSettings.showVisualizer : true) + readonly property bool compactMode: !!(mediaMiniSettings && mediaMiniSettings.compactMode !== undefined ? mediaMiniSettings.compactMode : false) + readonly property string scrollingMode: (mediaMiniSettings && mediaMiniSettings.scrollingMode !== undefined) ? mediaMiniSettings.scrollingMode : "hover" + + readonly property bool isSideBySide: root.compactMode && root.showAlbumArt + + readonly property bool needsSpectrum: root.showVisualizer && root.visualizerType !== "" && root.visualizerType !== "none" && root.isPanelOpen + + onNeedsSpectrumChanged: { + if (root.needsSpectrum) { + SpectrumService.registerComponent("mediaplayerpanel"); + } else { + SpectrumService.unregisterComponent("mediaplayerpanel"); + } + } + + Component.onCompleted: { + if (root.needsSpectrum) { + SpectrumService.registerComponent("mediaplayerpanel"); + } + } + + Component.onDestruction: { + SpectrumService.unregisterComponent("mediaplayerpanel"); + } + + panelContent: Item { + id: playerContent + anchors.fill: parent + + // implicitHeight + mainLayout vertical anchors.margins (each Style.marginL). + readonly property real contentPreferredHeight: { + const m = mainLayout.implicitHeight + 2 * Style.marginL; + const scale = Style.uiScaleRatio; + if (root.compactMode) + return Math.max(m, 240 * scale); + if (!root.showAlbumArt) + return Math.max(m, 300 * scale); + return Math.max(m, 260 * scale); + } + + property Component visualizerSource: { + switch (root.visualizerType) { + case "linear": + return linearComponent; + case "mirrored": + return mirroredComponent; + case "wave": + return waveComponent; + default: + return null; + } + } + + ColumnLayout { + id: mainLayout + anchors.fill: parent + anchors.margins: Style.marginL + spacing: root.compactMode ? Style.marginL : Style.marginM + z: 1 + + NBox { + Layout.fillWidth: true + Layout.preferredHeight: headerRow.implicitHeight + Style.margin2M + + RowLayout { + id: headerRow + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + + NIcon { + icon: "music" + pointSize: Style.fontSizeL + color: Color.mPrimary + } + + NText { + text: I18n.tr("common.media-player") + font.weight: Style.fontWeightBold + pointSize: Style.fontSizeL + color: Color.mOnSurface + Layout.fillWidth: true + } + + Rectangle { + radius: Style.radiusS + color: playerSelectorMouse.containsMouse ? Color.mPrimary : "transparent" + implicitWidth: playerRow.implicitWidth + Style.marginM + implicitHeight: Style.baseWidgetSize * 0.8 + visible: MediaService.getAvailablePlayers().length > 1 + + RowLayout { + id: playerRow + anchors.centerIn: parent + spacing: Style.marginXS + + NText { + text: MediaService.currentPlayer ? MediaService.currentPlayer.identity : "Select Player" + pointSize: Style.fontSizeXS + color: playerSelectorMouse.containsMouse ? Color.mOnPrimary : Color.mOnSurfaceVariant + } + NIcon { + icon: "chevron-down" + pointSize: Style.fontSizeXS + color: playerSelectorMouse.containsMouse ? Color.mOnPrimary : Color.mOnSurfaceVariant + } + } + + MouseArea { + id: playerSelectorMouse + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: playerContextMenu.open() + } + + Popup { + id: playerContextMenu + x: 0 + y: parent.height + width: 160 + padding: Style.marginS + + background: Rectangle { + color: Color.mSurfaceVariant + border.color: Color.mOutline + border.width: Style.borderS + radius: Style.iRadiusM + } + + contentItem: ColumnLayout { + spacing: 0 + Repeater { + model: MediaService.getAvailablePlayers() + delegate: Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 30 + color: "transparent" + + Rectangle { + anchors.fill: parent + color: itemMouse.containsMouse ? Color.mPrimary : "transparent" + radius: Style.iRadiusS + } + + RowLayout { + anchors.fill: parent + anchors.margins: Style.marginS + spacing: Style.marginS + + NIcon { + visible: MediaService.currentPlayer && MediaService.currentPlayer.identity === modelData.identity + icon: "check" + color: itemMouse.containsMouse ? Color.mOnPrimary : Color.mPrimary + pointSize: Style.fontSizeS + } + + NText { + text: modelData.identity + pointSize: Style.fontSizeS + color: itemMouse.containsMouse ? Color.mOnPrimary : Color.mOnSurface + Layout.fillWidth: true + elide: Text.ElideRight + } + } + + MouseArea { + id: itemMouse + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + MediaService.currentPlayer = modelData; + playerContextMenu.close(); + } + } + } + } + } + } + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: root.close() + } + } + } + + NBox { + Layout.fillWidth: true + Layout.preferredHeight: mediaContentGrid.implicitHeight + Style.marginM + Style.marginM + + // Visualizer background for content area + Loader { + anchors.fill: parent + z: 0 + active: !!(root.needsSpectrum && !root.showAlbumArt) + sourceComponent: visualizerSource + } + + GridLayout { + id: mediaContentGrid + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.leftMargin: root.compactMode ? Style.marginL : Style.marginM + anchors.rightMargin: root.compactMode ? Style.marginL : Style.marginM + anchors.topMargin: Style.marginM + anchors.bottomMargin: Style.marginM + columns: root.isSideBySide ? 2 : 1 + columnSpacing: Style.marginL + rowSpacing: root.compactMode ? Style.marginL : Style.marginM + + // Album Art (Vertical in normal, Horizontal in compact) + Item { + id: albumArtItem + readonly property real compactArtSize: Math.round(110 * Style.uiScaleRatio) + readonly property bool artSizeKnown: artSizeProbe.status === Image.Ready && artSizeProbe.sourceSize.width > 0 && artSizeProbe.sourceSize.height > 0 + readonly property real artAspectRatio: artSizeKnown ? artSizeProbe.sourceSize.width / artSizeProbe.sourceSize.height : 1 + // Non-compact: height from widthรทaspect so grid implicit height does not depend on panel height (no layout loop). + readonly property real artBoxW: root.compactMode ? compactArtSize : Math.max(parent.width, 1) + readonly property real artBoxH: root.compactMode ? compactArtSize : (artBoxW / Math.max(artAspectRatio, 0.001)) + readonly property real fitArtW: artBoxW / artBoxH > artAspectRatio ? artBoxH * artAspectRatio : artBoxW + readonly property real fitArtH: artBoxW / artBoxH > artAspectRatio ? artBoxH : artBoxW / artAspectRatio + + Layout.preferredWidth: fitArtW + Layout.preferredHeight: fitArtH + Layout.minimumWidth: fitArtW + Layout.maximumWidth: fitArtW + Layout.minimumHeight: fitArtH + Layout.maximumHeight: fitArtH + Layout.fillWidth: false + Layout.fillHeight: false + Layout.alignment: Qt.AlignHCenter + visible: root.showAlbumArt + + Image { + id: artSizeProbe + visible: false + asynchronous: true + source: MediaService.trackArtUrl + } + + NImageRounded { + anchors.fill: parent + radius: root.compactMode ? Style.radiusM : Style.radiusL + imagePath: MediaService.trackArtUrl + imageFillMode: Image.PreserveAspectCrop + fallbackIcon: "disc" + fallbackIconSize: root.compactMode ? Style.fontSizeXXXL * 3 : Style.fontSizeXXXL * 6 + borderWidth: 0 + } + + Loader { + anchors.fill: parent + anchors.margins: Style.marginS + z: 2 + active: !!(root.needsSpectrum && root.showAlbumArt) + sourceComponent: visualizerSource + } + } + + ColumnLayout { + id: controlsLayout + Layout.preferredWidth: root.compactMode ? -1 : albumArtItem.width + Layout.fillWidth: root.compactMode + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: root.compactMode + spacing: root.compactMode ? Style.marginXS : Style.marginS + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + NScrollText { + Layout.fillWidth: true + maxWidth: parent.width + text: { + if (root.showArtistFirst) { + return MediaService.trackArtist || (MediaService.trackAlbum || "Unknown Artist"); + } else { + return MediaService.trackTitle || "No Media"; + } + } + + scrollMode: { + if (root.scrollingMode === "always") + return NScrollText.ScrollMode.Always; + if (root.scrollingMode === "hover") + return NScrollText.ScrollMode.Hover; + return NScrollText.ScrollMode.Never; + } + fadeExtent: 0.01 + fadeCornerRadius: Style.radiusM + + delegate: NText { + pointSize: root.compactMode ? Style.fontSizeL : Style.fontSizeXL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + horizontalAlignment: root.isSideBySide ? Text.AlignLeft : Text.AlignHCenter + elide: Text.ElideNone + wrapMode: Text.NoWrap + } + } + + NScrollText { + Layout.fillWidth: true + maxWidth: parent.width + text: { + if (root.showArtistFirst) { + return MediaService.trackTitle || "No Media"; + } else { + return MediaService.trackArtist || (MediaService.trackAlbum || "Unknown Artist"); + } + } + + scrollMode: { + if (root.scrollingMode === "always") + return NScrollText.ScrollMode.Always; + if (root.scrollingMode === "hover") + return NScrollText.ScrollMode.Hover; + return NScrollText.ScrollMode.Never; + } + fadeExtent: 0.01 + fadeCornerRadius: Style.radiusM + + delegate: NText { + pointSize: root.compactMode ? Style.fontSizeS : Style.fontSizeM + color: Color.mOnSurfaceVariant + horizontalAlignment: root.isSideBySide ? Text.AlignLeft : Text.AlignHCenter + elide: Text.ElideNone + wrapMode: Text.NoWrap + } + } + } + + Item { + id: progressWrapper + visible: (MediaService.currentPlayer && MediaService.trackLength > 0) + Layout.fillWidth: true + Layout.preferredHeight: progressColumn.implicitHeight + + property real localSeekRatio: -1 + property real lastSentSeekRatio: -1 + property real seekEpsilon: 0.01 + property real progressRatio: { + if (!MediaService.currentPlayer || MediaService.trackLength <= 0) + return 0; + const r = MediaService.currentPosition / MediaService.trackLength; + if (isNaN(r) || !isFinite(r)) + return 0; + return Math.max(0, Math.min(1, r)); + } + + Timer { + id: seekDebounce + interval: 75 + repeat: false + onTriggered: { + if (MediaService.isSeeking && progressWrapper.localSeekRatio >= 0) { + const next = Math.max(0, Math.min(1, progressWrapper.localSeekRatio)); + if (progressWrapper.lastSentSeekRatio < 0 || Math.abs(next - progressWrapper.lastSentSeekRatio) >= progressWrapper.seekEpsilon) { + MediaService.seekByRatio(next); + progressWrapper.lastSentSeekRatio = next; + } + } + } + } + + ColumnLayout { + id: progressColumn + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: 2 + + Item { + Layout.fillWidth: true + Layout.preferredHeight: root.compactMode ? (Style.baseWidgetSize * 0.4) : (Style.baseWidgetSize * 0.5) + + NSlider { + id: progressSlider + anchors.fill: parent + from: 0 + to: 1 + stepSize: 0 + snapAlways: false + enabled: MediaService.trackLength > 0 && MediaService.canSeek + heightRatio: 0.4 + + value: (!MediaService.isSeeking) ? progressWrapper.progressRatio : (progressWrapper.localSeekRatio >= 0 ? progressWrapper.localSeekRatio : 0) + + onMoved: { + progressWrapper.localSeekRatio = value; + seekDebounce.restart(); + } + onPressedChanged: { + if (pressed) { + MediaService.isSeeking = true; + progressWrapper.localSeekRatio = value; + MediaService.seekByRatio(value); + progressWrapper.lastSentSeekRatio = value; + } else { + seekDebounce.stop(); + MediaService.seekByRatio(value); + MediaService.isSeeking = false; + progressWrapper.localSeekRatio = -1; + progressWrapper.lastSentSeekRatio = -1; + } + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: 0 + + NText { + text: MediaService.positionString || "0:00" + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + visible: progressWrapper.visible + } + + Item { + Layout.fillWidth: true + Layout.minimumWidth: 0 + } + + NText { + text: MediaService.lengthString || "0:00" + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + horizontalAlignment: Text.AlignRight + visible: progressWrapper.visible + } + } + } + } + + Item { + Layout.preferredHeight: root.isSideBySide ? Style.marginM : Style.marginS + } + + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: root.isSideBySide ? Style.marginL : Style.marginXL + + NIconButton { + icon: "media-prev" + baseSize: root.compactMode ? (Style.baseWidgetSize * 0.9) : (Style.baseWidgetSize * 1.2) + onClicked: MediaService.previous() + } + + Rectangle { + implicitWidth: root.compactMode ? (Style.baseWidgetSize * 1.3) : (Style.baseWidgetSize * 1.8) + implicitHeight: root.compactMode ? (Style.baseWidgetSize * 1.3) : (Style.baseWidgetSize * 1.8) + radius: root.compactMode ? Style.iRadiusM : Style.iRadiusL + color: Color.mPrimary + + NIcon { + anchors.centerIn: parent + icon: MediaService.isPlaying ? "media-pause" : "media-play" + pointSize: root.compactMode ? Style.fontSizeL : Style.fontSizeXXL + color: Color.mOnPrimary + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + onEntered: parent.color = Color.mPrimary + onClicked: MediaService.playPause() + } + } + + NIconButton { + icon: "media-next" + baseSize: root.compactMode ? (Style.baseWidgetSize * 0.9) : (Style.baseWidgetSize * 1.2) + onClicked: MediaService.next() + } + } + } + } + } + } + } + + // Visualizer Components + Component { + id: linearComponent + NLinearSpectrum { + width: parent.width - Style.marginS + height: 20 + values: SpectrumService.values + fillColor: Color.mPrimary + opacity: 0.4 + barPosition: Settings.getBarPositionForScreen(root.screen?.name) + mirrored: Settings.data.audio.spectrumMirrored + } + } + + Component { + id: mirroredComponent + NMirroredSpectrum { + width: parent.width - Style.marginS + height: parent.height - Style.marginS + values: SpectrumService.values + fillColor: Color.mPrimary + opacity: 0.4 + mirrored: Settings.data.audio.spectrumMirrored + } + } + + Component { + id: waveComponent + NWaveSpectrum { + width: parent.width - Style.marginS + height: parent.height - Style.marginS + values: SpectrumService.values + fillColor: Color.mPrimary + opacity: 0.4 + mirrored: Settings.data.audio.spectrumMirrored + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Network/NetworkPanel.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Network/NetworkPanel.qml new file mode 100644 index 0000000..2918c61 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Network/NetworkPanel.qml @@ -0,0 +1,950 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import "../Settings/Tabs/Connections" as WifiPrefs +import qs.Commons +import qs.Modules.MainScreen +import qs.Modules.Panels.Settings +import qs.Services.Networking +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +SmartPanel { + id: root + + preferredWidth: Math.round(440 * Style.uiScaleRatio) + preferredHeight: Math.round(500 * Style.uiScaleRatio) + + // Info panel collapsed by default, view mode persisted in settings + // Ethernet details UI state (mirrors Wiโ€‘Fi info behavior) + property bool ethernetInfoExpanded: false + property bool ethernetDetailsGrid: (Settings.data.network.wifiDetailsViewMode === "grid") + property int ipVersion: 4 + + // Unified panel view mode: "wifi" | "ethernet" (persisted) + property string panelViewMode: "wifi" + property bool panelViewPersistEnabled: false + + onPanelViewModeChanged: { + // Persist last view (only after restored the initial value) + if (panelViewPersistEnabled) { + Settings.data.network.networkPanelView = panelViewMode; + } + if (panelViewMode === "wifi") { + ethernetInfoExpanded = false; + if (NetworkService.wifiEnabled && !NetworkService.scanningActive) { + NetworkService.scan(); + NetworkService.refreshActiveWifiDetails(); + } + } else { + if (NetworkService.ethernetConnected) { + NetworkService.refreshActiveEthernetDetails(); + } + } + } + + // Effectively visible tracking + readonly property bool effectivelyVisible: root.visible && Window.window && Window.window.visible + + onEffectivelyVisibleChanged: { + if (effectivelyVisible) { + SystemStatService.registerComponent("network-panel"); + if (NetworkService.wifiEnabled && !NetworkService.scanningActive) { + NetworkService.scan(); + NetworkService.refreshActiveWifiDetails(); + } + if (NetworkService.ethernetConnected) { + NetworkService.refreshActiveEthernetDetails(); + } + } else { + SystemStatService.unregisterComponent("network-panel"); + } + } + + onOpened: { + // Restore last view if valid, otherwise choose what's available (prefer Wiโ€‘Fi when both exist) + if (Settings.data.network.networkPanelView) { + const last = Settings.data.network.networkPanelView; + if (last === "ethernet" && NetworkService.ethernetAvailable) { + panelViewMode = "ethernet"; + } else { + panelViewMode = "wifi"; + } + } else { + if (!NetworkService.wifiEnabled && NetworkService.ethernetAvailable) { + panelViewMode = "ethernet"; + } else { + panelViewMode = "wifi"; + } + } + panelViewPersistEnabled = true; + } + + panelContent: Rectangle { + color: "transparent" + + property real contentPreferredHeight: Math.min(root.preferredHeight, mainColumn.implicitHeight + Style.margin2L) + + ColumnLayout { + id: mainColumn + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + // Header + NBox { + Layout.fillWidth: true + Layout.preferredHeight: header.implicitHeight + Style.margin2M + + ColumnLayout { + id: header + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + + RowLayout { + NIcon { + id: modeIcon + icon: panelViewMode === "wifi" ? (NetworkService.wifiEnabled ? "wifi" : "wifi-off") : (NetworkService.ethernetAvailable ? (NetworkService.ethernetConnected ? "ethernet" : "ethernet") : "ethernet-off") + pointSize: Style.fontSizeXXL + color: { + if (panelViewMode === "wifi") { + return NetworkService.wifiEnabled ? Color.mPrimary : Color.mOnSurfaceVariant; + } else { + return NetworkService.ethernetConnected ? Color.mPrimary : Color.mOnSurfaceVariant; + } + } + MouseArea { + anchors.fill: parent + hoverEnabled: true + onClicked: { + if (panelViewMode === "wifi") { + if (NetworkService.ethernetAvailable) { + panelViewMode = "ethernet"; + } else { + TooltipService.show(parent, I18n.tr("wifi.panel.no-ethernet-devices")); + } + } else { + panelViewMode = "wifi"; + } + } + onEntered: TooltipService.show(parent, panelViewMode === "wifi" ? I18n.tr("common.wifi") : I18n.tr("common.ethernet")) + onExited: TooltipService.hide() + } + } + + NLabel { + label: panelViewMode === "wifi" ? I18n.tr("common.wifi") : I18n.tr("common.ethernet") + Layout.fillWidth: true + } + + NToggle { + id: wifiSwitch + visible: panelViewMode === "wifi" + checked: NetworkService.wifiEnabled + enabled: !NetworkService.airplaneModeEnabled && NetworkService.wifiAvailable + onToggled: checked => NetworkService.setWifiEnabled(checked) + baseSize: Style.baseWidgetSize * 0.7 // Slightly smaller + } + + NIconButton { + icon: "settings" + tooltipText: I18n.tr("tooltips.open-settings") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: SettingsPanelService.openToTab(SettingsPanel.Tab.Connections, 0, screen) + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: root.close() + } + } + + // Mode switch (Wiโ€‘Fi / Ethernet) + NTabBar { + id: modeTabBar + visible: NetworkService.ethernetAvailable && NetworkService.wifiAvailable + margins: Style.marginS + Layout.fillWidth: true + spacing: Style.marginM + distributeEvenly: true + currentIndex: root.panelViewMode === "wifi" ? 0 : 1 + onCurrentIndexChanged: { + root.panelViewMode = (currentIndex === 0) ? "wifi" : "ethernet"; + } + + NTabButton { + text: I18n.tr("common.wifi") + tabIndex: 0 + checked: modeTabBar.currentIndex === 0 + } + + NTabButton { + text: I18n.tr("common.ethernet") + tabIndex: 1 + checked: modeTabBar.currentIndex === 1 + } + } + } + } + + // Unified scrollable content (Wiโ€‘Fi or Ethernet view) + ColumnLayout { + id: wifiSectionContainer + visible: true + Layout.fillWidth: true + spacing: Style.marginM + + // Error message + Rectangle { + visible: panelViewMode === "wifi" && NetworkService.lastError.length > 0 + Layout.fillWidth: true + Layout.preferredHeight: errorRow.implicitHeight + Style.margin2M + color: Qt.alpha(Color.mError, 0.1) + radius: Style.radiusS + border.width: Style.borderS + border.color: Color.mError + + RowLayout { + id: errorRow + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginS + + NIcon { + icon: "warning" + pointSize: Style.fontSizeL + color: Color.mError + } + + NText { + text: NetworkService.lastError + color: Color.mError + pointSize: Style.fontSizeS + wrapMode: Text.Wrap + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + baseSize: Style.baseWidgetSize * 0.6 + onClicked: NetworkService.lastError = "" + } + } + } + + // Unified scrollable content + NScrollView { + id: contentScroll + Layout.fillWidth: true + Layout.fillHeight: true + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AsNeeded + reserveScrollbarSpace: false + gradientColor: Color.mSurface + + ColumnLayout { + id: contentColumn + width: contentScroll.availableWidth + spacing: Style.marginM + + // Wiโ€‘Fi disabled state + NBox { + id: disabledBox + visible: panelViewMode === "wifi" && !NetworkService.wifiEnabled + Layout.fillWidth: true + Layout.preferredHeight: disabledColumn.implicitHeight + Style.margin2M + + ColumnLayout { + id: disabledColumn + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginL + + Item { + Layout.fillHeight: true + } + + NIcon { + icon: "wifi-off" + pointSize: 48 + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: I18n.tr("wifi.panel.disabled") + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: I18n.tr("wifi.panel.enable-message") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + wrapMode: Text.WordWrap + } + + Item { + Layout.fillHeight: true + } + } + } + + // Scanning state (show when no networks and we haven't had any yet) + NBox { + id: scanningBox + visible: panelViewMode === "wifi" && NetworkService.wifiEnabled && Object.keys(NetworkService.networks).length === 0 && NetworkService.scanningActive + Layout.fillWidth: true + Layout.preferredHeight: scanningColumn.implicitHeight + Style.margin2M + + ColumnLayout { + id: scanningColumn + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginL + + Item { + Layout.fillHeight: true + } + + NBusyIndicator { + running: visible && root.effectivelyVisible + color: Color.mPrimary + size: Style.baseWidgetSize + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: I18n.tr("wifi.panel.searching") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + } + + Item { + Layout.fillHeight: true + } + } + } + + // Empty state when no networks (only show after we've had networks before, meaning a real empty result) + NBox { + id: emptyBox + visible: panelViewMode === "wifi" && NetworkService.wifiEnabled && Object.keys(NetworkService.networks).length === 0 && !NetworkService.scanningActive + Layout.fillWidth: true + Layout.preferredHeight: emptyColumn.implicitHeight + Style.margin2M + + ColumnLayout { + id: emptyColumn + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginL + + Item { + Layout.fillHeight: true + } + + NIcon { + icon: "wifi-question" + pointSize: 48 + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: I18n.tr("wifi.panel.no-networks") + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + } + + Item { + Layout.fillHeight: true + } + } + } + + // Networks list container (Wiโ€‘Fi) + ColumnLayout { + id: networksList + visible: panelViewMode === "wifi" && NetworkService.wifiEnabled && Object.keys(NetworkService.networks).length > 0 + width: parent.width + spacing: Style.marginM + + WifiPrefs.WifiSubTab { + showOnlyLists: true + } + } + + // Ethernet view + NBox { + id: ethernetSection + visible: panelViewMode === "ethernet" + Layout.fillWidth: true + Layout.preferredHeight: ethernetColumn.implicitHeight + Style.margin2M + + ColumnLayout { + id: ethernetColumn + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + + // Section label + NLabel { + label: I18n.tr("wifi.panel.available-interfaces") + visible: (NetworkService.ethernetInterfaces && NetworkService.ethernetInterfaces.length > 0) + } + + // Empty state when no Ethernet devices + ColumnLayout { + id: emptyEthColumn + + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + Layout.preferredHeight: emptyEthColumn.implicitHeight + Style.margin2M + visible: !(NetworkService.ethernetInterfaces && NetworkService.ethernetInterfaces.length > 0) + spacing: Style.marginL + + Item { + Layout.fillHeight: true + } + + NIcon { + icon: "ethernet-off" + pointSize: 48 + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: I18n.tr("wifi.panel.no-ethernet-devices") + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + } + + Item { + Layout.fillHeight: true + } + } + + // Interfaces list + ColumnLayout { + id: ethIfacesList + visible: NetworkService.ethernetInterfaces && NetworkService.ethernetInterfaces.length > 0 + width: parent.width + spacing: Style.marginXS + + Repeater { + model: NetworkService.ethernetInterfaces || [] + delegate: NBox { + id: ethItem + + function getContentColors(defaultColors = [Color.mSurface, Color.mOnSurface]) { + if (modelData.connected) { + return [Color.mPrimary, Color.mOnPrimary]; + } + return defaultColors; + } + + Layout.fillWidth: true + Layout.leftMargin: Style.marginXS + Layout.rightMargin: Style.marginXS + implicitHeight: ethItemColumn.implicitHeight + Style.margin2M + radius: Style.radiusM + forceOpaque: true + color: ethItem.getContentColors()[0] + + ColumnLayout { + id: ethItemColumn + width: parent.width - Style.margin2M + x: Style.marginM + y: Style.marginM + spacing: Style.marginS + + // Main row matching Wiโ€‘Fi card style + // Click handling for the whole header row is provided by a sibling MouseArea + // anchored to this row (defined right after this RowLayout). + RowLayout { + id: ethHeaderRow + Layout.fillWidth: true + spacing: Style.marginS + + NIcon { + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + horizontalAlignment: Text.AlignLeft + icon: NetworkService.getIcon(true) + pointSize: Style.fontSizeXXL + color: ethItem.getContentColors()[1] + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + + NText { + text: modelData.connectionName || modelData.ifname + pointSize: Style.fontSizeM + font.weight: modelData.connected ? Style.fontWeightBold : Style.fontWeightMedium + color: ethItem.getContentColors()[1] + elide: Text.ElideRight + Layout.fillWidth: true + } + + RowLayout { + spacing: Style.marginXS + + NText { + text: { + if (modelData.connected) { + switch (NetworkService.networkConnectivity) { + case "full": + return I18n.tr("common.connected"); + case "limited": + case "unknown": + return I18n.tr("wifi.panel.internet-limited"); + case "portal": + return I18n.tr("wifi.panel.action-required"); + default: + return NetworkService.networkConnectivity; + } + } + return I18n.tr("common.disconnected"); + } + pointSize: Style.fontSizeXXS + color: Qt.alpha(ethItem.getContentColors()[1], Style.opacityHeavy) + } + + // Network speed indicators (visible when connected and speed > 0) + RowLayout { + visible: (modelData.connected && NetworkService.networkConnectivity === "full") && (SystemStatService.rxSpeed > 0 || SystemStatService.txSpeed > 0) + spacing: 2 + Layout.leftMargin: Style.marginXS + Layout.fillWidth: false + + NIcon { + visible: SystemStatService.rxSpeed > 0 + icon: "arrow-down" + pointSize: Style.fontSizeXXS + color: Qt.alpha(ethItem.getContentColors()[1], Style.opacityHeavy) + } + + NText { + visible: SystemStatService.rxSpeed > 0 + text: SystemStatService.formatSpeed(SystemStatService.rxSpeed) + pointSize: Style.fontSizeXXS + color: Qt.alpha(ethItem.getContentColors()[1], Style.opacityHeavy) + elide: Text.ElideNone + } + + Item { + visible: SystemStatService.rxSpeed > 0 && SystemStatService.txSpeed > 0 + width: Style.marginXS + height: 1 + } + + NIcon { + visible: SystemStatService.txSpeed > 0 + icon: "arrow-up" + pointSize: Style.fontSizeXXS + color: Qt.alpha(ethItem.getContentColors()[1], Style.opacityHeavy) + } + + NText { + visible: SystemStatService.txSpeed > 0 + text: SystemStatService.formatSpeed(SystemStatService.txSpeed) + pointSize: Style.fontSizeXXS + color: Qt.alpha(ethItem.getContentColors()[1], Style.opacityHeavy) + elide: Text.ElideNone + } + } + } + } + + // Info button on the right + NIconButton { + icon: "info" + tooltipText: I18n.tr("common.info") + baseSize: Style.baseWidgetSize * 0.75 + colorBg: Color.mSurfaceVariant + colorFg: Color.mOnSurface + colorBorder: "transparent" + colorBorderHover: "transparent" + enabled: true + visible: NetworkService.ethernetConnected + onClicked: { + if (NetworkService.activeEthernetIf === modelData.ifname && ethernetInfoExpanded) { + ethernetInfoExpanded = false; + return; + } + if (NetworkService.activeEthernetIf !== modelData.ifname) { + NetworkService.activeEthernetIf = modelData.ifname; + NetworkService.activeEthernetDetailsTimestamp = 0; + } + ethernetInfoExpanded = true; + NetworkService.refreshActiveEthernetDetails(); + } + } + } + + // Click handling without anchors in a Layout-managed item + TapHandler { + target: ethHeaderRow + onTapped: { + if (NetworkService.activeEthernetIf === modelData.ifname && ethernetInfoExpanded) { + ethernetInfoExpanded = false; + return; + } + if (NetworkService.activeEthernetIf !== modelData.ifname) { + NetworkService.activeEthernetIf = modelData.ifname; + NetworkService.activeEthernetDetailsTimestamp = 0; + } + ethernetInfoExpanded = true; + NetworkService.refreshActiveEthernetDetails(); + } + } + + // Inline Ethernet details + Rectangle { + id: ethInfoInline + visible: ethernetInfoExpanded && NetworkService.activeEthernetIf === modelData.ifname + Layout.fillWidth: true + color: Color.mSurfaceVariant + radius: Style.radiusXS + border.width: Style.borderS + border.color: Style.boxBorderColor + implicitHeight: ethInfoGrid.implicitHeight + Style.margin2S + clip: true + Layout.topMargin: Style.marginXS + + // Grid/List toggle + NIconButton { + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: Style.marginS + icon: ethernetDetailsGrid ? "layout-list" : "layout-grid" + tooltipText: ethernetDetailsGrid ? I18n.tr("tooltips.list-view") : I18n.tr("tooltips.grid-view") + baseSize: Style.baseWidgetSize * 0.65 + onClicked: { + ethernetDetailsGrid = !ethernetDetailsGrid; + Settings.data.network.wifiDetailsViewMode = ethernetDetailsGrid ? "grid" : "list"; + } + z: 1 + } + + GridLayout { + id: ethInfoGrid + anchors.fill: parent + anchors.margins: Style.marginS + anchors.rightMargin: Style.baseWidgetSize + flow: ethernetDetailsGrid ? GridLayout.TopToBottom : GridLayout.LeftToRight + rows: ethernetDetailsGrid ? 3 : 6 + columns: ethernetDetailsGrid ? 2 : 1 + columnSpacing: Style.marginM + rowSpacing: Style.marginXS + onColumnsChanged: { + if (ethInfoGrid.forceLayout) { + Qt.callLater(function () { + ethInfoGrid.forceLayout(); + }); + } + } + + // --- Item 1: Interface --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + spacing: Style.marginXS + NIcon { + icon: "ethernet" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.alignment: Qt.AlignVCenter + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(parent, I18n.tr("wifi.panel.interface")) + onExited: TooltipService.hide() + } + } + NText { + text: (NetworkService.activeEthernetDetails.ifname && NetworkService.activeEthernetDetails.ifname.length > 0) ? NetworkService.activeEthernetDetails.ifname : (NetworkService.activeEthernetIf || "-") + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + wrapMode: ethernetDetailsGrid ? Text.NoWrap : Text.WrapAtWordBoundaryOrAnywhere + elide: ethernetDetailsGrid ? Text.ElideRight : Text.ElideNone + maximumLineCount: ethernetDetailsGrid ? 1 : 6 + clip: true + + // Click-to-copy Ethernet interface name + MouseArea { + anchors.fill: parent + // Guard against undefined by normalizing to empty strings + enabled: ((NetworkService.activeEthernetDetails.ifname || "").length > 0) || ((NetworkService.activeEthernetIf || "").length > 0) + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: TooltipService.show(parent, I18n.tr("tooltips.copy-address")) + onExited: TooltipService.hide() + onClicked: { + const value = (NetworkService.activeEthernetDetails.ifname && NetworkService.activeEthernetDetails.ifname.length > 0) ? NetworkService.activeEthernetDetails.ifname : (NetworkService.activeEthernetIf || ""); + if (value.length > 0) { + Quickshell.execDetached(["wl-copy", value]); + ToastService.showNotice(I18n.tr("common.ethernet"), I18n.tr("common.copied-to-clipboard"), "ethernet"); + } + } + } + } + } + + // --- Item 2: Hardware Address --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + spacing: Style.marginXS + NIcon { + icon: "hash" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.alignment: Qt.AlignVCenter + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(parent, I18n.tr("bluetooth.panel.device-address")) + onExited: TooltipService.hide() + } + } + NText { + text: NetworkService.activeEthernetDetails.hwAddr || "-" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + wrapMode: ethernetDetailsGrid ? Text.NoWrap : Text.WrapAtWordBoundaryOrAnywhere + elide: ethernetDetailsGrid ? Text.ElideRight : Text.ElideNone + maximumLineCount: ethernetDetailsGrid ? 1 : 6 + clip: true + + MouseArea { + anchors.fill: parent + enabled: (NetworkService.activeEthernetDetails.hwAddr || "").length > 0 + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: TooltipService.show(parent, I18n.tr("tooltips.copy-address")) + onExited: TooltipService.hide() + onClicked: { + const value = NetworkService.activeEthernetDetails.hwAddr || ""; + if (value.length > 0) { + Quickshell.execDetached(["wl-copy", value]); + ToastService.showNotice(I18n.tr("common.ethernet"), I18n.tr("common.copied-to-clipboard"), "ethernet"); + } + } + } + } + } + + // --- Item 3: Link speed --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + spacing: Style.marginXS + NIcon { + icon: "gauge" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.alignment: Qt.AlignVCenter + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(parent, I18n.tr("wifi.panel.link-speed")) + onExited: TooltipService.hide() + } + } + NText { + text: (NetworkService.activeEthernetDetails.speed && NetworkService.activeEthernetDetails.speed.length > 0) ? NetworkService.activeEthernetDetails.speed : "-" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + wrapMode: ethernetDetailsGrid ? Text.NoWrap : Text.WrapAtWordBoundaryOrAnywhere + elide: ethernetDetailsGrid ? Text.ElideRight : Text.ElideNone + maximumLineCount: ethernetDetailsGrid ? 1 : 6 + clip: true + } + } + + // --- Item 4: IPv4 || IPv6 --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + spacing: Style.marginXS + NIcon { + icon: "network" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.alignment: Qt.AlignVCenter + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(parent, root.ipVersion === 4 ? I18n.tr("wifi.panel.ipv4") : I18n.tr("wifi.panel.ipv6")) + onExited: TooltipService.hide() + onClicked: { + root.ipVersion = root.ipVersion === 4 ? 6 : 4; + TooltipService.show(parent, root.ipVersion === 4 ? I18n.tr("wifi.panel.ipv4") : I18n.tr("wifi.panel.ipv6")); + } + } + } + NText { + text: root.ipVersion === 4 ? (NetworkService.activeEthernetDetails.ipv4 || "-") : ((NetworkService.activeEthernetDetails.ipv6 || []).join(", ") || "-") + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + wrapMode: ethernetDetailsGrid ? Text.NoWrap : Text.WrapAtWordBoundaryOrAnywhere + elide: ethernetDetailsGrid ? Text.ElideRight : Text.ElideNone + maximumLineCount: ethernetDetailsGrid ? 1 : 6 + clip: true + + // Click-to-copy Ethernet IP address + MouseArea { + anchors.fill: parent + enabled: root.ipVersion === 4 ? (NetworkService.activeEthernetDetails.ipv4 || "").length > 0 : (NetworkService.activeEthernetDetails.ipv6 || []).length > 0 + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: TooltipService.show(parent, I18n.tr("tooltips.copy-address")) + onExited: TooltipService.hide() + onClicked: { + const value = root.ipVersion === 4 ? (NetworkService.activeEthernetDetails.ipv4 || "") : ((NetworkService.activeEthernetDetails.ipv6 || []).join(", ") || ""); + if (value.length > 0) { + Quickshell.execDetached(["wl-copy", value]); + ToastService.showNotice(I18n.tr("common.ethernet"), I18n.tr("common.copied-to-clipboard"), "ethernet"); + } + } + } + } + } + + // --- Item 5: DNS --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + spacing: Style.marginXS + NIcon { + icon: "world" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.alignment: Qt.AlignVCenter + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(parent, root.ipVersion === 4 ? I18n.tr("wifi.panel.dns") + " (" + I18n.tr("wifi.panel.ipv4") + ")" : I18n.tr("wifi.panel.dns") + " (" + I18n.tr("wifi.panel.ipv6") + ")") + onExited: TooltipService.hide() + onClicked: { + root.ipVersion = root.ipVersion === 4 ? 6 : 4; + TooltipService.show(parent, root.ipVersion === 4 ? I18n.tr("wifi.panel.dns") + " (" + I18n.tr("wifi.panel.ipv4") + ")" : I18n.tr("wifi.panel.dns") + " (" + I18n.tr("wifi.panel.ipv6") + ")"); + } + } + } + NText { + text: root.ipVersion === 4 ? ((NetworkService.activeEthernetDetails.dns4 || []).join(", ") || "-") : ((NetworkService.activeEthernetDetails.dns6 || []).join(", ") || "-") + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + wrapMode: ethernetDetailsGrid ? Text.NoWrap : Text.WrapAtWordBoundaryOrAnywhere + elide: ethernetDetailsGrid ? Text.ElideRight : Text.ElideNone + maximumLineCount: ethernetDetailsGrid ? 1 : 6 + clip: true + + // Click-to-copy Ethernet DNS + MouseArea { + anchors.fill: parent + enabled: root.ipVersion === 4 ? (NetworkService.activeEthernetDetails.dns4 || []).length > 0 : (NetworkService.activeEthernetDetails.dns6 || []).length > 0 + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: TooltipService.show(parent, I18n.tr("tooltips.copy-address")) + onExited: TooltipService.hide() + onClicked: { + const value = root.ipVersion === 4 ? ((NetworkService.activeEthernetDetails.dns4 || []).join(", ") || "") : ((NetworkService.activeEthernetDetails.dns6 || []).join(", ") || ""); + if (value.length > 0) { + Quickshell.execDetached(["wl-copy", value]); + ToastService.showNotice(I18n.tr("common.ethernet"), I18n.tr("common.copied-to-clipboard"), "ethernet"); + } + } + } + } + } + + // --- Item 6: Gateway --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + spacing: Style.marginXS + NIcon { + icon: "router" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.alignment: Qt.AlignVCenter + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(parent, root.ipVersion === 4 ? I18n.tr("common.gateway") + " (" + I18n.tr("wifi.panel.ipv4") + ")" : I18n.tr("common.gateway") + " (" + I18n.tr("wifi.panel.ipv6") + ")") + onExited: TooltipService.hide() + onClicked: { + root.ipVersion = root.ipVersion === 4 ? 6 : 4; + TooltipService.show(parent, root.ipVersion === 4 ? I18n.tr("common.gateway") + " (" + I18n.tr("wifi.panel.ipv4") + ")" : I18n.tr("common.gateway") + " (" + I18n.tr("wifi.panel.ipv6") + ")"); + } + } + } + NText { + text: root.ipVersion === 4 ? (NetworkService.activeEthernetDetails.gateway4 || "-") : ((NetworkService.activeEthernetDetails.gateway6 || []).join(", ") || "-") + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + wrapMode: ethernetDetailsGrid ? Text.NoWrap : Text.WrapAtWordBoundaryOrAnywhere + elide: ethernetDetailsGrid ? Text.ElideRight : Text.ElideNone + maximumLineCount: ethernetDetailsGrid ? 1 : 6 + clip: true + + // Click-to-copy Ethernet Gateway + MouseArea { + anchors.fill: parent + enabled: root.ipVersion === 4 ? (NetworkService.activeEthernetDetails.gateway4 || "").length > 0 : (NetworkService.activeEthernetDetails.gateway6 || []).length > 0 + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: TooltipService.show(parent, I18n.tr("tooltips.copy-address")) + onExited: TooltipService.hide() + onClicked: { + const value = root.ipVersion === 4 ? (NetworkService.activeEthernetDetails.gateway4 || "") : ((NetworkService.activeEthernetDetails.gateway6 || []).join(", ") || ""); + if (value.length > 0) { + Quickshell.execDetached(["wl-copy", value]); + ToastService.showNotice(I18n.tr("common.ethernet"), I18n.tr("common.copied-to-clipboard"), "ethernet"); + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml new file mode 100644 index 0000000..64968ff --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml @@ -0,0 +1,1022 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.Notifications +import Quickshell.Wayland +import qs.Commons +import qs.Modules.MainScreen +import qs.Modules.Panels.Settings +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +// Notification History panel +SmartPanel { + id: root + + preferredWidth: Math.round((Settings.data.notifications.enableMarkdown ? 540 : 440) * Style.uiScaleRatio) + preferredHeight: Math.round((Settings.data.notifications.enableMarkdown ? 640 : 540) * Style.uiScaleRatio) + + onOpened: { + NotificationService.updateLastSeenTs(); + } + + panelContent: Rectangle { + id: panelContent + color: "transparent" + focus: true + + // Force focus when opened + Connections { + target: root + function onOpened() { + panelContent.forceActiveFocus(); + } + } + + Keys.onPressed: event => { + // Tab navigation for categories + if (event.key === Qt.Key_Tab) { + currentRange = (currentRange + 1) % 4; + event.accepted = true; + return; + } + + if (event.key === Qt.Key_Backtab) { // Shift+Tab + currentRange = (currentRange - 1 + 4) % 4; + event.accepted = true; + return; + } + + // Navigation Up/Down + if (checkKey(event, 'up')) { + moveSelection(-1); + event.accepted = true; + return; + } + if (checkKey(event, 'down')) { + moveSelection(1); + event.accepted = true; + return; + } + + // Action Navigation Left/Right + if (checkKey(event, 'left')) { + moveAction(-1); + event.accepted = true; + return; + } + if (checkKey(event, 'right')) { + moveAction(1); + event.accepted = true; + return; + } + + // Activation (Enter) + if (checkKey(event, 'enter')) { + activateSelection(); + event.accepted = true; + return; + } + + // Removal (Delete/Remove) + if (checkKey(event, 'remove') || event.key === Qt.Key_Delete) { + removeSelection(); + event.accepted = true; + return; + } + } + + function parseActions(actions) { + try { + return JSON.parse(actions || "[]"); + } catch (e) { + return []; + } + } + + function moveSelection(dir) { + var m = NotificationService.historyModel; + if (!m || m.count === 0) + return; + + var newIndex = focusIndex; + var found = false; + var count = m.count; + + // If no selection yet, start from beginning (or end if up) + if (focusIndex === -1) { + if (dir > 0) + newIndex = -1; + else + newIndex = count; + } + + // Loop to find next visible item + var loopCount = 0; + while (loopCount < count) { + newIndex += dir; + + // Bounds check + if (newIndex < 0 || newIndex >= count) { + break; // Stop at edges + } + + var item = m.get(newIndex); + if (item && isInCurrentRange(item.timestamp)) { + found = true; + break; + } + loopCount++; + } + + if (found) { + focusIndex = newIndex; + actionIndex = -1; // Reset action selection + scrollToItem(focusIndex); + } + } + + function moveAction(dir) { + if (focusIndex === -1) + return; + var item = NotificationService.historyModel.get(focusIndex); + if (!item) + return; + + var actions = parseActions(item.actionsJson); + + if (actions.length === 0) + return; + + var newActionIndex = actionIndex + dir; + + // Clamp between -1 (body) and actions.length - 1 + if (newActionIndex < -1) + newActionIndex = -1; + if (newActionIndex >= actions.length) + newActionIndex = actions.length - 1; + + actionIndex = newActionIndex; + } + + function activateSelection() { + if (focusIndex === -1) + return; + var item = NotificationService.historyModel.get(focusIndex); + if (!item) + return; + + if (actionIndex >= 0) { + var actions = parseActions(item.actionsJson); + if (actionIndex < actions.length) { + if (NotificationService.invokeAction(item.id, actions[actionIndex].identifier)) + root.close(); + } + } else { + var delegate = notificationColumn.children[focusIndex]; + if (!delegate) + return; + if (!(delegate.canExpand || delegate.isExpanded)) + return; + + if (scrollView.expandedId === item.id) { + scrollView.expandedId = ""; + } else { + scrollView.expandedId = item.id; + } + } + } + + function removeSelection() { + if (focusIndex === -1) + return; + var item = NotificationService.historyModel.get(focusIndex); + if (!item) + return; + + NotificationService.removeFromHistory(item.id); + // selection updates automatically? + // If we remove item at index i, the next item becomes index i. + // So focusIndex is still valid (unless it was last item). + // But we should re-verify if it exists. + // Actually NotificationService removal might be async or immediate. + // If immediate, model count decreases. + // We might need to clamp focusIndex. + // Let's handle this in a helper or just let the user navigate again. + // Better UX: select next available or previous if last. + } + + function scrollToItem(index) { + // Find the delegate item + if (index < 0 || index >= notificationColumn.children.length) + return; + + var item = notificationColumn.children[index]; + if (item && item.visible) { + // Use the internal flickable from NScrollView for accurate scrolling + var flickable = scrollView._internalFlickable; + if (!flickable || !flickable.contentItem) + return; + + var pos = flickable.contentItem.mapFromItem(item, 0, 0); + var itemY = pos.y; + var itemHeight = item.height; + + var currentContentY = flickable.contentY; + var viewHeight = flickable.height; + + // Check if above visible area + if (itemY < currentContentY) { + flickable.contentY = Math.max(0, itemY - Style.marginM); + } else + // Check if below visible area + if (itemY + itemHeight > currentContentY + viewHeight) { + flickable.contentY = (itemY + itemHeight) - viewHeight + Style.marginM; + } + } + } + + // Calculate content height based on header + tabs (if visible) + content + property real calculatedHeight: { + if (NotificationService.historyModel.count === 0) { + return headerBox.implicitHeight + scrollView.implicitHeight + Style.margin2L + Style.marginM; + } + return headerBox.implicitHeight + scrollView.implicitHeight + Style.margin2L + Style.marginM; + } + property real contentPreferredHeight: Math.min(root.preferredHeight, Math.ceil(calculatedHeight)) + + property real layoutWidth: Math.max(1, root.preferredWidth - Style.margin2L) + + // State (lazy-loaded with panelContent) + property var rangeCounts: [0, 0, 0, 0] + property var lastKnownDate: null // Track the current date to detect day changes + + // UI state (lazy-loaded with panelContent) + // 0 = All, 1 = Today, 2 = Yesterday, 3 = Earlier + property int currentRange: 1 // start on Today by default + property bool groupByDate: true + onCurrentRangeChanged: resetFocus() + + // Keyboard navigation state + property int focusIndex: -1 + property int actionIndex: -1 // For actions within a notification + + function resetFocus() { + focusIndex = -1; + actionIndex = -1; + } + + function checkKey(event, settingName) { + return Keybinds.checkKey(event, settingName, Settings); + } + + // Helper functions (lazy-loaded with panelContent) + function dateOnly(d) { + return new Date(d.getFullYear(), d.getMonth(), d.getDate()); + } + + function getDateKey(d) { + // Returns a string key for the date (YYYY-MM-DD) for comparison + var date = dateOnly(d); + return date.getFullYear() + "-" + date.getMonth() + "-" + date.getDate(); + } + + function rangeForTimestamp(ts) { + var dt = new Date(ts); + var today = dateOnly(new Date()); + var thatDay = dateOnly(dt); + + var diffMs = today - thatDay; + var diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + + if (diffDays === 0) + return 0; + if (diffDays === 1) + return 1; + return 2; + } + + function recalcRangeCounts() { + var m = NotificationService.historyModel; + if (!m || typeof m.count === "undefined" || m.count <= 0) { + panelContent.rangeCounts = [0, 0, 0, 0]; + return; + } + + var counts = [0, 0, 0, 0]; + + counts[0] = m.count; + + for (var i = 0; i < m.count; ++i) { + var item = m.get(i); + if (!item || typeof item.timestamp === "undefined") + continue; + var r = rangeForTimestamp(item.timestamp); + counts[r + 1] = counts[r + 1] + 1; + } + + panelContent.rangeCounts = counts; + } + + function isInCurrentRange(ts) { + if (currentRange === 0) + return true; + return rangeForTimestamp(ts) === (currentRange - 1); + } + + function countForRange(range) { + return rangeCounts[range] || 0; + } + + function hasNotificationsInCurrentRange() { + var m = NotificationService.historyModel; + if (!m || m.count === 0) { + return false; + } + for (var i = 0; i < m.count; ++i) { + var item = m.get(i); + if (item && isInCurrentRange(item.timestamp)) + return true; + } + return false; + } + + Component.onCompleted: { + recalcRangeCounts(); + // Initialize lastKnownDate + lastKnownDate = getDateKey(new Date()); + } + + Connections { + target: NotificationService.historyModel + function onCountChanged() { + panelContent.recalcRangeCounts(); + } + } + + // Timer to check for day changes at midnight + Timer { + id: dayChangeTimer + interval: 60000 // Check every minute + repeat: true + running: true // Always runs when panelContent exists (panel is open) + onTriggered: { + var currentDateKey = panelContent.getDateKey(new Date()); + if (panelContent.lastKnownDate !== null && panelContent.lastKnownDate !== currentDateKey) { + // Day has changed, recalculate counts + panelContent.recalcRangeCounts(); + } + panelContent.lastKnownDate = currentDateKey; + } + } + + ColumnLayout { + id: mainColumn + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + // Header section + NBox { + id: headerBox + Layout.fillWidth: true + implicitHeight: header.implicitHeight + Style.margin2M + + ColumnLayout { + id: header + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + + RowLayout { + id: headerRow + NIcon { + icon: "bell" + pointSize: Style.fontSizeXXL + color: Color.mPrimary + } + + NText { + text: I18n.tr("common.notifications") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + } + + NIconButton { + icon: NotificationService.doNotDisturb ? "bell-off" : "bell" + tooltipText: NotificationService.doNotDisturb ? I18n.tr("tooltips.do-not-disturb-enabled") : I18n.tr("tooltips.do-not-disturb-enabled") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: NotificationService.doNotDisturb = !NotificationService.doNotDisturb + } + + NIconButton { + icon: "trash" + tooltipText: I18n.tr("actions.clear-history") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: { + NotificationService.clearHistory(); + // Close panel as there is nothing more to see. + root.close(); + } + } + + NIconButton { + icon: "settings" + tooltipText: I18n.tr("common.settings") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: { + SettingsPanelService.openToTab(SettingsPanel.Tab.Notifications, 0, screen); + root.close(); + } + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: root.close() + } + } + + // Time range tabs ([All] / [Today] / [Yesterday] / [Earlier]) + NTabBar { + id: tabsBox + Layout.fillWidth: true + visible: NotificationService.historyModel.count > 0 && panelContent.groupByDate + currentIndex: panelContent.currentRange + tabHeight: Style.toOdd(Style.baseWidgetSize * 0.8) + spacing: Style.marginXS + distributeEvenly: true + + NTabButton { + tabIndex: 0 + text: I18n.tr("launcher.categories.all") + " (" + panelContent.countForRange(0) + ")" + checked: tabsBox.currentIndex === 0 + onClicked: panelContent.currentRange = 0 + pointSize: Style.fontSizeXS + } + + NTabButton { + tabIndex: 1 + text: I18n.tr("notifications.range.today") + " (" + panelContent.countForRange(1) + ")" + checked: tabsBox.currentIndex === 1 + onClicked: panelContent.currentRange = 1 + pointSize: Style.fontSizeXS + } + + NTabButton { + tabIndex: 2 + text: I18n.tr("notifications.range.yesterday") + " (" + panelContent.countForRange(2) + ")" + checked: tabsBox.currentIndex === 2 + onClicked: panelContent.currentRange = 2 + pointSize: Style.fontSizeXS + } + + NTabButton { + tabIndex: 3 + text: I18n.tr("notifications.range.earlier") + " (" + panelContent.countForRange(3) + ")" + checked: tabsBox.currentIndex === 3 + onClicked: panelContent.currentRange = 3 + pointSize: Style.fontSizeXS + } + } + } + } + + // Notification list container with gradient overlay + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + NScrollView { + id: scrollView + anchors.fill: parent + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AsNeeded + reserveScrollbarSpace: false + gradientColor: Color.mSurface + + // Track which notification is expanded + property string expandedId: "" + + ColumnLayout { + width: panelContent.layoutWidth + spacing: Style.marginM + + // Empty state when no notifications + NBox { + visible: !panelContent.hasNotificationsInCurrentRange() + Layout.fillWidth: true + Layout.preferredHeight: emptyState.implicitHeight + Style.marginXL + + ColumnLayout { + id: emptyState + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + + Item { + Layout.fillHeight: true + } + + NIcon { + icon: "bell-off" + pointSize: (NotificationService.historyModel.count === 0) ? 48 : Style.baseWidgetSize + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: I18n.tr("notifications.panel.no-notifications") + pointSize: (NotificationService.historyModel.count === 0) ? Style.fontSizeL : Style.fontSizeM + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + } + + NText { + visible: NotificationService.historyModel.count === 0 + text: I18n.tr("notifications.panel.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + wrapMode: Text.WordWrap + } + + Item { + Layout.fillHeight: true + } + } + } + + // Notification list container + Item { + visible: panelContent.hasNotificationsInCurrentRange() + Layout.fillWidth: true + Layout.preferredHeight: notificationColumn.implicitHeight + + Column { + id: notificationColumn + width: panelContent.layoutWidth + spacing: Style.marginM + + Repeater { + model: NotificationService.historyModel + + delegate: Item { + id: notificationDelegate + width: parent.width + visible: panelContent.isInCurrentRange(model.timestamp) + height: visible && !isRemoving ? contentColumn.height + Style.margin2M : 0 + + property int listIndex: index + property string notificationId: model.id + property string appName: model.appName || "" + property bool isExpanded: scrollView.expandedId === notificationId + property bool canExpand: summaryText.truncated || bodyText.truncated + property real swipeOffset: 0 + property real pressGlobalX: 0 + property real pressGlobalY: 0 + property bool isSwiping: false + property bool isRemoving: false + property string pendingLink: "" + readonly property real swipeStartThreshold: Math.round(16 * Style.uiScaleRatio) + readonly property real swipeDismissThreshold: Math.max(110, width * 0.3) + readonly property int removeAnimationDuration: Style.animationNormal + readonly property int notificationTextFormat: (Settings.data.notifications.enableMarkdown && notificationDelegate.isExpanded) ? Text.MarkdownText : Text.StyledText + readonly property real actionButtonSize: Style.baseWidgetSize * 0.7 + readonly property real buttonClusterWidth: notificationDelegate.actionButtonSize * 2 + Style.marginXS + readonly property real iconSize: Math.round(40 * Style.uiScaleRatio) + + function isSafeLink(link) { + if (!link) + return false; + const lower = link.toLowerCase(); + const schemes = ["http://", "https://", "mailto:"]; + return schemes.some(scheme => lower.startsWith(scheme)); + } + + function linkAtPoint(x, y) { + if (!Settings.data.notifications.enableMarkdown || !notificationDelegate.isExpanded) + return ""; + + if (summaryText) { + const summaryPoint = summaryText.mapFromItem(historyInteractionArea, x, y); + if (summaryPoint.x >= 0 && summaryPoint.y >= 0 && summaryPoint.x <= summaryText.width && summaryPoint.y <= summaryText.height) { + const summaryLink = summaryText.linkAt ? summaryText.linkAt(summaryPoint.x, summaryPoint.y) : ""; + if (isSafeLink(summaryLink)) + return summaryLink; + } + } + + if (bodyText) { + const bodyPoint = bodyText.mapFromItem(historyInteractionArea, x, y); + if (bodyPoint.x >= 0 && bodyPoint.y >= 0 && bodyPoint.x <= bodyText.width && bodyPoint.y <= bodyText.height) { + const bodyLink = bodyText.linkAt ? bodyText.linkAt(bodyPoint.x, bodyPoint.y) : ""; + if (isSafeLink(bodyLink)) + return bodyLink; + } + } + + return ""; + } + + function updateCursorAt(x, y) { + if (notificationDelegate.isExpanded && notificationDelegate.linkAtPoint(x, y)) { + historyInteractionArea.cursorShape = Qt.PointingHandCursor; + } else { + historyInteractionArea.cursorShape = Qt.ArrowCursor; + } + } + + transform: Translate { + x: notificationDelegate.swipeOffset + } + + function dismissBySwipe() { + if (isRemoving) + return; + isRemoving = true; + isSwiping = false; + + if (Settings.data.general.animationDisabled) { + NotificationService.removeFromHistory(notificationId); + return; + } + + swipeOffset = swipeOffset >= 0 ? width + Style.marginL : -width - Style.marginL; + opacity = 0; + removeTimer.restart(); + } + + Timer { + id: removeTimer + interval: notificationDelegate.removeAnimationDuration + repeat: false + onTriggered: NotificationService.removeFromHistory(notificationId) + } + + Behavior on swipeOffset { + enabled: !Settings.data.general.animationDisabled && !notificationDelegate.isSwiping + NumberAnimation { + duration: notificationDelegate.removeAnimationDuration + easing.type: Easing.OutCubic + } + } + + Behavior on opacity { + enabled: !Settings.data.general.animationDisabled && notificationDelegate.isRemoving + NumberAnimation { + duration: notificationDelegate.removeAnimationDuration + easing.type: Easing.OutCubic + } + } + + Behavior on height { + enabled: !Settings.data.general.animationDisabled && notificationDelegate.isRemoving + NumberAnimation { + duration: notificationDelegate.removeAnimationDuration + easing.type: Easing.OutCubic + } + } + + Behavior on y { + enabled: !Settings.data.general.animationDisabled && notificationDelegate.isRemoving + NumberAnimation { + duration: notificationDelegate.removeAnimationDuration + easing.type: Easing.OutCubic + } + } + + // Parse actions safely + property var actionsList: parseActions(model.actionsJson) + + property bool isFocused: index === panelContent.focusIndex + + Rectangle { + anchors.fill: parent + radius: Style.radiusM + color: Color.mSurfaceVariant + border.color: { + if (notificationDelegate.isFocused) + return Color.mPrimary; + if (Settings.data.ui.boxBorderEnabled) + return Qt.alpha(Color.mOutline, Style.opacityHeavy); + return "transparent"; + } + border.width: notificationDelegate.isFocused ? Style.borderM : Style.borderS + + Behavior on color { + enabled: !Settings.data.general.animationDisabled + ColorAnimation { + duration: Style.animationFast + } + } + } + + // Click to expand/collapse + MouseArea { + id: historyInteractionArea + anchors.fill: parent + anchors.rightMargin: notificationDelegate.buttonClusterWidth + Style.marginM + enabled: !notificationDelegate.isRemoving + hoverEnabled: true + cursorShape: Qt.ArrowCursor + onPressed: mouse => { + panelContent.focusIndex = index; + panelContent.actionIndex = -1; + + if (notificationDelegate.isExpanded) { + const link = notificationDelegate.linkAtPoint(mouse.x, mouse.y); + if (link) { + notificationDelegate.pendingLink = link; + } else { + notificationDelegate.pendingLink = ""; + } + } + + if (mouse.button !== Qt.LeftButton) + return; + const globalPoint = historyInteractionArea.mapToGlobal(mouse.x, mouse.y); + notificationDelegate.pressGlobalX = globalPoint.x; + notificationDelegate.pressGlobalY = globalPoint.y; + notificationDelegate.isSwiping = false; + } + onPositionChanged: mouse => { + if (!(mouse.buttons & Qt.LeftButton) || notificationDelegate.isRemoving) + return; + + const globalPoint = historyInteractionArea.mapToGlobal(mouse.x, mouse.y); + const deltaX = globalPoint.x - notificationDelegate.pressGlobalX; + const deltaY = globalPoint.y - notificationDelegate.pressGlobalY; + + if (!notificationDelegate.isSwiping) { + if (Math.abs(deltaX) < notificationDelegate.swipeStartThreshold) + return; + + // Only start a swipe-dismiss when horizontal movement is dominant. + if (Math.abs(deltaX) <= Math.abs(deltaY) * 1.15) { + return; + } + notificationDelegate.isSwiping = true; + } + + if (notificationDelegate.pendingLink && Math.abs(deltaX) >= notificationDelegate.swipeStartThreshold) { + notificationDelegate.pendingLink = ""; + } + + notificationDelegate.swipeOffset = deltaX; + } + onReleased: mouse => { + if (mouse.button !== Qt.LeftButton) + return; + + if (notificationDelegate.isSwiping) { + if (Math.abs(notificationDelegate.swipeOffset) >= notificationDelegate.swipeDismissThreshold) { + notificationDelegate.dismissBySwipe(); + } else { + notificationDelegate.swipeOffset = 0; + } + notificationDelegate.isSwiping = false; + notificationDelegate.pendingLink = ""; + return; + } + + if (notificationDelegate.pendingLink) { + Qt.openUrlExternally(notificationDelegate.pendingLink); + notificationDelegate.pendingLink = ""; + return; + } + + // Without a default action, or if invoking it fails, + // fall back to focusing the sender window by app identity. + var actions = notificationDelegate.actionsList; + var hasDefault = actions.some(function (a) { + return a.identifier === "default"; + }); + if (hasDefault && NotificationService.invokeAction(notificationDelegate.notificationId, "default")) { + root.close(); + } else { + NotificationService.focusSenderWindow(notificationDelegate.appName); + root.close(); + } + } + onCanceled: { + notificationDelegate.isSwiping = false; + notificationDelegate.swipeOffset = 0; + notificationDelegate.pendingLink = ""; + historyInteractionArea.cursorShape = Qt.ArrowCursor; + } + } + + HoverHandler { + target: historyInteractionArea + onPointChanged: notificationDelegate.updateCursorAt(point.position.x, point.position.y) + onActiveChanged: { + if (!active) { + historyInteractionArea.cursorShape = Qt.ArrowCursor; + } + } + } + + onVisibleChanged: { + if (!visible) { + notificationDelegate.isSwiping = false; + notificationDelegate.swipeOffset = 0; + notificationDelegate.opacity = 1; + notificationDelegate.isRemoving = false; + removeTimer.stop(); + } + } + + Component.onDestruction: removeTimer.stop() + + Column { + id: contentColumn + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Style.marginM + spacing: Style.marginM + + Row { + width: parent.width + spacing: Style.marginM + + // Icon + NImageRounded { + anchors.verticalCenter: notificationDelegate.isExpanded ? undefined : parent.verticalCenter + width: notificationDelegate.iconSize + height: notificationDelegate.iconSize + radius: Math.min(Style.radiusL, width / 2) + imagePath: model.cachedImage || model.originalImage || "" + borderColor: "transparent" + borderWidth: 0 + fallbackIcon: "bell" + fallbackIconSize: 24 + } + + // Content + Column { + width: parent.width - notificationDelegate.iconSize - notificationDelegate.buttonClusterWidth - Style.margin2M + spacing: Style.marginXS + + // Header row with app name and timestamp + Row { + width: parent.width + spacing: Style.marginS + + // Urgency indicator + Rectangle { + width: 6 + height: 6 + anchors.verticalCenter: parent.verticalCenter + radius: 3 + visible: model.urgency !== 1 + color: { + if (model.urgency === 2) + return Color.mError; + else if (model.urgency === 0) + return Color.mOnSurfaceVariant; + else + return "transparent"; + } + } + + NText { + text: model.appName || "Unknown App" + pointSize: Style.fontSizeXS + font.weight: Style.fontWeightBold + color: Color.mSecondary + } + + NText { + textFormat: Text.PlainText + text: " " + Time.formatRelativeTime(model.timestamp) + pointSize: Style.fontSizeXXS + color: Color.mOnSurfaceVariant + anchors.bottom: parent.bottom + } + } + + // Summary + NText { + id: summaryText + width: parent.width + text: (Settings.data.notifications.enableMarkdown && notificationDelegate.isExpanded) ? (model.summaryMarkdown || I18n.tr("common.no-summary")) : (model.summary || I18n.tr("common.no-summary")) + pointSize: Style.fontSizeM + color: Color.mOnSurface + textFormat: notificationDelegate.notificationTextFormat + wrapMode: Text.Wrap + maximumLineCount: notificationDelegate.isExpanded ? 999 : 2 + elide: Text.ElideRight + } + + // Body + NText { + id: bodyText + width: parent.width + text: (Settings.data.notifications.enableMarkdown && notificationDelegate.isExpanded) ? (model.bodyMarkdown || "") : (model.body || "") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + textFormat: notificationDelegate.notificationTextFormat + wrapMode: Text.Wrap + maximumLineCount: notificationDelegate.isExpanded ? 999 : 3 + elide: Text.ElideRight + visible: text.length > 0 + } + + // Actions Flow + Flow { + width: parent.width + spacing: Style.marginS + visible: notificationDelegate.actionsList.length > 0 + + Repeater { + model: notificationDelegate.actionsList + + delegate: NButton { + text: modelData.text + fontSize: Style.fontSizeS + + readonly property bool actionNavActive: notificationDelegate.isFocused && panelContent.actionIndex !== -1 + readonly property bool isSelected: actionNavActive && panelContent.actionIndex === index + + backgroundColor: isSelected ? Color.mSecondary : Color.mPrimary + textColor: isSelected ? Color.mOnSecondary : Color.mOnPrimary + + outlined: false + implicitHeight: 24 + + onHoveredChanged: { + if (hovered) { + panelContent.focusIndex = notificationDelegate.listIndex; + } + } + + // Capture modelData in a property to avoid reference errors + property var actionData: modelData + onClicked: { + if (NotificationService.invokeAction(notificationDelegate.notificationId, actionData.identifier)) + root.close(); + } + } + } + } + } + + Item { + width: notificationDelegate.buttonClusterWidth + height: notificationDelegate.actionButtonSize + + Row { + anchors.right: parent.right + spacing: Style.marginXS + + NIconButton { + id: expandButton + icon: notificationDelegate.isExpanded ? "chevron-up" : "chevron-down" + tooltipText: notificationDelegate.isExpanded ? I18n.tr("notifications.panel.click-to-collapse") || "Click to collapse" : I18n.tr("notifications.panel.click-to-expand") || "Click to expand" + baseSize: notificationDelegate.actionButtonSize + opacity: (notificationDelegate.canExpand || notificationDelegate.isExpanded) ? 1.0 : 0.0 + enabled: notificationDelegate.canExpand || notificationDelegate.isExpanded + + onClicked: { + notificationDelegate.pendingLink = ""; + historyInteractionArea.cursorShape = Qt.ArrowCursor; + if (scrollView.expandedId === notificationId) { + scrollView.expandedId = ""; + } else { + scrollView.expandedId = notificationId; + } + } + } + + // Delete button + NIconButton { + icon: "trash" + tooltipText: I18n.tr("tooltips.delete-notification") + baseSize: notificationDelegate.actionButtonSize + + onClicked: { + NotificationService.removeFromHistory(notificationId); + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Plugins/PluginPanelSlot.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Plugins/PluginPanelSlot.qml new file mode 100644 index 0000000..098feac --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Plugins/PluginPanelSlot.qml @@ -0,0 +1,228 @@ +import QtQuick +import Quickshell +import qs.Commons +import qs.Modules.MainScreen +import qs.Services.Noctalia +import qs.Services.UI + +/** +* Generic plugin panel slot that can be reused for different plugins +*/ +SmartPanel { + id: root + + // Which plugin slot this is (1 or 2) + property int slotNumber: 1 + + // Currently loaded plugin ID (empty if no plugin using this slot) + property string currentPluginId: "" + + // Plugin instance + property var pluginInstance: null + + // Reference to the plugin content loader (set when panel content is created) + property var contentLoader: null + + // Pass through anchor properties from plugin panel content + panelAnchorHorizontalCenter: pluginInstance?.panelAnchorHorizontalCenter ?? false + panelAnchorVerticalCenter: pluginInstance?.panelAnchorVerticalCenter ?? false + panelAnchorTop: pluginInstance?.panelAnchorTop ?? false + panelAnchorBottom: pluginInstance?.panelAnchorBottom ?? false + panelAnchorLeft: pluginInstance?.panelAnchorLeft ?? false + panelAnchorRight: pluginInstance?.panelAnchorRight ?? false + + // Panel background color + panelBackgroundColor: pluginInstance?.panelBackgroundColor ?? Color.mSurface + + // Panel content is dynamically loaded + panelContent: Component { + Item { + id: panelContainer + + // Required by SmartPanel for background rendering geometry + readonly property var geometryPlaceholder: pluginContentItem + + // Panel properties expected by SmartPanel + // A plugin can opt out of attachment (allowAttach: false) but cannot override + // the global "attach panels to bar" setting โ€” if that setting is off, panels + // stay detached regardless of what the plugin requests. + readonly property bool allowAttach: { + var globalAllow = Settings.data.ui.panelsAttachedToBar || root.forceAttachToBar; + if (!globalAllow) + return false; + if (pluginContentLoader.item && pluginContentLoader.item.allowAttach !== undefined) + return pluginContentLoader.item.allowAttach; + return globalAllow; + } + // Expose preferred dimensions from plugin panel content + // Only define these if the plugin provides them + property var contentPreferredWidth: { + if (pluginContentLoader.item && pluginContentLoader.item.contentPreferredWidth !== undefined && pluginContentLoader.item.contentPreferredWidth > 0) { + return pluginContentLoader.item.contentPreferredWidth; + } + return undefined; + } + + property var contentPreferredHeight: { + if (pluginContentLoader.item && pluginContentLoader.item.contentPreferredHeight !== undefined && pluginContentLoader.item.contentPreferredHeight > 0) { + return pluginContentLoader.item.contentPreferredHeight; + } + return undefined; + } + + anchors.fill: parent + + // Dynamic plugin content + Item { + id: pluginContentItem + anchors.fill: parent + + // Plugin content loader, pluginApi is injected synchronously in loadPluginPanel() + Loader { + id: pluginContentLoader + anchors.fill: parent + active: false + } + } + + Component.onCompleted: { + // Store reference to the loader so loadPluginPanel can access it + root.contentLoader = pluginContentLoader; + + // Load plugin panel content if assigned + if (root.currentPluginId !== "") { + root.loadPluginPanel(root.currentPluginId); + } + } + } + } + + // Load a plugin's panel content + function loadPluginPanel(pluginId) { + if (!PluginService.isPluginLoaded(pluginId)) { + Logger.w("PluginPanelSlot", "Plugin not loaded:", pluginId); + return false; + } + + var plugin = PluginService.loadedPlugins[pluginId]; + if (!plugin || !plugin.manifest) { + Logger.w("PluginPanelSlot", "Plugin data not found:", pluginId); + return false; + } + + if (!plugin.manifest.entryPoints || !plugin.manifest.entryPoints.panel) { + Logger.w("PluginPanelSlot", "Plugin does not provide a panel:", pluginId); + return false; + } + + // Check if loader is available + if (!root.contentLoader) { + Logger.e("PluginPanelSlot", "Content loader not available yet"); + return false; + } + + // Clear any stale pluginInstance before loading new content + root.pluginInstance = null; + + var pluginDir = PluginRegistry.getPluginDir(pluginId); + var panelPath = pluginDir + "/" + plugin.manifest.entryPoints.panel; + + Logger.i("PluginPanelSlot", "Loading panel for plugin:", pluginId, "in slot", root.slotNumber); + + // Load the panel component with cache-busting version parameter + var loadVersion = PluginRegistry.pluginLoadVersions[pluginId] || 0; + var component = Qt.createComponent("file://" + panelPath + "?v=" + loadVersion); + + if (component.status === Component.Ready) { + finalizePluginLoad(pluginId, component); + return true; + } else if (component.status === Component.Loading) { + // Handle async component loading - wait for it to be ready + Logger.d("PluginPanelSlot", "Component loading asynchronously for:", pluginId); + component.statusChanged.connect(function () { + if (component.status === Component.Ready) { + finalizePluginLoad(pluginId, component); + // Force SmartPanel to recalculate position now that content is ready + if (root.isPanelVisible) { + root.setPosition(); + } + } else if (component.status === Component.Error) { + PluginService.recordPluginError(pluginId, "panel", component.errorString()); + } + }); + return true; // Will be loaded asynchronously + } else if (component.status === Component.Error) { + PluginService.recordPluginError(pluginId, "panel", component.errorString()); + return false; + } + + return false; + } + + // Helper function to finalize plugin content loading + function finalizePluginLoad(pluginId, component) { + // Get plugin API + var api = PluginService.getPluginAPI(pluginId); + + // Use setSource with initial properties so pluginApi is available + // from the first binding evaluation (before onLoaded) + root.contentLoader.active = true; + root.contentLoader.setSource(component.url, api ? { + "pluginApi": api + } : {}); + + if (root.contentLoader.item) { + root.pluginInstance = root.contentLoader.item; + root.currentPluginId = pluginId; + + Logger.i("PluginPanelSlot", "Panel loaded for:", pluginId); + } else { + Logger.e("PluginPanelSlot", "Failed to get loader item for:", pluginId); + } + } + + // Unload current plugin panel + function unloadPluginPanel() { + if (root.currentPluginId === "") { + return; + } + + Logger.i("PluginPanelSlot", "Unloading panel from slot", root.slotNumber); + + if (root.contentLoader) { + root.contentLoader.active = false; + root.contentLoader.sourceComponent = null; + } + root.pluginInstance = null; + root.currentPluginId = ""; + } + + // Register with PanelService + Component.onCompleted: { + PanelService.registerPanel(root); + } + + // Update plugin's panelOpenScreen when this panel opens/closes + onOpened: { + if (root.currentPluginId !== "") { + var api = PluginService.getPluginAPI(root.currentPluginId); + if (api) { + api.panelOpenScreen = root.screen; + Logger.d("PluginPanelSlot", "Set panelOpenScreen for", root.currentPluginId, "to", root.screen?.name); + } + } + } + + onClosed: { + if (root.currentPluginId !== "") { + var api = PluginService.getPluginAPI(root.currentPluginId); + if (api) { + api.panelOpenScreen = null; + Logger.d("PluginPanelSlot", "Cleared panelOpenScreen for", root.currentPluginId); + } + } + // Clear stale references when panel closes (content is destroyed by SmartPanel) + root.pluginInstance = null; + root.contentLoader = null; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/SessionMenu/SessionMenu.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/SessionMenu/SessionMenu.qml new file mode 100644 index 0000000..7d508ad --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/SessionMenu/SessionMenu.qml @@ -0,0 +1,1136 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Widgets +import qs.Commons +import qs.Modules.MainScreen +import qs.Services.Compositor +import qs.Services.UI +import qs.Widgets + +SmartPanel { + id: root + + readonly property bool largeButtonsStyle: Settings.data.sessionMenu.largeButtonsStyle || false + readonly property bool largeButtonsLayout: Settings.data.sessionMenu.largeButtonsLayout || "grid" + + // Large buttons style is fullscreen โ€” disable blur behind it + blurEnabled: !largeButtonsStyle + + // Make panel background transparent for large buttons style + panelBackgroundColor: largeButtonsStyle ? "transparent" : Color.mSurface + + preferredWidth: largeButtonsStyle ? 0 : Math.round(440 * Style.uiScaleRatio) + preferredWidthRatio: largeButtonsStyle ? 1.0 : 0 + preferredHeight: { + if (largeButtonsStyle) { + return 0; // Use ratio instead + } + var headerHeight = Settings.data.sessionMenu.showHeader ? Style.baseWidgetSize * 0.6 : 0; + + var dividerHeight = Settings.data.sessionMenu.showHeader ? Style.marginS : 0; + var buttonHeight = Style.baseWidgetSize * 1.3 * Style.uiScaleRatio; + var buttonSpacing = Style.marginS; + var enabledCount = powerOptions.length; + + var headerSpacing = Settings.data.sessionMenu.showHeader ? Style.margin2L : 0; + var baseHeight = (Style.marginL * 4) + headerHeight + dividerHeight + headerSpacing; + var buttonsHeight = enabledCount > 0 ? (buttonHeight * enabledCount) + (buttonSpacing * (enabledCount - 1)) : 0; + + return Math.round(baseHeight + buttonsHeight); + } + preferredHeightRatio: largeButtonsStyle ? 1.0 : 0 + + // Positioning - large buttons style is always centered and fullscreen + readonly property string panelPosition: Settings.data.sessionMenu.position + + panelAnchorHorizontalCenter: largeButtonsStyle || panelPosition === "center" || panelPosition.endsWith("_center") + panelAnchorVerticalCenter: largeButtonsStyle || panelPosition === "center" + panelAnchorLeft: !largeButtonsStyle && panelPosition !== "center" && panelPosition.endsWith("_left") + panelAnchorRight: !largeButtonsStyle && panelPosition !== "center" && panelPosition.endsWith("_right") + panelAnchorBottom: !largeButtonsStyle && panelPosition.startsWith("bottom_") + panelAnchorTop: !largeButtonsStyle && panelPosition.startsWith("top_") + + // SessionMenu handle it's own closing logic + property bool closeWithEscape: false + + // Timer properties + readonly property int timerDuration: Settings.data.sessionMenu.countdownDuration + property string pendingAction: "" + property bool timerActive: false + property int timeRemaining: 0 + + // Navigation properties + property int selectedIndex: -1 + property bool ignoreMouseHover: true // Transient flag, should always be true on init + + // Global mouse tracking for movement detection across delegates + property real globalLastMouseX: 0 + property real globalLastMouseY: 0 + property bool globalMouseInitialized: false + property bool mouseTrackingReady: false // Delay tracking until panel is settled + + Timer { + id: mouseTrackingDelayTimer + interval: Style.animationNormal + 50 // Wait for panel animation to complete + safety margin + repeat: false + onTriggered: { + root.mouseTrackingReady = true; + root.globalMouseInitialized = false; // Reset so we get fresh initial position + } + } + + // Action metadata mapping + readonly property var actionMetadata: { + "lock": { + "icon": "lock", + "title": I18n.tr("common.lock"), + "isShutdown": false + }, + "suspend": { + "icon": "suspend", + "title": I18n.tr("common.suspend"), + "isShutdown": false + }, + "hibernate": { + "icon": "hibernate", + "title": I18n.tr("common.hibernate"), + "isShutdown": false + }, + "reboot": { + "icon": "reboot", + "title": I18n.tr("common.reboot"), + "isShutdown": false + }, + "userspaceReboot": { + "icon": "rotate", + "title": I18n.tr("common.userspace-reboot"), + "isShutdown": false + }, + "rebootToUefi": { + "icon": "reboot", + "title": I18n.tr("common.reboot-to-uefi"), + "isShutdown": false + }, + "logout": { + "icon": "logout", + "title": I18n.tr("common.logout"), + "isShutdown": false + }, + "shutdown": { + "icon": "shutdown", + "title": I18n.tr("common.shutdown"), + "isShutdown": true + } + } + + // Build powerOptions from settings, filtering enabled ones and adding metadata + // _powerOptionsVersion forces re-evaluation when settings change + property int _powerOptionsVersion: 0 + property var powerOptions: { + // Reference version to trigger re-evaluation + void (_powerOptionsVersion); + var options = []; + var settingsOptions = Settings.data.sessionMenu.powerOptions || []; + + for (var i = 0; i < settingsOptions.length; i++) { + var settingOption = settingsOptions[i]; + if (settingOption.enabled && actionMetadata[settingOption.action]) { + var metadata = actionMetadata[settingOption.action]; + options.push({ + "action": settingOption.action, + "icon": metadata.icon, + "title": metadata.title, + "isShutdown": metadata.isShutdown, + "countdownEnabled": settingOption.countdownEnabled !== undefined ? settingOption.countdownEnabled : true, + "command": settingOption.command || "", + "keybind": settingOption.keybind || "" + }); + } + } + + return options; + } + + Connections { + target: Settings.data.sessionMenu + function onPowerOptionsChanged() { + root._powerOptionsVersion++; + } + } + + // Lifecycle handlers + onOpened: { + if (powerOptions.length > 0) { + selectedIndex = -1; + ignoreMouseHover = true; + globalMouseInitialized = false; + mouseTrackingReady = false; + mouseTrackingDelayTimer.restart(); + } else { + Logger.w("SessionMenu", "Trying to open an empty session menu"); + root.closeImmediately(); + } + } + + onClosed: { + cancelTimer(); + selectedIndex = -1; + ignoreMouseHover = true; + } + + // Timer management + function startTimer(action) { + // Check if global countdown is disabled + if (!Settings.data.sessionMenu.enableCountdown) { + executeAction(action); + return; + } + + // Check per-item countdown setting + var option = null; + for (var i = 0; i < powerOptions.length; i++) { + if (powerOptions[i].action === action) { + option = powerOptions[i]; + break; + } + } + + // If this specific action has countdown disabled, execute immediately + if (option && option.countdownEnabled === false) { + executeAction(action); + return; + } + + if (timerActive && pendingAction === action) { + // Second click - execute immediately + executeAction(action); + return; + } + + pendingAction = action; + timeRemaining = timerDuration; + timerActive = true; + countdownTimer.start(); + } + + function cancelTimer() { + timerActive = false; + pendingAction = ""; + timeRemaining = 0; + countdownTimer.stop(); + } + + function executeAction(action) { + // Stop timer but don't reset other properties yet + countdownTimer.stop(); + + // Use default behavior or custom command handled by CompositorService + switch (action) { + case "lock": + CompositorService.lock(); + break; + case "suspend": + // Check if we should lock before suspending + if (Settings.data.general.lockOnSuspend) { + CompositorService.lockAndSuspend(); + } else { + CompositorService.suspend(); + } + break; + case "hibernate": + CompositorService.hibernate(); + break; + case "reboot": + CompositorService.reboot(); + break; + case "userspaceReboot": + CompositorService.userspaceReboot(); + break; + case "rebootToUefi": + CompositorService.rebootToUefi(); + break; + case "logout": + CompositorService.logout(); + break; + case "shutdown": + CompositorService.shutdown(); + break; + } + + // Reset timer state and close panel + cancelTimer(); + root.close(); + } + + // Navigation functions + function selectNextWrapped() { + if (powerOptions.length > 0) { + if (selectedIndex < 0) { + selectedIndex = 0; + } else { + selectedIndex = (selectedIndex + 1) % powerOptions.length; + } + } + } + + function selectPreviousWrapped() { + if (powerOptions.length > 0) { + if (selectedIndex < 0) { + selectedIndex = powerOptions.length - 1; + } else { + selectedIndex = (((selectedIndex - 1) % powerOptions.length) + powerOptions.length) % powerOptions.length; + } + } + } + + function selectFirst() { + if (powerOptions.length > 0) { + selectedIndex = 0; + } else { + selectedIndex = -1; + } + } + + function selectLast() { + if (powerOptions.length > 0) { + selectedIndex = powerOptions.length - 1; + } else { + selectedIndex = -1; + } + } + + function getGridInfo() { + let columns, rows; + if (Settings.data.sessionMenu.largeButtonsLayout === "single-row") { + columns = powerOptions.length; + rows = 1; + } else { + columns = Math.min(3, Math.ceil(Math.sqrt(powerOptions.length))); + rows = Math.ceil(powerOptions.length / columns); + } + + return { + columns, + rows, + currentRow: selectedIndex >= 0 ? Math.floor(selectedIndex / columns) : -1, + currentCol: selectedIndex >= 0 ? selectedIndex % columns : -1, + itemsInRow: row => Math.min(columns, powerOptions.length - row * columns) + }; + } + + // Unified navigation function + function navigateGrid(direction) { + if (powerOptions.length === 0) + return; + + const grid = getGridInfo(); + // If no selection, start at first item + let newRow = grid.currentRow >= 0 ? grid.currentRow : 0; + let newCol = grid.currentCol >= 0 ? grid.currentCol : 0; + + switch (direction) { + case "left": + newCol = newCol - 1 < 0 ? grid.itemsInRow(newRow) - 1 : newCol - 1; + break; + case "right": + // We already moved to newCol to 0 if grid.currentCol was negative + newCol = grid.currentCol < 0 ? newRow : newCol + 1 >= grid.itemsInRow(newRow) ? 0 : newCol + 1; + break; + case "up": + newRow = newRow - 1 < 0 ? grid.rows - 1 : newRow - 1; + break; + case "down": + // We already moved to newRow to 0 if grid.currentRow was negative + newRow = grid.currentRow < 0 ? newRow : newRow + 1 >= grid.rows ? 0 : newRow + 1; + break; + } + + // For vertical movement, clamp column if row has fewer items + if (direction === "up" || direction === "down") { + const itemsInNewRow = grid.itemsInRow(newRow); + newCol = Math.min(newCol, itemsInNewRow - 1); + } + + const newIndex = newRow * grid.columns + newCol; + if (newIndex < powerOptions.length) { + selectedIndex = newIndex; + } + } + + function activate() { + if (powerOptions.length > 0 && selectedIndex >= 0 && powerOptions[selectedIndex]) { + const option = powerOptions[selectedIndex]; + startTimer(option.action); + } + } + + function checkKey(event, settingName) { + return Keybinds.checkKey(event, settingName, Settings); + } + + function handleUp() { + if (largeButtonsStyle) { + navigateGrid("up"); + } else { + selectPreviousWrapped(); + } + } + + function handleDown() { + if (largeButtonsStyle) { + navigateGrid("down"); + } else { + selectNextWrapped(); + } + } + + function handleLeft() { + if (largeButtonsStyle) { + navigateGrid("left"); + } else { + selectPreviousWrapped(); + } + } + + function handleRight() { + if (largeButtonsStyle) { + navigateGrid("right"); + } else { + selectNextWrapped(); + } + } + + function handleEnter() { + activate(); + } + + function handleEscape() { + if (timerActive) { + cancelTimer(); + } else { + root.close(); + } + } + + // Override keyboard handlers from SmartPanel + function onEscapePressed() { + handleEscape(); + } + function onTabPressed() { + selectNextWrapped(); + } + function onBackTabPressed() { + selectPreviousWrapped(); + } + function onLeftPressed() { + handleLeft(); + } + function onRightPressed() { + handleRight(); + } + function onUpPressed() { + handleUp(); + } + function onDownPressed() { + handleDown(); + } + function onEnterPressed() { + handleEnter(); + } + function onHomePressed() { + selectFirst(); + } + function onEndPressed() { + selectLast(); + } + + function checkKeybind(event) { + if (powerOptions.length === 0) + return false; + + // Construct key string in the same format as the recorder + // Ignore modifier keys by themselves + if (event.key === Qt.Key_Control || event.key === Qt.Key_Shift || event.key === Qt.Key_Alt || event.key === Qt.Key_Meta) { + return false; + } + + const pressedKeybind = Keybinds.getKeybindString(event); + if (!pressedKeybind) + return false; + + for (var i = 0; i < powerOptions.length; i++) { + const option = powerOptions[i]; + if (option.keybind === pressedKeybind) { + selectedIndex = i; + startTimer(option.action); + return true; + } + } + return false; + } + + // Countdown timer + Timer { + id: countdownTimer + interval: 100 + repeat: true + onTriggered: { + timeRemaining -= interval; + if (timeRemaining <= 0) { + executeAction(pendingAction); + } + } + } + + panelContent: Rectangle { + id: panelContent + color: "transparent" + focus: true + + // For large buttons style, use full screen dimensions + readonly property var contentPreferredWidth: largeButtonsStyle ? (root.screen?.width || root.width || 0) : undefined + readonly property var contentPreferredHeight: largeButtonsStyle ? (root.screen?.height || root.height || 0) : undefined + + // Focus management + Connections { + target: root + function onOpened() { + Qt.callLater(() => { + panelContent.forceActiveFocus(); + }); + } + } + + Keys.onPressed: event => { + // Check custom entry keybinds first + if (root.checkKeybind(event)) { + event.accepted = true; + return; + } + + // Check global navigation keybinds + if (checkKey(event, 'up')) { + handleUp(); + event.accepted = true; + return; + } + if (checkKey(event, 'down')) { + handleDown(); + event.accepted = true; + return; + } + if (checkKey(event, 'left')) { + handleLeft(); + event.accepted = true; + return; + } + if (checkKey(event, 'right')) { + handleRight(); + event.accepted = true; + return; + } + if (checkKey(event, 'enter')) { + handleEnter(); + event.accepted = true; + return; + } + if (checkKey(event, 'escape')) { + handleEscape(); + event.accepted = true; + return; + } + + // Block default keys if they weren't matched above + // This prevents 'Up' from working if rebinned to something else + if (event.key === Qt.Key_Up || event.key === Qt.Key_Down || event.key === Qt.Key_Left || event.key === Qt.Key_Right || event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_Escape) { + event.accepted = true; + return; + } + } + + HoverHandler { + id: globalHoverHandler + + onPointChanged: { + if (!root.mouseTrackingReady) { + return; + } + + if (!root.globalMouseInitialized) { + root.globalLastMouseX = point.position.x; + root.globalLastMouseY = point.position.y; + root.globalMouseInitialized = true; + return; + } + + const deltaX = Math.abs(point.position.x - root.globalLastMouseX); + const deltaY = Math.abs(point.position.y - root.globalLastMouseY); + if (deltaX + deltaY >= 5) { + root.ignoreMouseHover = false; + root.globalLastMouseX = point.position.x; + root.globalLastMouseY = point.position.y; + } + } + } + + // Timer text for large buttons style, positioned absolutely with background + Rectangle { + id: timerTextContainer + visible: largeButtonsStyle && timerActive + anchors.bottom: largeButtonsContainer.top + anchors.horizontalCenter: largeButtonsContainer.horizontalCenter + anchors.bottomMargin: Style.marginM + width: timerText.width + Style.margin2XL + height: timerText.height + Style.margin2L + radius: Style.radiusM + color: Color.mSurfaceVariant + border.color: Color.mOutline + border.width: Style.borderS + z: 1000 + + NText { + id: timerText + anchors.centerIn: parent + text: I18n.tr("session-menu.action-in-seconds", { + "action": root.actionMetadata[pendingAction] ? root.actionMetadata[pendingAction].title : "", + "seconds": Math.ceil(timeRemaining / 1000) + }) + font.weight: Style.fontWeightBold + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + } + } + + // Large buttons style layout container + ColumnLayout { + id: largeButtonsContainer + visible: largeButtonsStyle + anchors.centerIn: parent + + // Large buttons style layout (grid) + GridLayout { + id: largeButtonsGrid + Layout.alignment: Qt.AlignHCenter + columns: Settings.data.sessionMenu.largeButtonsLayout === "single-row" ? powerOptions.length : Math.min(3, Math.ceil(Math.sqrt(powerOptions.length))) + rowSpacing: Style.marginXL + columnSpacing: Style.marginXL + width: columns * 200 * Style.uiScaleRatio + (columns - 1) * Style.marginXL + height: Math.ceil(powerOptions.length / columns) * 200 * Style.uiScaleRatio + (Math.ceil(powerOptions.length / columns) - 1) * Style.marginXL + + Repeater { + model: powerOptions + delegate: LargeButton { + Layout.preferredWidth: Math.round(200 * Style.uiScaleRatio) + Layout.preferredHeight: Math.round(200 * Style.uiScaleRatio) + icon: modelData.icon + title: modelData.title + isShutdown: modelData.isShutdown || false + isSelected: index === selectedIndex + number: index + 1 + buttonIndex: index + onClicked: { + selectedIndex = index; + startTimer(modelData.action); + } + pending: timerActive && pendingAction === modelData.action + keybind: modelData.keybind || "" + } + } + } + } + + // Normal style layout + NBox { + visible: !largeButtonsStyle + anchors.fill: parent + anchors.margins: Style.marginL + color: Color.mSurfaceVariant + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginL + + // Header with title and close button + RowLayout { + visible: Settings.data.sessionMenu.showHeader + Layout.fillWidth: true + Layout.preferredHeight: Style.baseWidgetSize * 0.6 + + NText { + text: timerActive ? I18n.tr("session-menu.action-in-seconds", { + "action": root.actionMetadata[pendingAction] ? root.actionMetadata[pendingAction].title : "", + "seconds": Math.ceil(timeRemaining / 1000) + }) : I18n.tr("session-menu.title") + font.weight: Style.fontWeightBold + pointSize: Style.fontSizeL + color: timerActive ? Color.mPrimary : Color.mOnSurface + Layout.alignment: Qt.AlignVCenter + verticalAlignment: Text.AlignVCenter + } + + Item { + Layout.fillWidth: true + } + + NIconButton { + icon: timerActive ? "stop" : "close" + tooltipText: timerActive ? I18n.tr("session-menu.cancel-timer") : I18n.tr("common.close") + Layout.alignment: Qt.AlignVCenter + baseSize: Style.baseWidgetSize * 0.7 + colorBg: timerActive ? Qt.alpha(Color.mError, 0.08) : "transparent" + colorFg: timerActive ? Color.mError : Color.mOnSurface + onClicked: { + if (timerActive) { + cancelTimer(); + } else { + cancelTimer(); + root.close(); + } + } + } + } + + NDivider { + visible: Settings.data.sessionMenu.showHeader + Layout.fillWidth: true + } + + // Power options + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS + + Repeater { + model: powerOptions + delegate: PowerButton { + Layout.fillWidth: true + icon: modelData.icon + title: modelData.title + isShutdown: modelData.isShutdown || false + isSelected: index === selectedIndex + number: index + 1 + buttonIndex: index + onClicked: { + selectedIndex = index; + startTimer(modelData.action); + } + pending: timerActive && pendingAction === modelData.action + keybind: modelData.keybind || "" + } + } + } + } + } + + // Background MouseArea for large buttons style - closes panel when clicking outside buttons + MouseArea { + visible: largeButtonsStyle + anchors.fill: parent + z: -1 + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + onClicked: mouse => { + // Only close if not clicking on a button + // The buttons are above this MouseArea, so clicks on them won't reach here + if (timerActive) { + // Cancel countdown if active + cancelTimer(); + } else { + root.close(); + } + } + } + } + + // Custom power button component + component PowerButton: Rectangle { + id: buttonRoot + + property string icon: "" + property string title: "" + property bool pending: false + property bool isShutdown: false + property bool isSelected: false + property int number: 0 + property string keybind: "" + property int buttonIndex: -1 + + // Effective hover state that respects ignoreMouseHover + readonly property bool effectiveHover: !root.ignoreMouseHover && mouseArea.containsMouse + + signal clicked + + height: Style.baseWidgetSize * 1.3 * Style.uiScaleRatio + radius: Style.radiusS + color: { + if (pending) { + return Qt.alpha(Color.mPrimary, 0.08); + } + if (isSelected || effectiveHover) { + return Color.mHover; + } + return "transparent"; + } + + border.width: pending ? Math.max(Style.borderM) : 0 + border.color: pending ? Color.mPrimary : Color.mOutline + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCirc + } + } + + Item { + id: contentItem + anchors.fill: parent + anchors.margins: Style.marginM + + // Icon on the left + NIcon { + id: iconElement + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + icon: buttonRoot.icon + color: { + if (buttonRoot.pending) + return Color.mPrimary; + if (buttonRoot.isShutdown && !buttonRoot.isSelected && !buttonRoot.effectiveHover) + return Color.mError; + if (buttonRoot.isSelected || buttonRoot.effectiveHover) + return Color.mOnHover; + return Color.mOnSurface; + } + pointSize: Style.fontSizeXXL + width: Style.baseWidgetSize * 0.5 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCirc + } + } + } + + // Keybind indicator and countdown text at far right + Item { + id: indicatorGroup + width: (countdownText.visible ? countdownText.width + Style.marginXS : 0) + numberIndicatorRect.width + height: numberIndicatorRect.height + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + z: 20 + + // Countdown as plain text (left of keybind) + NText { + id: countdownText + visible: !Settings.data.sessionMenu.showHeader && buttonRoot.pending && timerActive && pendingAction === modelData.action + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + text: Math.ceil(timeRemaining / 1000) + pointSize: Style.fontSizeS + color: Color.mPrimary + font.weight: Style.fontWeightBold + } + + // Keybind indicator + Rectangle { + id: numberIndicatorRect + anchors.left: countdownText.visible ? countdownText.right : parent.left + anchors.leftMargin: countdownText.visible ? Style.marginXS : 0 + anchors.verticalCenter: parent.verticalCenter + width: labelText.implicitWidth + Style.margin2M + height: labelText.height + Style.margin2XS + radius: Math.min(Style.radiusM, height / 2) + color: (buttonRoot.isSelected || buttonRoot.effectiveHover) ? Color.mOnPrimary : Color.mSurface + border.width: Style.borderS + border.color: (buttonRoot.isSelected || buttonRoot.effectiveHover) ? Color.mOnPrimary : Color.mOutline + visible: Settings.data.sessionMenu.showKeybinds && (buttonRoot.keybind !== "") && !buttonRoot.pending + + NText { + id: labelText + anchors.centerIn: parent + text: buttonRoot.keybind + pointSize: Style.fontSizeXS + color: (buttonRoot.isSelected || buttonRoot.effectiveHover) ? Color.mPrimary : Color.mOnSurface + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCirc + } + } + } + } + } + + // Text content in the middle + ColumnLayout { + anchors.left: iconElement.right + anchors.right: indicatorGroup.left + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: Style.marginL + anchors.rightMargin: Style.marginM + spacing: 0 + + NText { + text: buttonRoot.title + font.weight: Style.fontWeightMedium + pointSize: Style.fontSizeM + color: { + if (buttonRoot.pending) + return Color.mPrimary; + if (buttonRoot.isShutdown && !buttonRoot.isSelected && !buttonRoot.effectiveHover) + return Color.mError; + if (buttonRoot.isSelected || buttonRoot.effectiveHover) + return Color.mOnHover; + return Color.mOnSurface; + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCirc + } + } + } + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + onEntered: { + if (!root.ignoreMouseHover) { + selectedIndex = buttonRoot.buttonIndex; + } + } + + onExited: { + if (!root.ignoreMouseHover && selectedIndex === buttonRoot.buttonIndex) { + selectedIndex = -1; + } + } + + onClicked: buttonRoot.clicked() + } + } + + // Large buttons style button component + component LargeButton: Rectangle { + id: largeButtonRoot + + property string icon: "" + property string title: "" + property bool pending: false + property bool isShutdown: false + property bool isSelected: false + property int number: 0 + property string keybind: "" + property int buttonIndex: -1 + + // Effective hover state that respects ignoreMouseHover + readonly property bool effectiveHover: !root.ignoreMouseHover && mouseArea.containsMouse + readonly property real hoveredScale: 1.05 + + signal clicked + + property real hoverScale: (isSelected || effectiveHover) ? hoveredScale : 1.0 + + radius: Style.radiusL + color: { + if (pending) { + return Qt.alpha(Color.mPrimary, 1.0); + } + if (isSelected || effectiveHover) { + return Qt.alpha(Color.mPrimary, 1.0); + } + return Color.mSurface; + } + + border.width: Style.borderS + border.color: Color.mOutline + + // Always enable layer to fix nvidia bug, render at 2x size to avoid blur when scaling up + layer.enabled: true + layer.smooth: true + layer.textureSize: Qt.size(Math.ceil(width * 2), Math.ceil(height * 2)) + + // Scale transform for hover effect + transform: Scale { + origin.x: largeButtonRoot.width / 2 + origin.y: largeButtonRoot.height / 2 + xScale: hoverScale + yScale: hoverScale + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCirc + } + } + + Behavior on border.width { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCirc + } + } + + Behavior on hoverScale { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutBack + easing.overshoot: 0.5 + } + } + + ColumnLayout { + anchors.centerIn: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + // Large icon with scale animation + NIcon { + id: iconElement + Layout.alignment: Qt.AlignHCenter + icon: largeButtonRoot.icon + color: { + if (largeButtonRoot.pending) + return Color.mOnPrimary; + if (largeButtonRoot.isShutdown && !largeButtonRoot.isSelected && !largeButtonRoot.effectiveHover) + return Color.mError; + if (largeButtonRoot.isSelected || largeButtonRoot.effectiveHover) + return Color.mOnPrimary; + return Color.mOnSurface; + } + pointSize: Style.fontSizeXXXL * 2.25 + width: 90 * Style.uiScaleRatio + height: 90 * Style.uiScaleRatio + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + readonly property real hoveredIconScale: 1.15 + property real iconScale: (largeButtonRoot.isSelected || largeButtonRoot.effectiveHover) ? hoveredIconScale : 1.0 + + // Always enable layer to fix nvidia bug, render at 2x size to avoid blur when scaling up + layer.enabled: true + layer.smooth: true + layer.textureSize: Qt.size(Math.ceil(width * 2), Math.ceil(height * 2)) + + transform: Scale { + origin.x: iconElement.width / 2 + origin.y: iconElement.height / 2 + xScale: iconElement.iconScale + yScale: iconElement.iconScale + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCirc + } + } + + Behavior on iconScale { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutBack + easing.overshoot: 0.6 + } + } + } + + // Title text + NText { + Layout.alignment: Qt.AlignHCenter + text: largeButtonRoot.title + font.weight: Style.fontWeightMedium + pointSize: Style.fontSizeL + color: { + if (largeButtonRoot.pending) + return Color.mOnPrimary; + if (largeButtonRoot.isShutdown && !largeButtonRoot.isSelected && !largeButtonRoot.effectiveHover) + return Color.mError; + if (largeButtonRoot.isSelected || largeButtonRoot.effectiveHover) + return Color.mOnPrimary; + return Color.mOnSurface; + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCirc + } + } + } + } + + // Keybind/Number indicator in top-right corner + Rectangle { + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: Style.marginM + width: largeNumberText.implicitWidth + Style.margin2M + height: largeNumberText.implicitHeight + Style.margin2XS + radius: Math.min(Style.radiusM, height / 2) + color: (largeButtonRoot.isSelected || largeButtonRoot.effectiveHover) ? Color.mOnPrimary : Qt.alpha(Color.mSurfaceVariant, 0.7) + border.width: Style.borderS + border.color: (largeButtonRoot.isSelected || largeButtonRoot.effectiveHover) ? Color.mOnPrimary : Color.mOutline + visible: Settings.data.sessionMenu.showKeybinds && (largeButtonRoot.keybind !== "") && !largeButtonRoot.pending + z: 10 + + NText { + id: largeNumberText + anchors.centerIn: parent + text: largeButtonRoot.keybind + pointSize: Style.fontSizeS + color: { + if (largeButtonRoot.isSelected || largeButtonRoot.effectiveHover) + return Color.mPrimary; + return Color.mOnSurfaceVariant; + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCirc + } + } + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + onEntered: { + if (!root.ignoreMouseHover) { + selectedIndex = largeButtonRoot.buttonIndex; + } + } + + onExited: { + if (!root.ignoreMouseHover && selectedIndex === largeButtonRoot.buttonIndex) { + selectedIndex = -1; + } + } + + onClicked: largeButtonRoot.clicked() + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml new file mode 100644 index 0000000..c218356 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml @@ -0,0 +1,193 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import qs.Commons +import qs.Services.UI +import qs.Widgets + +// Widget Settings Dialog Component +Popup { + id: root + + property int widgetIndex: -1 + property var widgetData: null + property string widgetId: "" + property string sectionId: "" + property var screen: null + property var settingsCache: ({}) + + readonly property real maxHeight: (screen ? screen.height : (parent ? parent.height : 800)) * 0.8 + + signal updateWidgetSettings(string section, int index, var settings) + + width: Math.max(content.implicitWidth + padding * 2, 640) + height: Math.min(content.implicitHeight + padding * 2, maxHeight) + padding: Style.marginXL + modal: true + closePolicy: Popup.NoAutoClose + dim: false + anchors.centerIn: parent + + onOpened: { + // Load settings when popup opens with data + if (widgetData && widgetId) { + loadWidgetSettings(); + } + // Request focus to ensure keyboard input works + forceActiveFocus(); + } + + background: Rectangle { + id: bgRect + + color: Color.mSurface + radius: Style.radiusL + border.color: Color.mPrimary + border.width: Style.borderM + } + + contentItem: FocusScope { + id: focusScope + focus: true + + ColumnLayout { + id: content + anchors.fill: parent + spacing: Style.marginM + + // Title + RowLayout { + id: titleRow + Layout.fillWidth: true + Layout.preferredHeight: implicitHeight + + NText { + text: I18n.tr("system.widget-settings-title", { + "widget": root.widgetId + }) + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mPrimary + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + onClicked: saveAndClose() + } + } + + // Separator + Rectangle { + id: separator + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + } + + // Scrollable settings area + NScrollView { + id: scrollView + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: 100 + gradientColor: Color.mSurface + + ColumnLayout { + width: scrollView.availableWidth + spacing: Style.marginM + + // Settings based on widget type + // Will be triggered via settingsLoader.setSource() + Loader { + id: settingsLoader + Layout.fillWidth: true + onLoaded: { + // Try to focus the first focusable item in the loaded settings + if (item) { + Qt.callLater(() => { + var firstInput = findFirstFocusable(item); + if (firstInput) { + firstInput.forceActiveFocus(); + } else { + focusScope.forceActiveFocus(); + } + }); + } + } + + function findFirstFocusable(item) { + if (!item) + return null; + // Check if this item can accept focus + if (item.focus !== undefined && item.focus === true) + return item; + // Check children + if (item.children) { + for (var i = 0; i < item.children.length; i++) { + var child = item.children[i]; + if (child && child.focus !== undefined && child.focus === true) + return child; + var found = findFirstFocusable(child); + if (found) + return found; + } + } + return null; + } + } + } + } + } + } + + Timer { + id: saveTimer + running: false + interval: 150 + onTriggered: { + root.updateWidgetSettings(root.sectionId, root.widgetIndex, root.settingsCache); + } + } + + Connections { + target: settingsLoader.item + ignoreUnknownSignals: true + function onSettingsChanged(newSettings) { + if (newSettings) { + root.settingsCache = newSettings; + saveTimer.start(); + } + } + } + + function saveAndClose() { + if (settingsLoader.item && typeof settingsLoader.item.saveSettings === 'function') { + var newSettings = settingsLoader.item.saveSettings(); + if (newSettings) { + root.updateWidgetSettings(root.sectionId, root.widgetIndex, newSettings); + } + } + root.close(); + } + + function loadWidgetSettings() { + const source = BarWidgetRegistry.widgetSettingsMap[widgetId]; + if (source) { + var currentWidgetData = widgetData; + if (sectionId && widgetIndex >= 0) { + var widgets = Settings.getBarWidgetsForScreen(screen?.name || "")[sectionId]; + if (widgets && widgetIndex < widgets.length) { + currentWidgetData = widgets[widgetIndex]; + } + } + settingsLoader.setSource(source, { + "screen": screen, + "widgetData": currentWidgetData, + "widgetMetadata": BarWidgetRegistry.widgetMetadata[widgetId] + }); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/MonitorWidgetsConfig.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/MonitorWidgetsConfig.qml new file mode 100644 index 0000000..fe4627e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/MonitorWidgetsConfig.qml @@ -0,0 +1,230 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import "../../../../Helpers/QtObj2JS.js" as QtObj2JS +import qs.Commons +import qs.Services.Noctalia +import qs.Services.UI +import qs.Widgets + +// Monitor Widgets Configuration (inline) +NBox { + id: root + + required property var screen + readonly property string screenName: screen?.name || "" + // determine bar orientation + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" + + color: Color.mSurfaceVariant + Layout.fillWidth: true + implicitHeight: content.implicitHeight + Style.margin2L + + // Helper to get widgets for this screen (ensures override exists) + function _getWidgetsContainer() { + if (!Settings.hasScreenOverride(screenName, "widgets")) { + var currentWidgets = Settings.getBarWidgetsForScreen(screenName); + var widgetsCopy = QtObj2JS.qtObjectToPlainObject(currentWidgets); + Settings.setScreenOverride(screenName, "widgets", widgetsCopy); + } + var entry = Settings.getScreenOverrideEntry(screenName); + return entry ? entry.widgets : Settings.data.bar.widgets; + } + + // Persist widget changes by reassigning the override (triggers change detection) + function _saveWidgets(widgets) { + Settings.setScreenOverride(screenName, "widgets", widgets); + } + + // Widget manipulation functions + function _addWidgetToSection(widgetId, section) { + var newWidget = { + "id": widgetId + }; + if (BarWidgetRegistry.widgetHasUserSettings(widgetId)) { + var metadata = BarWidgetRegistry.widgetMetadata[widgetId]; + if (metadata) { + Object.keys(metadata).forEach(function (key) { + newWidget[key] = metadata[key]; + }); + } + } + var widgets = _getWidgetsContainer(); + widgets[section].push(newWidget); + _saveWidgets(widgets); + BarService.widgetsRevision++; + } + + function _removeWidgetFromSection(section, index) { + var widgets = _getWidgetsContainer(); + if (index >= 0 && index < widgets[section].length) { + var newArray = widgets[section].slice(); + var removedWidgets = newArray.splice(index, 1); + widgets[section] = newArray; + _saveWidgets(widgets); + BarService.widgetsRevision++; + + if (removedWidgets[0].id === "ControlCenter" && BarService.lookupWidget("ControlCenter") === undefined) { + ToastService.showWarning(I18n.tr("toast.missing-control-center.label"), I18n.tr("toast.missing-control-center.description"), 12000); + } + } + } + + function _reorderWidgetInSection(section, fromIndex, toIndex) { + var widgets = _getWidgetsContainer(); + if (fromIndex >= 0 && fromIndex < widgets[section].length && toIndex >= 0 && toIndex < widgets[section].length) { + var newArray = widgets[section].slice(); + var item = newArray[fromIndex]; + newArray.splice(fromIndex, 1); + newArray.splice(toIndex, 0, item); + widgets[section] = newArray; + _saveWidgets(widgets); + BarService.widgetsRevision++; + } + } + + // Note: _updateWidgetSettingsInSection does NOT increment revision + // because it only changes settings, not widget structure + function _updateWidgetSettingsInSection(section, index, settings) { + var widgets = _getWidgetsContainer(); + widgets[section][index] = settings; + _saveWidgets(widgets); + } + + function _moveWidgetBetweenSections(fromSection, index, toSection) { + var widgets = _getWidgetsContainer(); + if (index >= 0 && index < widgets[fromSection].length) { + var widget = widgets[fromSection][index]; + var sourceArray = widgets[fromSection].slice(); + sourceArray.splice(index, 1); + widgets[fromSection] = sourceArray; + var targetArray = widgets[toSection].slice(); + targetArray.push(widget); + widgets[toSection] = targetArray; + _saveWidgets(widgets); + BarService.widgetsRevision++; + } + } + + // Available widgets ListModel + function updateAvailableWidgetsModel() { + availableWidgetsModel.clear(); + var widgetIds = BarWidgetRegistry.getAvailableWidgets(); + if (!widgetIds) + return; + for (var i = 0; i < widgetIds.length; i++) { + var id = widgetIds[i]; + var displayName = id; + const badges = []; + if (BarWidgetRegistry.isPluginWidget(id)) { + var pluginId = id.replace("plugin:", ""); + var manifest = PluginRegistry.getPluginManifest(pluginId); + if (manifest && manifest.name) { + displayName = manifest.name; + } else { + displayName = pluginId; + } + badges.push({ + "icon": "plugin", + "color": Color.mSecondary + }); + } + if (BarWidgetRegistry.isCpuIntensive(id)) { + badges.push({ + "icon": "cpu-intensive", + "color": Color.mSecondary + }); + } + availableWidgetsModel.append({ + "key": id, + "name": displayName, + "badges": badges + }); + } + } + + Component.onCompleted: Qt.callLater(updateAvailableWidgetsModel) + + ListModel { + id: availableWidgetsModel + } + + // Get effective widgets for this screen + readonly property var effectiveWidgets: Settings.getBarWidgetsForScreen(screenName) + + ColumnLayout { + id: content + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + NText { + text: I18n.tr("panels.bar.widgets-desc") + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + // Left Section + NSectionEditor { + sectionName: root.barIsVertical ? I18n.tr("positions.top") : I18n.tr("positions.left") + sectionId: "left" + barIsVertical: root.barIsVertical + screen: root.screen + settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml") + widgetRegistry: BarWidgetRegistry + widgetModel: root.effectiveWidgets.left + availableWidgets: availableWidgetsModel + onAddWidget: (widgetId, section) => root._addWidgetToSection(widgetId, section) + onRemoveWidget: (section, index) => root._removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => root._reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettings: (section, index, settings) => root._updateWidgetSettingsInSection(section, index, settings) + onMoveWidget: (fromSection, index, toSection) => root._moveWidgetBetweenSections(fromSection, index, toSection) + onOpenPluginSettingsRequested: manifest => pluginSettingsDialog.openPluginSettings(manifest) + } + + // Center Section + NSectionEditor { + sectionName: I18n.tr("positions.center") + sectionId: "center" + barIsVertical: root.barIsVertical + screen: root.screen + settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml") + widgetRegistry: BarWidgetRegistry + widgetModel: root.effectiveWidgets.center + availableWidgets: availableWidgetsModel + onAddWidget: (widgetId, section) => root._addWidgetToSection(widgetId, section) + onRemoveWidget: (section, index) => root._removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => root._reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettings: (section, index, settings) => root._updateWidgetSettingsInSection(section, index, settings) + onMoveWidget: (fromSection, index, toSection) => root._moveWidgetBetweenSections(fromSection, index, toSection) + onOpenPluginSettingsRequested: manifest => pluginSettingsDialog.openPluginSettings(manifest) + } + + // Right Section + NSectionEditor { + sectionName: root.barIsVertical ? I18n.tr("positions.bottom") : I18n.tr("positions.right") + sectionId: "right" + barIsVertical: root.barIsVertical + screen: root.screen + settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml") + widgetRegistry: BarWidgetRegistry + widgetModel: root.effectiveWidgets.right + availableWidgets: availableWidgetsModel + onAddWidget: (widgetId, section) => root._addWidgetToSection(widgetId, section) + onRemoveWidget: (section, index) => root._removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => root._reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettings: (section, index, settings) => root._updateWidgetSettingsInSection(section, index, settings) + onMoveWidget: (fromSection, index, toSection) => root._moveWidgetBetweenSections(fromSection, index, toSection) + onOpenPluginSettingsRequested: manifest => pluginSettingsDialog.openPluginSettings(manifest) + } + } + + // Plugin settings dialog + NPluginSettingsPopup { + id: pluginSettingsDialog + parent: Overlay.overlay + showToastOnSave: false + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml new file mode 100644 index 0000000..f997fd6 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml @@ -0,0 +1,168 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + // Local state + property bool valueShowIcon: widgetData.showIcon !== undefined ? widgetData.showIcon : widgetMetadata.showIcon + property bool valueShowText: widgetData.showText !== undefined ? widgetData.showText : widgetMetadata.showText + property string valueHideMode: widgetData.hideMode !== undefined ? widgetData.hideMode : widgetMetadata.hideMode + property string valueScrollingMode: widgetData.scrollingMode || widgetMetadata.scrollingMode + property int valueMaxWidth: widgetData.maxWidth !== undefined ? widgetData.maxWidth : widgetMetadata.maxWidth + property bool valueUseFixedWidth: widgetData.useFixedWidth !== undefined ? widgetData.useFixedWidth : widgetMetadata.useFixedWidth + property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons + property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor + + Component.onCompleted: { + if (widgetData && widgetData.hideMode !== undefined) { + valueHideMode = widgetData.hideMode; + } + } + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.hideMode = valueHideMode; + settings.showIcon = valueShowIcon; + settings.showText = valueShowText; + settings.scrollingMode = valueScrollingMode; + settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth; + settings.useFixedWidth = valueUseFixedWidth; + settings.colorizeIcons = valueColorizeIcons; + settings.textColor = valueTextColor; + settingsChanged(settings); + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("bar.taskbar.hide-mode-label") + description: I18n.tr("bar.active-window.hide-mode-description") + model: [ + { + "key": "visible", + "name": I18n.tr("hide-modes.visible") + }, + { + "key": "hidden", + "name": I18n.tr("hide-modes.hidden") + }, + { + "key": "transparent", + "name": I18n.tr("hide-modes.transparent") + } + ] + currentKey: root.valueHideMode + onSelected: key => { + root.valueHideMode = key; + saveSettings(); + } + defaultValue: widgetMetadata.hideMode + } + + NColorChoice { + label: I18n.tr("common.select-color") + currentKey: valueTextColor + onSelected: key => { + valueTextColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.textColor + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.active-window.show-app-text-label") + description: I18n.tr("bar.active-window.show-app-text-description") + checked: root.valueShowText + onToggled: checked => { + root.valueShowText = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showText + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.active-window.show-app-icon-label") + description: I18n.tr("bar.active-window.show-app-icon-description") + checked: root.valueShowIcon + onToggled: checked => { + root.valueShowIcon = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showIcon + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.tray.colorize-icons-label") + description: I18n.tr("bar.active-window.colorize-icons-description") + checked: root.valueColorizeIcons + onToggled: checked => { + root.valueColorizeIcons = checked; + saveSettings(); + } + visible: root.valueShowIcon + defaultValue: widgetMetadata.colorizeIcons + } + + NTextInput { + id: widthInput + Layout.fillWidth: true + label: I18n.tr("bar.taskbar.max-width-label") + description: I18n.tr("bar.media-mini.max-width-description") + placeholderText: widgetMetadata.maxWidth + text: valueMaxWidth + onTextChanged: saveSettings() + defaultValue: String(widgetMetadata.maxWidth) + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.media-mini.use-fixed-width-label") + description: I18n.tr("bar.media-mini.use-fixed-width-description") + checked: valueUseFixedWidth + onToggled: checked => { + valueUseFixedWidth = checked; + saveSettings(); + } + defaultValue: widgetMetadata.useFixedWidth + } + + NComboBox { + label: I18n.tr("bar.media-mini.scrolling-mode-label") + description: I18n.tr("bar.active-window.scrolling-mode-description") + model: [ + { + "key": "always", + "name": I18n.tr("options.scrolling-modes.always") + }, + { + "key": "hover", + "name": I18n.tr("options.scrolling-modes.hover") + }, + { + "key": "never", + "name": I18n.tr("options.scrolling-modes.never") + } + ] + currentKey: valueScrollingMode + defaultValue: widgetMetadata.scrollingMode + onSelected: key => { + valueScrollingMode = key; + saveSettings(); + } + minimumWidth: 200 + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/AudioVisualizerSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/AudioVisualizerSettings.qml new file mode 100644 index 0000000..499db6a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/AudioVisualizerSettings.qml @@ -0,0 +1,63 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + // Local state + property bool valueHideWhenIdle: widgetData.hideWhenIdle !== undefined ? widgetData.hideWhenIdle : widgetMetadata.hideWhenIdle + property string valueColorName: widgetData.colorName !== undefined ? widgetData.colorName : widgetMetadata.colorName + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.width = parseInt(widthInput.text) || widgetMetadata.width; + settings.hideWhenIdle = valueHideWhenIdle; + settings.colorName = valueColorName; + settingsChanged(settings); + } + + NTextInput { + id: widthInput + Layout.fillWidth: true + label: I18n.tr("common.width") + description: I18n.tr("bar.audio-visualizer.width-description") + text: widgetData.width || widgetMetadata.width + placeholderText: I18n.tr("placeholders.enter-width-pixels") + onTextChanged: saveSettings() + defaultValue: String(widgetMetadata.width) + } + + NColorChoice { + Layout.fillWidth: true + label: I18n.tr("bar.audio-visualizer.color-name-label") + description: I18n.tr("bar.audio-visualizer.color-name-description") + currentKey: root.valueColorName + onSelected: key => { + root.valueColorName = key; + saveSettings(); + } + defaultValue: widgetMetadata.colorName + } + + NToggle { + label: I18n.tr("bar.audio-visualizer.hide-when-idle-label") + description: I18n.tr("bar.audio-visualizer.hide-when-idle-description") + checked: valueHideWhenIdle + onToggled: checked => { + valueHideWhenIdle = checked; + saveSettings(); + } + defaultValue: widgetMetadata.hideWhenIdle + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/BatterySettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/BatterySettings.qml new file mode 100644 index 0000000..7c59cd2 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/BatterySettings.qml @@ -0,0 +1,138 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.Hardware +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + // Local state + property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode + property string valueDeviceNativePath: widgetData.deviceNativePath !== undefined ? widgetData.deviceNativePath : widgetMetadata.deviceNativePath + property bool valueShowPowerProfiles: widgetData.showPowerProfiles !== undefined ? widgetData.showPowerProfiles : widgetMetadata.showPowerProfiles + property bool valueShowNoctaliaPerformance: widgetData.showNoctaliaPerformance !== undefined ? widgetData.showNoctaliaPerformance : widgetMetadata.showNoctaliaPerformance + property bool valueHideIfNotDetected: widgetData.hideIfNotDetected !== undefined ? widgetData.hideIfNotDetected : widgetMetadata.hideIfNotDetected + property bool valueHideIfIdle: widgetData.hideIfIdle !== undefined ? widgetData.hideIfIdle : widgetMetadata.hideIfIdle + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + if (widgetData && widgetData.id) { + settings.id = widgetData.id; + } + settings.displayMode = valueDisplayMode; + settings.showPowerProfiles = valueShowPowerProfiles; + settings.showNoctaliaPerformance = valueShowNoctaliaPerformance; + settings.hideIfNotDetected = valueHideIfNotDetected; + settings.hideIfIdle = valueHideIfIdle; + settings.deviceNativePath = valueDeviceNativePath; + settingsChanged(settings); + } + + NComboBox { + id: deviceComboBox + Layout.fillWidth: true + label: I18n.tr("bar.battery.device-label") + description: I18n.tr("bar.battery.device-description") + minimumWidth: 240 + model: BatteryService.deviceModel + currentKey: root.valueDeviceNativePath + onSelected: key => { + root.valueDeviceNativePath = key; + saveSettings(); + } + defaultValue: widgetMetadata.deviceNativePath + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("common.display-mode") + description: I18n.tr("bar.battery.display-mode-description") + minimumWidth: 240 + model: [ + { + "key": "graphic", + "name": I18n.tr("bar.battery.display-mode-graphic") + }, + { + "key": "graphic-clean", + "name": I18n.tr("bar.battery.display-mode-graphic-clean") + }, + { + "key": "icon-hover", + "name": I18n.tr("bar.battery.display-mode-icon-hover") + }, + { + "key": "icon-always", + "name": I18n.tr("bar.battery.display-mode-icon-always") + }, + { + "key": "icon-only", + "name": I18n.tr("bar.battery.display-mode-icon-only") + } + ] + currentKey: root.valueDisplayMode + onSelected: key => { + root.valueDisplayMode = key; + saveSettings(); + } + defaultValue: widgetMetadata.displayMode + } + + NToggle { + label: I18n.tr("bar.battery.hide-if-not-detected-label") + description: I18n.tr("bar.battery.hide-if-not-detected-description") + checked: valueHideIfNotDetected + onToggled: checked => { + valueHideIfNotDetected = checked; + saveSettings(); + } + defaultValue: widgetMetadata.hideIfNotDetected + } + + NToggle { + label: I18n.tr("bar.battery.hide-if-idle-label") + description: I18n.tr("bar.battery.hide-if-idle-description") + checked: valueHideIfIdle + onToggled: checked => { + valueHideIfIdle = checked; + saveSettings(); + } + defaultValue: widgetMetadata.hideIfIdle + } + + NDivider { + Layout.fillWidth: true + } + + NToggle { + label: I18n.tr("bar.battery.show-power-profile-label") + description: I18n.tr("bar.battery.show-power-profile-description") + checked: valueShowPowerProfiles + onToggled: checked => { + valueShowPowerProfiles = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showPowerProfiles + } + + NToggle { + label: I18n.tr("bar.battery.show-noctalia-performance-label") + description: I18n.tr("bar.battery.show-noctalia-performance-description") + checked: valueShowNoctaliaPerformance + onToggled: checked => { + valueShowNoctaliaPerformance = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showNoctaliaPerformance + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/BluetoothSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/BluetoothSettings.qml new file mode 100644 index 0000000..cd1c53d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/BluetoothSettings.qml @@ -0,0 +1,74 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + // Local state + property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode + property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor + property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.displayMode = valueDisplayMode; + settings.iconColor = valueIconColor; + settings.textColor = valueTextColor; + settingsChanged(settings); + } + + NComboBox { + label: I18n.tr("common.display-mode") + description: I18n.tr("bar.volume.display-mode-description") + minimumWidth: 200 + model: [ + { + "key": "onhover", + "name": I18n.tr("display-modes.on-hover") + }, + { + "key": "alwaysShow", + "name": I18n.tr("display-modes.always-show") + }, + { + "key": "alwaysHide", + "name": I18n.tr("display-modes.always-hide") + } + ] + currentKey: root.valueDisplayMode + onSelected: key => { + root.valueDisplayMode = key; + saveSettings(); + } + defaultValue: widgetMetadata.displayMode + } + + NColorChoice { + label: I18n.tr("common.select-icon-color") + currentKey: valueIconColor + onSelected: key => { + valueIconColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.iconColor + } + + NColorChoice { + currentKey: valueTextColor + onSelected: key => { + valueTextColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.textColor + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/BrightnessSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/BrightnessSettings.qml new file mode 100644 index 0000000..38a3494 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/BrightnessSettings.qml @@ -0,0 +1,93 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + // Local state + property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode + property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor + property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor + property bool valueApplyToAllMonitors: widgetData.applyToAllMonitors !== undefined ? widgetData.applyToAllMonitors : widgetMetadata.applyToAllMonitors + + readonly property bool hasMultipleMonitors: (Quickshell.screens || []).length > 1 + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.displayMode = valueDisplayMode; + settings.iconColor = valueIconColor; + settings.textColor = valueTextColor; + settings.applyToAllMonitors = valueApplyToAllMonitors; + settingsChanged(settings); + } + + NComboBox { + label: I18n.tr("common.display-mode") + description: I18n.tr("bar.volume.display-mode-description") + minimumWidth: 200 + model: [ + { + "key": "onhover", + "name": I18n.tr("display-modes.on-hover") + }, + { + "key": "alwaysShow", + "name": I18n.tr("display-modes.always-show") + }, + { + "key": "alwaysHide", + "name": I18n.tr("display-modes.always-hide") + } + ] + currentKey: valueDisplayMode + onSelected: key => { + valueDisplayMode = key; + saveSettings(); + } + defaultValue: widgetMetadata.displayMode + } + + NColorChoice { + label: I18n.tr("common.select-icon-color") + currentKey: valueIconColor + onSelected: key => { + valueIconColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.iconColor + } + + NColorChoice { + currentKey: valueTextColor + onSelected: key => { + valueTextColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.textColor + } + + NToggle { + visible: hasMultipleMonitors + Layout.fillWidth: true + label: I18n.tr("bar.brightness.apply-all-label") + description: I18n.tr("bar.brightness.apply-all-description") + checked: valueApplyToAllMonitors + onToggled: checked => { + valueApplyToAllMonitors = checked; + saveSettings(); + } + defaultValue: widgetMetadata.applyToAllMonitors + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/ClockSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/ClockSettings.qml new file mode 100644 index 0000000..6d2f9c3 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/ClockSettings.qml @@ -0,0 +1,315 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.System +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + width: 700 + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + // Local state + property string valueClockColor: widgetData.clockColor !== undefined ? widgetData.clockColor : widgetMetadata.clockColor + property bool valueUseCustomFont: widgetData.useCustomFont !== undefined ? widgetData.useCustomFont : widgetMetadata.useCustomFont + property string valueCustomFont: widgetData.customFont !== undefined ? widgetData.customFont : widgetMetadata.customFont + property string valueFormatHorizontal: widgetData.formatHorizontal !== undefined ? widgetData.formatHorizontal : widgetMetadata.formatHorizontal + property string valueFormatVertical: widgetData.formatVertical !== undefined ? widgetData.formatVertical : widgetMetadata.formatVertical + property string valueTooltipFormat: widgetData.tooltipFormat !== undefined ? widgetData.tooltipFormat : widgetMetadata.tooltipFormat + + readonly property color textColor: Color.resolveColorKey(valueClockColor) + + // Track the currently focused input field + property var focusedInput: null + property int focusedLineIndex: -1 + + readonly property var now: Time.now + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.clockColor = valueClockColor; + settings.useCustomFont = valueUseCustomFont; + settings.customFont = valueCustomFont; + settings.formatHorizontal = valueFormatHorizontal.trim(); + settings.formatVertical = valueFormatVertical.trim(); + settings.tooltipFormat = valueTooltipFormat.trim(); + settingsChanged(settings); + } + + // Function to insert token at cursor position in the focused input + function insertToken(token) { + if (!focusedInput || !focusedInput.inputItem) { + // If no input is focused, default to horiz + if (inputHoriz.inputItem) { + inputHoriz.inputItem.focus = true; + focusedInput = inputHoriz; + } + } + + if (focusedInput && focusedInput.inputItem) { + var input = focusedInput.inputItem; + var cursorPos = input.cursorPosition; + var currentText = input.text; + + // Insert token at cursor position + var newText = currentText.substring(0, cursorPos) + token + currentText.substring(cursorPos); + input.text = newText + " "; + + // Move cursor after the inserted token + input.cursorPosition = cursorPos + token.length + 1; + + // Ensure the input keeps focus + input.focus = true; + } + } + + NColorChoice { + currentKey: valueClockColor + onSelected: key => { + valueClockColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.clockColor + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.clock.use-custom-font-label") + description: I18n.tr("bar.clock.use-custom-font-description") + checked: valueUseCustomFont + onToggled: checked => { + valueUseCustomFont = checked; + saveSettings(); + } + defaultValue: widgetMetadata.useCustomFont + } + + NSearchableComboBox { + Layout.fillWidth: true + visible: valueUseCustomFont + label: I18n.tr("bar.clock.custom-font-label") + description: I18n.tr("bar.clock.custom-font-description") + model: FontService.availableFonts + currentKey: valueCustomFont + placeholder: I18n.tr("bar.clock.custom-font-placeholder") + searchPlaceholder: I18n.tr("bar.clock.custom-font-search-placeholder") + popupHeight: 420 + minimumWidth: 300 + onSelected: function (key) { + valueCustomFont = key; + saveSettings(); + } + defaultValue: Settings.data.ui.fontDefault + } + + NDivider { + Layout.fillWidth: true + } + + NHeader { + label: I18n.tr("bar.clock.clock-display-label") + description: I18n.tr("bar.clock.clock-display-description") + } + + RowLayout { + id: main + + spacing: Style.marginL + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + + ColumnLayout { + spacing: Style.marginM + + Layout.fillWidth: true + Layout.preferredWidth: 1 // Equal sizing hint + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + + NTextInput { + id: inputHoriz + Layout.fillWidth: true + label: I18n.tr("bar.clock.horizontal-bar-label") + description: I18n.tr("bar.clock.horizontal-bar-description") + placeholderText: "HH:mm ddd, MMM dd" + text: valueFormatHorizontal + onTextChanged: { + valueFormatHorizontal = text; + saveSettings(); + } + Component.onCompleted: { + if (inputItem) { + inputItem.onActiveFocusChanged.connect(function () { + if (inputItem.activeFocus) { + root.focusedInput = inputHoriz; + } + }); + } + } + defaultValue: widgetMetadata.formatHorizontal + } + + Item { + Layout.fillHeight: true + } + + NTextInput { + id: inputVert + Layout.fillWidth: true + label: I18n.tr("bar.clock.vertical-bar-label") + description: I18n.tr("bar.clock.vertical-bar-description") + // Tokens are Qt format tokens and must not be localized + placeholderText: "HH mm dd MM" + text: valueFormatVertical + onTextChanged: { + valueFormatVertical = text; + saveSettings(); + } + Component.onCompleted: { + if (inputItem) { + inputItem.onActiveFocusChanged.connect(function () { + if (inputItem.activeFocus) { + root.focusedInput = inputVert; + } + }); + } + } + defaultValue: widgetMetadata.formatVertical + } + + NTextInput { + id: inputTooltip + Layout.fillWidth: true + label: I18n.tr("bar.clock.tooltip-format-label") + description: I18n.tr("bar.clock.tooltip-format-description") + placeholderText: "HH:mm, ddd MMM dd" + text: valueTooltipFormat + onTextChanged: { + valueTooltipFormat = text; + saveSettings(); + } + Component.onCompleted: { + if (inputItem) { + inputItem.onActiveFocusChanged.connect(function () { + if (inputItem.activeFocus) { + root.focusedInput = inputTooltip; + } + }); + } + } + defaultValue: widgetMetadata.tooltipFormat + } + } + + // -------------- + // Preview + ColumnLayout { + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + Layout.fillWidth: false + + NLabel { + label: I18n.tr("bar.clock.preview") + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + } + + Rectangle { + Layout.preferredWidth: 320 + Layout.preferredHeight: 160 // Fixed height instead of fillHeight + + color: Color.mSurfaceVariant + radius: Style.radiusM + border.color: Color.mSecondary + border.width: Style.borderS + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + + ColumnLayout { + spacing: Style.marginM + anchors.centerIn: parent + + ColumnLayout { + spacing: -2 + Layout.alignment: Qt.AlignHCenter + + // Horizontal + Repeater { + Layout.topMargin: Style.marginM + model: I18n.locale.toString(now, valueFormatHorizontal.trim()).split("\\n") + delegate: NText { + visible: text !== "" + text: modelData + family: valueUseCustomFont && valueCustomFont ? valueCustomFont : Settings.data.ui.fontDefault + pointSize: Style.fontSizeM + font.weight: Style.fontWeightBold + color: textColor + wrapMode: Text.WordWrap + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + } + } + + NDivider { + Layout.fillWidth: true + } + + // Vertical + ColumnLayout { + spacing: -2 + Layout.alignment: Qt.AlignHCenter + + Repeater { + Layout.topMargin: Style.marginM + model: I18n.locale.toString(now, valueFormatVertical.trim()).split(" ") + delegate: NText { + visible: text !== "" + text: modelData + family: valueUseCustomFont && valueCustomFont ? valueCustomFont : Settings.data.ui.fontDefault + pointSize: Style.fontSizeM + font.weight: Style.fontWeightBold + color: textColor + wrapMode: Text.WordWrap + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + } + } + } + } + } + } + + NDivider { + Layout.topMargin: Style.marginM + Layout.bottomMargin: Style.marginM + } + + NDateTimeTokens { + Layout.fillWidth: true + Layout.preferredHeight: 300 + + // Connect to token clicked signal if NDateTimeTokens provides it + onTokenClicked: token => root.insertToken(token) + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml new file mode 100644 index 0000000..d24602d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml @@ -0,0 +1,134 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + // Local state + property string valueIcon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon + property bool valueUseDistroLogo: widgetData.useDistroLogo !== undefined ? widgetData.useDistroLogo : widgetMetadata.useDistroLogo + property string valueCustomIconPath: widgetData.customIconPath !== undefined ? widgetData.customIconPath : widgetMetadata.customIconPath + property bool valueEnableColorization: widgetData.enableColorization !== undefined ? widgetData.enableColorization : widgetMetadata.enableColorization + property string valueColorizeSystemIcon: widgetData.colorizeSystemIcon !== undefined ? widgetData.colorizeSystemIcon : widgetMetadata.colorizeSystemIcon + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.icon = valueIcon; + settings.useDistroLogo = valueUseDistroLogo; + settings.customIconPath = valueCustomIconPath; + settings.enableColorization = valueEnableColorization; + settings.colorizeSystemIcon = valueColorizeSystemIcon; + settingsChanged(settings); + } + + NToggle { + label: I18n.tr("bar.control-center.use-distro-logo-label") + description: I18n.tr("bar.control-center.use-distro-logo-description") + checked: valueUseDistroLogo + onToggled: checked => { + valueUseDistroLogo = checked; + saveSettings(); + } + defaultValue: widgetMetadata.useDistroLogo + } + + NToggle { + label: I18n.tr("bar.custom-button.enable-colorization-label") + description: I18n.tr("bar.control-center.enable-colorization-description") + checked: valueEnableColorization + onToggled: checked => { + valueEnableColorization = checked; + saveSettings(); + } + defaultValue: widgetMetadata.enableColorization + } + + NColorChoice { + visible: valueEnableColorization + label: I18n.tr("common.select-icon-color") + description: I18n.tr("bar.control-center.color-selection-description") + currentKey: valueColorizeSystemIcon + onSelected: function (key) { + valueColorizeSystemIcon = key; + saveSettings(); + } + defaultValue: widgetMetadata.colorizeSystemIcon + } + + RowLayout { + spacing: Style.marginM + + NLabel { + label: I18n.tr("common.icon") + description: I18n.tr("bar.control-center.icon-description") + } + + NImageRounded { + Layout.preferredWidth: Style.fontSizeXL * 2 + Layout.preferredHeight: Style.fontSizeXL * 2 + Layout.alignment: Qt.AlignVCenter + radius: Math.min(Style.radiusL, Layout.preferredWidth / 2) + imagePath: valueCustomIconPath + visible: valueCustomIconPath !== "" && !valueUseDistroLogo + } + + NIcon { + Layout.alignment: Qt.AlignVCenter + icon: valueIcon + pointSize: Style.fontSizeXXL * 1.5 + visible: valueIcon !== "" && valueCustomIconPath === "" && !valueUseDistroLogo + } + } + + RowLayout { + spacing: Style.marginM + NButton { + enabled: !valueUseDistroLogo + text: I18n.tr("bar.control-center.browse-library") + onClicked: iconPicker.open() + } + + NButton { + enabled: !valueUseDistroLogo + text: I18n.tr("bar.control-center.browse-file") + onClicked: imagePicker.openFilePicker() + } + } + + NIconPicker { + id: iconPicker + initialIcon: valueIcon + onIconSelected: iconName => { + valueIcon = iconName; + valueCustomIconPath = ""; + saveSettings(); + } + } + + NFilePicker { + id: imagePicker + title: I18n.tr("bar.control-center.select-custom-icon") + selectionMode: "files" + nameFilters: ImageCacheService.basicImageFilters.concat(["*.svg"]) + initialPath: Quickshell.env("HOME") + onAccepted: paths => { + if (paths.length > 0) { + valueCustomIconPath = paths[0]; // Use first selected file + saveSettings(); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml new file mode 100644 index 0000000..0b8e1ce --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml @@ -0,0 +1,512 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import qs.Commons +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + property string valueIcon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon + property bool valueTextStream: widgetData.textStream !== undefined ? widgetData.textStream : widgetMetadata.textStream + property bool valueParseJson: widgetData.parseJson !== undefined ? widgetData.parseJson : widgetMetadata.parseJson + property int valueMaxTextLengthHorizontal: widgetData?.maxTextLength?.horizontal ?? widgetMetadata?.maxTextLength?.horizontal + property int valueMaxTextLengthVertical: widgetData?.maxTextLength?.vertical ?? widgetMetadata?.maxTextLength?.vertical + property string valueHideMode: (widgetData.hideMode !== undefined) ? widgetData.hideMode : widgetMetadata.hideMode + property bool valueShowIcon: (widgetData.showIcon !== undefined) ? widgetData.showIcon : widgetMetadata.showIcon + property bool valueShowExecTooltip: widgetData.showExecTooltip !== undefined ? widgetData.showExecTooltip : widgetMetadata.showExecTooltip + property bool valueShowTextTooltip: widgetData.showTextTooltip !== undefined ? widgetData.showTextTooltip : widgetMetadata.showTextTooltip + property bool valueEnableColorization: widgetData.enableColorization !== undefined ? widgetData.enableColorization : widgetMetadata.enableColorization + property string valueColorizeSystemIcon: widgetData.colorizeSystemIcon !== undefined ? widgetData.colorizeSystemIcon : widgetMetadata.colorizeSystemIcon + property string valueIpcIdentifier: widgetData.ipcIdentifier !== undefined ? widgetData.ipcIdentifier : widgetMetadata.ipcIdentifier + property string valueGeneralTooltipText: widgetData.generalTooltipText !== undefined ? widgetData.generalTooltipText : widgetMetadata.generalTooltipText + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.icon = valueIcon; + settings.leftClickExec = leftClickExecInput.text; + settings.leftClickUpdateText = leftClickUpdateText.checked; + settings.rightClickExec = rightClickExecInput.text; + settings.rightClickUpdateText = rightClickUpdateText.checked; + settings.middleClickExec = middleClickExecInput.text; + settings.middleClickUpdateText = middleClickUpdateText.checked; + settings.wheelMode = separateWheelToggle.internalChecked ? "separate" : "unified"; + settings.wheelExec = wheelExecInput.text; + settings.wheelUpExec = wheelUpExecInput.text; + settings.wheelDownExec = wheelDownExecInput.text; + settings.wheelUpdateText = wheelUpdateText.checked; + settings.wheelUpUpdateText = wheelUpUpdateText.checked; + settings.wheelDownUpdateText = wheelDownUpdateText.checked; + settings.textCommand = textCommandInput.text; + settings.textCollapse = textCollapseInput.text; + settings.textStream = valueTextStream; + settings.parseJson = valueParseJson; + settings.showIcon = valueShowIcon; + settings.showExecTooltip = valueShowExecTooltip; + settings.showTextTooltip = valueShowTextTooltip; + settings.hideMode = valueHideMode; + settings.maxTextLength = { + "horizontal": valueMaxTextLengthHorizontal, + "vertical": valueMaxTextLengthVertical + }; + settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10); + settings.enableColorization = valueEnableColorization; + settings.colorizeSystemIcon = valueColorizeSystemIcon; + settings.ipcIdentifier = valueIpcIdentifier; + settings.generalTooltipText = valueGeneralTooltipText; + settingsChanged(settings); + } + + RowLayout { + spacing: Style.marginM + + NLabel { + label: I18n.tr("common.icon") + description: I18n.tr("bar.custom-button.icon-description") + } + + NIcon { + Layout.alignment: Qt.AlignVCenter + icon: valueIcon + pointSize: Style.fontSizeXL + visible: valueIcon !== "" + } + + NButton { + text: I18n.tr("common.browse") + onClicked: iconPicker.open() + } + } + + NIconPicker { + id: iconPicker + initialIcon: valueIcon + onIconSelected: function (iconName) { + valueIcon = iconName; + saveSettings(); + } + } + + NToggle { + id: showIconToggle + label: I18n.tr("bar.custom-button.show-icon-label") + description: I18n.tr("bar.custom-button.show-icon-description") + checked: valueShowIcon + onToggled: checked => { + valueShowIcon = checked; + saveSettings(); + } + visible: textCommandInput.text !== "" + defaultValue: widgetMetadata.showIcon + } + + NToggle { + label: I18n.tr("bar.custom-button.enable-colorization-label") + description: I18n.tr("bar.custom-button.enable-colorization-description") + checked: valueEnableColorization + onToggled: checked => { + valueEnableColorization = checked; + saveSettings(); + } + defaultValue: widgetMetadata.enableColorization + } + + NColorChoice { + visible: valueEnableColorization + label: I18n.tr("common.select-icon-color") + description: I18n.tr("bar.custom-button.color-selection-description") + currentKey: valueColorizeSystemIcon + onSelected: key => { + valueColorizeSystemIcon = key; + saveSettings(); + } + defaultValue: widgetMetadata.colorizeSystemIcon + } + + NTextInput { + Layout.fillWidth: true + label: I18n.tr("bar.custom-button.general-tooltip-text-label") + description: I18n.tr("bar.custom-button.general-tooltip-text-description") + placeholderText: I18n.tr("placeholders.enter-tooltip") + text: valueGeneralTooltipText + onTextChanged: { + valueGeneralTooltipText = text; + saveSettings(); + } + defaultValue: widgetMetadata.generalTooltipText + } + + NToggle { + id: showExecTooltipToggle + label: I18n.tr("bar.custom-button.show-exec-tooltip-label") + description: I18n.tr("bar.custom-button.show-exec-tooltip-description") + checked: valueShowExecTooltip + onToggled: checked => { + valueShowExecTooltip = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showExecTooltip + } + + NToggle { + id: showTextTooltipToggle + label: I18n.tr("bar.custom-button.show-text-tooltip-label") + description: I18n.tr("bar.custom-button.show-text-tooltip-description") + checked: valueShowTextTooltip + onToggled: checked => { + valueShowTextTooltip = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showTextTooltip + } + + NTextInput { + Layout.fillWidth: true + label: I18n.tr("bar.custom-button.ipc-identifier-label") + description: I18n.tr("bar.custom-button.ipc-identifier-description") + placeholderText: I18n.tr("placeholders.enter-ipc-identifier") + text: valueIpcIdentifier + onTextChanged: { + valueIpcIdentifier = text; + saveSettings(); + } + defaultValue: widgetMetadata.ipcIdentifier + } + + RowLayout { + spacing: Style.marginM + + NTextInput { + id: leftClickExecInput + Layout.fillWidth: true + label: I18n.tr("bar.custom-button.left-click-label") + description: I18n.tr("bar.custom-button.left-click-description") + placeholderText: I18n.tr("placeholders.enter-command") + text: widgetData?.leftClickExec || widgetMetadata.leftClickExec + onTextChanged: saveSettings() + defaultValue: widgetMetadata.leftClickExec + } + + NToggle { + id: leftClickUpdateText + enabled: !valueTextStream + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + Layout.bottomMargin: Style.marginS + onEntered: TooltipService.show(leftClickUpdateText, I18n.tr("bar.custom-button.left-click-update-text")) + onExited: TooltipService.hide() + checked: widgetData?.leftClickUpdateText ?? widgetMetadata.leftClickUpdateText + onToggled: isChecked => { + checked = isChecked; + saveSettings(); + } + defaultValue: widgetMetadata.leftClickUpdateText + } + } + + RowLayout { + spacing: Style.marginM + + NTextInput { + id: rightClickExecInput + Layout.fillWidth: true + label: I18n.tr("bar.custom-button.right-click-label") + description: I18n.tr("bar.custom-button.right-click-description") + placeholderText: I18n.tr("placeholders.enter-command") + text: widgetData?.rightClickExec || widgetMetadata.rightClickExec + onTextChanged: saveSettings() + defaultValue: widgetMetadata.rightClickExec + } + + NToggle { + id: rightClickUpdateText + enabled: !valueTextStream + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + Layout.bottomMargin: Style.marginS + onEntered: TooltipService.show(rightClickUpdateText, I18n.tr("bar.custom-button.right-click-update-text")) + onExited: TooltipService.hide() + checked: widgetData?.rightClickUpdateText ?? widgetMetadata.rightClickUpdateText + onToggled: isChecked => { + checked = isChecked; + saveSettings(); + } + defaultValue: widgetMetadata.rightClickUpdateText + } + } + + RowLayout { + spacing: Style.marginM + + NTextInput { + id: middleClickExecInput + Layout.fillWidth: true + label: I18n.tr("bar.custom-button.middle-click-label") + description: I18n.tr("bar.custom-button.middle-click-description") + placeholderText: I18n.tr("placeholders.enter-command") + text: widgetData.middleClickExec || widgetMetadata.middleClickExec + onTextChanged: saveSettings() + defaultValue: widgetMetadata.middleClickExec + } + + NToggle { + id: middleClickUpdateText + enabled: !valueTextStream + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + Layout.bottomMargin: Style.marginS + onEntered: TooltipService.show(middleClickUpdateText, I18n.tr("bar.custom-button.middle-click-update-text")) + onExited: TooltipService.hide() + checked: widgetData?.middleClickUpdateText ?? widgetMetadata.middleClickUpdateText + onToggled: isChecked => { + checked = isChecked; + saveSettings(); + } + defaultValue: widgetMetadata.middleClickUpdateText + } + } + + // Wheel command settings + NToggle { + id: separateWheelToggle + Layout.fillWidth: true + label: I18n.tr("bar.custom-button.wheel-mode-separate-label") + description: I18n.tr("bar.custom-button.wheel-mode-separate-description") + property bool internalChecked: (widgetData?.wheelMode || widgetMetadata?.wheelMode) === "separate" + checked: internalChecked + onToggled: checked => { + internalChecked = checked; + saveSettings(); + } + defaultValue: widgetMetadata.wheelMode === "separate" + } + + ColumnLayout { + Layout.fillWidth: true + + RowLayout { + id: unifiedWheelLayout + visible: !separateWheelToggle.checked + spacing: Style.marginM + + NTextInput { + id: wheelExecInput + Layout.fillWidth: true + label: I18n.tr("bar.custom-button.wheel-label") + description: I18n.tr("bar.custom-button.wheel-description") + placeholderText: I18n.tr("placeholders.enter-command") + text: widgetData?.wheelExec || widgetMetadata?.wheelExec + onTextChanged: saveSettings() + defaultValue: widgetMetadata.wheelExec + } + + NToggle { + id: wheelUpdateText + enabled: !valueTextStream + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + Layout.bottomMargin: Style.marginS + onEntered: TooltipService.show(wheelUpdateText, I18n.tr("bar.custom-button.wheel-update-text")) + onExited: TooltipService.hide() + checked: widgetData?.wheelUpdateText ?? widgetMetadata?.wheelUpdateText + onToggled: isChecked => { + checked = isChecked; + saveSettings(); + } + defaultValue: widgetMetadata.wheelUpdateText + } + } + + ColumnLayout { + id: separatedWheelLayout + Layout.fillWidth: true + visible: separateWheelToggle.checked + + RowLayout { + spacing: Style.marginM + + NTextInput { + id: wheelUpExecInput + Layout.fillWidth: true + label: I18n.tr("bar.custom-button.wheel-up-label") + description: I18n.tr("bar.custom-button.wheel-up-description") + placeholderText: I18n.tr("placeholders.enter-command") + text: widgetData?.wheelUpExec || widgetMetadata?.wheelUpExec + onTextChanged: saveSettings() + defaultValue: widgetMetadata.wheelUpExec + } + + NToggle { + id: wheelUpUpdateText + enabled: !valueTextStream + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + Layout.bottomMargin: Style.marginS + onEntered: TooltipService.show(wheelUpUpdateText, I18n.tr("bar.custom-button.wheel-update-text")) + onExited: TooltipService.hide() + checked: (widgetData?.wheelUpUpdateText !== undefined) ? widgetData.wheelUpUpdateText : widgetMetadata?.wheelUpUpdateText + onToggled: isChecked => { + checked = isChecked; + saveSettings(); + } + defaultValue: widgetMetadata.wheelUpUpdateText + } + } + + RowLayout { + spacing: Style.marginM + + NTextInput { + id: wheelDownExecInput + Layout.fillWidth: true + label: I18n.tr("bar.custom-button.wheel-down-label") + description: I18n.tr("bar.custom-button.wheel-down-description") + placeholderText: I18n.tr("placeholders.enter-command") + text: widgetData?.wheelDownExec || widgetMetadata?.wheelDownExec + onTextChanged: saveSettings() + defaultValue: widgetMetadata.wheelDownExec + } + + NToggle { + id: wheelDownUpdateText + enabled: !valueTextStream + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + Layout.bottomMargin: Style.marginS + onEntered: TooltipService.show(wheelDownUpdateText, I18n.tr("bar.custom-button.wheel-update-text")) + onExited: TooltipService.hide() + checked: (widgetData?.wheelDownUpdateText !== undefined) ? widgetData.wheelDownUpdateText : widgetMetadata?.wheelDownUpdateText + onToggled: isChecked => { + checked = isChecked; + saveSettings(); + } + defaultValue: widgetMetadata.wheelDownUpdateText + } + } + } + } + + NDivider { + Layout.fillWidth: true + } + + NHeader { + label: I18n.tr("bar.custom-button.dynamic-text") + } + + NSpinBox { + label: I18n.tr("bar.custom-button.max-text-length-horizontal-label") + description: I18n.tr("bar.custom-button.max-text-length-horizontal-description") + from: 0 + to: 100 + value: valueMaxTextLengthHorizontal + onValueChanged: { + valueMaxTextLengthHorizontal = value; + saveSettings(); + } + defaultValue: widgetMetadata.maxTextLength.horizontal + } + + NSpinBox { + label: I18n.tr("bar.custom-button.max-text-length-vertical-label") + description: I18n.tr("bar.custom-button.max-text-length-vertical-description") + from: 0 + to: 100 + value: valueMaxTextLengthVertical + onValueChanged: { + valueMaxTextLengthVertical = value; + saveSettings(); + } + defaultValue: widgetMetadata.maxTextLength.vertical + } + + NToggle { + id: textStreamInput + label: I18n.tr("bar.custom-button.text-stream-label") + description: I18n.tr("bar.custom-button.text-stream-description") + checked: valueTextStream + onToggled: checked => { + valueTextStream = checked; + saveSettings(); + } + defaultValue: widgetMetadata.textStream + } + + NToggle { + id: parseJsonInput + label: I18n.tr("bar.custom-button.parse-json-label") + description: I18n.tr("bar.custom-button.parse-json-description") + checked: valueParseJson + onToggled: checked => { + valueParseJson = checked; + saveSettings(); + } + defaultValue: widgetMetadata.parseJson + } + + NTextInput { + id: textCommandInput + Layout.fillWidth: true + label: I18n.tr("bar.custom-button.display-command-output-label") + description: valueTextStream ? I18n.tr("bar.custom-button.display-command-output-stream-description") : I18n.tr("bar.custom-button.display-command-output-description") + placeholderText: I18n.tr("placeholders.command-example") + text: widgetData?.textCommand || widgetMetadata.textCommand + onTextChanged: saveSettings() + defaultValue: widgetMetadata.textCommand + } + + NTextInput { + id: textCollapseInput + Layout.fillWidth: true + visible: valueTextStream + label: I18n.tr("bar.custom-button.collapse-condition-label") + description: I18n.tr("bar.custom-button.collapse-condition-description") + placeholderText: I18n.tr("placeholders.enter-text-to-collapse") + text: widgetData?.textCollapse || widgetMetadata.textCollapse + onTextChanged: saveSettings() + defaultValue: widgetMetadata.textCollapse + } + + NTextInput { + id: textIntervalInput + Layout.fillWidth: true + visible: !valueTextStream + label: I18n.tr("bar.custom-button.refresh-interval-label") + description: I18n.tr("bar.custom-button.refresh-interval-description") + placeholderText: String(widgetMetadata.textIntervalMs) + text: widgetData && widgetData.textIntervalMs !== undefined ? String(widgetData.textIntervalMs) : "" + onTextChanged: saveSettings() + defaultValue: String(widgetMetadata.textIntervalMs) + } + + NComboBox { + id: hideModeComboBox + label: I18n.tr("bar.custom-button.hide-mode-label") + description: I18n.tr("bar.custom-button.hide-mode-description") + model: [ + { + name: I18n.tr("bar.custom-button.hide-mode-always-expanded"), + key: "alwaysExpanded" + }, + { + name: I18n.tr("bar.custom-button.hide-mode-expand-with-output"), + key: "expandWithOutput" + }, + { + name: I18n.tr("bar.custom-button.hide-mode-max-transparent"), + key: "maxTransparent" + } + ] + currentKey: valueHideMode + onSelected: key => { + valueHideMode = key; + saveSettings(); + } + visible: textCommandInput.text !== "" && valueTextStream == true + defaultValue: widgetMetadata.hideMode + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/DarkModeSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/DarkModeSettings.qml new file mode 100644 index 0000000..f6546e8 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/DarkModeSettings.qml @@ -0,0 +1,34 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.iconColor = valueIconColor; + settingsChanged(settings); + } + + NColorChoice { + label: I18n.tr("common.select-icon-color") + currentKey: valueIconColor + onSelected: key => { + valueIconColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.iconColor + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/KeepAwakeSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/KeepAwakeSettings.qml new file mode 100644 index 0000000..fc94235 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/KeepAwakeSettings.qml @@ -0,0 +1,45 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor + property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.iconColor = valueIconColor; + settings.textColor = valueTextColor; + settingsChanged(settings); + } + + NColorChoice { + label: I18n.tr("common.select-icon-color") + currentKey: valueIconColor + onSelected: key => { + valueIconColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.iconColor + } + + NColorChoice { + currentKey: valueTextColor + onSelected: key => { + valueTextColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.textColor + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/KeyboardLayoutSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/KeyboardLayoutSettings.qml new file mode 100644 index 0000000..9b40c80 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/KeyboardLayoutSettings.qml @@ -0,0 +1,89 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + // Local state + property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode + property bool valueShowIcon: widgetData.showIcon !== undefined ? widgetData.showIcon : widgetMetadata.showIcon + property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor + property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.displayMode = valueDisplayMode; + settings.showIcon = valueShowIcon; + settings.iconColor = valueIconColor; + settings.textColor = valueTextColor; + settingsChanged(settings); + } + + NComboBox { + visible: valueShowIcon // Hide display mode setting when icon is disabled + label: I18n.tr("common.display-mode") + description: I18n.tr("bar.volume.display-mode-description") + minimumWidth: 200 + model: [ + { + "key": "onhover", + "name": I18n.tr("display-modes.on-hover") + }, + { + "key": "forceOpen", + "name": I18n.tr("display-modes.force-open") + }, + { + "key": "alwaysHide", + "name": I18n.tr("display-modes.always-hide") + } + ] + currentKey: valueDisplayMode + onSelected: key => { + valueDisplayMode = key; + saveSettings(); + } + defaultValue: widgetMetadata.displayMode + } + + NToggle { + label: I18n.tr("bar.custom-button.show-icon-label") + description: I18n.tr("bar.keyboard-layout.show-icon-description") + checked: valueShowIcon + onToggled: checked => { + valueShowIcon = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showIcon + } + + NColorChoice { + label: I18n.tr("common.select-icon-color") + currentKey: valueIconColor + onSelected: key => { + valueIconColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.iconColor + } + + NColorChoice { + currentKey: valueTextColor + onSelected: key => { + valueTextColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.textColor + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/LauncherSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/LauncherSettings.qml new file mode 100644 index 0000000..ff91dc6 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/LauncherSettings.qml @@ -0,0 +1,134 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + // Local state + property string valueIcon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon + property bool valueUseDistroLogo: widgetData.useDistroLogo !== undefined ? widgetData.useDistroLogo : widgetMetadata.useDistroLogo + property string valueCustomIconPath: widgetData.customIconPath !== undefined ? widgetData.customIconPath : widgetMetadata.customIconPath + property bool valueEnableColorization: widgetData.enableColorization !== undefined ? widgetData.enableColorization : widgetMetadata.enableColorization + property string valueColorizeSystemIcon: widgetData.colorizeSystemIcon !== undefined ? widgetData.colorizeSystemIcon : widgetMetadata.colorizeSystemIcon + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.icon = valueIcon; + settings.useDistroLogo = valueUseDistroLogo; + settings.customIconPath = valueCustomIconPath; + settings.enableColorization = valueEnableColorization; + settings.colorizeSystemIcon = valueColorizeSystemIcon; + settingsChanged(settings); + } + + NToggle { + label: I18n.tr("bar.control-center.use-distro-logo-label") + description: I18n.tr("bar.control-center.use-distro-logo-description") + checked: valueUseDistroLogo + onToggled: checked => { + valueUseDistroLogo = checked; + saveSettings(); + } + defaultValue: widgetMetadata.useDistroLogo + } + + NToggle { + label: I18n.tr("bar.custom-button.enable-colorization-label") + description: I18n.tr("bar.control-center.enable-colorization-description") + checked: valueEnableColorization + onToggled: checked => { + valueEnableColorization = checked; + saveSettings(); + } + defaultValue: widgetMetadata.enableColorization + } + + NColorChoice { + visible: valueEnableColorization + label: I18n.tr("common.select-icon-color") + description: I18n.tr("bar.control-center.color-selection-description") + currentKey: valueColorizeSystemIcon + onSelected: function (key) { + valueColorizeSystemIcon = key; + saveSettings(); + } + defaultValue: widgetMetadata.colorizeSystemIcon + } + + RowLayout { + spacing: Style.marginM + + NLabel { + label: I18n.tr("common.icon") + description: I18n.tr("bar.control-center.icon-description") + } + + NImageRounded { + Layout.preferredWidth: Style.fontSizeXL * 2 + Layout.preferredHeight: Style.fontSizeXL * 2 + Layout.alignment: Qt.AlignVCenter + radius: Math.min(Style.radiusL, Layout.preferredWidth / 2) + imagePath: valueCustomIconPath + visible: valueCustomIconPath !== "" && !valueUseDistroLogo + } + + NIcon { + Layout.alignment: Qt.AlignVCenter + icon: valueIcon + pointSize: Style.fontSizeXXL * 1.5 + visible: valueIcon !== "" && valueCustomIconPath === "" && !valueUseDistroLogo + } + } + + RowLayout { + spacing: Style.marginM + NButton { + enabled: !valueUseDistroLogo + text: I18n.tr("bar.control-center.browse-library") + onClicked: iconPicker.open() + } + + NButton { + enabled: !valueUseDistroLogo + text: I18n.tr("bar.control-center.browse-file") + onClicked: imagePicker.openFilePicker() + } + } + + NIconPicker { + id: iconPicker + initialIcon: valueIcon + onIconSelected: iconName => { + valueIcon = iconName; + valueCustomIconPath = ""; + saveSettings(); + } + } + + NFilePicker { + id: imagePicker + title: I18n.tr("bar.control-center.select-custom-icon") + selectionMode: "files" + nameFilters: ImageCacheService.basicImageFilters.concat(["*.svg"]) + initialPath: Quickshell.env("HOME") + onAccepted: paths => { + if (paths.length > 0) { + valueCustomIconPath = paths[0]; + saveSettings(); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/LockKeysSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/LockKeysSettings.qml new file mode 100644 index 0000000..f5373a3 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/LockKeysSettings.qml @@ -0,0 +1,170 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + // Local state + property bool valueShowCapsLock: widgetData.showCapsLock !== undefined ? widgetData.showCapsLock : widgetMetadata.showCapsLock + property bool valueShowNumLock: widgetData.showNumLock !== undefined ? widgetData.showNumLock : widgetMetadata.showNumLock + property bool valueShowScrollLock: widgetData.showScrollLock !== undefined ? widgetData.showScrollLock : widgetMetadata.showScrollLock + + property string capsIcon: widgetData.capsLockIcon !== undefined ? widgetData.capsLockIcon : widgetMetadata.capsLockIcon + property string numIcon: widgetData.numLockIcon !== undefined ? widgetData.numLockIcon : widgetMetadata.numLockIcon + property string scrollIcon: widgetData.scrollLockIcon !== undefined ? widgetData.scrollLockIcon : widgetMetadata.scrollLockIcon + + property bool valueHideWhenOff: widgetData.hideWhenOff !== undefined ? widgetData.hideWhenOff : (widgetMetadata.hideWhenOff !== undefined ? widgetMetadata.hideWhenOff : false) + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.showCapsLock = valueShowCapsLock; + settings.showNumLock = valueShowNumLock; + settings.showScrollLock = valueShowScrollLock; + settings.capsLockIcon = capsIcon; + settings.numLockIcon = numIcon; + settings.scrollLockIcon = scrollIcon; + settings.hideWhenOff = valueHideWhenOff; + settingsChanged(settings); + } + + RowLayout { + spacing: Style.marginM + + NToggle { + label: I18n.tr("bar.lock-keys.show-caps-lock-label") + description: I18n.tr("bar.lock-keys.show-caps-lock-description") + checked: valueShowCapsLock + onToggled: checked => { + valueShowCapsLock = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showCapsLock + } + + NIcon { + Layout.alignment: Qt.AlignVCenter + icon: capsIcon + pointSize: Style.fontSizeXL + visible: capsIcon !== "" + } + + NButton { + text: I18n.tr("common.browse") + onClicked: capsPicker.open() + enabled: valueShowCapsLock + } + } + + NIconPicker { + id: capsPicker + initialIcon: capsIcon + query: "letter-c" + onIconSelected: function (iconName) { + capsIcon = iconName; + saveSettings(); + } + } + + RowLayout { + spacing: Style.marginM + + NToggle { + label: I18n.tr("bar.lock-keys.show-num-lock-label") + description: I18n.tr("bar.lock-keys.show-num-lock-description") + checked: valueShowNumLock + onToggled: checked => { + valueShowNumLock = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showNumLock + } + + NIcon { + Layout.alignment: Qt.AlignVCenter + icon: numIcon + pointSize: Style.fontSizeXL + visible: numIcon !== "" + } + + NButton { + text: I18n.tr("common.browse") + onClicked: numPicker.open() + enabled: valueShowNumLock + } + } + + NIconPicker { + id: numPicker + initialIcon: numIcon + query: "letter-n" + onIconSelected: function (iconName) { + numIcon = iconName; + saveSettings(); + } + } + + RowLayout { + spacing: Style.marginM + + NToggle { + label: I18n.tr("bar.lock-keys.show-scroll-lock-label") + description: I18n.tr("bar.lock-keys.show-scroll-lock-description") + checked: valueShowScrollLock + onToggled: checked => { + valueShowScrollLock = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showScrollLock + } + + NIcon { + Layout.alignment: Qt.AlignVCenter + icon: scrollIcon + pointSize: Style.fontSizeXL + visible: scrollIcon !== "" + } + + NButton { + text: I18n.tr("common.browse") + onClicked: scrollPicker.open() + enabled: valueShowScrollLock + } + } + + NIconPicker { + id: scrollPicker + initialIcon: scrollIcon + query: "letter-s" + onIconSelected: function (iconName) { + scrollIcon = iconName; + saveSettings(); + } + } + + NDivider { + Layout.fillWidth: true + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.lock-keys.hide-when-off-label") + description: I18n.tr("bar.lock-keys.hide-when-off-description") + checked: valueHideWhenOff + onToggled: checked => { + valueHideWhenOff = checked; + saveSettings(); + } + defaultValue: widgetMetadata.hideWhenOff + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/MediaMiniSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/MediaMiniSettings.qml new file mode 100644 index 0000000..da2f741 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/MediaMiniSettings.qml @@ -0,0 +1,248 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + // Local state + property string valueHideMode: widgetData.hideMode !== undefined ? widgetData.hideMode : widgetMetadata.hideMode + // Deprecated: hideWhenIdle now folded into hideMode = "idle" + property bool valueHideWhenIdle: widgetData.hideWhenIdle !== undefined ? widgetData.hideWhenIdle : widgetMetadata.hideWhenIdle + property bool valueShowAlbumArt: widgetData.showAlbumArt !== undefined ? widgetData.showAlbumArt : widgetMetadata.showAlbumArt + property bool valuePanelShowAlbumArt: widgetData.panelShowAlbumArt !== undefined ? widgetData.panelShowAlbumArt : widgetMetadata.panelShowAlbumArt + property bool valueShowArtistFirst: widgetData.showArtistFirst !== undefined ? widgetData.showArtistFirst : widgetMetadata.showArtistFirst + property bool valueShowVisualizer: widgetData.showVisualizer !== undefined ? widgetData.showVisualizer : widgetMetadata.showVisualizer + property string valueVisualizerType: widgetData.visualizerType !== undefined ? widgetData.visualizerType : widgetMetadata.visualizerType + property string valueScrollingMode: widgetData.scrollingMode !== undefined ? widgetData.scrollingMode : widgetMetadata.scrollingMode + property int valueMaxWidth: widgetData.maxWidth !== undefined ? widgetData.maxWidth : widgetMetadata.maxWidth + property bool valueUseFixedWidth: widgetData.useFixedWidth !== undefined ? widgetData.useFixedWidth : widgetMetadata.useFixedWidth + property bool valueShowProgressRing: widgetData.showProgressRing !== undefined ? widgetData.showProgressRing : widgetMetadata.showProgressRing + property bool valueCompactMode: widgetData.compactMode !== undefined ? widgetData.compactMode : widgetMetadata.compactMode + property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor + + Component.onCompleted: { + if (widgetData && widgetData.hideMode !== undefined) { + valueHideMode = widgetData.hideMode; + } + } + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.hideMode = valueHideMode; + // No longer store hideWhenIdle separately; kept for backward compatibility only + settings.showAlbumArt = valueShowAlbumArt; + settings.panelShowAlbumArt = valuePanelShowAlbumArt; + settings.showArtistFirst = valueShowArtistFirst; + settings.showVisualizer = valueShowVisualizer; + settings.visualizerType = valueVisualizerType; + settings.scrollingMode = valueScrollingMode; + settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth; + settings.useFixedWidth = valueUseFixedWidth; + settings.showProgressRing = valueShowProgressRing; + settings.compactMode = valueCompactMode; + settings.textColor = valueTextColor; + settingsChanged(settings); + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("bar.taskbar.hide-mode-label") + description: I18n.tr("bar.media-mini.hide-mode-description") + model: [ + { + "key": "visible", + "name": I18n.tr("hide-modes.visible") + }, + { + "key": "hidden", + "name": I18n.tr("hide-modes.hidden") + }, + { + "key": "transparent", + "name": I18n.tr("hide-modes.transparent") + }, + { + "key": "idle", + "name": I18n.tr("hide-modes.idle") + } + ] + currentKey: root.valueHideMode + onSelected: key => { + root.valueHideMode = key; + saveSettings(); + } + defaultValue: widgetMetadata.hideMode + } + + NToggle { + label: I18n.tr("bar.media-mini.show-album-art-label") + description: I18n.tr("bar.media-mini.show-album-art-description") + checked: valueShowAlbumArt + onToggled: checked => { + valueShowAlbumArt = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showAlbumArt + } + + NToggle { + label: I18n.tr("bar.media-mini.show-artist-first-label") + description: I18n.tr("bar.media-mini.show-artist-first-description") + checked: valueShowArtistFirst + onToggled: checked => { + valueShowArtistFirst = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showArtistFirst + } + + NToggle { + label: I18n.tr("bar.media-mini.show-visualizer-label") + description: I18n.tr("bar.media-mini.show-visualizer-description") + checked: valueShowVisualizer + onToggled: checked => { + valueShowVisualizer = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showVisualizer + } + + NComboBox { + visible: valueShowVisualizer + label: I18n.tr("bar.media-mini.visualizer-type-label") + description: I18n.tr("bar.media-mini.visualizer-type-description") + model: [ + { + "key": "linear", + "name": I18n.tr("options.visualizer-types.linear") + }, + { + "key": "mirrored", + "name": I18n.tr("options.visualizer-types.mirrored") + }, + { + "key": "wave", + "name": I18n.tr("options.visualizer-types.wave") + } + ] + currentKey: valueVisualizerType + onSelected: key => { + valueVisualizerType = key; + saveSettings(); + } + minimumWidth: 200 + defaultValue: widgetMetadata.visualizerType + } + + NTextInput { + id: widthInput + Layout.fillWidth: true + label: I18n.tr("bar.taskbar.max-width-label") + description: I18n.tr("bar.media-mini.max-width-description") + placeholderText: widgetMetadata.maxWidth + text: valueMaxWidth + onTextChanged: saveSettings() + defaultValue: String(widgetMetadata.maxWidth) + } + + NToggle { + label: I18n.tr("bar.media-mini.use-fixed-width-label") + description: I18n.tr("bar.media-mini.use-fixed-width-description") + checked: valueUseFixedWidth + onToggled: checked => { + valueUseFixedWidth = checked; + saveSettings(); + } + defaultValue: widgetMetadata.useFixedWidth + } + + NToggle { + label: I18n.tr("bar.media-mini.show-progress-ring-label") + description: I18n.tr("bar.media-mini.show-progress-ring-description") + checked: valueShowProgressRing + onToggled: checked => { + valueShowProgressRing = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showProgressRing + } + + NColorChoice { + currentKey: valueTextColor + onSelected: key => { + valueTextColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.textColor + } + + NComboBox { + label: I18n.tr("bar.media-mini.scrolling-mode-label") + description: I18n.tr("bar.media-mini.scrolling-mode-description") + model: [ + { + "key": "always", + "name": I18n.tr("options.scrolling-modes.always") + }, + { + "key": "hover", + "name": I18n.tr("options.scrolling-modes.hover") + }, + { + "key": "never", + "name": I18n.tr("options.scrolling-modes.never") + } + ] + currentKey: valueScrollingMode + onSelected: key => { + valueScrollingMode = key; + saveSettings(); + } + minimumWidth: 200 + defaultValue: widgetMetadata.scrollingMode + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginS + } + + NLabel { + label: I18n.tr("bar.media-mini.panel-section-label") + description: I18n.tr("bar.media-mini.panel-section-description") + labelColor: Color.mPrimary + } + + NToggle { + label: I18n.tr("bar.media-mini.show-album-art-label") + description: I18n.tr("bar.media-mini.show-album-art-description") + checked: valuePanelShowAlbumArt + onToggled: checked => { + valuePanelShowAlbumArt = checked; + saveSettings(); + } + defaultValue: widgetMetadata.panelShowAlbumArt + } + + NToggle { + label: I18n.tr("bar.media-mini.compact-mode-label") + description: I18n.tr("bar.media-mini.compact-mode-description") + checked: valueCompactMode + onToggled: checked => { + valueCompactMode = checked; + saveSettings(); + } + defaultValue: widgetMetadata.compactMode + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/MicrophoneSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/MicrophoneSettings.qml new file mode 100644 index 0000000..35e2b05 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/MicrophoneSettings.qml @@ -0,0 +1,90 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + // Local state + property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode + property string valueMiddleClickCommand: widgetData.middleClickCommand !== undefined ? widgetData.middleClickCommand : widgetMetadata.middleClickCommand + property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor + property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.displayMode = valueDisplayMode; + settings.middleClickCommand = valueMiddleClickCommand; + settings.iconColor = valueIconColor; + settings.textColor = valueTextColor; + settingsChanged(settings); + } + + NComboBox { + label: I18n.tr("common.display-mode") + description: I18n.tr("bar.volume.display-mode-description") + minimumWidth: 200 + model: [ + { + "key": "onhover", + "name": I18n.tr("display-modes.on-hover") + }, + { + "key": "alwaysShow", + "name": I18n.tr("display-modes.always-show") + }, + { + "key": "alwaysHide", + "name": I18n.tr("display-modes.always-hide") + } + ] + currentKey: valueDisplayMode + onSelected: key => { + valueDisplayMode = key; + saveSettings(); + } + defaultValue: widgetMetadata.displayMode + } + + NColorChoice { + label: I18n.tr("common.select-icon-color") + currentKey: valueIconColor + onSelected: key => { + valueIconColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.iconColor + } + + NColorChoice { + currentKey: valueTextColor + onSelected: key => { + valueTextColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.textColor + } + + // Middle click command + NTextInput { + label: I18n.tr("bar.custom-button.middle-click-label") + description: I18n.tr("panels.audio.on-middle-clicked-description") + placeholderText: I18n.tr("panels.audio.external-mixer-placeholder") + text: valueMiddleClickCommand + onTextChanged: { + valueMiddleClickCommand = text; + saveSettings(); + } + defaultValue: widgetMetadata.middleClickCommand + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/NetworkSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/NetworkSettings.qml new file mode 100644 index 0000000..cd1c53d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/NetworkSettings.qml @@ -0,0 +1,74 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + // Local state + property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode + property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor + property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.displayMode = valueDisplayMode; + settings.iconColor = valueIconColor; + settings.textColor = valueTextColor; + settingsChanged(settings); + } + + NComboBox { + label: I18n.tr("common.display-mode") + description: I18n.tr("bar.volume.display-mode-description") + minimumWidth: 200 + model: [ + { + "key": "onhover", + "name": I18n.tr("display-modes.on-hover") + }, + { + "key": "alwaysShow", + "name": I18n.tr("display-modes.always-show") + }, + { + "key": "alwaysHide", + "name": I18n.tr("display-modes.always-hide") + } + ] + currentKey: root.valueDisplayMode + onSelected: key => { + root.valueDisplayMode = key; + saveSettings(); + } + defaultValue: widgetMetadata.displayMode + } + + NColorChoice { + label: I18n.tr("common.select-icon-color") + currentKey: valueIconColor + onSelected: key => { + valueIconColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.iconColor + } + + NColorChoice { + currentKey: valueTextColor + onSelected: key => { + valueTextColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.textColor + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/NightLightSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/NightLightSettings.qml new file mode 100644 index 0000000..f6546e8 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/NightLightSettings.qml @@ -0,0 +1,34 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.iconColor = valueIconColor; + settingsChanged(settings); + } + + NColorChoice { + label: I18n.tr("common.select-icon-color") + currentKey: valueIconColor + onSelected: key => { + valueIconColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.iconColor + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/NoctaliaPerformanceSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/NoctaliaPerformanceSettings.qml new file mode 100644 index 0000000..f6546e8 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/NoctaliaPerformanceSettings.qml @@ -0,0 +1,34 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.iconColor = valueIconColor; + settingsChanged(settings); + } + + NColorChoice { + label: I18n.tr("common.select-icon-color") + currentKey: valueIconColor + onSelected: key => { + valueIconColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.iconColor + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/NotificationHistorySettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/NotificationHistorySettings.qml new file mode 100644 index 0000000..4a59f1a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/NotificationHistorySettings.qml @@ -0,0 +1,90 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + // Local state + property bool valueShowUnreadBadge: widgetData.showUnreadBadge !== undefined ? widgetData.showUnreadBadge : widgetMetadata.showUnreadBadge + property bool valueHideWhenZero: widgetData.hideWhenZero !== undefined ? widgetData.hideWhenZero : widgetMetadata.hideWhenZero + property bool valueHideWhenZeroUnread: widgetData.hideWhenZeroUnread !== undefined ? widgetData.hideWhenZeroUnread : widgetMetadata.hideWhenZeroUnread + property string valueUnreadBadgeColor: widgetData.unreadBadgeColor !== undefined ? widgetData.unreadBadgeColor : widgetMetadata.unreadBadgeColor + property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.showUnreadBadge = valueShowUnreadBadge; + settings.hideWhenZero = valueHideWhenZero; + settings.hideWhenZeroUnread = valueHideWhenZeroUnread; + settings.unreadBadgeColor = valueUnreadBadgeColor; + settings.iconColor = valueIconColor; + settingsChanged(settings); + } + + NToggle { + label: I18n.tr("bar.notification-history.show-unread-badge-label") + description: I18n.tr("bar.notification-history.show-unread-badge-description") + checked: valueShowUnreadBadge + onToggled: checked => { + valueShowUnreadBadge = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showUnreadBadge + } + + NColorChoice { + label: I18n.tr("common.select-icon-color") + currentKey: valueIconColor + onSelected: key => { + valueIconColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.iconColor + } + + NColorChoice { + label: I18n.tr("bar.notification-history.unread-badge-color-label") + description: I18n.tr("bar.notification-history.unread-badge-color-description") + currentKey: valueUnreadBadgeColor + onSelected: key => { + valueUnreadBadgeColor = key; + saveSettings(); + } + visible: valueShowUnreadBadge + defaultValue: widgetMetadata.unreadBadgeColor + } + + NToggle { + label: I18n.tr("bar.notification-history.hide-widget-when-zero-label") + description: I18n.tr("bar.notification-history.hide-widget-when-zero-description") + checked: valueHideWhenZero + onToggled: checked => { + valueHideWhenZero = checked; + saveSettings(); + } + enabled: !valueHideWhenZeroUnread + defaultValue: widgetMetadata.hideWhenZero + } + + NToggle { + label: I18n.tr("bar.notification-history.hide-widget-when-zero-unread-label") + description: I18n.tr("bar.notification-history.hide-widget-when-zero-unread-description") + checked: valueHideWhenZeroUnread + onToggled: checked => { + valueHideWhenZeroUnread = checked; + saveSettings(); + } + defaultValue: widgetMetadata.hideWhenZeroUnread + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/PowerProfileSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/PowerProfileSettings.qml new file mode 100644 index 0000000..f6546e8 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/PowerProfileSettings.qml @@ -0,0 +1,34 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.iconColor = valueIconColor; + settingsChanged(settings); + } + + NColorChoice { + label: I18n.tr("common.select-icon-color") + currentKey: valueIconColor + onSelected: key => { + valueIconColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.iconColor + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/SessionMenuSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/SessionMenuSettings.qml new file mode 100644 index 0000000..258d0ab --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/SessionMenuSettings.qml @@ -0,0 +1,38 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.System +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + width: 700 + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + // Local state + property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.iconColor = valueIconColor; + settingsChanged(settings); + } + + NColorChoice { + label: I18n.tr("common.select-icon-color") + currentKey: root.valueIconColor + onSelected: key => { + root.valueIconColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.iconColor + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/SettingsSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/SettingsSettings.qml new file mode 100644 index 0000000..d5f54b1 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/SettingsSettings.qml @@ -0,0 +1,35 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + // Local state + property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.iconColor = valueIconColor; + settingsChanged(settings); + } + + NColorChoice { + label: I18n.tr("common.select-icon-color") + currentKey: valueIconColor + onSelected: key => { + valueIconColor = key; + saveSettings(); + } + valueDefault: widgetMetadata.iconColor + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/SpacerSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/SpacerSettings.qml new file mode 100644 index 0000000..818f74f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/SpacerSettings.qml @@ -0,0 +1,34 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.width = parseInt(widthInput.text) || widgetMetadata.width; + settingsChanged(settings); + } + + NTextInput { + id: widthInput + Layout.fillWidth: true + label: I18n.tr("common.width") + description: I18n.tr("bar.spacer.width-description") + text: widgetData.width || widgetMetadata.width + placeholderText: I18n.tr("placeholders.enter-width-pixels") + onTextChanged: saveSettings() + defaultValue: String(widgetMetadata.width) + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml new file mode 100644 index 0000000..9d4f1e9 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml @@ -0,0 +1,325 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.System +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right" + + // Local, editable state for checkboxes + property bool valueCompactMode: widgetData.compactMode !== undefined ? widgetData.compactMode : widgetMetadata.compactMode + property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor + property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor + property bool valueUseMonospaceFont: widgetData.useMonospaceFont !== undefined ? widgetData.useMonospaceFont : widgetMetadata.useMonospaceFont + property bool valueUsePadding: widgetData.usePadding !== undefined ? widgetData.usePadding : widgetMetadata.usePadding + property bool valueShowCpuUsage: widgetData.showCpuUsage !== undefined ? widgetData.showCpuUsage : widgetMetadata.showCpuUsage + property bool valueShowCpuCores: widgetData.showCpuCores !== undefined ? widgetData.showCpuCores : widgetMetadata.showCpuCores + property bool valueShowCpuFreq: widgetData.showCpuFreq !== undefined ? widgetData.showCpuFreq : widgetMetadata.showCpuFreq + property bool valueShowCpuTemp: widgetData.showCpuTemp !== undefined ? widgetData.showCpuTemp : widgetMetadata.showCpuTemp + property bool valueShowGpuTemp: widgetData.showGpuTemp !== undefined ? widgetData.showGpuTemp : widgetMetadata.showGpuTemp + property bool valueShowLoadAverage: widgetData.showLoadAverage !== undefined ? widgetData.showLoadAverage : widgetMetadata.showLoadAverage + property bool valueShowMemoryUsage: widgetData.showMemoryUsage !== undefined ? widgetData.showMemoryUsage : widgetMetadata.showMemoryUsage + property bool valueShowMemoryAsPercent: widgetData.showMemoryAsPercent !== undefined ? widgetData.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent + property bool valueShowSwapUsage: widgetData.showSwapUsage !== undefined ? widgetData.showSwapUsage : widgetMetadata.showSwapUsage + property bool valueShowNetworkStats: widgetData.showNetworkStats !== undefined ? widgetData.showNetworkStats : widgetMetadata.showNetworkStats + property bool valueShowDiskUsage: widgetData.showDiskUsage !== undefined ? widgetData.showDiskUsage : widgetMetadata.showDiskUsage + property bool valueShowDiskUsageAsPercent: widgetData.showDiskUsageAsPercent !== undefined ? widgetData.showDiskUsageAsPercent : widgetMetadata.showDiskUsageAsPercent + property bool valueShowDiskAvailable: widgetData.showDiskAvailable !== undefined ? widgetData.showDiskAvailable : widgetMetadata.showDiskAvailable + property string valueDiskPath: widgetData.diskPath !== undefined ? widgetData.diskPath : widgetMetadata.diskPath + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.compactMode = valueCompactMode; + settings.iconColor = valueIconColor; + settings.textColor = valueTextColor; + settings.useMonospaceFont = valueUseMonospaceFont; + settings.usePadding = valueUsePadding; + settings.showCpuUsage = valueShowCpuUsage; + settings.showCpuCores = valueShowCpuCores; + settings.showCpuFreq = valueShowCpuFreq; + settings.showCpuTemp = valueShowCpuTemp; + settings.showGpuTemp = valueShowGpuTemp; + settings.showLoadAverage = valueShowLoadAverage; + settings.showMemoryUsage = valueShowMemoryUsage; + settings.showMemoryAsPercent = valueShowMemoryAsPercent; + settings.showSwapUsage = valueShowSwapUsage; + settings.showNetworkStats = valueShowNetworkStats; + settings.showDiskUsage = valueShowDiskUsage; + settings.showDiskUsageAsPercent = valueShowDiskUsageAsPercent; + settings.showDiskAvailable = valueShowDiskAvailable; + settings.diskPath = valueDiskPath; + + settingsChanged(settings); + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.system-monitor.compact-mode-label") + description: I18n.tr("bar.system-monitor.compact-mode-description") + checked: valueCompactMode + onToggled: checked => { + valueCompactMode = checked; + saveSettings(); + } + defaultValue: widgetMetadata.compactMode + } + + NColorChoice { + label: I18n.tr("common.select-icon-color") + currentKey: valueIconColor + onSelected: key => { + valueIconColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.iconColor + } + + NColorChoice { + currentKey: valueTextColor + onSelected: key => { + valueTextColor = key; + saveSettings(); + } + visible: !valueCompactMode + defaultValue: widgetMetadata.textColor + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.system-monitor.use-monospace-font-label") + description: I18n.tr("bar.system-monitor.use-monospace-font-description") + checked: valueUseMonospaceFont + onToggled: checked => { + valueUseMonospaceFont = checked; + saveSettings(); + } + visible: !valueCompactMode + defaultValue: widgetMetadata.useMonospaceFont + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.system-monitor.use-padding-label") + description: isVerticalBar ? I18n.tr("bar.system-monitor.use-padding-description-disabled-vertical") : !valueUseMonospaceFont ? I18n.tr("bar.system-monitor.use-padding-description-disabled-monospace-font") : I18n.tr("bar.system-monitor.use-padding-description") + checked: valueUsePadding && !isVerticalBar && valueUseMonospaceFont + onToggled: checked => { + valueUsePadding = checked; + saveSettings(); + } + visible: !valueCompactMode + enabled: !isVerticalBar && valueUseMonospaceFont + defaultValue: widgetMetadata.usePadding + } + + NDivider { + Layout.fillWidth: true + } + + NToggle { + id: showCpuUsage + Layout.fillWidth: true + label: I18n.tr("bar.system-monitor.cpu-usage-label") + description: I18n.tr("bar.system-monitor.cpu-usage-description") + checked: valueShowCpuUsage + onToggled: checked => { + valueShowCpuUsage = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showCpuUsage + } + + NToggle { + id: showCpuCores + Layout.fillWidth: true + label: I18n.tr("bar.system-monitor.cpu-cores-label") + description: I18n.tr("bar.system-monitor.cpu-cores-description") + checked: valueShowCpuCores + onToggled: checked => { + valueShowCpuCores = checked; + saveSettings(); + } + visible: valueCompactMode + } + + NToggle { + id: showCpuFreq + Layout.fillWidth: true + label: I18n.tr("bar.system-monitor.cpu-frequency-label") + description: I18n.tr("bar.system-monitor.cpu-frequency-description") + checked: valueShowCpuFreq + onToggled: checked => { + valueShowCpuFreq = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showCpuFreq + } + + NToggle { + id: showCpuTemp + Layout.fillWidth: true + label: I18n.tr("bar.system-monitor.cpu-temperature-label") + description: I18n.tr("bar.system-monitor.cpu-temperature-description") + checked: valueShowCpuTemp + onToggled: checked => { + valueShowCpuTemp = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showCpuTemp + } + + NToggle { + id: showLoadAverage + Layout.fillWidth: true + label: I18n.tr("bar.system-monitor.load-average-label") + description: I18n.tr("bar.system-monitor.load-average-description") + checked: valueShowLoadAverage + onToggled: checked => { + valueShowLoadAverage = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showLoadAverage + } + + NToggle { + id: showGpuTemp + Layout.fillWidth: true + label: I18n.tr("panels.system-monitor.gpu-section-label") + description: I18n.tr("bar.system-monitor.gpu-temperature-description") + checked: valueShowGpuTemp + onToggled: checked => { + valueShowGpuTemp = checked; + saveSettings(); + } + visible: SystemStatService.gpuAvailable + defaultValue: widgetMetadata.showGpuTemp + } + + NToggle { + id: showMemoryUsage + Layout.fillWidth: true + label: I18n.tr("bar.system-monitor.memory-usage-label") + description: I18n.tr("bar.system-monitor.memory-usage-description") + checked: valueShowMemoryUsage + onToggled: checked => { + valueShowMemoryUsage = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showMemoryUsage + } + + NToggle { + id: showMemoryAsPercent + Layout.fillWidth: true + label: I18n.tr("bar.system-monitor.memory-percentage-label") + description: I18n.tr("bar.system-monitor.memory-percentage-description") + checked: valueShowMemoryAsPercent + onToggled: checked => { + valueShowMemoryAsPercent = checked; + saveSettings(); + } + visible: valueShowMemoryUsage + defaultValue: widgetMetadata.showMemoryAsPercent + } + + NToggle { + id: showSwapUsage + Layout.fillWidth: true + label: I18n.tr("bar.system-monitor.swap-usage-label") + description: I18n.tr("bar.system-monitor.swap-usage-description") + checked: valueShowSwapUsage + onToggled: checked => { + valueShowSwapUsage = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showSwapUsage + } + + NToggle { + id: showNetworkStats + Layout.fillWidth: true + label: I18n.tr("bar.system-monitor.network-traffic-label") + description: I18n.tr("bar.system-monitor.network-traffic-description") + checked: valueShowNetworkStats + onToggled: checked => { + valueShowNetworkStats = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showNetworkStats + } + + NDivider { + Layout.fillWidth: true + } + + NToggle { + id: showDiskUsage + Layout.fillWidth: true + label: I18n.tr("bar.system-monitor.storage-usage-label") + description: I18n.tr("bar.system-monitor.storage-usage-description") + checked: valueShowDiskUsage + onToggled: checked => { + valueShowDiskUsage = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showDiskUsage + } + + NToggle { + id: showDiskUsageAsPercent + Layout.fillWidth: true + label: I18n.tr("bar.system-monitor.storage-as-percentage-label") + description: I18n.tr("bar.system-monitor.storage-as-percentage-description") + checked: valueShowDiskUsageAsPercent + onToggled: checked => { + valueShowDiskUsageAsPercent = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showDiskUsageAsPercent + } + + NToggle { + id: showDiskAvailable + Layout.fillWidth: true + label: I18n.tr("bar.system-monitor.storage-available-label") + description: I18n.tr("bar.system-monitor.storage-available-description") + checked: valueShowDiskAvailable + onToggled: checked => { + valueShowDiskAvailable = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showDiskAvailable + } + + NComboBox { + id: diskPathComboBox + Layout.fillWidth: true + label: I18n.tr("bar.system-monitor.disk-path-label") + description: I18n.tr("bar.system-monitor.disk-path-description") + model: { + const paths = Object.keys(SystemStatService.diskPercents).sort(); + return paths.map(path => ({ + key: path, + name: path + })); + } + currentKey: valueDiskPath + onSelected: key => { + valueDiskPath = key; + saveSettings(); + } + defaultValue: widgetMetadata.diskPath + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/TaskbarSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/TaskbarSettings.qml new file mode 100644 index 0000000..2628e59 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/TaskbarSettings.qml @@ -0,0 +1,201 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + readonly property bool isVerticalBar: Settings.data.bar.position === "left" || Settings.data.bar.position === "right" + + // Local state + property string valueHideMode: widgetData.hideMode !== undefined ? widgetData.hideMode : widgetMetadata.hideMode + property bool valueOnlyActiveWorkspaces: widgetData.onlyActiveWorkspaces !== undefined ? widgetData.onlyActiveWorkspaces : widgetMetadata.onlyActiveWorkspaces + property bool valueOnlySameOutput: widgetData.onlySameOutput !== undefined ? widgetData.onlySameOutput : widgetMetadata.onlySameOutput + property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons + property bool valueShowTitle: isVerticalBar ? false : widgetData.showTitle !== undefined ? widgetData.showTitle : widgetMetadata.showTitle + property bool valueSmartWidth: widgetData.smartWidth !== undefined ? widgetData.smartWidth : widgetMetadata.smartWidth + property int valueMaxTaskbarWidth: widgetData.maxTaskbarWidth !== undefined ? widgetData.maxTaskbarWidth : widgetMetadata.maxTaskbarWidth + property int valueTitleWidth: widgetData.titleWidth !== undefined ? widgetData.titleWidth : widgetMetadata.titleWidth + property bool valueShowPinnedApps: widgetData.showPinnedApps !== undefined ? widgetData.showPinnedApps : widgetMetadata.showPinnedApps + property real valueIconScale: widgetData.iconScale !== undefined ? widgetData.iconScale : widgetMetadata.iconScale + + Component.onCompleted: { + if (widgetData && widgetData.hideMode !== undefined) { + valueHideMode = widgetData.hideMode; + } else if (widgetMetadata && widgetMetadata.hideMode !== undefined) { + valueHideMode = widgetMetadata.hideMode; + } + } + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.hideMode = valueHideMode; + settings.onlySameOutput = valueOnlySameOutput; + settings.onlyActiveWorkspaces = valueOnlyActiveWorkspaces; + settings.colorizeIcons = valueColorizeIcons; + settings.showTitle = valueShowTitle; + settings.smartWidth = valueSmartWidth; + settings.maxTaskbarWidth = valueMaxTaskbarWidth; + settings.titleWidth = parseInt(titleWidthInput.text) || widgetMetadata.titleWidth; + settings.showPinnedApps = valueShowPinnedApps; + settings.iconScale = valueIconScale; + settingsChanged(settings); + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("bar.taskbar.hide-mode-label") + description: I18n.tr("bar.taskbar.hide-mode-description") + model: [ + { + "key": "visible", + "name": I18n.tr("hide-modes.visible") + }, + { + "key": "hidden", + "name": I18n.tr("hide-modes.hidden") + }, + { + "key": "transparent", + "name": I18n.tr("hide-modes.transparent") + } + ] + currentKey: root.valueHideMode + onSelected: key => { + root.valueHideMode = key; + saveSettings(); + } + defaultValue: widgetMetadata.hideMode + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.taskbar.only-same-monitor-label") + description: I18n.tr("bar.taskbar.only-same-monitor-description") + checked: root.valueOnlySameOutput + onToggled: checked => { + root.valueOnlySameOutput = checked; + saveSettings(); + } + defaultValue: widgetMetadata.onlySameOutput + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.taskbar.only-active-workspaces-label") + description: I18n.tr("bar.taskbar.only-active-workspaces-description") + checked: root.valueOnlyActiveWorkspaces + onToggled: checked => { + root.valueOnlyActiveWorkspaces = checked; + saveSettings(); + } + defaultValue: widgetMetadata.onlyActiveWorkspaces + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.tray.colorize-icons-label") + description: I18n.tr("bar.taskbar.colorize-icons-description") + checked: root.valueColorizeIcons + onToggled: checked => { + root.valueColorizeIcons = checked; + saveSettings(); + } + defaultValue: widgetMetadata.colorizeIcons + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.taskbar.show-pinned-apps-label") + description: I18n.tr("bar.taskbar.show-pinned-apps-description") + checked: root.valueShowPinnedApps + onToggled: checked => { + root.valueShowPinnedApps = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showPinnedApps + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("bar.taskbar.icon-scale-label") + description: I18n.tr("bar.taskbar.icon-scale-description") + from: 0.5 + to: 1 + stepSize: 0.01 + showReset: true + value: root.valueIconScale + defaultValue: widgetMetadata.iconScale + onMoved: value => { + root.valueIconScale = value; + saveSettings(); + } + text: Math.round(root.valueIconScale * 100) + "%" + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.taskbar.show-title-label") + description: isVerticalBar ? I18n.tr("bar.taskbar.show-title-description-disabled") : I18n.tr("bar.taskbar.show-title-description") + checked: root.valueShowTitle + onToggled: checked => { + root.valueShowTitle = checked; + saveSettings(); + } + enabled: !isVerticalBar + defaultValue: widgetMetadata.showTitle + } + + NTextInput { + id: titleWidthInput + visible: root.valueShowTitle && !isVerticalBar + Layout.fillWidth: true + label: I18n.tr("bar.taskbar.title-width-label") + description: I18n.tr("bar.taskbar.title-width-description") + text: widgetData.titleWidth || widgetMetadata.titleWidth + placeholderText: I18n.tr("placeholders.enter-width-pixels") + onTextChanged: saveSettings() + defaultValue: String(widgetMetadata.titleWidth) + } + + NToggle { + Layout.fillWidth: true + visible: !isVerticalBar && root.valueShowTitle + label: I18n.tr("bar.taskbar.smart-width-label") + description: I18n.tr("bar.taskbar.smart-width-description") + checked: root.valueSmartWidth + onToggled: checked => { + root.valueSmartWidth = checked; + saveSettings(); + } + defaultValue: widgetMetadata.smartWidth + } + + NValueSlider { + visible: root.valueSmartWidth && !isVerticalBar + Layout.fillWidth: true + label: I18n.tr("bar.taskbar.max-width-label") + description: I18n.tr("bar.taskbar.max-width-description") + from: 10 + to: 100 + stepSize: 5 + showReset: true + value: root.valueMaxTaskbarWidth + defaultValue: widgetMetadata.maxTaskbarWidth + onMoved: value => { + root.valueMaxTaskbarWidth = Math.round(value); + saveSettings(); + } + text: Math.round(root.valueMaxTaskbarWidth) + "%" + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/TraySettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/TraySettings.qml new file mode 100644 index 0000000..e3fb99b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/TraySettings.qml @@ -0,0 +1,199 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + // Local state + property var localBlacklist: widgetData.blacklist || [] + property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons + property string valueChevronColor: widgetData.chevronColor !== undefined ? widgetData.chevronColor : widgetMetadata.chevronColor + property bool valueDrawerEnabled: widgetData.drawerEnabled !== undefined ? widgetData.drawerEnabled : widgetMetadata.drawerEnabled + property bool valueHidePassive: widgetData.hidePassive !== undefined ? widgetData.hidePassive : widgetMetadata.hidePassive + + ListModel { + id: blacklistModel + } + + function populateBlacklist() { + for (var i = 0; i < localBlacklist.length; i++) { + blacklistModel.append({ + "rule": localBlacklist[i] + }); + } + } + + Component.onCompleted: { + Qt.callLater(populateBlacklist); + } + + spacing: Style.marginM + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.tray.drawer-enabled-label") + description: I18n.tr("bar.tray.drawer-enabled-description") + checked: root.valueDrawerEnabled + onToggled: checked => { + root.valueDrawerEnabled = checked; + saveSettings(); + } + defaultValue: widgetMetadata.drawerEnabled + } + + NColorChoice { + label: I18n.tr("bar.tray.chevron-color-label") + description: I18n.tr("bar.tray.chevron-color-description") + currentKey: root.valueChevronColor + onSelected: key => { + root.valueChevronColor = key; + saveSettings(); + } + visible: root.valueDrawerEnabled + defaultValue: widgetMetadata.chevronColor + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.tray.colorize-icons-label") + description: I18n.tr("bar.tray.colorize-icons-description") + checked: root.valueColorizeIcons + onToggled: checked => { + root.valueColorizeIcons = checked; + saveSettings(); + } + defaultValue: widgetMetadata.colorizeIcons + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.tray.hide-passive-label") + description: I18n.tr("bar.tray.hide-passive-description") + checked: root.valueHidePassive + onToggled: checked => { + root.valueHidePassive = checked; + saveSettings(); + } + defaultValue: widgetMetadata.hidePassive + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NLabel { + label: I18n.tr("panels.bar.tray-blacklist-label") + description: I18n.tr("panels.bar.tray-blacklist-description") + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NTextInputButton { + id: newRuleInput + Layout.fillWidth: true + placeholderText: I18n.tr("panels.bar.tray-blacklist-placeholder") + buttonIcon: "add" + onButtonClicked: { + if (newRuleInput.text.length > 0) { + var newRule = newRuleInput.text.trim(); + var exists = false; + for (var i = 0; i < blacklistModel.count; i++) { + if (blacklistModel.get(i).rule === newRule) { + exists = true; + break; + } + } + if (!exists) { + blacklistModel.append({ + "rule": newRule + }); + newRuleInput.text = ""; + saveSettings(); + } + } + } + } + } + } + + // List of current blacklist items + NListView { + Layout.fillWidth: true + Layout.preferredHeight: 150 + Layout.topMargin: Style.marginL // Increased top margin + gradientColor: Color.mSurface + + model: blacklistModel + delegate: Item { + width: ListView.width + height: 40 + + Rectangle { + id: itemBackground + anchors.fill: parent + anchors.margins: Style.marginXS + color: "transparent" // Make background transparent + border.color: Color.mOutline + border.width: Style.borderS + radius: Style.radiusS + visible: model.rule !== undefined && model.rule !== "" // Only visible if rule exists + } + + Row { + anchors.fill: parent + anchors.leftMargin: Style.marginS + anchors.rightMargin: Style.marginS + spacing: Style.marginS + + NText { + anchors.verticalCenter: parent.verticalCenter + text: model.rule + elide: Text.ElideRight + } + + NIconButton { + anchors.verticalCenter: parent.verticalCenter + icon: "close" + baseSize: 12 * Style.uiScaleRatio + colorBg: Color.mSurfaceVariant + colorFg: Color.mOnSurfaceVariant + colorBgHover: Color.mError + colorFgHover: Color.mOnError + onClicked: { + blacklistModel.remove(index); + saveSettings(); + } + } + } + } + } + + // This function will be called by the dialog to get the new settings + function saveSettings() { + var newBlacklist = []; + for (var i = 0; i < blacklistModel.count; i++) { + newBlacklist.push(blacklistModel.get(i).rule); + } + + // Return the updated settings for this widget instance + var settings = Object.assign({}, widgetData || {}); + settings.blacklist = newBlacklist; + settings.colorizeIcons = root.valueColorizeIcons; + settings.chevronColor = root.valueChevronColor; + settings.drawerEnabled = root.valueDrawerEnabled; + settings.hidePassive = root.valueHidePassive; + settingsChanged(settings); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/VPNSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/VPNSettings.qml new file mode 100644 index 0000000..1b94322 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/VPNSettings.qml @@ -0,0 +1,70 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode + property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor + property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.displayMode = valueDisplayMode; + settings.iconColor = valueIconColor; + settings.textColor = valueTextColor; + settingsChanged(settings); + } + + NComboBox { + label: I18n.tr("common.display-mode") + description: I18n.tr("bar.volume.display-mode-description") + minimumWidth: 200 + model: [ + { + "key": "onhover", + "name": I18n.tr("display-modes.on-hover") + }, + { + "key": "alwaysShow", + "name": I18n.tr("display-modes.always-show") + }, + { + "key": "alwaysHide", + "name": I18n.tr("display-modes.always-hide") + } + ] + currentKey: root.valueDisplayMode + onSelected: key => { + root.valueDisplayMode = key; + saveSettings(); + } + } + + NColorChoice { + label: I18n.tr("common.select-icon-color") + currentKey: valueIconColor + onSelected: key => { + valueIconColor = key; + saveSettings(); + } + } + + NColorChoice { + currentKey: valueTextColor + onSelected: key => { + valueTextColor = key; + saveSettings(); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/VolumeSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/VolumeSettings.qml new file mode 100644 index 0000000..35e2b05 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/VolumeSettings.qml @@ -0,0 +1,90 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + // Local state + property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode + property string valueMiddleClickCommand: widgetData.middleClickCommand !== undefined ? widgetData.middleClickCommand : widgetMetadata.middleClickCommand + property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor + property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.displayMode = valueDisplayMode; + settings.middleClickCommand = valueMiddleClickCommand; + settings.iconColor = valueIconColor; + settings.textColor = valueTextColor; + settingsChanged(settings); + } + + NComboBox { + label: I18n.tr("common.display-mode") + description: I18n.tr("bar.volume.display-mode-description") + minimumWidth: 200 + model: [ + { + "key": "onhover", + "name": I18n.tr("display-modes.on-hover") + }, + { + "key": "alwaysShow", + "name": I18n.tr("display-modes.always-show") + }, + { + "key": "alwaysHide", + "name": I18n.tr("display-modes.always-hide") + } + ] + currentKey: valueDisplayMode + onSelected: key => { + valueDisplayMode = key; + saveSettings(); + } + defaultValue: widgetMetadata.displayMode + } + + NColorChoice { + label: I18n.tr("common.select-icon-color") + currentKey: valueIconColor + onSelected: key => { + valueIconColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.iconColor + } + + NColorChoice { + currentKey: valueTextColor + onSelected: key => { + valueTextColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.textColor + } + + // Middle click command + NTextInput { + label: I18n.tr("bar.custom-button.middle-click-label") + description: I18n.tr("panels.audio.on-middle-clicked-description") + placeholderText: I18n.tr("panels.audio.external-mixer-placeholder") + text: valueMiddleClickCommand + onTextChanged: { + valueMiddleClickCommand = text; + saveSettings(); + } + defaultValue: widgetMetadata.middleClickCommand + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/WallpaperSelectorSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/WallpaperSelectorSettings.qml new file mode 100644 index 0000000..ec0584b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/WallpaperSelectorSettings.qml @@ -0,0 +1,33 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.iconColor = valueIconColor; + settingsChanged(settings); + } + + NColorChoice { + label: I18n.tr("common.select-icon-color") + currentKey: valueIconColor + onSelected: key => { + valueIconColor = key; + saveSettings(); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/WorkspaceSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/WorkspaceSettings.qml new file mode 100644 index 0000000..5606df0 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Bar/WidgetSettings/WorkspaceSettings.qml @@ -0,0 +1,323 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var screen: null + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + property string valueLabelMode: widgetData.labelMode !== undefined ? widgetData.labelMode : widgetMetadata.labelMode + property bool valueHideUnoccupied: widgetData.hideUnoccupied !== undefined ? widgetData.hideUnoccupied : widgetMetadata.hideUnoccupied + property bool valueFollowFocusedScreen: widgetData.followFocusedScreen !== undefined ? widgetData.followFocusedScreen : widgetMetadata.followFocusedScreen + property int valueCharacterCount: widgetData.characterCount !== undefined ? widgetData.characterCount : widgetMetadata.characterCount + + // Grouped mode settings + property bool valueShowApplications: widgetData.showApplications !== undefined ? widgetData.showApplications : widgetMetadata.showApplications + property bool valueShowApplicationsHover: widgetData.showApplicationsHover !== undefined ? widgetData.showApplicationsHover : widgetMetadata.showApplicationsHover + property bool valueShowLabelsOnlyWhenOccupied: widgetData.showLabelsOnlyWhenOccupied !== undefined ? widgetData.showLabelsOnlyWhenOccupied : widgetMetadata.showLabelsOnlyWhenOccupied + property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons + property real valueUnfocusedIconsOpacity: widgetData.unfocusedIconsOpacity !== undefined ? widgetData.unfocusedIconsOpacity : widgetMetadata.unfocusedIconsOpacity + property real valueGroupedBorderOpacity: widgetData.groupedBorderOpacity !== undefined ? widgetData.groupedBorderOpacity : widgetMetadata.groupedBorderOpacity + property bool valueEnableScrollWheel: widgetData.enableScrollWheel !== undefined ? widgetData.enableScrollWheel : widgetMetadata.enableScrollWheel + property real valueIconScale: widgetData.iconScale !== undefined ? widgetData.iconScale : widgetMetadata.iconScale + property string valueFocusedColor: widgetData.focusedColor !== undefined ? widgetData.focusedColor : widgetMetadata.focusedColor + property string valueOccupiedColor: widgetData.occupiedColor !== undefined ? widgetData.occupiedColor : widgetMetadata.occupiedColor + property string valueEmptyColor: widgetData.emptyColor !== undefined ? widgetData.emptyColor : widgetMetadata.emptyColor + property bool valueShowBadge: widgetData.showBadge !== undefined ? widgetData.showBadge : widgetMetadata.showBadge + property real valuePillSize: widgetData.pillSize !== undefined ? widgetData.pillSize : widgetMetadata.pillSize + property string valueFontWeight: widgetData.fontWeight !== undefined ? widgetData.fontWeight : widgetMetadata.fontWeight + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.labelMode = valueLabelMode; + settings.hideUnoccupied = valueHideUnoccupied; + settings.characterCount = valueCharacterCount; + settings.followFocusedScreen = valueFollowFocusedScreen; + settings.showApplications = valueShowApplications; + settings.showApplicationsHover = valueShowApplicationsHover; + settings.showLabelsOnlyWhenOccupied = valueShowLabelsOnlyWhenOccupied; + settings.colorizeIcons = valueColorizeIcons; + settings.unfocusedIconsOpacity = valueUnfocusedIconsOpacity; + settings.groupedBorderOpacity = valueGroupedBorderOpacity; + settings.enableScrollWheel = valueEnableScrollWheel; + settings.iconScale = valueIconScale; + settings.focusedColor = valueFocusedColor; + settings.occupiedColor = valueOccupiedColor; + settings.emptyColor = valueEmptyColor; + settings.showBadge = valueShowBadge; + settings.pillSize = valuePillSize; + settings.fontWeight = valueFontWeight; + settingsChanged(settings); + } + + NComboBox { + id: labelModeCombo + label: I18n.tr("bar.workspace.label-mode-label") + description: I18n.tr("bar.workspace.label-mode-description") + model: [ + { + "key": "none", + "name": I18n.tr("common.none") + }, + { + "key": "index", + "name": I18n.tr("options.workspace-labels.index") + }, + { + "key": "name", + "name": I18n.tr("options.workspace-labels.name") + }, + { + "key": "index+name", + "name": I18n.tr("options.workspace-labels.index-and-name") + } + ] + currentKey: widgetData.labelMode || widgetMetadata.labelMode + onSelected: key => { + valueLabelMode = key; + saveSettings(); + } + minimumWidth: 200 + } + + NSpinBox { + label: I18n.tr("bar.workspace.character-count-label") + description: I18n.tr("bar.workspace.character-count-description") + from: 1 + to: 10 + value: valueCharacterCount + onValueChanged: { + valueCharacterCount = value; + saveSettings(); + } + visible: valueLabelMode === "name" + } + + NValueSlider { + label: I18n.tr("bar.workspace.pill-size-label") + description: I18n.tr("bar.workspace.pill-size-description") + from: 0.4 + to: 1.0 + stepSize: 0.01 + value: valuePillSize + defaultValue: widgetMetadata.pillSize + showReset: true + onMoved: value => { + valuePillSize = value; + saveSettings(); + } + text: Math.round(valuePillSize * 100) + "%" + visible: !valueShowApplications + } + + NComboBox { + id: fontWeightCombo + label: I18n.tr("bar.workspace.font-weight-label") + description: I18n.tr("bar.workspace.font-weight-description") + model: [ + { + "key": "regular", + "name": I18n.tr("common.font-weight-regular") + }, + { + "key": "medium", + "name": I18n.tr("common.font-weight-medium") + }, + { + "key": "semibold", + "name": I18n.tr("common.font-weight-semibold") + }, + { + "key": "bold", + "name": I18n.tr("common.font-weight-bold") + }, + ] + currentKey: widgetData.fontWeight || widgetMetadata.fontWeight + onSelected: key => { + valueFontWeight = key; + saveSettings(); + } + minimumWidth: 200 + } + + NToggle { + label: I18n.tr("bar.workspace.hide-unoccupied-label") + description: I18n.tr("bar.workspace.hide-unoccupied-description") + checked: valueHideUnoccupied + onToggled: checked => { + valueHideUnoccupied = checked; + saveSettings(); + } + } + + NToggle { + label: I18n.tr("bar.workspace.show-labels-only-when-occupied-label") + description: I18n.tr("bar.workspace.show-labels-only-when-occupied-description") + checked: valueShowLabelsOnlyWhenOccupied + onToggled: checked => { + valueShowLabelsOnlyWhenOccupied = checked; + saveSettings(); + } + } + + NToggle { + label: I18n.tr("bar.workspace.follow-focused-screen-label") + description: I18n.tr("bar.workspace.follow-focused-screen-description") + checked: valueFollowFocusedScreen + onToggled: checked => { + valueFollowFocusedScreen = checked; + saveSettings(); + } + } + + NToggle { + label: I18n.tr("bar.workspace.enable-scrollwheel-label") + description: I18n.tr("bar.workspace.enable-scrollwheel-description") + checked: valueEnableScrollWheel + onToggled: checked => { + valueEnableScrollWheel = checked; + saveSettings(); + } + } + + NDivider { + Layout.fillWidth: true + } + + NToggle { + label: I18n.tr("bar.workspace.show-applications-label") + description: I18n.tr("bar.workspace.show-applications-description") + checked: valueShowApplications + onToggled: checked => { + valueShowApplications = checked; + saveSettings(); + } + } + + NToggle { + label: I18n.tr("bar.workspace.show-applications-hover-label") + description: I18n.tr("bar.workspace.show-applications-hover-description") + checked: valueShowApplicationsHover + onToggled: checked => { + valueShowApplicationsHover = checked; + saveSettings(); + } + visible: valueShowApplications + } + + NToggle { + label: I18n.tr("bar.workspace.show-badge-label") + description: I18n.tr("bar.workspace.show-badge-description") + checked: valueShowBadge + onToggled: checked => { + valueShowBadge = checked; + saveSettings(); + } + visible: valueShowApplications + } + + NToggle { + label: I18n.tr("bar.tray.colorize-icons-label") + description: I18n.tr("bar.active-window.colorize-icons-description") + checked: valueColorizeIcons + onToggled: checked => { + valueColorizeIcons = checked; + saveSettings(); + } + visible: valueShowApplications + } + + NValueSlider { + label: I18n.tr("bar.workspace.unfocused-icons-opacity-label") + description: I18n.tr("bar.workspace.unfocused-icons-opacity-description") + from: 0 + to: 1 + stepSize: 0.01 + showReset: true + value: valueUnfocusedIconsOpacity + defaultValue: widgetMetadata.unfocusedIconsOpacity + onMoved: value => { + valueUnfocusedIconsOpacity = value; + saveSettings(); + } + text: Math.floor(valueUnfocusedIconsOpacity * 100) + "%" + visible: valueShowApplications + } + + NValueSlider { + label: I18n.tr("bar.workspace.grouped-border-opacity-label") + description: I18n.tr("bar.workspace.grouped-border-opacity-description") + from: 0 + to: 1 + stepSize: 0.01 + showReset: true + value: valueGroupedBorderOpacity + defaultValue: widgetMetadata.groupedBorderOpacity + onMoved: value => { + valueGroupedBorderOpacity = value; + saveSettings(); + } + text: Math.floor(valueGroupedBorderOpacity * 100) + "%" + visible: valueShowApplications + } + + NValueSlider { + label: I18n.tr("bar.taskbar.icon-scale-label") + description: I18n.tr("bar.taskbar.icon-scale-description") + from: 0.5 + to: 1 + stepSize: 0.01 + showReset: true + value: valueIconScale + defaultValue: widgetMetadata.iconScale + onMoved: value => { + valueIconScale = value; + saveSettings(); + } + text: Math.round(valueIconScale * 100) + "%" + visible: valueShowApplications + } + + NDivider { + Layout.fillWidth: true + } + + NColorChoice { + label: I18n.tr("bar.workspace.focused-color-label") + description: I18n.tr("bar.workspace.focused-color-description") + currentKey: valueFocusedColor + onSelected: key => { + valueFocusedColor = key; + saveSettings(); + } + } + + NColorChoice { + label: I18n.tr("bar.workspace.occupied-color-label") + description: I18n.tr("bar.workspace.occupied-color-description") + currentKey: valueOccupiedColor + onSelected: key => { + valueOccupiedColor = key; + saveSettings(); + } + } + + NColorChoice { + label: I18n.tr("bar.workspace.empty-color-label") + description: I18n.tr("bar.workspace.empty-color-description") + currentKey: valueEmptyColor + onSelected: key => { + valueEmptyColor = key; + saveSettings(); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml new file mode 100644 index 0000000..53aa8b6 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml @@ -0,0 +1,160 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.UI +import qs.Widgets + +// Widget Settings Dialog Component +Popup { + id: root + + property int widgetIndex: -1 + property var widgetData: null + property string widgetId: "" + property string sectionId: "" + property var screen: null + property var settingsCache: ({}) + + signal updateWidgetSettings(string section, int index, var settings) + + readonly property real maxHeight: (screen ? screen.height : (parent ? parent.height : 800)) * 0.8 + readonly property real defaultContentWidth: Math.round(600 * Style.uiScaleRatio) + readonly property real settingsContentWidth: { + if (settingsLoader.item && settingsLoader.item.implicitWidth > 0) { + return settingsLoader.item.implicitWidth; + } + return defaultContentWidth; + } + + readonly property real dialogPadding: Style.marginXL + width: Math.max(settingsContentWidth + dialogPadding * 2, 500) + height: Math.min(content.implicitHeight + dialogPadding * 2, maxHeight) + padding: 0 + modal: true + closePolicy: Popup.NoAutoClose + anchors.centerIn: parent + + onOpened: { + if (widgetData && widgetId) { + loadWidgetSettings(); + } + } + + background: Rectangle { + color: Color.mSurface + radius: Style.radiusL + border.color: Color.mPrimary + border.width: Style.borderM + } + + contentItem: ColumnLayout { + id: content + anchors.fill: parent + anchors.margins: dialogPadding + spacing: Style.marginM + + // Title + RowLayout { + id: titleRow + Layout.fillWidth: true + Layout.preferredHeight: implicitHeight + + NText { + text: I18n.tr("system.widget-settings-title", { + "widget": root.widgetId + }) + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mPrimary + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + onClicked: saveAndClose() + } + } + + // Separator + Rectangle { + id: separator + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + } + + // Scrollable settings area + NScrollView { + id: scrollView + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: 100 + gradientColor: Color.mSurface + reserveScrollbarSpace: false + + ColumnLayout { + width: scrollView.availableWidth + spacing: Style.marginM + + Loader { + id: settingsLoader + Layout.fillWidth: true + onItemChanged: { + if (item) { + // Force width recalculation when content loads + Qt.callLater(() => { + root.width = Math.max(root.settingsContentWidth + root.dialogPadding * 2, 500); + }); + } + } + } + } + } + } + + Timer { + id: saveTimer + running: false + interval: 150 + onTriggered: { + root.updateWidgetSettings(root.sectionId, root.widgetIndex, root.settingsCache); + } + } + + Connections { + target: settingsLoader.item + ignoreUnknownSignals: true + function onSettingsChanged(newSettings) { + if (newSettings) { + root.settingsCache = newSettings; + saveTimer.start(); + } + } + } + + function saveAndClose() { + if (settingsLoader.item && typeof settingsLoader.item.saveSettings === 'function') { + var newSettings = settingsLoader.item.saveSettings(); + if (newSettings) { + root.updateWidgetSettings(root.sectionId, root.widgetIndex, newSettings); + } + } + root.close(); + } + + function loadWidgetSettings() { + const widgetSettingsMap = { + "CustomButton": "WidgetSettings/CustomButtonSettings.qml" + }; + + const source = widgetSettingsMap[widgetId]; + if (source) { + settingsLoader.setSource(source, { + "widgetData": widgetData, + "widgetMetadata": ControlCenterWidgetRegistry.widgetMetadata[widgetId] + }); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/ControlCenter/WidgetSettings/CustomButtonSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/ControlCenter/WidgetSettings/CustomButtonSettings.qml new file mode 100644 index 0000000..d27e285 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/ControlCenter/WidgetSettings/CustomButtonSettings.qml @@ -0,0 +1,303 @@ +import QtQml.Models +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + Layout.preferredWidth: Math.round(600 * Style.uiScaleRatio) + implicitWidth: Layout.preferredWidth + + property var widgetData: null + property var widgetMetadata: null + property var rootSettings: null + + signal settingsChanged(var settings) + + QtObject { + id: _settings + + property string icon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon + property string onClicked: widgetData.onClicked !== undefined ? widgetData.onClicked : widgetMetadata.onClicked + property string onRightClicked: widgetData.onRightClicked !== undefined ? widgetData.onRightClicked : widgetMetadata.onRightClicked + property string onMiddleClicked: widgetData.onMiddleClicked !== undefined ? widgetData.onMiddleClicked : widgetMetadata.onMiddleClicked + property ListModel _stateChecksListModel: ListModel {} + property string stateChecksJson: "[]" + property string generalTooltipText: widgetData.generalTooltipText !== undefined ? widgetData.generalTooltipText : widgetMetadata.generalTooltipText + property bool enableOnStateLogic: widgetData.enableOnStateLogic !== undefined ? widgetData.enableOnStateLogic : widgetMetadata.enableOnStateLogic + property bool showExecTooltip: widgetData.showExecTooltip !== undefined ? widgetData.showExecTooltip : widgetMetadata.showExecTooltip + + function populateStateChecks() { + try { + var initialChecks = JSON.parse(stateChecksJson); + if (initialChecks && Array.isArray(initialChecks)) { + for (var i = 0; i < initialChecks.length; i++) { + var item = initialChecks[i]; + if (item && typeof item === "object") { + _settings._stateChecksListModel.append({ + "command": item.command || "", + "icon": item.icon || "" + }); + } else { + Logger.w("CustomButtonSettings", "Invalid stateChecks entry at index " + i + ":", item); + } + } + } + } catch (e) { + Logger.e("CustomButtonSettings", "Failed to parse stateChecksJson:", e.message); + } + } + + Component.onCompleted: { + root.rootSettings = _settings; + stateChecksJson = (widgetData && widgetData.stateChecksJson !== undefined) ? widgetData.stateChecksJson : (widgetMetadata && widgetMetadata.stateChecksJson ? widgetMetadata.stateChecksJson : "[]"); + Qt.callLater(populateStateChecks); + } + + function saveSettings() { + var savedStateChecksArray = []; + for (var i = 0; i < _settings._stateChecksListModel.count; i++) { + savedStateChecksArray.push(_settings._stateChecksListModel.get(i)); + } + _settings.stateChecksJson = JSON.stringify(savedStateChecksArray); + + return { + "id": widgetData.id, + "icon": _settings.icon, + "onClicked": _settings.onClicked, + "onRightClicked": _settings.onRightClicked, + "onMiddleClicked": _settings.onMiddleClicked, + "stateChecksJson": _settings.stateChecksJson, + "generalTooltipText": _settings.generalTooltipText, + "enableOnStateLogic": _settings.enableOnStateLogic, + "showExecTooltip": _settings.showExecTooltip + }; + } + } + + function saveSettings() { + return _settings.saveSettings(); + } + + function updateStateCheck(index, command, icon) { + _settings._stateChecksListModel.set(index, { + "command": command, + "icon": icon + }); + _settings.saveSettings(); + } + + function removeStateCheck(index) { + _settings._stateChecksListModel.remove(index); + _settings.saveSettings(); + } + + function addStateCheck() { + _settings._stateChecksListModel.append({ + "command": "", + "icon": "" + }); + _settings.saveSettings(); + } + + RowLayout { + spacing: Style?.marginM ?? 8 + + NLabel { + label: I18n.tr("common.icon") + description: I18n.tr("panels.control-center.shortcuts-custom-button-icon-description") + } + + NIcon { + Layout.alignment: Qt.AlignVCenter + icon: _settings.icon || (widgetMetadata && widgetMetadata.icon ? widgetMetadata.icon : "") + pointSize: Style?.fontSizeXL ?? 24 + visible: (_settings.icon || (widgetMetadata && widgetMetadata.icon ? widgetMetadata.icon : "")) !== "" + } + + NButton { + text: I18n.tr("common.browse") + onClicked: iconPicker.open() + } + } + + NIconPicker { + id: iconPicker + initialIcon: _settings.icon + onIconSelected: function (iconName) { + _settings.icon = iconName; + saveSettings(); + } + } + + NTextInput { + Layout.fillWidth: true + label: I18n.tr("bar.custom-button.general-tooltip-text-label") + description: I18n.tr("bar.custom-button.general-tooltip-text-description") + placeholderText: I18n.tr("placeholders.enter-tooltip") + text: _settings.generalTooltipText + onTextChanged: { + _settings.generalTooltipText = text; + saveSettings(); + } + defaultValue: widgetMetadata.generalTooltipText + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.custom-button.show-exec-tooltip-label") + description: I18n.tr("bar.custom-button.show-exec-tooltip-description") + checked: _settings.showExecTooltip + onToggled: checked => { + _settings.showExecTooltip = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showExecTooltip + } + + NTextInput { + Layout.fillWidth: true + label: I18n.tr("bar.custom-button.left-click-label") + description: I18n.tr("bar.custom-button.left-click-description") + placeholderText: I18n.tr("placeholders.enter-command") + text: _settings.onClicked + onTextChanged: { + _settings.onClicked = text; + saveSettings(); + } + defaultValue: widgetMetadata.onClicked + } + + NTextInput { + Layout.fillWidth: true + label: I18n.tr("bar.custom-button.right-click-label") + description: I18n.tr("bar.custom-button.right-click-description") + placeholderText: I18n.tr("placeholders.enter-command") + text: _settings.onRightClicked + onTextChanged: { + _settings.onRightClicked = text; + saveSettings(); + } + defaultValue: widgetMetadata.onRightClicked + } + + NTextInput { + Layout.fillWidth: true + label: I18n.tr("bar.custom-button.middle-click-label") + description: I18n.tr("bar.custom-button.middle-click-description") + placeholderText: I18n.tr("placeholders.enter-command") + text: _settings.onMiddleClicked + onTextChanged: { + _settings.onMiddleClicked = text; + saveSettings(); + } + defaultValue: widgetMetadata.onMiddleClicked + } + + NDivider {} + + NToggle { + id: enableOnStateLogicToggle + Layout.fillWidth: true + label: I18n.tr("panels.control-center.shortcuts-custom-button-enable-on-state-logic-label") + description: I18n.tr("panels.control-center.shortcuts-custom-button-enable-on-state-logic-description") + checked: _settings.enableOnStateLogic + onToggled: checked => { + _settings.enableOnStateLogic = checked; + saveSettings(); + } + defaultValue: widgetMetadata.enableOnStateLogic + } + + ColumnLayout { + Layout.fillWidth: true + visible: root.rootSettings && root.rootSettings.enableOnStateLogic + spacing: Style?.marginM ?? 8 + + NLabel { + label: I18n.tr("panels.control-center.shortcuts-custom-button-state-checks-label") + } + + Repeater { + model: root.rootSettings ? root.rootSettings._stateChecksListModel : null + delegate: Item { + property int currentIndex: index + + implicitHeight: contentRow.implicitHeight + Layout.fillWidth: true + + RowLayout { + id: contentRow + anchors.fill: parent + spacing: Style?.marginM ?? 8 + + NTextInput { + Layout.fillWidth: true + placeholderText: I18n.tr("panels.control-center.shortcuts-custom-button-state-checks-command") + text: model.command + onTextChanged: { + updateStateCheck(currentIndex, text, model.icon); + } + } + + RowLayout { + Layout.alignment: Qt.AlignVCenter + spacing: Style?.marginS ?? 4 + + NIcon { + icon: model.icon + pointSize: Style?.fontSizeL ?? 20 + visible: model.icon !== undefined && model.icon !== "" + } + + NIconButton { + icon: "folder" + tooltipText: I18n.tr("common.browse") + baseSize: Style?.buttonSizeS ?? 24 + onClicked: iconPickerDelegate.open() + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("panels.control-center.shortcuts-custom-button-state-checks-remove") + baseSize: Style?.buttonSizeS ?? 24 + colorBorder: Qt.alpha(Color.mOutline, Style.opacityLight) + colorBg: Color.mError + colorFg: Color.mOnError + colorBgHover: Qt.alpha(Color.mError, Style.opacityMedium) + colorFgHover: Color.mOnError + onClicked: { + removeStateCheck(currentIndex); + } + } + } + } + + NIconPicker { + id: iconPickerDelegate + initialIcon: model.icon + onIconSelected: function (iconName) { + updateStateCheck(currentIndex, model.command, iconName); + } + } + } + } + + Item { + Layout.fillWidth: true + } + + RowLayout { + Layout.fillWidth: true + spacing: Style?.marginM ?? 8 + + NButton { + text: I18n.tr("panels.control-center.shortcuts-custom-button-state-checks-add") + onClicked: addStateCheck() + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/DesktopWidgets/DesktopWidgetSettingsDialog.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/DesktopWidgets/DesktopWidgetSettingsDialog.qml new file mode 100644 index 0000000..93c7892 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/DesktopWidgets/DesktopWidgetSettingsDialog.qml @@ -0,0 +1,276 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Noctalia +import qs.Services.UI +import qs.Widgets + +Popup { + id: root + + property int widgetIndex: -1 + property var widgetData: null + property string widgetId: "" + property string sectionId: "" // Not used for desktop widgets, but required by NSectionEditor + property var screen: null + property var settingsCache: ({}) + + readonly property real maxHeight: (screen ? screen.height : (parent ? parent.height : 800)) * 0.8 + + signal updateWidgetSettings(string section, int index, var settings) + + width: Math.max(content.implicitWidth + padding * 2, 500) + height: Math.min(content.implicitHeight + padding * 2, maxHeight) + padding: Style.marginXL + modal: true + closePolicy: Popup.NoAutoClose + dim: false + + // Center in parent + x: Math.round((parent.width - width) / 2) + y: Math.round((parent.height - height) / 2) + + onOpened: { + if (widgetData && widgetId) { + loadWidgetSettings(); + } + forceActiveFocus(); + } + + background: Rectangle { + id: bgRect + color: Color.mSurface + radius: Style.radiusL + border.color: Color.mPrimary + border.width: Style.borderM + } + + contentItem: FocusScope { + id: focusScope + focus: true + + ColumnLayout { + id: content + anchors.fill: parent + spacing: Style.marginM + + RowLayout { + id: titleRow + Layout.fillWidth: true + Layout.preferredHeight: implicitHeight + + NText { + text: I18n.tr("system.widget-settings-title", { + "widget": DesktopWidgetRegistry.getWidgetDisplayName(root.widgetId) + }) + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mPrimary + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + onClicked: saveAndClose() + } + } + + Rectangle { + id: separator + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + } + + // Scrollable settings area + NScrollView { + id: scrollView + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: 100 + gradientColor: Color.mSurface + + ColumnLayout { + width: scrollView.availableWidth + spacing: Style.marginM + + Loader { + id: settingsLoader + Layout.fillWidth: true + onLoaded: { + if (item) { + Qt.callLater(() => { + var firstInput = findFirstFocusable(item); + if (firstInput) { + firstInput.forceActiveFocus(); + } else { + focusScope.forceActiveFocus(); + } + }); + } + } + + function findFirstFocusable(item) { + if (!item) + return null; + if (item.focus !== undefined && item.focus === true) + return item; + if (item.children) { + for (var i = 0; i < item.children.length; i++) { + var child = item.children[i]; + if (child && child.focus !== undefined && child.focus === true) + return child; + var found = findFirstFocusable(child); + if (found) + return found; + } + } + return null; + } + } + } + } + } + } + + // Mouse area for a draggable popup + MouseArea { + x: titleRow.x + y: titleRow.y + width: titleRow.width + height: titleRow.height + z: -1 + + cursorShape: Qt.OpenHandCursor + + property real pressX: 0 + property real pressY: 0 + + onPressed: mouse => { + pressX = mouse.x; + pressY = mouse.y; + cursorShape = Qt.ClosedHandCursor; + } + + onReleased: { + cursorShape = Qt.OpenHandCursor; + } + + onPositionChanged: mouse => { + if (pressed) { + var deltaX = mouse.x - pressX; + var deltaY = mouse.y - pressY; + + root.x += deltaX; + root.y += deltaY; + } + } + } + + Timer { + id: saveTimer + running: false + interval: 150 + onTriggered: { + root.updateWidgetSettings(root.sectionId, root.widgetIndex, root.settingsCache); + } + } + + Connections { + target: settingsLoader.item + ignoreUnknownSignals: true + function onSettingsChanged(newSettings) { + if (newSettings) { + root.settingsCache = newSettings; + saveTimer.start(); + } + } + + function onSettingsSaved(newSettings) { + if (newSettings) { + root.updateWidgetSettings(root.sectionId, root.widgetIndex, newSettings); + } + } + } + + function saveAndClose() { + if (settingsLoader.item && typeof settingsLoader.item.saveSettings === 'function') { + var newSettings = settingsLoader.item.saveSettings(); + if (newSettings) { + root.updateWidgetSettings(root.sectionId, root.widgetIndex, newSettings); + } + } + root.close(); + } + + function loadWidgetSettings() { + // Handle plugin widgets + if (DesktopWidgetRegistry.isPluginWidget(widgetId)) { + var pluginId = widgetId.replace("plugin:", ""); + var manifest = PluginRegistry.getPluginManifest(pluginId); + + var pluginDir = PluginRegistry.getPluginDir(pluginId); + var loadVersion = PluginRegistry.pluginLoadVersions[pluginId] || 0; + var api = PluginService.getPluginAPI(pluginId); + + var settingsPath; + if (manifest && manifest.entryPoints && manifest.entryPoints.desktopWidgetSettings) { + settingsPath = "file://" + pluginDir + "/" + manifest.entryPoints.desktopWidgetSettings; + + var widgetSettings = {}; + widgetSettings.data = widgetData || {}; + widgetSettings.metadata = DesktopWidgetRegistry.widgetMetadata[widgetId] || {}; + widgetSettings.save = function () { + var newSettings = Object.assign({}, widgetSettings.data); + root.settingsCache = newSettings; + saveTimer.start(); + }; + + settingsLoader.setSource(settingsPath + "?v=" + loadVersion, { + "pluginApi": api, + "widgetSettings": widgetSettings + }); + } else { + Logger.w("DesktopWidgetSettingsDialog", "Plugin does not have desktop widget settings:", pluginId); + + // Fallback to the plugin settings + if (manifest && manifest.entryPoints && manifest.entryPoints.settings) { + settingsPath = "file://" + pluginDir + "/" + manifest.entryPoints.settings; + + settingsLoader.setSource(settingsPath + "?v=" + loadVersion, { + "pluginApi": api + }); + } else { + Logger.w("DesktopWidgetSettingsDialog", "Plugin does not have settings:", pluginId); + } + } + + return; + } + + // Handle core widgets + const source = DesktopWidgetRegistry.widgetSettingsMap[widgetId]; + if (source) { + var currentWidgetData = widgetData; + var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || []; + for (var i = 0; i < monitorWidgets.length; i++) { + if (monitorWidgets[i].name === sectionId) { + var widgets = monitorWidgets[i].widgets || []; + if (widgetIndex >= 0 && widgetIndex < widgets.length) { + currentWidgetData = widgets[widgetIndex]; + } + break; + } + } + var fullPath = Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/DesktopWidgets/" + source); + settingsLoader.setSource(fullPath, { + "widgetData": currentWidgetData, + "widgetMetadata": DesktopWidgetRegistry.widgetMetadata[widgetId] + }); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/AudioVisualizerSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/AudioVisualizerSettings.qml new file mode 100644 index 0000000..ea65462 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/AudioVisualizerSettings.qml @@ -0,0 +1,154 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + property int valueWidth: widgetData.width !== undefined ? widgetData.width : widgetMetadata.width + property int valueHeight: widgetData.height !== undefined ? widgetData.height : widgetMetadata.height + property string valueVisualizerType: widgetData.visualizerType !== undefined ? widgetData.visualizerType : widgetMetadata.visualizerType + property string valueColorName: widgetData.colorName !== undefined ? widgetData.colorName : widgetMetadata.colorName + property bool valueHideWhenIdle: widgetData.hideWhenIdle !== undefined ? widgetData.hideWhenIdle : widgetMetadata.hideWhenIdle + property bool valueShowBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : widgetMetadata.showBackground + property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : widgetMetadata.roundedCorners + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.width = valueWidth; + settings.height = valueHeight; + settings.visualizerType = valueVisualizerType; + settings.colorName = valueColorName; + settings.hideWhenIdle = valueHideWhenIdle; + settings.showBackground = valueShowBackground; + settings.roundedCorners = valueRoundedCorners; + settingsChanged(settings); + } + + NTextInput { + id: widthInput + Layout.fillWidth: true + label: I18n.tr("common.width") + description: I18n.tr("bar.audio-visualizer.width-description") + text: String(valueWidth) + placeholderText: I18n.tr("placeholders.enter-width-pixels") + inputMethodHints: Qt.ImhDigitsOnly + onEditingFinished: { + const parsed = parseInt(text); + if (!isNaN(parsed) && parsed > 0) { + valueWidth = parsed; + saveSettings(); + } else { + text = String(valueWidth); + } + } + defaultValue: String(widgetMetadata.width) + } + + NTextInput { + id: heightInput + Layout.fillWidth: true + label: I18n.tr("common.height") + description: I18n.tr("bar.audio-visualizer.height-description") + text: String(valueHeight) + placeholderText: I18n.tr("placeholders.enter-width-pixels") + inputMethodHints: Qt.ImhDigitsOnly + onEditingFinished: { + const parsed = parseInt(text); + if (!isNaN(parsed) && parsed > 0) { + valueHeight = parsed; + saveSettings(); + } else { + text = String(valueHeight); + } + } + defaultValue: String(widgetMetadata.height) + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.audio.visualizer-type-label") + description: I18n.tr("panels.desktop-widgets.media-player-visualizer-type-description") + model: [ + { + "key": "linear", + "name": I18n.tr("options.visualizer-types.linear") + }, + { + "key": "mirrored", + "name": I18n.tr("options.visualizer-types.mirrored") + }, + { + "key": "wave", + "name": I18n.tr("options.visualizer-types.wave") + } + ] + currentKey: valueVisualizerType + onSelected: key => { + valueVisualizerType = key; + saveSettings(); + } + defaultValue: widgetMetadata.visualizerType + } + + NColorChoice { + Layout.fillWidth: true + label: I18n.tr("bar.audio-visualizer.color-name-label") + description: I18n.tr("bar.audio-visualizer.color-name-description") + currentKey: valueColorName + onSelected: key => { + valueColorName = key; + saveSettings(); + } + defaultValue: widgetMetadata.colorName + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.audio-visualizer.hide-when-idle-label") + description: I18n.tr("bar.audio-visualizer.hide-when-idle-description") + checked: valueHideWhenIdle + onToggled: checked => { + valueHideWhenIdle = checked; + saveSettings(); + } + defaultValue: widgetMetadata.hideWhenIdle + } + + NDivider { + Layout.fillWidth: true + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.desktop-widgets.clock-show-background-label") + description: I18n.tr("panels.desktop-widgets.media-player-show-background-description") + checked: valueShowBackground + onToggled: checked => { + valueShowBackground = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showBackground + } + + NToggle { + Layout.fillWidth: true + visible: valueShowBackground + label: I18n.tr("panels.desktop-widgets.clock-rounded-corners-label") + description: I18n.tr("panels.desktop-widgets.media-player-rounded-corners-description") + checked: valueRoundedCorners + onToggled: checked => { + valueRoundedCorners = checked; + saveSettings(); + } + defaultValue: widgetMetadata.roundedCorners + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/ClockSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/ClockSettings.qml new file mode 100644 index 0000000..f86b1a1 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/ClockSettings.qml @@ -0,0 +1,293 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.System +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + width: 700 + + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + property bool valueShowBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : widgetMetadata.showBackground + property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : widgetMetadata.roundedCorners + property string valueClockStyle: widgetData.clockStyle !== undefined ? widgetData.clockStyle : widgetMetadata.clockStyle + property string valueClockColor: widgetData.clockColor !== undefined ? widgetData.clockColor : widgetMetadata.clockColor + property bool valueUseCustomFont: widgetData.useCustomFont !== undefined ? widgetData.useCustomFont : widgetMetadata.useCustomFont + property string valueCustomFont: widgetData.customFont !== undefined ? widgetData.customFont : widgetMetadata.custonFont + property string valueFormat: widgetData.format !== undefined ? widgetData.format : widgetMetadata.format + + // Track the currently focused input field + property var focusedInput: null + + readonly property bool isMinimalMode: valueClockStyle === "minimal" + readonly property var now: Time.now + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.showBackground = valueShowBackground; + settings.roundedCorners = valueRoundedCorners; + settings.clockStyle = valueClockStyle; + settings.clockColor = valueClockColor; + settings.useCustomFont = valueUseCustomFont; + settings.customFont = valueCustomFont; + settings.format = valueFormat.trim(); + settingsChanged(settings); + } + + // Function to insert token at cursor position in the focused input + function insertToken(token) { + if (!focusedInput || !focusedInput.inputItem) { + // If no input is focused, default to format input + if (formatInput.inputItem) { + formatInput.inputItem.focus = true; + focusedInput = formatInput; + } + } + + if (focusedInput && focusedInput.inputItem) { + var input = focusedInput.inputItem; + var cursorPos = input.cursorPosition; + var currentText = input.text; + + // Insert token at cursor position + var newText = currentText.substring(0, cursorPos) + token + currentText.substring(cursorPos); + input.text = newText + " "; + + // Move cursor after the inserted token + input.cursorPosition = cursorPos + token.length + 1; + + // Ensure the input keeps focus + input.focus = true; + saveSettings(); + } + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.desktop-widgets.clock-style-label") + description: I18n.tr("panels.desktop-widgets.clock-style-description") + currentKey: valueClockStyle + minimumWidth: 260 * Style.uiScaleRatio + model: [ + { + "key": "minimal", + "name": I18n.tr("panels.desktop-widgets.clock-style-minimal") + }, + { + "key": "digital", + "name": I18n.tr("panels.desktop-widgets.clock-style-digital") + }, + { + "key": "analog", + "name": I18n.tr("panels.desktop-widgets.clock-style-analog") + }, + { + "key": "binary", + "name": I18n.tr("panels.desktop-widgets.clock-style-binary") + } + ] + onSelected: key => { + valueClockStyle = key; + saveSettings(); + } + defaultValue: widgetMetadata.clockStyle + } + + NColorChoice { + label: I18n.tr("common.select-color") + description: I18n.tr("common.select-color-description") + currentKey: valueClockColor + onSelected: key => { + valueClockColor = key; + saveSettings(); + } + defaultValue: widgetMetadata.clockColor + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.clock.use-custom-font-label") + description: I18n.tr("bar.clock.use-custom-font-description") + checked: valueUseCustomFont + onToggled: checked => { + valueUseCustomFont = checked; + saveSettings(); + } + defaultValue: widgetMetadata.useCustomFont + } + + NSearchableComboBox { + Layout.fillWidth: true + visible: valueUseCustomFont + label: I18n.tr("bar.clock.custom-font-label") + description: I18n.tr("bar.clock.custom-font-description") + model: FontService.availableFonts + currentKey: valueCustomFont + placeholder: I18n.tr("bar.clock.custom-font-placeholder") + searchPlaceholder: I18n.tr("bar.clock.custom-font-search-placeholder") + popupHeight: 420 + minimumWidth: 300 + onSelected: function (key) { + valueCustomFont = key; + saveSettings(); + } + enabled: valueClockStyle === "minimal" + defaultValue: Settings.data.ui.fontDefault + } + + NDivider { + Layout.fillWidth: true + visible: isMinimalMode + } + + NHeader { + visible: isMinimalMode + label: I18n.tr("bar.clock.clock-display-label") + description: I18n.tr("bar.clock.clock-display-description") + } + + // Format editor - only visible in minimal mode + RowLayout { + id: main + visible: isMinimalMode + spacing: Style.marginL + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + + ColumnLayout { + spacing: Style.marginM + Layout.fillWidth: true + Layout.preferredWidth: 1 + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + + NTextInput { + id: formatInput + Layout.fillWidth: true + label: I18n.tr("panels.desktop-widgets.clock-format-label") + description: I18n.tr("bar.clock.horizontal-bar-description") + placeholderText: "HH:mm\\nd MMMM yyyy" + text: valueFormat + onTextChanged: { + valueFormat = text; + settingsChanged(saveSettings()); + } + Component.onCompleted: { + if (inputItem) { + inputItem.onActiveFocusChanged.connect(function () { + if (inputItem.activeFocus) { + root.focusedInput = formatInput; + } + }); + } + } + defaultValue: widgetMetadata.format + } + } + + // Preview + ColumnLayout { + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + Layout.fillWidth: false + + NLabel { + label: I18n.tr("bar.clock.preview") + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + } + + Rectangle { + Layout.preferredWidth: 320 + Layout.preferredHeight: 160 + color: Color.mSurfaceVariant + radius: Style.radiusM + border.color: Color.mSecondary + border.width: Style.borderS + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + + ColumnLayout { + spacing: Style.marginM + anchors.centerIn: parent + + ColumnLayout { + spacing: -2 + Layout.alignment: Qt.AlignHCenter + + Repeater { + Layout.topMargin: Style.marginM + model: I18n.locale.toString(now, valueFormat.trim()).split("\\n") + delegate: NText { + visible: text !== "" + text: modelData + family: valueUseCustomFont && valueCustomFont ? valueCustomFont : Settings.data.ui.fontDefault + pointSize: Style.fontSizeM + font.weight: Style.fontWeightBold + color: Color.resolveColorKey(valueClockColor) + wrapMode: Text.WordWrap + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + } + } + } + } + } + } + + NDivider { + Layout.topMargin: Style.marginM + Layout.bottomMargin: Style.marginM + visible: isMinimalMode + } + + NDateTimeTokens { + Layout.fillWidth: true + height: 200 + visible: isMinimalMode + onTokenClicked: token => root.insertToken(token) + } + + NDivider { + Layout.fillWidth: true + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.desktop-widgets.clock-show-background-label") + description: I18n.tr("panels.desktop-widgets.clock-show-background-description") + checked: valueShowBackground + onToggled: checked => { + valueShowBackground = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showBackground + } + + NToggle { + Layout.fillWidth: true + visible: valueShowBackground + label: I18n.tr("panels.desktop-widgets.clock-rounded-corners-label") + description: I18n.tr("panels.desktop-widgets.clock-rounded-corners-description") + checked: valueRoundedCorners + onToggled: checked => { + valueRoundedCorners = checked; + saveSettings(); + } + defaultValue: widgetMetadata.roundedCorners + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/MediaPlayerSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/MediaPlayerSettings.qml new file mode 100644 index 0000000..9bd9a6c --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/MediaPlayerSettings.qml @@ -0,0 +1,148 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + property bool valueShowBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : widgetMetadata.showBackground + property string valueVisualizerType: widgetData.visualizerType ? widgetData.visualizerType : widgetMetadata.visualizerType + property string valueHideMode: widgetData.hideMode !== undefined ? widgetData.hideMode : widgetMetadata.hideMode + property bool valueShowButtons: widgetData.showButtons !== undefined ? widgetData.showButtons : widgetMetadata.showButtons + property bool valueShowAlbumArt: widgetData.showAlbumArt !== undefined ? widgetData.showAlbumArt : widgetMetadata.showAlbumArt + property bool valueShowVisualizer: widgetData.showVisualizer !== undefined ? widgetData.showVisualizer : widgetMetadata.showVisualizer + property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : widgetMetadata.roundedCorners + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.showBackground = valueShowBackground; + settings.visualizerType = valueVisualizerType; + settings.hideMode = valueHideMode; + settings.showButtons = valueShowButtons; + settings.showAlbumArt = valueShowAlbumArt; + settings.showVisualizer = valueShowVisualizer; + settings.roundedCorners = valueRoundedCorners; + settingsChanged(settings); + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.desktop-widgets.clock-show-background-label") + description: I18n.tr("panels.desktop-widgets.media-player-show-background-description") + checked: valueShowBackground + onToggled: checked => { + valueShowBackground = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showBackground + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.desktop-widgets.clock-rounded-corners-label") + description: I18n.tr("panels.desktop-widgets.media-player-rounded-corners-description") + checked: valueRoundedCorners + onToggled: checked => { + valueRoundedCorners = checked; + saveSettings(); + } + defaultValue: widgetMetadata.roundedCorners + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.desktop-widgets.media-player-show-album-art-label") + description: I18n.tr("panels.desktop-widgets.media-player-show-album-art-description") + checked: valueShowAlbumArt + onToggled: checked => { + valueShowAlbumArt = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showAlbumArt + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.media-mini.show-visualizer-label") + description: I18n.tr("panels.desktop-widgets.media-player-show-visualizer-description") + checked: valueShowVisualizer + onToggled: checked => { + valueShowVisualizer = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showVisualizer + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.desktop-widgets.media-player-show-buttons-label") + description: I18n.tr("panels.desktop-widgets.media-player-show-buttons-description") + checked: valueShowButtons + onToggled: checked => { + valueShowButtons = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showButtons + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.audio.visualizer-type-label") + description: I18n.tr("panels.desktop-widgets.media-player-visualizer-type-description") + enabled: valueShowVisualizer + model: [ + { + "key": "linear", + "name": I18n.tr("options.visualizer-types.linear") + }, + { + "key": "mirrored", + "name": I18n.tr("options.visualizer-types.mirrored") + }, + { + "key": "wave", + "name": I18n.tr("options.visualizer-types.wave") + } + ] + currentKey: valueVisualizerType + onSelected: key => { + valueVisualizerType = key; + saveSettings(); + } + defaultValue: widgetMetadata.visualizerType + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("bar.taskbar.hide-mode-label") + description: I18n.tr("bar.media-mini.hide-mode-description") + model: [ + { + "key": "hidden", + "name": I18n.tr("hide-modes.hidden") + }, + { + "key": "idle", + "name": I18n.tr("hide-modes.idle") + }, + { + "key": "visible", + "name": I18n.tr("hide-modes.visible") + } + ] + currentKey: valueHideMode + onSelected: key => { + valueHideMode = key; + saveSettings(); + } + defaultValue: widgetMetadata.hideMode + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/SystemStatSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/SystemStatSettings.qml new file mode 100644 index 0000000..fe7e135 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/SystemStatSettings.qml @@ -0,0 +1,148 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.System +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + width: 700 + + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + property string valueStatType: widgetData.statType !== undefined ? widgetData.statType : widgetMetadata.statType + property string valueDiskPath: widgetData.diskPath !== undefined ? widgetData.diskPath : widgetMetadata.diskPath + property bool valueShowBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : widgetMetadata.showBackground + property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : widgetMetadata.roundedCorners + property string valueLayout: widgetData.layout !== undefined ? widgetData.layout : widgetMetadata.layout + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.statType = valueStatType; + settings.diskPath = valueDiskPath; + settings.showBackground = valueShowBackground; + settings.roundedCorners = valueRoundedCorners; + settings.layout = valueLayout; + settingsChanged(settings); + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.desktop-widgets.system-stat-stat-type-label") + description: I18n.tr("panels.desktop-widgets.system-stat-stat-type-description") + currentKey: valueStatType + minimumWidth: 260 * Style.uiScaleRatio + model: { + let items = [ + { + "key": "CPU", + "name": I18n.tr("system-monitor.cpu-usage") + }, + { + "key": "Memory", + "name": I18n.tr("common.memory") + }, + { + "key": "Network", + "name": I18n.tr("bar.system-monitor.network-traffic-label") + }, + { + "key": "Disk", + "name": I18n.tr("system-monitor.disk") + } + ]; + if (Settings.data.systemMonitor.enableDgpuMonitoring) + items.push({ + "key": "GPU", + "name": I18n.tr("panels.system-monitor.gpu-section-label") + }); + return items; + } + onSelected: key => { + valueStatType = key; + saveSettings(); + } + defaultValue: widgetMetadata.statType + } + + NComboBox { + Layout.fillWidth: true + visible: valueStatType === "Disk" + label: I18n.tr("bar.system-monitor.disk-path-label") + description: I18n.tr("bar.system-monitor.disk-path-description") + model: { + const paths = Object.keys(SystemStatService.diskPercents).sort(); + return paths.map(path => ({ + key: path, + name: path + })); + } + currentKey: valueDiskPath + onSelected: key => { + valueDiskPath = key; + saveSettings(); + } + defaultValue: widgetMetadata.diskPath + } + + NDivider { + Layout.fillWidth: true + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.desktop-widgets.system-stat-show-background-label") + description: I18n.tr("panels.desktop-widgets.system-stat-show-background-description") + checked: valueShowBackground + onToggled: checked => { + valueShowBackground = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showBackground + } + + NToggle { + Layout.fillWidth: true + visible: valueShowBackground + label: I18n.tr("panels.desktop-widgets.system-stat-rounded-corners-label") + description: I18n.tr("panels.desktop-widgets.system-stat-rounded-corners-description") + checked: valueRoundedCorners + onToggled: checked => { + valueRoundedCorners = checked; + saveSettings(); + } + defaultValue: widgetMetadata.roundedCorners + } + + NDivider { + Layout.fillWidth: true + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.desktop-widgets.system-stat-layout-label") + description: I18n.tr("panels.desktop-widgets.system-stat-layout-description") + currentKey: valueLayout + minimumWidth: 260 * Style.uiScaleRatio + model: [ + { + "key": "side", + "name": I18n.tr("panels.desktop-widgets.system-stat-layout-side") + }, + { + "key": "bottom", + "name": I18n.tr("panels.desktop-widgets.system-stat-layout-bottom") + } + ] + onSelected: key => { + valueLayout = key; + saveSettings(); + } + defaultValue: widgetMetadata.layout + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/WeatherSettings.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/WeatherSettings.qml new file mode 100644 index 0000000..4406921 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/WeatherSettings.qml @@ -0,0 +1,50 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + property bool valueShowBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : widgetMetadata.showBackground + property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : widgetMetadata.roundedCorners + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.showBackground = valueShowBackground; + settings.roundedCorners = valueRoundedCorners; + settingsChanged(settings); + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.desktop-widgets.clock-show-background-label") + description: I18n.tr("panels.desktop-widgets.weather-show-background-description") + checked: valueShowBackground + onToggled: checked => { + valueShowBackground = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showBackground + } + + NToggle { + Layout.fillWidth: true + visible: valueShowBackground + label: I18n.tr("panels.desktop-widgets.clock-rounded-corners-label") + description: I18n.tr("panels.desktop-widgets.clock-rounded-corners-description") + checked: valueRoundedCorners + onToggled: checked => { + valueRoundedCorners = checked; + saveSettings(); + } + defaultValue: widgetMetadata.roundedCorners + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/SettingsContent.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/SettingsContent.qml new file mode 100644 index 0000000..61e023a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/SettingsContent.qml @@ -0,0 +1,1331 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.Panels.Settings.Tabs +import qs.Modules.Panels.Settings.Tabs.About +import qs.Modules.Panels.Settings.Tabs.Audio +import qs.Modules.Panels.Settings.Tabs.Bar +import qs.Modules.Panels.Settings.Tabs.ColorScheme +import qs.Modules.Panels.Settings.Tabs.Connections +import qs.Modules.Panels.Settings.Tabs.ControlCenter +import qs.Modules.Panels.Settings.Tabs.Display +import qs.Modules.Panels.Settings.Tabs.Dock +import qs.Modules.Panels.Settings.Tabs.Hooks +import qs.Modules.Panels.Settings.Tabs.Idle +import qs.Modules.Panels.Settings.Tabs.Launcher +import qs.Modules.Panels.Settings.Tabs.LockScreen +import qs.Modules.Panels.Settings.Tabs.Notifications +import qs.Modules.Panels.Settings.Tabs.Osd +import qs.Modules.Panels.Settings.Tabs.Plugins +import qs.Modules.Panels.Settings.Tabs.Region +import qs.Modules.Panels.Settings.Tabs.SessionMenu +import qs.Modules.Panels.Settings.Tabs.SystemMonitor +import qs.Modules.Panels.Settings.Tabs.UserInterface +import qs.Modules.Panels.Settings.Tabs.Wallpaper +import qs.Services.Compositor +import qs.Services.Power +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + Component.onDestruction: SystemStatService.unregisterComponent("settings") + + // Screen reference for child components + property var screen + + // Input: which tab to show initially + property int requestedTab: 0 + + // Exposed state for parent to access + property int currentTabIndex: 0 + property var tabsModel: [] + property var activeScrollView: null + property var activeTabContent: null + property bool sidebarExpanded: true + // Track if sidebar was collapsed before searching started + property bool wasCollapsedBeforeSearch: false + + // Search state + property string searchText: "" + property var searchResults: [] + property int searchSelectedIndex: 0 + property string highlightLabelKey: "" + property bool navigatingFromSearch: false + + // Mouse hover suppression during keyboard navigation + property bool ignoreMouseHover: false + property real _lastMouseX: 0 + property real _lastMouseY: 0 + property bool _mouseInitialized: false + + readonly property bool sidebarCardStyle: Settings.data.ui.settingsPanelSideBarCardStyle + + onSearchResultsChanged: { + searchSelectedIndex = 0; + ignoreMouseHover = true; + _mouseInitialized = false; + } + + // Signal when close button is clicked + signal closeRequested + + // Search function + onSearchTextChanged: { + if (searchText.trim() === "") { + searchResults = []; + if (wasCollapsedBeforeSearch) { + root.sidebarExpanded = false; + wasCollapsedBeforeSearch = false; + } + return; + } + + // Auto-expand sidebar when searching + if (!root.sidebarExpanded) { + if (root.activeFocus) { + // If we are typing and the sidebar is collapsed and focused, we assume the user is typing to search + wasCollapsedBeforeSearch = true; + } + root.sidebarExpanded = true; + } + + if (SettingsSearchService.searchIndex.length === 0) + return; + + // Build searchable items with resolved translations, filtering out invisible entries + let items = []; + for (let j = 0; j < SettingsSearchService.searchIndex.length; j++) { + const entry = SettingsSearchService.searchIndex[j]; + if (!SettingsSearchService.isEntryVisible(entry)) + continue; + items.push({ + "labelKey": entry.labelKey, + "descriptionKey": entry.descriptionKey, + "widget": entry.widget, + "tab": entry.tab, + "tabLabel": entry.tabLabel, + "subTab": entry.subTab, + "subTabLabel": entry.subTabLabel || null, + "label": I18n.tr(entry.labelKey), + "description": entry.descriptionKey ? I18n.tr(entry.descriptionKey) : "", + "subTabName": entry.subTabLabel ? I18n.tr(entry.subTabLabel) : "" + }); + } + + const results = FuzzySort.go(searchText.trim(), items, { + "keys": ["label", "subTabName", "description"], + "limit": 20, + "scoreFn": function (r) { + // r[0]=label, r[1]=subTabName, r[2]=description + // Boost subTabName matches by 1.5x + const labelScore = r[0].score; + const subTabScore = r[1].score * 1.5; + const descScore = r[2].score; + return Math.max(labelScore, subTabScore, descScore); + } + }); + + let extracted = []; + for (let i = 0; i < results.length; i++) { + extracted.push(results[i].obj); + } + searchResults = extracted; + } + + // Navigate to a search result + property int _pendingSubTab: -1 + + function navigateToResult(entry) { + if (entry.tab < 0 || entry.tab >= tabsModel.length) + return; + + highlightLabelKey = entry.labelKey; + _pendingSubTab = (entry.subTab !== null && entry.subTab !== undefined) ? entry.subTab : -1; + + const alreadyOnTab = (currentTabIndex === entry.tab); + navigatingFromSearch = true; + currentTabIndex = entry.tab; + navigatingFromSearch = false; + + if (alreadyOnTab && activeTabContent) { + if (_pendingSubTab >= 0) { + navigatingFromSearch = true; + setSubTabIndex(_pendingSubTab); + navigatingFromSearch = false; + _pendingSubTab = -1; + } + highlightScrollTimer.targetKey = highlightLabelKey; + highlightScrollTimer.restart(); + } + + // Clear highlight after a delay + highlightClearTimer.restart(); + } + + // Navigate to a tab and optionally a subtab (simpler than navigateToResult, no highlighting) + function navigateToTab(tabId, subTabIndex) { + // Find the tab index by tab ID + let tabIndex = -1; + for (let i = 0; i < tabsModel.length; i++) { + if (tabsModel[i].id === tabId) { + tabIndex = i; + break; + } + } + + if (tabIndex < 0) + return; + + const hasSubTab = subTabIndex !== null && subTabIndex !== undefined && subTabIndex >= 0; + _pendingSubTab = hasSubTab ? subTabIndex : -1; + + // Check if we're already on this tab + const alreadyOnTab = (currentTabIndex === tabIndex); + + currentTabIndex = tabIndex; + + if (alreadyOnTab && activeTabContent && hasSubTab) { + // Tab is already loaded, apply subtab directly + setSubTabIndex(subTabIndex); + _pendingSubTab = -1; + } + } + + function searchSelectNext() { + if (searchResults.length === 0) + return; + ignoreMouseHover = true; + _mouseInitialized = false; + searchSelectedIndex = Math.min(searchSelectedIndex + 1, searchResults.length - 1); + searchResultsList.positionViewAtIndex(searchSelectedIndex, ListView.Contain); + } + + function searchSelectPrevious() { + if (searchResults.length === 0) + return; + ignoreMouseHover = true; + _mouseInitialized = false; + searchSelectedIndex = Math.max(searchSelectedIndex - 1, 0); + searchResultsList.positionViewAtIndex(searchSelectedIndex, ListView.Contain); + } + + function searchActivate() { + if (searchSelectedIndex >= 0 && searchSelectedIndex < searchResults.length) { + navigateToResult(searchResults[searchSelectedIndex]); + searchInput.text = ""; + } + } + + // Set sub-tab on the currently loaded tab content. Returns true if an NTabBar was found. + function setSubTabIndex(subTabIndex) { + if (activeTabContent) { + return setSubTabRecursive(activeTabContent, subTabIndex); + } + return false; + } + + function setSubTabRecursive(item, subTabIndex) { + if (!item) + return false; + + if (item.objectName === "NTabBar") { + // Prepare the sibling NTabView so the index change doesn't animate + if (item.parent) { + for (let j = 0; j < item.parent.children.length; j++) { + const sibling = item.parent.children[j]; + if (sibling.objectName === "NTabView" && sibling.setIndexWithoutAnimation) { + sibling.setIndexWithoutAnimation(subTabIndex); + break; + } + } + } + item.currentIndex = subTabIndex; + return true; + } + + const childCount = item.children ? item.children.length : 0; + for (let i = 0; i < childCount; i++) { + if (setSubTabRecursive(item.children[i], subTabIndex)) + return true; + } + return false; + } + + onCurrentTabIndexChanged: { + if (!navigatingFromSearch) { + clearHighlightImmediately(); + } + } + + property var currentSubTabBar: null + + onActiveTabContentChanged: { + if (currentSubTabBar) { + try { + currentSubTabBar.currentIndexChanged.disconnect(onSubTabChanged); + } catch (e) {} + currentSubTabBar = null; + } + + if (activeTabContent) { + const tabBar = findNTabBar(activeTabContent); + if (tabBar) { + currentSubTabBar = tabBar; + currentSubTabBar.currentIndexChanged.connect(onSubTabChanged); + } + } + } + + function onSubTabChanged() { + if (!navigatingFromSearch) { + clearHighlightImmediately(); + } + } + + function findNTabBar(item) { + if (!item) + return null; + + if (item.objectName === "NTabBar") { + return item; + } + + const childCount = item.children ? item.children.length : 0; + for (let i = 0; i < childCount; i++) { + const found = findNTabBar(item.children[i]); + if (found) + return found; + } + return null; + } + + function clearHighlightImmediately() { + highlightClearTimer.stop(); + highlightScrollTimer.stop(); + highlightAnimation.stop(); + highlightLabelKey = ""; + highlightOverlay.opacity = 0; + } + + function isEffectivelyVisible(item) { + var current = item; + while (current) { + if (current.visible === false) + return false; + if (current.opacity !== undefined && current.opacity <= 0) + return false; + current = current.parent; + } + return true; + } + + // Find and highlight a widget by its label key. + function findAndHighlightWidget(item, labelKey) { + if (!item) + return null; + + // Skip hidden branches to avoid highlighting controls that are not on screen. + if (!isEffectivelyVisible(item)) + return null; + + // Check if this item has a matching label. + if (item.hasOwnProperty("label") && item.label === I18n.tr(labelKey) && item.width > 0 && item.height > 0) { + return item; + } + + // Recursively search children + if (item.children) { + for (let i = 0; i < item.children.length; i++) { + const found = findAndHighlightWidget(item.children[i], labelKey); + if (found) + return found; + } + } + return null; + } + + Timer { + id: highlightClearTimer + interval: 3000 + onTriggered: root.highlightLabelKey = "" + } + + Timer { + id: highlightScrollTimer + interval: 333 + property string targetKey: "" + onTriggered: { + if (root.activeTabContent && targetKey) { + const widget = root.findAndHighlightWidget(root.activeTabContent, targetKey); + if (widget && root.activeScrollView) { + // Scroll widget into view using the Flickable directly + const flickable = root.activeScrollView.contentItem; + const mapped = widget.mapToItem(flickable.contentItem, 0, 0); + const targetY = mapped.y - flickable.height / 3; + flickable.contentY = Math.max(0, Math.min(targetY, flickable.contentHeight - flickable.height)); + + // Position highlight overlay after scroll layout has settled + Qt.callLater(function () { + const overlayPos = widget.mapToItem(tabContentArea, 0, 0); + highlightOverlay.x = overlayPos.x - Style.marginM; + highlightOverlay.y = overlayPos.y - Style.marginM; + highlightOverlay.width = widget.width + Style.margin2M; + highlightOverlay.height = widget.height + Style.margin2M; + highlightAnimation.restart(); + }); + } + } + targetKey = ""; + } + } + + // Clear highlight when the user scrolls so the outline doesn't stay in place + Connections { + target: root.activeScrollView ? root.activeScrollView.contentItem : null + enabled: root.highlightLabelKey !== "" && !highlightScrollTimer.running + function onContentYChanged() { + root.clearHighlightImmediately(); + } + } + + // Save sidebar state when it changes + onSidebarExpandedChanged: { + ShellState.setSettingsSidebarExpanded(sidebarExpanded); + if (!sidebarExpanded) { + root.searchText = ""; + searchInput.text = ""; + root.forceActiveFocus(); + } + } + + Component.onCompleted: { + SystemStatService.registerComponent("settings"); + // Restore sidebar state + sidebarExpanded = ShellState.getSettingsSidebarExpanded(); + } + + // Tab components + Component { + id: generalTab + GeneralTab {} + } + Component { + id: launcherTab + LauncherTab {} + } + Component { + id: barTab + BarTab {} + } + Component { + id: audioTab + AudioTab {} + } + Component { + id: displayTab + DisplayTab {} + } + Component { + id: osdTab + OsdTab {} + } + Component { + id: connectionsTab + ConnectionsTab {} + } + Component { + id: regionTab + RegionTab {} + } + Component { + id: colorSchemeTab + ColorSchemeTab {} + } + Component { + id: wallpaperTab + WallpaperTab {} + } + Component { + id: aboutTab + AboutTab {} + } + Component { + id: hooksTab + HooksTab {} + } + Component { + id: idleTab + IdleTab {} + } + Component { + id: dockTab + DockTab {} + } + Component { + id: notificationsTab + NotificationsTab {} + } + Component { + id: controlCenterTab + ControlCenterTab {} + } + Component { + id: userInterfaceTab + UserInterfaceTab {} + } + Component { + id: lockScreenTab + LockScreenTab {} + } + Component { + id: sessionMenuTab + SessionMenuTab {} + } + Component { + id: systemMonitorTab + SystemMonitorTab {} + } + Component { + id: pluginsTab + PluginsTab {} + } + Component { + id: desktopWidgetsTab + DesktopWidgetsTab {} + } + + function updateTabsModel() { + let newTabs = [ + { + "id": SettingsPanel.Tab.General, + "label": "common.general", + "icon": "settings-general", + "source": generalTab + }, + { + "id": SettingsPanel.Tab.UserInterface, + "label": "panels.user-interface.title", + "icon": "settings-user-interface", + "source": userInterfaceTab + }, + { + "id": SettingsPanel.Tab.ColorScheme, + "label": "panels.color-scheme.title", + "icon": "settings-color-scheme", + "source": colorSchemeTab + }, + { + "id": SettingsPanel.Tab.Wallpaper, + "label": "common.wallpaper", + "icon": "settings-wallpaper", + "source": wallpaperTab + }, + { + "id": SettingsPanel.Tab.Bar, + "label": "panels.bar.title", + "icon": "settings-bar", + "source": barTab + }, + { + "id": SettingsPanel.Tab.Dock, + "label": "panels.dock.title", + "icon": "settings-dock", + "source": dockTab + }, + { + "id": SettingsPanel.Tab.DesktopWidgets, + "label": "panels.desktop-widgets.title", + "icon": "clock", + "source": desktopWidgetsTab + }, + { + "id": SettingsPanel.Tab.ControlCenter, + "label": "panels.control-center.title", + "icon": "settings-control-center", + "source": controlCenterTab + }, + { + "id": SettingsPanel.Tab.Launcher, + "label": "panels.launcher.title", + "icon": "settings-launcher", + "source": launcherTab + }, + { + "id": SettingsPanel.Tab.Notifications, + "label": "common.notifications", + "icon": "settings-notifications", + "source": notificationsTab + }, + { + "id": SettingsPanel.Tab.OSD, + "label": "panels.osd.title", + "icon": "settings-osd", + "source": osdTab + }, + { + "id": SettingsPanel.Tab.LockScreen, + "label": "panels.lock-screen.title", + "icon": "settings-lock-screen", + "source": lockScreenTab + }, + { + "id": SettingsPanel.Tab.SessionMenu, + "label": "session-menu.title", + "icon": "settings-session-menu", + "source": sessionMenuTab + }, + { + "id": SettingsPanel.Tab.Idle, + "label": "panels.idle.title", + "icon": "settings-idle", + "source": idleTab + }, + { + "id": SettingsPanel.Tab.Audio, + "label": "panels.audio.title", + "icon": "settings-audio", + "source": audioTab + }, + { + "id": SettingsPanel.Tab.Display, + "label": "panels.display.title", + "icon": "settings-display", + "source": displayTab + }, + { + "id": SettingsPanel.Tab.Connections, + "label": "panels.connections.title", + "icon": "settings-network", + "source": connectionsTab + }, + { + "id": SettingsPanel.Tab.Location, + "label": "panels.region.title", + "icon": "settings-location", + "source": regionTab + }, + { + "id": SettingsPanel.Tab.System, + "label": "panels.system.title", + "icon": "settings-system-monitor", + "source": systemMonitorTab + }, + { + "id": SettingsPanel.Tab.Plugins, + "label": "panels.plugins.title", + "icon": "plugin", + "source": pluginsTab + }, + { + "id": SettingsPanel.Tab.Hooks, + "label": "panels.hooks.title", + "icon": "settings-hooks", + "source": hooksTab + }, + { + "id": SettingsPanel.Tab.About, + "label": "panels.about.title", + "icon": "settings-about", + "source": aboutTab + } + ]; + + root.tabsModel = newTabs; + } + + function selectTabById(tabId) { + for (var i = 0; i < tabsModel.length; i++) { + if (tabsModel[i].id === tabId) { + currentTabIndex = i; + return; + } + } + currentTabIndex = 0; + } + + function initialize() { + ProgramCheckerService.checkAllPrograms(); + // Guard _pendingSubTab during model rebuild: updateTabsModel() triggers + // a ListView model reset which can set currentTabIndex=0 via the sidebar + // sync handler, causing the wrong tab to load and consume _pendingSubTab. + const savedPendingSubTab = _pendingSubTab; + _pendingSubTab = -1; + updateTabsModel(); + _pendingSubTab = savedPendingSubTab; + selectTabById(requestedTab); + // Skip auto-focus on Nvidia GPUs - cursor blink causes UI choppiness + const isNvidia = SystemStatService.gpuType === "nvidia"; + if (sidebarExpanded && !isNvidia) { + Qt.callLater(() => { + if (searchInput.inputItem) + searchInput.inputItem.forceActiveFocus(); + }); + } else { + // Ensure root has focus so it can catch typing + Qt.callLater(() => root.forceActiveFocus()); + } + } + + // Handle typing when sidebar is collapsed + focus: true + Keys.onPressed: event => { + if (!sidebarExpanded && event.text.length > 0 && event.text.trim() !== "") { + // Only capture if it looks like visible text + if (event.modifiers & (Qt.ControlModifier | Qt.AltModifier | Qt.MetaModifier)) + return; + + // Explicitly ignore backspace and similar keys that might have text but shouldn't trigger search + if (event.key === Qt.Key_Backspace || event.key === Qt.Key_Delete || event.key === Qt.Key_Escape) + return; + + wasCollapsedBeforeSearch = true; + sidebarExpanded = true; + searchInput.text = event.text; + Qt.callLater(() => { + if (searchInput.inputItem) { + searchInput.inputItem.forceActiveFocus(); + // Cursor moves to end automatically usually, but let's be safe + searchInput.inputItem.cursorPosition = 1; + } + }); + event.accepted = true; + } + } + + // Scroll functions + function scrollDown() { + if (activeScrollView && activeScrollView.ScrollBar.vertical) { + const scrollBar = activeScrollView.ScrollBar.vertical; + const stepSize = activeScrollView.height * 0.1; + scrollBar.position = Math.min(scrollBar.position + stepSize / activeScrollView.contentHeight, 1.0 - scrollBar.size); + } + } + + function scrollUp() { + if (activeScrollView && activeScrollView.ScrollBar.vertical) { + const scrollBar = activeScrollView.ScrollBar.vertical; + const stepSize = activeScrollView.height * 0.1; + scrollBar.position = Math.max(scrollBar.position - stepSize / activeScrollView.contentHeight, 0); + } + } + + function scrollPageDown() { + if (activeScrollView && activeScrollView.ScrollBar.vertical) { + const scrollBar = activeScrollView.ScrollBar.vertical; + const pageSize = activeScrollView.height * 0.9; + scrollBar.position = Math.min(scrollBar.position + pageSize / activeScrollView.contentHeight, 1.0 - scrollBar.size); + } + } + + function scrollPageUp() { + if (activeScrollView && activeScrollView.ScrollBar.vertical) { + const scrollBar = activeScrollView.ScrollBar.vertical; + const pageSize = activeScrollView.height * 0.9; + scrollBar.position = Math.max(scrollBar.position - pageSize / activeScrollView.contentHeight, 0); + } + } + + // Tab navigation functions + function selectNextTab() { + if (tabsModel.length > 0) { + currentTabIndex = (currentTabIndex + 1) % tabsModel.length; + } + } + + function selectPreviousTab() { + if (tabsModel.length > 0) { + currentTabIndex = (currentTabIndex - 1 + tabsModel.length) % tabsModel.length; + } + } + + // Main UI + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginL + spacing: 0 + + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: true + spacing: Style.marginL + + // Sidebar + NBox { + id: sidebar + + clip: true + Layout.preferredWidth: Math.round(root.sidebarExpanded ? 200 * Style.uiScaleRatio : sidebarToggle.width + (root.sidebarCardStyle ? Style.margin2M : 0) + (sidebarList.verticalScrollBarActive ? Style.marginM : 0)) + Layout.fillHeight: true + Layout.alignment: Qt.AlignTop + + radius: root.sidebarCardStyle ? Style.radiusM : 0 + color: root.sidebarCardStyle ? Color.mSurfaceVariant : "transparent" + border.color: root.sidebarCardStyle ? Style.boxBorderColor : "transparent" + + Behavior on Layout.preferredWidth { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + + // Sidebar content + ColumnLayout { + anchors.fill: parent + spacing: Style.marginS + anchors.margins: root.sidebarCardStyle ? Style.marginM : 0 + + // Sidebar toggle button + Item { + id: toggleContainer + Layout.fillWidth: true + Layout.preferredHeight: Math.round(toggleRow.implicitHeight + Style.margin2S) + + Rectangle { + id: sidebarToggle + width: Math.round(toggleRow.implicitWidth + Style.margin2S) + height: parent.height + anchors.left: parent.left + radius: Style.radiusS + color: toggleMouseArea.containsMouse ? Color.mHover : "transparent" + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + + RowLayout { + id: toggleRow + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: Style.marginS + spacing: 0 + + NIcon { + icon: root.sidebarExpanded ? "layout-sidebar-right-expand" : "layout-sidebar-left-expand" + color: toggleMouseArea.containsMouse ? Color.mOnHover : Color.mOnSurface + pointSize: Style.fontSizeXL + } + } + + MouseArea { + id: toggleMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: { + TooltipService.show(sidebarToggle, root.sidebarExpanded ? I18n.tr("tooltips.collapse") : I18n.tr("tooltips.expand")); + } + onExited: { + TooltipService.hide(); + } + onClicked: { + TooltipService.hide(); + root.sidebarExpanded = !root.sidebarExpanded; + } + } + } + } + + // Search container wrapper to prevent layout jumps + Item { + id: searchContainerWrapper + Layout.fillWidth: true + Layout.preferredHeight: searchInput.implicitHeight > 0 ? searchInput.implicitHeight : (Style.fontSizeXL + Style.margin2M) + + // Search input + NTextInput { + id: searchInput + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + placeholderText: I18n.tr("common.search") + inputIconName: "search" + visible: opacity > 0 + opacity: root.sidebarExpanded ? 1.0 : 0.0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + + onTextChanged: root.searchText = text + onEditingFinished: { + if (root.searchText.trim() !== "") + root.searchActivate(); + } + } + + // Search button for collapsed sidebar + Item { + id: searchCollapsedContainer + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + height: Math.round(searchCollapsedRow.implicitHeight + Style.margin2S) + visible: opacity > 0 + opacity: !root.sidebarExpanded ? 1.0 : 0.0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + + Rectangle { + id: searchCollapsedButton + width: Math.round(searchCollapsedRow.implicitWidth + Style.margin2S) + height: parent.height + anchors.left: parent.left + radius: Style.radiusS + color: searchCollapsedMouseArea.containsMouse ? Color.mHover : "transparent" + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + + RowLayout { + id: searchCollapsedRow + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: Style.marginS + spacing: 0 + + NIcon { + icon: "search" + color: searchCollapsedMouseArea.containsMouse ? Color.mOnHover : Color.mOnSurface + pointSize: Style.fontSizeXL + } + } + + MouseArea { + id: searchCollapsedMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + root.sidebarExpanded = true; + root.wasCollapsedBeforeSearch = false; // Expanding manually resets this + Qt.callLater(() => searchInput.inputItem.forceActiveFocus()); + } + onEntered: { + TooltipService.show(searchCollapsedButton, I18n.tr("common.search")); + } + onExited: { + TooltipService.hide(); + } + } + } + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.bottomMargin: Style.marginXL + + // Search results list + NListView { + id: searchResultsList + anchors.fill: parent + model: root.searchResults + spacing: Style.marginXS + visible: root.searchText.trim() !== "" + verticalPolicy: ScrollBar.AsNeeded + gradientColor: "transparent" + reserveScrollbarSpace: false + + HoverHandler { + onPointChanged: { + if (!root._mouseInitialized) { + root._lastMouseX = point.position.x; + root._lastMouseY = point.position.y; + root._mouseInitialized = true; + return; + } + + const deltaX = Math.abs(point.position.x - root._lastMouseX); + const deltaY = Math.abs(point.position.y - root._lastMouseY); + if (deltaX + deltaY >= 5) { + root.ignoreMouseHover = false; + root._lastMouseX = point.position.x; + root._lastMouseY = point.position.y; + } + } + } + + delegate: Rectangle { + id: resultItem + width: searchResultsList.width - (searchResultsList.verticalScrollBarActive ? Style.marginM : 0) + height: resultColumn.implicitHeight + Style.margin2M + radius: Style.iRadiusS + readonly property bool selected: index === root.searchSelectedIndex + readonly property bool effectiveHover: !root.ignoreMouseHover && resultMouseArea.containsMouse + color: (effectiveHover || selected) ? Color.mHover : "transparent" + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + + ColumnLayout { + id: resultColumn + anchors.fill: parent + anchors.leftMargin: Style.marginL + anchors.rightMargin: Style.marginL + anchors.topMargin: Style.marginM + anchors.bottomMargin: Style.marginM + spacing: 0 + + NText { + text: I18n.tr(modelData.labelKey) + pointSize: Style.fontSizeM + font.weight: Style.fontWeightSemiBold + color: (resultItem.effectiveHover || resultItem.selected) ? Color.mOnHover : Color.mOnSurface + Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 + } + + NText { + text: { + let t = I18n.tr(modelData.tabLabel); + if (modelData.subTabLabel) + t += " โ€บ " + I18n.tr(modelData.subTabLabel); + return t; + } + pointSize: Style.fontSizeXS + color: (resultItem.effectiveHover || resultItem.selected) ? Color.mOnHover : Color.mOnSurfaceVariant + Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 + } + } + + MouseArea { + id: resultMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: { + if (!root.ignoreMouseHover) + root.searchSelectedIndex = index; + } + onClicked: { + root.searchSelectedIndex = index; + root.navigateToResult(modelData); + searchInput.text = ""; + } + } + } + } + + // Tab list + NListView { + id: sidebarList + visible: root.searchText.trim() === "" + anchors.fill: parent + model: root.tabsModel + spacing: Style.marginXS + currentIndex: root.currentTabIndex + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AlwaysOff + gradientColor: "transparent" + reserveScrollbarSpace: false + + delegate: Rectangle { + id: tabItem + width: sidebarList.width + height: tabEntryRow.implicitHeight + Style.margin2XS + radius: Style.iRadiusS + color: selected ? Color.mPrimary : (tabItem.hovering ? Color.mHover : "transparent") + readonly property bool selected: index === root.currentTabIndex + property bool hovering: false + property color tabTextColor: selected ? Color.mOnPrimary : (tabItem.hovering ? Color.mOnHover : Color.mOnSurface) + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + + Behavior on tabTextColor { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + + RowLayout { + id: tabEntryRow + anchors.fill: parent + anchors.leftMargin: Style.marginS + anchors.rightMargin: Style.marginS + spacing: Style.marginM + + NIcon { + icon: modelData.icon + color: tabTextColor + pointSize: Style.fontSizeXL + Layout.alignment: Qt.AlignVCenter + } + + NText { + text: I18n.tr(modelData.label) + color: tabTextColor + pointSize: Style.fontSizeM + font.weight: Style.fontWeightSemiBold + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + visible: root.sidebarExpanded + opacity: root.sidebarExpanded ? 1.0 : 0.0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton + cursorShape: Qt.PointingHandCursor + onEntered: { + tabItem.hovering = true; + // Show tooltip when sidebar is collapsed + if (!root.sidebarExpanded) { + TooltipService.show(tabItem, I18n.tr(modelData.label)); + } + } + onExited: { + tabItem.hovering = false; + // Hide tooltip when sidebar is collapsed + if (!root.sidebarExpanded) { + TooltipService.hide(); + } + } + onCanceled: { + tabItem.hovering = false; + if (!root.sidebarExpanded) { + TooltipService.hide(); + } + } + onClicked: { + root.currentTabIndex = index; + // Hide tooltip on click + if (!root.sidebarExpanded) { + TooltipService.hide(); + } + } + } + } + + onCurrentIndexChanged: { + if (currentIndex !== root.currentTabIndex) { + root.currentTabIndex = currentIndex; + } + } + + Connections { + target: root + function onCurrentTabIndexChanged() { + if (sidebarList.currentIndex !== root.currentTabIndex) { + sidebarList.currentIndex = root.currentTabIndex; + sidebarList.positionViewAtIndex(root.currentTabIndex, ListView.Contain); + } + } + } + } + } + } + } + + // Content pane + NBox { + id: contentPane + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignTop + radius: Style.radiusM + color: Color.mSurfaceVariant + + ColumnLayout { + id: contentLayout + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.margins: Style.marginL + width: Math.min(parent.width - Style.marginL * 2, 780 * Style.uiScaleRatio) + spacing: Style.marginS + + // Header row + RowLayout { + id: headerRow + Layout.fillWidth: true + spacing: Style.marginS + + NIcon { + icon: root.tabsModel[currentTabIndex]?.icon ?? "" + color: Color.mPrimary + pointSize: Style.fontSizeXXL + } + + NText { + text: root.tabsModel[root.currentTabIndex]?.label ? I18n.tr(root.tabsModel[root.currentTabIndex].label) : "" + pointSize: Style.fontSizeXL + font.weight: Style.fontWeightBold + color: Color.mPrimary + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + Layout.alignment: Qt.AlignVCenter + onClicked: root.closeRequested() + } + } + + // Tab content area + Rectangle { + id: tabContentArea + Layout.fillWidth: true + Layout.fillHeight: true + Layout.leftMargin: -Style.marginM + Layout.rightMargin: -Style.marginL + color: "transparent" + + Repeater { + id: contentRepeater + model: root.tabsModel + delegate: Loader { + anchors.fill: parent + active: index === root.currentTabIndex + opacity: 0 + + NumberAnimation on opacity { + id: fadeInAnim + from: 0 + to: 1 + duration: Style.animationSlowest + easing.type: Easing.OutCubic + running: false + } + + onStatusChanged: { + if (status === Loader.Ready && item) { + fadeInAnim.start(); + const scrollView = item.children[0]; + if (scrollView && scrollView.toString().includes("ScrollView")) { + root.activeScrollView = scrollView; + } + } + } + + sourceComponent: NScrollView { + id: scrollView + anchors.fill: parent + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AsNeeded + leftPadding: Style.marginL + topPadding: Style.marginL + bottomPadding: Style.marginL + userRightPadding: Style.marginL + reserveScrollbarSpace: false + + Component.onCompleted: { + root.activeScrollView = scrollView; + } + + Loader { + active: true + sourceComponent: root.tabsModel[index]?.source + width: scrollView.availableWidth + onLoaded: { + if (item && item.hasOwnProperty("screen")) { + item.screen = root.screen; + } + root.activeTabContent = item; + if (root._pendingSubTab >= 0) { + root.navigatingFromSearch = true; + if (root.setSubTabIndex(root._pendingSubTab)) + root._pendingSubTab = -1; + root.navigatingFromSearch = false; + } + if (root.highlightLabelKey) { + highlightScrollTimer.targetKey = root.highlightLabelKey; + highlightScrollTimer.restart(); + } + } + } + } + } + } + + // Highlight overlay for search results + Rectangle { + id: highlightOverlay + visible: opacity > 0 + opacity: 0 + color: Qt.alpha(Color.mSecondary, 0.2) + border.color: Qt.alpha(Color.mSecondary, 0.6) + border.width: Style.borderM + radius: Style.radiusS + z: 100 + + SequentialAnimation { + id: highlightAnimation + + NumberAnimation { + target: highlightOverlay + property: "opacity" + to: 1.0 + duration: Style.animationSlow + easing.type: Easing.OutQuad + } + + PauseAnimation { + duration: 2000 + } + + NumberAnimation { + target: highlightOverlay + property: "opacity" + to: 0 + duration: Style.animationSlowest + easing.type: Easing.InQuad + } + } + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/SettingsPanel.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/SettingsPanel.qml new file mode 100644 index 0000000..f4de52e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/SettingsPanel.qml @@ -0,0 +1,290 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.MainScreen +import qs.Services.UI +import qs.Widgets + +SmartPanel { + id: root + + preferredWidth: Math.round(840 * Style.uiScaleRatio) + preferredHeight: Math.round(910 * Style.uiScaleRatio) + + // Settings panel mode: "centered", "attached", "window" + readonly property string settingsPanelMode: Settings.data.ui.settingsPanelMode + readonly property bool isWindowMode: settingsPanelMode === "window" + readonly property bool attachToBar: settingsPanelMode === "attached" + + readonly property string barDensity: Settings.data.bar.density + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool barFloating: Settings.data.bar.barType === "floating" + readonly property real barMarginH: barFloating ? Math.ceil(Settings.data.bar.marginHorizontal) : 0 + readonly property real barMarginV: barFloating ? Math.ceil(Settings.data.bar.marginVertical) : 0 + + forceAttachToBar: attachToBar + panelAnchorHorizontalCenter: !root.useButtonPosition && (attachToBar ? (barPosition === "top" || barPosition === "bottom") : true) + panelAnchorVerticalCenter: !root.useButtonPosition && (attachToBar ? (barPosition === "left" || barPosition === "right") : true) + panelAnchorTop: !root.useButtonPosition && attachToBar && barPosition === "top" + panelAnchorBottom: !root.useButtonPosition && attachToBar && barPosition === "bottom" + panelAnchorLeft: !root.useButtonPosition && attachToBar && barPosition === "left" + panelAnchorRight: !root.useButtonPosition && attachToBar && barPosition === "right" + + onAttachToBarChanged: { + if (isPanelOpen) { + Qt.callLater(root.setPosition); + } + } + + onBarPositionChanged: { + if (isPanelOpen) { + Qt.callLater(root.setPosition); + } + } + + onBarDensityChanged: { + if (isPanelOpen) { + Qt.callLater(root.setPosition); + } + } + + onBarFloatingChanged: { + if (isPanelOpen) { + Qt.callLater(root.setPosition); + } + } + + onBarMarginHChanged: { + if (isPanelOpen) { + Qt.callLater(root.setPosition); + } + } + + onBarMarginVChanged: { + if (isPanelOpen) { + Qt.callLater(root.setPosition); + } + } + + // Tabs enumeration, order is NOT relevant + enum Tab { + About, + Audio, + Bar, + ColorScheme, + LockScreen, + ControlCenter, + DesktopWidgets, + OSD, + Display, + Dock, + General, + Hooks, + Idle, + Launcher, + Location, + Connections, + Notifications, + Plugins, + SessionMenu, + System, + UserInterface, + Wallpaper + } + + property int requestedTab: SettingsPanel.Tab.General + property int requestedSubTab: -1 + property var requestedEntry: null + + // Content state - these are synced with SettingsContent when panel opens + property int currentTabIndex: 0 + property var tabsModel: [] + property var activeScrollView: null + + // Internal reference to the content (set when panel content loads) + property var _settingsContent: null + + // Override toggle to handle window mode + function toggle(buttonItem, buttonName) { + if (isWindowMode) { + SettingsPanelService.toggleWindow(requestedTab); + return; + } + // Call parent toggle + if (isPanelOpen) { + close(); + } else { + open(buttonItem, buttonName); + } + } + + // Override open to handle window mode + function open(buttonItem, buttonName) { + if (isWindowMode) { + SettingsPanelService.openWindow(requestedTab); + return; + } + + // Panel mode: replicate SmartPanel.open() logic + if (!buttonItem && buttonName) { + if (typeof buttonName === "object" && buttonName.x !== undefined && buttonName.y !== undefined) { + root.buttonItem = null; + root.buttonPosition = buttonName; + root.buttonWidth = 0; + root.buttonHeight = 0; + root.useButtonPosition = true; + } else { + buttonItem = BarService.lookupWidget(buttonName, screen.name); + } + } + + if (buttonItem) { + root.buttonItem = buttonItem; + var buttonPos = buttonItem.mapToItem(null, 0, 0); + root.buttonPosition = Qt.point(buttonPos.x, buttonPos.y); + root.buttonWidth = buttonItem.width; + root.buttonHeight = buttonItem.height; + root.useButtonPosition = true; + } else if (!(buttonName && typeof buttonName === "object" && buttonName.x !== undefined && buttonName.y !== undefined)) { + root.buttonItem = null; + root.useButtonPosition = false; + } + + isPanelOpen = true; + PanelService.willOpenPanel(root); + } + + // Open to a specific tab and optionally a subtab + function openToTab(tab, subTab, buttonItem, buttonName) { + requestedTab = tab !== undefined ? tab : SettingsPanel.Tab.General; + requestedSubTab = subTab !== undefined ? subTab : -1; + open(buttonItem, buttonName); + } + + // When the panel opens, initialize content + onOpened: { + if (_settingsContent) { + if (requestedEntry) { + _settingsContent.requestedTab = requestedEntry.tab; + _settingsContent.initialize(); + const entry = requestedEntry; + requestedEntry = null; + Qt.callLater(() => _settingsContent.navigateToResult(entry)); + } else { + _settingsContent.requestedTab = requestedTab; + if (requestedSubTab >= 0) { + _settingsContent._pendingSubTab = requestedSubTab; + requestedSubTab = -1; + } + _settingsContent.initialize(); + } + } + } + + // Scroll functions - delegate to content + function scrollDown() { + if (_settingsContent) + _settingsContent.scrollDown(); + } + + function scrollUp() { + if (_settingsContent) + _settingsContent.scrollUp(); + } + + function scrollPageDown() { + if (_settingsContent) + _settingsContent.scrollPageDown(); + } + + function scrollPageUp() { + if (_settingsContent) + _settingsContent.scrollPageUp(); + } + + // Navigation functions - delegate to content + function selectNextTab() { + if (_settingsContent) + _settingsContent.selectNextTab(); + } + + function selectPreviousTab() { + if (_settingsContent) + _settingsContent.selectPreviousTab(); + } + + // Override keyboard handlers from SmartPanel + function onTabPressed() { + selectNextTab(); + } + + function onBackTabPressed() { + selectPreviousTab(); + } + + function onUpPressed() { + if (_settingsContent && _settingsContent.searchText.trim() !== "") { + _settingsContent.searchSelectPrevious(); + } else { + scrollUp(); + } + } + + function onDownPressed() { + if (_settingsContent && _settingsContent.searchText.trim() !== "") { + _settingsContent.searchSelectNext(); + } else { + scrollDown(); + } + } + + function onPageUpPressed() { + scrollPageUp(); + } + + function onPageDownPressed() { + scrollPageDown(); + } + + function onCtrlJPressed() { + if (_settingsContent && _settingsContent.searchText.trim() !== "") { + _settingsContent.searchSelectNext(); + } else { + scrollDown(); + } + } + + function onCtrlKPressed() { + if (_settingsContent && _settingsContent.searchText.trim() !== "") { + _settingsContent.searchSelectPrevious(); + } else { + scrollUp(); + } + } + + panelContent: Rectangle { + id: panelContent + color: "transparent" + + SettingsContent { + id: settingsContent + anchors.fill: parent + screen: root.screen + onCloseRequested: root.close() + Component.onCompleted: { + root._settingsContent = settingsContent; + root.tabsModel = Qt.binding(function () { + return settingsContent.tabsModel; + }); + root.currentTabIndex = Qt.binding(function () { + return settingsContent.currentTabIndex; + }); + root.activeScrollView = Qt.binding(function () { + return settingsContent.activeScrollView; + }); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/SettingsPanelWindow.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/SettingsPanelWindow.qml new file mode 100644 index 0000000..58d394b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/SettingsPanelWindow.qml @@ -0,0 +1,130 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services.UI +import qs.Widgets + +FloatingWindow { + id: root + + title: "Noctalia" + minimumSize: Qt.size(840 * Style.uiScaleRatio, 910 * Style.uiScaleRatio) + implicitWidth: Math.round(840 * Style.uiScaleRatio) + implicitHeight: Math.round(910 * Style.uiScaleRatio) + color: "transparent" + + visible: false + + // Register with SettingsPanelService + Component.onCompleted: { + SettingsPanelService.settingsWindow = root; + } + + property bool isInitialized: false + + // Navigate to a specific tab and optional subtab. + // Works whether the window is already visible or just becoming visible. + function navigateTo(tab, subTab) { + const tabId = tab !== undefined ? tab : 0; + const subTabId = (subTab !== undefined && subTab !== null && subTab >= 0) ? subTab : -1; + if (isInitialized) { + settingsContent.navigateToTab(tabId, subTabId); + } else { + settingsContent.requestedTab = tabId; + if (subTabId >= 0) + settingsContent._pendingSubTab = subTabId; + settingsContent.initialize(); + isInitialized = true; + // Tab content persists in window mode; if no subtab specified and the + // tab content is still loaded (same tab), reset to first subtab + if (subTabId < 0 && settingsContent.activeTabContent) + settingsContent.setSubTabIndex(0); + } + } + + // Navigate to a search result entry. + // Works whether the window is already visible or just becoming visible. + function navigateToEntry(entry) { + if (isInitialized) { + Qt.callLater(() => settingsContent.navigateToResult(entry)); + } else { + settingsContent.requestedTab = entry.tab; + settingsContent.initialize(); + Qt.callLater(() => settingsContent.navigateToResult(entry)); + isInitialized = true; + } + } + + // Sync visibility with service + onVisibleChanged: { + if (visible) { + SettingsPanelService.isWindowOpen = true; + } else { + isInitialized = false; + SettingsPanelService.isWindowOpen = false; + } + } + + // Keyboard shortcuts + Shortcut { + sequence: "Escape" + enabled: !PanelService.isKeybindRecording + onActivated: SettingsPanelService.closeWindow() + } + + Shortcut { + sequence: "Tab" + enabled: !PanelService.isKeybindRecording + onActivated: settingsContent.selectNextTab() + } + + Shortcut { + sequence: "Backtab" + enabled: !PanelService.isKeybindRecording + onActivated: settingsContent.selectPreviousTab() + } + + Instantiator { + model: Settings.data.general.keybinds.keyUp || [] + Shortcut { + sequence: modelData + enabled: !PanelService.isKeybindRecording + onActivated: { + if (settingsContent.searchText.trim() !== "") + settingsContent.searchSelectPrevious(); + else + settingsContent.scrollUp(); + } + } + } + + Instantiator { + model: Settings.data.general.keybinds.keyDown || [] + Shortcut { + sequence: modelData + enabled: !PanelService.isKeybindRecording + onActivated: { + if (settingsContent.searchText.trim() !== "") + settingsContent.searchSelectNext(); + else + settingsContent.scrollDown(); + } + } + } + + // Main content + Rectangle { + anchors.fill: parent + color: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity) + radius: Style.radiusL + + SettingsContent { + id: settingsContent + anchors.fill: parent + onCloseRequested: SettingsPanelService.closeWindow() + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/About/AboutTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/About/AboutTab.qml new file mode 100644 index 0000000..3172c77 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/About/AboutTab.qml @@ -0,0 +1,48 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.info") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.contributors") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + NTabButton { + text: I18n.tr("common.supporters") + tabIndex: 2 + checked: subTabBar.currentIndex === 2 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + VersionSubTab {} + ContributorsSubTab {} + SupportersSubTab {} + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/About/ContributorsSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/About/ContributorsSubTab.qml new file mode 100644 index 0000000..2448483 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/About/ContributorsSubTab.qml @@ -0,0 +1,253 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Noctalia +import qs.Widgets + +ColumnLayout { + id: root + + property var contributors: GitHubService.contributors + property int avatarCacheVersion: 0 + + readonly property int topContributorsCount: 20 + + Connections { + target: GitHubService + function onCachedAvatarsChanged() { + root.avatarCacheVersion++; + } + } + + spacing: Style.marginL + + NHeader { + description: I18n.trp("panels.about.contributors-description", root.contributors.length) + enableDescriptionRichText: true + } + + // Top 20 contributors with full cards (avoids GridView shader crashes on Qt 6.8) + Flow { + id: topContributorsFlow + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + spacing: Style.marginM + + Repeater { + model: Math.min(root.contributors.length, root.topContributorsCount) + + delegate: Rectangle { + width: Math.max(Math.round(topContributorsFlow.width / 2 - Style.marginM - 1), Math.round(Style.baseWidgetSize * 4)) + height: Math.round(Style.baseWidgetSize * 2.3) + radius: Style.radiusM + color: contributorArea.containsMouse ? Color.mHover : "transparent" + border.width: 1 + border.color: contributorArea.containsMouse ? Color.mPrimary : Color.mOutline + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + + RowLayout { + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + + // Avatar container with rectangular design (modern, no shader issues) + Item { + id: wrapper + Layout.alignment: Qt.AlignVCenter + Layout.preferredWidth: Style.baseWidgetSize * 1.8 + Layout.preferredHeight: Style.baseWidgetSize * 1.8 + + property bool isRounded: false + + // Background and image container + Item { + anchors.fill: parent + + // Simple circular image (pre-rendered, no shaders) + Image { + anchors.fill: parent + source: { + // Depend on avatarCacheVersion to trigger re-evaluation + var _ = root.avatarCacheVersion; + // Try cached circular version first + var username = root.contributors[index].login; + var cached = GitHubService.getAvatarPath(username); + if (cached) { + wrapper.isRounded = true; + return cached; + } + + // Fall back to original avatar URL + return root.contributors[index].avatar_url || ""; + } + fillMode: Image.PreserveAspectFit // Fit since image is already circular with transparency + mipmap: true + smooth: true + asynchronous: true + visible: root.contributors[index].avatar_url !== undefined && root.contributors[index].avatar_url !== "" + opacity: status === Image.Ready ? 1.0 : 0.0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + + // Fallback icon + NIcon { + anchors.centerIn: parent + visible: !root.contributors[index].avatar_url || root.contributors[index].avatar_url === "" + icon: "person" + pointSize: Style.fontSizeL + color: Color.mPrimary + } + } + + Rectangle { + visible: wrapper.isRounded + anchors.fill: parent + color: "transparent" + radius: width * 0.5 + border.width: Style.borderM + border.color: Color.mPrimary + } + } + + // Info column + ColumnLayout { + spacing: 2 + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + + NText { + text: root.contributors[index].login || "Unknown" + font.weight: Style.fontWeightBold + color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurface + elide: Text.ElideRight + Layout.fillWidth: true + pointSize: Style.fontSizeS + } + + RowLayout { + spacing: Style.marginXS + Layout.fillWidth: true + + NIcon { + icon: "git-commit" + pointSize: Style.fontSizeXS + color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant + } + + NText { + text: `${(root.contributors[index].contributions || 0).toString()} commits` + pointSize: Style.fontSizeXS + color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant + } + } + } + + // Hover indicator + NIcon { + Layout.alignment: Qt.AlignVCenter + icon: "arrow-right" + pointSize: Style.fontSizeS + color: Color.mPrimary + opacity: contributorArea.containsMouse ? 1.0 : 0.0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + } + + MouseArea { + id: contributorArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + if (root.contributors[index].html_url) + Quickshell.execDetached(["xdg-open", root.contributors[index].html_url]); + } + } + } + } + } + + // Remaining contributors (simple text links) + Flow { + id: remainingContributorsFlow + visible: root.contributors.length > root.topContributorsCount + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.topMargin: Style.marginL + spacing: Style.marginS + + Repeater { + model: Math.max(0, root.contributors.length - root.topContributorsCount) + + delegate: Rectangle { + width: nameText.implicitWidth + Style.margin2M + height: nameText.implicitHeight + Style.margin2S + radius: Style.radiusS + color: nameArea.containsMouse ? Color.mHover : "transparent" + border.width: Style.borderS + border.color: nameArea.containsMouse ? Color.mPrimary : Color.mOutline + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + + NText { + id: nameText + anchors.centerIn: parent + text: root.contributors[index + root.topContributorsCount].login || "Unknown" + pointSize: Style.fontSizeXS + color: nameArea.containsMouse ? Color.mOnHover : Color.mOnSurface + font.weight: Style.fontWeightMedium + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + MouseArea { + id: nameArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + if (root.contributors[index + root.topContributorsCount].html_url) + Quickshell.execDetached(["xdg-open", root.contributors[index + root.topContributorsCount].html_url]); + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/About/SupportersSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/About/SupportersSubTab.qml new file mode 100644 index 0000000..c23e987 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/About/SupportersSubTab.qml @@ -0,0 +1,229 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Noctalia +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + + property var supporters: SupporterService.supporters + property int avatarCacheVersion: 0 + + Connections { + target: SupporterService + function onCachedAvatarsChanged() { + root.avatarCacheVersion++; + } + } + + spacing: Style.marginL + + NHeader { + description: root.supporters.length === 0 ? I18n.tr("panels.about.supporters-loading") : I18n.trp("panels.about.supporters-desc", root.supporters.length) + enableDescriptionRichText: true + } + + NButton { + Layout.alignment: Qt.AlignHCenter + icon: "heart" + text: I18n.tr("panels.about.become-supporter") + outlined: true + onClicked: { + Quickshell.execDetached(["xdg-open", "https://buymeacoffee.com/noctalia"]); + ToastService.showNotice(I18n.tr("panels.about.support"), I18n.tr("toast.donation-opened")); + } + } + + NDivider { + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + } + + // Supporter cards + Flow { + id: supportersFlow + visible: root.supporters.length > 0 + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + spacing: Style.marginL + + Repeater { + model: root.supporters.length + + delegate: Rectangle { + id: supporterCard + + property bool hasGithub: !!root.supporters[index].github_username + + width: Math.max(Math.round(supportersFlow.width / 2 - Style.marginL - 1), Math.round(Style.baseWidgetSize * 4.5)) + height: Math.round(Style.baseWidgetSize * 2.6) + radius: Style.radiusM + color: supporterArea.containsMouse && hasGithub ? Color.mHover : Qt.alpha(Color.mPrimary, 0.05) + border.width: Style.borderM + border.color: supporterArea.containsMouse && hasGithub ? Color.mPrimary : Qt.alpha(Color.mPrimary, 0.5) + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + + RowLayout { + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + + // Avatar with heart badge + Item { + id: avatarWrapper + Layout.alignment: Qt.AlignVCenter + Layout.preferredWidth: Style.baseWidgetSize * 2.0 + Layout.preferredHeight: Style.baseWidgetSize * 2.0 + + property bool isRounded: false + + Item { + anchors.fill: parent + + Image { + anchors.fill: parent + visible: supporterCard.hasGithub + source: { + if (!supporterCard.hasGithub) + return ""; + var _ = root.avatarCacheVersion; + var username = root.supporters[index].github_username; + var cached = SupporterService.getAvatarPath(username); + if (cached) { + avatarWrapper.isRounded = true; + return cached; + } + return "https://github.com/" + username + ".png?size=256"; + } + fillMode: Image.PreserveAspectFit + mipmap: true + smooth: true + asynchronous: true + opacity: status === Image.Ready ? 1.0 : 0.0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + + // Fallback logo for supporters without GitHub + Rectangle { + anchors.fill: parent + visible: !supporterCard.hasGithub + radius: width * 0.5 + color: Qt.alpha(Color.mPrimary, 0.15) + + Image { + anchors.centerIn: parent + source: "../../../../../Assets/noctalia.svg" + width: parent.width * 0.75 + height: width + fillMode: Image.PreserveAspectFit + mipmap: true + smooth: true + } + } + } + + Rectangle { + visible: avatarWrapper.isRounded || !supporterCard.hasGithub + anchors.fill: parent + color: "transparent" + radius: width * 0.5 + border.width: Style.borderM + border.color: Color.mPrimary + } + + // Heart badge + Rectangle { + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.rightMargin: -2 + anchors.bottomMargin: -2 + width: Style.fontSizeM + Style.marginS + height: width + radius: width * 0.5 + color: Color.mPrimary + + NIcon { + anchors.centerIn: parent + icon: "heart-filled" + pointSize: Style.fontSizeXS + color: Color.mOnPrimary + } + } + } + + // Info column + ColumnLayout { + spacing: 2 + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + + NText { + text: root.supporters[index].name || root.supporters[index].github_username || "Unknown" + font.weight: Style.fontWeightBold + color: supporterArea.containsMouse && supporterCard.hasGithub ? Color.mOnHover : Color.mOnSurface + elide: Text.ElideRight + Layout.fillWidth: true + pointSize: Style.fontSizeS + } + + NText { + text: I18n.tr("panels.about.supporter-badge") + pointSize: Style.fontSizeXS + color: Color.mPrimary + font.weight: Style.fontWeightMedium + } + } + + // Hover indicator (only for supporters with GitHub) + NIcon { + Layout.alignment: Qt.AlignVCenter + icon: "arrow-right" + pointSize: Style.fontSizeS + color: Color.mPrimary + visible: supporterCard.hasGithub + opacity: supporterArea.containsMouse ? 1.0 : 0.0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + } + + MouseArea { + id: supporterArea + anchors.fill: parent + hoverEnabled: true + cursorShape: supporterCard.hasGithub ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: { + var username = root.supporters[index].github_username; + if (username) { + Quickshell.execDetached(["xdg-open", "https://github.com/" + username]); + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/About/VersionSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/About/VersionSubTab.qml new file mode 100644 index 0000000..7e5e82b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/About/VersionSubTab.qml @@ -0,0 +1,985 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.Compositor +import qs.Services.Noctalia +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + opacity: 0 + + onSystemInfoLoadingChanged: { + if (!systemInfoLoading) + tabAppearAnim.start(); + } + + NumberAnimation on opacity { + id: tabAppearAnim + from: 0 + to: 1 + duration: Style.animationSlowest + easing.type: Easing.OutCubic + running: false + } + + property string latestVersion: GitHubService.latestVersion + property string currentVersion: UpdateService.currentVersion + property string commitInfo: "" + property string qsVersion: "" + property string qsRevision: "" + + readonly property bool isGitVersion: root.currentVersion.endsWith("-git") + readonly property int gigaB: (1024 * 1024 * 1024) + readonly property int gigaD: (1000 * 1000 * 1000) + + // Update status: compare versions + readonly property bool updateAvailable: { + if (!root.latestVersion || !root.currentVersion || root.latestVersion === I18n.tr("common.unknown")) + return false; + return UpdateService.compareVersions(root.latestVersion, root.currentVersion) > 0 && !root.isGitVersion; + } + readonly property bool isUpToDate: { + if (!root.latestVersion || !root.currentVersion || root.latestVersion === I18n.tr("common.unknown")) + return false; + return UpdateService.compareVersions(root.latestVersion, root.currentVersion) <= 0; + } + + readonly property bool qsUpdateAvailable: { + if (!GitHubService.latestQSVersion || !root.qsVersion || GitHubService.latestQSVersion === I18n.tr("common.unknown")) + return false; + return UpdateService.compareVersions(GitHubService.latestQSVersion, root.qsVersion) > 0; + } + + readonly property bool qsIsUpToDate: { + if (!GitHubService.latestQSVersion || !root.qsVersion || GitHubService.latestQSVersion === I18n.tr("common.unknown")) + return false; + return UpdateService.compareVersions(GitHubService.latestQSVersion, root.qsVersion) <= 0; + } + + // System info properties + property var systemInfo: null + property bool systemInfoLoading: true + property bool systemInfoAvailable: true + + spacing: Style.marginL + + function getModule(type) { + if (!root.systemInfo) + return null; + return root.systemInfo.find(m => m.type === type); + } + + function getMonitorsText(separator) { + const sep = separator || "\n"; + const screens = Quickshell.screens || []; + const scales = CompositorService.displayScales || {}; + let lines = []; + for (let i = 0; i < screens.length; i++) { + const screen = screens[i]; + const name = screen.name || "Unknown"; + const scaleData = scales[name]; + const scaleValue = (typeof scaleData === "object" && scaleData !== null) ? (scaleData.scale || 1.0) : (scaleData || 1.0); + lines.push(name + ": " + screen.width + "x" + screen.height + " @ " + scaleValue + "x"); + } + return lines.join(sep); + } + + function getTelemetryPayload() { + const screens = Quickshell.screens || []; + const scales = CompositorService.displayScales || {}; + const monitors = []; + for (let i = 0; i < screens.length; i++) { + const screen = screens[i]; + const name = screen.name || "Unknown"; + const scaleData = scales[name]; + const scaleValue = (typeof scaleData === "object" && scaleData !== null) ? (scaleData.scale || 1.0) : (scaleData || 1.0); + monitors.push({ + width: screen.width || 0, + height: screen.height || 0, + scale: scaleValue + }); + } + return { + instanceId: TelemetryService.getInstanceId(), + version: UpdateService.currentVersion, + compositor: TelemetryService.getCompositorType(), + os: HostService.osPretty || "Unknown", + ramGb: Math.round((root.getModule("Memory")?.result?.total || 0) / root.gigaB), + monitors: monitors, + ui: { + scaleRatio: Settings.data.general.scaleRatio, + fontDefaultScale: Settings.data.ui.fontDefaultScale, + fontFixedScale: Settings.data.ui.fontFixedScale + } + }; + } + + function copyTelemetryData() { + const payload = getTelemetryPayload(); + const json = JSON.stringify(payload, null, 2); + Quickshell.execDetached(["wl-copy", json]); + ToastService.showNotice(I18n.tr("panels.about.telemetry-title"), I18n.tr("panels.about.telemetry-data-copied")); + } + + function copyInfoToClipboard() { + let info = "Noctalia Shell: " + root.currentVersion; + if (root.isGitVersion && root.commitInfo) { + info += " (" + root.commitInfo + ")"; + } + info += "\n"; + + if (root.qsVersion) { + let qsV = root.qsVersion.startsWith("v") ? root.qsVersion : "v" + root.qsVersion; + info += "Noctalia QS: " + qsV; + if (root.qsRevision) { + info += " (" + root.qsRevision + ")"; + } + info += "\n"; + } + + info += "\nSystem Information\n"; + info += "==================\n"; + if (root.systemInfo) { + const os = root.getModule("OS"); + const kernel = root.getModule("Kernel"); + const title = root.getModule("Title"); + const product = root.getModule("Host"); + const board = root.getModule("Board"); + const cpu = root.getModule("CPU"); + const gpu = root.getModule("GPU"); + const mem = root.getModule("Memory"); + const wm = root.getModule("WM"); + info += "OS: " + (os?.result?.prettyName || "N/A") + "\n"; + info += "Kernel: " + (kernel?.result?.release || "N/A") + "\n"; + info += "Host: " + (title?.result?.hostName || "N/A") + "\n"; + info += "Product: " + (product?.result?.name || "N/A") + "\n"; + info += "Board: " + (board?.result?.name || "N/A") + "\n"; + info += "CPU: " + (cpu?.result?.cpu || "N/A") + "\n"; + if (gpu?.result && Array.isArray(gpu.result) && gpu.result.length > 0) { + info += "GPU: " + gpu.result.map(g => g.name || "Unknown").join(", ") + "\n"; + } + if (mem?.result) { + info += "Memory: " + (mem.result.total / root.gigaB).toFixed(1) + " GB \n"; + } + if (wm?.result) { + info += "WM: " + (wm.result.prettyName || wm.result.processName || "N/A") + "\n"; + } + } + const monitors = getMonitorsText("\n").split("\n"); + for (const mon of monitors) { + info += "Monitor: " + mon + "\n"; + } + info += "\nSettings\n"; + info += "========\n"; + info += "UI Scale: " + Settings.data.general.scaleRatio + "\n"; + info += "Default Font: " + (Settings.data.ui.fontDefault || "default") + " @ " + Settings.data.ui.fontDefaultScale + "x\n"; + info += "Fixed Font: " + (Settings.data.ui.fontFixed || "default") + " @ " + Settings.data.ui.fontFixedScale + "x\n"; + Quickshell.execDetached(["wl-copy", info]); + ToastService.showNotice(I18n.tr("panels.about.title"), I18n.tr("panels.about.info-copied")); + } + + Component.onCompleted: { + // Check if fastfetch is available before trying to run it + checkFastfetchProcess.running = true; + qsVersionProcess.running = true; + + Logger.d("VersionSubTab", "Current version:", root.currentVersion); + Logger.d("VersionSubTab", "Is git version:", root.isGitVersion); + // Only fetch commit info for -git versions + if (root.isGitVersion) { + // On NixOS, extract commit hash from the store path first + if (HostService.isNixOS) { + var shellDir = Quickshell.shellDir || ""; + Logger.d("VersionSubTab", "Component.onCompleted - NixOS detected, shellDir:", shellDir); + if (shellDir) { + // Extract commit hash from path like: /nix/store/...-noctalia-shell-2025-11-30_225e6d3/share/noctalia-shell + // Pattern matches: noctalia-shell-YYYY-MM-DD_ + var match = shellDir.match(/noctalia-shell-\d{4}-\d{2}-\d{2}_([0-9a-f]{7,})/i); + if (match && match[1]) { + // Use first 7 characters of the commit hash + root.commitInfo = match[1].substring(0, 7); + Logger.d("VersionSubTab", "Component.onCompleted - Extracted commit from NixOS path:", root.commitInfo); + return; + } else { + Logger.d("VersionSubTab", "Component.onCompleted - Could not extract commit from NixOS path, trying fallback"); + } + } + } + fetchGitCommit(); + } + } + + function fetchGitCommit() { + var shellDir = Quickshell.shellDir || ""; + Logger.d("VersionSubTab", "fetchGitCommit - shellDir:", shellDir); + if (!shellDir) { + Logger.d("VersionSubTab", "fetchGitCommit - Cannot determine shell directory, skipping git commit fetch"); + return; + } + + gitProcess.workingDirectory = shellDir; + gitProcess.running = true; + } + + Process { + id: gitProcess + command: ["git", "rev-parse", "--short", "HEAD"] + running: false + + onExited: function (exitCode) { + Logger.d("VersionSubTab", "gitProcess - Process exited with code:", exitCode); + if (exitCode === 0) { + var gitOutput = stdout.text.trim(); + Logger.d("VersionSubTab", "gitProcess - gitOutput:", gitOutput); + if (gitOutput) { + root.commitInfo = gitOutput; + Logger.d("VersionSubTab", "gitProcess - Set commitInfo to:", root.commitInfo); + } + } else { + Logger.d("VersionSubTab", "gitProcess - Git command failed. Exit code:", exitCode); + } + } + + stdout: StdioCollector {} + stderr: StdioCollector {} + } + + Process { + id: qsVersionProcess + command: ["qs", "--version"] + running: false + + onExited: function (exitCode) { + if (exitCode === 0) { + var output = stdout.text.trim(); + // Format (old): "noctalia-qs 0.3.0, revision abc12345, distributed by: ..." + // Format (new): "noctalia-qs 0.0.9 (revision b602b69c81d96a1d7c645328feb7b1e1d4b7b7a4, distributed by Unset)" + // Only set if this is actually noctalia-qs; leave empty for upstream quickshell + var match = output.match(/noctalia-qs\s+(\S+?)[\s,(]+revision\s*([0-9a-f]*)/i); + if (match) { + root.qsVersion = match[1]; + root.qsRevision = match[2] ? match[2].substring(0, 9) : ""; + } + } + } + + stdout: StdioCollector {} + stderr: StdioCollector {} + } + + // Check if fastfetch is available before attempting to run it + Process { + id: checkFastfetchProcess + command: ["sh", "-c", "command -v fastfetch"] + running: false + + onExited: function (exitCode) { + if (exitCode === 0) { + // fastfetch is available, run it + Logger.d("VersionSubTab", "fastfetch found, running it"); + fastfetchProcess.running = true; + } else { + // fastfetch not found, show error state immediately + Logger.w("VersionSubTab", "fastfetch not found"); + root.systemInfoLoading = false; + root.systemInfoAvailable = false; + } + } + + stdout: StdioCollector {} + stderr: StdioCollector {} + } + + Process { + id: fastfetchProcess + command: ["fastfetch", "--format", "json", "--config", Quickshell.shellDir + "/Assets/Services/fastfetch/system-info.jsonc"] + running: false + + onExited: function (exitCode) { + root.systemInfoLoading = false; + if (exitCode === 0) { + try { + root.systemInfo = JSON.parse(stdout.text); + root.systemInfoAvailable = true; + } catch (e) { + Logger.w("VersionSubTab", "Failed to parse fastfetch JSON: " + e); + root.systemInfoAvailable = false; + } + } else { + root.systemInfoAvailable = false; + } + } + + stdout: StdioCollector {} + stderr: StdioCollector {} + } + + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: Style.marginXL + + // Noctalia logo + Image { + source: "../../../../../Assets/noctalia.svg" + width: 96 * Style.uiScaleRatio + height: width + fillMode: Image.PreserveAspectFit + sourceSize.width: width + sourceSize.height: height + mipmap: true + smooth: true + Layout.alignment: Qt.AlignBottom + rotation: Settings.isDebug ? 180 : 0 + + Behavior on rotation { + NumberAnimation { + duration: Style.animationSlowest + easing.type: Easing.OutBack + } + } + + property int debugTapCount: 0 + + Timer { + id: debugTapTimer + interval: 5000 + onTriggered: parent.debugTapCount = 0 + } + + MouseArea { + anchors.fill: parent + onClicked: { + if (parent.debugTapCount === 0) { + debugTapTimer.restart(); + } + parent.debugTapCount++; + if (parent.debugTapCount >= 8) { + parent.debugTapCount = 0; + debugTapTimer.stop(); + Settings.isDebug = !Settings.isDebug; + if (Settings.isDebug) { + ToastService.showNotice("Debug", I18n.tr("panels.about.debug-enabled")); + } else { + ToastService.showNotice("Debug", I18n.tr("panels.about.debug-disabled")); + } + } + } + } + } + + ColumnLayout { + NHeader { + label: "Noctalia Shell" + } + + // Versions + GridLayout { + columns: 2 + rowSpacing: Style.marginXS + columnSpacing: Style.marginM + + // Installed Version (Shell) + NText { + text: "Noctalia Shell:" + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + } + + RowLayout { + spacing: Style.marginS + + NText { + text: root.currentVersion + color: Color.mOnSurface + font.weight: Style.fontWeightBold + } + + // Git commit in parentheses + NText { + id: commitText + visible: root.isGitVersion + text: "(" + (root.commitInfo || I18n.tr("common.loading")) + ")" + color: commitMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurfaceVariant + pointSize: Style.fontSizeXS + font.underline: commitMouseArea.containsMouse && root.commitInfo + + MouseArea { + id: commitMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: root.commitInfo ? Qt.PointingHandCursor : Qt.ArrowCursor + onEntered: { + if (root.commitInfo) { + TooltipService.show(commitText, I18n.tr("panels.about.view-commit")); + } + } + onExited: TooltipService.hide() + onClicked: { + if (root.commitInfo) { + Quickshell.execDetached(["xdg-open", "https://github.com/noctalia-dev/noctalia-shell/commit/" + root.commitInfo]); + } + } + } + } + + // Update status indicator + NIcon { + id: upToDateIcon + visible: root.isUpToDate + icon: "circle-check" + pointSize: Style.fontSizeM + color: Color.mPrimary + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(upToDateIcon, I18n.tr("panels.about.up-to-date")) + onExited: TooltipService.hide() + } + } + + NIcon { + id: updateAvailableIcon + visible: root.updateAvailable + icon: "arrow-up-circle" + pointSize: Style.fontSizeS + color: Color.mPrimary + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(updateAvailableIcon, I18n.tr("panels.about.update-available")) + onExited: TooltipService.hide() + } + } + } + + // Latest Version (Shell) + NText { + visible: root.updateAvailable + text: I18n.tr("panels.about.noctalia-available") + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + } + + NText { + visible: root.updateAvailable + text: root.latestVersion + color: Color.mOnSurface + font.weight: Style.fontWeightBold + } + + // Divider-like spacing + Item { + visible: root.qsUpdateAvailable || root.updateAvailable + Layout.columnSpan: 2 + Layout.preferredHeight: Style.marginXS + } + + // Quickshell Version + NText { + visible: root.qsVersion !== "" + text: "Noctalia QS:" + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + } + + RowLayout { + visible: root.qsVersion !== "" + spacing: Style.marginS + + NText { + text: root.qsVersion.startsWith("v") ? root.qsVersion : "v" + root.qsVersion + color: Color.mOnSurface + font.weight: Style.fontWeightBold + } + + // Git revision in parentheses + NText { + id: qsRevisionText + visible: root.qsRevision !== "" + text: "(" + root.qsRevision + ")" + color: qsRevisionMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurfaceVariant + pointSize: Style.fontSizeXS + font.underline: qsRevisionMouseArea.containsMouse + + MouseArea { + id: qsRevisionMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: TooltipService.show(qsRevisionText, I18n.tr("panels.about.view-commit")) + onExited: TooltipService.hide() + onClicked: { + Quickshell.execDetached(["xdg-open", "https://github.com/noctalia-dev/noctalia-qs/commit/" + root.qsRevision]); + } + } + } + + // Update status indicator + NIcon { + id: qsUpToDateIcon + visible: root.qsIsUpToDate + icon: "circle-check" + pointSize: Style.fontSizeM + color: Color.mPrimary + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(qsUpToDateIcon, I18n.tr("panels.about.up-to-date")) + onExited: TooltipService.hide() + } + } + + NIcon { + id: qsUpdateAvailableIcon + visible: root.qsUpdateAvailable + icon: "arrow-up-circle" + pointSize: Style.fontSizeS + color: Color.mPrimary + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(qsUpdateAvailableIcon, I18n.tr("panels.about.update-available")) + onExited: TooltipService.hide() + } + } + } + + // Latest Quickshell Version + NText { + visible: root.qsUpdateAvailable + text: I18n.tr("panels.about.noctalia-available") + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + } + + NText { + visible: root.qsUpdateAvailable + text: GitHubService.latestQSVersion + color: Color.mOnSurface + font.weight: Style.fontWeightBold + } + } + } + } + + GridLayout { + id: actionsGrid + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: Style.marginM + Layout.bottomMargin: Style.marginM + rowSpacing: Style.marginM + columnSpacing: Style.marginM + + columns: (changelogBtn.implicitWidth + copyBtn.implicitWidth + supportBtn.implicitWidth + 2 * columnSpacing) < root.width ? 3 : 1 + + NButton { + id: changelogBtn + icon: "sparkles" + text: I18n.tr("panels.about.changelog") + outlined: true + Layout.alignment: Qt.AlignHCenter + onClicked: { + var screen = PanelService.openedPanel?.screen || SettingsPanelService.settingsWindow?.screen || PanelService.findScreenForPanels(); + SettingsPanelService.close(screen); + UpdateService.viewChangelog(screen); + } + } + + NButton { + id: copyBtn + icon: "copy" + text: I18n.tr("panels.about.copy-info") + outlined: true + Layout.alignment: Qt.AlignHCenter + onClicked: root.copyInfoToClipboard() + } + + NButton { + id: supportBtn + icon: "heart" + text: I18n.tr("panels.about.support") + outlined: true + Layout.alignment: Qt.AlignHCenter + onClicked: { + Quickshell.execDetached(["xdg-open", "https://buymeacoffee.com/noctalia"]); + ToastService.showNotice(I18n.tr("panels.about.support"), I18n.tr("toast.donation-opened")); + } + } + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.about.changelog-on-startup") + description: I18n.tr("panels.about.changelog-on-startup-desc") + checked: Settings.data.general.showChangelogOnStartup + onToggled: checked => Settings.data.general.showChangelogOnStartup = checked + } + + // System Information Section + NDivider { + Layout.fillWidth: true + } + + NHeader { + label: I18n.tr("panels.about.system-title") + } + + // Error state (fastfetch not installed) + ColumnLayout { + visible: !root.systemInfoAvailable + Layout.fillWidth: true + spacing: Style.marginS + + NText { + text: I18n.tr("panels.about.system-not-installed") + color: Color.mOnSurfaceVariant + } + + NText { + text: I18n.tr("panels.about.system-install-hint") + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeXS + } + } + + // System info grid + GridLayout { + id: sysInfo + readonly property real textSize: Style.fontSizeS + + visible: root.systemInfoAvailable && root.systemInfo + Layout.fillWidth: true + columns: 2 + rowSpacing: Style.marginXS + columnSpacing: Style.marginM + + // OS + NText { + text: I18n.tr("panels.about.system-os") + color: Color.mOnSurfaceVariant + pointSize: sysInfo.textSize + } + NText { + text: { + const os = root.getModule("OS"); + return os?.result?.prettyName || "N/A"; + } + color: Color.mOnSurface + pointSize: sysInfo.textSize + Layout.fillWidth: true + wrapMode: Text.Wrap + } + + // Kernel + NText { + text: I18n.tr("panels.about.system-kernel") + color: Color.mOnSurfaceVariant + pointSize: sysInfo.textSize + } + NText { + text: { + const kernel = root.getModule("Kernel"); + return kernel?.result?.release || "N/A"; + } + color: Color.mOnSurface + pointSize: sysInfo.textSize + Layout.fillWidth: true + wrapMode: Text.Wrap + } + + // Host + NText { + text: I18n.tr("panels.about.system-host") + color: Color.mOnSurfaceVariant + pointSize: sysInfo.textSize + } + NText { + text: { + const title = root.getModule("Title"); + return title?.result?.hostName || "N/A"; + } + color: Color.mOnSurface + pointSize: sysInfo.textSize + Layout.fillWidth: true + wrapMode: Text.Wrap + } + + // Product name + NText { + text: I18n.tr("panels.about.system-product") + color: Color.mOnSurfaceVariant + pointSize: sysInfo.textSize + } + NText { + text: { + const title = root.getModule("Host"); + return title?.result?.name || "N/A"; + } + color: Color.mOnSurface + pointSize: sysInfo.textSize + Layout.fillWidth: true + wrapMode: Text.Wrap + } + + // Board name + NText { + text: I18n.tr("panels.about.system-board") + color: Color.mOnSurfaceVariant + pointSize: sysInfo.textSize + } + NText { + text: { + const title = root.getModule("Board"); + return title?.result?.name || "N/A"; + } + color: Color.mOnSurface + pointSize: sysInfo.textSize + Layout.fillWidth: true + wrapMode: Text.Wrap + } + + // Uptime + NText { + text: I18n.tr("panels.about.system-uptime") + color: Color.mOnSurfaceVariant + pointSize: sysInfo.textSize + } + NText { + text: { + const value = root.getModule("Uptime")?.result?.uptime; + return value ? Time.formatVagueHumanReadableDuration(value / 1000) : "-"; + } + color: Color.mOnSurface + pointSize: sysInfo.textSize + Layout.fillWidth: true + wrapMode: Text.Wrap + } + + // CPU + NText { + text: I18n.tr("panels.about.system-cpu") + color: Color.mOnSurfaceVariant + pointSize: sysInfo.textSize + } + NText { + text: { + const cpu = root.getModule("CPU"); + if (!cpu?.result) + return "N/A"; + let cpuText = cpu.result.cpu || "N/A"; + const cores = cpu.result.cores; + if (cores?.logical) { + cpuText += " (" + cores.logical + " threads)"; + } + return cpuText; + } + color: Color.mOnSurface + pointSize: sysInfo.textSize + Layout.fillWidth: true + wrapMode: Text.Wrap + } + + // GPU + NText { + text: I18n.tr("panels.about.system-gpu") + color: Color.mOnSurfaceVariant + pointSize: sysInfo.textSize + } + NText { + text: { + const gpu = root.getModule("GPU"); + if (!gpu?.result || !Array.isArray(gpu.result) || gpu.result.length === 0) + return "N/A"; + return gpu.result.map(g => g.name || "Unknown").join(", "); + } + color: Color.mOnSurface + pointSize: sysInfo.textSize + Layout.fillWidth: true + wrapMode: Text.Wrap + } + + // Memory + NText { + text: I18n.tr("panels.about.system-memory") + color: Color.mOnSurfaceVariant + pointSize: sysInfo.textSize + } + NText { + text: { + const mem = root.getModule("Memory"); + if (!mem?.result) + return "N/A"; + const used = (mem.result.used / root.gigaB).toFixed(1); + const total = (mem.result.total / root.gigaB).toFixed(1); + return used + " GiB / " + total + " GiB"; + } + color: Color.mOnSurface + pointSize: sysInfo.textSize + Layout.fillWidth: true + wrapMode: Text.Wrap + } + + // Disk + NText { + text: I18n.tr("panels.about.system-disk") + color: Color.mOnSurfaceVariant + pointSize: sysInfo.textSize + } + NText { + text: { + const disk = root.getModule("Disk"); + if (!disk?.result || !Array.isArray(disk.result) || disk.result.length === 0) + return "N/A"; + const rootDisk = disk.result.find(d => d.mountpoint === "/"); + if (!rootDisk?.bytes) + return "N/A"; + const used = (rootDisk.bytes.used / root.gigaD).toFixed(1); + const total = (rootDisk.bytes.total / root.gigaD).toFixed(1); + return used + " GB / " + total + " GB" + " (" + rootDisk.filesystem + ")"; + } + color: Color.mOnSurface + pointSize: sysInfo.textSize + Layout.fillWidth: true + wrapMode: Text.Wrap + } + + // WM + NText { + text: I18n.tr("panels.about.system-wm") + color: Color.mOnSurfaceVariant + pointSize: sysInfo.textSize + } + NText { + text: { + const wm = root.getModule("WM"); + if (!wm?.result) + return "N/A"; + let wmText = wm.result.prettyName || wm.result.processName || "N/A"; + if (wm.result.protocolName) { + wmText += " (" + wm.result.protocolName + ")"; + } + return wmText; + } + color: Color.mOnSurface + pointSize: sysInfo.textSize + Layout.fillWidth: true + wrapMode: Text.Wrap + } + + // Packages + NText { + text: I18n.tr("panels.about.system-packages") + color: Color.mOnSurfaceVariant + pointSize: sysInfo.textSize + } + NText { + text: { + const pkg = root.getModule("Packages"); + if (!pkg?.result) + return "N/A"; + const result = pkg.result; + if (result.all) { + const managers = []; + if (result.rpm > 0) + managers.push("rpm: " + result.rpm); + if (result.pacman > 0) + managers.push("pacman: " + result.pacman); + if (result.dpkg > 0) + managers.push("dpkg: " + result.dpkg); + if (result.flatpakSystem > 0 || result.flatpakUser > 0) { + const flatpak = (result.flatpakSystem || 0) + (result.flatpakUser || 0); + managers.push("flatpak: " + flatpak); + } + if (result.snap > 0) + managers.push("snap: " + result.snap); + if (result.nixSystem > 0 || result.nixUser > 0 || result.nixDefault > 0) { + const nix = (result.nixSystem || 0) + (result.nixUser || 0) + (result.nixDefault || 0); + managers.push("nix: " + nix); + } + if (result.brew > 0) + managers.push("brew: " + result.brew); + if (managers.length > 0) { + return result.all + " (" + managers.join(", ") + ")"; + } + return result.all.toString(); + } + return "N/A"; + } + color: Color.mOnSurface + pointSize: sysInfo.textSize + Layout.fillWidth: true + wrapMode: Text.Wrap + } + + // Monitors (2 items per screen: label + value) + Repeater { + model: Quickshell.screens.length * 2 + + NText { + readonly property int screenIndex: Math.floor(index / 2) + readonly property bool isLabel: index % 2 === 0 + readonly property var screen: Quickshell.screens[screenIndex] + + text: { + if (isLabel) + return I18n.tr("panels.about.system-monitor"); + const name = screen?.name || "Unknown"; + const scales = CompositorService.displayScales || {}; + const scaleData = scales[name]; + const scaleValue = (typeof scaleData === "object" && scaleData !== null) ? (scaleData.scale || 1.0) : (scaleData || 1.0); + return name + ": " + (screen?.width || 0) + "x" + (screen?.height || 0) + " @ " + scaleValue + "x"; + } + color: isLabel ? Color.mOnSurfaceVariant : Color.mOnSurface + pointSize: sysInfo.textSize + Layout.fillWidth: !isLabel + wrapMode: Text.Wrap + } + } + } + + // Telemetry Section + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginL + } + + NHeader { + label: I18n.tr("panels.about.telemetry-title") + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.about.telemetry-enabled") + description: I18n.tr("panels.about.telemetry-desc") + checked: Settings.data.general.telemetryEnabled + onToggled: checked => Settings.data.general.telemetryEnabled = checked + } + + RowLayout { + spacing: Style.marginM + + NButton { + icon: "eye" + text: I18n.tr("panels.about.telemetry-show-data") + outlined: true + onClicked: root.copyTelemetryData() + } + + NButton { + icon: "shield-lock" + text: I18n.tr("panels.about.privacy-policy") + outlined: true + onClicked: Quickshell.execDetached(["xdg-open", "https://noctalia.dev/privacy"]) + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Audio/AudioTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Audio/AudioTab.qml new file mode 100644 index 0000000..8be4512 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Audio/AudioTab.qml @@ -0,0 +1,54 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.volumes") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.devices") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + NTabButton { + text: I18n.tr("common.media") + tabIndex: 2 + checked: subTabBar.currentIndex === 2 + } + NTabButton { + text: I18n.tr("common.visualizer") + tabIndex: 3 + checked: subTabBar.currentIndex === 3 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + VolumesSubTab {} + DevicesSubTab {} + MediaSubTab {} + VisualizerSubTab {} + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Audio/DevicesSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Audio/DevicesSubTab.qml new file mode 100644 index 0000000..2df10a9 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Audio/DevicesSubTab.qml @@ -0,0 +1,70 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Services.Pipewire +import qs.Commons +import qs.Services.Media +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + // Output Devices + ButtonGroup { + id: sinks + } + + ColumnLayout { + spacing: Style.marginXS + Layout.fillWidth: true + Layout.bottomMargin: Style.marginL + + NLabel { + label: I18n.tr("panels.audio.devices-output-device-label") + description: I18n.tr("panels.audio.devices-output-device-description") + } + + Repeater { + model: AudioService.sinks + NRadioButton { + ButtonGroup.group: sinks + required property PwNode modelData + text: modelData.description + checked: AudioService.sink?.id === modelData.id + onClicked: { + AudioService.setAudioSink(modelData); + } + Layout.fillWidth: true + } + } + } + + // Input Devices + ButtonGroup { + id: sources + } + + ColumnLayout { + spacing: Style.marginXS + Layout.fillWidth: true + + NLabel { + label: I18n.tr("panels.audio.devices-input-device-label") + description: I18n.tr("panels.audio.devices-input-device-description") + } + + Repeater { + model: AudioService.sources + NRadioButton { + ButtonGroup.group: sources + required property PwNode modelData + text: modelData.description + checked: AudioService.source?.id === modelData.id + onClicked: AudioService.setAudioSource(modelData) + Layout.fillWidth: true + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Audio/MediaSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Audio/MediaSubTab.qml new file mode 100644 index 0000000..8740bc5 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Audio/MediaSubTab.qml @@ -0,0 +1,104 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.Media +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + // Preferred player + NTextInput { + label: I18n.tr("panels.audio.media-primary-player-label") + description: I18n.tr("panels.audio.media-primary-player-description") + placeholderText: I18n.tr("panels.audio.media-primary-player-placeholder") + text: Settings.data.audio.preferredPlayer + defaultValue: Settings.getDefaultValue("audio.preferredPlayer") + onTextChanged: { + Settings.data.audio.preferredPlayer = text; + MediaService.updateCurrentPlayer(); + } + } + + // Blacklist editor + ColumnLayout { + spacing: Style.marginS + Layout.fillWidth: true + + NTextInputButton { + id: blacklistInput + label: I18n.tr("panels.audio.media-excluded-player-label") + description: I18n.tr("panels.audio.media-excluded-player-description") + placeholderText: I18n.tr("panels.audio.media-excluded-player-placeholder") + buttonIcon: "add" + Layout.fillWidth: true + onButtonClicked: { + const val = (blacklistInput.text || "").trim(); + if (val !== "") { + const arr = (Settings.data.audio.mprisBlacklist || []); + if (!arr.find(x => String(x).toLowerCase() === val.toLowerCase())) { + Settings.data.audio.mprisBlacklist = [...arr, val]; + blacklistInput.text = ""; + MediaService.updateCurrentPlayer(); + } + } + } + } + + // Current blacklist entries + Flow { + Layout.fillWidth: true + Layout.leftMargin: Style.marginS + spacing: Style.marginS + + Repeater { + model: Settings.data.audio.mprisBlacklist + delegate: Rectangle { + required property string modelData + property real pad: Style.marginS + color: Qt.alpha(Color.mOnSurface, 0.125) + border.color: Qt.alpha(Color.mOnSurface, Style.opacityLight) + border.width: Style.borderS + + RowLayout { + id: chipRow + spacing: Style.marginXS + anchors.fill: parent + anchors.margins: pad + + NText { + text: modelData + color: Color.mOnSurface + pointSize: Style.fontSizeS + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: Style.marginS + } + + NIconButton { + icon: "close" + baseSize: Style.baseWidgetSize * 0.8 + Layout.alignment: Qt.AlignVCenter + Layout.rightMargin: Style.marginXS + onClicked: { + const arr = (Settings.data.audio.mprisBlacklist || []); + const idx = arr.findIndex(x => String(x) === modelData); + if (idx >= 0) { + arr.splice(idx, 1); + Settings.data.audio.mprisBlacklist = arr; + MediaService.updateCurrentPlayer(); + } + } + } + } + + implicitWidth: chipRow.implicitWidth + pad * 2 + implicitHeight: Math.max(chipRow.implicitHeight + pad * 2, Style.baseWidgetSize * 0.8) + radius: Style.radiusM + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Audio/VisualizerSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Audio/VisualizerSubTab.qml new file mode 100644 index 0000000..e1ebf70 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Audio/VisualizerSubTab.qml @@ -0,0 +1,97 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + NComboBox { + label: I18n.tr("panels.audio.visualizer-type-label") + description: I18n.tr("panels.audio.visualizer-type-description") + model: [ + { + "key": "none", + "name": I18n.tr("common.none") + }, + { + "key": "linear", + "name": I18n.tr("options.visualizer-types.linear") + }, + { + "key": "mirrored", + "name": I18n.tr("options.visualizer-types.mirrored") + }, + { + "key": "wave", + "name": I18n.tr("options.visualizer-types.wave") + } + ] + currentKey: Settings.data.audio.visualizerType + defaultValue: Settings.getDefaultValue("audio.visualizerType") + onSelected: key => Settings.data.audio.visualizerType = key + } + + NToggle { + label: I18n.tr("panels.audio.spectrum-mirrored-label") + description: I18n.tr("panels.audio.spectrum-mirrored-description") + checked: Settings.data.audio.spectrumMirrored + defaultValue: Settings.getDefaultValue("audio.spectrumMirrored") + onToggled: Settings.data.audio.spectrumMirrored = checked + } + + NComboBox { + label: I18n.tr("panels.audio.media-frame-rate-label") + description: I18n.tr("panels.audio.media-frame-rate-description") + model: [ + { + "key": "30", + "name": I18n.tr("options.frame-rates-fps", { + "fps": "30" + }) + }, + { + "key": "60", + "name": I18n.tr("options.frame-rates-fps", { + "fps": "60" + }) + }, + { + "key": "100", + "name": I18n.tr("options.frame-rates-fps", { + "fps": "100" + }) + }, + { + "key": "120", + "name": I18n.tr("options.frame-rates-fps", { + "fps": "120" + }) + }, + { + "key": "144", + "name": I18n.tr("options.frame-rates-fps", { + "fps": "144" + }) + }, + { + "key": "165", + "name": I18n.tr("options.frame-rates-fps", { + "fps": "165" + }) + }, + { + "key": "240", + "name": I18n.tr("options.frame-rates-fps", { + "fps": "240" + }) + } + ] + currentKey: Settings.data.audio.spectrumFrameRate + defaultValue: Settings.getDefaultValue("audio.spectrumFrameRate") + onSelected: key => Settings.data.audio.spectrumFrameRate = key + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Audio/VolumesSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Audio/VolumesSubTab.qml new file mode 100644 index 0000000..ed0ef37 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Audio/VolumesSubTab.qml @@ -0,0 +1,196 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Media +import qs.Services.System +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + property real localVolume: AudioService.volume + + Connections { + target: AudioService + function onSinkChanged() { + localVolume = AudioService.volume; + } + function onVolumeChanged() { + localVolume = AudioService.volume; + } + } + + Connections { + target: AudioService.sink?.audio ? AudioService.sink?.audio : null + function onVolumeChanged() { + localVolume = AudioService.volume; + } + } + + // Output Volume + ColumnLayout { + spacing: Style.marginXXS + Layout.fillWidth: true + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.osd.types-volume-label") + description: I18n.tr("panels.audio.volumes-output-volume-description") + from: 0 + to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0 + value: localVolume + stepSize: 0.01 + text: Math.round(AudioService.volume * 100) + "%" + onMoved: value => localVolume = value + } + + Timer { + interval: 100 + running: true + repeat: true + onTriggered: { + if (!AudioService.isSwitchingSink && Math.abs(localVolume - AudioService.volume) >= 0.01) { + AudioService.setVolume(localVolume); + } + } + } + } + + // Mute Toggle + ColumnLayout { + spacing: Style.marginS + Layout.fillWidth: true + + NToggle { + label: I18n.tr("panels.audio.volumes-mute-output-label") + description: I18n.tr("panels.audio.volumes-mute-output-description") + checked: AudioService.muted + onToggled: checked => AudioService.setOutputMuted(checked) + } + } + + // Volume Feedback sound Toggle + ColumnLayout { + spacing: Style.marginS + Layout.fillWidth: true + + NToggle { + label: I18n.tr("panels.audio.volumes-volume-feedback-label") + description: I18n.tr("panels.audio.volumes-volume-feedback-description") + checked: Settings.data.audio.volumeFeedback + defaultValue: Settings.getDefaultValue("audio.volumeFeedback") + onToggled: checked => Settings.data.audio.volumeFeedback = checked + } + + ColumnLayout { + enabled: SoundService.multimediaAvailable && Settings.data.audio.volumeFeedback + spacing: Style.marginXXS + Layout.fillWidth: true + + NLabel { + label: I18n.tr("panels.audio.volumes-feedback-sound-file-label") + description: I18n.tr("panels.audio.volumes-feedback-sound-file-description") + } + + NTextInputButton { + enabled: parent.enabled + Layout.fillWidth: true + placeholderText: I18n.tr("panels.notifications.sounds-files-placeholder") + text: Settings.data.audio.volumeFeedbackSoundFile ?? "" + buttonIcon: "folder-open" + buttonTooltip: I18n.tr("panels.notifications.sounds-files-select-file") + onInputTextChanged: text => Settings.data.audio.volumeFeedbackSoundFile = text + onButtonClicked: volumeFeedbackFilePicker.open() + } + } + } + + NDivider { + Layout.fillWidth: true + } + + // Input Volume + ColumnLayout { + spacing: Style.marginXS + Layout.fillWidth: true + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.osd.types-input-volume-label") + description: I18n.tr("panels.audio.volumes-input-volume-description") + from: 0 + to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0 + value: AudioService.inputVolume + stepSize: 0.01 + text: Math.round(AudioService.inputVolume * 100) + "%" + onMoved: value => AudioService.setInputVolume(value) + } + } + + // Input Mute Toggle + ColumnLayout { + spacing: Style.marginS + Layout.fillWidth: true + + NToggle { + label: I18n.tr("panels.audio.volumes-mute-input-label") + description: I18n.tr("panels.audio.volumes-mute-input-description") + checked: AudioService.inputMuted + onToggled: checked => AudioService.setInputMuted(checked) + } + } + + // Volume Step Size + ColumnLayout { + spacing: Style.marginS + Layout.fillWidth: true + + NSpinBox { + Layout.fillWidth: true + label: I18n.tr("panels.audio.volumes-step-size-label") + description: I18n.tr("panels.audio.volumes-step-size-description") + minimum: 1 + maximum: 25 + value: Settings.data.audio.volumeStep + stepSize: 1 + suffix: "%" + defaultValue: Settings.getDefaultValue("audio.volumeStep") + onValueChanged: Settings.data.audio.volumeStep = value + } + } + + NDivider { + Layout.fillWidth: true + } + + // Raise maximum volume above 100% + ColumnLayout { + spacing: Style.marginS + Layout.fillWidth: true + + NToggle { + label: I18n.tr("panels.audio.volumes-volume-overdrive-label") + description: I18n.tr("panels.audio.volumes-volume-overdrive-description") + checked: Settings.data.audio.volumeOverdrive + defaultValue: Settings.getDefaultValue("audio.volumeOverdrive") + onToggled: checked => Settings.data.audio.volumeOverdrive = checked + } + } + + NFilePicker { + id: volumeFeedbackFilePicker + title: I18n.tr("panels.audio.volumes-feedback-sound-file-select-title") + selectionMode: "files" + initialPath: Quickshell.env("HOME") + nameFilters: ["*.wav", "*.mp3", "*.ogg", "*.flac", "*.m4a", "*.aac"] + onAccepted: paths => { + if (paths.length > 0) { + Settings.data.audio.volumeFeedbackSoundFile = paths[0]; + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Bar/AppearanceSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Bar/AppearanceSubTab.qml new file mode 100644 index 0000000..61ae9f6 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Bar/AppearanceSubTab.qml @@ -0,0 +1,379 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.Compositor +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-position-label") + description: I18n.tr("panels.bar.appearance-position-description") + model: [ + { + "key": "top", + "name": I18n.tr("positions.top") + }, + { + "key": "bottom", + "name": I18n.tr("positions.bottom") + }, + { + "key": "left", + "name": I18n.tr("positions.left") + }, + { + "key": "right", + "name": I18n.tr("positions.right") + } + ] + currentKey: Settings.data.bar.position + defaultValue: Settings.getDefaultValue("bar.position") + onSelected: key => Settings.data.bar.position = key + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-density-label") + description: I18n.tr("panels.bar.appearance-density-description") + model: [ + { + "key": "mini", + "name": I18n.tr("options.bar.density-mini") + }, + { + "key": "compact", + "name": I18n.tr("options.bar.density-compact") + }, + { + "key": "default", + "name": I18n.tr("options.bar.density-default") + }, + { + "key": "comfortable", + "name": I18n.tr("options.bar.density-comfortable") + }, + { + "key": "spacious", + "name": I18n.tr("options.bar.density-spacious") + } + ] + currentKey: Settings.data.bar.density + defaultValue: Settings.getDefaultValue("bar.density") + onSelected: key => Settings.data.bar.density = key + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-type-label") + description: I18n.tr("panels.bar.appearance-type-description") + model: [ + { + "key": "simple", + "name": I18n.tr("options.bar.type-simple") + }, + { + "key": "floating", + "name": I18n.tr("options.bar.type-floating") + }, + { + "key": "framed", + "name": I18n.tr("options.bar.type-framed") + } + ] + currentKey: Settings.data.bar.barType + defaultValue: Settings.getDefaultValue("bar.barType") + onSelected: key => { + Settings.data.bar.barType = key; + } + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("common.display-mode") + description: I18n.tr("panels.bar.appearance-display-mode-description") + model: [ + { + "key": "always_visible", + "name": I18n.tr("hide-modes.visible") + }, + { + "key": "non_exclusive", + "name": I18n.tr("hide-modes.non-exclusive") + }, + { + "key": "auto_hide", + "name": I18n.tr("hide-modes.auto-hide") + } + ] + currentKey: Settings.data.bar.displayMode + defaultValue: Settings.getDefaultValue("bar.displayMode") + onSelected: key => Settings.data.bar.displayMode = key + } + + NToggle { + label: I18n.tr("panels.bar.appearance-use-separate-opacity-label") + description: I18n.tr("panels.bar.appearance-use-separate-opacity-description") + checked: Settings.data.bar.useSeparateOpacity + defaultValue: Settings.getDefaultValue("bar.useSeparateOpacity") + onToggled: checked => Settings.data.bar.useSeparateOpacity = checked + } + + NValueSlider { + Layout.fillWidth: true + visible: Settings.data.bar.useSeparateOpacity + label: I18n.tr("panels.bar.appearance-background-opacity-label") + description: I18n.tr("panels.bar.appearance-background-opacity-description") + from: 0 + to: 1 + stepSize: 0.01 + showReset: true + value: Settings.data.bar.backgroundOpacity + defaultValue: Settings.getDefaultValue("bar.backgroundOpacity") + onMoved: value => Settings.data.bar.backgroundOpacity = value + text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%" + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-font-scale-label") + description: I18n.tr("panels.bar.appearance-font-scale-description") + from: 0.5 + to: 2.0 + stepSize: 0.01 + showReset: true + value: Settings.data.bar.fontScale + defaultValue: Settings.getDefaultValue("bar.fontScale") + onMoved: value => Settings.data.bar.fontScale = value + text: Math.floor(Settings.data.bar.fontScale * 100) + "%" + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-widget-spacing-label") + description: I18n.tr("panels.bar.appearance-widget-spacing-description") + from: 0 + to: 30 + stepSize: 1 + showReset: true + value: Settings.data.bar.widgetSpacing + defaultValue: Settings.getDefaultValue("bar.widgetSpacing") + onMoved: value => Settings.data.bar.widgetSpacing = value + text: Settings.data.bar.widgetSpacing + "px" + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-content-padding-label") + description: I18n.tr("panels.bar.appearance-content-padding-description") + from: 0 + to: 30 + stepSize: 1 + showReset: true + value: Settings.data.bar.contentPadding + defaultValue: Settings.getDefaultValue("bar.contentPadding") + onMoved: value => Settings.data.bar.contentPadding = value + text: Settings.data.bar.contentPadding + "px" + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-show-outline-label") + description: I18n.tr("panels.bar.appearance-show-outline-description") + checked: Settings.data.bar.showOutline + defaultValue: Settings.getDefaultValue("bar.showOutline") + onToggled: checked => Settings.data.bar.showOutline = checked + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-show-capsule-label") + description: I18n.tr("panels.bar.appearance-show-capsule-description") + checked: Settings.data.bar.showCapsule + defaultValue: Settings.getDefaultValue("bar.showCapsule") + onToggled: checked => Settings.data.bar.showCapsule = checked + } + + NColorChoice { + Layout.fillWidth: true + visible: Settings.data.bar.showCapsule + label: I18n.tr("panels.bar.appearance-capsule-color-label") + description: I18n.tr("panels.bar.appearance-capsule-color-description") + noneColor: Color.mSurfaceVariant + noneOnColor: Color.mOnSurfaceVariant + currentKey: Settings.data.bar.capsuleColorKey + onSelected: key => Settings.data.bar.capsuleColorKey = key + } + + NValueSlider { + Layout.fillWidth: true + visible: Settings.data.bar.showCapsule + label: I18n.tr("panels.bar.appearance-capsule-opacity-label") + description: I18n.tr("panels.bar.appearance-capsule-opacity-description") + from: 0 + to: 1 + stepSize: 0.01 + showReset: true + value: Settings.data.bar.capsuleOpacity + defaultValue: Settings.getDefaultValue("bar.capsuleOpacity") + onMoved: value => Settings.data.bar.capsuleOpacity = value + text: Math.floor(Settings.data.bar.capsuleOpacity * 100) + "%" + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-enable-exclusion-zone-inset-label") + description: I18n.tr("panels.bar.appearance-enable-exclusion-zone-inset-description") + checked: Settings.data.bar.enableExclusionZoneInset + defaultValue: Settings.getDefaultValue("bar.enableExclusionZoneInset") + onToggled: checked => Settings.data.bar.enableExclusionZoneInset = checked + } + + NToggle { + Layout.fillWidth: true + visible: CompositorService.isNiri + label: I18n.tr("panels.bar.appearance-hide-on-overview-label") + description: I18n.tr("panels.bar.appearance-hide-on-overview-description") + checked: Settings.data.bar.hideOnOverview + defaultValue: Settings.getDefaultValue("bar.hideOnOverview") + onToggled: checked => Settings.data.bar.hideOnOverview = checked + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-outer-corners-label") + description: I18n.tr("panels.bar.appearance-outer-corners-description") + checked: Settings.data.bar.outerCorners + visible: Settings.data.bar.barType === "simple" + defaultValue: Settings.getDefaultValue("bar.outerCorners") + onToggled: checked => Settings.data.bar.outerCorners = checked + } + + ColumnLayout { + visible: Settings.data.bar.barType === "framed" + spacing: Style.marginS + Layout.fillWidth: true + + NLabel { + label: I18n.tr("panels.bar.appearance-frame-settings-label") + description: I18n.tr("panels.bar.appearance-frame-settings-description") + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginL + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-frame-thickness") + from: 4 + to: 24 + stepSize: 1 + showReset: true + value: Settings.data.bar.frameThickness + defaultValue: Settings.getDefaultValue("bar.frameThickness") + onMoved: value => Settings.data.bar.frameThickness = value + text: Settings.data.bar.frameThickness + "px" + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-frame-radius") + from: 4 + to: 24 + stepSize: 1 + showReset: true + value: Settings.data.bar.frameRadius + defaultValue: Settings.getDefaultValue("bar.frameRadius") + onMoved: value => Settings.data.bar.frameRadius = value + text: Settings.data.bar.frameRadius + "px" + } + } + } + + ColumnLayout { + visible: Settings.data.bar.barType === "floating" + spacing: Style.marginL + Layout.fillWidth: true + + NDivider { + Layout.fillWidth: true + } + + NSpinBox { + label: I18n.tr("panels.bar.appearance-margins-vertical") + description: I18n.tr("panels.bar.appearance-margins-description") + from: 0 + to: 500 + suffix: "px" + value: Settings.data.bar.marginVertical + defaultValue: Settings.getDefaultValue("bar.marginVertical") + onValueChanged: Settings.data.bar.marginVertical = value + } + + NSpinBox { + label: I18n.tr("panels.bar.appearance-margins-horizontal") + description: I18n.tr("panels.bar.appearance-margins-description") + from: 0 + to: 500 + suffix: "px" + value: Settings.data.bar.marginHorizontal + defaultValue: Settings.getDefaultValue("bar.marginHorizontal") + onValueChanged: Settings.data.bar.marginHorizontal = value + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginS + visible: Settings.data.bar.displayMode === "auto_hide" + } + + ColumnLayout { + visible: Settings.data.bar.displayMode === "auto_hide" + spacing: Style.marginS + Layout.fillWidth: true + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-auto-hide-delay-label") + description: I18n.tr("panels.bar.appearance-auto-hide-delay-description") + from: 100 + to: 2000 + stepSize: 100 + showReset: true + value: Settings.data.bar.autoHideDelay + defaultValue: Settings.getDefaultValue("bar.autoHideDelay") + onMoved: value => Settings.data.bar.autoHideDelay = value + text: Settings.data.bar.autoHideDelay + "ms" + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-auto-show-delay-label") + description: I18n.tr("panels.bar.appearance-auto-show-delay-description") + from: 0 + to: 500 + stepSize: 50 + showReset: true + value: Settings.data.bar.autoShowDelay + defaultValue: Settings.getDefaultValue("bar.autoShowDelay") + onMoved: value => Settings.data.bar.autoShowDelay = value + text: Settings.data.bar.autoShowDelay + "ms" + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-show-on-workspace-switch-label") + description: I18n.tr("panels.bar.appearance-show-on-workspace-switch-description") + checked: Settings.data.bar.showOnWorkspaceSwitch + defaultValue: Settings.getDefaultValue("bar.showOnWorkspaceSwitch") + onToggled: checked => Settings.data.bar.showOnWorkspaceSwitch = checked + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Bar/BarTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Bar/BarTab.qml new file mode 100644 index 0000000..3cdf719 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Bar/BarTab.qml @@ -0,0 +1,234 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Noctalia +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + // Helper functions to update arrays immutably + function addMonitor(list, name) { + const arr = (list || []).slice(); + if (!arr.includes(name)) + arr.push(name); + return arr; + } + function removeMonitor(list, name) { + return (list || []).filter(function (n) { + return n !== name; + }); + } + + // Signal functions for widgets sub-tab (global widgets only). + // These intentionally edit Settings.data.bar.widgets (global defaults), + // not per-screen overrides. Per-screen editing is handled by MonitorWidgetsConfig.qml. + function _addWidgetToSection(widgetId, section) { + var newWidget = { + "id": widgetId + }; + if (BarWidgetRegistry.widgetHasUserSettings(widgetId)) { + var metadata = BarWidgetRegistry.widgetMetadata[widgetId]; + if (metadata) { + Object.keys(metadata).forEach(function (key) { + newWidget[key] = metadata[key]; + }); + } + } + Settings.data.bar.widgets[section].push(newWidget); + BarService.widgetsRevision++; + } + + function _removeWidgetFromSection(section, index) { + var widgets = Settings.data.bar.widgets; + if (index >= 0 && index < widgets[section].length) { + var newArray = widgets[section].slice(); + var removedWidgets = newArray.splice(index, 1); + widgets[section] = newArray; + BarService.widgetsRevision++; + + if (removedWidgets[0].id === "ControlCenter" && BarService.lookupWidget("ControlCenter") === undefined) { + ToastService.showWarning(I18n.tr("toast.missing-control-center.label"), I18n.tr("toast.missing-control-center.description"), 6000); + } + } + } + + function _reorderWidgetInSection(section, fromIndex, toIndex) { + var widgets = Settings.data.bar.widgets; + if (fromIndex >= 0 && fromIndex < widgets[section].length && toIndex >= 0 && toIndex < widgets[section].length) { + var newArray = widgets[section].slice(); + var item = newArray[fromIndex]; + newArray.splice(fromIndex, 1); + newArray.splice(toIndex, 0, item); + widgets[section] = newArray; + BarService.widgetsRevision++; + } + } + + // Note: _updateWidgetSettingsInSection does NOT increment revision + // because it only changes settings, not widget structure + function _updateWidgetSettingsInSection(section, index, settings) { + Settings.data.bar.widgets[section][index] = settings; + } + + function _moveWidgetBetweenSections(fromSection, index, toSection) { + var widgets = Settings.data.bar.widgets; + if (index >= 0 && index < widgets[fromSection].length) { + var widget = widgets[fromSection][index]; + var sourceArray = widgets[fromSection].slice(); + sourceArray.splice(index, 1); + widgets[fromSection] = sourceArray; + var targetArray = widgets[toSection].slice(); + targetArray.push(widget); + widgets[toSection] = targetArray; + BarService.widgetsRevision++; + Logger.d("BarTab", "_moveWidgetBetweenSections: revision now", BarService.widgetsRevision); + } + } + + function getWidgetLocations(widgetId) { + if (!BarService) + return []; + const instances = BarService.getAllRegisteredWidgets(); + const locations = {}; + for (var i = 0; i < instances.length; i++) { + if (instances[i].widgetId === widgetId) { + const section = instances[i].section; + if (section === "left") + locations["arrow-bar-to-left"] = true; + else if (section === "center") + locations["layout-columns"] = true; + else if (section === "right") + locations["arrow-bar-to-right"] = true; + } + } + return Object.keys(locations); + } + + function createBadges(isPlugin, locations) { + const badges = []; + if (isPlugin) { + badges.push({ + "icon": "plugin", + "color": Color.mSecondary + }); + } + locations.forEach(function (location) { + badges.push({ + "icon": location, + "color": Color.mOnSurfaceVariant + }); + }); + return badges; + } + + function updateAvailableWidgetsModel() { + availableWidgets.clear(); + const widgets = BarWidgetRegistry.getAvailableWidgets(); + widgets.forEach(entry => { + const isPlugin = BarWidgetRegistry.isPluginWidget(entry); + let displayName = entry; + if (isPlugin) { + const pluginId = entry.replace("plugin:", ""); + const manifest = PluginRegistry.getPluginManifest(pluginId); + if (manifest && manifest.name) { + displayName = manifest.name; + } else { + displayName = pluginId; + } + } + availableWidgets.append({ + "key": entry, + "name": displayName, + "badges": createBadges(isPlugin, getWidgetLocations(entry)) + }); + }); + } + + ListModel { + id: availableWidgets + } + + Component.onCompleted: { + Qt.callLater(updateAvailableWidgetsModel); + } + + Connections { + target: BarService + function onActiveWidgetsChanged() { + updateAvailableWidgetsModel(); + } + } + + Connections { + target: BarWidgetRegistry + function onPluginWidgetRegistryUpdated() { + updateAvailableWidgetsModel(); + } + } + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.appearance") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.widgets") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + NTabButton { + text: I18n.tr("common.behavior") + tabIndex: 2 + checked: subTabBar.currentIndex === 2 + } + NTabButton { + text: I18n.tr("common.monitors") + tabIndex: 3 + checked: subTabBar.currentIndex === 3 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginS + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + AppearanceSubTab {} + WidgetsSubTab { + availableWidgets: availableWidgets + addWidgetToSection: root._addWidgetToSection + removeWidgetFromSection: root._removeWidgetFromSection + reorderWidgetInSection: root._reorderWidgetInSection + updateWidgetSettingsInSection: root._updateWidgetSettingsInSection + moveWidgetBetweenSections: root._moveWidgetBetweenSections + onOpenPluginSettings: manifest => pluginSettingsDialog.openPluginSettings(manifest) + } + BehaviorSubTab {} + MonitorsSubTab { + addMonitor: root.addMonitor + removeMonitor: root.removeMonitor + } + } + + NPluginSettingsPopup { + id: pluginSettingsDialog + parent: Overlay.overlay + showToastOnSave: false + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Bar/BehaviorSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Bar/BehaviorSubTab.qml new file mode 100644 index 0000000..326ae71 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Bar/BehaviorSubTab.qml @@ -0,0 +1,172 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.Compositor +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + readonly property string effectiveWheelAction: Settings.data.bar.mouseWheelAction || "none" + readonly property string effectiveMiddleClickAction: Settings.data.bar.middleClickAction || "none" + readonly property string effectiveRightClickAction: Settings.data.bar.rightClickAction || "controlCenter" + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.bar.behavior-workspace-scroll-label") + description: I18n.tr("panels.bar.behavior-workspace-scroll-description") + model: { + var items = [ + { + "key": "none", + "name": I18n.tr("common.none") + }, + { + "key": "volume", + "name": I18n.tr("common.volume") + }, + { + "key": "workspace", + "name": I18n.tr("panels.bar.behavior-workspace-scroll-option-workspace") + } + ]; + if (CompositorService.isNiri) { + items.push({ + "key": "content", + "name": I18n.tr("panels.bar.behavior-workspace-scroll-option-content") + }); + } + return items; + } + currentKey: root.effectiveWheelAction + defaultValue: Settings.getDefaultValue("bar.mouseWheelAction") + onSelected: key => Settings.data.bar.mouseWheelAction = key + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.general.reverse-scrolling-label") + description: I18n.tr("panels.general.reverse-scrolling-description") + checked: Settings.data.bar.reverseScroll + defaultValue: Settings.getDefaultValue("bar.reverseScroll") + onToggled: checked => Settings.data.bar.reverseScroll = checked + visible: Settings.data.bar.mouseWheelAction !== "none" + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.bar.behavior-wheel-wrap-label") + description: I18n.tr("panels.bar.behavior-wheel-wrap-description") + checked: Settings.data.bar.mouseWheelWrap + defaultValue: Settings.getDefaultValue("bar.mouseWheelWrap") + onToggled: checked => Settings.data.bar.mouseWheelWrap = checked + visible: Settings.data.bar.mouseWheelAction === "workspace" + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.bar.behavior-middle-click-label") + description: I18n.tr("panels.bar.behavior-middle-click-description") + model: [ + { + "key": "none", + "name": I18n.tr("common.none") + }, + { + "key": "controlCenter", + "name": I18n.tr("tooltips.open-control-center") + }, + { + "key": "settings", + "name": I18n.tr("tooltips.open-settings") + }, + { + "key": "launcherPanel", + "name": I18n.tr("actions.open-launcher") + }, + { + "key": "command", + "name": I18n.tr("actions.run-custom-command") + } + ] + currentKey: root.effectiveMiddleClickAction + defaultValue: Settings.getDefaultValue("bar.middleClickAction") + onSelected: key => Settings.data.bar.middleClickAction = key + } + + NTextInput { + Layout.fillWidth: true + label: I18n.tr("panels.bar.behavior-middle-click-command-label") + description: I18n.tr("panels.bar.behavior-middle-click-command-description") + placeholderText: I18n.tr("panels.bar.behavior-middle-click-command-placeholder") + text: Settings.data.bar.middleClickCommand + fontFamily: Settings.data.ui.fontFixed + onTextChanged: Settings.data.bar.middleClickCommand = text + visible: Settings.data.bar.middleClickAction === "command" + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.bar.behavior-middle-click-follow-mouse-label") + description: I18n.tr("panels.bar.behavior-middle-click-follow-mouse-description") + checked: Settings.data.bar.middleClickFollowMouse + defaultValue: Settings.getDefaultValue("bar.middleClickFollowMouse") + onToggled: checked => Settings.data.bar.middleClickFollowMouse = checked + visible: Settings.data.bar.middleClickAction !== "none" && Settings.data.bar.middleClickAction !== "command" && !(Settings.data.bar.middleClickAction === "settings" && Settings.data.ui.settingsPanelMode === "window") + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.bar.behavior-right-click-label") + description: I18n.tr("panels.bar.behavior-right-click-description") + model: [ + { + "key": "none", + "name": I18n.tr("common.none") + }, + { + "key": "controlCenter", + "name": I18n.tr("tooltips.open-control-center") + }, + { + "key": "settings", + "name": I18n.tr("tooltips.open-settings") + }, + { + "key": "launcherPanel", + "name": I18n.tr("actions.open-launcher") + }, + { + "key": "command", + "name": I18n.tr("actions.run-custom-command") + } + ] + currentKey: root.effectiveRightClickAction + defaultValue: Settings.getDefaultValue("bar.rightClickAction") + onSelected: key => Settings.data.bar.rightClickAction = key + } + + NTextInput { + Layout.fillWidth: true + label: I18n.tr("panels.bar.behavior-right-click-command-label") + description: I18n.tr("panels.bar.behavior-right-click-command-description") + placeholderText: I18n.tr("panels.bar.behavior-right-click-command-placeholder") + text: Settings.data.bar.rightClickCommand + fontFamily: Settings.data.ui.fontFixed + onTextChanged: Settings.data.bar.rightClickCommand = text + visible: Settings.data.bar.rightClickAction === "command" + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.bar.behavior-right-click-follow-mouse-label") + description: I18n.tr("panels.bar.behavior-right-click-follow-mouse-description") + checked: Settings.data.bar.rightClickFollowMouse + defaultValue: Settings.getDefaultValue("bar.rightClickFollowMouse") + onToggled: checked => Settings.data.bar.rightClickFollowMouse = checked + visible: Settings.data.bar.rightClickAction !== "none" && Settings.data.bar.rightClickAction !== "command" && !(Settings.data.bar.rightClickAction === "settings" && Settings.data.ui.settingsPanelMode === "window") + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Bar/MonitorsSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Bar/MonitorsSubTab.qml new file mode 100644 index 0000000..c6ad380 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Bar/MonitorsSubTab.qml @@ -0,0 +1,276 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import "../../Bar" as BarSettings +import qs.Commons +import qs.Services.Compositor +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + property var addMonitor + property var removeMonitor + + NText { + text: I18n.tr("panels.bar.monitors-desc-new") + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + // Monitor cards + Repeater { + model: Quickshell.screens || [] + delegate: NBox { + id: monitorCard + Layout.fillWidth: true + implicitHeight: cardContent.implicitHeight + Style.margin2L + color: Color.mSurface + + required property var modelData + readonly property string screenName: modelData.name || "Unknown" + readonly property real compositorScale: { + const info = CompositorService.displayScales[screenName]; + return (info && info.scale) ? info.scale : 1.0; + } + readonly property bool barEnabled: (Settings.data.bar.monitors || []).indexOf(screenName) !== -1 + readonly property bool hasOverride: Settings.hasScreenOverride(screenName) + + // Track if override is enabled (controls both visibility AND whether overrides are applied) + readonly property bool overrideEnabled: Settings.isScreenOverrideEnabled(screenName) + + // Get effective values for this screen + readonly property string effectivePosition: Settings.getBarPositionForScreen(screenName) + readonly property string effectiveDensity: Settings.getBarDensityForScreen(screenName) + + ColumnLayout { + id: cardContent + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + + // Header: Monitor name and specs + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXXS + + NText { + Layout.fillWidth: true + text: monitorCard.screenName + pointSize: Style.fontSizeM + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: { + return I18n.tr("system.monitor-description", { + "model": monitorCard.modelData.model || I18n.tr("common.unknown"), + "width": Math.round(monitorCard.modelData.width * monitorCard.compositorScale), + "height": Math.round(monitorCard.modelData.height * monitorCard.compositorScale), + "scale": monitorCard.compositorScale + }); + } + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + + // Enable bar toggle + NToggle { + Layout.fillWidth: true + checked: monitorCard.barEnabled + onToggled: checked => { + if (checked) { + Settings.data.bar.monitors = root.addMonitor(Settings.data.bar.monitors, monitorCard.screenName); + } else { + Settings.data.bar.monitors = root.removeMonitor(Settings.data.bar.monitors, monitorCard.screenName); + } + } + } + } + + NDivider { + Layout.fillWidth: true + visible: Settings.data.bar.monitors.includes(monitorCard.screenName) + } + + // Override section (only visible when bar is enabled) + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS + visible: monitorCard.barEnabled + + // Override toggle + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.bar.monitor-override-settings") + description: I18n.tr("panels.bar.monitor-override-settings-description") + checked: monitorCard.overrideEnabled + onToggled: checked => { + Settings.setScreenOverride(monitorCard.screenName, "enabled", checked); + BarService.widgetsRevision++; + } + } + + // Override controls (only visible when override toggle is on) + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS + visible: monitorCard.overrideEnabled + + // Position override + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-position-label") + description: I18n.tr("panels.bar.appearance-position-description") + model: [ + { + "key": "top", + "name": I18n.tr("positions.top") + }, + { + "key": "bottom", + "name": I18n.tr("positions.bottom") + }, + { + "key": "left", + "name": I18n.tr("positions.left") + }, + { + "key": "right", + "name": I18n.tr("positions.right") + } + ] + currentKey: monitorCard.effectivePosition + onSelected: key => Settings.setScreenOverride(monitorCard.screenName, "position", key) + } + } + + // Density override + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-density-label") + description: I18n.tr("panels.bar.appearance-density-description") + model: [ + { + "key": "mini", + "name": I18n.tr("options.bar.density-mini") + }, + { + "key": "compact", + "name": I18n.tr("options.bar.density-compact") + }, + { + "key": "default", + "name": I18n.tr("options.bar.density-default") + }, + { + "key": "comfortable", + "name": I18n.tr("options.bar.density-comfortable") + }, + { + "key": "spacious", + "name": I18n.tr("options.bar.density-spacious") + } + ] + currentKey: monitorCard.effectiveDensity + onSelected: key => Settings.setScreenOverride(monitorCard.screenName, "density", key) + } + } + + // DisplayMode override + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("common.display-mode") + description: I18n.tr("panels.bar.appearance-display-mode-description") + model: [ + { + "key": "always_visible", + "name": I18n.tr("hide-modes.visible") + }, + { + "key": "non_exclusive", + "name": I18n.tr("hide-modes.non-exclusive") + }, + { + "key": "auto_hide", + "name": I18n.tr("hide-modes.auto-hide") + } + ] + currentKey: Settings.getBarDisplayModeForScreen(monitorCard.screenName) + onSelected: key => Settings.setScreenOverride(monitorCard.screenName, "displayMode", key) + } + } + + // Widgets configuration button and Reset all + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NButton { + id: widgetConfigButton + property bool expanded: false + Layout.fillWidth: true + fontSize: Style.fontSizeS + text: I18n.tr("panels.bar.monitor-configure-widgets") + icon: expanded ? "chevron-up" : "layout-grid" + onClicked: expanded = !expanded + } + + NButton { + visible: Settings.hasScreenOverride(monitorCard.screenName, "widgets") + Layout.fillWidth: true + fontSize: Style.fontSizeS + text: I18n.tr("panels.bar.use-global-widgets") + icon: "refresh" + onClicked: { + Settings.clearScreenOverride(monitorCard.screenName, "widgets"); + BarService.widgetsRevision++; + } + } + + NButton { + Layout.fillWidth: true + fontSize: Style.fontSizeS + text: I18n.tr("panels.bar.monitor-reset-all") + icon: "restore" + onClicked: { + Settings.clearScreenOverride(monitorCard.screenName); + BarService.widgetsRevision++; + } + } + } + + // Inline widget configuration + BarSettings.MonitorWidgetsConfig { + visible: widgetConfigButton.expanded + screen: monitorCard.modelData + Layout.fillWidth: true + Layout.topMargin: Style.marginS + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Bar/WidgetsSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Bar/WidgetsSubTab.qml new file mode 100644 index 0000000..5eaf510 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Bar/WidgetsSubTab.qml @@ -0,0 +1,97 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + property var availableWidgets + property var addWidgetToSection + property var removeWidgetFromSection + property var reorderWidgetInSection + property var updateWidgetSettingsInSection + property var moveWidgetBetweenSections + + signal openPluginSettings(var manifest) + + // This sub-tab edits the global default widget configuration (Settings.data.bar.widgets). + // Per-screen widget overrides are edited in MonitorWidgetsConfig.qml (Monitors sub-tab). + + // determine bar orientation + readonly property string barPosition: Settings.data.bar.position + readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" + + function getSectionIcons() { + return { + "left": "arrow-bar-to-up", + "center": "layout-distribute-horizontal", + "right": "arrow-bar-to-down" + }; + } + + NText { + text: I18n.tr("panels.bar.widgets-desc") + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + // Left Section + NSectionEditor { + sectionName: root.barIsVertical ? I18n.tr("positions.top") : I18n.tr("positions.left") + sectionId: "left" + barIsVertical: root.barIsVertical + settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml") + widgetRegistry: BarWidgetRegistry + widgetModel: Settings.data.bar.widgets.left + sectionIcons: root.getSectionIcons() + availableWidgets: root.availableWidgets + onAddWidget: (widgetId, section) => root.addWidgetToSection(widgetId, section) + onRemoveWidget: (section, index) => root.removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => root.reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettings: (section, index, settings) => root.updateWidgetSettingsInSection(section, index, settings) + onMoveWidget: (fromSection, index, toSection) => root.moveWidgetBetweenSections(fromSection, index, toSection) + onOpenPluginSettingsRequested: manifest => root.openPluginSettings(manifest) + } + + // Center Section + NSectionEditor { + sectionName: I18n.tr("positions.center") + sectionId: "center" + barIsVertical: root.barIsVertical + settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml") + widgetRegistry: BarWidgetRegistry + widgetModel: Settings.data.bar.widgets.center + sectionIcons: root.getSectionIcons() + availableWidgets: root.availableWidgets + onAddWidget: (widgetId, section) => root.addWidgetToSection(widgetId, section) + onRemoveWidget: (section, index) => root.removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => root.reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettings: (section, index, settings) => root.updateWidgetSettingsInSection(section, index, settings) + onMoveWidget: (fromSection, index, toSection) => root.moveWidgetBetweenSections(fromSection, index, toSection) + onOpenPluginSettingsRequested: manifest => root.openPluginSettings(manifest) + } + + // Right Section + NSectionEditor { + sectionName: root.barIsVertical ? I18n.tr("positions.bottom") : I18n.tr("positions.right") + sectionId: "right" + barIsVertical: root.barIsVertical + settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml") + widgetRegistry: BarWidgetRegistry + widgetModel: Settings.data.bar.widgets.right + sectionIcons: root.getSectionIcons() + availableWidgets: root.availableWidgets + onAddWidget: (widgetId, section) => root.addWidgetToSection(widgetId, section) + onRemoveWidget: (section, index) => root.removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => root.reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettings: (section, index, settings) => root.updateWidgetSettingsInSection(section, index, settings) + onMoveWidget: (fromSection, index, toSection) => root.moveWidgetBetweenSections(fromSection, index, toSection) + onOpenPluginSettingsRequested: manifest => root.openPluginSettings(manifest) + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ColorScheme/ColorSchemeTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ColorScheme/ColorSchemeTab.qml new file mode 100644 index 0000000..3bd4543 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ColorScheme/ColorSchemeTab.qml @@ -0,0 +1,104 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import "." +import qs.Commons +import qs.Services.System +import qs.Services.Theming +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + // Time dropdown options (00:00 .. 23:30) + ListModel { + id: timeOptions + } + + property var screen + + function populateTimeOptions() { + for (var h = 0; h < 24; h++) { + for (var m = 0; m < 60; m += 30) { + var hh = ("0" + h).slice(-2); + var mm = ("0" + m).slice(-2); + var key = hh + ":" + mm; + timeOptions.append({ + "key": key, + "name": key + }); + } + } + } + + Component.onCompleted: { + Qt.callLater(populateTimeOptions); + } + + // Download popup + Loader { + id: downloadPopupLoader + active: false + sourceComponent: SchemeDownloader { + parent: Overlay.overlay + } + + property bool pendingOpen: false + + function open() { + pendingOpen = true; + active = true; + if (item) { + item.open(); + pendingOpen = false; + } + } + + onItemChanged: { + if (item && pendingOpen) { + item.open(); + pendingOpen = false; + } + } + } + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.colors") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.templates") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + ColorsSubTab { + screen: root.screen + timeOptions: timeOptions + onOpenDownloadPopup: downloadPopupLoader.open() + } + TemplatesSubTab {} + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ColorScheme/ColorsSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ColorScheme/ColorsSubTab.qml new file mode 100644 index 0000000..37891d1 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ColorScheme/ColorsSubTab.qml @@ -0,0 +1,458 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.System +import qs.Services.Theming +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + property var timeOptions + property var schemeColorsCache: ({}) + property int cacheVersion: 0 + property var screen + + signal openDownloadPopup + + function extractSchemeName(schemePath) { + var pathParts = schemePath.split("/"); + var filename = pathParts[pathParts.length - 1]; + var schemeName = filename.replace(".json", ""); + + if (schemeName === "Noctalia-default") { + schemeName = "Noctalia (default)"; + } else if (schemeName === "Noctalia-legacy") { + schemeName = "Noctalia (legacy)"; + } else if (schemeName === "Tokyo-Night") { + schemeName = "Tokyo Night"; + } else if (schemeName === "Rosepine") { + schemeName = "Rose Pine"; + } + + return schemeName; + } + + function getSchemeColor(schemeName, colorKey) { + var _ = cacheVersion; + + if (schemeColorsCache[schemeName]) { + var entry = schemeColorsCache[schemeName]; + var variant = entry; + + if (entry.dark || entry.light) { + variant = Settings.data.colorSchemes.darkMode ? (entry.dark || entry.light) : (entry.light || entry.dark); + } + + if (variant && variant[colorKey]) { + return variant[colorKey]; + } + } + + if (colorKey === "mSurface") + return Color.mSurfaceVariant; + if (colorKey === "mPrimary") + return Color.mPrimary; + if (colorKey === "mSecondary") + return Color.mSecondary; + if (colorKey === "mTertiary") + return Color.mTertiary; + if (colorKey === "mError") + return Color.mError; + return Color.mOnSurfaceVariant; + } + + function schemeLoaded(schemeName, jsonData) { + var value = jsonData || {}; + schemeColorsCache[schemeName] = value; + cacheVersion++; + } + + Connections { + target: ColorSchemeService + function onSchemesChanged() { + root.schemeColorsCache = {}; + root.cacheVersion++; + } + } + + Item { + id: fileLoaders + visible: false + + Repeater { + model: ColorSchemeService.schemes + delegate: Item { + FileView { + path: modelData + blockLoading: false + onLoaded: { + var schemeName = root.extractSchemeName(path); + + try { + var jsonData = JSON.parse(text()); + root.schemeLoaded(schemeName, jsonData); + } catch (e) { + Logger.w("ColorSchemeTab", "Failed to parse JSON for scheme:", schemeName, e); + root.schemeLoaded(schemeName, null); + } + } + } + } + } + } + + NToggle { + label: I18n.tr("tooltips.switch-to-dark-mode") + description: I18n.tr("panels.color-scheme.dark-mode-switch-description") + checked: Settings.data.colorSchemes.darkMode + onToggled: checked => { + Settings.data.colorSchemes.darkMode = checked; + root.cacheVersion++; + } + } + + NToggle { + label: I18n.tr("panels.color-scheme.sync-gsettings-label") + description: I18n.tr("panels.color-scheme.sync-gsettings-description") + checked: Settings.data.colorSchemes.syncGsettings + onToggled: checked => { + Settings.data.colorSchemes.syncGsettings = checked; + if (checked) + ColorSchemeService.pushSystemColorScheme(); + } + } + + NComboBox { + label: I18n.tr("panels.color-scheme.dark-mode-mode-label") + description: I18n.tr("panels.color-scheme.dark-mode-mode-description") + + model: [ + { + "name": I18n.tr("panels.color-scheme.dark-mode-mode-off"), + "key": "off" + }, + { + "name": I18n.tr("panels.color-scheme.dark-mode-mode-manual"), + "key": "manual" + }, + { + "name": I18n.tr("common.location"), + "key": "location" + } + ] + + currentKey: Settings.data.colorSchemes.schedulingMode + + onSelected: key => { + Settings.data.colorSchemes.schedulingMode = key; + AppThemeService.generate(); + } + } + + ColumnLayout { + spacing: Style.marginS + visible: Settings.data.colorSchemes.schedulingMode === "manual" + + NLabel { + label: I18n.tr("panels.display.night-light-manual-schedule-label") + description: I18n.tr("panels.display.night-light-manual-schedule-description") + } + + RowLayout { + Layout.fillWidth: false + spacing: Style.marginS + + NText { + text: I18n.tr("panels.display.night-light-manual-schedule-sunrise") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + + NComboBox { + model: root.timeOptions + currentKey: Settings.data.colorSchemes.manualSunrise + placeholder: I18n.tr("panels.display.night-light-manual-schedule-select-start") + onSelected: key => Settings.data.colorSchemes.manualSunrise = key + minimumWidth: 120 + } + + Item { + Layout.preferredWidth: 20 + } + + NText { + text: I18n.tr("panels.display.night-light-manual-schedule-sunset") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + + NComboBox { + model: root.timeOptions + currentKey: Settings.data.colorSchemes.manualSunset + placeholder: I18n.tr("panels.display.night-light-manual-schedule-select-stop") + onSelected: key => Settings.data.colorSchemes.manualSunset = key + minimumWidth: 120 + } + } + } + + NDivider { + Layout.fillWidth: true + } + + NToggle { + label: I18n.tr("panels.color-scheme.color-source-use-wallpaper-colors-label") + description: I18n.tr("panels.color-scheme.color-source-use-wallpaper-colors-description") + checked: Settings.data.colorSchemes.useWallpaperColors + onToggled: checked => { + Settings.data.colorSchemes.useWallpaperColors = checked; + if (checked) { + AppThemeService.generate(); + } else { + ToastService.showNotice(I18n.tr("toast.wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.disabled"), "settings-color-scheme"); + if (Settings.data.colorSchemes.predefinedScheme) { + ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme); + } + } + } + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.color-scheme.wallpaper-monitor-source-label") + description: I18n.tr("panels.color-scheme.wallpaper-monitor-source-description") + enabled: Settings.data.colorSchemes.useWallpaperColors + model: { + var m = []; + if (Quickshell.screens) { + for (var i = 0; i < Quickshell.screens.length; i++) { + var screen = Quickshell.screens[i]; + var name = screen.name; + var displayName = name + " (" + screen.width + "x" + screen.height + ")"; + m.push({ + "key": name, + "name": displayName + }); + } + } + return m; + } + currentKey: Settings.data.colorSchemes.monitorForColors || (screen ? screen.name : "") + onSelected: key => { + Settings.data.colorSchemes.monitorForColors = key; + AppThemeService.generate(); + } + defaultValue: "" + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.color-scheme.wallpaper-method-label") + description: I18n.tr("panels.color-scheme.wallpaper-method-description") + enabled: Settings.data.colorSchemes.useWallpaperColors + model: TemplateProcessor.schemeTypes + currentKey: Settings.data.colorSchemes.generationMethod + onSelected: key => { + Settings.data.colorSchemes.generationMethod = key; + AppThemeService.generate(); + } + } + + NBox { + visible: Settings.data.colorSchemes.useWallpaperColors + Layout.fillWidth: true + implicitHeight: descriptionColumn.implicitHeight + Style.margin2L + color: Color.mSurface + + Column { + id: descriptionColumn + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Style.marginL + spacing: Style.marginM + + NText { + width: parent.width + wrapMode: Text.WordWrap + text: I18n.tr("panels.color-scheme.method-description." + Settings.data.colorSchemes.generationMethod) + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + + Row { + id: colorPreviewRow + spacing: Style.marginS + + property int diameter: 16 * Style.uiScaleRatio + + Repeater { + model: [Color.mPrimary, Color.mSecondary, Color.mTertiary, Color.mError] + + Rectangle { + width: colorPreviewRow.diameter + height: colorPreviewRow.diameter + radius: width * 0.5 + color: modelData + } + } + } + } + } + + NDivider { + Layout.fillWidth: true + } + + ColumnLayout { + spacing: Style.marginM + Layout.fillWidth: true + enabled: !Settings.data.colorSchemes.useWallpaperColors + + NHeader { + label: I18n.tr("panels.color-scheme.predefined-title") + description: I18n.tr("panels.color-scheme.predefined-desc") + Layout.fillWidth: true + } + + GridLayout { + columns: 2 + rowSpacing: Style.marginM + columnSpacing: Style.marginM + Layout.fillWidth: true + + Repeater { + model: ColorSchemeService.schemes + + Rectangle { + id: schemeItem + + property string schemePath: modelData + property string schemeName: root.extractSchemeName(modelData) + + opacity: enabled ? 1.0 : 0.6 + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + height: 50 * Style.uiScaleRatio + radius: Style.radiusS + color: root.getSchemeColor(schemeName, "mSurface") + border.width: Style.borderL + border.color: { + if ((Settings.data.colorSchemes.predefinedScheme === schemeName) && schemeItem.enabled) { + return Color.mSecondary; + } + if (itemMouseArea.containsMouse) { + return Color.mHover; + } + return Color.mOutline; + } + + RowLayout { + id: scheme + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginS + + NText { + text: schemeItem.schemeName + pointSize: Style.fontSizeS + color: Color.mOnSurface + Layout.fillWidth: true + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + maximumLineCount: 1 + } + + property int diameter: 16 * Style.uiScaleRatio + + Rectangle { + width: scheme.diameter + height: scheme.diameter + radius: scheme.diameter * 0.5 + color: root.getSchemeColor(schemeItem.schemeName, "mPrimary") + } + + Rectangle { + width: scheme.diameter + height: scheme.diameter + radius: scheme.diameter * 0.5 + color: root.getSchemeColor(schemeItem.schemeName, "mSecondary") + } + + Rectangle { + width: scheme.diameter + height: scheme.diameter + radius: scheme.diameter * 0.5 + color: root.getSchemeColor(schemeItem.schemeName, "mTertiary") + } + + Rectangle { + width: scheme.diameter + height: scheme.diameter + radius: scheme.diameter * 0.5 + color: root.getSchemeColor(schemeItem.schemeName, "mError") + } + } + + MouseArea { + id: itemMouseArea + anchors.fill: parent + enabled: schemeItem.enabled + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + Settings.data.colorSchemes.useWallpaperColors = false; + Logger.i("ColorSchemeTab", "Disabled wallpaper colors"); + + Settings.data.colorSchemes.predefinedScheme = schemeItem.schemeName; + ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme); + } + } + + Rectangle { + visible: (Settings.data.colorSchemes.predefinedScheme === schemeItem.schemeName) && schemeItem.enabled + anchors.right: parent.right + anchors.top: parent.top + anchors.rightMargin: 0 + anchors.topMargin: -3 + width: 20 + height: 20 + radius: Math.min(Style.radiusL, width / 2) + color: Color.mSecondary + border.width: Style.borderS + border.color: Color.mOnSecondary + + NIcon { + icon: "check" + pointSize: Style.fontSizeXS + color: Color.mOnSecondary + anchors.centerIn: parent + } + } + + Behavior on border.color { + ColorAnimation { + duration: Style.animationNormal + } + } + } + } + } + + NButton { + text: I18n.tr("panels.color-scheme.download-button") + icon: "download" + onClicked: root.openDownloadPopup() + Layout.alignment: Qt.AlignRight + Layout.topMargin: Style.marginS + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ColorScheme/SchemeDownloader.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ColorScheme/SchemeDownloader.qml new file mode 100644 index 0000000..33defbd --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ColorScheme/SchemeDownloader.qml @@ -0,0 +1,972 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.Theming +import qs.Services.UI +import qs.Widgets + +Popup { + id: root + + property var availableSchemes: [] + property bool fetching: false + property bool downloading: false + property bool hasInitialData: false // Track if we've loaded data at least once + property string downloadError: "" + property string downloadingScheme: "" + property string pendingApplyScheme: "" // Scheme name to apply after reload + property string lastStderrOutput: "" // Store stderr from download process + property real lastApiFetchTime: 0 // Track when we last fetched from API to prevent rapid calls + property int minApiFetchInterval: 60 // Minimum seconds between API fetches (1 minute) + + // Cache for remote scheme colors + property var schemeColorsCache: ({}) + property int cacheVersion: 0 + + // Cache for available schemes list (uses ShellState singleton) + property int schemesCacheUpdateFrequency: 2 * 60 * 60 // 2 hours in seconds + + // Cache for repo branch info (to reduce API calls during downloads) + property string cachedBranch: "main" + property string cachedBranchSha: "" + + width: Math.max(500, contentColumn.implicitWidth + Style.margin2XL) + height: Math.min(800, contentColumn.implicitHeight + Style.margin2XL) + padding: Style.marginXL + modal: true + closePolicy: Popup.NoAutoClose + dim: false + anchors.centerIn: parent + + // Helper function to get color from cached scheme data + function getSchemeColor(schemeName, colorKey) { + var _ = cacheVersion; // Create dependency + + if (schemeColorsCache[schemeName]) { + var entry = schemeColorsCache[schemeName]; + var variant = entry; + + // Check if scheme has dark/light variants + if (entry.dark || entry.light) { + variant = Settings.data.colorSchemes.darkMode ? (entry.dark || entry.light) : (entry.light || entry.dark); + } + + if (variant && variant[colorKey]) { + return variant[colorKey]; + } + } + + // Return visible defaults while loading + var defaults = { + "mSurface": Color.mSurfaceVariant, + "mPrimary": Color.mPrimary, + "mSecondary": Color.mSecondary, + "mTertiary": Color.mTertiary, + "mError": Color.mError, + "mOnSurface": Color.mOnSurfaceVariant + }; + return defaults[colorKey] || Color.mOnSurfaceVariant; + } + + // Colors are now provided directly in the registry, no need to fetch individual files + function fetchSchemeColors(scheme) { + return; + } + + background: Rectangle { + color: Color.mSurface + radius: Style.radiusL + border.color: Color.mPrimary + border.width: Style.borderM + } + + function loadSchemesFromCache() { + try { + const now = Time.timestamp; + const cacheData = ShellState.getColorSchemesList(); + const cachedSchemes = cacheData.schemes || []; + const cachedTimestamp = cacheData.timestamp || 0; + + // Check if cache is expired or missing + if (!cachedTimestamp || (now >= cachedTimestamp + schemesCacheUpdateFrequency)) { + // Migration is now handled in Settings.qml + + // Only fetch from API if we haven't fetched recently (prevent rapid repeated calls) + const timeSinceLastFetch = now - lastApiFetchTime; + if (timeSinceLastFetch >= minApiFetchInterval) { + Logger.d("ColorSchemeDownload", "Cache expired or missing, fetching new schemes"); + fetchAvailableSchemesFromAPI(); + return; + } else { + // Use cached data even if expired, to avoid rate limits + Logger.d("ColorSchemeDownload", "Cache expired but recent API call detected, using cached data"); + if (cachedSchemes.length > 0) { + availableSchemes = cachedSchemes; + hasInitialData = true; + fetching = false; + return; + } + } + } + + const ageMinutes = Math.round((now - cachedTimestamp) / 60); + Logger.d("ColorSchemeDownload", "Loading cached schemes from ShellState (age:", ageMinutes, "minutes)"); + + if (cachedSchemes.length > 0) { + availableSchemes = cachedSchemes; + // Restore color cache from cached schemes + for (var i = 0; i < cachedSchemes.length; i++) { + var cachedScheme = cachedSchemes[i]; + if (cachedScheme.dark || cachedScheme.light) { + schemeColorsCache[cachedScheme.name] = { + "dark": cachedScheme.dark || null, + "light": cachedScheme.light || null + }; + } + } + hasInitialData = true; + cacheVersion++; + fetching = false; + } else { + // Cache is empty, only fetch if we haven't fetched recently + const timeSinceLastFetch = now - lastApiFetchTime; + if (timeSinceLastFetch >= minApiFetchInterval) { + fetchAvailableSchemesFromAPI(); + } else { + Logger.d("ColorSchemeDownload", "Cache empty but recent API call detected, skipping fetch"); + fetching = false; + } + } + } catch (error) { + Logger.e("ColorSchemeDownload", "Failed to load schemes from cache:", error); + fetching = false; + } + } + + function saveSchemesToCache() { + try { + ShellState.setColorSchemesList({ + schemes: availableSchemes, + timestamp: Time.timestamp + }); + Logger.d("ColorSchemeDownload", "Schemes list saved to ShellState"); + } catch (error) { + Logger.e("ColorSchemeDownload", "Failed to save schemes to cache:", error); + } + } + + function fetchAvailableSchemes() { + if (fetching) { + return; + } + + // Try to load from ShellState cache first + if (typeof ShellState !== 'undefined' && ShellState.isLoaded) { + loadSchemesFromCache(); + } else { + // ShellState not ready, fetch directly from API + fetchAvailableSchemesFromAPI(); + } + } + + function fetchAvailableSchemesFromAPI() { + if (fetching) { + return; + } + + // Check if we've fetched recently to prevent rapid repeated calls + const now = Time.timestamp; + const timeSinceLastFetch = now - lastApiFetchTime; + if (timeSinceLastFetch < minApiFetchInterval) { + Logger.d("ColorSchemeDownload", "Skipping API fetch - too soon since last fetch (", Math.round(timeSinceLastFetch), "s ago)"); + return; + } + + fetching = true; + lastApiFetchTime = now; + // Don't clear availableSchemes immediately to prevent flicker - keep showing old list while fetching + // availableSchemes = []; + downloadError = ""; + + // Fetch registry.json + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState === XMLHttpRequest.DONE) { + fetching = false; + if (xhr.status === 200) { + try { + var registry = JSON.parse(xhr.responseText); + if (registry && registry.themes && Array.isArray(registry.themes)) { + var schemes = []; + // Process themes + for (var i = 0; i < registry.themes.length; i++) { + var theme = registry.themes[i]; + schemes.push({ + "name": theme.name, + "path": theme.path, + "dark": theme.dark || null, + "light": theme.light || null + }); + schemeColorsCache[theme.name] = { + "dark": theme.dark || null, + "light": theme.light || null + }; + } + availableSchemes = schemes; + hasInitialData = true; + cacheVersion++; + Logger.d("ColorSchemeDownload", "Fetched", schemes.length, "available schemes from registry.json"); + // Save to cache + saveSchemesToCache(); + } else { + downloadError = I18n.tr("panels.color-scheme.download-error-invalid-response"); + Logger.e("ColorSchemeDownload", downloadError); + } + } catch (e) { + downloadError = I18n.tr("panels.color-scheme.download-error-parse-failed", { + "error": e.toString() + }); + Logger.e("ColorSchemeDownload", downloadError); + } + } else if (xhr.status === 403) { + // Rate limit hit - try to use cache if available + downloadError = I18n.tr("panels.color-scheme.download-error-rate-limit"); + Logger.w("ColorSchemeDownload", downloadError); + if (typeof ShellState !== 'undefined' && ShellState.isLoaded) { + const cacheData = ShellState.getColorSchemesList(); + const cachedSchemes = cacheData.schemes || []; + if (cachedSchemes.length > 0) { + availableSchemes = cachedSchemes; + // Restore color cache from cached schemes + for (var j = 0; j < cachedSchemes.length; j++) { + var cachedScheme = cachedSchemes[j]; + if (cachedScheme.dark || cachedScheme.light) { + schemeColorsCache[cachedScheme.name] = { + "dark": cachedScheme.dark || null, + "light": cachedScheme.light || null + }; + } + } + hasInitialData = true; + cacheVersion++; + Logger.i("ColorSchemeDownload", "Using cached schemes due to rate limit"); + } + } + } else { + downloadError = I18n.tr("panels.color-scheme.download-error-api-error", { + "status": xhr.status + }); + Logger.e("ColorSchemeDownload", downloadError); + } + } + }; + + xhr.open("GET", "https://raw.githubusercontent.com/noctalia-dev/noctalia-colorschemes/main/registry.json"); + xhr.send(); + } + + function downloadScheme(scheme) { + if (downloading) { + return; + } + + downloading = true; + downloadingScheme = scheme.name; + downloadError = ""; + + Logger.i("ColorSchemeDownload", "Downloading scheme:", scheme.name); + + // Use cached branch/SHA if available, otherwise fetch + if (cachedBranchSha) { + // Use cached SHA directly + getSchemeTreeWithSha(scheme, cachedBranch, cachedBranchSha); + } else if (cachedBranch) { + // We have branch name, just need SHA + getSchemeTree(scheme, cachedBranch); + } else { + // Need to fetch branch info first + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState === XMLHttpRequest.DONE) { + if (xhr.status === 200) { + try { + var repoInfo = JSON.parse(xhr.responseText); + var defaultBranch = repoInfo.default_branch || "main"; + cachedBranch = defaultBranch; + // Now get the tree for the scheme directory + getSchemeTree(scheme, defaultBranch); + } catch (e) { + // Fallback: try to get files directly + getSchemeFilesDirect(scheme); + } + } else { + // Fallback: try to get files directly + getSchemeFilesDirect(scheme); + } + } + }; + xhr.open("GET", "https://api.github.com/repos/noctalia-dev/noctalia-colorschemes"); + xhr.send(); + } + } + + function getSchemeTree(scheme, branch) { + // First get the SHA of the branch + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState === XMLHttpRequest.DONE) { + if (xhr.status === 200) { + try { + var refResponse = JSON.parse(xhr.responseText); + var sha = refResponse.object ? refResponse.object.sha : null; + if (sha) { + // Cache the SHA for future downloads + cachedBranchSha = sha; + // Now get the tree + getSchemeTreeWithSha(scheme, branch, sha); + } else { + // Fallback to direct method + getSchemeFilesDirect(scheme); + } + } catch (e) { + // Fallback to direct method + getSchemeFilesDirect(scheme); + } + } else { + // Fallback to direct method + getSchemeFilesDirect(scheme); + } + } + }; + xhr.open("GET", "https://api.github.com/repos/noctalia-dev/noctalia-colorschemes/git/refs/heads/" + branch); + xhr.send(); + } + + function getSchemeTreeWithSha(scheme, branch, sha) { + // Use git trees API to get all files recursively + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState === XMLHttpRequest.DONE) { + if (xhr.status === 200) { + try { + var response = JSON.parse(xhr.responseText); + if (response.tree && Array.isArray(response.tree)) { + // Filter files that belong to this scheme + var files = []; + for (var i = 0; i < response.tree.length; i++) { + var item = response.tree[i]; + if (item.type === "blob" && item.path.startsWith(scheme.path + "/")) { + files.push({ + "path": item.path, + "url": "https://raw.githubusercontent.com/noctalia-dev/noctalia-colorschemes/" + branch + "/" + item.path, + "name": item.path.split("/").pop() + }); + } + } + downloadSchemeFiles(scheme.name, files); + } else { + // Fallback to direct method + getSchemeFilesDirect(scheme); + } + } catch (e) { + downloadError = I18n.tr("panels.color-scheme.download-error-parse-failed", { + "error": e.toString() + }); + downloading = false; + downloadingScheme = ""; + Logger.e("ColorSchemeDownload", downloadError); + } + } else { + // Fallback to direct method + getSchemeFilesDirect(scheme); + } + } + }; + xhr.open("GET", "https://api.github.com/repos/noctalia-dev/noctalia-colorschemes/git/trees/" + sha + "?recursive=1"); + xhr.send(); + } + + function getSchemeFilesDirect(scheme) { + // Fallback: get files directly using contents API (non-recursive, but works) + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState === XMLHttpRequest.DONE) { + if (xhr.status === 200) { + try { + var response = JSON.parse(xhr.responseText); + if (Array.isArray(response)) { + // Recursively get all files + getAllFilesRecursive(scheme, response, []); + } else { + downloadError = I18n.tr("panels.color-scheme.download-error-invalid-response"); + downloading = false; + downloadingScheme = ""; + Logger.e("ColorSchemeDownload", downloadError); + } + } catch (e) { + downloadError = I18n.tr("panels.color-scheme.download-error-parse-failed", { + "error": e.toString() + }); + downloading = false; + downloadingScheme = ""; + Logger.e("ColorSchemeDownload", downloadError); + } + } else { + downloadError = I18n.tr("panels.color-scheme.download-error-api-error", { + "status": xhr.status + }); + downloading = false; + downloadingScheme = ""; + Logger.e("ColorSchemeDownload", downloadError); + } + } + }; + xhr.open("GET", "https://api.github.com/repos/noctalia-dev/noctalia-colorschemes/contents/" + scheme.path); + xhr.send(); + } + + function getAllFilesRecursive(scheme, items, allFiles, callback) { + if (!callback) { + callback = function () { + downloadSchemeFiles(scheme.name, allFiles); + }; + } + + if (items.length === 0) { + callback(); + return; + } + + var pending = 0; + var completed = 0; + + function checkComplete() { + completed++; + if (completed === items.length && pending === 0) { + callback(); + } + } + + for (var i = 0; i < items.length; i++) { + var item = items[i]; + if (item.type === "file") { + allFiles.push({ + "path": item.path, + "url": item.download_url, + "name": item.name + }); + checkComplete(); + } else if (item.type === "dir") { + pending++; + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function (dirItem) { + return function () { + if (xhr.readyState === XMLHttpRequest.DONE) { + pending--; + if (xhr.status === 200) { + try { + var dirResponse = JSON.parse(xhr.responseText); + if (Array.isArray(dirResponse)) { + getAllFilesRecursive(scheme, dirResponse, allFiles, function () { + checkComplete(); + }); + } else { + checkComplete(); + } + } catch (e) { + Logger.e("ColorSchemeDownload", "Failed to parse directory response:", e); + checkComplete(); + } + } else { + checkComplete(); + } + } + }; + }(item); + xhr.open("GET", item.url); + xhr.send(); + } else { + checkComplete(); + } + } + } + + function downloadSchemeFiles(schemeName, files) { + if (files.length === 0) { + downloadError = I18n.tr("panels.color-scheme.download-error-no-files"); + downloading = false; + downloadingScheme = ""; + Logger.e("ColorSchemeDownload", downloadError); + return; + } + + var targetDir = ColorSchemeService.downloadedSchemesDirectory + "/" + schemeName; + var downloadScript = "mkdir -p '" + targetDir + "'\n"; + + // Build download script for all files + for (var i = 0; i < files.length; i++) { + var file = files[i]; + var filePath = file.path; + // Remove scheme name and leading / from path + var relativePath = filePath; + if (filePath.startsWith(schemeName + "/")) { + relativePath = filePath.substring(schemeName.length + 1); + } else if (filePath.startsWith("/" + schemeName + "/")) { + relativePath = filePath.substring(schemeName.length + 2); + } + var localPath = targetDir + "/" + relativePath; + var localDir = localPath.substring(0, localPath.lastIndexOf('/')); + + downloadScript += "mkdir -p '" + localDir + "'\n"; + var downloadUrl = file.url || file.download_url; + if (downloadUrl) { + downloadScript += "curl -L -s -o '" + localPath + "' '" + downloadUrl + "' || wget -q -O '" + localPath + "' '" + downloadUrl + "'\n"; + } + } + + Logger.d("ColorSchemeDownload", "Downloading", files.length, "files for scheme", schemeName); + + // Execute download script + var stderrOutput = ""; + var downloadProcess = Qt.createQmlObject(` + import QtQuick + import Quickshell.Io + import qs.Commons + Process { + id: downloadProcess + command: ["sh", "-c", ` + JSON.stringify(downloadScript) + `] + stderr: StdioCollector { + onStreamFinished: { + if (text && text.trim()) { + Logger.e("ColorSchemeDownload", "Download stderr:", text); + root.lastStderrOutput = text.trim(); + } + } + } + } + `, root, "DownloadProcess_" + schemeName); + + downloadProcess.exited.connect(function (exitCode) { + if (exitCode === 0) { + Logger.i("ColorSchemeDownload", "Scheme downloaded successfully:", schemeName); + ToastService.showNotice(I18n.tr("panels.color-scheme.download-success-title"), I18n.tr("panels.color-scheme.download-success-description", { + "scheme": schemeName + }), "settings-color-scheme"); + // Set pending scheme to apply after reload + pendingApplyScheme = schemeName; + // Reload color schemes + ColorSchemeService.loadColorSchemes(); + downloading = false; + downloadingScheme = ""; + } else { + var errorDetails = "Exit code: " + exitCode; + if (root.lastStderrOutput) { + errorDetails += " - " + root.lastStderrOutput; + } + downloadError = I18n.tr("panels.color-scheme.download-error-download-failed", { + "code": exitCode + }) + "\n" + errorDetails; + Logger.e("ColorSchemeDownload", downloadError); + ToastService.showError(I18n.tr("panels.color-scheme.download-error-title"), I18n.tr("panels.color-scheme.download-error-description", { + "scheme": schemeName + }) + "\n" + errorDetails); + // Clean up the partially downloaded directory on failure + var cleanupScript = "rm -rf '" + targetDir + "'"; + var cleanupProcess = Qt.createQmlObject(` + import QtQuick + import Quickshell.Io + Process { + id: cleanupProcess + command: ["sh", "-c", ` + JSON.stringify(cleanupScript) + `] + } + `, root, "CleanupProcess_" + schemeName); + + cleanupProcess.exited.connect(function (cleanupExitCode) { + if (cleanupExitCode === 0) { + Logger.d("ColorSchemeDownload", "Partially downloaded scheme directory cleaned up:", targetDir); + } else { + Logger.w("ColorSchemeDownload", "Failed to clean up partially downloaded scheme directory:", targetDir); + } + downloading = false; + downloadingScheme = ""; + cleanupProcess.destroy(); + }); + + cleanupProcess.running = true; + } + root.lastStderrOutput = ""; + downloadProcess.destroy(); + }); + + downloadProcess.running = true; + } + + function isSchemeInstalled(schemeName) { + // Check if scheme already exists in ColorSchemeService + for (var i = 0; i < ColorSchemeService.schemes.length; i++) { + var path = ColorSchemeService.schemes[i]; + if (path.indexOf("/" + schemeName + "/") !== -1 || path.indexOf("/" + schemeName + ".json") !== -1) { + return true; + } + } + return false; + } + + function isSchemeDownloaded(schemeName) { + // Check if scheme is in the downloaded directory (not preinstalled) + for (var i = 0; i < ColorSchemeService.schemes.length; i++) { + var path = ColorSchemeService.schemes[i]; + if ((path.indexOf("/" + schemeName + "/") !== -1 || path.indexOf("/" + schemeName + ".json") !== -1) && path.indexOf(ColorSchemeService.downloadedSchemesDirectory) !== -1) { + return true; + } + } + return false; + } + + function deleteScheme(schemeName) { + if (downloading) { + return; + } + + Logger.i("ColorSchemeDownload", "Deleting scheme:", schemeName); + + // Check if the deleted scheme is the currently active one + var currentScheme = Settings.data.colorSchemes.predefinedScheme || ""; + var deletedSchemeDisplayName = ColorSchemeService.getBasename(schemeName); + var needsReset = (currentScheme === deletedSchemeDisplayName); + + // Only allow deleting downloaded schemes, not preinstalled ones + var targetDir = ColorSchemeService.downloadedSchemesDirectory + "/" + schemeName; + var deleteScript = "rm -rf '" + targetDir + "'"; + + var deleteProcess = Qt.createQmlObject(` + import QtQuick + import Quickshell.Io + Process { + id: deleteProcess + command: ["sh", "-c", ` + JSON.stringify(deleteScript) + `] + } + `, root, "DeleteProcess_" + schemeName); + + deleteProcess.exited.connect(function (exitCode) { + if (exitCode === 0) { + Logger.i("ColorSchemeDownload", "Scheme deleted successfully:", schemeName); + ToastService.showNotice(I18n.tr("panels.color-scheme.delete-success-title"), I18n.tr("panels.color-scheme.delete-success-description", { + "scheme": schemeName + }), "settings-color-scheme"); + + // If the deleted scheme was the active one, reset to default BEFORE reloading + if (needsReset) { + Logger.i("ColorSchemeDownload", "Deleted scheme was active, resetting to Noctalia (default)"); + // Clear the setting immediately so ColorSchemeService won't try to apply the deleted scheme + Settings.data.colorSchemes.predefinedScheme = "Noctalia (default)"; + // Apply the default scheme immediately + ColorSchemeService.setPredefinedScheme("Noctalia (default)"); + } + + // Reload color schemes + ColorSchemeService.loadColorSchemes(); + } else { + Logger.e("ColorSchemeDownload", "Delete failed with exit code:", exitCode); + ToastService.showError(I18n.tr("panels.color-scheme.delete-error-title"), I18n.tr("panels.color-scheme.delete-error-description", { + "scheme": schemeName + })); + } + deleteProcess.destroy(); + }); + + deleteProcess.running = true; + } + + Connections { + target: ColorSchemeService + function onScanningChanged() { + // When scanning completes and we have a pending scheme, apply it + if (!ColorSchemeService.scanning && pendingApplyScheme !== "") { + var schemeToApply = pendingApplyScheme; + pendingApplyScheme = ""; // Clear pending before applying + + // Wait a tiny bit to ensure schemes array is updated + applyTimer.schemeName = schemeToApply; + applyTimer.restart(); + } + } + } + + Timer { + id: applyTimer + property string schemeName: "" + interval: 100 // Small delay to ensure schemes array is populated + onTriggered: { + if (schemeName !== "") { + Logger.i("ColorSchemeDownload", "Auto-applying downloaded scheme:", schemeName); + // Use setPredefinedScheme which will apply and set it as the current scheme + ColorSchemeService.setPredefinedScheme(schemeName); + schemeName = ""; + } + } + } + + onOpened: { + fetchAvailableSchemes(); + } + + function preFetchSchemeColors() { + if (availableSchemes.length > 0 && visible) { + Qt.callLater(function () { + // Just trigger a cache version update to ensure UI refreshes + cacheVersion++; + }); + } + } + + onAvailableSchemesChanged: preFetchSchemeColors() + onVisibleChanged: { + preFetchSchemeColors(); + + // Load schemes from ShellState when popup becomes visible + if (visible) { + if (typeof ShellState !== 'undefined' && ShellState.isLoaded) { + loadSchemesFromCache(); + } + } + } + + Connections { + target: typeof ShellState !== 'undefined' ? ShellState : null + function onIsLoadedChanged() { + if (root.visible && ShellState.isLoaded) { + loadSchemesFromCache(); + } + } + } + + contentItem: ColumnLayout { + id: contentColumn + width: parent.width + spacing: Style.marginL + + // Header + RowLayout { + Layout.fillWidth: true + + NText { + text: I18n.tr("panels.color-scheme.download-title") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mPrimary + Layout.fillWidth: true + } + + NIconButton { + icon: "refresh" + tooltipText: I18n.tr("common.refresh") + enabled: !fetching && !downloading + onClicked: { + // Force refresh by clearing cache timestamp and fetching directly from API + if (typeof ShellState !== 'undefined' && ShellState.isLoaded) { + ShellState.setColorSchemesList({ + schemes: [], + timestamp: 0 + }); + } + // Fetch directly from API to avoid cache check delay + fetchAvailableSchemesFromAPI(); + } + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + onClicked: root.close() + } + } + + // Separator + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + } + + // Error message + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: errorText.implicitHeight + Style.marginM + visible: downloadError !== "" + color: Color.mError + radius: Style.radiusS + + NText { + id: errorText + anchors.fill: parent + anchors.margins: Style.marginM + text: downloadError + pointSize: Style.fontSizeS + color: Color.mOnError + wrapMode: Text.WordWrap + } + } + + // Loading indicator - only show on initial load, not during refresh + RowLayout { + Layout.fillWidth: true + visible: fetching && !hasInitialData + spacing: Style.marginM + + NBusyIndicator { + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + } + + NText { + text: I18n.tr("panels.color-scheme.download-fetching") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + } + + // Schemes list - keep visible during refresh to prevent flicker + NScrollView { + id: schemesScrollView + Layout.fillWidth: true + Layout.preferredHeight: 400 + visible: hasInitialData && availableSchemes.length > 0 + verticalPolicy: ScrollBar.AsNeeded + horizontalPolicy: ScrollBar.AlwaysOff + gradientColor: Color.mSurface + + ColumnLayout { + width: schemesScrollView.availableWidth + spacing: Style.marginS + + Repeater { + model: availableSchemes + + Rectangle { + id: schemeItem + Layout.fillWidth: true + Layout.preferredHeight: 50 * Style.uiScaleRatio + radius: Style.radiusS + property string schemeName: modelData.name + color: root.getSchemeColor(schemeName, "mSurface") + border.width: Style.borderL + border.color: hoverHandler.hovered ? root.getSchemeColor(schemeName, "mPrimary") : Color.mOutline + + HoverHandler { + id: hoverHandler + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutCubic + } + } + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutCubic + } + } + + Component.onCompleted: { + if (root.visible && !root.schemeColorsCache[schemeName]) { + root.fetchSchemeColors(modelData); + } + } + + RowLayout { + id: schemeRow + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: Style.marginL + anchors.rightMargin: Style.marginL + spacing: Style.marginS + + property string schemeName: modelData.name + property int diameter: 16 * Style.uiScaleRatio + property var colorKeys: ["mPrimary", "mSecondary", "mTertiary", "mError"] + + NText { + text: schemeRow.schemeName + pointSize: Style.fontSizeS + color: Color.mOnSurface + Layout.fillWidth: true + elide: Text.ElideRight + Layout.alignment: Qt.AlignVCenter + } + + // Color swatches + Repeater { + model: schemeRow.colorKeys + Rectangle { + width: schemeRow.diameter + height: schemeRow.diameter + radius: schemeRow.diameter * 0.5 + color: root.getSchemeColor(schemeRow.schemeName, modelData) + Layout.alignment: Qt.AlignVCenter + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutCubic + } + } + } + } + + // Download/Delete button + NIconButton { + property bool isDownloading: downloading && downloadingScheme === schemeRow.schemeName + property bool isInstalled: root.isSchemeInstalled(schemeRow.schemeName) + property bool isDownloaded: root.isSchemeDownloaded(schemeRow.schemeName) + + icon: isDownloading ? "" : (isDownloaded ? "trash" : "download") + tooltipText: isDownloading ? I18n.tr("panels.color-scheme.download-downloading") : (isDownloaded ? I18n.tr("common.delete") : I18n.tr("common.download")) + enabled: !downloading + Layout.alignment: Qt.AlignVCenter + visible: !isInstalled || isDownloaded // Show button only if not installed (can download) or if downloaded (can delete) + onClicked: isDownloaded ? root.deleteScheme(schemeRow.schemeName) : root.downloadScheme(modelData) + + NBusyIndicator { + anchors.centerIn: parent + width: 16 * Style.uiScaleRatio + height: 16 * Style.uiScaleRatio + visible: parent.isDownloading + } + } + } + } + } + } + } + + // Empty state + ColumnLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + spacing: Style.marginM + visible: !fetching && availableSchemes.length === 0 && downloadError === "" + + NIcon { + icon: "package" + pointSize: 48 + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: I18n.tr("panels.color-scheme.download-empty") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ColorScheme/TemplatesSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ColorScheme/TemplatesSubTab.qml new file mode 100644 index 0000000..127787f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ColorScheme/TemplatesSubTab.qml @@ -0,0 +1,318 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.System +import qs.Services.Theming +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + // Helper to format path description + function getDesc(fallbackPath) { + return I18n.tr("panels.color-scheme.templates-write-path", { + "filepath": fallbackPath + }); + } + + // Build a combined list of all available templates from TemplateRegistry, sorted alphabetically + readonly property var allTemplates: { + var templates = []; + + // Add terminals with category "terminal" + for (var i = 0; i < TemplateRegistry.terminals.length; i++) { + var t = TemplateRegistry.terminals[i]; + templates.push({ + "id": t.id, + "name": t.name, + "category": "terminal", + "tooltip": getDesc(t.outputPath) + }); + } + + // Add applications + for (var j = 0; j < TemplateRegistry.applications.length; j++) { + var app = TemplateRegistry.applications[j]; + var path = ""; + + // Determine path to show + if (app.outputs && app.outputs.length > 0) { + var paths = []; + for (var k = 0; k < app.outputs.length; k++) { + paths.push(app.outputs[k].path); + } + path = paths.join("\n"); + } else if (app.id === "emacs") { + // Emacs clients are detected dynamically by ProgramCheckerService + var emacsClients = ProgramCheckerService.availableEmacsClients; + if (emacsClients && emacsClients.length > 0) { + var emacsPaths = []; + for (var k = 0; k < emacsClients.length; k++) { + emacsPaths.push(emacsClients[k].path); + } + path = emacsPaths.join("\n"); + } else { + path = I18n.tr("panels.color-scheme.templates-none-detected"); + } + } else if (app.clients && app.clients.length > 0) { + var validClients = []; + for (var k = 0; k < app.clients.length; k++) { + var client = app.clients[k]; + var include = true; + + if (app.id === "discord") { + include = TemplateProcessor.isDiscordClientEnabled(client.name); + } else if (app.id === "code") { + // For code clients, resolve all theme paths dynamically (version-independent) + if (TemplateProcessor.isCodeClientEnabled(client.name)) { + var resolvedPaths = TemplateRegistry.resolvedCodeClientPaths(client.name); + for (var p = 0; p < resolvedPaths.length; p++) { + validClients.push(resolvedPaths[p]); + } + } + continue; + } + + if (include) { + if (client.path) + validClients.push(client.path); + } + } + + if (validClients.length > 0) { + path = validClients.join("\n"); + } else { + path = I18n.tr("panels.color-scheme.templates-none-detected"); + } + } + + templates.push({ + "id": app.id, + "name": app.name, + "category": app.category || "misc", + "tooltip": getDesc(path) + }); + } + + // Sort alphabetically by name + templates.sort((a, b) => a.name.localeCompare(b.name)); + + return templates; + } + + // Category filter + property string selectedCategory: "" + + // Build available categories dynamically + readonly property var availableCategories: { + var cats = {}; + for (var i = 0; i < allTemplates.length; i++) { + cats[allTemplates[i].category] = true; + } + return Object.keys(cats).sort(); + } + + // Filter toggle + property bool showOnlyActive: false + + // Filtered templates based on category, search, and toggle + property string searchText: "" + readonly property var filteredTemplates: { + var result = allTemplates; + + // Filter by category first (unless searching) + if (selectedCategory !== "" && searchText.trim() === "") { + result = result.filter(t => t.category === selectedCategory); + } + + // Search overrides category filter + if (searchText.trim() !== "") { + var query = searchText.toLowerCase().trim(); + result = result.filter(t => t.name.toLowerCase().includes(query)); + } + + // Filter by active if enabled (and not searching) + if (showOnlyActive && searchText.trim() === "") { + result = result.filter(t => isTemplateActive(t.id)); + } + + return result; + } + + // Check if a template is active + function isTemplateActive(templateId) { + for (var i = 0; i < Settings.data.templates.activeTemplates.length; i++) { + if (Settings.data.templates.activeTemplates[i].id === templateId) { + return true; + } + } + return false; + } + + // Toggle a template on/off + function toggleTemplate(templateId) { + var current = Settings.data.templates.activeTemplates.slice(); + var existingIndex = -1; + + for (var i = 0; i < current.length; i++) { + if (current[i].id === templateId) { + existingIndex = i; + break; + } + } + + if (existingIndex >= 0) { + // Remove it + current.splice(existingIndex, 1); + } else { + // Add it + current.push({ + "id": templateId, + "enabled": true + }); + } + + Settings.data.templates.activeTemplates = current; + AppThemeService.generate(); + + // Clear search context on interaction to return to filtered view + if (searchText !== "") { + searchText = ""; + } + } + + NText { + text: I18n.tr("panels.color-scheme.templates-desc") + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + // Category filter chips + NTagFilter { + tags: root.availableCategories + selectedTag: root.selectedCategory + onSelectedTagChanged: root.selectedCategory = selectedTag + label: I18n.tr("panels.color-scheme.templates-filter-label") + description: I18n.tr("panels.color-scheme.templates-filter-description") + expanded: true + } + + // Search/filter input row + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NTextInput { + Layout.fillWidth: true + placeholderText: I18n.tr("placeholders.search") + text: root.searchText + onTextChanged: root.searchText = text + } + + NIconButton { + icon: "filter" + tooltipText: root.showOnlyActive ? I18n.tr("actions.show-all") : I18n.tr("actions.show-active-only") + + colorBg: root.showOnlyActive ? Color.mPrimary : Color.mSurface + colorFg: root.showOnlyActive ? Color.mOnPrimary : Color.mOnSurface + + onClicked: root.showOnlyActive = !root.showOnlyActive + } + } + + // Chip grid - uniform columns + GridLayout { + Layout.fillWidth: true + columns: 4 + columnSpacing: Style.marginS + rowSpacing: Style.marginS + + Repeater { + model: filteredTemplates + + Rectangle { + id: chip + Layout.fillWidth: true + Layout.preferredHeight: Math.round(Style.baseWidgetSize * 0.9) + radius: Style.iRadiusM + color: chipMouse.containsMouse ? Color.mHover : (isActive ? Color.mPrimary : Color.mSurface) + border.color: isActive ? Color.mPrimary : Color.mOutline + border.width: Style.borderS + + required property int index + required property var modelData + readonly property bool isActive: root.isTemplateActive(modelData.id) + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + NText { + id: chipText + anchors.centerIn: parent + width: parent.width - Style.margin2L + text: chip.modelData.name + pointSize: Style.fontSizeS + color: chipMouse.containsMouse ? Color.mOnHover : (isActive ? Color.mOnPrimary : Color.mOnSurface) + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + MouseArea { + id: chipMouse + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + onClicked: root.toggleTemplate(chip.modelData.id) + onEntered: { + if (chip.modelData.tooltip) { + TooltipService.show(chip, chip.modelData.tooltip, "bottom"); + } + } + onExited: { + TooltipService.hide(); + } + } + } + } + } + + // No results message + NText { + visible: filteredTemplates.length === 0 && searchText.trim() !== "" + text: I18n.tr("common.no-results") + color: Color.mOnSurfaceVariant + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginM + } + + // User templates checkbox + NCheckbox { + label: I18n.tr("panels.color-scheme.templates-misc-user-templates-label") + description: I18n.tr("panels.color-scheme.templates-misc-user-templates-description") + checked: Settings.data.templates.enableUserTheming + onToggled: checked => { + Settings.data.templates.enableUserTheming = checked; + if (checked) { + TemplateRegistry.writeUserTemplatesToml(); + } + AppThemeService.generate(); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Connections/BluetoothSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Connections/BluetoothSubTab.qml new file mode 100644 index 0000000..c424a52 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Connections/BluetoothSubTab.qml @@ -0,0 +1,786 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import Quickshell +import Quickshell.Bluetooth + +import qs.Commons +import qs.Services.Hardware +import qs.Services.Networking +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +Item { + id: root + Layout.fillWidth: true + implicitHeight: mainLayout.implicitHeight + + // Configuration for shared use (e.g. by BluetoothPanel) + property bool showOnlyLists: false + + readonly property bool isScanningActive: BluetoothService.scanningActive + readonly property bool isDiscoverable: BluetoothService.discoverable + + // Device lists with local filtering logic + readonly property var connectedDevices: { + if (!BluetoothService.adapter || !BluetoothService.adapter.devices) + return []; + var filtered = BluetoothService.adapter.devices.values.filter(dev => dev && !dev.blocked && dev.connected); + filtered = BluetoothService.dedupeDevices(filtered); + return BluetoothService.sortDevices(filtered); + } + + readonly property var pairedDevices: { + if (!BluetoothService.adapter || !BluetoothService.adapter.devices) + return []; + var filtered = BluetoothService.adapter.devices.values.filter(dev => dev && !dev.blocked && !dev.connected && (dev.paired || dev.trusted)); + filtered = BluetoothService.dedupeDevices(filtered); + return BluetoothService.sortDevices(filtered); + } + + readonly property var unnamedAvailableDevices: { + if (!BluetoothService.adapter || !BluetoothService.adapter.devices) + return []; + return BluetoothService.adapter.devices.values.filter(dev => dev && !dev.blocked && !dev.paired && !dev.trusted); + } + + readonly property var availableDevices: { + var list = root.unnamedAvailableDevices; + + if (Settings.data.network.bluetoothHideUnnamedDevices) { + list = list.filter(function (dev) { + var dn = dev.name || dev.deviceName || ""; + var s = String(dn).trim(); + if (s.length === 0) + return false; + var lower = s.toLowerCase(); + if (lower === "unknown" || lower === "unnamed" || lower === "n/a" || lower === "na") + return false; + var addr = dev.address || dev.bdaddr || dev.mac || ""; + if (addr.length > 0) { + var normName = s.toLowerCase().replace(/[^0-9a-z]/g, ""); + var normAddr = String(addr).toLowerCase().replace(/[^0-9a-z]/g, ""); + if (normName.length > 0 && normName === normAddr) + return false; + } + var macRegexComb = /^(([0-9A-Fa-f]{2}[:\-]){5}[0-9A-Fa-f]{2}|([0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4}|[0-9A-Fa-f]{12})$/; + if (macRegexComb.test(s)) { + return false; + } + return true; + }); + } + list = BluetoothService.dedupeDevices(list); + return BluetoothService.sortDevices(list); + } + + // For managing expanded device details + property string expandedDeviceKey: "" + property bool detailsGrid: (Settings.data.network.bluetoothDetailsViewMode === "grid") + + // Combined visibility check: tab must be visible AND the window must be visible + readonly property bool effectivelyVisible: root.visible && Window.window && Window.window.visible + + Connections { + target: BluetoothService + function onEnabledChanged() { + stateChangeDebouncer.restart(); + } + function onDiscoverableChanged() { + stateChangeDebouncer.restart(); + } + } + + onEffectivelyVisibleChanged: stateChangeDebouncer.restart() + + Timer { + id: stateChangeDebouncer + interval: 100 // 100ms debounce + repeat: false + onTriggered: root._updateScanningState() + } + + function _updateScanningState() { + if (effectivelyVisible && BluetoothService.enabled && !showOnlyLists) { + Logger.d("BluetoothPrefs", "Panel/tab active"); + if (!isScanningActive) { + BluetoothService.setScanActive(true); + } + if (!Settings.data.network.disableDiscoverability && !isDiscoverable) { + BluetoothService.setDiscoverable(true); + } + } else { + Logger.d("BluetoothPrefs", "Panel/tab inactive"); + if (isScanningActive && !showOnlyLists) { + BluetoothService.setScanActive(false); + } + if (isDiscoverable && !showOnlyLists) { + BluetoothService.setDiscoverable(false); + } + } + } + + Component.onDestruction: { + // Ensure scanning is stopped when component is closed + if (isScanningActive && !showOnlyLists) { + BluetoothService.setScanActive(false); + } + // Ensure discoverable is disabled when component is closed + if (isDiscoverable && !showOnlyLists) { + BluetoothService.setDiscoverable(false); + } + Logger.d("BluetoothPrefs", "Panel closed"); + } + + ColumnLayout { + id: mainLayout + anchors.left: parent.left + anchors.right: parent.right + spacing: Style.marginL + + // Master Control Section + NBox { + visible: !root.showOnlyLists + Layout.fillWidth: true + Layout.preferredHeight: masterControlCol.implicitHeight + Style.margin2L + color: Color.mSurface + + ColumnLayout { + id: masterControlCol + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NToggle { + label: I18n.tr("common.bluetooth") + icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off" + checked: BluetoothService.enabled + enabled: !NetworkService.airplaneModeEnabled && BluetoothService.bluetoothAvailable + onToggled: checked => BluetoothService.setBluetoothEnabled(checked) + Layout.alignment: Qt.AlignVCenter + } + } + + NDivider { + Layout.fillWidth: true + visible: BluetoothService.enabled && isDiscoverable + } + + NText { + visible: BluetoothService.enabled && isDiscoverable + Layout.fillWidth: true + text: I18n.tr("panels.connections.bluetooth-discoverable", { + hostName: HostService.hostName + }) + color: Color.mOnSurfaceVariant + richTextEnabled: true + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + } + } + } + + Item { + visible: !showOnlyLists + Layout.fillWidth: true + } + + // Device List [1] (Connected) + NBox { + id: connectedDevicesBox + visible: root.connectedDevices.length > 0 && BluetoothService.enabled + Layout.fillWidth: true + Layout.preferredHeight: connectedDevicesCol.implicitHeight + Style.margin2M + border.color: showOnlyLists ? Style.boxBorderColor : "transparent" + color: showOnlyLists ? Color.mSurfaceVariant : "transparent" + + ColumnLayout { + id: connectedDevicesCol + anchors.fill: parent + anchors.topMargin: Style.marginM + anchors.bottomMargin: Style.marginM + anchors.leftMargin: showOnlyLists ? Style.marginL : 0 + anchors.rightMargin: showOnlyLists ? Style.marginL : 0 + spacing: Style.marginM + + NLabel { + label: I18n.tr("bluetooth.panel.connected-devices") + Layout.fillWidth: true + Layout.leftMargin: Style.marginS + } + + Repeater { + model: root.connectedDevices + delegate: nboxDelegate + } + } + } + + // Devices List [2] (Paired) + NBox { + id: pairedDevicesBox + visible: root.pairedDevices.length > 0 && BluetoothService.enabled + Layout.fillWidth: true + Layout.preferredHeight: pairedDevicesCol.implicitHeight + Style.margin2M + border.color: showOnlyLists ? Style.boxBorderColor : "transparent" + color: showOnlyLists ? Color.mSurfaceVariant : "transparent" + + ColumnLayout { + id: pairedDevicesCol + anchors.fill: parent + anchors.topMargin: Style.marginM + anchors.bottomMargin: Style.marginM + anchors.leftMargin: showOnlyLists ? Style.marginL : 0 + anchors.rightMargin: showOnlyLists ? Style.marginL : 0 + spacing: Style.marginM + + NLabel { + label: I18n.tr("bluetooth.panel.paired-devices") + Layout.fillWidth: true + Layout.leftMargin: Style.marginS + } + + Repeater { + model: root.pairedDevices + delegate: nboxDelegate + } + } + } + + // Device List [3] (Available) + NBox { + id: availableDevicesBox + visible: !root.showOnlyLists && root.unnamedAvailableDevices.length > 0 && BluetoothService.enabled + Layout.fillWidth: true + Layout.preferredHeight: availableDevicesCol.implicitHeight + Style.margin2M + border.color: "transparent" + color: showOnlyLists ? Color.mSurfaceVariant : "transparent" + + ColumnLayout { + id: availableDevicesCol + anchors.fill: parent + anchors.topMargin: Style.marginM + anchors.bottomMargin: Style.marginM + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: Style.marginS + spacing: Style.marginS + + NLabel { + label: I18n.tr("bluetooth.panel.available-devices") + description: BluetoothService.scanningActive ? I18n.tr("bluetooth.panel.scanning") : "" + Layout.fillWidth: true + } + } + + Repeater { + model: root.availableDevices + delegate: nboxDelegate + } + + NText { + visible: root.availableDevices.length === 0 && root.unnamedAvailableDevices.length > 0 + text: I18n.tr("panels.connections.bluetooth-devices-unnamed") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + Layout.margins: Style.marginL + } + } + } + + Item { + visible: !showOnlyLists + Layout.fillWidth: true + } + + NBox { + id: miscSettingsBox + visible: !root.showOnlyLists && BluetoothService.enabled + Layout.fillWidth: true + Layout.preferredHeight: miscSettingsCol.implicitHeight + Style.margin2XL + color: Color.mSurface + + ColumnLayout { + id: miscSettingsCol + anchors.fill: parent + anchors.margins: Style.marginXL + spacing: Style.marginM + + NToggle { + label: I18n.tr("panels.connections.bluetooth-auto-connect-label") + description: I18n.tr("panels.connections.bluetooth-auto-connect-description") + checked: Settings.data.network.bluetoothAutoConnect + onToggled: checked => Settings.data.network.bluetoothAutoConnect = checked + } + + NToggle { + label: I18n.tr("panels.connections.hide-unnamed-devices-label") + description: I18n.tr("panels.connections.hide-unnamed-devices-description") + checked: Settings.data.network.bluetoothHideUnnamedDevices + onToggled: checked => Settings.data.network.bluetoothHideUnnamedDevices = checked + } + + NToggle { + label: I18n.tr("panels.connections.disable-discoverability-label") + description: I18n.tr("panels.connections.disable-discoverability-description") + checked: Settings.data.network.disableDiscoverability + onToggled: checked => { + Settings.data.network.disableDiscoverability = checked; + BluetoothService.setDiscoverable(!checked); + } + } + + // RSSI Polling + NToggle { + label: I18n.tr("panels.connections.bluetooth-rssi-polling-label") + description: I18n.tr("panels.connections.bluetooth-rssi-polling-description") + checked: Settings.data.network.bluetoothRssiPollingEnabled + onToggled: checked => Settings.data.network.bluetoothRssiPollingEnabled = checked + } + NSpinBox { + label: I18n.tr("panels.connections.bluetooth-rssi-polling-interval-label") + description: I18n.tr("panels.connections.bluetooth-rssi-polling-interval-description") + from: 10000 + to: 120000 + stepSize: 1000 + value: Settings.data.network.bluetoothRssiPollIntervalMs + defaultValue: Settings.getDefaultValue("network.bluetoothRssiPollIntervalMs") + onValueChanged: Settings.data.network.bluetoothRssiPollIntervalMs = value + suffix: " ms" + Layout.alignment: Qt.AlignVCenter + visible: Settings.data.network.bluetoothRssiPollingEnabled + } + } + } + } + + // Shared Delegate + Component { + id: nboxDelegate + NBox { + id: device + + readonly property bool canConnect: BluetoothService.canConnect(modelData) + readonly property bool canDisconnect: BluetoothService.canDisconnect(modelData) + readonly property bool canPair: BluetoothService.canPair(modelData) + readonly property bool isBusy: BluetoothService.isDeviceBusy(modelData) + readonly property bool isExpanded: root.expandedDeviceKey === BluetoothService.deviceKey(modelData) + + function getContentColors(defaultColors = [Color.mSurface, Color.mOnSurface]) { + if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting) { + return [Color.mPrimary, Color.mOnPrimary]; + } + if (modelData.connected && modelData.state !== BluetoothDeviceState.Disconnecting) { + return [Color.mPrimary, Color.mOnPrimary]; + } + if (modelData.blocked || modelData.state === BluetoothDeviceState.Disconnecting) { + return [Color.mError, Color.mOnError]; + } + return defaultColors; + } + + Layout.fillWidth: true + Layout.preferredHeight: deviceColumn.implicitHeight + (Style.marginXL) + radius: Style.radiusM + clip: true + forceOpaque: true + color: device.getContentColors()[0] + + ColumnLayout { + id: deviceColumn + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginS + + RowLayout { + id: deviceLayout + Layout.fillWidth: true + spacing: Style.marginM + Layout.alignment: Qt.AlignVCenter + + NIcon { + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + horizontalAlignment: Text.AlignLeft + icon: BluetoothService.getDeviceIcon(modelData) + pointSize: Style.fontSizeXXL + color: device.getContentColors()[1] + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXXS + + NText { + text: modelData.name || modelData.deviceName + pointSize: Style.fontSizeM + font.weight: modelData.connected ? Style.fontWeightBold : Style.fontWeightMedium + elide: Text.ElideRight + color: device.getContentColors()[1] + Layout.fillWidth: true + } + + NText { + text: { + const k = BluetoothService.getStatusKey(modelData); + if (k === "pairing") + return I18n.tr("common.pairing"); + if (k === "blocked") + return I18n.tr("bluetooth.panel.blocked"); + if (k === "connecting") + return I18n.tr("common.connecting"); + if (k === "disconnecting") + return I18n.tr("common.disconnecting"); + return ""; + } + visible: text !== "" + pointSize: Style.fontSizeXS + color: Qt.alpha(device.getContentColors([Color.mSurfaceVariant, Color.mOnSurfaceVariant])[1], Style.opacityHeavy) + } + + RowLayout { + visible: modelData.batteryAvailable + spacing: Style.marginS + NIcon { + icon: { + var b = BluetoothService.getBatteryPercent(modelData); + return BatteryService.getIcon(b !== null ? b : 0, false, false, b !== null); + } + pointSize: Style.fontSizeXS + color: Qt.alpha(device.getContentColors()[1], Style.opacityHeavy) + } + NText { + text: { + var b = BluetoothService.getBatteryPercent(modelData); + return b === null ? "-" : (b + "%"); + } + pointSize: Style.fontSizeXS + color: Qt.alpha(device.getContentColors([Color.mSurfaceVariant, Color.mOnSurfaceVariant])[1], Style.opacityHeavy) + } + } + } + + Item { + Layout.fillWidth: true + } + + RowLayout { + spacing: Style.marginS + + NBusyIndicator { + visible: isBusy + running: visible && root.effectivelyVisible + color: device.getContentColors()[1] + size: Style.baseWidgetSize * 0.5 + } + + NIconButton { + visible: modelData.connected && modelData.state !== BluetoothDeviceState.Disconnecting + icon: "info" + tooltipText: I18n.tr("common.info") + baseSize: Style.baseWidgetSize * 0.75 + colorBg: Color.mSurfaceVariant + colorFg: Color.mOnSurface + colorBorder: "transparent" + colorBorderHover: "transparent" + onClicked: { + const key = BluetoothService.deviceKey(modelData); + root.expandedDeviceKey = (root.expandedDeviceKey === key) ? "" : key; + } + } + + NIconButton { + visible: !root.showOnlyLists && (modelData.paired || modelData.trusted) && !modelData.connected && !isBusy && !modelData.blocked + icon: "trash" + tooltipText: I18n.tr("common.unpair") + baseSize: Style.baseWidgetSize * 0.75 + colorBg: Color.mPrimary + colorFg: Color.mOnPrimary + colorBorder: "transparent" + colorBorderHover: "transparent" + onClicked: BluetoothService.unpairDevice(modelData) + } + + NButton { + id: button + visible: modelData.state !== BluetoothDeviceState.Connecting && modelData.state !== BluetoothDeviceState.Disconnecting + enabled: (canConnect || canDisconnect || (root.showOnlyLists ? false : canPair)) && !isBusy + fontSize: Style.fontSizeS + backgroundColor: modelData.connected ? Color.mSurfaceVariant : Color.mPrimary + textColor: modelData.connected ? Color.mOnSurface : Color.mOnPrimary + text: { + if (modelData.pairing) + return I18n.tr("common.pairing"); + if (modelData.blocked) + return I18n.tr("bluetooth.panel.blocked"); + if (modelData.connected) + return I18n.tr("common.disconnect"); + if (!root.showOnlyLists && device.canPair) + return I18n.tr("common.pair"); + return I18n.tr("common.connect"); + } + onClicked: { + if (modelData.connected) { + BluetoothService.disconnectDevice(modelData); + } else { + if (!root.showOnlyLists && device.canPair) { + BluetoothService.pairDevice(modelData); + } else { + BluetoothService.connectDeviceWithTrust(modelData); + } + } + } + } + } + } + + // Expanded info section + Rectangle { + visible: device.isExpanded + Layout.fillWidth: true + implicitHeight: infoColumn.implicitHeight + Style.margin2S + radius: Style.radiusXS + color: Color.mSurfaceVariant + border.width: Style.borderS + border.color: Style.boxBorderColor + clip: true + + NIconButton { + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: Style.marginS + icon: root.detailsGrid ? "layout-list" : "layout-grid" + tooltipText: root.detailsGrid ? I18n.tr("tooltips.list-view") : I18n.tr("tooltips.grid-view") + baseSize: Style.baseWidgetSize * 0.65 + onClicked: { + root.detailsGrid = !root.detailsGrid; + Settings.data.network.bluetoothDetailsViewMode = root.detailsGrid ? "grid" : "list"; + } + z: 1 + } + + GridLayout { + id: infoColumn + anchors.fill: parent + anchors.margins: Style.marginS + flow: root.detailsGrid ? GridLayout.TopToBottom : GridLayout.LeftToRight + rows: root.detailsGrid ? 3 : 6 + columns: root.detailsGrid ? 2 : 1 + columnSpacing: Style.marginM + rowSpacing: Style.marginXS + + // --- Item 1: Signal Strength --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + spacing: Style.marginXS + NIcon { + icon: BluetoothService.getSignalIcon(modelData) + pointSize: Style.fontSizeXS + color: Color.mOnSurface + } + NText { + text: BluetoothService.getSignalStrength(modelData) + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + } + } + + // --- Item 2: Battery --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + spacing: Style.marginXS + NIcon { + icon: { + var b = BluetoothService.getBatteryPercent(modelData); + return BatteryService.getIcon(b !== null ? b : 0, false, false, b !== null); + } + pointSize: Style.fontSizeXS + color: Color.mOnSurface + } + NText { + text: { + var b = BluetoothService.getBatteryPercent(modelData); + return b === null ? "-" : (b + "%"); + } + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + } + } + // --- Item 3: Pair state --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + spacing: Style.marginXS + NIcon { + icon: "link" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + } + NText { + text: modelData.paired ? I18n.tr("common.yes") : I18n.tr("common.no") + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + } + } + // --- Item 4: Trust state --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + spacing: Style.marginXS + NIcon { + icon: "shield-check" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + } + NText { + text: modelData.trusted ? I18n.tr("common.yes") : I18n.tr("common.no") + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + } + } + // --- Item 5: Address --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + spacing: Style.marginXS + NIcon { + icon: "hash" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + } + NText { + text: modelData.address || "-" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + } + } + // --- Item 6: Auto-connect --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + Layout.topMargin: -Style.marginXXS + spacing: Style.marginXS + visible: Settings.data.network.bluetoothAutoConnect + NIcon { + icon: BluetoothService.getDeviceAutoConnect(modelData) ? "repeat" : "repeat-off" + pointSize: Style.fontSizeXS + } + NCheckbox { + label: I18n.tr("common.auto-connect") + labelSize: Style.fontSizeXS + baseSize: Style.baseWidgetSize * 0.5 + checked: BluetoothService.getDeviceAutoConnect(modelData) + onToggled: checked => BluetoothService.setDeviceAutoConnect(modelData, checked) + } + } + } + } + } + } + } + + // PIN Authentication Overlay (This part needs some love :P) + Rectangle { + id: pinOverlay + visible: !root.showOnlyLists && BluetoothService.pinRequired + anchors.centerIn: parent + width: Math.min(parent.width * 0.9, 400) + height: pinCol.implicitHeight + Style.margin2L + color: Color.mSurface + radius: Style.radiusM + border.color: Style.boxBorderColor + border.width: Style.borderS + z: 1000 + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.AllButtons + onClicked: mouse => mouse.accepted = true + onWheel: wheel => wheel.accepted = true + } + + ColumnLayout { + id: pinCol + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginL + + NIcon { + icon: "lock" + pointSize: 48 + color: Color.mPrimary + Layout.alignment: Qt.AlignHCenter + } + NText { + text: I18n.tr("panels.connections.authentication-required") + pointSize: Style.fontSizeXL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + } + NText { + text: I18n.tr("panels.connections.pin-instructions") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + } + NTextInput { + id: pinInput + Layout.fillWidth: true + placeholderText: "123456" + inputIconName: "key" + onVisibleChanged: { + if (visible) { + text = ""; + inputItem.forceActiveFocus(); + } + } + inputItem.onEditingFinished: { + if (text.length > 0) { + BluetoothService.submitPin(text); + text = ""; + } + } + } + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: Style.marginM + NButton { + text: I18n.tr("common.cancel") + icon: "x" + onClicked: BluetoothService.cancelPairing() + } + NButton { + text: I18n.tr("common.confirm") + icon: "check" + backgroundColor: Color.mPrimary + textColor: Color.mOnPrimary + enabled: pinInput.text.length > 0 + onClicked: { + BluetoothService.submitPin(pinInput.text); + pinInput.text = ""; + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Connections/ConnectionsTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Connections/ConnectionsTab.qml new file mode 100644 index 0000000..de2f7c8 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Connections/ConnectionsTab.qml @@ -0,0 +1,43 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.Networking +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.wifi") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.bluetooth") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + WifiSubTab {} + BluetoothSubTab {} + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Connections/WifiSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Connections/WifiSubTab.qml new file mode 100644 index 0000000..092ac77 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Connections/WifiSubTab.qml @@ -0,0 +1,1462 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import Quickshell +import Quickshell.Bluetooth + +import qs.Commons +import qs.Services.Networking +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +Item { + id: root + Layout.fillWidth: true + implicitHeight: mainLayout.implicitHeight + + // Configuration for shared use (e.g. by NetworkPanel) + property bool showOnlyLists: false + + // State properties + property string passwordSsid: "" + property string identity: "" + property string enterpriseEap: "peap" + property string enterprisePhase2: "mschapv2" + property string enterpriseAnonIdentity: "" + property string enterpriseCaCert: "" + property string expandedSsid: "" + property string infoSsid: "" + property int ipVersion: 4 + property bool detailsGrid: (Settings.data && Settings.data.network && Settings.data.network.wifiDetailsViewMode === "grid") + + // Freezing models for password entry + property var cachedNetworks: ({}) + + onPasswordSsidChanged: { + if (passwordSsid && passwordSsid.length > 0) { + try { + cachedNetworks = JSON.parse(JSON.stringify(NetworkService.networks)); + } catch (e) { + cachedNetworks = Object.assign({}, NetworkService.networks); + } + } else { + cachedNetworks = ({}); + } + } + + readonly property var activeNetworks: (passwordSsid && passwordSsid.length > 0) ? Object.values(cachedNetworks) : Object.values(NetworkService.networks) + + readonly property var connectedNetworks: { + if (!NetworkService.wifiEnabled) { + return []; + } + return activeNetworks.filter(n => n.connected).sort((a, b) => b.signal - a.signal); + } + + readonly property var savedNetworks: { + if (!NetworkService.wifiEnabled) { + return []; + } + return activeNetworks.filter(n => !n.connected && n.existing).sort((a, b) => b.signal - a.signal); + } + + readonly property var availableNetworks: { + if (!NetworkService.wifiEnabled) { + return []; + } + return activeNetworks.filter(n => !n.connected && !n.existing).sort((a, b) => b.signal - a.signal); + } + + // Combined visibility check: tab must be visible AND the window must be visible + readonly property bool effectivelyVisible: root.visible && Window.window && Window.window.visible + + onEffectivelyVisibleChanged: { + if (effectivelyVisible) { + SystemStatService.registerComponent("wifi-subtab"); + if (NetworkService.wifiEnabled && !NetworkService.scanningActive && !showOnlyLists) { + NetworkService.scan(); + NetworkService.refreshActiveWifiDetails(); + } + } else { + SystemStatService.unregisterComponent("wifi-subtab"); + } + } + + Component.onDestruction: { + SystemStatService.unregisterComponent("wifi-subtab"); + } + + // Actions + function requestPassword(ssid) { + passwordSsid = ssid; + identity = ""; + enterpriseEap = "peap"; + enterprisePhase2 = "mschapv2"; + enterpriseAnonIdentity = ""; + enterpriseCaCert = ""; + expandedSsid = ""; + } + function submitPassword(ssid, password, identity = "") { + NetworkService.connect(ssid, password, false, identity, { + eap: enterpriseEap, + phase2: enterprisePhase2, + anonIdentity: enterpriseAnonIdentity, + caCert: enterpriseCaCert + }); + passwordSsid = ""; + } + function cancelPassword() { + passwordSsid = ""; + } + function requestForget(ssid) { + expandedSsid = (expandedSsid === ssid) ? "" : ssid; + } + function confirmForget(ssid) { + NetworkService.forget(ssid); + expandedSsid = ""; + } + function cancelForget() { + expandedSsid = ""; + } + + ColumnLayout { + id: mainLayout + anchors.left: parent.left + anchors.right: parent.right + spacing: Style.marginL + + // Master Control Section + NBox { + visible: !root.showOnlyLists + Layout.fillWidth: true + Layout.preferredHeight: masterControlCol.implicitHeight + Style.margin2L + color: Color.mSurface + + ColumnLayout { + id: masterControlCol + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NToggle { + label: I18n.tr("common.wifi") + icon: NetworkService.wifiEnabled ? "wifi" : "wifi-off" + checked: NetworkService.wifiEnabled + onToggled: checked => NetworkService.setWifiEnabled(checked) + enabled: !NetworkService.airplaneModeEnabled && NetworkService.wifiAvailable + Layout.alignment: Qt.AlignVCenter + } + } + + NDivider { + Layout.fillWidth: true + visible: NetworkService.wifiEnabled + } + + NText { + visible: !root.showOnlyLists && NetworkService.wifiEnabled + Layout.fillWidth: true + text: I18n.tr("panels.connections.wifi-header-text") + color: Color.mOnSurfaceVariant + richTextEnabled: true + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + } + } + } + + Item { + visible: !showOnlyLists + Layout.fillWidth: true + } + + // Network List [1] (Connected) + NBox { + id: connectedBox + visible: root.connectedNetworks.length > 0 && NetworkService.wifiEnabled + Layout.fillWidth: true + Layout.preferredHeight: connectedCol.implicitHeight + Style.margin2M + border.color: showOnlyLists ? Style.boxBorderColor : "transparent" + color: showOnlyLists ? Color.mSurfaceVariant : "transparent" + + ColumnLayout { + id: connectedCol + anchors.fill: parent + anchors.topMargin: Style.marginM + anchors.bottomMargin: Style.marginM + anchors.leftMargin: showOnlyLists ? Style.marginL : 0 + anchors.rightMargin: showOnlyLists ? Style.marginL : 0 + spacing: Style.marginM + + NLabel { + label: I18n.tr("common.connected") + Layout.fillWidth: true + Layout.leftMargin: Style.marginS + } + + Repeater { + model: root.connectedNetworks + delegate: nboxDelegate + } + } + } + + // Network List [2] (Saved) + NBox { + id: savedBox + visible: root.savedNetworks.length > 0 && NetworkService.wifiEnabled + Layout.fillWidth: true + Layout.preferredHeight: savedCol.implicitHeight + Style.margin2M + border.color: showOnlyLists ? Style.boxBorderColor : "transparent" + color: showOnlyLists ? Color.mSurfaceVariant : "transparent" + + ColumnLayout { + id: savedCol + anchors.fill: parent + anchors.topMargin: Style.marginM + anchors.bottomMargin: Style.marginM + anchors.leftMargin: showOnlyLists ? Style.marginL : 0 + anchors.rightMargin: showOnlyLists ? Style.marginL : 0 + spacing: Style.marginM + + NLabel { + label: I18n.tr("wifi.panel.known-networks") + Layout.fillWidth: true + Layout.leftMargin: Style.marginS + } + + Repeater { + model: root.savedNetworks + delegate: nboxDelegate + } + } + } + + // Network List [3] (Available) + NBox { + id: availableBox + visible: root.availableNetworks.length > 0 && NetworkService.wifiEnabled + Layout.fillWidth: true + Layout.preferredHeight: availableCol.implicitHeight + Style.margin2M + border.color: showOnlyLists ? Style.boxBorderColor : "transparent" + color: showOnlyLists ? Color.mSurfaceVariant : "transparent" + + ColumnLayout { + id: availableCol + anchors.fill: parent + anchors.topMargin: Style.marginM + anchors.bottomMargin: Style.marginM + anchors.leftMargin: showOnlyLists ? Style.marginL : 0 + anchors.rightMargin: showOnlyLists ? Style.marginL : 0 + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: Style.marginS + spacing: Style.marginS + + NLabel { + label: I18n.tr("wifi.panel.available-networks") + Layout.fillWidth: true + } + } + + Repeater { + model: root.availableNetworks + delegate: nboxDelegate + } + + // Add hidden network button + NBox { + visible: !root.showOnlyLists + Layout.fillWidth: true + Layout.preferredHeight: addHiddenContent.implicitHeight + Style.margin2M + color: addHiddenMouseArea.containsMouse ? Color.mSurfaceVariant : Color.mSurface + radius: Style.radiusM + + RowLayout { + id: addHiddenContent + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + + NIcon { + icon: "plus" + pointSize: Style.fontSizeXXL + color: Color.mOnSurfaceVariant + } + + NText { + text: I18n.tr("wifi.panel.add-network") + pointSize: Style.fontSizeM + color: Color.mOnSurface + Layout.fillWidth: true + } + } + + MouseArea { + id: addHiddenMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + addNetworkPopup.customSsid = ""; + addNetworkPopup.customPassword = ""; + addNetworkPopup.customSecurityKey = "wpa2-psk"; + addNetworkPopup.open(); + } + } + } + } + } + + Item { + visible: !showOnlyLists && NetworkService.wifiEnabled + Layout.fillWidth: true + } + + // Airplane Mode + NBox { + id: miscSettingsBox + visible: !root.showOnlyLists && miscSettingsCol.visibleChildren.length > 0 + Layout.fillWidth: true + Layout.preferredHeight: miscSettingsCol.implicitHeight + Style.margin2XL + color: Color.mSurface + + ColumnLayout { + id: miscSettingsCol + anchors.fill: parent + anchors.margins: Style.marginXL + spacing: Style.marginM + + NToggle { + visible: NetworkService.wifiAvailable && BluetoothService.bluetoothAvailable + label: I18n.tr("toast.airplane-mode.title") + description: I18n.tr("toast.airplane-mode.description") + icon: NetworkService.airplaneModeEnabled ? "plane" : "plane-off" + checked: NetworkService.airplaneModeEnabled + onToggled: checked => NetworkService.setAirplaneMode(checked) + } + } + } + } + + // Add Hidden Network Popup + Popup { + id: addNetworkPopup + visible: false + anchors.centerIn: parent + width: Math.min(parent.width * 0.9, 400 * Style.uiScaleRatio) + height: addNetworkContent.implicitHeight + Style.margin2L + modal: true + focus: true + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + + property string customSsid: "" + property string customPassword: "" + property string customIdentity: "" + property string customSecurityKey: "wpa2-psk" + property string customEnterpriseEap: "peap" + property string customEnterprisePhase2: "mschapv2" + property string customEnterpriseAnonIdentity: "" + property string customEnterpriseCaCert: "" + property bool customShowPassword: false + property bool customIsHidden: false + + onOpened: { + customSsidInput.inputItem.forceActiveFocus(); + } + + // Make background transparent so we can use NDropShadow + background: Item {} + + // Shadow effect (behind background) + NDropShadow { + anchors.fill: customPopupBg + source: customPopupBg + autoPaddingEnabled: true + z: -1 + } + + Rectangle { + id: customPopupBg + anchors.fill: parent + radius: Style.radiusL + color: Qt.alpha(Color.mSurface, 0.95) + border.color: Color.mOutline + border.width: Style.borderS + } + + ColumnLayout { + id: addNetworkContent + anchors.centerIn: parent + width: parent.width - (Style.marginL * 2) + spacing: Style.marginM + + // Header with Icon + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NImageRounded { + Layout.preferredWidth: Style.fontSizeXXL * 2 + Layout.preferredHeight: Style.fontSizeXXL * 2 + fallbackIcon: "wifi" + borderWidth: 0 + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NText { + text: I18n.tr("wifi.panel.add-network") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + wrapMode: Text.Wrap + Layout.fillWidth: true + } + } + } + + // Input Fields + NTextInput { + id: customSsidInput + Layout.fillWidth: true + inputIconName: "wifi" + placeholderText: I18n.tr("wifi.panel.network-name-ssid") + label: I18n.tr("wifi.panel.network-name-ssid") + text: addNetworkPopup.customSsid + onTextChanged: addNetworkPopup.customSsid = text + onEditingFinished: { + if (addNetworkPopup.customSsid.length > 0 && (addNetworkPopup.customSecurityKey === "open" || addNetworkPopup.customPassword.length > 0)) { + NetworkService.connect(addNetworkPopup.customSsid, addNetworkPopup.customPassword, addNetworkPopup.customIsHidden, addNetworkPopup.customSecurityKey, addNetworkPopup.customIdentity, { + eap: addNetworkPopup.customEnterpriseEap, + phase2: addNetworkPopup.customEnterprisePhase2, + anonIdentity: addNetworkPopup.customEnterpriseAnonIdentity, + caCert: addNetworkPopup.customEnterpriseCaCert + }); + addNetworkPopup.close(); + } + } + } + + NComboBox { + Layout.fillWidth: true + model: NetworkService.supportedSecurityTypes + currentKey: addNetworkPopup.customSecurityKey + onSelected: key => { + addNetworkPopup.customSecurityKey = key; + } + } + + ColumnLayout { + visible: addNetworkPopup.customSecurityKey.indexOf("-eap") !== -1 + Layout.fillWidth: true + spacing: Style.marginM + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("wifi.enterprise.eap-method") + model: [ + { + key: "peap", + name: "PEAP" + }, + { + key: "ttls", + name: "TTLS" + } + ] + currentKey: addNetworkPopup.customEnterpriseEap + onSelected: key => addNetworkPopup.customEnterpriseEap = key + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("wifi.enterprise.phase2-auth") + model: [ + { + key: "mschapv2", + name: "MSCHAPv2" + }, + { + key: "pap", + name: "PAP" + }, + { + key: "mschap", + name: "MSCHAP" + }, + { + key: "chap", + name: "CHAP" + } + ] + currentKey: addNetworkPopup.customEnterprisePhase2 + onSelected: key => addNetworkPopup.customEnterprisePhase2 = key + } + } + + NTextInput { + id: customAnonIdentityInput + Layout.fillWidth: true + inputIconName: "user-question" + visible: addNetworkPopup.customSecurityKey.indexOf("-eap") !== -1 + placeholderText: I18n.tr("wifi.enterprise.anonymous-identity") + label: I18n.tr("wifi.enterprise.anonymous-identity") + text: addNetworkPopup.customEnterpriseAnonIdentity + onTextChanged: addNetworkPopup.customEnterpriseAnonIdentity = text + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + visible: addNetworkPopup.customSecurityKey.indexOf("-eap") !== -1 + + NTextInput { + id: customCaCertInput + Layout.fillWidth: true + inputIconName: "certificate" + placeholderText: I18n.tr("wifi.enterprise.ca-cert") + label: I18n.tr("wifi.enterprise.ca-cert") + text: addNetworkPopup.customEnterpriseCaCert + onTextChanged: addNetworkPopup.customEnterpriseCaCert = text + } + + NIconButton { + icon: "folder-open" + Layout.alignment: Qt.AlignBottom + onClicked: caCertPicker.openForAddNetwork() + } + } + + NTextInput { + id: customIdentityInput + Layout.fillWidth: true + inputIconName: "user" + visible: addNetworkPopup.customSecurityKey.indexOf("-eap") !== -1 + placeholderText: I18n.tr("wifi.enterprise.username") + label: I18n.tr("wifi.enterprise.username") + text: addNetworkPopup.customIdentity + onTextChanged: addNetworkPopup.customIdentity = text + } + + NTextInput { + id: customPasswordInput + Layout.fillWidth: true + inputIconName: "key" + visible: addNetworkPopup.customSecurityKey !== "open" + placeholderText: I18n.tr("common.password") + label: I18n.tr("common.password") + text: addNetworkPopup.customPassword + onTextChanged: addNetworkPopup.customPassword = text + inputItem.echoMode: addNetworkPopup.customShowPassword ? TextInput.Normal : TextInput.Password + onEditingFinished: { + if (addNetworkPopup.customSsid.length > 0 && addNetworkPopup.customPassword.length > 0) { + NetworkService.connect(addNetworkPopup.customSsid, addNetworkPopup.customPassword, addNetworkPopup.customIsHidden, addNetworkPopup.customSecurityKey, addNetworkPopup.customIdentity, { + eap: addNetworkPopup.customEnterpriseEap, + phase2: addNetworkPopup.customEnterprisePhase2, + anonIdentity: addNetworkPopup.customEnterpriseAnonIdentity, + caCert: addNetworkPopup.customEnterpriseCaCert + }); + addNetworkPopup.close(); + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NCheckbox { + Layout.fillWidth: true + label: I18n.tr("wifi.panel.show-password") + checked: addNetworkPopup.customShowPassword + onToggled: checked => addNetworkPopup.customShowPassword = checked + visible: addNetworkPopup.customSecurityKey !== "open" + } + + NCheckbox { + Layout.fillWidth: true + label: I18n.tr("wifi.panel.hidden-network") + checked: addNetworkPopup.customIsHidden + onToggled: checked => addNetworkPopup.customIsHidden = checked + } + } + + // Actions + RowLayout { + Layout.fillWidth: true + Layout.topMargin: Style.marginS + spacing: Style.marginM + + Item { + Layout.fillWidth: true + } // Spacer + + NButton { + text: I18n.tr("common.cancel") + backgroundColor: Color.mSurfaceVariant + textColor: Color.mOnSurfaceVariant + outlined: false + onClicked: addNetworkPopup.close() + } + + NButton { + text: I18n.tr("common.connect") + backgroundColor: Color.mPrimary + textColor: Color.mOnPrimary + enabled: addNetworkPopup.customSsid.length > 0 && (addNetworkPopup.customSecurityKey === "open" || addNetworkPopup.customPassword.length > 0) && (addNetworkPopup.customSecurityKey.indexOf("-eap") === -1 || addNetworkPopup.customIdentity.length > 0) + onClicked: { + NetworkService.connect(addNetworkPopup.customSsid, addNetworkPopup.customPassword, addNetworkPopup.customIsHidden, addNetworkPopup.customSecurityKey, addNetworkPopup.customIdentity, { + eap: addNetworkPopup.customEnterpriseEap, + phase2: addNetworkPopup.customEnterprisePhase2, + anonIdentity: addNetworkPopup.customEnterpriseAnonIdentity, + caCert: addNetworkPopup.customEnterpriseCaCert + }); + addNetworkPopup.close(); + } + } + } + } + } + + // Shared Delegate + Component { + id: nboxDelegate + NBox { + id: networkItem + + readonly property bool isBusy: NetworkService.connectingTo === modelData.ssid || NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid + readonly property bool isExpanded: root.infoSsid === modelData.ssid + readonly property bool isEnterprise: NetworkService.isEnterprise(modelData.security) + + function getContentColors(defaultColors = [Color.mSurface, Color.mOnSurface]) { + if (root.passwordSsid === modelData.ssid || NetworkService.connectingTo === modelData.ssid) { + return [Color.mPrimary, Color.mOnPrimary]; + } + if (modelData.connected && NetworkService.internetConnectivity && NetworkService.disconnectingFrom !== modelData.ssid) { + return [Color.mPrimary, Color.mOnPrimary]; + } + if (NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid) { + return [Color.mError, Color.mOnError]; + } + if (modelData.connected && !NetworkService.internetConnectivity) { + return [Color.mError, Color.mOnError]; + } + return defaultColors; + } + + Layout.fillWidth: true + Layout.preferredHeight: deviceColumn.implicitHeight + (Style.marginXL) + radius: Style.radiusM + clip: true + forceOpaque: true + color: networkItem.getContentColors()[0] + + ColumnLayout { + id: deviceColumn + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginS + + RowLayout { + id: deviceLayout + Layout.fillWidth: true + spacing: Style.marginM + Layout.alignment: Qt.AlignVCenter + + NIcon { + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + horizontalAlignment: Text.AlignLeft + icon: NetworkService.getSignalInfo(modelData.signal, modelData.connected).icon + pointSize: Style.fontSizeXXL + color: networkItem.getContentColors()[1] + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(parent, NetworkService.getSignalInfo(modelData.signal, modelData.connected).label + " (" + modelData.signal + "%)") + onExited: TooltipService.hide() + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXXS + + NText { + text: modelData.ssid + pointSize: Style.fontSizeM + font.weight: modelData.connected ? Style.fontWeightBold : Style.fontWeightMedium + elide: Text.ElideRight + color: networkItem.getContentColors()[1] + Layout.fillWidth: true + } + + RowLayout { + spacing: Style.marginXS + + NIcon { + icon: NetworkService.isSecured(modelData.security) ? "lock" : "lock-open" + pointSize: Style.fontSizeXXS + color: Qt.alpha(networkItem.getContentColors()[1], Style.opacityHeavy) + visible: !modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid + } + + NText { + text: { + if (NetworkService.disconnectingFrom === modelData.ssid) { + return I18n.tr("wifi.panel.disconnecting"); + } + if (NetworkService.forgettingNetwork === modelData.ssid) { + return I18n.tr("wifi.panel.forgetting"); + } + if (modelData.connected) { + switch (NetworkService.networkConnectivity) { + case "full": + return I18n.tr("common.connected"); + case "limited": + return I18n.tr("wifi.panel.internet-limited"); + case "portal": + return I18n.tr("wifi.panel.action-required"); + default: + return NetworkService.networkConnectivity; + } + } + return NetworkService.isSecured(modelData.security) ? modelData.security : I18n.tr("wifi.panel.security-open"); + } + pointSize: Style.fontSizeXXS + color: Qt.alpha(networkItem.getContentColors()[1], Style.opacityHeavy) + } + + // Network speed indicators (visible when connected and speed > 0) + RowLayout { + visible: (modelData.connected && NetworkService.networkConnectivity === "full") && (SystemStatService.rxSpeed > 0 || SystemStatService.txSpeed > 0) + spacing: 2 + Layout.leftMargin: Style.marginXS + Layout.fillWidth: false + + NIcon { + visible: SystemStatService.rxSpeed > 0 + icon: "arrow-down" + pointSize: Style.fontSizeXXS + color: Qt.alpha(networkItem.getContentColors()[1], Style.opacityHeavy) + } + + NText { + visible: SystemStatService.rxSpeed > 0 + text: SystemStatService.formatSpeed(SystemStatService.rxSpeed) + pointSize: Style.fontSizeXXS + color: Qt.alpha(networkItem.getContentColors()[1], Style.opacityHeavy) + elide: Text.ElideNone + } + + Item { + visible: SystemStatService.rxSpeed > 0 && SystemStatService.txSpeed > 0 + width: Style.marginXS + height: 1 + } + + NIcon { + visible: SystemStatService.txSpeed > 0 + icon: "arrow-up" + pointSize: Style.fontSizeXXS + color: Qt.alpha(networkItem.getContentColors()[1], Style.opacityHeavy) + } + + NText { + visible: SystemStatService.txSpeed > 0 + text: SystemStatService.formatSpeed(SystemStatService.txSpeed) + pointSize: Style.fontSizeXXS + color: Qt.alpha(networkItem.getContentColors()[1], Style.opacityHeavy) + elide: Text.ElideNone + } + } + } + } + + Item { + Layout.fillWidth: true + } + + RowLayout { + spacing: Style.marginS + + NBusyIndicator { + visible: networkItem.isBusy + running: visible && root.effectivelyVisible + color: networkItem.getContentColors()[1] + size: Style.baseWidgetSize * 0.5 + } + + NIconButton { + visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid + icon: "info" + tooltipText: I18n.tr("common.info") + baseSize: Style.baseWidgetSize * 0.75 + colorBg: Color.mSurfaceVariant + colorFg: Color.mOnSurface + colorBorder: "transparent" + colorBorderHover: "transparent" + onClicked: { + if (root.infoSsid === modelData.ssid) { + root.infoSsid = ""; + } else { + root.infoSsid = modelData.ssid; + NetworkService.refreshActiveWifiDetails(); + } + } + } + + NIconButton { + visible: !root.showOnlyLists && modelData.existing && !modelData.connected && !networkItem.isBusy + icon: "trash" + tooltipText: I18n.tr("tooltips.forget-network") + baseSize: Style.baseWidgetSize * 0.75 + colorBg: Color.mPrimary + colorFg: Color.mOnPrimary + colorBorder: "transparent" + colorBorderHover: "transparent" + onClicked: root.requestForget(modelData.ssid) + } + + NButton { + id: button + visible: !modelData.connected && NetworkService.connectingTo !== modelData.ssid && root.passwordSsid !== modelData.ssid + enabled: !NetworkService.connecting && !networkItem.isBusy + fontSize: Style.fontSizeS + backgroundColor: Color.mPrimary + textColor: Color.mOnPrimary + text: I18n.tr("common.connect") + onClicked: { + if (modelData.existing || !NetworkService.isSecured(modelData.security)) { + NetworkService.connect(modelData.ssid); + } else { + root.requestPassword(modelData.ssid); + } + } + } + + NButton { + id: disconnectButton + visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid + text: I18n.tr("common.disconnect") + fontSize: Style.fontSizeS + backgroundColor: Color.mSurfaceVariant + textColor: Color.mOnSurface + onClicked: NetworkService.disconnect(modelData.ssid) + } + } + } + + // Connection info details + Rectangle { + visible: networkItem.isExpanded + Layout.fillWidth: true + implicitHeight: infoColumn.implicitHeight + Style.margin2S + radius: Style.radiusXS + color: Color.mSurfaceVariant + border.width: Style.borderS + border.color: Style.boxBorderColor + clip: true + + onVisibleChanged: { + if (visible && infoColumn && infoColumn.forceLayout) { + Qt.callLater(function () { + infoColumn.forceLayout(); + }); + } + } + + NIconButton { + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: Style.marginS + icon: root.detailsGrid ? "layout-list" : "layout-grid" + tooltipText: root.detailsGrid ? I18n.tr("tooltips.list-view") : I18n.tr("tooltips.grid-view") + baseSize: Style.baseWidgetSize * 0.65 + onClicked: { + root.detailsGrid = !root.detailsGrid; + Settings.data.network.wifiDetailsViewMode = root.detailsGrid ? "grid" : "list"; + } + z: 1 + } + + GridLayout { + id: infoColumn + anchors.fill: parent + anchors.margins: Style.marginS + flow: root.detailsGrid ? GridLayout.TopToBottom : GridLayout.LeftToRight + rows: root.detailsGrid ? 3 : 6 + columns: root.detailsGrid ? 2 : 1 + columnSpacing: Style.marginM + rowSpacing: Style.marginXS + onColumnsChanged: { + if (infoColumn.forceLayout) { + Qt.callLater(function () { + infoColumn.forceLayout(); + }); + } + } + + // --- Item 1: Interface --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + spacing: Style.marginXS + NIcon { + icon: "network" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(parent, I18n.tr("wifi.panel.interface")) + onExited: TooltipService.hide() + } + } + NText { + text: NetworkService.activeWifiIf || "-" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + wrapMode: root.detailsGrid ? Text.NoWrap : Text.WrapAtWordBoundaryOrAnywhere + elide: root.detailsGrid ? Text.ElideRight : Text.ElideNone + maximumLineCount: root.detailsGrid ? 1 : 6 + clip: true + + MouseArea { + anchors.fill: parent + enabled: (NetworkService.activeWifiIf && NetworkService.activeWifiIf.length > 0) + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: TooltipService.show(parent, I18n.tr("tooltips.copy-address")) + onExited: TooltipService.hide() + onClicked: { + const value = NetworkService.activeWifiIf || ""; + if (value.length > 0) { + Quickshell.execDetached(["wl-copy", value]); + ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("common.copied-to-clipboard"), "wifi"); + } + } + } + } + } + // --- Item 2: Band & Channel & Width of channel --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + spacing: Style.marginXS + NIcon { + icon: "router" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(parent, I18n.tr("common.frequency")) + onExited: TooltipService.hide() + } + } + NText { + text: NetworkService.activeWifiDetails.band || "-" + pointSize: Style.fontSizeXS + Layout.fillWidth: true + } + } + // --- Item 3: Link Speed --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + spacing: Style.marginXS + NIcon { + icon: "gauge" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(parent, I18n.tr("wifi.panel.link-speed")) + onExited: TooltipService.hide() + } + } + NText { + text: (NetworkService.activeWifiDetails.rateShort && NetworkService.activeWifiDetails.rateShort.length > 0) ? NetworkService.activeWifiDetails.rateShort : ((NetworkService.activeWifiDetails.rate && NetworkService.activeWifiDetails.rate.length > 0) ? NetworkService.activeWifiDetails.rate : "-") + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + } + } + // --- Item 4: IPv4 || IPv6 --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + spacing: Style.marginXS + NIcon { + icon: "network" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(parent, root.ipVersion === 4 ? I18n.tr("wifi.panel.ipv4") : I18n.tr("wifi.panel.ipv6")) + onExited: TooltipService.hide() + onClicked: { + root.ipVersion = root.ipVersion === 4 ? 6 : 4; + TooltipService.show(parent, root.ipVersion === 4 ? I18n.tr("wifi.panel.ipv4") : I18n.tr("wifi.panel.ipv6")); + } + } + } + NText { + text: root.ipVersion === 4 ? (NetworkService.activeWifiDetails.ipv4 || "-") : ((NetworkService.activeWifiDetails.ipv6 || []).join(", ") || "-") + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + + MouseArea { + anchors.fill: parent + enabled: root.ipVersion === 4 ? !!(NetworkService.activeWifiDetails.ipv4 && NetworkService.activeWifiDetails.ipv4.length > 0) : !!(NetworkService.activeWifiDetails.ipv6 && NetworkService.activeWifiDetails.ipv6.length > 0) + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: TooltipService.show(parent, I18n.tr("tooltips.copy-address")) + onExited: TooltipService.hide() + onClicked: { + const value = root.ipVersion === 4 ? (NetworkService.activeWifiDetails.ipv4 || "") : ((NetworkService.activeWifiDetails.ipv6 || []).join(", ") || ""); + if (value.length > 0) { + Quickshell.execDetached(["wl-copy", value]); + ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("common.copied-to-clipboard"), "wifi"); + } + } + } + } + } + // --- Item 5: DNS --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + spacing: Style.marginXS + NIcon { + icon: "world" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(parent, root.ipVersion === 4 ? I18n.tr("wifi.panel.dns") + " (" + I18n.tr("wifi.panel.ipv4") + ")" : I18n.tr("wifi.panel.dns") + " (" + I18n.tr("wifi.panel.ipv6") + ")") + onExited: TooltipService.hide() + onClicked: { + root.ipVersion = root.ipVersion === 4 ? 6 : 4; + TooltipService.show(parent, root.ipVersion === 4 ? I18n.tr("wifi.panel.dns") + " (" + I18n.tr("wifi.panel.ipv4") + ")" : I18n.tr("wifi.panel.dns") + " (" + I18n.tr("wifi.panel.ipv6") + ")"); + } + } + } + NText { + text: root.ipVersion === 4 ? ((NetworkService.activeWifiDetails.dns4 || []).join(", ") || "-") : ((NetworkService.activeWifiDetails.dns6 || []).join(", ") || "-") + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + + MouseArea { + anchors.fill: parent + enabled: root.ipVersion === 4 ? !!(NetworkService.activeWifiDetails.dns4 && NetworkService.activeWifiDetails.dns4.length > 0) : !!(NetworkService.activeWifiDetails.dns6 && NetworkService.activeWifiDetails.dns6.length > 0) + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: TooltipService.show(parent, I18n.tr("tooltips.copy-address")) + onExited: TooltipService.hide() + onClicked: { + const value = root.ipVersion === 4 ? ((NetworkService.activeWifiDetails.dns4 || []).join(", ") || "") : ((NetworkService.activeWifiDetails.dns6 || []).join(", ") || ""); + if (value.length > 0) { + Quickshell.execDetached(["wl-copy", value]); + ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("common.copied-to-clipboard"), "wifi"); + } + } + } + } + } + // --- Item 6: Gateway --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + spacing: Style.marginXS + NIcon { + icon: "router" + pointSize: Style.fontSizeXS + color: Color.mOnSurface + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(parent, root.ipVersion === 4 ? I18n.tr("common.gateway") + " (" + I18n.tr("wifi.panel.ipv4") + ")" : I18n.tr("common.gateway") + " (" + I18n.tr("wifi.panel.ipv6") + ")") + onExited: TooltipService.hide() + onClicked: { + root.ipVersion = root.ipVersion === 4 ? 6 : 4; + TooltipService.show(parent, root.ipVersion === 4 ? I18n.tr("common.gateway") + " (" + I18n.tr("wifi.panel.ipv4") + ")" : I18n.tr("common.gateway") + " (" + I18n.tr("wifi.panel.ipv6") + ")"); + } + } + } + NText { + text: root.ipVersion === 4 ? (NetworkService.activeWifiDetails.gateway4 || "-") : ((NetworkService.activeWifiDetails.gateway6 || []).join(", ") || "-") + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + + MouseArea { + anchors.fill: parent + enabled: root.ipVersion === 4 ? !!(NetworkService.activeWifiDetails.gateway4 && NetworkService.activeWifiDetails.gateway4.length > 0) : !!(NetworkService.activeWifiDetails.gateway6 && NetworkService.activeWifiDetails.gateway6.length > 0) + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: TooltipService.show(parent, I18n.tr("tooltips.copy-address")) + onExited: TooltipService.hide() + onClicked: { + const value = root.ipVersion === 4 ? (NetworkService.activeWifiDetails.gateway4 || "") : ((NetworkService.activeWifiDetails.gateway6 || []).join(", ") || ""); + if (value.length > 0) { + Quickshell.execDetached(["wl-copy", value]); + ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("common.copied-to-clipboard"), "wifi"); + } + } + } + } + } + } + } + + // Password input overlay-style within card + Rectangle { + visible: root.passwordSsid === modelData.ssid && !networkItem.isBusy + Layout.fillWidth: true + height: passwordLayout.implicitHeight + Style.margin2S + color: Color.mSurfaceVariant + border.color: Color.mOutline + border.width: Style.borderS + radius: Style.iRadiusXS + + ColumnLayout { + id: passwordLayout + anchors.fill: parent + anchors.margins: Style.marginS + spacing: Style.marginS + + // Inputs Container + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS + + // Enterprise Configuration + ColumnLayout { + visible: networkItem.isEnterprise + Layout.fillWidth: true + spacing: Style.marginS + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("wifi.enterprise.eap-method") + model: [ + { + key: "peap", + name: "PEAP" + }, + { + key: "ttls", + name: "TTLS" + } + ] + currentKey: root.enterpriseEap + onSelected: key => root.enterpriseEap = key + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("wifi.enterprise.phase2-auth") + model: [ + { + key: "mschapv2", + name: "MSCHAPv2" + }, + { + key: "pap", + name: "PAP" + }, + { + key: "mschap", + name: "MSCHAP" + }, + { + key: "chap", + name: "CHAP" + } + ] + currentKey: root.enterprisePhase2 + onSelected: key => root.enterprisePhase2 = key + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: Style.baseWidgetSize * 0.9 + radius: Style.iRadiusXS + color: Color.mSurface + border.color: caCertInput.activeFocus ? Color.mSecondary : Color.mOutline + border.width: Style.borderS + + TextInput { + id: caCertInput + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Style.marginS + font.family: Settings.data.ui.fontFixed + font.pointSize: Style.fontSizeS + color: Color.mOnSurface + selectByMouse: true + text: root.enterpriseCaCert + onTextChanged: root.enterpriseCaCert = text + + NText { + visible: parent.text.length === 0 + anchors.verticalCenter: parent.verticalCenter + text: I18n.tr("wifi.enterprise.ca-cert") + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeS + } + } + } + + NIconButton { + icon: "folder-open" + baseSize: Style.baseWidgetSize * 0.75 + onClicked: caCertPicker.openForInline() + } + } + } + + // Anonymous Identity field (Enterprise only) + Rectangle { + visible: networkItem.isEnterprise + Layout.fillWidth: true + Layout.preferredHeight: Style.baseWidgetSize * 0.9 + radius: Style.iRadiusXS + color: Color.mSurface + border.color: anonIdentityInput.activeFocus ? Color.mSecondary : Color.mOutline + border.width: Style.borderS + + TextInput { + id: anonIdentityInput + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Style.marginS + font.family: Settings.data.ui.fontFixed + font.pointSize: Style.fontSizeS + color: Color.mOnSurface + selectByMouse: true + text: root.enterpriseAnonIdentity + onTextChanged: root.enterpriseAnonIdentity = text + onEditingFinished: identityInput.forceActiveFocus() + + NText { + visible: parent.text.length === 0 + anchors.verticalCenter: parent.verticalCenter + text: I18n.tr("wifi.enterprise.anonymous-identity") + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeS + } + } + } + + // Identity field (Enterprise only) + Rectangle { + visible: networkItem.isEnterprise + Layout.fillWidth: true + Layout.preferredHeight: Style.baseWidgetSize * 0.9 + radius: Style.iRadiusXS + color: Color.mSurface + border.color: identityInput.activeFocus ? Color.mSecondary : Color.mOutline + border.width: Style.borderS + + TextInput { + id: identityInput + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Style.marginS + font.family: Settings.data.ui.fontFixed + font.pointSize: Style.fontSizeS + color: Color.mOnSurface + selectByMouse: true + onVisibleChanged: { + if (visible) { + forceActiveFocus(); + } + } + onEditingFinished: pwdInput.forceActiveFocus() + + NText { + visible: parent.text.length === 0 + anchors.verticalCenter: parent.verticalCenter + text: I18n.tr("wifi.enterprise.username") + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeS + } + } + } + + // Password field + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: Style.baseWidgetSize * 0.9 + radius: Style.iRadiusXS + color: Color.mSurface + border.color: pwdInput.activeFocus ? Color.mSecondary : Color.mOutline + border.width: Style.borderS + + TextInput { + id: pwdInput + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Style.marginS + font.family: Settings.data.ui.fontFixed + font.pointSize: Style.fontSizeS + color: Color.mOnSurface + echoMode: TextInput.Password + selectByMouse: true + passwordCharacter: "โ—" + onVisibleChanged: { + if (visible && !networkItem.isEnterprise) { + forceActiveFocus(); + } + } + onEditingFinished: { + if (text && !NetworkService.connecting) { + if (!networkItem.isEnterprise || identityInput.text.length > 0) { + root.submitPassword(modelData.ssid, text, identityInput.text); + } + } + } + + NText { + visible: parent.text.length === 0 + anchors.verticalCenter: parent.verticalCenter + text: networkItem.isEnterprise ? I18n.tr("wifi.enterprise.password") : I18n.tr("wifi.panel.enter-password") + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeS + } + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + Item { + Layout.fillWidth: true + } + + NButton { + text: I18n.tr("common.connect") + fontSize: Style.fontSizeS + enabled: pwdInput.text.length > 0 && (!networkItem.isEnterprise || identityInput.text.length > 0) && !NetworkService.connecting + outlined: true + onClicked: root.submitPassword(modelData.ssid, pwdInput.text, identityInput.text) + } + + NIconButton { + icon: "close" + baseSize: Style.baseWidgetSize * 0.75 + onClicked: root.cancelPassword() + } + } + } + } + + // Forget network confirmation within card + Rectangle { + visible: root.expandedSsid === modelData.ssid && !networkItem.isBusy + Layout.fillWidth: true + height: forgetRow.implicitHeight + Style.margin2S + color: Color.mSurfaceVariant + radius: Style.radiusS + border.width: Style.borderS + border.color: Color.mOutline + + RowLayout { + id: forgetRow + anchors.fill: parent + anchors.margins: Style.marginS + spacing: Style.marginM + + RowLayout { + NIcon { + icon: "trash" + pointSize: Style.fontSizeL + color: Color.mError + } + + NText { + text: I18n.tr("wifi.panel.forget-network") + pointSize: Style.fontSizeS + color: Color.mError + Layout.fillWidth: true + } + } + + NButton { + id: forgetButton + text: I18n.tr("wifi.panel.forget") + fontSize: Style.fontSizeXXS + backgroundColor: Color.mError + outlined: !forgetButton.hovered + onClicked: root.confirmForget(modelData.ssid) + } + + NIconButton { + icon: "close" + baseSize: Style.baseWidgetSize * 0.75 + onClicked: root.cancelForget() + } + } + } + } + } + } + + NFilePicker { + id: caCertPicker + title: I18n.tr("wifi.enterprise.ca-cert") + nameFilters: ["*.pem", "*.crt", "*.cer", "*.der", "*"] + + property bool isForAddNetwork: false + + function openForInline() { + isForAddNetwork = false; + openFilePicker(); + } + + function openForAddNetwork() { + isForAddNetwork = true; + openFilePicker(); + } + + onAccepted: paths => { + if (paths.length > 0) { + if (isForAddNetwork) { + addNetworkPopup.customEnterpriseCaCert = paths[0]; + } else { + root.enterpriseCaCert = paths[0]; + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ControlCenter/AppearanceSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ControlCenter/AppearanceSubTab.qml new file mode 100644 index 0000000..279d0d1 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ControlCenter/AppearanceSubTab.qml @@ -0,0 +1,87 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + Layout.fillHeight: true + + ColumnLayout { + spacing: Style.marginL + Layout.fillWidth: true + + NComboBox { + id: controlCenterPosition + label: I18n.tr("common.position") + description: I18n.tr("panels.control-center.position-description") + Layout.fillWidth: true + model: [ + { + "key": "close_to_bar_button", + "name": I18n.tr("positions.close-to-bar") + }, + { + "key": "center", + "name": I18n.tr("positions.center") + }, + { + "key": "top_center", + "name": I18n.tr("positions.top-center") + }, + { + "key": "top_left", + "name": I18n.tr("positions.top-left") + }, + { + "key": "top_right", + "name": I18n.tr("positions.top-right") + }, + { + "key": "bottom_center", + "name": I18n.tr("positions.bottom-center") + }, + { + "key": "bottom_left", + "name": I18n.tr("positions.bottom-left") + }, + { + "key": "bottom_right", + "name": I18n.tr("positions.bottom-right") + } + ] + currentKey: Settings.data.controlCenter.position + onSelected: function (key) { + Settings.data.controlCenter.position = key; + } + defaultValue: Settings.getDefaultValue("controlCenter.position") + } + + NComboBox { + id: diskPathComboBox + Layout.fillWidth: true + label: I18n.tr("panels.control-center.system-monitor-disk-path-label") + description: I18n.tr("panels.control-center.system-monitor-disk-path-description") + model: { + const paths = Object.keys(SystemStatService.diskPercents).sort(); + return paths.map(path => ({ + key: path, + name: path + })); + } + currentKey: Settings.data.controlCenter.diskPath || "/" + onSelected: key => Settings.data.controlCenter.diskPath = key + defaultValue: Settings.getDefaultValue("controlCenter.diskPath") || "/" + } + } + + Rectangle { + Layout.fillHeight: true + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ControlCenter/CardsSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ControlCenter/CardsSubTab.qml new file mode 100644 index 0000000..aae1ea3 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ControlCenter/CardsSubTab.qml @@ -0,0 +1,79 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + Layout.fillHeight: true + + required property var cardsModel + required property list cardsDefault + + function saveCards() { + var toSave = []; + for (var i = 0; i < cardsModel.length; i++) { + toSave.push({ + "id": cardsModel[i].id, + "enabled": cardsModel[i].enabled + }); + } + Settings.data.controlCenter.cards = toSave; + } + + NText { + text: I18n.tr("panels.control-center.cards-desc") + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + ColumnLayout { + spacing: Style.marginXXS + Layout.fillWidth: true + + Connections { + target: Settings.data.location + function onWeatherEnabledChanged() { + // Auto-disable weather card when weather is disabled + var newModel = root.cardsModel.slice(); + for (var i = 0; i < newModel.length; i++) { + if (newModel[i].id === "weather-card") { + newModel[i] = Object.assign({}, newModel[i], { + "enabled": Settings.data.location.weatherEnabled + }); + root.cardsModel = newModel; + saveCards(); + break; + } + } + } + } + + NReorderCheckboxes { + Layout.fillWidth: true + model: root.cardsModel + disabledIds: Settings.data.location.weatherEnabled ? [] : ["weather-card"] + onItemToggled: function (index, enabled) { + var newModel = root.cardsModel.slice(); + newModel[index] = Object.assign({}, newModel[index], { + "enabled": enabled + }); + root.cardsModel = newModel; + saveCards(); + } + onItemsReordered: function (fromIndex, toIndex) { + var newModel = root.cardsModel.slice(); + var item = newModel.splice(fromIndex, 1)[0]; + newModel.splice(toIndex, 0, item); + root.cardsModel = newModel; + saveCards(); + } + } + } + } diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ControlCenter/ControlCenterTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ControlCenter/ControlCenterTab.qml new file mode 100644 index 0000000..9f5502c --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ControlCenter/ControlCenterTab.qml @@ -0,0 +1,252 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Noctalia +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + property list cardsModel: [] + property list cardsDefault: [ + { + "id": "profile-card", + "text": "Profile", + "enabled": true, + "required": true + }, + { + "id": "shortcuts-card", + "text": "Shortcuts", + "enabled": true, + "required": false + }, + { + "id": "audio-card", + "text": "Audio Sliders", + "enabled": true, + "required": false + }, + { + "id": "brightness-card", + "text": "Brightness", + "enabled": false, + "required": false + }, + { + "id": "weather-card", + "text": "Weather", + "enabled": true, + "required": false + }, + { + "id": "media-sysmon-card", + "text": "Media and System Monitor", + "enabled": true, + "required": false + } + ] + + Component.onCompleted: { + // Fill out availableWidgets ListModel + availableWidgets.clear(); + var sortedEntries = ControlCenterWidgetRegistry.getAvailableWidgets().slice().sort(); + sortedEntries.forEach(entry => { + const isPlugin = ControlCenterWidgetRegistry.isPluginWidget(entry); + let displayName = entry; + let badges = []; + if (isPlugin) { + const pluginId = entry.replace("plugin:", ""); + const manifest = PluginRegistry.getPluginManifest(pluginId); + if (manifest && manifest.name) { + displayName = manifest.name; + } else { + displayName = pluginId; + } + badges.push({ + "icon": "plugin", + "color": Color.mSecondary + }); + } + availableWidgets.append({ + "key": entry, + "name": displayName, + "badges": badges + }); + }); + // Starts empty + cardsModel = []; + + // Add the cards available in settings + for (var i = 0; i < Settings.data.controlCenter.cards.length; i++) { + const settingCard = Settings.data.controlCenter.cards[i]; + + for (var j = 0; j < cardsDefault.length; j++) { + if (settingCard.id === cardsDefault[j].id) { + var card = cardsDefault[j]; + card.enabled = settingCard.enabled; + // Auto-disable weather card if weather is disabled + if (card.id === "weather-card" && !Settings.data.location.weatherEnabled) { + card.enabled = false; + } + cardsModel.push(card); + } + } + } + + // Add any missing cards from default + for (var i = 0; i < cardsDefault.length; i++) { + var found = false; + for (var j = 0; j < cardsModel.length; j++) { + if (cardsModel[j].id === cardsDefault[i].id) { + found = true; + break; + } + } + + if (!found) { + var card = cardsDefault[i]; + // Auto-disable weather card if weather is disabled + if (card.id === "weather-card" && !Settings.data.location.weatherEnabled) { + card.enabled = false; + } + cardsModel.push(card); + } + } + } + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.appearance") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.cards") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + NTabButton { + text: I18n.tr("common.shortcuts") + tabIndex: 2 + checked: subTabBar.currentIndex === 2 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + Layout.fillWidth: true + Layout.fillHeight: true + + AppearanceSubTab {} + + CardsSubTab { + cardsModel: root.cardsModel + cardsDefault: root.cardsDefault + } + + ShortcutsSubTab { + availableWidgets: availableWidgets + onAddWidgetToSection: (widgetId, section) => _addWidgetToSection(widgetId, section) + onRemoveWidgetFromSection: (section, index) => _removeWidgetFromSection(section, index) + onReorderWidgetInSection: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettingsInSection: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings) + onMoveWidgetBetweenSections: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection) + onOpenPluginSettingsRequested: manifest => pluginSettingsDialog.openPluginSettings(manifest) + } + } + + // --------------------------------- + // Signal functions + // --------------------------------- + function _addWidgetToSection(widgetId, section) { + var newWidget = { + "id": widgetId + }; + if (ControlCenterWidgetRegistry.widgetHasUserSettings(widgetId)) { + var metadata = ControlCenterWidgetRegistry.widgetMetadata[widgetId]; + if (metadata) { + Object.keys(metadata).forEach(function (key) { + newWidget[key] = metadata[key]; + }); + } + } + Settings.data.controlCenter.shortcuts[section].push(newWidget); + } + + function _removeWidgetFromSection(section, index) { + if (index >= 0 && index < Settings.data.controlCenter.shortcuts[section].length) { + var newArray = Settings.data.controlCenter.shortcuts[section].slice(); + var removedWidgets = newArray.splice(index, 1); + Settings.data.controlCenter.shortcuts[section] = newArray; + } + } + + function _reorderWidgetInSection(section, fromIndex, toIndex) { + if (fromIndex >= 0 && fromIndex < Settings.data.controlCenter.shortcuts[section].length && toIndex >= 0 && toIndex < Settings.data.controlCenter.shortcuts[section].length) { + + // Create a new array to avoid modifying the original + var newArray = Settings.data.controlCenter.shortcuts[section].slice(); + var item = newArray[fromIndex]; + newArray.splice(fromIndex, 1); + newArray.splice(toIndex, 0, item); + + Settings.data.controlCenter.shortcuts[section] = newArray; + } + } + + function _moveWidgetBetweenSections(fromSection, index, toSection) { + // Get the widget from the source section + if (index >= 0 && index < Settings.data.controlCenter.shortcuts[fromSection].length) { + var widget = Settings.data.controlCenter.shortcuts[fromSection][index]; + + // Remove from source section + var sourceArray = Settings.data.controlCenter.shortcuts[fromSection].slice(); + sourceArray.splice(index, 1); + Settings.data.controlCenter.shortcuts[fromSection] = sourceArray; + + // Add to target section + var targetArray = Settings.data.controlCenter.shortcuts[toSection].slice(); + targetArray.push(widget); + Settings.data.controlCenter.shortcuts[toSection] = targetArray; + } + } + + function _updateWidgetSettingsInSection(section, index, settings) { + // Create a new array to trigger QML's change detection for persistence. + // This is crucial for Settings.data to detect the change and persist it. + var newSectionArray = Settings.data.controlCenter.shortcuts[section].slice(); + newSectionArray[index] = settings; + Settings.data.controlCenter.shortcuts[section] = newSectionArray; + Settings.saveImmediate(); + } + + // Base list model for all combo boxes + ListModel { + id: availableWidgets + } + + // Shared Plugin Settings Popup + NPluginSettingsPopup { + id: pluginSettingsDialog + parent: Overlay.overlay + showToastOnSave: false + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ControlCenter/ShortcutsSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ControlCenter/ShortcutsSubTab.qml new file mode 100644 index 0000000..d93c9b2 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/ControlCenter/ShortcutsSubTab.qml @@ -0,0 +1,87 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + Layout.fillHeight: true + + required property var availableWidgets + + signal addWidgetToSection(string widgetId, string section) + signal removeWidgetFromSection(string section, int index) + signal reorderWidgetInSection(string section, int fromIndex, int toIndex) + signal updateWidgetSettingsInSection(string section, int index, var settings) + signal moveWidgetBetweenSections(string fromSection, int index, string toSection) + signal openPluginSettingsRequested(var manifest) + + function getSectionIcons() { + return { + "left": "arrow-bar-to-up", + "right": "arrow-bar-to-down" + }; + } + + // Widgets Management Section + ColumnLayout { + spacing: Style.marginXXS + Layout.fillWidth: true + + // Sections + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.topMargin: Style.marginM + spacing: Style.marginM + + // Left + NSectionEditor { + sectionName: I18n.tr("positions.left") + sectionId: "left" + settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml") + maxWidgets: Settings.data.controlCenter.shortcuts["right"].length > 5 ? 0 : (Settings.data.controlCenter.shortcuts["right"].length > 0 ? 5 : 10) + widgetRegistry: ControlCenterWidgetRegistry + widgetModel: Settings.data.controlCenter.shortcuts["left"] + sectionIcons: root.getSectionIcons() + availableWidgets: root.availableWidgets + availableSections: ["left", "right"] + onAddWidget: (widgetId, section) => root.addWidgetToSection(widgetId, section) + onRemoveWidget: (section, index) => root.removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => root.reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettings: (section, index, settings) => root.updateWidgetSettingsInSection(section, index, settings) + onMoveWidget: (fromSection, index, toSection) => root.moveWidgetBetweenSections(fromSection, index, toSection) + onOpenPluginSettingsRequested: manifest => root.openPluginSettingsRequested(manifest) + } + + // Right + NSectionEditor { + sectionName: I18n.tr("positions.right") + sectionId: "right" + settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml") + maxWidgets: Settings.data.controlCenter.shortcuts["left"].length > 5 ? 0 : (Settings.data.controlCenter.shortcuts["left"].length > 0 ? 5 : 10) + widgetRegistry: ControlCenterWidgetRegistry + widgetModel: Settings.data.controlCenter.shortcuts["right"] + sectionIcons: root.getSectionIcons() + availableWidgets: root.availableWidgets + availableSections: ["left", "right"] + onAddWidget: (widgetId, section) => root.addWidgetToSection(widgetId, section) + onRemoveWidget: (section, index) => root.removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => root.reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettings: (section, index, settings) => root.updateWidgetSettingsInSection(section, index, settings) + onMoveWidget: (fromSection, index, toSection) => root.moveWidgetBetweenSections(fromSection, index, toSection) + onOpenPluginSettingsRequested: manifest => root.openPluginSettingsRequested(manifest) + } + } + } + + Rectangle { + Layout.fillHeight: true + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/DesktopWidgetsTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/DesktopWidgetsTab.qml new file mode 100644 index 0000000..9707524 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/DesktopWidgetsTab.qml @@ -0,0 +1,332 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Compositor +import qs.Services.Noctalia +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + + spacing: Style.marginL + + // Available widgets model - declared early so Repeater delegates can access it + property alias availableWidgetsModel: availableWidgets + ListModel { + id: availableWidgets + } + + // Listen for plugin widget registration/unregistration + Connections { + target: DesktopWidgetRegistry + function onPluginWidgetRegistryUpdated() { + updateAvailableWidgetsModel(); + } + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.desktop-widgets.enabled-label") + description: I18n.tr("panels.desktop-widgets.enabled-description") + checked: Settings.data.desktopWidgets.enabled + defaultValue: Settings.getDefaultValue("desktopWidgets.enabled") + onToggled: checked => Settings.data.desktopWidgets.enabled = checked + } + + NToggle { + Layout.fillWidth: true + enabled: Settings.data.desktopWidgets.enabled + label: I18n.tr("panels.desktop-widgets.overview-enabled-label") + description: I18n.tr("panels.desktop-widgets.overview-enabled-description") + checked: Settings.data.desktopWidgets.overviewEnabled + defaultValue: Settings.getDefaultValue("desktopWidgets.overviewEnabled") + onToggled: checked => Settings.data.desktopWidgets.overviewEnabled = checked + } + + ColumnLayout { + enabled: Settings.data.desktopWidgets.enabled + + NLabel { + description: I18n.tr("panels.desktop-widgets.cpu-intensive-note") + } + + NButton { + Layout.fillWidth: true + Layout.topMargin: Style.marginM + Layout.bottomMargin: Style.marginM + text: DesktopWidgetRegistry.editMode ? I18n.tr("panels.desktop-widgets.edit-mode-exit-button") : I18n.tr("panels.desktop-widgets.edit-mode-button-label") + icon: "edit" + onClicked: { + DesktopWidgetRegistry.editMode = !DesktopWidgetRegistry.editMode; + if (DesktopWidgetRegistry.editMode && Settings.data.ui.settingsPanelMode !== "window") { + var item = root.parent; + while (item) { + if (item.closeRequested !== undefined) { + item.closeRequested(); + break; + } + item = item.parent; + } + } + } + } + + // One NSectionEditor per monitor + Repeater { + model: Quickshell.screens + + NSectionEditor { + required property var modelData + readonly property real compositorScale: { + const info = CompositorService.displayScales[modelData.name]; + return (info && info.scale) ? info.scale : 1.0; + } + + Layout.fillWidth: true + sectionName: modelData.name + sectionSubtitle: { + // Format scale to 2 decimal places to prevent overly long text + var formattedScale = compositorScale.toFixed(2); + return "(" + modelData.width + "x" + modelData.height + " @ " + formattedScale + "x)"; + } + + sectionId: modelData.name + screen: modelData + settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/DesktopWidgets/DesktopWidgetSettingsDialog.qml") + widgetRegistry: DesktopWidgetRegistry + widgetModel: getWidgetsForMonitor(modelData.name) + availableWidgets: root.availableWidgetsModel + availableSections: root.getScreenNames() + sectionLabels: root.getScreenLabels() + sectionIcons: root.getScreenIcons() + draggable: false // Desktop widgets are positioned by X,Y, not list order + crossSectionDraggable: true + maxWidgets: -1 + onAddWidget: (widgetId, section) => _addWidgetToMonitor(modelData.name, widgetId) + onRemoveWidget: (section, index) => _removeWidgetFromMonitor(modelData.name, index) + onMoveWidget: (fromSection, index, toSection) => _moveWidgetToMonitor(fromSection, index, toSection) + onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsForMonitor(modelData.name, index, settings) + pluginSettingsEntryPoints: ["desktopWidgetSettings", "settings"] + onOpenPluginSettingsRequested: (manifest, entryPoint) => pluginSettingsDialog.openPluginSettings(manifest, entryPoint) + } + } + } + + // Shared Plugin Settings Popup + NPluginSettingsPopup { + id: pluginSettingsDialog + parent: Overlay.overlay + showToastOnSave: false + } + + Component.onCompleted: { + // Use Qt.callLater to ensure DesktopWidgetRegistry is ready + Qt.callLater(updateAvailableWidgetsModel); + } + + // Helper to get screen names array + function getScreenNames() { + var names = []; + for (var i = 0; i < Quickshell.screens.length; i++) { + names.push(Quickshell.screens[i].name); + } + return names; + } + + // Helper to get screen labels map (just screen names, NSectionEditor adds "Move to" prefix) + function getScreenLabels() { + var labels = {}; + for (var i = 0; i < Quickshell.screens.length; i++) { + var screen = Quickshell.screens[i]; + labels[screen.name] = screen.name; + } + return labels; + } + + // Helper to get screen icons map + function getScreenIcons() { + var icons = {}; + for (var i = 0; i < Quickshell.screens.length; i++) { + icons[Quickshell.screens[i].name] = "device-desktop"; + } + return icons; + } + + function updateAvailableWidgetsModel() { + availableWidgets.clear(); + try { + if (typeof DesktopWidgetRegistry === "undefined" || !DesktopWidgetRegistry) { + Logger.e("DesktopWidgetsTab", "DesktopWidgetRegistry is not available"); + // Retry after a short delay + Qt.callLater(function () { + if (typeof DesktopWidgetRegistry !== "undefined" && DesktopWidgetRegistry) { + updateAvailableWidgetsModel(); + } + }); + return; + } + var widgetIds = DesktopWidgetRegistry.getAvailableWidgets(); + Logger.d("DesktopWidgetsTab", "Found widgets:", widgetIds, "count:", widgetIds ? widgetIds.length : 0); + if (!widgetIds || widgetIds.length === 0) { + Logger.w("DesktopWidgetsTab", "No widgets found in registry"); + return; + } + for (var i = 0; i < widgetIds.length; i++) { + var widgetId = widgetIds[i]; + var displayName = widgetId; + + // Get plugin name for plugin widgets + var isPlugin = false; + if (DesktopWidgetRegistry.isPluginWidget(widgetId)) { + isPlugin = true; + var pluginId = widgetId.replace("plugin:", ""); + var manifest = PluginRegistry.getPluginManifest(pluginId); + if (manifest && manifest.name) { + displayName = manifest.name; + } + } + + // Add plugin badge first (with custom color) + const badges = []; + if (isPlugin) { + badges.push({ + "icon": "plugin", + "color": Color.mSecondary + }); + } + if (DesktopWidgetRegistry.isCpuIntensive(widgetId)) { + badges.push({ + "icon": "cpu-intensive", + "color": Color.mSecondary + }); + } + + availableWidgets.append({ + "key": widgetId, + "name": displayName, + "badges": badges + }); + } + Logger.d("DesktopWidgetsTab", "Available widgets model count:", availableWidgets.count); + } catch (e) { + Logger.e("DesktopWidgetsTab", "Error updating available widgets:", e, e.stack); + } + } + + // Get widgets for a specific monitor + function getWidgetsForMonitor(monitorName) { + var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || []; + for (var i = 0; i < monitorWidgets.length; i++) { + if (monitorWidgets[i].name === monitorName) { + return monitorWidgets[i].widgets || []; + } + } + return []; + } + + // Set widgets for a specific monitor + function setWidgetsForMonitor(monitorName, widgets) { + var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || []; + var newMonitorWidgets = monitorWidgets.slice(); + var found = false; + for (var i = 0; i < newMonitorWidgets.length; i++) { + if (newMonitorWidgets[i].name === monitorName) { + newMonitorWidgets[i] = { + "name": monitorName, + "widgets": widgets + }; + found = true; + break; + } + } + if (!found) { + newMonitorWidgets.push({ + "name": monitorName, + "widgets": widgets + }); + } + Settings.data.desktopWidgets.monitorWidgets = newMonitorWidgets; + } + + function _addWidgetToMonitor(monitorName, widgetId) { + var newWidget = { + "id": widgetId + }; + if (DesktopWidgetRegistry.widgetHasUserSettings(widgetId)) { + var metadata = DesktopWidgetRegistry.widgetMetadata[widgetId]; + if (metadata) { + Object.keys(metadata).forEach(function (key) { + newWidget[key] = metadata[key]; + }); + } + } + // Set default positions + if (widgetId === "Clock") { + newWidget.x = 50; + newWidget.y = 50; + } else if (widgetId === "MediaPlayer") { + newWidget.x = 100; + newWidget.y = 200; + } else if (widgetId === "AudioVisualizer") { + newWidget.x = 120; + newWidget.y = 280; + } else if (widgetId === "Weather") { + newWidget.x = 100; + newWidget.y = 300; + } else { + // Default position for plugin widgets + newWidget.x = 150; + newWidget.y = 150; + } + var widgets = getWidgetsForMonitor(monitorName).slice(); + widgets.push(newWidget); + setWidgetsForMonitor(monitorName, widgets); + } + + function _removeWidgetFromMonitor(monitorName, index) { + var widgets = getWidgetsForMonitor(monitorName); + if (index >= 0 && index < widgets.length) { + var newArray = widgets.slice(); + newArray.splice(index, 1); + setWidgetsForMonitor(monitorName, newArray); + } + } + + function _updateWidgetSettingsForMonitor(monitorName, index, settings) { + var widgets = getWidgetsForMonitor(monitorName); + if (index >= 0 && index < widgets.length) { + var newArray = widgets.slice(); + newArray[index] = Object.assign({}, newArray[index], settings); + setWidgetsForMonitor(monitorName, newArray); + } + } + + function _moveWidgetToMonitor(fromMonitor, index, toMonitor) { + Logger.i("DesktopWidgetsTab", "Moving widget from", fromMonitor, "index", index, "to", toMonitor); + var sourceWidgets = getWidgetsForMonitor(fromMonitor); + if (index < 0 || index >= sourceWidgets.length) { + Logger.e("DesktopWidgetsTab", "Invalid index", index, "for monitor", fromMonitor); + return; + } + + // Get the widget to move + var newSourceWidgets = sourceWidgets.slice(); + var widget = Object.assign({}, newSourceWidgets.splice(index, 1)[0]); + + // Reset position and scale to ensure widget is accessible on new screen + widget.x = 100; + widget.y = 100; + widget.scale = 1.0; + + // Add to destination monitor + var destWidgets = getWidgetsForMonitor(toMonitor).slice(); + destWidgets.push(widget); + + // Update both monitors + setWidgetsForMonitor(fromMonitor, newSourceWidgets); + setWidgetsForMonitor(toMonitor, destWidgets); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Display/BrightnessSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Display/BrightnessSubTab.qml new file mode 100644 index 0000000..63e6725 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Display/BrightnessSubTab.qml @@ -0,0 +1,249 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Compositor +import qs.Services.Hardware +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + ColumnLayout { + spacing: Style.marginL + + Repeater { + model: Quickshell.screens || [] + delegate: NBox { + Layout.fillWidth: true + implicitHeight: Math.round(contentCol.implicitHeight + Style.margin2L) + color: Color.mSurface + + property var brightnessMonitor: BrightnessService.getMonitorForScreen(modelData) + property real localBrightness: 0.5 + property bool localBrightnessChanging: false + readonly property string automaticOptionLabel: { + var baseLabel = I18n.tr("panels.display.monitors-backlight-device-auto-option"); + var autoDevicePath = (BrightnessService.availableBacklightDevices && BrightnessService.availableBacklightDevices.length > 0) ? BrightnessService.availableBacklightDevices[0] : ""; + if (autoDevicePath === "") + return baseLabel; + + var autoDeviceName = BrightnessService.getBacklightDeviceName(autoDevicePath) || autoDevicePath; + return baseLabel + "(" + autoDeviceName + ")"; + } + readonly property var backlightDeviceOptions: { + var options = [ + { + "key": "", + "name": automaticOptionLabel + } + ]; + + var devices = BrightnessService.availableBacklightDevices || []; + for (var i = 0; i < devices.length; i++) { + var devicePath = devices[i]; + var deviceName = BrightnessService.getBacklightDeviceName(devicePath) || devicePath; + options.push({ + "key": devicePath, + "name": deviceName + }); + } + return options; + } + + onBrightnessMonitorChanged: { + if (brightnessMonitor && !localBrightnessChanging) + localBrightness = brightnessMonitor.brightness || 0.5; + } + + Connections { + target: BrightnessService + function onMonitorBrightnessChanged(monitor, newBrightness) { + if (monitor === brightnessMonitor && !localBrightnessChanging) { + localBrightness = newBrightness; + } + } + } + Connections { + target: brightnessMonitor + ignoreUnknownSignals: true + function onBrightnessUpdated() { + if (brightnessMonitor && !localBrightnessChanging) { + localBrightness = brightnessMonitor.brightness || 0; + } + } + } + Timer { + id: debounceTimer + interval: 120 + repeat: false + onTriggered: { + if (brightnessMonitor && brightnessMonitor.brightnessControlAvailable && Math.abs(localBrightness - brightnessMonitor.brightness) >= 0.005) { + brightnessMonitor.setBrightness(localBrightness); + } + } + } + + ColumnLayout { + id: contentCol + width: parent.width - 2 * Style.marginL + x: Style.marginL + y: Style.marginL + spacing: Style.marginXXS + + RowLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignBottom + + NText { + text: modelData.name || "Unknown" + pointSize: Style.fontSizeL + font.weight: Style.fontWeightSemiBold + Layout.alignment: Qt.AlignBottom + } + + NText { + Layout.fillWidth: true + readonly property real compositorScale: { + const info = CompositorService.displayScales[modelData.name]; + return (info && info.scale) ? info.scale : 1.0; + } + text: { + I18n.tr("system.monitor-description", { + "model": modelData.model, + "width": modelData.width * compositorScale, + "height": modelData.height * compositorScale, + "scale": compositorScale + }); + } + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignRight + Layout.alignment: Qt.AlignBottom + } + } + + ColumnLayout { + spacing: Style.marginS + Layout.fillWidth: true + visible: brightnessMonitor !== undefined && brightnessMonitor !== null + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginL + + NText { + text: I18n.tr("common.brightness") + Layout.preferredWidth: 90 + Layout.alignment: Qt.AlignVCenter + } + + NValueSlider { + id: brightnessSlider + from: 0 + to: 1 + value: localBrightness + stepSize: 0.01 + enabled: brightnessMonitor ? brightnessMonitor.brightnessControlAvailable : false + onMoved: value => { + if (brightnessMonitor && brightnessMonitor.brightnessControlAvailable) { + localBrightness = value; + debounceTimer.restart(); + } + } + onPressedChanged: (pressed, value) => { + localBrightnessChanging = pressed; + if (brightnessMonitor && brightnessMonitor.brightnessControlAvailable) { + if (pressed) { + localBrightness = value; + debounceTimer.restart(); + } else { + localBrightness = value; + debounceTimer.restart(); + } + } + } + Layout.fillWidth: true + } + + NText { + text: brightnessMonitor ? Math.round(localBrightness * 100) + "%" : "N/A" + Layout.preferredWidth: 55 + horizontalAlignment: Text.AlignRight + Layout.alignment: Qt.AlignVCenter + opacity: brightnessMonitor && !brightnessMonitor.brightnessControlAvailable ? 0.5 : 1.0 + } + + Item { + Layout.preferredWidth: 30 + Layout.fillHeight: true + NIcon { + icon: brightnessMonitor && brightnessMonitor.method == "internal" ? "device-laptop" : "device-desktop" + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + opacity: brightnessMonitor && !brightnessMonitor.brightnessControlAvailable ? 0.5 : 1.0 + } + } + } + + NText { + visible: brightnessMonitor && !brightnessMonitor.brightnessControlAvailable && !(brightnessMonitor.method === "internal" && brightnessMonitor.initInProgress) + text: !Settings.data.brightness.enableDdcSupport ? I18n.tr("panels.display.monitors-brightness-unavailable-ddc-disabled") : I18n.tr("panels.display.monitors-brightness-unavailable-generic") + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + Layout.fillWidth: true + wrapMode: Text.WordWrap + } + + NComboBox { + Layout.fillWidth: true + visible: brightnessMonitor && brightnessMonitor.method === "internal" + label: I18n.tr("panels.display.monitors-backlight-device-label") + description: I18n.tr("panels.display.monitors-backlight-device-description") + model: backlightDeviceOptions + currentKey: BrightnessService.getMappedBacklightDevice(modelData.name) || "" + onSelected: key => BrightnessService.setMappedBacklightDevice(modelData.name, key) + } + } + } + } + } + + NSpinBox { + Layout.fillWidth: true + label: I18n.tr("panels.display.monitors-brightness-step-label") + description: I18n.tr("panels.display.monitors-brightness-step-description") + minimum: 1 + maximum: 50 + value: Settings.data.brightness.brightnessStep + stepSize: 1 + suffix: "%" + onValueChanged: Settings.data.brightness.brightnessStep = value + defaultValue: Settings.getDefaultValue("brightness.brightnessStep") + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.display.monitors-enforce-minimum-label") + description: I18n.tr("panels.display.monitors-enforce-minimum-description") + checked: Settings.data.brightness.enforceMinimum + onToggled: checked => Settings.data.brightness.enforceMinimum = checked + defaultValue: Settings.getDefaultValue("brightness.enforceMinimum") + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.display.monitors-external-brightness-label") + description: I18n.tr("panels.display.monitors-external-brightness-description") + checked: Settings.data.brightness.enableDdcSupport + onToggled: checked => { + Settings.data.brightness.enableDdcSupport = checked; + } + defaultValue: Settings.getDefaultValue("brightness.enableDdcSupport") + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Display/DisplayTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Display/DisplayTab.qml new file mode 100644 index 0000000..8d2d990 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Display/DisplayTab.qml @@ -0,0 +1,92 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io +import qs.Commons +import qs.Services.Location +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + // Time dropdown options (00:00 .. 23:30) + ListModel { + id: timeOptions + } + + function populateTimeOptions() { + for (var h = 0; h < 24; h++) { + for (var m = 0; m < 60; m += 30) { + var hh = ("0" + h).slice(-2); + var mm = ("0" + m).slice(-2); + var key = hh + ":" + mm; + timeOptions.append({ + "key": key, + "name": key + }); + } + } + } + + Component.onCompleted: { + Qt.callLater(populateTimeOptions); + } + + // Check for wlsunset availability when enabling Night Light + Process { + id: wlsunsetCheck + command: ["sh", "-c", "command -v wlsunset"] + running: false + + onExited: function (exitCode) { + if (exitCode === 0) { + Settings.data.nightLight.enabled = true; + NightLightService.apply(); + ToastService.showNotice(I18n.tr("common.night-light"), I18n.tr("common.enabled"), "nightlight-on"); + } else { + Settings.data.nightLight.enabled = false; + ToastService.showWarning(I18n.tr("common.night-light"), I18n.tr("toast.night-light.not-installed")); + } + } + + stdout: StdioCollector {} + stderr: StdioCollector {} + } + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.brightness") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.night-light") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + BrightnessSubTab {} + NightLightSubTab { + timeOptions: timeOptions + onCheckWlsunset: wlsunsetCheck.running = true + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Display/NightLightSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Display/NightLightSubTab.qml new file mode 100644 index 0000000..982910e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Display/NightLightSubTab.qml @@ -0,0 +1,219 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.Location +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + property var timeOptions + + signal checkWlsunset + + NToggle { + label: I18n.tr("panels.display.night-light-enable-label") + description: I18n.tr("panels.display.night-light-enable-description") + checked: Settings.data.nightLight.enabled + onToggled: checked => { + if (checked) { + root.checkWlsunset(); + } else { + Settings.data.nightLight.enabled = false; + Settings.data.nightLight.forced = false; + NightLightService.apply(); + ToastService.showNotice(I18n.tr("common.night-light"), I18n.tr("common.disabled"), "nightlight-off"); + } + } + } + + ColumnLayout { + enabled: Settings.data.nightLight.enabled + spacing: Style.marginL + Layout.fillWidth: true + + NLabel { + label: I18n.tr("panels.display.night-light-temperature-night") + description: I18n.tr("panels.display.night-light-temperature-night-description") + Layout.fillWidth: true + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NSlider { + id: nightSlider + Layout.fillWidth: true + from: 1000 + to: 6500 + value: Settings.data.nightLight.nightTemp + + onValueChanged: { + var dayTemp = parseInt(Settings.data.nightLight.dayTemp); + var v = Math.round(value); + if (!isNaN(dayTemp)) { + var maxNight = dayTemp - 500; + v = Math.min(maxNight, Math.max(1000, v)); + } else { + v = Math.max(1000, v); + } + if (v !== value) + value = v; + } + + onPressedChanged: { + if (!pressed) { + var dayTemp = parseInt(Settings.data.nightLight.dayTemp); + var v = Math.round(value); + if (!isNaN(dayTemp)) { + var maxNight = dayTemp - 500; + v = Math.min(maxNight, Math.max(1000, v)); + } else { + v = Math.max(1000, v); + } + Settings.data.nightLight.nightTemp = v; + } + } + } + + NText { + text: nightSlider.value + "K" + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignVCenter + } + } + + NLabel { + label: I18n.tr("panels.display.night-light-temperature-day") + description: I18n.tr("panels.display.night-light-temperature-day-description") + Layout.fillWidth: true + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NSlider { + id: daySlider + Layout.fillWidth: true + from: 1000 + to: 6500 + value: Settings.data.nightLight.dayTemp + + onValueChanged: { + var nightTemp = parseInt(Settings.data.nightLight.nightTemp); + var v = Math.round(value); + if (!isNaN(nightTemp)) { + var minDay = nightTemp + 500; + v = Math.max(minDay, Math.min(6500, v)); + } else { + v = Math.min(6500, v); + } + if (v !== value) + value = v; + } + + onPressedChanged: { + if (!pressed) { + var nightTemp = parseInt(Settings.data.nightLight.nightTemp); + var v = Math.round(value); + if (!isNaN(nightTemp)) { + var minDay = nightTemp + 500; + v = Math.max(minDay, Math.min(6500, v)); + } else { + v = Math.min(6500, v); + } + Settings.data.nightLight.dayTemp = v; + } + } + } + + NText { + text: daySlider.value + "K" + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignVCenter + } + } + + NToggle { + label: I18n.tr("panels.display.night-light-auto-schedule-label") + description: I18n.tr("panels.display.night-light-auto-schedule-description", { + "location": LocationService.stableName + }) + checked: Settings.data.nightLight.autoSchedule + onToggled: checked => Settings.data.nightLight.autoSchedule = checked + } + + ColumnLayout { + spacing: Style.marginS + Layout.fillWidth: true + visible: !Settings.data.nightLight.autoSchedule && !Settings.data.nightLight.forced + + NLabel { + label: I18n.tr("panels.display.night-light-manual-schedule-label") + description: I18n.tr("panels.display.night-light-manual-schedule-description") + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NText { + text: I18n.tr("panels.display.night-light-manual-schedule-sunrise") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignVCenter + } + + NComboBox { + model: root.timeOptions + currentKey: Settings.data.nightLight.manualSunrise + placeholder: I18n.tr("panels.display.night-light-manual-schedule-select-start") + onSelected: key => Settings.data.nightLight.manualSunrise = key + Layout.fillWidth: true + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NText { + text: I18n.tr("panels.display.night-light-manual-schedule-sunset") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignVCenter + } + + NComboBox { + model: root.timeOptions + currentKey: Settings.data.nightLight.manualSunset + placeholder: I18n.tr("panels.display.night-light-manual-schedule-select-stop") + onSelected: key => Settings.data.nightLight.manualSunset = key + Layout.fillWidth: true + } + } + } + + NToggle { + label: I18n.tr("panels.display.night-light-force-activation-label") + description: I18n.tr("panels.display.night-light-force-activation-description") + checked: Settings.data.nightLight.forced + onToggled: checked => { + Settings.data.nightLight.forced = checked; + if (checked && !Settings.data.nightLight.enabled) { + root.checkWlsunset(); + } else { + NightLightService.apply(); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Dock/AppearanceSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Dock/AppearanceSubTab.qml new file mode 100644 index 0000000..01887a0 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Dock/AppearanceSubTab.qml @@ -0,0 +1,433 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.System +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + readonly property color launcherPreviewColor: Color.resolveColorKey((Settings.data.dock.launcherIconColor !== undefined) ? Settings.data.dock.launcherIconColor : "none") + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.dock.enabled-label") + description: I18n.tr("panels.dock.enabled-description") + checked: Settings.data.dock.enabled + defaultValue: Settings.getDefaultValue("dock.enabled") + onToggled: checked => Settings.data.dock.enabled = checked + } + + ColumnLayout { + spacing: Style.marginL + enabled: Settings.data.dock.enabled + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.dock.appearance-position-label") + description: I18n.tr("panels.dock.appearance-position-description") + model: [ + { + "key": "top", + "name": I18n.tr("positions.top") + }, + { + "key": "bottom", + "name": I18n.tr("positions.bottom") + }, + { + "key": "left", + "name": I18n.tr("positions.left") + }, + { + "key": "right", + "name": I18n.tr("positions.right") + } + ] + currentKey: Settings.data.dock.position + defaultValue: Settings.getDefaultValue("dock.position") + onSelected: key => Settings.data.dock.position = key + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.dock.appearance-type-label") + description: I18n.tr("panels.dock.appearance-type-description") + model: [ + { + "key": "floating", + "name": I18n.tr("panels.dock.appearance-type-floating") + }, + { + "key": "attached", + "name": I18n.tr("panels.dock.appearance-type-attached") + } + ] + currentKey: Settings.data.dock.dockType + defaultValue: Settings.getDefaultValue("dock.dockType") + onSelected: key => Settings.data.dock.dockType = key + } + + NComboBox { + visible: Settings.data.dock.dockType === "floating" + Layout.fillWidth: true + label: I18n.tr("panels.display.title") + description: I18n.tr("panels.dock.appearance-display-description") + model: [ + { + "key": "always_visible", + "name": I18n.tr("hide-modes.visible") + }, + { + "key": "auto_hide", + "name": I18n.tr("panels.dock.appearance-display-auto-hide") + }, + { + "key": "exclusive", + "name": I18n.tr("panels.dock.appearance-display-exclusive") + } + ] + currentKey: Settings.data.dock.displayMode + defaultValue: Settings.getDefaultValue("dock.displayMode") + onSelected: key => { + Settings.data.dock.displayMode = key; + } + } + + NToggle { + Layout.fillWidth: true + visible: Settings.data.dock.dockType === "attached" && Settings.data.bar.barType === "framed" + label: I18n.tr("panels.dock.appearance-sit-on-frame-label") + description: I18n.tr("panels.dock.appearance-sit-on-frame-description") + checked: Settings.data.dock.sitOnFrame + defaultValue: Settings.getDefaultValue("dock.sitOnFrame") + onToggled: checked => Settings.data.dock.sitOnFrame = checked + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.dock.appearance-dock-indicator-label") + description: I18n.tr("panels.dock.appearance-dock-indicator-description") + checked: Settings.data.dock.showDockIndicator + defaultValue: Settings.getDefaultValue("dock.showDockIndicator") + onToggled: checked => Settings.data.dock.showDockIndicator = checked + } + + NToggle { + Layout.fillWidth: true + visible: Settings.data.dock.showDockIndicator + label: I18n.tr("panels.dock.appearance-indicator-thickness-label") + description: I18n.tr("panels.dock.appearance-indicator-thickness-description") + checked: (Settings.data.dock.indicatorThickness || 3) >= 6 + defaultValue: (Settings.getDefaultValue("dock.indicatorThickness") || 3) >= 6 + onToggled: checked => Settings.data.dock.indicatorThickness = checked ? 6 : 3 + } + + NColorChoice { + Layout.fillWidth: true + visible: Settings.data.dock.showDockIndicator + label: I18n.tr("panels.dock.appearance-indicator-color-label") + description: I18n.tr("panels.dock.appearance-indicator-color-description") + currentKey: Settings.data.dock.indicatorColor || "primary" + defaultValue: Settings.getDefaultValue("dock.indicatorColor") + onSelected: key => Settings.data.dock.indicatorColor = key + } + + NValueSlider { + Layout.fillWidth: true + visible: Settings.data.dock.showDockIndicator + label: I18n.tr("panels.dock.appearance-indicator-opacity-label") + description: I18n.tr("panels.dock.appearance-indicator-opacity-description") + from: 0.1 + to: 1 + stepSize: 0.01 + showReset: true + value: Settings.data.dock.indicatorOpacity + defaultValue: Settings.getDefaultValue("dock.indicatorOpacity") + onMoved: value => Settings.data.dock.indicatorOpacity = value + text: Math.floor(Settings.data.dock.indicatorOpacity * 100) + "%" + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.osd.background-opacity-label") + description: I18n.tr("panels.dock.appearance-background-opacity-description") + from: 0 + to: 1 + stepSize: 0.01 + showReset: true + value: Settings.data.dock.backgroundOpacity + defaultValue: Settings.getDefaultValue("dock.backgroundOpacity") + onMoved: value => Settings.data.dock.backgroundOpacity = value + text: Math.floor(Settings.data.dock.backgroundOpacity * 100) + "%" + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.dock.appearance-dead-opacity-label") + description: I18n.tr("panels.dock.appearance-dead-opacity-description") + from: 0 + to: 1 + stepSize: 0.01 + showReset: true + value: Settings.data.dock.deadOpacity + defaultValue: Settings.getDefaultValue("dock.deadOpacity") + onMoved: value => Settings.data.dock.deadOpacity = value + text: Math.floor(Settings.data.dock.deadOpacity * 100) + "%" + } + + NValueSlider { + Layout.fillWidth: true + visible: Settings.data.dock.dockType === "floating" + label: I18n.tr("panels.dock.appearance-floating-distance-label") + description: I18n.tr("panels.dock.appearance-floating-distance-description") + from: 0 + to: 4 + stepSize: 0.01 + showReset: true + value: Settings.data.dock.floatingRatio + defaultValue: Settings.getDefaultValue("dock.floatingRatio") + onMoved: value => Settings.data.dock.floatingRatio = value + text: Math.floor(Settings.data.dock.floatingRatio * 100) + "%" + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.dock.appearance-icon-size-label") + description: I18n.tr("panels.dock.appearance-icon-size-description") + from: 0 + to: 2 + stepSize: 0.01 + showReset: true + value: Settings.data.dock.size + defaultValue: Settings.getDefaultValue("dock.size") + onMoved: value => Settings.data.dock.size = value + text: Math.floor(Settings.data.dock.size * 100) + "%" + } + + NValueSlider { + visible: Settings.data.dock.dockType === "floating" && Settings.data.dock.displayMode === "auto_hide" + Layout.fillWidth: true + label: I18n.tr("panels.dock.appearance-hide-show-speed-label") + description: I18n.tr("panels.dock.appearance-hide-show-speed-description") + from: 0.1 + to: 2.0 + stepSize: 0.01 + showReset: true + value: Settings.data.dock.animationSpeed + defaultValue: Settings.getDefaultValue("dock.animationSpeed") + onMoved: value => Settings.data.dock.animationSpeed = value + text: (Settings.data.dock.animationSpeed * 100).toFixed(0) + "%" + } + + NToggle { + label: I18n.tr("panels.dock.appearance-inactive-indicators-label") + description: I18n.tr("panels.dock.appearance-inactive-indicators-description") + checked: Settings.data.dock.inactiveIndicators + defaultValue: Settings.getDefaultValue("dock.inactiveIndicators") + onToggled: checked => Settings.data.dock.inactiveIndicators = checked + } + + NToggle { + label: I18n.tr("panels.dock.appearance-pinned-static-label") + description: I18n.tr("panels.dock.appearance-pinned-static-description") + checked: Settings.data.dock.pinnedStatic + defaultValue: Settings.getDefaultValue("dock.pinnedStatic") + onToggled: checked => Settings.data.dock.pinnedStatic = checked + } + + NToggle { + label: I18n.tr("panels.dock.appearance-group-apps-label") + description: I18n.tr("panels.dock.appearance-group-apps-description") + checked: Settings.data.dock.groupApps + defaultValue: Settings.getDefaultValue("dock.groupApps") + onToggled: checked => Settings.data.dock.groupApps = checked + } + + NComboBox { + Layout.fillWidth: true + visible: Settings.data.dock.groupApps + label: I18n.tr("panels.dock.appearance-group-click-action-label") + description: I18n.tr("panels.dock.appearance-group-click-action-description") + model: [ + { + "key": "cycle", + "name": I18n.tr("panels.dock.appearance-group-click-action-cycle") + }, + { + "key": "list", + "name": I18n.tr("panels.dock.appearance-group-click-action-list") + } + ] + currentKey: Settings.data.dock.groupClickAction + defaultValue: Settings.getDefaultValue("dock.groupClickAction") + onSelected: key => Settings.data.dock.groupClickAction = key + } + + NComboBox { + Layout.fillWidth: true + visible: Settings.data.dock.groupApps + label: I18n.tr("panels.dock.appearance-group-context-menu-mode-label") + description: I18n.tr("panels.dock.appearance-group-context-menu-mode-description") + model: [ + { + "key": "list", + "name": I18n.tr("panels.dock.appearance-group-context-menu-mode-list") + }, + { + "key": "extended", + "name": I18n.tr("panels.dock.appearance-group-context-menu-mode-extended") + } + ] + currentKey: Settings.data.dock.groupContextMenuMode + defaultValue: Settings.getDefaultValue("dock.groupContextMenuMode") + onSelected: key => Settings.data.dock.groupContextMenuMode = key + } + + NComboBox { + Layout.fillWidth: true + visible: Settings.data.dock.groupApps + label: I18n.tr("panels.dock.appearance-group-indicator-style-label") + description: I18n.tr("panels.dock.appearance-group-indicator-style-description") + model: [ + { + "key": "number", + "name": I18n.tr("panels.dock.appearance-group-indicator-style-number") + }, + { + "key": "dots", + "name": I18n.tr("panels.dock.appearance-group-indicator-style-dots") + } + ] + currentKey: Settings.data.dock.groupIndicatorStyle + defaultValue: Settings.getDefaultValue("dock.groupIndicatorStyle") + onSelected: key => Settings.data.dock.groupIndicatorStyle = key + } + + NToggle { + label: I18n.tr("panels.dock.monitors-only-same-monitor-label") + description: I18n.tr("panels.dock.monitors-only-same-monitor-description") + checked: Settings.data.dock.onlySameOutput + defaultValue: Settings.getDefaultValue("dock.onlySameOutput") + onToggled: checked => Settings.data.dock.onlySameOutput = checked + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.dock.appearance-colorize-icons-label") + description: I18n.tr("panels.dock.appearance-colorize-icons-description") + checked: Settings.data.dock.colorizeIcons + defaultValue: Settings.getDefaultValue("dock.colorizeIcons") + onToggled: checked => Settings.data.dock.colorizeIcons = checked + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.dock.appearance-show-launcher-icon-label") + description: I18n.tr("panels.dock.appearance-show-launcher-icon-description") + checked: Settings.data.dock.showLauncherIcon + defaultValue: Settings.getDefaultValue("dock.showLauncherIcon") + onToggled: checked => Settings.data.dock.showLauncherIcon = checked + } + + NComboBox { + Layout.fillWidth: true + visible: Settings.data.dock.showLauncherIcon + label: I18n.tr("panels.dock.appearance-launcher-position-label") + description: I18n.tr("panels.dock.appearance-launcher-position-description") + model: [ + { + "key": "start", + "name": I18n.tr("panels.dock.appearance-launcher-position-start") + }, + { + "key": "end", + "name": I18n.tr("panels.dock.appearance-launcher-position-end") + } + ] + currentKey: Settings.data.dock.launcherPosition + defaultValue: Settings.getDefaultValue("dock.launcherPosition") + onSelected: key => Settings.data.dock.launcherPosition = key + } + + NToggle { + Layout.fillWidth: true + visible: Settings.data.dock.showLauncherIcon + label: I18n.tr("panels.dock.appearance-launcher-use-distro-logo-label") + description: I18n.tr("panels.dock.appearance-launcher-use-distro-logo-description") + checked: Settings.data.dock.launcherUseDistroLogo + defaultValue: Settings.getDefaultValue("dock.launcherUseDistroLogo") + onToggled: checked => Settings.data.dock.launcherUseDistroLogo = checked + } + + RowLayout { + visible: Settings.data.dock.showLauncherIcon + Layout.fillWidth: true + + NLabel { + Layout.fillWidth: true + label: I18n.tr("panels.dock.appearance-launcher-icon-label") + description: I18n.tr("panels.dock.appearance-launcher-icon-description") + } + + NIconButton { + visible: !Settings.data.dock.launcherUseDistroLogo + enabled: !Settings.data.dock.launcherUseDistroLogo + icon: (Settings.data.dock.launcherIcon && Settings.data.dock.launcherIcon !== "") ? Settings.data.dock.launcherIcon : "search" + colorFg: root.launcherPreviewColor + colorFgHover: root.launcherPreviewColor + tooltipText: I18n.tr("bar.control-center.browse-library") + onClicked: launcherIconPicker.open() + } + + Rectangle { + visible: Settings.data.dock.launcherUseDistroLogo + width: Style.toOdd(Style.baseWidgetSize * Style.uiScaleRatio) + height: width + radius: Math.min(Style.iRadiusL, width / 2) + color: Color.smartAlpha(Color.mSurfaceVariant) + border.color: Color.mOutline + border.width: Style.borderS + + Image { + anchors.centerIn: parent + width: parent.width * 0.62 + height: width + source: HostService.osLogo + fillMode: Image.PreserveAspectFit + smooth: true + asynchronous: true + layer.enabled: visible + layer.effect: ShaderEffect { + property color targetColor: root.launcherPreviewColor + property real colorizeMode: 2.0 + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } + } + } + } + + NIconPicker { + id: launcherIconPicker + initialIcon: (Settings.data.dock.launcherIcon && Settings.data.dock.launcherIcon !== "") ? Settings.data.dock.launcherIcon : "search" + onIconSelected: iconName => { + Settings.data.dock.launcherIcon = iconName; + Settings.saveImmediate(); + } + } + + NColorChoice { + Layout.fillWidth: true + visible: Settings.data.dock.showLauncherIcon + label: I18n.tr("common.select-icon-color") + currentKey: Settings.data.dock.launcherIconColor + defaultValue: Settings.getDefaultValue("dock.launcherIconColor") + onSelected: key => Settings.data.dock.launcherIconColor = key + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Dock/DockTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Dock/DockTab.qml new file mode 100644 index 0000000..497f256 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Dock/DockTab.qml @@ -0,0 +1,42 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.appearance") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.monitors") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + AppearanceSubTab {} + MonitorsSubTab {} + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Dock/MonitorsSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Dock/MonitorsSubTab.qml new file mode 100644 index 0000000..a0e08d9 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Dock/MonitorsSubTab.qml @@ -0,0 +1,61 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Compositor +import qs.Widgets + +ColumnLayout { + id: root + enabled: Settings.data.dock.enabled + spacing: Style.marginL + Layout.fillWidth: true + + // Helper functions to update arrays immutably + function addMonitor(list, name) { + const arr = (list || []).slice(); + if (!arr.includes(name)) + arr.push(name); + return arr; + } + function removeMonitor(list, name) { + return (list || []).filter(function (n) { + return n !== name; + }); + } + + NText { + text: I18n.tr("panels.dock.monitors-desc") + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + Repeater { + model: Quickshell.screens || [] + delegate: NCheckbox { + Layout.fillWidth: true + readonly property real compositorScale: { + const info = CompositorService.displayScales[modelData.name]; + return (info && info.scale) ? info.scale : 1.0; + } + label: modelData.name || "Unknown" + description: { + I18n.tr("system.monitor-description", { + "model": modelData.model, + "width": modelData.width * compositorScale, + "height": modelData.height * compositorScale, + "scale": compositorScale + }); + } + checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1 + onToggled: checked => { + if (checked) { + Settings.data.dock.monitors = root.addMonitor(Settings.data.dock.monitors, modelData.name); + } else { + Settings.data.dock.monitors = root.removeMonitor(Settings.data.dock.monitors, modelData.name); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/General/BasicsSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/General/BasicsSubTab.qml new file mode 100644 index 0000000..f459e79 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/General/BasicsSubTab.qml @@ -0,0 +1,221 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import "../../../../../Helpers/QtObj2JS.js" as QtObj2JS +import qs.Commons +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + + // Profile section + RowLayout { + Layout.fillWidth: true + spacing: Style.marginL + + // Avatar preview + NImageRounded { + Layout.preferredWidth: 128 * Style.uiScaleRatio + Layout.preferredHeight: width + radius: width / 2 + imagePath: Settings.preprocessPath(Settings.data.general.avatarImage) + fallbackIcon: "person" + borderColor: Color.mPrimary + borderWidth: Style.borderM + Layout.alignment: Qt.AlignTop + } + + ColumnLayout { + NText { + text: HostService.displayName + pointSize: Style.fontSizeM + color: Color.mPrimary + } + + NTextInputButton { + label: I18n.tr("panels.general.profile-picture-label") + description: I18n.tr("panels.general.profile-picture-description") + text: Settings.data.general.avatarImage + placeholderText: '~/.face' // don't translate path + buttonIcon: "photo" + buttonTooltip: I18n.tr("panels.general.profile-tooltip") + onInputTextChanged: text => Settings.data.general.avatarImage = text + onButtonClicked: { + avatarPicker.openFilePicker(); + } + } + } + } + + NFilePicker { + id: avatarPicker + title: I18n.tr("panels.general.profile-select-avatar") + selectionMode: "files" + initialPath: Settings.preprocessPath(Settings.data.general.avatarImage).substr(0, Settings.preprocessPath(Settings.data.general.avatarImage).lastIndexOf("/")) || Quickshell.env("HOME") + nameFilters: ImageCacheService.basicImageFilters + onAccepted: paths => { + if (paths.length > 0) { + Settings.data.general.avatarImage = paths[0]; + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginM + Layout.bottomMargin: Style.marginM + } + + // Fonts + ColumnLayout { + spacing: Style.marginL + Layout.fillWidth: true + + // Font configuration section + ColumnLayout { + spacing: Style.marginL + Layout.fillWidth: true + + NSearchableComboBox { + label: I18n.tr("panels.general.fonts-default-label") + description: I18n.tr("panels.general.fonts-default-description") + model: FontService.availableFonts + currentKey: Settings.data.ui.fontDefault + placeholder: I18n.tr("panels.general.fonts-default-placeholder") + searchPlaceholder: I18n.tr("panels.general.fonts-default-search-placeholder") + popupHeight: 420 + defaultValue: Settings.getDefaultValue("ui.fontDefault") + settingsPath: "ui.fontDefault" + onSelected: key => Settings.data.ui.fontDefault = key + } + + NSearchableComboBox { + label: I18n.tr("panels.general.fonts-monospace-label") + description: I18n.tr("panels.general.fonts-monospace-description") + model: FontService.monospaceFonts + currentKey: Settings.data.ui.fontFixed + placeholder: I18n.tr("panels.general.fonts-monospace-placeholder") + searchPlaceholder: I18n.tr("panels.general.fonts-monospace-search-placeholder") + popupHeight: 320 + defaultValue: Settings.getDefaultValue("ui.fontFixed") + settingsPath: "ui.fontFixed" + onSelected: key => Settings.data.ui.fontFixed = key + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.general.fonts-default-scale-label") + description: I18n.tr("panels.general.fonts-default-scale-description") + from: 0.75 + to: 1.25 + stepSize: 0.01 + showReset: true + value: Settings.data.ui.fontDefaultScale + defaultValue: Settings.getDefaultValue("ui.fontDefaultScale") + onMoved: value => Settings.data.ui.fontDefaultScale = value + text: Math.floor(Settings.data.ui.fontDefaultScale * 100) + "%" + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.general.fonts-monospace-scale-label") + description: I18n.tr("panels.general.fonts-monospace-scale-description") + from: 0.75 + to: 1.25 + stepSize: 0.01 + showReset: true + value: Settings.data.ui.fontFixedScale + defaultValue: Settings.getDefaultValue("ui.fontFixedScale") + onMoved: value => Settings.data.ui.fontFixedScale = value + text: Math.floor(Settings.data.ui.fontFixedScale * 100) + "%" + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginM + Layout.bottomMargin: Style.marginM + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.general.reverse-scrolling-label") + description: I18n.tr("panels.general.reverse-scrolling-description") + checked: Settings.data.general.reverseScroll + defaultValue: Settings.getDefaultValue("general.reverseScroll") + onToggled: checked => Settings.data.general.reverseScroll = checked + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.general.smooth-scrolling-label") + description: I18n.tr("panels.general.smooth-scrolling-description") + checked: Settings.data.general.smoothScrollEnabled + defaultValue: Settings.getDefaultValue("general.smoothScrollEnabled") + onToggled: checked => Settings.data.general.smoothScrollEnabled = checked + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginM + Layout.bottomMargin: Style.marginM + } + + RowLayout { + spacing: Style.marginL + Layout.fillWidth: true + + NButton { + icon: "wand" + text: I18n.tr("panels.general.launch-setup-wizard") + outlined: true + Layout.fillWidth: true + onClicked: { + var targetScreen = PanelService.openedPanel ? PanelService.openedPanel.screen : (Quickshell.screens.length > 0 ? Quickshell.screens[0] : null); + if (!targetScreen) { + return; + } + var setupPanel = PanelService.getPanel("setupWizardPanel", targetScreen); + if (setupPanel) { + setupPanel.telemetryOnlyMode = false; + setupPanel.open(); + } else { + Qt.callLater(() => { + var sp = PanelService.getPanel("setupWizardPanel", targetScreen); + if (sp) { + sp.telemetryOnlyMode = false; + sp.open(); + } + }); + } + } + } + + NButton { + icon: "external-link" + text: I18n.tr("common.documentation") + outlined: true + Layout.fillWidth: true + onClicked: { + Qt.openUrlExternally("https://docs.noctalia.dev"); + } + } + + NButton { + icon: "json" + text: I18n.tr("panels.general.copy-settings") + outlined: true + Layout.fillWidth: true + onClicked: { + var plainData = QtObj2JS.qtObjectToPlainObject(Settings.data); + var json = JSON.stringify(plainData, null, 2); + Quickshell.execDetached(["wl-copy", json]); + ToastService.showNotice(I18n.tr("panels.general.settings-copied")); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/General/KeybindsSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/General/KeybindsSubTab.qml new file mode 100644 index 0000000..661589c --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/General/KeybindsSubTab.qml @@ -0,0 +1,81 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + width: parent.width + + // Keybinds section + NLabel { + label: I18n.tr("panels.general.keybinds-title") + description: I18n.tr("panels.general.keybinds-description") + Layout.fillWidth: true + } + + NKeybindRecorder { + Layout.fillWidth: true + label: I18n.tr("panels.general.keybinds-up") + currentKeybinds: Settings.data.general.keybinds.keyUp + defaultKeybind: "Up" + settingsPath: "general.keybinds.keyUp" + onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyUp = newKeybinds + } + + NKeybindRecorder { + Layout.fillWidth: true + label: I18n.tr("panels.general.keybinds-down") + currentKeybinds: Settings.data.general.keybinds.keyDown + defaultKeybind: "Down" + settingsPath: "general.keybinds.keyDown" + onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyDown = newKeybinds + } + + NKeybindRecorder { + Layout.fillWidth: true + label: I18n.tr("panels.general.keybinds-left") + currentKeybinds: Settings.data.general.keybinds.keyLeft + defaultKeybind: "Left" + settingsPath: "general.keybinds.keyLeft" + onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyLeft = newKeybinds + } + + NKeybindRecorder { + Layout.fillWidth: true + label: I18n.tr("panels.general.keybinds-right") + currentKeybinds: Settings.data.general.keybinds.keyRight + defaultKeybind: "Right" + settingsPath: "general.keybinds.keyRight" + onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyRight = newKeybinds + } + + NKeybindRecorder { + Layout.fillWidth: true + label: I18n.tr("panels.general.keybinds-enter") + currentKeybinds: Settings.data.general.keybinds.keyEnter + defaultKeybind: "Return" + settingsPath: "general.keybinds.keyEnter" + onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyEnter = newKeybinds + } + + NKeybindRecorder { + Layout.fillWidth: true + label: I18n.tr("panels.general.keybinds-escape") + currentKeybinds: Settings.data.general.keybinds.keyEscape + defaultKeybind: "Esc" + settingsPath: "general.keybinds.keyEscape" + onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyEscape = newKeybinds + } + + NKeybindRecorder { + Layout.fillWidth: true + label: I18n.tr("panels.general.keybinds-remove") + currentKeybinds: Settings.data.general.keybinds.keyRemove + defaultKeybind: "Del" + settingsPath: "general.keybinds.keyRemove" + onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyRemove = newKeybinds + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/GeneralTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/GeneralTab.qml new file mode 100644 index 0000000..17d0450 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/GeneralTab.qml @@ -0,0 +1,46 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import "../../../../Helpers/QtObj2JS.js" as QtObj2JS +import "General" +import qs.Commons +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("panels.general.tab-basics") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("panels.general.tab-keybinds") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + BasicsSubTab {} + KeybindsSubTab {} + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Hooks/GeneralSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Hooks/GeneralSubTab.qml new file mode 100644 index 0000000..9ec09c4 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Hooks/GeneralSubTab.qml @@ -0,0 +1,34 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + width: parent.width + + // Enable/Disable Toggle + NToggle { + label: I18n.tr("panels.hooks.system-hooks-enable-label") + description: I18n.tr("panels.hooks.system-hooks-enable-description") + checked: Settings.data.hooks.enabled + onToggled: checked => Settings.data.hooks.enabled = checked + } + + NDivider { + Layout.fillWidth: true + } + + // Info section + ColumnLayout { + spacing: Style.marginM + Layout.fillWidth: true + + NLabel { + label: I18n.tr("panels.hooks.info-parameters-label") + description: I18n.tr("panels.hooks.info-parameters-description") + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Hooks/HookEditPopup.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Hooks/HookEditPopup.qml new file mode 100644 index 0000000..31cf3ec --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Hooks/HookEditPopup.qml @@ -0,0 +1,118 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +Popup { + id: root + modal: true + closePolicy: Popup.NoAutoClose + dim: true + anchors.centerIn: parent + + // Use a reasonable width/height relative to parent or fixed + width: Math.min(600 * Style.uiScaleRatio, parent.width * 0.9) + height: Math.min(contentLayout.implicitHeight + padding * 2, parent.height * 0.9) + padding: Style.marginL + + property string hookLabel: "" + property string hookDescription: "" + property string hookPlaceholder: "" + property string initialValue: "" + + signal saved(string newValue) + signal test(string value) + + property var _savedSlot: null + property var _testSlot: null + + background: Rectangle { + color: Color.mSurface + radius: Style.radiusL + border.color: Color.mOutline + border.width: Style.borderS + } + + onOpened: { + commandInput.text = initialValue; + commandInput.forceActiveFocus(); + } + + contentItem: ColumnLayout { + id: contentLayout + spacing: Style.marginL + + // Header + RowLayout { + Layout.fillWidth: true + NText { + text: root.hookLabel + font.weight: Style.fontWeightBold + pointSize: Style.fontSizeL + Layout.fillWidth: true + } + NIconButton { + icon: "close" + onClicked: root.close() + } + } + + // Description/Help + NText { + text: root.hookDescription + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + // Input Area + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NTextInput { + id: commandInput + Layout.fillWidth: true + placeholderText: root.hookPlaceholder + // Allow multiline? Hooks are usually oneline commands but can be scripts. + // NTextInput is likely single line. Let's assume single line for now as per previous implementation. + } + } + + // Action Buttons + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NButton { + text: I18n.tr("common.test") + icon: "caret-right" + onClicked: root.test(commandInput.text) + // Disable test if empty? Or maybe allow testing defined script. + enabled: commandInput.text !== "" + } + + Item { + Layout.fillWidth: true + } // Spacer + + NButton { + text: I18n.tr("common.cancel") + outlined: true + onClicked: root.close() + } + + NButton { + text: I18n.tr("common.save") + icon: "check" + backgroundColor: Color.mPrimary + textColor: Color.mOnPrimary + onClicked: { + root.saved(commandInput.text); + root.close(); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Hooks/HookRow.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Hooks/HookRow.qml new file mode 100644 index 0000000..6ebc5a1 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Hooks/HookRow.qml @@ -0,0 +1,28 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +RowLayout { + id: root + + property string label: "" + property string description: "" + property string value: "" + + signal editClicked + + spacing: Style.marginM + + NLabel { + label: root.label + description: root.description + labelColor: root.value ? Color.mPrimary : Color.mOnSurface + } + + NIconButton { + icon: "settings" + onClicked: root.editClicked() + tooltipText: I18n.tr("common.edit") + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Hooks/HooksListSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Hooks/HooksListSubTab.qml new file mode 100644 index 0000000..9e434c0 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Hooks/HooksListSubTab.qml @@ -0,0 +1,172 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Control +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + enabled: Settings.data.hooks.enabled + spacing: Style.marginL + width: parent.width + + // Shared Edit Popup + HookEditPopup { + id: editPopup + parent: Overlay.overlay + } + + // Helper to open popup + function openEdit(label, description, placeholder, value, onSave, onTest) { + editPopup.hookLabel = label; + editPopup.hookDescription = description; + editPopup.hookPlaceholder = placeholder; + editPopup.initialValue = value; + + // Disconnect previous signals + try { + editPopup.saved.disconnect(editPopup._savedSlot); + } catch (e) {} + try { + editPopup.test.disconnect(editPopup._testSlot); + } catch (e) {} + + // Define slots + editPopup._savedSlot = onSave; + editPopup._testSlot = onTest; + + // Connect new signals + editPopup.saved.connect(editPopup._savedSlot); + editPopup.test.connect(editPopup._testSlot); + + editPopup.open(); + } + + // Startup Hook + HookRow { + label: I18n.tr("panels.hooks.noctalia-started-label") + description: I18n.tr("panels.hooks.noctalia-started-description") + value: Settings.data.hooks.startup + onEditClicked: openEdit(label, description, I18n.tr("panels.hooks.noctalia-started-placeholder"), value, newValue => { + Settings.data.hooks.startup = newValue; + Settings.saveImmediate(); + }, val => { + HooksService.executeStartupHook(); + }) + } + + // Wallpaper Hook + HookRow { + label: I18n.tr("panels.hooks.wallpaper-changed-label") + description: I18n.tr("panels.hooks.wallpaper-changed-description") + value: Settings.data.hooks.wallpaperChange + onEditClicked: openEdit(label, description, I18n.tr("panels.hooks.wallpaper-changed-placeholder"), value, newValue => { + Settings.data.hooks.wallpaperChange = newValue; + Settings.saveImmediate(); + }, val => { + if (val) + Quickshell.execDetached(["sh", "-lc", val.replace("$1", "test_wallpaper_path").replace("$2", "test_screen").replace("$3", "dark")]); + }) + } + + // Color Generation Hook + HookRow { + label: I18n.tr("panels.hooks.color-generation-label") + description: I18n.tr("panels.hooks.color-generation-description") + value: Settings.data.hooks.colorGeneration + onEditClicked: openEdit(label, description, I18n.tr("panels.hooks.color-generation-placeholder"), value, newValue => { + Settings.data.hooks.colorGeneration = newValue; + Settings.saveImmediate(); + }, val => { + if (val) + Quickshell.execDetached(["sh", "-lc", val.replace("$1", "dark")]); + }) + } + + // Theme Hook + HookRow { + label: I18n.tr("panels.hooks.theme-changed-label") + description: I18n.tr("panels.hooks.theme-changed-description") + value: Settings.data.hooks.darkModeChange + onEditClicked: openEdit(label, description, I18n.tr("panels.hooks.theme-changed-placeholder"), value, newValue => { + Settings.data.hooks.darkModeChange = newValue; + Settings.saveImmediate(); + }, val => { + if (val) + Quickshell.execDetached(["sh", "-lc", val.replace("$1", "true")]); + }) + } + + // Screen Lock Hook + HookRow { + label: I18n.tr("panels.hooks.screen-lock-label") + description: I18n.tr("panels.hooks.screen-lock-description") + value: Settings.data.hooks.screenLock + onEditClicked: openEdit(label, description, I18n.tr("panels.hooks.screen-lock-placeholder"), value, newValue => { + Settings.data.hooks.screenLock = newValue; + Settings.saveImmediate(); + }, val => { + if (val) + Quickshell.execDetached(["sh", "-lc", val]); + }) + } + + // Screen Unlock Hook + HookRow { + label: I18n.tr("panels.hooks.screen-unlock-label") + description: I18n.tr("panels.hooks.screen-unlock-description") + value: Settings.data.hooks.screenUnlock + onEditClicked: openEdit(label, description, I18n.tr("panels.hooks.screen-unlock-placeholder"), value, newValue => { + Settings.data.hooks.screenUnlock = newValue; + Settings.saveImmediate(); + }, val => { + if (val) + Quickshell.execDetached(["sh", "-lc", val]); + }) + } + + // Performance Mode Enabled Hook + HookRow { + label: I18n.tr("panels.hooks.performance-mode-enabled-label") + description: I18n.tr("panels.hooks.performance-mode-enabled-description") + value: Settings.data.hooks.performanceModeEnabled + onEditClicked: openEdit(label, description, I18n.tr("panels.hooks.performance-mode-enabled-placeholder"), value, newValue => { + Settings.data.hooks.performanceModeEnabled = newValue; + Settings.saveImmediate(); + }, val => { + if (val) + Quickshell.execDetached(["sh", "-lc", val]); + }) + } + + // Performance Mode Disabled Hook + HookRow { + label: I18n.tr("panels.hooks.performance-mode-disabled-label") + description: I18n.tr("panels.hooks.performance-mode-disabled-description") + value: Settings.data.hooks.performanceModeDisabled + onEditClicked: openEdit(label, description, I18n.tr("panels.hooks.performance-mode-disabled-placeholder"), value, newValue => { + Settings.data.hooks.performanceModeDisabled = newValue; + Settings.saveImmediate(); + }, val => { + if (val) + Quickshell.execDetached(["sh", "-lc", val]); + }) + } + + // Session Hook + HookRow { + label: I18n.tr("panels.hooks.session-label") + description: I18n.tr("panels.hooks.session-description") + value: Settings.data.hooks.session + onEditClicked: openEdit(label, description, I18n.tr("panels.hooks.session-placeholder"), value, newValue => { + Settings.data.hooks.session = newValue; + Settings.saveImmediate(); + }, val => { + if (val) + Quickshell.execDetached(["sh", "-lc", val + " test"]); + }) + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Hooks/HooksTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Hooks/HooksTab.qml new file mode 100644 index 0000000..436a325 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Hooks/HooksTab.qml @@ -0,0 +1,43 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.general") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("panels.hooks.title") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginS + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + GeneralSubTab {} + HooksListSubTab {} + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Idle/BehaviorSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Idle/BehaviorSubTab.qml new file mode 100644 index 0000000..c26bb55 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Idle/BehaviorSubTab.qml @@ -0,0 +1,199 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.Power +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + // Master enable + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.idle.enable-label") + description: I18n.tr("panels.idle.enable-description") + checked: Settings.data.idle.enabled + defaultValue: Settings.getDefaultValue("idle.enabled") + onToggled: checked => Settings.data.idle.enabled = checked + } + + // Live idle status + RowLayout { + Layout.fillWidth: true + enabled: Settings.data.idle.enabled + visible: IdleService.nativeIdleMonitorAvailable + + NLabel { + label: I18n.tr("panels.idle.status-label") + description: I18n.tr("panels.idle.status-description") + } + + Item { + Layout.fillWidth: true + } + + NText { + Layout.alignment: Qt.AlignBottom | Qt.AlignRight + text: IdleService.idleSeconds > 0 ? I18n.trp("common.second", IdleService.idleSeconds) : I18n.tr("common.active") + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeM + color: IdleService.idleSeconds > 0 ? Color.mPrimary : Color.mOnSurfaceVariant + } + } + + NLabel { + visible: !IdleService.nativeIdleMonitorAvailable + description: I18n.tr("panels.idle.unavailable") + } + + NDivider { + Layout.fillWidth: true + } + + IdleCommandEditPopup { + id: editPopup + parent: Overlay.overlay + } + + function openEdit(actionName, cmdVal, resumeCmdVal, onSaveCmd, onSaveResume) { + editPopup.editIndex = -1; + editPopup.showCommand = true; + editPopup.showTimeout = false; + editPopup.titleText = I18n.tr("common.edit") + " " + actionName; + editPopup.timeoutValue = 0; + editPopup.commandValue = cmdVal; + editPopup.resumeCommandValue = resumeCmdVal; + + try { + editPopup.saved.disconnect(editPopup._savedSlot); + } catch (e) {} + + editPopup._savedSlot = function (timeout, cmd, resumeCmd, name) { + onSaveCmd(cmd); + onSaveResume(resumeCmd); + }; + + editPopup.saved.connect(editPopup._savedSlot); + editPopup.open(); + } + + // Timeout spinboxes and resume commands + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginL + enabled: Settings.data.idle.enabled + + NLabel { + label: I18n.tr("panels.idle.timeouts-label") + description: I18n.tr("panels.idle.timeouts-description") + } + + DefaultActionRow { + actionName: I18n.tr("panels.idle.screen-off-label") + actionDescription: I18n.tr("panels.idle.screen-off-description") + timeoutValue: Settings.data.idle.screenOffTimeout + defaultValue: Settings.getDefaultValue("idle.screenOffTimeout") + command: Settings.data.idle.screenOffCommand + resumeCommand: Settings.data.idle.resumeScreenOffCommand + onActionTimeoutChanged: val => Settings.data.idle.screenOffTimeout = val + onActionCommandChanged: cmd => { + Settings.data.idle.screenOffCommand = cmd; + Settings.saveImmediate(); + } + onActionResumeCommandChanged: cmd => { + Settings.data.idle.resumeScreenOffCommand = cmd; + Settings.saveImmediate(); + } + } + + DefaultActionRow { + actionName: I18n.tr("panels.idle.lock-label") + actionDescription: I18n.tr("panels.idle.lock-description") + timeoutValue: Settings.data.idle.lockTimeout + defaultValue: Settings.getDefaultValue("idle.lockTimeout") + command: Settings.data.idle.lockCommand + resumeCommand: Settings.data.idle.resumeLockCommand + onActionTimeoutChanged: val => Settings.data.idle.lockTimeout = val + onActionCommandChanged: cmd => { + Settings.data.idle.lockCommand = cmd; + Settings.saveImmediate(); + } + onActionResumeCommandChanged: cmd => { + Settings.data.idle.resumeLockCommand = cmd; + Settings.saveImmediate(); + } + } + + DefaultActionRow { + actionName: I18n.tr("common.suspend") + actionDescription: I18n.tr("panels.idle.suspend-description") + timeoutValue: Settings.data.idle.suspendTimeout + defaultValue: Settings.getDefaultValue("idle.suspendTimeout") + command: Settings.data.idle.suspendCommand + resumeCommand: Settings.data.idle.resumeSuspendCommand + onActionTimeoutChanged: val => Settings.data.idle.suspendTimeout = val + onActionCommandChanged: cmd => { + Settings.data.idle.suspendCommand = cmd; + Settings.saveImmediate(); + } + onActionResumeCommandChanged: cmd => { + Settings.data.idle.resumeSuspendCommand = cmd; + Settings.saveImmediate(); + } + } + + NDivider { + Layout.fillWidth: true + } + + NSpinBox { + label: I18n.tr("panels.idle.fade-duration-label") + description: I18n.tr("panels.idle.fade-duration-description") + from: 1 + to: 60 + suffix: "s" + value: Settings.data.idle.fadeDuration + defaultValue: Settings.getDefaultValue("idle.fadeDuration") + onValueChanged: Settings.data.idle.fadeDuration = value + } + } + + component DefaultActionRow: RowLayout { + id: rowRoot + Layout.fillWidth: true + spacing: Style.marginM + + property string actionName + property string actionDescription + property alias timeoutValue: spinBox.value + property int defaultValue + property string command + property string resumeCommand + + signal actionTimeoutChanged(int newValue) + signal actionCommandChanged(string newCmd) + signal actionResumeCommandChanged(string newCmd) + + NSpinBox { + id: spinBox + Layout.fillWidth: true + label: rowRoot.actionName + description: rowRoot.actionDescription + from: 0 + to: 86400 + suffix: "s" + defaultValue: rowRoot.defaultValue + onValueChanged: rowRoot.actionTimeoutChanged(value) + } + + NIconButton { + Layout.alignment: Qt.AlignVCenter + icon: "settings" + tooltipText: I18n.tr("common.edit") + onClicked: root.openEdit(rowRoot.actionName, rowRoot.command, rowRoot.resumeCommand, rowRoot.actionCommandChanged, rowRoot.actionResumeCommandChanged) + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Idle/CustomSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Idle/CustomSubTab.qml new file mode 100644 index 0000000..211519f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Idle/CustomSubTab.qml @@ -0,0 +1,163 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.Power +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + enabled: Settings.data.idle.enabled + + property bool _saving: false + + ListModel { + id: entriesModel + } + + function _loadToModel() { + if (_saving) + return; + entriesModel.clear(); + var entries = []; + try { + entries = JSON.parse(Settings.data.idle.customCommands); + } catch (e) { + Logger.w("CustomSubTab", "Failed to parse customCommands:", e); + } + for (var i = 0; i < entries.length; i++) { + entriesModel.append({ + "name": String(entries[i].name || ""), + "timeout": parseInt(entries[i].timeout) || 60, + "command": String(entries[i].command || ""), + "resumeCommand": String(entries[i].resumeCommand || "") + }); + } + } + + function _saveFromModel() { + _saving = true; + var arr = []; + for (var i = 0; i < entriesModel.count; i++) { + var item = entriesModel.get(i); + arr.push({ + "name": item.name, + "timeout": item.timeout, + "command": item.command, + "resumeCommand": item.resumeCommand + }); + } + Settings.data.idle.customCommands = JSON.stringify(arr); + _saving = false; + } + + function _removeEntry(index) { + entriesModel.remove(index, 1); + _saveFromModel(); + } + + Component.onCompleted: Qt.callLater(_loadToModel) + + Connections { + target: Settings.data.idle + function onCustomCommandsChanged() { + root._loadToModel(); + } + } + + // Shared Edit Popup + IdleCommandEditPopup { + id: editPopup + parent: Overlay.overlay + } + + function openEdit(index, nameVal, timeoutVal, cmdVal, resumeCmdVal) { + editPopup.editIndex = index; + editPopup.nameValue = nameVal; + editPopup.timeoutValue = timeoutVal; + editPopup.commandValue = cmdVal; + editPopup.resumeCommandValue = resumeCmdVal; + editPopup.showName = true; + + try { + editPopup.saved.disconnect(editPopup._savedSlot); + } catch (e) {} + + editPopup._savedSlot = function (timeout, cmd, resumeCmd, name) { + if (index >= 0 && index < entriesModel.count) { + entriesModel.setProperty(index, "name", name); + entriesModel.setProperty(index, "timeout", timeout); + entriesModel.setProperty(index, "command", cmd); + entriesModel.setProperty(index, "resumeCommand", resumeCmd); + } else { + entriesModel.append({ + "name": name, + "timeout": timeout, + "command": cmd, + "resumeCommand": resumeCmd + }); + } + root._saveFromModel(); + }; + + editPopup.saved.connect(editPopup._savedSlot); + editPopup.open(); + } + + NLabel { + label: I18n.tr("panels.idle.custom-label") + description: I18n.tr("panels.idle.custom-description") + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + Repeater { + model: entriesModel + + delegate: RowLayout { + id: entryDelegate + required property int index + required property string name + required property int timeout + required property string command + required property string resumeCommand + + spacing: Style.marginM + Layout.fillWidth: true + + NLabel { + Layout.fillWidth: true + label: entryDelegate.name || I18n.tr("panels.idle.custom-entry-unnamed") + description: I18n.trp("common.second", entryDelegate.timeout) + labelColor: (entryDelegate.command || entryDelegate.resumeCommand) ? Color.mPrimary : Color.mOnSurface + } + + NIconButton { + icon: "settings" + tooltipText: I18n.tr("common.edit") + onClicked: root.openEdit(entryDelegate.index, entryDelegate.name, entryDelegate.timeout, entryDelegate.command, entryDelegate.resumeCommand) + } + + NIconButton { + icon: "trash" + tooltipText: I18n.tr("panels.idle.custom-entry-delete") + onClicked: root._removeEntry(entryDelegate.index) + } + } + } + + NButton { + text: I18n.tr("panels.idle.custom-add") + icon: "add" + enabled: Settings.data.idle.enabled + onClicked: { + root.openEdit(-1, "", 60, "", ""); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Idle/IdleCommandEditPopup.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Idle/IdleCommandEditPopup.qml new file mode 100644 index 0000000..374c4aa --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Idle/IdleCommandEditPopup.qml @@ -0,0 +1,138 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +Popup { + id: root + modal: true + closePolicy: Popup.NoAutoClose + dim: true + anchors.centerIn: parent + + width: Math.min(600 * Style.uiScaleRatio, parent.width * 0.9) + height: Math.min(contentLayout.implicitHeight + padding * 2, parent.height * 0.9) + padding: Style.marginL + + property int editIndex: -1 + property int timeoutValue: 60 + property string commandValue: "" + property string resumeCommandValue: "" + property string nameValue: "" + property bool showCommand: true + property bool showTimeout: true + property bool showName: false + property string titleText: root.editIndex >= 0 ? I18n.tr("panels.idle.custom-entry-edit") : I18n.tr("panels.idle.custom-entry-new") + + signal saved(int timeout, string command, string resumeCommand, string name) + + property var _savedSlot: null + + background: Rectangle { + color: Color.mSurface + radius: Style.radiusL + border.color: Color.mOutline + border.width: Style.borderS + } + + onOpened: { + nameInput.text = nameValue; + timeoutSpinBox.value = timeoutValue; + commandInput.text = commandValue; + resumeCommandInput.text = resumeCommandValue; + if (showName) { + nameInput.forceActiveFocus(); + } else { + timeoutSpinBox.forceActiveFocus(); + } + } + + contentItem: ColumnLayout { + id: contentLayout + spacing: Style.marginL + + // Header + RowLayout { + Layout.fillWidth: true + NText { + text: root.titleText + font.weight: Style.fontWeightBold + pointSize: Style.fontSizeL + Layout.fillWidth: true + } + NIconButton { + icon: "close" + onClicked: root.close() + } + } + + // Input Area + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NTextInput { + id: nameInput + visible: root.showName + Layout.fillWidth: true + label: I18n.tr("panels.idle.custom-entry-name") + placeholderText: I18n.tr("panels.idle.custom-entry-name-placeholder") + } + + NSpinBox { + id: timeoutSpinBox + visible: root.showTimeout + Layout.fillWidth: true + label: I18n.tr("panels.idle.custom-entry-timeout") + from: 0 + to: 86400 + suffix: "s" + } + + NTextInput { + id: commandInput + visible: root.showCommand + Layout.fillWidth: true + label: I18n.tr("panels.idle.custom-entry-command") + placeholderText: "notify-send \"Idle\"" + fontFamily: Settings.data.ui.fontFixed + } + + NTextInput { + id: resumeCommandInput + Layout.fillWidth: true + label: I18n.tr("panels.idle.resume-command-label") + placeholderText: "notify-send \"Welcome back!\"" + fontFamily: Settings.data.ui.fontFixed + } + } + + // Action Buttons + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + Item { + Layout.fillWidth: true + } // Spacer + + NButton { + text: I18n.tr("common.cancel") + outlined: true + onClicked: root.close() + } + + NButton { + text: I18n.tr("common.save") + icon: "check" + backgroundColor: Color.mPrimary + textColor: Color.mOnPrimary + onClicked: { + root.saved(timeoutSpinBox.value, commandInput.text, resumeCommandInput.text, nameInput.text); + root.close(); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Idle/IdleTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Idle/IdleTab.qml new file mode 100644 index 0000000..c16156b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Idle/IdleTab.qml @@ -0,0 +1,42 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("panels.idle.tab-behavior") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("panels.idle.tab-custom") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + BehaviorSubTab {} + CustomSubTab {} + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Launcher/ClipboardSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Launcher/ClipboardSubTab.qml new file mode 100644 index 0000000..991ef00 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Launcher/ClipboardSubTab.qml @@ -0,0 +1,90 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.System +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + NToggle { + label: I18n.tr("panels.launcher.settings-clipboard-history-label") + description: I18n.tr("panels.launcher.settings-clipboard-history-description") + checked: Settings.data.appLauncher.enableClipboardHistory + onToggled: checked => Settings.data.appLauncher.enableClipboardHistory = checked + defaultValue: Settings.getDefaultValue("appLauncher.enableClipboardHistory") + } + + NToggle { + label: I18n.tr("panels.launcher.settings-clip-preview-label") + description: I18n.tr("panels.launcher.settings-clip-preview-description") + checked: Settings.data.appLauncher.enableClipPreview + onToggled: checked => Settings.data.appLauncher.enableClipPreview = checked + defaultValue: Settings.getDefaultValue("appLauncher.enableClipPreview") + enabled: Settings.data.appLauncher.enableClipboardHistory + } + + NToggle { + label: I18n.tr("panels.launcher.settings-clip-wrap-text-label") + description: I18n.tr("panels.launcher.settings-clip-wrap-text-description") + checked: Settings.data.appLauncher.clipboardWrapText + onToggled: checked => Settings.data.appLauncher.clipboardWrapText = checked + defaultValue: Settings.getDefaultValue("appLauncher.clipboardWrapText") + enabled: Settings.data.appLauncher.enableClipboardHistory + } + + NToggle { + label: I18n.tr("panels.launcher.settings-auto-paste-label") + description: I18n.tr("panels.launcher.settings-auto-paste-description") + checked: Settings.data.appLauncher.autoPasteClipboard + onToggled: checked => Settings.data.appLauncher.autoPasteClipboard = checked + defaultValue: Settings.getDefaultValue("appLauncher.autoPasteClipboard") + enabled: Settings.data.appLauncher.enableClipboardHistory && ProgramCheckerService.wtypeAvailable + } + + NToggle { + label: I18n.tr("panels.launcher.settings-clip-smart-icons-label") + description: I18n.tr("panels.launcher.settings-clip-smart-icons-description") + checked: Settings.data.appLauncher.enableClipboardSmartIcons + onToggled: checked => Settings.data.appLauncher.enableClipboardSmartIcons = checked + defaultValue: Settings.getDefaultValue("appLauncher.enableClipboardSmartIcons") + enabled: Settings.data.appLauncher.enableClipboardHistory + } + + NToggle { + label: I18n.tr("panels.launcher.settings-clip-chips-label") + description: I18n.tr("panels.launcher.settings-clip-chips-description") + checked: Settings.data.appLauncher.enableClipboardChips + onToggled: checked => Settings.data.appLauncher.enableClipboardChips = checked + defaultValue: Settings.getDefaultValue("appLauncher.enableClipboardChips") + enabled: Settings.data.appLauncher.enableClipboardHistory + } + + NDivider { + Layout.fillWidth: true + visible: Settings.data.appLauncher.enableClipboardHistory + } + + NTextInput { + label: I18n.tr("panels.launcher.settings-clipboard-watch-text-label") + description: I18n.tr("panels.launcher.settings-clipboard-watch-text-description") + Layout.fillWidth: true + text: Settings.data.appLauncher.clipboardWatchTextCommand + onTextChanged: Settings.data.appLauncher.clipboardWatchTextCommand = text + enabled: Settings.data.appLauncher.enableClipboardHistory + visible: Settings.data.appLauncher.enableClipboardHistory + } + + NTextInput { + label: I18n.tr("panels.launcher.settings-clipboard-watch-image-label") + description: I18n.tr("panels.launcher.settings-clipboard-watch-image-description") + Layout.fillWidth: true + text: Settings.data.appLauncher.clipboardWatchImageCommand + onTextChanged: Settings.data.appLauncher.clipboardWatchImageCommand = text + enabled: Settings.data.appLauncher.enableClipboardHistory + visible: Settings.data.appLauncher.enableClipboardHistory + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Launcher/ExecuteSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Launcher/ExecuteSubTab.qml new file mode 100644 index 0000000..07fab76 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Launcher/ExecuteSubTab.qml @@ -0,0 +1,49 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.System +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + NTextInput { + label: I18n.tr("panels.launcher.settings-terminal-command-label") + description: I18n.tr("panels.launcher.settings-terminal-command-description") + Layout.fillWidth: true + text: Settings.data.appLauncher.terminalCommand + onTextChanged: { + Settings.data.appLauncher.terminalCommand = text; + } + } + + NToggle { + label: I18n.tr("panels.launcher.settings-custom-launch-prefix-enabled-label") + description: I18n.tr("panels.launcher.settings-custom-launch-prefix-enabled-description") + checked: Settings.data.appLauncher.customLaunchPrefixEnabled + onToggled: checked => Settings.data.appLauncher.customLaunchPrefixEnabled = checked + defaultValue: Settings.getDefaultValue("appLauncher.customLaunchPrefixEnabled") + } + + NTextInput { + label: I18n.tr("panels.launcher.settings-custom-launch-prefix-label") + description: I18n.tr("panels.launcher.settings-custom-launch-prefix-description") + Layout.fillWidth: true + text: Settings.data.appLauncher.customLaunchPrefix + enabled: Settings.data.appLauncher.customLaunchPrefixEnabled + visible: Settings.data.appLauncher.customLaunchPrefixEnabled + onTextChanged: Settings.data.appLauncher.customLaunchPrefix = text + } + + NTextInput { + label: I18n.tr("panels.launcher.settings-annotation-tool-label") + description: I18n.tr("panels.launcher.settings-annotation-tool-description") + Layout.fillWidth: true + text: Settings.data.appLauncher.screenshotAnnotationTool + placeholderText: I18n.tr("panels.launcher.settings-annotation-tool-placeholder") + onTextChanged: Settings.data.appLauncher.screenshotAnnotationTool = text + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Launcher/GeneralSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Launcher/GeneralSubTab.qml new file mode 100644 index 0000000..e6dc189 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Launcher/GeneralSubTab.qml @@ -0,0 +1,179 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + NComboBox { + label: I18n.tr("common.position") + description: I18n.tr("panels.launcher.settings-position-description") + Layout.fillWidth: true + model: [ + { + "key": "follow_bar", + "name": I18n.tr("positions.follow-bar") + }, + { + "key": "center", + "name": I18n.tr("positions.center") + }, + { + "key": "top_center", + "name": I18n.tr("positions.top-center") + }, + { + "key": "top_left", + "name": I18n.tr("positions.top-left") + }, + { + "key": "top_right", + "name": I18n.tr("positions.top-right") + }, + { + "key": "bottom_left", + "name": I18n.tr("positions.bottom-left") + }, + { + "key": "bottom_right", + "name": I18n.tr("positions.bottom-right") + }, + { + "key": "bottom_center", + "name": I18n.tr("positions.bottom-center") + } + ] + currentKey: Settings.data.appLauncher.position + onSelected: function (key) { + Settings.data.appLauncher.position = key; + } + + defaultValue: Settings.getDefaultValue("appLauncher.position") + } + + NToggle { + label: I18n.tr("panels.launcher.settings-overlay-layer-label") + description: I18n.tr("panels.launcher.settings-overlay-layer-description") + checked: Settings.data.appLauncher.overviewLayer + onToggled: checked => Settings.data.appLauncher.overviewLayer = checked + defaultValue: Settings.getDefaultValue("appLauncher.overviewLayer") + } + + NComboBox { + label: I18n.tr("panels.launcher.settings-view-mode-label") + description: I18n.tr("panels.launcher.settings-view-mode-description") + Layout.fillWidth: true + model: [ + { + "key": "list", + "name": I18n.tr("options.launcher-view-mode.list") + }, + { + "key": "grid", + "name": I18n.tr("options.launcher-view-mode.grid") + } + ] + currentKey: Settings.data.appLauncher.viewMode + onSelected: function (key) { + Settings.data.appLauncher.viewMode = key; + } + defaultValue: Settings.getDefaultValue("appLauncher.viewMode") + } + + NComboBox { + label: I18n.tr("panels.launcher.settings-density-label") + description: I18n.tr("panels.launcher.settings-density-description") + Layout.fillWidth: true + model: [ + { + "key": "compact", + "name": I18n.tr("options.launcher-density.compact") + }, + { + "key": "default", + "name": I18n.tr("options.launcher-density.default") + }, + { + "key": "comfortable", + "name": I18n.tr("options.launcher-density.comfortable") + } + ] + currentKey: Settings.data.appLauncher.density || "compact" + onSelected: function (key) { + Settings.data.appLauncher.density = key; + } + defaultValue: Settings.getDefaultValue("appLauncher.density") + } + + NToggle { + label: I18n.tr("panels.launcher.settings-show-categories-label") + description: I18n.tr("panels.launcher.settings-show-categories-description") + checked: Settings.data.appLauncher.showCategories + onToggled: checked => Settings.data.appLauncher.showCategories = checked + defaultValue: Settings.getDefaultValue("appLauncher.showCategories") + } + + NToggle { + label: I18n.tr("panels.launcher.settings-icon-mode-label") + description: I18n.tr("panels.launcher.settings-icon-mode-description") + checked: Settings.data.appLauncher.iconMode === "native" + onToggled: checked => Settings.data.appLauncher.iconMode = checked ? "native" : "tabler" + defaultValue: Settings.getDefaultValue("appLauncher.iconMode") === "native" + } + + NToggle { + label: I18n.tr("panels.launcher.settings-show-icon-background-label") + description: I18n.tr("panels.launcher.settings-show-icon-background-description") + checked: Settings.data.appLauncher.showIconBackground + onToggled: checked => Settings.data.appLauncher.showIconBackground = checked + defaultValue: Settings.getDefaultValue("appLauncher.showIconBackground") + } + + NDivider { + Layout.fillWidth: true + } + + NToggle { + label: I18n.tr("panels.launcher.settings-sort-by-usage-label") + description: I18n.tr("panels.launcher.settings-sort-by-usage-description") + checked: Settings.data.appLauncher.sortByMostUsed + onToggled: checked => Settings.data.appLauncher.sortByMostUsed = checked + defaultValue: Settings.getDefaultValue("appLauncher.sortByMostUsed") + } + + NToggle { + label: I18n.tr("panels.launcher.settings-enable-settings-search-label") + description: I18n.tr("panels.launcher.settings-enable-settings-search-description") + checked: Settings.data.appLauncher.enableSettingsSearch + onToggled: checked => Settings.data.appLauncher.enableSettingsSearch = checked + defaultValue: Settings.getDefaultValue("appLauncher.enableSettingsSearch") + } + + NToggle { + label: I18n.tr("panels.launcher.settings-enable-windows-search-label") + description: I18n.tr("panels.launcher.settings-enable-windows-search-description") + checked: Settings.data.appLauncher.enableWindowsSearch + onToggled: checked => Settings.data.appLauncher.enableWindowsSearch = checked + defaultValue: Settings.getDefaultValue("appLauncher.enableWindowsSearch") + } + + NToggle { + label: I18n.tr("panels.launcher.settings-enable-session-search-label") + description: I18n.tr("panels.launcher.settings-enable-session-search-description") + checked: Settings.data.appLauncher.enableSessionSearch + onToggled: checked => Settings.data.appLauncher.enableSessionSearch = checked + defaultValue: Settings.getDefaultValue("appLauncher.enableSessionSearch") + } + + NToggle { + label: I18n.tr("panels.launcher.settings-ignore-mouse-input-label") + description: I18n.tr("panels.launcher.settings-ignore-mouse-input-description") + checked: Settings.data.appLauncher.ignoreMouseInput + onToggled: checked => Settings.data.appLauncher.ignoreMouseInput = checked + defaultValue: Settings.getDefaultValue("appLauncher.ignoreMouseInput") + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Launcher/LauncherTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Launcher/LauncherTab.qml new file mode 100644 index 0000000..46f2c2b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Launcher/LauncherTab.qml @@ -0,0 +1,48 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.general") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.clipboard") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + NTabButton { + text: I18n.tr("common.execute") + tabIndex: 2 + checked: subTabBar.currentIndex === 2 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + GeneralSubTab {} + ClipboardSubTab {} + ExecuteSubTab {} + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/LockScreen/AppearanceSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/LockScreen/AppearanceSubTab.qml new file mode 100644 index 0000000..545b047 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/LockScreen/AppearanceSubTab.qml @@ -0,0 +1,123 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + function insertToken(token) { + if (formatInput.inputItem) { + var input = formatInput.inputItem; + var cursorPos = input.cursorPosition; + var currentText = input.text; + var newText = currentText.substring(0, cursorPos) + token + currentText.substring(cursorPos); + input.text = newText + " "; + input.cursorPosition = cursorPos + token.length + 1; + input.forceActiveFocus(); + } + } + + NComboBox { + label: I18n.tr("panels.lock-screen.clock-style-label") + description: I18n.tr("panels.lock-screen.clock-style-description") + model: [ + { + "key": "analog", + "name": I18n.tr("panels.lock-screen.clock-style-analog") + }, + { + "key": "digital", + "name": I18n.tr("panels.lock-screen.clock-style-digital") + }, + { + "key": "custom", + "name": I18n.tr("panels.lock-screen.clock-style-custom") + } + ] + currentKey: Settings.data.general.clockStyle + onSelected: key => Settings.data.general.clockStyle = key + defaultValue: Settings.getDefaultValue("general.clockStyle") + z: 10 + } + + NTextInput { + id: formatInput + label: I18n.tr("panels.lock-screen.clock-format-label") + description: I18n.tr("panels.lock-screen.clock-format-description") + text: Settings.data.general.clockFormat + onTextChanged: Settings.data.general.clockFormat = text + visible: Settings.data.general.clockStyle === "custom" + defaultValue: Settings.getDefaultValue("general.clockFormat") + } + + NDateTimeTokens { + Layout.fillWidth: true + Layout.preferredHeight: 300 + visible: Settings.data.general.clockStyle === "custom" + onTokenClicked: token => root.insertToken(token) + } + + NToggle { + label: I18n.tr("panels.lock-screen.password-chars-label") + description: I18n.tr("panels.lock-screen.password-chars-description") + checked: Settings.data.general.passwordChars + onToggled: checked => Settings.data.general.passwordChars = checked + defaultValue: Settings.getDefaultValue("general.passwordChars") + } + + NToggle { + label: I18n.tr("panels.lock-screen.compact-lockscreen-label") + description: I18n.tr("panels.lock-screen.compact-lockscreen-description") + checked: Settings.data.general.compactLockScreen + onToggled: checked => Settings.data.general.compactLockScreen = checked + defaultValue: Settings.getDefaultValue("general.compactLockScreen") + } + + NToggle { + label: I18n.tr("panels.lock-screen.enable-lockscreen-media-controls-label") + description: I18n.tr("panels.lock-screen.enable-lockscreen-media-controls-description") + checked: Settings.data.general.enableLockScreenMediaControls + onToggled: checked => Settings.data.general.enableLockScreenMediaControls = checked + visible: !Settings.data.general.compactLockScreen + defaultValue: Settings.getDefaultValue("general.enableLockScreenMediaControls") + } + + NToggle { + label: I18n.tr("panels.lock-screen.lock-screen-animations-label") + description: I18n.tr("panels.lock-screen.lock-screen-animations-description") + checked: Settings.data.general.lockScreenAnimations + onToggled: checked => Settings.data.general.lockScreenAnimations = checked + defaultValue: Settings.getDefaultValue("general.lockScreenAnimations") + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.lock-screen.lock-screen-blur-strength-label") + description: I18n.tr("panels.lock-screen.lock-screen-blur-strength-description") + from: 0.0 + to: 1.0 + stepSize: 0.01 + showReset: true + value: Settings.data.general.lockScreenBlur + onMoved: value => Settings.data.general.lockScreenBlur = value + text: ((Settings.data.general.lockScreenBlur) * 100).toFixed(0) + "%" + defaultValue: Settings.getDefaultValue("general.lockScreenBlur") + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.lock-screen.lock-screen-tint-strength-label") + description: I18n.tr("panels.lock-screen.lock-screen-tint-strength-description") + from: 0.0 + to: 1.0 + stepSize: 0.01 + showReset: true + value: Settings.data.general.lockScreenTint + onMoved: value => Settings.data.general.lockScreenTint = value + text: ((Settings.data.general.lockScreenTint) * 100).toFixed(0) + "%" + defaultValue: Settings.getDefaultValue("general.lockScreenTint") + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/LockScreen/BehaviorSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/LockScreen/BehaviorSubTab.qml new file mode 100644 index 0000000..e3a464f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/LockScreen/BehaviorSubTab.qml @@ -0,0 +1,75 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + + NToggle { + label: I18n.tr("panels.lock-screen.lock-on-suspend-label") + description: I18n.tr("panels.lock-screen.lock-on-suspend-description") + checked: Settings.data.general.lockOnSuspend + onToggled: checked => Settings.data.general.lockOnSuspend = checked + defaultValue: Settings.getDefaultValue("general.lockOnSuspend") + } + + NToggle { + label: I18n.tr("panels.lock-screen.auto-start-auth-label") + description: I18n.tr("panels.lock-screen.auto-start-auth-description") + checked: Settings.data.general.autoStartAuth + onToggled: checked => Settings.data.general.autoStartAuth = checked + defaultValue: Settings.getDefaultValue("general.autoStartAuth") + } + + NToggle { + label: I18n.tr("panels.lock-screen.allow-password-with-fprintd-label") + description: I18n.tr("panels.lock-screen.allow-password-with-fprintd-description") + checked: Settings.data.general.allowPasswordWithFprintd + onToggled: checked => Settings.data.general.allowPasswordWithFprintd = checked + defaultValue: Settings.getDefaultValue("general.allowPasswordWithFprintd") + } + + NToggle { + label: I18n.tr("panels.lock-screen.show-session-buttons-label") + description: I18n.tr("panels.lock-screen.show-session-buttons-description") + checked: Settings.data.general.showSessionButtonsOnLockScreen + onToggled: checked => Settings.data.general.showSessionButtonsOnLockScreen = checked + defaultValue: Settings.getDefaultValue("general.showSessionButtonsOnLockScreen") + } + + NToggle { + label: I18n.tr("panels.lock-screen.show-hibernate-label") + description: I18n.tr("panels.lock-screen.show-hibernate-description") + checked: Settings.data.general.showHibernateOnLockScreen + onToggled: checked => Settings.data.general.showHibernateOnLockScreen = checked + visible: Settings.data.general.showSessionButtonsOnLockScreen + defaultValue: Settings.getDefaultValue("general.showSessionButtonsOnLockScreen") + } + + NToggle { + label: I18n.tr("panels.session-menu.enable-countdown-label") + description: I18n.tr("panels.session-menu.enable-countdown-description") + checked: Settings.data.general.enableLockScreenCountdown + onToggled: checked => Settings.data.general.enableLockScreenCountdown = checked + visible: Settings.data.general.showSessionButtonsOnLockScreen + defaultValue: Settings.getDefaultValue("general.enableLockScreenCountdown") + } + + NValueSlider { + visible: Settings.data.general.showSessionButtonsOnLockScreen && Settings.data.general.enableLockScreenCountdown + Layout.fillWidth: true + label: I18n.tr("panels.session-menu.countdown-duration-label") + description: I18n.tr("panels.session-menu.countdown-duration-description") + from: 1000 + to: 30000 + stepSize: 1000 + showReset: true + value: Settings.data.general.lockScreenCountdownDuration + onMoved: value => Settings.data.general.lockScreenCountdownDuration = value + text: Math.round(Settings.data.general.lockScreenCountdownDuration / 1000) + "s" + defaultValue: Settings.getDefaultValue("general.lockScreenCountdownDuration") + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/LockScreen/LockScreenTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/LockScreen/LockScreenTab.qml new file mode 100644 index 0000000..b6a1320 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/LockScreen/LockScreenTab.qml @@ -0,0 +1,48 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.appearance") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.behavior") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + NTabButton { + text: I18n.tr("common.monitors") + tabIndex: 2 + checked: subTabBar.currentIndex === 2 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + AppearanceSubTab {} + BehaviorSubTab {} + MonitorsSubTab {} + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/LockScreen/MonitorsSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/LockScreen/MonitorsSubTab.qml new file mode 100644 index 0000000..13239ab --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/LockScreen/MonitorsSubTab.qml @@ -0,0 +1,61 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Compositor +import qs.Widgets + +ColumnLayout { + id: root + enabled: true + spacing: Style.marginL + Layout.fillWidth: true + + // Helper functions to update arrays immutably + function addMonitor(list, name) { + const arr = (list || []).slice(); + if (!arr.includes(name)) + arr.push(name); + return arr; + } + function removeMonitor(list, name) { + return (list || []).filter(function (n) { + return n !== name; + }); + } + + NText { + text: I18n.tr("panels.lock-screen.monitors-desc") + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + Repeater { + model: Quickshell.screens || [] + delegate: NCheckbox { + Layout.fillWidth: true + readonly property real compositorScale: { + const info = CompositorService.displayScales[modelData.name]; + return (info && info.scale) ? info.scale : 1.0; + } + label: modelData.name || "Unknown" + description: { + I18n.tr("system.monitor-description", { + "model": modelData.model, + "width": modelData.width * compositorScale, + "height": modelData.height * compositorScale, + "scale": compositorScale + }); + } + checked: (Settings.data.general.lockScreenMonitors || []).indexOf(modelData.name) !== -1 + onToggled: checked => { + if (checked) { + Settings.data.general.lockScreenMonitors = root.addMonitor(Settings.data.general.lockScreenMonitors, modelData.name); + } else { + Settings.data.general.lockScreenMonitors = root.removeMonitor(Settings.data.general.lockScreenMonitors, modelData.name); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/DurationSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/DurationSubTab.qml new file mode 100644 index 0000000..708e272 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/DurationSubTab.qml @@ -0,0 +1,62 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + enabled: Settings.data.notifications.enabled + + NToggle { + label: I18n.tr("panels.notifications.duration-respect-expire-label") + description: I18n.tr("panels.notifications.duration-respect-expire-description") + checked: Settings.data.notifications.respectExpireTimeout + onToggled: checked => Settings.data.notifications.respectExpireTimeout = checked + defaultValue: Settings.getDefaultValue("notifications.respectExpireTimeout") + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.notifications.duration-low-urgency-label") + description: I18n.tr("panels.notifications.duration-low-urgency-description") + from: 1 + to: 30 + stepSize: 1 + showReset: true + value: Settings.data.notifications.lowUrgencyDuration + onMoved: value => Settings.data.notifications.lowUrgencyDuration = value + text: Settings.data.notifications.lowUrgencyDuration + "s" + defaultValue: Settings.getDefaultValue("notifications.lowUrgencyDuration") + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.notifications.duration-normal-urgency-label") + description: I18n.tr("panels.notifications.duration-normal-urgency-description") + from: 1 + to: 30 + stepSize: 1 + showReset: true + value: Settings.data.notifications.normalUrgencyDuration + onMoved: value => Settings.data.notifications.normalUrgencyDuration = value + text: Settings.data.notifications.normalUrgencyDuration + "s" + defaultValue: Settings.getDefaultValue("notifications.normalUrgencyDuration") + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.notifications.duration-critical-urgency-label") + description: I18n.tr("panels.notifications.duration-critical-urgency-description") + from: 1 + to: 30 + stepSize: 1 + showReset: true + value: Settings.data.notifications.criticalUrgencyDuration + onMoved: value => Settings.data.notifications.criticalUrgencyDuration = value + text: Settings.data.notifications.criticalUrgencyDuration + "s" + defaultValue: Settings.getDefaultValue("notifications.criticalUrgencyDuration") + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/GeneralSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/GeneralSubTab.qml new file mode 100644 index 0000000..bad998b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/GeneralSubTab.qml @@ -0,0 +1,149 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Compositor +import qs.Services.System +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + property var addMonitor + property var removeMonitor + + NToggle { + label: I18n.tr("panels.notifications.settings-enabled-label") + description: I18n.tr("panels.notifications.settings-enabled-description") + checked: Settings.data.notifications.enabled !== false + onToggled: checked => Settings.data.notifications.enabled = checked + defaultValue: Settings.getDefaultValue("notifications.enabled") + } + + ColumnLayout { + spacing: Style.marginL + enabled: Settings.data.notifications.enabled + + NComboBox { + label: I18n.tr("panels.notifications.settings-density-label") + description: I18n.tr("panels.notifications.settings-density-description") + model: [ + { + "key": "default", + "name": I18n.tr("options.notification-density.default") + }, + { + "key": "compact", + "name": I18n.tr("options.notification-density.compact") + } + ] + currentKey: Settings.data.notifications.density || "default" + onSelected: key => Settings.data.notifications.density = key + defaultValue: Settings.getDefaultValue("notifications.density") + } + + NToggle { + label: I18n.tr("tooltips.do-not-disturb-enabled") + description: I18n.tr("panels.notifications.settings-do-not-disturb-description") + checked: NotificationService.doNotDisturb + onToggled: checked => NotificationService.doNotDisturb = checked + } + + NComboBox { + label: I18n.tr("common.position") + description: I18n.tr("panels.notifications.settings-location-description") + model: [ + { + "key": "top", + "name": I18n.tr("positions.top-center") + }, + { + "key": "top_left", + "name": I18n.tr("positions.top-left") + }, + { + "key": "top_right", + "name": I18n.tr("positions.top-right") + }, + { + "key": "bottom", + "name": I18n.tr("positions.bottom-center") + }, + { + "key": "bottom_left", + "name": I18n.tr("positions.bottom-left") + }, + { + "key": "bottom_right", + "name": I18n.tr("positions.bottom-right") + } + ] + currentKey: Settings.data.notifications.location || "top_right" + onSelected: key => Settings.data.notifications.location = key + defaultValue: Settings.getDefaultValue("notifications.location") + } + + NToggle { + label: I18n.tr("panels.osd.always-on-top-label") + description: I18n.tr("panels.notifications.settings-always-on-top-description") + checked: Settings.data.notifications.overlayLayer + onToggled: checked => Settings.data.notifications.overlayLayer = checked + defaultValue: Settings.getDefaultValue("notifications.overlayLayer") + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.osd.background-opacity-label") + description: I18n.tr("panels.notifications.settings-background-opacity-description") + from: 0 + to: 1 + stepSize: 0.01 + showReset: true + value: Settings.data.notifications.backgroundOpacity + onMoved: value => Settings.data.notifications.backgroundOpacity = value + text: Math.round(Settings.data.notifications.backgroundOpacity * 100) + "%" + defaultValue: Settings.getDefaultValue("notifications.backgroundOpacity") + } + + NDivider { + Layout.fillWidth: true + } + + NText { + text: I18n.tr("panels.notifications.monitors-desc") + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + Repeater { + model: Quickshell.screens || [] + delegate: NCheckbox { + Layout.fillWidth: true + readonly property real compositorScale: { + const info = CompositorService.displayScales[modelData.name]; + return (info && info.scale) ? info.scale : 1.0; + } + label: modelData.name || I18n.tr("common.unknown") + description: { + I18n.tr("system.monitor-description", { + "model": modelData.model, + "width": modelData.width * compositorScale, + "height": modelData.height * compositorScale, + "scale": compositorScale + }); + } + checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1 + onToggled: checked => { + if (checked) { + Settings.data.notifications.monitors = root.addMonitor(Settings.data.notifications.monitors, modelData.name); + } else { + Settings.data.notifications.monitors = root.removeMonitor(Settings.data.notifications.monitors, modelData.name); + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/HistorySubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/HistorySubTab.qml new file mode 100644 index 0000000..929de49 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/HistorySubTab.qml @@ -0,0 +1,52 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + enabled: Settings.data.notifications.enabled + + NToggle { + label: I18n.tr("panels.notifications.history-clear-dismiss-label") + description: I18n.tr("panels.notifications.history-clear-dismiss-description") + checked: Settings.data.notifications.clearDismissed + onToggled: checked => Settings.data.notifications.clearDismissed = checked + defaultValue: Settings.getDefaultValue("notifications.clearDismissed") + } + + NToggle { + label: I18n.tr("panels.notifications.settings-markdown-label") + description: I18n.tr("panels.notifications.settings-markdown-description") + checked: Settings.data.notifications.enableMarkdown + onToggled: checked => Settings.data.notifications.enableMarkdown = checked + defaultValue: Settings.getDefaultValue("notifications.enableMarkdown") + } + + NToggle { + label: I18n.tr("panels.notifications.history-low-urgency-label") + description: I18n.tr("panels.notifications.history-low-urgency-description") + checked: Settings.data.notifications?.saveToHistory?.low !== false + onToggled: checked => Settings.data.notifications.saveToHistory.low = checked + defaultValue: Settings.getDefaultValue("notifications.saveToHistory.low") + } + + NToggle { + label: I18n.tr("panels.notifications.history-normal-urgency-label") + description: I18n.tr("panels.notifications.history-normal-urgency-description") + checked: Settings.data.notifications?.saveToHistory?.normal !== false + onToggled: checked => Settings.data.notifications.saveToHistory.normal = checked + defaultValue: Settings.getDefaultValue("notifications.saveToHistory.normal") + } + + NToggle { + label: I18n.tr("panels.notifications.history-critical-urgency-label") + description: I18n.tr("panels.notifications.history-critical-urgency-description") + checked: Settings.data.notifications?.saveToHistory?.critical !== false + onToggled: checked => Settings.data.notifications.saveToHistory.critical = checked + defaultValue: Settings.getDefaultValue("notifications.saveToHistory.critical") + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/NotificationRuleEditPopup.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/NotificationRuleEditPopup.qml new file mode 100644 index 0000000..8b71f0a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/NotificationRuleEditPopup.qml @@ -0,0 +1,130 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.System +import qs.Widgets + +Popup { + id: root + modal: true + closePolicy: Popup.NoAutoClose + dim: true + anchors.centerIn: parent + + width: Math.min(500 * Style.uiScaleRatio, parent.width * 0.9) + padding: Style.marginL + + property int editIndex: -1 + property string patternValue: "" + property string actionValue: "block" + + signal saved(string pattern, string action) + + property var _savedSlot: null + property string _selectedAction: "block" + + background: Rectangle { + color: Color.mSurface + radius: Style.radiusL + border.color: Color.mOutline + border.width: Style.borderS + } + + onOpened: { + patternInput.text = patternValue; + actionCombo.currentKey = actionValue; + _selectedAction = actionValue; + patternInput.forceActiveFocus(); + } + + contentItem: ColumnLayout { + id: contentLayout + spacing: Style.marginL + + RowLayout { + Layout.fillWidth: true + NText { + text: editIndex >= 0 ? I18n.tr("panels.notifications.rules-edit") : I18n.tr("panels.notifications.rules-add") + font.weight: Style.fontWeightBold + pointSize: Style.fontSizeL + Layout.fillWidth: true + } + NIconButton { + icon: "close" + onClicked: root.close() + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NTextInput { + id: patternInput + Layout.fillWidth: true + label: I18n.tr("panels.notifications.rules-pattern-label") + placeholderText: I18n.tr("panels.notifications.rules-pattern-placeholder") + fontFamily: Settings.data.ui.fontFixed + } + + NComboBox { + id: actionCombo + Layout.fillWidth: true + label: I18n.tr("panels.notifications.rules-action-label") + model: [ + { + "key": "block", + "name": I18n.tr("panels.notifications.rules-action-block") + }, + { + "key": "mute", + "name": I18n.tr("panels.notifications.rules-action-mute") + }, + { + "key": "hide", + "name": I18n.tr("panels.notifications.rules-action-hide") + } + ] + currentKey: actionValue + onSelected: key => { + actionValue = key; + _selectedAction = key; + } + } + + NLabel { + Layout.fillWidth: true + label: _selectedAction === "block" ? I18n.tr("panels.notifications.rules-action-block-desc") : (_selectedAction === "mute" ? I18n.tr("panels.notifications.rules-action-mute-desc") : I18n.tr("panels.notifications.rules-action-hide-desc")) + labelColor: Color.mOnSurfaceVariant + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + Item { + Layout.fillWidth: true + } + + NButton { + text: I18n.tr("common.cancel") + outlined: true + onClicked: root.close() + } + + NButton { + text: I18n.tr("common.save") + icon: "check" + backgroundColor: Color.mPrimary + textColor: Color.mOnPrimary + enabled: patternInput.text.trim() !== "" + onClicked: { + root.saved(patternInput.text.trim(), _selectedAction || "block"); + root.close(); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/NotificationsTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/NotificationsTab.qml new file mode 100644 index 0000000..831dbb9 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/NotificationsTab.qml @@ -0,0 +1,158 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + // Helper functions to update arrays immutably + function addMonitor(list, name) { + const arr = (list || []).slice(); + if (!arr.includes(name)) + arr.push(name); + return arr; + } + function removeMonitor(list, name) { + return (list || []).filter(function (n) { + return n !== name; + }); + } + + // File pickers for sound sub-tab + function openUnifiedSoundPicker() { + unifiedSoundFilePicker.open(); + } + function openLowSoundPicker() { + lowSoundFilePicker.open(); + } + function openNormalSoundPicker() { + normalSoundFilePicker.open(); + } + function openCriticalSoundPicker() { + criticalSoundFilePicker.open(); + } + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: false // this is too cramped on this tab to split evenly + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.appearance") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.duration") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + NTabButton { + text: I18n.tr("common.history") + tabIndex: 2 + checked: subTabBar.currentIndex === 2 + } + NTabButton { + text: I18n.tr("common.sound") + tabIndex: 3 + checked: subTabBar.currentIndex === 3 + } + NTabButton { + text: I18n.tr("common.toast") + tabIndex: 4 + checked: subTabBar.currentIndex === 4 + } + NTabButton { + text: I18n.tr("panels.notifications.rules-tab") + tabIndex: 5 + checked: subTabBar.currentIndex === 5 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + GeneralSubTab { + addMonitor: root.addMonitor + removeMonitor: root.removeMonitor + } + DurationSubTab {} + HistorySubTab {} + SoundSubTab { + onOpenUnifiedPicker: root.openUnifiedSoundPicker() + onOpenLowPicker: root.openLowSoundPicker() + onOpenNormalPicker: root.openNormalSoundPicker() + onOpenCriticalPicker: root.openCriticalSoundPicker() + } + ToastSubTab {} + RulesSubTab {} + } + + // File Pickers for Sound Files + NFilePicker { + id: unifiedSoundFilePicker + title: I18n.tr("panels.notifications.sounds-files-unified-select-title") + selectionMode: "files" + initialPath: Quickshell.env("HOME") + nameFilters: ["*.wav", "*.mp3", "*.ogg", "*.flac", "*.m4a", "*.aac"] + onAccepted: paths => { + if (paths.length > 0) { + const soundPath = paths[0]; + Settings.data.notifications.sounds.normalSoundFile = soundPath; + Settings.data.notifications.sounds.lowSoundFile = soundPath; + Settings.data.notifications.sounds.criticalSoundFile = soundPath; + } + } + } + + NFilePicker { + id: lowSoundFilePicker + title: I18n.tr("panels.notifications.sounds-files-low-select-title") + selectionMode: "files" + initialPath: Quickshell.env("HOME") + nameFilters: ["*.wav", "*.mp3", "*.ogg", "*.flac", "*.m4a", "*.aac"] + onAccepted: paths => { + if (paths.length > 0) { + Settings.data.notifications.sounds.lowSoundFile = paths[0]; + } + } + } + + NFilePicker { + id: normalSoundFilePicker + title: I18n.tr("panels.notifications.sounds-files-normal-select-title") + selectionMode: "files" + initialPath: Quickshell.env("HOME") + nameFilters: ["*.wav", "*.mp3", "*.ogg", "*.flac", "*.m4a", "*.aac"] + onAccepted: paths => { + if (paths.length > 0) { + Settings.data.notifications.sounds.normalSoundFile = paths[0]; + } + } + } + + NFilePicker { + id: criticalSoundFilePicker + title: I18n.tr("panels.notifications.sounds-files-critical-select-title") + selectionMode: "files" + initialPath: Quickshell.env("HOME") + nameFilters: ["*.wav", "*.mp3", "*.ogg", "*.flac", "*.m4a", "*.aac"] + onAccepted: paths => { + if (paths.length > 0) { + Settings.data.notifications.sounds.criticalSoundFile = paths[0]; + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/RulesSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/RulesSubTab.qml new file mode 100644 index 0000000..daf9633 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/RulesSubTab.qml @@ -0,0 +1,114 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.System +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + enabled: Settings.data.notifications.enabled + + function _saveToService() { + NotificationRulesService.save(); + } + + function _removeRule(index) { + var arr = (NotificationRulesService.rules || []).slice(); + arr.splice(index, 1); + NotificationRulesService.rules = arr; + _saveToService(); + } + + NotificationRuleEditPopup { + id: editPopup + parent: Overlay.overlay + } + + function openEdit(index, patternVal, actionVal) { + editPopup.editIndex = index; + editPopup.patternValue = patternVal || ""; + editPopup.actionValue = actionVal || "block"; + + try { + editPopup.saved.disconnect(editPopup._savedSlot); + } catch (e) {} + + editPopup._savedSlot = function (pattern, action) { + const trimmed = (pattern || "").trim(); + if (trimmed === "") + return; + var arr = (NotificationRulesService.rules || []).slice(); + var rule = { + "pattern": trimmed, + "action": action + }; + if (index >= 0 && index < arr.length) { + arr[index] = rule; + } else { + arr.push(rule); + } + NotificationRulesService.rules = arr; + _saveToService(); + }; + + editPopup.saved.connect(editPopup._savedSlot); + editPopup.open(); + } + + NLabel { + label: I18n.tr("panels.notifications.rules-label") + description: I18n.tr("panels.notifications.rules-description") + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + Repeater { + model: NotificationRulesService.rules || [] + + delegate: RowLayout { + id: entryDelegate + required property int index + required property var modelData + + property string pattern: modelData.pattern || "" + property string action: modelData.action || "block" + property bool isRegex: pattern.length >= 3 && pattern.startsWith("/") && pattern.endsWith("/") + + spacing: Style.marginM + Layout.fillWidth: true + + NLabel { + Layout.fillWidth: true + label: (entryDelegate.isRegex ? "regex: " : "") + entryDelegate.pattern + description: entryDelegate.action === "block" ? I18n.tr("panels.notifications.rules-action-block") : (entryDelegate.action === "mute" ? I18n.tr("panels.notifications.rules-action-mute") : I18n.tr("panels.notifications.rules-action-hide")) + labelColor: entryDelegate.pattern ? Color.mPrimary : Color.mOnSurface + } + + NIconButton { + icon: "settings" + tooltipText: I18n.tr("common.edit") + onClicked: root.openEdit(entryDelegate.index, entryDelegate.pattern, entryDelegate.action) + } + + NIconButton { + icon: "trash" + tooltipText: I18n.tr("panels.notifications.rules-delete") + onClicked: root._removeRule(entryDelegate.index) + } + } + } + + NButton { + text: I18n.tr("panels.notifications.rules-add") + icon: "add" + enabled: Settings.data.notifications.enabled + onClicked: root.openEdit(-1, "", "block") + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/SoundSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/SoundSubTab.qml new file mode 100644 index 0000000..2a69bdd --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/SoundSubTab.qml @@ -0,0 +1,210 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.System +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + enabled: Settings.data.notifications.enabled + + signal openUnifiedPicker + signal openLowPicker + signal openNormalPicker + signal openCriticalPicker + + // QtMultimedia unavailable message + NBox { + Layout.fillWidth: true + visible: !SoundService.multimediaAvailable + implicitHeight: unavailableContent.implicitHeight + Style.margin2L + + RowLayout { + id: unavailableContent + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + NIcon { + icon: "warning" + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeXL + Layout.alignment: Qt.AlignVCenter + } + + NLabel { + Layout.fillWidth: true + label: I18n.tr("panels.notifications.sounds-unavailable-label") + description: I18n.tr("panels.notifications.sounds-unavailable-description") + } + } + } + + NToggle { + enabled: SoundService.multimediaAvailable + label: I18n.tr("panels.notifications.sounds-enabled-label") + description: I18n.tr("panels.notifications.sounds-enabled-description") + checked: Settings.data.notifications?.sounds?.enabled ?? false + onToggled: checked => Settings.data.notifications.sounds.enabled = checked + defaultValue: Settings.getDefaultValue("notifications.sounds.enabled") + } + + // Sound Volume + NValueSlider { + enabled: SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false) + Layout.fillWidth: true + label: I18n.tr("panels.notifications.sounds-volume-label") + description: I18n.tr("panels.notifications.sounds-volume-description") + from: 0 + to: 1 + stepSize: 0.01 + showReset: true + value: Settings.data.notifications?.sounds?.volume ?? 0.5 + onMoved: value => Settings.data.notifications.sounds.volume = value + text: Math.round((Settings.data.notifications?.sounds?.volume ?? 0.5) * 100) + "%" + defaultValue: Settings.getDefaultValue("notifications.sounds.volume") + } + + // Separate Sounds Toggle + NToggle { + enabled: SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false) + Layout.fillWidth: true + label: I18n.tr("panels.notifications.sounds-separate-label") + description: I18n.tr("panels.notifications.sounds-separate-description") + checked: Settings.data.notifications?.sounds?.separateSounds ?? false + onToggled: checked => Settings.data.notifications.sounds.separateSounds = checked + defaultValue: Settings.getDefaultValue("notifications.sounds.separateSounds") + } + + // Unified Sound File (shown when separateSounds is false) + ColumnLayout { + enabled: SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false) + visible: !(Settings.data.notifications?.sounds?.separateSounds ?? false) + spacing: Style.marginXXS + Layout.fillWidth: true + + NLabel { + label: I18n.tr("panels.notifications.sounds-files-unified-label") + description: I18n.tr("panels.notifications.sounds-files-unified-description") + } + + NTextInputButton { + enabled: parent.enabled + Layout.fillWidth: true + placeholderText: I18n.tr("panels.notifications.sounds-files-placeholder") + text: Settings.data.notifications?.sounds?.normalSoundFile ?? "" + buttonIcon: "folder-open" + buttonTooltip: I18n.tr("panels.notifications.sounds-files-select-file") + onInputTextChanged: text => { + // When separate sounds are enabled, this row is hidden but still + // bound to normalSoundFile; user edits to normal still update that + // key and would re-trigger this handler and wipe low/critical. + if (Settings.data.notifications.sounds.separateSounds) { + return; + } + const soundPath = text; + Settings.data.notifications.sounds.normalSoundFile = soundPath; + Settings.data.notifications.sounds.lowSoundFile = soundPath; + Settings.data.notifications.sounds.criticalSoundFile = soundPath; + } + onButtonClicked: root.openUnifiedPicker() + } + } + + // Separate Sound Files (shown when separateSounds is true) + ColumnLayout { + visible: SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false) && (Settings.data.notifications?.sounds?.separateSounds ?? false) + spacing: Style.marginXXS + Layout.fillWidth: true + + // Low Urgency Sound File + ColumnLayout { + spacing: Style.marginXXS + Layout.fillWidth: true + + NLabel { + label: I18n.tr("panels.notifications.sounds-files-low-label") + description: I18n.tr("panels.notifications.sounds-files-low-description") + } + + NTextInputButton { + enabled: parent.enabled + Layout.fillWidth: true + placeholderText: I18n.tr("panels.notifications.sounds-files-placeholder") + text: Settings.data.notifications?.sounds?.lowSoundFile ?? "" + buttonIcon: "folder-open" + buttonTooltip: I18n.tr("panels.notifications.sounds-files-select-file") + onInputTextChanged: text => Settings.data.notifications.sounds.lowSoundFile = text + onButtonClicked: root.openLowPicker() + } + } + + // Normal Urgency Sound File + ColumnLayout { + spacing: Style.marginXXS + Layout.fillWidth: true + + NLabel { + label: I18n.tr("panels.notifications.sounds-files-normal-label") + description: I18n.tr("panels.notifications.sounds-files-normal-description") + } + + NTextInputButton { + enabled: parent.enabled + Layout.fillWidth: true + placeholderText: I18n.tr("panels.notifications.sounds-files-placeholder") + text: Settings.data.notifications?.sounds?.normalSoundFile ?? "" + buttonIcon: "folder-open" + buttonTooltip: I18n.tr("panels.notifications.sounds-files-select-file") + onInputTextChanged: text => Settings.data.notifications.sounds.normalSoundFile = text + onButtonClicked: root.openNormalPicker() + } + } + + // Critical Urgency Sound File + ColumnLayout { + spacing: Style.marginXXS + Layout.fillWidth: true + + NLabel { + label: I18n.tr("panels.notifications.sounds-files-critical-label") + description: I18n.tr("panels.notifications.sounds-files-critical-description") + } + + NTextInputButton { + enabled: parent.enabled + Layout.fillWidth: true + placeholderText: I18n.tr("panels.notifications.sounds-files-placeholder") + text: Settings.data.notifications?.sounds?.criticalSoundFile ?? "" + buttonIcon: "folder-open" + buttonTooltip: I18n.tr("panels.notifications.sounds-files-select-file") + onInputTextChanged: text => Settings.data.notifications.sounds.criticalSoundFile = text + onButtonClicked: root.openCriticalPicker() + } + } + } + + // Excluded Apps List + ColumnLayout { + enabled: SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false) + spacing: Style.marginXXS + Layout.fillWidth: true + + NLabel { + label: I18n.tr("panels.notifications.sounds-excluded-apps-label") + description: I18n.tr("panels.notifications.sounds-excluded-apps-description") + } + + NTextInput { + enabled: parent.enabled + Layout.fillWidth: true + placeholderText: I18n.tr("panels.notifications.sounds-excluded-apps-placeholder") + text: Settings.data.notifications?.sounds?.excludedApps ?? "" + onTextChanged: Settings.data.notifications.sounds.excludedApps = text + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/ToastSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/ToastSubTab.qml new file mode 100644 index 0000000..f6aa029 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Notifications/ToastSubTab.qml @@ -0,0 +1,35 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + NCheckbox { + Layout.fillWidth: true + label: I18n.tr("panels.notifications.toast-media-label") + description: I18n.tr("panels.notifications.toast-media-description") + checked: Settings.data.notifications.enableMediaToast + onToggled: checked => Settings.data.notifications.enableMediaToast = checked + } + + NCheckbox { + Layout.fillWidth: true + label: I18n.tr("panels.notifications.toast-keyboard-label") + description: I18n.tr("panels.notifications.toast-keyboard-description") + checked: Settings.data.notifications.enableKeyboardLayoutToast + onToggled: checked => Settings.data.notifications.enableKeyboardLayoutToast = checked + } + + NCheckbox { + Layout.fillWidth: true + label: I18n.tr("panels.notifications.toast-battery-label") + description: I18n.tr("panels.notifications.toast-battery-description") + checked: Settings.data.notifications.enableBatteryToast + onToggled: checked => Settings.data.notifications.enableBatteryToast = checked + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Osd/EventsSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Osd/EventsSubTab.qml new file mode 100644 index 0000000..c780d7d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Osd/EventsSubTab.qml @@ -0,0 +1,50 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Modules.OSD +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + property var addType + property var removeType + + Repeater { + model: [ + { + type: OSD.Type.Volume, + key: "types-volume" + }, + { + type: OSD.Type.InputVolume, + key: "types-input-volume" + }, + { + type: OSD.Type.Brightness, + key: "types-brightness" + }, + { + type: OSD.Type.LockKey, + key: "types-lockkey" + } + ] + delegate: NCheckbox { + required property var modelData + Layout.fillWidth: true + label: I18n.tr("panels.osd." + modelData.key + "-label") + description: I18n.tr("panels.osd." + modelData.key + "-description") + checked: (Settings.data.osd.enabledTypes || []).includes(modelData.type) + onToggled: checked => { + if (checked) { + Settings.data.osd.enabledTypes = root.addType(Settings.data.osd.enabledTypes, modelData.type); + } else { + Settings.data.osd.enabledTypes = root.removeType(Settings.data.osd.enabledTypes, modelData.type); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Osd/GeneralSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Osd/GeneralSubTab.qml new file mode 100644 index 0000000..3b9ff78 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Osd/GeneralSubTab.qml @@ -0,0 +1,140 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Compositor +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + property var addMonitor + property var removeMonitor + + NComboBox { + label: I18n.tr("common.position") + description: I18n.tr("panels.osd.location-description") + model: [ + { + "key": "top", + "name": I18n.tr("positions.top-center") + }, + { + "key": "top_left", + "name": I18n.tr("positions.top-left") + }, + { + "key": "top_right", + "name": I18n.tr("positions.top-right") + }, + { + "key": "bottom", + "name": I18n.tr("positions.bottom-center") + }, + { + "key": "bottom_left", + "name": I18n.tr("positions.bottom-left") + }, + { + "key": "bottom_right", + "name": I18n.tr("positions.bottom-right") + }, + { + "key": "left", + "name": I18n.tr("positions.center-left") + }, + { + "key": "right", + "name": I18n.tr("positions.center-right") + } + ] + currentKey: Settings.data.osd.location || "top_right" + defaultValue: Settings.getDefaultValue("osd.location") + onSelected: key => Settings.data.osd.location = key + } + + NToggle { + label: I18n.tr("panels.osd.enabled-label") + description: I18n.tr("panels.osd.enabled-description") + checked: Settings.data.osd.enabled + defaultValue: Settings.getDefaultValue("osd.enabled") + onToggled: checked => Settings.data.osd.enabled = checked + } + + NToggle { + label: I18n.tr("panels.osd.always-on-top-label") + description: I18n.tr("panels.osd.always-on-top-description") + checked: Settings.data.osd.overlayLayer + defaultValue: Settings.getDefaultValue("osd.overlayLayer") + onToggled: checked => Settings.data.osd.overlayLayer = checked + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.osd.background-opacity-label") + description: I18n.tr("panels.osd.background-opacity-description") + from: 0 + to: 100 + stepSize: 1 + showReset: true + value: Settings.data.osd.backgroundOpacity * 100 + defaultValue: (Settings.getDefaultValue("osd.backgroundOpacity") || 1) * 100 + onMoved: value => Settings.data.osd.backgroundOpacity = value / 100 + text: Math.round(Settings.data.osd.backgroundOpacity * 100) + "%" + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.osd.duration-auto-hide-label") + description: I18n.tr("panels.osd.duration-auto-hide-description") + from: 500 + to: 5000 + stepSize: 100 + showReset: true + value: Settings.data.osd.autoHideMs + defaultValue: Settings.getDefaultValue("osd.autoHideMs") + onMoved: value => Settings.data.osd.autoHideMs = value + text: Math.round(Settings.data.osd.autoHideMs / 1000 * 10) / 10 + "s" + } + + NDivider { + Layout.fillWidth: true + } + + NText { + text: I18n.tr("panels.osd.monitors-desc") + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + Repeater { + model: Quickshell.screens || [] + delegate: NCheckbox { + Layout.fillWidth: true + readonly property real compositorScale: { + const info = CompositorService.displayScales[modelData.name]; + return (info && info.scale) ? info.scale : 1.0; + } + label: modelData.name || I18n.tr("common.unknown") + description: { + I18n.tr("system.monitor-description", { + "model": modelData.model, + "width": modelData.width * compositorScale, + "height": modelData.height * compositorScale, + "scale": compositorScale + }); + } + checked: (Settings.data.osd.monitors || []).indexOf(modelData.name) !== -1 + onToggled: checked => { + if (checked) { + Settings.data.osd.monitors = root.addMonitor(Settings.data.osd.monitors, modelData.name); + } else { + Settings.data.osd.monitors = root.removeMonitor(Settings.data.osd.monitors, modelData.name); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Osd/OsdTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Osd/OsdTab.qml new file mode 100644 index 0000000..6e5bc61 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Osd/OsdTab.qml @@ -0,0 +1,72 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + // Helper functions to update arrays immutably + function addMonitor(list, name) { + const arr = (list || []).slice(); + if (!arr.includes(name)) + arr.push(name); + return arr; + } + function removeMonitor(list, name) { + return (list || []).filter(function (n) { + return n !== name; + }); + } + function addType(list, type) { + const arr = (list || []).slice(); + if (!arr.includes(type)) + arr.push(type); + return arr; + } + function removeType(list, type) { + return (list || []).filter(function (t) { + return t !== type; + }); + } + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.general") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.events") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + GeneralSubTab { + addMonitor: root.addMonitor + removeMonitor: root.removeMonitor + } + EventsSubTab { + addType: root.addType + removeType: root.removeType + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Plugins/AvailableSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Plugins/AvailableSubTab.qml new file mode 100644 index 0000000..de00411 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Plugins/AvailableSubTab.qml @@ -0,0 +1,393 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Services.Noctalia +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + property string pluginSearchText: "" + property string selectedTag: "" + property int tagsRefreshCounter: 0 + property int availablePluginsRefreshCounter: 0 + + // Pseudo tags for filtering + readonly property var pseudoTags: ["official", "downloaded", "notDownloaded"] + + readonly property var availableTags: { + // Reference counter to force re-evaluation + void (root.tagsRefreshCounter); + var tags = {}; + var plugins = PluginService.availablePlugins || []; + for (var i = 0; i < plugins.length; i++) { + var pluginTags = plugins[i].tags || []; + for (var j = 0; j < pluginTags.length; j++) { + tags[pluginTags[j]] = true; + } + } + return Object.keys(tags).sort(); + } + + function stripAuthorEmail(author) { + if (!author) + return ""; + var lastBracket = author.lastIndexOf("<"); + if (lastBracket >= 0) { + return author.substring(0, lastBracket).trim(); + } + return author; + } + + // Tag filter chips in collapsible + NTagFilter { + tags: root.pseudoTags.concat(root.availableTags) + selectedTag: root.selectedTag + onSelectedTagChanged: root.selectedTag = selectedTag + label: I18n.tr("panels.plugins.filter-tags-label") + description: I18n.tr("panels.plugins.filter-tags-description") + expanded: true + + formatTag: function (tag) { + if (tag === "") + return I18n.tr("launcher.categories.all"); + if (tag === "official") + return I18n.tr("common.official"); + if (tag === "downloaded") + return I18n.tr("panels.plugins.filter-downloaded"); + if (tag === "notDownloaded") + return I18n.tr("panels.plugins.filter-not-downloaded"); + return tag; + } + } + + // Search input with refresh button + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NTextInput { + placeholderText: I18n.tr("placeholders.search") + inputIconName: "search" + text: root.pluginSearchText + onTextChanged: root.pluginSearchText = text + Layout.fillWidth: true + } + + NIconButton { + icon: "refresh" + tooltipText: I18n.tr("panels.plugins.refresh-tooltip") + baseSize: Style.baseWidgetSize * 0.9 + onClicked: { + PluginService.refreshAvailablePlugins(); + checkUpdatesTimer.restart(); + ToastService.showNotice(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.refresh-refreshing")); + } + } + } + + // Available plugins list + ColumnLayout { + spacing: Style.marginM + Layout.fillWidth: true + + Repeater { + id: availablePluginsRepeater + + model: { + // Reference counter to force re-evaluation when plugins are updated + void (root.availablePluginsRefreshCounter); + + var all = PluginService.availablePlugins || []; + var filtered = []; + + // Apply filter based on selectedTag + for (var i = 0; i < all.length; i++) { + var plugin = all[i]; + var downloaded = plugin.downloaded || false; + var pluginTags = plugin.tags || []; + + if (root.selectedTag === "") { + // "All" - no filter + filtered.push(plugin); + } else if (root.selectedTag === "official") { + // Official (team-maintained) pseudo tag + if (plugin.official === true) + filtered.push(plugin); + } else if (root.selectedTag === "downloaded") { + // Downloaded pseudo tag + if (downloaded) + filtered.push(plugin); + } else if (root.selectedTag === "notDownloaded") { + // Not Downloaded pseudo tag + if (!downloaded) + filtered.push(plugin); + } else { + // Actual category tag + if (pluginTags.indexOf(root.selectedTag) >= 0) { + filtered.push(plugin); + } + } + } + + // Then apply fuzzy search if there's search text + var query = root.pluginSearchText.trim(); + if (query !== "") { + var results = FuzzySort.go(query, filtered, { + "keys": ["name", "description"], + "limit": 50 + }); + filtered = []; + for (var j = 0; j < results.length; j++) { + filtered.push(results[j].obj); + } + } else { + // Sort by lastUpdated (most recent first) when not searching + filtered.sort(function (a, b) { + var dateA = a.lastUpdated ? new Date(a.lastUpdated).getTime() : 0; + var dateB = b.lastUpdated ? new Date(b.lastUpdated).getTime() : 0; + return dateB - dateA; + }); + } + + // Move hello-world plugin to the end + var helloWorldIndex = -1; + for (var h = 0; h < filtered.length; h++) { + if (filtered[h].id === "hello-world") { + helloWorldIndex = h; + break; + } + } + if (helloWorldIndex >= 0) { + var helloWorld = filtered.splice(helloWorldIndex, 1)[0]; + filtered.push(helloWorld); + } + + return filtered; + } + + delegate: NBox { + id: pluginBox + + Layout.fillWidth: true + Layout.leftMargin: Style.borderS + Layout.rightMargin: Style.borderS + implicitHeight: Math.round(contentColumn.implicitHeight + Style.margin2L) + color: Color.mSurface + + ColumnLayout { + id: contentColumn + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginS + + RowLayout { + spacing: Style.marginM + Layout.fillWidth: true + + NIcon { + icon: "plugin" + pointSize: Style.fontSizeL + color: Color.mPrimary + } + + NText { + text: modelData.name + color: Color.mPrimary + elide: Text.ElideRight + } + + // Official badge (Noctalia Team maintained) + Rectangle { + visible: modelData.official === true + color: Color.mSecondary + radius: Style.radiusXS + implicitWidth: officialBadgeRow.implicitWidth + Style.margin2S + implicitHeight: officialBadgeRow.implicitHeight + Style.margin2XS + + RowLayout { + id: officialBadgeRow + anchors.centerIn: parent + spacing: Style.marginXS + + NIcon { + icon: "official-plugin" + pointSize: Style.fontSizeXXS + color: Color.mOnSecondary + } + + NText { + text: I18n.tr("common.official") + font.pointSize: Style.fontSizeXXS + font.weight: Style.fontWeightMedium + color: Color.mOnSecondary + } + } + } + + // Spacer + Item { + Layout.fillWidth: true + } + + // Open plugin page button + NIconButton { + icon: "external-link" + baseSize: Style.baseWidgetSize * 0.7 + tooltipText: I18n.tr("panels.plugins.open-plugin-page") + onClicked: { + var sourceUrl = modelData.source?.url || ""; + Qt.openUrlExternally(sourceUrl && !PluginRegistry.isMainSource(sourceUrl) ? sourceUrl : "https://noctalia.dev/plugins/" + modelData.id + "/"); + } + } + + // Downloaded indicator + NIcon { + icon: "circle-check" + pointSize: Style.baseWidgetSize * 0.5 + color: Color.mPrimary + visible: modelData.downloaded === true + } + + // Install button (only shown when not downloaded and not installing) + NIconButton { + visible: modelData.downloaded === false && !PluginService.installingPlugins[modelData.id] + icon: "download" + baseSize: Style.baseWidgetSize * 0.7 + tooltipText: I18n.tr("common.install") + onClicked: installPlugin(modelData) + } + + // Installing spinner + NBusyIndicator { + visible: !modelData.downloaded && (PluginService.installingPlugins[modelData.id] === true) + size: Style.baseWidgetSize * 0.5 + running: visible + } + } + + // Description + NText { + visible: modelData.description + text: modelData.description || "" + font.pointSize: Style.fontSizeXS + color: Color.mOnSurface + wrapMode: Text.WordWrap + maximumLineCount: 2 + elide: Text.ElideRight + Layout.fillWidth: true + } + + // Details row + RowLayout { + spacing: Style.marginS + Layout.fillWidth: true + + NText { + text: "v" + modelData.version + font.pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + + NText { + text: "โ€ข" + font.pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + + NText { + text: stripAuthorEmail(modelData.author) + font.pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + + NText { + text: "โ€ข" + font.pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + + NText { + text: modelData.source ? modelData.source.name : "" + font.pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + + NText { + visible: !!modelData.lastUpdated + text: "โ€ข" + font.pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + + NText { + visible: !!modelData.lastUpdated + text: modelData.lastUpdated ? Time.formatRelativeTime(new Date(modelData.lastUpdated)) : "" + font.pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + + Item { + Layout.fillWidth: true + } + } + } + } + } + + NLabel { + visible: availablePluginsRepeater.count === 0 + label: I18n.tr("panels.plugins.available-no-plugins-label") + description: I18n.tr("panels.plugins.available-no-plugins-description") + Layout.fillWidth: true + } + } + + // Timer to check for updates after refresh starts + Timer { + id: checkUpdatesTimer + interval: 100 + onTriggered: { + PluginService.checkForUpdates(); + } + } + + function installPlugin(pluginMetadata) { + ToastService.showNotice(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.installing", { + "plugin": pluginMetadata.name + })); + + PluginService.installPlugin(pluginMetadata, false, function (success, error, registeredKey) { + if (success) { + ToastService.showNotice(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.install-success", { + "plugin": pluginMetadata.name + })); + // Auto-enable the plugin after installation (use registered key which may be composite) + PluginService.enablePlugin(registeredKey); + } else { + ToastService.showError(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.install-error", { + "error": error || "Unknown error" + })); + } + }); + } + + // Listen to plugin service signals + Connections { + target: PluginService + + function onAvailablePluginsUpdated() { + // Force tags and plugins model to re-evaluate + root.tagsRefreshCounter++; + root.availablePluginsRefreshCounter++; + + // Manually trigger update check after a small delay to ensure all registries are loaded + Qt.callLater(function () { + PluginService.checkForUpdates(); + }); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Plugins/InstalledSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Plugins/InstalledSubTab.qml new file mode 100644 index 0000000..32de24f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Plugins/InstalledSubTab.qml @@ -0,0 +1,536 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.Noctalia +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + // Track which plugins are currently updating + property var updatingPlugins: ({}) + property int installedPluginsRefreshCounter: 0 + + function stripAuthorEmail(author) { + if (!author) + return ""; + var lastBracket = author.lastIndexOf("<"); + if (lastBracket >= 0) { + return author.substring(0, lastBracket).trim(); + } + return author; + } + + // Check for updates when tab becomes visible + onVisibleChanged: { + if (visible && PluginService.pluginsFullyLoaded) { + PluginService.checkForUpdates(); + } + } + + // Auto-update toggle + NToggle { + label: I18n.tr("panels.plugins.auto-update") + description: I18n.tr("panels.plugins.auto-update-description") + checked: Settings.data.plugins.autoUpdate + onToggled: checked => Settings.data.plugins.autoUpdate = checked + } + + // Update notification toggle + NToggle { + label: I18n.tr("panels.plugins.notify-updates") + description: I18n.tr("panels.plugins.notify-updates-description") + checked: Settings.data.plugins.notifyUpdates + onToggled: checked => Settings.data.plugins.notifyUpdates = checked + } + + // Check for updates button + NButton { + property bool isChecking: Object.keys(PluginService.activeFetches).length > 0 + + text: isChecking ? I18n.tr("panels.plugins.checking-for-updates") : I18n.tr("panels.plugins.check-for-updates") + icon: "refresh" + enabled: !isChecking + visible: Object.keys(PluginService.pluginUpdates).length === 0 + Layout.fillWidth: true + onClicked: PluginService.checkForUpdates() + } + + // Update All button + NButton { + property int updateCount: Object.keys(PluginService.pluginUpdates).length + property bool isUpdating: false + + text: I18n.tr("panels.plugins.update-all", { + "count": updateCount + }) + icon: "download" + visible: (updateCount > 0) + enabled: !isUpdating + backgroundColor: Color.mPrimary + textColor: Color.mOnPrimary + Layout.fillWidth: true + onClicked: { + isUpdating = true; + var pluginIds = Object.keys(PluginService.pluginUpdates); + var currentIndex = 0; + + function updateNext() { + if (currentIndex >= pluginIds.length) { + isUpdating = false; + ToastService.showNotice(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.update-all-success")); + return; + } + + var pluginId = pluginIds[currentIndex]; + currentIndex++; + + PluginService.updatePlugin(pluginId, function (success, error) { + if (!success) { + Logger.w("InstalledSubTab", "Failed to update", pluginId + ":", error); + } + Qt.callLater(updateNext); + }); + } + + updateNext(); + } + } + + // Installed plugins list + ColumnLayout { + spacing: Style.marginM + Layout.fillWidth: true + + Repeater { + id: installedPluginsRepeater + + model: { + // Force refresh when counter changes + var _ = root.installedPluginsRefreshCounter; + + var allIds = PluginRegistry.getAllInstalledPluginIds(); + var plugins = []; + for (var i = 0; i < allIds.length; i++) { + var compositeKey = allIds[i]; + var manifest = PluginRegistry.getPluginManifest(compositeKey); + if (manifest) { + // Create a copy of manifest and include update info, enabled state, and source info + var pluginData = JSON.parse(JSON.stringify(manifest)); + pluginData.compositeKey = compositeKey; + pluginData.updateInfo = PluginService.pluginUpdates[compositeKey]; + pluginData.pendingUpdateInfo = PluginService.pluginUpdatesPending[compositeKey]; + pluginData.enabled = PluginRegistry.isPluginEnabled(compositeKey); + + // Add source info + var parsed = PluginRegistry.parseCompositeKey(compositeKey); + pluginData.isFromOfficialRepo = parsed.isOfficial; + if (!parsed.isOfficial) { + pluginData.sourceName = PluginRegistry.getSourceNameByHash(parsed.sourceHash); + } + + // Look up "official" (team-maintained) status and lastUpdated from available plugins + pluginData.official = false; + pluginData.lastUpdated = null; + var availablePlugins = PluginService.availablePlugins || []; + for (var j = 0; j < availablePlugins.length; j++) { + if (availablePlugins[j].id === manifest.id) { + pluginData.official = availablePlugins[j].official === true; + pluginData.lastUpdated = availablePlugins[j].lastUpdated || null; + break; + } + } + + plugins.push(pluginData); + } + } + return plugins; + } + + delegate: NBox { + Layout.fillWidth: true + Layout.leftMargin: Style.borderS + Layout.rightMargin: Style.borderS + implicitHeight: Math.round(contentColumn.implicitHeight + Style.margin2L) + color: Color.mSurface + + ColumnLayout { + id: contentColumn + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginS + + // Top row: icon, name, badge, spacer, action buttons + RowLayout { + spacing: Style.marginM + Layout.fillWidth: true + + NIcon { + icon: "plugin" + pointSize: Style.fontSizeL + color: PluginService.hasPluginError(modelData.compositeKey) ? Color.mError : Color.mPrimary + } + + NText { + text: modelData.name + color: Color.mPrimary + elide: Text.ElideRight + } + + // Official badge (Noctalia Team maintained) + Rectangle { + visible: modelData.official === true + color: Color.mSecondary + radius: Style.radiusXS + implicitWidth: officialBadgeRow.implicitWidth + Style.margin2S + implicitHeight: officialBadgeRow.implicitHeight + Style.margin2XS + + RowLayout { + id: officialBadgeRow + anchors.centerIn: parent + spacing: Style.marginXS + + NIcon { + icon: "official-plugin" + pointSize: Style.fontSizeXXS + color: Color.mOnSecondary + } + + NText { + text: I18n.tr("common.official") + font.pointSize: Style.fontSizeXXS + font.weight: Style.fontWeightMedium + color: Color.mOnSecondary + } + } + } + + // Spacer + Item { + Layout.fillWidth: true + } + + NIconButtonHot { + icon: "bug" + hot: PluginService.isPluginHotReloadEnabled(modelData.id) + tooltipText: PluginService.isPluginHotReloadEnabled(modelData.id) ? I18n.tr("panels.plugins.development-disable") : I18n.tr("panels.plugins.development-enable") + baseSize: Style.baseWidgetSize * 0.7 + onClicked: PluginService.togglePluginHotReload(modelData.id) + visible: Settings.isDebug + } + + NIconButton { + icon: "settings" + tooltipText: I18n.tr("panels.plugins.settings-tooltip") + baseSize: Style.baseWidgetSize * 0.7 + visible: (modelData.entryPoints?.settings !== undefined) + enabled: modelData.enabled + onClicked: { + pluginSettingsDialog.openPluginSettings(modelData); + } + } + + NIconButton { + icon: "external-link" + tooltipText: I18n.tr("panels.plugins.open-plugin-page") + baseSize: Style.baseWidgetSize * 0.7 + visible: true + onClicked: { + var sourceUrl = PluginRegistry.getPluginSourceUrl(modelData.compositeKey) || ""; + Qt.openUrlExternally(sourceUrl && !PluginRegistry.isMainSource(sourceUrl) ? sourceUrl : "https://noctalia.dev/plugins/" + modelData.id); + } + } + + NIconButton { + icon: "trash" + tooltipText: I18n.tr("common.uninstall") + baseSize: Style.baseWidgetSize * 0.7 + onClicked: { + uninstallDialog.pluginToUninstall = modelData; + uninstallDialog.open(); + } + } + + NButton { + id: updateButton + property string pluginId: modelData.compositeKey + property bool isUpdating: root.updatingPlugins[pluginId] === true + + text: isUpdating ? I18n.tr("panels.plugins.updating") : I18n.tr("common.update") + icon: isUpdating ? "" : "download" + visible: modelData.updateInfo !== undefined + enabled: !isUpdating + backgroundColor: Color.mPrimary + textColor: Color.mOnPrimary + fontSize: Style.fontSizeXXS + fontWeight: Style.fontWeightMedium + onClicked: { + var pid = pluginId; + var pname = modelData.name; + var pversion = modelData.updateInfo?.availableVersion || ""; + var rootRef = root; + var updates = Object.assign({}, rootRef.updatingPlugins); + updates[pid] = true; + rootRef.updatingPlugins = updates; + + PluginService.updatePlugin(pid, function (success, error) { + var updates2 = Object.assign({}, rootRef.updatingPlugins); + updates2[pid] = false; + rootRef.updatingPlugins = updates2; + + if (success) { + ToastService.showNotice(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.update-success", { + "plugin": pname, + "version": pversion + })); + } else { + ToastService.showError(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.update-error", { + "plugin": pname, + "error": error || "Unknown error" + })); + } + }); + } + } + + NToggle { + checked: modelData.enabled + baseSize: Style.baseWidgetSize * 0.7 + onToggled: checked => { + if (checked) { + PluginService.enablePlugin(modelData.compositeKey); + } else { + PluginService.disablePlugin(modelData.compositeKey); + } + } + } + } + + // Description + NText { + visible: modelData.description + text: modelData.description || "" + font.pointSize: Style.fontSizeXS + color: Color.mOnSurface + wrapMode: Text.WordWrap + maximumLineCount: 2 + elide: Text.ElideRight + Layout.fillWidth: true + } + + // Details row + RowLayout { + spacing: Style.marginS + Layout.fillWidth: true + + NText { + text: { + if (modelData.updateInfo) { + return I18n.tr("panels.plugins.update-version", { + "current": modelData.version, + "new": modelData.updateInfo.availableVersion + }); + } else if (modelData.pendingUpdateInfo) { + return I18n.tr("panels.plugins.update-pending", { + "current": modelData.version, + "new": modelData.pendingUpdateInfo.availableVersion, + "required": modelData.pendingUpdateInfo.minNoctaliaVersion + }); + } + return "v" + modelData.version; + } + font.pointSize: Style.fontSizeXS + color: modelData.updateInfo ? Color.mPrimary : (modelData.pendingUpdateInfo ? Color.mTertiary : Color.mOnSurfaceVariant) + font.weight: (modelData.updateInfo || modelData.pendingUpdateInfo) ? Style.fontWeightMedium : Style.fontWeightRegular + } + + NText { + text: "โ€ข" + font.pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + + NText { + text: stripAuthorEmail(modelData.author) + font.pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + + // Source indicator for plugins from non-official repos + NText { + visible: !modelData.isFromOfficialRepo + text: "โ€ข" + font.pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + + NText { + visible: !modelData.isFromOfficialRepo + text: modelData.sourceName || I18n.tr("panels.plugins.source-custom") + font.pointSize: Style.fontSizeXS + color: Color.mTertiary + } + + NText { + visible: !!modelData.lastUpdated + text: "โ€ข" + font.pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + + NText { + visible: !!modelData.lastUpdated + text: modelData.lastUpdated ? Time.formatRelativeTime(new Date(modelData.lastUpdated)) : "" + font.pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + + Item { + Layout.fillWidth: true + } + } + + // Error indicator + RowLayout { + spacing: Style.marginS + visible: PluginService.hasPluginError(modelData.compositeKey) + + NIcon { + icon: "alert-triangle" + pointSize: Style.fontSizeS + color: Color.mError + } + + NText { + property var errorInfo: PluginService.getPluginError(modelData.compositeKey) + text: errorInfo ? errorInfo.error : "" + font.pointSize: Style.fontSizeXXS + color: Color.mError + wrapMode: Text.WordWrap + Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 3 + } + } + } + } + } + + NLabel { + visible: PluginRegistry.getAllInstalledPluginIds().length === 0 + label: I18n.tr("panels.plugins.installed-no-plugins-label") + description: I18n.tr("panels.plugins.installed-no-plugins-description") + Layout.fillWidth: true + } + } + + // Uninstall confirmation dialog + Popup { + id: uninstallDialog + parent: Overlay.overlay + modal: true + dim: false + anchors.centerIn: parent + width: 400 * Style.uiScaleRatio + padding: Style.marginL + + property var pluginToUninstall: null + + background: Rectangle { + color: Color.mSurface + radius: Style.radiusS + border.color: Color.mPrimary + border.width: Style.borderM + } + + contentItem: ColumnLayout { + width: parent.width + spacing: Style.marginL + + NHeader { + label: I18n.tr("panels.plugins.uninstall-dialog-title") + description: I18n.tr("panels.plugins.uninstall-dialog-description", { + "plugin": uninstallDialog.pluginToUninstall?.name || "" + }) + } + + RowLayout { + spacing: Style.marginM + Layout.fillWidth: true + + Item { + Layout.fillWidth: true + } + + NButton { + text: I18n.tr("common.cancel") + onClicked: uninstallDialog.close() + } + + NButton { + text: I18n.tr("common.uninstall") + backgroundColor: Color.mPrimary + textColor: Color.mOnPrimary + onClicked: { + if (uninstallDialog.pluginToUninstall) { + root.uninstallPlugin(uninstallDialog.pluginToUninstall.compositeKey); + uninstallDialog.close(); + } + } + } + } + } + } + + // Plugin settings popup + NPluginSettingsPopup { + id: pluginSettingsDialog + parent: Overlay.overlay + showToastOnSave: true + } + + function uninstallPlugin(pluginId) { + var manifest = PluginRegistry.getPluginManifest(pluginId); + var pluginName = manifest?.name || pluginId; + + BarService.widgetsRevision++; + + ToastService.showNotice(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.uninstalling", { + "plugin": pluginName + })); + + PluginService.uninstallPlugin(pluginId, function (success, error) { + if (success) { + ToastService.showNotice(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.uninstall-success", { + "plugin": pluginName + })); + } else { + ToastService.showError(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.uninstall-error", { + "error": error || "Unknown error" + })); + } + }); + } + + // Listen to plugin registry changes + Connections { + target: PluginRegistry + + function onPluginsChanged() { + root.installedPluginsRefreshCounter++; + } + } + + // Listen to plugin service signals + Connections { + target: PluginService + + function onPluginUpdatesChanged() { + root.installedPluginsRefreshCounter++; + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Plugins/PluginsTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Plugins/PluginsTab.qml new file mode 100644 index 0000000..aaddc90 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Plugins/PluginsTab.qml @@ -0,0 +1,48 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.installed") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.available") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + NTabButton { + text: I18n.tr("common.sources") + tabIndex: 2 + checked: subTabBar.currentIndex === 2 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + InstalledSubTab {} + AvailableSubTab {} + SourcesSubTab {} + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Plugins/SourcesSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Plugins/SourcesSubTab.qml new file mode 100644 index 0000000..5adeba9 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Plugins/SourcesSubTab.qml @@ -0,0 +1,181 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.Noctalia +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + // List of plugin sources + ColumnLayout { + spacing: Style.marginM + Layout.fillWidth: true + + Repeater { + id: pluginSourcesRepeater + model: PluginRegistry.pluginSources || [] + + delegate: NBox { + Layout.fillWidth: true + implicitHeight: sourceRow.implicitHeight + Style.margin2L + color: Color.mSurface + + RowLayout { + id: sourceRow + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + NIcon { + icon: "brand-github" + pointSize: Style.fontSizeL + } + + ColumnLayout { + spacing: 2 + Layout.fillWidth: true + + NText { + text: modelData.name + color: Color.mOnSurface + Layout.fillWidth: true + } + + NText { + text: modelData.url + font.pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + Layout.fillWidth: true + elide: Text.ElideRight + } + } + + NIconButton { + icon: "trash" + tooltipText: I18n.tr("panels.plugins.sources-remove-tooltip") + visible: index !== 0 // Cannot remove official source + baseSize: Style.baseWidgetSize * 0.7 + onClicked: { + PluginRegistry.removePluginSource(modelData.url); + } + } + + // Enable/Disable a source + NToggle { + checked: modelData.enabled !== false // Default to true if not set + baseSize: Style.baseWidgetSize * 0.7 + onToggled: checked => { + PluginRegistry.setSourceEnabled(modelData.url, checked); + PluginService.refreshAvailablePlugins(); + ToastService.showNotice(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.refresh-refreshing")); + } + } + } + } + } + } + + // Add custom repository + NButton { + text: I18n.tr("panels.plugins.sources-add-custom") + icon: "plus" + onClicked: { + addSourceDialog.open(); + } + Layout.fillWidth: true + } + + // Add source dialog + Popup { + id: addSourceDialog + parent: Overlay.overlay + modal: true + dim: false + anchors.centerIn: parent + width: 500 + padding: Style.marginL + + background: Rectangle { + color: Color.mSurface + radius: Style.radiusS + border.color: Color.mPrimary + border.width: Style.borderM + } + + contentItem: ColumnLayout { + width: parent.width + spacing: Style.marginL + + NHeader { + label: I18n.tr("panels.plugins.sources-add-dialog-title") + description: I18n.tr("panels.plugins.sources-add-dialog-description") + } + + NTextInput { + id: sourceNameInput + label: I18n.tr("panels.plugins.sources-add-dialog-name") + placeholderText: I18n.tr("panels.plugins.sources-add-dialog-name-placeholder") + Layout.fillWidth: true + } + + NTextInput { + id: sourceUrlInput + label: I18n.tr("panels.plugins.sources-add-dialog-url") + placeholderText: "https://github.com/user/repo" + Layout.fillWidth: true + } + + RowLayout { + spacing: Style.marginM + Layout.fillWidth: true + + Item { + Layout.fillWidth: true + } + + NButton { + text: I18n.tr("common.cancel") + onClicked: addSourceDialog.close() + } + + NButton { + text: I18n.tr("common.add") + backgroundColor: Color.mPrimary + textColor: Color.mOnPrimary + enabled: sourceNameInput.text.length > 0 && sourceUrlInput.text.length > 0 + onClicked: { + if (PluginRegistry.addPluginSource(sourceNameInput.text, sourceUrlInput.text)) { + ToastService.showNotice(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.sources-add-dialog-success")); + PluginService.refreshAvailablePlugins(); + addSourceDialog.close(); + sourceNameInput.text = ""; + sourceUrlInput.text = ""; + } else { + ToastService.showError(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.sources-add-dialog-error")); + } + } + } + } + } + } + + // Listen to plugin registry changes + Connections { + target: PluginRegistry + + function onPluginsChanged() { + // Force model refresh for plugin sources + pluginSourcesRepeater.model = undefined; + Qt.callLater(function () { + pluginSourcesRepeater.model = Qt.binding(function () { + return PluginRegistry.pluginSources || []; + }); + }); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Region/ClockPanelSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Region/ClockPanelSubTab.qml new file mode 100644 index 0000000..4b7e943 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Region/ClockPanelSubTab.qml @@ -0,0 +1,164 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + property list cardsModel: [] + property list cardsDefault: [ + { + "id": "calendar-header-card", + "text": I18n.tr("panels.location.calendar-header-label"), + "enabled": true, + "required": true + }, + { + "id": "calendar-month-card", + "text": I18n.tr("panels.location.calendar-month-label"), + "enabled": true, + "required": false + }, + { + "id": "weather-card", + "text": I18n.tr("common.weather"), + "enabled": true, + "required": false + } + ] + + function saveCards() { + var toSave = []; + for (var i = 0; i < cardsModel.length; i++) { + toSave.push({ + "id": cardsModel[i].id, + "enabled": cardsModel[i].enabled + }); + } + Settings.data.calendar.cards = toSave; + } + + Component.onCompleted: { + // Starts empty + cardsModel = []; + + // Add the cards available in settings + for (var i = 0; i < Settings.data.calendar.cards.length; i++) { + const settingCard = Settings.data.calendar.cards[i]; + + for (var j = 0; j < cardsDefault.length; j++) { + if (settingCard.id === cardsDefault[j].id) { + var card = cardsDefault[j]; + card.enabled = settingCard.enabled; + // Auto-disable weather card if weather is disabled + if (card.id === "weather-card" && !Settings.data.location.weatherEnabled) { + card.enabled = false; + } + cardsModel.push(card); + } + } + } + + // Add any missing cards from default + for (var i = 0; i < cardsDefault.length; i++) { + var found = false; + for (var j = 0; j < cardsModel.length; j++) { + if (cardsModel[j].id === cardsDefault[i].id) { + found = true; + break; + } + } + + if (!found) { + var card = cardsDefault[i]; + // Auto-disable weather card if weather is disabled + if (card.id === "weather-card" && !Settings.data.location.weatherEnabled) { + card.enabled = false; + } + cardsModel.push(card); + } + } + + saveCards(); + } + + // Clock style section + ColumnLayout { + spacing: Style.marginM + Layout.fillWidth: true + + NToggle { + label: I18n.tr("panels.location.date-time-use-analog-label") + description: I18n.tr("panels.location.date-time-use-analog-description") + checked: Settings.data.location.analogClockInCalendar + onToggled: checked => Settings.data.location.analogClockInCalendar = checked + } + + NToggle { + label: I18n.tr("panels.location.date-time-week-numbers-label") + description: I18n.tr("panels.location.date-time-week-numbers-description") + checked: Settings.data.location.showWeekNumberInCalendar + onToggled: checked => Settings.data.location.showWeekNumberInCalendar = checked + } + + NToggle { + label: I18n.tr("panels.location.date-time-show-events-label") + description: I18n.tr("panels.location.date-time-show-events-description") + checked: Settings.data.location.showCalendarEvents + onToggled: checked => Settings.data.location.showCalendarEvents = checked + } + } + + NDivider { + Layout.fillWidth: true + } + + // Calendar Cards Management Section + ColumnLayout { + spacing: Style.marginXXS + Layout.fillWidth: true + + Connections { + target: Settings.data.location + function onWeatherEnabledChanged() { + // Auto-disable weather card when weather is disabled + var newModel = cardsModel.slice(); + for (var i = 0; i < newModel.length; i++) { + if (newModel[i].id === "weather-card") { + newModel[i] = Object.assign({}, newModel[i], { + "enabled": Settings.data.location.weatherEnabled + }); + cardsModel = newModel; + saveCards(); + break; + } + } + } + } + + NReorderCheckboxes { + Layout.fillWidth: true + model: cardsModel + disabledIds: Settings.data.location.weatherEnabled ? [] : ["weather-card"] + onItemToggled: function (index, enabled) { + var newModel = cardsModel.slice(); + newModel[index] = Object.assign({}, newModel[index], { + "enabled": enabled + }); + cardsModel = newModel; + saveCards(); + } + onItemsReordered: function (fromIndex, toIndex) { + var newModel = cardsModel.slice(); + var item = newModel.splice(fromIndex, 1)[0]; + newModel.splice(toIndex, 0, item); + cardsModel = newModel; + saveCards(); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Region/DateSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Region/DateSubTab.qml new file mode 100644 index 0000000..82a965b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Region/DateSubTab.qml @@ -0,0 +1,46 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + NToggle { + label: I18n.tr("panels.location.date-time-12hour-format-label") + description: I18n.tr("panels.location.date-time-12hour-format-description") + checked: Settings.data.location.use12hourFormat + onToggled: checked => Settings.data.location.use12hourFormat = checked + } + + NComboBox { + label: I18n.tr("panels.location.date-time-first-day-of-week-label") + description: I18n.tr("panels.location.date-time-first-day-of-week-description") + currentKey: Settings.data.location.firstDayOfWeek.toString() + minimumWidth: 260 * Style.uiScaleRatio + model: [ + { + "key": "-1", + "name": I18n.tr("panels.location.date-time-first-day-of-week-automatic") + }, + { + "key": "6", + "name": I18n.locale.dayName(6, Locale.LongFormat).trim() + } // Saturday + , + { + "key": "0", + "name": I18n.locale.dayName(0, Locale.LongFormat).trim() + } // Sunday + , + { + "key": "1", + "name": I18n.locale.dayName(1, Locale.LongFormat).trim() + } // Monday + ] + onSelected: key => Settings.data.location.firstDayOfWeek = parseInt(key) + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Region/LocationSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Region/LocationSubTab.qml new file mode 100644 index 0000000..7e7c099 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Region/LocationSubTab.qml @@ -0,0 +1,157 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.Location +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + // Language + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.general.language-select-label") + description: I18n.tr("panels.general.language-select-description") + defaultValue: Settings.getDefaultValue("general.language") + model: [ + { + "key": "", + "name": I18n.tr("panels.general.language-select-auto-detect") + " (" + I18n.systemDetectedLangCode + ")" + } + ].concat(I18n.availableLanguages.map(function (langCode) { + return { + "key": langCode, + "name": langCode + }; + })) + currentKey: Settings.data.general.language + settingsPath: "general.language" + onSelected: key => { + // Need to change language on next frame using "callLater" or it will pull the rug below our feet: the NComboBox would be rebuilt immediately before it can close properly. + Qt.callLater(() => { + Settings.data.general.language = key; + if (key === "") { + I18n.detectLanguage(); // Re-detect system language if "Automatic" is selected + } else { + I18n.setLanguage(key); // Set specific language + } + }); + } + } + + NDivider { + Layout.fillWidth: true + } + + // Location + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS + + // Auto-locate + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.location.auto-locate-label") + description: I18n.tr("panels.location.auto-locate-description") + checked: Settings.data.location.autoLocate + onToggled: checked => Settings.data.location.autoLocate = checked + defaultValue: Settings.getDefaultValue("location.autoLocate") + } + + NButton { + text: I18n.tr("panels.location.geolocate-now-button") + icon: "current-location" + enabled: !LocationService.isFetchingWeather + onClicked: LocationService.geolocateAndApply() + } + } + + NTextInput { + visible: !Settings.data.location.autoLocate + Layout.maximumWidth: root.width / 2 + label: I18n.tr("panels.location.location-search-label") + description: I18n.tr("panels.location.location-search-description") + text: Settings.data.location.name + placeholderText: I18n.tr("panels.location.location-search-placeholder") + onEditingFinished: { + // Verify the location has really changed to avoid extra resets + var newLocation = text.trim(); + if (newLocation != Settings.data.location.name) { + Settings.data.location.name = newLocation; + LocationService.resetWeather(); + } + } + } + + NText { + text: LocationService.coordinatesReady ? I18n.tr("system.location-display", { + "name": LocationService.stableName, + "coordinates": LocationService.displayCoordinates + }) : "" + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + font.italic: true + } + } + + ColumnLayout { + spacing: Style.marginL + Layout.fillWidth: true + + NToggle { + label: I18n.tr("panels.location.weather-enabled-label") + description: I18n.tr("panels.location.weather-enabled-description") + checked: Settings.data.location.weatherEnabled + onToggled: checked => Settings.data.location.weatherEnabled = checked + defaultValue: Settings.getDefaultValue("location.weatherEnabled") + } + + NToggle { + label: I18n.tr("panels.location.weather-fahrenheit-label") + description: I18n.tr("panels.location.weather-fahrenheit-description") + checked: Settings.data.location.useFahrenheit + onToggled: checked => Settings.data.location.useFahrenheit = checked + enabled: Settings.data.location.weatherEnabled + } + + NToggle { + label: I18n.tr("panels.location.weather-show-effects-label") + description: I18n.tr("panels.location.weather-show-effects-description") + checked: Settings.data.location.weatherShowEffects + onToggled: checked => Settings.data.location.weatherShowEffects = checked + enabled: Settings.data.location.weatherEnabled + } + + NToggle { + label: I18n.tr("panels.location.weather-talia-mascot-always-label") + description: I18n.tr("panels.location.weather-talia-mascot-always-description") + checked: Settings.data.location.weatherTaliaMascotAlways + onToggled: checked => Settings.data.location.weatherTaliaMascotAlways = checked + enabled: Settings.data.location.weatherEnabled + defaultValue: Settings.getDefaultValue("location.weatherTaliaMascotAlways") + } + + NToggle { + label: I18n.tr("panels.location.weather-hide-city-label") + description: I18n.tr("panels.location.weather-hide-city-description") + checked: Settings.data.location.hideWeatherCityName + onToggled: checked => Settings.data.location.hideWeatherCityName = checked + enabled: Settings.data.location.weatherEnabled + } + + NToggle { + label: I18n.tr("panels.location.weather-hide-timezone-label") + description: I18n.tr("panels.location.weather-hide-timezone-description") + checked: Settings.data.location.hideWeatherTimezone + onToggled: checked => Settings.data.location.hideWeatherTimezone = checked + enabled: Settings.data.location.weatherEnabled + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Region/RegionTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Region/RegionTab.qml new file mode 100644 index 0000000..b56a9ef --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Region/RegionTab.qml @@ -0,0 +1,48 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.location") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.date") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + NTabButton { + text: I18n.tr("common.calendar-panel") + tabIndex: 2 + checked: subTabBar.currentIndex === 2 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + LocationSubTab {} + DateSubTab {} + ClockPanelSubTab {} + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SessionMenu/ActionsSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SessionMenu/ActionsSubTab.qml new file mode 100644 index 0000000..0318b6b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SessionMenu/ActionsSubTab.qml @@ -0,0 +1,252 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + property var entriesModel: [] + property var updateEntry + property var reorderEntries + property var openEntrySettingsDialog + + // List of items + Item { + Layout.fillWidth: true + implicitHeight: listView.contentHeight + + NListView { + id: listView + anchors.fill: parent + spacing: Style.marginS + interactive: false + reserveScrollbarSpace: false + model: root.entriesModel + + delegate: Item { + id: delegateItem + width: listView.availableWidth + height: contentRow.height + + required property int index + required property var modelData + + property bool dragging: false + property int dragStartY: 0 + property int dragStartIndex: -1 + property int dragTargetIndex: -1 + + Rectangle { + anchors.fill: parent + radius: Style.radiusM + color: delegateItem.dragging ? Color.mSurfaceVariant : "transparent" + border.color: delegateItem.dragging ? Color.mOutline : "transparent" + border.width: Style.borderS + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + RowLayout { + id: contentRow + width: parent.width + spacing: Style.marginS + + // Drag handle + Rectangle { + Layout.preferredWidth: Style.baseWidgetSize * 0.7 + Layout.preferredHeight: Style.baseWidgetSize * 0.7 + Layout.alignment: Qt.AlignVCenter + radius: Style.radiusXS + color: dragHandleMouseArea.containsMouse ? Color.mSurfaceVariant : "transparent" + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + ColumnLayout { + anchors.centerIn: parent + spacing: 2 + + Repeater { + model: 3 + Rectangle { + Layout.preferredWidth: Style.baseWidgetSize * 0.28 + Layout.preferredHeight: 2 + radius: 1 + color: Color.mOutline + } + } + } + + MouseArea { + id: dragHandleMouseArea + anchors.fill: parent + cursorShape: Qt.SizeVerCursor + hoverEnabled: true + preventStealing: false + z: 1000 + + onPressed: mouse => { + delegateItem.dragStartIndex = delegateItem.index; + delegateItem.dragTargetIndex = delegateItem.index; + delegateItem.dragStartY = delegateItem.y; + delegateItem.dragging = true; + delegateItem.z = 999; + preventStealing = true; + } + + onPositionChanged: mouse => { + if (delegateItem.dragging) { + var dy = mouse.y - height / 2; + var newY = delegateItem.y + dy; + newY = Math.max(0, Math.min(newY, listView.contentHeight - delegateItem.height)); + delegateItem.y = newY; + var targetIndex = Math.floor((newY + delegateItem.height / 2) / (delegateItem.height + Style.marginS)); + targetIndex = Math.max(0, Math.min(targetIndex, listView.count - 1)); + delegateItem.dragTargetIndex = targetIndex; + } + } + + onReleased: { + preventStealing = false; + if (delegateItem.dragStartIndex !== -1 && delegateItem.dragTargetIndex !== -1 && delegateItem.dragStartIndex !== delegateItem.dragTargetIndex) { + root.reorderEntries(delegateItem.dragStartIndex, delegateItem.dragTargetIndex); + } + delegateItem.dragging = false; + delegateItem.dragStartIndex = -1; + delegateItem.dragTargetIndex = -1; + delegateItem.z = 0; + } + + onCanceled: { + preventStealing = false; + delegateItem.dragging = false; + delegateItem.dragStartIndex = -1; + delegateItem.dragTargetIndex = -1; + delegateItem.z = 0; + } + } + } + + // Enable checkbox + Rectangle { + Layout.preferredWidth: Style.baseWidgetSize * 0.7 + Layout.preferredHeight: Style.baseWidgetSize * 0.7 + Layout.alignment: Qt.AlignVCenter + radius: Style.radiusXS + color: modelData.enabled ? Color.mPrimary : Color.mSurface + border.color: Color.mOutline + border.width: Style.borderS + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + NIcon { + visible: modelData.enabled + anchors.centerIn: parent + anchors.horizontalCenterOffset: -1 + icon: "check" + color: Color.mOnPrimary + pointSize: Math.max(Style.fontSizeXS, Style.baseWidgetSize * 0.35) + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + root.updateEntry(index, { + "enabled": !modelData.enabled + }); + } + } + } + + // Label + NText { + Layout.fillWidth: true + text: modelData.text + color: Color.mOnSurface + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + // Countdown toggle (only shown when global countdown is enabled) + NIconButtonHot { + visible: Settings.data.sessionMenu.enableCountdown + icon: "clock" + hot: modelData.countdownEnabled !== undefined ? modelData.countdownEnabled : true + baseSize: Style.baseWidgetSize * 0.8 + Layout.alignment: Qt.AlignVCenter + tooltipText: I18n.tr("common.countdown") + onClicked: root.updateEntry(delegateItem.index, { + "countdownEnabled": !(modelData.countdownEnabled !== undefined ? modelData.countdownEnabled : true) + }) + } + + // Settings button (cogwheel) + NIconButton { + icon: "settings" + tooltipText: I18n.tr("panels.session-menu.entry-settings-tooltip") + baseSize: Style.baseWidgetSize * 0.8 + Layout.alignment: Qt.AlignVCenter + onClicked: root.openEntrySettingsDialog(delegateItem.index) + } + } + + // Position binding for non-dragging state + y: { + if (delegateItem.dragging) { + return delegateItem.y; + } + + var draggedIndex = -1; + var targetIndex = -1; + for (var i = 0; i < listView.count; i++) { + var item = listView.itemAtIndex(i); + if (item && item.dragging) { + draggedIndex = item.dragStartIndex; + targetIndex = item.dragTargetIndex; + break; + } + } + + if (draggedIndex !== -1 && targetIndex !== -1 && draggedIndex !== targetIndex) { + var currentIndex = delegateItem.index; + if (draggedIndex < targetIndex) { + if (currentIndex > draggedIndex && currentIndex <= targetIndex) { + return (currentIndex - 1) * (delegateItem.height + Style.marginS); + } + } else { + if (currentIndex >= targetIndex && currentIndex < draggedIndex) { + return (currentIndex + 1) * (delegateItem.height + Style.marginS); + } + } + } + + return delegateItem.index * (delegateItem.height + Style.marginS); + } + + Behavior on y { + enabled: !delegateItem.dragging + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutQuad + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SessionMenu/GeneralSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SessionMenu/GeneralSubTab.qml new file mode 100644 index 0000000..3d52085 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SessionMenu/GeneralSubTab.qml @@ -0,0 +1,122 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.session-menu.large-buttons-style-label") + description: I18n.tr("panels.session-menu.large-buttons-style-description") + checked: Settings.data.sessionMenu.largeButtonsStyle + onToggled: checked => Settings.data.sessionMenu.largeButtonsStyle = checked + } + + NComboBox { + visible: Settings.data.sessionMenu.largeButtonsStyle + Layout.fillWidth: true + label: I18n.tr("panels.session-menu.large-buttons-layout-label") + description: I18n.tr("panels.session-menu.large-buttons-layout-description") + model: [ + { + "key": "grid", + "name": I18n.tr("options.session-menu-grid-layout.grid") + }, + { + "key": "single-row", + "name": I18n.tr("options.session-menu-grid-layout.single-row") + } + ] + currentKey: Settings.data.sessionMenu.largeButtonsLayout + defaultValue: Settings.getDefaultValue("sessionMenu.largeButtonsLayout") + onSelected: key => Settings.data.sessionMenu.largeButtonsLayout = key + } + + NComboBox { + label: I18n.tr("common.position") + description: I18n.tr("panels.session-menu.position-description") + Layout.fillWidth: true + model: [ + { + "key": "center", + "name": I18n.tr("positions.center") + }, + { + "key": "top_center", + "name": I18n.tr("positions.top-center") + }, + { + "key": "top_left", + "name": I18n.tr("positions.top-left") + }, + { + "key": "top_right", + "name": I18n.tr("positions.top-right") + }, + { + "key": "bottom_center", + "name": I18n.tr("positions.bottom-center") + }, + { + "key": "bottom_left", + "name": I18n.tr("positions.bottom-left") + }, + { + "key": "bottom_right", + "name": I18n.tr("positions.bottom-right") + } + ] + currentKey: Settings.data.sessionMenu.position + onSelected: key => Settings.data.sessionMenu.position = key + visible: !Settings.data.sessionMenu.largeButtonsStyle + defaultValue: Settings.getDefaultValue("sessionMenu.position") + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.session-menu.show-header-label") + description: I18n.tr("panels.session-menu.show-header-description") + checked: Settings.data.sessionMenu.showHeader + onToggled: checked => Settings.data.sessionMenu.showHeader = checked + visible: !Settings.data.sessionMenu.largeButtonsStyle + defaultValue: Settings.getDefaultValue("sessionMenu.showHeader") + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.session-menu.show-keybinds-label") + description: I18n.tr("panels.session-menu.show-keybinds-description") + checked: Settings.data.sessionMenu.showKeybinds + onToggled: checked => Settings.data.sessionMenu.showKeybinds = checked + defaultValue: Settings.getDefaultValue("sessionMenu.showKeybinds") + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.session-menu.enable-countdown-label") + description: I18n.tr("panels.session-menu.enable-countdown-description") + checked: Settings.data.sessionMenu.enableCountdown + onToggled: checked => Settings.data.sessionMenu.enableCountdown = checked + defaultValue: Settings.getDefaultValue("sessionMenu.enableCountdown") + } + + NValueSlider { + visible: Settings.data.sessionMenu.enableCountdown + Layout.fillWidth: true + label: I18n.tr("panels.session-menu.countdown-duration-label") + description: I18n.tr("panels.session-menu.countdown-duration-description") + from: 1000 + to: 30000 + stepSize: 1000 + showReset: true + value: Settings.data.sessionMenu.countdownDuration + onMoved: value => Settings.data.sessionMenu.countdownDuration = value + text: Math.round(Settings.data.sessionMenu.countdownDuration / 1000) + "s" + defaultValue: Settings.getDefaultValue("sessionMenu.countdownDuration") + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SessionMenu/SessionMenuEntrySettingsDialog.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SessionMenu/SessionMenuEntrySettingsDialog.qml new file mode 100644 index 0000000..3e4310a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SessionMenu/SessionMenuEntrySettingsDialog.qml @@ -0,0 +1,186 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +// Session Menu Entry Settings Dialog Component +Popup { + id: root + + property int entryIndex: -1 + property var entryData: null + property string entryId: "" + property string entryText: "" + property string keybindInputText: "" + + signal updateEntryProperties(int index, var properties) + + // Default commands mapping + readonly property var defaultCommands: { + "lock": I18n.tr("panels.session-menu.entry-settings-default-command-lock"), + "suspend": "systemctl suspend || loginctl suspend", + "hibernate": "systemctl hibernate || loginctl hibernate", + "reboot": "systemctl reboot || loginctl reboot", + "rebootToUefi": "systemctl reboot --firmware-setup || loginctl reboot --firmware-setup", + "logout": I18n.tr("panels.session-menu.entry-settings-default-command-logout"), + "shutdown": "systemctl poweroff || loginctl poweroff", + "userspaceReboot": "systemctl soft-reboot" + } + + readonly property string defaultCommand: defaultCommands[entryId] || "" + + width: Math.max(content.implicitWidth + padding * 2, 500) + height: content.implicitHeight + padding * 2 + padding: Style.marginXL + modal: true + closePolicy: Popup.NoAutoClose + dim: false + anchors.centerIn: parent + + onOpened: { + // Load command when popup opens + if (entryData) { + commandInput.text = entryData.command || ""; + keybindInputText = entryData.keybind || ""; + } + // Request focus to ensure keyboard input works + forceActiveFocus(); + } + + function save() { + root.updateEntryProperties(root.entryIndex, { + "command": commandInput.text, + "keybind": keybindInputText + }); + } + + background: Rectangle { + id: bgRect + + color: Color.mSurface + radius: Style.radiusL + border.color: Color.mPrimary + border.width: Style.borderM + } + + contentItem: FocusScope { + id: focusScope + focus: true + + ColumnLayout { + id: content + anchors.fill: parent + spacing: Style.marginM + + // Title + RowLayout { + Layout.fillWidth: true + + NText { + text: I18n.tr("panels.session-menu.entry-settings-title", { + "entry": root.entryText + }) + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mPrimary + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + onClicked: { + root.save(); + root.close(); + } + } + } + + // Separator + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + } + + // Command input + NTextInput { + id: commandInput + Layout.fillWidth: true + label: I18n.tr("common.command") + description: I18n.tr("panels.session-menu.entry-settings-command-description") + placeholderText: I18n.tr("panels.session-menu.entry-settings-command-placeholder") + onTextChanged: root.save() + } + + // Default command info + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NLabel { + label: I18n.tr("panels.session-menu.entry-settings-default-info-label") + description: I18n.tr("panels.session-menu.entry-settings-default-info-description") + Layout.fillWidth: true + } + + // Default command display + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: defaultCommandText.implicitHeight + Style.margin2M + radius: Style.radiusM + color: Color.mSurfaceVariant + border.color: Color.mOutline + border.width: Style.borderS + + RowLayout { + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginS + + NIcon { + icon: "info" + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeM + } + + NText { + id: defaultCommandText + Layout.fillWidth: true + text: root.defaultCommand + color: Color.mOnSurfaceVariant + font.family: "monospace" + font.pointSize: Style.fontSizeS + wrapMode: Text.Wrap + } + } + } + } + + NKeybindRecorder { + id: keybindRecorder + Layout.fillWidth: true + label: I18n.tr("common.keybind") + description: I18n.tr("placeholders.keybind-recording") + allowEmpty: true + maxKeybinds: 1 + requireModifierForNormalKeys: false + currentKeybinds: keybindInputText ? [keybindInputText] : [] + settingsPath: "sessionMenu.powerOptions[" + root.entryIndex + "].keybind" + onKeybindsChanged: newKeybinds => { + keybindInputText = newKeybinds.length > 0 ? newKeybinds[0] : ""; + root.save(); + } + } + + // Hidden property to store the text since NKeybindRecorder manages its own state + // but we need to initialize it and read from it + + // Bottom spacer to maintain padding + Item { + Layout.preferredHeight: Style.marginS + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SessionMenu/SessionMenuTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SessionMenu/SessionMenuTab.qml new file mode 100644 index 0000000..89375a7 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SessionMenu/SessionMenuTab.qml @@ -0,0 +1,266 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + property var _activeDialog: null + property list entriesModel: [] + property list entriesDefault: [ + { + "id": "lock", + "text": I18n.tr("common.lock"), + "enabled": true, + "required": false + }, + { + "id": "suspend", + "text": I18n.tr("common.suspend"), + "enabled": true, + "required": false + }, + { + "id": "hibernate", + "text": I18n.tr("common.hibernate"), + "enabled": true, + "required": false + }, + { + "id": "reboot", + "text": I18n.tr("common.reboot"), + "enabled": true, + "required": false + }, + { + "id": "userspaceReboot", + "text": I18n.tr("common.userspace-reboot"), + "enabled": false, + "required": false + }, + { + "id": "rebootToUefi", + "text": I18n.tr("common.reboot-to-uefi"), + "enabled": true, + "required": false + }, + { + "id": "logout", + "text": I18n.tr("common.logout"), + "enabled": true, + "required": false + }, + { + "id": "shutdown", + "text": I18n.tr("common.shutdown"), + "enabled": true, + "required": false + } + ] + + function saveEntries() { + var toSave = []; + var enabledNumber = 1; + + for (var i = 0; i < entriesModel.length; i++) { + var keybind = entriesModel[i].keybind || ""; + + if (entriesModel[i].enabled) { + // For enabled entries with numeric or empty keybinds, assign sequential number + if (keybind === "" || /^\d+$/.test(keybind)) { + keybind = String(enabledNumber); + } + enabledNumber++; + } else { + // For disabled entries with numeric keybinds, clear them + if (/^\d+$/.test(keybind)) { + keybind = ""; + } + } + + toSave.push({ + "action": entriesModel[i].id, + "enabled": entriesModel[i].enabled, + "countdownEnabled": entriesModel[i].countdownEnabled !== undefined ? entriesModel[i].countdownEnabled : true, + "command": entriesModel[i].command || "", + "keybind": keybind + }); + } + + // Update local model with renumbered keybinds + var newModel = []; + for (var i = 0; i < entriesModel.length; i++) { + newModel.push(Object.assign({}, entriesModel[i], { + "keybind": toSave[i].keybind + })); + } + entriesModel = newModel; + + Settings.data.sessionMenu.powerOptions = toSave; + } + + function updateEntry(idx, properties) { + var newModel = entriesModel.slice(); + newModel[idx] = Object.assign({}, newModel[idx], properties); + entriesModel = newModel; + saveEntries(); + } + + function reorderEntries(fromIndex, toIndex) { + var newModel = entriesModel.slice(); + var item = newModel.splice(fromIndex, 1)[0]; + newModel.splice(toIndex, 0, item); + entriesModel = newModel; + saveEntries(); + } + + Component.onDestruction: { + if (_activeDialog && _activeDialog.close) { + var dialog = _activeDialog; + _activeDialog = null; + dialog.close(); + dialog.destroy(); + } + } + + function openEntrySettingsDialog(index) { + if (index < 0 || index >= entriesModel.length) { + return; + } + + var entry = entriesModel[index]; + var component = Qt.createComponent(Quickshell.shellDir + "/Modules/Panels/Settings/Tabs/SessionMenu/SessionMenuEntrySettingsDialog.qml"); + + function instantiateAndOpen() { + if (root._activeDialog) { + root._activeDialog.close(); + root._activeDialog.destroy(); + root._activeDialog = null; + } + + var dialog = component.createObject(Overlay.overlay, { + "entryIndex": index, + "entryData": entry, + "entryId": entry.id, + "entryText": entry.text + }); + + if (dialog) { + root._activeDialog = dialog; + dialog.updateEntryProperties.connect((idx, properties) => { + root.updateEntry(idx, properties); + }); + dialog.closed.connect(() => { + if (root._activeDialog === dialog) { + root._activeDialog = null; + dialog.destroy(); + } + }); + dialog.open(); + } else { + Logger.e("SessionMenuTab", "Failed to create entry settings dialog"); + } + } + + if (component.status === Component.Ready) { + instantiateAndOpen(); + } else if (component.status === Component.Error) { + Logger.e("SessionMenuTab", "Error loading entry settings dialog:", component.errorString()); + } else { + component.statusChanged.connect(function () { + if (component.status === Component.Ready) { + instantiateAndOpen(); + } else if (component.status === Component.Error) { + Logger.e("SessionMenuTab", "Error loading entry settings dialog:", component.errorString()); + } + }); + } + } + + Component.onCompleted: { + entriesModel = []; + + // Add the entries available in settings + for (var i = 0; i < Settings.data.sessionMenu.powerOptions.length; i++) { + const settingEntry = Settings.data.sessionMenu.powerOptions[i]; + + for (var j = 0; j < entriesDefault.length; j++) { + if (settingEntry.action === entriesDefault[j].id) { + var entry = entriesDefault[j]; + entry.enabled = settingEntry.enabled; + // Default countdownEnabled to true for backward compatibility + entry.countdownEnabled = settingEntry.countdownEnabled !== undefined ? settingEntry.countdownEnabled : true; + // Load custom command if defined + entry.command = settingEntry.command || ""; + entry.keybind = settingEntry.keybind || ""; + entriesModel.push(entry); + } + } + } + + // Add any missing entries from default + for (var i = 0; i < entriesDefault.length; i++) { + var found = false; + for (var j = 0; j < entriesModel.length; j++) { + if (entriesModel[j].id === entriesDefault[i].id) { + found = true; + break; + } + } + + if (!found) { + var entry = entriesDefault[i]; + // Default countdownEnabled to true for new entries + entry.countdownEnabled = true; + // Default command to empty string for new entries + entry.command = ""; + entry.keybind = ""; + entriesModel.push(entry); + } + } + + saveEntries(); + } + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.general") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.actions") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + GeneralSubTab {} + ActionsSubTab { + entriesModel: root.entriesModel + updateEntry: root.updateEntry + reorderEntries: root.reorderEntries + openEntrySettingsDialog: root.openEntrySettingsDialog + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SystemMonitor/GeneralSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SystemMonitor/GeneralSubTab.qml new file mode 100644 index 0000000..74af597 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SystemMonitor/GeneralSubTab.qml @@ -0,0 +1,105 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + property var screen + + NToggle { + Layout.fillWidth: true + Layout.topMargin: Style.marginM + label: I18n.tr("panels.system-monitor.enable-dgpu-monitoring-label") + description: I18n.tr("panels.system-monitor.enable-dgpu-monitoring-description") + checked: Settings.data.systemMonitor.enableDgpuMonitoring + defaultValue: Settings.getDefaultValue("systemMonitor.enableDgpuMonitoring") + onToggled: checked => Settings.data.systemMonitor.enableDgpuMonitoring = checked + } + + // Colors Section + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NToggle { + label: I18n.tr("panels.system-monitor.use-custom-highlight-colors-label") + description: I18n.tr("panels.system-monitor.use-custom-highlight-colors-description") + checked: Settings.data.systemMonitor.useCustomColors + defaultValue: Settings.getDefaultValue("systemMonitor.useCustomColors") + onToggled: checked => { + // If enabling custom colors and no custom color is saved, persist current theme colors + if (checked) { + if (!Settings.data.systemMonitor.warningColor || Settings.data.systemMonitor.warningColor === "") { + Settings.data.systemMonitor.warningColor = Color.mTertiary.toString(); + } + if (!Settings.data.systemMonitor.criticalColor || Settings.data.systemMonitor.criticalColor === "") { + Settings.data.systemMonitor.criticalColor = Color.mError.toString(); + } + } + Settings.data.systemMonitor.useCustomColors = checked; + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginXL + visible: Settings.data.systemMonitor.useCustomColors + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NText { + text: I18n.tr("panels.system-monitor.warning-color-label") + pointSize: Style.fontSizeM + } + + NColorPicker { + screen: root.screen + Layout.preferredWidth: Style.sliderWidth + Layout.preferredHeight: Style.baseWidgetSize + enabled: Settings.data.systemMonitor.useCustomColors + selectedColor: Settings.data.systemMonitor.warningColor || Color.mTertiary + onColorSelected: color => Settings.data.systemMonitor.warningColor = color + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NText { + text: I18n.tr("panels.system-monitor.critical-color-label") + pointSize: Style.fontSizeM + } + + NColorPicker { + screen: root.screen + Layout.preferredWidth: Style.sliderWidth + Layout.preferredHeight: Style.baseWidgetSize + enabled: Settings.data.systemMonitor.useCustomColors + selectedColor: Settings.data.systemMonitor.criticalColor || Color.mError + onColorSelected: color => Settings.data.systemMonitor.criticalColor = color + } + } + } + + NDivider { + Layout.fillWidth: true + } + + NTextInput { + label: I18n.tr("panels.system-monitor.external-monitor-label") + description: I18n.tr("panels.system-monitor.external-monitor-description") + placeholderText: I18n.tr("panels.system-monitor.external-monitor-placeholder") + text: Settings.data.systemMonitor.externalMonitor + defaultValue: Settings.getDefaultValue("systemMonitor.externalMonitor") + onTextChanged: Settings.data.systemMonitor.externalMonitor = text + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SystemMonitor/PerformanceSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SystemMonitor/PerformanceSubTab.qml new file mode 100644 index 0000000..b461c6f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SystemMonitor/PerformanceSubTab.qml @@ -0,0 +1,29 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.system.noctalia-performance-disable-wallpaper-label") + description: I18n.tr("panels.system.noctalia-performance-disable-wallpaper-description") + checked: !Settings.data.noctaliaPerformance.disableWallpaper + defaultValue: !Settings.getDefaultValue("noctaliaPerformance.disableWallpaper") + onToggled: checked => Settings.data.noctaliaPerformance.disableWallpaper = !checked + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.system.noctalia-performance-disable-desktop-widgets-label") + description: I18n.tr("panels.system.noctalia-performance-disable-desktop-widgets-description") + checked: !Settings.data.noctaliaPerformance.disableDesktopWidgets + defaultValue: !Settings.getDefaultValue("noctaliaPerformance.disableDesktopWidgets") + onToggled: checked => Settings.data.noctaliaPerformance.disableDesktopWidgets = !checked + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SystemMonitor/SystemMonitorTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SystemMonitor/SystemMonitorTab.qml new file mode 100644 index 0000000..e5418ca --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SystemMonitor/SystemMonitorTab.qml @@ -0,0 +1,52 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + property var screen + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("system-monitor.title") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.thresholds") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + NTabButton { + text: I18n.tr("common.performance") + tabIndex: 2 + checked: subTabBar.currentIndex === 2 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + GeneralSubTab { + screen: root.screen + } + ThresholdsSubTab {} + PerformanceSubTab {} + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SystemMonitor/ThresholdsSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SystemMonitor/ThresholdsSubTab.qml new file mode 100644 index 0000000..e91db98 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/SystemMonitor/ThresholdsSubTab.qml @@ -0,0 +1,313 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.System +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + Layout.fillWidth: true + + NLabel { + Layout.fillWidth: true + description: I18n.tr("panels.system-monitor.thresholds-section-description") + } + + GridLayout { + Layout.fillWidth: true + Layout.topMargin: Style.marginM + columns: 3 + columnSpacing: Style.marginM + rowSpacing: Style.marginM + + // Header row + Item { + Layout.fillWidth: true + } + + NText { + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + text: I18n.tr("panels.system-monitor.threshold-warning") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + + NText { + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + text: I18n.tr("panels.system-monitor.threshold-critical") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + + // CPU Usage + NText { + text: I18n.tr("bar.system-monitor.cpu-usage-label") + pointSize: Style.fontSizeM + } + + NSpinBox { + Layout.alignment: Qt.AlignHCenter + from: 0 + to: 100 + stepSize: 5 + value: Settings.data.systemMonitor.cpuWarningThreshold + defaultValue: Settings.getDefaultValue("systemMonitor.cpuWarningThreshold") + suffix: "%" + onValueChanged: { + Settings.data.systemMonitor.cpuWarningThreshold = value; + if (Settings.data.systemMonitor.cpuCriticalThreshold < value) { + Settings.data.systemMonitor.cpuCriticalThreshold = value; + } + } + } + + NSpinBox { + Layout.alignment: Qt.AlignHCenter + from: Settings.data.systemMonitor.cpuWarningThreshold + to: 100 + stepSize: 5 + value: Settings.data.systemMonitor.cpuCriticalThreshold + defaultValue: Settings.getDefaultValue("systemMonitor.cpuCriticalThreshold") + suffix: "%" + onValueChanged: Settings.data.systemMonitor.cpuCriticalThreshold = value + } + + // CPU Temperature + NText { + text: I18n.tr("bar.system-monitor.cpu-temperature-label") + pointSize: Style.fontSizeM + } + + NSpinBox { + Layout.alignment: Qt.AlignHCenter + from: 0 + to: 100 + stepSize: 5 + value: Settings.data.systemMonitor.tempWarningThreshold + defaultValue: Settings.getDefaultValue("systemMonitor.tempWarningThreshold") + suffix: "ยฐC" + onValueChanged: { + Settings.data.systemMonitor.tempWarningThreshold = value; + if (Settings.data.systemMonitor.tempCriticalThreshold < value) { + Settings.data.systemMonitor.tempCriticalThreshold = value; + } + } + } + + NSpinBox { + Layout.alignment: Qt.AlignHCenter + from: Settings.data.systemMonitor.tempWarningThreshold + to: 100 + stepSize: 5 + value: Settings.data.systemMonitor.tempCriticalThreshold + defaultValue: Settings.getDefaultValue("systemMonitor.tempCriticalThreshold") + suffix: "ยฐC" + onValueChanged: Settings.data.systemMonitor.tempCriticalThreshold = value + } + + // GPU Temperature + NText { + visible: SystemStatService.gpuAvailable + text: I18n.tr("panels.system-monitor.gpu-section-label") + pointSize: Style.fontSizeM + } + + NSpinBox { + visible: SystemStatService.gpuAvailable + Layout.alignment: Qt.AlignHCenter + from: 0 + to: 120 + stepSize: 5 + value: Settings.data.systemMonitor.gpuWarningThreshold + defaultValue: Settings.getDefaultValue("systemMonitor.gpuWarningThreshold") + suffix: "ยฐC" + onValueChanged: { + Settings.data.systemMonitor.gpuWarningThreshold = value; + if (Settings.data.systemMonitor.gpuCriticalThreshold < value) { + Settings.data.systemMonitor.gpuCriticalThreshold = value; + } + } + } + + NSpinBox { + visible: SystemStatService.gpuAvailable + Layout.alignment: Qt.AlignHCenter + from: Settings.data.systemMonitor.gpuWarningThreshold + to: 120 + stepSize: 5 + value: Settings.data.systemMonitor.gpuCriticalThreshold + defaultValue: Settings.getDefaultValue("systemMonitor.gpuCriticalThreshold") + suffix: "ยฐC" + onValueChanged: Settings.data.systemMonitor.gpuCriticalThreshold = value + } + + // Memory Usage + NText { + text: I18n.tr("bar.system-monitor.memory-usage-label") + pointSize: Style.fontSizeM + } + + NSpinBox { + Layout.alignment: Qt.AlignHCenter + from: 0 + to: 100 + stepSize: 5 + value: Settings.data.systemMonitor.memWarningThreshold + defaultValue: Settings.getDefaultValue("systemMonitor.memWarningThreshold") + suffix: "%" + onValueChanged: { + Settings.data.systemMonitor.memWarningThreshold = value; + if (Settings.data.systemMonitor.memCriticalThreshold < value) { + Settings.data.systemMonitor.memCriticalThreshold = value; + } + } + } + + NSpinBox { + Layout.alignment: Qt.AlignHCenter + from: Settings.data.systemMonitor.memWarningThreshold + to: 100 + stepSize: 5 + value: Settings.data.systemMonitor.memCriticalThreshold + defaultValue: Settings.getDefaultValue("systemMonitor.memCriticalThreshold") + suffix: "%" + onValueChanged: Settings.data.systemMonitor.memCriticalThreshold = value + } + + // Swap Usage + NText { + text: I18n.tr("bar.system-monitor.swap-usage-label") + pointSize: Style.fontSizeM + } + + NSpinBox { + Layout.alignment: Qt.AlignHCenter + from: 0 + to: 100 + stepSize: 5 + value: Settings.data.systemMonitor.swapWarningThreshold + defaultValue: Settings.getDefaultValue("systemMonitor.swapWarningThreshold") + suffix: "%" + onValueChanged: { + Settings.data.systemMonitor.swapWarningThreshold = value; + if (Settings.data.systemMonitor.swapCriticalThreshold < value) { + Settings.data.systemMonitor.swapCriticalThreshold = value; + } + } + } + + NSpinBox { + Layout.alignment: Qt.AlignHCenter + from: Settings.data.systemMonitor.swapWarningThreshold + to: 100 + stepSize: 5 + value: Settings.data.systemMonitor.swapCriticalThreshold + defaultValue: Settings.getDefaultValue("systemMonitor.swapCriticalThreshold") + suffix: "%" + onValueChanged: Settings.data.systemMonitor.swapCriticalThreshold = value + } + + // Disk Usage + NText { + text: I18n.tr("panels.system-monitor.disk-section-label") + pointSize: Style.fontSizeM + } + + NSpinBox { + Layout.alignment: Qt.AlignHCenter + from: 0 + to: 100 + stepSize: 5 + value: Settings.data.systemMonitor.diskWarningThreshold + defaultValue: Settings.getDefaultValue("systemMonitor.diskWarningThreshold") + suffix: "%" + onValueChanged: { + Settings.data.systemMonitor.diskWarningThreshold = value; + if (Settings.data.systemMonitor.diskCriticalThreshold < value) { + Settings.data.systemMonitor.diskCriticalThreshold = value; + } + } + } + + NSpinBox { + Layout.alignment: Qt.AlignHCenter + from: Settings.data.systemMonitor.diskWarningThreshold + to: 100 + stepSize: 5 + value: Settings.data.systemMonitor.diskCriticalThreshold + defaultValue: Settings.getDefaultValue("systemMonitor.diskCriticalThreshold") + suffix: "%" + onValueChanged: Settings.data.systemMonitor.diskCriticalThreshold = value + } + + // Disk Available + NText { + text: I18n.tr("panels.system-monitor.disk-available-label") + pointSize: Style.fontSizeM + } + + NSpinBox { + Layout.alignment: Qt.AlignHCenter + from: 0 + to: 100 + stepSize: 5 + value: Settings.data.systemMonitor.diskAvailWarningThreshold + defaultValue: Settings.getDefaultValue("systemMonitor.diskAvailWarningThreshold") + suffix: "%" + onValueChanged: { + Settings.data.systemMonitor.diskAvailWarningThreshold = value; + if (Settings.data.systemMonitor.diskAvailCriticalThreshold > value) { + Settings.data.systemMonitor.diskAvailCriticalThreshold = value; + } + } + } + + NSpinBox { + Layout.alignment: Qt.AlignHCenter + from: 0 + to: 20 + stepSize: 5 + value: Settings.data.systemMonitor.diskAvailCriticalThreshold + defaultValue: Settings.getDefaultValue("systemMonitor.diskAvailCriticalThreshold") + suffix: "%" + onValueChanged: Settings.data.systemMonitor.diskAvailCriticalThreshold = value + } + + // Battery + NText { + text: I18n.tr("panels.notifications.toast-battery-label") + pointSize: Style.fontSizeM + } + + NSpinBox { + Layout.alignment: Qt.AlignHCenter + from: 0 + to: 100 + stepSize: 5 + value: Settings.data.systemMonitor.batteryWarningThreshold + defaultValue: Settings.getDefaultValue("systemMonitor.batteryWarningThreshold") + suffix: "%" + onValueChanged: { + Settings.data.systemMonitor.batteryWarningThreshold = value; + if (Settings.data.systemMonitor.batteryCriticalThreshold > value) { + Settings.data.systemMonitor.batteryCriticalThreshold = value; + } + } + } + + NSpinBox { + Layout.alignment: Qt.AlignHCenter + from: 0 + to: Settings.data.systemMonitor.batteryWarningThreshold + stepSize: 5 + value: Settings.data.systemMonitor.batteryCriticalThreshold + defaultValue: Settings.getDefaultValue("systemMonitor.batteryCriticalThreshold") + suffix: "%" + onValueChanged: Settings.data.systemMonitor.batteryCriticalThreshold = value + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/UserInterface/AppearanceSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/UserInterface/AppearanceSubTab.qml new file mode 100644 index 0000000..76f6fa7 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/UserInterface/AppearanceSubTab.qml @@ -0,0 +1,211 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + NToggle { + label: I18n.tr("panels.user-interface.tooltips-label") + description: I18n.tr("panels.user-interface.tooltips-description") + checked: Settings.data.ui.tooltipsEnabled + defaultValue: Settings.getDefaultValue("ui.tooltipsEnabled") + onToggled: checked => Settings.data.ui.tooltipsEnabled = checked + } + + NToggle { + label: I18n.tr("panels.user-interface.box-border-label") + description: I18n.tr("panels.user-interface.box-border-description") + checked: Settings.data.ui.boxBorderEnabled + defaultValue: Settings.getDefaultValue("ui.boxBorderEnabled") + onToggled: checked => Settings.data.ui.boxBorderEnabled = checked + } + + NToggle { + label: I18n.tr("panels.user-interface.scrollbar-always-visible-label") + description: I18n.tr("panels.user-interface.scrollbar-always-visible-description") + checked: Settings.data.ui.scrollbarAlwaysVisible + defaultValue: Settings.getDefaultValue("ui.scrollbarAlwaysVisible") + onToggled: checked => Settings.data.ui.scrollbarAlwaysVisible = checked + } + + NToggle { + label: I18n.tr("panels.user-interface.shadows-label") + description: I18n.tr("panels.user-interface.shadows-description") + checked: Settings.data.general.enableShadows + defaultValue: Settings.getDefaultValue("general.enableShadows") + onToggled: checked => Settings.data.general.enableShadows = checked + } + + NToggle { + label: I18n.tr("panels.user-interface.blur-behind-label") + description: I18n.tr("panels.user-interface.blur-behind-description") + checked: Settings.data.general.enableBlurBehind + defaultValue: Settings.getDefaultValue("general.enableBlurBehind") + onToggled: checked => Settings.data.general.enableBlurBehind = checked + } + + NToggle { + label: I18n.tr("panels.user-interface.translucent-widgets-label") + description: I18n.tr("panels.user-interface.translucent-widgets-description") + checked: Settings.data.ui.translucentWidgets + defaultValue: Settings.getDefaultValue("ui.translucentWidgets") + onToggled: checked => Settings.data.ui.translucentWidgets = checked + } + + NComboBox { + visible: Settings.data.general.enableShadows + label: I18n.tr("panels.user-interface.shadows-direction-label") + description: I18n.tr("panels.user-interface.shadows-direction-description") + Layout.fillWidth: true + + readonly property var shadowOptionsMap: ({ + "top_left": { + "name": I18n.tr("positions.top-left"), + "p": Qt.point(-2, -2) + }, + "top": { + "name": I18n.tr("positions.top"), + "p": Qt.point(0, -3) + }, + "top_right": { + "name": I18n.tr("positions.top-right"), + "p": Qt.point(2, -2) + }, + "left": { + "name": I18n.tr("positions.left"), + "p": Qt.point(-3, 0) + }, + "center": { + "name": I18n.tr("positions.center"), + "p": Qt.point(0, 0) + }, + "right": { + "name": I18n.tr("positions.right"), + "p": Qt.point(3, 0) + }, + "bottom_left": { + "name": I18n.tr("positions.bottom-left"), + "p": Qt.point(-2, 2) + }, + "bottom": { + "name": I18n.tr("positions.bottom"), + "p": Qt.point(0, 3) + }, + "bottom_right": { + "name": I18n.tr("positions.bottom-right"), + "p": Qt.point(2, 3) + } + }) + + model: Object.keys(shadowOptionsMap).map(function (k) { + return { + "key": k, + "name": shadowOptionsMap[k].name + }; + }) + + currentKey: Settings.data.general.shadowDirection + defaultValue: Settings.getDefaultValue("general.shadowDirection") + + onSelected: function (key) { + var opt = shadowOptionsMap[key]; + if (opt) { + Settings.data.general.shadowDirection = key; + Settings.data.general.shadowOffsetX = opt.p.x; + Settings.data.general.shadowOffsetY = opt.p.y; + } + } + } + + NDivider { + Layout.fillWidth: true + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.user-interface.scaling-label") + description: I18n.tr("panels.user-interface.scaling-description") + from: 0.8 + to: 1.2 + stepSize: 0.05 + showReset: true + value: Settings.data.general.scaleRatio + defaultValue: Settings.getDefaultValue("general.scaleRatio") + onMoved: value => Settings.data.general.scaleRatio = value + text: Math.floor(Settings.data.general.scaleRatio * 100) + "%" + } + + NDivider { + Layout.fillWidth: true + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.user-interface.box-border-radius-label") + description: I18n.tr("panels.user-interface.box-border-radius-description") + from: 0 + to: 2 + stepSize: 0.01 + showReset: true + value: Settings.data.general.radiusRatio + defaultValue: Settings.getDefaultValue("general.radiusRatio") + onMoved: value => Settings.data.general.radiusRatio = value + text: Math.floor(Settings.data.general.radiusRatio * 100) + "%" + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.user-interface.control-border-radius-label") + description: I18n.tr("panels.user-interface.control-border-radius-description") + from: 0 + to: 2 + stepSize: 0.01 + showReset: true + value: Settings.data.general.iRadiusRatio + defaultValue: Settings.getDefaultValue("general.iRadiusRatio") + onMoved: value => Settings.data.general.iRadiusRatio = value + text: Math.floor(Settings.data.general.iRadiusRatio * 100) + "%" + } + + NDivider { + Layout.fillWidth: true + } + + ColumnLayout { + spacing: Style.marginL + Layout.fillWidth: true + + NToggle { + label: I18n.tr("panels.user-interface.animation-disable-label") + description: I18n.tr("panels.user-interface.animation-disable-description") + checked: Settings.data.general.animationDisabled + defaultValue: Settings.getDefaultValue("general.animationDisabled") + onToggled: checked => Settings.data.general.animationDisabled = checked + } + + ColumnLayout { + spacing: Style.marginXXS + Layout.fillWidth: true + visible: !Settings.data.general.animationDisabled + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.user-interface.animation-speed-label") + description: I18n.tr("panels.user-interface.animation-speed-description") + from: 0 + to: 2.0 + stepSize: 0.01 + showReset: true + value: Settings.data.general.animationSpeed + defaultValue: Settings.getDefaultValue("general.animationSpeed") + onMoved: value => Settings.data.general.animationSpeed = Math.max(value, 0.05) + text: Math.round(Settings.data.general.animationSpeed * 100) + "%" + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/UserInterface/PanelsSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/UserInterface/PanelsSubTab.qml new file mode 100644 index 0000000..7ee185e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/UserInterface/PanelsSubTab.qml @@ -0,0 +1,113 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.Panels.Settings // For SettingsPanel +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + NToggle { + label: I18n.tr("panels.user-interface.panels-attached-to-bar-label") + description: I18n.tr("panels.user-interface.panels-attached-to-bar-description") + checked: Settings.data.ui.panelsAttachedToBar + defaultValue: Settings.getDefaultValue("ui.panelsAttachedToBar") + onToggled: checked => Settings.data.ui.panelsAttachedToBar = checked + } + + NToggle { + visible: (Quickshell.screens.length > 1) + label: I18n.tr("panels.user-interface.allow-panels-without-bar-label") + description: I18n.tr("panels.user-interface.allow-panels-without-bar-description") + checked: Settings.data.general.allowPanelsOnScreenWithoutBar + defaultValue: Settings.getDefaultValue("general.allowPanelsOnScreenWithoutBar") + onToggled: checked => Settings.data.general.allowPanelsOnScreenWithoutBar = checked + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.user-interface.panel-background-opacity-label") + description: I18n.tr("panels.user-interface.panel-background-opacity-description") + from: 0 + to: 1 + stepSize: 0.01 + showReset: true + value: Settings.data.ui.panelBackgroundOpacity + defaultValue: Settings.getDefaultValue("ui.panelBackgroundOpacity") + onMoved: value => Settings.data.ui.panelBackgroundOpacity = value + text: Math.floor(Settings.data.ui.panelBackgroundOpacity * 100) + "%" + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.user-interface.dimmer-opacity-label") + description: I18n.tr("panels.user-interface.dimmer-opacity-description") + from: 0 + to: 1 + stepSize: 0.01 + showReset: true + value: Settings.data.general.dimmerOpacity + defaultValue: Settings.getDefaultValue("general.dimmerOpacity") + onMoved: value => Settings.data.general.dimmerOpacity = value + text: Math.floor(Settings.data.general.dimmerOpacity * 100) + "%" + } + + NDivider { + Layout.fillWidth: true + } + + NHeader { + label: I18n.tr("panels.user-interface.settings-panel-header") + } + + NComboBox { + label: I18n.tr("panels.user-interface.settings-panel-mode-label") + description: I18n.tr("panels.user-interface.settings-panel-mode-description") + Layout.fillWidth: true + minimumWidth: 220 * Style.uiScaleRatio + model: [ + { + "key": "attached", + "name": I18n.tr("options.settings-panel-mode.attached") + }, + { + "key": "centered", + "name": I18n.tr("options.settings-panel-mode.centered") + }, + { + "key": "window", + "name": I18n.tr("options.settings-panel-mode.window") + } + ] + currentKey: Settings.data.ui.settingsPanelMode + defaultValue: Settings.getDefaultValue("ui.settingsPanelMode") + onSelected: key => { + // Defer setup to next update so close can do its work properly + Qt.callLater(() => { + Settings.data.ui.settingsPanelMode = key; + }); + if (Settings.data.ui.settingsPanelMode === "window" || key === "window") { + // Just switched from/to window, need to close panel + var screen = PanelService.openedPanel?.screen || SettingsPanelService.settingsWindow?.screen || PanelService.findScreenForPanels(); + SettingsPanelService.close(screen); + + Qt.callLater(() => { + SettingsPanelService.openToTab(SettingsPanel.Tab.UserInterface, 1, screen); + }); + } + } + } + + NToggle { + label: I18n.tr("panels.user-interface.settings-panel-sidebar-card-style-label") + description: I18n.tr("panels.user-interface.settings-panel-sidebar-card-style-description") + checked: Settings.data.ui.settingsPanelSideBarCardStyle + defaultValue: Settings.getDefaultValue("ui.settingsPanelSideBarCardStyle") + onToggled: checked => Settings.data.ui.settingsPanelSideBarCardStyle = checked + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/UserInterface/ScreenCornersSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/UserInterface/ScreenCornersSubTab.qml new file mode 100644 index 0000000..1607abc --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/UserInterface/ScreenCornersSubTab.qml @@ -0,0 +1,46 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + NToggle { + label: I18n.tr("panels.general.screen-corners-show-corners-label") + description: I18n.tr("panels.general.screen-corners-show-corners-description") + checked: Settings.data.general.showScreenCorners + defaultValue: Settings.getDefaultValue("general.showScreenCorners") + onToggled: checked => Settings.data.general.showScreenCorners = checked + } + + NToggle { + label: I18n.tr("panels.general.screen-corners-solid-black-label") + description: I18n.tr("panels.general.screen-corners-solid-black-description") + checked: Settings.data.general.forceBlackScreenCorners + defaultValue: Settings.getDefaultValue("general.forceBlackScreenCorners") + onToggled: checked => Settings.data.general.forceBlackScreenCorners = checked + } + + ColumnLayout { + spacing: Style.marginXXS + Layout.fillWidth: true + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.general.screen-corners-radius-label") + description: I18n.tr("panels.general.screen-corners-radius-description") + from: 0 + to: 2 + stepSize: 0.01 + showReset: true + value: Settings.data.general.screenRadiusRatio + defaultValue: Settings.getDefaultValue("general.screenRadiusRatio") + onMoved: value => Settings.data.general.screenRadiusRatio = value + text: Math.floor(Settings.data.general.screenRadiusRatio * 100) + "%" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/UserInterface/UserInterfaceTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/UserInterface/UserInterfaceTab.qml new file mode 100644 index 0000000..8df9994 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/UserInterface/UserInterfaceTab.qml @@ -0,0 +1,48 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.appearance") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.panels") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + NTabButton { + text: I18n.tr("common.screen-corners") + tabIndex: 2 + checked: subTabBar.currentIndex === 2 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + AppearanceSubTab {} + PanelsSubTab {} + ScreenCornersSubTab {} + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Wallpaper/AutomationSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Wallpaper/AutomationSubTab.qml new file mode 100644 index 0000000..69d7429 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Wallpaper/AutomationSubTab.qml @@ -0,0 +1,65 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + enabled: Settings.data.wallpaper.enabled + + NToggle { + label: I18n.tr("panels.wallpaper.automation-scheduled-change-label") + description: I18n.tr("panels.wallpaper.automation-scheduled-change-description") + checked: Settings.data.wallpaper.automationEnabled + onToggled: checked => Settings.data.wallpaper.automationEnabled = checked + } + + ColumnLayout { + enabled: Settings.data.wallpaper.automationEnabled + spacing: Style.marginL + Layout.fillWidth: true + + NComboBox { + + label: I18n.tr("panels.wallpaper.automation-change-mode-label") + description: I18n.tr("panels.wallpaper.automation-change-mode-description") + Layout.fillWidth: true + model: [ + { + "key": "random", + "name": I18n.tr("common.random") + }, + { + "key": "alphabetical", + "name": I18n.tr("panels.wallpaper.automation-change-mode-alphabetical") + } + ] + currentKey: Settings.data.wallpaper.wallpaperChangeMode || "random" + onSelected: key => Settings.data.wallpaper.wallpaperChangeMode = key + defaultValue: Settings.getDefaultValue("wallpaper.wallpaperChangeMode") + } + + NSpinBox { + label: I18n.tr("panels.wallpaper.automation-interval-label") + description: I18n.tr("panels.wallpaper.automation-interval-description") + Layout.fillWidth: true + from: 1 + to: 1440 + stepSize: 1 + suffix: "m" + value: Math.round(Settings.data.wallpaper.randomIntervalSec / 60) + defaultValue: Settings.getDefaultValue("wallpaper.randomIntervalSec") !== undefined ? Math.round(Settings.getDefaultValue("wallpaper.randomIntervalSec") / 60) : undefined + onValueChanged: { + let newSec = value * 60; + if (newSec !== Settings.data.wallpaper.randomIntervalSec) { + Settings.data.wallpaper.randomIntervalSec = newSec; + WallpaperService.restartRandomWallpaperTimer(); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Wallpaper/GeneralSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Wallpaper/GeneralSubTab.qml new file mode 100644 index 0000000..712d71c --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Wallpaper/GeneralSubTab.qml @@ -0,0 +1,261 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Compositor +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + property var screen + + signal openMainFolderPicker + signal openMonitorFolderPicker(string monitorName) + + NToggle { + label: I18n.tr("panels.wallpaper.settings-enable-management-label") + description: I18n.tr("panels.wallpaper.settings-enable-management-description") + checked: Settings.data.wallpaper.enabled + onToggled: checked => Settings.data.wallpaper.enabled = checked + defaultValue: Settings.getDefaultValue("wallpaper.enabled") + } + + ColumnLayout { + enabled: Settings.data.wallpaper.enabled + spacing: Style.marginL + Layout.fillWidth: true + + RowLayout { + + NLabel { + label: I18n.tr("tooltips.wallpaper-selector") + description: I18n.tr("panels.wallpaper.settings-selector-description") + Layout.alignment: Qt.AlignTop + } + + NIconButton { + icon: "wallpaper-selector" + tooltipText: I18n.tr("tooltips.wallpaper-selector") + onClicked: PanelService.getPanel("wallpaperPanel", root.screen)?.toggle() + } + } + + NComboBox { + label: I18n.tr("common.position") + description: I18n.tr("panels.wallpaper.settings-selector-position-description") + Layout.fillWidth: true + model: [ + { + "key": "follow_bar", + "name": I18n.tr("positions.follow-bar") + }, + { + "key": "center", + "name": I18n.tr("positions.center") + }, + { + "key": "top_center", + "name": I18n.tr("positions.top-center") + }, + { + "key": "top_left", + "name": I18n.tr("positions.top-left") + }, + { + "key": "top_right", + "name": I18n.tr("positions.top-right") + }, + { + "key": "bottom_left", + "name": I18n.tr("positions.bottom-left") + }, + { + "key": "bottom_right", + "name": I18n.tr("positions.bottom-right") + }, + { + "key": "bottom_center", + "name": I18n.tr("positions.bottom-center") + } + ] + currentKey: Settings.data.wallpaper.panelPosition + onSelected: key => Settings.data.wallpaper.panelPosition = key + defaultValue: Settings.getDefaultValue("wallpaper.panelPosition") + } + + NComboBox { + label: I18n.tr("panels.wallpaper.settings-view-mode-label") + description: I18n.tr("panels.wallpaper.settings-view-mode-description") + Layout.fillWidth: true + model: [ + { + "key": "single", + "name": I18n.tr("panels.wallpaper.view-mode-single") + }, + { + "key": "recursive", + "name": I18n.tr("panels.wallpaper.view-mode-recursive") + }, + { + "key": "browse", + "name": I18n.tr("panels.wallpaper.view-mode-browse") + } + ] + currentKey: Settings.data.wallpaper.viewMode + onSelected: key => Settings.data.wallpaper.viewMode = key + defaultValue: Settings.getDefaultValue("wallpaper.viewMode") + } + + NTextInputButton { + id: wallpaperPathInput + label: I18n.tr("panels.wallpaper.settings-folder-label") + description: I18n.tr("panels.wallpaper.settings-folder-description") + text: Settings.data.wallpaper.directory + buttonIcon: "folder-open" + buttonTooltip: I18n.tr("panels.wallpaper.settings-folder-label") + Layout.fillWidth: true + onInputTextChanged: text => Settings.data.wallpaper.directory = text + onButtonClicked: root.openMainFolderPicker() + } + + NToggle { + label: I18n.tr("panels.wallpaper.settings-monitor-specific-label") + description: I18n.tr("panels.wallpaper.settings-monitor-specific-description") + checked: Settings.data.wallpaper.enableMultiMonitorDirectories + onToggled: checked => Settings.data.wallpaper.enableMultiMonitorDirectories = checked + defaultValue: Settings.getDefaultValue("wallpaper.enableMultiMonitorDirectories") + } + + NBox { + visible: Settings.data.wallpaper.enableMultiMonitorDirectories + Layout.fillWidth: true + radius: Style.radiusM + color: Color.mSurface + border.color: Color.mOutline + border.width: Style.borderS + implicitHeight: contentCol.implicitHeight + Style.margin2L + clip: true + + ColumnLayout { + id: contentCol + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + Repeater { + model: Quickshell.screens || [] + delegate: ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NText { + text: (modelData.name || "Unknown") + color: Color.mPrimary + font.weight: Style.fontWeightBold + pointSize: Style.fontSizeM + } + + NTextInputButton { + id: monitorDirInput + text: WallpaperService.getMonitorDirectory(modelData.name) + buttonIcon: "folder-open" + buttonTooltip: I18n.tr("panels.wallpaper.settings-monitor-specific-tooltip") + Layout.fillWidth: true + onInputEditingFinished: WallpaperService.setMonitorDirectory(modelData.name, monitorDirInput.text) + onButtonClicked: root.openMonitorFolderPicker(modelData.name) + } + } + } + } + } + } + + NDivider { + Layout.fillWidth: true + } + + NToggle { + label: I18n.tr("panels.wallpaper.settings-use-original-images-label") + description: I18n.tr("panels.wallpaper.settings-use-original-images-description") + checked: Settings.data.wallpaper.useOriginalImages + enabled: Settings.data.wallpaper.enabled + onToggled: checked => Settings.data.wallpaper.useOriginalImages = checked + defaultValue: Settings.getDefaultValue("wallpaper.useOriginalImages") + } + + RowLayout { + spacing: Style.marginM + Layout.fillWidth: true + enabled: Settings.data.wallpaper.enabled + + NLabel { + label: I18n.tr("panels.wallpaper.settings-clear-cache-label") + description: I18n.tr("panels.wallpaper.settings-clear-cache-description") + Layout.fillWidth: true + } + + NButton { + icon: "trash" + text: I18n.tr("panels.wallpaper.settings-clear-cache-button") + outlined: true + onClicked: { + ImageCacheService.clearLarge(); + ToastService.showNotice(I18n.tr("panels.wallpaper.settings-clear-cache-toast")); + } + } + } + + NDivider { + Layout.fillWidth: true + visible: CompositorService.isNiri + } + + ColumnLayout { + visible: CompositorService.isNiri + enabled: Settings.data.wallpaper.enabled + spacing: Style.marginL + Layout.fillWidth: true + + NToggle { + label: I18n.tr("panels.wallpaper.settings-enable-overview-label") + description: I18n.tr("panels.wallpaper.settings-enable-overview-description") + checked: Settings.data.wallpaper.enabled && Settings.data.wallpaper.overviewEnabled + onToggled: checked => Settings.data.wallpaper.overviewEnabled = checked + defaultValue: Settings.getDefaultValue("wallpaper.overviewEnabled") + } + + NValueSlider { + Layout.fillWidth: true + visible: Settings.data.wallpaper.overviewEnabled + label: I18n.tr("panels.wallpaper.settings-overview-blur-strength-label") + description: I18n.tr("panels.wallpaper.settings-overview-blur-strength-description") + from: 0.0 + to: 1.0 + stepSize: 0.01 + showReset: true + value: Settings.data.wallpaper.overviewBlur + onMoved: value => Settings.data.wallpaper.overviewBlur = value + text: ((Settings.data.wallpaper.overviewBlur) * 100).toFixed(0) + "%" + defaultValue: Settings.getDefaultValue("wallpaper.overviewBlur") + } + + NValueSlider { + Layout.fillWidth: true + visible: Settings.data.wallpaper.overviewEnabled + label: I18n.tr("panels.wallpaper.settings-overview-tint-label") + description: I18n.tr("panels.wallpaper.settings-overview-tint-description") + from: 0.0 + to: 1.0 + stepSize: 0.01 + showReset: true + value: Settings.data.wallpaper.overviewTint + onMoved: value => Settings.data.wallpaper.overviewTint = value + text: ((Settings.data.wallpaper.overviewTint) * 100).toFixed(0) + "%" + defaultValue: Settings.getDefaultValue("wallpaper.overviewTint") + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Wallpaper/LookAndFeelSubTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Wallpaper/LookAndFeelSubTab.qml new file mode 100644 index 0000000..2adb36c --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Wallpaper/LookAndFeelSubTab.qml @@ -0,0 +1,114 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + enabled: Settings.data.wallpaper.enabled + + property var screen + + NComboBox { + label: I18n.tr("panels.wallpaper.look-feel-fill-mode-label") + description: I18n.tr("panels.wallpaper.look-feel-fill-mode-description") + model: WallpaperService.fillModeModel + currentKey: Settings.data.wallpaper.fillMode + onSelected: key => Settings.data.wallpaper.fillMode = key + defaultValue: Settings.getDefaultValue("wallpaper.fillMode") + } + + RowLayout { + NLabel { + label: I18n.tr("bar.audio-visualizer.color-name-label") + description: I18n.tr("panels.wallpaper.look-feel-fill-color-description") + Layout.alignment: Qt.AlignTop + } + + NColorPicker { + screen: root.screen + selectedColor: Settings.data.wallpaper.fillColor + onColorSelected: color => Settings.data.wallpaper.fillColor = color + } + } + + NDivider { + Layout.fillWidth: true + } + + ColumnLayout { + spacing: Style.marginS + Layout.fillWidth: true + + NLabel { + label: I18n.tr("panels.wallpaper.look-feel-transition-type-label") + description: I18n.tr("panels.wallpaper.look-feel-transition-type-description") + } + + Repeater { + model: WallpaperService.allTransitions + + NCheckbox { + required property string modelData + label: { + var key = "wallpaper.transitions." + modelData; + return I18n.tr(key); + } + labelSize: Style.fontSizeM + checked: Settings.data.wallpaper.transitionType.includes(modelData) + onToggled: checked => { + var arr = Array.from(Settings.data.wallpaper.transitionType); + if (checked) { + if (!arr.includes(modelData)) + arr.push(modelData); + } else { + arr = arr.filter(k => k !== modelData); + } + Settings.data.wallpaper.transitionType = arr; + } + } + } + } + + NDivider { + Layout.fillWidth: true + } + + NToggle { + label: I18n.tr("panels.wallpaper.look-feel-skip-startup-transition-label") + description: I18n.tr("panels.wallpaper.look-feel-skip-startup-transition-description") + checked: Settings.data.wallpaper.skipStartupTransition + onToggled: Settings.data.wallpaper.skipStartupTransition = checked + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.wallpaper.look-feel-transition-duration-label") + description: I18n.tr("panels.wallpaper.look-feel-transition-duration-description") + from: 500 + to: 10000 + stepSize: 100 + showReset: true + value: Settings.data.wallpaper.transitionDuration + onMoved: value => Settings.data.wallpaper.transitionDuration = value + text: (Settings.data.wallpaper.transitionDuration / 1000).toFixed(1) + "s" + defaultValue: Settings.getDefaultValue("wallpaper.transitionDuration") + } + + NValueSlider { + Layout.fillWidth: true + label: I18n.tr("panels.wallpaper.look-feel-edge-smoothness-label") + description: I18n.tr("panels.wallpaper.look-feel-edge-smoothness-description") + from: 0.0 + to: 1.0 + showReset: true + value: Settings.data.wallpaper.transitionEdgeSmoothness + onMoved: value => Settings.data.wallpaper.transitionEdgeSmoothness = value + text: Math.round(Settings.data.wallpaper.transitionEdgeSmoothness * 100) + "%" + defaultValue: Settings.getDefaultValue("wallpaper.transitionEdgeSmoothness") + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Wallpaper/WallpaperTab.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Wallpaper/WallpaperTab.qml new file mode 100644 index 0000000..64d1a31 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Settings/Tabs/Wallpaper/WallpaperTab.qml @@ -0,0 +1,93 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + property var screen + + function openMainFolderPicker() { + mainFolderPicker.open(); + } + + function openMonitorFolderPicker(monitorName) { + specificFolderMonitorName = monitorName; + monitorFolderPicker.open(); + } + + property string specificFolderMonitorName: "" + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.general") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.look") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + NTabButton { + text: I18n.tr("common.automation") + tabIndex: 2 + checked: subTabBar.currentIndex === 2 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + GeneralSubTab { + screen: root.screen + onOpenMainFolderPicker: root.openMainFolderPicker() + onOpenMonitorFolderPicker: monitorName => root.openMonitorFolderPicker(monitorName) + } + LookAndFeelSubTab { + screen: root.screen + } + AutomationSubTab {} + } + + NFilePicker { + id: mainFolderPicker + selectionMode: "folders" + title: I18n.tr("setup.wallpaper.dir-select-title") + initialPath: Settings.data.wallpaper.directory || Quickshell.env("HOME") + "/Pictures" + onAccepted: paths => { + if (paths.length > 0) { + Settings.data.wallpaper.directory = paths[0]; + } + } + } + + NFilePicker { + id: monitorFolderPicker + selectionMode: "folders" + title: I18n.tr("panels.wallpaper.settings-select-monitor-folder") + initialPath: WallpaperService.getMonitorDirectory(specificFolderMonitorName) || Quickshell.env("HOME") + "/Pictures" + onAccepted: paths => { + if (paths.length > 0) { + WallpaperService.setMonitorDirectory(specificFolderMonitorName, paths[0]); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/SetupWizard/SetupAppearanceStep.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/SetupWizard/SetupAppearanceStep.qml new file mode 100644 index 0000000..34a6430 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/SetupWizard/SetupAppearanceStep.qml @@ -0,0 +1,395 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io +import qs.Commons +import qs.Services.System +import qs.Services.Theming +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + function extractSchemeName(path) { + var basename = path.split('/').pop(); + return basename.replace('.json', ''); + } + + // Cache for scheme colors (mirrors ColorSchemeTab approach) + property var schemeColorsCache: ({}) + property int cacheVersion: 0 + + function getSchemeColor(schemeName, key) { + try { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"; + var data = schemeColorsCache[schemeName]; + if (data && data[mode] && data[mode][key]) + return data[mode][key]; + } catch (e) {} + return Color.mSurfaceVariant; + } + + // Match ColorSchemeTab helpers + function schemeLoaded(schemeName, jsonData) { + var value = jsonData || {}; + schemeColorsCache[schemeName] = value; + cacheVersion++; + Logger.i("SetupAppearanceStep", `Loaded scheme ${schemeName}`); + } + + Connections { + target: ColorSchemeService + function onSchemesChanged() { + Logger.i("SetupAppearanceStep", `Color schemes changed: ${ColorSchemeService.schemes.length}`); + schemeColorsCache = {}; + cacheVersion++; + } + } + + // Beautiful header with icon + RowLayout { + Layout.fillWidth: true + Layout.bottomMargin: Style.marginL + spacing: Style.marginM + + Rectangle { + width: 40 + height: 40 + radius: Style.radiusL + color: Color.mSurfaceVariant + opacity: 0.6 + + NIcon { + icon: "palette" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NText { + text: I18n.tr("common.appearance") + pointSize: Style.fontSizeXL + font.weight: Style.fontWeightBold + color: Color.mPrimary + } + + NText { + text: I18n.tr("setup.appearance.subheader") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + } + } + + NScrollView { + id: appearanceScrollView + Layout.fillWidth: true + Layout.fillHeight: true + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AsNeeded + + ColumnLayout { + width: appearanceScrollView.availableWidth + spacing: Style.marginM + + // Dark Mode Toggle + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + Rectangle { + width: 28 + height: 28 + radius: Style.radiusM + color: Color.mSurface + + NIcon { + icon: "moon" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + + NText { + text: I18n.tr("tooltips.switch-to-dark-mode") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: I18n.tr("panels.color-scheme.dark-mode-switch-description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + } + + NToggle { + checked: Settings.data.colorSchemes.darkMode + onToggled: checked => Settings.data.colorSchemes.darkMode = checked + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // Wallpaper Colors Toggle + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + Rectangle { + width: 28 + height: 28 + radius: Style.radiusM + color: Color.mSurface + + NIcon { + icon: "color-picker" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + + NText { + text: I18n.tr("panels.color-scheme.color-source-use-wallpaper-colors-label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: I18n.tr("panels.color-scheme.color-source-use-wallpaper-colors-description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + } + + NToggle { + checked: Settings.data.colorSchemes.useWallpaperColors + onToggled: checked => { + if (checked) { + Settings.data.colorSchemes.useWallpaperColors = true; + AppThemeService.generate(); + } else { + Settings.data.colorSchemes.useWallpaperColors = false; + if (Settings.data.colorSchemes.predefinedScheme) { + ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme); + } + } + } + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + visible: !Settings.data.colorSchemes.useWallpaperColors + } + + // Predefined schemes section (visible when wallpaper colors disabled) + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + visible: !Settings.data.colorSchemes.useWallpaperColors + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + Rectangle { + width: 28 + height: 28 + radius: Style.radiusM + color: Color.mSurface + + NIcon { + icon: "palette" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + + NText { + text: I18n.tr("panels.color-scheme.predefined-title") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: I18n.tr("panels.color-scheme.predefined-desc") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } + + // Predefined schemes Grid (matches ColorSchemeTab) + GridLayout { + id: schemesGrid + columns: Math.max(2, Math.floor((parent.width - Style.margin2M) / 180)) + rowSpacing: Style.marginM + columnSpacing: Style.marginM + Layout.fillWidth: true + + Repeater { + model: ColorSchemeService.schemes + + delegate: Rectangle { + id: schemeItem + + property string schemePath: modelData + property string schemeName: root.extractSchemeName(modelData) + + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + height: 50 + radius: Style.radiusS + color: root.cacheVersion >= 0 ? root.getSchemeColor(schemeName, "mSurface") : root.getSchemeColor(schemeName, "mSurface") + border.width: Style.borderL + border.color: itemMouseArea.containsMouse ? Color.mHover : (Settings.data.colorSchemes.predefinedScheme === schemeName ? Color.mSecondary : Color.mOutline) + + RowLayout { + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginXS + + NText { + text: schemeItem.schemeName + pointSize: Style.fontSizeS + font.weight: Style.fontWeightMedium + color: Color.mOnSurface + Layout.fillWidth: true + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + maximumLineCount: 1 + } + + Rectangle { + width: 14 + height: 14 + radius: width * 0.5 + color: root.cacheVersion >= 0 ? (function () { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"; + var cached = root.schemeColorsCache[schemeItem.schemeName]; + return (cached && cached[mode] && cached[mode].mPrimary) || root.getSchemeColor(schemeItem.schemeName, "mPrimary"); + })() : Color.mPrimary + } + Rectangle { + width: 14 + height: 14 + radius: width * 0.5 + color: root.cacheVersion >= 0 ? (function () { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"; + var cached = root.schemeColorsCache[schemeItem.schemeName]; + return (cached && cached[mode] && cached[mode].mSecondary) || root.getSchemeColor(schemeItem.schemeName, "mSecondary"); + })() : Color.mSecondary + } + Rectangle { + width: 14 + height: 14 + radius: width * 0.5 + color: root.cacheVersion >= 0 ? (function () { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"; + var cached = root.schemeColorsCache[schemeItem.schemeName]; + return (cached && cached[mode] && cached[mode].mTertiary) || root.getSchemeColor(schemeItem.schemeName, "mTertiary"); + })() : Color.mTertiary + } + Rectangle { + width: 14 + height: 14 + radius: width * 0.5 + color: root.cacheVersion >= 0 ? (function () { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"; + var cached = root.schemeColorsCache[schemeItem.schemeName]; + return (cached && cached[mode] && cached[mode].mError) || root.getSchemeColor(schemeItem.schemeName, "mError"); + })() : Color.mError + } + } + + MouseArea { + id: itemMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + Settings.data.colorSchemes.useWallpaperColors = false; + Settings.data.colorSchemes.predefinedScheme = schemeItem.schemeName; + ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme); + } + } + } + } + } + } + + // Bottom spacer + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + } + } + + // Hidden loader to populate schemeColorsCache from files + Item { + visible: false + Repeater { + model: ColorSchemeService.schemes + delegate: Item { + FileView { + path: modelData + blockLoading: false + onLoaded: { + var schemeName = root.extractSchemeName(path); + try { + var jsonData = JSON.parse(text()); + root.schemeLoaded(schemeName, jsonData); + } catch (e) { + root.schemeLoaded(schemeName, null); + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/SetupWizard/SetupCustomizeStep.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/SetupWizard/SetupCustomizeStep.qml new file mode 100644 index 0000000..955f29c --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/SetupWizard/SetupCustomizeStep.qml @@ -0,0 +1,617 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + + property real selectedScaleRatio: 1.0 + property string selectedBarPosition: "top" + + signal scaleRatioChanged(real ratio) + signal barPositionChanged(string position) + + spacing: Style.marginM + + // Beautiful header with icon + RowLayout { + Layout.fillWidth: true + Layout.bottomMargin: Style.marginL + spacing: Style.marginM + + Rectangle { + width: 40 + height: 40 + radius: Style.radiusL + color: Color.mSurfaceVariant + opacity: 0.6 + + NIcon { + icon: "palette" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NText { + text: I18n.tr("setup.customize.header") + pointSize: Style.fontSizeXL + font.weight: Style.fontWeightBold + color: Color.mPrimary + } + + NText { + text: I18n.tr("setup.customize.subheader") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + } + } + + NScrollView { + id: customizeScrollView + Layout.fillWidth: true + Layout.fillHeight: true + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AsNeeded + + ColumnLayout { + width: customizeScrollView.availableWidth + spacing: Style.marginM + + // Bar Position section + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + Rectangle { + width: 28 + height: 28 + radius: Style.radiusM + color: Color.mSurface + + NIcon { + icon: "layout-2" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + + NText { + text: I18n.tr("panels.bar.appearance-position-label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: I18n.tr("panels.bar.appearance-position-description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + Repeater { + model: [ + { + "key": "top", + "name": I18n.tr("positions.top"), + "icon": "arrow-up" + }, + { + "key": "bottom", + "name": I18n.tr("positions.bottom"), + "icon": "arrow-down" + }, + { + "key": "left", + "name": I18n.tr("positions.left"), + "icon": "arrow-left" + }, + { + "key": "right", + "name": I18n.tr("positions.right"), + "icon": "arrow-right" + } + ] + delegate: Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 40 + radius: Style.radiusM + border.width: Style.borderS + + property bool isActive: selectedBarPosition === modelData.key + + color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mSurfaceVariant + border.color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mOutline + opacity: (hoverHandler.hovered || isActive) ? 1.0 : 0.8 + + NText { + text: modelData.name + pointSize: Style.fontSizeM + font.weight: (hoverHandler.hovered || parent.isActive) ? Style.fontWeightBold : Style.fontWeightMedium + color: (hoverHandler.hovered || parent.isActive) ? Color.mOnPrimary : Color.mOnSurface + anchors.centerIn: parent + } + + HoverHandler { + id: hoverHandler + } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + selectedBarPosition = modelData.key; + barPositionChanged(modelData.key); + } + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + } + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // Bar Density section + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + Rectangle { + width: 32 + height: 32 + radius: Style.radiusM + color: Color.mSurface + NIcon { + icon: "minimize" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + NText { + text: I18n.tr("panels.bar.appearance-density-label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + NText { + text: I18n.tr("panels.bar.appearance-density-description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + } + + RowLayout { + spacing: Style.marginS + Repeater { + model: [ + { + "key": "mini", + "name": I18n.tr("options.bar.density-mini") + }, + { + "key": "compact", + "name": I18n.tr("options.bar.density-compact") + }, + { + "key": "default", + "name": I18n.tr("options.bar.density-default") + }, + { + "key": "comfortable", + "name": I18n.tr("options.bar.density-comfortable") + }, + { + "key": "spacious", + "name": I18n.tr("options.bar.density-spacious") + } + ] + delegate: Rectangle { + radius: Style.radiusM + border.width: Style.borderS + Layout.preferredHeight: 32 + Layout.preferredWidth: Math.max(90, densityText.implicitWidth + Style.margin2XL) + + property bool isActive: Settings.data.bar.density === modelData.key + + color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mSurfaceVariant + border.color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mOutline + opacity: (hoverHandler.hovered || isActive) ? 1.0 : 0.8 + + NText { + id: densityText + text: modelData.name + pointSize: Style.fontSizeS + font.weight: (hoverHandler.hovered || parent.isActive) ? Style.fontWeightBold : Style.fontWeightMedium + color: (hoverHandler.hovered || parent.isActive) ? Color.mOnPrimary : Color.mOnSurface + anchors.centerIn: parent + } + + HoverHandler { + id: hoverHandler + } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + Settings.data.bar.density = modelData.key; + } + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + } + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // UI Scale section + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + Rectangle { + width: 32 + height: 32 + radius: Style.radiusM + color: Color.mSurface + NIcon { + icon: "maximize" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + NText { + text: I18n.tr("panels.user-interface.scaling-label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + NText { + text: I18n.tr("panels.user-interface.scaling-description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } + + NValueSlider { + Layout.fillWidth: true + from: 0.8 + to: 1.2 + stepSize: 0.05 + value: selectedScaleRatio + onMoved: function (value) { + selectedScaleRatio = value; + scaleRatioChanged(value); + } + text: Math.floor(selectedScaleRatio * 100) + "%" + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // Bar Type section + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + Rectangle { + width: 28 + height: 28 + radius: Style.radiusM + color: Color.mSurface + + NIcon { + icon: "layout-2" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + + NText { + text: I18n.tr("panels.bar.appearance-type-label") ?? "Bar Type" + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: I18n.tr("panels.bar.appearance-type-description") ?? "Choose the style of the bar: Simple, Floating or Framed" + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + Repeater { + model: [ + { + "key": "simple", + "name": I18n.tr("options.bar.type-simple") ?? "Simple" + }, + { + "key": "floating", + "name": I18n.tr("options.bar.type-floating") ?? "Floating" + }, + { + "key": "framed", + "name": I18n.tr("options.bar.type-framed") ?? "Framed" + } + ] + delegate: Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 40 + radius: Style.radiusM + border.width: Style.borderS + + property bool isActive: Settings.data.bar.barType === modelData.key + + color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mSurfaceVariant + border.color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mOutline + opacity: (hoverHandler.hovered || isActive) ? 1.0 : 0.8 + + NText { + text: modelData.name + pointSize: Style.fontSizeM + font.weight: (hoverHandler.hovered || parent.isActive) ? Style.fontWeightBold : Style.fontWeightMedium + color: (hoverHandler.hovered || parent.isActive) ? Color.mOnPrimary : Color.mOnSurface + anchors.centerIn: parent + } + + HoverHandler { + id: hoverHandler + } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + Settings.data.bar.barType = modelData.key; + } + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + } + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // Dim Desktop opacity + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + Rectangle { + width: 32 + height: 32 + radius: Style.radiusM + color: Color.mSurface + NIcon { + icon: "screen-share" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + NText { + text: I18n.tr("panels.user-interface.dimmer-opacity-label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + NText { + text: I18n.tr("panels.user-interface.dimmer-opacity-description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + NValueSlider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXS + from: 0 + to: 1 + stepSize: 0.01 + value: Settings.data.general.dimmerOpacity + onMoved: value => Settings.data.general.dimmerOpacity = value + text: Math.floor(Settings.data.general.dimmerOpacity * 100) + "%" + } + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // Drop Shadows toggle + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + Rectangle { + width: 32 + height: 32 + radius: Style.radiusM + color: Color.mSurface + NIcon { + icon: "shadow" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + NText { + text: I18n.tr("panels.user-interface.shadows-label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + NText { + text: I18n.tr("panels.user-interface.shadows-description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + } + NToggle { + checked: Settings.data.general.enableShadows + onToggled: checked => Settings.data.general.enableShadows = checked + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/SetupWizard/SetupDockStep.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/SetupWizard/SetupDockStep.qml new file mode 100644 index 0000000..103e47f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/SetupWizard/SetupDockStep.qml @@ -0,0 +1,210 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Compositor +import qs.Widgets + +ColumnLayout { + id: root + + spacing: Style.marginM + + // Header + RowLayout { + Layout.fillWidth: true + Layout.bottomMargin: Style.marginL + spacing: Style.marginM + + Rectangle { + width: 40 + height: 40 + radius: Style.radiusL + color: Color.mSurfaceVariant + opacity: 0.6 + + NIcon { + icon: "device-desktop" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NText { + text: I18n.tr("panels.dock.title") || "Dock" + pointSize: Style.fontSizeXL + font.weight: Style.fontWeightBold + color: Color.mPrimary + } + + NText { + text: I18n.tr("panels.dock.monitors-desc") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + } + } + + // Options + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginL + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.dock.enabled-label") + description: I18n.tr("panels.dock.enabled-description") + checked: Settings.data.dock.enabled + onToggled: checked => Settings.data.dock.enabled = checked + } + + // Display behavior + NComboBox { + visible: Settings.data.dock.enabled + Layout.fillWidth: true + label: I18n.tr("panels.display.title") + description: I18n.tr("panels.dock.appearance-display-description") + model: [ + { + "key": "always_visible", + "name": I18n.tr("hide-modes.visible") + }, + { + "key": "auto_hide", + "name": I18n.tr("panels.dock.appearance-display-auto-hide") + }, + { + "key": "exclusive", + "name": I18n.tr("panels.dock.appearance-display-exclusive") + } + ] + currentKey: Settings.data.dock.displayMode + onSelected: key => Settings.data.dock.displayMode = key + } + + // Background opacity + ColumnLayout { + visible: Settings.data.dock.enabled + spacing: Style.marginXXS + Layout.fillWidth: true + NLabel { + label: I18n.tr("panels.osd.background-opacity-label") + description: I18n.tr("panels.dock.appearance-background-opacity-description") + } + NValueSlider { + Layout.fillWidth: true + from: 0 + to: 1 + stepSize: 0.01 + value: Settings.data.dock.backgroundOpacity + onMoved: value => Settings.data.dock.backgroundOpacity = value + text: Math.floor(Settings.data.dock.backgroundOpacity * 100) + "%" + } + } + + // Floating distance + ColumnLayout { + visible: Settings.data.dock.enabled + spacing: Style.marginXXS + Layout.fillWidth: true + NLabel { + label: I18n.tr("panels.dock.appearance-floating-distance-label") + description: I18n.tr("panels.dock.appearance-floating-distance-description") + } + NValueSlider { + Layout.fillWidth: true + from: 0 + to: 4 + stepSize: 0.01 + value: Settings.data.dock.floatingRatio + onMoved: value => Settings.data.dock.floatingRatio = value + text: Math.floor(Settings.data.dock.floatingRatio * 100) + "%" + } + } + + // Icon size + ColumnLayout { + visible: Settings.data.dock.enabled + spacing: Style.marginXXS + Layout.fillWidth: true + NLabel { + label: I18n.tr("panels.dock.appearance-icon-size-label") + description: I18n.tr("panels.dock.appearance-icon-size-description") + } + NValueSlider { + Layout.fillWidth: true + from: 0 + to: 2 + stepSize: 0.01 + value: Settings.data.dock.size + onMoved: value => Settings.data.dock.size = value + text: Math.floor(Settings.data.dock.size * 100) + "%" + } + } + + NToggle { + visible: Settings.data.dock.enabled + Layout.fillWidth: true + label: I18n.tr("panels.dock.monitors-only-same-monitor-label") + description: I18n.tr("panels.dock.monitors-only-same-monitor-description") + checked: Settings.data.dock.onlySameOutput + onToggled: checked => Settings.data.dock.onlySameOutput = checked + } + + NToggle { + visible: Settings.data.dock.enabled + Layout.fillWidth: true + label: I18n.tr("panels.dock.appearance-colorize-icons-label") + description: I18n.tr("panels.dock.appearance-colorize-icons-description") + checked: Settings.data.dock.colorizeIcons + onToggled: checked => Settings.data.dock.colorizeIcons = checked + } + + NHeader { + visible: Settings.data.dock.enabled + label: I18n.tr("panels.dock.monitors-title") + description: I18n.tr("panels.dock.monitors-desc") + } + + Repeater { + visible: Settings.data.dock.enabled + model: Quickshell.screens || [] + delegate: NCheckbox { + Layout.fillWidth: true + readonly property real compositorScale: { + const info = CompositorService.displayScales[modelData.name]; + return (info && info.scale) ? info.scale : 1.0; + } + label: modelData.name || "Unknown" + visible: Settings.data.dock.enabled + description: { + I18n.tr("system.monitor-description", { + "model": modelData.model, + "width": modelData.width * compositorScale, + "height": modelData.height * compositorScale, + "scale": compositorScale + }); + } + checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1 + onToggled: checked => { + if (checked) { + const arr = (Settings.data.dock.monitors || []).slice(); + if (arr.indexOf(modelData.name) === -1) + arr.push(modelData.name); + Settings.data.dock.monitors = arr; + } else { + Settings.data.dock.monitors = (Settings.data.dock.monitors || []).filter(function (n) { + return n !== modelData.name; + }); + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/SetupWizard/SetupWallpaperStep.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/SetupWizard/SetupWallpaperStep.qml new file mode 100644 index 0000000..fa8c410 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/SetupWizard/SetupWallpaperStep.qml @@ -0,0 +1,463 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + + property string selectedDirectory: "" + property string selectedWallpaper: "" + property string previewCachedPath: "" + + signal directoryChanged(string directory) + signal wallpaperChanged(string wallpaper) + + onSelectedWallpaperChanged: { + if (!selectedWallpaper) { + previewCachedPath = ""; + return; + } + if (ImageCacheService.initialized) { + ImageCacheService.getLarge(selectedWallpaper, Screen.width, Screen.height, function (path, success) { + if (root) + root.previewCachedPath = success ? path : selectedWallpaper; + }); + } else { + previewCachedPath = selectedWallpaper; + } + } + + spacing: Style.marginL + + // Beautiful header with icon + ColumnLayout { + Layout.fillWidth: true + Layout.bottomMargin: Style.marginL + spacing: Style.marginM + + RowLayout { + spacing: Style.marginM + + Rectangle { + width: 40 + height: 40 + radius: Style.radiusL + color: Color.mSurfaceVariant + opacity: 0.6 + + NIcon { + icon: "image" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + spacing: Style.marginXS + + NText { + text: I18n.tr("setup.wallpaper.header") + pointSize: Style.fontSizeXL + font.weight: Style.fontWeightBold + color: Color.mPrimary + } + + NText { + text: I18n.tr("setup.wallpaper.subheader") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + } + } + } + + // Large preview area + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: 180 + color: Color.mSurfaceVariant + radius: Style.radiusL + + // Image with rounded corners + NImageRounded { + anchors.fill: parent + visible: previewCachedPath !== "" + imagePath: previewCachedPath + radius: Style.radiusL + borderColor: previewCachedPath !== "" ? Color.mPrimary : Color.mOutline + borderWidth: previewCachedPath !== "" ? 2 : 1 + imageFillMode: Image.PreserveAspectCrop + } + + ColumnLayout { + anchors.centerIn: parent + spacing: Style.marginL + visible: selectedWallpaper === "" + + Rectangle { + Layout.alignment: Qt.AlignHCenter + width: 64 + height: 64 + radius: width / 2 + color: Color.mPrimary + + NIcon { + icon: "sparkles" + pointSize: Style.fontSizeXXL + color: Color.mOnPrimary + anchors.centerIn: parent + } + } + + NText { + text: I18n.tr("setup.wallpaper.select-prompt") + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + font.weight: Style.fontWeightMedium + } + } + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + // Wallpaper gallery strip + Item { + Layout.fillWidth: true + Layout.preferredHeight: 92 + visible: filteredWallpapers.length > 0 + + NScrollView { + id: galleryScroll + anchors.fill: parent + horizontalPolicy: ScrollBar.AsNeeded + verticalPolicy: ScrollBar.AlwaysOff + showGradientMasks: false + reserveScrollbarSpace: false + + // Enable vertical mouse wheel to scroll the horizontal strip by moving contentX + WheelHandler { + target: galleryScroll.contentItem + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onWheel: event => { + const flick = galleryScroll.contentItem; + if (!flick) + return; + const delta = event.pixelDelta.x !== 0 || event.pixelDelta.y !== 0 ? (event.pixelDelta.y !== 0 ? event.pixelDelta.y : event.pixelDelta.x) : (event.angleDelta.y !== 0 ? event.angleDelta.y : event.angleDelta.x); + // Move opposite of wheel to scroll content to the right for wheel down + const step = -delta; + const maxX = Math.max(0, flick.contentWidth - flick.width); + let newX = flick.contentX + step; + if (newX < 0) + newX = 0; + if (newX > maxX) + newX = maxX; + flick.contentX = newX; + event.accepted = true; + } + } + + RowLayout { + spacing: Style.marginM + height: parent.height + + Repeater { + model: filteredWallpapers + delegate: Item { + id: thumbDelegate + readonly property int baseWidth: 120 + readonly property int baseHeight: 80 + readonly property int thumbRadius: Style.radiusM + property string cachedPath: "" + + Layout.preferredWidth: baseWidth + Layout.preferredHeight: baseHeight + + Component.onCompleted: { + if (ImageCacheService.initialized) { + ImageCacheService.getThumbnail(modelData, function (path, success) { + if (thumbDelegate) + thumbDelegate.cachedPath = success ? path : modelData; + }); + } else { + cachedPath = modelData; + } + } + + // Rounded thumbnail using NImageRounded with caching + NImageRounded { + id: thumbImage + anchors.fill: parent + imagePath: thumbDelegate.cachedPath + radius: thumbDelegate.thumbRadius + borderColor: selectedWallpaper === modelData ? Color.mPrimary : Color.mOutline + borderWidth: selectedWallpaper === modelData ? 2 : 1 + imageFillMode: Image.PreserveAspectCrop + } + + // Loading/error state background + Rectangle { + anchors.fill: parent + color: Color.mSurfaceVariant + radius: thumbDelegate.thumbRadius + visible: thumbImage.status === Image.Loading || thumbImage.status === Image.Error || thumbDelegate.cachedPath === "" + + NIcon { + icon: "image" + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + anchors.centerIn: parent + } + } + + NBusyIndicator { + anchors.centerIn: parent + visible: thumbImage.status === Image.Loading || thumbDelegate.cachedPath === "" + running: visible + size: 18 + } + + // Hover overlay + Rectangle { + anchors.fill: parent + radius: thumbDelegate.thumbRadius + color: Color.mPrimary + opacity: hoverHandler.hovered ? 0.1 : 0 + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + + // Selection badge + Rectangle { + visible: selectedWallpaper === modelData + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 6 + width: 24 + height: 24 + radius: width / 2 + color: Color.mPrimary + + NIcon { + icon: "check" + pointSize: Style.fontSizeS + color: Color.mOnPrimary + anchors.centerIn: parent + } + } + + HoverHandler { + id: hoverHandler + } + + TapHandler { + onTapped: { + selectedWallpaper = modelData; + wallpaperChanged(modelData); + } + } + } + } + } + } + } + + // Helpful info card + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 80 + color: Color.mSurfaceVariant + radius: Style.radiusM + opacity: 0.4 + visible: filteredWallpapers.length === 0 + + RowLayout { + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + NIcon { + icon: "folder-open" + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXS + NText { + text: filteredWallpapers.length === 0 && selectedDirectory !== "" ? I18n.tr("setup.wallpaper.none-in-dir") : I18n.tr("setup.wallpaper.no-dir") + pointSize: Style.fontSizeM + font.weight: Style.fontWeightBold + color: Color.mOnSurfaceVariant + } + NText { + text: selectedDirectory !== "" ? I18n.tr("setup.wallpaper.no-valid", { + "dir": selectedDirectory + }) : I18n.tr("setup.wallpaper.choose-dir") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + opacity: 0.8 + } + } + } + } + + // Directory selection + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NTextInputButton { + id: wallpaperPathInput + label: I18n.tr("setup.wallpaper.dir-label") + description: I18n.tr("setup.wallpaper.dir-description") + text: selectedDirectory + buttonIcon: "folder-open" + buttonTooltip: I18n.tr("setup.wallpaper.dir-browse") + Layout.fillWidth: true + onInputTextChanged: text => { + selectedDirectory = text; + directoryChanged(text); + } + onButtonClicked: directoryPicker.open() + } + } + + // Internal properties and functions + property list wallpapersList: [] + property list filteredWallpapers: [] + + function updateFilteredWallpapers() { + filteredWallpapers = wallpapersList; + } + + function refreshWallpapers() { + if (!selectedDirectory || selectedDirectory === "") { + wallpapersList = []; + filteredWallpapers = []; + return; + } + if (typeof WallpaperService !== "undefined" && WallpaperService.getWallpapersList) { + var wallpapers = WallpaperService.getWallpapersList(Screen.name); + wallpapersList = wallpapers; + updateFilteredWallpapers(); + if (wallpapersList.length > 0 && selectedWallpaper === "") { + selectedWallpaper = wallpapersList[0]; + } + } else { + readDirectoryImages(selectedDirectory); + } + } + + function readDirectoryImages(directoryPath) { + // Build find command args dynamically from ImageCacheService filters + var filters = ImageCacheService.imageFilters; + var findArgs = ["find", directoryPath, "-type", "f", "("]; + for (var i = 0; i < filters.length; i++) { + if (i > 0) { + findArgs.push("-o"); + } + findArgs.push("-iname"); + findArgs.push(filters[i]); + } + findArgs.push(")"); + directoryScanner.command = findArgs; + directoryScanner.running = true; + return []; + } + + onSelectedDirectoryChanged: { + if (typeof Settings !== "undefined" && Settings.data && Settings.data.wallpaper) { + Settings.data.wallpaper.directory = selectedDirectory; + } + if (typeof WallpaperService !== "undefined" && WallpaperService.refreshWallpapersList) { + WallpaperService.refreshWallpapersList(); + } + Qt.callLater(refreshWallpapers); + } + + Connections { + target: WallpaperService + enabled: typeof WallpaperService !== "undefined" + function onWallpaperListChanged(screenName, count) { + if (screenName === Screen.name) { + Qt.callLater(refreshWallpapers); + } + } + } + + Timer { + id: initialRefreshTimer + interval: 1000 + running: false + repeat: false + onTriggered: refreshWallpapers() + } + + Component.onCompleted: { + if (typeof Settings !== "undefined" && Settings.data && Settings.data.wallpaper && Settings.data.wallpaper.directory) { + selectedDirectory = Settings.data.wallpaper.directory; + } else { + selectedDirectory = Quickshell.env("HOME") + "/Pictures/Wallpapers"; + } + if (typeof WallpaperService !== "undefined" && WallpaperService.currentWallpaper) { + selectedWallpaper = WallpaperService.currentWallpaper; + } + initialRefreshTimer.start(); + } + + NFilePicker { + id: directoryPicker + selectionMode: "folders" + title: I18n.tr("setup.wallpaper.dir-select-title") + initialPath: selectedDirectory || Quickshell.env("HOME") + "/Pictures" + onAccepted: paths => { + if (paths.length > 0) { + selectedDirectory = paths[0]; + directoryChanged(paths[0]); + } + } + } + + Process { + id: directoryScanner + command: [] + running: false + stdout: StdioCollector {} + stderr: StdioCollector {} + onExited: function (exitCode) { + if (exitCode === 0) { + var lines = stdout.text.split('\n'); + var images = []; + for (var i = 0; i < lines.length; i++) { + var line = lines[i].trim(); + if (line !== '') { + images.push(line); + } + } + wallpapersList = images; + updateFilteredWallpapers(); + if (wallpapersList.length > 0 && selectedWallpaper === "") { + selectedWallpaper = wallpapersList[0]; + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/SetupWizard/SetupWizard.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/SetupWizard/SetupWizard.qml new file mode 100644 index 0000000..849e4dd --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/SetupWizard/SetupWizard.qml @@ -0,0 +1,477 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Modules.MainScreen +import qs.Services.Noctalia +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +SmartPanel { + id: root + + // When true, only shows step 0 with modified text for returning users (telemetry notification) + property bool telemetryOnlyMode: false + + signal telemetryWizardCompleted + + preferredWidth: Math.round(preferredWidthRatio * 2560 * Style.uiScaleRatio) + preferredHeight: Math.round(preferredHeightRatio * 1440 * Style.uiScaleRatio) + preferredWidthRatio: 0.4 + preferredHeightRatio: root.telemetryOnlyMode ? 0.45 : 0.6 + + panelAnchorHorizontalCenter: true + panelAnchorVerticalCenter: true + + closeWithEscape: false + + panelContent: Item { + id: panelContent + + // Wizard state (lazy-loaded with panelContent) + property int currentStep: 0 + readonly property int totalSteps: root.telemetryOnlyMode ? 1 : 5 + property bool isCompleting: false + + // Setup wizard data + property string selectedWallpaperDirectory: Settings.defaultWallpapersDirectory + property string selectedWallpaper: "" + property real selectedScaleRatio: 1.0 + property string selectedBarPosition: "top" + + Component.onCompleted: { + selectedScaleRatio = Settings.data.general.scaleRatio; + selectedBarPosition = Settings.data.bar.position; + selectedWallpaperDirectory = Settings.data.wallpaper.directory || Settings.defaultWallpapersDirectory; + } + + Connections { + target: Settings + function onSettingsSaved() { + if (panelContent.isCompleting) { + Logger.i("SetupWizard", "Settings saved, closing panel"); + panelContent.isCompleting = false; + root.close(); + } + } + } + + Timer { + id: closeTimer + interval: 2000 + onTriggered: { + if (panelContent.isCompleting) { + Logger.w("SetupWizard", "Settings save timeout, closing panel anyway"); + panelContent.isCompleting = false; + root.close(); + } + } + } + + function completeSetup() { + if (isCompleting) { + Logger.w("SetupWizard", "completeSetup() called while already completing, ignoring"); + return; + } + + try { + Logger.i("SetupWizard", root.telemetryOnlyMode ? "Completing telemetry wizard" : "Completing setup with selected options"); + isCompleting = true; + + // In telemetry-only mode, we only need to save the telemetry setting + if (!root.telemetryOnlyMode) { + if (typeof WallpaperService !== "undefined" && WallpaperService.refreshWallpapersList) { + if (selectedWallpaperDirectory !== Settings.data.wallpaper.directory) { + Settings.data.wallpaper.directory = selectedWallpaperDirectory; + WallpaperService.refreshWallpapersList(); + } + + if (selectedWallpaper !== "") { + WallpaperService.changeWallpaper(selectedWallpaper, undefined); + } + } + + Settings.data.general.scaleRatio = selectedScaleRatio; + Settings.data.bar.position = selectedBarPosition; + } + + // Mark the current version as seen to prevent telemetry wizard on next startup + // (only for full setup wizard - telemetry wizard lets changelog mark it seen) + if (!root.telemetryOnlyMode) { + UpdateService.markChangelogSeen(UpdateService.currentVersion); + } + + // Initialize telemetry now that user has made their choice + TelemetryService.init(); + + // Save settings immediately and wait for settingsSaved signal before closing + Settings.saveImmediate(); + Logger.i("SetupWizard", "Setup completed successfully, waiting for settings save confirmation"); + + // Emit signal for telemetry wizard completion (shell.qml will show changelog) + if (root.telemetryOnlyMode) { + root.telemetryWizardCompleted(); + } + + // Fallback: if settingsSaved signal doesn't fire within 2 seconds, close anyway + closeTimer.start(); + } catch (error) { + Logger.e("SetupWizard", "Error completing setup:", error); + isCompleting = false; + } + } + + function applyWallpaperSettings() { + if (typeof WallpaperService !== "undefined" && WallpaperService.refreshWallpapersList) { + if (selectedWallpaperDirectory !== Settings.data.wallpaper.directory) { + Settings.data.wallpaper.directory = selectedWallpaperDirectory; + WallpaperService.refreshWallpapersList(); + } + + if (selectedWallpaper !== "") { + WallpaperService.changeWallpaper(selectedWallpaper, undefined); + } + } + } + + function applyUISettings() { + Settings.data.general.scaleRatio = selectedScaleRatio; + Settings.data.bar.position = selectedBarPosition; + } + + ColumnLayout { + id: wizardContent + anchors.fill: parent + anchors.margins: Style.marginXL + spacing: Style.marginL + + // Step content - takes most of the space + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: Math.round(300 * Style.uiScaleRatio) + + StackLayout { + id: stepStack + anchors.fill: parent + currentIndex: currentStep + + // Step 0: Welcome - Beautiful centered design + Item { + ColumnLayout { + anchors.centerIn: parent + width: Math.round(Math.max(parent.width * 0.5, 420)) + spacing: Style.marginXL + + // Logo with subtle glow effect + Item { + Layout.fillWidth: true + Layout.preferredHeight: 120 + Layout.alignment: Qt.AlignHCenter + + Rectangle { + anchors.centerIn: parent + width: 120 + height: 120 + radius: width / 2 + color: Color.mPrimary + opacity: 0.08 + scale: 1.3 + } + + Image { + anchors.centerIn: parent + width: 110 + height: 110 + source: Qt.resolvedUrl(Quickshell.shellDir + "/Assets/noctalia.svg") + fillMode: Image.PreserveAspectFit + smooth: true + + Rectangle { + anchors.fill: parent + color: Color.mSurfaceVariant + radius: width / 2 + border.color: Color.mOutline + border.width: Style.borderM + visible: parent.status === Image.Error + + NIcon { + icon: "sparkles" + pointSize: Style.fontSizeXXL * 1.5 + color: Color.mPrimary + anchors.centerIn: parent + } + } + + // Subtle pulse animation + SequentialAnimation on scale { + running: true + loops: Animation.Infinite + NumberAnimation { + from: 1.0 + to: 1.05 + duration: 2000 + easing.type: Easing.InOutQuad + } + NumberAnimation { + from: 1.05 + to: 1.0 + duration: 2000 + easing.type: Easing.InOutQuad + } + } + } + } + + // Welcome text with gradient feel + ColumnLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + spacing: Style.marginM + + NText { + text: root.telemetryOnlyMode ? I18n.tr("setup.telemetry-wizard-title") : I18n.tr("setup.welcome-title") + pointSize: Style.fontSizeXXL * 1.4 + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + + NText { + text: root.telemetryOnlyMode ? I18n.tr("setup.telemetry-wizard-subtitle") : I18n.tr("setup.welcome-subtitle") + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + } + + // Friendly subtext + Rectangle { + Layout.fillWidth: true + Layout.topMargin: Style.marginL + Layout.preferredHeight: childrenRect.height + Style.margin2M + color: Color.mSurfaceVariant + radius: Style.radiusL + + NText { + anchors.centerIn: parent + width: parent.width - Style.margin2L + text: root.telemetryOnlyMode ? I18n.tr("setup.telemetry-wizard-note") : I18n.tr("setup.welcome-note") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + } + } + + // Telemetry toggle + NToggle { + Layout.fillWidth: true + Layout.topMargin: Style.marginM + label: I18n.tr("panels.about.telemetry-enabled") + description: I18n.tr("panels.about.telemetry-desc") + checked: Settings.data.general.telemetryEnabled + onToggled: checked => Settings.data.general.telemetryEnabled = checked + } + } + } + } + + // Step 1: Wallpaper Setup + SetupWallpaperStep { + id: step1 + selectedDirectory: panelContent.selectedWallpaperDirectory + selectedWallpaper: panelContent.selectedWallpaper + onDirectoryChanged: function (directory) { + panelContent.selectedWallpaperDirectory = directory; + panelContent.applyWallpaperSettings(); + } + onWallpaperChanged: function (wallpaper) { + panelContent.selectedWallpaper = wallpaper; + panelContent.applyWallpaperSettings(); + } + } + + // Step 2: Appearance - Dark mode and color source + SetupAppearanceStep { + id: step3 + } + + // Step 3: UI Configuration + SetupCustomizeStep { + id: step2 + selectedScaleRatio: panelContent.selectedScaleRatio + selectedBarPosition: panelContent.selectedBarPosition + onScaleRatioChanged: function (ratio) { + panelContent.selectedScaleRatio = ratio; + panelContent.applyUISettings(); + } + onBarPositionChanged: function (position) { + panelContent.selectedBarPosition = position; + panelContent.applyUISettings(); + } + } + + // Step 4: Dock Setup + SetupDockStep { + id: stepDock + } + } + } + + // Elegant divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + visible: !root.telemetryOnlyMode + } + + // Modern progress indicator with labels + Item { + Layout.fillWidth: true + Layout.preferredHeight: 32 + visible: !root.telemetryOnlyMode + + RowLayout { + anchors.centerIn: parent + spacing: Style.marginM + + Repeater { + model: [ + { + "icon": "sparkles", + "label": I18n.tr("setup.welcome") + }, + { + "icon": "image", + "label": I18n.tr("common.wallpaper") + }, + { + "icon": "palette", + "label": I18n.tr("common.appearance") + }, + { + "icon": "settings", + "label": I18n.tr("common.customize") + }, + { + "icon": "device-desktop", + "label": I18n.tr("panels.dock.title") + } + ] + delegate: RowLayout { + spacing: Style.marginS + + Rectangle { + width: 24 + height: 24 + radius: width / 2 + color: index <= currentStep ? Color.mPrimary : Color.mSurfaceVariant + border.color: index === currentStep ? Color.mPrimary : "transparent" + border.width: index === currentStep ? 2 : 0 + + NIcon { + icon: modelData.icon + pointSize: Style.fontSizeS + color: index <= currentStep ? Color.mOnPrimary : Color.mOnSurfaceVariant + anchors.centerIn: parent + } + + Behavior on color { + ColorAnimation { + duration: Style.animationNormal + } + } + } + + NText { + text: modelData.label + pointSize: Style.fontSizeS + color: index <= currentStep ? Color.mPrimary : Color.mOnSurfaceVariant + font.weight: index === currentStep ? Style.fontWeightBold : Style.fontWeightRegular + + Behavior on color { + ColorAnimation { + duration: Style.animationNormal + } + } + } + + // Connector line + Rectangle { + width: 40 + height: 2 + radius: 1 + color: index < currentStep ? Color.mPrimary : Color.mSurfaceVariant + visible: index < totalSteps - 1 + + Behavior on color { + ColorAnimation { + duration: Style.animationNormal + } + } + } + } + } + } + } + + // Smooth navigation buttons + Item { + Layout.fillWidth: true + Layout.preferredHeight: 44 + Layout.topMargin: Style.marginS + + RowLayout { + anchors.fill: parent + spacing: Style.marginM + + NButton { + text: I18n.tr("setup.skip-setup") + outlined: true + visible: !root.telemetryOnlyMode + Layout.preferredHeight: 44 + onClicked: { + panelContent.completeSetup(); + } + } + + Item { + Layout.fillWidth: true + } + + NButton { + text: "โ† " + I18n.tr("common.back") + outlined: true + visible: currentStep > 0 && !root.telemetryOnlyMode + Layout.preferredHeight: 44 + onClicked: { + if (currentStep > 0) { + currentStep--; + } + } + } + + NButton { + text: root.telemetryOnlyMode ? I18n.tr("setup.telemetry-wizard-done") : (currentStep === totalSteps - 1 ? I18n.tr("setup.all-done") : I18n.tr("common.continue") + " โ†’") + Layout.preferredHeight: 44 + onClicked: { + if (currentStep < totalSteps - 1) { + currentStep++; + } else { + panelContent.completeSetup(); + } + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/SystemStats/SystemStatsPanel.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/SystemStats/SystemStatsPanel.qml new file mode 100644 index 0000000..2cc85c1 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/SystemStats/SystemStatsPanel.qml @@ -0,0 +1,403 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.MainScreen +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +SmartPanel { + id: root + + Component.onCompleted: SystemStatService.registerComponent("panel-systemstats") + Component.onDestruction: SystemStatService.unregisterComponent("panel-systemstats") + + preferredWidth: Math.round(440 * Style.uiScaleRatio) + + panelContent: Item { + id: panelContent + property real contentPreferredHeight: mainColumn.implicitHeight + Style.margin2L + readonly property real cardHeight: 90 * Style.uiScaleRatio + + // Get diskPath from bar's SystemMonitor widget if available, otherwise use "/" + readonly property string diskPath: { + const sysMonWidget = BarService.lookupWidget("SystemMonitor"); + if (sysMonWidget && sysMonWidget.diskPath) { + return sysMonWidget.diskPath; + } + return "/"; + } + + ColumnLayout { + id: mainColumn + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + // HEADER + NBox { + Layout.fillWidth: true + implicitHeight: headerRow.implicitHeight + Style.margin2M + + RowLayout { + id: headerRow + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + + NIcon { + icon: "device-analytics" + pointSize: Style.fontSizeXXL + color: Color.mPrimary + } + + NText { + text: I18n.tr("system-monitor.title") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: { + root.close(); + } + } + } + } + + // CPU Card (dual-line: usage % + temperature ยฐC) + NBox { + Layout.fillWidth: true + Layout.preferredHeight: panelContent.cardHeight + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginS + anchors.bottomMargin: Style.radiusM * 0.5 + spacing: Style.marginXS + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NIcon { + icon: "cpu-usage" + pointSize: Style.fontSizeXS + color: Color.mPrimary + } + + NText { + text: `${Math.round(SystemStatService.cpuUsage)}% (${SystemStatService.cpuFreq.replace(/[^0-9.]/g, "")} GHz)` + pointSize: Style.fontSizeXS + color: Color.mPrimary + font.family: Settings.data.ui.fontFixed + } + + NIcon { + icon: "cpu-temperature" + pointSize: Style.fontSizeXS + color: Color.mSecondary + } + + NText { + text: `${Math.round(SystemStatService.cpuTemp)}ยฐC` + pointSize: Style.fontSizeXS + color: Color.mSecondary + font.family: Settings.data.ui.fontFixed + Layout.rightMargin: Style.marginS + } + + Item { + Layout.fillWidth: true + } + + NText { + text: I18n.tr("system-monitor.cpu-usage") + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + } + + NGraph { + Layout.fillWidth: true + Layout.fillHeight: true + values: SystemStatService.cpuHistory + values2: SystemStatService.cpuTempHistory + minValue: 0 + maxValue: 100 + minValue2: Math.max(SystemStatService.cpuTempHistoryMin - 5, 0) + maxValue2: Math.max(SystemStatService.cpuTempHistoryMax + 5, 1) + color: Color.mPrimary + color2: Color.mSecondary + strokeWidth: Math.max(1, Style.uiScaleRatio) + fill: true + fillOpacity: 0.15 + updateInterval: SystemStatService.cpuUsageIntervalMs + } + } + } + + // Memory Card (single-line + optional swap indicator) + NBox { + Layout.fillWidth: true + Layout.preferredHeight: panelContent.cardHeight + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginS + anchors.bottomMargin: Style.radiusM * 0.5 + spacing: Style.marginXS + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NIcon { + icon: "memory" + pointSize: Style.fontSizeXS + color: Color.mPrimary + } + + NText { + text: `${Math.round(SystemStatService.memPercent)}% (${(SystemStatService.memGb).toFixed(1)} GiB)` + pointSize: Style.fontSizeXS + color: Color.mPrimary + font.family: Settings.data.ui.fontFixed + } + + Item { + Layout.fillWidth: true + } + + NText { + text: I18n.tr("common.memory") + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + } + + NGraph { + Layout.fillWidth: true + Layout.fillHeight: true + values: SystemStatService.memHistory + minValue: 0 + maxValue: 100 + color: Color.mPrimary + strokeWidth: Math.max(1, Style.uiScaleRatio) + fill: true + fillOpacity: 0.15 + updateInterval: SystemStatService.memIntervalMs + } + } + } + + // Network Card (dual-line: RX + TX speeds) + NBox { + Layout.fillWidth: true + Layout.preferredHeight: panelContent.cardHeight + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginS + anchors.bottomMargin: Style.radiusM * 0.5 + spacing: Style.marginXS + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NIcon { + icon: "download-speed" + pointSize: Style.fontSizeXS + color: Color.mPrimary + } + + NText { + text: SystemStatService.formatSpeed(SystemStatService.rxSpeed).replace(/([0-9.]+)([A-Za-z]+)/, "$1 $2") + "/s" + pointSize: Style.fontSizeXS + color: Color.mPrimary + font.family: Settings.data.ui.fontFixed + Layout.rightMargin: Style.marginS + } + + NIcon { + icon: "upload-speed" + pointSize: Style.fontSizeXS + color: Color.mSecondary + } + + NText { + text: SystemStatService.formatSpeed(SystemStatService.txSpeed).replace(/([0-9.]+)([A-Za-z]+)/, "$1 $2") + "/s" + pointSize: Style.fontSizeXS + color: Color.mSecondary + font.family: Settings.data.ui.fontFixed + } + + Item { + Layout.fillWidth: true + } + + NText { + text: I18n.tr("common.network") + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + } + + NGraph { + Layout.fillWidth: true + Layout.fillHeight: true + values: SystemStatService.rxSpeedHistory + values2: SystemStatService.txSpeedHistory + minValue: 0 + maxValue: SystemStatService.rxMaxSpeed + minValue2: 0 + maxValue2: SystemStatService.txMaxSpeed + color: Color.mPrimary + color2: Color.mSecondary + strokeWidth: Math.max(1, Style.uiScaleRatio) + fill: true + fillOpacity: 0.15 + updateInterval: SystemStatService.networkIntervalMs + animateScale: true + } + } + } + + // Detailed Stats section + NBox { + Layout.fillWidth: true + implicitHeight: detailsColumn.implicitHeight + Style.margin2M + + ColumnLayout { + id: detailsColumn + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Style.marginM + spacing: Style.marginXS + + // Load Average + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + visible: SystemStatService.nproc > 0 + + NIcon { + icon: "cpu-usage" + pointSize: Style.fontSizeM + color: Color.mPrimary + } + + NText { + text: I18n.tr("system-monitor.load-average") + ":" + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + + NText { + text: `${SystemStatService.loadAvg1.toFixed(2)} โ€ข ${SystemStatService.loadAvg5.toFixed(2)} โ€ข ${SystemStatService.loadAvg15.toFixed(2)}` + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + } + } + + // GPU Temperature (only if available) + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + visible: SystemStatService.gpuAvailable + + NIcon { + icon: "gpu-temperature" + pointSize: Style.fontSizeM + color: Color.mPrimary + } + + NText { + text: I18n.tr("system-monitor.gpu-temp") + ":" + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + + NText { + text: `${Math.round(SystemStatService.gpuTemp)}ยฐC` + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + } + } + + // Disk usage + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NIcon { + icon: "storage" + pointSize: Style.fontSizeM + color: Color.mPrimary + } + + NText { + text: I18n.tr("system-monitor.disk") + ":" + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + + NText { + text: { + const usedGb = SystemStatService.diskUsedGb[panelContent.diskPath] || 0; + const sizeGb = SystemStatService.diskSizeGb[panelContent.diskPath] || 0; + const percent = SystemStatService.diskPercents[panelContent.diskPath] || 0; + return `${percent}% (${usedGb.toFixed(1)} / ${sizeGb.toFixed(1)} GB)`; + } + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + elide: Text.ElideMiddle + } + } + + // Swap details (only visible if swap is enabled) + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + visible: SystemStatService.swapTotalGb > 0 + + NIcon { + icon: "exchange" + pointSize: Style.fontSizeM + color: Color.mPrimary + } + + NText { + text: I18n.tr("bar.system-monitor.swap-usage-label") + ":" + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + + NText { + text: `${(SystemStatService.swapGb).toFixed(1)} / ${(SystemStatService.swapTotalGb).toFixed(1)} GiB` + pointSize: Style.fontSizeXS + color: Color.mOnSurface + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Tray/TrayDrawerPanel.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Tray/TrayDrawerPanel.qml new file mode 100644 index 0000000..e490587 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Tray/TrayDrawerPanel.qml @@ -0,0 +1,289 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.SystemTray +import Quickshell.Widgets +import qs.Commons +import qs.Modules.MainScreen +import qs.Services.UI +import qs.Widgets + +// A compact grid panel listing all tray items, opened from the Tray widget +SmartPanel { + id: root + + // Do not give exclusive focus to the TrayDrawer or it will prevent the dropdown menu to request it. + exclusiveKeyboard: false + + // Widget info for menu functionality (set by Tray widget when opening) + property string widgetSection: "" + property int widgetIndex: -1 + + // Sizing properties must stay at root for preferredWidth/Height + readonly property int maxColumns: 8 + readonly property real cellSize: Math.round(Style.capsuleHeight * 0.65) + readonly property real outerPadding: Style.marginM + readonly property real innerSpacing: Style.marginM + + // All tray items from SystemTray + readonly property var trayValuesAll: (SystemTray.items && SystemTray.items.values) ? SystemTray.items.values : [] + + // Filtered items - computed in panelContent where isPinned is available + property var trayValues: [] + + readonly property int itemCount: trayValues.length + readonly property int columns: Math.max(1, Math.min(maxColumns, itemCount)) + readonly property int rows: Math.max(1, Math.ceil(itemCount / Math.max(1, columns))) + + preferredWidth: (columns * cellSize) + ((columns - 1) * innerSpacing) + (2 * outerPadding) + preferredHeight: (rows * cellSize) + ((rows - 1) * innerSpacing) + (2 * outerPadding) + + // Auto-close drawer when all items are pinned (drawer becomes empty) + onTrayValuesChanged: { + if (visible && trayValues.length === 0) { + close(); + } + } + + // Force refresh panelContent settings when drawer opens + onOpened: { + if (panelContent && panelContent.settingsVersion !== undefined) { + panelContent.settingsVersion++; + } + } + + panelContent: Item { + id: panelContent + + // Settings state (lazy-loaded with panelContent) + property int settingsVersion: 0 + + readonly property var widgetSettings: { + // Reference settingsVersion to force recalculation when it changes + void (settingsVersion); + if (root.widgetSection === "" || root.widgetIndex < 0) + return {}; + var widgets = Settings.getBarWidgetsForScreen(root.screen?.name)[root.widgetSection]; + if (!widgets || root.widgetIndex >= widgets.length) + return {}; + var settings = widgets[root.widgetIndex]; + if (!settings || settings.id !== "Tray") + return {}; + return settings; + } + + readonly property var pinnedList: widgetSettings.pinned || [] + readonly property bool hidePassive: widgetSettings.hidePassive !== undefined ? widgetSettings.hidePassive : true + + // Filter tray items - this runs in panelContent context where isPinned is available + function updateFilteredItems() { + var filtered = []; + for (var i = 0; i < root.trayValuesAll.length; i++) { + var item = root.trayValuesAll[i]; + if (!item) + continue; + + // Filter out passive items if hidePassive is enabled + if (hidePassive && item.status !== undefined && (item.status === SystemTray.Passive || item.status === 0)) { + continue; + } + + // Filter out pinned items + if (isPinned(item)) { + continue; + } + + filtered.push(item); + } + root.trayValues = filtered; + } + + // Update filtered items when dependencies change + Component.onCompleted: updateFilteredItems() + onPinnedListChanged: updateFilteredItems() + onHidePassiveChanged: updateFilteredItems() + + Connections { + target: root + function onTrayValuesAllChanged() { + panelContent.updateFilteredItems(); + } + } + + // Helper functions (lazy-loaded with panelContent) + function wildCardMatch(str, rule) { + if (!str || !rule) + return false; + const placeholder = '\uE000'; + let processedRule = rule.replace(/\*/g, placeholder); + let escaped = processedRule.replace(/[.+?^${}()|[\]\\]/g, '\\$&'); + let pattern = '^' + escaped.replace(new RegExp(placeholder, 'g'), '.*') + '$'; + try { + return new RegExp(pattern, 'i').test(str); + } catch (e) { + return false; + } + } + + function isPinned(item) { + if (!pinnedList || pinnedList.length === 0) + return false; + const title = item?.tooltipTitle || item?.name || item?.id || ""; + for (var i = 0; i < pinnedList.length; i++) { + if (wildCardMatch(title, pinnedList[i])) + return true; + } + return false; + } + + // Popup menu state (lazy-loaded with panelContent) + property int popupMenuUpdateTrigger: 0 + + readonly property var popupMenuWindow: { + void (popupMenuUpdateTrigger); + return PanelService.getPopupMenuWindow(screen); + } + + readonly property var trayMenu: popupMenuWindow ? popupMenuWindow.trayMenuLoader : null + + // Dynamic content sizing + property real contentPreferredWidth: (root.columns * root.cellSize) + ((root.columns - 1) * root.innerSpacing) + (2 * root.outerPadding) + property real contentPreferredHeight: (root.rows * root.cellSize) + ((root.rows - 1) * root.innerSpacing) + (2 * root.outerPadding) + + // Connections (lazy-loaded with panelContent) + Connections { + target: Settings + function onSettingsSaved() { + panelContent.settingsVersion++; + } + } + + Connections { + target: PanelService + function onPopupMenuWindowRegistered(registeredScreen) { + if (registeredScreen === screen) { + panelContent.popupMenuUpdateTrigger++; + } + } + } + + Grid { + id: grid + anchors.fill: parent + anchors.margins: root.outerPadding + spacing: root.innerSpacing + columns: root.columns + rowSpacing: root.innerSpacing + columnSpacing: root.innerSpacing + + Repeater { + id: repeater + model: root.trayValues + + delegate: Item { + width: root.cellSize + height: root.cellSize + + IconImage { + id: trayIcon + anchors.fill: parent + asynchronous: true + backer.fillMode: Image.PreserveAspectFit + source: { + let icon = modelData?.icon || ""; + if (!icon) + return ""; + if (icon.includes("?path=")) { + const chunks = icon.split("?path="); + const name = chunks[0]; + const path = chunks[1]; + const fileName = name.substring(name.lastIndexOf("/") + 1); + return `file://${path}/${fileName}`; + } + return icon; + } + + layer.enabled: panelContent.widgetSettings.colorizeIcons !== false + layer.effect: ShaderEffect { + property color targetColor: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mSurfaceVariant + property real colorizeMode: 1.0 + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + + onClicked: mouse => { + if (!modelData) + return; + if (mouse.button === Qt.LeftButton) { + if (!modelData.onlyMenu) { + modelData.activate(); + } + if ((PanelService.openedPanel !== null) && !PanelService.openedPanel.isClosing) { + PanelService.openedPanel.close(); + } + } else if (mouse.button === Qt.MiddleButton) { + modelData.secondaryActivate && modelData.secondaryActivate(); + if ((PanelService.openedPanel !== null) && !PanelService.openedPanel.isClosing) { + PanelService.openedPanel.close(); + } + } else if (mouse.button === Qt.RightButton) { + TooltipService.hideImmediately(); + + if (panelContent.popupMenuWindow && panelContent.popupMenuWindow.visible) { + panelContent.popupMenuWindow.close(); + return; + } + + if (modelData.hasMenu && modelData.menu && panelContent.trayMenu && panelContent.trayMenu.item) { + const barPosition = Settings.getBarPositionForScreen(root.screen?.name); + // Increased spacing for better alignment with other context menus + let menuX, menuY; + + if (barPosition === "left") { + menuX = trayIcon.width + Style.marginL; + menuY = 0; + } else if (barPosition === "right") { + menuX = -panelContent.trayMenu.item.width - Style.marginL; + menuY = 0; + } else if (barPosition === "bottom") { + // For bottom bar: let TrayMenu handle positioning by passing anchorY >= 0 + // TrayMenu will position above the anchor item + menuX = (trayIcon.width / 2) - (panelContent.trayMenu.item.width / 2); + menuY = trayIcon.height + Style.marginL; + } else { + // For top bar: position menu below the icon with more spacing + menuX = (trayIcon.width / 2) - (panelContent.trayMenu.item.width / 2); + menuY = trayIcon.height + Style.marginL; + } + + PanelService.showTrayMenu(root.screen, modelData, panelContent.trayMenu.item, trayIcon, menuX, menuY, root.widgetSection, root.widgetIndex); + } + } + } + + onWheel: wheel => { + if (wheel.angleDelta.y > 0) + modelData?.scrollUp(); + else if (wheel.angleDelta.y < 0) + modelData?.scrollDown(); + } + + onEntered: { + if (panelContent.popupMenuWindow) { + panelContent.popupMenuWindow.close(); + } + TooltipService.show(trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection(root.screen?.name)); + } + onExited: TooltipService.hide() + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Wallpaper/WallhavenSettingsPopup.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Wallpaper/WallhavenSettingsPopup.qml new file mode 100644 index 0000000..f19858a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Wallpaper/WallhavenSettingsPopup.qml @@ -0,0 +1,993 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.UI +import qs.Widgets + +Popup { + id: root + + property ShellScreen screen + + // Measure the ENV placeholder text at current font settings + TextMetrics { + id: envPlaceholderMetrics + text: I18n.tr("wallpaper.panel.apikey-managed-by-env") + font.pointSize: Style.fontSizeM + } + + // Dynamic width: use measured ENV placeholder width + input padding, or fallback to 440 + width: Math.max(440, Math.round(envPlaceholderMetrics.width + (Style.marginL * 4)), Math.round(contentColumn.implicitWidth + Style.margin2L)) + height: Math.round(contentColumn.implicitHeight + Style.margin2L) + padding: Style.marginL + modal: true + dim: false + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + + function showAt(item) { + open(); + } + + onOpened: { + // Center on screen after popup is opened and parent position is known + if (screen && parent) { + var parentPos = parent.mapToItem(null, 0, 0); + x = Math.round((screen.width - width) / 2 - parentPos.x); + y = Math.round((screen.height - height) / 2 - parentPos.y); + } + Qt.callLater(() => { + if (resolutionWidthInput.inputItem) { + resolutionWidthInput.inputItem.forceActiveFocus(); + } + }); + } + + function hide() { + close(); + } + + function updateResolution(triggerSearch) { + if (typeof WallhavenService === "undefined") { + return; + } + + var width = Settings.data.wallpaper.wallhavenResolutionWidth || ""; + var height = Settings.data.wallpaper.wallhavenResolutionHeight || ""; + var mode = Settings.data.wallpaper.wallhavenResolutionMode || "atleast"; + + if (width && height) { + var resolution = width + "x" + height; + if (mode === "atleast") { + WallhavenService.minResolution = resolution; + WallhavenService.resolutions = ""; + } else { + WallhavenService.minResolution = ""; + WallhavenService.resolutions = resolution; + } + } else { + WallhavenService.minResolution = ""; + WallhavenService.resolutions = ""; + } + + // Trigger new search with updated resolution only if requested + if (triggerSearch && Settings.data.wallpaper.useWallhaven) { + WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", 1); + } + } + + background: Rectangle { + id: backgroundRect + color: Color.mSurface + radius: Style.radiusL + border.color: Color.mOutline + border.width: Style.borderM + + NDropShadow { + source: backgroundRect + } + } + + contentItem: ColumnLayout { + id: contentColumn + spacing: Style.marginM + + // Header + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NIcon { + icon: "settings" + pointSize: Style.fontSizeL + color: Color.mPrimary + } + + NText { + text: I18n.tr("wallpaper.panel.wallhaven-settings-title") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: root.hide() + } + } + + NDivider { + Layout.fillWidth: true + } + + // API Key + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NText { + text: I18n.tr("wallpaper.panel.apikey-label") + color: Color.mOnSurface + pointSize: Style.fontSizeM + } + + NTextInput { + id: apiKeyInput + Layout.fillWidth: true + enabled: !WallhavenService.apiKeyManagedByEnv + placeholderText: WallhavenService.apiKeyManagedByEnv ? I18n.tr("wallpaper.panel.apikey-managed-by-env") : I18n.tr("wallpaper.panel.apikey-placeholder") + text: WallhavenService.apiKeyManagedByEnv ? "" : (Settings.data.wallpaper.wallhavenApiKey || "") + + // Fix for password echo mode + Component.onCompleted: { + if (apiKeyInput.inputItem) { + apiKeyInput.inputItem.echoMode = TextInput.Password; + } + } + + onEditingFinished: { + if (!WallhavenService.apiKeyManagedByEnv) { + Settings.data.wallpaper.wallhavenApiKey = text; + } + } + } + + NText { + text: I18n.tr("wallpaper.panel.apikey-help") + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeS + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + } + + NDivider { + Layout.fillWidth: true + } + + // Sorting + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NText { + text: I18n.tr("wallpaper.panel.sorting-label") + color: Color.mOnSurface + pointSize: Style.fontSizeM + Layout.preferredWidth: implicitWidth + } + + NComboBox { + id: sortingComboBox + Layout.fillWidth: true + Layout.minimumWidth: 200 + model: [ + { + "key": "date_added", + "name": I18n.tr("wallpaper.panel.sorting-date-added") + }, + { + "key": "relevance", + "name": I18n.tr("wallpaper.panel.sorting-relevance") + }, + { + "key": "random", + "name": I18n.tr("common.random") + }, + { + "key": "views", + "name": I18n.tr("wallpaper.panel.sorting-views") + }, + { + "key": "favorites", + "name": I18n.tr("wallpaper.panel.sorting-favorites") + }, + { + "key": "toplist", + "name": I18n.tr("wallpaper.panel.sorting-toplist") + } + ] + currentKey: Settings.data.wallpaper.wallhavenSorting || "relevance" + onSelected: key => { + Settings.data.wallpaper.wallhavenSorting = key; + if (typeof WallhavenService !== "undefined") { + WallhavenService.sorting = key; + WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", 1); + } + } + } + } + + // Order + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + visible: sortingComboBox.currentKey !== "random" + + NText { + text: I18n.tr("wallpaper.panel.order-label") + color: Color.mOnSurface + pointSize: Style.fontSizeM + Layout.preferredWidth: implicitWidth + } + + NComboBox { + id: orderComboBox + Layout.fillWidth: true + Layout.minimumWidth: 200 + model: [ + { + "key": "desc", + "name": I18n.tr("wallpaper.panel.order-desc") + }, + { + "key": "asc", + "name": I18n.tr("wallpaper.panel.order-asc") + } + ] + currentKey: Settings.data.wallpaper.wallhavenOrder || "desc" + onSelected: key => { + Settings.data.wallpaper.wallhavenOrder = key; + if (typeof WallhavenService !== "undefined") { + WallhavenService.order = key; + WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", 1); + } + } + } + } + + // Purity selector + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NText { + text: I18n.tr("wallpaper.panel.purity-label") + color: Color.mOnSurface + pointSize: Style.fontSizeM + Layout.preferredWidth: implicitWidth + } + + Item { + Layout.fillWidth: true + } + + RowLayout { + id: purityRow + spacing: Style.marginL + + function getPurityValue(index) { + var purity = Settings.data.wallpaper.wallhavenPurity || "100"; + return purity.length > index && purity.charAt(index) === "1"; + } + + function updatePurity(sfw, sketchy, nsfw) { + var purity = (sfw ? "1" : "0") + (sketchy ? "1" : "0") + (nsfw ? "1" : "0"); + Settings.data.wallpaper.wallhavenPurity = purity; + // Update checkboxes immediately + sfwToggle.checked = sfw; + sketchyToggle.checked = sketchy; + nsfwToggle.checked = nsfw; + if (typeof WallhavenService !== "undefined") { + WallhavenService.purity = purity; + WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", 1); + } + } + + Connections { + target: Settings.data.wallpaper + function onWallhavenPurityChanged() { + sfwToggle.checked = purityRow.getPurityValue(0); + sketchyToggle.checked = purityRow.getPurityValue(1); + nsfwToggle.checked = purityRow.getPurityValue(2); + } + function onWallhavenApiKeyChanged() { + // If API key is removed (and no ENV key), disable NSFW + if (!WallhavenService.apiKey && nsfwToggle.checked) { + nsfwToggle.toggled(false); + } + } + } + + Component.onCompleted: { + sfwToggle.checked = purityRow.getPurityValue(0); + sketchyToggle.checked = purityRow.getPurityValue(1); + nsfwToggle.checked = purityRow.getPurityValue(2); + } + + // SFW checkbox + Item { + Layout.preferredWidth: sfwCheckboxRow.implicitWidth + Layout.preferredHeight: sfwCheckboxRow.implicitHeight + + RowLayout { + id: sfwCheckboxRow + anchors.fill: parent + spacing: Style.marginS + + NText { + text: I18n.tr("wallpaper.panel.purity-sfw") + color: Color.mOnSurface + pointSize: Style.fontSizeM + } + + Rectangle { + id: sfwBox + implicitWidth: Math.round(Style.baseWidgetSize * 0.7) + implicitHeight: Math.round(Style.baseWidgetSize * 0.7) + radius: Style.radiusXS + color: sfwToggle.checked ? Color.mPrimary : Color.mSurface + border.color: Color.mOutline + border.width: Style.borderS + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + NIcon { + visible: sfwToggle.checked + anchors.centerIn: parent + anchors.horizontalCenterOffset: -1 + icon: "check" + color: Color.mOnPrimary + pointSize: Math.max(Style.fontSizeXS, sfwBox.width * 0.5) + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: sfwToggle.toggled(!sfwToggle.checked) + } + } + } + } + + // Sketchy checkbox + Item { + Layout.preferredWidth: sketchyCheckboxRow.implicitWidth + Layout.preferredHeight: sketchyCheckboxRow.implicitHeight + + RowLayout { + id: sketchyCheckboxRow + anchors.fill: parent + spacing: Style.marginS + + NText { + text: I18n.tr("wallpaper.panel.purity-sketchy") + color: Color.mOnSurface + pointSize: Style.fontSizeM + } + + Rectangle { + id: sketchyBox + implicitWidth: Math.round(Style.baseWidgetSize * 0.7) + implicitHeight: Math.round(Style.baseWidgetSize * 0.7) + radius: Style.radiusXS + color: sketchyToggle.checked ? Color.mPrimary : Color.mSurface + border.color: Color.mOutline + border.width: Style.borderS + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + NIcon { + visible: sketchyToggle.checked + anchors.centerIn: parent + anchors.horizontalCenterOffset: -1 + icon: "check" + color: Color.mOnPrimary + pointSize: Math.max(Style.fontSizeXS, sketchyBox.width * 0.5) + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: sketchyToggle.toggled(!sketchyToggle.checked) + } + } + } + } + + // NSFW checkbox + Item { + Layout.preferredWidth: nsfwCheckboxRow.implicitWidth + Layout.preferredHeight: nsfwCheckboxRow.implicitHeight + visible: WallhavenService.apiKey !== "" + + RowLayout { + id: nsfwCheckboxRow + anchors.fill: parent + spacing: Style.marginS + + NText { + text: I18n.tr("wallpaper.panel.purity-nsfw") + color: Color.mOnSurface + pointSize: Style.fontSizeM + } + + Rectangle { + id: nsfwBox + implicitWidth: Math.round(Style.baseWidgetSize * 0.7) + implicitHeight: Math.round(Style.baseWidgetSize * 0.7) + radius: Style.radiusXS + color: nsfwToggle.checked ? Color.mPrimary : Color.mSurface + border.color: Color.mOutline + border.width: Style.borderS + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + NIcon { + visible: nsfwToggle.checked + anchors.centerIn: parent + anchors.horizontalCenterOffset: -1 + icon: "check" + color: Color.mOnPrimary + pointSize: Math.max(Style.fontSizeXS, nsfwBox.width * 0.5) + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: nsfwToggle.toggled(!nsfwToggle.checked) + } + } + } + } + + // Invisible objects for logic + QtObject { + id: sfwToggle + property bool checked: false + signal toggled(bool checked) + onToggled: checked => { + purityRow.updatePurity(checked, purityRow.getPurityValue(1), purityRow.getPurityValue(2)); + } + } + + QtObject { + id: sketchyToggle + property bool checked: false + signal toggled(bool checked) + onToggled: checked => { + purityRow.updatePurity(purityRow.getPurityValue(0), checked, purityRow.getPurityValue(2)); + } + } + + QtObject { + id: nsfwToggle + property bool checked: false + signal toggled(bool checked) + onToggled: checked => { + purityRow.updatePurity(purityRow.getPurityValue(0), purityRow.getPurityValue(1), checked); + } + } + } + } + + // Aspect Ratio + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NText { + text: I18n.tr("wallpaper.panel.ratios-label") + color: Color.mOnSurface + pointSize: Style.fontSizeM + Layout.preferredWidth: implicitWidth + } + + NComboBox { + id: ratioComboBox + Layout.fillWidth: true + Layout.minimumWidth: 200 + model: [ + { + "key": "", + "name": I18n.tr("wallpaper.panel.ratios-any") + }, + { + "key": "landscape", + "name": I18n.tr("wallpaper.panel.ratios-all-wide") + }, + { + "key": "portrait", + "name": I18n.tr("wallpaper.panel.ratios-all-portrait") + }, + { + "key": "16x9", + "name": "16x9" + }, + { + "key": "16x10", + "name": "16x10" + }, + { + "key": "21x9", + "name": "21x9" + }, + { + "key": "32x9", + "name": "32x9" + }, + { + "key": "48x9", + "name": "48x9" + }, + { + "key": "9x16", + "name": "9x16" + }, + { + "key": "10x16", + "name": "10x16" + }, + { + "key": "9x18", + "name": "9x18" + }, + { + "key": "1x1", + "name": "1x1" + }, + { + "key": "3x2", + "name": "3x2" + }, + { + "key": "4x3", + "name": "4x3" + }, + { + "key": "5x4", + "name": "5x4" + } + ] + currentKey: Settings.data.wallpaper.wallhavenRatios || "" + onSelected: key => { + Settings.data.wallpaper.wallhavenRatios = key; + if (typeof WallhavenService !== "undefined") { + WallhavenService.ratios = key; + WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", 1); + } + } + } + } + + // Categories + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NText { + text: I18n.tr("wallpaper.panel.categories-label") + color: Color.mOnSurface + pointSize: Style.fontSizeM + Layout.preferredWidth: implicitWidth + } + + Item { + Layout.fillWidth: true + } + + RowLayout { + id: categoriesRow + spacing: Style.marginL + + function getCategoryValue(index) { + var cats = Settings.data.wallpaper.wallhavenCategories || "111"; + return cats.length > index && cats.charAt(index) === "1"; + } + + function updateCategories(general, anime, people) { + var categories = (general ? "1" : "0") + (anime ? "1" : "0") + (people ? "1" : "0"); + Settings.data.wallpaper.wallhavenCategories = categories; + // Update checkboxes immediately + generalToggle.checked = general; + animeToggle.checked = anime; + peopleToggle.checked = people; + if (typeof WallhavenService !== "undefined") { + WallhavenService.categories = categories; + WallhavenService.search(Settings.data.wallpaper.wallhavenQuery, 1); + } + } + + Connections { + target: Settings.data.wallpaper + function onWallhavenCategoriesChanged() { + generalToggle.checked = categoriesRow.getCategoryValue(0); + animeToggle.checked = categoriesRow.getCategoryValue(1); + peopleToggle.checked = categoriesRow.getCategoryValue(2); + } + } + + Component.onCompleted: { + generalToggle.checked = categoriesRow.getCategoryValue(0); + animeToggle.checked = categoriesRow.getCategoryValue(1); + peopleToggle.checked = categoriesRow.getCategoryValue(2); + } + + // General checkbox + Item { + Layout.preferredWidth: generalCheckboxRow.implicitWidth + Layout.preferredHeight: generalCheckboxRow.implicitHeight + + RowLayout { + id: generalCheckboxRow + anchors.fill: parent + spacing: Style.marginS + + NText { + text: I18n.tr("common.general") + color: Color.mOnSurface + pointSize: Style.fontSizeM + } + + Rectangle { + id: generalBox + implicitWidth: Math.round(Style.baseWidgetSize * 0.7) + implicitHeight: Math.round(Style.baseWidgetSize * 0.7) + radius: Style.radiusXS + color: generalToggle.checked ? Color.mPrimary : Color.mSurface + border.color: Color.mOutline + border.width: Style.borderS + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + NIcon { + visible: generalToggle.checked + anchors.centerIn: parent + anchors.horizontalCenterOffset: -1 + icon: "check" + color: Color.mOnPrimary + pointSize: Math.max(Style.fontSizeXS, generalBox.width * 0.5) + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: generalToggle.toggled(!generalToggle.checked) + } + } + } + } + + // Anime checkbox + Item { + Layout.preferredWidth: animeCheckboxRow.implicitWidth + Layout.preferredHeight: animeCheckboxRow.implicitHeight + + RowLayout { + id: animeCheckboxRow + anchors.fill: parent + spacing: Style.marginS + + NText { + text: I18n.tr("wallpaper.panel.categories-anime") + color: Color.mOnSurface + pointSize: Style.fontSizeM + } + + Rectangle { + id: animeBox + implicitWidth: Math.round(Style.baseWidgetSize * 0.7) + implicitHeight: Math.round(Style.baseWidgetSize * 0.7) + radius: Style.radiusXS + color: animeToggle.checked ? Color.mPrimary : Color.mSurface + border.color: Color.mOutline + border.width: Style.borderS + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + NIcon { + visible: animeToggle.checked + anchors.centerIn: parent + anchors.horizontalCenterOffset: -1 + icon: "check" + color: Color.mOnPrimary + pointSize: Math.max(Style.fontSizeXS, animeBox.width * 0.5) + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: animeToggle.toggled(!animeToggle.checked) + } + } + } + } + + // People checkbox + Item { + Layout.preferredWidth: peopleCheckboxRow.implicitWidth + Layout.preferredHeight: peopleCheckboxRow.implicitHeight + + RowLayout { + id: peopleCheckboxRow + anchors.fill: parent + spacing: Style.marginS + + NText { + text: I18n.tr("wallpaper.panel.categories-people") + color: Color.mOnSurface + pointSize: Style.fontSizeM + } + + Rectangle { + id: peopleBox + implicitWidth: Math.round(Style.baseWidgetSize * 0.7) + implicitHeight: Math.round(Style.baseWidgetSize * 0.7) + radius: Style.radiusXS + color: peopleToggle.checked ? Color.mPrimary : Color.mSurface + border.color: Color.mOutline + border.width: Style.borderS + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + NIcon { + visible: peopleToggle.checked + anchors.centerIn: parent + anchors.horizontalCenterOffset: -1 + icon: "check" + color: Color.mOnPrimary + pointSize: Math.max(Style.fontSizeXS, peopleBox.width * 0.5) + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: peopleToggle.toggled(!peopleToggle.checked) + } + } + } + } + + // Invisible checkboxes to maintain the signal handlers + QtObject { + id: generalToggle + property bool checked: false + signal toggled(bool checked) + onToggled: checked => { + categoriesRow.updateCategories(checked, categoriesRow.getCategoryValue(1), categoriesRow.getCategoryValue(2)); + } + } + + QtObject { + id: animeToggle + property bool checked: false + signal toggled(bool checked) + onToggled: checked => { + categoriesRow.updateCategories(categoriesRow.getCategoryValue(0), checked, categoriesRow.getCategoryValue(2)); + } + } + + QtObject { + id: peopleToggle + property bool checked: false + signal toggled(bool checked) + onToggled: checked => { + categoriesRow.updateCategories(categoriesRow.getCategoryValue(0), categoriesRow.getCategoryValue(1), checked); + } + } + } + } + + // Resolution filter + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NText { + text: I18n.tr("wallpaper.panel.resolution-label") + color: Color.mOnSurface + pointSize: Style.fontSizeM + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NText { + text: I18n.tr("wallpaper.panel.resolution-mode-label") + color: Color.mOnSurface + pointSize: Style.fontSizeM + Layout.preferredWidth: implicitWidth + } + + NComboBox { + id: resolutionModeComboBox + Layout.fillWidth: true + Layout.minimumWidth: 200 + model: [ + { + "key": "atleast", + "name": I18n.tr("wallpaper.panel.resolution-atleast") + }, + { + "key": "exact", + "name": I18n.tr("wallpaper.panel.resolution-exact") + } + ] + currentKey: Settings.data.wallpaper.wallhavenResolutionMode || "atleast" + + Connections { + target: Settings.data.wallpaper + function onWallhavenResolutionModeChanged() { + if (resolutionModeComboBox.currentKey !== Settings.data.wallpaper.wallhavenResolutionMode) { + resolutionModeComboBox.currentKey = Settings.data.wallpaper.wallhavenResolutionMode || "atleast"; + } + } + } + + onSelected: key => { + Settings.data.wallpaper.wallhavenResolutionMode = key; + updateResolution(false); + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NTextInput { + id: resolutionWidthInput + Layout.preferredWidth: 80 + placeholderText: I18n.tr("common.width") + inputMethodHints: Qt.ImhDigitsOnly + text: Settings.data.wallpaper.wallhavenResolutionWidth || "" + + Component.onCompleted: { + if (resolutionWidthInput.inputItem) { + resolutionWidthInput.inputItem.focusPolicy = Qt.StrongFocus; + // Ensure the TextField can receive keyboard input + resolutionWidthInput.inputItem.activeFocusOnPress = true; + } + } + + // Ensure focus when clicked + onActiveFocusChanged: { + if (activeFocus && resolutionWidthInput.inputItem) { + resolutionWidthInput.inputItem.forceActiveFocus(); + } + } + + Connections { + target: Settings.data.wallpaper + function onWallhavenResolutionWidthChanged() { + if (resolutionWidthInput.text !== Settings.data.wallpaper.wallhavenResolutionWidth) { + resolutionWidthInput.text = Settings.data.wallpaper.wallhavenResolutionWidth || ""; + } + } + } + + onEditingFinished: { + Settings.data.wallpaper.wallhavenResolutionWidth = text; + updateResolution(false); + } + } + + NText { + text: "ร—" + color: Color.mOnSurface + pointSize: Style.fontSizeM + Layout.preferredWidth: implicitWidth + } + + NTextInput { + id: resolutionHeightInput + Layout.preferredWidth: 80 + placeholderText: I18n.tr("common.height") + inputMethodHints: Qt.ImhDigitsOnly + text: Settings.data.wallpaper.wallhavenResolutionHeight || "" + + Component.onCompleted: { + if (resolutionHeightInput.inputItem) { + resolutionHeightInput.inputItem.focusPolicy = Qt.StrongFocus; + resolutionHeightInput.inputItem.activeFocusOnPress = true; + } + } + + // Ensure focus when clicked + onActiveFocusChanged: { + if (activeFocus && resolutionHeightInput.inputItem) { + resolutionHeightInput.inputItem.forceActiveFocus(); + } + } + + Connections { + target: Settings.data.wallpaper + function onWallhavenResolutionHeightChanged() { + if (resolutionHeightInput.text !== Settings.data.wallpaper.wallhavenResolutionHeight) { + resolutionHeightInput.text = Settings.data.wallpaper.wallhavenResolutionHeight || ""; + } + } + } + + onEditingFinished: { + Settings.data.wallpaper.wallhavenResolutionHeight = text; + updateResolution(false); + } + } + } + } + + // Apply button + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginM + } + + NButton { + Layout.fillWidth: true + text: I18n.tr("common.apply") + onClicked: { + // Ensure all settings are synced to the service + if (typeof WallhavenService !== "undefined" && Settings.data.wallpaper.useWallhaven) { + // Sync all settings to the service + WallhavenService.categories = Settings.data.wallpaper.wallhavenCategories; + WallhavenService.purity = Settings.data.wallpaper.wallhavenPurity; + WallhavenService.sorting = Settings.data.wallpaper.wallhavenSorting; + WallhavenService.order = Settings.data.wallpaper.wallhavenOrder; + WallhavenService.ratios = Settings.data.wallpaper.wallhavenRatios; + WallhavenService.apiKey = Settings.data.wallpaper.wallhavenApiKey; + + // Update resolution settings (without triggering search) + updateResolution(false); + + // Refresh the wallpaper search with current settings + WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", 1); + + // Close the popup after applying (delay to prevent click propagation) + Qt.callLater(() => { + root.hide(); + }); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Panels/Wallpaper/WallpaperPanel.qml b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Wallpaper/WallpaperPanel.qml new file mode 100644 index 0000000..a183fdc --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Panels/Wallpaper/WallpaperPanel.qml @@ -0,0 +1,1937 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.MainScreen +import qs.Modules.Panels.Settings +import qs.Services.Theming +import qs.Services.UI +import qs.Widgets + +SmartPanel { + id: root + + preferredWidth: 800 * Style.uiScaleRatio + preferredHeight: 650 * Style.uiScaleRatio + preferredWidthRatio: 0.5 + preferredHeightRatio: 0.5 + + // Positioning + readonly property string screenBarPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property string panelPosition: { + if (Settings.data.wallpaper.panelPosition === "follow_bar") { + if (screenBarPosition === "left" || screenBarPosition === "right") { + return `center_${screenBarPosition}`; + } else { + return `${screenBarPosition}_center`; + } + } else { + return Settings.data.wallpaper.panelPosition; + } + } + panelAnchorHorizontalCenter: panelPosition === "center" || panelPosition.endsWith("_center") + panelAnchorVerticalCenter: panelPosition === "center" + panelAnchorLeft: panelPosition !== "center" && panelPosition.endsWith("_left") + panelAnchorRight: panelPosition !== "center" && panelPosition.endsWith("_right") + panelAnchorBottom: panelPosition.startsWith("bottom_") + panelAnchorTop: panelPosition.startsWith("top_") + + // Store direct reference to content for instant access + property var contentItem: null + + // Override keyboard handlers to enable grid navigation + function onDownPressed() { + if (!contentItem) + return; + let view = contentItem.screenRepeater.itemAt(contentItem.currentScreenIndex); + if (view?.gridView) { + if (!view.gridView.hasActiveFocus) { + view.gridView.forceActiveFocus(); + if (view.gridView.currentIndex < 0 && view.gridView.model.length > 0) { + view.gridView.currentIndex = 0; + } + } else { + if (view.gridView.currentIndex < 0 && view.gridView.model.length > 0) { + view.gridView.currentIndex = 0; + } else { + view.gridView.moveCurrentIndexDown(); + } + } + } + } + + function onUpPressed() { + if (!contentItem) + return; + let view = contentItem.screenRepeater.itemAt(contentItem.currentScreenIndex); + if (view?.gridView?.hasActiveFocus) { + if (view.gridView.currentIndex < 0 && view.gridView.model.length > 0) { + view.gridView.currentIndex = 0; + } else { + view.gridView.moveCurrentIndexUp(); + } + } + } + + function onLeftPressed() { + if (!contentItem) + return; + let view = contentItem.screenRepeater.itemAt(contentItem.currentScreenIndex); + if (view?.gridView?.hasActiveFocus) { + if (view.gridView.currentIndex < 0 && view.gridView.model.length > 0) { + view.gridView.currentIndex = 0; + } else { + view.gridView.moveCurrentIndexLeft(); + } + } + } + + function onRightPressed() { + if (!contentItem) + return; + let view = contentItem.screenRepeater.itemAt(contentItem.currentScreenIndex); + if (view?.gridView?.hasActiveFocus) { + if (view.gridView.currentIndex < 0 && view.gridView.model.length > 0) { + view.gridView.currentIndex = 0; + } else { + view.gridView.moveCurrentIndexRight(); + } + } + } + + function onReturnPressed() { + if (!contentItem) + return; + + // Check if Wallhaven page input has focus + if (contentItem.wallhavenView && contentItem.wallhavenView.visible && contentItem.wallhavenView.pageInput && contentItem.wallhavenView.pageInput.inputItem.activeFocus) { + contentItem.wallhavenView.pageInput.submitPage(); + return; + } + + let view = contentItem.screenRepeater.itemAt(contentItem.currentScreenIndex); + if (view?.gridView?.hasActiveFocus) { + let gridView = view.gridView; + if (gridView.currentIndex >= 0 && gridView.currentIndex < gridView.model.count) { + var item = gridView.model.get(gridView.currentIndex); + view.selectItem(item.path, item.isDirectory); + } + } + } + + function onEnterPressed() { + onReturnPressed(); + } + + panelContent: Rectangle { + id: panelContent + + property alias wallhavenView: wallhavenView + property int currentScreenIndex: { + if (screen !== null) { + for (var i = 0; i < Quickshell.screens.length; i++) { + if (Quickshell.screens[i].name == screen.name) { + return i; + } + } + } + return 0; + } + property var currentScreen: Quickshell.screens[currentScreenIndex] + property string filterText: "" + property int appearanceTabIndex: 0 + readonly property bool headerScreensStripAvailable: !Settings.data.wallpaper.setWallpaperOnAllMonitors || Settings.data.wallpaper.enableMultiMonitorDirectories + readonly property bool headerDevicesButtonVisible: Quickshell.screens.length > 1 || Settings.data.wallpaper.enableMultiMonitorDirectories + property alias screenRepeater: screenRepeater + + Component.onCompleted: { + root.contentItem = panelContent; + } + + // Function to update Wallhaven resolution filter + function updateWallhavenResolution() { + if (typeof WallhavenService === "undefined") { + return; + } + + var width = Settings.data.wallpaper.wallhavenResolutionWidth || ""; + var height = Settings.data.wallpaper.wallhavenResolutionHeight || ""; + var mode = Settings.data.wallpaper.wallhavenResolutionMode || "atleast"; + + if (width && height) { + var resolution = width + "x" + height; + if (mode === "atleast") { + WallhavenService.minResolution = resolution; + WallhavenService.resolutions = ""; + } else { + WallhavenService.minResolution = ""; + WallhavenService.resolutions = resolution; + } + } else { + WallhavenService.minResolution = ""; + WallhavenService.resolutions = ""; + } + + // Trigger new search with updated resolution + if (Settings.data.wallpaper.useWallhaven) { + if (wallhavenView) { + wallhavenView.loading = true; + } + WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", 1); + } + } + + color: "transparent" + + // Wallhaven settings popup + Loader { + id: wallhavenSettingsPopup + source: "WallhavenSettingsPopup.qml" + onLoaded: { + if (item) { + item.screen = screen; + } + } + } + + // Solid color picker dialog + NColorPickerDialog { + id: solidColorPicker + screen: root.screen + selectedColor: Settings.data.wallpaper.solidColor + onColorSelected: color => WallpaperService.setSolidColor(color.toString()) + } + + // Focus management + Connections { + target: root + function onOpened() { + // Ensure contentItem is set + if (!root.contentItem) { + root.contentItem = panelContent; + } + // Reset grid view selections + for (var i = 0; i < screenRepeater.count; i++) { + let item = screenRepeater.itemAt(i); + if (item && item.gridView) { + item.gridView.currentIndex = -1; + } + } + if (wallhavenView && wallhavenView.gridView) { + wallhavenView.gridView.currentIndex = -1; + } + panelContent.appearanceTabIndex = Settings.data.colorSchemes.darkMode ? 1 : 0; + WallpaperService.wallpaperSelectionAppearance = panelContent.appearanceTabIndex === 1 ? "dark" : "light"; + // Give initial focus to search input + Qt.callLater(() => { + if (searchInput.inputItem) { + searchInput.inputItem.forceActiveFocus(); + } + }); + } + } + + // Debounce timer for search + Timer { + id: searchDebounceTimer + interval: 150 + onTriggered: { + panelContent.filterText = searchInput.text; + // Trigger update on all screen views + for (var i = 0; i < screenRepeater.count; i++) { + let item = screenRepeater.itemAt(i); + if (item && item.updateFiltered) { + item.updateFiltered(); + } + } + } + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + // Debounce timer for Wallhaven search + Timer { + id: wallhavenSearchDebounceTimer + interval: 500 + onTriggered: { + Settings.data.wallpaper.wallhavenQuery = searchInput.text; + if (typeof WallhavenService !== "undefined") { + wallhavenView.loading = true; + WallhavenService.search(searchInput.text, 1); + } + } + } + + // Header + NBox { + Layout.fillWidth: true + Layout.preferredHeight: headerColumn.implicitHeight + Style.margin2L + color: Color.mSurfaceVariant + + ColumnLayout { + id: headerColumn + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NIcon { + icon: "settings-wallpaper-selector" + pointSize: Style.fontSizeXXL + color: Color.mPrimary + } + + NText { + text: I18n.tr("wallpaper.panel.title") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + } + + NIconButton { + visible: Settings.data.wallpaper.enabled + icon: "sun" + tooltipText: Settings.data.wallpaper.linkLightAndDarkWallpapers ? I18n.tr("wallpaper.panel.header-sun-linked-tooltip") : I18n.tr("wallpaper.panel.header-sun-separate-tooltip") + baseSize: Style.baseWidgetSize * 0.8 + colorBg: !Settings.data.wallpaper.linkLightAndDarkWallpapers ? Color.mPrimary : Color.smartAlpha(Color.mSurfaceVariant) + colorFg: !Settings.data.wallpaper.linkLightAndDarkWallpapers ? Color.mOnPrimary : Color.mPrimary + onClicked: Settings.data.wallpaper.linkLightAndDarkWallpapers = !Settings.data.wallpaper.linkLightAndDarkWallpapers + } + + NIconButton { + visible: Settings.data.wallpaper.enabled && panelContent.headerDevicesButtonVisible + icon: "devices" + tooltipText: Settings.data.wallpaper.setWallpaperOnAllMonitors ? I18n.tr("wallpaper.panel.header-devices-apply-all-tooltip") : I18n.tr("wallpaper.panel.header-devices-per-monitor-tooltip") + baseSize: Style.baseWidgetSize * 0.8 + colorBg: !Settings.data.wallpaper.setWallpaperOnAllMonitors ? Color.mPrimary : Color.smartAlpha(Color.mSurfaceVariant) + colorFg: !Settings.data.wallpaper.setWallpaperOnAllMonitors ? Color.mOnPrimary : Color.mPrimary + onClicked: Settings.data.wallpaper.setWallpaperOnAllMonitors = !Settings.data.wallpaper.setWallpaperOnAllMonitors + } + + NIconButton { + icon: "palette" + tooltipText: I18n.tr("wallpaper.panel.solid-color-tooltip") + baseSize: Style.baseWidgetSize * 0.8 + colorBg: Settings.data.wallpaper.useSolidColor ? Color.mPrimary : Color.mSurfaceVariant + colorFg: Settings.data.wallpaper.useSolidColor ? Color.mOnPrimary : Color.mPrimary + onClicked: solidColorPicker.open() + } + + NIconButton { + icon: "settings" + tooltipText: I18n.tr("panels.wallpaper.settings-title") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: { + var settingsPanel = PanelService.getPanel("settingsPanel", screen); + settingsPanel.requestedTab = SettingsPanel.Tab.Wallpaper; + settingsPanel.open(); + } + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: root.close() + } + } + + NDivider { + Layout.fillWidth: true + } + + NTabBar { + id: appearanceTabBar + visible: Settings.data.wallpaper.enabled && !Settings.data.wallpaper.linkLightAndDarkWallpapers + Layout.fillWidth: true + currentIndex: panelContent.appearanceTabIndex + spacing: Style.marginM + distributeEvenly: true + + onCurrentIndexChanged: { + if (currentIndex < 0) { + return; + } + panelContent.appearanceTabIndex = currentIndex; + WallpaperService.wallpaperSelectionAppearance = currentIndex === 1 ? "dark" : "light"; + Settings.data.colorSchemes.darkMode = currentIndex === 1; + } + + NTabButton { + text: I18n.tr("wallpaper.panel.appearance-light-tab") + tabIndex: 0 + checked: appearanceTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("wallpaper.panel.appearance-dark-tab") + tabIndex: 1 + checked: appearanceTabBar.currentIndex === 1 + } + } + + NTabBar { + id: screenTabBar + visible: panelContent.headerScreensStripAvailable + Layout.fillWidth: true + currentIndex: currentScreenIndex + onCurrentIndexChanged: currentScreenIndex = currentIndex + spacing: Style.marginM + distributeEvenly: true + + Repeater { + model: Quickshell.screens + NTabButton { + required property var modelData + required property int index + text: modelData.name || `Screen ${index + 1}` + tabIndex: index + checked: { + screenTabBar.currentIndex === index; + } + } + } + } + + // Unified search input and source + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NTextInput { + id: searchInput + placeholderText: Settings.data.wallpaper.useWallhaven ? I18n.tr("placeholders.search-wallhaven") : I18n.tr("placeholders.search-wallpapers") + fontSize: Style.fontSizeM + Layout.fillWidth: true + + property bool initializing: true + Component.onCompleted: { + // Initialize text based on current mode + if (Settings.data.wallpaper.useWallhaven) { + searchInput.text = Settings.data.wallpaper.wallhavenQuery || ""; + } else { + searchInput.text = panelContent.filterText || ""; + } + // Give focus to search input + if (searchInput.inputItem && searchInput.inputItem.visible) { + searchInput.inputItem.forceActiveFocus(); + } + // Mark initialization as complete after a short delay + Qt.callLater(function () { + searchInput.initializing = false; + }); + } + + Connections { + target: Settings.data.wallpaper + function onUseWallhavenChanged() { + // Update text when mode changes + if (Settings.data.wallpaper.useWallhaven) { + searchInput.text = Settings.data.wallpaper.wallhavenQuery || ""; + } else { + searchInput.text = panelContent.filterText || ""; + } + } + } + + onTextChanged: { + // Don't trigger search during initialization - Component.onCompleted will handle initial search + if (initializing) { + return; + } + if (Settings.data.wallpaper.useWallhaven) { + wallhavenSearchDebounceTimer.restart(); + } else { + searchDebounceTimer.restart(); + } + } + + onEditingFinished: { + if (Settings.data.wallpaper.useWallhaven) { + wallhavenSearchDebounceTimer.stop(); + // Only search if the query actually changed + if (typeof WallhavenService !== "undefined" && text !== WallhavenService.currentQuery) { + Settings.data.wallpaper.wallhavenQuery = text; + wallhavenView.loading = true; + WallhavenService.search(text, 1); + } + } + } + + Keys.onPressed: event => { + if (Keybinds.checkKey(event, 'down', Settings)) { + if (Settings.data.wallpaper.useWallhaven) { + if (wallhavenView && wallhavenView.gridView) { + wallhavenView.gridView.forceActiveFocus(); + } + } else { + let currentView = screenRepeater.itemAt(currentScreenIndex); + if (currentView && currentView.gridView) { + currentView.gridView.forceActiveFocus(); + } + } + event.accepted = true; + } + } + } + + NIconButton { + icon: "color-swatch" + tooltipText: Settings.data.colorSchemes.useWallpaperColors ? I18n.tr("wallpaper.panel.color-extraction-enabled") : I18n.tr("wallpaper.panel.color-extraction-disabled") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: { + Settings.data.colorSchemes.useWallpaperColors = !Settings.data.colorSchemes.useWallpaperColors; + if (Settings.data.colorSchemes.useWallpaperColors) { + AppThemeService.generate(); + } else { + ColorSchemeService.setPredefinedScheme(Settings.data.colorSchemes.predefinedScheme); + } + } + } + + NComboBox { + id: colorSchemeComboBox + Layout.fillWidth: false + Layout.minimumWidth: 200 + minimumWidth: 200 + + property bool _initialized: false + property bool _userChanging: false + Component.onCompleted: Qt.callLater(() => { + _initialized = true; + }) + + model: Settings.data.colorSchemes.useWallpaperColors ? TemplateProcessor.schemeTypes : ColorSchemeService.schemes.map(s => ({ + "key": ColorSchemeService.getBasename(s), + "name": ColorSchemeService.getBasename(s) + })) + currentKey: Settings.data.colorSchemes.useWallpaperColors ? Settings.data.colorSchemes.generationMethod : Settings.data.colorSchemes.predefinedScheme + onCurrentKeyChanged: { + if (!_initialized) + return; + if (_userChanging) { + _userChanging = false; + return; + } + schemeGlowAnimation.restart(); + } + onSelected: key => { + _userChanging = true; + if (Settings.data.colorSchemes.useWallpaperColors) { + Settings.data.colorSchemes.generationMethod = key; + AppThemeService.generate(); + } else { + ColorSchemeService.setPredefinedScheme(key); + } + Qt.callLater(() => { + _userChanging = false; + }); + } + + SequentialAnimation { + id: schemeGlowAnimation + NumberAnimation { + target: colorSchemeComboBox + property: "opacity" + to: 0.3 + duration: Style.animationSlow + easing.type: Easing.OutCubic + } + NumberAnimation { + target: colorSchemeComboBox + property: "opacity" + to: 1.0 + duration: Style.animationSlow + easing.type: Easing.InCubic + } + } + } + + NComboBox { + id: sourceComboBox + Layout.fillWidth: false + + model: [ + { + "key": "local", + "name": I18n.tr("common.local") + }, + { + "key": "wallhaven", + "name": I18n.tr("wallpaper.panel.source-wallhaven") + } + ] + currentKey: Settings.data.wallpaper.useWallhaven ? "wallhaven" : "local" + property bool skipNextSelected: false + Component.onCompleted: { + // Skip the first onSelected if it fires during initialization + skipNextSelected = true; + Qt.callLater(function () { + skipNextSelected = false; + }); + } + onSelected: key => { + if (skipNextSelected) { + return; + } + var useWallhaven = (key === "wallhaven"); + Settings.data.wallpaper.useWallhaven = useWallhaven; + // Update search input text based on mode + if (useWallhaven) { + searchInput.text = Settings.data.wallpaper.wallhavenQuery || ""; + } else { + searchInput.text = panelContent.filterText || ""; + } + if (useWallhaven && typeof WallhavenService !== "undefined") { + // Update service properties when switching to Wallhaven + // Don't search here - Component.onCompleted will handle it when the component is created + // This prevents duplicate searches + WallhavenService.categories = Settings.data.wallpaper.wallhavenCategories; + WallhavenService.purity = Settings.data.wallpaper.wallhavenPurity; + WallhavenService.sorting = Settings.data.wallpaper.wallhavenSorting; + WallhavenService.order = Settings.data.wallpaper.wallhavenOrder; + + // Update resolution settings + panelContent.updateWallhavenResolution(); + + // If the view is already initialized, trigger a new search when switching to it + // Preserve current page when switching back to Wallhaven source + if (wallhavenView && wallhavenView.initialized && !WallhavenService.fetching) { + wallhavenView.loading = true; + WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", WallhavenService.currentPage); + } + } + } + } + + // Settings button (only visible for Wallhaven) + NIconButton { + id: wallhavenSettingsButton + icon: "settings" + tooltipText: I18n.tr("wallpaper.panel.wallhaven-settings-title") + baseSize: Style.baseWidgetSize * 0.8 + visible: Settings.data.wallpaper.useWallhaven + onClicked: { + if (searchInput.inputItem) { + searchInput.inputItem.focus = false; + } + if (wallhavenSettingsPopup.item) { + wallhavenSettingsPopup.item.showAt(wallhavenSettingsButton); + } + } + } + } + } + } + + // Content stack: Wallhaven or Local + NBox { + Layout.fillWidth: true + Layout.fillHeight: true + color: Color.mSurfaceVariant + + StackLayout { + id: contentStack + anchors.fill: parent + anchors.margins: Style.marginL + + currentIndex: Settings.data.wallpaper.useWallhaven ? 1 : 0 + + // Local wallpapers + StackLayout { + id: screenStack + currentIndex: currentScreenIndex + + Repeater { + id: screenRepeater + model: Quickshell.screens + delegate: WallpaperScreenView { + targetScreen: modelData + } + } + } + + // Wallhaven wallpapers + WallhavenView { + id: wallhavenView + } + } + } + } + } + + // Component for each screen's wallpaper view + component WallpaperScreenView: Item { + id: wallpaperScreenView + property var targetScreen + property alias gridView: wallpaperGridView + + // Local reactive state for this screen + property list wallpapersList: [] + property string currentWallpaper: "" + property var filteredItems: [] // Combined list of { path, name, isDirectory } + property var wallpapersWithNames: [] // Cached basenames for files + property var directoriesList: [] // List of directories in browse mode + + // ListModel for the grid โ€” enables animated reordering via move() + ListModel { + id: wallpaperModel + } + + // Browse mode properties + property string currentBrowsePath: WallpaperService.getCurrentBrowsePath(targetScreen?.name ?? "") + property bool isBrowseMode: Settings.data.wallpaper.viewMode === "browse" + property int _browseScanGeneration: 0 + + // Favorited paths (any light/dark) first, then the rest + function sortFavoritesToTop(items) { + var favs = []; + var rest = []; + for (var i = 0; i < items.length; i++) { + var it = items[i]; + if (!it.isDirectory && WallpaperService.isFavorite(it.path)) { + favs.push(it); + } else { + rest.push(it); + } + } + return favs.concat(rest); + } + + // Rebuild filteredItems and sync to wallpaperModel (full replacement, no animation). + // When skipSync is true the caller will animate the model itself. + function updateFiltered(skipSync) { + var combinedItems = []; + + // In browse mode, add directories first + if (isBrowseMode) { + for (var i = 0; i < directoriesList.length; i++) { + var dirPath = directoriesList[i]; + combinedItems.push({ + "path": dirPath, + "name": dirPath.split('/').pop(), + "isDirectory": true + }); + } + } + + // Add files + for (var i = 0; i < wallpapersList.length; i++) { + combinedItems.push({ + "path": wallpapersList[i], + "name": wallpapersList[i].split('/').pop(), + "isDirectory": false + }); + } + + combinedItems = sortFavoritesToTop(combinedItems); + + // Apply filter if text is present + if (!panelContent.filterText || panelContent.filterText.trim().length === 0) { + filteredItems = combinedItems; + if (!skipSync) + syncModel(); + return; + } + + const results = FuzzySort.go(panelContent.filterText.trim(), combinedItems, { + "key": 'name', + "limit": 200 + }); + var filtered = results.map(function (r) { + return r.obj; + }); + filteredItems = sortFavoritesToTop(filtered); + if (!skipSync) + syncModel(); + } + + // Copy filteredItems into the ListModel (full rebuild, no animation). + function syncModel() { + wallpaperModel.clear(); + for (var i = 0; i < filteredItems.length; i++) { + wallpaperModel.append(filteredItems[i]); + } + wallpaperGridView.currentIndex = -1; + wallpaperGridView.positionViewAtBeginning(); + } + + // Animate a single item moving to its new position after a favorite toggle. + function handleFavoriteMove(path) { + // Find where the item currently sits in the model + var fromIndex = -1; + for (var i = 0; i < wallpaperModel.count; i++) { + if (wallpaperModel.get(i).path === path) { + fromIndex = i; + break; + } + } + if (fromIndex === -1) + return; + + // Find where it should be in the freshly-computed filteredItems + var toIndex = -1; + for (var j = 0; j < filteredItems.length; j++) { + if (filteredItems[j].path === path) { + toIndex = j; + break; + } + } + if (toIndex === -1 || fromIndex === toIndex) + return; + + wallpaperGridView.animateMovement = true; + wallpaperModel.move(fromIndex, toIndex, 1); + animateMovementResetTimer.restart(); + } + + // Turn off move animations shortly after the move completes so that + // non-favorite model rebuilds (sort, filter, navigation) don't animate. + Timer { + id: animateMovementResetTimer + readonly property int settleDelay: 50 + interval: Style.animationNormal + settleDelay + onTriggered: { + wallpaperGridView.animateMovement = false; + reconcileModel(); + } + } + + // Reorder wallpaperModel to match filteredItems using in-place moves. + // Avoids clear+rebuild which would destroy delegates and flash thumbnails. + function reconcileModel() { + for (var i = 0; i < filteredItems.length; i++) { + var currentPos = -1; + for (var j = i; j < wallpaperModel.count; j++) { + if (wallpaperModel.get(j).path === filteredItems[i].path) { + currentPos = j; + break; + } + } + if (currentPos !== -1 && currentPos !== i) { + wallpaperModel.move(currentPos, i, 1); + } + } + } + + Component.onCompleted: { + refreshWallpaperScreenData(); + } + + Connections { + target: WallpaperService + function onWallpaperChanged(screenName, path) { + if (targetScreen !== null && screenName === targetScreen.name) { + currentWallpaper = WallpaperService.getWallpaperPathForSlot(targetScreen.name, WallpaperService.wallpaperSelectionAppearance); + } + } + function onWallpaperSelectionAppearanceChanged() { + if (targetScreen !== null) { + currentWallpaper = WallpaperService.getWallpaperPathForSlot(targetScreen.name, WallpaperService.wallpaperSelectionAppearance); + updateFiltered(false); + } + } + function onWallpaperDirectoryChanged(screenName, directory) { + if (targetScreen !== null && screenName === targetScreen.name) { + // Reset browse path when root directory changes + if (isBrowseMode) { + WallpaperService.navigateToRoot(targetScreen.name); + } + refreshWallpaperScreenData(); + } + } + function onWallpaperListChanged(screenName, count) { + if (targetScreen !== null && screenName === targetScreen.name) { + refreshWallpaperScreenData(); + } + } + function onBrowsePathChanged(screenName, path) { + if (targetScreen !== null && screenName === targetScreen.name) { + currentBrowsePath = path; + refreshWallpaperScreenData(); + } + } + function onFavoritesChanged(path) { + updateFiltered(true); // recompute filteredItems but skip full model rebuild + handleFavoriteMove(path); // animate the item to its new position + } + } + + function refreshWallpaperScreenData() { + if (targetScreen === null) { + return; + } + + currentWallpaper = WallpaperService.getWallpaperPathForSlot(targetScreen.name, WallpaperService.wallpaperSelectionAppearance); + + if (isBrowseMode) { + // In browse mode, scan current directory for both files and directories + var browsePath = WallpaperService.getCurrentBrowsePath(targetScreen.name); + currentBrowsePath = browsePath; + + // Bump generation so stale scan callbacks from rapid navigation are ignored + var gen = ++_browseScanGeneration; + WallpaperService.scanDirectoryWithDirs(targetScreen.name, browsePath, function (result) { + if (gen !== _browseScanGeneration) + return; // Stale callback from a superseded navigation + wallpapersList = result.files; + directoriesList = result.directories; + Logger.d("WallpaperPanel", "Browse mode: Got", wallpapersList.length, "files and", directoriesList.length, "directories for screen", targetScreen.name); + updateFiltered(); + }); + } else { + // Normal mode: just use the wallpaper list from service + wallpapersList = WallpaperService.getWallpapersList(targetScreen.name); + directoriesList = []; + Logger.d("WallpaperPanel", "Got", wallpapersList.length, "wallpapers for screen", targetScreen.name); + updateFiltered(); + } + } + + function selectItem(path, isDirectory) { + if (isDirectory) { + WallpaperService.setBrowsePath(targetScreen.name, path); + } else { + var screen = Settings.data.wallpaper.setWallpaperOnAllMonitors ? undefined : targetScreen.name; + WallpaperService.changeWallpaper(path, screen, WallpaperService.wallpaperSelectionAppearance); + WallpaperService.applyFavoriteTheme(path, screen, WallpaperService.wallpaperSelectionAppearance); + } + } + + // Helper function to cycle view modes + function cycleViewMode() { + var mode = Settings.data.wallpaper.viewMode; + if (mode === "single") { + Settings.data.wallpaper.viewMode = "recursive"; + } else if (mode === "recursive") { + Settings.data.wallpaper.viewMode = "browse"; + } else { + Settings.data.wallpaper.viewMode = "single"; + } + } + + // Helper function to get icon for current view mode + function getViewModeIcon() { + var mode = Settings.data.wallpaper.viewMode; + if (mode === "single") + return "folder"; + if (mode === "recursive") + return "folders"; + return "folder-open"; + } + + // Helper function to get tooltip for current view mode + function getViewModeTooltip() { + var mode = Settings.data.wallpaper.viewMode; + var modeName; + if (mode === "single") + modeName = I18n.tr("panels.wallpaper.view-mode-single"); + else if (mode === "recursive") + modeName = I18n.tr("panels.wallpaper.view-mode-recursive"); + else + modeName = I18n.tr("panels.wallpaper.view-mode-browse"); + return I18n.tr("panels.wallpaper.view-mode-cycle-tooltip").replace("{mode}", modeName); + } + + ColumnLayout { + anchors.fill: parent + spacing: Style.marginM + + // Combined toolbar: navigation (left) + actions (right) + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + // Left side: navigation (back, home, path) + NIconButton { + icon: "arrow-left" + tooltipText: I18n.tr("wallpaper.browse.go-up") + enabled: isBrowseMode && currentBrowsePath !== WallpaperService.getMonitorDirectory(targetScreen?.name ?? "") + onClicked: WallpaperService.navigateUp(targetScreen?.name ?? "") + baseSize: Style.baseWidgetSize * 0.8 + } + + NIconButton { + icon: "home" + tooltipText: I18n.tr("wallpaper.browse.go-root") + enabled: isBrowseMode && currentBrowsePath !== WallpaperService.getMonitorDirectory(targetScreen?.name ?? "") + onClicked: WallpaperService.navigateToRoot(targetScreen?.name ?? "") + baseSize: Style.baseWidgetSize * 0.8 + } + + NScrollText { + text: isBrowseMode ? currentBrowsePath : WallpaperService.getMonitorDirectory(targetScreen?.name ?? "") + Layout.fillWidth: true + scrollMode: NScrollText.ScrollMode.Hover + fadeCornerRadius: Style.radiusM + + NText { + text: isBrowseMode ? currentBrowsePath : WallpaperService.getMonitorDirectory(targetScreen?.name ?? "") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + + // Right side: actions (view mode, hide filenames, refresh) + NIconButton { + property string sortOrder: Settings.data.wallpaper.sortOrder || "name" + icon: { + if (sortOrder === "date_desc") + return "clock"; + if (sortOrder === "date_asc") + return "history"; + if (sortOrder === "name_desc") + return "sort-descending"; + if (sortOrder === "random") + return "arrows-shuffle"; + return "sort-ascending"; + } + tooltipText: { + if (sortOrder === "date_desc") + return I18n.tr("wallpaper.panel.sort-date-desc"); + if (sortOrder === "date_asc") + return I18n.tr("wallpaper.panel.sort-date-asc"); + if (sortOrder === "name_desc") + return I18n.tr("wallpaper.panel.sort-name-desc"); + if (sortOrder === "random") + return I18n.tr("wallpaper.panel.sort-random"); + return I18n.tr("wallpaper.panel.sort-name-asc"); + } + baseSize: Style.baseWidgetSize * 0.8 + onClicked: { + var next = "name"; + if (sortOrder === "name") + next = "date_desc"; + else if (sortOrder === "date_desc") + next = "name"; // Toggle simpler: Name -> Newest -> Name + // Expanded cycle: Name -> Newest -> Oldest -> Z-A -> Random -> Name + // User just asked for "newest first", so let's make it easy to reach. + // Let's do: Name (A-Z) -> Newest -> Oldest -> Name (Z-A) -> ... + + if (sortOrder === "name") + next = "date_desc"; + else if (sortOrder === "date_desc") + next = "date_asc"; + else if (sortOrder === "date_asc") + next = "name_desc"; + else if (sortOrder === "name_desc") + next = "random"; + else + next = "name"; + + Settings.data.wallpaper.sortOrder = next; + } + } + + NIconButton { + icon: getViewModeIcon() + tooltipText: getViewModeTooltip() + baseSize: Style.baseWidgetSize * 0.8 + onClicked: cycleViewMode() + } + + NIconButton { + icon: Settings.data.wallpaper.hideWallpaperFilenames ? "id-off" : "id" + tooltipText: Settings.data.wallpaper.hideWallpaperFilenames ? I18n.tr("panels.wallpaper.settings-hide-wallpaper-filenames-tooltip-show") : I18n.tr("panels.wallpaper.settings-hide-wallpaper-filenames-tooltip-hide") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: Settings.data.wallpaper.hideWallpaperFilenames = !Settings.data.wallpaper.hideWallpaperFilenames + } + + NIconButton { + icon: Settings.data.wallpaper.showHiddenFiles ? "eye" : "eye-closed" + tooltipText: Settings.data.wallpaper.showHiddenFiles ? I18n.tr("panels.wallpaper.settings-show-hidden-files-tooltip-hide") : I18n.tr("panels.wallpaper.settings-show-hidden-files-tooltip-show") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: Settings.data.wallpaper.showHiddenFiles = !Settings.data.wallpaper.showHiddenFiles + } + + NIconButton { + icon: "refresh" + tooltipText: I18n.tr("tooltips.refresh-wallpaper-list") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: { + if (isBrowseMode) { + refreshWallpaperScreenData(); + } else { + WallpaperService.refreshWallpapersList(); + } + } + } + } + + NGridView { + id: wallpaperGridView + + Layout.fillWidth: true + Layout.fillHeight: true + + visible: !WallpaperService.scanning + interactive: true + keyNavigationEnabled: true + keyNavigationWraps: false + highlightFollowsCurrentItem: false + currentIndex: -1 + + model: wallpaperModel + + Component.onCompleted: { + positionViewAtBeginning(); + } + + property int columns: (screen.width > 1920) ? 5 : 4 + property int itemSize: cellWidth + + cellWidth: Math.floor((availableWidth - leftMargin - rightMargin) / columns) + cellHeight: Math.floor(itemSize * 0.7) + Style.marginXS + Style.fontSizeXS + Style.marginM + + leftMargin: Style.marginS + rightMargin: Style.marginS + topMargin: Style.marginS + bottomMargin: Style.marginS + + onCurrentIndexChanged: { + if (currentIndex >= 0) { + positionViewAtIndex(currentIndex, GridView.Contain); + } + } + + onKeyPressed: event => { + if (Keybinds.checkKey(event, 'enter', Settings)) { + if (currentIndex >= 0 && currentIndex < filteredItems.length) { + selectItem(filteredItems[currentIndex]); + } + event.accepted = true; + } + } + + delegate: Item { + id: wallpaperItemWrapper + width: wallpaperGridView.cellWidth + height: wallpaperGridView.cellHeight + + ColumnLayout { + id: wallpaperItem + anchors.fill: parent + anchors.margins: Style.marginXS + + property string wallpaperPath: model.path ?? "" + property bool isDirectory: model.isDirectory ?? false + property bool isSelected: !isDirectory && (wallpaperPath === currentWallpaper) + property bool isFavorited: { + WallpaperService.favoritesRevision; + return !isDirectory && WallpaperService.isFavorite(wallpaperPath); + } + property string filename: model.name ?? wallpaperPath.split('/').pop() + property string cachedPath: "" + + spacing: Style.marginXS + + Component.onCompleted: { + if (!isDirectory && ImageCacheService.initialized) { + ImageCacheService.getThumbnail(wallpaperPath, function (path, success) { + if (wallpaperItem) + wallpaperItem.cachedPath = success ? path : wallpaperPath; + }); + } else if (!isDirectory) { + cachedPath = wallpaperPath; + } + } + + Item { + id: imageContainer + Layout.fillWidth: true + Layout.fillHeight: true + + property real imageHeight: Math.round(wallpaperGridView.itemSize * 0.67) + + // Directory display + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: imageContainer.imageHeight + color: Color.mSurfaceVariant + radius: Style.radiusM + visible: wallpaperItem.isDirectory + border.color: wallpaperGridView.currentIndex === index ? Color.mHover : Color.mSurface + border.width: Math.max(1, Style.borderL * 1.5) + + ColumnLayout { + anchors.centerIn: parent + spacing: Style.marginS + + NIcon { + icon: "folder" + pointSize: Style.fontSizeXXXL + color: Color.mPrimary + Layout.alignment: Qt.AlignHCenter + } + } + } + + // Image display (for non-directories) + NImageRounded { + id: img + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: imageContainer.imageHeight + visible: !wallpaperItem.isDirectory + imagePath: wallpaperItem.cachedPath + radius: Style.radiusM + borderColor: { + if (wallpaperItem.isSelected) { + return Color.mSecondary; + } + if (wallpaperGridView.currentIndex === index) { + return Color.mHover; + } + return Color.mSurface; + } + borderWidth: Math.max(1, Style.borderL * 1.5) + imageFillMode: Image.PreserveAspectCrop + } + + // Loading/error state background (for non-directories) + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: imageContainer.imageHeight + color: Color.mSurfaceVariant + radius: Style.radiusM + visible: !wallpaperItem.isDirectory && (img.status === Image.Loading || img.status === Image.Error || wallpaperItem.cachedPath === "") + + NIcon { + icon: "image" + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + anchors.centerIn: parent + } + } + + NBusyIndicator { + anchors.horizontalCenter: parent.horizontalCenter + y: (imageContainer.imageHeight - height) / 2 + visible: !wallpaperItem.isDirectory && (img.status === Image.Loading || wallpaperItem.cachedPath === "") + running: visible + size: 18 + } + + Rectangle { + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: Style.marginS + width: 28 + height: 28 + radius: width / 2 + z: 6 + color: Color.mSecondary + border.color: Color.mOutline + border.width: Style.borderS + visible: wallpaperItem.isSelected + + NIcon { + icon: "check" + pointSize: Style.fontSizeM + color: Color.mOnSecondary + anchors.centerIn: parent + } + + MouseArea { + anchors.fill: parent + onClicked: {} + } + } + + // Favorite star button (top-left) + Rectangle { + anchors.top: parent.top + anchors.left: parent.left + anchors.margins: Style.marginS + width: 28 + height: 28 + radius: width / 2 + visible: !wallpaperItem.isDirectory && (wallpaperItem.isFavorited || hoverHandler.hovered || wallpaperGridView.currentIndex === index) + color: { + if (wallpaperItem.isFavorited) + return starHoverHandler.hovered ? Color.mHover : Color.mPrimary; + return starHoverHandler.hovered ? Color.mSurfaceVariant : Color.mSurface; + } + opacity: wallpaperItem.isFavorited || starHoverHandler.hovered ? 1.0 : 0.7 + z: 11 + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + + NIcon { + icon: wallpaperItem.isFavorited ? "star-filled" : "star" + pointSize: Style.fontSizeM + color: { + if (wallpaperItem.isFavorited) + return starHoverHandler.hovered ? Color.mOnHover : Color.mOnPrimary; + return starHoverHandler.hovered ? Color.mOnSurface : Color.mOnSurfaceVariant; + } + anchors.centerIn: parent + } + + HoverHandler { + id: starHoverHandler + } + + TapHandler { + onTapped: { + var mon = Settings.data.wallpaper.setWallpaperOnAllMonitors ? undefined : (wallpaperScreenView.targetScreen ? wallpaperScreenView.targetScreen.name : undefined); + WallpaperService.toggleFavorite(wallpaperItem.wallpaperPath, WallpaperService.wallpaperSelectionAppearance, mon); + } + } + } + + // Palette color dots (bottom-center, favorites only) โ€” taps must not fall through to selectItem + Item { + id: paletteRow + anchors.bottom: img.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottomMargin: Style.marginS + z: 10 + implicitWidth: paletteRowRow.implicitWidth + implicitHeight: paletteRowRow.implicitHeight + width: implicitWidth + height: implicitHeight + visible: wallpaperItem.isFavorited && paletteRow.colors.length > 0 + + property int diameter: 25 * Style.uiScaleRatio + property int _favRevision: 0 + property var favData: { + _favRevision; + WallpaperService.favoritesRevision; + Settings.data.wallpaper.linkLightAndDarkWallpapers; + return WallpaperService.getFavoriteForDisplay(wallpaperItem.wallpaperPath); + } + property var colors: favData && favData.paletteColors ? favData.paletteColors : [] + property bool isDark: { + if (!favData) { + return false; + } + if (favData.appearance === "dark") { + return true; + } + if (favData.appearance === "light") { + return false; + } + return favData.darkMode === true; + } + + Connections { + target: WallpaperService + function onFavoriteDataUpdated(path) { + if (path === wallpaperItem.wallpaperPath) + paletteRow._favRevision++; + } + } + + Row { + id: paletteRowRow + spacing: Style.marginXS + + // Sun/moon only when light/dark share one wallpaper (which appearance the favorite targets) + Rectangle { + width: paletteRow.diameter + height: paletteRow.diameter + radius: width * 0.5 + visible: Settings.data.wallpaper.linkLightAndDarkWallpapers + color: Color.mSurface + border.color: Color.mShadow + border.width: Style.borderS + + NIcon { + icon: paletteRow.isDark ? "moon" : "sun" + pointSize: parent.width * 0.45 + color: Color.mOnSurface + anchors.centerIn: parent + } + } + + Repeater { + model: paletteRow.colors + + Rectangle { + width: paletteRow.diameter + height: paletteRow.diameter + radius: width * 0.5 + color: modelData + border.color: Color.mShadow + border.width: Style.borderS + } + } + } + + TapHandler { + onTapped: {} + } + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: imageContainer.imageHeight + color: Color.mSurface + radius: Style.radiusM + opacity: (hoverHandler.hovered || wallpaperItem.isSelected || wallpaperGridView.currentIndex === index) ? 0 : 0.3 + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + + HoverHandler { + id: hoverHandler + } + + TapHandler { + onTapped: { + wallpaperGridView.forceActiveFocus(); + wallpaperGridView.currentIndex = index; + selectItem(wallpaperItem.wallpaperPath, wallpaperItem.isDirectory); + } + } + } + + NText { + text: wallpaperItem.filename + visible: !Settings.data.wallpaper.hideWallpaperFilenames + color: (hoverHandler.hovered || wallpaperItem.isSelected || wallpaperGridView.currentIndex === index) ? Color.mOnSurface : Color.mOnSurfaceVariant + pointSize: Style.fontSizeXS + Layout.fillWidth: true + Layout.leftMargin: Style.marginS + Layout.rightMargin: Style.marginS + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + } + } + } + } + + // Empty / scanning state + Rectangle { + color: Color.mSurface + radius: Style.radiusM + border.color: Color.mOutline + border.width: Style.borderS + visible: (wallpaperModel.count === 0 && !WallpaperService.scanning) || WallpaperService.scanning + Layout.fillWidth: true + Layout.preferredHeight: 130 + + ColumnLayout { + anchors.fill: parent + visible: WallpaperService.scanning + NBusyIndicator { + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } + } + + ColumnLayout { + anchors.fill: parent + visible: wallpaperModel.count === 0 && !WallpaperService.scanning + Item { + Layout.fillHeight: true + } + NIcon { + icon: "folder-open" + pointSize: Style.fontSizeXXL + color: Color.mOnSurface + Layout.alignment: Qt.AlignHCenter + } + NText { + text: (panelContent.filterText && panelContent.filterText.length > 0) ? I18n.tr("wallpaper.no-match") : (isBrowseMode ? I18n.tr("wallpaper.browse.empty-directory") : I18n.tr("wallpaper.no-wallpaper")) + color: Color.mOnSurface + font.weight: Style.fontWeightBold + Layout.alignment: Qt.AlignHCenter + } + NText { + text: (panelContent.filterText && panelContent.filterText.length > 0) ? I18n.tr("wallpaper.try-different-search") : (isBrowseMode ? I18n.tr("wallpaper.browse.go-up-hint") : I18n.tr("wallpaper.configure-directory")) + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.alignment: Qt.AlignHCenter + } + Item { + Layout.fillHeight: true + } + } + } + } + } + + // Component for Wallhaven wallpapers view + component WallhavenView: Item { + id: wallhavenViewRoot + property alias gridView: wallhavenGridView + property alias pageInput: pageInput + + property var wallpapers: [] + property bool loading: false + property string errorMessage: "" + property bool initialized: false + property bool searchScheduled: false + + Connections { + target: typeof WallhavenService !== "undefined" ? WallhavenService : null + function onSearchCompleted(results, meta) { + wallhavenViewRoot.wallpapers = results || []; + wallhavenViewRoot.loading = false; + wallhavenViewRoot.errorMessage = ""; + wallhavenViewRoot.searchScheduled = false; + } + function onSearchFailed(error) { + wallhavenViewRoot.loading = false; + wallhavenViewRoot.errorMessage = error || ""; + wallhavenViewRoot.searchScheduled = false; + } + } + + Component.onCompleted: { + // Initialize service properties and perform initial search if Wallhaven is active + if (typeof WallhavenService !== "undefined" && Settings.data.wallpaper.useWallhaven && !initialized) { + // Set flags immediately to prevent race conditions + if (WallhavenService.initialSearchScheduled) { + // Another instance already scheduled the search, just initialize properties + initialized = true; + return; + } + + // We're the first one - claim the search + initialized = true; + WallhavenService.initialSearchScheduled = true; + WallhavenService.categories = Settings.data.wallpaper.wallhavenCategories; + WallhavenService.purity = Settings.data.wallpaper.wallhavenPurity; + WallhavenService.sorting = Settings.data.wallpaper.wallhavenSorting; + WallhavenService.order = Settings.data.wallpaper.wallhavenOrder; + + // Initialize resolution settings + var width = Settings.data.wallpaper.wallhavenResolutionWidth || ""; + var height = Settings.data.wallpaper.wallhavenResolutionHeight || ""; + var mode = Settings.data.wallpaper.wallhavenResolutionMode || "atleast"; + if (width && height) { + var resolution = width + "x" + height; + if (mode === "atleast") { + WallhavenService.minResolution = resolution; + WallhavenService.resolutions = ""; + } else { + WallhavenService.minResolution = ""; + WallhavenService.resolutions = resolution; + } + } else { + WallhavenService.minResolution = ""; + WallhavenService.resolutions = ""; + } + + // Now check if we can actually search (fetching check is in WallhavenService.search) + // Use persisted currentPage to maintain state across window reopening + loading = true; + WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", WallhavenService.currentPage); + } + } + + ColumnLayout { + anchors.fill: parent + spacing: Style.marginM + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + NGridView { + id: wallhavenGridView + + anchors.fill: parent + + visible: !loading && errorMessage === "" && (wallpapers && wallpapers.length > 0) + interactive: true + keyNavigationEnabled: true + keyNavigationWraps: false + highlightFollowsCurrentItem: false + currentIndex: -1 + + model: wallpapers || [] + + onModelChanged: { + // Reset selection and scroll position when model changes + currentIndex = -1; + positionViewAtBeginning(); + } + + Component.onCompleted: { + positionViewAtBeginning(); + } + + property int columns: (screen.width > 1920) ? 5 : 4 + property int itemSize: cellWidth + + cellWidth: Math.floor((availableWidth - leftMargin - rightMargin) / columns) + cellHeight: Math.floor(itemSize * 0.7) + Style.marginXS + Style.fontSizeXS + Style.marginM + + leftMargin: Style.marginS + rightMargin: Style.marginS + topMargin: Style.marginS + bottomMargin: Style.marginS + + onCurrentIndexChanged: { + if (currentIndex >= 0) { + positionViewAtIndex(currentIndex, GridView.Contain); + } + } + + onKeyPressed: event => { + if (Keybinds.checkKey(event, 'enter', Settings)) { + if (currentIndex >= 0 && currentIndex < wallpapers.length) { + let wallpaper = wallpapers[currentIndex]; + wallhavenDownloadAndApply(wallpaper); + } + event.accepted = true; + } + } + + delegate: Item { + id: wallhavenItemWrapper + width: wallhavenGridView.cellWidth + height: wallhavenGridView.cellHeight + + ColumnLayout { + id: wallhavenItem + anchors.fill: parent + anchors.margins: Style.marginXS + + property string thumbnailUrl: (modelData && typeof WallhavenService !== "undefined") ? WallhavenService.getThumbnailUrl(modelData, "large") : "" + property string wallpaperId: (modelData && modelData.id) ? modelData.id : "" + + spacing: Style.marginXS + + Item { + id: imageContainer + Layout.fillWidth: true + Layout.fillHeight: true + + property real imageHeight: Math.round(wallhavenGridView.itemSize * 0.67) + + NImageRounded { + id: img + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: imageContainer.imageHeight + imagePath: wallhavenItem.thumbnailUrl + radius: Style.radiusM + borderColor: { + if (wallhavenGridView.currentIndex === index) { + return Color.mHover; + } + return Color.mSurface; + } + borderWidth: Math.max(1, Style.borderL * 1.5) + imageFillMode: Image.PreserveAspectCrop + } + + // Loading/error state background + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: imageContainer.imageHeight + color: Color.mSurfaceVariant + radius: Style.radiusM + visible: img.status === Image.Loading || img.status === Image.Error || wallhavenItem.thumbnailUrl === "" + + NIcon { + icon: "image" + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + anchors.centerIn: parent + } + } + + NBusyIndicator { + anchors.horizontalCenter: parent.horizontalCenter + y: (imageContainer.imageHeight - height) / 2 + visible: img.status === Image.Loading + running: visible + size: 18 + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: imageContainer.imageHeight + color: Color.mSurface + radius: Style.radiusM + opacity: (hoverHandler.hovered || wallhavenGridView.currentIndex === index) ? 0 : 0.3 + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + + HoverHandler { + id: hoverHandler + } + + TapHandler { + onTapped: { + wallhavenGridView.forceActiveFocus(); + wallhavenGridView.currentIndex = index; + wallhavenDownloadAndApply(modelData); + } + } + } + + NText { + text: wallhavenItem.wallpaperId || I18n.tr("common.unknown") + visible: !Settings.data.wallpaper.hideWallpaperFilenames + color: (hoverHandler.hovered || wallhavenGridView.currentIndex === index) ? Color.mOnSurface : Color.mOnSurfaceVariant + pointSize: Style.fontSizeXS + Layout.fillWidth: true + Layout.leftMargin: Style.marginS + Layout.rightMargin: Style.marginS + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + } + } + } + } + + // Loading overlay - fills same space as GridView to prevent jumping + Rectangle { + anchors.fill: parent + color: Color.mSurface + radius: Style.radiusM + border.color: Color.mOutline + border.width: Style.borderS + visible: loading || (typeof WallhavenService !== "undefined" && WallhavenService.fetching) + z: 10 + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + Item { + Layout.fillHeight: true + } + + NBusyIndicator { + size: Style.baseWidgetSize * 1.5 + color: Color.mPrimary + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: I18n.tr("wallpaper.wallhaven.loading") + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeM + Layout.alignment: Qt.AlignHCenter + } + + Item { + Layout.fillHeight: true + } + } + } + + // Error overlay + Rectangle { + anchors.fill: parent + color: Color.mSurface + radius: Style.radiusM + border.color: Color.mOutline + border.width: Style.borderS + visible: errorMessage !== "" && !loading + z: 10 + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + Item { + Layout.fillHeight: true + } + + NIcon { + icon: "alert-circle" + pointSize: Style.fontSizeXXL + color: Color.mError + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: errorMessage + color: Color.mOnSurface + wrapMode: Text.WordWrap + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + + Item { + Layout.fillHeight: true + } + } + } + + // Empty state overlay + Rectangle { + anchors.fill: parent + color: Color.mSurface + radius: Style.radiusM + border.color: Color.mOutline + border.width: Style.borderS + visible: (!wallpapers || wallpapers.length === 0) && !loading && errorMessage === "" + z: 10 + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + Item { + Layout.fillHeight: true + } + + NIcon { + icon: "image" + pointSize: Style.fontSizeXXL + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: I18n.tr("wallpaper.wallhaven.no-results") + color: Color.mOnSurface + wrapMode: Text.WordWrap + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + + Item { + Layout.fillHeight: true + } + } + } + } + + // Pagination + RowLayout { + Layout.fillWidth: true + visible: errorMessage === "" && typeof WallhavenService !== "undefined" + spacing: Style.marginS + + Item { + Layout.fillWidth: true + } + + NIconButton { + icon: "chevron-left" + enabled: !loading && WallhavenService.currentPage > 1 && !WallhavenService.fetching + onClicked: WallhavenService.previousPage() + } + + RowLayout { + spacing: Style.marginXS + + NText { + text: I18n.tr("wallpaper.wallhaven.page-prefix") + color: Color.mOnSurface + } + + NTextInput { + id: pageInput + text: "" + WallhavenService.currentPage + Layout.preferredWidth: 50 * Style.uiScaleRatio + Layout.maximumWidth: 50 * Style.uiScaleRatio + Layout.fillWidth: false + minimumInputWidth: 50 * Style.uiScaleRatio + horizontalAlignment: Text.AlignHCenter + inputMethodHints: Qt.ImhDigitsOnly + enabled: !loading && !WallhavenService.fetching + showClearButton: false + + Connections { + target: WallhavenService + function onCurrentPageChanged() { + pageInput.text = "" + WallhavenService.currentPage; + } + } + + function submitPage() { + var page = parseInt(text); + if (!isNaN(page) && page >= 1 && page <= WallhavenService.lastPage) { + if (page !== WallhavenService.currentPage) { + WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", page); + } + } else { + // Reset to current page if invalid + text = "" + WallhavenService.currentPage; + } + // Force focus loss to ensure UI updates cleanly + pageInput.inputItem.focus = false; + } + + onEditingFinished: submitPage() + } + + NText { + text: I18n.tr("wallpaper.wallhaven.page-suffix").replace("{total}", WallhavenService.lastPage) + color: Color.mOnSurface + } + } + + NIconButton { + icon: "chevron-right" + enabled: WallhavenService.currentPage < WallhavenService.lastPage && !WallhavenService.fetching + onClicked: WallhavenService.nextPage() + } + + Item { + Layout.fillWidth: true + } + } + } + + // ------------------------------- + function wallhavenDownloadAndApply(wallpaper, targetScreen) { + if (typeof WallhavenService !== "undefined") { + WallhavenService.downloadWallpaper(wallpaper, function (success, localPath) { + if (success) { + var whScreen = Settings.data.wallpaper.setWallpaperOnAllMonitors ? undefined : Quickshell.screens[currentScreenIndex].name; + if (!Settings.data.wallpaper.setWallpaperOnAllMonitors && currentScreenIndex < Quickshell.screens.length) { + WallpaperService.changeWallpaper(localPath, Quickshell.screens[currentScreenIndex].name, WallpaperService.wallpaperSelectionAppearance); + } else { + WallpaperService.changeWallpaper(localPath, undefined, WallpaperService.wallpaperSelectionAppearance); + } + WallpaperService.applyFavoriteTheme(localPath, whScreen, WallpaperService.wallpaperSelectionAppearance); + } + }); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Toast/Toast.qml b/arch/.config/quickshell/noctalia-shell/Modules/Toast/Toast.qml new file mode 100644 index 0000000..a3cf7df --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Toast/Toast.qml @@ -0,0 +1,415 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.System +import qs.Widgets + +Item { + id: root + + property string title: "" + property string description: "" + property string icon: "" + property string type: "notice" + property int duration: 3000 + property string actionLabel: "" + property var actionCallback: null + readonly property real initialScale: 0.7 + + signal hidden + + readonly property bool isCompact: Settings.data.notifications?.density === "compact" + readonly property int notificationWidth: Math.round((isCompact ? 320 : 440) * Style.uiScaleRatio) + readonly property int shadowPadding: Style.shadowBlurMax + Style.marginL + + width: notificationWidth + shadowPadding * 2 + height: Math.round(contentLayout.implicitHeight + Style.margin2M * 2 + shadowPadding * 2) + visible: true + opacity: 0 + scale: initialScale + + property real progress: 1.0 + property bool isHovered: false + property real swipeOffset: 0 + property real swipeOffsetY: 0 + property real pressGlobalX: 0 + property real pressGlobalY: 0 + property bool isSwiping: false + readonly property string location: Settings.data.notifications?.location || "top_right" + readonly property bool isLeft: location.endsWith("_left") + readonly property bool isRight: location.endsWith("_right") + readonly property bool useVerticalSwipe: location === "bottom" || location === "top" + readonly property real swipeStartThreshold: Math.round(18 * Style.uiScaleRatio) + readonly property real swipeDismissThreshold: Math.max(110, background.width * 0.32) + readonly property real verticalSwipeDismissThreshold: Math.max(70, background.height * 0.35) + + transform: Translate { + x: root.swipeOffset + y: root.swipeOffsetY + } + + function clampSwipeDelta(deltaX) { + if (isRight) + return Math.max(0, deltaX); + if (isLeft) + return Math.min(0, deltaX); + return deltaX; + } + + function clampVerticalSwipeDelta(deltaY) { + if (location === "bottom") + return Math.max(0, deltaY); + if (location === "top") + return Math.min(0, deltaY); + return deltaY; + } + + HoverHandler { + onHoveredChanged: { + isHovered = hovered; + if (isHovered) { + resumeTimer.stop(); + if (progressAnimation.running && !progressAnimation.paused) { + progressAnimation.pause(); + } + } else { + resumeTimer.start(); + } + } + } + + Timer { + id: resumeTimer + interval: 50 + repeat: false + onTriggered: { + if (!isHovered && progressAnimation.paused) { + progressAnimation.resume(); + } + } + } + + // Background rectangle (apply shadows here) + Rectangle { + id: background + anchors.fill: parent + anchors.margins: shadowPadding + radius: Style.radiusL + color: Qt.alpha(Color.mSurface, Color.adaptiveOpacity(Settings.data.notifications.backgroundOpacity) || 1.0) + + // Colored border based on type + border.width: Style.borderS + border.color: { + var baseColor; + switch (root.type) { + case "error": + baseColor = Color.mError; + break; + default: + baseColor = Color.mOutline; + break; + } + return Qt.alpha(baseColor, Color.adaptiveOpacity(Settings.data.notifications.backgroundOpacity) || 1.0); + } + + // Progress bar + Rectangle { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + height: 2 + color: "transparent" + + Rectangle { + id: progressBar + readonly property real progressWidth: background.width - (2 * background.radius) + height: parent.height + // Mirrored logic: centers the bar as it shrinks + x: background.radius + (progressWidth * (1 - root.progress)) / 2 + width: progressWidth * root.progress + + color: { + var baseColor; + switch (root.type) { + case "warning": + baseColor = Color.mPrimary; + break; + case "error": + baseColor = Color.mError; + break; + default: + baseColor = Color.mPrimary; // Match standard notification color + break; + } + return Qt.alpha(baseColor, Color.adaptiveOpacity(Settings.data.notifications.backgroundOpacity) || 1.0); + } + } + } + } + + NDropShadow { + anchors.fill: background + source: background + autoPaddingEnabled: true + } + + NumberAnimation { + id: progressAnimation + target: root + property: "progress" + from: 1.0 + to: 0.0 + duration: root.duration + easing.type: Easing.Linear + onFinished: { + if (root.progress === 0.0 && root.visible) { + root.hide(); + } + } + } + + // Timer: hideTimer removed, using progressAnimation + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + + Behavior on scale { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + + Behavior on swipeOffset { + enabled: !root.isSwiping + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + Behavior on swipeOffsetY { + enabled: !root.isSwiping + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + Timer { + id: hideAnimation + interval: Style.animationFast + onTriggered: { + root.visible = false; + root.hidden(); + } + } + + // Cleanup on destruction + Component.onDestruction: { + progressAnimation.stop(); + hideAnimation.stop(); + } + + // Click anywhere dismiss the toast (must be before content so action link can override) + MouseArea { + id: toastDragArea + anchors.fill: background + acceptedButtons: Qt.LeftButton + hoverEnabled: true + onPressed: mouse => { + const globalPoint = toastDragArea.mapToGlobal(mouse.x, mouse.y); + root.pressGlobalX = globalPoint.x; + root.pressGlobalY = globalPoint.y; + root.isSwiping = false; + } + onPositionChanged: mouse => { + if (!(mouse.buttons & Qt.LeftButton)) + return; + const globalPoint = toastDragArea.mapToGlobal(mouse.x, mouse.y); + const rawDeltaX = globalPoint.x - root.pressGlobalX; + const rawDeltaY = globalPoint.y - root.pressGlobalY; + const deltaX = root.clampSwipeDelta(rawDeltaX); + const deltaY = root.clampVerticalSwipeDelta(rawDeltaY); + if (!root.isSwiping) { + if (root.useVerticalSwipe) { + if (Math.abs(deltaY) < root.swipeStartThreshold) + return; + root.isSwiping = true; + } else { + if (Math.abs(deltaX) < root.swipeStartThreshold) + return; + root.isSwiping = true; + } + } + if (root.useVerticalSwipe) { + root.swipeOffset = 0; + root.swipeOffsetY = deltaY; + } else { + root.swipeOffset = deltaX; + root.swipeOffsetY = 0; + } + } + onReleased: mouse => { + if (mouse.button !== Qt.LeftButton) + return; + if (root.isSwiping) { + root.isSwiping = false; + const dismissDistance = root.useVerticalSwipe ? Math.abs(root.swipeOffsetY) : Math.abs(root.swipeOffset); + const threshold = root.useVerticalSwipe ? root.verticalSwipeDismissThreshold : root.swipeDismissThreshold; + if (dismissDistance >= threshold) { + root.hide(); + } else { + root.swipeOffset = 0; + root.swipeOffsetY = 0; + } + return; + } + root.hide(); + } + onCanceled: { + root.isSwiping = false; + root.swipeOffset = 0; + root.swipeOffsetY = 0; + } + cursorShape: Qt.PointingHandCursor + } + + RowLayout { + id: contentLayout + anchors.fill: background + anchors.topMargin: isCompact ? Style.marginS : Style.marginM + anchors.bottomMargin: isCompact ? Style.marginS : Style.marginM + anchors.leftMargin: isCompact ? Style.marginM : Style.margin2M + anchors.rightMargin: isCompact ? Style.marginM : Style.margin2M + spacing: isCompact ? Style.marginM : Style.marginL + + // Icon + NIcon { + icon: if (root.icon !== "") { + return root.icon; + } else if (type === "warning") { + return "toast-warning"; + } else if (type === "error") { + return "toast-error"; + } else { + return "toast-notice"; + } + color: { + switch (type) { + case "warning": + return Color.mPrimary; + case "error": + return Color.mError; + default: + return Color.mOnSurface; + } + } + pointSize: isCompact ? Style.fontSizeXL : Style.fontSizeXXL * 1.5 + Layout.alignment: Qt.AlignVCenter + } + + // Label and description + ColumnLayout { + spacing: Style.marginXXS + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + + NText { + Layout.fillWidth: true + text: root.title + color: Color.mOnSurface + pointSize: isCompact ? Style.fontSizeM : Style.fontSizeL + font.weight: Style.fontWeightBold + wrapMode: Text.WordWrap + visible: text.length > 0 + } + + NText { + Layout.fillWidth: true + text: root.description + color: Color.mOnSurface + pointSize: isCompact ? Style.fontSizeS : Style.fontSizeM + wrapMode: Text.WordWrap + maximumLineCount: isCompact ? 2 : 20 + elide: isCompact ? Text.ElideRight : Text.ElideNone + visible: text.length > 0 + } + + // Action button + NButton { + text: root.actionLabel + visible: root.actionLabel.length > 0 && root.actionCallback !== null + Layout.topMargin: Style.marginXS + fontSize: Style.fontSizeS + backgroundColor: Color.mPrimary + textColor: hovered ? Color.mOnHover : Color.mOnPrimary + hoverColor: Color.mHover + outlined: false + implicitHeight: 24 + + onClicked: { + if (root.actionCallback) { + root.actionCallback(); + root.hide(); + } + } + } + } + } + + function show(msgTitle, msgDescription, msgIcon, msgType, msgDuration, msgActionLabel, msgActionCallback) { + // Stop all timers first + progressAnimation.stop(); + hideAnimation.stop(); + + title = msgTitle; + description = msgDescription || ""; + icon = msgIcon || ""; + type = msgType || "notice"; + duration = msgDuration || 3000; + actionLabel = msgActionLabel || ""; + actionCallback = msgActionCallback || null; + + visible = true; + opacity = 1.0; + scale = 1.0; + progress = 1.0; + isHovered = false; + isSwiping = false; + swipeOffset = 0; + swipeOffsetY = 0; + + // Configure and start animation + progressAnimation.duration = duration; + progressAnimation.from = 1.0; + progressAnimation.to = 0.0; + progressAnimation.restart(); + } + + function hide() { + progressAnimation.stop(); + isSwiping = false; + swipeOffset = 0; + swipeOffsetY = 0; + opacity = 0; + scale = initialScale; + hideAnimation.restart(); + } + + function hideImmediately() { + hideAnimation.stop(); + progressAnimation.stop(); + isSwiping = false; + swipeOffset = 0; + swipeOffsetY = 0; + opacity = 0; + scale = initialScale; + root.hidden(); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Toast/ToastOverlay.qml b/arch/.config/quickshell/noctalia-shell/Modules/Toast/ToastOverlay.qml new file mode 100644 index 0000000..d2ae91b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Toast/ToastOverlay.qml @@ -0,0 +1,22 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Widgets + +Variants { + model: { + const screens = Quickshell.screens.filter(screen => Settings.data.notifications.monitors.includes(screen.name)); + // Empty list can mean two things : + // - No (visible) notification display activated in settings + // - One or more (not visible) displays are activated but unplugged + // In both cases we fallback to show notification on all screens + return screens.length === 0 ? Quickshell.screens : screens; + } + + delegate: ToastScreen { + required property ShellScreen modelData + screen: modelData + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Toast/ToastScreen.qml b/arch/.config/quickshell/noctalia-shell/Modules/Toast/ToastScreen.qml new file mode 100644 index 0000000..22f4253 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Toast/ToastScreen.qml @@ -0,0 +1,246 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services.UI + +Item { + id: root + + required property ShellScreen screen + + // Local queue for this screen only (bounded to prevent memory issues) + property var messageQueue: [] + property int maxQueueSize: 10 + property bool isShowingToast: false + + // If true, immediately show new toasts + property bool replaceOnNew: true + + Connections { + target: ToastService + + function onNotify(title, description, icon, type, duration, actionLabel, actionCallback) { + root.enqueueToast({ + "title": title, + "description": description, + "icon": icon, + "type": type, + "duration": duration, + "actionLabel": actionLabel || "", + "actionCallback": actionCallback || null, + "timestamp": Date.now() + }); + } + + function onDismiss() { + root.dismissToast(); + } + } + + // Clear queue on component destruction to prevent orphaned toasts + Component.onDestruction: { + messageQueue = []; + isShowingToast = false; + hideTimer.stop(); + quickSwitchTimer.stop(); + } + + function dismissToast() { + if (windowLoader.item) { + hideTimer.stop(); + windowLoader.item.hideToast(); + } + } + + function enqueueToast(toastData) { + // Safe logging - fix the substring bug + var descPreview = (toastData.description || "").substring(0, 100).replace(/\n/g, " "); + Logger.d("ToastScreen", "Queuing", toastData.type, ":", toastData.title, descPreview); + + // Bounded queue to prevent unbounded memory growth + if (messageQueue.length >= maxQueueSize) { + Logger.d("ToastScreen", "Queue full, dropping oldest toast"); + messageQueue.shift(); + } + + if (replaceOnNew && isShowingToast) { + // Cancel current toast and clear queue for latest toast + messageQueue = []; // Clear existing queue + messageQueue.push(toastData); + + // Hide current toast immediately + root.dismissToast(); + + // Process new toast after a brief delay + isShowingToast = false; + quickSwitchTimer.restart(); + } else { + // Queue the toast + messageQueue.push(toastData); + processQueue(); + } + } + + Timer { + id: quickSwitchTimer + interval: 50 // Brief delay for smooth transition + onTriggered: root.processQueue() + } + + function processQueue() { + if (messageQueue.length === 0 || isShowingToast) { + return; + } + + var data = messageQueue.shift(); + isShowingToast = true; + + // Store the toast data for when loader is ready + windowLoader.pendingToast = data; + + // Activate the loader - onStatusChanged will handle showing the toast + windowLoader.active = true; + } + + function onToastHidden() { + isShowingToast = false; + + // Deactivate the loader to completely remove the window and free memory + windowLoader.active = false; + + // Small delay before processing next toast + hideTimer.restart(); + } + + Timer { + id: hideTimer + interval: 200 + onTriggered: root.processQueue() + } + + // The loader that creates/destroys the PanelWindow as needed + // This is good for RAM efficiency when toasts are infrequent + Loader { + id: windowLoader + active: false // Only active when showing a toast + + // Store pending toast data + property var pendingToast: null + + onStatusChanged: { + // When loader becomes ready, show the pending toast + if (status === Loader.Ready && pendingToast !== null) { + item.showToast(pendingToast.title, pendingToast.description, pendingToast.icon, pendingToast.type, pendingToast.duration, pendingToast.actionLabel, pendingToast.actionCallback); + pendingToast = null; + } + } + + sourceComponent: PanelWindow { + id: panel + + property alias toastItem: toastItem + + screen: root.screen + + // Parse location setting + readonly property string location: Settings.data.notifications?.location || "top_right" + readonly property bool isTop: location.startsWith("top") + readonly property bool isBottom: location.startsWith("bottom") + readonly property bool isLeft: location.endsWith("_left") + readonly property bool isRight: location.endsWith("_right") + readonly property bool isCentered: location === "top" || location === "bottom" + + readonly property bool isFramed: Settings.data.bar.barType === "framed" + readonly property real frameThickness: Settings.data.bar.frameThickness ?? 8 + + readonly property string barPos: Settings.getBarPositionForScreen(panel.screen?.name) + readonly property bool isFloating: Settings.data.bar.barType === "floating" + readonly property real barHeight: Style.getBarHeightForScreen(panel.screen?.name) + + // Calculate bar and frame offsets for each edge separately + readonly property int barOffsetTop: { + if (barPos !== "top") + return isFramed ? frameThickness : 0; + const floatMarginV = isFloating ? Math.ceil(Settings.data.bar.marginVertical) : 0; + return barHeight + floatMarginV; + } + + readonly property int barOffsetBottom: { + if (barPos !== "bottom") + return isFramed ? frameThickness : 0; + const floatMarginV = isFloating ? Math.ceil(Settings.data.bar.marginVertical) : 0; + return barHeight + floatMarginV; + } + + readonly property int barOffsetLeft: { + if (barPos !== "left") + return isFramed ? frameThickness : 0; + const floatMarginH = isFloating ? Math.ceil(Settings.data.bar.marginHorizontal) : 0; + return barHeight + floatMarginH; + } + + readonly property int barOffsetRight: { + if (barPos !== "right") + return isFramed ? frameThickness : 0; + const floatMarginH = isFloating ? Math.ceil(Settings.data.bar.marginHorizontal) : 0; + return barHeight + floatMarginH; + } + + readonly property int shadowPadding: Style.shadowBlurMax + Style.marginL + + // Anchoring + anchors.top: isTop + anchors.bottom: isBottom + anchors.left: isLeft + anchors.right: isRight + + // Margins for PanelWindow - only apply bar offset for the specific edge where the bar is + margins.top: isTop ? barOffsetTop - shadowPadding + Style.marginM : 0 + margins.bottom: isBottom ? barOffsetBottom - shadowPadding + Style.marginM : 0 + margins.left: isLeft ? barOffsetLeft - shadowPadding + Style.marginM : 0 + margins.right: isRight ? barOffsetRight - shadowPadding + Style.marginM : 0 + + implicitWidth: Math.round(toastItem.width) + implicitHeight: Math.round(toastItem.height) + + color: "transparent" + + WlrLayershell.layer: (Settings.data.notifications && Settings.data.notifications.overlayLayer) ? WlrLayer.Overlay : WlrLayer.Top + WlrLayershell.namespace: "noctalia-toast-" + (screen?.name || "unknown") + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + WlrLayershell.exclusionMode: ExclusionMode.Ignore + + // Make shadow area click-through, only toast content is clickable + mask: Region { + x: 0 + y: 0 + width: panel.width + height: panel.height + intersection: Intersection.Xor + + Region { + // The clickable content area is inset by shadowPadding from all edges + x: panel.shadowPadding + y: panel.shadowPadding + width: Math.max(0, panel.width - panel.shadowPadding * 2) + height: Math.max(0, panel.height - panel.shadowPadding * 2) + intersection: Intersection.Subtract + } + } + + function showToast(title, description, icon, type, duration, actionLabel, actionCallback) { + toastItem.show(title, description, icon, type, duration, actionLabel, actionCallback); + } + + function hideToast() { + toastItem.hideImmediately(); + } + + Toast { + id: toastItem + onHidden: root.onToastHidden() + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Modules/Tooltip/Tooltip.qml b/arch/.config/quickshell/noctalia-shell/Modules/Tooltip/Tooltip.qml new file mode 100644 index 0000000..d3deb3d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Modules/Tooltip/Tooltip.qml @@ -0,0 +1,701 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Widgets + +PopupWindow { + id: root + + property string text: "" + property var rows: null // Array of arrays for grid mode: [["col1", "col2"], ["row2col1", "row2col2"]] + property bool isGridMode: rows !== null && Array.isArray(rows) && rows.length > 0 + property int columnCount: isGridMode && rows.length > 0 ? rows[0].length : 0 + property string direction: "auto" // "auto", "left", "right", "top", "bottom" + property int margin: Style.marginXS // distance from target + property int padding: Style.marginM + property int gridPaddingVertical: Style.marginXS // extra vertical padding for grid mode + property int delay: 0 + property int hideDelay: 0 + property int maxWidth: 340 + + property int animationDuration: Style.animationFast + property real animationScale: 0.85 + + // Font scale calculation matching NText behavior + readonly property real gridFontScale: { + const baseScale = (tooltipText.family === Settings.data.ui.fontDefault ? Settings.data.ui.fontDefaultScale : Settings.data.ui.fontFixedScale); + return baseScale * Style.uiScaleRatio; + } + + // For measuring grid cell widths - must match actual NText font + TextMetrics { + id: cellMetrics + font.family: tooltipText.family + font.pointSize: Math.max(1, Style.fontSizeS * gridFontScale) + } + + // For measuring grid row height - hidden NText matches actual grid items exactly + NText { + id: rowHeightMeasure + visible: false + text: "Ag" // Characters with ascenders and descenders + pointSize: Style.fontSizeS + family: tooltipText.family + } + + // Internal properties + property var targetItem: null + property real anchorX: 0 + property real anchorY: 0 + property bool isPositioned: false + property bool animatingOut: true + property int screenWidth: 1920 + property int screenHeight: 1080 + property int screenX: 0 + property int screenY: 0 + + visible: false + color: "transparent" + + anchor.item: targetItem + anchor.rect.x: anchorX + anchor.rect.y: anchorY + + // Timer for showing tooltip after delay + Timer { + id: showTimer + interval: root.delay + repeat: false + onTriggered: { + root.positionAndShow(); + } + } + + // Timer for hiding tooltip after delay + Timer { + id: hideTimer + interval: root.hideDelay + repeat: false + onTriggered: { + root.startHideAnimation(); + } + } + + // Show animation + ParallelAnimation { + id: showAnimation + + PropertyAnimation { + target: tooltipContainer + property: "opacity" + from: 0.0 + to: 1.0 + duration: root.animationDuration + easing.type: Easing.OutCubic + } + + PropertyAnimation { + target: tooltipContainer + property: "scale" + from: root.animationScale + to: 1.0 + duration: root.animationDuration + easing.type: Easing.OutBack + easing.overshoot: 1.2 + } + } + + // Hide animation + ParallelAnimation { + id: hideAnimation + + PropertyAnimation { + target: tooltipContainer + property: "opacity" + from: 1.0 + to: 0.0 + duration: root.animationDuration * 0.75 // Slightly faster hide + easing.type: Easing.InCubic + } + + PropertyAnimation { + target: tooltipContainer + property: "scale" + from: 1.0 + to: root.animationScale + duration: root.animationDuration * 0.75 + easing.type: Easing.InCubic + } + + onFinished: { + root.completeHide(); + } + } + + // Function to show tooltip (content can be string or array of arrays for grid mode) + function show(target, content, customDirection, showDelay, fontFamily) { + if (!target || !content || content === "" || (Array.isArray(content) && content.length === 0)) + return; + + root.delay = showDelay; + + // Stop any running timers and animations + hideTimer.stop(); + showTimer.stop(); + hideAnimation.stop(); + animatingOut = false; + + // If we're already showing for a different target, hide immediately + if (visible && targetItem !== target) { + hideImmediately(); + } + + // Set content based on type + if (Array.isArray(content)) { + rows = content; + text = ""; + } else { + // Convert \n to
for RichText format + const processedText = content.replace(/\n/g, '
'); + text = processedText; + rows = null; + } + + targetItem = target; + + // Find the correct screen dimensions based on target's global position + // Respect all screens positionning + const targetGlobal = target.mapToGlobal(target.width / 2, target.height / 2); + let foundScreen = false; + for (let i = 0; i < Quickshell.screens.length; i++) { + const s = Quickshell.screens[i]; + if (targetGlobal.x >= s.x && targetGlobal.x < s.x + s.width && targetGlobal.y >= s.y && targetGlobal.y < s.y + s.height) { + screenWidth = s.width; + screenHeight = s.height; + screenX = s.x; + screenY = s.y; + foundScreen = true; + break; + } + } + if (!foundScreen) { + Logger.w("Tooltip", "No screen found for target position!"); + } + + // Initialize animation state (hidden) + tooltipContainer.opacity = 0.0; + tooltipContainer.scale = root.animationScale; + + // Start show timer (will position and then make visible) + showTimer.start(); + + if (customDirection !== undefined) { + direction = customDirection; + } else { + direction = "auto"; + } + + tooltipText.family = fontFamily ? fontFamily : Settings.data.ui.fontDefault; + } + + // Calculate grid dimensions using measurements that match actual NText rendering + function calculateGridSize() { + if (!rows || rows.length === 0) + return { + width: 0, + height: 0 + }; + + const numCols = rows[0].length; + const numRows = rows.length; + let columnWidths = []; + + // Find max width for each column using TextMetrics + for (let col = 0; col < numCols; col++) { + let maxWidth = 0; + for (let row = 0; row < numRows; row++) { + cellMetrics.text = rows[row][col] || ""; + if (cellMetrics.width > maxWidth) { + maxWidth = cellMetrics.width; + } + } + columnWidths.push(maxWidth); + } + + // Calculate total width: sum of columns + spacing between columns + let totalWidth = 0; + for (let i = 0; i < columnWidths.length; i++) { + totalWidth += columnWidths[i]; + } + totalWidth += (numCols - 1) * Style.marginM; // columnSpacing + + // Calculate total height using hidden NText for accurate row height + const rowHeight = rowHeightMeasure.implicitHeight; + const totalHeight = numRows * rowHeight; + + return { + width: totalWidth, + height: totalHeight + }; + } + + // Function to position and display the tooltip + function positionAndShow() { + if (!targetItem || !targetItem.parent) { + return; + } + + // Calculate tooltip dimensions based on content mode + let contentWidth, contentHeight; + if (isGridMode) { + const gridSize = calculateGridSize(); + contentWidth = gridSize.width; + contentHeight = gridSize.height; + } else { + contentWidth = tooltipText.implicitWidth; + contentHeight = tooltipText.implicitHeight; + } + + const extraPad = isGridMode ? gridPaddingVertical : 0; + const tipWidth = Math.ceil(Math.min(contentWidth + ((padding + extraPad) * 2), maxWidth)); + root.implicitWidth = tipWidth; + + const tipHeight = Math.ceil(contentHeight + ((padding + extraPad) * 2)); + root.implicitHeight = tipHeight; + + // Get target's global position and convert to screen-relative + var targetGlobalAbs = targetItem.mapToGlobal(0, 0); + var targetGlobal = { + "x": targetGlobalAbs.x - screenX, + "y": targetGlobalAbs.y - screenY + }; + const targetWidth = targetItem.width; + const targetHeight = targetItem.height; + + var newAnchorX = 0; + var newAnchorY = 0; + + if (direction === "auto") { + // Calculate available space in each direction (screen-relative) + const spaceLeft = targetGlobal.x; + const spaceRight = screenWidth - (targetGlobal.x + targetWidth); + const spaceTop = targetGlobal.y; + const spaceBottom = screenHeight - (targetGlobal.y + targetHeight); + + // Try positions in order of available space + const positions = [ + { + "dir": "bottom", + "space": spaceBottom, + "x": (targetWidth - tipWidth) / 2, + "y": targetHeight + margin, + "fits": spaceBottom >= tipHeight + margin + }, + { + "dir": "top", + "space": spaceTop, + "x": (targetWidth - tipWidth) / 2, + "y": -tipHeight - margin, + "fits": spaceTop >= tipHeight + margin + }, + { + "dir": "right", + "space": spaceRight, + "x": targetWidth + margin, + "y": (targetHeight - tipHeight) / 2, + "fits": spaceRight >= tipWidth + margin + }, + { + "dir": "left", + "space": spaceLeft, + "x": -tipWidth - margin, + "y": (targetHeight - tipHeight) / 2, + "fits": spaceLeft >= tipWidth + margin + } + ]; + + // Find first position that fits + var selectedPosition = null; + for (var i = 0; i < positions.length; i++) { + if (positions[i].fits) { + selectedPosition = positions[i]; + break; + } + } + + // If none fit perfectly + if (!selectedPosition) { + // Sort by available space and use position with most space + positions.sort(function (a, b) { + return b.space - a.space; + }); + selectedPosition = positions[0]; + } + + newAnchorX = selectedPosition.x; + newAnchorY = selectedPosition.y; + } else { + // Manual direction positioning + switch (direction) { + case "left": + newAnchorX = -tipWidth - margin; + newAnchorY = (targetHeight - tipHeight) / 2; + break; + case "right": + newAnchorX = targetWidth + margin; + newAnchorY = (targetHeight - tipHeight) / 2; + break; + case "top": + newAnchorX = (targetWidth - tipWidth) / 2; + newAnchorY = -tipHeight - margin; + break; + case "bottom": + newAnchorX = (targetWidth - tipWidth) / 2; + newAnchorY = targetHeight + margin; + break; + } + } + + // Adjust horizontal position to keep tooltip on screen. + // For top/bottom tooltips, always adjust horizontally (they don't overlap horizontally). + // For left/right tooltips, check for overlap before adjusting to avoid landing on the target. + const globalX = targetGlobal.x + newAnchorX; + const effectiveDirection = direction === "auto" ? selectedPosition.dir : direction; + const isHorizontalTooltip = (effectiveDirection === "top" || effectiveDirection === "bottom"); + + if (globalX < 0) { + const adjustedX = -targetGlobal.x + margin; + if (isHorizontalTooltip) { + newAnchorX = adjustedX; + } else { + const wouldOverlap = adjustedX < targetWidth && adjustedX + tipWidth > 0; + if (!wouldOverlap) + newAnchorX = adjustedX; + } + } else if (globalX + tipWidth > screenWidth) { + const adjustedX = screenWidth - targetGlobal.x - tipWidth - margin; + if (isHorizontalTooltip) { + newAnchorX = adjustedX; + } else { + const wouldOverlap = adjustedX < targetWidth && adjustedX + tipWidth > 0; + if (!wouldOverlap) + newAnchorX = adjustedX; + } + } + + // Adjust vertical position to keep tooltip on screen. + // For left/right tooltips, always adjust vertically (they don't overlap vertically). + // For top/bottom tooltips, check for overlap before adjusting to avoid landing on the target. + const globalY = targetGlobal.y + newAnchorY; + const isVerticalTooltip = (effectiveDirection === "left" || effectiveDirection === "right"); + + if (globalY < 0) { + const adjustedY = -targetGlobal.y + margin; + if (isVerticalTooltip) { + newAnchorY = adjustedY; + } else { + const wouldOverlap = adjustedY < targetHeight && adjustedY + tipHeight > 0; + if (!wouldOverlap) + newAnchorY = adjustedY; + } + } else if (globalY + tipHeight > screenHeight) { + const adjustedY = screenHeight - targetGlobal.y - tipHeight - margin; + if (isVerticalTooltip) { + newAnchorY = adjustedY; + } else { + const wouldOverlap = adjustedY < targetHeight && adjustedY + tipHeight > 0; + if (!wouldOverlap) + newAnchorY = adjustedY; + } + } + + // Apply position first (before making visible) + // Use floor for negative values to push tooltip away from target + anchorX = newAnchorX < 0 ? Math.floor(newAnchorX) : Math.round(newAnchorX); + anchorY = newAnchorY < 0 ? Math.floor(newAnchorY) : Math.round(newAnchorY); + isPositioned = true; + + // Now make visible and start animation + root.visible = true; + showAnimation.start(); + } + + // Function to hide tooltip + function hide() { + // Stop show timer if it's running + showTimer.stop(); + + // Stop hide timer if it's running + hideTimer.stop(); + + if (hideDelay > 0 && visible && !animatingOut) { + hideTimer.start(); + } else { + startHideAnimation(); + } + } + + function startHideAnimation() { + if (!visible || animatingOut) + return; + animatingOut = true; + showAnimation.stop(); // Stop show animation if running + hideAnimation.start(); + } + + function completeHide() { + visible = false; + animatingOut = false; + text = ""; + rows = null; + isPositioned = false; + tooltipContainer.opacity = 1.0; + tooltipContainer.scale = 1.0; + } + + // Quick hide without delay or animation + function hideImmediately() { + showTimer.stop(); + hideTimer.stop(); + showAnimation.stop(); + hideAnimation.stop(); + animatingOut = false; + completeHide(); + } + + // Update content function (supports both text and rows) + function updateContent(newContent) { + if (visible && targetItem) { + if (Array.isArray(newContent)) { + rows = newContent; + text = ""; + } else { + // Convert \n to
for RichText format + const processedText = newContent.replace(/\n/g, '
'); + text = processedText; + rows = null; + } + + // Defer dimension recalculation to allow GridLayout to update + Qt.callLater(updateContentDeferred); + } + } + + function updateContentDeferred() { + if (!visible || !targetItem) { + return; + } + + // Recalculate dimensions based on content mode + // Use calculateGridSize() for consistency with initial show + let contentWidth, contentHeight; + if (isGridMode) { + const gridSize = calculateGridSize(); + contentWidth = gridSize.width; + contentHeight = gridSize.height; + } else { + contentWidth = tooltipText.implicitWidth; + contentHeight = tooltipText.implicitHeight; + } + + const extraPad = isGridMode ? gridPaddingVertical : 0; + const tipWidth = Math.ceil(Math.min(contentWidth + ((padding + extraPad) * 2), maxWidth)); + root.implicitWidth = tipWidth; + + const tipHeight = Math.ceil(contentHeight + ((padding + extraPad) * 2)); + root.implicitHeight = tipHeight; + + // Reposition based on current direction (screen-relative) + // Round all values to avoid sub-pixel positioning issues with fractional scaling + var targetGlobalAbs = targetItem.mapToGlobal(0, 0); + var targetGlobal = { + "x": targetGlobalAbs.x - screenX, + "y": targetGlobalAbs.y - screenY + }; + const targetWidth = targetItem.width; + const targetHeight = targetItem.height; + + // Recalculate base anchor position (center on target for top/bottom, etc.) + var newAnchorX = anchorX; + var newAnchorY = anchorY; + + // Determine which direction the tooltip is currently positioned + // and recalculate the centering for that direction + var isHorizontalTooltip = false; + var isVerticalTooltip = false; + if (anchorY > targetHeight / 2) { + // Tooltip is below target + newAnchorX = (targetWidth - tipWidth) / 2; + isHorizontalTooltip = true; + } else if (anchorY < -tipHeight / 2) { + // Tooltip is above target + newAnchorX = (targetWidth - tipWidth) / 2; + isHorizontalTooltip = true; + } else if (anchorX > targetWidth / 2) { + // Tooltip is to the right + newAnchorY = (targetHeight - tipHeight) / 2; + isVerticalTooltip = true; + } else if (anchorX < -tipWidth / 2) { + // Tooltip is to the left + newAnchorY = (targetHeight - tipHeight) / 2; + isVerticalTooltip = true; + } + + // Adjust horizontal position to keep tooltip on screen if needed. + const globalX = targetGlobal.x + newAnchorX; + if (globalX < 0) { + const adjustedX = -targetGlobal.x + margin; + if (isHorizontalTooltip) { + newAnchorX = adjustedX; + } else { + const wouldOverlap = adjustedX < targetWidth && adjustedX + tipWidth > 0; + if (!wouldOverlap) + newAnchorX = adjustedX; + } + } else if (globalX + tipWidth > screenWidth) { + const adjustedX = screenWidth - targetGlobal.x - tipWidth - margin; + if (isHorizontalTooltip) { + newAnchorX = adjustedX; + } else { + const wouldOverlap = adjustedX < targetWidth && adjustedX + tipWidth > 0; + if (!wouldOverlap) + newAnchorX = adjustedX; + } + } + + // Adjust vertical position to keep tooltip on screen if needed. + const globalY = targetGlobal.y + newAnchorY; + if (globalY < 0) { + const adjustedY = -targetGlobal.y + margin; + if (isVerticalTooltip) { + newAnchorY = adjustedY; + } else { + const wouldOverlap = adjustedY < targetHeight && adjustedY + tipHeight > 0; + if (!wouldOverlap) + newAnchorY = adjustedY; + } + } else if (globalY + tipHeight > screenHeight) { + const adjustedY = screenHeight - targetGlobal.y - tipHeight - margin; + if (isVerticalTooltip) { + newAnchorY = adjustedY; + } else { + const wouldOverlap = adjustedY < targetHeight && adjustedY + tipHeight > 0; + if (!wouldOverlap) + newAnchorY = adjustedY; + } + } + + // Apply the new anchor positions + // Use floor for negative values to push tooltip away from target + anchorX = newAnchorX < 0 ? Math.floor(newAnchorX) : Math.round(newAnchorX); + anchorY = newAnchorY < 0 ? Math.floor(newAnchorY) : Math.round(newAnchorY); + + // Force anchor update + Qt.callLater(() => { + if (root.anchor && root.visible) { + root.anchor.updateAnchor(); + } + }); + } + + // Backward compatibility alias + function updateText(newText) { + updateContent(newText); + } + + // Reset function to clean up state + function reset() { + // Stop all timers and animations + showTimer.stop(); + hideTimer.stop(); + showAnimation.stop(); + hideAnimation.stop(); + + // Clear all state + visible = false; + animatingOut = false; + text = ""; + rows = null; + isPositioned = false; + + // Reset to defaults + direction = "auto"; + delay = 0; + hideDelay = 0; + + // Reset container state + tooltipContainer.opacity = 1.0; + tooltipContainer.scale = 1.0; + } + + // Tooltip content container for animations + Item { + id: tooltipContainer + anchors.fill: parent + + // Animation properties + opacity: 1.0 + scale: 1.0 + transformOrigin: Item.Center + + Rectangle { + anchors.fill: parent + anchors.margins: border.width + color: Color.mSurface + border.color: Color.mOutline + border.width: Style.borderS + radius: Math.min(Style.radiusS, Math.min(width, height) / 3) + + // Only show content when we have content + visible: root.text !== "" || root.isGridMode + + // Text content (default mode) + NText { + id: tooltipText + visible: !root.isGridMode + anchors.centerIn: parent + anchors.margins: root.padding + text: root.text + pointSize: Style.fontSizeS + family: Settings.data.ui.fontFixed + color: Color.mOnSurfaceVariant + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + width: Math.min(implicitWidth, root.maxWidth - (root.padding * 2)) + richTextEnabled: true + } + + // Grid content (grid mode) + GridLayout { + id: gridContent + visible: root.isGridMode + anchors.centerIn: parent + columns: root.columnCount + rowSpacing: 0 + columnSpacing: Style.marginM + + Repeater { + model: root.isGridMode ? [].concat.apply([], root.rows) : [] + + NText { + text: modelData + pointSize: Style.fontSizeS + family: tooltipText.family + color: Color.mOnSurfaceVariant + Layout.preferredHeight: rowHeightMeasure.implicitHeight + } + } + } + } + } + + Component.onCompleted: { + reset(); + } + + Component.onDestruction: { + reset(); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/README.md b/arch/.config/quickshell/noctalia-shell/README.md new file mode 100644 index 0000000..b5f6b90 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/README.md @@ -0,0 +1,189 @@ +# Noctalia Shell + +**_quiet by design_** + +

+ Noctalia Logo +

+ +

+ + Install Noctalia + +

+ +


+ +

+ + Last commit + + + GitHub stars + + + Documentation + + + Discord + +

+ +--- + +## What is Noctalia? + +A beautiful, minimal desktop shell for Wayland that actually gets out of your way. Built on [Quickshell](https://quickshell.outfoxxed.me/) (Qt/QML) with a warm lavender aesthetic that you can easily customize to match your vibe. + +**โœจ Key Features:** +- ๐ŸชŸ Native support for Niri, Hyprland, Sway, Scroll, Labwc and MangoWC +- ๐ŸŽจ Extensive theming with predefined color schemes and automatic color generation from your wallpaper +- ๐Ÿ–ผ๏ธ Wallpaper management with Wallhaven integration +- ๐Ÿ”” Notification system with history and Do Not Disturb +- ๐Ÿ–ฅ๏ธ Multi-monitor support +- ๐Ÿ”’ Lock screen +- ๐Ÿงฉ Desktop widgets (clock, media player and more) +- ๐Ÿ’ก OSD for volume and brightness +- ๐Ÿ”Œ Nearly 100 plugins available ([explore plugins](https://noctalia.dev/plugins/)) +- ๐Ÿช„ Setup wizard for first-time users +- โšก Built on Quickshell for performance + +--- + +## Preview + +https://github.com/user-attachments/assets/bf46f233-8d66-439a-a1ae-ab0446270f2d + +
+Screenshots + +![Dark 1](/Assets/Screenshots/noctalia-dark-1.png) +![Dark 2](/Assets/Screenshots/noctalia-dark-2.png) +![Dark 3](/Assets/Screenshots/noctalia-dark-3.png) + +![Light 1](/Assets/Screenshots/noctalia-light-1.png) +![Light 2](/Assets/Screenshots/noctalia-light-2.png) +![Light 3](/Assets/Screenshots/noctalia-light-3.png) + +
+ +--- + +## ๐Ÿ“‹ Requirements + +- Wayland compositor (see supported compositors below) +- Quickshell: [noctalia-qs](https://github.com/noctalia-dev/noctalia-qs) +- Additional dependencies are listed in our [documentation](https://docs.noctalia.dev) + +--- + +## ๐Ÿš€ Getting Started + +**New to Noctalia?** +Check out our comprehensive documentation and installation guide to get up and running! + +

+ + Installation Guide + + + FAQ + + + Discord + +

+ +--- + +## ๐Ÿ–ฅ๏ธ Wayland Compositors + +Noctalia provides native support for **Niri**, **Hyprland**, **Sway**, **Scroll**, **Labwc** and **MangoWC**. Other Wayland compositors may work but could require additional configuration for compositor-specific features like workspaces and window management. + +--- + +## Scope + +Noctalia is a **desktop shell**, not a full desktop environment. It provides the visual layer that sits on top of your Wayland compositor (bars, panels, notifications, a dock, and widgets) but it intentionally stays within that boundary. Understanding this helps set the right expectations for feature requests. + +### What Noctalia does + +Noctalia focuses on the things a shell is responsible for: status bar, panels, application launcher, notifications, lock screen, idle management, OSD, theming, wallpapers, desktop widgets, dock, and multi-monitor support. + +### What belongs in a plugin + +If a feature is useful to some users but not essential to the core shell experience, it's a great candidate for a [plugin](https://noctalia.dev/plugins/). The plugin system is designed to make this easy: plugins can add bar widgets, panels, launcher providers, desktop widgets, and more. + +Some examples of features that are better suited as plugins: +- Compositor-specific extras (e.g., Steam overlay for Hyprland) +- Hardware-specific controls (e.g., laptop fan profiles, battery thresholds) +- Third-party service integrations (e.g., smart home controls, Tailscale) +- Niche productivity tools (e.g., Pomodoro timer, RSS reader, Docker manager) +- Alternative visualizations or widgets + +If you have an idea that fits this category, consider [building a plugin](https://docs.noctalia.dev/development/guideline) for it! + +### What falls outside our scope + +Some features go beyond what a desktop shell can or should do. These are typically responsibilities of the compositor, a dedicated application, or the system itself: + +- **File management**: use a file manager application +- **Display/login greeter**: this runs before the shell and is managed separately +- **Window management and overview**: workspace switching and window tiling are compositor responsibilities +- **Removable drive mounting**: handled by system services like udisks and desktop applications +- **Screen mirroring/casting**: managed by the compositor or dedicated tools + +We appreciate feature suggestions, but if a request falls into this category, it's likely outside what Noctalia can provide. When in doubt, feel free to ask in our [Discord](https://discord.noctalia.dev). + +--- + +## ๐Ÿค Contributing + +We welcome contributions of any size - bug fixes, new features, documentation improvements, or custom themes and configs. + +**Get involved:** +- **Found a bug?** [Open an issue](https://github.com/noctalia-dev/noctalia-shell/issues/new) +- **Want to code?** Check out our [development guidelines](https://docs.noctalia.dev/development/guideline) +- **Need help?** Join our [Discord](https://discord.noctalia.dev) + +--- + +## ๐Ÿ’œ Credits + +A heartfelt thank you to our incredible community of [**contributors**](https://github.com/noctalia-dev/noctalia-shell/graphs/contributors). We are immensely grateful for your dedicated participation and the constructive feedback you've provided, which continue to shape and improve our project for everyone. + +--- + +## โ˜• Donations + +While all donations are greatly appreciated, they are completely voluntary. +Thank you to everyone who supports the project! ๐Ÿ’œ + +

+ + Buy Me a Coffee + + + Ko-fi + +

+ +--- + +## ๐Ÿ“„ License + +MIT License - see [LICENSE](./LICENSE) for details. + +--- + +## โญ Star History + +

+ + Star History + +

diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/bash/template-apply.sh b/arch/.config/quickshell/noctalia-shell/Scripts/bash/template-apply.sh new file mode 100755 index 0000000..0e9d62b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/bash/template-apply.sh @@ -0,0 +1,540 @@ +#!/usr/bin/env -S bash + +# Ensure at least one argument is provided. +if [ "$#" -lt 1 ]; then + # Print usage information to standard error. + echo "Error: No application specified." >&2 + echo "Usage: $0 {kitty|ghostty|foot|alacritty|wezterm|fuzzel|walker|pywalfox|cava|yazi|labwc|niri|hyprland|sway|scroll|mango|btop|zathura} [dark|light]" >&2 + exit 1 +fi + +APP_NAME="$1" +MODE="${2:-}" # Optional second argument for dark/light mode + +# --- Apply theme based on the application name --- +case "$APP_NAME" in +kitty) + KITTY_CONF="$HOME/.config/kitty/kitty.conf" + if [ -w "$KITTY_CONF" ]; then + kitty +kitten themes --reload-in=all noctalia + else + kitty +runpy "from kitty.utils import *; reload_conf_in_all_kitties()" + fi + ;; + +ghostty) + # Check both potential config files + CONFIG_FILES=("$HOME/.config/ghostty/config" "$HOME/.config/ghostty/config.ghostty") + FOUND_CONFIG=false + + for CONFIG_FILE in "${CONFIG_FILES[@]}"; do + if [ -f "$CONFIG_FILE" ]; then + FOUND_CONFIG=true + # Check if theme is already set to noctalia (flexible spacing) + if grep -qE "^theme\s*=\s*noctalia$" "$CONFIG_FILE"; then + : # Already correct + elif grep -qE "^theme\s*=" "$CONFIG_FILE"; then + # Replace existing theme line in-place + sed -i -E 's/^theme\s*=.*/theme = noctalia/' "$CONFIG_FILE" + else + # Add the new theme line to the end of the file + echo "theme = noctalia" >>"$CONFIG_FILE" + fi + fi + done + + if [ "$FOUND_CONFIG" = true ]; then + # Only signal if ghostty is running + pgrep -f ghostty >/dev/null && pkill -SIGUSR2 ghostty || true + else + echo "Error: No ghostty config file found at ${CONFIG_FILES[*]}" >&2 + exit 1 + fi + ;; + +foot) + CONFIG_FILE="$HOME/.config/foot/foot.ini" + + # Check if the config file exists, create it if it doesn't. + if [ ! -f "$CONFIG_FILE" ]; then + # Create the config directory if it doesn't exist + mkdir -p "$(dirname "$CONFIG_FILE")" + # Create the config file with the noctalia theme + cat >"$CONFIG_FILE" <<'EOF' +[main] +include=~/.config/foot/themes/noctalia +EOF + else + # Check if theme is already set to noctalia + if ! grep -q "include.*noctalia" "$CONFIG_FILE"; then + # Remove any existing theme include line to prevent duplicates. + sed -i '/include=.*themes/d' "$CONFIG_FILE" + if grep -q '^\[main\]' "$CONFIG_FILE"; then + # Insert the include line after the existing [main] section header + sed -i '/^\[main\]/a include=~/.config/foot/themes/noctalia' "$CONFIG_FILE" + else + # If [main] doesn't exist, create it at the beginning with the include + sed -i '1i [main]\ninclude=~/.config/foot/themes/noctalia\n' "$CONFIG_FILE" + fi + fi + fi + ;; + +alacritty) + CONFIG_FILE="$HOME/.config/alacritty/alacritty.toml" + NEW_THEME_PATH='~/.config/alacritty/themes/noctalia.toml' + + # Check if the config file exists, create it if it doesn't. + if [ ! -f "$CONFIG_FILE" ]; then + # Create the config directory if it doesn't exist + mkdir -p "$(dirname "$CONFIG_FILE")" + # Create the config file with the noctalia theme import + cat >"$CONFIG_FILE" <<'EOF' +[general] +import = [ + "~/.config/alacritty/themes/noctalia.toml" +] +EOF + else + # Check if noctalia theme is already imported (any path variant) + if grep -q 'noctalia\.toml' "$CONFIG_FILE"; then + # Update old relative path to new absolute path if needed + if grep -q '"themes/noctalia.toml"' "$CONFIG_FILE"; then + sed -i 's|"themes/noctalia.toml"|"'"$NEW_THEME_PATH"'"|g' "$CONFIG_FILE" + fi + # Already has noctalia import with correct path, nothing to do + else + # No noctalia import found, add it + if grep -q '^\[general\]' "$CONFIG_FILE"; then + # Check if import line already exists under [general] + if grep -q '^import\s*=' "$CONFIG_FILE"; then + # Append to existing import array (before the closing bracket) + sed -i '/^import\s*=\s*\[/,/\]/{/\]/s|]| "'"$NEW_THEME_PATH"'",\n]|}' "$CONFIG_FILE" + else + # Add import line after [general] section header + sed -i '/^\[general\]/a import = ["'"$NEW_THEME_PATH"'"]' "$CONFIG_FILE" + fi + else + # Create [general] section with import at the beginning of the file + sed -i '1i [general]\nimport = ["'"$NEW_THEME_PATH"'"]\n' "$CONFIG_FILE" + fi + fi + fi + ;; + +wezterm) + CONFIG_FILE="$HOME/.config/wezterm/wezterm.lua" + WEZTERM_SCHEME_LINE='config.color_scheme = "Noctalia"' + + # Check if the config file exists. + if [ -f "$CONFIG_FILE" ]; then + + # Check if theme is already set to Noctalia (matches 'Noctalia' or "Noctalia") + if ! grep -q "^\s*config\.color_scheme\s*=\s*['\"]Noctalia['\"]\s*" "$CONFIG_FILE"; then + # Not set to Noctalia. Check if *any* color_scheme line exists. + if grep -q '^\s*config\.color_scheme\s*=' "$CONFIG_FILE"; then + # It exists, so we replace it with our desired line. + sed -i "s|^\(\s*config\.color_scheme\s*=\s*\).*$|\1\"Noctalia\"|" "$CONFIG_FILE" + else + # It doesn't exist, so we add it before the 'return config' line. + if grep -q '^\s*return\s*config' "$CONFIG_FILE"; then + # 'return config' exists. Insert the line before it. + sed -i '/^\s*return\s*config/i\'"$WEZTERM_SCHEME_LINE" "$CONFIG_FILE" + else + # This is a problem. We can't find the insertion point. + echo "Warning: 'config.color_scheme' not set and 'return config' line not found." >&2 + echo " Make sure $CONFIG_FILE is correct: https://wezterm.org/config/files.html" >&2 + fi + fi + fi + # touching the config file fools wezterm into reloading it + touch "$CONFIG_FILE" + else + echo "Error: wezterm.lua not found at $CONFIG_FILE" >&2 + echo "Instructions to create it: https://wezterm.org/config/files.html" >&2 + exit 1 + fi + ;; + +fuzzel) + CONFIG_FILE="$HOME/.config/fuzzel/fuzzel.ini" + + # Check if the config file exists, create it if it doesn't. + if [ ! -f "$CONFIG_FILE" ]; then + # Create the config directory if it doesn't exist + mkdir -p "$(dirname "$CONFIG_FILE")" + # Create the config file with the noctalia theme + cat >"$CONFIG_FILE" <<'EOF' +include=~/.config/fuzzel/themes/noctalia +EOF + else + # Check if theme is already set to noctalia + if grep -q "^include=~/.config/fuzzel/themes/noctalia$" "$CONFIG_FILE"; then + : # Already correct + elif grep -q "^include=.*themes" "$CONFIG_FILE"; then + # Replace existing theme include line in-place + sed -i 's|^include=.*themes.*|include=~/.config/fuzzel/themes/noctalia|' "$CONFIG_FILE" + else + # Add the new theme include line + echo "include=~/.config/fuzzel/themes/noctalia" >>"$CONFIG_FILE" + fi + fi + ;; + +walker) + CONFIG_FILE="$HOME/.config/walker/config.toml" + + # Check if the config file exists. + if [ -f "$CONFIG_FILE" ]; then + # Check if theme is already set to noctalia (flexible spacing) + if grep -qE '^theme\s*=\s*"noctalia"' "$CONFIG_FILE"; then + : # Already correct + elif grep -qE '^theme\s*=' "$CONFIG_FILE"; then + # Replace existing theme line in-place + sed -i -E 's/^theme\s*=.*/theme = "noctalia"/' "$CONFIG_FILE" + else + echo 'theme = "noctalia"' >>"$CONFIG_FILE" + fi + else + echo "Error: walker config file not found at $CONFIG_FILE" >&2 + exit 1 + fi + ;; + +vicinae) + # Apply the theme + vicinae theme set noctalia + ;; + +pywalfox) + # Set dark/light mode first if MODE is specified + if [ -n "$MODE" ]; then + if [ "$MODE" = "dark" ] || [ "$MODE" = "light" ]; then + pywalfox "$MODE" + else + echo "Warning: Invalid mode '$MODE'. Expected 'dark' or 'light'. Skipping mode switch." >&2 + fi + fi + # Update the theme + pywalfox update + ;; + +cava) + CONFIG_FILE="$HOME/.config/cava/config" + THEME_MODIFIED=false + + # Check if the config file exists. + if [ -f "$CONFIG_FILE" ]; then + # Check if [color] section exists + if grep -q '^\[color\]' "$CONFIG_FILE"; then + # Check if theme is already set to noctalia under [color] (flexible spacing) + if sed -n '/^\[color\]/,/^\[/p' "$CONFIG_FILE" | grep -qE '^theme\s*=\s*"noctalia"'; then + : # Already correct + elif sed -n '/^\[color\]/,/^\[/p' "$CONFIG_FILE" | grep -qE '^theme\s*='; then + # Replace existing theme line under [color] + sed -i -E '/^\[color\]/,/^\[/{s/^theme\s*=.*/theme = "noctalia"/}' "$CONFIG_FILE" + THEME_MODIFIED=true + else + # Add theme line after [color] + sed -i '/^\[color\]/a theme = "noctalia"' "$CONFIG_FILE" + THEME_MODIFIED=true + fi + else + # Add [color] section with theme at the end of file + echo "" >>"$CONFIG_FILE" + echo "[color]" >>"$CONFIG_FILE" + echo 'theme = "noctalia"' >>"$CONFIG_FILE" + THEME_MODIFIED=true + fi + + # Reload cava if it's running, but only if it's not using stdin config + if pgrep -f cava >/dev/null; then + # Check if Cava is running with -p /dev/stdin (standalone cava) + if ! pgrep -af cava | grep -q -- "-p.*stdin"; then + pkill -USR1 cava + fi + fi + else + echo "Error: cava config file not found at $CONFIG_FILE" >&2 + exit 1 + fi + ;; + +yazi) + CONFIG_FILE="$HOME/.config/yazi/theme.toml" + + # Create config directory if it doesn't exist + mkdir -p "$(dirname "$CONFIG_FILE")" + + if [ ! -f "$CONFIG_FILE" ]; then + cat >"$CONFIG_FILE" <<'EOF' +[flavor] +dark = "noctalia" +light = "noctalia" +EOF + else + # Check if [flavor] section exists + if grep -q '^\[flavor\]' "$CONFIG_FILE"; then + # Update or add dark/light lines under [flavor] + if sed -n '/^\[flavor\]/,/^\[/p' "$CONFIG_FILE" | grep -q '^dark\s*='; then + sed -i '/^\[flavor\]/,/^\[/{s/^dark\s*=.*/dark = "noctalia"/}' "$CONFIG_FILE" + else + sed -i '/^\[flavor\]/a dark = "noctalia"' "$CONFIG_FILE" + fi + if sed -n '/^\[flavor\]/,/^\[/p' "$CONFIG_FILE" | grep -q '^light\s*='; then + sed -i '/^\[flavor\]/,/^\[/{s/^light\s*=.*/light = "noctalia"/}' "$CONFIG_FILE" + else + sed -i '/^\[flavor\]/,/^dark/a light = "noctalia"' "$CONFIG_FILE" + fi + else + # Add [flavor] section at the end + echo "" >>"$CONFIG_FILE" + echo "[flavor]" >>"$CONFIG_FILE" + echo 'dark = "noctalia"' >>"$CONFIG_FILE" + echo 'light = "noctalia"' >>"$CONFIG_FILE" + fi + fi + ;; + +labwc) + # Update the theme + labwc -r + ;; + +niri) + CONFIG_FILE="$HOME/.config/niri/config.kdl" + INCLUDE_LINE='include "./noctalia.kdl"' + + # Check if the config file exists. + if [ ! -f "$CONFIG_FILE" ]; then + mkdir -p "$(dirname "$CONFIG_FILE")" + echo -e "\n$INCLUDE_LINE\n" >"$CONFIG_FILE" + else + # Check if noctalia include already exists (flexible: quotes, ./ prefix) + if grep -qE 'include\s+["'"'"'](\./)?noctalia\.kdl["'"'"']' "$CONFIG_FILE"; then + : # Already included + else + # Add the include line to the end of the file + echo -e "\n$INCLUDE_LINE\n" >>"$CONFIG_FILE" + fi + fi + ;; + +hyprland) + echo "๐ŸŽจ Applying 'noctalia' theme to Hyprland..." + CONFIG_DIR="$HOME/.config/hypr" + CONFIG_FILE="$CONFIG_DIR/hyprland.conf" + THEME_FILE="$CONFIG_DIR/noctalia/noctalia-colors.conf" + + INCLUDE_LINE="source = $THEME_FILE" + + # Check if the config file exists. + if [ ! -f "$CONFIG_FILE" ]; then + echo "Config file not found, creating $CONFIG_FILE..." + mkdir -p "$(dirname "$CONFIG_FILE")" + echo -e "\n$INCLUDE_LINE\n" >"$CONFIG_FILE" + echo "Created new config file with noctalia theme." + else + # Check if noctalia theme source already exists (flexible matching) + if grep -qE 'source\s*=\s*.*noctalia.*\.conf' "$CONFIG_FILE"; then + echo "Theme already included, skipping modification." + else + # Only convert symlink when we actually need to write (NixOS read-only symlinks) + if [ -L "$CONFIG_FILE" ] && [ ! -w "$CONFIG_FILE" ]; then + echo "Detected read-only symlink, converting to local file..." + cp --remove-destination "$(readlink -f "$CONFIG_FILE")" "$CONFIG_FILE" + chmod +w "$CONFIG_FILE" + fi + # Add the include line to the end of the file + echo -e "\n$INCLUDE_LINE\n" >>"$CONFIG_FILE" + echo "โœ… Added noctalia theme include to config." + fi + fi + + # Reload hyprland + hyprctl reload + ;; + +sway) + echo "๐ŸŽจ Applying 'noctalia' theme to Sway..." + CONFIG_DIR="$HOME/.config/sway" + CONFIG_FILE="$CONFIG_DIR/config" + INCLUDE_LINE='include ~/.config/sway/noctalia' + + # Check if the config file exists. + if [ ! -f "$CONFIG_FILE" ]; then + echo "Config file not found, creating $CONFIG_FILE..." + mkdir -p "$(dirname "$CONFIG_FILE")" + echo -e "\n$INCLUDE_LINE\n" >"$CONFIG_FILE" + echo "Created new config file with noctalia theme." + else + # Check if noctalia include already exists (flexible matching) + if grep -qE 'include\s+.*noctalia' "$CONFIG_FILE"; then + echo "Theme already included, skipping modification." + else + # Only convert symlink when we actually need to write (NixOS read-only symlinks) + if [ -L "$CONFIG_FILE" ] && [ ! -w "$CONFIG_FILE" ]; then + echo "Detected read-only symlink, converting to local file..." + cp --remove-destination "$(readlink -f "$CONFIG_FILE")" "$CONFIG_FILE" + chmod +w "$CONFIG_FILE" + fi + # Add the include line to the end of the file + echo -e "\n$INCLUDE_LINE\n" >>"$CONFIG_FILE" + echo "โœ… Added noctalia theme include to config." + fi + fi + + # Reload sway + swaymsg reload + ;; + +scroll) + echo "Applying 'noctalia' theme to Scroll..." + CONFIG_DIR="$HOME/.config/scroll" + CONFIG_FILE="$CONFIG_DIR/config" + INCLUDE_LINE='include ~/.config/scroll/noctalia' + + # Check if the config file exists. + if [ ! -f "$CONFIG_FILE" ]; then + echo "Config file not found, creating $CONFIG_FILE..." + mkdir -p "$(dirname "$CONFIG_FILE")" + echo -e "\n$INCLUDE_LINE\n" >"$CONFIG_FILE" + echo "Created new config file with noctalia theme." + else + # Check if noctalia include already exists (flexible matching) + if grep -qE 'include\s+.*noctalia' "$CONFIG_FILE"; then + echo "Theme already included, skipping modification." + else + # Only convert symlink when we actually need to write + if [ -L "$CONFIG_FILE" ] && [ ! -w "$CONFIG_FILE" ]; then + echo "Detected read-only symlink, converting to local file..." + cp --remove-destination "$(readlink -f "$CONFIG_FILE")" "$CONFIG_FILE" + chmod +w "$CONFIG_FILE" + fi + # Add the include line to the end of the file + echo -e "\n$INCLUDE_LINE\n" >>"$CONFIG_FILE" + echo "Added noctalia theme include to config." + fi + fi + + # Reload scroll + scrollmsg reload + ;; + +mango) + CONFIG_DIR="$HOME/.config/mango" + MAIN_CONFIG="$CONFIG_DIR/config.conf" + THEME_FILE="$CONFIG_DIR/noctalia.conf" + BACKUP_FILE="$CONFIG_DIR/theme.conf.bak" + # This sources the noctalia theme file + SOURCE_LINE="source = $THEME_FILE" + + # Color variables that should be moved to theme file + COLOR_VARS="shadowscolor|rootcolor|bordercolor|focuscolor|maximizescreencolor|urgentcolor|scratchpadcolor|globalcolor|overlaycolor" + + # Create config directory if it doesn't exist + mkdir -p "$CONFIG_DIR" + + # Check if theme is already sourced in main config + if [ -f "$MAIN_CONFIG" ] && grep -qF "$SOURCE_LINE" "$MAIN_CONFIG"; then + : # Theme already set + else + # First-time setup: backup and remove legacy color definitions + + # Scan all .conf files in config directory for legacy color variables + for conf_file in "$CONFIG_DIR"/*.conf; do + # Skip if no .conf files exist or if it's the theme file itself + [ -e "$conf_file" ] || continue + [ "$conf_file" = "$THEME_FILE" ] && continue + + # Check if this file contains any color variable definitions + if grep -qE "^($COLOR_VARS)\s*=" "$conf_file"; then + # Extract and append color definitions to backup file + grep -E "^($COLOR_VARS)\s*=" "$conf_file" >>"$BACKUP_FILE" + + # Remove color definitions from original file + if [ -L "$conf_file" ] && [ ! -w "$conf_file" ]; then + # Read-only symlink (e.g. NixOS): convert to local file + cp --remove-destination "$(readlink -f "$conf_file")" "$conf_file" + chmod +w "$conf_file" + sed -i -E "/^($COLOR_VARS)\s*=/d" "$conf_file" + else + # Edit the real file, preserving any writable symlink + sed -i -E "/^($COLOR_VARS)\s*=/d" "$(readlink -f "$conf_file")" + fi + fi + done + + # Only convert symlink when we actually need to write + if [ -L "$MAIN_CONFIG" ] && [ ! -w "$MAIN_CONFIG" ]; then + echo "Detected read-only symlink, converting to local file..." + cp --remove-destination "$(readlink -f "$MAIN_CONFIG")" "$MAIN_CONFIG" + chmod +w "$MAIN_CONFIG" + fi + + # Add source line to main config + if [ -f "$MAIN_CONFIG" ]; then + echo "" >>"$MAIN_CONFIG" + echo "# This sources the noctalia theme" >>"$MAIN_CONFIG" + echo -e "\n$SOURCE_LINE\n" >>"$MAIN_CONFIG" + else + echo "# This sources the noctalia theme" >"$MAIN_CONFIG" + echo -e "\n$SOURCE_LINE\n" >>"$MAIN_CONFIG" + fi + fi + + # Trigger live reload + if command -v mmsg >/dev/null 2>&1; then + mmsg -s -d reload_config + else + echo "Warning: mmsg command not found, manual restart may be needed." >&2 + fi + ;; + +btop) + CONFIG_FILE="$HOME/.config/btop/btop.conf" + + if [ -f "$CONFIG_FILE" ]; then + # Check if theme is already set to noctalia (flexible spacing) + if grep -qE '^color_theme\s*=\s*"noctalia"' "$CONFIG_FILE"; then + : # Already correct + elif grep -qE '^color_theme\s*=' "$CONFIG_FILE"; then + # Replace existing color_theme line in-place + sed -i -E 's/^color_theme\s*=.*/color_theme = "noctalia"/' "$CONFIG_FILE" + else + echo 'color_theme = "noctalia"' >>"$CONFIG_FILE" + fi + + if pgrep -x btop >/dev/null; then + pkill -SIGUSR2 -x btop + fi + else + echo "Warning: btop config file not found at $CONFIG_FILE" >&2 + fi + ;; + +zathura) + ZATHURA_INSTANCES=$(dbus-send --session \ + --dest=org.freedesktop.DBus \ + --type=method_call \ + --print-reply \ + /org/freedesktop/DBus \ + org.freedesktop.DBus.ListNames | + grep -o 'org.pwmt.zathura.PID-[0-9]*') + + for id in $ZATHURA_INSTANCES; do + dbus-send --session \ + --dest="$id" \ + --type=method_call \ + /org/pwmt/zathura \ + org.pwmt.zathura.ExecuteCommand \ + string:"source" + done + ;; + +*) + # Handle unknown application names. + echo "Error: Unknown application '$APP_NAME'." >&2 + exit 1 + ;; +esac diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/dev/build-settings-search-index.py b/arch/.config/quickshell/noctalia-shell/Scripts/dev/build-settings-search-index.py new file mode 100755 index 0000000..aa5e760 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/dev/build-settings-search-index.py @@ -0,0 +1,436 @@ +#!/usr/bin/env python3 +""" +Build settings search index from QML source files. + +Parses settings tab QML files to extract searchable metadata +(i18n keys, widget types, tab/sub-tab locations, visibility conditions). + +Output: Assets/settings-search-index.json + +Usage: + python Scripts/dev/build-settings-search-index.py +""" + +import json +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent.parent +SETTINGS_DIR = ROOT / "Modules" / "Panels" / "Settings" +TABS_DIR = SETTINGS_DIR / "Tabs" +OUTPUT = ROOT / "Assets" / "settings-search-index.json" + +# Widget types that have searchable label/description +WIDGET_TYPES = ( + "NToggle", + "NComboBox", + "NValueSlider", + "NSpinBox", + "NSearchableComboBox", + "NTextInputButton", + "NTextInput", + "NCheckbox", + "NLabel", + "NColorChoice", + "HookRow", +) + +# Regex patterns +RE_WIDGET_OPEN = re.compile( + r"^\s*(" + "|".join(WIDGET_TYPES) + r")\s*\{", re.MULTILINE +) +RE_LABEL = re.compile(r'label:\s*I18n\.tr\("([^"]+)"') +RE_DESCRIPTION = re.compile(r'description:\s*I18n\.tr\("([^"]+)"') +RE_VISIBLE = re.compile(r'^\s*visible:\s*(.+?)(?:\s*;)?\s*$') + +# Prefixes that indicate externally-resolvable conditions (singleton services or globals). +# Conditions referencing local variables (root., parent., model, index, etc.) are skipped. +RESOLVABLE_PREFIXES = ( + "CompositorService.", + "Settings.data.", + "Quickshell.", + "IdleService.", + "SystemStatService.", + "SoundService.", + "BluetoothService.", + "LocationService.", + "false", +) + + +def parse_component_declarations(content: str) -> dict[str, str]: + """ + Parse Component declarations from SettingsContent.qml. + + Returns: component_id -> QML type name (e.g. "generalTab" -> "GeneralTab") + """ + components = {} + # Match patterns like: + # Component { + # id: generalTab + # GeneralTab {} + # } + pattern = re.compile( + r"Component\s*\{\s*\n\s*id:\s*(\w+)\s*\n\s*(\w+)\s*\{", + re.MULTILINE, + ) + for m in pattern.finditer(content): + comp_id = m.group(1) + type_name = m.group(2) + components[comp_id] = type_name + + return components + + +def parse_tabs_model_order(content: str) -> list[tuple[str, str]]: + """ + Parse updateTabsModel() to get the ordered list of (source_id, label_key) pairs. + + Returns: list of (component_id, i18n_label_key) in display order. + """ + # Find the updateTabsModel function body + match = re.search(r"function updateTabsModel\(\)\s*\{", content) + if not match: + return [] + + func_body = content[match.end():] + + # Extract tab entries: each has "label" and "source" fields + entries = [] + for m in re.finditer( + r'"label":\s*"([^"]+)"[^}]*?"source":\s*(\w+)', + func_body, + re.DOTALL, + ): + entries.append((m.group(2), m.group(1))) + + return entries + + +def build_tab_mappings(content: str) -> tuple[dict[str, int], dict[str, str]]: + """ + Build mappings from QML type name to tabsModel index and label key. + + Parses Component declarations and updateTabsModel() order. + Returns: (type_to_index, type_to_label) + - type_to_index: e.g. {"GeneralTab": 0, ...} + - type_to_label: e.g. {"GeneralTab": "common.general", ...} + """ + components = parse_component_declarations(content) + entries = parse_tabs_model_order(content) + + type_to_index = {} + type_to_label = {} + for idx, (source_id, label_key) in enumerate(entries): + type_name = components.get(source_id) + if type_name: + type_to_index[type_name] = idx + type_to_label[type_name] = label_key + + return type_to_index, type_to_label + + +def get_subtab_info(parent_tab_file: Path) -> tuple[list[str], list[str | None]]: + """ + Parse a parent tab file to get subtab order and labels. + + Returns: (subtab_type_names, subtab_label_keys) + - subtab_type_names: list of component names like ["VolumesSubTab", ...] + - subtab_label_keys: list of i18n keys like ["common.volumes", ...] (same order) + """ + content = parent_tab_file.read_text() + + # Extract NTabButton labels in order from NTabBar + labels = [] + in_tabbar = False + tabbar_depth = 0 + + for line in content.splitlines(): + stripped = line.strip() + + if not in_tabbar: + if re.match(r"NTabBar\s*\{", stripped): + in_tabbar = True + tabbar_depth = 1 + continue + continue + + tabbar_depth += stripped.count("{") - stripped.count("}") + + if tabbar_depth <= 0: + break + + # Match text: I18n.tr("...") inside NTabButton + m = re.search(r'text:\s*I18n\.tr\("([^"]+)"', stripped) + if m: + labels.append(m.group(1)) + + # Extract subtab component names from NTabView + subtabs = [] + in_tabview = False + tabview_depth = 0 + + for line in content.splitlines(): + stripped = line.strip() + + if not in_tabview: + if re.match(r"NTabView\s*\{", stripped): + in_tabview = True + tabview_depth = 1 + continue + continue + + tabview_depth += stripped.count("{") - stripped.count("}") + + if tabview_depth <= 0: + break + + # Match component instantiations like "VolumesSubTab {}" or "VolumesSubTab {" + m = re.match(r"(\w+SubTab)\s*\{", stripped) + if m: + subtabs.append(m.group(1)) + + # Pad labels list if shorter than subtabs (shouldn't happen, but safety) + while len(labels) < len(subtabs): + labels.append(None) + + return subtabs, labels[:len(subtabs)] + + +def resolve_tab_info( + qml_file: Path, + type_to_index: dict[str, int], + type_to_label: dict[str, str], +) -> tuple[int | None, str | None, int | None, str | None]: + """ + Determine the tab index, tab label, sub-tab index, and sub-tab label for a QML file. + + Returns (tab_index, tab_label_key, sub_tab_index, sub_tab_label_key) + """ + parent = qml_file.parent + stem = qml_file.stem + + # Top-level tab files (directly in Tabs/) + if parent == TABS_DIR: + tab_index = type_to_index.get(stem) + tab_label = type_to_label.get(stem) + return tab_index, tab_label, None, None + + # Sub-directory files + dir_name = parent.name # e.g. "Audio", "Bar" + parent_type = f"{dir_name}Tab" # e.g. "AudioTab" + tab_index = type_to_index.get(parent_type) + tab_label = type_to_label.get(parent_type) + + if tab_index is None: + return None, None, None, None + + # Skip the parent tab file itself (e.g. AudioTab.qml) โ€” still scan for widgets + if stem.endswith("Tab") and not stem.endswith("SubTab"): + return tab_index, tab_label, None, None + + # Determine sub-tab index and label from parent tab's NTabBar/NTabView + parent_tab_file = parent / f"{dir_name}Tab.qml" + if not parent_tab_file.exists(): + return tab_index, tab_label, None, None + + subtab_names, subtab_labels = get_subtab_info(parent_tab_file) + try: + idx = subtab_names.index(stem) + sub_label = subtab_labels[idx] if idx < len(subtab_labels) else None + return tab_index, tab_label, idx, sub_label + except ValueError: + # File doesn't map to any subtab (e.g. a dialog). If the parent tab + # has subtabs, the focus ring can't reach widgets inside dialogs, so + # exclude them from the index. + if subtab_names: + return None, None, None, None + return tab_index, tab_label, None, None + + +def is_resolvable_condition(cond: str) -> bool: + """Check if a visibility condition can be resolved at runtime by the shell.""" + # Strip negation for prefix checking + check = cond.lstrip("!").lstrip(" ").lstrip("(").lstrip(" ") + return any(check.startswith(p) for p in RESOLVABLE_PREFIXES) + + +def build_scope_visibility(content: str) -> dict[int, list[str]]: + """ + For each line number, compute the list of inherited visibility conditions + from all enclosing QML scopes. + + Tracks brace-depth to maintain a scope stack. When a ``visible:`` property + is found, it is associated with the innermost scope. The conditions are + inherited by all lines within that scope. + + Returns: line_number -> list of condition strings + """ + lines = content.splitlines() + # Each entry: visibility condition string or None + scope_stack: list[str | None] = [] + result: dict[int, list[str]] = {} + + for line_num, raw_line in enumerate(lines): + stripped = raw_line.strip() + + # Record inherited conditions BEFORE processing this line's braces. + # This means a widget opening on this line inherits from parent scopes, + # not from its own scope (which hasn't been populated yet). + result[line_num] = [c for c in scope_stack if c is not None] + + # Process opening braces โ€” push new scopes + n_opens = stripped.count("{") + for _ in range(n_opens): + scope_stack.append(None) + + # Check for visible: property โ€” assign to the innermost scope + vis_match = RE_VISIBLE.match(stripped) + if vis_match and scope_stack: + cond = vis_match.group(1).strip() + if cond != "true": + scope_stack[-1] = cond + + # Process closing braces โ€” pop scopes + n_closes = stripped.count("}") + for _ in range(n_closes): + if scope_stack: + scope_stack.pop() + + return result + + +def extract_widget_blocks(content: str) -> list[tuple[str, str, int]]: + """ + Extract (widget_type, block_text, start_line) tuples from QML content. + + Uses brace-depth tracking to capture the full widget block. + """ + results = [] + lines = content.splitlines() + i = 0 + + while i < len(lines): + m = RE_WIDGET_OPEN.match(lines[i]) + if m: + widget_type = m.group(1) + depth = 0 + block_lines = [] + start_line = i + j = i + + while j < len(lines): + line = lines[j] + block_lines.append(line) + depth += line.count("{") - line.count("}") + if depth <= 0: + break + j += 1 + + block_text = "\n".join(block_lines) + results.append((widget_type, block_text, start_line)) + i = j + 1 + else: + i += 1 + + return results + + +def extract_entries( + qml_file: Path, + type_to_index: dict[str, int], + type_to_label: dict[str, str], +) -> list[dict]: + """Extract all searchable settings entries from a QML file.""" + tab_index, tab_label, sub_tab, sub_tab_label = resolve_tab_info( + qml_file, type_to_index, type_to_label + ) + if tab_index is None: + return [] + + content = qml_file.read_text() + scope_vis = build_scope_visibility(content) + entries = [] + + for widget_type, block, start_line in extract_widget_blocks(content): + label_match = RE_LABEL.search(block) + if not label_match: + continue + + label_key = label_match.group(1) + desc_match = RE_DESCRIPTION.search(block) + desc_key = desc_match.group(1) if desc_match else None + + # Collect visibility conditions: ancestor scopes + widget's own visible: + conditions = list(scope_vis.get(start_line, [])) + widget_vis = re.search( + r'^\s*visible:\s*(.+?)(?:\s*;)?\s*$', block, re.MULTILINE + ) + if widget_vis: + cond = widget_vis.group(1).strip() + if cond != "true" and cond not in conditions: + conditions.append(cond) + + # Keep only externally-resolvable conditions + conditions = [c for c in conditions if is_resolvable_condition(c)] + + entry = { + "labelKey": label_key, + "descriptionKey": desc_key, + "widget": widget_type, + "tab": tab_index, + "tabLabel": tab_label, + "subTab": sub_tab, + } + if sub_tab_label is not None: + entry["subTabLabel"] = sub_tab_label + if conditions: + entry["visibleWhen"] = conditions + + entries.append(entry) + + return entries + + +def main(): + if not TABS_DIR.exists(): + print(f"Error: Tabs directory not found: {TABS_DIR}", file=sys.stderr) + sys.exit(1) + + settings_content = SETTINGS_DIR / "SettingsContent.qml" + if not settings_content.exists(): + print(f"Error: SettingsContent.qml not found: {settings_content}", file=sys.stderr) + sys.exit(1) + + # Build type -> tabsModel index/label mappings from SettingsContent.qml + content = settings_content.read_text() + type_to_index, type_to_label = build_tab_mappings(content) + + if not type_to_index: + print("Error: Could not parse tab model from SettingsContent.qml", file=sys.stderr) + sys.exit(1) + + print(f"Parsed {len(type_to_index)} tab types from SettingsContent.qml") + + all_entries = [] + seen_labels = set() + + # Scan all QML files in Tabs/ (recursive) + for qml_file in sorted(TABS_DIR.rglob("*.qml")): + entries = extract_entries(qml_file, type_to_index, type_to_label) + for entry in entries: + if entry["labelKey"] not in seen_labels: + seen_labels.add(entry["labelKey"]) + all_entries.append(entry) + + # Write output + OUTPUT.parent.mkdir(parents=True, exist_ok=True) + with open(OUTPUT, "w") as f: + json.dump(all_entries, f, indent=2) + + print(f"Generated {len(all_entries)} entries -> {OUTPUT.relative_to(ROOT)}") + + +if __name__ == "__main__": + main() diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/dev/colorscheme-registry.sh b/arch/.config/quickshell/noctalia-shell/Scripts/dev/colorscheme-registry.sh new file mode 100755 index 0000000..e67f6cd --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/dev/colorscheme-registry.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Generate a registry.json from all color schemes in Assets/ColorScheme +# Output format matches ~/Development/misc/noctalia/noctalia-colorschemes/registry.json + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +COLORSCHEME_DIR="$PROJECT_ROOT/Assets/ColorScheme" + +# Start JSON output +echo '{' +echo ' "version": 1,' +echo ' "themes": [' + +first=true +for dir in "$COLORSCHEME_DIR"/*/; do + [ -d "$dir" ] || continue + + name=$(basename "$dir") + json_file="$dir/$name.json" + + [ -f "$json_file" ] || continue + + # Read the JSON file content + content=$(cat "$json_file") + + # Extract dark and light objects using jq + dark=$(echo "$content" | jq -c '.dark') + light=$(echo "$content" | jq -c '.light') + + # Skip if missing dark or light + [ "$dark" = "null" ] || [ "$light" = "null" ] && continue + + # Add comma before all but first entry + if [ "$first" = true ]; then + first=false + else + echo ',' + fi + + # Output theme entry + printf ' {\n' + printf ' "name": "%s",\n' "$name" + printf ' "path": "%s",\n' "$name" + printf ' "dark": %s,\n' "$dark" + printf ' "light": %s\n' "$light" + printf ' }' +done + +echo '' +echo ' ]' +echo '}' diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/dev/i18n-pull.sh b/arch/.config/quickshell/noctalia-shell/Scripts/dev/i18n-pull.sh new file mode 100755 index 0000000..3585824 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/dev/i18n-pull.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +# Pull translations from Noctalia Translate API +# Usage: ./i18n-pull.sh +# Example: ./i18n-pull.sh noctalia-shell ./Assets/Translations + +set -e + +# Configuration +API_BASE="${I18N_API_BASE:-https://i18n.noctalia.dev}" +PROJECT_SLUG="${1:-noctalia-shell}" +OUTPUT_DIR="${2:-./Assets/Translations}" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${YELLOW}Pulling translations for project: ${PROJECT_SLUG}${NC}" +echo "API: ${API_BASE}" +echo "Output: ${OUTPUT_DIR}" +echo "" + +# Confirmation +read -p "Pull translations and overwrite local files? [y/N] " -n 1 -r +echo "" +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Aborted." + exit 0 +fi +echo "" + +# Create output directory if it doesn't exist +mkdir -p "$OUTPUT_DIR" + +# Pull all translations +RESPONSE=$(curl -s -w "\n%{http_code}" "${API_BASE}/api/projects/${PROJECT_SLUG}/pull") +HTTP_CODE=$(echo "$RESPONSE" | tail -n1) +BODY=$(echo "$RESPONSE" | sed '$d') + +if [ "$HTTP_CODE" != "200" ]; then + echo -e "${RED}Error: HTTP ${HTTP_CODE}${NC}" + echo "$BODY" + exit 1 +fi + +# Check if jq is available +if ! command -v jq &> /dev/null; then + echo -e "${RED}Error: jq is required but not installed${NC}" + echo "Install with: apt install jq (Linux) or brew install jq (macOS)" + exit 1 +fi + +# Get list of locales +LOCALES=$(echo "$BODY" | jq -r 'keys[]') + +if [ -z "$LOCALES" ]; then + echo -e "${RED}Error: No translations found${NC}" + exit 1 +fi + +# Save each locale to a separate file +COUNT=0 +for LOCALE in $LOCALES; do + OUTPUT_FILE="${OUTPUT_DIR}/${LOCALE}.json" + echo "$BODY" | jq ".[\"${LOCALE}\"]" > "$OUTPUT_FILE" + echo -e "${GREEN}Saved: ${OUTPUT_FILE}${NC}" + COUNT=$((COUNT + 1)) +done + +echo "" +echo -e "${GREEN}Successfully pulled ${COUNT} language(s)${NC}" \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/dev/i18n-push.sh b/arch/.config/quickshell/noctalia-shell/Scripts/dev/i18n-push.sh new file mode 100755 index 0000000..433727a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/dev/i18n-push.sh @@ -0,0 +1,143 @@ +#!/bin/bash + +# Push translations to Noctalia Translate API +# Usage: TRANSLATION_PUSH_SECRET=your_secret ./push-translations.sh [--overwrite] [--lang ] [/path/to/Assets/Translations] +# Or set the secret in environment and pass the path as argument +# +# Options: +# --overwrite Overwrite existing translations +# --lang Upload only a single language (e.g., --lang fr) + +set -e + +# Parse arguments +OVERWRITE=false +TRANSLATIONS_DIR="Assets/Translations" +SINGLE_LANG="" + +while [[ $# -gt 0 ]]; do + case $1 in + --overwrite) + OVERWRITE=true + shift + ;; + --lang) + SINGLE_LANG="$2" + shift 2 + ;; + *) + TRANSLATIONS_DIR="$1" + shift + ;; + esac +done + +# Configuration +API_URL="${TRANSLATION_API_URL:-https://i18n.noctalia.dev}" +PROJECT_SLUG="${TRANSLATION_PROJECT:-noctalia-shell}" + +# Check for secret +if [ -z "$NOCTALIA_SHELL_TRANSLATION_PUSH_SECRET" ]; then + echo "Error: NOCTALIA_SHELL_TRANSLATION_PUSH_SECRET environment variable is required" + exit 1 +fi + +# Check if directory exists +if [ ! -d "$TRANSLATIONS_DIR" ]; then + echo "Error: Directory not found: $TRANSLATIONS_DIR" + exit 1 +fi + +# Check for jq +if ! command -v jq &> /dev/null; then + echo "Error: jq is required but not installed" + echo "Install with: apt install jq" + exit 1 +fi + +echo "Pushing translations from: $TRANSLATIONS_DIR" +echo "Target: $API_URL/api/projects/$PROJECT_SLUG/push" + +# Build combined JSON object +COMBINED_JSON="{}" + +if [ -n "$SINGLE_LANG" ]; then + # Single language mode + file="$TRANSLATIONS_DIR/$SINGLE_LANG.json" + if [ ! -f "$file" ]; then + echo "Error: Language file not found: $file" + exit 1 + fi + echo " Loading: $SINGLE_LANG ($SINGLE_LANG.json)" + COMBINED_JSON=$(echo "$COMBINED_JSON" | jq --arg locale "$SINGLE_LANG" --slurpfile content "$file" '. + {($locale): $content[0]}') +else + # All languages mode + for file in "$TRANSLATIONS_DIR"/*.json; do + if [ -f "$file" ]; then + # Extract locale from filename (e.g., en.json -> en) + filename=$(basename "$file") + locale="${filename%.json}" + + echo " Loading: $locale ($filename)" + + # Add this locale's translations to the combined object + COMBINED_JSON=$(echo "$COMBINED_JSON" | jq --arg locale "$locale" --slurpfile content "$file" '. + {($locale): $content[0]}') + fi + done +fi + +# Count locales +LOCALE_COUNT=$(echo "$COMBINED_JSON" | jq 'keys | length') +echo "Found $LOCALE_COUNT locale(s)" + +if [ "$LOCALE_COUNT" -eq 0 ]; then + echo "Error: No JSON files found in $TRANSLATIONS_DIR" + exit 1 +fi + +# Check if English exists (only required when pushing all languages) +if [ -z "$SINGLE_LANG" ] && ! echo "$COMBINED_JSON" | jq -e '.en' > /dev/null 2>&1; then + echo "Error: English (en.json) is required" + exit 1 +fi + +# Confirmation +echo "" +if [ -n "$SINGLE_LANG" ]; then + read -p "Push '$SINGLE_LANG' to $API_URL? [y/N] " -n 1 -r +else + read -p "Push $LOCALE_COUNT locale(s) to $API_URL? [y/N] " -n 1 -r +fi +echo "" +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Aborted." + exit 0 +fi + +# Build URL with optional overwrite parameter +PUSH_URL="$API_URL/api/projects/$PROJECT_SLUG/push" +if [ "$OVERWRITE" = true ]; then + PUSH_URL="$PUSH_URL?overwrite=true" + echo "Overwrite mode enabled" +fi + +# Push to API +echo "Pushing to API..." +RESPONSE=$(echo "$COMBINED_JSON" | curl -s -w "\n%{http_code}" -X POST \ + "$PUSH_URL" \ + -H "Authorization: Bearer $NOCTALIA_SHELL_TRANSLATION_PUSH_SECRET" \ + -H "Content-Type: application/json" \ + -d @-) + +# Extract HTTP status code (last line) and body (everything else) +HTTP_CODE=$(echo "$RESPONSE" | tail -n1) +BODY=$(echo "$RESPONSE" | sed '$d') + +if [ "$HTTP_CODE" -eq 200 ]; then + echo "Success!" + echo "$BODY" | jq . +else + echo "Error: HTTP $HTTP_CODE" + echo "$BODY" | jq . 2>/dev/null || echo "$BODY" + exit 1 +fi diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/dev/notifications-test-replace.sh b/arch/.config/quickshell/noctalia-shell/Scripts/dev/notifications-test-replace.sh new file mode 100755 index 0000000..50fcf1a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/dev/notifications-test-replace.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Test script for notification replacement functionality + +echo "Testing notification replacement..." +echo "" + +# Send initial notification and capture the ID +echo "Step 1: Sending initial notification 'asdf'..." +NOTIF_ID=$(notify-send -p "asdf") +echo "Notification ID: $NOTIF_ID" +echo "" + +# Wait a moment for the notification to appear +sleep 1 + +# Replace the notification +echo "Step 2: Replacing notification $NOTIF_ID with 'test'..." +notify-send -r "$NOTIF_ID" -p "test" +echo "" + +echo "The notification should now show 'test' instead of 'asdf'." +echo "If it still shows 'asdf', the replacement is not working." + diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/dev/notifications-test.sh b/arch/.config/quickshell/noctalia-shell/Scripts/dev/notifications-test.sh new file mode 100755 index 0000000..db985c8 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/dev/notifications-test.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env -S bash + +echo "Sending test notifications..." + +# Send a bunch of notifications with numbers +for i in {1..4}; do + notify-send "Notification $i" "This is test notification number $i with a very long text that will probably break the layout or maybe not? Who knows? Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + sleep 1 +done + +echo "All notifications sent!" + +# Additional tests for icon/image handling +if command -v notify-send >/dev/null 2>&1; then + echo "Sending icon/image tests..." + + # 1) Themed icon name + notify-send -i dialog-information "Icon name test" "Should resolve from theme (dialog-information)" + + sleep 1 + + # 2) Absolute path if a sample image exists + SAMPLE_IMG="/usr/share/pixmaps/steam.png" + if [ -f "$SAMPLE_IMG" ]; then + notify-send -i "$SAMPLE_IMG" "Absolute path test" "Should show the provided image path" + fi + + sleep 1 + + # 3) file:// URL form + if [ -f "$SAMPLE_IMG" ]; then + notify-send -i "file://$SAMPLE_IMG" "file:// URL test" "Should display after stripping scheme" + fi + + sleep 1 + + echo "Icon/image tests sent!" +fi + +# A test notification with actions +gdbus call --session \ + --dest org.freedesktop.Notifications \ + --object-path /org/freedesktop/Notifications \ + --method org.freedesktop.Notifications.Notify \ + "my-app" \ + 0 \ + "dialog-question" \ + "Confirmation Required" \ + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Do you want to proceed with the action? " \ + "['default', 'OK', 'cancel', 'Cancel', 'maybe', 'Maybe', 'undecided', 'Undecided']" \ + "{}" \ + 5000 diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/dev/qmlfmt.sh b/arch/.config/quickshell/noctalia-shell/Scripts/dev/qmlfmt.sh new file mode 100755 index 0000000..049070c --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/dev/qmlfmt.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env -S bash +set -euo pipefail + +# QML Formatter Script + +# Suppress Qt debug logging from qmlformat +export QT_LOGGING_RULES="qt.qmldom.*=false" + +# Find qmlformat binary +QMLFORMAT="" +for path in "/usr/lib64/qt6/bin/qmlformat" "/usr/lib/qt6/bin/qmlformat"; do + if [ -x "$path" ]; then + QMLFORMAT="$path" + break + fi +done + +# Fallback to PATH +if [ -z "$QMLFORMAT" ] && command -v qmlformat &>/dev/null; then + QMLFORMAT="qmlformat" +fi + +if [ -z "$QMLFORMAT" ]; then + echo "No 'qmlformat' found in standard locations or PATH." >&2 + echo "To proceed, install it via 'qt6-tools', 'qt6-declarative-tools' or 'qt6-qtdeclarative-devel'" >&2 + exit 1 +fi + +# Detect qmlformat version for flag compatibility +EXTRA_FLAGS="" +if version=$("$QMLFORMAT" --version 2>&1) && [[ "$version" =~ ([0-9]+\.[0-9]+) ]]; then + if [[ "$(printf '%s\n6.10\n' "${BASH_REMATCH[1]}" | sort -V | head -1)" == "6.10" ]]; then + EXTRA_FLAGS="-S --semicolon-rule always" + fi +fi + +format_file() { + ${QMLFORMAT} -w 2 -W 360 ${EXTRA_FLAGS} -i "$1" || { echo "Failed: $1" >&2; return 1; } +} + +export -f format_file +export QMLFORMAT EXTRA_FLAGS + +# Find all .qml files +mapfile -t all_files < <(find "${1:-.}" -name "*.qml" -type f) +[ ${#all_files[@]} -eq 0 ] && { echo "No QML files found"; exit 0; } + +echo "Formatting ${#all_files[@]} files..." +printf '%s\0' "${all_files[@]}" | \ + xargs -0 -P "${QMLFMT_JOBS:-$(nproc)}" -I {} bash -c 'format_file "$@"' _ {} \ + && echo "Done" || { echo "Errors occurred" >&2; exit 1; } diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/dev/shaders-compile.sh b/arch/.config/quickshell/noctalia-shell/Scripts/dev/shaders-compile.sh new file mode 100755 index 0000000..415c60d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/dev/shaders-compile.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +# Find qsb binary in common locations. +QSB_PATHS=( + "/usr/lib/qt6/bin/qsb" + "/usr/lib64/qt6/bin/qsb" +) + +QSB_BIN="" +for path in "${QSB_PATHS[@]}"; do + if [ -x "$path" ]; then + QSB_BIN="$path" + break + fi +done + +if [ -z "$QSB_BIN" ]; then + echo "Error: qsb binary not found in any of: ${QSB_PATHS[*]}" + exit 1 +fi + +# Directory containing the source shaders. +SOURCE_DIR="Shaders/frag/" + +# Directory where the compiled shaders will be saved. +DEST_DIR="Shaders/qsb/" + +# Check if the source directory exists. +if [ ! -d "$SOURCE_DIR" ]; then + echo "Source directory $SOURCE_DIR not found!" + exit 1 +fi + +# Create the destination directory if it doesn't exist. +mkdir -p "$DEST_DIR" + +# Array to hold the list of full paths to the shaders. +SHADERS_TO_COMPILE=() + +# Specific files mode. +if [ "$#" -gt 0 ]; then + + # Loop through all command-line arguments ($@ holds all arguments). + for SINGLE_FILE in "$@"; do + + # Construct the full path to the source file. + FULL_PATH="$SOURCE_DIR$SINGLE_FILE" + + # Check if the specified file exists in the SOURCE_DIR. + if [ ! -f "$FULL_PATH" ]; then + echo "Error: Specified file '$SINGLE_FILE' not found in $SOURCE_DIR! Skipping." + continue + fi + + # Add the valid file to the compilation list. + SHADERS_TO_COMPILE+=("$FULL_PATH") + done + + # Check if any valid files were found to compile. + if [ ${#SHADERS_TO_COMPILE[@]} -eq 0 ]; then + echo "No valid shaders found to compile." + exit 1 + fi + +# Whole directory mode (no argument provided). +else + # Use find to generate the list of files and assign it to the array. + while IFS= read -r shader_path; do + if [ -n "$shader_path" ]; then + SHADERS_TO_COMPILE+=("$shader_path") + fi + done < <(find "$SOURCE_DIR" -maxdepth 1 -name "*.frag") + +fi + +# Loop through the list of shaders to compile. +for shader in "${SHADERS_TO_COMPILE[@]}"; do + + # Get the base name of the file (e.g., wp_fade). + shader_name=$(basename "$shader" .frag) + + # Construct the output path for the compiled shader. + output_path="$DEST_DIR$shader_name.frag.qsb" + + # Construct and run the qsb command. + "$QSB_BIN" --qt6 -o "$output_path" "$shader" + + # Print a message to confirm compilation. + echo "Compiled $(basename "$shader") to $output_path" +done + +echo "Shader compilation complete." diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/dev/template-processor-analysis.py b/arch/.config/quickshell/noctalia-shell/Scripts/dev/template-processor-analysis.py new file mode 100755 index 0000000..72d0588 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/dev/template-processor-analysis.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python3 +""" +Analyze Noctalia's template-processor color extraction. + +Usage: + ./template-processor-analysis.py + ./template-processor-analysis.py ~/Pictures/Wallpapers/example.png + +Shows extracted colors for all scheme types and compares M3 schemes with matugen. + +Scheme types: +- M3 schemes (tonal-spot, fruit-salad, rainbow, content): Compared with matugen +- vibrant: Prioritizes the most saturated colors regardless of area +- faithful: Prioritizes dominant colors by area coverage +- dysfunctional: Like faithful but picks the 2nd most dominant color family +- muted: Preserves hue but caps saturation low (for monochrome wallpapers) +""" + +import argparse +import json +import subprocess +import sys +from pathlib import Path + +# Add the theming lib to path +SCRIPT_DIR = Path(__file__).parent.resolve() +THEMING_DIR = SCRIPT_DIR.parent / "python" / "src" / "theming" +sys.path.insert(0, str(THEMING_DIR)) + +from lib.color import Color +from lib.hct import Hct + + +def hue_diff(h1: float, h2: float) -> float: + """Calculate circular hue difference.""" + diff = abs(h1 - h2) + return min(diff, 360.0 - diff) + + +def hex_to_rgb(hex_color: str) -> tuple[int, int, int]: + """Convert hex to RGB tuple.""" + h = hex_color.lstrip('#') + return tuple(int(h[i:i+2], 16) for i in (0, 2, 4)) + + +def rgb_distance(hex1: str, hex2: str) -> float: + """Calculate Euclidean RGB distance (0-441 range).""" + r1, g1, b1 = hex_to_rgb(hex1) + r2, g2, b2 = hex_to_rgb(hex2) + return ((r1-r2)**2 + (g1-g2)**2 + (b1-b2)**2) ** 0.5 + + +def get_hct(hex_color: str) -> Hct: + """Convert hex color to HCT.""" + return Color.from_hex(hex_color).to_hct() + + +def hue_to_name(hue: float) -> str: + """Convert hue to color name.""" + if hue < 30 or hue >= 330: + return "RED" + elif hue < 60: + return "ORANGE" + elif hue < 90: + return "YELLOW" + elif hue < 150: + return "GREEN" + elif hue < 210: + return "CYAN" + elif hue < 270: + return "BLUE" + elif hue < 330: + return "PURPLE" + return "RED" + + +def run_our_processor(image_path: Path, scheme: str) -> dict | None: + """Run our template-processor and return colors.""" + cmd = [ + sys.executable, + str(THEMING_DIR / "template-processor.py"), + str(image_path), + "--scheme-type", scheme, + "--dark" + ] + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + data = json.loads(result.stdout) + return data.get("dark", {}) + except (subprocess.CalledProcessError, json.JSONDecodeError) as e: + print(f"Error running our processor: {e}", file=sys.stderr) + return None + + +def run_matugen(image_path: Path, scheme: str) -> dict | None: + """Run matugen and return colors.""" + matugen_scheme = f"scheme-{scheme}" + cmd = [ + "matugen", "image", str(image_path), + "--json", "hex", + "--dry-run", + "-t", matugen_scheme + ] + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + data = json.loads(result.stdout) + colors = data.get("colors", {}) + # Extract dark mode values + return {k: v.get("dark", v) for k, v in colors.items() if isinstance(v, dict)} + except subprocess.CalledProcessError as e: + print(f"Error running matugen: {e}", file=sys.stderr) + return None + except json.JSONDecodeError as e: + print(f"Error parsing matugen output: {e}", file=sys.stderr) + return None + + +def analyze_vibrant_faithful_muted(image_path: Path) -> None: + """Analyze vibrant, faithful, dysfunctional, and muted mode outputs.""" + print("\n" + "=" * 78) + print("VIBRANT vs FAITHFUL vs DYSFUNCTIONAL vs MUTED COMPARISON") + print("=" * 78) + print() + print("Vibrant: Prioritizes the most saturated colors regardless of area") + print("Faithful: Prioritizes dominant colors by area coverage") + print("Dysfunctional: Like faithful but picks 2nd most dominant color family") + print("Muted: Preserves hue but caps saturation low (monochrome wallpapers)") + print() + print("-" * 78) + print(f"{'Mode':<14} {'Color':<12} {'Hex':<10} {'Hue':>8} {'Chroma':>8} {'Name':<10}") + print("-" * 78) + + for scheme in ["vibrant", "faithful", "dysfunctional", "muted"]: + colors = run_our_processor(image_path, scheme) + if not colors: + print(f"{scheme}: Failed to get colors") + continue + + for key in ["primary", "secondary", "tertiary"]: + hex_color = colors.get(key, "") + if not hex_color: + continue + + try: + hct = get_hct(hex_color) + name = hue_to_name(hct.hue) + print(f"{scheme:<14} {key:<12} {hex_color:<10} {hct.hue:>7.1f}ยฐ {hct.chroma:>7.1f} {name:<10}") + except Exception as e: + print(f"{scheme:<14} {key:<12} Error: {e}") + + print("-" * 78) + + # Summary comparison + vibrant = run_our_processor(image_path, "vibrant") + faithful = run_our_processor(image_path, "faithful") + dysfunctional = run_our_processor(image_path, "dysfunctional") + muted = run_our_processor(image_path, "muted") + + if vibrant and faithful and dysfunctional and muted: + print() + print("Summary:") + v_hct = get_hct(vibrant.get("primary", "#000000")) + f_hct = get_hct(faithful.get("primary", "#000000")) + d_hct = get_hct(dysfunctional.get("primary", "#000000")) + m_hct = get_hct(muted.get("primary", "#000000")) + + v_name = hue_to_name(v_hct.hue) + f_name = hue_to_name(f_hct.hue) + d_name = hue_to_name(d_hct.hue) + m_name = hue_to_name(m_hct.hue) + + vf_diff = hue_diff(v_hct.hue, f_hct.hue) + fd_diff = hue_diff(f_hct.hue, d_hct.hue) + + print(f" Vibrant primary: {vibrant.get('primary')} ({v_name}, hue {v_hct.hue:.0f}ยฐ, chroma {v_hct.chroma:.1f})") + print(f" Faithful primary: {faithful.get('primary')} ({f_name}, hue {f_hct.hue:.0f}ยฐ, chroma {f_hct.chroma:.1f})") + print(f" Dysfunctional primary:{dysfunctional.get('primary')} ({d_name}, hue {d_hct.hue:.0f}ยฐ, chroma {d_hct.chroma:.1f})") + print(f" Muted primary: {muted.get('primary')} ({m_name}, hue {m_hct.hue:.0f}ยฐ, chroma {m_hct.chroma:.1f})") + print(f" V-F hue diff: {vf_diff:.1f}ยฐ") + print(f" F-D hue diff: {fd_diff:.1f}ยฐ") + + if vf_diff > 60: + print(f" โ†’ Vibrant/Faithful picked DIFFERENT color families ({v_name} vs {f_name})") + else: + print(f" โ†’ Vibrant/Faithful picked SIMILAR colors") + + if fd_diff > 30: + print(f" โ†’ Faithful/Dysfunctional picked DIFFERENT color families ({f_name} vs {d_name})") + else: + print(f" โ†’ Faithful/Dysfunctional picked SIMILAR colors (may only have 1 dominant family)") + + # Note the muted chroma reduction + if m_hct.chroma < 20: + print(f" โ†’ Muted successfully reduced chroma to {m_hct.chroma:.1f}") + else: + print(f" โ†’ Muted chroma still moderately high ({m_hct.chroma:.1f})") + + +def compare_m3_schemes(image_path: Path, has_matugen: bool) -> None: + """Compare all M3 schemes between our processor and matugen.""" + schemes = ["tonal-spot", "fruit-salad", "rainbow", "content", "monochrome"] + color_keys = ["primary", "secondary", "tertiary", "surface", "on_surface"] + + print("\n" + "=" * 78) + print("M3 SCHEMES" + (" (compared with matugen)" if has_matugen else "")) + print("=" * 78) + + if has_matugen: + # Header for comparison mode + print(f"{'Scheme':<12} {'Color':<14} {'Ours':<10} {'Matugen':<10} {'Diff':>10} {'Match':<10}") + print("-" * 78) + + for scheme in schemes: + ours = run_our_processor(image_path, scheme) + matugen = run_matugen(image_path, scheme) + + if not ours or not matugen: + print(f"{scheme}: Failed to get colors") + continue + + for key in color_keys: + our_hex = ours.get(key, "") + mat_hex = matugen.get(key, "") + + if not our_hex or not mat_hex: + continue + + try: + our_hct = get_hct(our_hex) + mat_hct = get_hct(mat_hex) + avg_chroma = (our_hct.chroma + mat_hct.chroma) / 2 + + # For low-chroma colors, use RGB distance instead of hue + if avg_chroma < 15: + rgb_dist = rgb_distance(our_hex, mat_hex) + if rgb_dist < 10: + match = "excellent" + elif rgb_dist < 25: + match = "good" + elif rgb_dist < 50: + match = "fair" + else: + match = "poor" + diff_str = f"{rgb_dist:>5.1f} rgb" + else: + diff = hue_diff(our_hct.hue, mat_hct.hue) + if diff < 5: + match = "excellent" + elif diff < 15: + match = "good" + elif diff < 30: + match = "fair" + else: + match = "poor" + diff_str = f"{diff:>5.1f} hue" + + print(f"{scheme:<12} {key:<14} {our_hex:<10} {mat_hex:<10} {diff_str:>10} {match:<10}") + except Exception as e: + print(f"{scheme:<12} {key:<14} Error: {e}") + + print("-" * 78) + else: + # Header for standalone mode + print(f"{'Scheme':<12} {'Color':<14} {'Hex':<10} {'Hue':>8} {'Chroma':>8} {'Name':<10}") + print("-" * 78) + + for scheme in schemes: + ours = run_our_processor(image_path, scheme) + + if not ours: + print(f"{scheme}: Failed to get colors") + continue + + for key in ["primary", "secondary", "tertiary"]: + our_hex = ours.get(key, "") + if not our_hex: + continue + + try: + hct = get_hct(our_hex) + name = hue_to_name(hct.hue) + print(f"{scheme:<12} {key:<14} {our_hex:<10} {hct.hue:>7.1f}ยฐ {hct.chroma:>7.1f} {name:<10}") + except Exception as e: + print(f"{scheme:<12} {key:<14} Error: {e}") + + print("-" * 78) + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Analyze Noctalia template-processor color extraction" + ) + parser.add_argument( + "wallpaper", + type=Path, + help="Path to wallpaper image" + ) + parser.add_argument( + "--no-matugen", + action="store_true", + help="Skip matugen comparison" + ) + + args = parser.parse_args() + + if not args.wallpaper.exists(): + print(f"Error: File not found: {args.wallpaper}", file=sys.stderr) + return 1 + + print(f"\nAnalyzing: {args.wallpaper.name}") + + # Check if matugen is available + has_matugen = False + if not args.no_matugen: + try: + subprocess.run(["matugen", "--version"], capture_output=True, check=True) + has_matugen = True + except (subprocess.CalledProcessError, FileNotFoundError): + print("Note: matugen not found, skipping M3 comparison") + + # Always show vibrant vs faithful vs muted first (most useful) + analyze_vibrant_faithful_muted(args.wallpaper) + + # Then show M3 schemes + compare_m3_schemes(args.wallpaper, has_matugen) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/calendar/calendar-events.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/calendar/calendar-events.py new file mode 100755 index 0000000..e080eb8 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/calendar/calendar-events.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 +import gi +gi.require_version('EDataServer', '1.2') +gi.require_version('ECal', '2.0') +gi.require_version('ICalGLib', "3.0") + +import json, sys +from datetime import datetime, timedelta, timezone +from zoneinfo import ZoneInfo +from gi.repository import ECal, EDataServer, ICalGLib + +start_time = int(sys.argv[1]) +end_time = int(sys.argv[2]) + +print(f"Starting with time range: {start_time} to {end_time}", file=sys.stderr) + +all_events = [] + +def safe_get_time(ical_time): + if not ical_time: + return None, False + try: + year, month, day = ical_time.get_year(), ical_time.get_month(), ical_time.get_day() + is_all_day = hasattr(ical_time, "is_date") and ical_time.is_date() + if is_all_day: + # All-day events (birthdays, holidays) should not need + # to be timezone converted + return int(datetime(year, month, day).timestamp()), True + + hour, minute, second = ical_time.get_hour(), ical_time.get_minute(), ical_time.get_second() + + # Determine timezone for proper conversion + tz_obj = ical_time.get_timezone() if hasattr(ical_time, 'get_timezone') else None + tzid = tz_obj.get_tzid() if tz_obj else None + tz = None + if ical_time.is_utc() if hasattr(ical_time, 'is_utc') else False: + tz = timezone.utc # Explicit UTC time + elif tzid: + # Evolution uses non-standard format: /freeassociation.sourceforge.net/America/Los_Angeles + # Strip prefix to get IANA name: America/Los_Angeles + iana = tzid.replace('/freeassociation.sourceforge.net/', '') if tzid.startswith('/') else tzid + try: tz = ZoneInfo(iana) + except: pass + + # Create timezone-aware datetime + dt = datetime(year, month, day, hour, minute, second, tzinfo=tz) + return int(dt.timestamp()), False + except: + return None, False + +def add_event(summary, calendar_name, start_ts, end_ts, location="", description="", all_day=False, calendar_uid="", uid=""): + all_events.append({ + 'calendar': calendar_name, + 'summary': summary, + 'start': start_ts, + 'end': end_ts, + 'location': location, + 'description': description, + 'calendar_uid': calendar_uid, + 'uid': uid + }) + +registry = EDataServer.SourceRegistry.new_sync(None) +sources = registry.list_sources(EDataServer.SOURCE_EXTENSION_CALENDAR) + +for source in sources: + if not source.get_enabled(): + continue + + calendar_name = source.get_display_name() + print(f"\nProcessing calendar: {calendar_name}", file=sys.stderr) + + try: + client = ECal.Client.connect_sync(source, ECal.ClientSourceType.EVENTS, 5, None) + + start_dt = datetime.fromtimestamp(start_time) + end_dt = datetime.fromtimestamp(end_time) + start_str = start_dt.strftime("%Y%m%dT%H%M%S") + end_str = end_dt.strftime("%Y%m%dT%H%M%S") + + query = f'(occur-in-time-range? (make-time "{start_str}") (make-time "{end_str}"))' + success, raw_events = client.get_object_list_sync(query, None) + + if not success or not raw_events: + continue + + for raw_obj in raw_events: + obj = raw_obj[1] if isinstance(raw_obj, tuple) else raw_obj + comp = None + + if isinstance(obj, ICalGLib.Component): + comp = obj + elif isinstance(obj, ECal.Component): + try: + ical_str = obj.to_string() + temp_comp = ICalGLib.Component.new_from_string(ical_str) + if temp_comp.getName() == "VEVENT": + comp = temp_comp + except Exception: + comp = None + + if not comp: + summary = getattr(obj, "get_summary", lambda: "(No title)")() + dtstart = getattr(obj, "get_dtstart", lambda: None)() + dtend = getattr(obj, "get_dtend", lambda: None)() + location = getattr(obj, "get_location", lambda: "")() or "" + description = getattr(obj, "get_description", lambda: "")() or "" + start_ts, all_day = safe_get_time(dtstart) + end_ts, _ = safe_get_time(dtend) + if start_ts: + if end_ts is None: + end_ts = start_ts + 3600 + event_uid = getattr(obj, "get_uid", lambda: "")() or "" + add_event(summary, calendar_name, start_ts, end_ts, location, description, + calendar_uid=source.get_uid(), uid=event_uid) + continue + + summary = getattr(comp, "get_summary", lambda: "(No title)")() + location = getattr(comp, "get_location", lambda: "")() or "" + description = getattr(comp, "get_description", lambda: "")() or "" + dtstart = getattr(comp, "get_dtstart", lambda: None)() + dtend = getattr(comp, "get_dtend", lambda: None)() + start_ts, all_day = safe_get_time(dtstart) + end_ts, _ = safe_get_time(dtend) + if end_ts is None and start_ts is not None: + end_ts = start_ts + 3600 + + rrule_getter = getattr(comp, "get_first_property", None) + if rrule_getter: + rrule_prop = comp.get_first_property(73) # ICAL_RRULE_PROPERTY + if rrule_prop: + rrule_value = rrule_prop.get_value() # ICalGLib.Value + + try: + recurrence = rrule_value.get_recur() # -> ICalGLib.Recurrence + + except AttributeError: + rrule_str = str(rrule_value) + recurrence = ICalGLib.Recurrence.new_from_string(rrule_str) + + if recurrence: + freq = recurrence.get_freq() + + rdates = getattr(comp, "get_rdate_list", lambda: [])() + exdates = getattr(comp, "get_exdate_list", lambda: [])() + + # --- normal event --- + if not rrule_prop and not rdates: + add_event(summary, calendar_name, start_ts, end_ts, location, description, + calendar_uid=source.get_uid(), uid=comp.get_uid() or "") + continue + + # --- recurrent events --- + if freq: + summary = comp.get_summary() or "(No title)" + dtstart = comp.get_dtstart() + dtend = comp.get_dtend() + start_ts, all_day = safe_get_time(dtstart) + end_ts, _ = safe_get_time(dtend) + if end_ts is None and start_ts is not None: + end_ts = start_ts + 3600 # 1h default + + interval = recurrence.get_interval() or 1 + count = recurrence.get_count() + until_dt = recurrence.get_until() + until_ts, _ = safe_get_time(until_dt) if until_dt else (None, False) + if until_ts is None: + until_ts = end_time + + occurrences = [] + current_ts = start_ts + added = 0 + + match freq: + case 0: #SECONDLY + delta = timedelta(seconds=interval) + while (current_ts <= until_ts) and (not count or added < count): + occurrences.append((current_ts, current_ts + (end_ts - start_ts))) + current_ts += int(delta.total_seconds()) + added += 1 + + case 1: #MINUTELY + delta = timedelta(minutes=interval) + while (current_ts <= until_ts) and (not count or added < count): + occurrences.append((current_ts, current_ts + (end_ts - start_ts))) + current_ts += int(delta.total_seconds()) + added += 1 + + case 2: #HOURLY + delta = timedelta(hours=interval) + while (current_ts <= until_ts) and (not count or added < count): + occurrences.append((current_ts, current_ts + (end_ts - start_ts))) + current_ts += int(delta.total_seconds()) + added += 1 + + case 3: # DAILY + delta = timedelta(days=interval) + while (current_ts <= until_ts) and (not count or added < count): + occurrences.append((current_ts, current_ts + (end_ts - start_ts))) + current_ts += int(delta.total_seconds()) + added += 1 + + case 4: # WEEKLY + delta = timedelta(weeks=interval) + while (current_ts <= until_ts) and (not count or added < count): + occurrences.append((current_ts, current_ts + (end_ts - start_ts))) + current_ts += int(delta.total_seconds()) + added += 1 + + case 5: # MONTHLY + from dateutil.relativedelta import relativedelta + dt = datetime.fromtimestamp(current_ts) + while (current_ts <= until_ts) and (not count or added < count): + occurrences.append((current_ts, current_ts + (end_ts - start_ts))) + dt += relativedelta(months=interval) + current_ts = int(dt.timestamp()) + added += 1 + + case 6: # YEARLY + from dateutil.relativedelta import relativedelta + dt = datetime.fromtimestamp(current_ts) + while (current_ts <= until_ts) and (not count or added < count): + occurrences.append((current_ts, current_ts + (end_ts - start_ts))) + dt += relativedelta(years=interval) + current_ts = int(dt.timestamp()) + added += 1 + + case _: # NONE + occurrences.append((start_ts, end_ts)) + + # --- add occurences to all_events --- + for occ_start, occ_end in occurrences: + add_event(summary, calendar_name, occ_start, occ_end, location, description, + calendar_uid=source.get_uid(), uid=comp.get_uid() or "") + + + except Exception as e: + print(f" Error for {calendar_name}: {e}", file=sys.stderr) + +all_events.sort(key=lambda x: x['start']) +print(json.dumps(all_events, indent=4)) + diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/calendar/check-calendar.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/calendar/check-calendar.py new file mode 100755 index 0000000..463d2e4 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/calendar/check-calendar.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +import gi + +gi.require_version('EDataServer', '1.2') +gi.require_version('ECal', '2.0') + +try: + from gi.repository import ECal, EDataServer + print("available") +except ImportError as e: + print(f"unavailable: {e}") diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/calendar/khal-events.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/calendar/khal-events.py new file mode 100755 index 0000000..6b54542 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/calendar/khal-events.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +import json +import os +import re +import subprocess +import sys +from datetime import datetime +from pathlib import Path + +def get_khal_date_format(): + """Read the khal config and extract the longdatetimeformat.""" + xdg_config = os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) + config_path = Path(xdg_config) / 'khal' / 'config' + + if not config_path.exists(): + return '%c' + + with open(config_path, 'r') as f: + for line in f: + if m := re.match(r'^longdatetimeformat\s?=\s?(.+?)\s*$', line): + date_format = m.group(1).strip() + return date_format + + return '%c' + + +def to_khal(date_str, khal_format): + dt = datetime.strptime(date_str, "%Y-%m-%d") + return dt.strftime(khal_format) + +def from_khal(date_str, khal_format): + if not date_str: + return '' + dt = datetime.strptime(date_str, khal_format) + return dt.isoformat() + +def convert_event(event, khal_format): + event['start-long-full'] = from_khal(event.get('start-long-full', ''), khal_format) + event['end-long-full'] = from_khal(event.get('end-long-full', ''), khal_format) + return event + +def main(): + start_date = sys.argv[1] + duration = sys.argv[2] + + khal_format = get_khal_date_format() + khal_start = to_khal(start_date, khal_format) + + cmd = [ + 'khal', 'list', + '--json', 'uid', + '--json', 'title', + '--json', 'start-long-full', + '--json', 'end-long-full', + '--json', 'calendar', + '--json', 'description', + '--json', 'location', + '--json', 'repeat-pattern', + khal_start, + duration + ] + + result = subprocess.run(cmd, capture_output=True, text=True) + output = result.stdout.strip() + + for line in output.split('\n'): + day_events = json.loads(line) + print(json.dumps([convert_event(e, khal_format) for e in day_events])) + +if __name__ == '__main__': + main() diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/calendar/list-calendars.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/calendar/list-calendars.py new file mode 100755 index 0000000..12c67bd --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/calendar/list-calendars.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +import gi + +gi.require_version('EDataServer', '1.2') +import json + +from gi.repository import EDataServer + +registry = EDataServer.SourceRegistry.new_sync(None) +sources = registry.list_sources(EDataServer.SOURCE_EXTENSION_CALENDAR) + +calendars = [] +for source in sources: + if source.get_enabled(): + calendars.append({ + 'uid': source.get_uid(), + 'name': source.get_display_name(), + 'enabled': True + }) + +print(json.dumps(calendars)) diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/network/bluetooth-pair.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/network/bluetooth-pair.py new file mode 100644 index 0000000..6d8eddc --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/network/bluetooth-pair.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +import errno +import os +import pty +import select +import subprocess +import sys +import time +# flake8: noqa: E501 # Line too long +version = "0.0.2-1" + + +def log(msg) -> None: + sys.stdout.write(f"[pair] {msg}\n") + sys.stdout.flush() # Flush to ensure the message is passed + + +def pair_fast(): + if len(sys.argv) < 5: + log("Usage: bluetooth-pair.py ") + sys.exit(2) + + addr = sys.argv[1] + # We won't use pair_wait_seconds in the same way, but we'll respect the timeout logic. + pair_wait_seconds = float(sys.argv[2]) + if pair_wait_seconds < 30: + log(f"Warning: pairWaitSeconds ({pair_wait_seconds}s) is too short. Enforcing 45s minimum.") + pair_wait_seconds = 45.0 + + attempts = int(sys.argv[3]) + interval_sec = float(sys.argv[4]) + + if not addr or len(addr) < 17: + # Basic MAC address length check + log(f"Invalid Bluetooth address: '{addr}'") + sys.exit(2) + + # m/s PTY for interactive control + mfd, sfd = pty.openpty() + + # Start bluetoothctl + subprocess.Popen(['bluetoothctl'], stdin=sfd, stdout=sfd, stderr=sfd, close_fds=True, text=True) + + os.close(sfd) + + def send_command(cmd): + log(f"Sending cmd: {cmd}") + os.write(mfd, (cmd + "\n").encode('utf-8')) + + def read_output(timeout=1.0): + # Reads available output from mfd + output = b"" + end_time = time.time() + timeout + while time.time() < end_time: + r, _, _ = select.select([mfd], [], [], 0.1) + if mfd in r: + try: + data = os.read(mfd, 1024) + if not data: + break + output += data + except OSError as e: + if e.errno == errno.EIO: + break + raise + else: + pass + return output.decode('utf-8', errors='replace') + + log("Initializing bluetoothctl...") + time.sleep(1) # Wait for startup + # initial_out = read_output(timeout=1) + # print(initial_out) # Debug + + send_command("agent on") + send_command("default-agent") + # send_command("power on") # If we are pairing bluetooth is already powered on + time.sleep(0.5) + + # Pair directly since the device is already discovered in the UI/Panel (Removed previous scan/wait part) + log(f"Attempting to pair with {addr}...") + send_command(f"pair {addr}") + + # Loop to watch for confirmation or success + start_time = time.time() + paired = False + + log("Waiting for pairing sequence start...") + while time.time() - start_time < pair_wait_seconds: + out = read_output(timeout=0.5) + if out: + print(out, end='') + # Device not found yet + device_not_discovered: list[str] = [f"Device {addr} not available"] + if any(e in out for e in device_not_discovered): + log(f"Device {addr} is discovered yet...") + pair_wait_seconds += 30 # Add additional time for device discovery + + # Confirm Passkey + # Numberic Comparison (NC) 1 of 4 - Tested pairing with my iPhone. + expected_confirmation: list[str] = ["Confirm passkey", "yes/no", "Request confirmation"] + if any(e in out for e in expected_confirmation): + log("Detected passkey prompt. Sending 'yes'.") + send_command("yes") + + # Authorization Request + expected_auth: list[str] = ["Authorize service", "Request authorization"] + if any(e in out for e in expected_auth): + log("Detected authorization request. Sending 'yes'.") + send_command("yes") + + # Interactive PIN/Passkey Entry (Device displays code, User must enter on PC) + expected_pin: list[str] = ["Enter passkey", "Enter PIN code", "Passkey: "] + if any(e in out for e in expected_pin): + log("Device requested PIN/Passkey. Waiting for user input...") + log("PIN_REQUIRED") # Signal to service, to prompt user. + + try: + # Read PIN from stdin (blocking) + user_pin = sys.stdin.readline().strip() + if user_pin: + log(f"Received PIN: {user_pin}, relaying to bluetoothctl...") + send_command(user_pin) + except Exception as e: + log(f"Error reading stdin: {e}") + break + + # Just Works (JW) is implicit (no prompt) + expected_success: list[str] = ["Pairing successful", "Paired: yes", "Bonded: yes"] + if any(e in out for e in expected_success): + paired = True + log("Pairing successful detected in stream.") + break + + if "Failed to pair" in out: + log("Pairing failed explicitly.") + break + + expected_already_paired: list[str] = ["Already joined", "Already exists"] + if any(e in out for e in expected_already_paired): + paired = True + log("Device already paired.") + break + + # Double check pairing status via info command if not sure + if not paired: + send_command(f"info {addr}") + time.sleep(1) + out = read_output(timeout=1) + if "Paired: yes" in out: + paired = True + + if paired: + log("Device is paired. Trusting...") + send_command(f"trust {addr}") + time.sleep(1) + + log("Connecting...") + connected = False + for i in range(attempts): + send_command(f"connect {addr}") + # Wait a bit for connection + time.sleep(interval_sec) + + # Check status + send_command(f"info {addr}") + time.sleep(1) + out = read_output(timeout=1) + if "Connected: yes" in out: + log("Connected successfully, we are done here.") + connected = True + break + else: + log(f"Connection attempt {i + 1}/{attempts} failed. Retrying...") + + if connected: + send_command("quit") + sys.exit(0) + else: + log("Failed to connect after all attempts.") + send_command("quit") + sys.exit(1) + + else: + log("Failed to pair within timeout.") + send_command("quit") + sys.exit(1) + + +if __name__ == "__main__": + pair_fast() diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/gtk-refresh.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/gtk-refresh.py new file mode 100644 index 0000000..be21bbc --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/gtk-refresh.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 + +import asyncio +import os +import sys +import shutil +from pathlib import Path + + +async def run_command(*args): + process = await asyncio.create_subprocess_exec( + *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + if process.returncode != 0: + print(f"Error running {' '.join(args)}: {stderr.decode().strip()}", file=sys.stderr) + return stdout.decode().strip() + + +def theme_exists(theme_name: str) -> bool: + """Check if a GTK theme exists in common locations.""" + search_paths = [ + Path.home() / ".themes", + Path.home() / ".local/share/themes", + Path("/usr/share/themes"), + Path("/usr/local/share/themes"), + ] + + # Add paths from XDG_DATA_DIRS + xdg_data_dirs = os.environ.get("XDG_DATA_DIRS", "") + if xdg_data_dirs: + for path in xdg_data_dirs.split(":"): + if path: + search_paths.append(Path(path) / "themes") + + for base_path in search_paths: + if (base_path / theme_name).is_dir(): + return True + + return False + + +GTK_IMPORT = '@import url("noctalia.css");' + + +def ensure_gtk_css_import(gtk_css: Path, colors_file: Path, label: str) -> bool: + """ + Append the noctalia.css import to gtk.css if not already present. + If gtk.css doesn't exist, create it with the import. + Does not overwrite user modifications (similar to niri template). + """ + if not colors_file.exists(): + print(f"Error: {label} noctalia.css not found at {colors_file}", file=sys.stderr) + return False + + if gtk_css.exists() or gtk_css.is_symlink(): + content = gtk_css.read_text() + # Already has the import (flexible: allow optional whitespace / different quoting) + if "noctalia.css" in content and "@import" in content: + return True + # Need to modify โ€” handle symlinks carefully + target = gtk_css + if gtk_css.is_symlink(): + resolved = gtk_css.resolve() + if os.access(resolved, os.W_OK): + # Writable symlink (e.g. dotfiles): edit the target directly + target = resolved + else: + # Read-only symlink (e.g. NixOS): convert to local file + gtk_css.unlink() + gtk_css.write_text(resolved.read_text()) + # Append import to the end + new_content = content.rstrip() + if new_content and not new_content.endswith("\n"): + new_content += "\n" + new_content += "\n" + GTK_IMPORT + "\n" + target.write_text(new_content) + print(f"Appended {label} noctalia.css import to gtk.css") + else: + gtk_css.write_text(GTK_IMPORT + "\n") + print(f"Created {label} gtk.css with noctalia.css import") + return True + + +async def apply_gtk3_colors(config_dir: Path): + gtk3_dir = config_dir / "gtk-3.0" + colors_file = gtk3_dir / "noctalia.css" + gtk_css = gtk3_dir / "gtk.css" + return ensure_gtk_css_import(gtk_css, colors_file, "GTK3") + + +async def apply_gtk4_colors(config_dir: Path): + gtk4_dir = config_dir / "gtk-4.0" + colors_file = gtk4_dir / "noctalia.css" + gtk_css = gtk4_dir / "gtk.css" + return ensure_gtk_css_import(gtk_css, colors_file, "GTK4") + + +async def sync_system_appearance(mode: str, *, update_gtk_theme: bool = True) -> None: + """ + Push light/dark to org.gnome.desktop.interface (gsettings or dconf fallback). + Used by the GTK template post-hook and ColorSchemeService when "Sync system theme" + is on (both set color-scheme and gtk-theme when themes exist). --appearance-only + skips CSS and only updates color-scheme for narrow tooling use. + """ + has_gsettings = shutil.which("gsettings") + has_dconf = shutil.which("dconf") + + if not has_gsettings and not has_dconf: + print("No gsettings or dconf found, skip system appearance sync") + return + + target_theme = "adw-gtk3" if mode == "light" else "adw-gtk3-dark" + theme_available = update_gtk_theme and theme_exists(target_theme) + if update_gtk_theme and not theme_available: + print(f"Theme '{target_theme}' not found, skipping GTK theme set") + + if has_gsettings: + schemas = await run_command("gsettings", "list-schemas") + if schemas and "org.gnome.desktop.interface" in schemas: + await run_command("gsettings", "set", "org.gnome.desktop.interface", "color-scheme", f"prefer-{mode}") + if theme_available: + await run_command("gsettings", "set", "org.gnome.desktop.interface", "gtk-theme", f"{target_theme}") + return + + if has_dconf: + await run_command("dconf", "write", "/org/gnome/desktop/interface/color-scheme", f"'prefer-{mode}'") + if theme_available: + await run_command("dconf", "write", "/org/gnome/desktop/interface/gtk-theme", f"'{target_theme}'") + + +async def get_config_dir() -> Path: + # Returns the XDG config home (e.g. ~/.config) + # GTK config lives at ~/.config/gtk-3.0/ and ~/.config/gtk-4.0/. + + # 1. XDG standard + if value := os.environ.get("XDG_CONFIG_HOME"): + return Path(value).expanduser() + + # 2. fallback + return Path.home() / ".config" + + +def parse_args(): + argv = sys.argv[1:] + appearance_only = False + if argv and argv[0] == "--appearance-only": + appearance_only = True + argv = argv[1:] + if len(argv) != 1 or argv[0] not in ("dark", "light"): + print( + "Usage: gtk-refresh.py [--appearance-only] (dark|light)", + file=sys.stderr, + ) + sys.exit(1) + return appearance_only, argv[0] + + +async def main(): + appearance_only, mode = parse_args() + + if appearance_only: + await sync_system_appearance(mode, update_gtk_theme=False) + return + + config_dir = await get_config_dir() + + if not config_dir.is_dir(): + print(f"Error: Config directory not found: {config_dir}", file=sys.stderr) + sys.exit(1) + + (config_dir / "gtk-3.0").mkdir(parents=True, exist_ok=True) + (config_dir / "gtk-4.0").mkdir(parents=True, exist_ok=True) + + results = await asyncio.gather(apply_gtk3_colors(config_dir), apply_gtk4_colors(config_dir)) + + if all(results): + await sync_system_appearance(mode, update_gtk_theme=True) + print("GTK colors applied successfully") + else: + # Still push light/dark preference so portal/GTK apps follow the shell even when + # gtk.css / noctalia.css setup failed. + await sync_system_appearance(mode, update_gtk_theme=False) + sys.exit(1) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/__init__.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/__init__.py new file mode 100644 index 0000000..644a55a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/__init__.py @@ -0,0 +1,58 @@ +""" +Theming library - Color extraction and theme generation. + +This package provides: +- HCT color space implementation (CAM16, Hct, TonalPalette) +- Material Design 3 scheme generation +- Color utilities (RGB, HSL conversions) +- Image reading and palette extraction +- Template rendering (Matugen compatible) +""" + +from .color import Color, rgb_to_hsl, hsl_to_rgb, adjust_surface +from .hct import Hct, Cam16, TonalPalette, TemperatureCache, fix_if_disliked +from .material import MaterialScheme, SchemeContent, harmonize_color +from .contrast import ensure_contrast, contrast_ratio, is_dark +from .image import read_image, ImageReadError +from .palette import extract_palette +from .quantizer import extract_source_color, source_color_to_rgb +from .theme import generate_theme +from .renderer import TemplateRenderer +from .scheme import expand_predefined_scheme, inject_terminal_colors + +__all__ = [ + # Color + "Color", + "rgb_to_hsl", + "hsl_to_rgb", + "adjust_surface", + # HCT + "Hct", + "Cam16", + "TonalPalette", + "TemperatureCache", + "fix_if_disliked", + # Material + "MaterialScheme", + "SchemeContent", + "harmonize_color", + # Contrast + "ensure_contrast", + "contrast_ratio", + "is_dark", + # Image + "read_image", + "ImageReadError", + # Palette + "extract_palette", + # Quantizer (Wu + Score algorithm matching matugen) + "extract_source_color", + "source_color_to_rgb", + # Theme + "generate_theme", + # Renderer + "TemplateRenderer", + # Scheme + "expand_predefined_scheme", + "inject_terminal_colors", +] diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/color.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/color.py new file mode 100644 index 0000000..5881cad --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/color.py @@ -0,0 +1,353 @@ +""" +Color representation and conversion utilities. + +This module provides the Color class and functions for converting between +RGB, HSL, and Lab color spaces. +""" + +import math +from dataclasses import dataclass +from typing import TYPE_CHECKING + +# Type aliases +RGB = tuple[int, int, int] +HSL = tuple[float, float, float] +LAB = tuple[float, float, float] + +if TYPE_CHECKING: + from .hct import Hct + + +@dataclass +class Color: + """Represents a color with RGB values (0-255).""" + r: int + g: int + b: int + + @classmethod + def from_rgb(cls, rgb: RGB) -> 'Color': + return cls(rgb[0], rgb[1], rgb[2]) + + @classmethod + def from_hex(cls, hex_str: str) -> 'Color': + """Parse hex color string (#RRGGBB or RRGGBB).""" + hex_str = hex_str.lstrip('#') + return cls( + int(hex_str[0:2], 16), + int(hex_str[2:4], 16), + int(hex_str[4:6], 16) + ) + + def to_rgb(self) -> RGB: + return (self.r, self.g, self.b) + + def to_hex(self) -> str: + """Convert to hex string (#RRGGBB).""" + return f"#{self.r:02x}{self.g:02x}{self.b:02x}" + + def to_hsl(self) -> HSL: + """Convert RGB to HSL.""" + return rgb_to_hsl(self.r, self.g, self.b) + + def to_hct(self) -> 'Hct': + """Convert to HCT color space.""" + from .hct import Hct + return Hct.from_rgb(self.r, self.g, self.b) + + @classmethod + def from_hsl(cls, h: float, s: float, l: float) -> 'Color': + """Create Color from HSL values.""" + r, g, b = hsl_to_rgb(h, s, l) + return cls(r, g, b) + + @classmethod + def from_hct(cls, hct: 'Hct') -> 'Color': + """Create Color from HCT.""" + r, g, b = hct.to_rgb() + return cls(r, g, b) + + +def rgb_to_hsl(r: int, g: int, b: int) -> HSL: + """ + Convert RGB (0-255) to HSL (0-360, 0-1, 0-1). + + Args: + r: Red component (0-255) + g: Green component (0-255) + b: Blue component (0-255) + + Returns: + Tuple of (hue, saturation, lightness) + """ + r_norm = r / 255.0 + g_norm = g / 255.0 + b_norm = b / 255.0 + + max_c = max(r_norm, g_norm, b_norm) + min_c = min(r_norm, g_norm, b_norm) + delta = max_c - min_c + + # Lightness + l = (max_c + min_c) / 2.0 + + if delta == 0: + h = 0.0 + s = 0.0 + else: + # Saturation + s = delta / (1 - abs(2 * l - 1)) if l != 0 and l != 1 else 0 + + # Hue + if max_c == r_norm: + h = 60.0 * (((g_norm - b_norm) / delta) % 6) + elif max_c == g_norm: + h = 60.0 * (((b_norm - r_norm) / delta) + 2) + else: + h = 60.0 * (((r_norm - g_norm) / delta) + 4) + + return (h, s, l) + + +def hsl_to_rgb(h: float, s: float, l: float) -> RGB: + """ + Convert HSL (0-360, 0-1, 0-1) to RGB (0-255). + + Args: + h: Hue (0-360) + s: Saturation (0-1) + l: Lightness (0-1) + + Returns: + Tuple of (r, g, b) + """ + if s == 0: + # Achromatic (gray) + v = int(round(l * 255)) + return (v, v, v) + + def hue_to_rgb(p: float, q: float, t: float) -> float: + if t < 0: + t += 1 + if t > 1: + t -= 1 + if t < 1/6: + return p + (q - p) * 6 * t + if t < 1/2: + return q + if t < 2/3: + return p + (q - p) * (2/3 - t) * 6 + return p + + q = l * (1 + s) if l < 0.5 else l + s - l * s + p = 2 * l - q + h_norm = h / 360.0 + + r = hue_to_rgb(p, q, h_norm + 1/3) + g = hue_to_rgb(p, q, h_norm) + b = hue_to_rgb(p, q, h_norm - 1/3) + + return ( + int(round(r * 255)), + int(round(g * 255)), + int(round(b * 255)) + ) + + +def adjust_lightness(color: Color, target_l: float) -> Color: + """Adjust a color's lightness to a target value (0-1).""" + h, s, _ = color.to_hsl() + return Color.from_hsl(h, s, target_l) + + +def shift_hue(color: Color, degrees: float) -> Color: + """Shift a color's hue by specified degrees.""" + h, s, l = color.to_hsl() + new_h = (h + degrees) % 360 + return Color.from_hsl(new_h, s, l) + + +def hue_distance(h1: float, h2: float) -> float: + """Calculate minimum angular distance between two hues (0-180).""" + diff = abs(h1 - h2) + return min(diff, 360 - diff) + + +def adjust_surface(color: Color, s_max: float, l_target: float) -> Color: + """Derive a surface color from a base color with saturation limit and target lightness.""" + h, s, _ = color.to_hsl() + return Color.from_hsl(h, min(s, s_max), l_target) + + +def saturate(color: Color, amount: float) -> Color: + """Adjust saturation by amount (-1 to 1).""" + h, s, l = color.to_hsl() + new_s = max(0.0, min(1.0, s + amount)) + return Color.from_hsl(h, new_s, l) + + +# ============================================================================= +# Lab Color Space (CIE L*a*b*) +# ============================================================================= + +# D65 white point +_WHITE_X = 95.047 +_WHITE_Y = 100.0 +_WHITE_Z = 108.883 + + +def _linearize(channel: int) -> float: + """Convert sRGB channel (0-255) to linear RGB (0-1).""" + normalized = channel / 255.0 + if normalized <= 0.04045: + return normalized / 12.92 + return math.pow((normalized + 0.055) / 1.055, 2.4) + + +def _delinearize(linear: float) -> int: + """Convert linear RGB (0-1) to sRGB channel (0-255).""" + if linear <= 0.0031308: + normalized = linear * 12.92 + else: + normalized = 1.055 * math.pow(linear, 1.0 / 2.4) - 0.055 + return max(0, min(255, round(normalized * 255))) + + +def _lab_f(t: float) -> float: + """Lab forward transform function.""" + if t > 0.008856: + return math.pow(t, 1.0 / 3.0) + return (903.3 * t + 16.0) / 116.0 + + +def _lab_f_inv(t: float) -> float: + """Lab inverse transform function.""" + if t > 0.206893: + return t * t * t + return (116.0 * t - 16.0) / 903.3 + + +def rgb_to_lab(r: int, g: int, b: int) -> LAB: + """ + Convert sRGB (0-255) to CIE L*a*b*. + + Returns: + Tuple of (L*, a*, b*) where L* is 0-100 + """ + # sRGB to linear RGB + linear_r = _linearize(r) + linear_g = _linearize(g) + linear_b = _linearize(b) + + # Linear RGB to XYZ (D65) + x = 0.4124564 * linear_r + 0.3575761 * linear_g + 0.1804375 * linear_b + y = 0.2126729 * linear_r + 0.7151522 * linear_g + 0.0721750 * linear_b + z = 0.0193339 * linear_r + 0.1191920 * linear_g + 0.9503041 * linear_b + + # Scale to 0-100 range + x *= 100.0 + y *= 100.0 + z *= 100.0 + + # XYZ to Lab + fx = _lab_f(x / _WHITE_X) + fy = _lab_f(y / _WHITE_Y) + fz = _lab_f(z / _WHITE_Z) + + L = 116.0 * fy - 16.0 + a = 500.0 * (fx - fy) + b = 200.0 * (fy - fz) + + return (L, a, b) + + +def lab_to_rgb(L: float, a: float, b: float) -> RGB: + """ + Convert CIE L*a*b* to sRGB (0-255). + + Args: + L: Lightness (0-100) + a: Green-red component + b: Blue-yellow component + + Returns: + Tuple of (r, g, b) + """ + # Lab to XYZ + fy = (L + 16.0) / 116.0 + fx = a / 500.0 + fy + fz = fy - b / 200.0 + + x = _WHITE_X * _lab_f_inv(fx) + y = _WHITE_Y * _lab_f_inv(fy) + z = _WHITE_Z * _lab_f_inv(fz) + + # Scale back to 0-1 range + x /= 100.0 + y /= 100.0 + z /= 100.0 + + # XYZ to linear RGB + linear_r = 3.2404542 * x - 1.5371385 * y - 0.4985314 * z + linear_g = -0.9692660 * x + 1.8760108 * y + 0.0415560 * z + linear_b = 0.0556434 * x - 0.2040259 * y + 1.0572252 * z + + # Clamp and delinearize + return ( + _delinearize(max(0.0, min(1.0, linear_r))), + _delinearize(max(0.0, min(1.0, linear_g))), + _delinearize(max(0.0, min(1.0, linear_b))) + ) + + +def lab_distance(lab1: LAB, lab2: LAB) -> float: + """ + Calculate Euclidean distance between two Lab colors. + + This is a simple perceptual distance metric. + """ + dL = lab1[0] - lab2[0] + da = lab1[1] - lab2[1] + db = lab1[2] - lab2[2] + return math.sqrt(dL * dL + da * da + db * db) + + +def find_closest_color( + compare_to: str, + colors: list[dict[str, str]] +) -> str: + """ + Find the closest named color from a list (matugen-compatible). + + Uses Lab color space Euclidean distance for perceptual color matching. + + Args: + compare_to: Hex color to compare (e.g., "#ff5500") + colors: List of {"name": "...", "color": "#..."} dicts + + Returns: + Name of the closest color, or empty string if no colors provided + """ + if not colors: + return "" + + # Parse target color + target = Color.from_hex(compare_to) + target_lab = rgb_to_lab(target.r, target.g, target.b) + + closest_name = "" + closest_dist = float('inf') + + for entry in colors: + try: + entry_color = Color.from_hex(entry["color"]) + entry_lab = rgb_to_lab(entry_color.r, entry_color.g, entry_color.b) + dist = lab_distance(target_lab, entry_lab) + if dist < closest_dist: + closest_dist = dist + closest_name = entry["name"] + except (KeyError, ValueError): + # Skip invalid entries + continue + + return closest_name diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/contrast.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/contrast.py new file mode 100644 index 0000000..2f67157 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/contrast.py @@ -0,0 +1,123 @@ +""" +Contrast calculation utilities (WCAG luminance and contrast). + +This module provides functions for calculating relative luminance, +contrast ratios, and ensuring accessible color combinations. +""" + +from .color import Color + + +def relative_luminance(r: int, g: int, b: int) -> float: + """ + Calculate relative luminance per WCAG 2.1. + + The formula converts sRGB to linear RGB, then applies the luminance formula: + L = 0.2126 * R + 0.7152 * G + 0.0722 * B + + Args: + r, g, b: RGB components (0-255) + + Returns: + Relative luminance (0-1) + """ + def linearize(c: int) -> float: + c_norm = c / 255.0 + if c_norm <= 0.03928: + return c_norm / 12.92 + return ((c_norm + 0.055) / 1.055) ** 2.4 + + r_lin = linearize(r) + g_lin = linearize(g) + b_lin = linearize(b) + + return 0.2126 * r_lin + 0.7152 * g_lin + 0.0722 * b_lin + + +def contrast_ratio(color1: Color, color2: Color) -> float: + """ + Calculate WCAG contrast ratio between two colors. + + Returns a value between 1:1 (identical) and 21:1 (black/white). + """ + l1 = relative_luminance(color1.r, color1.g, color1.b) + l2 = relative_luminance(color2.r, color2.g, color2.b) + + lighter = max(l1, l2) + darker = min(l1, l2) + + return (lighter + 0.05) / (darker + 0.05) + + +def is_dark(color: Color) -> bool: + """Determine if a color is perceptually dark.""" + return relative_luminance(color.r, color.g, color.b) < 0.179 + + +def ensure_contrast( + foreground: Color, + background: Color, + min_ratio: float = 4.5, + prefer_light: bool | None = None +) -> Color: + """ + Adjust foreground color to meet minimum contrast ratio against background. + + Args: + foreground: The color to adjust + background: The background color (not modified) + min_ratio: Minimum contrast ratio (default 4.5 for WCAG AA) + prefer_light: If True, prefer lightening; if False, prefer darkening; + if None, auto-detect based on background + + Returns: + Adjusted foreground color meeting contrast requirements + """ + current_ratio = contrast_ratio(foreground, background) + if current_ratio >= min_ratio: + return foreground + + h, s, l = foreground.to_hsl() + bg_dark = is_dark(background) + + # Determine direction to adjust + if prefer_light is None: + prefer_light = bg_dark + + # Binary search for the right lightness + if prefer_light: + low, high = l, 1.0 + else: + low, high = 0.0, l + + best_color = foreground + for _ in range(20): # Max iterations + mid = (low + high) / 2 + test_color = Color.from_hsl(h, s, mid) + ratio = contrast_ratio(test_color, background) + + if ratio >= min_ratio: + best_color = test_color + if prefer_light: + high = mid + else: + low = mid + else: + if prefer_light: + low = mid + else: + high = mid + + return best_color + + +def get_contrasting_color(background: Color, min_ratio: float = 4.5) -> Color: + """Get a contrasting foreground color (black or white variant).""" + if is_dark(background): + # Light foreground for dark background + fg = Color(243, 237, 247) # Off-white + else: + # Dark foreground for light background + fg = Color(14, 14, 67) # Dark blue-black + + return ensure_contrast(fg, background, min_ratio) diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/hct.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/hct.py new file mode 100644 index 0000000..2500e36 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/hct.py @@ -0,0 +1,1071 @@ +""" +HCT (Hue, Chroma, Tone) Color Space Implementation. + +Based on Material Color Utilities (Google). +HCT combines CAM16 hue and chroma with CIELAB lightness (L*) for +Material Design 3's perceptual color space. +""" + +from __future__ import annotations +import math + +# ============================================================================= +# Type Definitions +# ============================================================================= + +RGB = tuple[int, int, int] + +# ============================================================================= +# CAM16 / HCT Color Space Implementation +# ============================================================================= + +# sRGB to XYZ matrix (D65 illuminant) +SRGB_TO_XYZ = [ + [0.41233895, 0.35762064, 0.18051042], + [0.2126, 0.7152, 0.0722], + [0.01932141, 0.11916382, 0.95034478], +] + +# XYZ to sRGB matrix +XYZ_TO_SRGB = [ + [3.2413774792388685, -1.5376652402851851, -0.49885366846268053], + [-0.9691452513005321, 1.8758853451067872, 0.04156585616912061], + [0.05562093689691305, -0.20395524564742123, 1.0571799111220335], +] + + +class ViewingConditions: + """CAM16 viewing conditions for sRGB display.""" + # White point (D65) + WHITE_POINT_D65 = [95.047, 100.0, 108.883] + + # Precomputed values for standard conditions + n = 0.18418651851244416 + aw = 29.980997194447333 + nbb = 1.0169191804458755 + ncb = 1.0169191804458755 + c = 0.69 + nc = 1.0 + fl = 0.3884814537800353 + fl_root = 0.7894826179304937 + z = 1.909169568483652 + + # RGB to CAM16 adaptation matrix + RGB_D = [1.0211931250282205, 0.9862992588498498, 0.9338046048498166] + + +def _linearize(channel: int) -> float: + """Convert sRGB channel (0-255) to linear RGB (0-1).""" + normalized = channel / 255.0 + if normalized <= 0.040449936: + return normalized / 12.92 + return math.pow((normalized + 0.055) / 1.055, 2.4) + + +def _delinearize(linear: float) -> int: + """Convert linear RGB (0-1) to sRGB channel (0-255).""" + if linear <= 0.0031308: + normalized = linear * 12.92 + else: + normalized = 1.055 * math.pow(linear, 1.0 / 2.4) - 0.055 + return max(0, min(255, round(normalized * 255))) + + +def _matrix_multiply(matrix: list[list[float]], vector: list[float]) -> list[float]: + """Multiply 3x3 matrix by 3-element vector.""" + return [ + matrix[0][0] * vector[0] + matrix[0][1] * vector[1] + matrix[0][2] * vector[2], + matrix[1][0] * vector[0] + matrix[1][1] * vector[1] + matrix[1][2] * vector[2], + matrix[2][0] * vector[0] + matrix[2][1] * vector[1] + matrix[2][2] * vector[2], + ] + + +def _signum(x: float) -> float: + """Return sign of x: -1, 0, or 1.""" + if x < 0: + return -1.0 + elif x > 0: + return 1.0 + return 0.0 + + +def _lerp(a: float, b: float, t: float) -> float: + """Linear interpolation between a and b.""" + return a + (b - a) * t + + +def _sanitize_degrees(degrees: float) -> float: + """Ensure degrees is in [0, 360) range.""" + degrees = degrees % 360.0 + if degrees < 0: + degrees += 360.0 + return degrees + + +# ============================================================================= +# HCT Solver - Ported from Material Color Utilities +# ============================================================================= + +# Matrices for chromatic adaptation +_SCALED_DISCOUNT_FROM_LINRGB = [ + [0.001200833568784504, 0.002389694492170889, 0.0002795742885861124], + [0.0005891086651375999, 0.0029785502573438758, 0.0003270666104008398], + [0.00010146692491640572, 0.0005364214359186694, 0.0032979401770712076], +] + +_LINRGB_FROM_SCALED_DISCOUNT = [ + [1373.2198709594231, -1100.4251190754821, -7.278681089101213], + [-271.815969077903, 559.6580465940733, -32.46047482791194], + [1.9622899599665666, -57.173814538844006, 308.7233197812385], +] + +_Y_FROM_LINRGB = [0.2126, 0.7152, 0.0722] + +# Critical planes for bisection (precomputed delinearized values 0-254) +_CRITICAL_PLANES = [ + 0.015176349177441876, 0.045529047532325624, 0.07588174588720938, + 0.10623444424209313, 0.13658714259697685, 0.16693984095186062, + 0.19729253930674434, 0.2276452376616281, 0.2579979360165119, + 0.28835063437139563, 0.3188300904430532, 0.350925934958123, + 0.3848314933096426, 0.42057480301049466, 0.458183274052838, + 0.4976837250274023, 0.5391024159806381, 0.5824650784040898, + 0.6277969426914107, 0.6751227633498623, 0.7244668422128921, + 0.775853049866786, 0.829304845476233, 0.8848452951698498, + 0.942497089126609, 1.0022825574869039, 1.0642236851973577, + 1.1283421258858297, 1.1946592148522128, 1.2631959812511864, + 1.3339731595349034, 1.407011200216447, 1.4823302800086415, + 1.5599503113873272, 1.6398909516233677, 1.7221716113234105, + 1.8068114625156377, 1.8938294463134073, 1.9832442801866852, + 2.075074464868551, 2.1693382909216234, 2.2660538449872063, + 2.36523901573795, 2.4669114995532007, 2.5710888059345764, + 2.6777882626779785, 2.7870270208169257, 2.898822059350997, + 3.0131901897720907, 3.1301480604002863, 3.2497121605402226, + 3.3718988244681087, 3.4967242352587946, 3.624204428461639, + 3.754355295633311, 3.887192587735158, 4.022731918402185, + 4.160988767090289, 4.301978482107941, 4.445716283538092, + 4.592217266055746, 4.741496401646282, 4.893568542229298, + 5.048448422192488, 5.20615066083972, 5.3666897647573375, + 5.5300801301023865, 5.696336044816294, 5.865471690767354, + 6.037501145825082, 6.212438385869475, 6.390297286737924, + 6.571091626112461, 6.7548350853498045, 6.941541251256611, + 7.131223617812143, 7.323895587840543, 7.5195704746346665, + 7.7182615035334345, 7.919981813454504, 8.124744458384042, + 8.332562408825165, 8.543448553206703, 8.757415699253682, + 8.974476575321063, 9.194643831691977, 9.417930041841839, + 9.644347703669503, 9.873909240696694, 10.106627003236781, + 10.342513269534024, 10.58158024687427, 10.8238400726681, + 11.069304815507364, 11.317986476196008, 11.569896988756009, + 11.825048221409341, 12.083451977536606, 12.345119996613247, + 12.610063955123938, 12.878295467455942, 13.149826086772048, + 13.42466730586372, 13.702830557985108, 13.984327217668513, + 14.269168601521828, 14.55736596900856, 14.848930523210871, + 15.143873411576273, 15.44220572664832, 15.743938506781891, + 16.04908273684337, 16.35764934889634, 16.66964922287304, + 16.985093187232053, 17.30399201960269, 17.62635644741625, + 17.95219714852476, 18.281524751807332, 18.614349837764564, + 18.95068293910138, 19.290534541298456, 19.633915083172692, + 19.98083495742689, 20.331304511189067, 20.685334046541502, + 21.042933821039977, 21.404114048223256, 21.76888489811322, + 22.137256497705877, 22.50923893145328, 22.884842241736916, + 23.264076429332462, 23.6469514538663, 24.033477234264016, + 24.42366364919083, 24.817520537484558, 25.21505769858089, + 25.61628489293138, 26.021211842414342, 26.429848230738664, + 26.842203703840827, 27.258287870275353, 27.678110301598522, + 28.10168053274597, 28.529008062403893, 28.96010235337422, + 29.39497283293396, 29.83362889318845, 30.276079891419332, + 30.722335150426627, 31.172403958865512, 31.62629557157785, + 32.08401920991837, 32.54558406207592, 33.010999283389665, + 33.4802739966603, 33.953417292456834, 34.430438229418264, + 34.911345834551085, 35.39614910352207, 35.88485700094671, + 36.37747846067349, 36.87402238606382, 37.37449765026789, + 37.87891309649659, 38.38727753828926, 38.89959975977785, + 39.41588851594697, 39.93615253289054, 40.460400508064545, + 40.98864111053629, 41.520882981230194, 42.05713473317016, + 42.597404951718396, 43.141702194811224, 43.6900349931913, + 44.24241185063697, 44.798841244188324, 45.35933162437017, + 45.92389141541209, 46.49252901546552, 47.065252796817916, + 47.64207110610409, 48.22299226451468, 48.808024568002054, + 49.3971762874833, 49.9904556690408, 50.587870934119984, + 51.189430279724725, 51.79514187861014, 52.40501387947288, + 53.0190544071392, 53.637271562750364, 54.259673423945976, + 54.88626804504493, 55.517063457223934, 56.15206766869424, + 56.79128866487574, 57.43473440856916, 58.08241284012621, + 58.734331877617365, 59.39049941699807, 60.05092333227251, + 60.715611475655585, 61.38457167773311, 62.057811747619894, + 62.7353394731159, 63.417162620860914, 64.10328893648692, + 64.79372614476921, 65.48848194977529, 66.18756403501224, + 66.89098006357258, 67.59873767827808, 68.31084450182222, + 69.02730813691093, 69.74813616640164, 70.47333615344107, + 71.20291564160104, 71.93688215501312, 72.67524319850172, + 73.41800625771542, 74.16517879925733, 74.9167682708136, + 75.67278210128072, 76.43322770089146, 77.1981124613393, + 77.96744375590167, 78.74122893956174, 79.51947534912904, + 80.30219030335869, 81.08938110306934, 81.88105503125999, + 82.67721935322541, 83.4778813166706, 84.28304815182372, + 85.09272707154808, 85.90692527145302, 86.72564993000343, + 87.54890820862819, 88.3767072518277, 89.2090541872801, + 90.04595612594655, 90.88742016217518, 91.73345337380438, + 92.58406282226491, 93.43925555268066, 94.29903859396902, + 95.16341895893969, 96.03240364439274, 96.9059996312159, + 97.78421388448044, 98.6670533535366, 99.55452497210776, +] + + +class HctSolver: + """ + Solves HCT to RGB conversion with proper gamut mapping. + + Ported from Material Color Utilities (Rust/TypeScript). + When the requested chroma is out of gamut, this solver finds + the maximum achievable chroma while preserving the exact hue. + """ + + @staticmethod + def _sanitize_radians(angle: float) -> float: + """Ensure angle is in [0, 2ฯ€) range.""" + return (angle + math.pi * 8) % (math.pi * 2) + + @staticmethod + def _true_delinearized(rgb_component: float) -> float: + """Delinearize RGB component (0-100) to (0-255).""" + normalized = rgb_component / 100.0 + if normalized <= 0.0031308: + delinearized = normalized * 12.92 + else: + delinearized = 1.055 * (normalized ** (1.0 / 2.4)) - 0.055 + return delinearized * 255.0 + + @staticmethod + def _chromatic_adaptation(component: float) -> float: + """Apply chromatic adaptation.""" + af = abs(component) ** 0.42 + return _signum(component) * 400.0 * af / (af + 27.13) + + @staticmethod + def _hue_of(linrgb: list[float]) -> float: + """Calculate hue of linear RGB color in radians.""" + scaled_discount = _matrix_multiply(_SCALED_DISCOUNT_FROM_LINRGB, linrgb) + + r_a = HctSolver._chromatic_adaptation(scaled_discount[0]) + g_a = HctSolver._chromatic_adaptation(scaled_discount[1]) + b_a = HctSolver._chromatic_adaptation(scaled_discount[2]) + + # redness-greenness + a = (11.0 * r_a - 12.0 * g_a + b_a) / 11.0 + # yellowness-blueness + b = (r_a + g_a - 2.0 * b_a) / 9.0 + + return math.atan2(b, a) + + @staticmethod + def _are_in_cyclic_order(a: float, b: float, c: float) -> bool: + """Check if a, b, c are in cyclic order.""" + delta_ab = HctSolver._sanitize_radians(b - a) + delta_ac = HctSolver._sanitize_radians(c - a) + return delta_ab < delta_ac + + @staticmethod + def _intercept(source: float, mid: float, target: float) -> float: + """Solve lerp equation: find t such that lerp(source, target, t) = mid.""" + return (mid - source) / (target - source) + + @staticmethod + def _lerp_point(source: list[float], t: float, target: list[float]) -> list[float]: + """Linear interpolation between two 3D points.""" + return [ + source[0] + (target[0] - source[0]) * t, + source[1] + (target[1] - source[1]) * t, + source[2] + (target[2] - source[2]) * t, + ] + + @staticmethod + def _set_coordinate(source: list[float], coordinate: float, + target: list[float], axis: int) -> list[float]: + """Find point on segment where axis equals coordinate.""" + t = HctSolver._intercept(source[axis], coordinate, target[axis]) + return HctSolver._lerp_point(source, t, target) + + @staticmethod + def _is_bounded(x: float) -> bool: + """Check if x is in [0, 100].""" + return 0.0 <= x <= 100.0 + + @staticmethod + def _nth_vertex(y: float, n: int) -> list[float]: + """ + Get nth vertex of RGB cube intersection with Y plane. + + Returns [-1, -1, -1] if vertex is outside cube. + """ + k_r, k_g, k_b = _Y_FROM_LINRGB + + coord_a = 0.0 if n % 4 <= 1 else 100.0 + coord_b = 0.0 if n % 2 == 0 else 100.0 + + if n < 4: + g = coord_a + b = coord_b + r = (y - k_g * g - k_b * b) / k_r + if HctSolver._is_bounded(r): + return [r, g, b] + return [-1.0, -1.0, -1.0] + elif n < 8: + b = coord_a + r = coord_b + g = (y - k_r * r - k_b * b) / k_g + if HctSolver._is_bounded(g): + return [r, g, b] + return [-1.0, -1.0, -1.0] + else: + r = coord_a + g = coord_b + b = (y - k_r * r - k_g * g) / k_b + if HctSolver._is_bounded(b): + return [r, g, b] + return [-1.0, -1.0, -1.0] + + @staticmethod + def _bisect_to_segment(y: float, target_hue: float) -> list[list[float]]: + """Find segment on RGB cube containing target hue.""" + left = [-1.0, -1.0, -1.0] + right = [-1.0, -1.0, -1.0] + left_hue = 0.0 + right_hue = 0.0 + initialized = False + uncut = True + + for n in range(12): + mid = HctSolver._nth_vertex(y, n) + + if mid[0] < 0: + continue + + mid_hue = HctSolver._hue_of(mid) + + if not initialized: + left = mid + right = mid + left_hue = mid_hue + right_hue = mid_hue + initialized = True + continue + + if uncut or HctSolver._are_in_cyclic_order(left_hue, mid_hue, right_hue): + uncut = False + + if HctSolver._are_in_cyclic_order(left_hue, target_hue, mid_hue): + right = mid + right_hue = mid_hue + else: + left = mid + left_hue = mid_hue + + return [left, right] + + @staticmethod + def _mid_point(a: list[float], b: list[float]) -> list[float]: + """Calculate midpoint of two 3D points.""" + return [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2] + + @staticmethod + def _critical_plane_below(x: float) -> int: + """Get critical plane index below x.""" + return int(math.floor(x - 0.5)) + + @staticmethod + def _critical_plane_above(x: float) -> int: + """Get critical plane index above x.""" + return int(math.ceil(x - 0.5)) + + @staticmethod + def _bisect_to_limit(y: float, target_hue: float) -> list[float]: + """ + Find color on RGB cube boundary with exact target hue. + + This is the key function for hue-preserving gamut mapping. + """ + segment = HctSolver._bisect_to_segment(y, target_hue) + left = segment[0] + left_hue = HctSolver._hue_of(left) + right = segment[1] + + for axis in range(3): + if abs(left[axis] - right[axis]) > 1e-10: + if left[axis] < right[axis]: + l_plane = HctSolver._critical_plane_below( + HctSolver._true_delinearized(left[axis])) + r_plane = HctSolver._critical_plane_above( + HctSolver._true_delinearized(right[axis])) + else: + l_plane = HctSolver._critical_plane_above( + HctSolver._true_delinearized(left[axis])) + r_plane = HctSolver._critical_plane_below( + HctSolver._true_delinearized(right[axis])) + + for _ in range(8): + if abs(r_plane - l_plane) <= 1: + break + + m_plane = int((l_plane + r_plane) / 2) + # Clamp to valid index range + m_plane = max(0, min(len(_CRITICAL_PLANES) - 1, m_plane)) + mid_plane_coordinate = _CRITICAL_PLANES[m_plane] + mid = HctSolver._set_coordinate(left, mid_plane_coordinate, right, axis) + mid_hue = HctSolver._hue_of(mid) + + if HctSolver._are_in_cyclic_order(left_hue, target_hue, mid_hue): + right = mid + r_plane = m_plane + else: + left = mid + left_hue = mid_hue + l_plane = m_plane + + return HctSolver._mid_point(left, right) + + @staticmethod + def _inverse_chromatic_adaptation(adapted: float) -> float: + """Inverse of chromatic adaptation.""" + adapted_abs = abs(adapted) + base = max(0.0, 27.13 * adapted_abs / (400.0 - adapted_abs)) + return _signum(adapted) * (base ** (1.0 / 0.42)) + + @staticmethod + def _find_result_by_j(hue_radians: float, chroma: float, y: float) -> tuple[int, int, int] | None: + """ + Try to find exact color with given hue, chroma, and Y. + + Returns None if out of gamut. + """ + j = math.sqrt(y) * 11.0 + + t_inner_coeff = 1.0 / ((1.64 - (0.29 ** ViewingConditions.n)) ** 0.73) + e_hue = 0.25 * (math.cos(hue_radians + 2.0) + 3.8) + p1 = e_hue * (50000.0 / 13.0) * ViewingConditions.nc * ViewingConditions.ncb + h_sin = math.sin(hue_radians) + h_cos = math.cos(hue_radians) + + for iteration in range(5): + j_normalized = j / 100.0 + if chroma == 0 or j == 0: + alpha = 0.0 + else: + alpha = chroma / math.sqrt(j_normalized) + + t = (alpha * t_inner_coeff) ** (1.0 / 0.9) + ac = ViewingConditions.aw * (j_normalized ** (1.0 / ViewingConditions.c / ViewingConditions.z)) + p2 = ac / ViewingConditions.nbb + gamma = 23.0 * (p2 + 0.305) * t / (23.0 * p1 + 11.0 * t * h_cos + 108.0 * t * h_sin) + a = gamma * h_cos + b = gamma * h_sin + + r_a = (460.0 * p2 + 451.0 * a + 288.0 * b) / 1403.0 + g_a = (460.0 * p2 - 891.0 * a - 261.0 * b) / 1403.0 + b_a = (460.0 * p2 - 220.0 * a - 6300.0 * b) / 1403.0 + + r_cscaled = HctSolver._inverse_chromatic_adaptation(r_a) + g_cscaled = HctSolver._inverse_chromatic_adaptation(g_a) + b_cscaled = HctSolver._inverse_chromatic_adaptation(b_a) + + linrgb = _matrix_multiply(_LINRGB_FROM_SCALED_DISCOUNT, + [r_cscaled, g_cscaled, b_cscaled]) + + # Check if in gamut + if linrgb[0] < 0 or linrgb[1] < 0 or linrgb[2] < 0: + return None + + k_r, k_g, k_b = _Y_FROM_LINRGB + fnj = k_r * linrgb[0] + k_g * linrgb[1] + k_b * linrgb[2] + + if fnj <= 0: + return None + + if iteration == 4 or abs(fnj - y) < 0.002: + if linrgb[0] > 100.01 or linrgb[1] > 100.01 or linrgb[2] > 100.01: + return None + + # Convert linear RGB to sRGB + return ( + _delinearize(linrgb[0] / 100.0), + _delinearize(linrgb[1] / 100.0), + _delinearize(linrgb[2] / 100.0), + ) + + # Newton iteration + j = j - (fnj - y) * j / (2.0 * fnj) + + return None + + @staticmethod + def solve_to_rgb(hue_degrees: float, chroma: float, tone: float) -> tuple[int, int, int]: + """ + Solve HCT to RGB with proper gamut mapping. + + If the exact color is out of gamut, finds the maximum achievable + chroma while preserving the exact hue. + """ + if chroma < 0.0001 or tone < 0.0001 or tone > 99.9999: + # Achromatic - just convert tone to gray + y = lstar_to_y(tone) + gray = _delinearize(y / 100.0) + return (gray, gray, gray) + + hue_degrees = _sanitize_degrees(hue_degrees) + hue_radians = math.radians(hue_degrees) + # Y is in 0-100 range (same scale as internal linear RGB in the solver) + y = lstar_to_y(tone) + + # Try to find exact solution + exact = HctSolver._find_result_by_j(hue_radians, chroma, y) + if exact is not None: + return exact + + # Fall back to bisection - find max chroma that preserves hue + linrgb = HctSolver._bisect_to_limit(y, hue_radians) + + return ( + _delinearize(linrgb[0] / 100.0), + _delinearize(linrgb[1] / 100.0), + _delinearize(linrgb[2] / 100.0), + ) + + +def rgb_to_xyz(r: int, g: int, b: int) -> tuple[float, float, float]: + """Convert sRGB to CIE XYZ.""" + linear_r = _linearize(r) + linear_g = _linearize(g) + linear_b = _linearize(b) + xyz = _matrix_multiply(SRGB_TO_XYZ, [linear_r, linear_g, linear_b]) + return (xyz[0] * 100, xyz[1] * 100, xyz[2] * 100) + + +def xyz_to_rgb(x: float, y: float, z: float) -> tuple[int, int, int]: + """Convert CIE XYZ to sRGB.""" + linear = _matrix_multiply(XYZ_TO_SRGB, [x / 100, y / 100, z / 100]) + return (_delinearize(linear[0]), _delinearize(linear[1]), _delinearize(linear[2])) + + +def y_to_lstar(y: float) -> float: + """Convert XYZ Y component to L* (CIELAB lightness / HCT Tone).""" + if y <= 0: + return 0.0 + y_normalized = y / 100.0 + if y_normalized <= 0.008856: + return 903.2962962962963 * y_normalized + return 116.0 * math.pow(y_normalized, 1.0 / 3.0) - 16.0 + + +def lstar_to_y(lstar: float) -> float: + """Convert L* (Tone) to XYZ Y component.""" + if lstar <= 0: + return 0.0 + if lstar > 100: + lstar = 100.0 + if lstar <= 8.0: + return lstar / 903.2962962962963 * 100.0 + fy = (lstar + 16.0) / 116.0 + return fy * fy * fy * 100.0 + + +def argb_to_int(r: int, g: int, b: int) -> int: + """Convert RGB to ARGB integer (alpha = 255).""" + return (255 << 24) | (r << 16) | (g << 8) | b + + +def int_to_rgb(argb: int) -> tuple[int, int, int]: + """Convert ARGB integer to RGB tuple.""" + return ((argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF) + + +class Cam16: + """CAM16 color appearance model representation.""" + + def __init__(self, hue: float, chroma: float, j: float, q: float, + m: float, s: float, jstar: float, astar: float, bstar: float): + self.hue = hue + self.chroma = chroma + self.j = j # Lightness + self.q = q # Brightness + self.m = m # Colorfulness + self.s = s # Saturation + self.jstar = jstar # CAM16-UCS J* + self.astar = astar # CAM16-UCS a* + self.bstar = bstar # CAM16-UCS b* + + @classmethod + def from_rgb(cls, r: int, g: int, b: int) -> 'Cam16': + """Create CAM16 from sRGB values.""" + x, y, z = rgb_to_xyz(r, g, b) + + r_c = 0.401288 * x + 0.650173 * y - 0.051461 * z + g_c = -0.250268 * x + 1.204414 * y + 0.045854 * z + b_c = -0.002079 * x + 0.048952 * y + 0.953127 * z + + r_d = ViewingConditions.RGB_D[0] * r_c + g_d = ViewingConditions.RGB_D[1] * g_c + b_d = ViewingConditions.RGB_D[2] * b_c + + r_af = math.pow(ViewingConditions.fl * abs(r_d) / 100.0, 0.42) + g_af = math.pow(ViewingConditions.fl * abs(g_d) / 100.0, 0.42) + b_af = math.pow(ViewingConditions.fl * abs(b_d) / 100.0, 0.42) + + r_a = _signum(r_d) * 400.0 * r_af / (r_af + 27.13) + g_a = _signum(g_d) * 400.0 * g_af / (g_af + 27.13) + b_a = _signum(b_d) * 400.0 * b_af / (b_af + 27.13) + + a = (11.0 * r_a + -12.0 * g_a + b_a) / 11.0 + b = (r_a + g_a - 2.0 * b_a) / 9.0 + + hue_radians = math.atan2(b, a) + hue = math.degrees(hue_radians) + if hue < 0: + hue += 360.0 + + u = (20.0 * r_a + 20.0 * g_a + 21.0 * b_a) / 20.0 + p2 = (40.0 * r_a + 20.0 * g_a + b_a) / 20.0 + ac = p2 * ViewingConditions.nbb + + j = 100.0 * math.pow(ac / ViewingConditions.aw, ViewingConditions.c * ViewingConditions.z) + q = (4.0 / ViewingConditions.c) * math.sqrt(j / 100.0) * (ViewingConditions.aw + 4.0) * ViewingConditions.fl_root + + hue_prime = hue + 360.0 if hue < 20.14 else hue + e_hue = 0.25 * (math.cos(math.radians(hue_prime) + 2.0) + 3.8) + + t = 50000.0 / 13.0 * ViewingConditions.nc * ViewingConditions.ncb * e_hue * math.sqrt(a * a + b * b) / (u + 0.305) + alpha = math.pow(t, 0.9) * math.pow(1.64 - math.pow(0.29, ViewingConditions.n), 0.73) + chroma = alpha * math.sqrt(j / 100.0) + + m = chroma * ViewingConditions.fl_root + s = 50.0 * math.sqrt((ViewingConditions.c * alpha) / (ViewingConditions.aw + 4.0)) + + jstar = (1.0 + 100.0 * 0.007) * j / (1.0 + 0.007 * j) + mstar = 1.0 / 0.0228 * math.log(1.0 + 0.0228 * m) if m > 0 else 0 + astar = mstar * math.cos(hue_radians) + bstar = mstar * math.sin(hue_radians) + + return cls(hue, chroma, j, q, m, s, jstar, astar, bstar) + + @classmethod + def from_jch(cls, j: float, chroma: float, hue: float) -> 'Cam16': + """Create CAM16 from J (lightness), chroma, and hue.""" + q = (4.0 / ViewingConditions.c) * math.sqrt(j / 100.0) * (ViewingConditions.aw + 4.0) * ViewingConditions.fl_root + m = chroma * ViewingConditions.fl_root + alpha = chroma / math.sqrt(j / 100.0) if j > 0 else 0 + s = 50.0 * math.sqrt((ViewingConditions.c * alpha) / (ViewingConditions.aw + 4.0)) + + hue_radians = math.radians(hue) + jstar = (1.0 + 100.0 * 0.007) * j / (1.0 + 0.007 * j) + mstar = 1.0 / 0.0228 * math.log(1.0 + 0.0228 * m) if m > 0 else 0 + astar = mstar * math.cos(hue_radians) + bstar = mstar * math.sin(hue_radians) + + return cls(hue, chroma, j, q, m, s, jstar, astar, bstar) + + def to_rgb(self) -> tuple[int, int, int]: + """Convert CAM16 back to sRGB.""" + if self.chroma == 0 or self.j == 0: + y = lstar_to_y(self.j) + return xyz_to_rgb(y, y, y) + + hue_radians = math.radians(self.hue) + + alpha = self.chroma / math.sqrt(self.j / 100.0) if self.j > 0 else 0 + t = math.pow(alpha / math.pow(1.64 - math.pow(0.29, ViewingConditions.n), 0.73), 1.0 / 0.9) + + hue_prime = self.hue + 360.0 if self.hue < 20.14 else self.hue + e_hue = 0.25 * (math.cos(math.radians(hue_prime) + 2.0) + 3.8) + + ac = ViewingConditions.aw * math.pow(self.j / 100.0, 1.0 / (ViewingConditions.c * ViewingConditions.z)) + p1 = 50000.0 / 13.0 * ViewingConditions.nc * ViewingConditions.ncb * e_hue + p2 = ac / ViewingConditions.nbb + + gamma = 23.0 * (p2 + 0.305) * t / (23.0 * p1 + 11.0 * t * math.cos(hue_radians) + 108.0 * t * math.sin(hue_radians)) + + a = gamma * math.cos(hue_radians) + b = gamma * math.sin(hue_radians) + + r_a = (460.0 * p2 + 451.0 * a + 288.0 * b) / 1403.0 + g_a = (460.0 * p2 - 891.0 * a - 261.0 * b) / 1403.0 + b_a = (460.0 * p2 - 220.0 * a - 6300.0 * b) / 1403.0 + + def reverse_adapt(adapted: float) -> float: + abs_adapted = abs(adapted) + base = max(0, 27.13 * abs_adapted / (400.0 - abs_adapted)) + return _signum(adapted) * 100.0 / ViewingConditions.fl * math.pow(base, 1.0 / 0.42) + + r_c = reverse_adapt(r_a) / ViewingConditions.RGB_D[0] + g_c = reverse_adapt(g_a) / ViewingConditions.RGB_D[1] + b_c = reverse_adapt(b_a) / ViewingConditions.RGB_D[2] + + x = 1.8620678 * r_c - 1.0112547 * g_c + 0.1491867 * b_c + y = 0.3875265 * r_c + 0.6214474 * g_c - 0.0089739 * b_c + z = -0.0158415 * r_c - 0.0344156 * g_c + 1.0502571 * b_c + + return xyz_to_rgb(x, y, z) + + +class Hct: + """ + HCT (Hue, Chroma, Tone) color representation. + + Material Design 3's perceptual color space combining: + - Hue: CAM16 hue (0-360) + - Chroma: CAM16 chroma (colorfulness, typically 0-120+) + - Tone: CIELAB L* lightness (0-100) + """ + + def __init__(self, hue: float, chroma: float, tone: float): + self._hue = hue % 360.0 + self._chroma = max(0.0, chroma) + self._tone = max(0.0, min(100.0, tone)) + self._argb: int | None = None + + @property + def hue(self) -> float: + return self._hue + + @property + def chroma(self) -> float: + return self._chroma + + @property + def tone(self) -> float: + return self._tone + + @classmethod + def from_rgb(cls, r: int, g: int, b: int) -> 'Hct': + """Create HCT from sRGB values.""" + cam = Cam16.from_rgb(r, g, b) + _, y, _ = rgb_to_xyz(r, g, b) + tone = y_to_lstar(y) + return cls(cam.hue, cam.chroma, tone) + + @classmethod + def from_argb(cls, argb: int) -> 'Hct': + """Create HCT from ARGB integer.""" + r, g, b = int_to_rgb(argb) + return cls.from_rgb(r, g, b) + + def to_rgb(self) -> tuple[int, int, int]: + """Convert HCT to sRGB, solving for the color.""" + return self._solve_to_rgb(self._hue, self._chroma, self._tone) + + def to_argb(self) -> int: + """Convert HCT to ARGB integer.""" + if self._argb is None: + r, g, b = self.to_rgb() + self._argb = argb_to_int(r, g, b) + return self._argb + + def to_hex(self) -> str: + """Convert HCT to hex string.""" + r, g, b = self.to_rgb() + return f"#{r:02x}{g:02x}{b:02x}" + + @staticmethod + def _solve_to_rgb(hue: float, chroma: float, tone: float) -> tuple[int, int, int]: + """ + Solve for RGB given HCT values using the Material HctSolver. + + This uses proper gamut mapping that preserves hue exactly. + When the requested chroma is out of gamut, it finds the maximum + achievable chroma while maintaining the exact target hue. + """ + return HctSolver.solve_to_rgb(hue, chroma, tone) + + def set_hue(self, hue: float) -> 'Hct': + """Return new HCT with different hue.""" + return Hct(hue, self._chroma, self._tone) + + def set_chroma(self, chroma: float) -> 'Hct': + """Return new HCT with different chroma.""" + return Hct(self._hue, chroma, self._tone) + + def set_tone(self, tone: float) -> 'Hct': + """Return new HCT with different tone.""" + return Hct(self._hue, self._chroma, tone) + + +class TemperatureCache: + """ + Color temperature analysis for finding harmonious colors. + + Based on Material Color Utilities - calculates relative warmth of colors + and finds analogous colors based on temperature similarity. + """ + + def __init__(self, input_hct: Hct): + self.input = input_hct + self._hcts_by_temp: list[Hct] | None = None + self._hcts_by_hue: list[Hct] | None = None + self._temps_by_hct: dict[tuple[float, float, float], float] | None = None + self._input_relative_temp: float | None = None + self._complement: Hct | None = None + + @staticmethod + def raw_temperature(hct: Hct) -> float: + """ + Calculate raw temperature of a color using Ou-Woodcock-Wright algorithm. + + Based on material-colors Rust implementation. + Uses LAB a* and b* to determine warm-cool factor. + Values below 0 are cool, above 0 are warm. + """ + # Convert HCT to RGB then to LAB + rgb = hct.to_rgb() + x, y, z = rgb_to_xyz(rgb[0], rgb[1], rgb[2]) + + # XYZ to LAB + def f(t: float) -> float: + delta = 6.0 / 29.0 + if t > delta ** 3: + return t ** (1.0 / 3.0) + return t / (3 * delta * delta) + 4.0 / 29.0 + + xn, yn, zn = 95.047, 100.0, 108.883 # D65 reference + lab_a = 500.0 * (f(x / xn) - f(y / yn)) + lab_b = 200.0 * (f(y / yn) - f(z / zn)) + + # Calculate LAB hue and chroma + lab_hue = math.degrees(math.atan2(lab_b, lab_a)) + if lab_hue < 0: + lab_hue += 360.0 + lab_chroma = math.hypot(lab_a, lab_b) + + # Ou-Woodcock-Wright formula for temperature + # temp = -0.5 + 0.02 * chroma^1.07 * cos(toRadians(hue - 50)) + hue_rad = math.radians((lab_hue - 50.0) % 360.0) + return -0.5 + 0.02 * (lab_chroma ** 1.07) * math.cos(hue_rad) + + def _get_hcts_by_hue(self) -> list[Hct]: + """Generate HCT colors at regular hue intervals.""" + if self._hcts_by_hue is not None: + return self._hcts_by_hue + + hcts = [] + for hue in range(360): + color_at_hue = Hct(float(hue), self.input.chroma, self.input.tone) + hcts.append(color_at_hue) + + self._hcts_by_hue = hcts + return hcts + + def _get_temps_by_hct(self) -> dict[tuple[float, float, float], float]: + """Cache temperatures for all hue variants.""" + if self._temps_by_hct is not None: + return self._temps_by_hct + + hcts = self._get_hcts_by_hue() + temps = {} + for hct in hcts: + key = (hct.hue, hct.chroma, hct.tone) + temps[key] = self.raw_temperature(hct) + + self._temps_by_hct = temps + return temps + + def _get_hcts_by_temp(self) -> list[Hct]: + """Get HCT colors sorted by temperature.""" + if self._hcts_by_temp is not None: + return self._hcts_by_temp + + hcts = list(self._get_hcts_by_hue()) + temps = self._get_temps_by_hct() + hcts.sort(key=lambda h: temps[(h.hue, h.chroma, h.tone)]) + + self._hcts_by_temp = hcts + return hcts + + def _relative_temperature(self, hct: Hct) -> float: + """ + Calculate relative temperature (0-1) based on position in temperature-sorted list. + """ + temps = self._get_temps_by_hct() + hcts_by_temp = self._get_hcts_by_temp() + + key = (hct.hue, hct.chroma, hct.tone) + if key in temps: + raw = temps[key] + else: + raw = self.raw_temperature(hct) + + # Find position in sorted list + coldest = self.raw_temperature(hcts_by_temp[0]) + warmest = self.raw_temperature(hcts_by_temp[-1]) + + if warmest == coldest: + return 0.5 + + return (raw - coldest) / (warmest - coldest) + + def _input_relative_temperature_value(self) -> float: + """Get relative temperature of the input color.""" + if self._input_relative_temp is None: + self._input_relative_temp = self._relative_temperature(self.input) + return self._input_relative_temp + + def complement(self) -> Hct: + """ + Find the complement: color with opposite temperature. + """ + if self._complement is not None: + return self._complement + + input_temp = self._input_relative_temperature_value() + hcts_by_temp = self._get_hcts_by_temp() + temps = self._get_temps_by_hct() + + # Target is opposite temperature + target_temp = 1.0 - input_temp + + # Find closest match + best_hct = hcts_by_temp[0] + best_diff = float('inf') + + for hct in hcts_by_temp: + key = (hct.hue, hct.chroma, hct.tone) + raw = temps.get(key, self.raw_temperature(hct)) + rel = self._relative_temperature(hct) + diff = abs(rel - target_temp) + if diff < best_diff: + best_diff = diff + best_hct = hct + + self._complement = best_hct + return best_hct + + def analogous(self, count: int | None = None, divisions: int | None = None) -> list[Hct]: + """ + Find analogous colors based on temperature. + + Uses material-colors algorithm: + 1. Build a list of all `divisions` colors at equal temperature steps + 2. Pick `count` colors from this list, centered around the input + + Args: + count: Number of colors to return (default 5) + divisions: How many divisions of the temperature range (default 12) + + Returns: + List of HCT colors including the input, spread by temperature. + """ + if count is None: + count = 5 + if divisions is None: + divisions = 12 + + hcts_by_hue = self._get_hcts_by_hue() + start_hue = round(self.input.hue) % 360 + start_hct = hcts_by_hue[start_hue] + + # Calculate total absolute temperature delta around the color wheel + last_temp = self._relative_temperature(start_hct) + absolute_total_temp_delta = 0.0 + + for i in range(360): + hue = (start_hue + i) % 360 + hct = hcts_by_hue[hue] + temp = self._relative_temperature(hct) + temp_delta = abs(temp - last_temp) + last_temp = temp + absolute_total_temp_delta += temp_delta + + # Build list of all colors at equal temperature steps + temp_step = absolute_total_temp_delta / divisions + all_colors: list[Hct] = [start_hct] + total_temp_delta = 0.0 + last_temp = self._relative_temperature(start_hct) + hue_addend = 1 + + while len(all_colors) < divisions and hue_addend <= 360: + hue = (start_hue + hue_addend) % 360 + hct = hcts_by_hue[hue] + temp = self._relative_temperature(hct) + temp_delta = abs(temp - last_temp) + total_temp_delta += temp_delta + + desired_total = len(all_colors) * temp_step + + # Add this hue until its temperature is insufficient + while total_temp_delta >= desired_total and len(all_colors) < divisions: + all_colors.append(hct) + desired_total = (len(all_colors) + 1) * temp_step + + last_temp = temp + hue_addend += 1 + + # Fill remaining slots if needed + while len(all_colors) < divisions: + all_colors.append(all_colors[-1] if all_colors else start_hct) + + # Build final answer list centered around input + answers: list[Hct] = [self.input] + + # Counter-clockwise (negative indices) + increase_hue_count = int((count - 1) // 2) + for i in range(1, increase_hue_count + 1): + index = (-i) % len(all_colors) + answers.insert(0, all_colors[index]) + + # Clockwise (positive indices) + decrease_hue_count = count - increase_hue_count - 1 + for i in range(1, decrease_hue_count + 1): + index = i % len(all_colors) + answers.append(all_colors[index]) + + return answers + + +def fix_if_disliked(hct: Hct) -> Hct: + """ + Fix colors in the "disliked" hue range (yellow-green). + + These colors often look muddy or unpleasant. If detected, + shift the hue slightly to improve appearance. + """ + # Disliked range: roughly 80-110 degrees (yellow-green) + if hct.hue >= 80.0 and hct.hue <= 110.0 and hct.chroma > 16.0: + # Shift towards warmer yellow or cooler green + new_hue = 75.0 if hct.hue < 95.0 else 115.0 + return Hct(new_hue, hct.chroma, hct.tone) + return hct + + +class TonalPalette: + """ + A palette of tones for a single hue and chroma. + + Material Design 3 uses specific tone values for different UI elements. + """ + + def __init__(self, hue: float, chroma: float): + self.hue = hue + self.chroma = chroma + self._cache: dict[int, int] = {} + + @classmethod + def from_hct(cls, hct: Hct) -> 'TonalPalette': + """Create TonalPalette from HCT color.""" + return cls(hct.hue, hct.chroma) + + @classmethod + def from_rgb(cls, r: int, g: int, b: int) -> 'TonalPalette': + """Create TonalPalette from RGB color.""" + hct = Hct.from_rgb(r, g, b) + return cls(hct.hue, hct.chroma) + + def tone(self, t: int) -> int: + """Get ARGB color at the specified tone (0-100).""" + if t not in self._cache: + hct = Hct(self.hue, self.chroma, float(t)) + self._cache[t] = hct.to_argb() + return self._cache[t] + + def get_rgb(self, t: int) -> tuple[int, int, int]: + """Get RGB color at the specified tone.""" + return int_to_rgb(self.tone(t)) + + def get_hex(self, t: int) -> str: + """Get hex color at the specified tone.""" + r, g, b = self.get_rgb(t) + return f"#{r:02x}{g:02x}{b:02x}" diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/image.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/image.py new file mode 100644 index 0000000..7e0c31a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/image.py @@ -0,0 +1,366 @@ +""" +Image reading utilities for PNG and JPEG files. + +This module provides functions for extracting RGB pixels from image files +without external dependencies (except ImageMagick for fallback). +""" + +import struct +import zlib +from pathlib import Path + +# Type alias +RGB = tuple[int, int, int] + + +class ImageReadError(Exception): + """Raised when image cannot be read or parsed.""" + pass + + +def read_png(path: Path) -> list[RGB]: + """ + Parse a PNG file and extract RGB pixels. + + Supports 8-bit RGB and RGBA color types (most common for wallpapers). + Uses zlib for IDAT decompression and handles PNG filters. + """ + with open(path, 'rb') as f: + data = f.read() + + # Verify PNG signature + if data[:8] != b'\x89PNG\r\n\x1a\n': + raise ImageReadError("Invalid PNG signature") + + pos = 8 + width = 0 + height = 0 + bit_depth = 0 + color_type = 0 + idat_chunks: list[bytes] = [] + + while pos < len(data): + # Read chunk length and type + chunk_len = struct.unpack('>I', data[pos:pos+4])[0] + chunk_type = data[pos+4:pos+8] + chunk_data = data[pos+8:pos+8+chunk_len] + pos += 12 + chunk_len # length + type + data + crc + + if chunk_type == b'IHDR': + width = struct.unpack('>I', chunk_data[0:4])[0] + height = struct.unpack('>I', chunk_data[4:8])[0] + bit_depth = chunk_data[8] + color_type = chunk_data[9] + + if bit_depth != 8: + raise ImageReadError(f"Unsupported bit depth: {bit_depth}") + if color_type not in (2, 6): # RGB or RGBA + raise ImageReadError(f"Unsupported color type: {color_type}") + + elif chunk_type == b'IDAT': + idat_chunks.append(chunk_data) + + elif chunk_type == b'IEND': + break + + if not idat_chunks or width == 0: + raise ImageReadError("Missing image data") + + # Decompress all IDAT chunks + compressed = b''.join(idat_chunks) + raw_data = zlib.decompress(compressed) + + # Calculate bytes per pixel and row + bpp = 3 if color_type == 2 else 4 # RGB or RGBA + stride = width * bpp + 1 # +1 for filter byte + + pixels: list[RGB] = [] + prev_row: list[int] = [0] * (width * bpp) + + for y in range(height): + row_start = y * stride + filter_type = raw_data[row_start] + row_data = list(raw_data[row_start + 1:row_start + stride]) + + # Apply PNG filter reconstruction + unfiltered = _png_unfilter(row_data, prev_row, bpp, filter_type) + prev_row = unfiltered + + # Extract RGB values (skip alpha if present) + for x in range(width): + idx = x * bpp + r, g, b = unfiltered[idx], unfiltered[idx+1], unfiltered[idx+2] + pixels.append((r, g, b)) + + return pixels + + +def _png_unfilter( + row: list[int], + prev_row: list[int], + bpp: int, + filter_type: int +) -> list[int]: + """Apply PNG filter reconstruction.""" + result = [0] * len(row) + + for i in range(len(row)): + x = row[i] + a = result[i - bpp] if i >= bpp else 0 + b = prev_row[i] + c = prev_row[i - bpp] if i >= bpp else 0 + + if filter_type == 0: # None + result[i] = x + elif filter_type == 1: # Sub + result[i] = (x + a) & 0xFF + elif filter_type == 2: # Up + result[i] = (x + b) & 0xFF + elif filter_type == 3: # Average + result[i] = (x + (a + b) // 2) & 0xFF + elif filter_type == 4: # Paeth + result[i] = (x + _paeth_predictor(a, b, c)) & 0xFF + else: + raise ImageReadError(f"Unknown PNG filter type: {filter_type}") + + return result + + +def _paeth_predictor(a: int, b: int, c: int) -> int: + """Paeth predictor for PNG filter reconstruction.""" + p = a + b - c + pa = abs(p - a) + pb = abs(p - b) + pc = abs(p - c) + + if pa <= pb and pa <= pc: + return a + elif pb <= pc: + return b + return c + + +def read_jpeg(path: Path) -> list[RGB]: + """ + Parse a JPEG file and extract RGB pixels. + + Supports baseline (SOF0), extended (SOF1), and progressive (SOF2) JPEG. + This is a simplified decoder that extracts dimensions then samples colors. + """ + with open(path, 'rb') as f: + data = f.read() + + # Verify JPEG signature (SOI marker) + if data[:2] != b'\xff\xd8': + raise ImageReadError("Invalid JPEG signature") + + pos = 2 + width = 0 + height = 0 + + # SOF markers that contain image dimensions + # SOF0=Baseline, SOF1=Extended, SOF2=Progressive, SOF3=Lossless + # SOF5-7=Differential variants, SOF9-11=Arithmetic coding variants + sof_markers = {0xC0, 0xC1, 0xC2, 0xC3, 0xC5, 0xC6, 0xC7, + 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF} + + # Standalone markers (no length field) + standalone_markers = {0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, # RST0-7 + 0xD8, # SOI + 0xD9, # EOI + 0x01} # TEM + + while pos < len(data) - 1: + # Find next marker + if data[pos] != 0xFF: + pos += 1 + continue + + # Skip padding 0xFF bytes + while pos < len(data) and data[pos] == 0xFF: + pos += 1 + + if pos >= len(data): + break + + marker = data[pos] + pos += 1 + + # Check for SOF marker (contains dimensions) + if marker in sof_markers: + if pos + 7 <= len(data): + # Skip segment length (2 bytes), precision (1 byte) + height = struct.unpack('>H', data[pos+3:pos+5])[0] + width = struct.unpack('>H', data[pos+5:pos+7])[0] + break + + # End of image + if marker == 0xD9: + break + + # Skip segment data for markers with length field + if marker not in standalone_markers and marker != 0x00: + if pos + 2 <= len(data): + seg_len = struct.unpack('>H', data[pos:pos+2])[0] + pos += seg_len + + if width == 0 or height == 0: + raise ImageReadError("Could not parse JPEG dimensions") + + # Since full JPEG decoding is extremely complex without external libraries, + # we fall back to sampling the raw data for color approximation. + return _sample_jpeg_colors(data, width, height) + + +def _sample_jpeg_colors(data: bytes, width: int, height: int) -> list[RGB]: + """ + Sample colors from JPEG data without full decoding. + + This is a rough approximation that samples byte triplets from the + compressed data. Not accurate, but provides some color information. + For accurate results, use ImageMagick via read_image(). + """ + # Sample every Nth byte triplet from the image data + # Skip headers and look for image data after SOS marker + pixels: list[RGB] = [] + step = max(1, len(data) // (width * height // 16)) + + for i in range(0, len(data) - 2, step): + r, g, b = data[i], data[i+1], data[i+2] + # Filter out obviously non-image data (markers, etc.) + if not (r == 0xFF and g in (0xD8, 0xD9, 0xE0, 0xE1)): + pixels.append((r, g, b)) + + return pixels if pixels else [(128, 128, 128)] + + +def _read_image_imagemagick(path: Path, resize_filter: str = "Triangle") -> list[RGB]: + """ + Read image using ImageMagick's convert command. + + Converts image to PPM format (trivial to parse) and extracts RGB pixels. + This method works accurately for any image format ImageMagick supports. + """ + import subprocess + + # Use magick or convert command + # -depth 8: 8 bits per channel + # -resize: downsample for performance (we don't need full resolution for color extraction) + # ppm: output as PPM format (easy to parse) + + # Resize to 112x112 to match matugen's color extraction + # Use -filter Triangle (bilinear) for M3 schemes to match matugen's FilterType::Triangle default + # Use -filter Box for k-means schemes (sharper, preserves distinct color regions) + # Use -depth 8 -colorspace sRGB -strip to reduce variance between HDRI/non-HDRI builds + resize_spec = "112x112!" + + try: + # Try 'magick' first (ImageMagick 7+), fallback to 'convert' (ImageMagick 6) + try: + result = subprocess.run( + ['magick', str(path), '-filter', resize_filter, '-resize', resize_spec, + '-depth', '8', '-colorspace', 'sRGB', '-strip', 'ppm:-'], + capture_output=True, + check=True + ) + except FileNotFoundError: + result = subprocess.run( + ['convert', str(path), '-filter', resize_filter, '-resize', resize_spec, + '-depth', '8', '-colorspace', 'sRGB', '-strip', 'ppm:-'], + capture_output=True, + check=True + ) + except subprocess.CalledProcessError as e: + raise ImageReadError(f"ImageMagick failed: {e.stderr.decode()}") + except FileNotFoundError: + raise ImageReadError("ImageMagick not found. Please install imagemagick.") + + ppm_data = result.stdout + return _parse_ppm(ppm_data) + + +def _parse_ppm(data: bytes) -> list[RGB]: + """ + Parse PPM (Portable Pixmap) binary format. + + PPM P6 format: + P6 + width height + maxval + + """ + pos = 0 + tokens: list[str] = [] + + # Read header tokens (need 4: P6, width, height, maxval) + while len(tokens) < 4 and pos < len(data): + # Skip whitespace + while pos < len(data) and data[pos:pos+1] in (b' ', b'\t', b'\n', b'\r'): + pos += 1 + + # Skip comments + if pos < len(data) and data[pos:pos+1] == b'#': + while pos < len(data) and data[pos:pos+1] != b'\n': + pos += 1 + continue + + # Read token + token_start = pos + while pos < len(data) and data[pos:pos+1] not in (b' ', b'\t', b'\n', b'\r', b'#'): + pos += 1 + + if pos > token_start: + tokens.append(data[token_start:pos].decode('ascii')) + + if len(tokens) < 4 or tokens[0] != 'P6': + raise ImageReadError(f"Invalid PPM format: {tokens}") + + width = int(tokens[1]) + height = int(tokens[2]) + maxval = int(tokens[3]) + + # Skip exactly one whitespace character after maxval (per PPM spec) + if pos < len(data) and data[pos:pos+1] in (b' ', b'\t', b'\n', b'\r'): + pos += 1 + + pixel_data = data[pos:] + + # Parse RGB triplets + pixels: list[RGB] = [] + scale = 255.0 / maxval if maxval != 255 else 1.0 + + for i in range(0, min(len(pixel_data), width * height * 3), 3): + if i + 2 < len(pixel_data): + r = int(pixel_data[i] * scale) + g = int(pixel_data[i + 1] * scale) + b = int(pixel_data[i + 2] * scale) + pixels.append((r, g, b)) + + if not pixels: + raise ImageReadError("No pixels extracted from PPM data") + + return pixels + + +def read_image(path: Path, resize_filter: str = "Triangle") -> list[RGB]: + """ + Read an image file and return its pixels as RGB tuples. + + Uses ImageMagick for accurate color extraction from any format. + Falls back to native PNG parsing if ImageMagick is unavailable. + + Args: + path: Path to the image file. + resize_filter: ImageMagick resize filter. "Triangle" for M3 schemes + (matches matugen), "Box" for k-means schemes. + """ + suffix = path.suffix.lower() + + # Try ImageMagick first (works for any format) + try: + return _read_image_imagemagick(path, resize_filter) + except ImageReadError: + # Fall back to native parsing for PNG + if suffix == '.png': + return read_png(path) + raise diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/material.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/material.py new file mode 100644 index 0000000..9bc318a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/material.py @@ -0,0 +1,540 @@ +""" +Material Design 3 color scheme implementation. + +This module provides scheme classes for generating MD3 color schemes +from a source color using the HCT color space. + +Supported schemes (matching Matugen): +- SchemeTonalSpot: Default Android 12-13 scheme, mid-vibrancy +- SchemeFruitSalad: Bold/playful with -50ยฐ hue rotation +- SchemeRainbow: Chromatic accents with grayscale neutrals +- SchemeContent: Preserves source color's chroma +""" + +from .hct import Hct, TonalPalette, TemperatureCache, fix_if_disliked + + +# ============================================================================= +# Tone Values (shared across all schemes) +# ============================================================================= + +# Tone values for Material Design 3 (dark theme) +DARK_TONES = { + 'primary': 80, + 'on_primary': 20, + 'primary_container': 30, + 'on_primary_container': 90, + 'secondary': 80, + 'on_secondary': 20, + 'secondary_container': 30, + 'on_secondary_container': 90, + 'tertiary': 80, + 'on_tertiary': 20, + 'tertiary_container': 30, + 'on_tertiary_container': 90, + 'error': 80, + 'on_error': 20, + 'error_container': 30, + 'on_error_container': 90, + 'surface': 6, + 'on_surface': 90, + 'surface_variant': 30, + 'on_surface_variant': 80, + 'surface_container_lowest': 4, + 'surface_container_low': 10, + 'surface_container': 12, + 'surface_container_high': 17, + 'surface_container_highest': 22, + 'outline': 60, + 'outline_variant': 30, + 'shadow': 0, + 'scrim': 0, + 'inverse_surface': 90, + 'inverse_on_surface': 20, + 'inverse_primary': 40, +} + +# Tone values for Material Design 3 (light theme) +LIGHT_TONES = { + 'primary': 40, + 'on_primary': 100, + 'primary_container': 90, + 'on_primary_container': 10, + 'secondary': 40, + 'on_secondary': 100, + 'secondary_container': 90, + 'on_secondary_container': 10, + 'tertiary': 40, + 'on_tertiary': 100, + 'tertiary_container': 90, + 'on_tertiary_container': 10, + 'error': 40, + 'on_error': 100, + 'error_container': 90, + 'on_error_container': 10, + 'surface': 98, + 'on_surface': 10, + 'surface_variant': 90, + 'on_surface_variant': 30, + 'surface_container_lowest': 100, + 'surface_container_low': 96, + 'surface_container': 94, + 'surface_container_high': 92, + 'surface_container_highest': 90, + 'outline': 50, + 'outline_variant': 80, + 'shadow': 0, + 'scrim': 0, + 'inverse_surface': 20, + 'inverse_on_surface': 95, + 'inverse_primary': 80, +} + +# Monochrome scheme uses different tone values (from material-colors library) +# Primary/tertiary get special treatment for higher contrast in grayscale +MONOCHROME_DARK_TONES = { + **DARK_TONES, + 'primary': 100, # White (was 80) + 'on_primary': 10, # Near-black (was 20) + 'primary_container': 85, # Light gray (was 30) + 'on_primary_container': 0, # Black (was 90) + 'tertiary': 90, # Light gray (was 80) + 'on_tertiary': 10, # Near-black (was 20) + 'tertiary_container': 60, # Mid gray (was 30) + 'on_tertiary_container': 0, # Black (was 90) + 'secondary_container': 30, # Same as normal +} + +MONOCHROME_LIGHT_TONES = { + **LIGHT_TONES, + 'primary': 0, # Black (was 40) + 'on_primary': 90, # Light gray (was 100) + 'primary_container': 25, # Dark gray (was 90) + 'on_primary_container': 100, # White (was 10) + 'tertiary': 25, # Dark gray (was 40) + 'on_tertiary': 90, # Light gray (was 100) + 'tertiary_container': 49, # Mid gray (was 90) + 'on_tertiary_container': 100, # White (was 10) + 'secondary_container': 90, # Same as normal +} + + +# ============================================================================= +# Base Scheme Class +# ============================================================================= + +class _BaseScheme: + """Base class for all Material Design 3 schemes.""" + + # Error palette is the same for all schemes + error_palette: TonalPalette + + def __init__(self, source_color: Hct): + """Initialize with source color. Subclasses must set palettes.""" + self.source = source_color + self.error_palette = TonalPalette(25.0, 84.0) # Material red + + @classmethod + def from_rgb(cls, r: int, g: int, b: int) -> '_BaseScheme': + """Create scheme from RGB color.""" + return cls(Hct.from_rgb(r, g, b)) + + @classmethod + def from_hex(cls, hex_color: str) -> '_BaseScheme': + """Create scheme from hex color string.""" + hex_color = hex_color.lstrip('#') + r = int(hex_color[0:2], 16) + g = int(hex_color[2:4], 16) + b = int(hex_color[4:6], 16) + return cls.from_rgb(r, g, b) + + def get_dark_scheme(self) -> dict[str, str]: + """Generate dark theme color dictionary.""" + return self._generate_scheme(is_dark=True) + + def get_light_scheme(self) -> dict[str, str]: + """Generate light theme color dictionary.""" + return self._generate_scheme(is_dark=False) + + def _generate_scheme(self, is_dark: bool) -> dict[str, str]: + """Generate scheme with appropriate tone values.""" + tones = DARK_TONES if is_dark else LIGHT_TONES + + scheme = { + # Primary colors + 'primary': self.primary_palette.get_hex(tones['primary']), + 'on_primary': self.primary_palette.get_hex(tones['on_primary']), + 'primary_container': self.primary_palette.get_hex(tones['primary_container']), + 'on_primary_container': self.primary_palette.get_hex(tones['on_primary_container']), + + # Surface tint (same as primary, used for M3 elevation tinting) + 'surface_tint': self.primary_palette.get_hex(tones['primary']), + + # Secondary colors + 'secondary': self.secondary_palette.get_hex(tones['secondary']), + 'on_secondary': self.secondary_palette.get_hex(tones['on_secondary']), + 'secondary_container': self.secondary_palette.get_hex(tones['secondary_container']), + 'on_secondary_container': self.secondary_palette.get_hex(tones['on_secondary_container']), + + # Tertiary colors + 'tertiary': self.tertiary_palette.get_hex(tones['tertiary']), + 'on_tertiary': self.tertiary_palette.get_hex(tones['on_tertiary']), + 'tertiary_container': self.tertiary_palette.get_hex(tones['tertiary_container']), + 'on_tertiary_container': self.tertiary_palette.get_hex(tones['on_tertiary_container']), + + # Error colors + 'error': self.error_palette.get_hex(tones['error']), + 'on_error': self.error_palette.get_hex(tones['on_error']), + 'error_container': self.error_palette.get_hex(tones['error_container']), + 'on_error_container': self.error_palette.get_hex(tones['on_error_container']), + + # Surface colors + 'surface': self.neutral_palette.get_hex(tones['surface']), + 'on_surface': self.neutral_palette.get_hex(tones['on_surface']), + 'surface_variant': self.neutral_variant_palette.get_hex(tones['surface_variant']), + 'on_surface_variant': self.neutral_variant_palette.get_hex(tones['on_surface_variant']), + + # Surface containers + 'surface_container_lowest': self.neutral_palette.get_hex(tones['surface_container_lowest']), + 'surface_container_low': self.neutral_palette.get_hex(tones['surface_container_low']), + 'surface_container': self.neutral_palette.get_hex(tones['surface_container']), + 'surface_container_high': self.neutral_palette.get_hex(tones['surface_container_high']), + 'surface_container_highest': self.neutral_palette.get_hex(tones['surface_container_highest']), + + # Outline and other + 'outline': self.neutral_variant_palette.get_hex(tones['outline']), + 'outline_variant': self.neutral_variant_palette.get_hex(tones['outline_variant']), + 'shadow': self.neutral_palette.get_hex(tones['shadow']), + 'scrim': self.neutral_palette.get_hex(tones['scrim']), + + # Inverse colors + 'inverse_surface': self.neutral_palette.get_hex(tones['inverse_surface']), + 'inverse_on_surface': self.neutral_palette.get_hex(tones['inverse_on_surface']), + 'inverse_primary': self.primary_palette.get_hex(tones['inverse_primary']), + + # Background (alias for surface) + 'background': self.neutral_palette.get_hex(tones['surface']), + 'on_background': self.neutral_palette.get_hex(tones['on_surface']), + + # Surface dim and bright + 'surface_dim': self.neutral_palette.get_hex(87 if not is_dark else 6), + 'surface_bright': self.neutral_palette.get_hex(98 if not is_dark else 24), + + # Fixed colors - consistent across light/dark modes (MD3 spec) + 'primary_fixed': self.primary_palette.get_hex(90), + 'primary_fixed_dim': self.primary_palette.get_hex(80), + 'on_primary_fixed': self.primary_palette.get_hex(10), + 'on_primary_fixed_variant': self.primary_palette.get_hex(30), + + 'secondary_fixed': self.secondary_palette.get_hex(90), + 'secondary_fixed_dim': self.secondary_palette.get_hex(80), + 'on_secondary_fixed': self.secondary_palette.get_hex(10), + 'on_secondary_fixed_variant': self.secondary_palette.get_hex(30), + + 'tertiary_fixed': self.tertiary_palette.get_hex(90), + 'tertiary_fixed_dim': self.tertiary_palette.get_hex(80), + 'on_tertiary_fixed': self.tertiary_palette.get_hex(10), + 'on_tertiary_fixed_variant': self.tertiary_palette.get_hex(30), + } + + return scheme + + +# ============================================================================= +# Scheme Implementations +# ============================================================================= + +class SchemeTonalSpot(_BaseScheme): + """ + Tonal Spot scheme - the default Android 12-13 Material You scheme. + + Uses fixed chroma values for consistent, harmonious palettes: + - Primary: source hue, chroma 48 + - Secondary: source hue, chroma 16 + - Tertiary: hue +60ยฐ, chroma 24 + - Neutrals: low chroma (tinted with source hue) + """ + + def __init__(self, source_color: Hct): + super().__init__(source_color) + + # Primary: source hue with fixed chroma 48 + self.primary_palette = TonalPalette(source_color.hue, 48.0) + + # Secondary: source hue with lower chroma 16 + self.secondary_palette = TonalPalette(source_color.hue, 16.0) + + # Tertiary: 60ยฐ hue rotation with chroma 24 + tertiary_hue = (source_color.hue + 60.0) % 360.0 + self.tertiary_palette = TonalPalette(tertiary_hue, 24.0) + + # Neutral: source hue with very low chroma (tinted grays) + self.neutral_palette = TonalPalette(source_color.hue, 4.0) + + # Neutral variant: slightly more chroma for contrast + self.neutral_variant_palette = TonalPalette(source_color.hue, 8.0) + + +class SchemeFruitSalad(_BaseScheme): + """ + Fruit Salad scheme - bold, playful theme with hue rotation. + + Designed for expressive, colorful themes: + - Primary: hue -50ยฐ, chroma 48 + - Secondary: hue -50ยฐ, chroma 36 + - Tertiary: source hue (original), chroma 36 + - Neutrals: tinted (chroma 10-16) + """ + + def __init__(self, source_color: Hct): + super().__init__(source_color) + + # Rotate hue by -50ยฐ for primary and secondary + rotated_hue = (source_color.hue - 50.0) % 360.0 + + # Primary: rotated hue with chroma 48 + self.primary_palette = TonalPalette(rotated_hue, 48.0) + + # Secondary: rotated hue with chroma 36 + self.secondary_palette = TonalPalette(rotated_hue, 36.0) + + # Tertiary: original source hue with chroma 36 + self.tertiary_palette = TonalPalette(source_color.hue, 36.0) + + # Neutral: source hue with higher chroma (tinted) + self.neutral_palette = TonalPalette(source_color.hue, 10.0) + + # Neutral variant: even more tinted + self.neutral_variant_palette = TonalPalette(source_color.hue, 16.0) + + +class SchemeRainbow(_BaseScheme): + """ + Rainbow scheme - chromatic accents with grayscale neutrals. + + Same structure as Tonal Spot but with pure grayscale neutrals: + - Primary: source hue, chroma 48 + - Secondary: source hue, chroma 16 + - Tertiary: hue +60ยฐ, chroma 24 + - Neutrals: pure grayscale (chroma 0) + """ + + def __init__(self, source_color: Hct): + super().__init__(source_color) + + # Primary: source hue with fixed chroma 48 + self.primary_palette = TonalPalette(source_color.hue, 48.0) + + # Secondary: source hue with lower chroma 16 + self.secondary_palette = TonalPalette(source_color.hue, 16.0) + + # Tertiary: 60ยฐ hue rotation with chroma 24 + tertiary_hue = (source_color.hue + 60.0) % 360.0 + self.tertiary_palette = TonalPalette(tertiary_hue, 24.0) + + # Neutral: pure grayscale (chroma 0) + self.neutral_palette = TonalPalette(0.0, 0.0) + + # Neutral variant: also grayscale + self.neutral_variant_palette = TonalPalette(0.0, 0.0) + + +class SchemeContent(_BaseScheme): + """ + Content scheme - preserves source color's chroma. + + This is the Material Design 3 "content" scheme that preserves the source + color's characteristics while creating harmonious palettes: + - Primary: source hue and chroma (full preservation) + - Secondary: same hue, reduced chroma: max(chroma - 32, chroma * 0.5) + - Tertiary: analogous color from temperature analysis (warm-cool harmony) + - Neutrals: low chroma (chroma / 8, tinted with source hue) + """ + + def __init__(self, source_color: Hct): + super().__init__(source_color) + + # Primary: preserve source color's hue and chroma (full preservation) + self.primary_palette = TonalPalette(source_color.hue, source_color.chroma) + + # Secondary: same hue, reduced chroma + # Formula from matugen: max(chroma - 32, chroma * 0.5) + secondary_chroma = max(source_color.chroma - 32.0, source_color.chroma * 0.5) + self.secondary_palette = TonalPalette(source_color.hue, secondary_chroma) + + # Tertiary: use analogous color from temperature analysis + # Get 3 analogous colors with 6 divisions, pick the last one (most different) + temp_cache = TemperatureCache(source_color) + analogous_colors = temp_cache.analogous(3, 6) + tertiary_hct = fix_if_disliked(analogous_colors[-1]) + self.tertiary_palette = TonalPalette.from_hct(tertiary_hct) + + # Neutral: source hue, low chroma (chroma / 8) + neutral_chroma = source_color.chroma / 8.0 + self.neutral_palette = TonalPalette(source_color.hue, neutral_chroma) + + # Neutral variant: slightly more chroma (chroma / 8 + 4) + neutral_variant_chroma = (source_color.chroma / 8.0) + 4.0 + self.neutral_variant_palette = TonalPalette(source_color.hue, neutral_variant_chroma) + + +class SchemeMonochrome(_BaseScheme): + """ + Material Design 3 Monochrome scheme. + + All color palettes use chroma=0.0, producing a pure grayscale theme. + Only the error color retains saturation for accessibility. + + Uses special tone mappings (different from other M3 schemes) for higher + contrast in grayscale - e.g., primary is tone 100 (white) in dark mode. + + Palette configuration: + - Primary: chroma 0.0 (grayscale) + - Secondary: chroma 0.0 (grayscale) + - Tertiary: chroma 0.0 (grayscale) + - Neutral: chroma 0.0 (grayscale) + - Neutral variant: chroma 0.0 (grayscale) + - Error: hue 25ยฐ, chroma 84 (vibrant red) + """ + + def __init__(self, source_color: Hct): + super().__init__(source_color) + + # All palettes use chroma=0 (grayscale) + # Source hue is preserved but irrelevant at chroma 0 + self.primary_palette = TonalPalette(source_color.hue, 0.0) + self.secondary_palette = TonalPalette(source_color.hue, 0.0) + self.tertiary_palette = TonalPalette(source_color.hue, 0.0) + self.neutral_palette = TonalPalette(source_color.hue, 0.0) + self.neutral_variant_palette = TonalPalette(source_color.hue, 0.0) + + # Error palette keeps vibrant red for accessibility + self.error_palette = TonalPalette(25.0, 84.0) + + def _generate_scheme(self, is_dark: bool) -> dict[str, str]: + """Generate scheme with monochrome-specific tone values.""" + # Monochrome uses different tones for higher contrast in grayscale + tones = MONOCHROME_DARK_TONES if is_dark else MONOCHROME_LIGHT_TONES + + scheme = { + # Primary colors + 'primary': self.primary_palette.get_hex(tones['primary']), + 'on_primary': self.primary_palette.get_hex(tones['on_primary']), + 'primary_container': self.primary_palette.get_hex(tones['primary_container']), + 'on_primary_container': self.primary_palette.get_hex(tones['on_primary_container']), + + # Surface tint (same as primary, used for M3 elevation tinting) + 'surface_tint': self.primary_palette.get_hex(tones['primary']), + + # Secondary colors + 'secondary': self.secondary_palette.get_hex(tones['secondary']), + 'on_secondary': self.secondary_palette.get_hex(tones['on_secondary']), + 'secondary_container': self.secondary_palette.get_hex(tones['secondary_container']), + 'on_secondary_container': self.secondary_palette.get_hex(tones['on_secondary_container']), + + # Tertiary colors + 'tertiary': self.tertiary_palette.get_hex(tones['tertiary']), + 'on_tertiary': self.tertiary_palette.get_hex(tones['on_tertiary']), + 'tertiary_container': self.tertiary_palette.get_hex(tones['tertiary_container']), + 'on_tertiary_container': self.tertiary_palette.get_hex(tones['on_tertiary_container']), + + # Error colors + 'error': self.error_palette.get_hex(tones['error']), + 'on_error': self.error_palette.get_hex(tones['on_error']), + 'error_container': self.error_palette.get_hex(tones['error_container']), + 'on_error_container': self.error_palette.get_hex(tones['on_error_container']), + + # Surface colors + 'surface': self.neutral_palette.get_hex(tones['surface']), + 'on_surface': self.neutral_palette.get_hex(tones['on_surface']), + 'surface_variant': self.neutral_variant_palette.get_hex(tones['surface_variant']), + 'on_surface_variant': self.neutral_variant_palette.get_hex(tones['on_surface_variant']), + + # Surface containers + 'surface_container_lowest': self.neutral_palette.get_hex(tones['surface_container_lowest']), + 'surface_container_low': self.neutral_palette.get_hex(tones['surface_container_low']), + 'surface_container': self.neutral_palette.get_hex(tones['surface_container']), + 'surface_container_high': self.neutral_palette.get_hex(tones['surface_container_high']), + 'surface_container_highest': self.neutral_palette.get_hex(tones['surface_container_highest']), + + # Outline and other + 'outline': self.neutral_variant_palette.get_hex(tones['outline']), + 'outline_variant': self.neutral_variant_palette.get_hex(tones['outline_variant']), + 'shadow': self.neutral_palette.get_hex(tones['shadow']), + 'scrim': self.neutral_palette.get_hex(tones['scrim']), + + # Inverse colors + 'inverse_surface': self.neutral_palette.get_hex(tones['inverse_surface']), + 'inverse_on_surface': self.neutral_palette.get_hex(tones['inverse_on_surface']), + 'inverse_primary': self.primary_palette.get_hex(tones['inverse_primary']), + + # Background (same as surface in MD3) + 'background': self.neutral_palette.get_hex(tones['surface']), + 'on_background': self.neutral_palette.get_hex(tones['on_surface']), + + # Surface dim and bright + 'surface_dim': self.neutral_palette.get_hex(tones['surface']), + 'surface_bright': self.neutral_palette.get_hex(tones['surface_container_highest'] + 5), + + # Fixed colors + 'primary_fixed': self.primary_palette.get_hex(90), + 'primary_fixed_dim': self.primary_palette.get_hex(80), + 'on_primary_fixed': self.primary_palette.get_hex(10), + 'on_primary_fixed_variant': self.primary_palette.get_hex(30), + 'secondary_fixed': self.secondary_palette.get_hex(90), + 'secondary_fixed_dim': self.secondary_palette.get_hex(80), + 'on_secondary_fixed': self.secondary_palette.get_hex(10), + 'on_secondary_fixed_variant': self.secondary_palette.get_hex(30), + 'tertiary_fixed': self.tertiary_palette.get_hex(90), + 'tertiary_fixed_dim': self.tertiary_palette.get_hex(80), + 'on_tertiary_fixed': self.tertiary_palette.get_hex(10), + 'on_tertiary_fixed_variant': self.tertiary_palette.get_hex(30), + } + + return scheme + + +# Backward compatibility alias +MaterialScheme = SchemeContent + + +# ============================================================================= +# Helper Functions +# ============================================================================= + +def harmonize_color(design_color: Hct, source_color: Hct, amount: float = 0.5) -> Hct: + """ + Shift a design color's hue towards a source color's hue. + + Used to make custom colors feel more cohesive with the theme. + + Args: + design_color: The color to adjust + source_color: The reference color to harmonize towards + amount: How much to shift (0-1, default 0.5) + + Returns: + Harmonized HCT color + """ + diff = _hue_difference(source_color.hue, design_color.hue) + rotation = min(diff * amount, 15.0) # Max 15ยฐ rotation + if _shorter_rotation(source_color.hue, design_color.hue) < 0: + rotation = -rotation + new_hue = (design_color.hue + rotation) % 360.0 + return Hct(new_hue, design_color.chroma, design_color.tone) + + +def _hue_difference(hue1: float, hue2: float) -> float: + """Calculate the absolute difference between two hues.""" + diff = abs(hue1 - hue2) + return min(diff, 360.0 - diff) + + +def _shorter_rotation(from_hue: float, to_hue: float) -> float: + """Calculate the shorter rotation direction between hues.""" + diff = to_hue - from_hue + if diff > 180.0: + return diff - 360.0 + elif diff < -180.0: + return diff + 360.0 + return diff diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/palette.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/palette.py new file mode 100644 index 0000000..2d8d507 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/palette.py @@ -0,0 +1,672 @@ +""" +Palette extraction using K-means clustering. + +This module provides functions for extracting dominant colors from images +using perceptual color distance calculations and k-means clustering. +""" + +import math + +from .color import Color, rgb_to_hsl, hsl_to_rgb, hue_distance, rgb_to_lab, lab_to_rgb, lab_distance +from .hct import Cam16, Hct + +# Type aliases +RGB = tuple[int, int, int] +HSL = tuple[float, float, float] +LAB = tuple[float, float, float] + + +def downsample_pixels(pixels: list[RGB], factor: int = 4) -> list[RGB]: + """ + Downsample pixels for faster processing. + + Takes every Nth pixel to reduce dataset size while maintaining + color distribution characteristics. + """ + if factor <= 1: + return pixels + + # Calculate step based on factor squared (for 2D image) + step = factor * factor + return pixels[::step] + + +def kmeans_cluster( + colors: list[RGB], + k: int = 5, + iterations: int = 10 +) -> list[tuple[RGB, RGB, int]]: + """ + Perform K-means clustering on colors in Lab color space. + + Lab space is perceptually uniform, matching matugen's approach. + Returns list of (centroid_rgb, representative_rgb, cluster_size) tuples, + sorted by cluster size. + + - centroid_rgb: averaged color from the cluster (smoother, blended) + - representative_rgb: actual image pixel closest to centroid + """ + if len(colors) < k: + # Not enough colors, return what we have (same color for centroid and representative) + unique = list(set(colors)) + return [(c, c, colors.count(c)) for c in unique[:k]] + + # Convert to Lab for perceptual clustering (like matugen's WSMeans) + colors_lab = [rgb_to_lab(*c) for c in colors] + + # Deterministic initialization: pick evenly spaced colors from sorted list + # Sort by L (lightness) first for better spread + sorted_indices = sorted(range(len(colors_lab)), key=lambda i: colors_lab[i][0]) + step = len(sorted_indices) // k + centroids = [colors_lab[sorted_indices[i * step]] for i in range(k)] + + # K-means iterations + assignments = [0] * len(colors_lab) + for _ in range(iterations): + # Assign colors to nearest centroid + for idx, color in enumerate(colors_lab): + min_dist = float('inf') + min_cluster = 0 + for i, centroid in enumerate(centroids): + dist = lab_distance(color, centroid) + if dist < min_dist: + min_dist = dist + min_cluster = i + assignments[idx] = min_cluster + + # Update centroids (simple mean in Lab space) + new_centroids = [] + for i in range(k): + cluster_colors = [colors_lab[j] for j in range(len(colors_lab)) if assignments[j] == i] + if cluster_colors: + avg_L = sum(c[0] for c in cluster_colors) / len(cluster_colors) + avg_a = sum(c[1] for c in cluster_colors) / len(cluster_colors) + avg_b = sum(c[2] for c in cluster_colors) / len(cluster_colors) + new_centroids.append((avg_L, avg_a, avg_b)) + else: + new_centroids.append(centroids[i]) + + centroids = new_centroids + + # Final assignment and count, also find representative pixel (closest to centroid) + cluster_counts = [0] * k + cluster_representatives: list[tuple[RGB, float]] = [(colors[0], float('inf'))] * k + + for idx, color_lab in enumerate(colors_lab): + cluster_idx = assignments[idx] + cluster_counts[cluster_idx] += 1 + + # Track the pixel closest to the centroid as the representative + dist = lab_distance(color_lab, centroids[cluster_idx]) + if dist < cluster_representatives[cluster_idx][1]: + cluster_representatives[cluster_idx] = (colors[idx], dist) + + # Return both centroid (averaged) and representative (actual pixel) colors + results = [] + for i in range(k): + if cluster_counts[i] > 0: + # Convert Lab centroid back to RGB + centroid_rgb = lab_to_rgb(*centroids[i]) + representative_rgb = cluster_representatives[i][0] + results.append((centroid_rgb, representative_rgb, cluster_counts[i])) + + # Sort by cluster size (most common first) + results.sort(key=lambda x: -x[2]) + + return results + + +def _score_colors_chroma( + colors_with_counts: list[tuple[RGB, int]], +) -> list[tuple[Color, float]]: + """ + Score colors prioritizing chroma (vibrancy) over area coverage. + + Uses count^0.3 weighting so saturated colors win even with small area. + Used for "vibrant" mode to find the most eye-catching colors. + + Args: + colors_with_counts: List of (RGB, count) tuples from clustering + + Returns: + List of (Color, score) tuples, sorted by score descending + """ + result_colors = [] + for rgb, count in colors_with_counts: + color = Color.from_rgb(rgb) + try: + hct = color.to_hct() + + # Chroma contribution - prefer colorful colors + chroma_score = hct.chroma + + # Tone penalty - prefer mid-tones (40-60 is ideal) + if hct.tone < 20: + tone_penalty = (20 - hct.tone) * 2 + elif hct.tone > 80: + tone_penalty = (hct.tone - 80) * 1.5 + elif hct.tone < 40: + tone_penalty = (40 - hct.tone) * 0.5 + elif hct.tone > 60: + tone_penalty = (hct.tone - 60) * 0.3 + else: + tone_penalty = 0 + + # Hue penalty - slight penalty for yellow-green hues + if 80 < hct.hue < 110: + hue_penalty = 5 + else: + hue_penalty = 0 + + # Combined score: chroma minus penalties, balanced with count + # Using count^0.3 so chroma dominates while still considering area + score = (chroma_score - tone_penalty - hue_penalty) * (count ** 0.3) + result_colors.append((color, score)) + except (ValueError, ZeroDivisionError): + result_colors.append((color, 0.0)) + + result_colors.sort(key=lambda x: -x[1]) + return result_colors + + +def _hue_to_family(hue: float) -> int: + """ + Map hue to perceptual color family. + + Uses non-uniform ranges that match human color perception: + - 0: RED (330-30ยฐ, wraps around) + - 1: ORANGE (30-60ยฐ) + - 2: YELLOW (60-105ยฐ) + - 3: GREEN (105-190ยฐ, includes green-leaning teal) + - 4: BLUE (190-270ยฐ, includes cyan) + - 5: PURPLE (270-330ยฐ) + """ + if hue >= 330 or hue < 30: + return 0 # RED + elif hue < 60: + return 1 # ORANGE + elif hue < 105: + return 2 # YELLOW + elif hue < 190: + return 3 # GREEN (includes green-leaning teal) + elif hue < 270: + return 4 # BLUE (includes cyan) + else: + return 5 # PURPLE + + +def _score_colors_count( + colors_with_counts: list[tuple[RGB, int]], +) -> list[tuple[Color, float]]: + """ + Score colors prioritizing pixel count (area coverage) by hue family. + + Groups colors into perceptual hue families, sums counts per family, + then picks the dominant family. This is more faithful to human perception + where we see "green" as a category, not individual shades. + + Args: + colors_with_counts: List of (RGB, count) tuples from clustering + + Returns: + List of (Color, score) tuples, sorted by family dominance then count + """ + MIN_CHROMA = 10.0 # Filter out near-gray colors + + # First pass: collect colorful colors and group by hue family + hue_families: dict[int, list[tuple[Color, float, float, int]]] = {} # family -> [(color, hue, chroma, count), ...] + + for rgb, count in colors_with_counts: + color = Color.from_rgb(rgb) + try: + hct = color.to_hct() + if hct.chroma >= MIN_CHROMA: + family = _hue_to_family(hct.hue) + if family not in hue_families: + hue_families[family] = [] + hue_families[family].append((color, hct.hue, hct.chroma, count)) + except (ValueError, ZeroDivisionError): + pass + + # If no colorful colors found, fall back to all colors + if not hue_families: + result = [] + for rgb, count in colors_with_counts: + color = Color.from_rgb(rgb) + result.append((color, float(count))) + result.sort(key=lambda x: -x[1]) + return result + + # Calculate total count per hue family + family_totals: list[tuple[int, int]] = [] + for family, colors in hue_families.items(): + total = sum(c[3] for c in colors) + family_totals.append((family, total)) + + # Sort families by total count (dominant family first) + family_totals.sort(key=lambda x: -x[1]) + + # Build result: colors from dominant families first, sorted by count within each family + result_colors = [] + for family, _ in family_totals: + family_colors = hue_families[family] + # Sort by count descending, chroma as tiebreaker + family_colors.sort(key=lambda x: (-x[3], -x[2])) + for color, hue, chroma, count in family_colors: + # Score encodes family rank + count for proper ordering + family_rank = next(i for i, (f, _) in enumerate(family_totals) if f == family) + score = (len(family_totals) - family_rank) * 1000000 + count * 1000 + chroma + result_colors.append((color, score)) + + result_colors.sort(key=lambda x: -x[1]) + return result_colors + + +def _family_center_hue(family: int) -> float: + """Get the center hue for a family index.""" + # Family centers based on _hue_to_family ranges: + # 0: RED (330-30ยฐ, wraps) -> center 0ยฐ + # 1: ORANGE (30-60ยฐ) -> center 45ยฐ + # 2: YELLOW (60-105ยฐ) -> center 82.5ยฐ + # 3: GREEN (105-190ยฐ) -> center 147.5ยฐ + # 4: BLUE (190-270ยฐ) -> center 230ยฐ + # 5: PURPLE (270-330ยฐ) -> center 300ยฐ + centers = [0.0, 45.0, 82.5, 147.5, 230.0, 300.0] + return centers[family] + + +def _circular_hue_diff(h1: float, h2: float) -> float: + """Calculate circular hue difference (0-180).""" + diff = abs(h1 - h2) + return min(diff, 360.0 - diff) + + +def _score_colors_dysfunctional( + colors_with_counts: list[tuple[RGB, int]], +) -> list[tuple[Color, float]]: + """ + Score colors prioritizing the 2nd most dominant hue family. + + Like count scoring but skips the dominant family (and any families + too close to it) to pick a visually distinct secondary color. + + Args: + colors_with_counts: List of (RGB, count) tuples from clustering + + Returns: + List of (Color, score) tuples, sorted by family dominance then count + """ + MIN_CHROMA = 10.0 # Filter out near-gray colors + MIN_HUE_DISTANCE = 45.0 # Minimum hue distance from dominant family + MIN_COUNT_RATIO = 0.02 # Distant family must have at least 2% of total colorful pixels + + # First pass: collect colorful colors and group by hue family + hue_families: dict[int, list[tuple[Color, float, float, int]]] = {} # family -> [(color, hue, chroma, count), ...] + + for rgb, count in colors_with_counts: + color = Color.from_rgb(rgb) + try: + hct = color.to_hct() + if hct.chroma >= MIN_CHROMA: + family = _hue_to_family(hct.hue) + if family not in hue_families: + hue_families[family] = [] + hue_families[family].append((color, hct.hue, hct.chroma, count)) + except (ValueError, ZeroDivisionError): + pass + + # If no colorful colors found, fall back to all colors + if not hue_families: + result = [] + for rgb, count in colors_with_counts: + color = Color.from_rgb(rgb) + result.append((color, float(count))) + result.sort(key=lambda x: -x[1]) + return result + + # Calculate total count per hue family + family_totals: list[tuple[int, int]] = [] + for family, colors in hue_families.items(): + total = sum(c[3] for c in colors) + family_totals.append((family, total)) + + # Sort families by total count (dominant family first) + family_totals.sort(key=lambda x: -x[1]) + + # Find the dominant family and its center hue + dominant_family, dominant_count = family_totals[0] + dominant_center = _family_center_hue(dominant_family) + total_colorful_pixels = sum(count for _, count in family_totals) + min_count = total_colorful_pixels * MIN_COUNT_RATIO + + # Find families that are far enough from the dominant one AND have enough pixels + distant_families = [] + close_families = [dominant_family] + for family, count in family_totals[1:]: + family_center = _family_center_hue(family) + hue_diff = _circular_hue_diff(dominant_center, family_center) + if hue_diff >= MIN_HUE_DISTANCE and count >= min_count: + # Get max chroma in this family - we want families with vibrant colors + max_chroma = max(c[2] for c in hue_families[family]) + distant_families.append((family, count, hue_diff, max_chroma)) + else: + close_families.append(family) + + # Build result: colors from distant families first + result_colors = [] + + # Sort distant families by weighted score: hue_distance * max_chroma + # This balances visual distinctness (hue distance) with color quality (chroma) + # A family that's far away AND has good colors beats one that's close with great colors + distant_families.sort(key=lambda x: -(x[2] * x[3])) + + for family, _, _, _ in distant_families: + family_colors = hue_families[family] + # Sort by chroma descending - we want the most vibrant color from this family + # Count is tiebreaker to avoid picking tiny noise clusters + family_colors.sort(key=lambda x: (-x[2], -x[3])) + for color, hue, chroma, count in family_colors: + # Score encodes family rank + chroma for proper ordering + # Chroma is primary (we want vibrant), count is tiebreaker + family_rank = next(i for i, (f, _, _, _) in enumerate(distant_families) if f == family) + score = (len(distant_families) - family_rank) * 1000000 + chroma * 1000 + count + result_colors.append((color, score)) + + # Add colors from close families (including dominant) at lower priority + for family in close_families: + family_colors = hue_families[family] + family_colors.sort(key=lambda x: (-x[3], -x[2])) + for color, hue, chroma, count in family_colors: + # Lower score than all distant-family colors + score = count * 1000 + chroma + result_colors.append((color, score)) + + result_colors.sort(key=lambda x: -x[1]) + return result_colors + + +def _score_colors_muted( + colors_with_counts: list[tuple[RGB, int]], +) -> list[tuple[Color, float]]: + """ + Score colors for muted mode - pure pixel count without chroma filtering. + + Unlike count scoring which filters to chroma >= 10, this accepts all colors + including grayscale. Designed for monochrome/monotonal wallpapers where + the dominant color may have very low or zero saturation. + + Args: + colors_with_counts: List of (RGB, count) tuples from clustering + + Returns: + List of (Color, score) tuples, sorted by count descending + """ + result = [] + for rgb, count in colors_with_counts: + color = Color.from_rgb(rgb) + result.append((color, float(count))) + + result.sort(key=lambda x: -x[1]) + return result + + +def _score_colors_population( + colors_with_counts: list[tuple[RGB, int]], + total_pixels: int +) -> list[tuple[Color, float]]: + """ + Score colors using Material Design's Score algorithm. + + This matches matugen's scoring approach exactly: + - Build per-hue population histogram (360 buckets) + - Calculate "excited proportions" (ยฑ15ยฐ hue window sum) + - Score: proportion * 100 * 0.7 + (chroma - 48) * weight + - Filter by chroma >= 5 and proportion >= 1% + - Deduplicate by maximizing hue distance + + Args: + colors_with_counts: List of (RGB, count) tuples from clustering + total_pixels: Total number of pixels in the sample + + Returns: + List of (Color, score) tuples, sorted by score descending + """ + # Constants matching Material Score + TARGET_CHROMA = 48.0 + WEIGHT_PROPORTION = 0.7 + WEIGHT_CHROMA_ABOVE = 0.3 + WEIGHT_CHROMA_BELOW = 0.1 + CUTOFF_CHROMA = 5.0 + CUTOFF_EXCITED_PROPORTION = 0.01 + + # Build per-hue population histogram (360 buckets) + hue_population = [0] * 360 + population_sum = 0 + + colors_hct: list[tuple[Color, Hct, int]] = [] + for rgb, count in colors_with_counts: + try: + color = Color.from_rgb(rgb) + hct = color.to_hct() + hue_bucket = int(hct.hue) % 360 + hue_population[hue_bucket] += count + population_sum += count + colors_hct.append((color, hct, count)) + except (ValueError, ZeroDivisionError): + continue + + if not colors_hct or population_sum == 0: + # Fallback: return colors without scoring + result = [] + for rgb, count in colors_with_counts: + color = Color.from_rgb(rgb) + result.append((color, float(count))) + return sorted(result, key=lambda x: -x[1]) + + # Calculate "excited proportions" - sum of proportions in ยฑ15ยฐ hue window + hue_excited_proportions = [0.0] * 360 + for hue in range(360): + proportion = hue_population[hue] / population_sum + # Spread to neighboring hues (ยฑ15ยฐ, so 30ยฐ total window) + for offset in range(-14, 16): + neighbor_hue = (hue + offset) % 360 + hue_excited_proportions[neighbor_hue] += proportion + + # Score each color + scored_hcts: list[tuple[Color, Hct, float]] = [] + for color, hct, count in colors_hct: + hue_bucket = int(hct.hue) % 360 + proportion = hue_excited_proportions[hue_bucket] + + # Filter by chroma and proportion + if hct.chroma < CUTOFF_CHROMA: + continue + if proportion <= CUTOFF_EXCITED_PROPORTION: + continue + + # Proportion score (70% weight) + proportion_score = proportion * 100.0 * WEIGHT_PROPORTION + + # Chroma score: (chroma - target) * weight + # This gives bonus for high chroma, penalty for low chroma + if hct.chroma < TARGET_CHROMA: + chroma_weight = WEIGHT_CHROMA_BELOW + else: + chroma_weight = WEIGHT_CHROMA_ABOVE + chroma_score = (hct.chroma - TARGET_CHROMA) * chroma_weight + + score = proportion_score + chroma_score + scored_hcts.append((color, hct, score)) + + if not scored_hcts: + # Fallback if filtering removed everything + result = [] + for rgb, count in colors_with_counts: + color = Color.from_rgb(rgb) + result.append((color, float(count))) + return sorted(result, key=lambda x: -x[1]) + + # Sort by score descending + scored_hcts.sort(key=lambda x: -x[2]) + + # Deduplicate by hue distance - pick colors maximizing hue diversity + # Start at 90ยฐ minimum distance, decrease to 15ยฐ if needed + chosen_colors: list[tuple[Color, float]] = [] + + for min_hue_diff in range(90, 14, -1): + chosen_colors.clear() + for color, hct, score in scored_hcts: + # Check if this hue is far enough from all chosen colors + is_far_enough = True + for chosen_color, _ in chosen_colors: + chosen_hct = chosen_color.to_hct() + if hue_distance(hct.hue, chosen_hct.hue) < min_hue_diff: + is_far_enough = False + break + + if is_far_enough: + chosen_colors.append((color, score)) + + # Stop if we have enough colors (4 is Material default) + if len(chosen_colors) >= 4: + break + + # If we found enough colors, stop decreasing threshold + if len(chosen_colors) >= 4: + break + + # If deduplication yielded nothing, fall back to top scored + if not chosen_colors: + chosen_colors = [(c, s) for c, h, s in scored_hcts[:4]] + + return chosen_colors + + +def extract_palette( + pixels: list[RGB], + k: int = 5, + scoring: str = "population" +) -> list[Color]: + """ + Extract K dominant colors from pixel data. + + Args: + pixels: List of RGB tuples + k: Number of colors to extract + scoring: Scoring method: + - "population": matugen-like, representative colors (M3 schemes) + - "chroma": vibrant, chroma-prioritized with centroid averaging + - "count": area-dominant, picks by pixel count (faithful mode) + - "dysfunctional": picks 2nd most dominant color family + - "muted": like count but without chroma filtering (monochrome wallpapers) + + Returns: + List of Color objects, sorted by score + """ + # Downsample for performance + sampled = downsample_pixels(pixels, factor=4) + total_sampled = len(sampled) + + # For population scoring, we need many clusters then score/filter them + # For chroma scoring, fewer clusters work fine + if scoring == "population": + # Use more clusters for Material scoring (like matugen's 128-256) + cluster_count = min(128, max(k * 10, len(set(sampled)) // 10)) + # Don't pre-filter for population scoring - let the Score algorithm filter + # This matches matugen which quantizes all pixels, then filters in scoring + filtered = sampled + elif scoring == "count": + # Faithful mode: many clusters to capture color diversity, no pre-filtering + # Scoring will filter to colorful colors and pick by count + cluster_count = 48 + filtered = sampled + elif scoring == "dysfunctional": + # Dysfunctional mode: same as count but picks 2nd dominant family + cluster_count = 48 + filtered = sampled + elif scoring == "muted": + # Muted mode: similar to count but accepts low-chroma colors + # For monochrome/monotonal wallpapers + cluster_count = 24 + filtered = sampled + else: + # Vibrant mode: more clusters to capture high-chroma colors that might + # otherwise get averaged away, with colorfulness pre-filter + cluster_count = 20 + # Filter to colorful pixels for smoother averaged results + filtered = [] + for p in sampled: + try: + cam = Cam16.from_rgb(p[0], p[1], p[2]) + if cam.chroma >= 5.0: + filtered.append(p) + except (ValueError, ZeroDivisionError): + continue + + if len(filtered) < cluster_count * 2: + filtered = sampled + + # Cluster - returns (centroid_rgb, representative_rgb, count) tuples + clusters = kmeans_cluster(filtered, k=cluster_count) + + # Score colors based on method + # - chroma: centroid colors (averaged, smoother - vibrant mode) + # - count: representative pixels by area dominance (faithful mode) + # - muted: like count but accepts low/zero chroma (monochrome wallpapers) + # - population: representative colors with Material scoring (M3 schemes) + if scoring == "chroma": + # Use centroid colors for vibrant mode (smoother, blended) + colors_for_scoring = [(c[0], c[2]) for c in clusters] + scored = _score_colors_chroma(colors_for_scoring) + elif scoring == "count": + # Use representative colors with count scoring (faithful mode) + colors_for_scoring = [(c[1], c[2]) for c in clusters] + scored = _score_colors_count(colors_for_scoring) + elif scoring == "dysfunctional": + # Use representative colors with dysfunctional scoring (2nd dominant family) + colors_for_scoring = [(c[1], c[2]) for c in clusters] + scored = _score_colors_dysfunctional(colors_for_scoring) + elif scoring == "muted": + # Use representative colors with muted scoring (no chroma filter) + colors_for_scoring = [(c[1], c[2]) for c in clusters] + scored = _score_colors_muted(colors_for_scoring) + else: + # Use representative colors for M3 schemes + colors_for_scoring = [(c[1], c[2]) for c in clusters] + scored = _score_colors_population(colors_for_scoring, total_sampled) + + # Extract colors + final_colors = [c[0] for c in scored] + + # Ensure we have enough colors by deriving from primary using HCT + while len(final_colors) < k: + if not final_colors: + final_colors.append(Color.from_hex("#6750A4")) + continue + + primary = final_colors[0] + primary_hct = primary.to_hct() + offset = len(final_colors) * 60.0 + new_hct = Hct((primary_hct.hue + offset) % 360.0, primary_hct.chroma, primary_hct.tone) + final_colors.append(Color.from_hct(new_hct)) + + return final_colors[:k] + + +def find_error_color(palette: list[Color]) -> Color: + """ + Find or generate an error color (red-biased). + + Looks for existing red in palette, otherwise returns a default. + """ + # Look for a red-ish color in the palette + for color in palette: + h, s, l = color.to_hsl() + # Red hues: 0-30 or 330-360 + if (h <= 30 or h >= 330) and s > 0.4 and 0.3 < l < 0.7: + return color + + # Default error red + return Color.from_hex("#FD4663") + + diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/quantizer.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/quantizer.py new file mode 100644 index 0000000..2432ff2 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/quantizer.py @@ -0,0 +1,815 @@ +""" +Wu and WSMeans quantizer implementations matching material-color-utilities. + +Wu implements Xiaolin Wu's color quantization algorithm from Graphics Gems II (1991). +WSMeans refines Wu's output via weighted k-means in Lab space (QuantizerCelebi pipeline). +Together they match the QuantizerCelebi pipeline used by matugen/material-color-utilities. +""" + +from typing import Dict, List, Tuple + +from .color import rgb_to_lab, lab_to_rgb + +# Constants matching material-color-utilities +INDEX_BITS = 5 +SIDE_LENGTH = 33 # (1 << INDEX_BITS) + 1 +TOTAL_SIZE = 35937 # SIDE_LENGTH^3 + +# Direction constants +DIR_RED = 0 +DIR_GREEN = 1 +DIR_BLUE = 2 + + +class Box: + """Represents a box in RGB color space.""" + __slots__ = ('r0', 'r1', 'g0', 'g1', 'b0', 'b1', 'vol') + + def __init__(self): + self.r0 = 0 + self.r1 = 0 + self.g0 = 0 + self.g1 = 0 + self.b0 = 0 + self.b1 = 0 + self.vol = 0 + + +def _get_index(r: int, g: int, b: int) -> int: + """Calculate 3D array index from RGB coordinates.""" + return (r << (INDEX_BITS * 2)) + (r << (INDEX_BITS + 1)) + r + (g << INDEX_BITS) + g + b + + +def _argb_from_rgb(r: int, g: int, b: int) -> int: + """Convert RGB to ARGB integer format.""" + return (255 << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF) + + +def _rgb_from_argb(argb: int) -> Tuple[int, int, int]: + """Extract RGB from ARGB integer.""" + return ((argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF) + + +class QuantizerWu: + """ + Wu color quantizer implementation. + + Divides image pixels into clusters by recursively cutting an RGB cube, + based on the weight of pixels in each area of the cube. + """ + + def __init__(self): + self.weights: List[int] = [] + self.moments_r: List[int] = [] + self.moments_g: List[int] = [] + self.moments_b: List[int] = [] + self.moments: List[float] = [] + self.cubes: List[Box] = [] + + def quantize(self, pixels: List[int], max_colors: int) -> List[int]: + """ + Quantize pixels to a reduced color palette. + + Args: + pixels: List of colors in ARGB integer format + max_colors: Maximum number of colors to return + + Returns: + List of colors in ARGB format + """ + self._construct_histogram(pixels) + self._compute_moments() + result_count = self._create_boxes(max_colors) + return self._create_result(result_count) + + def _construct_histogram(self, pixels: List[int]): + """Build histogram of pixel colors.""" + self.weights = [0] * TOTAL_SIZE + self.moments_r = [0] * TOTAL_SIZE + self.moments_g = [0] * TOTAL_SIZE + self.moments_b = [0] * TOTAL_SIZE + self.moments = [0.0] * TOTAL_SIZE + + # Count pixels by color + count_by_color: Dict[int, int] = {} + for pixel in pixels: + # Only count fully opaque pixels + if (pixel >> 24) & 0xFF == 255: + count_by_color[pixel] = count_by_color.get(pixel, 0) + 1 + + bits_to_remove = 8 - INDEX_BITS + for pixel, count in count_by_color.items(): + red = (pixel >> 16) & 0xFF + green = (pixel >> 8) & 0xFF + blue = pixel & 0xFF + + i_r = (red >> bits_to_remove) + 1 + i_g = (green >> bits_to_remove) + 1 + i_b = (blue >> bits_to_remove) + 1 + index = _get_index(i_r, i_g, i_b) + + self.weights[index] += count + self.moments_r[index] += count * red + self.moments_g[index] += count * green + self.moments_b[index] += count * blue + self.moments[index] += count * (red * red + green * green + blue * blue) + + def _compute_moments(self): + """Compute cumulative moments for efficient volume calculations.""" + for r in range(1, SIDE_LENGTH): + area = [0] * SIDE_LENGTH + area_r = [0] * SIDE_LENGTH + area_g = [0] * SIDE_LENGTH + area_b = [0] * SIDE_LENGTH + area2 = [0.0] * SIDE_LENGTH + + for g in range(1, SIDE_LENGTH): + line = 0 + line_r = 0 + line_g = 0 + line_b = 0 + line2 = 0.0 + + for b in range(1, SIDE_LENGTH): + index = _get_index(r, g, b) + line += self.weights[index] + line_r += self.moments_r[index] + line_g += self.moments_g[index] + line_b += self.moments_b[index] + line2 += self.moments[index] + + area[b] += line + area_r[b] += line_r + area_g[b] += line_g + area_b[b] += line_b + area2[b] += line2 + + prev_index = _get_index(r - 1, g, b) + self.weights[index] = self.weights[prev_index] + area[b] + self.moments_r[index] = self.moments_r[prev_index] + area_r[b] + self.moments_g[index] = self.moments_g[prev_index] + area_g[b] + self.moments_b[index] = self.moments_b[prev_index] + area_b[b] + self.moments[index] = self.moments[prev_index] + area2[b] + + def _create_boxes(self, max_colors: int) -> int: + """Create color boxes by recursive cutting.""" + self.cubes = [Box() for _ in range(max_colors)] + volume_variance = [0.0] * max_colors + + # Initialize first box to cover entire color space + self.cubes[0].r1 = SIDE_LENGTH - 1 + self.cubes[0].g1 = SIDE_LENGTH - 1 + self.cubes[0].b1 = SIDE_LENGTH - 1 + + generated_color_count = max_colors + next_box = 0 + i = 1 + + while i < max_colors: + if self._cut(self.cubes[next_box], self.cubes[i]): + volume_variance[next_box] = ( + self._variance(self.cubes[next_box]) + if self.cubes[next_box].vol > 1 else 0.0 + ) + volume_variance[i] = ( + self._variance(self.cubes[i]) + if self.cubes[i].vol > 1 else 0.0 + ) + else: + volume_variance[next_box] = 0.0 + i -= 1 + + # Find box with maximum variance + next_box = 0 + temp = volume_variance[0] + for j in range(1, i + 1): + if volume_variance[j] > temp: + temp = volume_variance[j] + next_box = j + + if temp <= 0.0: + generated_color_count = i + 1 + break + + i += 1 + + return generated_color_count + + def _create_result(self, color_count: int) -> List[int]: + """Extract final colors from boxes.""" + colors = [] + for i in range(color_count): + cube = self.cubes[i] + weight = self._volume(cube, self.weights) + if weight > 0: + r = int(self._volume(cube, self.moments_r) / weight) + g = int(self._volume(cube, self.moments_g) / weight) + b = int(self._volume(cube, self.moments_b) / weight) + color = _argb_from_rgb(r, g, b) + colors.append(color) + return colors + + def _variance(self, cube: Box) -> float: + """Calculate variance within a box.""" + dr = self._volume(cube, self.moments_r) + dg = self._volume(cube, self.moments_g) + db = self._volume(cube, self.moments_b) + + xx = ( + self.moments[_get_index(cube.r1, cube.g1, cube.b1)] + - self.moments[_get_index(cube.r1, cube.g1, cube.b0)] + - self.moments[_get_index(cube.r1, cube.g0, cube.b1)] + + self.moments[_get_index(cube.r1, cube.g0, cube.b0)] + - self.moments[_get_index(cube.r0, cube.g1, cube.b1)] + + self.moments[_get_index(cube.r0, cube.g1, cube.b0)] + + self.moments[_get_index(cube.r0, cube.g0, cube.b1)] + - self.moments[_get_index(cube.r0, cube.g0, cube.b0)] + ) + + hypotenuse = dr * dr + dg * dg + db * db + volume = self._volume(cube, self.weights) + if volume == 0: + return 0.0 + return xx - hypotenuse / volume + + def _cut(self, one: Box, two: Box) -> bool: + """Cut a box into two boxes along the optimal axis.""" + whole_r = self._volume(one, self.moments_r) + whole_g = self._volume(one, self.moments_g) + whole_b = self._volume(one, self.moments_b) + whole_w = self._volume(one, self.weights) + + max_r_cut, max_r = self._maximize( + one, DIR_RED, one.r0 + 1, one.r1, whole_r, whole_g, whole_b, whole_w + ) + max_g_cut, max_g = self._maximize( + one, DIR_GREEN, one.g0 + 1, one.g1, whole_r, whole_g, whole_b, whole_w + ) + max_b_cut, max_b = self._maximize( + one, DIR_BLUE, one.b0 + 1, one.b1, whole_r, whole_g, whole_b, whole_w + ) + + if max_r >= max_g and max_r >= max_b: + if max_r_cut < 0: + return False + direction = DIR_RED + cut_location = max_r_cut + elif max_g >= max_r and max_g >= max_b: + direction = DIR_GREEN + cut_location = max_g_cut + else: + direction = DIR_BLUE + cut_location = max_b_cut + + two.r1 = one.r1 + two.g1 = one.g1 + two.b1 = one.b1 + + if direction == DIR_RED: + one.r1 = cut_location + two.r0 = one.r1 + two.g0 = one.g0 + two.b0 = one.b0 + elif direction == DIR_GREEN: + one.g1 = cut_location + two.r0 = one.r0 + two.g0 = one.g1 + two.b0 = one.b0 + else: # DIR_BLUE + one.b1 = cut_location + two.r0 = one.r0 + two.g0 = one.g0 + two.b0 = one.b1 + + one.vol = (one.r1 - one.r0) * (one.g1 - one.g0) * (one.b1 - one.b0) + two.vol = (two.r1 - two.r0) * (two.g1 - two.g0) * (two.b1 - two.b0) + return True + + def _maximize( + self, + cube: Box, + direction: int, + first: int, + last: int, + whole_r: int, + whole_g: int, + whole_b: int, + whole_w: int, + ) -> Tuple[int, float]: + """Find the optimal cut position along an axis.""" + bottom_r = self._bottom(cube, direction, self.moments_r) + bottom_g = self._bottom(cube, direction, self.moments_g) + bottom_b = self._bottom(cube, direction, self.moments_b) + bottom_w = self._bottom(cube, direction, self.weights) + + max_val = 0.0 + cut = -1 + + for i in range(first, last): + half_r = bottom_r + self._top(cube, direction, i, self.moments_r) + half_g = bottom_g + self._top(cube, direction, i, self.moments_g) + half_b = bottom_b + self._top(cube, direction, i, self.moments_b) + half_w = bottom_w + self._top(cube, direction, i, self.weights) + + if half_w == 0: + continue + + temp = (half_r * half_r + half_g * half_g + half_b * half_b) / half_w + + half_r = whole_r - half_r + half_g = whole_g - half_g + half_b = whole_b - half_b + half_w = whole_w - half_w + + if half_w == 0: + continue + + temp += (half_r * half_r + half_g * half_g + half_b * half_b) / half_w + + if temp > max_val: + max_val = temp + cut = i + + return cut, max_val + + def _volume(self, cube: Box, moment: List) -> int: + """Calculate volume sum using inclusion-exclusion.""" + return ( + moment[_get_index(cube.r1, cube.g1, cube.b1)] + - moment[_get_index(cube.r1, cube.g1, cube.b0)] + - moment[_get_index(cube.r1, cube.g0, cube.b1)] + + moment[_get_index(cube.r1, cube.g0, cube.b0)] + - moment[_get_index(cube.r0, cube.g1, cube.b1)] + + moment[_get_index(cube.r0, cube.g1, cube.b0)] + + moment[_get_index(cube.r0, cube.g0, cube.b1)] + - moment[_get_index(cube.r0, cube.g0, cube.b0)] + ) + + def _bottom(self, cube: Box, direction: int, moment: List) -> int: + """Calculate bottom sum for maximize.""" + if direction == DIR_RED: + return ( + -moment[_get_index(cube.r0, cube.g1, cube.b1)] + + moment[_get_index(cube.r0, cube.g1, cube.b0)] + + moment[_get_index(cube.r0, cube.g0, cube.b1)] + - moment[_get_index(cube.r0, cube.g0, cube.b0)] + ) + elif direction == DIR_GREEN: + return ( + -moment[_get_index(cube.r1, cube.g0, cube.b1)] + + moment[_get_index(cube.r1, cube.g0, cube.b0)] + + moment[_get_index(cube.r0, cube.g0, cube.b1)] + - moment[_get_index(cube.r0, cube.g0, cube.b0)] + ) + else: # DIR_BLUE + return ( + -moment[_get_index(cube.r1, cube.g1, cube.b0)] + + moment[_get_index(cube.r1, cube.g0, cube.b0)] + + moment[_get_index(cube.r0, cube.g1, cube.b0)] + - moment[_get_index(cube.r0, cube.g0, cube.b0)] + ) + + def _top(self, cube: Box, direction: int, position: int, moment: List) -> int: + """Calculate top sum for maximize.""" + if direction == DIR_RED: + return ( + moment[_get_index(position, cube.g1, cube.b1)] + - moment[_get_index(position, cube.g1, cube.b0)] + - moment[_get_index(position, cube.g0, cube.b1)] + + moment[_get_index(position, cube.g0, cube.b0)] + ) + elif direction == DIR_GREEN: + return ( + moment[_get_index(cube.r1, position, cube.b1)] + - moment[_get_index(cube.r1, position, cube.b0)] + - moment[_get_index(cube.r0, position, cube.b1)] + + moment[_get_index(cube.r0, position, cube.b0)] + ) + else: # DIR_BLUE + return ( + moment[_get_index(cube.r1, cube.g1, position)] + - moment[_get_index(cube.r1, cube.g0, position)] + - moment[_get_index(cube.r0, cube.g1, position)] + + moment[_get_index(cube.r0, cube.g0, position)] + ) + + +def quantize_wu(pixels: List[Tuple[int, int, int]], max_colors: int = 128) -> Dict[int, int]: + """ + Quantize RGB pixels using Wu algorithm. + + Args: + pixels: List of (R, G, B) tuples + max_colors: Maximum colors to extract + + Returns: + Dictionary mapping ARGB colors to pixel counts + """ + # Convert RGB tuples to ARGB integers + argb_pixels = [_argb_from_rgb(r, g, b) for r, g, b in pixels] + + # Run Wu quantizer + quantizer = QuantizerWu() + result_colors = quantizer.quantize(argb_pixels, max_colors) + + # Build color to count mapping in box order (matching Rust's IndexMap insertion order) + # Wu returns colors with count 0; WSMeans uses only the keys as starting clusters + color_to_count: Dict[int, int] = {c: 0 for c in result_colors} + + return color_to_count + + +# ============================================================================= +# WSMeans Quantizer - weighted k-means refinement in Lab space +# ============================================================================= + +# Mask for 48-bit LCG state +_LCG_MASK = (1 << 48) - 1 + + +class _Random: + """LCG matching Java's java.util.Random / material-color-utilities.""" + + def __init__(self, seed: int): + self._seed = (seed ^ 0x5DEECE66D) & _LCG_MASK + + def _next(self, bits: int) -> int: + self._seed = (self._seed * 0x5DEECE66D + 0xB) & _LCG_MASK + # Unsigned right shift: treat as unsigned 48-bit, shift, return as signed 32-bit + val = self._seed >> (48 - bits) + # Convert to signed 32-bit int to match Java behavior + if val >= (1 << 31): + val -= (1 << 32) + return val + + def next_range(self, range_val: int) -> int: + if (range_val & -range_val) == range_val: + # Power of 2 + return (range_val * self._next(31)) >> 31 + while True: + bits = self._next(31) + val = bits % range_val + if bits - val + (range_val - 1) >= 0: + return val + + +def _lab_distance_squared(a: Tuple[float, float, float], b: Tuple[float, float, float]) -> float: + """Squared Euclidean distance in Lab space (no sqrt).""" + dL = a[0] - b[0] + da = a[1] - b[1] + db = a[2] - b[2] + return dL * dL + da * da + db * db + + +def quantize_wsmeans( + pixels: List[Tuple[int, int, int]], + max_colors: int, + starting_clusters: List[int], +) -> Dict[int, int]: + """ + Refine quantized colors via weighted k-means in Lab space. + + Port of QuantizerWsmeans from material-colors-0.4.2 Rust crate. + + Args: + pixels: List of (R, G, B) tuples (original image pixels) + max_colors: Maximum number of colors + starting_clusters: List of ARGB colors from Wu quantizer + + Returns: + Dictionary mapping ARGB colors to pixel counts + """ + # Deduplicate pixels, build count map and Lab points + pixel_to_count: Dict[int, int] = {} + unique_pixels: List[int] = [] # ARGB values in insertion order + points: List[Tuple[float, float, float]] = [] # Lab coordinates + + for r, g, b in pixels: + argb = _argb_from_rgb(r, g, b) + if argb in pixel_to_count: + pixel_to_count[argb] += 1 + else: + unique_pixels.append(argb) + points.append(rgb_to_lab(r, g, b)) + pixel_to_count[argb] = 1 + + cluster_count = min(max_colors, len(points)) + if cluster_count == 0: + return {} + + # Convert starting clusters from ARGB to Lab + clusters: List[Tuple[float, float, float]] = [] + for argb in starting_clusters: + cr, cg, cb = _rgb_from_argb(argb) + clusters.append(rgb_to_lab(cr, cg, cb)) + + # Fill remaining clusters with actual image pixels using seeded LCG + additional_needed = cluster_count - len(clusters) + if additional_needed > 0: + rng = _Random(0x42688) + indices: List[int] = [] + for _ in range(additional_needed): + index = rng.next_range(len(points)) + while index in indices: + index = rng.next_range(len(points)) + indices.append(index) + for index in indices: + clusters.append(points[index]) + + # Initialize assignments + cluster_indices = [i % cluster_count for i in range(len(points))] + + # Distance matrix and sorted index matrix + distance_to_index_matrix: List[List[List]] = [ + [[0.0, j] for j in range(cluster_count)] + for _ in range(cluster_count) + ] + pixel_count_sums = [0] * cluster_count + + for iteration in range(10): + points_moved = 0 + + # Compute inter-cluster distance matrix + for i in range(cluster_count): + for j in range(i + 1, cluster_count): + dist = _lab_distance_squared(clusters[i], clusters[j]) + distance_to_index_matrix[j][i][0] = dist + distance_to_index_matrix[j][i][1] = i + distance_to_index_matrix[i][j][0] = dist + distance_to_index_matrix[i][j][1] = j + + # Sort row by distance + distance_to_index_matrix[i].sort(key=lambda x: x[0]) + + # Assignment step: find nearest cluster for each point + for i in range(len(points)): + point = points[i] + prev_idx = cluster_indices[i] + prev_dist = _lab_distance_squared(point, clusters[prev_idx]) + + min_dist = prev_dist + new_idx = -1 + + for j in range(cluster_count): + # Triangle inequality: skip if inter-cluster dist >= 4 * current dist + if distance_to_index_matrix[prev_idx][j][0] >= 4.0 * prev_dist: + continue + + dist = _lab_distance_squared(point, clusters[j]) + if dist < min_dist: + min_dist = dist + new_idx = j + + if new_idx != -1: + points_moved += 1 + cluster_indices[i] = new_idx + + # Early stop + if points_moved == 0 and iteration > 0: + break + + # Update step: compute new centroids as weighted mean in Lab space + component_l = [0.0] * cluster_count + component_a = [0.0] * cluster_count + component_b = [0.0] * cluster_count + for k in range(cluster_count): + pixel_count_sums[k] = 0 + + for i in range(len(points)): + cidx = cluster_indices[i] + pt = points[i] + count = pixel_to_count[unique_pixels[i]] + pixel_count_sums[cidx] += count + component_l[cidx] += pt[0] * count + component_a[cidx] += pt[1] * count + component_b[cidx] += pt[2] * count + + for i in range(cluster_count): + count = pixel_count_sums[i] + if count == 0: + clusters[i] = (0.0, 0.0, 0.0) + else: + clusters[i] = ( + component_l[i] / count, + component_a[i] / count, + component_b[i] / count, + ) + + # Build result: convert cluster centroids from Lab to ARGB with populations + cluster_argbs: List[int] = [] + cluster_populations: List[int] = [] + + for i in range(cluster_count): + count = pixel_count_sums[i] + if count == 0: + continue + + lab = clusters[i] + cr, cg, cb = lab_to_rgb(lab[0], lab[1], lab[2]) + argb = _argb_from_rgb(cr, cg, cb) + + if argb in cluster_argbs: + continue + + cluster_argbs.append(argb) + cluster_populations.append(count) + + color_to_count: Dict[int, int] = {} + for i in range(len(cluster_argbs)): + color_to_count[cluster_argbs[i]] = cluster_populations[i] + + return color_to_count + + +# ============================================================================= +# Score Algorithm - ranks colors for UI theme suitability +# ============================================================================= + +# Score constants matching material-color-utilities +TARGET_CHROMA = 48.0 +WEIGHT_PROPORTION = 0.7 +WEIGHT_CHROMA_ABOVE = 0.3 +WEIGHT_CHROMA_BELOW = 0.1 +CUTOFF_CHROMA = 5.0 +CUTOFF_EXCITED_PROPORTION = 0.01 +FALLBACK_COLOR_ARGB = 0xFF4285F4 # Google Blue + + +def _sanitize_degrees(degrees: float) -> int: + """Sanitize degrees to 0-359 range.""" + return int(degrees) % 360 + + +def _difference_degrees(a: float, b: float) -> float: + """Calculate the shortest distance between two angles.""" + diff = abs(a - b) + return min(diff, 360.0 - diff) + + +def score_colors( + color_to_population: Dict[int, int], + desired: int = 4, + fallback_color: int = FALLBACK_COLOR_ARGB, + filter_colors: bool = True, +) -> List[int]: + """ + Rank colors based on suitability for UI themes. + + Given a map of colors to population counts, removes unsuitable colors + and ranks the rest based on chroma and proportion. + + Args: + color_to_population: Dict mapping ARGB colors to pixel counts + desired: Maximum number of colors to return + fallback_color: Color to return if no suitable colors found + filter_colors: Whether to filter out low-chroma/low-proportion colors + + Returns: + List of ARGB colors sorted by suitability (best first) + """ + # Import here to avoid circular dependency + from .hct import Cam16, Hct + + # Build HCT colors and hue population histogram + colors_hct: List[Tuple[int, Hct]] = [] + hue_population = [0] * 360 + population_sum = 0 + + for argb, population in color_to_population.items(): + r = (argb >> 16) & 0xFF + g = (argb >> 8) & 0xFF + b = argb & 0xFF + + try: + hct = Hct.from_rgb(r, g, b) + colors_hct.append((argb, hct)) + hue = _sanitize_degrees(hct.hue) + hue_population[hue] += population + population_sum += population + except (ValueError, ZeroDivisionError): + continue + + if not colors_hct or population_sum == 0: + return [fallback_color] + + # Calculate "excited proportions" - sum of proportions in ยฑ15ยฐ hue window + hue_excited_proportions = [0.0] * 360 + for hue in range(360): + proportion = hue_population[hue] / population_sum + for offset in range(-14, 16): + neighbor_hue = _sanitize_degrees(hue + offset) + hue_excited_proportions[neighbor_hue] += proportion + + # Score each color + scored_hct: List[Tuple[int, Hct, float]] = [] + for argb, hct in colors_hct: + hue = _sanitize_degrees(round(hct.hue)) + proportion = hue_excited_proportions[hue] + + # Filter by chroma and proportion + if filter_colors: + if hct.chroma < CUTOFF_CHROMA: + continue + if proportion <= CUTOFF_EXCITED_PROPORTION: + continue + + # Proportion score (70% weight) + proportion_score = proportion * 100.0 * WEIGHT_PROPORTION + + # Chroma score + if hct.chroma < TARGET_CHROMA: + chroma_weight = WEIGHT_CHROMA_BELOW + else: + chroma_weight = WEIGHT_CHROMA_ABOVE + chroma_score = (hct.chroma - TARGET_CHROMA) * chroma_weight + + score = proportion_score + chroma_score + scored_hct.append((argb, hct, score)) + + if not scored_hct: + return [fallback_color] + + # Sort by score descending + scored_hct.sort(key=lambda x: -x[2]) + + # Deduplicate by hue distance - maximize hue diversity + # Start at 90ยฐ (max for 4 colors), decrease to 15ยฐ minimum + chosen_colors: List[Tuple[int, Hct]] = [] + + for diff_degrees in range(90, 14, -1): + chosen_colors.clear() + for argb, hct, score in scored_hct: + # Check if this hue is far enough from all chosen colors + is_duplicate = False + for chosen_argb, chosen_hct in chosen_colors: + if _difference_degrees(hct.hue, chosen_hct.hue) < diff_degrees: + is_duplicate = True + break + + if not is_duplicate: + chosen_colors.append((argb, hct)) + + if len(chosen_colors) >= desired: + break + + if len(chosen_colors) >= desired: + break + + if not chosen_colors: + return [fallback_color] + + return [argb for argb, hct in chosen_colors] + + +def extract_source_color( + pixels: List[Tuple[int, int, int]], + fallback_color: int = FALLBACK_COLOR_ARGB, +) -> int: + """ + Extract the primary source color from image pixels. + + Uses Wu + WSMeans quantizer (QuantizerCelebi) + Score algorithm matching + matugen/material-color-utilities. + + Args: + pixels: List of (R, G, B) tuples + fallback_color: Color to return if extraction fails + + Returns: + Source color in ARGB format + """ + from .hct import Cam16 + + if not pixels: + return fallback_color + + # Quantize using Wu + WSMeans (QuantizerCelebi pipeline like matugen) + wu_result = quantize_wu(pixels, max_colors=128) + starting_clusters = list(wu_result.keys()) + color_to_count = quantize_wsmeans(pixels, 128, starting_clusters) + + # Filter out low-chroma colors before scoring (like matugen) + filtered = {} + for argb, count in color_to_count.items(): + r = (argb >> 16) & 0xFF + g = (argb >> 8) & 0xFF + b = argb & 0xFF + try: + cam = Cam16.from_rgb(r, g, b) + if cam.chroma >= 5.0: + filtered[argb] = count + except (ValueError, ZeroDivisionError): + continue + + if not filtered: + filtered = color_to_count + + # Score and rank colors + ranked = score_colors(filtered, desired=4, fallback_color=fallback_color) + + return ranked[0] if ranked else fallback_color + + +def source_color_to_rgb(argb: int) -> Tuple[int, int, int]: + """Convert ARGB integer to RGB tuple.""" + return _rgb_from_argb(argb) diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/renderer.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/renderer.py new file mode 100644 index 0000000..bb900c3 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/renderer.py @@ -0,0 +1,1181 @@ +""" +Template rendering for Matugen compatibility. + +This module provides the TemplateRenderer class for processing template files +using the {{colors.name.mode.format}} syntax compatible with Matugen. + +Supports: +- Pipe filters: {{ colors.primary.dark.hex | set_alpha 0.5 | grayscale }} +- For loops: <* for name, value in colors *> ... <* endfor *> +- If/else: <* if {{ expr }} *> ... <* else *> ... <* endif *> +- Loop variables: loop.index, loop.first, loop.last +- Color filters: grayscale, invert, set_alpha, set_lightness, set_hue, + set_saturation, set_red, set_green, set_blue, lighten, darken, + saturate, desaturate, auto_lightness, blend, harmonize, to_color +- String filters: replace, lower_case, camel_case, pascal_case, + snake_case, kebab_case +- Custom colors: [config.custom_colors] in TOML config generates + {name}, on_{name}, {name}_container, on_{name}_container tokens +""" + +import re +import sys +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any, Optional, Union + +try: + import tomllib +except ImportError: + tomllib = None + +from .color import Color, find_closest_color +from .hct import Hct + + +# --- Node Types for the template AST --- + +@dataclass +class TextNode: + text: str + +@dataclass +class ForNode: + variables: list[str] + iterable: str + body: list + +@dataclass +class IfNode: + condition_expr: str + negated: bool + then_body: list + else_body: list = field(default_factory=list) + + +# --- Variable Scope Stack --- + +class VariableScope: + """Stack-based variable scope for loop variables.""" + + def __init__(self): + self._scopes: list[dict[str, Any]] = [] + + def push(self, bindings: Optional[dict[str, Any]] = None): + self._scopes.append(bindings or {}) + + def pop(self): + if self._scopes: + self._scopes.pop() + + def set(self, name: str, value: Any): + if self._scopes: + self._scopes[-1][name] = value + + def get(self, name: str) -> Optional[Any]: + """Look up variable, searching from innermost scope outward.""" + for scope in reversed(self._scopes): + if name in scope: + return scope[name] + return None + + @property + def is_active(self) -> bool: + return len(self._scopes) > 0 + + +# --- Known color format types --- + +KNOWN_FORMATS = frozenset({ + "hex", "hex_stripped", "rgb", "rgba", "hsl", "hsla", + "red", "green", "blue", "alpha", "hue", "saturation", "lightness", +}) + + +class TemplateRenderer: + """ + Renders templates using the generated theme colors. + Compatible with Matugen-style {{colors.name.mode.format}} tags. + + Supports filters via pipe syntax: + {{ colors.primary.dark.hex | grayscale }} + {{ colors.primary.dark.rgba | set_alpha 0.5 }} + + Supports block constructs: + <* for name, value in colors *> ... <* endfor *> + <* if {{ loop.first }} *> ... <* else *> ... <* endif *> + + Theme data uses snake_case keys (e.g., 'primary', 'surface_container'). + """ + + # Aliases for custom/legacy keys + COLOR_ALIASES = { + "hover": "surface_container_high", + "on_hover": "on_surface", + } + + # Supported color filters and their argument requirements + SUPPORTED_FILTERS = { + # No arguments + "grayscale": 0, + "invert": 0, + # One argument (float) + "set_alpha": 1, + "set_lightness": 1, + "set_hue": 1, + "set_saturation": 1, + "set_red": 1, + "set_green": 1, + "set_blue": 1, + "lighten": 1, + "darken": 1, + "saturate": 1, + "desaturate": 1, + "auto_lightness": 1, + } + + # Filters that take a hex color argument (+ optional numeric) + COLOR_ARG_FILTERS = {"blend", "harmonize"} + + # Regex for block delimiters: <* ... *> + _BLOCK_RE = re.compile(r'<\*(.*?)\*>', re.DOTALL) + + # Regex for expression tags: {{ ... }} + _EXPR_RE = re.compile(r"\{\{([^}\n]+?)\}\}") + + def __init__(self, theme_data: dict[str, dict[str, str]], verbose: bool = True, default_mode: str = "dark", image_path: Optional[str] = None, scheme_type: str = "content"): + self.theme_data = theme_data + self.closest_color = "" + self.verbose = verbose + self.default_mode = default_mode + self.image_path = image_path + self.scheme_type = scheme_type + self._current_file: Optional[str] = None + self._error_count = 0 + self._colors_map: Optional[dict[str, dict[str, str]]] = None + + def _log_error(self, message: str, line_hint: str = ""): + """Log an error to stderr.""" + self._error_count += 1 + prefix = f"[{self._current_file}] " if self._current_file else "" + hint = f" near '{line_hint}'" if line_hint else "" + print(f"Template error: {prefix}{message}{hint}", file=sys.stderr) + + def _log_warning(self, message: str): + """Log a warning to stderr.""" + if self.verbose: + prefix = f"[{self._current_file}] " if self._current_file else "" + print(f"Template warning: {prefix}{message}", file=sys.stderr) + + # --- Colors Map --- + + def _build_colors_map(self) -> dict[str, dict[str, str]]: + """Build iterable colors map: {name: {mode: hex_value, ...}}.""" + if self._colors_map is not None: + return self._colors_map + + colors_map: dict[str, dict[str, str]] = {} + all_names: set[str] = set() + for mode_data in self.theme_data.values(): + all_names.update(mode_data.keys()) + + for name in sorted(all_names): + color_modes: dict[str, str] = {} + for mode, mode_data in self.theme_data.items(): + if name in mode_data: + color_modes[mode] = mode_data[name] + # Add "default" as alias for self.default_mode + if self.default_mode in color_modes: + color_modes["default"] = color_modes[self.default_mode] + colors_map[name] = color_modes + + self._colors_map = colors_map + return colors_map + + # --- Palette Support --- + + def _get_palette_entries(self, palette_name: str) -> list[dict[str, str]]: + """Get tonal palette entries for iteration.""" + from .hct import Hct, TonalPalette + + TONES = [0, 5, 10, 15, 20, 25, 30, 35, 40, 50, 60, 70, 80, 90, 95, 98, 99, 100] + + palette_color_map = { + "primary": "primary", + "secondary": "secondary", + "tertiary": "tertiary", + "error": "error", + "neutral": "surface", + "neutral_variant": "surface_variant", + } + + color_name = palette_color_map.get(palette_name) + if not color_name: + self._log_warning(f"Unknown palette: {palette_name}") + return [] + + mode_data = self.theme_data.get(self.default_mode, {}) + hex_color = mode_data.get(color_name) + if not hex_color: + return [] + + color = Color.from_hex(hex_color) + palette = TonalPalette.from_rgb(color.r, color.g, color.b) + + entries = [] + for tone in TONES: + tone_hex = palette.get_hex(tone) + # Each entry is a color modes dict (same hex for all modes in palette context) + entries.append({"default": tone_hex, "dark": tone_hex, "light": tone_hex}) + + return entries + + # --- Block Parser --- + + def _parse_template(self, text: str) -> list: + """Parse template text into a tree of nodes.""" + tokens = self._tokenize(text) + nodes, _ = self._parse_nodes(tokens, 0) + return nodes + + def _tokenize(self, text: str) -> list[Union[str, tuple[str, str]]]: + """Split text into raw text strings and ('block', content) tuples. + + Standalone block tags (only content on their line) consume the entire + line including the trailing newline, matching matugen's behavior. + """ + tokens = [] + last_end = 0 + + for match in self._BLOCK_RE.finditer(text): + start = match.start() + end = match.end() + + # Check if this block tag is standalone on its line: + # preceded only by whitespace (after newline or start), followed by optional whitespace + newline + line_start = text.rfind('\n', last_end, start) + line_start = line_start + 1 if line_start != -1 else (last_end if last_end <= start else 0) + before_on_line = text[line_start:start] + + if before_on_line.strip() == '': + # Check what follows the block tag + after_end = end + while after_end < len(text) and text[after_end] in (' ', '\t'): + after_end += 1 + if after_end < len(text) and text[after_end] == '\n': + # Standalone: consume leading whitespace and trailing newline + start = line_start + end = after_end + 1 + elif after_end == len(text): + # At end of file, still standalone + start = line_start + end = after_end + + # Text before the block + if start > last_end: + tokens.append(text[last_end:start]) + # The block command + tokens.append(("block", match.group(1).strip())) + last_end = end + + # Remaining text after last block + if last_end < len(text): + tokens.append(text[last_end:]) + + return tokens + + def _parse_nodes(self, tokens: list, pos: int, stop_keywords: Optional[set[str]] = None) -> tuple[list, int]: + """Parse tokens into nodes, stopping at specified keywords. + + Returns (nodes, position_after_stop_keyword). + """ + nodes = [] + stop_keywords = stop_keywords or set() + + while pos < len(tokens): + token = tokens[pos] + + if isinstance(token, str): + # Raw text + if token: + nodes.append(TextNode(token)) + pos += 1 + else: + # Block command + _, cmd = token + + # Check if this is a stop keyword + if any(cmd.startswith(kw) for kw in stop_keywords): + return nodes, pos + + if cmd.startswith("for "): + node, pos = self._parse_for(tokens, pos) + nodes.append(node) + elif cmd.startswith("if "): + node, pos = self._parse_if(tokens, pos) + nodes.append(node) + else: + # Unknown block command - treat as text + self._log_warning(f"Unknown block command: {cmd}") + pos += 1 + + return nodes, pos + + def _parse_for(self, tokens: list, pos: int) -> tuple[ForNode, int]: + """Parse a for loop block starting at pos.""" + _, cmd = tokens[pos] + pos += 1 + + # Parse: for var1, var2 in iterable + for_match = re.match(r'for\s+(.+?)\s+in\s+(.+)$', cmd) + if not for_match: + self._log_error(f"Invalid for syntax: {cmd}") + return ForNode([], "", []), pos + + vars_str, iterable = for_match.groups() + variables = [v.strip() for v in vars_str.split(',')] + iterable = iterable.strip() + + # Parse body until endfor + body, pos = self._parse_nodes(tokens, pos, stop_keywords={"endfor"}) + + # Skip past 'endfor' + if pos < len(tokens): + pos += 1 + + return ForNode(variables, iterable, body), pos + + def _parse_if(self, tokens: list, pos: int) -> tuple[IfNode, int]: + """Parse an if/else/endif block starting at pos.""" + _, cmd = tokens[pos] + pos += 1 + + # Parse: if [not] {{ expr }} + negated = False + condition_part = cmd[3:].strip() # Remove 'if ' + + if condition_part.startswith("not "): + negated = True + condition_part = condition_part[4:].strip() + + # Extract expression from {{ ... }} if present + expr_match = re.match(r'\{\{(.+?)\}\}', condition_part) + if expr_match: + condition_expr = expr_match.group(1).strip() + else: + condition_expr = condition_part + + # Parse then body until else or endif + then_body, pos = self._parse_nodes(tokens, pos, stop_keywords={"else", "endif"}) + + else_body = [] + if pos < len(tokens): + _, stop_cmd = tokens[pos] + if stop_cmd.strip() == "else": + pos += 1 + # Parse else body until endif + else_body, pos = self._parse_nodes(tokens, pos, stop_keywords={"endif"}) + + # Skip past 'endif' + if pos < len(tokens): + pos += 1 + + return IfNode(condition_expr, negated, then_body, else_body), pos + + # --- Node Evaluation --- + + def _evaluate_nodes(self, nodes: list, scope: VariableScope) -> str: + """Evaluate a list of nodes with the given variable scope.""" + parts = [] + for node in nodes: + if isinstance(node, TextNode): + parts.append(self._resolve_text(node.text, scope)) + elif isinstance(node, ForNode): + parts.append(self._evaluate_for(node, scope)) + elif isinstance(node, IfNode): + parts.append(self._evaluate_if(node, scope)) + return ''.join(parts) + + def _evaluate_for(self, node: ForNode, scope: VariableScope) -> str: + """Evaluate a for loop node.""" + iterable = self._resolve_iterable(node.iterable, scope) + if not iterable: + return "" + + results = [] + total = len(iterable) + + for index, item in enumerate(iterable): + loop_meta = { + "index": index, + "first": index == 0, + "last": index == total - 1, + } + + scope.push({"loop": loop_meta}) + + if isinstance(item, tuple) and len(item) == 2: + # Map iteration: (key, value) + if len(node.variables) >= 2: + scope.set(node.variables[0], item[0]) + scope.set(node.variables[1], item[1]) + elif len(node.variables) == 1: + scope.set(node.variables[0], item[0]) + else: + # Array or range iteration + if node.variables: + scope.set(node.variables[0], item) + + results.append(self._evaluate_nodes(node.body, scope)) + scope.pop() + + return ''.join(results) + + def _evaluate_if(self, node: IfNode, scope: VariableScope) -> str: + """Evaluate an if/else node.""" + condition_value = self._resolve_expression(node.condition_expr, scope) + is_truthy = self._is_truthy(condition_value) + + if node.negated: + is_truthy = not is_truthy + + if is_truthy: + return self._evaluate_nodes(node.then_body, scope) + else: + return self._evaluate_nodes(node.else_body, scope) + + def _is_truthy(self, value: Any) -> bool: + """Determine if a value is truthy.""" + if value is None: + return False + if isinstance(value, bool): + return value + if isinstance(value, (int, float)): + return value != 0 + s = str(value).strip().lower() + return s not in ("", "false", "0", "none") + + # --- Iterable Resolution --- + + def _resolve_iterable(self, iterable_expr: str, scope: VariableScope) -> list: + """Resolve an iterable expression to a list of items.""" + # Range: "0..10" or "-5..5" + range_match = re.match(r'^(-?\d+)\.\.(-?\d+)$', iterable_expr) + if range_match: + start, end = int(range_match.group(1)), int(range_match.group(2)) + return list(range(start, end)) + + # Colors map: "colors" + if iterable_expr == "colors": + colors_map = self._build_colors_map() + return list(colors_map.items()) + + # Palette: "palettes.primary" etc. + if iterable_expr.startswith("palettes."): + palette_name = iterable_expr.split('.', 1)[1] + entries = self._get_palette_entries(palette_name) + # Return as list of dicts (each entry is a color modes dict) + return entries + + # Check scope for iterable variable + val = scope.get(iterable_expr) + if val is not None: + if isinstance(val, dict): + return list(val.items()) + if isinstance(val, (list, tuple)): + return list(val) + + self._log_warning(f"Unknown iterable: {iterable_expr}") + return [] + + # --- Expression Resolution --- + + def _resolve_text(self, text: str, scope: VariableScope) -> str: + """Resolve all {{ expr }} tags in a text segment.""" + def replace(match): + expr = match.group(1).strip() + return str(self._resolve_expression(expr, scope)) + + return self._EXPR_RE.sub(replace, text) + + def _resolve_expression(self, expr: str, scope: VariableScope) -> Any: + """Resolve an expression, checking scope variables first, then colors.""" + # Split by pipe for filters + parts = self._split_pipes(expr) + + if not parts: + return "" + + base = parts[0].strip() + filters = [p.strip() for p in parts[1:]] + + # Handle {{mode}} tag - resolves to current theme mode + if base == "mode": + result_str = self.default_mode + for filter_str in filters: + result_str = self._apply_string_or_color_filter(result_str, filter_str, expr) + return result_str + + # Try scope resolution first + resolved = self._resolve_from_scope(base, scope) + if resolved is not None: + result_str = str(resolved) + for filter_str in filters: + result_str = self._apply_string_or_color_filter(result_str, filter_str, expr) + return result_str + + # Handle {{image}} tag - resolves to source image path + if base == 'image': + result_str = self.image_path or "" + for filter_str in filters: + result_str = self._apply_string_or_color_filter(result_str, filter_str, expr) + return result_str + + # Fall back to colors.name.mode.format parsing + if base.startswith('colors.'): + return self._process_color_expression(base, filters, expr) + + # Unknown expression - return as-is + return f"{{{{{expr}}}}}" + + def _split_pipes(self, expr: str) -> list[str]: + """Split expression by pipe, respecting quoted strings.""" + parts = [] + current = [] + in_quotes = False + quote_char = None + + for char in expr: + if char in ('"', "'") and not in_quotes: + in_quotes = True + quote_char = char + current.append(char) + elif char == quote_char and in_quotes: + in_quotes = False + quote_char = None + current.append(char) + elif char == '|' and not in_quotes: + parts.append(''.join(current)) + current = [] + else: + current.append(char) + + if current: + parts.append(''.join(current)) + + return parts + + def _resolve_from_scope(self, base: str, scope: VariableScope) -> Optional[Any]: + """Resolve a dotted path from scope variables.""" + if not scope.is_active: + return None + + parts = base.split('.') + first = parts[0] + + val = scope.get(first) + if val is None: + return None + + # Navigate dotted path + for i, part in enumerate(parts[1:], 1): + if isinstance(val, dict) and part in val: + val = val[part] + elif isinstance(val, str) and val.startswith('#') and part in KNOWN_FORMATS: + # It's a hex color string and remaining part is a format specifier + color = Color.from_hex(val) + return self._format_color(color, part) + else: + return None + + # If the final value is a hex color string, return it as-is + return val + + def _process_color_expression(self, base: str, filters: list[str], raw_expr: str) -> str: + """Process a colors.name.mode.format expression with optional filters.""" + base_match = re.match(r'^colors\.([a-z_0-9]+)\.([a-z_0-9]+)\.([a-z_0-9]+)$', base) + + if not base_match: + self._log_error(f"Invalid syntax '{base}'. Expected: colors...", raw_expr) + return f"{{{{{raw_expr}}}}}" + + color_name, mode, format_type = base_match.groups() + + hex_color = self._get_hex_color(color_name, mode) + if not hex_color: + return f"{{{{UNKNOWN:{color_name}.{mode}}}}}" + + color = Color.from_hex(hex_color) + + # Apply color filters + for filter_str in filters: + filter_name, arg = self._parse_filter(filter_str) + if filter_name: + if filter_name == "replace": + # Replace works on the formatted string, apply after formatting + formatted = self._format_color(color, format_type) + return self._apply_replace(formatted, arg, raw_expr) + elif filter_name in self.COLOR_ARG_FILTERS: + hex_result = self._apply_color_arg_filter(color.to_hex(), filter_name, arg, raw_expr) + color = Color.from_hex(hex_result) + elif filter_name == "to_color": + pass # Already a color, no-op + elif filter_name in self.SUPPORTED_FILTERS: + color = self._apply_filter(color, filter_name, arg, raw_expr) + elif filter_name in ("lower_case", "camel_case", "pascal_case", "snake_case", "kebab_case"): + # String case filters apply to formatted output + formatted = self._format_color(color, format_type) + return self._apply_string_or_color_filter(formatted, filter_str, raw_expr) + else: + self._log_warning(f"Unknown filter '{filter_name}'") + + return self._format_color(color, format_type) + + # --- Color Access --- + + def _get_hex_color(self, color_name: str, mode: str) -> Optional[str]: + """Get raw hex color value for a color name and mode.""" + key = self.COLOR_ALIASES.get(color_name, color_name) + + if mode == "default": + mode_data = self.theme_data.get(self.default_mode) or self.theme_data.get("dark") or self.theme_data.get("light") + else: + mode_data = self.theme_data.get(mode) + + if not mode_data: + self._log_error(f"Unknown mode '{mode}'", f"colors.{color_name}.{mode}") + return None + + hex_color = mode_data.get(key) + if not hex_color: + self._log_error(f"Unknown color '{key}'", f"colors.{color_name}.{mode}") + return None + + return hex_color + + def _format_color(self, color: Color, format_type: str) -> str: + """Format a Color object to the requested format string.""" + if format_type == "hex": + return color.to_hex() + elif format_type == "hex_stripped": + return color.to_hex().lstrip('#') + elif format_type == "rgb": + return f"rgb({color.r}, {color.g}, {color.b})" + elif format_type == "rgba": + alpha = getattr(color, 'alpha', 1.0) + return f"rgba({color.r}, {color.g}, {color.b}, {alpha})" + elif format_type == "hsl": + h, s, l = color.to_hsl() + return f"hsl({int(h)}, {int(s * 100)}%, {int(l * 100)}%)" + elif format_type == "hsla": + h, s, l = color.to_hsl() + alpha = getattr(color, 'alpha', 1.0) + return f"hsla({int(h)}, {int(s * 100)}%, {int(l * 100)}%, {alpha})" + elif format_type == "hue": + h, _, _ = color.to_hsl() + return str(int(h)) + elif format_type == "saturation": + _, s, _ = color.to_hsl() + return str(int(s * 100)) + elif format_type == "lightness": + _, _, l = color.to_hsl() + return str(int(l * 100)) + elif format_type == "red": + return str(color.r) + elif format_type == "green": + return str(color.g) + elif format_type == "blue": + return str(color.b) + elif format_type == "alpha": + return str(getattr(color, 'alpha', 1.0)) + else: + self._log_error(f"Unknown format '{format_type}'") + return color.to_hex() + + # --- Filters --- + + def _parse_filter(self, filter_str: str) -> tuple[str, Optional[str]]: + """Parse a filter string into (name, argument). + + Supports both syntaxes: + | set_alpha 0.5 (space-separated) + | set_alpha: 0.5 (colon-separated, matugen-style) + | replace: "_", "-" (colon with quoted args) + """ + filter_str = filter_str.strip() + + # Check for colon syntax: name: args + colon_match = re.match(r'^([a-z_]+)\s*:\s*(.+)$', filter_str) + if colon_match: + return colon_match.group(1), colon_match.group(2).strip() + + # Space syntax: name arg + if ' ' in filter_str: + name, arg = filter_str.split(None, 1) + return name.strip(), arg.strip() + + return filter_str, None + + def _apply_string_or_color_filter(self, value: str, filter_str: str, raw_expr: str) -> str: + """Apply a filter to a string value (may be a color hex or plain string).""" + name, arg = self._parse_filter(filter_str) + + if name == "replace": + return self._apply_replace(value, arg, raw_expr) + + # String case transforms (work on any string) + if name == "lower_case": + return value.lower() + if name == "camel_case": + return self._to_camel_case(value) + if name == "pascal_case": + return self._to_pascal_case(value) + if name == "snake_case": + return self._to_snake_case(value) + if name == "kebab_case": + return self._to_kebab_case(value) + + # to_color: treat value as color string (pass-through, validates hex) + if name == "to_color": + if value.startswith('#') and len(value) in (7, 9): + return value + self._log_error(f"to_color: value '{value}' is not a valid hex color", raw_expr) + return value + + # Color-arg filters (blend, harmonize) - need hex color argument + if name in self.COLOR_ARG_FILTERS: + if not (value.startswith('#') and len(value) == 7): + self._log_error(f"Filter '{name}' requires a hex color value", raw_expr) + return value + return self._apply_color_arg_filter(value, name, arg, raw_expr) + + # Try as color filter if value looks like a hex color + if name in self.SUPPORTED_FILTERS and value.startswith('#') and len(value) == 7: + color = Color.from_hex(value) + color = self._apply_filter(color, name, arg, raw_expr) + return color.to_hex() + + self._log_warning(f"Cannot apply filter '{name}' to non-color value") + return value + + def _apply_color_arg_filter(self, value: str, name: str, arg: Optional[str], raw_expr: str) -> str: + """Apply blend or harmonize filter (takes hex color argument).""" + if not arg: + self._log_error(f"Filter '{name}' requires a color argument", raw_expr) + return value + + # Parse arguments: "#hexcolor", amount (for blend) or just "#hexcolor" (for harmonize) + # Support both quoted and unquoted hex + hex_match = re.match(r'["\']?(#[0-9a-fA-F]{6})["\']?\s*(?:,\s*(.+))?', arg) + if not hex_match: + self._log_error(f"Filter '{name}' requires a hex color argument, got '{arg}'", raw_expr) + return value + + target_hex = hex_match.group(1) + extra_arg = hex_match.group(2) + + src = Color.from_hex(value) + target = Color.from_hex(target_hex) + + src_hct = Hct.from_rgb(src.r, src.g, src.b) + target_hct = Hct.from_rgb(target.r, target.g, target.b) + + if name == "blend": + # Blend hue in HCT space by amount, keep source chroma/tone + if not extra_arg: + self._log_error("blend filter requires amount argument: blend: \"#hex\", 0.5", raw_expr) + return value + try: + amount = float(extra_arg.strip().strip('"\'')) + except ValueError: + self._log_error(f"blend amount must be numeric, got '{extra_arg}'", raw_expr) + return value + amount = max(0.0, min(1.0, amount)) + + # Shortest arc hue interpolation + diff = target_hct.hue - src_hct.hue + if diff > 180.0: + diff -= 360.0 + elif diff < -180.0: + diff += 360.0 + new_hue = (src_hct.hue + diff * amount) % 360.0 + result_hct = Hct(new_hue, src_hct.chroma, src_hct.tone) + + elif name == "harmonize": + # M3 harmonize: rotate hue toward target by min(diff * 0.5, 15ยฐ) + diff = target_hct.hue - src_hct.hue + if diff > 180.0: + diff -= 360.0 + elif diff < -180.0: + diff += 360.0 + max_rotation = 15.0 + rotation = min(abs(diff) * 0.5, max_rotation) + if diff < 0: + rotation = -rotation + new_hue = (src_hct.hue + rotation) % 360.0 + result_hct = Hct(new_hue, src_hct.chroma, src_hct.tone) + else: + return value + + r, g, b = result_hct.to_rgb() + return Color(r, g, b).to_hex() + + @staticmethod + def _split_words(s: str) -> list[str]: + """Split a string into words for case conversion.""" + # Handle camelCase/PascalCase boundaries + s = re.sub(r'([a-z])([A-Z])', r'\1_\2', s) + # Split on non-alphanumeric + return [w for w in re.split(r'[^a-zA-Z0-9]+', s) if w] + + def _to_camel_case(self, s: str) -> str: + words = self._split_words(s) + if not words: + return s + return words[0].lower() + ''.join(w.capitalize() for w in words[1:]) + + def _to_pascal_case(self, s: str) -> str: + words = self._split_words(s) + if not words: + return s + return ''.join(w.capitalize() for w in words) + + def _to_snake_case(self, s: str) -> str: + words = self._split_words(s) + return '_'.join(w.lower() for w in words) + + def _to_kebab_case(self, s: str) -> str: + words = self._split_words(s) + return '-'.join(w.lower() for w in words) + + def _apply_replace(self, value: str, args: Optional[str], raw_expr: str) -> str: + """Apply the replace filter: | replace: "search", "replacement" """ + if not args: + self._log_error("replace filter requires arguments", raw_expr) + return value + + # Parse quoted arguments: "search", "replacement" + match = re.match(r'"([^"]*?)"\s*,\s*"([^"]*?)"', args) + if match: + find_str, replace_str = match.groups() + return value.replace(find_str, replace_str) + + # Try single-quoted: 'search', 'replacement' + match = re.match(r"'([^']*?)'\s*,\s*'([^']*?)'", args) + if match: + find_str, replace_str = match.groups() + return value.replace(find_str, replace_str) + + self._log_error(f"replace filter syntax: replace: \"find\", \"replacement\"", raw_expr) + return value + + def _apply_filter(self, color: Color, filter_name: str, arg: Optional[str], raw_expr: str) -> Color: + """Apply a single color filter.""" + if filter_name not in self.SUPPORTED_FILTERS: + supported = ", ".join(sorted(self.SUPPORTED_FILTERS.keys())) + self._log_error(f"Unknown filter '{filter_name}'. Supported: {supported}", raw_expr) + return color + + expected_args = self.SUPPORTED_FILTERS[filter_name] + + if expected_args > 0 and arg is None: + self._log_error(f"Filter '{filter_name}' requires an argument", raw_expr) + return color + if expected_args == 0 and arg is not None: + self._log_warning(f"Filter '{filter_name}' ignores argument '{arg}'") + + num_arg = None + if expected_args > 0: + try: + num_arg = float(arg) + except (ValueError, TypeError): + self._log_error(f"Filter '{filter_name}' requires numeric argument, got '{arg}'", raw_expr) + return color + + h, s, l = color.to_hsl() + + if filter_name == "grayscale": + gray = int(0.299 * color.r + 0.587 * color.g + 0.114 * color.b) + result = Color(gray, gray, gray) + elif filter_name == "invert": + result = Color(255 - color.r, 255 - color.g, 255 - color.b) + elif filter_name == "set_alpha": + result = Color(color.r, color.g, color.b) + result.alpha = max(0.0, min(1.0, num_arg)) + elif filter_name == "set_lightness": + new_l = max(0.0, min(1.0, num_arg / 100.0)) + result = Color.from_hsl(h, s, new_l) + elif filter_name == "set_hue": + new_h = num_arg % 360 + result = Color.from_hsl(new_h, s, l) + elif filter_name == "set_saturation": + new_s = max(0.0, min(1.0, num_arg / 100.0)) + result = Color.from_hsl(h, new_s, l) + elif filter_name == "lighten": + new_l = max(0.0, min(1.0, l + num_arg / 100.0)) + result = Color.from_hsl(h, s, new_l) + elif filter_name == "darken": + new_l = max(0.0, min(1.0, l - num_arg / 100.0)) + result = Color.from_hsl(h, s, new_l) + elif filter_name == "saturate": + new_s = max(0.0, min(1.0, s + num_arg / 100.0)) + result = Color.from_hsl(h, new_s, l) + elif filter_name == "desaturate": + new_s = max(0.0, min(1.0, s - num_arg / 100.0)) + result = Color.from_hsl(h, new_s, l) + elif filter_name == "auto_lightness": + # If lightness < 50%, lighten; otherwise darken (push toward mid-lightness) + if l < 0.5: + new_l = max(0.0, min(1.0, l + num_arg / 100.0)) + else: + new_l = max(0.0, min(1.0, l - num_arg / 100.0)) + result = Color.from_hsl(h, s, new_l) + elif filter_name == "set_red": + result = Color(max(0, min(255, int(num_arg))), color.g, color.b) + elif filter_name == "set_green": + result = Color(color.r, max(0, min(255, int(num_arg))), color.b) + elif filter_name == "set_blue": + result = Color(color.r, color.g, max(0, min(255, int(num_arg)))) + else: + result = color + + if hasattr(color, 'alpha') and not hasattr(result, 'alpha'): + result.alpha = color.alpha + + return result + + # --- Main Render Methods --- + + def render(self, template_text: str) -> str: + """Render a template string, processing blocks and expressions.""" + self._error_count = 0 + + # Parse template into node tree + nodes = self._parse_template(template_text) + + # Evaluate with empty scope + scope = VariableScope() + result = self._evaluate_nodes(nodes, scope) + + if self.closest_color: + result = self._substitute_closest_color(result) + + # Process escape sequences (matugen-compatible) + result = result.replace('\\\\', '\\') + + if self._error_count > 0: + print(f"Template rendering completed with {self._error_count} error(s)", file=sys.stderr) + + return result + + def render_file(self, input_path: Path, output_path: Path) -> bool: + """Render a template file to an output path. + + Returns True if successful, False if skipped due to errors. + """ + self._current_file = str(input_path) + success = False + try: + template_text = input_path.read_text() + rendered_text = self.render(template_text) + + if self._error_count > 0: + print(f"Skipping {output_path}: template has {self._error_count} error(s)", file=sys.stderr) + else: + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text(rendered_text) + success = True + except FileNotFoundError: + self._log_error(f"Template file not found: {input_path}") + except PermissionError: + self._log_error(f"Permission denied: {output_path}") + except Exception as e: + self._log_error(f"Unexpected error: {e}") + finally: + self._current_file = None + return success + + # --- Custom Colors --- + + # Standard M3 tone mappings for primary color group + _CUSTOM_COLOR_TONES = { + "dark": { + "color": 80, + "on_color": 20, + "color_container": 30, + "on_color_container": 90, + }, + "light": { + "color": 40, + "on_color": 100, + "color_container": 90, + "on_color_container": 10, + }, + } + + def _apply_custom_colors(self, custom_colors: dict[str, Any]): + """Generate and merge custom color tokens into theme_data. + + Matches matugen's [config.custom_colors] behavior: + - Each custom color generates 6 tokens per mode + - Optional blend (harmonize toward source color) + - Uses the same scheme type as the main theme for palette generation + - Tokens: {name}_source, {name}_value, {name}, on_{name}, + {name}_container, on_{name}_container + """ + from .hct import Hct, TonalPalette + from .material import ( + SchemeTonalSpot, SchemeFruitSalad, SchemeRainbow, + SchemeContent, SchemeMonochrome + ) + + SCHEME_CLASSES = { + "tonal-spot": SchemeTonalSpot, + "content": SchemeContent, + "fruit-salad": SchemeFruitSalad, + "rainbow": SchemeRainbow, + "monochrome": SchemeMonochrome, + } + scheme_class = SCHEME_CLASSES.get(self.scheme_type, SchemeContent) + + # Get source color for harmonization (primary from default mode) + source_hex = None + mode_data = self.theme_data.get(self.default_mode, {}) + source_hex = mode_data.get("primary") or mode_data.get("source_color") + + for name, config in custom_colors.items(): + # Parse config: either a string "#hex" or {color: "#hex", blend: bool} + if isinstance(config, str): + color_hex = config + blend = True + elif isinstance(config, dict): + color_hex = config.get("color", "") + blend = config.get("blend", True) + else: + self._log_warning(f"Invalid custom_color config for '{name}': {config}") + continue + + # Validate hex color + if not (color_hex.startswith('#') and len(color_hex) == 7): + self._log_error(f"Custom color '{name}' has invalid hex: '{color_hex}'") + continue + + original_color = Color.from_hex(color_hex) + + # Optionally harmonize toward source color + if blend and source_hex: + src = Color.from_hex(color_hex) + target = Color.from_hex(source_hex) + src_hct = Hct.from_rgb(src.r, src.g, src.b) + target_hct = Hct.from_rgb(target.r, target.g, target.b) + + # M3 harmonize: rotate hue toward target by min(diff * 0.5, 15ยฐ) + diff = target_hct.hue - src_hct.hue + if diff > 180.0: + diff -= 360.0 + elif diff < -180.0: + diff += 360.0 + max_rotation = 15.0 + rotation = min(abs(diff) * 0.5, max_rotation) + if diff < 0: + rotation = -rotation + new_hue = (src_hct.hue + rotation) % 360.0 + harmonized_hct = Hct(new_hue, src_hct.chroma, src_hct.tone) + r, g, b = harmonized_hct.to_rgb() + palette_color = Color(r, g, b) + else: + palette_color = original_color + + # Generate palette using the scheme class (matches matugen behavior) + palette_hct = Hct.from_rgb(palette_color.r, palette_color.g, palette_color.b) + scheme = scheme_class(palette_hct) + palette = scheme.primary_palette + + # Generate tokens for each mode + for mode in self.theme_data: + tones = self._CUSTOM_COLOR_TONES.get(mode) + if not tones: + continue + + # Source/value tokens (always the original unharmonized color) + self.theme_data[mode][f"{name}_source"] = original_color.to_hex() + self.theme_data[mode][f"{name}_value"] = original_color.to_hex() + + # Primary role tokens from the tonal palette + self.theme_data[mode][name] = palette.get_hex(tones["color"]) + self.theme_data[mode][f"on_{name}"] = palette.get_hex(tones["on_color"]) + self.theme_data[mode][f"{name}_container"] = palette.get_hex(tones["color_container"]) + self.theme_data[mode][f"on_{name}_container"] = palette.get_hex(tones["on_color_container"]) + + # Invalidate colors map cache so new colors appear in iterations + self._colors_map = None + + def _substitute_closest_color(self, text: str) -> str: + """Substitute {{closest_color}} in text.""" + return re.sub(r"\{\{\s*closest_color\s*\}\}", self.closest_color, text) + + def process_config_file(self, config_path: Path): + """Process Matugen TOML configuration file.""" + if not tomllib: + print("Error: tomllib module not available (requires Python 3.11+)", file=sys.stderr) + return + + try: + with open(config_path, "rb") as f: + data = tomllib.load(f) + + # Apply custom colors before rendering templates + config_section = data.get("config", {}) + custom_colors = config_section.get("custom_colors") + if custom_colors: + self._apply_custom_colors(custom_colors) + + templates = data.get("templates", {}) + for name, template in templates.items(): + input_path = template.get("input_path") + output_path = template.get("output_path") + + if not input_path or not output_path: + print(f"Warning: Template '{name}' missing input_path or output_path", file=sys.stderr) + continue + + # Handle closest_color if configured (matugen-compatible) + # Reset for each template to avoid state pollution between templates + self.closest_color = "" + colors_to_compare = template.get("colors_to_compare") + compare_to = template.get("compare_to") + + if colors_to_compare and compare_to: + rendered_compare_to = self.render(compare_to) + self.closest_color = find_closest_color(rendered_compare_to, colors_to_compare) + + self.render_file(Path(input_path).expanduser(), Path(output_path).expanduser()) + + # Execute pre_hook if specified + pre_hook = template.get("pre_hook") + if pre_hook: + import subprocess + if self.closest_color: + pre_hook = self._substitute_closest_color(pre_hook) + pre_hook = self.render(pre_hook) + try: + subprocess.run(pre_hook, shell=True, check=False) + except Exception as e: + print(f"Error running pre_hook for {name}: {e}", file=sys.stderr) + + # Execute post_hook if specified + post_hook = template.get("post_hook") + if post_hook: + import subprocess + if self.closest_color: + post_hook = self._substitute_closest_color(post_hook) + post_hook = self.render(post_hook) + try: + subprocess.run(post_hook, shell=True, check=False) + except Exception as e: + print(f"Error running post_hook for {name}: {e}", file=sys.stderr) + + except FileNotFoundError: + print(f"Error: Config file not found: {config_path}", file=sys.stderr) + except Exception as e: + print(f"Error processing config file {config_path}: {e}", file=sys.stderr) diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/scheme.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/scheme.py new file mode 100644 index 0000000..6ea0d62 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/scheme.py @@ -0,0 +1,351 @@ +""" +Predefined scheme expansion - Convert 14-color schemes to full palette. + +This module expands predefined color schemes (like Tokyo-Night) from their +14 core colors to the full 48-color palette used by templates. + +Input format (14 colors): + mPrimary, mOnPrimary, mSecondary, mOnSecondary, mTertiary, mOnTertiary, + mError, mOnError, mSurface, mOnSurface, mSurfaceVariant, mOnSurfaceVariant, + mOutline, mHover + +Output: Full 48-color palette matching generate_theme() output. +""" + +from typing import Literal + +from .color import Color +from .contrast import ensure_contrast + +ThemeMode = Literal["dark", "light"] + + +def _hex_to_color(hex_str: str) -> Color: + """Convert hex string to Color object.""" + hex_str = hex_str.lstrip("#") + r = int(hex_str[0:2], 16) + g = int(hex_str[2:4], 16) + b = int(hex_str[4:6], 16) + return Color(r, g, b) + + +def _make_container_dark(base: Color) -> Color: + """Generate container color for dark mode.""" + h, s, l = base.to_hsl() + return Color.from_hsl(h, min(s + 0.15, 1.0), max(l - 0.35, 0.15)) + + +def _make_container_light(base: Color) -> Color: + """Generate container color for light mode.""" + h, s, l = base.to_hsl() + return Color.from_hsl(h, max(s - 0.20, 0.30), min(l + 0.35, 0.85)) + + +def _make_fixed_dark(base: Color) -> tuple[Color, Color]: + """Generate fixed and fixed_dim colors for dark mode.""" + h, s, _ = base.to_hsl() + fixed = Color.from_hsl(h, max(s, 0.70), 0.85) + fixed_dim = Color.from_hsl(h, max(s, 0.65), 0.75) + return fixed, fixed_dim + + +def _make_fixed_light(base: Color) -> tuple[Color, Color]: + """Generate fixed and fixed_dim colors for light mode.""" + h, s, _ = base.to_hsl() + fixed = Color.from_hsl(h, max(s, 0.70), 0.40) + fixed_dim = Color.from_hsl(h, max(s, 0.65), 0.30) + return fixed, fixed_dim + + +def _interpolate_color(c1: Color, c2: Color, t: float) -> Color: + """Interpolate between two colors. t=0 returns c1, t=1 returns c2.""" + r = int(c1.r + (c2.r - c1.r) * t) + g = int(c1.g + (c2.g - c1.g) * t) + b = int(c1.b + (c2.b - c1.b) * t) + return Color(max(0, min(255, r)), max(0, min(255, g)), max(0, min(255, b))) + + +def expand_predefined_scheme(scheme_data: dict[str, str], mode: ThemeMode) -> dict[str, str]: + """ + Expand 14-color predefined scheme to full 48-color palette. + + Args: + scheme_data: Dictionary with keys like mPrimary, mSecondary, etc. + mode: "dark" or "light" + + Returns: + Dictionary with all 48 color names mapped to hex values. + """ + is_dark = mode == "dark" + + # Parse input colors + primary = _hex_to_color(scheme_data["mPrimary"]) + on_primary = _hex_to_color(scheme_data["mOnPrimary"]) + secondary = _hex_to_color(scheme_data["mSecondary"]) + on_secondary = _hex_to_color(scheme_data["mOnSecondary"]) + tertiary = _hex_to_color(scheme_data["mTertiary"]) + on_tertiary = _hex_to_color(scheme_data["mOnTertiary"]) + error = _hex_to_color(scheme_data["mError"]) + on_error = _hex_to_color(scheme_data["mOnError"]) + surface = _hex_to_color(scheme_data["mSurface"]) + on_surface = _hex_to_color(scheme_data["mOnSurface"]) + surface_variant = _hex_to_color(scheme_data["mSurfaceVariant"]) + on_surface_variant = _hex_to_color(scheme_data["mOnSurfaceVariant"]) + outline_raw = _hex_to_color(scheme_data["mOutline"]) + shadow = _hex_to_color(scheme_data.get("mShadow", scheme_data["mSurface"])) + + # Generate container colors + if is_dark: + primary_container = _make_container_dark(primary) + secondary_container = _make_container_dark(secondary) + tertiary_container = _make_container_dark(tertiary) + error_container = _make_container_dark(error) + else: + primary_container = _make_container_light(primary) + secondary_container = _make_container_light(secondary) + tertiary_container = _make_container_light(tertiary) + error_container = _make_container_light(error) + + # Generate "on container" colors with proper contrast + primary_h, primary_s, _ = primary.to_hsl() + secondary_h, secondary_s, _ = secondary.to_hsl() + tertiary_h, tertiary_s, _ = tertiary.to_hsl() + error_h, error_s, _ = error.to_hsl() + + if is_dark: + # Light text on dark containers + on_primary_container = ensure_contrast( + Color.from_hsl(primary_h, primary_s, 0.90), primary_container, 4.5 + ) + on_secondary_container = ensure_contrast( + Color.from_hsl(secondary_h, secondary_s, 0.90), secondary_container, 4.5 + ) + on_tertiary_container = ensure_contrast( + Color.from_hsl(tertiary_h, tertiary_s, 0.90), tertiary_container, 4.5 + ) + on_error_container = ensure_contrast( + Color.from_hsl(error_h, error_s, 0.90), error_container, 4.5 + ) + else: + # Dark text on light containers + on_primary_container = ensure_contrast( + Color.from_hsl(primary_h, primary_s, 0.15), primary_container, 4.5 + ) + on_secondary_container = ensure_contrast( + Color.from_hsl(secondary_h, secondary_s, 0.15), secondary_container, 4.5 + ) + on_tertiary_container = ensure_contrast( + Color.from_hsl(tertiary_h, tertiary_s, 0.15), tertiary_container, 4.5 + ) + on_error_container = ensure_contrast( + Color.from_hsl(error_h, error_s, 0.15), error_container, 4.5 + ) + + # Generate fixed colors + if is_dark: + primary_fixed, primary_fixed_dim = _make_fixed_dark(primary) + secondary_fixed, secondary_fixed_dim = _make_fixed_dark(secondary) + tertiary_fixed, tertiary_fixed_dim = _make_fixed_dark(tertiary) + else: + primary_fixed, primary_fixed_dim = _make_fixed_light(primary) + secondary_fixed, secondary_fixed_dim = _make_fixed_light(secondary) + tertiary_fixed, tertiary_fixed_dim = _make_fixed_light(tertiary) + + # Generate "on fixed" colors + if is_dark: + on_primary_fixed = ensure_contrast( + Color.from_hsl(primary_h, 0.15, 0.15), primary_fixed, 4.5 + ) + on_primary_fixed_variant = ensure_contrast( + Color.from_hsl(primary_h, 0.15, 0.20), primary_fixed_dim, 4.5 + ) + on_secondary_fixed = ensure_contrast( + Color.from_hsl(secondary_h, 0.15, 0.15), secondary_fixed, 4.5 + ) + on_secondary_fixed_variant = ensure_contrast( + Color.from_hsl(secondary_h, 0.15, 0.20), secondary_fixed_dim, 4.5 + ) + on_tertiary_fixed = ensure_contrast( + Color.from_hsl(tertiary_h, 0.15, 0.15), tertiary_fixed, 4.5 + ) + on_tertiary_fixed_variant = ensure_contrast( + Color.from_hsl(tertiary_h, 0.15, 0.20), tertiary_fixed_dim, 4.5 + ) + else: + on_primary_fixed = ensure_contrast( + Color.from_hsl(primary_h, 0.15, 0.90), primary_fixed, 4.5 + ) + on_primary_fixed_variant = ensure_contrast( + Color.from_hsl(primary_h, 0.15, 0.85), primary_fixed_dim, 4.5 + ) + on_secondary_fixed = ensure_contrast( + Color.from_hsl(secondary_h, 0.15, 0.90), secondary_fixed, 4.5 + ) + on_secondary_fixed_variant = ensure_contrast( + Color.from_hsl(secondary_h, 0.15, 0.85), secondary_fixed_dim, 4.5 + ) + on_tertiary_fixed = ensure_contrast( + Color.from_hsl(tertiary_h, 0.15, 0.90), tertiary_fixed, 4.5 + ) + on_tertiary_fixed_variant = ensure_contrast( + Color.from_hsl(tertiary_h, 0.15, 0.85), tertiary_fixed_dim, 4.5 + ) + + # Generate surface containers using mSurfaceVariant as the middle container + # This respects the scheme author's color choices + surface_h, surface_s, surface_l = surface.to_hsl() + sv_h, sv_s, sv_l = surface_variant.to_hsl() + + # surface_container = mSurfaceVariant (direct assignment) + surface_container = surface_variant + + if is_dark: + # Dark mode: surface is darkest, surface_variant is the middle container + # Lower containers interpolate between surface and surface_variant + surface_container_lowest = _interpolate_color(surface, surface_variant, 0.2) + surface_container_low = _interpolate_color(surface, surface_variant, 0.5) + # Higher containers go beyond surface_variant (lighter) + surface_container_high = Color.from_hsl(sv_h, sv_s, min(sv_l + 0.04, 0.40)) + surface_container_highest = Color.from_hsl(sv_h, sv_s, min(sv_l + 0.08, 0.45)) + # Dim is darker than surface, bright is lighter than highest container + surface_dim = Color.from_hsl(surface_h, surface_s, max(surface_l - 0.04, 0.02)) + surface_bright = Color.from_hsl(sv_h, sv_s, min(sv_l + 0.12, 0.50)) + else: + # Light mode: surface is lightest, surface_variant is the middle container + # Lower containers interpolate between surface and surface_variant + surface_container_lowest = _interpolate_color(surface, surface_variant, 0.2) + surface_container_low = _interpolate_color(surface, surface_variant, 0.5) + # Higher containers go beyond surface_variant (darker) + surface_container_high = Color.from_hsl(sv_h, sv_s, max(sv_l - 0.04, 0.60)) + surface_container_highest = Color.from_hsl(sv_h, sv_s, max(sv_l - 0.08, 0.55)) + # Dim is darker than highest, bright is lighter than surface + surface_dim = Color.from_hsl(sv_h, sv_s, max(sv_l - 0.12, 0.50)) + surface_bright = Color.from_hsl(surface_h, surface_s, min(surface_l + 0.03, 0.98)) + + # Ensure outline has sufficient contrast against surface (3:1 minimum for UI) + outline = ensure_contrast(outline_raw, surface, 3.0) + + # Generate outline variant + outline_h, outline_s, outline_l = outline.to_hsl() + if is_dark: + outline_variant = Color.from_hsl(outline_h, outline_s, max(outline_l - 0.15, 0.1)) + else: + outline_variant = Color.from_hsl(outline_h, outline_s, min(outline_l + 0.15, 0.9)) + + # Scrim is always black + scrim = Color(0, 0, 0) + + # Inverse colors + if is_dark: + inverse_surface = Color.from_hsl(surface_h, 0.08, 0.90) + inverse_on_surface = Color.from_hsl(surface_h, 0.05, 0.15) + inverse_primary = Color.from_hsl(primary_h, max(primary_s * 0.8, 0.5), 0.40) + else: + inverse_surface = Color.from_hsl(surface_h, 0.08, 0.15) + inverse_on_surface = Color.from_hsl(surface_h, 0.05, 0.90) + inverse_primary = Color.from_hsl(primary_h, max(primary_s * 0.8, 0.5), 0.70) + + # Background is same as surface in MD3 + background = surface + on_background = on_surface + + return { + # Primary + "primary": primary.to_hex(), + "on_primary": on_primary.to_hex(), + "primary_container": primary_container.to_hex(), + "on_primary_container": on_primary_container.to_hex(), + "primary_fixed": primary_fixed.to_hex(), + "primary_fixed_dim": primary_fixed_dim.to_hex(), + "on_primary_fixed": on_primary_fixed.to_hex(), + "on_primary_fixed_variant": on_primary_fixed_variant.to_hex(), + # Secondary + "secondary": secondary.to_hex(), + "on_secondary": on_secondary.to_hex(), + "secondary_container": secondary_container.to_hex(), + "on_secondary_container": on_secondary_container.to_hex(), + "secondary_fixed": secondary_fixed.to_hex(), + "secondary_fixed_dim": secondary_fixed_dim.to_hex(), + "on_secondary_fixed": on_secondary_fixed.to_hex(), + "on_secondary_fixed_variant": on_secondary_fixed_variant.to_hex(), + # Tertiary + "tertiary": tertiary.to_hex(), + "on_tertiary": on_tertiary.to_hex(), + "tertiary_container": tertiary_container.to_hex(), + "on_tertiary_container": on_tertiary_container.to_hex(), + "tertiary_fixed": tertiary_fixed.to_hex(), + "tertiary_fixed_dim": tertiary_fixed_dim.to_hex(), + "on_tertiary_fixed": on_tertiary_fixed.to_hex(), + "on_tertiary_fixed_variant": on_tertiary_fixed_variant.to_hex(), + # Error + "error": error.to_hex(), + "on_error": on_error.to_hex(), + "error_container": error_container.to_hex(), + "on_error_container": on_error_container.to_hex(), + # Surface + "surface": surface.to_hex(), + "on_surface": on_surface.to_hex(), + "surface_variant": surface_variant.to_hex(), + "on_surface_variant": on_surface_variant.to_hex(), + "surface_dim": surface_dim.to_hex(), + "surface_bright": surface_bright.to_hex(), + # Surface containers + "surface_container_lowest": surface_container_lowest.to_hex(), + "surface_container_low": surface_container_low.to_hex(), + "surface_container": surface_container.to_hex(), + "surface_container_high": surface_container_high.to_hex(), + "surface_container_highest": surface_container_highest.to_hex(), + # Outline and other + "outline": outline.to_hex(), + "outline_variant": outline_variant.to_hex(), + "shadow": shadow.to_hex(), + "scrim": scrim.to_hex(), + # Inverse + "inverse_surface": inverse_surface.to_hex(), + "inverse_on_surface": inverse_on_surface.to_hex(), + "inverse_primary": inverse_primary.to_hex(), + # Background + "background": background.to_hex(), + "on_background": on_background.to_hex(), + } + + +def inject_terminal_colors(result: dict[str, str], scheme_mode_data: dict) -> dict[str, str]: + """Flatten scheme's terminal section into template-ready color keys. + + Adds keys like terminal_foreground, terminal_normal_black, terminal_bright_red, etc. + so predefined terminal templates can reference them as + {{colors.terminal_foreground.default.hex_stripped}}. + + Args: + result: Expanded color palette dict to augment. + scheme_mode_data: Raw scheme JSON mode data (e.g., scheme_data["dark"]). + + Returns: + The same result dict with terminal_ keys added. + """ + terminal = scheme_mode_data.get("terminal") + if not terminal: + return result + + # Map of JSON keys to flattened key names + direct_keys = { + "foreground": "terminal_foreground", + "background": "terminal_background", + "cursor": "terminal_cursor", + "cursorText": "terminal_cursor_text", + "selectionFg": "terminal_selection_fg", + "selectionBg": "terminal_selection_bg", + } + + for json_key, flat_key in direct_keys.items(): + if json_key in terminal: + result[flat_key] = terminal[json_key] + + # ANSI normal/bright color groups + for group in ("normal", "bright"): + if group in terminal: + for name, hex_val in terminal[group].items(): + result[f"terminal_{group}_{name}"] = hex_val + + return result diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/theme.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/theme.py new file mode 100644 index 0000000..871dfc8 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/lib/theme.py @@ -0,0 +1,878 @@ +""" +Theme generation functions for Material and Normal modes. + +This module provides functions for generating complete color themes +from a color palette, supporting both Material Design 3 and a more +vibrant "wallust-style" theme. + +Supported scheme types: +- tonal-spot: Default Android 12-13 scheme (recommended) +- fruit-salad: Bold/playful with hue rotation +- rainbow: Chromatic accents with grayscale neutrals +- monochrome: Pure grayscale M3 scheme (chroma = 0) +- vibrant: Prioritizes the most saturated colors regardless of area +- faithful: Prioritizes dominant colors by area coverage +- muted: Preserves hue but caps saturation low (for monochrome wallpapers) +""" + +from typing import Literal + +from .color import Color, shift_hue, hue_distance, adjust_surface +from .contrast import ensure_contrast +from .material import SchemeTonalSpot, SchemeFruitSalad, SchemeRainbow, SchemeContent, SchemeMonochrome +from .palette import find_error_color + +# Type aliases +ThemeMode = Literal["dark", "light"] +SchemeType = Literal["tonal-spot", "fruit-salad", "rainbow", "content", "monochrome", "vibrant", "faithful", "muted"] + +# Map scheme type strings to classes +SCHEME_CLASSES = { + "tonal-spot": SchemeTonalSpot, + "fruit-salad": SchemeFruitSalad, + "rainbow": SchemeRainbow, + "content": SchemeContent, + "monochrome": SchemeMonochrome, + # "vibrant", "faithful", and "muted" use generate_*_* functions, not a scheme class +} + + +def generate_material_dark(palette: list[Color], scheme_type: str = "tonal-spot") -> dict[str, str]: + """ + Generate Material Design 3 dark theme from palette using HCT color space. + + Args: + palette: List of extracted colors (primary color is index 0) + scheme_type: One of "tonal-spot", "fruit-salad", "rainbow" + + Returns: + Dictionary of color token names to hex values + """ + primary = palette[0] if palette else Color(255, 245, 155) + + # Get the appropriate scheme class + scheme_class = SCHEME_CLASSES.get(scheme_type, SchemeTonalSpot) + scheme = scheme_class.from_rgb(primary.r, primary.g, primary.b) + return scheme.get_dark_scheme() + + +def generate_material_light(palette: list[Color], scheme_type: str = "tonal-spot") -> dict[str, str]: + """ + Generate Material Design 3 light theme from palette using HCT color space. + + Args: + palette: List of extracted colors (primary color is index 0) + scheme_type: One of "tonal-spot", "fruit-salad", "rainbow" + + Returns: + Dictionary of color token names to hex values + """ + primary = palette[0] if palette else Color(93, 101, 245) + + # Get the appropriate scheme class + scheme_class = SCHEME_CLASSES.get(scheme_type, SchemeTonalSpot) + scheme = scheme_class.from_rgb(primary.r, primary.g, primary.b) + return scheme.get_light_scheme() + + +def generate_normal_dark(palette: list[Color]) -> dict[str, str]: + """ + Generate wallust-style dark theme from palette. + + More vibrant than Material - uses palette colors directly and keeps + surfaces saturated with the primary hue. Outputs same keys as Material. + """ + # Use extracted colors directly (wallust style) + # But check if colors are distinct enough - if not, derive from primary + primary = palette[0] if palette else Color(255, 245, 155) + primary_h, primary_s, primary_l = primary.to_hsl() + + # Secondary: use palette[1] only if hue is >30ยฐ different, otherwise derive + MIN_HUE_DISTANCE = 30 + if len(palette) > 1: + sec_h, _, _ = palette[1].to_hsl() + if hue_distance(primary_h, sec_h) > MIN_HUE_DISTANCE: + secondary = palette[1] + else: + # Colors too similar - shift hue by 30ยฐ to stay in same color family + secondary = shift_hue(primary, 30) + else: + secondary = shift_hue(primary, 30) + + # Tertiary: use palette[2] only if hue is >30ยฐ different from both primary and secondary + if len(palette) > 2: + ter_h, _, _ = palette[2].to_hsl() + sec_h, _, _ = secondary.to_hsl() + if hue_distance(primary_h, ter_h) > MIN_HUE_DISTANCE and hue_distance(sec_h, ter_h) > MIN_HUE_DISTANCE: + tertiary = palette[2] + else: + # Colors too similar - shift hue by 60ยฐ from primary to stay closer to original + tertiary = shift_hue(primary, 60) + else: + tertiary = shift_hue(primary, 60) + + error = find_error_color(palette) + + # Keep colors vibrant - preserve saturation + h, s, l = primary.to_hsl() + primary_adjusted = Color.from_hsl(h, max(s, 0.7), max(l, 0.65)) + + h, s, l = secondary.to_hsl() + secondary_adjusted = Color.from_hsl(h, max(s, 0.6), max(l, 0.60)) + + h, s, l = tertiary.to_hsl() + tertiary_adjusted = Color.from_hsl(h, max(s, 0.5), max(l, 0.60)) + + # Container colors - darker, more saturated versions of accent colors + def make_container_dark(base: Color) -> Color: + h, s, l = base.to_hsl() + return Color.from_hsl(h, min(s + 0.15, 1.0), max(l - 0.35, 0.15)) + + primary_container = make_container_dark(primary_adjusted) + secondary_container = make_container_dark(secondary_adjusted) + tertiary_container = make_container_dark(tertiary_adjusted) + error_container = make_container_dark(error) + + # Surface: COLORFUL dark - a deep, saturated version of primary + # Heuristic: Shift Cyan (160-200) slightly towards Blue (+10) to avoid "Teal" look + surface_hue, s, _ = palette[0].to_hsl() + if 160 <= surface_hue <= 200: + surface_hue = (surface_hue + 10) % 360 + + # Reduce saturation for warm hues (red/orange/yellow) - they feel overwhelming as surfaces + # Warm hues: 0-60 and 300-360 + if surface_hue < 60 or surface_hue > 300: + surface_saturation_cap = 0.35 # More desaturated for warm colors + elif 60 <= surface_hue < 120: + surface_saturation_cap = 0.50 # Moderate for yellow-greens + else: + surface_saturation_cap = 0.90 # Keep cool colors vibrant + + base_surface = Color.from_hsl(surface_hue, min(s, surface_saturation_cap), 0.5) + + # Preserving saturation (up to the cap) to be true to primary color + surface = adjust_surface(base_surface, surface_saturation_cap, 0.12) + surface_variant = adjust_surface(base_surface, min(0.80, surface_saturation_cap), 0.16) + + # Surface containers - progressive lightness for visual hierarchy (keep primary hue) + surface_container_lowest = adjust_surface(base_surface, 0.85, 0.06) + surface_container_low = adjust_surface(base_surface, 0.85, 0.10) + surface_container = adjust_surface(base_surface, 0.70, 0.20) + surface_container_high = adjust_surface(base_surface, 0.75, 0.18) + surface_container_highest = adjust_surface(base_surface, 0.70, 0.22) + + # Text colors - desaturated + text_h, _, _ = palette[0].to_hsl() + base_on_surface = Color.from_hsl(text_h, 0.05, 0.95) + on_surface = ensure_contrast(base_on_surface, surface, 4.5) + + base_on_surface_variant = Color.from_hsl(text_h, 0.05, 0.70) + on_surface_variant = ensure_contrast(base_on_surface_variant, surface_variant, 4.5) + + outline = ensure_contrast(adjust_surface(palette[0], 0.10, 0.30), surface, 3.0) + outline_variant = ensure_contrast(adjust_surface(palette[0], 0.10, 0.40), surface, 3.0) + + # Contrasting foregrounds - dark text on bright accent colors + dark_fg = Color.from_hsl(palette[0].to_hsl()[0], 0.20, 0.12) # Darker for better contrast + on_primary = ensure_contrast(dark_fg, primary_adjusted, 7.0) # Higher contrast target + on_secondary = ensure_contrast(dark_fg, secondary_adjusted, 7.0) + on_tertiary = ensure_contrast(dark_fg, tertiary_adjusted, 7.0) + on_error = ensure_contrast(dark_fg, error, 7.0) + + # "On" colors for containers - light text on dark containers, tinted with respective color + # Explicitly prefer_light=True since containers in dark mode are dark + on_primary_container = ensure_contrast(Color.from_hsl(primary_h, primary_s, 0.90), primary_container, 4.5, prefer_light=True) + sec_h, sec_s, _ = secondary.to_hsl() + on_secondary_container = ensure_contrast(Color.from_hsl(sec_h, sec_s, 0.90), secondary_container, 4.5, prefer_light=True) + ter_h, ter_s, _ = tertiary.to_hsl() + on_tertiary_container = ensure_contrast(Color.from_hsl(ter_h, ter_s, 0.90), tertiary_container, 4.5, prefer_light=True) + err_h, err_s, _ = error.to_hsl() + on_error_container = ensure_contrast(Color.from_hsl(err_h, err_s, 0.90), error_container, 4.5, prefer_light=True) + + # Shadow and scrim + shadow = surface + scrim = Color(0, 0, 0) # Pure black + + # Inverse colors - for inverted surfaces (light surface on dark theme) + inv_h = palette[0].to_hsl()[0] + inverse_surface = Color.from_hsl(inv_h, 0.08, 0.90) + inverse_on_surface = Color.from_hsl(inv_h, 0.05, 0.15) + inverse_primary = Color.from_hsl(primary_h, max(primary_s * 0.8, 0.5), 0.40) + + # Background aliases (same as surface in MD3) + background = surface + on_background = on_surface + + # Fixed colors - high-chroma accents consistent across light/dark + # In dark mode: lighter versions of accent colors + def make_fixed_dark(base: Color) -> tuple[Color, Color]: + h, s, _ = base.to_hsl() + fixed = Color.from_hsl(h, max(s, 0.70), 0.85) # Light, saturated + fixed_dim = Color.from_hsl(h, max(s, 0.65), 0.75) # Slightly darker + return fixed, fixed_dim + + primary_fixed, primary_fixed_dim = make_fixed_dark(primary_adjusted) + secondary_fixed, secondary_fixed_dim = make_fixed_dark(secondary_adjusted) + tertiary_fixed, tertiary_fixed_dim = make_fixed_dark(tertiary_adjusted) + + # "On" colors for fixed - dark text on light fixed colors + on_primary_fixed = ensure_contrast(Color.from_hsl(primary_h, 0.15, 0.15), primary_fixed, 4.5) + on_primary_fixed_variant = ensure_contrast(Color.from_hsl(primary_h, 0.15, 0.20), primary_fixed_dim, 4.5) + on_secondary_fixed = ensure_contrast(Color.from_hsl(secondary.to_hsl()[0], 0.15, 0.15), secondary_fixed, 4.5) + on_secondary_fixed_variant = ensure_contrast(Color.from_hsl(secondary.to_hsl()[0], 0.15, 0.20), secondary_fixed_dim, 4.5) + on_tertiary_fixed = ensure_contrast(Color.from_hsl(tertiary.to_hsl()[0], 0.15, 0.15), tertiary_fixed, 4.5) + on_tertiary_fixed_variant = ensure_contrast(Color.from_hsl(tertiary.to_hsl()[0], 0.15, 0.20), tertiary_fixed_dim, 4.5) + + # Surface dim - darker than surface for dimmed areas + surface_dim = adjust_surface(base_surface, 0.85, 0.08) + # Surface bright - lighter than surface + surface_bright = adjust_surface(base_surface, 0.75, 0.24) + + return { + # Primary + "primary": primary_adjusted.to_hex(), + "on_primary": on_primary.to_hex(), + "primary_container": primary_container.to_hex(), + "on_primary_container": on_primary_container.to_hex(), + "primary_fixed": primary_fixed.to_hex(), + "primary_fixed_dim": primary_fixed_dim.to_hex(), + "on_primary_fixed": on_primary_fixed.to_hex(), + "on_primary_fixed_variant": on_primary_fixed_variant.to_hex(), + "surface_tint": primary_adjusted.to_hex(), + # Secondary + "secondary": secondary_adjusted.to_hex(), + "on_secondary": on_secondary.to_hex(), + "secondary_container": secondary_container.to_hex(), + "on_secondary_container": on_secondary_container.to_hex(), + "secondary_fixed": secondary_fixed.to_hex(), + "secondary_fixed_dim": secondary_fixed_dim.to_hex(), + "on_secondary_fixed": on_secondary_fixed.to_hex(), + "on_secondary_fixed_variant": on_secondary_fixed_variant.to_hex(), + # Tertiary + "tertiary": tertiary_adjusted.to_hex(), + "on_tertiary": on_tertiary.to_hex(), + "tertiary_container": tertiary_container.to_hex(), + "on_tertiary_container": on_tertiary_container.to_hex(), + "tertiary_fixed": tertiary_fixed.to_hex(), + "tertiary_fixed_dim": tertiary_fixed_dim.to_hex(), + "on_tertiary_fixed": on_tertiary_fixed.to_hex(), + "on_tertiary_fixed_variant": on_tertiary_fixed_variant.to_hex(), + # Error + "error": error.to_hex(), + "on_error": on_error.to_hex(), + "error_container": error_container.to_hex(), + "on_error_container": on_error_container.to_hex(), + # Surface + "surface": surface.to_hex(), + "on_surface": on_surface.to_hex(), + "surface_variant": surface_variant.to_hex(), + "on_surface_variant": on_surface_variant.to_hex(), + "surface_dim": surface_dim.to_hex(), + "surface_bright": surface_bright.to_hex(), + # Surface containers + "surface_container_lowest": surface_container_lowest.to_hex(), + "surface_container_low": surface_container_low.to_hex(), + "surface_container": surface_container.to_hex(), + "surface_container_high": surface_container_high.to_hex(), + "surface_container_highest": surface_container_highest.to_hex(), + # Outline and other + "outline": outline.to_hex(), + "outline_variant": outline_variant.to_hex(), + "shadow": shadow.to_hex(), + "scrim": scrim.to_hex(), + # Inverse + "inverse_surface": inverse_surface.to_hex(), + "inverse_on_surface": inverse_on_surface.to_hex(), + "inverse_primary": inverse_primary.to_hex(), + # Background + "background": background.to_hex(), + "on_background": on_background.to_hex(), + } + + +def generate_normal_light(palette: list[Color]) -> dict[str, str]: + """ + Generate wallust-style light theme from palette. + + More vibrant than Material - uses palette colors directly and keeps + surfaces saturated with the primary hue. Outputs same keys as Material. + """ + # Use extracted colors directly, but check if distinct enough + primary = palette[0] if palette else Color(93, 101, 245) + primary_h, _, _ = primary.to_hsl() + + # Secondary: use palette[1] only if hue is >30ยฐ different + MIN_HUE_DISTANCE = 30 + if len(palette) > 1: + sec_h, _, _ = palette[1].to_hsl() + if hue_distance(primary_h, sec_h) > MIN_HUE_DISTANCE: + secondary = palette[1] + else: + secondary = shift_hue(primary, 30) + else: + secondary = shift_hue(primary, 30) + + # Tertiary: use palette[2] only if hue is >30ยฐ different from both + if len(palette) > 2: + ter_h, _, _ = palette[2].to_hsl() + sec_h, _, _ = secondary.to_hsl() + if hue_distance(primary_h, ter_h) > MIN_HUE_DISTANCE and hue_distance(sec_h, ter_h) > MIN_HUE_DISTANCE: + tertiary = palette[2] + else: + tertiary = shift_hue(primary, 60) + else: + tertiary = shift_hue(primary, 60) + + error = find_error_color(palette) + + # Keep colors vibrant - darken for visibility on light bg + # Clamp lightness to [0.25, 0.45] so colors are never near-black nor washed out + h, s, l = primary.to_hsl() + primary_adjusted = Color.from_hsl(h, max(s, 0.7), max(min(l, 0.45), 0.25)) + + h, s, l = secondary.to_hsl() + secondary_adjusted = Color.from_hsl(h, max(s, 0.6), max(min(l, 0.40), 0.22)) + + h, s, l = tertiary.to_hsl() + tertiary_adjusted = Color.from_hsl(h, max(s, 0.5), max(min(l, 0.35), 0.20)) + + # Container colors - lighter, less saturated versions of accent colors for light mode + def make_container_light(base: Color) -> Color: + h, s, l = base.to_hsl() + return Color.from_hsl(h, max(s - 0.20, 0.30), min(l + 0.35, 0.85)) + + primary_container = make_container_light(primary_adjusted) + secondary_container = make_container_light(secondary_adjusted) + tertiary_container = make_container_light(tertiary_adjusted) + error_container = make_container_light(error) + + # Surface: COLORFUL light - a pastel, saturated version of primary + # Preserving saturation (up to 0.9) to be true to primary color + surface = adjust_surface(palette[0], 0.90, 0.90) + surface_variant = adjust_surface(palette[0], 0.80, 0.78) # Darker than surface + + # Surface containers - progressive darkening for light mode (keep primary hue) + surface_container_lowest = adjust_surface(palette[0], 0.85, 0.96) # Lightest + surface_container_low = adjust_surface(palette[0], 0.85, 0.92) + surface_container = adjust_surface(palette[0], 0.80, 0.86) + surface_container_high = adjust_surface(palette[0], 0.75, 0.84) + surface_container_highest = adjust_surface(palette[0], 0.70, 0.80) # Darkest + + # Foreground colors - tinted with primary hue + text_h, _, _ = palette[0].to_hsl() + base_on_surface = Color.from_hsl(text_h, 0.05, 0.10) + on_surface = ensure_contrast(base_on_surface, surface, 4.5) + + base_on_surface_variant = Color.from_hsl(text_h, 0.05, 0.35) + on_surface_variant = ensure_contrast(base_on_surface_variant, surface_variant, 4.5) + + # Contrasting foregrounds - light text on dark accent colors + light_fg = Color.from_hsl(text_h, 0.1, 0.98) # Brighter for better contrast + on_primary = ensure_contrast(light_fg, primary_adjusted, 7.0) # Higher contrast target + on_secondary = ensure_contrast(light_fg, secondary_adjusted, 7.0) + on_tertiary = ensure_contrast(light_fg, tertiary_adjusted, 7.0) + on_error = ensure_contrast(light_fg, error, 7.0) + + # "On" colors for containers - dark text on light containers, tinted with respective color + # Explicitly prefer_light=False since containers in light mode are light + primary_h, primary_s, _ = primary.to_hsl() + on_primary_container = ensure_contrast(Color.from_hsl(primary_h, primary_s, 0.15), primary_container, 4.5, prefer_light=False) + sec_h, sec_s, _ = secondary.to_hsl() + on_secondary_container = ensure_contrast(Color.from_hsl(sec_h, sec_s, 0.15), secondary_container, 4.5, prefer_light=False) + ter_h, ter_s, _ = tertiary.to_hsl() + on_tertiary_container = ensure_contrast(Color.from_hsl(ter_h, ter_s, 0.15), tertiary_container, 4.5, prefer_light=False) + err_h, err_s, _ = error.to_hsl() + on_error_container = ensure_contrast(Color.from_hsl(err_h, err_s, 0.15), error_container, 4.5, prefer_light=False) + + # Fixed colors - high-chroma accents consistent across light/dark + # In light mode: darker versions of accent colors + def make_fixed_light(base: Color) -> tuple[Color, Color]: + h, s, _ = base.to_hsl() + fixed = Color.from_hsl(h, max(s, 0.70), 0.40) # Darker, saturated + fixed_dim = Color.from_hsl(h, max(s, 0.65), 0.30) # Even darker + return fixed, fixed_dim + + primary_fixed, primary_fixed_dim = make_fixed_light(primary_adjusted) + secondary_fixed, secondary_fixed_dim = make_fixed_light(secondary_adjusted) + tertiary_fixed, tertiary_fixed_dim = make_fixed_light(tertiary_adjusted) + + # "On" colors for fixed - light text on dark fixed colors + on_primary_fixed = ensure_contrast(Color.from_hsl(primary_h, 0.15, 0.90), primary_fixed, 4.5) + on_primary_fixed_variant = ensure_contrast(Color.from_hsl(primary_h, 0.15, 0.85), primary_fixed_dim, 4.5) + on_secondary_fixed = ensure_contrast(Color.from_hsl(secondary.to_hsl()[0], 0.15, 0.90), secondary_fixed, 4.5) + on_secondary_fixed_variant = ensure_contrast(Color.from_hsl(secondary.to_hsl()[0], 0.15, 0.85), secondary_fixed_dim, 4.5) + on_tertiary_fixed = ensure_contrast(Color.from_hsl(tertiary.to_hsl()[0], 0.15, 0.90), tertiary_fixed, 4.5) + on_tertiary_fixed_variant = ensure_contrast(Color.from_hsl(tertiary.to_hsl()[0], 0.15, 0.85), tertiary_fixed_dim, 4.5) + + # Surface dim - slightly darker than surface + surface_dim = adjust_surface(palette[0], 0.85, 0.82) + # Surface bright - brighter than surface + surface_bright = adjust_surface(palette[0], 0.90, 0.95) + + # Outline uses primary hue, more saturated + surface_h, surface_s, _ = palette[0].to_hsl() + outline = ensure_contrast(Color.from_hsl(surface_h, max(surface_s * 0.4, 0.25), 0.65), surface, 3.0) + outline_variant = ensure_contrast(Color.from_hsl(surface_h, max(surface_s * 0.3, 0.20), 0.75), surface, 3.0) + shadow = Color.from_hsl(surface_h, max(surface_s * 0.3, 0.15), 0.80) + scrim = Color(0, 0, 0) # Pure black + + # Inverse colors - for inverted surfaces (dark surface on light theme) + inverse_surface = Color.from_hsl(surface_h, 0.08, 0.15) + inverse_on_surface = Color.from_hsl(surface_h, 0.05, 0.90) + inverse_primary = Color.from_hsl(primary_h, max(primary_s * 0.8, 0.5), 0.70) + + # Background aliases (same as surface in MD3) + background = surface + on_background = on_surface + + return { + # Primary + "primary": primary_adjusted.to_hex(), + "on_primary": on_primary.to_hex(), + "primary_container": primary_container.to_hex(), + "on_primary_container": on_primary_container.to_hex(), + "primary_fixed": primary_fixed.to_hex(), + "primary_fixed_dim": primary_fixed_dim.to_hex(), + "on_primary_fixed": on_primary_fixed.to_hex(), + "on_primary_fixed_variant": on_primary_fixed_variant.to_hex(), + "surface_tint": primary_adjusted.to_hex(), + # Secondary + "secondary": secondary_adjusted.to_hex(), + "on_secondary": on_secondary.to_hex(), + "secondary_container": secondary_container.to_hex(), + "on_secondary_container": on_secondary_container.to_hex(), + "secondary_fixed": secondary_fixed.to_hex(), + "secondary_fixed_dim": secondary_fixed_dim.to_hex(), + "on_secondary_fixed": on_secondary_fixed.to_hex(), + "on_secondary_fixed_variant": on_secondary_fixed_variant.to_hex(), + # Tertiary + "tertiary": tertiary_adjusted.to_hex(), + "on_tertiary": on_tertiary.to_hex(), + "tertiary_container": tertiary_container.to_hex(), + "on_tertiary_container": on_tertiary_container.to_hex(), + "tertiary_fixed": tertiary_fixed.to_hex(), + "tertiary_fixed_dim": tertiary_fixed_dim.to_hex(), + "on_tertiary_fixed": on_tertiary_fixed.to_hex(), + "on_tertiary_fixed_variant": on_tertiary_fixed_variant.to_hex(), + # Error + "error": error.to_hex(), + "on_error": on_error.to_hex(), + "error_container": error_container.to_hex(), + "on_error_container": on_error_container.to_hex(), + # Surface + "surface": surface.to_hex(), + "on_surface": on_surface.to_hex(), + "surface_variant": surface_variant.to_hex(), + "on_surface_variant": on_surface_variant.to_hex(), + "surface_dim": surface_dim.to_hex(), + "surface_bright": surface_bright.to_hex(), + # Surface containers + "surface_container_lowest": surface_container_lowest.to_hex(), + "surface_container_low": surface_container_low.to_hex(), + "surface_container": surface_container.to_hex(), + "surface_container_high": surface_container_high.to_hex(), + "surface_container_highest": surface_container_highest.to_hex(), + # Outline and other + "outline": outline.to_hex(), + "outline_variant": outline_variant.to_hex(), + "shadow": shadow.to_hex(), + "scrim": scrim.to_hex(), + # Inverse + "inverse_surface": inverse_surface.to_hex(), + "inverse_on_surface": inverse_on_surface.to_hex(), + "inverse_primary": inverse_primary.to_hex(), + # Background + "background": background.to_hex(), + "on_background": on_background.to_hex(), + } + + +def generate_muted_dark(palette: list[Color]) -> dict[str, str]: + """ + Generate muted dark theme from palette. + + Designed for monochrome/monotonal wallpapers - preserves the dominant hue + but caps saturation to very low values for a subtle, understated look. + Outputs same keys as Material for compatibility. + """ + # Use primary color's hue but with very low saturation + primary = palette[0] if palette else Color(128, 128, 128) + primary_h, primary_s, primary_l = primary.to_hsl() + + # Derive secondary and tertiary with subtle hue shifts (monochromatic feel) + # Much smaller shifts than normal mode since we want cohesion + secondary = shift_hue(primary, 15) + tertiary = shift_hue(primary, 30) + error = find_error_color(palette) + + # Cap saturation low - this is the key difference from normal mode + MUTED_SAT_PRIMARY = 0.15 + MUTED_SAT_SECONDARY = 0.12 + MUTED_SAT_TERTIARY = 0.10 + MUTED_SAT_SURFACE = 0.08 + + h, s, l = primary.to_hsl() + primary_adjusted = Color.from_hsl(h, min(s, MUTED_SAT_PRIMARY), max(l, 0.65)) + + h, s, l = secondary.to_hsl() + secondary_adjusted = Color.from_hsl(h, min(s, MUTED_SAT_SECONDARY), max(l, 0.60)) + + h, s, l = tertiary.to_hsl() + tertiary_adjusted = Color.from_hsl(h, min(s, MUTED_SAT_TERTIARY), max(l, 0.60)) + + # Container colors - darker, slightly saturated versions + def make_container_dark(base: Color) -> Color: + h, s, l = base.to_hsl() + return Color.from_hsl(h, min(s + 0.05, MUTED_SAT_PRIMARY), max(l - 0.35, 0.15)) + + primary_container = make_container_dark(primary_adjusted) + secondary_container = make_container_dark(secondary_adjusted) + tertiary_container = make_container_dark(tertiary_adjusted) + error_container = make_container_dark(error) + + # Surface: very low saturation, preserving hue for subtle tint + surface_hue = primary_h + base_surface = Color.from_hsl(surface_hue, MUTED_SAT_SURFACE, 0.5) + + surface = adjust_surface(base_surface, MUTED_SAT_SURFACE, 0.12) + surface_variant = adjust_surface(base_surface, MUTED_SAT_SURFACE, 0.16) + + # Surface containers - progressive lightness with minimal saturation + surface_container_lowest = adjust_surface(base_surface, MUTED_SAT_SURFACE, 0.06) + surface_container_low = adjust_surface(base_surface, MUTED_SAT_SURFACE, 0.10) + surface_container = adjust_surface(base_surface, MUTED_SAT_SURFACE, 0.20) + surface_container_high = adjust_surface(base_surface, MUTED_SAT_SURFACE, 0.18) + surface_container_highest = adjust_surface(base_surface, MUTED_SAT_SURFACE, 0.22) + + # Text colors - near-neutral with slight hue tint + base_on_surface = Color.from_hsl(primary_h, 0.03, 0.95) + on_surface = ensure_contrast(base_on_surface, surface, 4.5) + + base_on_surface_variant = Color.from_hsl(primary_h, 0.03, 0.70) + on_surface_variant = ensure_contrast(base_on_surface_variant, surface_variant, 4.5) + + outline = ensure_contrast(Color.from_hsl(primary_h, 0.05, 0.30), surface, 3.0) + outline_variant = ensure_contrast(Color.from_hsl(primary_h, 0.05, 0.40), surface, 3.0) + + # Contrasting foregrounds + dark_fg = Color.from_hsl(primary_h, 0.10, 0.12) + on_primary = ensure_contrast(dark_fg, primary_adjusted, 7.0) + on_secondary = ensure_contrast(dark_fg, secondary_adjusted, 7.0) + on_tertiary = ensure_contrast(dark_fg, tertiary_adjusted, 7.0) + on_error = ensure_contrast(dark_fg, error, 7.0) + + # "On" colors for containers + on_primary_container = ensure_contrast(Color.from_hsl(primary_h, 0.05, 0.90), primary_container, 4.5, prefer_light=True) + sec_h, _, _ = secondary.to_hsl() + on_secondary_container = ensure_contrast(Color.from_hsl(sec_h, 0.05, 0.90), secondary_container, 4.5, prefer_light=True) + ter_h, _, _ = tertiary.to_hsl() + on_tertiary_container = ensure_contrast(Color.from_hsl(ter_h, 0.05, 0.90), tertiary_container, 4.5, prefer_light=True) + err_h, _, _ = error.to_hsl() + on_error_container = ensure_contrast(Color.from_hsl(err_h, 0.05, 0.90), error_container, 4.5, prefer_light=True) + + # Shadow and scrim + shadow = surface + scrim = Color(0, 0, 0) + + # Inverse colors + inverse_surface = Color.from_hsl(primary_h, 0.05, 0.90) + inverse_on_surface = Color.from_hsl(primary_h, 0.03, 0.15) + inverse_primary = Color.from_hsl(primary_h, min(primary_s * 0.5, MUTED_SAT_PRIMARY), 0.40) + + # Background aliases + background = surface + on_background = on_surface + + # Fixed colors - still muted + def make_fixed_dark(base: Color) -> tuple[Color, Color]: + h, s, _ = base.to_hsl() + fixed = Color.from_hsl(h, min(s, MUTED_SAT_PRIMARY), 0.85) + fixed_dim = Color.from_hsl(h, min(s, MUTED_SAT_PRIMARY), 0.75) + return fixed, fixed_dim + + primary_fixed, primary_fixed_dim = make_fixed_dark(primary_adjusted) + secondary_fixed, secondary_fixed_dim = make_fixed_dark(secondary_adjusted) + tertiary_fixed, tertiary_fixed_dim = make_fixed_dark(tertiary_adjusted) + + # "On" colors for fixed + on_primary_fixed = ensure_contrast(Color.from_hsl(primary_h, 0.05, 0.15), primary_fixed, 4.5) + on_primary_fixed_variant = ensure_contrast(Color.from_hsl(primary_h, 0.05, 0.20), primary_fixed_dim, 4.5) + on_secondary_fixed = ensure_contrast(Color.from_hsl(sec_h, 0.05, 0.15), secondary_fixed, 4.5) + on_secondary_fixed_variant = ensure_contrast(Color.from_hsl(sec_h, 0.05, 0.20), secondary_fixed_dim, 4.5) + on_tertiary_fixed = ensure_contrast(Color.from_hsl(ter_h, 0.05, 0.15), tertiary_fixed, 4.5) + on_tertiary_fixed_variant = ensure_contrast(Color.from_hsl(ter_h, 0.05, 0.20), tertiary_fixed_dim, 4.5) + + # Surface dim and bright + surface_dim = adjust_surface(base_surface, MUTED_SAT_SURFACE, 0.08) + surface_bright = adjust_surface(base_surface, MUTED_SAT_SURFACE, 0.24) + + return { + # Primary + "primary": primary_adjusted.to_hex(), + "on_primary": on_primary.to_hex(), + "primary_container": primary_container.to_hex(), + "on_primary_container": on_primary_container.to_hex(), + "primary_fixed": primary_fixed.to_hex(), + "primary_fixed_dim": primary_fixed_dim.to_hex(), + "on_primary_fixed": on_primary_fixed.to_hex(), + "on_primary_fixed_variant": on_primary_fixed_variant.to_hex(), + "surface_tint": primary_adjusted.to_hex(), + # Secondary + "secondary": secondary_adjusted.to_hex(), + "on_secondary": on_secondary.to_hex(), + "secondary_container": secondary_container.to_hex(), + "on_secondary_container": on_secondary_container.to_hex(), + "secondary_fixed": secondary_fixed.to_hex(), + "secondary_fixed_dim": secondary_fixed_dim.to_hex(), + "on_secondary_fixed": on_secondary_fixed.to_hex(), + "on_secondary_fixed_variant": on_secondary_fixed_variant.to_hex(), + # Tertiary + "tertiary": tertiary_adjusted.to_hex(), + "on_tertiary": on_tertiary.to_hex(), + "tertiary_container": tertiary_container.to_hex(), + "on_tertiary_container": on_tertiary_container.to_hex(), + "tertiary_fixed": tertiary_fixed.to_hex(), + "tertiary_fixed_dim": tertiary_fixed_dim.to_hex(), + "on_tertiary_fixed": on_tertiary_fixed.to_hex(), + "on_tertiary_fixed_variant": on_tertiary_fixed_variant.to_hex(), + # Error + "error": error.to_hex(), + "on_error": on_error.to_hex(), + "error_container": error_container.to_hex(), + "on_error_container": on_error_container.to_hex(), + # Surface + "surface": surface.to_hex(), + "on_surface": on_surface.to_hex(), + "surface_variant": surface_variant.to_hex(), + "on_surface_variant": on_surface_variant.to_hex(), + "surface_dim": surface_dim.to_hex(), + "surface_bright": surface_bright.to_hex(), + # Surface containers + "surface_container_lowest": surface_container_lowest.to_hex(), + "surface_container_low": surface_container_low.to_hex(), + "surface_container": surface_container.to_hex(), + "surface_container_high": surface_container_high.to_hex(), + "surface_container_highest": surface_container_highest.to_hex(), + # Outline and other + "outline": outline.to_hex(), + "outline_variant": outline_variant.to_hex(), + "shadow": shadow.to_hex(), + "scrim": scrim.to_hex(), + # Inverse + "inverse_surface": inverse_surface.to_hex(), + "inverse_on_surface": inverse_on_surface.to_hex(), + "inverse_primary": inverse_primary.to_hex(), + # Background + "background": background.to_hex(), + "on_background": on_background.to_hex(), + } + + +def generate_muted_light(palette: list[Color]) -> dict[str, str]: + """ + Generate muted light theme from palette. + + Designed for monochrome/monotonal wallpapers - preserves the dominant hue + but caps saturation to very low values for a subtle, understated look. + Outputs same keys as Material for compatibility. + """ + primary = palette[0] if palette else Color(128, 128, 128) + primary_h, primary_s, _ = primary.to_hsl() + + # Derive secondary and tertiary with subtle hue shifts + secondary = shift_hue(primary, 15) + tertiary = shift_hue(primary, 30) + error = find_error_color(palette) + + # Cap saturation low + MUTED_SAT_PRIMARY = 0.15 + MUTED_SAT_SECONDARY = 0.12 + MUTED_SAT_TERTIARY = 0.10 + MUTED_SAT_SURFACE = 0.08 + + h, s, l = primary.to_hsl() + primary_adjusted = Color.from_hsl(h, min(s, MUTED_SAT_PRIMARY), min(l, 0.45)) + + h, s, l = secondary.to_hsl() + secondary_adjusted = Color.from_hsl(h, min(s, MUTED_SAT_SECONDARY), min(l, 0.40)) + + h, s, l = tertiary.to_hsl() + tertiary_adjusted = Color.from_hsl(h, min(s, MUTED_SAT_TERTIARY), min(l, 0.35)) + + # Container colors - lighter, less saturated + def make_container_light(base: Color) -> Color: + h, s, l = base.to_hsl() + return Color.from_hsl(h, max(s - 0.05, 0.05), min(l + 0.35, 0.85)) + + primary_container = make_container_light(primary_adjusted) + secondary_container = make_container_light(secondary_adjusted) + tertiary_container = make_container_light(tertiary_adjusted) + error_container = make_container_light(error) + + # Surface: very low saturation, preserving hue for subtle tint + surface = adjust_surface(primary, MUTED_SAT_SURFACE, 0.90) + surface_variant = adjust_surface(primary, MUTED_SAT_SURFACE, 0.78) + + # Surface containers - progressive darkening with minimal saturation + surface_container_lowest = adjust_surface(primary, MUTED_SAT_SURFACE, 0.96) + surface_container_low = adjust_surface(primary, MUTED_SAT_SURFACE, 0.92) + surface_container = adjust_surface(primary, MUTED_SAT_SURFACE, 0.86) + surface_container_high = adjust_surface(primary, MUTED_SAT_SURFACE, 0.84) + surface_container_highest = adjust_surface(primary, MUTED_SAT_SURFACE, 0.80) + + # Text colors - near-neutral with slight hue tint + base_on_surface = Color.from_hsl(primary_h, 0.03, 0.10) + on_surface = ensure_contrast(base_on_surface, surface, 4.5) + + base_on_surface_variant = Color.from_hsl(primary_h, 0.03, 0.35) + on_surface_variant = ensure_contrast(base_on_surface_variant, surface_variant, 4.5) + + # Contrasting foregrounds + light_fg = Color.from_hsl(primary_h, 0.05, 0.98) + on_primary = ensure_contrast(light_fg, primary_adjusted, 7.0) + on_secondary = ensure_contrast(light_fg, secondary_adjusted, 7.0) + on_tertiary = ensure_contrast(light_fg, tertiary_adjusted, 7.0) + on_error = ensure_contrast(light_fg, error, 7.0) + + # "On" colors for containers + on_primary_container = ensure_contrast(Color.from_hsl(primary_h, 0.05, 0.15), primary_container, 4.5, prefer_light=False) + sec_h, _, _ = secondary.to_hsl() + on_secondary_container = ensure_contrast(Color.from_hsl(sec_h, 0.05, 0.15), secondary_container, 4.5, prefer_light=False) + ter_h, _, _ = tertiary.to_hsl() + on_tertiary_container = ensure_contrast(Color.from_hsl(ter_h, 0.05, 0.15), tertiary_container, 4.5, prefer_light=False) + err_h, _, _ = error.to_hsl() + on_error_container = ensure_contrast(Color.from_hsl(err_h, 0.05, 0.15), error_container, 4.5, prefer_light=False) + + # Fixed colors - still muted + def make_fixed_light(base: Color) -> tuple[Color, Color]: + h, s, _ = base.to_hsl() + fixed = Color.from_hsl(h, min(s, MUTED_SAT_PRIMARY), 0.40) + fixed_dim = Color.from_hsl(h, min(s, MUTED_SAT_PRIMARY), 0.30) + return fixed, fixed_dim + + primary_fixed, primary_fixed_dim = make_fixed_light(primary_adjusted) + secondary_fixed, secondary_fixed_dim = make_fixed_light(secondary_adjusted) + tertiary_fixed, tertiary_fixed_dim = make_fixed_light(tertiary_adjusted) + + # "On" colors for fixed + on_primary_fixed = ensure_contrast(Color.from_hsl(primary_h, 0.05, 0.90), primary_fixed, 4.5) + on_primary_fixed_variant = ensure_contrast(Color.from_hsl(primary_h, 0.05, 0.85), primary_fixed_dim, 4.5) + on_secondary_fixed = ensure_contrast(Color.from_hsl(sec_h, 0.05, 0.90), secondary_fixed, 4.5) + on_secondary_fixed_variant = ensure_contrast(Color.from_hsl(sec_h, 0.05, 0.85), secondary_fixed_dim, 4.5) + on_tertiary_fixed = ensure_contrast(Color.from_hsl(ter_h, 0.05, 0.90), tertiary_fixed, 4.5) + on_tertiary_fixed_variant = ensure_contrast(Color.from_hsl(ter_h, 0.05, 0.85), tertiary_fixed_dim, 4.5) + + # Surface dim and bright + surface_dim = adjust_surface(primary, MUTED_SAT_SURFACE, 0.82) + surface_bright = adjust_surface(primary, MUTED_SAT_SURFACE, 0.95) + + # Outline + outline = ensure_contrast(Color.from_hsl(primary_h, 0.05, 0.65), surface, 3.0) + outline_variant = ensure_contrast(Color.from_hsl(primary_h, 0.05, 0.75), surface, 3.0) + shadow = Color.from_hsl(primary_h, 0.05, 0.80) + scrim = Color(0, 0, 0) + + # Inverse colors + inverse_surface = Color.from_hsl(primary_h, 0.05, 0.15) + inverse_on_surface = Color.from_hsl(primary_h, 0.03, 0.90) + inverse_primary = Color.from_hsl(primary_h, min(primary_s * 0.5, MUTED_SAT_PRIMARY), 0.70) + + # Background aliases + background = surface + on_background = on_surface + + return { + # Primary + "primary": primary_adjusted.to_hex(), + "on_primary": on_primary.to_hex(), + "primary_container": primary_container.to_hex(), + "on_primary_container": on_primary_container.to_hex(), + "primary_fixed": primary_fixed.to_hex(), + "primary_fixed_dim": primary_fixed_dim.to_hex(), + "on_primary_fixed": on_primary_fixed.to_hex(), + "on_primary_fixed_variant": on_primary_fixed_variant.to_hex(), + "surface_tint": primary_adjusted.to_hex(), + # Secondary + "secondary": secondary_adjusted.to_hex(), + "on_secondary": on_secondary.to_hex(), + "secondary_container": secondary_container.to_hex(), + "on_secondary_container": on_secondary_container.to_hex(), + "secondary_fixed": secondary_fixed.to_hex(), + "secondary_fixed_dim": secondary_fixed_dim.to_hex(), + "on_secondary_fixed": on_secondary_fixed.to_hex(), + "on_secondary_fixed_variant": on_secondary_fixed_variant.to_hex(), + # Tertiary + "tertiary": tertiary_adjusted.to_hex(), + "on_tertiary": on_tertiary.to_hex(), + "tertiary_container": tertiary_container.to_hex(), + "on_tertiary_container": on_tertiary_container.to_hex(), + "tertiary_fixed": tertiary_fixed.to_hex(), + "tertiary_fixed_dim": tertiary_fixed_dim.to_hex(), + "on_tertiary_fixed": on_tertiary_fixed.to_hex(), + "on_tertiary_fixed_variant": on_tertiary_fixed_variant.to_hex(), + # Error + "error": error.to_hex(), + "on_error": on_error.to_hex(), + "error_container": error_container.to_hex(), + "on_error_container": on_error_container.to_hex(), + # Surface + "surface": surface.to_hex(), + "on_surface": on_surface.to_hex(), + "surface_variant": surface_variant.to_hex(), + "on_surface_variant": on_surface_variant.to_hex(), + "surface_dim": surface_dim.to_hex(), + "surface_bright": surface_bright.to_hex(), + # Surface containers + "surface_container_lowest": surface_container_lowest.to_hex(), + "surface_container_low": surface_container_low.to_hex(), + "surface_container": surface_container.to_hex(), + "surface_container_high": surface_container_high.to_hex(), + "surface_container_highest": surface_container_highest.to_hex(), + # Outline and other + "outline": outline.to_hex(), + "outline_variant": outline_variant.to_hex(), + "shadow": shadow.to_hex(), + "scrim": scrim.to_hex(), + # Inverse + "inverse_surface": inverse_surface.to_hex(), + "inverse_on_surface": inverse_on_surface.to_hex(), + "inverse_primary": inverse_primary.to_hex(), + # Background + "background": background.to_hex(), + "on_background": on_background.to_hex(), + } + + +def generate_theme( + palette: list[Color], + mode: ThemeMode, + scheme_type: str = "tonal-spot" +) -> dict[str, str]: + """ + Generate theme for specified mode and scheme type. + + Args: + palette: List of extracted colors + mode: "dark" or "light" + scheme_type: One of "tonal-spot", "fruit-salad", "rainbow", "vibrant", "faithful", "dysfunctional", "muted" + + Returns: + Dictionary of color token names to hex values + """ + # Handle vibrant/faithful/dysfunctional modes (use generate_normal_* functions) + # All three use same theme generation, but different color extraction (handled in palette.py) + if scheme_type in ("vibrant", "faithful", "dysfunctional"): + if mode == "dark": + return generate_normal_dark(palette) + return generate_normal_light(palette) + + # Handle muted mode (low saturation, monochrome wallpapers) + if scheme_type == "muted": + if mode == "dark": + return generate_muted_dark(palette) + return generate_muted_light(palette) + + # All other schemes use Material Design 3 generation + if mode == "dark": + return generate_material_dark(palette, scheme_type) + return generate_material_light(palette, scheme_type) diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/migrate-colorschemes.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/migrate-colorschemes.py new file mode 100644 index 0000000..0f87043 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/migrate-colorschemes.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +import os +import json +import sys +import urllib.request +import urllib.parse +from pathlib import Path + +# Registry URL for color schemes +REGISTRY_URL = "https://raw.githubusercontent.com/noctalia-dev/noctalia-colorschemes/main/registry.json" +RAW_BASE_URL = "https://raw.githubusercontent.com/noctalia-dev/noctalia-colorschemes/main/" + +def is_valid_format(data): + """Check if the scheme data has the new terminal format.""" + for variant in ['dark', 'light']: + if variant in data: + v_data = data[variant] + if isinstance(v_data, dict) and 'terminal' in v_data: + term = v_data['terminal'] + if isinstance(term, dict) and 'normal' in term: + if isinstance(term['normal'], dict) and 'black' in term['normal']: + return True + return False + +def get_registry(): + """Fetch the remote registry to get correct paths for schemes.""" + try: + with urllib.request.urlopen(REGISTRY_URL) as response: + return json.loads(response.read().decode()) + except Exception as e: + print(f"Error fetching registry: {e}") + return None + +def migrate(config_dir): + colorschemes_dir = Path(config_dir) / "colorschemes" + if not colorschemes_dir.exists(): + return + + registry = get_registry() + if not registry: + return + + # Map name to path from registry + theme_map = {t['name']: t['path'] for t in registry.get('themes', [])} + + for scheme_dir in colorschemes_dir.iterdir(): + if not scheme_dir.is_dir(): + continue + + scheme_name = scheme_dir.name + json_file = scheme_dir / f"{scheme_name}.json" + + if not json_file.exists(): + continue + + try: + with open(json_file, 'r') as f: + data = json.load(f) + except Exception: + continue + + if not is_valid_format(data): + print(f"Scheme '{scheme_name}' has old format. Attempting to redownload...") + + # Use registry path if available, otherwise fallback to name + remote_path = theme_map.get(scheme_name, scheme_name) + + # Encode URL parts to handle spaces and special characters + encoded_path = urllib.parse.quote(remote_path) + encoded_name = urllib.parse.quote(scheme_name) + remote_url = f"{RAW_BASE_URL}{encoded_path}/{encoded_name}.json" + + try: + with urllib.request.urlopen(remote_url) as response: + new_data = json.loads(response.read().decode()) + with open(json_file, 'w') as f: + json.dump(new_data, f, indent=2) + + print(f"Successfully migrated '{scheme_name}'") + except Exception as e: + print(f"Failed to migrate '{scheme_name}': {e}") + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: migrate-colorschemes.py ") + sys.exit(1) + + migrate(sys.argv[1]) diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/template-processor.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/template-processor.py new file mode 100644 index 0000000..f7bad69 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/template-processor.py @@ -0,0 +1,347 @@ +#!/usr/bin/env python3 +""" +Noctalia's Template processor - Wallpaper-based color extraction and theme generation. + +A CLI tool that extracts dominant colors from wallpaper images and generates palettes with optional templating. + +Supported scheme types: +- tonal-spot: Default Android 12-13 Material You scheme (recommended) +- content: Preserves source color's chroma with temperature-based tertiary (matugen default) +- fruit-salad: Bold/playful with -50ยฐ hue rotation +- rainbow: Chromatic accents with grayscale neutrals +- monochrome: Pure grayscale M3 scheme (chroma = 0, only error has color) +- vibrant: Prioritizes the most saturated colors regardless of area coverage +- faithful: Prioritizes dominant colors by area, what you see is what you get +- dysfunctional: Like faithful but picks the 2nd most dominant color family +- muted: Preserves hue but caps saturation low (for monochrome/monotonal wallpapers) + +Usage: + python3 template-processor.py IMAGE_OR_JSON [OPTIONS] + +Options: + --scheme-type Scheme type: tonal-spot (default), content, fruit-salad, rainbow, monochrome, vibrant, faithful, dysfunctional, muted + --dark Generate dark theme only + --light Generate light theme only + --both Generate both themes (default) + -o, --output Write JSON output to file (stdout if omitted) + -r, --render Render a template (input_path:output_path) + -c, --config Path to TOML configuration file with template definitions + --mode Theme mode: dark or light + +Input: + Can be an image file (PNG/JPG) or a JSON color palette file. + +Example: + python3 template-processor.py ~/wallpaper.png --scheme-type tonal-spot + python3 template-processor.py ~/wallpaper.png --scheme-type fruit-salad --dark + python3 template-processor.py ~/wallpaper.jpg --dark -o theme.json + python3 template-processor.py ~/wallpaper.png -r template.txt:output.txt + python3 template-processor.py ~/wallpaper.png -c config.toml --mode dark + +Author: Noctalia Team +License: MIT +""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +# Import from lib package +from lib import ( + read_image, ImageReadError, extract_palette, generate_theme, + TemplateRenderer, expand_predefined_scheme, + extract_source_color, source_color_to_rgb, Color, +) +from lib.scheme import inject_terminal_colors + + +def parse_args() -> argparse.Namespace: + """Parse command-line arguments.""" + parser = argparse.ArgumentParser( + prog='template-processor', + description='Extract color palettes from wallpapers and generate themes', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + python3 template-processor.py wallpaper.png # tonal-spot (default), both themes + python3 template-processor.py wallpaper.png --scheme-type content --dark # content scheme, dark only + python3 template-processor.py wallpaper.jpg --dark -o theme.json # output to file + python3 template-processor.py wallpaper.png -r template.txt:output.txt # render template + python3 template-processor.py wallpaper.png -c config.toml --mode dark # render config, dark only + """ + ) + + parser.add_argument( + 'image', + type=Path, + nargs='?', + help='Path to wallpaper image (PNG/JPG) or JSON color palette (not required if --scheme is used)' + ) + + # Scheme type selection + parser.add_argument( + '--scheme-type', + choices=['tonal-spot', 'content', 'fruit-salad', 'rainbow', 'monochrome', 'vibrant', 'faithful', 'dysfunctional', 'muted'], + default='tonal-spot', + help='Color scheme type (default: tonal-spot)' + ) + + # Theme mode (mutually exclusive) + mode_group = parser.add_mutually_exclusive_group() + mode_group.add_argument( + '--dark', + action='store_true', + help='Generate dark theme only' + ) + mode_group.add_argument( + '--light', + action='store_true', + help='Generate light theme only' + ) + mode_group.add_argument( + '--both', + action='store_true', + default=True, + help='Generate both dark and light themes (default)' + ) + + parser.add_argument( + '--output', '-o', + type=Path, + help='Write JSON output to file (stdout if omitted)' + ) + + parser.add_argument( + '--render', '-r', + action='append', + help='Render a template (input_path:output_path)' + ) + + parser.add_argument( + '--config', '-c', + type=Path, + help='Path to TOML configuration file with template definitions' + ) + parser.add_argument( + '--mode', + choices=['dark', 'light'], + help='Theme mode: dark or light' + ) + + parser.add_argument( + '--scheme', + type=Path, + help='Path to predefined scheme JSON file (bypasses image extraction)' + ) + + parser.add_argument( + '--default-mode', + choices=['dark', 'light'], + default='dark', + help='Theme mode to use for "default" in templates (default: dark)' + ) + + return parser.parse_args() + + +def main() -> int: + """Main entry point.""" + args = parse_args() + + # Initialize result dictionary + result: dict[str, dict[str, str]] = {} + + # Determine mode from arguments + if args.mode == 'dark': + modes = ["dark"] + elif args.mode == 'light': + modes = ["light"] + elif args.dark: + modes = ["dark"] + elif args.light: + modes = ["light"] + else: + modes = ["dark", "light"] + + # Path 1: Predefined scheme (--scheme flag) + if args.scheme: + if not args.scheme.exists(): + print(f"Error: Scheme file not found: {args.scheme}", file=sys.stderr) + return 1 + + try: + with open(args.scheme, 'r') as f: + scheme_data = json.load(f) + + # Scheme format: {"dark": {"mPrimary": "#...", ...}, "light": {...}} + # or single mode: {"mPrimary": "#...", ...} + for mode in modes: + if mode in scheme_data: + # Multi-mode format + result[mode] = expand_predefined_scheme(scheme_data[mode], mode) + inject_terminal_colors(result[mode], scheme_data[mode]) + elif "mPrimary" in scheme_data: + # Single-mode format - use same colors for requested mode + result[mode] = expand_predefined_scheme(scheme_data, mode) + inject_terminal_colors(result[mode], scheme_data) + else: + print(f"Error: Invalid scheme format - missing '{mode}' or 'mPrimary'", file=sys.stderr) + return 1 + + except json.JSONDecodeError as e: + print(f"Error parsing scheme JSON: {e}", file=sys.stderr) + return 1 + except KeyError as e: + print(f"Error: Missing required color in scheme: {e}", file=sys.stderr) + return 1 + except Exception as e: + print(f"Error processing scheme: {e}", file=sys.stderr) + return 1 + + # Path 2: Image-based extraction (default) + else: + # Validate image argument is provided + if args.image is None: + print("Error: Image path is required (unless --scheme is used)", file=sys.stderr) + return 1 + + # Validate image path + if not args.image.exists(): + print(f"Error: Image not found: {args.image}", file=sys.stderr) + return 1 + + # Check if input is a JSON palette (Predefined Color Scheme) + if args.image.suffix.lower() == '.json': + try: + with open(args.image, 'r') as f: + input_data = json.load(f) + + # Expect {"colors": ...} or direct dict + colors_data = input_data.get("colors", input_data) + + # Flatten QML-style object structure if needed + # structure: key -> { default: { hex: "#..." } } or key -> "#..." + flat_colors = {} + for k, v in colors_data.items(): + if isinstance(v, dict) and 'default' in v and 'hex' in v['default']: + flat_colors[k] = v['default']['hex'] + elif isinstance(v, str): + flat_colors[k] = v + else: + # Best effort fallback + flat_colors[k] = str(v) + + # Assign to requested modes + for mode in modes: + result[mode] = flat_colors + + except Exception as e: + print(f"Error reading JSON palette: {e}", file=sys.stderr) + return 1 + else: + # Standard Image Extraction + if not args.image.is_file(): + print(f"Error: Not a file: {args.image}", file=sys.stderr) + return 1 + + # Determine scheme type + scheme_type = args.scheme_type + + # M3 schemes use Triangle filter (matches matugen), others use Box + # (sharper downscale preserves distinct color regions for k-means) + m3_schemes = {"tonal-spot", "content", "fruit-salad", "rainbow", "monochrome"} + resize_filter = "Triangle" if scheme_type in m3_schemes else "Box" + + try: + pixels = read_image(args.image, resize_filter) + except ImageReadError as e: + print(f"Error reading image: {e}", file=sys.stderr) + return 1 + except Exception as e: + print(f"Unexpected error reading image: {e}", file=sys.stderr) + return 1 + + # Extract palette based on scheme type: + # - M3 schemes (tonal-spot, fruit-salad, rainbow, content): Use Wu quantizer + Score + # This matches matugen's color extraction exactly + # - vibrant: Use k-means clustering for colorful/blended colors + # - faithful: Use Wu quantizer for primary (dominant by area), k-means for accents + # - dysfunctional: Like faithful but picks 2nd most dominant color family + # - muted: Like count but without chroma filtering (for monochrome wallpapers) + if scheme_type == "vibrant": + # K-means with chroma scoring for vibrant, blended colors + palette = extract_palette(pixels, k=5, scoring="chroma") + elif scheme_type == "faithful": + # K-means with count scoring - picks dominant color by area coverage + # This ensures primary reflects what you actually see in the image + palette = extract_palette(pixels, k=5, scoring="count") + elif scheme_type == "dysfunctional": + # K-means with dysfunctional scoring - picks 2nd most dominant color family + # For when the dominant color is not what you want as primary + palette = extract_palette(pixels, k=5, scoring="dysfunctional") + elif scheme_type == "muted": + # K-means with muted scoring - accepts low/zero chroma colors + # For monochrome/monotonal wallpapers where dominant color has low saturation + palette = extract_palette(pixels, k=5, scoring="muted") + else: + # Wu quantizer + Score algorithm (matches matugen) + source_argb = extract_source_color(pixels) + r, g, b = source_color_to_rgb(source_argb) + palette = [Color(r, g, b)] + + if not palette: + print("Error: Could not extract colors from image", file=sys.stderr) + return 1 + + # Generate theme for each mode + for mode in modes: + result[mode] = generate_theme(palette, mode, scheme_type) + + # Output JSON + json_output = json.dumps(result, indent=2) + + if args.output: + try: + args.output.write_text(json_output) + print(f"Theme written to: {args.output}", file=sys.stderr) + except IOError as e: + print(f"Error writing output: {e}", file=sys.stderr) + return 1 + elif not args.render and not args.config: + print(json_output) + + # Process templates + if args.render or args.config: + image_path = str(args.image) if args.image else None + renderer = TemplateRenderer(result, default_mode=args.default_mode, image_path=image_path, scheme_type=args.scheme_type) + + if args.render: + for render_spec in args.render: + if ':' not in render_spec: + print(f"Error: Invalid render spec (must be input:output): {render_spec}", file=sys.stderr) + continue + + input_str, output_str = render_spec.split(':', 1) + input_path = Path(input_str).expanduser() + output_path = Path(output_str).expanduser() + + if not input_path.exists(): + print(f"Error: Template not found: {input_path}", file=sys.stderr) + continue + + renderer.render_file(input_path, output_path) + + if args.config: + if not args.config.exists(): + print(f"Error: Config file not found: {args.config}", file=sys.stderr) + else: + renderer.process_config_file(args.config) + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/vscode-helper.py b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/vscode-helper.py new file mode 100644 index 0000000..722092a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Scripts/python/src/theming/vscode-helper.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# Finds all installed Noctalia theme extensions for VSCode/VSCodium. + +import sys +from pathlib import Path + + +def find_all_noctalia_themes(extensions_dir: Path, prefix: str) -> list[str]: + # Bail early if the extensions directory doesn't exist + if not extensions_dir.is_dir(): + return [] + # Collect all directories matching the extension prefix + candidates = [d for d in extensions_dir.iterdir() if d.is_dir() and d.name.startswith(prefix)] + # Return theme file paths for all matching extensions + return [str(d / "themes" / "NoctaliaTheme-color-theme.json") for d in candidates] + + +if __name__ == "__main__": + # Resolve ~ in the provided extensions directory path + extensions_dir = Path(sys.argv[1]).expanduser() + prefix = sys.argv[2] if len(sys.argv) > 2 else "noctalia.noctaliatheme-" + + # Print the resolved paths to stdout for the QML Process to capture + results = find_all_noctalia_themes(extensions_dir, prefix) + if results: + for path in results: + print(path) + else: + print(f"No matching extension found in {extensions_dir}", file=sys.stderr) + sys.exit(1) \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Services/Compositor/CompositorService.qml b/arch/.config/quickshell/noctalia-shell/Services/Compositor/CompositorService.qml new file mode 100644 index 0000000..62b6232 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Compositor/CompositorService.qml @@ -0,0 +1,665 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.Control +import qs.Services.UI + +Singleton { + id: root + + // Compositor detection + property bool isHyprland: false + property bool isNiri: false + property bool isSway: false + property bool isMango: false + property bool isLabwc: false + property bool isExtWorkspace: false + property bool isScroll: false + + // Generic workspace and window data + property ListModel workspaces: ListModel {} + property ListModel windows: ListModel {} + property int focusedWindowIndex: -1 + + // Display scale data + property var displayScales: ({}) + property bool displayScalesLoaded: false + + // Overview state (Niri-specific, defaults to false for other compositors) + property bool overviewActive: false + + // Global workspaces flag (workspaces shared across all outputs) + // True for LabWC (stacking compositor), false for tiling WMs with per-output workspaces + property bool globalWorkspaces: false + + // Generic events + signal workspaceChanged + signal activeWindowChanged + signal windowListChanged + + // Backend service loader + property var backend: null + + Component.onCompleted: { + // Load display scales from ShellState + Qt.callLater(() => { + if (typeof ShellState !== 'undefined' && ShellState.isLoaded) { + loadDisplayScalesFromState(); + } + }); + + detectCompositor(); + } + + Connections { + target: typeof ShellState !== 'undefined' ? ShellState : null + function onIsLoadedChanged() { + if (ShellState.isLoaded) { + loadDisplayScalesFromState(); + } + } + } + + function detectCompositor() { + const hyprlandSignature = Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE"); + const niriSocket = Quickshell.env("NIRI_SOCKET"); + const swaySock = Quickshell.env("SWAYSOCK"); + const currentDesktop = Quickshell.env("XDG_CURRENT_DESKTOP"); + const labwcPid = Quickshell.env("LABWC_PID"); + + // Check for MangoWC using XDG_CURRENT_DESKTOP environment variable + // MangoWC sets XDG_CURRENT_DESKTOP=mango + if (currentDesktop && currentDesktop.toLowerCase().includes("mango")) { + isHyprland = false; + isNiri = false; + isSway = false; + isMango = true; + isLabwc = false; + isExtWorkspace = false; + backendLoader.sourceComponent = mangoComponent; + } else if (labwcPid && labwcPid.length > 0) { + isHyprland = false; + isNiri = false; + isSway = false; + isMango = false; + isLabwc = true; + isExtWorkspace = false; + backendLoader.sourceComponent = labwcComponent; + Logger.i("CompositorService", "Detected LabWC with PID: " + labwcPid); + } else if (niriSocket && niriSocket.length > 0) { + isHyprland = false; + isNiri = true; + isSway = false; + isMango = false; + isLabwc = false; + isExtWorkspace = false; + backendLoader.sourceComponent = niriComponent; + } else if (hyprlandSignature && hyprlandSignature.length > 0) { + isHyprland = true; + isNiri = false; + isSway = false; + isMango = false; + isLabwc = false; + isExtWorkspace = false; + backendLoader.sourceComponent = hyprlandComponent; + } else if (swaySock && swaySock.length > 0) { + isHyprland = false; + isNiri = false; + isSway = true; + isMango = false; + isLabwc = false; + isExtWorkspace = false; + isScroll = currentDesktop && currentDesktop.toLowerCase().includes("scroll"); + backendLoader.sourceComponent = swayComponent; + } else { + // Always fallback to ext-workspace-v1 + isHyprland = false; + isNiri = false; + isSway = false; + isMango = false; + isLabwc = false; + isExtWorkspace = true; + backendLoader.sourceComponent = extWorkspaceComponent; + Logger.i("CompositorService", "Using generic ext-workspace backend (no recognized compositor env)"); + } + } + + Loader { + id: backendLoader + onLoaded: { + if (item) { + if (isScroll) { + item.msgCommand = "scrollmsg"; + } + root.backend = item; + setupBackendConnections(); + backend.initialize(); + } + } + } + + // Load display scales from ShellState + function loadDisplayScalesFromState() { + try { + const cached = ShellState.getDisplay(); + if (cached && Object.keys(cached).length > 0) { + displayScales = cached; + displayScalesLoaded = true; + Logger.d("CompositorService", "Loaded display scales from ShellState"); + } else { + // Migration is now handled in Settings.qml + displayScalesLoaded = true; + } + } catch (error) { + Logger.e("CompositorService", "Failed to load display scales:", error); + displayScalesLoaded = true; + } + } + + // Hyprland backend component + Component { + id: hyprlandComponent + HyprlandService { + id: hyprlandBackend + } + } + + // Niri backend component + Component { + id: niriComponent + NiriService { + id: niriBackend + } + } + + // Sway backend component + Component { + id: swayComponent + SwayService { + id: swayBackend + } + } + + // Mango backend component + Component { + id: mangoComponent + MangoService { + id: mangoBackend + } + } + + // Labwc backend component + Component { + id: labwcComponent + LabwcService { + id: labwcBackend + } + } + + // Generic ext-workspace (WindowManager) when compositor env is unknown + Component { + id: extWorkspaceComponent + ExtWorkspaceService { + id: extWorkspaceBackend + } + } + + function setupBackendConnections() { + if (!backend) + return; + + // Connect backend signals to facade signals + backend.workspaceChanged.connect(() => { + // Sync workspaces when they change + syncWorkspaces(); + // Forward the signal + workspaceChanged(); + }); + + backend.activeWindowChanged.connect(() => { + // Only sync focus state, not entire window list + syncFocusedWindow(); + // Forward the signal + activeWindowChanged(); + }); + + backend.windowListChanged.connect(() => { + syncWindows(); + }); + + // Property bindings - use automatic property change signal + backend.focusedWindowIndexChanged.connect(() => { + focusedWindowIndex = backend.focusedWindowIndex; + }); + + // Overview state (Niri-specific) + if (backend.overviewActiveChanged) { + backend.overviewActiveChanged.connect(() => { + overviewActive = backend.overviewActive; + }); + } + + // Initial sync + syncWorkspaces(); + syncWindows(); + focusedWindowIndex = backend.focusedWindowIndex; + if (backend.overviewActive !== undefined) { + overviewActive = backend.overviewActive; + } + if (backend.globalWorkspaces !== undefined) { + globalWorkspaces = backend.globalWorkspaces; + } + } + + function syncWorkspaces() { + workspaces.clear(); + const ws = backend.workspaces; + for (var i = 0; i < ws.count; i++) { + workspaces.append(ws.get(i)); + } + // Emit signal to notify listeners that workspace list has been updated + workspacesChanged(); + } + + function syncWindows() { + windows.clear(); + const ws = backend.windows; + for (var i = 0; i < ws.length; i++) { + windows.append(ws[i]); + } + // Emit signal to notify listeners that window list has been updated + windowListChanged(); + } + + // Sync only the focused window state, not the entire window list + function syncFocusedWindow() { + const newIndex = backend.focusedWindowIndex; + + // Update isFocused flags by syncing from backend + for (var i = 0; i < windows.count && i < backend.windows.length; i++) { + const backendFocused = backend.windows[i].isFocused; + if (windows.get(i).isFocused !== backendFocused) { + windows.setProperty(i, "isFocused", backendFocused); + } + } + + focusedWindowIndex = newIndex; + } + + // Update display scales from backend + function updateDisplayScales() { + if (!backend || !backend.queryDisplayScales) { + Logger.w("CompositorService", "Backend does not support display scale queries"); + return; + } + + backend.queryDisplayScales(); + } + + // Called by backend when display scales are ready + function onDisplayScalesUpdated(scales) { + displayScales = scales; + saveDisplayScalesToCache(); + Logger.d("CompositorService", "Display scales updated"); + } + + // Save display scales to cache + function saveDisplayScalesToCache() { + try { + ShellState.setDisplay(displayScales); + Logger.d("CompositorService", "Saved display scales to ShellState"); + } catch (error) { + Logger.e("CompositorService", "Failed to save display scales:", error); + } + } + + // Public function to get scale for a specific display + function getDisplayScale(displayName) { + if (!displayName || !displayScales[displayName]) { + return 1.0; + } + return displayScales[displayName].scale || 1.0; + } + + // Public function to get all display info for a specific display + function getDisplayInfo(displayName) { + if (!displayName || !displayScales[displayName]) { + return null; + } + return displayScales[displayName]; + } + + // Get focused window + function getFocusedWindow() { + if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.count) { + return windows.get(focusedWindowIndex); + } + return null; + } + + // Get focused screen from compositor + function getFocusedScreen() { + if (backend && backend.getFocusedScreen) { + return backend.getFocusedScreen(); + } + return null; + } + + // Get focused window title + function getFocusedWindowTitle() { + if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.count) { + var title = windows.get(focusedWindowIndex).title; + if (title !== undefined) { + title = title.replace(/(\r\n|\n|\r)/g, ""); + } + return title || ""; + } + return ""; + } + + // Get clean app name from appId + // Extracts the last segment from reverse domain notation (e.g., "org.kde.dolphin" -> "Dolphin") + // Falls back to title if appId is empty + function getCleanAppName(appId, fallbackTitle) { + var name = (appId || "").split(".").pop() || fallbackTitle || "Unknown"; + return name.charAt(0).toUpperCase() + name.slice(1); + } + + function getWindowsForWorkspace(workspaceId) { + var windowsInWs = []; + for (var i = 0; i < windows.count; i++) { + var window = windows.get(i); + if (window.workspaceId === workspaceId) { + // Snapshot to plain JS object so callers never hold live ListModel + // proxies that become invalid when syncWindows() clears the model. + windowsInWs.push({ + id: window.id, + title: window.title, + appId: window.appId, + isFocused: window.isFocused, + workspaceId: window.workspaceId, + handle: window.handle + }); + } + } + return windowsInWs; + } + + // Generic workspace switching + function switchToWorkspace(workspace) { + if (backend && backend.switchToWorkspace) { + backend.switchToWorkspace(workspace); + } else { + Logger.w("Compositor", "No backend available for workspace switching"); + } + } + + // Scrollable workspace content (Niri) + function scrollWorkspaceContent(direction) { + if (backend && backend.scrollWorkspaceContent) { + backend.scrollWorkspaceContent(direction); + } + } + + // Get current workspace + function getCurrentWorkspace() { + for (var i = 0; i < workspaces.count; i++) { + const ws = workspaces.get(i); + if (ws.isFocused) { + return ws; + } + } + return null; + } + + // Get active workspaces + function getActiveWorkspaces() { + const activeWorkspaces = []; + for (var i = 0; i < workspaces.count; i++) { + const ws = workspaces.get(i); + if (ws.isActive) { + activeWorkspaces.push(ws); + } + } + return activeWorkspaces; + } + + // Set focused window + function focusWindow(window) { + if (backend && backend.focusWindow) { + backend.focusWindow(window); + } else { + Logger.w("Compositor", "No backend available for window focus"); + } + } + + // Close window + function closeWindow(window) { + if (backend && backend.closeWindow) { + backend.closeWindow(window); + } else { + Logger.w("Compositor", "No backend available for window closing"); + } + } + + // Spawn command + function spawn(command) { + // Ensure command is a proper JS array (QML lists can behave unexpectedly in some contexts) + const cmdArray = Array.isArray(command) ? command : (command && typeof command === "object" && command.length !== undefined) ? Array.from(command) : [command]; + + Logger.d("CompositorService", `Spawning: ${cmdArray.join(" ")}`); + if (backend && backend.spawn) { + backend.spawn(cmdArray); + } else { + try { + Quickshell.execDetached(cmdArray); + } catch (e) { + Logger.e("CompositorService", "Failed to execute detached:", e); + } + } + } + + // Session management helper for custom commands + function getCustomCommand(action) { + const powerOptions = Settings.data.sessionMenu.powerOptions || []; + for (let i = 0; i < powerOptions.length; i++) { + const option = powerOptions[i]; + if (option.action === action && option.enabled && option.command && option.command.trim() !== "") { + return option.command.trim(); + } + } + return ""; + } + + function executeSessionAction(action, defaultCommand) { + const customCommand = getCustomCommand(action); + if (customCommand) { + Logger.i("Compositor", `Executing custom command for action: ${action} Command: ${customCommand}`); + Quickshell.execDetached(["sh", "-c", customCommand]); + return true; + } + return false; + } + + // Session management + function logout() { + Logger.i("Compositor", "Logout requested"); + if (executeSessionAction("logout")) + return; + + if (backend && backend.logout) { + backend.logout(); + } else { + Logger.w("Compositor", "No backend available for logout"); + } + } + + function shutdown() { + Logger.i("Compositor", "Shutdown requested"); + if (executeSessionAction("shutdown")) + return; + + HooksService.executeSessionHook("shutdown", () => { + Quickshell.execDetached(["sh", "-c", "systemctl poweroff || loginctl poweroff"]); + }); + } + + function reboot() { + Logger.i("Compositor", "Reboot requested"); + if (executeSessionAction("reboot")) + return; + + HooksService.executeSessionHook("reboot", () => { + Quickshell.execDetached(["sh", "-c", "systemctl reboot || loginctl reboot"]); + }); + } + + function userspaceReboot() { + Logger.i("Compositor", "Userspace reboot requested"); + if (executeSessionAction("userspaceReboot")) + return; + + HooksService.executeSessionHook("userspaceReboot", () => { + Quickshell.execDetached(["sh", "-c", "systemctl soft-reboot"]); + }); + } + + function rebootToUefi() { + Logger.i("Compositor", "Reboot to UEFI firmware requested requested"); + if (executeSessionAction("rebootToUefi")) + return; + + HooksService.executeSessionHook("rebootToUefi", () => { + Quickshell.execDetached(["sh", "-c", "systemctl reboot --firmware-setup || loginctl reboot --firmware-setup"]); + }); + } + + function turnOffMonitors() { + Logger.i("Compositor", "Turn off monitors requested"); + if (backend && backend.turnOffMonitors) { + backend.turnOffMonitors(); + } else { + Logger.w("Compositor", "No backend available for turnOffMonitors"); + } + } + + function turnOnMonitors() { + Logger.i("Compositor", "Turn on monitors requested"); + if (backend && backend.turnOnMonitors) { + backend.turnOnMonitors(); + } else { + Logger.w("Compositor", "No backend available for turnOnMonitors"); + } + } + + function suspend() { + Logger.i("Compositor", "Suspend requested"); + if (executeSessionAction("suspend")) + return; + + Quickshell.execDetached(["sh", "-c", "systemctl suspend || loginctl suspend"]); + } + + function lock() { + Logger.i("Compositor", "LockScreen requested"); + if (executeSessionAction("lock")) + return; + + if (PanelService && PanelService.lockScreen) { + PanelService.lockScreen.active = true; + } + } + + function hibernate() { + Logger.i("Compositor", "Hibernate requested"); + if (executeSessionAction("hibernate")) + return; + + Quickshell.execDetached(["sh", "-c", "systemctl hibernate || loginctl hibernate"]); + } + + function cycleKeyboardLayout() { + if (backend && backend.cycleKeyboardLayout) { + backend.cycleKeyboardLayout(); + } + } + + property int lockAndSuspendCheckCount: 0 + + function lockAndSuspend() { + Logger.i("Compositor", "Lock and suspend requested"); + + // if a custom lock command exists, execute it and suspend without wait + if (executeSessionAction("lock")) { + suspend(); + return; + } + + // If already locked, suspend immediately + if (PanelService && PanelService.lockScreen && PanelService.lockScreen.active) { + Logger.i("Compositor", "Screen already locked, suspending"); + suspend(); + return; + } + + // Lock the screen first + try { + if (PanelService && PanelService.lockScreen) { + PanelService.lockScreen.active = true; + lockAndSuspendCheckCount = 0; + + // Wait for lock screen to be confirmed active before suspending + lockAndSuspendTimer.start(); + } else { + Logger.w("Compositor", "Lock screen not available, suspending without lock"); + suspend(); + } + } catch (e) { + Logger.w("Compositor", "Failed to activate lock screen before suspend: " + e); + suspend(); + } + } + + Timer { + id: lockAndSuspendTimer + interval: 100 + repeat: true + running: false + + onTriggered: { + lockAndSuspendCheckCount++; + + // Check if lock screen is now active + if (PanelService && PanelService.lockScreen && PanelService.lockScreen.active) { + // Verify the lock screen component is loaded + if (PanelService.lockScreen.item) { + Logger.i("Compositor", "Lock screen confirmed active, suspending"); + stop(); + lockAndSuspendCheckCount = 0; + suspend(); + } else { + // Lock screen is active but component not loaded yet, wait a bit more + if (lockAndSuspendCheckCount > 20) { + // Max 2 seconds wait + Logger.w("Compositor", "Lock screen active but component not loaded, suspending anyway"); + stop(); + lockAndSuspendCheckCount = 0; + suspend(); + } + } + } else { + // Lock screen not active yet, keep checking + if (lockAndSuspendCheckCount > 30) { + // Max 3 seconds wait + Logger.w("Compositor", "Lock screen failed to activate, suspending anyway"); + stop(); + lockAndSuspendCheckCount = 0; + suspend(); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Compositor/ExtWorkspaceService.qml b/arch/.config/quickshell/noctalia-shell/Services/Compositor/ExtWorkspaceService.qml new file mode 100644 index 0000000..1ed4551 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Compositor/ExtWorkspaceService.qml @@ -0,0 +1,300 @@ +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.WindowManager +import qs.Commons + +// Generic ext-workspace-v1 + toplevel handling +Item { + id: root + + property ListModel workspaces: ListModel {} + property var windows: [] + property int focusedWindowIndex: -1 + property var trackedToplevels: new Set() + + property bool globalWorkspaces: false + + property var nativeWorkspaceMap: ({}) + property var connectedWorkspaces: ({}) + + signal workspaceChanged + signal activeWindowChanged + signal windowListChanged + signal displayScalesChanged + + function initialize() { + updateWindows(); + connectWorkspaceSignals(); + syncWorkspaces(); + Logger.i("ExtWorkspaceService", "Service started (generic ext-workspace-v1)"); + } + + Connections { + target: WindowManager + + function onWindowsetsChanged() { + root.connectWorkspaceSignals(); + Qt.callLater(root.syncWorkspaces); + } + + function onWindowsetProjectionsChanged() { + Qt.callLater(root.syncWorkspaces); + } + } + + Timer { + interval: 500 + running: true + repeat: false + onTriggered: { + if (WindowManager.windowsets.length > 0) { + root.connectWorkspaceSignals(); + root.syncWorkspaces(); + } + } + } + + function connectWorkspaceSignals() { + const nativeWs = WindowManager.windowsets; + const newConnected = {}; + + for (const ws of nativeWs) { + const key = ws.id || ws.toString(); + newConnected[key] = true; + + if (connectedWorkspaces[key]) + continue; + + ws.activeChanged.connect(() => { + Qt.callLater(root.syncWorkspaces); + }); + + ws.urgentChanged.connect(() => { + Qt.callLater(root.syncWorkspaces); + }); + + ws.shouldDisplayChanged.connect(() => { + Qt.callLater(root.syncWorkspaces); + }); + + ws.nameChanged.connect(() => { + Qt.callLater(root.syncWorkspaces); + }); + } + + connectedWorkspaces = newConnected; + } + + function syncWorkspaces() { + const nativeWs = WindowManager.windowsets; + + workspaces.clear(); + nativeWorkspaceMap = {}; + + /* Per-output (projection) index: compositors expose one workspace group per + * monitor with names "1"โ€ฆ"9". A single global idx produced 10โ€“18 on the + * second head because all windowsets were merged into one list. */ + const perOutputNextIdx = {}; + + for (const ws of nativeWs) { + if (!ws.shouldDisplay) { + continue; + } + + let outputName = ""; + if (ws.projection) { + const projScreens = ws.projection.screens; + if (projScreens && projScreens.length > 0) { + outputName = projScreens[0].name || ""; + } + } + + const groupKey = outputName || "_"; + let idx; + const numericName = ws.name && /^\d+$/.test(String(ws.name)) ? parseInt(ws.name, 10) : NaN; + if (!isNaN(numericName) && numericName >= 1) { + idx = numericName; + } else { + if (perOutputNextIdx[groupKey] === undefined) { + perOutputNextIdx[groupKey] = 1; + } + idx = perOutputNextIdx[groupKey]++; + } + + const wsEntry = { + "id": ws.id || idx.toString(), + "idx": idx, + "name": ws.name || ("Workspace " + idx), + "output": outputName, + "isFocused": ws.active, + "isActive": true, + "isUrgent": ws.urgent, + "isOccupied": false, + "oid": ws.id || idx.toString() + }; + + workspaces.append(wsEntry); + nativeWorkspaceMap[wsEntry.id] = ws; + } + + updateWindowWorkspaces(); + workspaceChanged(); + } + + function updateWindowWorkspaces() { + let activeId = ""; + for (let i = 0; i < workspaces.count; i++) { + const ws = workspaces.get(i); + if (ws.isFocused) { + activeId = ws.id; + break; + } + } + + for (let i = 0; i < windows.length; i++) { + if (activeId) { + windows[i].workspaceId = activeId; + } + } + windowListChanged(); + } + + Connections { + target: ToplevelManager.toplevels + function onValuesChanged() { + updateWindows(); + } + } + + function connectToToplevel(toplevel) { + if (!toplevel) + return; + + toplevel.activatedChanged.connect(() => { + Qt.callLater(onToplevelActivationChanged); + }); + + toplevel.titleChanged.connect(() => { + Qt.callLater(updateWindows); + }); + } + + function onToplevelActivationChanged() { + updateWindows(); + activeWindowChanged(); + } + + function updateWindows() { + const newWindows = []; + const toplevels = ToplevelManager.toplevels?.values || []; + + let focusedIdx = -1; + let idx = 0; + + let activeId = ""; + for (let i = 0; i < workspaces.count; i++) { + const ws = workspaces.get(i); + if (ws.isFocused) { + activeId = ws.id; + break; + } + } + + for (const toplevel of toplevels) { + if (!toplevel) + continue; + + if (!trackedToplevels.has(toplevel)) { + connectToToplevel(toplevel); + trackedToplevels.add(toplevel); + } + + const output = (toplevel.screens && toplevel.screens.length > 0) ? (toplevel.screens[0].name || "") : ""; + + const windowId = (toplevel.appId || "") + ":" + idx; + + newWindows.push({ + "id": windowId, + "appId": toplevel.appId || "", + "title": toplevel.title || "", + "output": output, + "workspaceId": activeId || "1", + "isFocused": toplevel.activated || false, + "toplevel": toplevel + }); + + if (toplevel.activated) { + focusedIdx = idx; + } + idx++; + } + windows = newWindows; + focusedWindowIndex = focusedIdx; + + windowListChanged(); + } + + function focusWindow(window) { + if (window.toplevel && typeof window.toplevel.activate === "function") { + window.toplevel.activate(); + } + } + + function closeWindow(window) { + if (window.toplevel && typeof window.toplevel.close === "function") { + window.toplevel.close(); + } + } + + function switchToWorkspace(workspace) { + const nativeWs = nativeWorkspaceMap[workspace.id] || nativeWorkspaceMap[workspace.oid]; + if (nativeWs && nativeWs.canActivate) { + nativeWs.activate(); + } else { + Logger.w("ExtWorkspaceService", "Cannot activate workspace: " + (workspace.name || workspace.id)); + } + } + + function turnOffMonitors() { + try { + Quickshell.execDetached(["wlr-randr", "--off"]); + } catch (e) { + Logger.e("ExtWorkspaceService", "Failed to turn off monitors:", e); + } + } + + function turnOnMonitors() { + try { + Quickshell.execDetached(["wlr-randr", "--on"]); + } catch (e) { + Logger.e("ExtWorkspaceService", "Failed to turn on monitors:", e); + } + } + + function logout() { + const sid = Quickshell.env("XDG_SESSION_ID"); + try { + if (sid && sid.length > 0) { + Quickshell.execDetached(["loginctl", "terminate-session", sid]); + } else { + Logger.w("ExtWorkspaceService", "logout: XDG_SESSION_ID unset; use session menu custom command or compositor-specific backend"); + } + } catch (e) { + Logger.e("ExtWorkspaceService", "Failed to logout:", e); + } + } + + function cycleKeyboardLayout() { + Logger.w("ExtWorkspaceService", "Keyboard layout cycling not supported"); + } + + function queryDisplayScales() { + Logger.w("ExtWorkspaceService", "Display scale queries not supported via ToplevelManager"); + } + + function getFocusedScreen() { + return null; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Compositor/HyprlandService.qml b/arch/.config/quickshell/noctalia-shell/Services/Compositor/HyprlandService.qml new file mode 100644 index 0000000..19cd804 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Compositor/HyprlandService.qml @@ -0,0 +1,615 @@ +import QtQuick +import Quickshell +import Quickshell.Hyprland +import Quickshell.Io +import qs.Commons +import qs.Services.Keyboard + +Item { + id: root + + // Properties that match the facade interface + property ListModel workspaces: ListModel {} + property var windows: [] + property int focusedWindowIndex: -1 + + // Signals that match the facade interface + signal workspaceChanged + signal activeWindowChanged + signal windowListChanged + signal displayScalesChanged + + // Hyprland-specific properties + property bool initialized: false + property var workspaceCache: ({}) + property var windowCache: ({}) + + // Debounce timer for window updates + Timer { + id: updateTimer + interval: 50 + repeat: false + onTriggered: safeUpdate() + } + + // Deferred via Qt.callLater to coalesce workspace updates: onRawEvent calls + // refreshWorkspaces() which triggers onValuesChanged synchronously in the + // same call stack โ€” without deferral the ListModel gets cleared+repopulated + // twice per event. Qt.callLater deduplicates by function identity. + function _deferredWorkspaceUpdate() { + safeUpdateWorkspaces(); + workspaceChanged(); + } + + // Initialization + function initialize() { + if (initialized) + return; + try { + Hyprland.refreshWorkspaces(); + Hyprland.refreshToplevels(); + Qt.callLater(() => { + safeUpdateWorkspaces(); + safeUpdateWindows(); + queryDisplayScales(); + queryKeyboardLayout(); + }); + initialized = true; + Logger.i("HyprlandService", "Service started"); + } catch (e) { + Logger.e("HyprlandService", "Failed to initialize:", e); + } + } + + // Query display scales + function queryDisplayScales() { + hyprlandMonitorsProcess.running = true; + } + + // Hyprland monitors process for display scale detection + Process { + id: hyprlandMonitorsProcess + running: false + command: ["hyprctl", "monitors", "-j"] + + property string accumulatedOutput: "" + + stdout: SplitParser { + onRead: function (line) { + // Accumulate lines instead of parsing each one + hyprlandMonitorsProcess.accumulatedOutput += line; + } + } + + onExited: function (exitCode) { + if (exitCode !== 0 || !accumulatedOutput) { + Logger.e("HyprlandService", "Failed to query monitors, exit code:", exitCode); + accumulatedOutput = ""; + return; + } + + try { + const monitorsData = JSON.parse(accumulatedOutput); + const scales = {}; + + for (const monitor of monitorsData) { + if (monitor.name) { + scales[monitor.name] = { + "name": monitor.name, + "scale": monitor.scale || 1.0, + "width": monitor.width || 0, + "height": monitor.height || 0, + "refresh_rate": monitor.refreshRate || 0, + "x": monitor.x || 0, + "y": monitor.y || 0, + "active_workspace": monitor.activeWorkspace ? monitor.activeWorkspace.id : -1, + "vrr": monitor.vrr || false, + "focused": monitor.focused || false + }; + } + } + + // Notify CompositorService (it will emit displayScalesChanged) + if (CompositorService && CompositorService.onDisplayScalesUpdated) { + CompositorService.onDisplayScalesUpdated(scales); + } + } catch (e) { + Logger.e("HyprlandService", "Failed to parse monitors:", e); + } finally { + // Clear accumulated output for next query + accumulatedOutput = ""; + } + } + } + + function queryKeyboardLayout() { + hyprlandDevicesProcess.running = true; + } + // Hyprland devices process for keyboard layout detection + Process { + id: hyprlandDevicesProcess + running: false + command: ["hyprctl", "devices", "-j"] + + property string accumulatedOutput: "" + + stdout: SplitParser { + onRead: function (line) { + // Accumulate lines instead of parsing each one + hyprlandDevicesProcess.accumulatedOutput += line; + } + } + + onExited: function (exitCode) { + if (exitCode !== 0 || !accumulatedOutput) { + Logger.e("HyprlandService", "Failed to query devices, exit code:", exitCode); + accumulatedOutput = ""; + return; + } + + try { + const devicesData = JSON.parse(accumulatedOutput); + for (const keyboard of devicesData.keyboards) { + if (keyboard.main) { + const layoutName = keyboard.active_keymap; + KeyboardLayoutService.setCurrentLayout(layoutName); + Logger.d("HyprlandService", "Keyboard layout switched:", layoutName); + } + } + } catch (e) { + Logger.e("HyprlandService", "Failed to parse devices:", e); + } finally { + // Clear accumulated output for next query + accumulatedOutput = ""; + } + } + } + + // Safe update wrapper + function safeUpdate() { + safeUpdateWindows(); + safeUpdateWorkspaces(); + workspaceChanged(); + windowListChanged(); + } + + // Safe workspace update + function safeUpdateWorkspaces() { + try { + workspaces.clear(); + workspaceCache = {}; + + if (!Hyprland.workspaces || !Hyprland.workspaces.values) { + return; + } + + const hlWorkspaces = Hyprland.workspaces.values; + const occupiedIds = getOccupiedWorkspaceIds(); + + for (var i = 0; i < hlWorkspaces.length; i++) { + const ws = hlWorkspaces[i]; + if (ws.name && ws.name.startsWith("special:")) + continue; + + const wsData = { + "id": ws.id, + "idx": ws.id, + "name": ws.name || "", + "output": (ws.monitor && ws.monitor.name) ? ws.monitor.name : "", + "isActive": ws.active === true, + "isFocused": ws.focused === true, + "isUrgent": ws.urgent === true, + "isOccupied": occupiedIds[ws.id] === true + }; + + workspaceCache[ws.id] = wsData; + workspaces.append(wsData); + } + } catch (e) { + Logger.e("HyprlandService", "Error updating workspaces:", e); + } + } + + // Get occupied workspace IDs safely + function getOccupiedWorkspaceIds() { + const occupiedIds = {}; + + try { + if (!Hyprland.toplevels || !Hyprland.toplevels.values) { + return occupiedIds; + } + + const hlToplevels = Hyprland.toplevels.values; + for (var i = 0; i < hlToplevels.length; i++) { + const toplevel = hlToplevels[i]; + if (!toplevel) + continue; + try { + const wsId = toplevel.workspace ? toplevel.workspace.id : null; + if (wsId !== null && wsId !== undefined) { + occupiedIds[wsId] = true; + } + } catch (e) + + // Ignore individual toplevel errors + {} + } + } catch (e) + + // Return empty if we can't determine occupancy + {} + + return occupiedIds; + } + + // Safe window update + function safeUpdateWindows() { + try { + const windowsList = []; + windowCache = {}; + + if (!Hyprland.toplevels || !Hyprland.toplevels.values) { + windows = []; + focusedWindowIndex = -1; + return; + } + + const hlToplevels = Hyprland.toplevels.values; + let focusedWindowId = null; + + // Get active workspaces to filter focus + const activeWorkspaceIds = {}; + if (Hyprland.workspaces && Hyprland.workspaces.values) { + const hlWorkspaces = Hyprland.workspaces.values; + for (var j = 0; j < hlWorkspaces.length; j++) { + if (hlWorkspaces[j].active) { + activeWorkspaceIds[hlWorkspaces[j].id] = true; + } + } + } + + for (var i = 0; i < hlToplevels.length; i++) { + const toplevel = hlToplevels[i]; + if (!toplevel) + continue; + const windowData = extractWindowData(toplevel); + if (windowData) { + // If the window claims to be focused, verify it's on an active workspace + if (windowData.isFocused) { + if (!activeWorkspaceIds[windowData.workspaceId]) { + windowData.isFocused = false; + } + } + + // Normalize to a plain, backend-independent window object + const normalized = { + "id": windowData.id ? String(windowData.id) : "", + "title": windowData.title ? String(windowData.title) : "", + "appId": windowData.appId ? String(windowData.appId) : "", + "workspaceId": (typeof windowData.workspaceId === "number" && !isNaN(windowData.workspaceId)) ? windowData.workspaceId : -1, + "isFocused": windowData.isFocused === true, + "output": windowData.output ? String(windowData.output) : "", + "x": (typeof windowData.x === "number" && !isNaN(windowData.x)) ? windowData.x : 0, + "y": (typeof windowData.y === "number" && !isNaN(windowData.y)) ? windowData.y : 0 + }; + + windowsList.push(normalized); + windowCache[normalized.id] = normalized; + + if (normalized.isFocused) { + focusedWindowId = normalized.id; + } + } + } + + windows = toSortedWindowList(windowsList); + + // Resolve focused index from sorted list (order changes after sort) + let newFocusedIndex = -1; + if (focusedWindowId) { + for (let k = 0; k < windows.length; k++) { + if (windows[k].id === focusedWindowId) { + newFocusedIndex = k; + break; + } + } + } + + if (newFocusedIndex !== focusedWindowIndex) { + focusedWindowIndex = newFocusedIndex; + activeWindowChanged(); + } + } catch (e) { + Logger.e("HyprlandService", "Error updating windows:", e); + } + } + + // Extract window data safely from a toplevel + function extractWindowData(toplevel) { + if (!toplevel) + return null; + + try { + // Safely extract properties + const windowId = safeGetProperty(toplevel, "address", ""); + if (!windowId) + return null; + + const appId = getAppId(toplevel); + const title = getAppTitle(toplevel); + const wsId = toplevel.workspace ? toplevel.workspace.id : null; + const focused = toplevel.activated === true; + const output = toplevel.monitor?.name || ""; + + // Extract position + let x = 0; + let y = 0; + try { + const ipcData = toplevel.lastIpcObject; + if (ipcData && ipcData.at) { + x = ipcData.at[0]; + y = ipcData.at[1]; + } else if (typeof toplevel.x !== 'undefined') { + x = toplevel.x; + y = toplevel.y; + } + } catch (e) {} + + // Normalize coordinates to safe numeric values + const safeX = (typeof x === "number" && !isNaN(x)) ? x : 0; + const safeY = (typeof y === "number" && !isNaN(y)) ? y : 0; + + return { + "id": windowId, + "title": title, + "appId": appId, + "workspaceId": wsId || -1, + "isFocused": focused, + "output": output, + "x": safeX, + "y": safeY + }; + } catch (e) { + return null; + } + } + + function toSortedWindowList(windowList) { + return windowList.sort((a, b) => { + // Sort by workspace first (just in case they are mixed) + if (a.workspaceId !== b.workspaceId) { + return a.workspaceId - b.workspaceId; + } + // Then sort by X position (left to right) + if (a.x !== b.x) { + return a.x - b.x; + } + // Then sort by Y position (top to bottom) + if (a.y !== b.y) { + return a.y - b.y; + } + // Fallback to Window ID mapping + return a.id.localeCompare(b.id); + }); + } + + function getAppTitle(toplevel) { + try { + var title = toplevel.wayland.title; + if (title) + return title; + } catch (e) {} + + return safeGetProperty(toplevel, "title", ""); + } + + function getAppId(toplevel) { + if (!toplevel) + return ""; + + var appId = ""; + + // Try the wayland object first! + // From my (Lemmy) testing it works fine so we could probably get rid of all the other attempts below. + // Leaving them in for now, just in case... + try { + appId = toplevel.wayland.appId; + if (appId) + return appId; + } catch (e) {} + + // Try direct properties + appId = safeGetProperty(toplevel, "class", ""); + if (appId) + return appId; + + appId = safeGetProperty(toplevel, "initialClass", ""); + if (appId) + return appId; + + appId = safeGetProperty(toplevel, "appId", ""); + if (appId) + return appId; + + // Try lastIpcObject + try { + const ipcData = toplevel.lastIpcObject; + if (ipcData) { + return String(ipcData.class || ipcData.initialClass || ipcData.appId || ipcData.wm_class || ""); + } + } catch (e) {} + + return ""; + } + + // Safe property getter + function safeGetProperty(obj, prop, defaultValue) { + try { + const value = obj[prop]; + if (value !== undefined && value !== null) { + return String(value); + } + } catch (e) + + // Property access failed + {} + return defaultValue; + } + + function handleActiveLayoutEvent(ev) { + try { + let beforeParenthesis; + const parenthesisPos = ev.lastIndexOf('('); + + if (parenthesisPos === -1) { + beforeParenthesis = ev; + } else { + beforeParenthesis = ev.substring(0, parenthesisPos); + } + + const layoutNameStart = beforeParenthesis.lastIndexOf(',') + 1; + const layoutName = ev.substring(layoutNameStart); + + // Ignore bogus "error" layout reported by virtual keyboards (e.g. wtype) + if (layoutName.toLowerCase() === "error") { + Logger.d("HyprlandService", "Ignoring bogus 'error' layout from activelayout event"); + return; + } + + KeyboardLayoutService.setCurrentLayout(layoutName); + Logger.d("HyprlandService", "Keyboard layout switched:", layoutName); + } catch (e) { + Logger.e("HyprlandService", "Error handling activelayout:", e); + } + } + + // Connections to Hyprland + Connections { + target: Hyprland.workspaces + enabled: initialized + function onValuesChanged() { + Qt.callLater(_deferredWorkspaceUpdate); + } + } + + Connections { + target: Hyprland.toplevels + enabled: initialized + function onValuesChanged() { + updateTimer.restart(); + } + } + + Connections { + target: Hyprland + enabled: initialized + function onRawEvent(event) { + Hyprland.refreshWorkspaces(); + Hyprland.refreshToplevels(); + // Workspace and window updates are deferred โ€” refreshWorkspaces()/ + // refreshToplevels() trigger onValuesChanged which also calls + // Qt.callLater, so the deduplication coalesces into one update. + Qt.callLater(_deferredWorkspaceUpdate); + updateTimer.restart(); + + const monitorsEvents = ["configreloaded", "monitoradded", "monitorremoved", "monitoraddedv2", "monitorremovedv2"]; + + if (monitorsEvents.includes(event.name)) { + Qt.callLater(queryDisplayScales); + } + + if (event.name == "activelayout") { + handleActiveLayoutEvent(event.data); + } + } + } + + // Public functions + function switchToWorkspace(workspace) { + try { + if (workspace.name) { + Hyprland.dispatch(`workspace ${workspace.name}`); + return; + } + Hyprland.dispatch(`workspace ${workspace.idx}`); + } catch (e) { + Logger.e("HyprlandService", "Failed to switch workspace:", e); + } + } + + function focusWindow(window) { + try { + if (!window || !window.id) { + Logger.w("HyprlandService", "Invalid window object for focus"); + return; + } + + const windowId = window.id.toString(); + Hyprland.dispatch(`focuswindow address:0x${windowId}`); + Hyprland.dispatch(`alterzorder top,address:0x${windowId}`); // Bring the focused window to the top (essential for Float Mode) + } catch (e) { + Logger.e("HyprlandService", "Failed to switch window:", e); + } + } + + function closeWindow(window) { + try { + Hyprland.dispatch(`killwindow address:0x${window.id}`); + } catch (e) { + Logger.e("HyprlandService", "Failed to close window:", e); + } + } + + function turnOffMonitors() { + try { + Quickshell.execDetached(["hyprctl", "dispatch", "dpms", "off"]); + } catch (e) { + Logger.e("HyprlandService", "Failed to turn off monitors:", e); + } + } + + function turnOnMonitors() { + try { + Quickshell.execDetached(["hyprctl", "dispatch", "dpms", "on"]); + } catch (e) { + Logger.e("HyprlandService", "Failed to turn on monitors:", e); + } + } + + function logout() { + try { + Quickshell.execDetached(["hyprctl", "dispatch", "exit"]); + } catch (e) { + Logger.e("HyprlandService", "Failed to logout:", e); + } + } + + function cycleKeyboardLayout() { + try { + Quickshell.execDetached(["hyprctl", "switchxkblayout", "all", "next"]); + } catch (e) { + Logger.e("HyprlandService", "Failed to cycle keyboard layout:", e); + } + } + + function getFocusedScreen() { + const hyprMon = Hyprland.focusedMonitor; + if (hyprMon) { + const monitorName = hyprMon.name; + for (let i = 0; i < Quickshell.screens.length; i++) { + if (Quickshell.screens[i].name === monitorName) { + return Quickshell.screens[i]; + } + } + } + return null; + } + + function spawn(command) { + try { + Quickshell.execDetached(["hyprctl", "dispatch", "--", "exec"].concat(command)); + } catch (e) { + Logger.e("HyprlandService", "Failed to spawn command:", e); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Compositor/LabwcService.qml b/arch/.config/quickshell/noctalia-shell/Services/Compositor/LabwcService.qml new file mode 100644 index 0000000..fe24a8b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Compositor/LabwcService.qml @@ -0,0 +1,300 @@ +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.WindowManager +import qs.Commons + +Item { + id: root + + property ListModel workspaces: ListModel {} + property var windows: [] + property int focusedWindowIndex: -1 + property var trackedToplevels: new Set() + + // LabWC typically has global workspaces (shared across all outputs) + property bool globalWorkspaces: true + + // Map from native workspace id to the native Workspace object for activation + property var nativeWorkspaceMap: ({}) + + // Set of workspace objects we've already connected signals to + property var connectedWorkspaces: ({}) + + signal workspaceChanged + signal activeWindowChanged + signal windowListChanged + signal displayScalesChanged + + function initialize() { + updateWindows(); + connectWorkspaceSignals(); + syncWorkspaces(); + Logger.i("LabwcService", "Service started (ext-workspace-v1)"); + } + + // Watch for windowsets being added/removed + Connections { + target: WindowManager + + function onWindowsetsChanged() { + root.connectWorkspaceSignals(); + Qt.callLater(root.syncWorkspaces); + } + + function onWindowsetProjectionsChanged() { + Qt.callLater(root.syncWorkspaces); + } + } + + // Re-check windowsets after a short delay - the Wayland protocol data + // may arrive after init and the changed signal can be missed + Timer { + interval: 500 + running: true + repeat: false + onTriggered: { + if (WindowManager.windowsets.length > 0) { + root.connectWorkspaceSignals(); + root.syncWorkspaces(); + } + } + } + + // Connect to property change signals on each native windowset object + function connectWorkspaceSignals() { + const nativeWs = WindowManager.windowsets; + const newConnected = {}; + + for (const ws of nativeWs) { + const key = ws.id || ws.toString(); + newConnected[key] = true; + + if (connectedWorkspaces[key]) + continue; + + ws.activeChanged.connect(() => { + Qt.callLater(root.syncWorkspaces); + }); + + ws.urgentChanged.connect(() => { + Qt.callLater(root.syncWorkspaces); + }); + + ws.shouldDisplayChanged.connect(() => { + Qt.callLater(root.syncWorkspaces); + }); + + ws.nameChanged.connect(() => { + Qt.callLater(root.syncWorkspaces); + }); + } + + connectedWorkspaces = newConnected; + } + + function syncWorkspaces() { + const nativeWs = WindowManager.windowsets; + + workspaces.clear(); + nativeWorkspaceMap = {}; + + let idx = 1; + + for (const ws of nativeWs) { + // Skip hidden workspaces (shouldDisplay = false means hidden) + if (!ws.shouldDisplay) { + continue; + } + + // Find which outputs this windowset's projection spans + let outputName = ""; + if (ws.projection) { + const projScreens = ws.projection.screens; + if (projScreens && projScreens.length > 0) { + outputName = projScreens[0].name || ""; + } + } + + const wsEntry = { + "id": ws.id || idx.toString(), + "idx": idx, + "name": ws.name || ("Workspace " + idx), + "output": outputName, + "isFocused": ws.active, + "isActive": true, + "isUrgent": ws.urgent, + "isOccupied": false, + "oid": ws.id || idx.toString() + }; + + workspaces.append(wsEntry); + nativeWorkspaceMap[wsEntry.id] = ws; + + idx++; + } + + // Update windows with workspace info + updateWindowWorkspaces(); + workspaceChanged(); + } + + function updateWindowWorkspaces() { + // ext-workspace-v1 doesn't provide window-to-workspace mapping + // Assign all windows to the active workspace + let activeId = ""; + for (let i = 0; i < workspaces.count; i++) { + const ws = workspaces.get(i); + if (ws.isFocused) { + activeId = ws.id; + break; + } + } + + for (let i = 0; i < windows.length; i++) { + if (activeId) { + windows[i].workspaceId = activeId; + } + } + windowListChanged(); + } + + Connections { + target: ToplevelManager.toplevels + function onValuesChanged() { + updateWindows(); + } + } + + function connectToToplevel(toplevel) { + if (!toplevel) + return; + + toplevel.activatedChanged.connect(() => { + Qt.callLater(onToplevelActivationChanged); + }); + + toplevel.titleChanged.connect(() => { + Qt.callLater(updateWindows); + }); + } + + function onToplevelActivationChanged() { + updateWindows(); + activeWindowChanged(); + } + + function updateWindows() { + const newWindows = []; + const toplevels = ToplevelManager.toplevels?.values || []; + + let focusedIdx = -1; + let idx = 0; + + // Find active workspace id + let activeId = ""; + for (let i = 0; i < workspaces.count; i++) { + const ws = workspaces.get(i); + if (ws.isFocused) { + activeId = ws.id; + break; + } + } + + for (const toplevel of toplevels) { + if (!toplevel) + continue; + + if (!trackedToplevels.has(toplevel)) { + connectToToplevel(toplevel); + trackedToplevels.add(toplevel); + } + + // Get output name from toplevel's screen list + const output = (toplevel.screens && toplevel.screens.length > 0) ? (toplevel.screens[0].name || "") : ""; + + // Use appId + title as a stable id since Toplevel has no address property + const windowId = (toplevel.appId || "") + ":" + idx; + + newWindows.push({ + "id": windowId, + "appId": toplevel.appId || "", + "title": toplevel.title || "", + "output": output, + "workspaceId": activeId || "1", + "isFocused": toplevel.activated || false, + "toplevel": toplevel + }); + + if (toplevel.activated) { + focusedIdx = idx; + } + idx++; + } + windows = newWindows; + focusedWindowIndex = focusedIdx; + + windowListChanged(); + } + + function focusWindow(window) { + if (window.toplevel && typeof window.toplevel.activate === "function") { + window.toplevel.activate(); + } + } + + function closeWindow(window) { + if (window.toplevel && typeof window.toplevel.close === "function") { + window.toplevel.close(); + } + } + + function switchToWorkspace(workspace) { + // Find the native Workspace object and activate it directly + const nativeWs = nativeWorkspaceMap[workspace.id] || nativeWorkspaceMap[workspace.oid]; + if (nativeWs && nativeWs.canActivate) { + nativeWs.activate(); + } else { + Logger.w("LabwcService", "Cannot activate workspace: " + (workspace.name || workspace.id)); + } + } + + function turnOffMonitors() { + try { + Quickshell.execDetached(["wlr-randr", "--off"]); + } catch (e) { + Logger.e("LabwcService", "Failed to turn off monitors:", e); + } + } + + function turnOnMonitors() { + try { + Quickshell.execDetached(["wlr-randr", "--on"]); + } catch (e) { + Logger.e("LabwcService", "Failed to turn on monitors:", e); + } + } + + function logout() { + try { + // Exit labwc by sending SIGTERM to $LABWC_PID or using --exit flag + Quickshell.execDetached(["sh", "-c", "labwc --exit || kill -s SIGTERM $LABWC_PID"]); + } catch (e) { + Logger.e("LabwcService", "Failed to logout:", e); + } + } + + function cycleKeyboardLayout() { + Logger.w("LabwcService", "Keyboard layout cycling not supported"); + } + + function queryDisplayScales() { + Logger.w("LabwcService", "Display scale queries not supported via ToplevelManager"); + } + + function getFocusedScreen() { + // de-activated until proper testing + return null; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Compositor/MangoService.qml b/arch/.config/quickshell/noctalia-shell/Services/Compositor/MangoService.qml new file mode 100644 index 0000000..39c34b1 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Compositor/MangoService.qml @@ -0,0 +1,491 @@ +import QtQuick +import Quickshell +import Quickshell.DWL +import Quickshell.Io +import Quickshell.Wayland +import qs.Commons +import qs.Services.Keyboard + +Item { + id: root + + // ===== PUBLIC INTERFACE (CompositorService compatibility) ===== + + property ListModel workspaces: ListModel {} + property var windows: [] + property int focusedWindowIndex: -1 + property bool initialized: false + + signal workspaceChanged + signal activeWindowChanged + signal windowListChanged + signal displayScalesChanged + + // ===== MANGOSERVICE-SPECIFIC PROPERTIES ===== + + property string selectedMonitor: "" + property string currentLayoutSymbol: "" + + // ===== INTERNAL STATE ===== + + QtObject { + id: internal + + // Window-to-tag persistence: Map + property var windowTagMap: ({}) + + // Window-to-output persistence: Map + property var windowOutputMap: ({}) + + // Toplevel-to-ID mapping: Map + property var toplevelIdMap: new Map() + property int windowIdCounter: 0 + + // Output name to index mapping for unique workspace IDs + property var outputIndices: ({}) + property int outputCounter: 0 + + // Monitor scales: Map + property var monitorScales: ({}) + + // Window signature for change detection + property string lastWindowSignature: "" + + // Scale regex + readonly property var scalePattern: /^(\S+)\s+scale_factor\s+(\d+(?:\.\d+)?)$/ + + // Get all screen names mapped to their DwlIpcOutput + function getOutputMap() { + const map = {}; + const screens = Quickshell.screens; + for (let i = 0; i < screens.length; i++) { + const name = screens[i].name; + const dwlOutput = DwlIpc.outputForName(name); + if (dwlOutput) { + map[name] = dwlOutput; + } + } + return map; + } + + // ===== REBUILD WORKSPACES FROM DWL ===== + + function rebuildWorkspaces() { + if (!DwlIpc.available) { + return; + } + + const outputMap = getOutputMap(); + const workspaceList = []; + + for (const outputName in outputMap) { + const output = outputMap[outputName]; + + // Assign stable index to output + if (internal.outputIndices[outputName] === undefined) { + internal.outputIndices[outputName] = internal.outputCounter++; + } + const outputIdx = internal.outputIndices[outputName]; + + // Track selected monitor and layout + if (output.active) { + root.selectedMonitor = outputName; + root.currentLayoutSymbol = output.layoutSymbol; + } + + const tags = output.tags; + for (let ti = 0; ti < tags.length; ti++) { + const tag = tags[ti]; + const tagId = tag.index + 1; // DwlTag.index is zero-based, our IDs are 1-based + const uniqueId = outputIdx * 100 + tagId; + + workspaceList.push({ + id: uniqueId, + idx: tagId, + name: tagId.toString(), + output: outputName, + isActive: tag.active, + isFocused: tag.active && output.active, + isUrgent: tag.urgent, + isOccupied: tag.clientCount > 0 + }); + } + } + + // Sort by unique ID + workspaceList.sort((a, b) => a.id - b.id); + + root.workspaces.clear(); + for (let k = 0; k < workspaceList.length; k++) { + root.workspaces.append(workspaceList[k]); + } + + root.workspaceChanged(); + } + + // ===== UPDATE WINDOWS ===== + + function updateWindows() { + if (!ToplevelManager.toplevels || !DwlIpc.available) { + return; + } + + const outputMap = getOutputMap(); + const toplevels = ToplevelManager.toplevels.values; + const windowList = []; + let newFocusedIdx = -1; + const currentWindows = new Set(); + + // Build per-output state from DWL + // Map + // Always populated (activeTagId needed for visible-window inference), + // title/appId may be empty if no window is focused. + const outputState = {}; + for (const outputName in outputMap) { + const output = outputMap[outputName]; + + // Find active tag for this output + let activeTagId = 1; + const tags = output.tags; + for (let ti = 0; ti < tags.length; ti++) { + if (tags[ti].active) { + activeTagId = tags[ti].index + 1; + break; + } + } + + outputState[outputName] = { + title: output.title || "", + appId: output.appId || "", + activeTagId: activeTagId + }; + + // Ensure output index exists + if (internal.outputIndices[outputName] === undefined) { + internal.outputIndices[outputName] = internal.outputCounter++; + } + } + + for (let i = 0; i < toplevels.length; i++) { + const toplevel = toplevels[i]; + if (!toplevel || toplevel.outliers) { + continue; + } + + const appId = toplevel.appId || toplevel.wayland?.appId || ""; + const title = toplevel.title || toplevel.wayland?.title || ""; + const isFocused = toplevel.activated; + + // Get or assign a stable ID + let windowId; + if (internal.toplevelIdMap.has(toplevel)) { + windowId = internal.toplevelIdMap.get(toplevel); + } else { + windowId = `win-${internal.windowIdCounter++}`; + internal.toplevelIdMap.set(toplevel, windowId); + } + + currentWindows.add(windowId); + + // Determine output + let outputName; + + // Priority 1: Focused window matched to DWL output metadata + if (isFocused && (title || appId)) { + for (const oName in outputState) { + const os = outputState[oName]; + if ((os.title || os.appId) && title === os.title && appId === os.appId) { + outputName = oName; + internal.windowOutputMap[windowId] = oName; + break; + } + } + } + + // Priority 2: Remembered output + if (!outputName && internal.windowOutputMap[windowId]) { + outputName = internal.windowOutputMap[windowId]; + } + + // Priority 3: toplevel.screens (wlr-foreign-toplevel visible screens) + if (!outputName && toplevel.screens && toplevel.screens.length > 0) { + outputName = toplevel.screens[0].name; + } + + // Fallback: selected monitor + if (!outputName) { + outputName = root.selectedMonitor || "DP-1"; + } + + // Determine tag + let tagId = null; + + const os = outputState[outputName]; + if (isFocused && os && !os.consumed && (os.title || os.appId) && title === os.title && appId === os.appId) { + // Focused window: assign to the active tag from DWL metadata + tagId = os.activeTagId; + internal.windowTagMap[windowId] = tagId; + // Consume so a second toplevel with identical title+appId cannot also claim focus + os.consumed = true; + } else if (internal.windowTagMap[windowId] !== undefined) { + // Previously seen window: use remembered tag + tagId = internal.windowTagMap[windowId]; + } + + if (tagId === null) { + // DWL only reports the focused window per output, so we can't + // determine the tag for unfocused windows until they gain focus. + continue; + } + + // Convert to unique workspace ID + const outputIdx = internal.outputIndices[outputName]; + if (outputIdx === undefined) { + Logger.e("MangoService", "No output index for", outputName); + continue; + } + const workspaceId = outputIdx * 100 + tagId; + + windowList.push({ + id: `${outputName}:${appId}:${title}:${i}`, + title: title, + appId: appId, + class: appId, + workspaceId: workspaceId, + isFocused: isFocused, + output: outputName, + handle: toplevel, + fullscreen: toplevel.fullscreen || false, + floating: toplevel.maximized === false && toplevel.fullscreen === false + }); + + if (isFocused) { + newFocusedIdx = windowList.length - 1; + } + } + + // Clean up stale window tracking + if (Object.keys(internal.windowTagMap).length > toplevels.length + 20) { + const newTagMap = {}; + const newOutputMap = {}; + for (const windowId of currentWindows) { + if (internal.windowTagMap[windowId] !== undefined) { + newTagMap[windowId] = internal.windowTagMap[windowId]; + } + if (internal.windowOutputMap[windowId] !== undefined) { + newOutputMap[windowId] = internal.windowOutputMap[windowId]; + } + } + internal.windowTagMap = newTagMap; + internal.windowOutputMap = newOutputMap; + } + + // Check if window list changed + const signature = JSON.stringify(windowList.map(w => w.id + w.workspaceId + w.isFocused)); + if (signature !== internal.lastWindowSignature) { + internal.lastWindowSignature = signature; + root.windows = windowList; + root.windowListChanged(); + } + + if (newFocusedIdx !== root.focusedWindowIndex) { + root.focusedWindowIndex = newFocusedIdx; + root.activeWindowChanged(); + } + } + + // ===== PROCESS SCALES ===== + + function processScales(output) { + const lines = output.trim().split('\n'); + + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + const match = line.match(scalePattern); + + if (match) { + const outputName = match[1]; + const scale = parseFloat(match[2]); + internal.monitorScales[outputName] = scale; + } + } + + const scalesMap = {}; + for (const name in internal.monitorScales) { + scalesMap[name] = { + name: name, + scale: internal.monitorScales[name] || 1.0, + width: 0, + height: 0, + x: 0, + y: 0 + }; + } + + if (CompositorService && CompositorService.onDisplayScalesUpdated) { + CompositorService.onDisplayScalesUpdated(scalesMap); + } + + root.displayScalesChanged(); + } + } + + // ===== DWL CONNECTIONS ===== + + // React to DWL frame events on each output (atomic state updates) + Instantiator { + model: DwlIpc.outputs + delegate: Connections { + required property DwlIpcOutput modelData + target: modelData + + function onFrame() { + internal.rebuildWorkspaces(); + internal.updateWindows(); + } + + function onKbLayoutChanged() { + if (KeyboardLayoutService) { + KeyboardLayoutService.setCurrentLayout(modelData.kbLayout); + } + } + } + } + + // ===== PROCESSES ===== + + // Scale query (mmsg -g -A) - DWL doesn't provide scale info + property QtObject _scaleQuery: Process { + id: scaleQuery + command: ["mmsg", "-g", "-A"] + + property string buffer: "" + + stdout: SplitParser { + onRead: line => { + scaleQuery.buffer += line + "\n"; + } + } + + onExited: code => { + if (code === 0) { + internal.processScales(scaleQuery.buffer); + scaleQuery.buffer = ""; + } + } + } + + // ===== TOPLEVEL MANAGER CONNECTION ===== + + property QtObject _toplevelConnection: Connections { + target: ToplevelManager.toplevels + + function onValuesChanged() { + internal.updateWindows(); + } + } + + // ===== PUBLIC FUNCTIONS ===== + + function initialize() { + if (initialized) { + return; + } + + Logger.i("MangoService", "Initializing MangoWC/DWL compositor integration (DWL protocol)"); + + // Query display scales (only thing still needing mmsg) + scaleQuery.running = true; + + // Initial build from DWL state + if (DwlIpc.available && DwlIpc.outputs.length > 0) { + internal.rebuildWorkspaces(); + internal.updateWindows(); + } + + initialized = true; + } + + function queryDisplayScales() { + scaleQuery.running = true; + } + + function switchToWorkspace(workspace) { + const tagId = workspace.idx || workspace.id || 1; + const outputName = workspace.output || root.selectedMonitor || ""; + + // Use DWL protocol to switch tags + const dwlOutput = DwlIpc.outputForName(outputName); + if (dwlOutput) { + dwlOutput.setTags(1 << (tagId - 1)); // tagId is 1-based, bitmask is 0-based + } else { + // Fallback to mmsg + const cmd = ["mmsg", "-s", "-t", tagId.toString()]; + if (outputName && Object.keys(internal.monitorScales).length > 1) { + cmd.push("-o", outputName); + } + Quickshell.execDetached(cmd); + } + } + + function focusWindow(window) { + if (window && window.handle) { + window.handle.activate(); + } else if (window.workspaceId) { + switchToWorkspace({ + id: window.workspaceId, + output: window.output + }); + } + } + + function closeWindow(window) { + if (window && window.handle) { + window.handle.close(); + } else { + Quickshell.execDetached(["mmsg", "-s", "-d", "killclient"]); + } + } + + function turnOffMonitors() { + const screens = Quickshell.screens; + const cmds = []; + for (let i = 0; i < screens.length; i++) { + cmds.push("mmsg -s -d disable_monitor," + screens[i].name); + } + if (cmds.length > 0) { + Quickshell.execDetached(["sh", "-c", cmds.join(" && ")]); + } + } + + function turnOnMonitors() { + const screens = Quickshell.screens; + const cmds = []; + for (let i = 0; i < screens.length; i++) { + cmds.push("mmsg -s -d enable_monitor," + screens[i].name); + } + if (cmds.length > 0) { + Quickshell.execDetached(["sh", "-c", cmds.join(" && ")]); + } + } + + function logout() { + Quickshell.execDetached(["mmsg", "-s", "-q"]); + } + + function cycleKeyboardLayout() { + Logger.w("MangoService", "Keyboard layout cycling not supported"); + } + + function getFocusedScreen() { + return null; + } + + function spawn(command) { + try { + Quickshell.execDetached(["mmsg", "-s", "-d", "spawn_shell," + command.join(" ")]); + } catch (e) { + Logger.e("MangoService", "Failed to spawn command:", e); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Compositor/NiriService.qml b/arch/.config/quickshell/noctalia-shell/Services/Compositor/NiriService.qml new file mode 100644 index 0000000..a74efb0 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Compositor/NiriService.qml @@ -0,0 +1,294 @@ +import QtQuick +import Quickshell +import Quickshell.Niri +import qs.Commons +import qs.Services.Keyboard + +Item { + id: root + + property int floatingWindowPosition: Number.MAX_SAFE_INTEGER + + property ListModel workspaces: ListModel {} + property var windows: [] + property int focusedWindowIndex: -1 + + property bool overviewActive: false + + property var keyboardLayouts: [] + + signal workspaceChanged + signal activeWindowChanged + signal windowListChanged + signal displayScalesChanged + + property var outputCache: ({}) + property var workspaceCache: ({}) + + function initialize() { + Niri.refreshOutputs(); + Niri.refreshWorkspaces(); + Niri.refreshWindows(); + + Qt.callLater(() => { + safeUpdateOutputs(); + safeUpdateWorkspaces(); + safeUpdateWindows(); + queryDisplayScales(); + }); + + Logger.i("NiriService", "Service started"); + } + + // Connections to the C++ Niri IPC module + Connections { + target: Niri + function onWorkspacesUpdated() { + safeUpdateWorkspaces(); + workspaceChanged(); + } + function onWindowsUpdated() { + safeUpdateWindows(); + windowListChanged(); + activeWindowChanged(); + } + function onOutputsUpdated() { + safeUpdateOutputs(); + queryDisplayScales(); + } + function onOverviewActiveChanged() { + overviewActive = Niri.overviewActive; + } + function onKeyboardLayoutsChanged() { + keyboardLayouts = Niri.keyboardLayoutNames; + const layoutName = Niri.currentKeyboardLayoutName; + if (layoutName) { + KeyboardLayoutService.setCurrentLayout(layoutName); + } + Logger.d("NiriService", "Keyboard layouts changed:", keyboardLayouts.toString()); + } + function onKeyboardLayoutSwitched() { + const layoutName = Niri.currentKeyboardLayoutName; + if (layoutName) { + KeyboardLayoutService.setCurrentLayout(layoutName); + } + Logger.d("NiriService", "Keyboard layout switched:", layoutName); + } + } + + function safeUpdateOutputs() { + const niriOutputs = Niri.outputs.values; + outputCache = {}; + + for (var i = 0; i < niriOutputs.length; i++) { + const output = niriOutputs[i]; + outputCache[output.name] = { + "name": output.name, + "connected": output.connected, + "scale": output.scale, + "width": output.width, + "height": output.height, + "x": output.x, + "y": output.y, + "physical_width": output.physicalWidth, + "physical_height": output.physicalHeight, + "refresh_rate": output.refreshRate, + "vrr_supported": output.vrrSupported, + "vrr_enabled": output.vrrEnabled, + "transform": output.transform + }; + } + } + + function safeUpdateWorkspaces() { + const niriWorkspaces = Niri.workspaces.values; + workspaceCache = {}; + + const workspacesList = []; + for (var i = 0; i < niriWorkspaces.length; i++) { + const ws = niriWorkspaces[i]; + const wsData = { + "id": ws.id, + "idx": ws.idx, + "name": ws.name, + "output": ws.output, + "isFocused": ws.focused, + "isActive": ws.active, + "isUrgent": ws.urgent, + "isOccupied": ws.occupied + }; + workspacesList.push(wsData); + workspaceCache[ws.id] = wsData; + } + + // Workspaces come pre-sorted from C++ (by output then idx) + workspaces.clear(); + for (var j = 0; j < workspacesList.length; j++) { + workspaces.append(workspacesList[j]); + } + } + + function getWindowOutput(win) { + for (var i = 0; i < workspaces.count; i++) { + if (workspaces.get(i).id === win.workspaceId) { + return workspaces.get(i).output; + } + } + return null; + } + + function toSortedWindowList(windowList) { + return windowList.map(win => { + const workspace = workspaceCache[win.workspaceId]; + const output = (workspace && workspace.output) ? outputCache[workspace.output] : null; + + return { + window: win, + workspaceIdx: workspace ? workspace.idx : 0, + outputX: output ? output.x : 0, + outputY: output ? output.y : 0 + }; + }).sort((a, b) => { + // Sort by output position first + if (a.outputX !== b.outputX) { + return a.outputX - b.outputX; + } + if (a.outputY !== b.outputY) { + return a.outputY - b.outputY; + } + // Then by workspace index + if (a.workspaceIdx !== b.workspaceIdx) { + return a.workspaceIdx - b.workspaceIdx; + } + // Then by window position + if (a.window.position.x !== b.window.position.x) { + return a.window.position.x - b.window.position.x; + } + if (a.window.position.y !== b.window.position.y) { + return a.window.position.y - b.window.position.y; + } + // Finally by window ID to ensure consistent ordering + return a.window.id - b.window.id; + }).map(info => info.window); + } + + function safeUpdateWindows() { + const niriWindows = Niri.windows.values; + const windowsList = []; + + for (var i = 0; i < niriWindows.length; i++) { + const win = niriWindows[i]; + windowsList.push({ + "id": win.id, + "title": win.title || "", + "appId": win.appId || "", + "workspaceId": win.workspaceId || -1, + "isFocused": win.focused, + "output": win.output || getWindowOutput(win) || "", + "position": { + "x": win.isFloating ? floatingWindowPosition : win.positionX, + "y": win.isFloating ? floatingWindowPosition : win.positionY + } + }); + } + + windows = toSortedWindowList(windowsList); + safeUpdateFocusedWindow(); + } + + function safeUpdateFocusedWindow() { + focusedWindowIndex = -1; + for (var i = 0; i < windows.length; i++) { + if (windows[i].isFocused) { + focusedWindowIndex = i; + break; + } + } + } + + function queryDisplayScales() { + if (CompositorService && CompositorService.onDisplayScalesUpdated) { + CompositorService.onDisplayScalesUpdated(outputCache); + } + } + + function switchToWorkspace(workspace) { + try { + Niri.dispatch(["focus-workspace", workspace.idx.toString()]); + } catch (e) { + Logger.e("NiriService", "Failed to switch workspace:", e); + } + } + + function scrollWorkspaceContent(direction) { + try { + var action = direction < 0 ? "focus-column-left" : "focus-column-right"; + Niri.dispatch([action]); + } catch (e) { + Logger.e("NiriService", "Failed to scroll workspace content:", e); + } + } + + function focusWindow(window) { + try { + Niri.dispatch(["focus-window", "--id", window.id.toString()]); + } catch (e) { + Logger.e("NiriService", "Failed to switch window:", e); + } + } + + function closeWindow(window) { + try { + Niri.dispatch(["close-window", "--id", window.id.toString()]); + } catch (e) { + Logger.e("NiriService", "Failed to close window:", e); + } + } + + function turnOffMonitors() { + try { + Niri.dispatch(["power-off-monitors"]); + } catch (e) { + Logger.e("NiriService", "Failed to turn off monitors:", e); + } + } + + function turnOnMonitors() { + try { + Niri.dispatch(["power-on-monitors"]); + } catch (e) { + Logger.e("NiriService", "Failed to turn on monitors:", e); + } + } + + function logout() { + try { + Niri.dispatch(["quit", "--skip-confirmation"]); + } catch (e) { + Logger.e("NiriService", "Failed to logout:", e); + } + } + + function cycleKeyboardLayout() { + try { + Niri.dispatch(["switch-layout", "next"]); + } catch (e) { + Logger.e("NiriService", "Failed to cycle keyboard layout:", e); + } + } + + function getFocusedScreen() { + // On niri the code below only works when you have an actual app selected on that screen. + return null; + } + + function spawn(command) { + try { + const niriArgs = ["spawn", "--"].concat(command); + Logger.d("NiriService", "Calling niri spawn: niri msg action " + niriArgs.join(" ")); + Niri.dispatch(niriArgs); + } catch (e) { + Logger.e("NiriService", "Failed to spawn command:", e); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Compositor/SwayService.qml b/arch/.config/quickshell/noctalia-shell/Services/Compositor/SwayService.qml new file mode 100644 index 0000000..397c114 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Compositor/SwayService.qml @@ -0,0 +1,593 @@ +import QtQuick +import Quickshell +import Quickshell.I3 +import Quickshell.Io +import Quickshell.Wayland +import qs.Commons +import qs.Services.Keyboard + +Item { + id: root + + // Configurable IPC command name (overridden to "scrollmsg" for Scroll) + property string msgCommand: "swaymsg" + + // Properties that match the facade interface + property ListModel workspaces: ListModel {} + property var windows: [] + property int focusedWindowIndex: -1 + + // Signals that match the facade interface + signal workspaceChanged + signal activeWindowChanged + signal windowListChanged + signal displayScalesChanged + + // I3-specific properties + property bool initialized: false + + // Cache for window-to-workspace mapping + property var windowWorkspaceMap: ({}) + + // Track window usage counts per workspace to handle duplicates + property var windowUsageCountsPerWorkspace: ({}) + + // Debounce timer for updates + Timer { + id: updateTimer + interval: 50 + repeat: false + onTriggered: safeUpdate() + } + + // Initialization + function initialize() { + if (initialized) + return; + try { + I3.refreshWorkspaces(); + Qt.callLater(() => { + safeUpdateWorkspaces(); + queryWindowWorkspaces(); + queryDisplayScales(); + queryKeyboardLayout(); + }); + initialized = true; + Logger.i("SwayService", "Service started"); + } catch (e) { + Logger.e("SwayService", "Failed to initialize:", e); + } + } + + // Query window-to-workspace mapping via IPC + function queryWindowWorkspaces() { + swayTreeProcess.running = true; + } + + // Sway tree process for getting window workspace information + Process { + id: swayTreeProcess + running: false + command: [msgCommand, "-t", "get_tree", "-r"] + + property string accumulatedOutput: "" + + stdout: SplitParser { + onRead: function (line) { + swayTreeProcess.accumulatedOutput += line; + } + } + + onExited: function (exitCode) { + if (exitCode !== 0 || !accumulatedOutput) { + Logger.e("SwayService", "Failed to query tree, exit code:", exitCode); + accumulatedOutput = ""; + return; + } + + try { + const treeData = JSON.parse(accumulatedOutput); + const newMap = {}; + const workspaceWindows = {}; // Track windows per workspace + + // Recursively find all windows and their workspaces + function traverseTree(node, workspaceNum) { + if (!node) + return; + + // If this is a workspace node, update the workspace number + if (node.type === "workspace" && node.num !== undefined) { + workspaceNum = node.num; + if (!workspaceWindows[workspaceNum]) { + workspaceWindows[workspaceNum] = []; + } + } + + // If this is a regular or floating container with app_id/class (i.e., a window) + if ((node.type === "con" || node.type === "floating_con") && (node.app_id || node.window_properties)) { + const appId = node.app_id || (node.window_properties ? node.window_properties.class : null); + const title = node.name || ""; + const id = node.id; + + if (appId && workspaceNum !== undefined && workspaceNum >= 0) { + // Store window info for this workspace + workspaceWindows[workspaceNum].push({ + appId: appId, + title: title, + id: id + }); + } + } + + // Traverse children + if (node.nodes && node.nodes.length > 0) { + for (const child of node.nodes) { + traverseTree(child, workspaceNum); + } + } + + // Traverse floating nodes + if (node.floating_nodes && node.floating_nodes.length > 0) { + for (const child of node.floating_nodes) { + traverseTree(child, workspaceNum); + } + } + } + + traverseTree(treeData, -1); + + // Now build the map with workspace-specific keys + for (const wsNum in workspaceWindows) { + const windows = workspaceWindows[wsNum]; + const appTitleCounts = {}; // Count occurrences of each appId:title in this workspace + + for (const win of windows) { + const baseKey = `${win.appId}:${win.title}`; + + // Track how many times we've seen this appId:title combo in this workspace + if (!appTitleCounts[baseKey]) { + appTitleCounts[baseKey] = 0; + } + const occurrence = appTitleCounts[baseKey]; + appTitleCounts[baseKey]++; + + // Create unique key with workspace and occurrence index + const uniqueKey = `ws${wsNum}:${baseKey}[${occurrence}]`; + newMap[uniqueKey] = parseInt(wsNum); + + // Also store by ID if available (most reliable) + if (win.id) { + newMap[`id:${win.id}`] = parseInt(wsNum); + } + } + } + + windowWorkspaceMap = newMap; + + // Update windows with new workspace information + Qt.callLater(safeUpdateWindows); + } catch (e) { + Logger.e("SwayService", "Failed to parse tree:", e); + } finally { + accumulatedOutput = ""; + } + } + } + + // Query display scales + function queryDisplayScales() { + swayOutputsProcess.running = true; + } + + // Sway outputs process for display scale detection + Process { + id: swayOutputsProcess + running: false + command: [msgCommand, "-t", "get_outputs", "-r"] + + property string accumulatedOutput: "" + + stdout: SplitParser { + onRead: function (line) { + swayOutputsProcess.accumulatedOutput += line; + } + } + + onExited: function (exitCode) { + if (exitCode !== 0 || !accumulatedOutput) { + Logger.e("SwayService", "Failed to query outputs, exit code:", exitCode); + accumulatedOutput = ""; + return; + } + + try { + const outputsData = JSON.parse(accumulatedOutput); + const scales = {}; + + for (const output of outputsData) { + if (output.name) { + scales[output.name] = { + "name": output.name, + "scale": output.scale || 1.0, + "width": output.current_mode ? output.current_mode.width : 0, + "height": output.current_mode ? output.current_mode.height : 0, + "refresh_rate": output.current_mode ? output.current_mode.refresh : 0, + "x": output.rect ? output.rect.x : 0, + "y": output.rect ? output.rect.y : 0, + "active": output.active || false, + "focused": output.focused || false, + "current_workspace": output.current_workspace || "" + }; + } + } + + // Notify CompositorService (it will emit displayScalesChanged) + if (CompositorService && CompositorService.onDisplayScalesUpdated) { + CompositorService.onDisplayScalesUpdated(scales); + } + } catch (e) { + Logger.e("SwayService", "Failed to parse outputs:", e); + } finally { + // Clear accumulated output for next query + accumulatedOutput = ""; + } + } + } + + function queryKeyboardLayout() { + swayInputsProcess.running = true; + } + // Sway inputs process for keyboard layout detection + Process { + id: swayInputsProcess + running: false + command: [msgCommand, "-t", "get_inputs", "-r"] + + property string accumulatedOutput: "" + + stdout: SplitParser { + onRead: function (line) { + // Accumulate lines instead of parsing each one + swayInputsProcess.accumulatedOutput += line; + } + } + + onExited: function (exitCode) { + if (exitCode !== 0 || !accumulatedOutput) { + Logger.e("SwayService", "Failed to query inputs, exit code:", exitCode); + accumulatedOutput = ""; + return; + } + + try { + const inputsData = JSON.parse(accumulatedOutput); + for (const input of inputsData) { + if (input.type == "keyboard") { + const layoutName = input.xkb_active_layout_name; + KeyboardLayoutService.setCurrentLayout(layoutName); + Logger.d("SwayService", "Keyboard layout switched:", layoutName); + break; + } + } + } catch (e) { + Logger.e("SwayService", "Failed to parse inputs:", e); + } finally { + // Clear accumulated output for next query + accumulatedOutput = ""; + } + } + } + + // Safe update wrapper + function safeUpdate() { + queryWindowWorkspaces(); + safeUpdateWorkspaces(); + } + + // Safe workspace update + function safeUpdateWorkspaces() { + try { + workspaces.clear(); + + if (!I3.workspaces || !I3.workspaces.values) { + return; + } + + const hlWorkspaces = I3.workspaces.values; + + for (var i = 0; i < hlWorkspaces.length; i++) { + const ws = hlWorkspaces[i]; + if (!ws || ws.id < 1) + continue; + const wsData = { + "id": i, + "idx": ws.num, + "name": ws.name || "", + "output": (ws.monitor && ws.monitor.name) ? ws.monitor.name : "", + "isActive": ws.active === true, + "isFocused": ws.focused === true, + "isUrgent": ws.urgent === true, + "isOccupied": true, + "handle": ws + }; + + workspaces.append(wsData); + } + } catch (e) { + Logger.e("SwayService", "Error updating workspaces:", e); + } + } + + // Safe window update + function safeUpdateWindows() { + try { + const windowsList = []; + + // Reset usage counts per workspace before processing windows + windowUsageCountsPerWorkspace = {}; + + if (!ToplevelManager.toplevels || !ToplevelManager.toplevels.values) { + windows = []; + focusedWindowIndex = -1; + windowListChanged(); + return; + } + + const hlToplevels = ToplevelManager.toplevels.values; + let newFocusedIndex = -1; + + for (var i = 0; i < hlToplevels.length; i++) { + const toplevel = hlToplevels[i]; + if (!toplevel) + continue; + const windowData = extractWindowData(toplevel); + if (windowData) { + windowsList.push(windowData); + + if (windowData.isFocused) { + newFocusedIndex = windowsList.length - 1; + } + } + } + + windows = windowsList; + + if (newFocusedIndex !== focusedWindowIndex) { + focusedWindowIndex = newFocusedIndex; + activeWindowChanged(); + } + + windowListChanged(); + } catch (e) { + Logger.e("SwayService", "Error updating windows:", e); + } + } + + // Extract window data safely from a toplevel + function extractWindowData(toplevel) { + if (!toplevel) + return null; + + try { + // Safely extract properties + const appId = getAppId(toplevel); + const title = safeGetProperty(toplevel, "title", ""); + const focused = toplevel.activated === true; + + // Try to find workspace ID from our cached map by trying all workspaces + let workspaceId = -1; + let foundWorkspaceNum = -1; + + // Build base key for this window + const baseKey = `${appId}:${title}`; + + // Try to find this window in any workspace + for (var i = 0; i < workspaces.count; i++) { + const ws = workspaces.get(i); + if (!ws) + continue; + + const wsNum = ws.idx; + + // Initialize usage count for this workspace if needed + if (!windowUsageCountsPerWorkspace[wsNum]) { + windowUsageCountsPerWorkspace[wsNum] = {}; + } + + // Get current usage count for this appId:title in this workspace + if (!windowUsageCountsPerWorkspace[wsNum][baseKey]) { + windowUsageCountsPerWorkspace[wsNum][baseKey] = 0; + } + + const occurrence = windowUsageCountsPerWorkspace[wsNum][baseKey]; + const uniqueKey = `ws${wsNum}:${baseKey}[${occurrence}]`; + + // Check if this key exists in our map + if (windowWorkspaceMap[uniqueKey] !== undefined) { + foundWorkspaceNum = windowWorkspaceMap[uniqueKey]; + workspaceId = ws.id; + + // Increment the usage count for this workspace + windowUsageCountsPerWorkspace[wsNum][baseKey]++; + break; + } + } + + return { + "title": title, + "appId": appId, + "isFocused": focused, + "workspaceId": workspaceId, + "handle": toplevel + }; + } catch (e) { + return null; + } + } + + function getAppId(toplevel) { + if (!toplevel) + return ""; + + return toplevel.appId; + } + + // Safe property getter + function safeGetProperty(obj, prop, defaultValue) { + try { + const value = obj[prop]; + if (value !== undefined && value !== null) { + return String(value); + } + } catch (e) + + // Property access failed + {} + return defaultValue; + } + + function handleInputEvent(ev) { + try { + const eventData = JSON.parse(ev); + if (eventData.change == "xkb_layout" && eventData.input != null) { + const input = eventData.input; + if (input.type == "keyboard" && input.xkb_active_layout_name != null) { + const layoutName = input.xkb_active_layout_name; + KeyboardLayoutService.setCurrentLayout(layoutName); + Logger.d("SwayService", "Keyboard layout switched:", layoutName); + } + } + } catch (e) { + Logger.e("SwayService", "Error handling input event:", e); + } + } + + // Connections to I3 + Connections { + target: I3.workspaces + enabled: initialized + function onValuesChanged() { + safeUpdateWorkspaces(); + workspaceChanged(); + } + } + + Connections { + target: ToplevelManager + enabled: initialized + function onActiveToplevelChanged() { + updateTimer.restart(); + } + } + + // Some programs change title of window dependent on content + Connections { + target: ToplevelManager ? ToplevelManager.activeToplevel : null + enabled: initialized + function onTitleChanged() { + updateTimer.restart(); + } + } + + Connections { + target: I3 + enabled: initialized + function onRawEvent(event) { + safeUpdateWorkspaces(); + workspaceChanged(); + updateTimer.restart(); + + if (event.type === "output") { + Qt.callLater(queryDisplayScales); + } + } + } + + I3IpcListener { + subscriptions: ["input"] + onIpcEvent: function (event) { + handleInputEvent(event.data); + } + } + + // Public functions + function switchToWorkspace(workspace) { + try { + workspace.handle.activate(); + } catch (e) { + Logger.e("SwayService", "Failed to switch workspace:", e); + } + } + + function focusWindow(window) { + try { + window.handle.activate(); + } catch (e) { + Logger.e("SwayService", "Failed to switch window:", e); + } + } + + function closeWindow(window) { + try { + window.handle.close(); + } catch (e) { + Logger.e("SwayService", "Failed to close window:", e); + } + } + + function turnOffMonitors() { + try { + Quickshell.execDetached([msgCommand, "output", "*", "dpms", "off"]); + } catch (e) { + Logger.e("SwayService", "Failed to turn off monitors:", e); + } + } + + function turnOnMonitors() { + try { + Quickshell.execDetached([msgCommand, "output", "*", "dpms", "on"]); + } catch (e) { + Logger.e("SwayService", "Failed to turn on monitors:", e); + } + } + + function logout() { + try { + Quickshell.execDetached([msgCommand, "exit"]); + } catch (e) { + Logger.e("SwayService", "Failed to logout:", e); + } + } + + function cycleKeyboardLayout() { + try { + Quickshell.execDetached([msgCommand, "input", "type:keyboard", "xkb_switch_layout", "next"]); + } catch (e) { + Logger.e("SwayService", "Failed to cycle keyboard layout:", e); + } + } + + function getFocusedScreen() { + // de-activated until proper testing + return null; + + // const i3Mon = I3.focusedMonitor; + // if (i3Mon) { + // const monitorName = i3Mon.name; + // for (let i = 0; i < Quickshell.screens.length; i++) { + // if (Quickshell.screens[i].name === monitorName) { + // return Quickshell.screens[i]; + // } + // } + // } + // return null; + } + + function spawn(command) { + try { + Quickshell.execDetached([msgCommand, "exec", "--"].concat(command)); + } catch (e) { + Logger.e("SwayService", "Failed to spawn command:", e); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Control/CurrentScreenDetector.qml b/arch/.config/quickshell/noctalia-shell/Services/Control/CurrentScreenDetector.qml new file mode 100644 index 0000000..8ab719e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Control/CurrentScreenDetector.qml @@ -0,0 +1,163 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services.Compositor + +/** +* Detects which screen the cursor is currently on by creating a temporary +* invisible PanelWindow. Use withCurrentScreen() to get the screen asynchronously. +* +* Usage: +* CurrentScreenDetector { +* id: screenDetector +* } +* +* function doSomething() { +* screenDetector.withCurrentScreen(function(screen) { +* // screen is the ShellScreen where cursor is +* }) +* } +*/ +Item { + id: root + + // Pending callback to execute once screen is detected + property var pendingCallback: null + property bool pendingSkipBarCheck: false + + // Detected screen + property var detectedScreen: null + + // Signal emitted when screen is detected from the PanelWindow + signal screenDetected(var detectedScreen) + + onScreenDetected: function (detectedScreen) { + root.detectedScreen = detectedScreen; + screenDetectorDebounce.restart(); + } + + /** + * Find a fallback screen that has a bar configured. + * Prioritizes the screen at position 0x0 (likely the primary screen). + */ + function findScreenWithBar(): var { + const monitors = Settings.data.bar.monitors || []; + let primaryCandidate = null; + let firstWithBar = null; + + for (let i = 0; i < Quickshell.screens.length; i++) { + const s = Quickshell.screens[i]; + const hasBar = monitors.length === 0 || monitors.includes(s.name); + + if (hasBar) { + // Check if this is at 0x0 (primary position) + if (s.x === 0 && s.y === 0) { + primaryCandidate = s; + } + // Track first screen with bar as fallback + if (!firstWithBar) { + firstWithBar = s; + } + } + } + + // Prefer primary (0x0), then first with bar, then just first screen + return primaryCandidate || firstWithBar || Quickshell.screens[0]; + } + + /** + * Execute callback with the screen where the cursor currently is. + * On single-monitor setups, executes immediately. + * On multi-monitor setups, briefly opens an invisible window to detect the screen. + */ + function withCurrentScreen(callback: var, skipBarCheck: bool) { + if (root.pendingCallback) { + Logger.w("CurrentScreenDetector", "Another detection is pending, ignoring new call"); + return; + } + + // Single monitor setup can execute immediately + if (Quickshell.screens.length === 1) { + callback(Quickshell.screens[0]); + return; + } + + // Try compositor-specific focused monitor detection first + let screen = CompositorService.getFocusedScreen(); + + if (screen) { + // Apply the bar check if configured (skip for overlay launcher etc.) + if (!skipBarCheck && !Settings.data.general.allowPanelsOnScreenWithoutBar) { + const monitors = Settings.data.bar.monitors || []; + const hasBar = monitors.length === 0 || monitors.includes(screen.name); + if (!hasBar) { + screen = findScreenWithBar(); + } + } + Logger.d("CurrentScreenDetector", "Using compositor-detected screen:", screen.name); + callback(screen); + return; + } + + // Fallback: Multi-monitor setup needs async detection via invisible PanelWindow + root.detectedScreen = null; + root.pendingCallback = callback; + root.pendingSkipBarCheck = !!skipBarCheck; + screenDetectorLoader.active = true; + } + + Timer { + id: screenDetectorDebounce + running: false + interval: 40 + onTriggered: { + Logger.d("CurrentScreenDetector", "Screen debounced to:", root.detectedScreen?.name || "null"); + + // Execute pending callback if any + if (root.pendingCallback) { + if (!root.pendingSkipBarCheck && !Settings.data.general.allowPanelsOnScreenWithoutBar) { + // If we explicitly disabled panels on screen without bar, check if bar is configured + // for this screen, and fallback to primary screen if necessary + var monitors = Settings.data.bar.monitors || []; + const hasBar = monitors.length === 0 || monitors.includes(root.detectedScreen?.name); + if (!hasBar) { + root.detectedScreen = findScreenWithBar(); + } + } + + Logger.d("CurrentScreenDetector", "Executing callback on screen:", root.detectedScreen.name); + // Store callback locally and clear pendingCallback first to prevent deadlock + // if the callback throws an error + var callback = root.pendingCallback; + root.pendingCallback = null; + root.pendingSkipBarCheck = false; + try { + callback(root.detectedScreen); + } catch (e) { + Logger.e("CurrentScreenDetector", "Callback failed:", e); + } + } + + // Clean up + screenDetectorLoader.active = false; + } + } + + // Invisible dummy PanelWindow to detect which screen should receive the action + Loader { + id: screenDetectorLoader + active: false + + sourceComponent: PanelWindow { + implicitWidth: 0 + implicitHeight: 0 + color: "transparent" + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.namespace: "noctalia-screen-detector" + mask: Region {} + + onScreenChanged: root.screenDetected(screen) + } + } + } diff --git a/arch/.config/quickshell/noctalia-shell/Services/Control/CustomButtonIPCService.qml b/arch/.config/quickshell/noctalia-shell/Services/Control/CustomButtonIPCService.qml new file mode 100644 index 0000000..85c5e8b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Control/CustomButtonIPCService.qml @@ -0,0 +1,318 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.UI + +Singleton { + id: root + + // Must be called at startup to force singleton instantiation, + // which registers the IpcHandler with the IPC system. + function init() { + Logger.i("CustomButtonIPCService", "Service started"); + } + + // Registry to store references to active custom buttons by their user-defined identifier + property var customButtonRegistry: ({}) + + // Register a custom button instance + function registerButton(button) { + if (!button || !button.ipcIdentifier) { + Logger.w("CustomButtonIPCService", "Cannot register button without ipcIdentifier"); + return false; + } + + customButtonRegistry[button.ipcIdentifier] = button; + Logger.d("CustomButtonIPCService", `Registered button with identifier: ${button.ipcIdentifier}`); + return true; + } + + // Unregister a custom button instance + function unregisterButton(button) { + if (!button || !button.ipcIdentifier) { + return false; + } + + if (customButtonRegistry[button.ipcIdentifier] === button) { + delete customButtonRegistry[button.ipcIdentifier]; + Logger.d("CustomButtonIPCService", `Unregistered button with identifier: ${button.ipcIdentifier}`); + return true; + } + return false; + } + + // Find a button by identifier + function findButton(identifier) { + return customButtonRegistry[identifier] || null; + } + + // Find button config from Settings for when the live widget is not loaded + function findButtonConfig(identifier) { + var screens = Quickshell.screens; + for (var i = 0; i < screens.length; i++) { + var widgets = Settings.getBarWidgetsForScreen(screens[i].name); + var config = _searchWidgetsForIdentifier(widgets, identifier); + if (config) + return config; + } + // Also check global widgets as a final fallback + var globalConfig = _searchWidgetsForIdentifier(Settings.data.bar.widgets, identifier); + if (globalConfig) + return globalConfig; + return null; + } + + function _searchWidgetsForIdentifier(widgets, identifier) { + var sections = ["left", "center", "right"]; + for (var s = 0; s < sections.length; s++) { + var list = widgets[sections[s]]; + if (!list) + continue; + for (var j = 0; j < list.length; j++) { + var w = list[j]; + if (w.id === "CustomButton" && w.ipcIdentifier === identifier) { + return w; + } + } + } + return null; + } + + // Resolve a command property from config with fallback to widgetMetadata defaults + function resolveCommand(config, prop) { + if (config[prop]) + return config[prop]; + var meta = BarWidgetRegistry.widgetMetadata["CustomButton"]; + return meta ? (meta[prop] || "") : ""; + } + + // Substitute $delta expressions in a command string + function substituteWheelDelta(command, delta) { + var normalizedDelta = delta > 0 ? 1 : -1; + return command.replace(/\$delta([+\-*/]\d+)?/g, function (match, operation) { + if (operation) { + try { + var operator = operation.charAt(0); + var operand = parseInt(operation.substring(1)); + var result; + switch (operator) { + case '+': + result = normalizedDelta + operand; + break; + case '-': + result = normalizedDelta - operand; + break; + case '*': + result = normalizedDelta * operand; + break; + case '/': + result = Math.floor(normalizedDelta / operand); + break; + default: + result = normalizedDelta; + } + return result.toString(); + } catch (e) { + return normalizedDelta.toString(); + } + } else { + return normalizedDelta.toString(); + } + }); + } + + // IpcHandler for custom button commands using short alias 'cb' + IpcHandler { + target: "cb" + + // Handle left click: cb left "identifier" + function left(identifier: string) { + const button = findButton(identifier); + if (button) { + if (button.leftClickExec || button.textCommand) { + button.clicked(); + Logger.i("CustomButtonIPCService", `Triggered left click on button '${identifier}'`); + } else { + Logger.w("CustomButtonIPCService", `Button '${identifier}' has no left click action configured`); + } + return; + } + + // Fallback: read from Settings + const config = findButtonConfig(identifier); + if (!config) { + Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`); + return; + } + const cmd = resolveCommand(config, "leftClickExec"); + if (cmd) { + Quickshell.execDetached(["sh", "-lc", cmd]); + Logger.i("CustomButtonIPCService", `Triggered left click on button '${identifier}' (from settings)`); + } else { + Logger.w("CustomButtonIPCService", `Button '${identifier}' has no left click action configured`); + } + } + + // Handle right click: cb right "identifier" + function right(identifier: string) { + const button = findButton(identifier); + if (button) { + if (button.rightClickExec) { + button.rightClicked(); + Logger.i("CustomButtonIPCService", `Triggered right click on button '${identifier}'`); + } else { + Logger.w("CustomButtonIPCService", `Button '${identifier}' has no right click action configured`); + } + return; + } + + const config = findButtonConfig(identifier); + if (!config) { + Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`); + return; + } + const cmd = resolveCommand(config, "rightClickExec"); + if (cmd) { + Quickshell.execDetached(["sh", "-lc", cmd]); + Logger.i("CustomButtonIPCService", `Triggered right click on button '${identifier}' (from settings)`); + } else { + Logger.w("CustomButtonIPCService", `Button '${identifier}' has no right click action configured`); + } + } + + // Handle middle click: cb middle "identifier" + function middle(identifier: string) { + const button = findButton(identifier); + if (button) { + if (button.middleClickExec) { + button.middleClicked(); + Logger.i("CustomButtonIPCService", `Triggered middle click on button '${identifier}'`); + } else { + Logger.w("CustomButtonIPCService", `Button '${identifier}' has no middle click action configured`); + } + return; + } + + const config = findButtonConfig(identifier); + if (!config) { + Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`); + return; + } + const cmd = resolveCommand(config, "middleClickExec"); + if (cmd) { + Quickshell.execDetached(["sh", "-lc", cmd]); + Logger.i("CustomButtonIPCService", `Triggered middle click on button '${identifier}' (from settings)`); + } else { + Logger.w("CustomButtonIPCService", `Button '${identifier}' has no middle click action configured`); + } + } + + // Handle wheel up: cb up "identifier" + function up(identifier: string) { + const button = findButton(identifier); + if (button) { + if (button.wheelMode === "separate" && button.wheelUpExec) { + button.wheeled(1); + Logger.i("CustomButtonIPCService", `Triggered wheel up on button '${identifier}'`); + } else { + Logger.w("CustomButtonIPCService", `Button '${identifier}' has no separate wheel up action configured or is not in separate mode`); + } + return; + } + + const config = findButtonConfig(identifier); + if (!config) { + Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`); + return; + } + const mode = config.wheelMode || BarWidgetRegistry.widgetMetadata["CustomButton"].wheelMode; + const cmd = resolveCommand(config, "wheelUpExec"); + if (mode === "separate" && cmd) { + const resolved = substituteWheelDelta(cmd, 1); + Quickshell.execDetached(["sh", "-lc", resolved]); + Logger.i("CustomButtonIPCService", `Triggered wheel up on button '${identifier}' (from settings)`); + } else { + Logger.w("CustomButtonIPCService", `Button '${identifier}' has no separate wheel up action configured or is not in separate mode`); + } + } + + // Handle wheel down: cb down "identifier" + function down(identifier: string) { + const button = findButton(identifier); + if (button) { + if (button.wheelMode === "separate" && button.wheelDownExec) { + button.wheeled(-1); + Logger.i("CustomButtonIPCService", `Triggered wheel down on button '${identifier}'`); + } else { + Logger.w("CustomButtonIPCService", `Button '${identifier}' has no separate wheel down action configured or is not in separate mode`); + } + return; + } + + const config = findButtonConfig(identifier); + if (!config) { + Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`); + return; + } + const mode = config.wheelMode || BarWidgetRegistry.widgetMetadata["CustomButton"].wheelMode; + const cmd = resolveCommand(config, "wheelDownExec"); + if (mode === "separate" && cmd) { + const resolved = substituteWheelDelta(cmd, -1); + Quickshell.execDetached(["sh", "-lc", resolved]); + Logger.i("CustomButtonIPCService", `Triggered wheel down on button '${identifier}' (from settings)`); + } else { + Logger.w("CustomButtonIPCService", `Button '${identifier}' has no separate wheel down action configured or is not in separate mode`); + } + } + + // Handle wheel action: cb wheel "identifier" + function wheel(identifier: string) { + const button = findButton(identifier); + if (button) { + if (button.wheelMode === "unified" && button.wheelExec) { + button.wheeled(1); + Logger.i("CustomButtonIPCService", `Triggered wheel action on button '${identifier}'`); + } else { + Logger.w("CustomButtonIPCService", `Button '${identifier}' has no unified wheel action configured or is not in unified mode`); + } + return; + } + + const config = findButtonConfig(identifier); + if (!config) { + Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`); + return; + } + const mode = config.wheelMode || BarWidgetRegistry.widgetMetadata["CustomButton"].wheelMode; + const cmd = resolveCommand(config, "wheelExec"); + if (mode === "unified" && cmd) { + const resolved = substituteWheelDelta(cmd, 1); + Quickshell.execDetached(["sh", "-lc", resolved]); + Logger.i("CustomButtonIPCService", `Triggered wheel action on button '${identifier}' (from settings)`); + } else { + Logger.w("CustomButtonIPCService", `Button '${identifier}' has no unified wheel action configured or is not in unified mode`); + } + } + + // Handle refresh: cb refresh "identifier" + function refresh(identifier: string) { + const button = findButton(identifier); + if (!button) { + Logger.w("CustomButtonIPCService", `Button '${identifier}' is not currently loaded โ€” refresh requires a live widget instance`); + return; + } + + if (button.textCommand && button.textCommand.length > 0 && !button.textStream) { + button.runTextCommand(); + Logger.i("CustomButtonIPCService", `Triggered refresh (text command) on button '${identifier}'`); + } else if (button.textStream) { + Logger.w("CustomButtonIPCService", `Button '${identifier}' uses streaming, manual refresh disabled`); + } else { + Logger.w("CustomButtonIPCService", `Button '${identifier}' has no text command to refresh`); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Control/HooksService.qml b/arch/.config/quickshell/noctalia-shell/Services/Control/HooksService.qml new file mode 100644 index 0000000..c11c8c7 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Control/HooksService.qml @@ -0,0 +1,326 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.Power +import qs.Services.Theming +import qs.Services.UI + +Singleton { + id: root + + // Hook connections for automatic script execution + Connections { + target: Settings.data.colorSchemes + function onDarkModeChanged() { + executeDarkModeHook(Settings.data.colorSchemes.darkMode); + } + } + + // Pending wallpaper hook when waiting for color generation + property var pendingWallpaperHook: null + + Connections { + target: WallpaperService + function onWallpaperChanged(screenName, path) { + // Check if we need to wait for color generation + if (Settings.data.colorSchemes.useWallpaperColors) { + var effectiveMonitor = Settings.data.colorSchemes.monitorForColors; + if (effectiveMonitor === "" || effectiveMonitor === undefined) { + effectiveMonitor = Screen.name; + } + + if (screenName === effectiveMonitor) { + // Store pending hook and wait for colors to be generated + root.pendingWallpaperHook = { + path: path, + screenName: screenName + }; + return; + } + } + // No color generation, execute immediately + executeWallpaperHook(path, screenName); + } + } + + Connections { + target: TemplateProcessor + function onColorsGenerated() { + // Execute pending wallpaper hook after colors are ready + if (root.pendingWallpaperHook) { + const hook = root.pendingWallpaperHook; + root.pendingWallpaperHook = null; + executeWallpaperHook(hook.path, hook.screenName); + } + executeColorGenerationHook(); + } + } + + // Track lock screen state for unlock hook + property bool wasLocked: false + + Connections { + target: PanelService + function onLockScreenChanged() { + if (PanelService.lockScreen) { + lockScreenActiveConnection.target = PanelService.lockScreen; + } + } + } + + Connections { + id: lockScreenActiveConnection + target: PanelService.lockScreen + function onActiveChanged() { + // Detect lock: was unlocked, now locked + if (!wasLocked && PanelService.lockScreen.active) { + executeLockHook(); + } + // Detect unlock: was locked, now not locked + if (wasLocked && !PanelService.lockScreen.active) { + executeUnlockHook(); + } + wasLocked = PanelService.lockScreen.active; + } + } + + // Track performance mode state for hooks + property bool wasPerformanceModeEnabled: false + + Connections { + target: PowerProfileService + function onNoctaliaPerformanceModeChanged() { + const isEnabled = PowerProfileService.noctaliaPerformanceMode; + + // Detect enabled: was disabled, now enabled + if (!wasPerformanceModeEnabled && isEnabled) { + executePerformanceModeEnabledHook(); + } + // Detect disabled: was enabled, now disabled + if (wasPerformanceModeEnabled && !isEnabled) { + executePerformanceModeDisabledHook(); + } + wasPerformanceModeEnabled = isEnabled; + } + } + + // Execute wallpaper change hook + function executeWallpaperHook(wallpaperPath, screenName) { + if (!Settings.data.hooks?.enabled) { + return; + } + + const script = Settings.data.hooks?.wallpaperChange; + if (!script || script === "") { + return; + } + + try { + const theme = Settings.data.colorSchemes.darkMode ? "dark" : "light"; + let command = script.replace(/\$1/g, wallpaperPath); + command = command.replace(/\$2/g, screenName || ""); + command = command.replace(/\$3/g, theme); + Quickshell.execDetached(["sh", "-lc", command]); + Logger.d("HooksService", `Executed wallpaper hook: ${command}`); + } catch (e) { + Logger.e("HooksService", `Failed to execute wallpaper hook: ${e}`); + } + } + + // Execute dark mode change hook + function executeDarkModeHook(isDarkMode) { + if (!Settings.data.hooks?.enabled) { + return; + } + + const script = Settings.data.hooks?.darkModeChange; + if (!script || script === "") { + return; + } + + try { + const command = script.replace(/\$1/g, isDarkMode ? "true" : "false"); + Quickshell.execDetached(["sh", "-lc", command]); + Logger.d("HooksService", `Executed dark mode hook: ${command}`); + } catch (e) { + Logger.e("HooksService", `Failed to execute dark mode hook: ${e}`); + } + } + + // Execute screen lock hook + function executeLockHook() { + if (!Settings.data.hooks?.enabled) { + return; + } + + const script = Settings.data.hooks?.screenLock; + if (!script || script === "") { + return; + } + + try { + // Pass "lock" as $1 via shell arguments so the script receives it + Quickshell.execDetached(["sh", "-lc", script, "lock-hook", "lock"]); + Logger.d("HooksService", `Executed screen lock hook: ${script}`); + } catch (e) { + Logger.e("HooksService", `Failed to execute screen lock hook: ${e}`); + } + } + + // Execute screen unlock hook + function executeUnlockHook() { + if (!Settings.data.hooks?.enabled) { + return; + } + + const script = Settings.data.hooks?.screenUnlock; + if (!script || script === "") { + return; + } + + try { + // Pass "unlock" as $1 via shell arguments so the script receives it + Quickshell.execDetached(["sh", "-lc", script, "unlock-hook", "unlock"]); + Logger.d("HooksService", `Executed screen unlock hook: ${script}`); + } catch (e) { + Logger.e("HooksService", `Failed to execute screen unlock hook: ${e}`); + } + } + + // Execute performance mode enabled hook + function executePerformanceModeEnabledHook() { + if (!Settings.data.hooks?.enabled) { + return; + } + + const script = Settings.data.hooks?.performanceModeEnabled; + if (!script || script === "") { + return; + } + + try { + Quickshell.execDetached(["sh", "-lc", script]); + } catch (e) { + Logger.e("HooksService", `Failed to execute performance mode enabled hook: ${e}`); + } + } + + // Execute performance mode disabled hook + function executePerformanceModeDisabledHook() { + if (!Settings.data.hooks?.enabled) { + return; + } + + const script = Settings.data.hooks?.performanceModeDisabled; + if (!script || script === "") { + return; + } + + try { + Quickshell.execDetached(["sh", "-lc", script]); + } catch (e) { + Logger.e("HooksService", `Failed to execute performance mode disabled hook: ${e}`); + } + } + + // Execute color generation hook + function executeColorGenerationHook() { + if (!Settings.data.hooks?.enabled) { + return; + } + + const script = Settings.data.hooks?.colorGeneration; + if (!script || script === "") { + return; + } + + try { + const theme = Settings.data.colorSchemes.darkMode ? "dark" : "light"; + const command = script.replace(/\$1/g, theme); + Quickshell.execDetached(["sh", "-lc", command]); + Logger.d("HooksService", `Executed color generation hook: ${command}`); + } catch (e) { + Logger.e("HooksService", `Failed to execute color generation hook: ${e}`); + } + } + + // Blocking power hook infrastructure + property var pendingPowerCallback: null + + Process { + id: powerHookProcess + onExited: (exitCode, exitStatus) => { + if (exitCode !== 0) { + Logger.w("HooksService", `Power hook failed with exit code ${exitCode}`); + } + + if (pendingPowerCallback !== null) { + const callback = pendingPowerCallback; + pendingPowerCallback = null; + callback(); + } + } + } + + function runPowerHook(script, callback) { + pendingPowerCallback = callback; + powerHookProcess.command = ["sh", "-lc", script]; + powerHookProcess.running = true; + } + + function executeSessionHook(action, callback) { + if (!Settings.data.hooks?.enabled) { + callback(); + + return; + } + + const script = Settings.data.hooks?.session; + if (!script) { + callback(); + + return; + } + + Logger.i("HooksService", `Executing session hook for ${action}`); + runPowerHook(`${script} ${action}`, callback); + } + + // Execute startup hook + function executeStartupHook() { + if (!Settings.data.hooks?.enabled) { + return; + } + + const script = Settings.data.hooks?.startup; + if (!script || script === "") { + return; + } + + try { + Quickshell.execDetached(["sh", "-lc", script]); + Logger.d("HooksService", `Executed startup hook: ${script}`); + } catch (e) { + Logger.e("HooksService", `Failed to execute startup hook: ${e}`); + } + } + + // Initialize the service + function init() { + Logger.i("HooksService", "Service started"); + // Initialize lock screen state tracking + Qt.callLater(() => { + if (PanelService.lockScreen) { + wasLocked = PanelService.lockScreen.active; + lockScreenActiveConnection.target = PanelService.lockScreen; + } + // Initialize performance mode state tracking + wasPerformanceModeEnabled = PowerProfileService.noctaliaPerformanceMode; + // Execute startup hook + executeStartupHook(); + }); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Control/IPCService.qml b/arch/.config/quickshell/noctalia-shell/Services/Control/IPCService.qml new file mode 100644 index 0000000..f07bb7b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Control/IPCService.qml @@ -0,0 +1,940 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Widgets + +import qs.Commons +import qs.Modules.Panels.Settings +import qs.Services.Compositor +import qs.Services.Hardware +import qs.Services.Location +import qs.Services.Media +import qs.Services.Networking +import qs.Services.Noctalia +import qs.Services.Power +import qs.Services.System +import qs.Services.Theming +import qs.Services.UI + +Singleton { + id: root + + // Screen detector, set via init() + property var screenDetector: null + + function init(detector) { + root.screenDetector = detector; + Logger.i("IPCService", "Service started"); + } + + // Helper for index-based notification lookups in IPC + function _getNotificationByIndex(index: string, funcName: string) { + var idx = index === "" ? 0 : parseInt(index); + if (isNaN(idx)) { + Logger.w("IPC", "Argument to ipc call '" + funcName + "' must be a number"); + return null; + } + if (idx < 0 || idx >= NotificationService.popupModel.count) { + Logger.w("IPC", "Notification index out of range: " + idx); + return null; + } + return NotificationService.popupModel.get(idx); + } + + IpcHandler { + target: "bar" + function toggle() { + BarService.toggleVisibility(); + } + function hideBar() { + BarService.hide(); + } + function showBar() { + BarService.show(); + } + function peek() { + BarService.peek(); + } + function setDisplayMode(mode: string, screen: string) { + if (mode === "always_visible" || mode === "non_exclusive" || mode === "auto_hide") { + if (!screen || screen === "all") { + Settings.data.bar.displayMode = mode; + } else { + Settings.setScreenOverride(screen, "displayMode", mode); + } + } + } + function setPosition(position: string, screen: string) { + var valid = position === "top" || position === "bottom" || position === "left" || position === "right"; + if (!valid) { + Logger.w("IPC", "Invalid bar position: " + position + ". Valid: top, bottom, left, right"); + return; + } + if (!screen || screen === "all") { + Settings.data.bar.position = position; + } else { + Settings.setScreenOverride(screen, "position", position); + } + } + } + + // Settings IPC helpers (outside IpcHandler to avoid QVariant IPC warnings) + readonly property var _settingsTabMap: ({ + "about": SettingsPanel.Tab.About, + "audio": SettingsPanel.Tab.Audio, + "bar": SettingsPanel.Tab.Bar, + "colorscheme": SettingsPanel.Tab.ColorScheme, + "lockscreen": SettingsPanel.Tab.LockScreen, + "controlcenter": SettingsPanel.Tab.ControlCenter, + "desktopwidgets": SettingsPanel.Tab.DesktopWidgets, + "osd": SettingsPanel.Tab.OSD, + "display": SettingsPanel.Tab.Display, + "dock": SettingsPanel.Tab.Dock, + "general": SettingsPanel.Tab.General, + "hooks": SettingsPanel.Tab.Hooks, + "launcher": SettingsPanel.Tab.Launcher, + "location": SettingsPanel.Tab.Location, + "connections": SettingsPanel.Tab.Connections, + "notifications": SettingsPanel.Tab.Notifications, + "plugins": SettingsPanel.Tab.Plugins, + "sessionmenu": SettingsPanel.Tab.SessionMenu, + "system": SettingsPanel.Tab.System, + "systemmonitor": SettingsPanel.Tab.System, + "userinterface": SettingsPanel.Tab.UserInterface, + "wallpaper": SettingsPanel.Tab.Wallpaper + }) + + function _parseSettingsTabArg(tabArg) { + var parts = tabArg.split("/"); + var tabId = _resolveSettingsTab(parts[0]); + var subTabId = parts.length > 1 ? parseInt(parts[1]) : -1; + return { + "tab": tabId, + "subTab": isNaN(subTabId) ? -1 : subTabId + }; + } + + function _resolveSettingsTab(tabName) { + if (!tabName) + return SettingsPanel.Tab.General; + var key = tabName.toLowerCase().replace(/[-_]/g, ""); + if (key in _settingsTabMap) + return _settingsTabMap[key]; + Logger.w("IPC", "Unknown settings tab: " + tabName); + return SettingsPanel.Tab.General; + } + + function _settingsToggle(tabId, subTabId) { + root.screenDetector.withCurrentScreen(screen => { + SettingsPanelService.toggle(tabId, subTabId, screen); + }); + } + + function _settingsOpen(tabId, subTabId) { + root.screenDetector.withCurrentScreen(screen => { + SettingsPanelService.openToTab(tabId, subTabId, screen); + }); + } + + IpcHandler { + target: "settings" + + function toggle() { + root._settingsToggle(SettingsPanel.Tab.General, -1); + } + + function toggleTab(tab: string) { + var parsed = root._parseSettingsTabArg(tab); + root._settingsToggle(parsed.tab, parsed.subTab); + } + + function open() { + root._settingsOpen(SettingsPanel.Tab.General, -1); + } + + function openTab(tab: string) { + var parsed = root._parseSettingsTabArg(tab); + root._settingsOpen(parsed.tab, parsed.subTab); + } + } + + IpcHandler { + target: "calendar" + function toggle() { + root.screenDetector.withCurrentScreen(screen => { + var clockPanel = PanelService.getPanel("clockPanel", screen); + clockPanel?.toggle(null, "Clock"); + }); + } + } + + IpcHandler { + target: "notifications" + function toggleHistory() { + // Will attempt to open the panel next to the bar button if any. + root.screenDetector.withCurrentScreen(screen => { + var notificationHistoryPanel = PanelService.getPanel("notificationHistoryPanel", screen); + notificationHistoryPanel.toggle(null, "NotificationHistory"); + }); + } + function toggleDND() { + NotificationService.doNotDisturb = !NotificationService.doNotDisturb; + } + function enableDND() { + NotificationService.doNotDisturb = true; + } + function disableDND() { + NotificationService.doNotDisturb = false; + } + function clear() { + NotificationService.clearHistory(); + } + + function dismissOldest() { + NotificationService.dismissOldestPopup(); + } + + function removeOldestHistory() { + NotificationService.removeOldestHistory(); + } + + function dismissAll() { + NotificationService.dismissAllPopups(); + } + + function getHistory(): string { + return JSON.stringify(NotificationService.getHistorySnapshot(), null, 2); + } + + function removeFromHistory(id: string): bool { + return NotificationService.removeFromHistory(id); + } + + function invokeDefault(index: string): bool { + var notif = root._getNotificationByIndex(index, "notifications invokeDefault"); + if (!notif) + return false; + + var actions = JSON.parse(notif.actionsJson || "[]"); + if (actions.length === 0) + return false; + + var actionId = actions.find(a => a.identifier === "default")?.identifier ?? actions[0].identifier; + return NotificationService.invokeAction(notif.id, actionId); + } + + function invokeDefaultAndDismiss(index: string): bool { + var notif = root._getNotificationByIndex(index, "notifications invokeDefaultAndDismiss"); + if (!notif) + return false; + + var actions = JSON.parse(notif.actionsJson || "[]"); + if (actions.length === 0) { + NotificationService.dismissPopup(notif.id); + return false; + } + + var actionId = actions.find(a => a.identifier === "default")?.identifier ?? actions[0].identifier; + var result = NotificationService.invokeAction(notif.id, actionId); + NotificationService.dismissPopup(notif.id); + return result; + } + + function invokeAction(id: string, actionId: string): bool { + if (!id || !actionId) { + Logger.w("IPC", "Both 'id' and 'actionId' are required for 'notifications invokeAction'"); + return false; + } + return NotificationService.invokeAction(id, actionId); + } + + function getActions(index: string): string { + var notif = root._getNotificationByIndex(index, "notifications getActions"); + if (!notif) + return "[]"; + return notif.actionsJson || "[]"; + } + } + + IpcHandler { + target: "toast" + + function send(json: string) { + try { + var data = JSON.parse(json); + var title = data.title || ""; + var body = data.body || ""; + var icon = data.icon || ""; + var type = data.type || "notice"; + var duration = data.duration; + + switch (type) { + case "warning": + ToastService.showWarning(title, body, duration ?? 4000); + break; + case "error": + ToastService.showError(title, body, duration ?? 6000); + break; + default: + ToastService.showNotice(title, body, icon, duration ?? 3000); + } + } catch (error) { + Logger.e("IPC", "Failed to parse toast JSON: " + error); + } + } + + function dismiss() { + ToastService.dismissToast(); + } + } + + // Idle Inhibitor / Keep Awake + IpcHandler { + target: "idleInhibitor" + function toggle() { + IdleInhibitorService.manualToggle(); + } + function enable() { + IdleInhibitorService.addManualInhibitor(null); + } + function disable() { + IdleInhibitorService.removeManualInhibitor(); + } + function enableFor(seconds: string) { + var secs = parseInt(seconds); + if (isNaN(secs) || secs <= 0) { + Logger.w("IPC", "Argument to 'idleInhibitor enableFor' must be a positive number"); + return; + } + IdleInhibitorService.addManualInhibitor(secs); + } + } + + IpcHandler { + target: "launcher" + function toggle() { + root.screenDetector.withCurrentScreen(screen => { + var searchText = PanelService.getLauncherSearchText(screen); + var isInAppMode = !searchText.startsWith(">"); + if (!PanelService.isLauncherOpen(screen)) { + // Closed -> open in app mode + PanelService.openLauncherWithSearch(screen, ""); + } else if (isInAppMode) { + // Already in app mode -> close + PanelService.closeLauncher(screen); + } else { + // In another mode -> switch to app mode + PanelService.setLauncherSearchText(screen, ""); + } + }, Settings.data.appLauncher.overviewLayer); + } + function clipboard() { + root.screenDetector.withCurrentScreen(screen => { + var searchText = PanelService.getLauncherSearchText(screen); + var isInClipMode = searchText.startsWith(">clip"); + if (!PanelService.isLauncherOpen(screen)) { + // Closed -> open in clipboard mode + PanelService.openLauncherWithSearch(screen, ">clip "); + } else if (isInClipMode) { + // Already in clipboard mode -> close + PanelService.closeLauncher(screen); + } else { + // In another mode -> switch to clipboard mode + PanelService.setLauncherSearchText(screen, ">clip "); + } + }, Settings.data.appLauncher.overviewLayer); + } + function command() { + root.screenDetector.withCurrentScreen(screen => { + var searchText = PanelService.getLauncherSearchText(screen); + var isInCmdMode = searchText.startsWith(">cmd"); + if (!PanelService.isLauncherOpen(screen)) { + PanelService.openLauncherWithSearch(screen, ">cmd "); + } else if (isInCmdMode) { + PanelService.closeLauncher(screen); + } else { + PanelService.setLauncherSearchText(screen, ">cmd "); + } + }, Settings.data.appLauncher.overviewLayer); + } + function emoji() { + root.screenDetector.withCurrentScreen(screen => { + var searchText = PanelService.getLauncherSearchText(screen); + var isInEmojiMode = searchText.startsWith(">emoji"); + if (!PanelService.isLauncherOpen(screen)) { + // Closed -> open in emoji mode + PanelService.openLauncherWithSearch(screen, ">emoji "); + } else if (isInEmojiMode) { + // Already in emoji mode -> close + PanelService.closeLauncher(screen); + } else { + // In another mode -> switch to emoji mode + PanelService.setLauncherSearchText(screen, ">emoji "); + } + }, Settings.data.appLauncher.overviewLayer); + } + function windows() { + root.screenDetector.withCurrentScreen(screen => { + var searchText = PanelService.getLauncherSearchText(screen); + var isInWindowsMode = searchText.startsWith(">win"); + if (!PanelService.isLauncherOpen(screen)) { + PanelService.openLauncherWithSearch(screen, ">win "); + } else if (isInWindowsMode) { + PanelService.closeLauncher(screen); + } else { + PanelService.setLauncherSearchText(screen, ">win "); + } + }, Settings.data.appLauncher.overviewLayer); + } + function settings() { + root.screenDetector.withCurrentScreen(screen => { + var searchText = PanelService.getLauncherSearchText(screen); + var isInSettingsMode = searchText.startsWith(">settings"); + if (!PanelService.isLauncherOpen(screen)) { + PanelService.openLauncherWithSearch(screen, ">settings "); + } else if (isInSettingsMode) { + PanelService.closeLauncher(screen); + } else { + PanelService.setLauncherSearchText(screen, ">settings "); + } + }, Settings.data.appLauncher.overviewLayer); + } + } + + IpcHandler { + target: "lockScreen" + + // New preferred method - lock the screen + function lock() { + // Only lock if not already locked (prevents the red screen issue) + if (!PanelService.lockScreen.active) { + CompositorService.lock(); + } + } + } + + IpcHandler { + target: "brightness" + function increase() { + BrightnessService.increaseBrightness(); + } + function decrease() { + BrightnessService.decreaseBrightness(); + } + function set(value: string) { + var val = parseFloat(value); + if (isNaN(val)) + return; + + // Normalize logic: heuristic handling of 0-100 vs 0-1 + if (val > 1.0) + val = val / 100.0; + + // Clamp + val = Math.max(0.0, Math.min(1.0, val)); + + BrightnessService.setBrightness(val); + } + } + + IpcHandler { + target: "monitors" + function on() { + CompositorService.turnOnMonitors(); + } + function off() { + CompositorService.turnOffMonitors(); + } + } + + IpcHandler { + target: "darkMode" + function toggle() { + Settings.data.colorSchemes.darkMode = !Settings.data.colorSchemes.darkMode; + } + function setDark() { + Settings.data.colorSchemes.darkMode = true; + } + function setLight() { + Settings.data.colorSchemes.darkMode = false; + } + } + + IpcHandler { + target: "nightLight" + function toggle() { + if (!ProgramCheckerService.wlsunsetAvailable) { + Logger.w("IPC", "wlsunset not available, cannot toggle night light"); + return; + } + + if (Settings.data.nightLight.forced) { + Settings.data.nightLight.forced = false; + } else { + if (Settings.data.nightLight.enabled) { + Settings.data.nightLight.enabled = false; + } else { + Settings.data.nightLight.forced = true; + Settings.data.nightLight.enabled = true; + } + } + } + } + + IpcHandler { + target: "colorScheme" + function set(schemeName: string) { + ColorSchemeService.setPredefinedScheme(schemeName); + } + function setGenerationMethod(method: string) { + var valid = false; + for (var i = 0; i < TemplateProcessor.schemeTypes.length; i++) { + if (TemplateProcessor.schemeTypes[i].key === method) { + valid = true; + break; + } + } + + if (valid) { + Settings.data.colorSchemes.generationMethod = method; + } else { + Logger.w("IPC", "Invalid generation method received: " + method); + } + } + } + + IpcHandler { + target: "volume" + function increase() { + AudioService.increaseVolume(); + } + function decrease() { + AudioService.decreaseVolume(); + } + function muteOutput() { + AudioService.setOutputMuted(!AudioService.muted); + } + function increaseInput() { + AudioService.increaseInputVolume(); + } + function decreaseInput() { + AudioService.decreaseInputVolume(); + } + function muteInput() { + AudioService.setInputMuted(!AudioService.inputMuted); + } + function togglePanel() { + root.screenDetector.withCurrentScreen(screen => { + var panel = PanelService.getPanel("audioPanel", screen); + panel?.toggle(null, "Volume"); + }); + } + function openPanel() { + root.screenDetector.withCurrentScreen(screen => { + var panel = PanelService.getPanel("audioPanel", screen); + panel?.open(null, "Volume"); + }); + } + function closePanel() { + root.screenDetector.withCurrentScreen(screen => { + var panel = PanelService.getPanel("audioPanel", screen); + panel?.close(null, "Volume"); + }); + } + } + + IpcHandler { + target: "sessionMenu" + function toggle() { + root.screenDetector.withCurrentScreen(screen => { + var sessionMenuPanel = PanelService.getPanel("sessionMenuPanel", screen); + // Session Menu is never open near the bar + sessionMenuPanel?.toggle(); + }); + } + + function lock() { + if (!PanelService.lockScreen.active) { + CompositorService.lock(); + } + } + + function lockAndSuspend() { + // Only lock and suspend if not already locked + if (!PanelService.lockScreen.active) { + CompositorService.lockAndSuspend(); + } + } + } + + IpcHandler { + target: "controlCenter" + function toggle() { + root.screenDetector.withCurrentScreen(screen => { + var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen); + if (Settings.data.controlCenter.position === "close_to_bar_button") { + // Will attempt to open the panel next to the bar button if any. + controlCenterPanel?.toggle(null, "ControlCenter"); + } else { + controlCenterPanel?.toggle(); + } + }); + } + } + + IpcHandler { + target: "dock" + function toggle() { + Settings.data.dock.enabled = !Settings.data.dock.enabled; + } + } + + // Wallpaper IPC: trigger a new random wallpaper + IpcHandler { + target: "wallpaper" + function toggle() { + if (Settings.data.wallpaper.enabled) { + root.screenDetector.withCurrentScreen(screen => { + var wallpaperPanel = PanelService.getPanel("wallpaperPanel", screen); + wallpaperPanel?.toggle(); + }); + } + } + + function random(screen: string) { + if (Settings.data.wallpaper.enabled) { + if (!screen || screen === "all" || screen.trim().length === 0) { + screen = undefined; + } + WallpaperService.setRandomWallpaper(screen); + } + } + + function get(screen: string): string { + if (screen === "all" || screen === "") { + if (Quickshell.screens.length > 1) { + return JSON.stringify(WallpaperService.getWallpapersEffectiveMap()); + } + return WallpaperService.getWallpaper(Quickshell.screens[0].name) ?? ""; + } else { + var found = Quickshell.screens.find(s => s.name === screen); + if (!found) { + Logger.w("IPC", "wallpaper get: unknown screen: " + screen); + return ""; + } + return WallpaperService.getWallpaper(screen) ?? ""; + } + } + + function set(path: string, screen: string) { + if (screen === "all" || screen === "") { + screen = undefined; + } + WallpaperService.changeWallpaper(path, screen); + } + + function refresh() { + WallpaperService.refreshWallpapersList(); + } + + function toggleAutomation() { + Settings.data.wallpaper.automationEnabled = !Settings.data.wallpaper.automationEnabled; + } + function disableAutomation() { + Settings.data.wallpaper.automationEnabled = false; + } + function enableAutomation() { + Settings.data.wallpaper.automationEnabled = true; + } + } + + IpcHandler { + target: "wifi" + function toggle() { + NetworkService.setWifiEnabled(!NetworkService.wifiEnabled); + } + function enable() { + NetworkService.setWifiEnabled(true); + } + function disable() { + NetworkService.setWifiEnabled(false); + } + } + + IpcHandler { + target: "network" + function togglePanel() { + root.screenDetector.withCurrentScreen(screen => { + var networkPanel = PanelService.getPanel("networkPanel", screen); + networkPanel?.toggle(null, "Network"); + }); + } + } + + IpcHandler { + target: "bluetooth" + function toggle() { + BluetoothService.setBluetoothEnabled(!BluetoothService.enabled); + } + function enable() { + BluetoothService.setBluetoothEnabled(true); + } + function disable() { + BluetoothService.setBluetoothEnabled(false); + } + function togglePanel() { + root.screenDetector.withCurrentScreen(screen => { + var bluetoothPanel = PanelService.getPanel("bluetoothPanel", screen); + bluetoothPanel?.toggle(null, "Bluetooth"); + }); + } + function toggleAutoConnect() { + Settings.data.network.bluetoothAutoConnect = !Settings.data.network.bluetoothAutoConnect; + } + function enableAutoConnect() { + Settings.data.network.bluetoothAutoConnect = true; + } + function disableAutoConnect() { + Settings.data.network.bluetoothAutoConnect = false; + } + } + + IpcHandler { + target: "airplaneMode" + function toggle() { + NetworkService.setAirplaneMode(!NetworkService.airplaneModeEnabled); + } + function enable() { + NetworkService.setAirplaneMode(true); + } + function disable() { + NetworkService.setAirplaneMode(false); + } + } + + IpcHandler { + target: "battery" + function togglePanel() { + root.screenDetector.withCurrentScreen(screen => { + var batteryPanel = PanelService.getPanel("batteryPanel", screen); + batteryPanel?.toggle(null, "Battery"); + }); + } + } + + IpcHandler { + target: "powerProfile" + function cycle() { + PowerProfileService.cycleProfile(); + } + + function cycleReverse() { + PowerProfileService.cycleProfileReverse(); + } + + function set(mode: string) { + switch (mode) { + case "performance": + PowerProfileService.setProfile(2); + break; + case "balanced": + PowerProfileService.setProfile(1); + break; + case "powersaver": + PowerProfileService.setProfile(0); + break; + } + } + + function toggleNoctaliaPerformance() { + PowerProfileService.toggleNoctaliaPerformance(); + } + + function enableNoctaliaPerformance() { + PowerProfileService.setNoctaliaPerformance(true); + } + + function disableNoctaliaPerformance() { + PowerProfileService.setNoctaliaPerformance(false); + } + } + + IpcHandler { + target: "media" + + function toggle() { + root.screenDetector.withCurrentScreen(screen => { + var panel = PanelService.getPanel("mediaPlayerPanel", screen); + panel?.toggle(null, "MediaMini"); + }); + } + + function playPause() { + MediaService.playPause(); + } + + function play() { + MediaService.play(); + } + + function stop() { + MediaService.stop(); + } + + function pause() { + MediaService.pause(); + } + + function next() { + MediaService.next(); + } + + function previous() { + MediaService.previous(); + } + + function seekRelative(offset: string) { + var offsetVal = parseFloat(offset); + if (Number.isNaN(offsetVal)) { + Logger.w("Media", "Argument to ipc call 'media seekRelative' must be a number"); + return; + } + MediaService.seekRelative(offsetVal); + } + + function seekByRatio(position: string) { + var positionVal = parseFloat(position); + if (Number.isNaN(positionVal)) { + Logger.w("Media", "Argument to ipc call 'media seekByRatio' must be a number"); + return; + } + MediaService.seekByRatio(positionVal); + } + } + + IpcHandler { + target: "state" + + // Returns all settings and shell state as JSON + function all(): string { + try { + var snapshot = ShellState.buildStateSnapshot(); + if (!snapshot) { + throw new Error("State snapshot unavailable"); + } + return JSON.stringify(snapshot, null, 2); + } catch (error) { + Logger.e("IPC", "Failed to serialize state:", error); + return JSON.stringify({ + "error": "Failed to serialize state: " + error + }, null, 2); + } + } + } + + IpcHandler { + target: "desktopWidgets" + function toggle() { + Settings.data.desktopWidgets.enabled = !Settings.data.desktopWidgets.enabled; + } + function disable() { + Settings.data.desktopWidgets.enabled = false; + } + function enable() { + Settings.data.desktopWidgets.enabled = true; + } + function edit() { + DesktopWidgetRegistry.editMode = !DesktopWidgetRegistry.editMode; + } + } + + IpcHandler { + target: "location" + function get(): string { + return Settings.data.location.name; + } + function set(name: string) { + Settings.data.location.name = name; + LocationService.update(); + } + } + + IpcHandler { + target: "systemMonitor" + function toggle() { + root.screenDetector.withCurrentScreen(screen => { + var panel = PanelService.getPanel("systemStatsPanel", screen); + panel?.toggle(null, "SystemMonitor"); + }); + } + } + + IpcHandler { + target: "plugin" + function openSettings(key: string) { + var manifest = PluginRegistry.getPluginManifest(key); + if (!manifest) { + Logger.w("IPC", "Plugin not found:", key); + return; + } + if (!manifest.entryPoints?.settings) { + Logger.w("IPC", "Plugin has no settings entry point:", key); + return; + } + root.screenDetector.withCurrentScreen(screen => { + BarService.openPluginSettings(screen, manifest); + }); + } + + function openPanel(key: string) { + var manifest = PluginRegistry.getPluginManifest(key); + if (!manifest) { + Logger.w("IPC", "Plugin not found:", key); + return; + } + if (!manifest.entryPoints?.panel) { + Logger.w("IPC", "Plugin has no panel entry point:", key); + return; + } + root.screenDetector.withCurrentScreen(screen => { + PluginService.openPluginPanel(key, screen, null); + }); + } + + function closePanel(key: string) { + var manifest = PluginRegistry.getPluginManifest(key); + if (!manifest) { + Logger.w("IPC", "Plugin not found:", key); + return; + } + if (!manifest.entryPoints?.panel) { + Logger.w("IPC", "Plugin has no panel entry point:", key); + return; + } + root.screenDetector.withCurrentScreen(screen => { + var api = PluginService.getPluginAPI(key); + if (api) { + api.closePanel(screen); + } + }); + } + + function togglePanel(key: string) { + var manifest = PluginRegistry.getPluginManifest(key); + if (!manifest) { + Logger.w("IPC", "Plugin not found:", key); + return; + } + if (!manifest.entryPoints?.panel) { + Logger.w("IPC", "Plugin has no panel entry point:", key); + return; + } + root.screenDetector.withCurrentScreen(screen => { + PluginService.togglePluginPanel(key, screen, null); + }); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Hardware/BatteryService.qml b/arch/.config/quickshell/noctalia-shell/Services/Hardware/BatteryService.qml new file mode 100644 index 0000000..f615354 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Hardware/BatteryService.qml @@ -0,0 +1,368 @@ +pragma Singleton +import QtQml +import QtQuick + +import Quickshell +import Quickshell.Io +import Quickshell.Services.UPower +import qs.Commons +import qs.Services.Networking +import qs.Services.UI + +Singleton { + id: root + + readonly property var primaryDevice: _laptopBattery || _bluetoothBattery || null // Primary battery device (prioritizes laptop over Bluetooth) + readonly property real batteryPercentage: getPercentage(primaryDevice) + readonly property bool batteryCharging: isCharging(primaryDevice) + readonly property bool batteryPluggedIn: isPluggedIn(primaryDevice) + readonly property bool batteryReady: isDeviceReady(primaryDevice) + readonly property bool batteryPresent: isDevicePresent(primaryDevice) + readonly property real warningThreshold: Settings.data.systemMonitor.batteryWarningThreshold + readonly property real criticalThreshold: Settings.data.systemMonitor.batteryCriticalThreshold + readonly property string batteryIcon: getIcon(batteryPercentage, batteryCharging, batteryPluggedIn, batteryReady) + + readonly property var laptopBatteries: UPower.devices.values.filter(d => d.isLaptopBattery).sort((x, y) => { + // Force DisplayDevice to the top + if (x.nativePath.includes("DisplayDevice")) + return -1; + if (y.nativePath.includes("DisplayDevice")) + return 1; + + // Standard string comparison works for BAT0 vs BAT1 + return x.nativePath.localeCompare(y.nativePath, undefined, { + numeric: true + }); + }) + + readonly property var bluetoothBatteries: { + var list = []; + var btArray = BluetoothService.devices?.values || []; + for (var i = 0; i < btArray.length; i++) { + var btd = btArray[i]; + if (btd && btd.connected && btd.batteryAvailable) { + list.push(btd); + } + } + return list; + } + + readonly property var _laptopBattery: UPower.displayDevice.isPresent ? UPower.displayDevice : (laptopBatteries.length > 0 ? laptopBatteries[0] : null) + readonly property var _bluetoothBattery: bluetoothBatteries.length > 0 ? bluetoothBatteries[0] : null + + property var deviceModel: { + var model = [ + { + "key": "__default__", + "name": I18n.tr("bar.battery.device-default") + } + ]; + const devices = UPower.devices?.values || []; + for (let d of devices) { + if (!d || d.type === UPowerDeviceType.LinePower) { + continue; + } + model.push({ + key: d.nativePath || "", + name: d.model || d.nativePath || I18n.tr("common.unknown") + }); + } + return model; + } + + property var _hasNotified: ({}) + + function findDevice(nativePath) { + if (!nativePath || nativePath === "__default__" || nativePath === "DisplayDevice") { + return _laptopBattery; + } + + if (!UPower.devices) { + return null; + } + + const devices = UPower.devices?.values || []; + for (let d of devices) { + if (d && d.nativePath === nativePath) { + if (d.type === UPowerDeviceType.LinePower) { + continue; + } + return d; + } + } + return null; + } + + function isDevicePresent(device) { + if (!device) { + return false; + } + + // Handle Bluetooth devices (identified by having batteryAvailable property) + if (device.batteryAvailable !== undefined) { + return device.connected === true; + } + + // Handle UPower devices + if (device.type !== undefined) { + if (device.type === UPowerDeviceType.Battery && device.isPresent !== undefined) { + return device.isPresent === true; + } + // Fallback for non-battery UPower devices or if isPresent is missing + return device.ready && device.percentage !== undefined; + } + return false; + } + + function isDeviceReady(device) { + if (!isDevicePresent(device)) { + return false; + } + if (device.batteryAvailable !== undefined) { + return device.battery !== undefined; + } + return device.ready && device.percentage !== undefined; + } + + function getPercentage(device) { + if (!device) { + return -1; + } + if (device.batteryAvailable !== undefined) { + return Math.round((device.battery || 0) * 100); + } + return Math.round((device.percentage || 0) * 100); + } + + function isCharging(device) { + if (!device || isBluetoothDevice(device)) { + // Tracking bluetooth devices can charge or not is a loop hole, none of my devices has it, even if it possible?! + return false; // Assuming not charging until someone/quickshell brings a way to do pretty unlikely. + } + if (device.state !== undefined) { + return device.state === UPowerDeviceState.Charging; + } + return false; + } + + function isPluggedIn(device) { + if (!device || isBluetoothDevice(device)) { + // Tracking bluetooth devices can charge or not is a loop hole, none of my devices has it, even if it possible?! + return false; // Assuming not charging until someone/quickshell brings a way to do pretty unlikely. + } + if (device.state !== undefined) { + return device.state === UPowerDeviceState.FullyCharged || device.state === UPowerDeviceState.PendingCharge; + } + return false; + } + + function isCriticalBattery(device) { + return (!isCharging(device) && !isPluggedIn(device)) && getPercentage(device) <= criticalThreshold; + } + + function isLowBattery(device) { + return (!isCharging(device) && !isPluggedIn(device)) && getPercentage(device) <= warningThreshold && getPercentage(device) > criticalThreshold; + } + + function isBluetoothDevice(device) { + if (!device) { + return false; + } + // Check for Quickshell Bluetooth device property + if (device.batteryAvailable !== undefined) { + return true; + } + // Check for UPower device path indicating it's a Bluetooth device + if (device.nativePath && (device.nativePath.includes("bluez") || device.nativePath.includes("bluetooth"))) { + return true; + } + return false; + } + + function getDeviceName(device) { + if (!isDeviceReady(device)) { + return ""; + } + + if (!isBluetoothDevice(device) && device.isLaptopBattery) { + // If there is more than one battery explicitly name them + // Logger.e("BatteryDebug", "Available Battery count: " + laptopBatteries.length); // can be useful for debugging + if (laptopBatteries.length > 1 && device.nativePath) { + if (device.nativePath === "DisplayDevice") { + return I18n.tr("battery.all-batteries"); + } + var match = device.nativePath.match(/(\d+)$/); + if (match) { + // In case of 2 batteries: bat0 => bat1 bat1 => bat2 + return I18n.tr("common.battery") + " " + (parseInt(match[1]) + 1); // Append numbers + } + } + // Return Battery if there is only one + return I18n.tr("common.battery"); + } + + if (isBluetoothDevice(device) && device.name) { + return device.name; + } + + if (device.model) { + return device.model; + } + + return ""; + } + + function getIcon(percent, charging, pluggedIn, isReady) { + if (!isReady) { + return "battery-exclamation"; + } + if (charging) { + return "battery-charging"; + } + if (pluggedIn) { + return "battery-charging-2"; + } + + const icons = [ + { + threshold: 86, + icon: "battery-4" + }, + { + threshold: 56, + icon: "battery-3" + }, + { + threshold: 31, + icon: "battery-2" + }, + { + threshold: 11, + icon: "battery-1" + }, + { + threshold: 0, + icon: "battery" + } + ]; + + const match = icons.find(tier => percent >= tier.threshold); + return match ? match.icon : "battery-off"; // New fallback icon clearly represent if nothing is true here. + } + + function getRateText(device) { + if (!device || device.changeRate === undefined) { + return ""; + } + const rate = Math.abs(device.changeRate); + if (device.timeToFull > 0) { + return I18n.tr("battery.charging-rate", { + "rate": rate.toFixed(2) + }); + } else if (device.timeToEmpty > 0) { + return I18n.tr("battery.discharging-rate", { + "rate": rate.toFixed(2) + }); + } + } + + function getTimeRemainingText(device) { + if (!isDeviceReady(device)) { + return I18n.tr("battery.no-battery-detected"); + } + if (isPluggedIn(device)) { + return I18n.tr("battery.plugged-in"); + } else if (device.timeToFull > 0) { + return I18n.tr("battery.time-until-full", { + "time": Time.formatVagueHumanReadableDuration(device.timeToFull) + }); + } else if (device.timeToEmpty > 0) { + return I18n.tr("battery.time-left", { + "time": Time.formatVagueHumanReadableDuration(device.timeToEmpty) + }); + } + return I18n.tr("common.idle"); + } + + function checkDevice(device) { + if (!device || !isDeviceReady(device)) { + return; + } + + const percentage = getPercentage(device); + const charging = isCharging(device); + const pluggedIn = isPluggedIn(device); + const level = isLowBattery(device) ? "low" : (isCriticalBattery(device) ? "critical" : ""); + var deviceKey = device.nativePath; + + if (!_hasNotified[deviceKey]) { + _hasNotified[deviceKey] = { + low: false, + critical: false + }; + } + + if (charging || pluggedIn) { + _hasNotified[deviceKey].low = false; + _hasNotified[deviceKey].critical = false; + } + + if (percentage > warningThreshold) { + _hasNotified[deviceKey].low = false; + _hasNotified[deviceKey].critical = false; + } else if (percentage > criticalThreshold) { + _hasNotified[deviceKey].critical = false; + } + + if (level) { + if (!_hasNotified[deviceKey][level]) { + notify(device, level); + _hasNotified[deviceKey][level] = true; + } + } + } + + function notify(device, level) { + if (!Settings.data.notifications.enableBatteryToast) { + return; + } + var name = getDeviceName(device); + var titleKey = level === "critical" ? "toast.battery.critical" : "toast.battery.low"; + var descKey = level === "critical" ? "toast.battery.critical-desc" : "toast.battery.low-desc"; + + var title = I18n.tr(titleKey); + var desc = I18n.tr(descKey, { + "percent": getPercentage(device) + }); + var icon = level === "critical" ? "battery-exclamation" : "battery-charging-2"; + + if (device == _bluetoothBattery && name) { + title = title + " " + name; + } + + // Only 'showNotice' supports custom icons + ToastService.showNotice(title, desc, icon, 6000); + } + + Instantiator { + model: deviceModel + delegate: Connections { + required property var modelData + property var device: findDevice(modelData.key) + target: device + + function onPercentageChanged() { + if (device.isLaptopBattery && modelData.key !== "__default__") { + return; + } + checkDevice(device); + } + function onStateChanged() { + if (device.isLaptopBattery && modelData.key !== "__default__") { + return; + } + checkDevice(device); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Hardware/BrightnessService.qml b/arch/.config/quickshell/noctalia-shell/Services/Hardware/BrightnessService.qml new file mode 100644 index 0000000..26847a5 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Hardware/BrightnessService.qml @@ -0,0 +1,560 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons + +Singleton { + id: root + + property list ddcMonitors: [] + readonly property list monitors: variants.instances + property bool appleDisplayPresent: false + property list availableBacklightDevices: [] + + function getMonitorForScreen(screen: ShellScreen): var { + return monitors.find(m => m.modelData === screen); + } + + // Signal emitted when a specific monitor's brightness changes, includes monitor context + signal monitorBrightnessChanged(var monitor, real newBrightness) + + function getAvailableMethods(): list { + var methods = []; + if (Settings.data.brightness.enableDdcSupport && monitors.some(m => m.isDdc)) + methods.push("ddcutil"); + if (monitors.some(m => !m.isDdc)) + methods.push("internal"); + if (appleDisplayPresent) + methods.push("apple"); + return methods; + } + + // Global helpers for IPC and shortcuts + function increaseBrightness(): void { + monitors.forEach(m => m.increaseBrightness()); + } + + function decreaseBrightness(): void { + monitors.forEach(m => m.decreaseBrightness()); + } + + function setBrightness(value: real): void { + monitors.forEach(m => m.setBrightnessDebounced(value)); + } + + function getDetectedDisplays(): list { + return detectedDisplays; + } + + function normalizeBacklightDevicePath(devicePath): string { + if (devicePath === undefined || devicePath === null) + return ""; + + var normalized = String(devicePath).trim(); + if (normalized === "") + return ""; + + if (normalized.startsWith("/sys/class/backlight/")) + return normalized; + + if (normalized.indexOf("/") === -1) + return "/sys/class/backlight/" + normalized; + + return normalized; + } + + function getBacklightDeviceName(devicePath): string { + var normalized = normalizeBacklightDevicePath(devicePath); + if (normalized === "") + return ""; + + var parts = normalized.split("/"); + while (parts.length > 0 && parts[parts.length - 1] === "") { + parts.pop(); + } + return parts.length > 0 ? parts[parts.length - 1] : ""; + } + + function getMappedBacklightDevice(outputName): string { + var normalizedOutput = String(outputName || "").trim(); + if (normalizedOutput === "") + return ""; + + var mappings = Settings.data.brightness.backlightDeviceMappings || []; + for (var i = 0; i < mappings.length; i++) { + var mapping = mappings[i]; + if (!mapping || typeof mapping !== "object") + continue; + + if (String(mapping.output || "").trim() === normalizedOutput) + return normalizeBacklightDevicePath(mapping.device || ""); + } + + return ""; + } + + function setMappedBacklightDevice(outputName, devicePath): void { + var normalizedOutput = String(outputName || "").trim(); + if (normalizedOutput === "") + return; + + var normalizedDevicePath = normalizeBacklightDevicePath(devicePath); + var mappings = Settings.data.brightness.backlightDeviceMappings || []; + var nextMappings = []; + var replaced = false; + + for (var i = 0; i < mappings.length; i++) { + var mapping = mappings[i]; + if (!mapping || typeof mapping !== "object") + continue; + + var mappingOutput = String(mapping.output || "").trim(); + var mappingDevice = normalizeBacklightDevicePath(mapping.device || ""); + if (mappingOutput === "" || mappingDevice === "") + continue; + + if (mappingOutput === normalizedOutput) { + if (!replaced && normalizedDevicePath !== "") { + nextMappings.push({ + "output": normalizedOutput, + "device": normalizedDevicePath + }); + } + replaced = true; + } else { + nextMappings.push({ + "output": mappingOutput, + "device": mappingDevice + }); + } + } + + if (!replaced && normalizedDevicePath !== "") { + nextMappings.push({ + "output": normalizedOutput, + "device": normalizedDevicePath + }); + } + + Settings.data.brightness.backlightDeviceMappings = nextMappings; + } + + function scanBacklightDevices(): void { + if (!scanBacklightProc.running) + scanBacklightProc.running = true; + } + + reloadableId: "brightness" + + Component.onCompleted: { + Logger.i("Brightness", "Service started"); + scanBacklightDevices(); + if (Settings.data.brightness.enableDdcSupport) { + ddcProc.running = true; + } + } + + onMonitorsChanged: { + ddcMonitors = []; + scanBacklightDevices(); + if (Settings.data.brightness.enableDdcSupport) { + ddcProc.running = true; + } + } + + Connections { + target: Settings.data.brightness + function onEnableDdcSupportChanged() { + if (Settings.data.brightness.enableDdcSupport) { + // Re-detect DDC monitors when enabled + ddcMonitors = []; + ddcProc.running = true; + } else { + // Clear DDC monitors when disabled + ddcMonitors = []; + } + } + function onBacklightDeviceMappingsChanged() { + scanBacklightDevices(); + for (var i = 0; i < monitors.length; i++) { + var m = monitors[i]; + if (m && !m.isDdc && !m.isAppleDisplay) + m.initBrightness(); + } + } + } + + Variants { + id: variants + model: Quickshell.screens + Monitor {} + } + + // Check for Apple Display support + Process { + running: true + command: ["sh", "-c", "which asdbctl >/dev/null 2>&1 && asdbctl get || echo ''"] + stdout: StdioCollector { + onStreamFinished: root.appleDisplayPresent = text.trim().length > 0 + } + } + + // Detect available internal backlight devices + Process { + id: scanBacklightProc + command: ["sh", "-c", "for dev in /sys/class/backlight/*; do if [ -f \"$dev/brightness\" ] && [ -f \"$dev/max_brightness\" ]; then echo \"$dev\"; fi; done"] + stdout: StdioCollector { + onStreamFinished: { + var data = text.trim(); + if (data === "") { + root.availableBacklightDevices = []; + return; + } + + var lines = data.split("\n"); + var found = []; + var seen = ({}); + for (var i = 0; i < lines.length; i++) { + var path = root.normalizeBacklightDevicePath(lines[i]); + if (path === "" || seen[path]) + continue; + seen[path] = true; + found.push(path); + } + + root.availableBacklightDevices = found; + } + } + } + + // Detect DDC monitors + Process { + id: ddcProc + property list ddcMonitors: [] + command: ["ddcutil", "detect", "--enable-dynamic-sleep", "--sleep-multiplier=0.5"] + stdout: StdioCollector { + onStreamFinished: { + var displays = text.trim().split("\n\n"); + ddcProc.ddcMonitors = displays.map(d => { + var ddcModelMatch = d.match(/(This monitor does not support DDC\/CI|Invalid display)/); + var modelMatch = d.match(/Model:\s*(.*)/); + var busMatch = d.match(/I2C bus:[ ]*\/dev\/i2c-([0-9]+)/); + var connectorMatch = d.match(/DRM[_ ]connector:\s*card\d+-(.+)/); + var ddcModel = ddcModelMatch ? ddcModelMatch.length > 0 : false; + var model = modelMatch ? modelMatch[1] : "Unknown"; + var bus = busMatch ? busMatch[1] : "Unknown"; + var connector = connectorMatch ? connectorMatch[1].trim() : ""; + Logger.i("Brightness", "Detected DDC Monitor:", model, "connector:", connector, "bus:", bus, "is DDC:", !ddcModel); + return { + "model": model, + "busNum": bus, + "connector": connector, + "isDdc": !ddcModel + }; + }); + root.ddcMonitors = ddcProc.ddcMonitors.filter(m => m.isDdc); + } + } + } + + component Monitor: QtObject { + id: monitor + + required property ShellScreen modelData + readonly property bool isDdc: Settings.data.brightness.enableDdcSupport && root.ddcMonitors.some(m => m.connector === modelData.name) + readonly property string busNum: root.ddcMonitors.find(m => m.connector === modelData.name)?.busNum ?? "" + readonly property bool isAppleDisplay: root.appleDisplayPresent && modelData.model.startsWith("StudioDisplay") + readonly property string method: isAppleDisplay ? "apple" : (isDdc ? "ddcutil" : "internal") + + // Check if brightness control is available for this monitor + readonly property bool brightnessControlAvailable: { + if (isAppleDisplay) + return true; + if (isDdc) + return true; + // For internal displays, check if we have a brightness path + return brightnessPath !== ""; + } + + property real brightness + property real lastBrightness: 0 + property real queuedBrightness: NaN + property bool commandRunning: false + + // For internal displays - store the backlight device path + property string backlightDevice: "" + property string brightnessPath: "" + property string maxBrightnessPath: "" + property int maxBrightness: 100 + property bool ignoreNextChange: false + property bool initInProgress: false + + // Signal for brightness changes + signal brightnessUpdated(real newBrightness) + + // Execute a system command to get the current brightness value directly + readonly property Process refreshProc: Process { + stdout: StdioCollector { + onStreamFinished: { + var dataText = text.trim(); + if (dataText === "") { + return; + } + + var newBrightness = NaN; + + if (monitor.isAppleDisplay) { + // Apple display format: single integer (0-101) + var val = parseInt(dataText); + if (!isNaN(val)) { + newBrightness = val / 101; + } + } else if (monitor.isDdc) { + // DDC format: "VCP 10 C 100 100" (space-separated) + var parts = dataText.split(" "); + if (parts.length >= 4) { + var current = parseInt(parts[3]); + var max = parseInt(parts[4]); + if (!isNaN(current) && !isNaN(max) && max > 0) { + monitor.maxBrightness = max; + newBrightness = current / max; + } + } + } else { + // Internal display format: two lines (current\nmax) + var lines = dataText.split("\n"); + if (lines.length >= 2) { + var current = parseInt(lines[0].trim()); + var max = parseInt(lines[1].trim()); + if (!isNaN(current) && !isNaN(max) && max > 0) { + newBrightness = current / max; + } + } + } + + // Update if we got a valid brightness value + if (!isNaN(newBrightness) && (Math.abs(newBrightness - monitor.brightness) > 0.001 || monitor.brightness === 0)) { + monitor.brightness = newBrightness; + monitor.brightnessUpdated(monitor.brightness); + root.monitorBrightnessChanged(monitor, monitor.brightness); + Logger.d("Brightness", "Refreshed brightness from system:", monitor.modelData.name, monitor.brightness); + } + } + } + } + + readonly property Process setBrightnessProc: Process { + stdout: StdioCollector {} + onExited: (exitCode, exitStatus) => { + monitor.commandRunning = false; + // If there's a queued brightness change, process it now + if (!isNaN(monitor.queuedBrightness)) { + Qt.callLater(() => { + monitor.setBrightness(monitor.queuedBrightness); + monitor.queuedBrightness = NaN; + }); + } + } + } + + // Function to actively refresh the brightness from system + function refreshBrightnessFromSystem() { + if (!monitor.isDdc && !monitor.isAppleDisplay) { + // For internal displays, query the system directly + refreshProc.command = ["sh", "-c", "cat " + monitor.brightnessPath + " && " + "cat " + monitor.maxBrightnessPath]; + refreshProc.running = true; + } else if (monitor.isDdc && monitor.busNum !== "") { + // For DDC displays, get the current value + refreshProc.command = ["ddcutil", "-b", monitor.busNum, "--enable-dynamic-sleep", "--sleep-multiplier=0.05", "getvcp", "10", "--brief"]; + refreshProc.running = true; + } else if (monitor.isAppleDisplay) { + // For Apple displays, get the current value + refreshProc.command = ["asdbctl", "get"]; + refreshProc.running = true; + } + } + + // FileView to watch for external brightness changes (internal displays only) + readonly property FileView brightnessWatcher: FileView { + id: brightnessWatcher + // Only set path for internal displays with a valid brightness path + path: (!monitor.isDdc && !monitor.isAppleDisplay && monitor.brightnessPath !== "") ? monitor.brightnessPath : "" + watchChanges: path !== "" + onFileChanged: { + // When a file change is detected, actively refresh from system + // to ensure we get the most up-to-date value + Qt.callLater(() => { + monitor.refreshBrightnessFromSystem(); + }); + } + } + + // Initialize brightness + readonly property Process initProc: Process { + stdout: StdioCollector { + onStreamFinished: { + var dataText = text.trim(); + if (dataText === "") { + return; + } + + //Logger.i("Brightness", "Raw brightness data for", monitor.modelData.name + ":", dataText) + if (monitor.isAppleDisplay) { + var val = parseInt(dataText); + if (!isNaN(val)) { + monitor.brightness = val / 101; + Logger.d("Brightness", "Apple display brightness:", monitor.brightness); + } + } else if (monitor.isDdc) { + var parts = dataText.split(" "); + if (parts.length >= 4) { + var current = parseInt(parts[3]); + var max = parseInt(parts[4]); + if (!isNaN(current) && !isNaN(max) && max > 0) { + monitor.maxBrightness = max; + monitor.brightness = current / max; + Logger.d("Brightness", "DDC brightness:", current + "/" + max + " =", monitor.brightness); + } + } + } else { + // Internal backlight - parse the response which includes device path + var lines = dataText.split("\n"); + if (lines.length >= 3) { + monitor.backlightDevice = lines[0]; + monitor.brightnessPath = monitor.backlightDevice + "/brightness"; + monitor.maxBrightnessPath = monitor.backlightDevice + "/max_brightness"; + + var current = parseInt(lines[1]); + var max = parseInt(lines[2]); + if (!isNaN(current) && !isNaN(max) && max > 0) { + monitor.maxBrightness = max; + monitor.brightness = current / max; + Logger.d("Brightness", "Internal brightness:", current + "/" + max + " =", monitor.brightness); + Logger.d("Brightness", "Using backlight device:", monitor.backlightDevice); + } + } else { + monitor.backlightDevice = ""; + monitor.brightnessPath = ""; + monitor.maxBrightnessPath = ""; + } + } + + monitor.initInProgress = false; + } + } + onExited: (exitCode, exitStatus) => { + monitor.initInProgress = false; + } + } + + readonly property real stepSize: Settings.data.brightness.brightnessStep / 100.0 + readonly property real minBrightnessValue: (Settings.data.brightness.enforceMinimum ? 0.01 : 0.0) + + // Timer for debouncing rapid changes + readonly property Timer timer: Timer { + interval: monitor.isDdc ? 250 : 33 + onTriggered: { + if (!isNaN(monitor.queuedBrightness)) { + monitor.setBrightness(monitor.queuedBrightness); + monitor.queuedBrightness = NaN; + } + } + } + + function setBrightnessDebounced(value: real): void { + monitor.queuedBrightness = value; + timer.start(); + } + + function increaseBrightness(): void { + const value = !isNaN(monitor.queuedBrightness) ? monitor.queuedBrightness : monitor.brightness; + // Enforce minimum brightness if enabled + if (Settings.data.brightness.enforceMinimum && value < minBrightnessValue) { + setBrightnessDebounced(Math.max(stepSize, minBrightnessValue)); + } else { + // Normal brightness increase + setBrightnessDebounced(value + stepSize); + } + } + + function decreaseBrightness(): void { + const value = !isNaN(monitor.queuedBrightness) ? monitor.queuedBrightness : monitor.brightness; + setBrightnessDebounced(value - stepSize); + } + + function setBrightness(value: real): void { + value = Math.max(minBrightnessValue, Math.min(1, value)); + var rounded = Math.round(value * 100); + + // Always update internal value and trigger UI feedback immediately + monitor.brightness = value; + monitor.brightnessUpdated(value); + root.monitorBrightnessChanged(monitor, monitor.brightness); + + if (timer.running) { + monitor.queuedBrightness = value; + return; + } + + // If a command is already running, queue this value + if (monitor.commandRunning) { + monitor.queuedBrightness = value; + return; + } + + // Execute the brightness change command + if (isAppleDisplay) { + monitor.commandRunning = true; + monitor.ignoreNextChange = true; + setBrightnessProc.command = ["asdbctl", "set", rounded]; + setBrightnessProc.running = true; + } else if (isDdc && busNum !== "") { + monitor.commandRunning = true; + monitor.ignoreNextChange = true; + var ddcValue = Math.round(value * monitor.maxBrightness); + var ddcBus = busNum; + Qt.callLater(() => { + setBrightnessProc.command = ["ddcutil", "-b", ddcBus, "--noverify", "--async", "--enable-dynamic-sleep", "--sleep-multiplier=0.05", "setvcp", "10", ddcValue]; + setBrightnessProc.running = true; + }); + } else if (!isDdc) { + monitor.commandRunning = true; + monitor.ignoreNextChange = true; + var backlightDeviceName = root.getBacklightDeviceName(monitor.backlightDevice); + if (backlightDeviceName !== "") { + setBrightnessProc.command = ["brightnessctl", "-d", backlightDeviceName, "s", rounded + "%"]; + } else { + setBrightnessProc.command = ["brightnessctl", "s", rounded + "%"]; + } + setBrightnessProc.running = true; + } + } + + function initBrightness(): void { + monitor.initInProgress = true; + if (isAppleDisplay) { + initProc.command = ["asdbctl", "get"]; + initProc.running = true; + } else if (isDdc && busNum !== "") { + initProc.command = ["ddcutil", "-b", busNum, "--enable-dynamic-sleep", "--sleep-multiplier=0.05", "getvcp", "10", "--brief"]; + initProc.running = true; + } else if (!isDdc) { + // Internal backlight: first try explicit output mapping, then fall back to first available. + var preferredDevicePath = root.getMappedBacklightDevice(modelData.name); + var probeScript = ["preferred=\"$1\"", "if [ -n \"$preferred\" ] && [ ! -d \"$preferred\" ]; then preferred=\"/sys/class/backlight/$preferred\"; fi", "selected=\"\"", + "if [ -n \"$preferred\" ] && [ -f \"$preferred/brightness\" ] && [ -f \"$preferred/max_brightness\" ]; then selected=\"$preferred\"; else for dev in /sys/class/backlight/*; do if [ -f \"$dev/brightness\" ] && [ -f \"$dev/max_brightness\" ]; then selected=\"$dev\"; break; fi; done; fi", + "if [ -n \"$selected\" ]; then echo \"$selected\"; cat \"$selected/brightness\"; cat \"$selected/max_brightness\"; fi"].join("; "); + initProc.command = ["sh", "-c", probeScript, "sh", preferredDevicePath]; + initProc.running = true; + } else { + monitor.initInProgress = false; + } + } + + onBusNumChanged: initBrightness() + onIsDdcChanged: initBrightness() + Component.onCompleted: initBrightness() + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Keyboard/ClipboardService.qml b/arch/.config/quickshell/noctalia-shell/Services/Keyboard/ClipboardService.qml new file mode 100644 index 0000000..3c04bd2 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Keyboard/ClipboardService.qml @@ -0,0 +1,536 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.UI + +// Clipboard history service using cliphist + local content cache +Singleton { + id: root + + // Public API + property bool active: Settings.data.appLauncher.enableClipboardHistory && cliphistAvailable + property bool loading: false + property var items: [] // [{id, preview, mime, isImage}] + + // Check if cliphist is available on the system + property bool cliphistAvailable: false + property bool dependencyChecked: false + + // Optional automatic watchers to feed cliphist DB + property bool autoWatch: true + property bool watchersStarted: false + + // Expose decoded thumbnails by id and a revision to notify bindings + property var imageDataById: ({}) + property var _imageDataInsertOrder: [] // insertion-order IDs for LRU eviction + readonly property int _imageDataMaxEntries: 20 // max decoded images held in RAM at once + property int revision: 0 + + // Local content cache - stores full text content by ID + // This avoids relying on cliphist decode which can be unreliable + property var contentCache: ({}) + + // Track the most recent clipboard content for instant access + property string _latestTextContent: "" + property string _latestTextId: "" + + // Approximate first-seen timestamps for entries this session (seconds) + property var firstSeenById: ({}) + + // Internal: store callback for decode + property var _decodeCallback: null + property int _decodeRequestId: 0 + + // Queue for base64 decodes + property var _b64Queue: [] + property var _b64CurrentCb: null + property string _b64CurrentMime: "" + property string _b64CurrentId: "" + + signal listCompleted + + // Check if cliphist is available + Component.onCompleted: { + checkCliphistAvailability(); + } + + // Check dependency availability + function checkCliphistAvailability() { + if (dependencyChecked) + return; + dependencyCheckProcess.command = ["sh", "-c", "command -v cliphist"]; + dependencyCheckProcess.running = true; + } + + // Process to check if cliphist is available + Process { + id: dependencyCheckProcess + stdout: StdioCollector {} + onExited: (exitCode, exitStatus) => { + root.dependencyChecked = true; + if (exitCode === 0) { + root.cliphistAvailable = true; + // Start watchers if feature is enabled + if (root.active) { + startWatchers(); + } + } else { + root.cliphistAvailable = false; + // Show toast notification if feature is enabled but cliphist is missing + if (Settings.data.appLauncher.enableClipboardHistory) { + ToastService.showWarning(I18n.tr("toast.clipboard.unavailable"), I18n.tr("toast.clipboard.unavailable-desc"), 6000); + } + } + } + } + + // Start/stop watchers when enabled changes + onActiveChanged: { + if (root.active) { + startWatchers(); + } else { + stopWatchers(); + loading = false; + items = []; + } + } + + // Fallback: periodically refresh list so UI updates even if not in clip mode + Timer { + interval: 5000 + repeat: true + running: root.active + onTriggered: list() + } + + // Internal process objects + Process { + id: listProc + stdout: StdioCollector {} + onExited: (exitCode, exitStatus) => { + const out = String(stdout.text); + const lines = out.split('\n').filter(l => l.length > 0); + // cliphist list default format: " " or "\t" + const parsed = lines.map((l, i) => { + let id = ""; + let preview = ""; + const m = l.match(/^(\d+)\s+(.+)$/); + if (m) { + id = m[1]; + preview = m[2]; + } else { + const tab = l.indexOf('\t'); + id = tab > -1 ? l.slice(0, tab) : l; + preview = tab > -1 ? l.slice(tab + 1) : ""; + } + const lower = preview.toLowerCase(); + const isImage = lower.startsWith("[image]") || lower.includes(" binary data "); + // Best-effort mime guess from preview + var mime = "text/plain"; + if (isImage) { + if (lower.includes(" png")) + mime = "image/png"; + else if (lower.includes(" jpg") || lower.includes(" jpeg")) + mime = "image/jpeg"; + else if (lower.includes(" webp")) + mime = "image/webp"; + else if (lower.includes(" gif")) + mime = "image/gif"; + else + mime = "image/*"; + } + // Record first seen time for new ids (approximate copy time) + if (!root.firstSeenById[id]) { + const assumedAge = i * 15 * 60; + root.firstSeenById[id] = Time.timestamp - assumedAge; + } + // Smart type detection + var contentType = "text"; + if (isImage) { + contentType = "image"; + } else { + const t = preview.trim(); + const tLower = t.toLowerCase(); + if (/^#([a-f0-9]{3}|[a-f0-9]{6}|[a-f0-9]{8})$/.test(tLower)) { + contentType = "color"; + } else if (/^https?:\/\//i.test(t)) { + contentType = "link"; + } else if (/^(\/|~\/|file:\/\/)/i.test(t) && !t.startsWith('//') && !t.includes('\n')) { + contentType = "file"; + } else if ((t.includes('{') && t.includes('}') && (t.includes(';') || t.includes('='))) || t.includes('') || t.includes('=>') || t.includes('===') || t.includes('!==') || t.includes('::') || t.includes('->') || + /^(?:const|let|var|function|class|struct|interface|type|enum|import|export|func|fn|pub|def|using|namespace|property|public|private|protected)\b/i.test(t) || /^(?:#include|#define|#\[|@|\/\/|\/\*|<\?| { + if (item.isImage) + return true; + const p = item.preview; + // Skip UTF-16 encoded text (has null bytes between chars), chromium browser artifact + const nullCount = (p.match(/\x00/g) || []).length; + if (nullCount > p.length * 0.2) + return false; + // Skip browser-generated HTML wrapper, firefox + if (p.toLowerCase().startsWith(" root._imageDataMaxEntries) { + const evicted = root._imageDataInsertOrder.shift(); + delete root.imageDataById[evicted]; + } + root.revision += 1; + } + root._b64CurrentCb = null; + root._b64CurrentMime = ""; + root._b64CurrentId = ""; + Qt.callLater(root._startNextB64); + } + } + + // Text watcher - stores to cliphist and triggers content capture + Process { + id: watchText + stdout: StdioCollector {} + onExited: (exitCode, exitStatus) => { + if (root.autoWatch && root.watchersStarted && Settings.data.appLauncher.clipboardWatchTextCommand.trim() !== "") { + watchTextRestartTimer.restart(); + } + } + } + + Timer { + id: watchTextRestartTimer + interval: 1000 + repeat: false + onTriggered: { + if (root.autoWatch && root.watchersStarted) + watchText.running = true; + } + } + + // Image watcher + Process { + id: watchImage + stdout: StdioCollector {} + onExited: (exitCode, exitStatus) => { + if (root.autoWatch && root.watchersStarted && Settings.data.appLauncher.clipboardWatchImageCommand.trim() !== "") { + watchImageRestartTimer.restart(); + } + } + } + + Timer { + id: watchImageRestartTimer + interval: 1000 + repeat: false + onTriggered: { + if (root.autoWatch && root.watchersStarted) + watchImage.running = true; + } + } + + // Capture current clipboard text when needed + Process { + id: captureTextProc + stdout: StdioCollector {} + onExited: (exitCode, exitStatus) => { + if (exitCode === 0) { + const content = String(stdout.text); + if (content.length > 0) { + root._latestTextContent = content; + // Associate with newest item if we have one + if (root.items.length > 0 && !root.items[0].isImage) { + const newestId = root.items[0].id; + if (!root.contentCache[newestId]) { + root.contentCache[newestId] = content; + root.revision++; + } + } + } + } + } + } + + function startWatchers() { + if (!root.active || !autoWatch || watchersStarted || !root.cliphistAvailable) + return; + watchersStarted = true; + + // Text watcher + watchText.command = ["sh", "-c", Settings.data.appLauncher.clipboardWatchTextCommand]; + watchText.running = true; + + // Image watcher + watchImage.command = ["sh", "-c", Settings.data.appLauncher.clipboardWatchImageCommand]; + watchImage.running = true; + } + + function stopWatchers() { + if (!watchersStarted) + return; + watchText.running = false; + watchImage.running = false; + watchersStarted = false; + } + + // Capture current clipboard text and cache it + function captureCurrentClipboard() { + if (captureTextProc.running) + return; + captureTextProc.command = ["wl-paste", "--no-newline"]; + captureTextProc.running = true; + } + + function list(maxPreviewWidth) { + if (!root.active || !root.cliphistAvailable) { + return; + } + if (listProc.running) + return; + loading = true; + const width = maxPreviewWidth || 100; + listProc.command = ["cliphist", "list", "-preview-width", String(width)]; + listProc.running = true; + } + + // Get content for an ID - uses cache first, falls back to cliphist decode + function getContent(id) { + if (root.contentCache[id]) { + return root.contentCache[id]; + } + return null; + } + + // Async decode - checks cache first, then falls back to cliphist + function decode(id, cb) { + if (!root.cliphistAvailable) { + if (cb) + cb(""); + return; + } + + // Check cache first + const cached = root.contentCache[id]; + if (cached) { + if (cb) + cb(cached); + return; + } + + // Fall back to cliphist decode + if (decodeProc.running) { + decodeProc.running = false; + } + root._decodeRequestId++; + decodeProc.requestId = root._decodeRequestId; + root._decodeCallback = function (content) { + // Cache the result if successful + if (content && content.trim()) { + root.contentCache[id] = content; + } + if (cb) + cb(content); + }; + const idStr = String(id); + decodeProc.command = ["cliphist", "decode", idStr]; + decodeProc.running = true; + } + + function decodeToDataUrl(id, mime, cb) { + if (!root.cliphistAvailable) { + if (cb) + cb(""); + return; + } + // If cached, return immediately + if (root.imageDataById[id]) { + if (cb) + cb(root.imageDataById[id]); + return; + } + // Queue request; ensures single process handles sequentially + root._b64Queue.push({ + "id": id, + "mime": mime || "image/*", + "cb": cb + }); + if (!decodeB64Proc.running && root._b64CurrentCb === null) { + _startNextB64(); + } + } + + function getImageData(id) { + if (id === undefined) { + return null; + } + return root.imageDataById[id]; + } + + function _startNextB64() { + if (root._b64Queue.length === 0 || !root.cliphistAvailable) + return; + const job = root._b64Queue.shift(); + root._b64CurrentCb = job.cb; + root._b64CurrentMime = job.mime; + root._b64CurrentId = job.id; + decodeB64Proc.command = ["sh", "-c", `cliphist decode ${job.id} | base64 -w 0`]; + decodeB64Proc.running = true; + } + + function copyToClipboard(id) { + if (!root.cliphistAvailable) { + return; + } + copyProc.command = ["sh", "-c", `cliphist decode ${id} | wl-copy`]; + copyProc.running = true; + } + + function pasteFromClipboard(id, mime) { + if (!root.cliphistAvailable) { + return; + } + const isImage = mime && mime.startsWith("image/"); + const typeArg = isImage ? ` --type ${mime}` : ""; + const pasteKeys = isImage ? "wtype -M ctrl -k v" : "wtype -M ctrl -M shift v"; + const cmd = `cliphist decode ${id} | wl-copy${typeArg} && ${pasteKeys}`; + pasteProc.command = ["sh", "-c", cmd]; + pasteProc.running = true; + } + + function pasteText(text) { + if (!text) + return; + const escaped = text.replace(/'/g, "'\\''"); + const cmd = `printf '%s' '${escaped}' | wl-copy && wtype -M ctrl -M shift v`; + pasteProc.command = ["sh", "-c", cmd]; + pasteProc.running = true; + } + + function deleteById(id) { + if (!root.cliphistAvailable) { + return; + } + if (deleteProc.running) { + return; + } + const idStr = String(id).trim(); + // Remove from caches + delete root.contentCache[idStr]; + delete root.imageDataById[idStr]; + const orderIdx = root._imageDataInsertOrder.indexOf(idStr); + if (orderIdx !== -1) + root._imageDataInsertOrder.splice(orderIdx, 1); + deleteProc.command = ["sh", "-c", `echo ${idStr} | cliphist delete`]; + deleteProc.running = true; + } + + function wipeAll() { + if (!root.cliphistAvailable) { + return; + } + // Clear caches + root.contentCache = {}; + root.imageDataById = {}; + root._imageDataInsertOrder = []; + root._latestTextContent = ""; + root._latestTextId = ""; + + Quickshell.execDetached(["cliphist", "wipe"]); + revision++; + Qt.callLater(() => list()); + } + + // Parse image metadata from cliphist preview string + function parseImageMeta(preview) { + const re = /\[\[\s*binary data\s+([\d\.]+\s*(?:KiB|MiB|GiB|B))\s+(\w+)\s+(\d+)x(\d+)\s*\]\]/i; + const match = (preview || "").match(re); + if (!match) + return null; + return { + "size": match[1], + "fmt": (match[2] || "").toUpperCase(), + "w": Number(match[3]), + "h": Number(match[4]) + }; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Keyboard/EmojiService.qml b/arch/.config/quickshell/noctalia-shell/Services/Keyboard/EmojiService.qml new file mode 100644 index 0000000..ef818d2 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Keyboard/EmojiService.qml @@ -0,0 +1,314 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons + +// Manages emoji data loading, searching, and clipboard operations +Singleton { + id: root + + property var emojis: [] + property bool loaded: false + + // Usage tracking for popular emojis + // Format: { "emoji": { count: number, lastUsed: timestamp } } + property var usageCounts: ({}) + + // File path for persisting usage data + readonly property string usageFilePath: Settings.cacheDir + "emoji_usage.json" + + // Searches emojis based on query + function search(query) { + if (!loaded) { + return []; + } + + if (!query || query.trim() === "") { + return emojis; + } + + const terms = query.toLowerCase().split(" ").filter(t => t); + const results = emojis.filter(emoji => { + for (let term of terms) { + const emojiMatch = emoji.emoji.toLowerCase().includes(term); + const nameMatch = emoji.name.toLowerCase().includes(term); + const keywordMatch = emoji.keywords.some(kw => kw.toLowerCase().includes(term)); + const categoryMatch = emoji.category.toLowerCase().includes(term); + + if (!emojiMatch && !nameMatch && !keywordMatch && !categoryMatch) { + return false; + } + } + return true; + }); + + return results; + } + + function _getPopularEmojis(limit) { + var emojisWithUsage = emojis.map(function (emoji) { + const usageData = usageCounts[emoji.emoji]; + return { + emoji: emoji, + usageCount: usageData ? (usageData.count || 0) : 0, + lastUsed: usageData ? (usageData.lastUsed || 0) : 0 + }; + }).filter(function (item) { + return item.usageCount > 0; + }); + + // Sort by last used timestamp (descending - most recent first) + // Then by usage count if timestamps are equal + emojisWithUsage.sort(function (a, b) { + if (b.lastUsed !== a.lastUsed) { + return b.lastUsed - a.lastUsed; + } + if (b.usageCount !== a.usageCount) { + return b.usageCount - a.usageCount; + } + return a.emoji.name.localeCompare(b.emoji.name); + }); + + // Return the emoji objects limited by the specified count + return emojisWithUsage.slice(0, limit).map(function (item) { + return item.emoji; + }); + } + + function getCategoriesWithCounts() { + if (!loaded) { + return []; + } + + var categoryCounts = {}; + + for (var i = 0; i < emojis.length; i++) { + var emoji = emojis[i]; + var category = emoji.category || "other"; + if (!categoryCounts[category]) { + categoryCounts[category] = 0; + } + categoryCounts[category]++; + } + + var categories = []; + for (var cat in categoryCounts) { + categories.push({ + name: cat, + count: categoryCounts[cat] + }); + } + + return categories; + } + + function getEmojisByCategory(category) { + if (!loaded) { + return []; + } + + if (category === "recent") { + return _getPopularEmojis(25); + } + + return emojis.filter(function (emoji) { + return emoji.category === category; + }); + } + + // Record emoji usage + function recordUsage(emojiChar) { + if (emojiChar) { + const currentData = usageCounts[emojiChar] || { + count: 0, + lastUsed: 0 + }; + usageCounts[emojiChar] = { + count: currentData.count + 1, + lastUsed: Date.now() + }; + _saveUsageData(); + } + } + + // Ensure usage file exists with default content + function _ensureUsageFileExists() { + Quickshell.execDetached(["sh", "-c", `mkdir -p "$(dirname "${root.usageFilePath}")" && echo '{}' > "${root.usageFilePath}"`]); + } + + // File paths + readonly property string userEmojiPath: Settings.configDir + "emoji.json" + readonly property string builtinEmojiPath: `${Quickshell.shellDir}/Assets/Launcher/emoji.json` + + // Internal data + property var _userEmojiData: [] + property var _builtinEmojiData: [] + property int _pendingLoads: 0 + + // Initialize on component completion + Component.onCompleted: { + _loadUsageData(); + _loadEmojis(); + } + + // File loaders + FileView { + id: userEmojiFile + path: root.userEmojiPath + printErrors: false + watchChanges: false + + onLoaded: { + try { + const content = text(); + if (content) { + const parsed = JSON.parse(content); + _userEmojiData = Array.isArray(parsed) ? parsed : []; + } else { + _userEmojiData = []; + } + } catch (e) { + _userEmojiData = []; + } + _onLoadComplete(); + } + + onLoadFailed: function (error) { + _userEmojiData = []; + _onLoadComplete(); + } + } + + FileView { + id: builtinEmojiFile + path: root.builtinEmojiPath + printErrors: false + watchChanges: false + + onLoaded: { + try { + const content = text(); + if (content) { + const parsed = JSON.parse(content); + _builtinEmojiData = Array.isArray(parsed) ? parsed : []; + } else { + _builtinEmojiData = []; + } + } catch (e) { + _builtinEmojiData = []; + } + _onLoadComplete(); + } + + onLoadFailed: function (error) { + _builtinEmojiData = []; + _onLoadComplete(); + } + } + + // Load emoji files + function _loadEmojis() { + _pendingLoads = 2; + userEmojiFile.reload(); + builtinEmojiFile.reload(); + } + + // Called when one file finishes loading + function _onLoadComplete() { + _pendingLoads--; + if (_pendingLoads <= 0) { + _finalizeEmojis(); + } + } + + // Merge and deduplicate emojis + function _finalizeEmojis() { + const emojiMap = new Map(); + + // Add built-in emojis first + for (const emoji of _builtinEmojiData) { + if (emoji && emoji.emoji) { + emojiMap.set(emoji.emoji, emoji); + } + } + + // Add user emojis (override built-ins if duplicate) + for (const emoji of _userEmojiData) { + if (emoji && emoji.emoji) { + emojiMap.set(emoji.emoji, emoji); + } + } + + emojis = Array.from(emojiMap.values()); + loaded = true; + } + + // FileView for usage data + FileView { + id: usageFile + path: root.usageFilePath + printErrors: false + watchChanges: false + + onLoaded: { + try { + const content = text(); + if (content && content.trim() !== "") { + const parsed = JSON.parse(content); + if (parsed && typeof parsed === 'object') { + root.usageCounts = parsed; + } else { + root.usageCounts = {}; + } + } else { + root.usageCounts = {}; + } + } catch (e) { + root.usageCounts = {}; + } + } + + onLoadFailed: function (error) { + root.usageCounts = {}; + Qt.callLater(_ensureUsageFileExists); + } + } + + // Timer for debouncing usage data saves + Timer { + id: saveTimer + interval: 1000 + repeat: false + onTriggered: _doSaveUsageData() + } + + // Load usage data + function _loadUsageData() { + usageFile.reload(); + } + + // Save usage data with debounce + function _saveUsageData() { + saveTimer.restart(); + } + + // Actually save usage data to file + function _doSaveUsageData() { + try { + const content = JSON.stringify(root.usageCounts); + Quickshell.execDetached(["sh", "-c", `mkdir -p "$(dirname "${root.usageFilePath}")" && echo '${content}' > "${root.usageFilePath}"`]); + } catch (e) { + Logger.e("EmojiService", "Failed to save usage data: " + e.message); + } + } + + // Copies emoji to clipboard + function copy(emojiChar) { + if (emojiChar) { + recordUsage(emojiChar); // Record usage before copying + Quickshell.execDetached(["sh", "-c", `echo -n "${emojiChar}" | wl-copy`]); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Keyboard/KeyboardLayoutService.qml b/arch/.config/quickshell/noctalia-shell/Services/Keyboard/KeyboardLayoutService.qml new file mode 100644 index 0000000..f6e455f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Keyboard/KeyboardLayoutService.qml @@ -0,0 +1,319 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.Compositor +import qs.Services.UI + +Singleton { + id: root + property string currentLayout: I18n.tr("common.unknown") + property string fullLayoutName: I18n.tr("common.unknown") + property string previousLayout: "" + property bool isInitialized: false + + // Updates current layout from various format strings. Called by compositors + function setCurrentLayout(layoutString) { + root.fullLayoutName = layoutString || I18n.tr("common.unknown"); + root.currentLayout = extractLayoutCode(layoutString); + } + + // Extract layout code from various format strings + // Priority: variant > country code > language lookup > fallback + function extractLayoutCode(layoutString) { + if (!layoutString) + return I18n.tr("common.unknown"); + + const str = layoutString.toLowerCase(); + + // If it's already a short code (2-3 chars), return uppercase + if (/^[a-z]{2,3}(\+.*)?$/.test(str)) { + return str.split('+')[0].toUpperCase(); + } + + // Check for layout variants first - these are more meaningful than country codes + // when distinguishing between multiple layouts of the same language + for (const [pattern, display] of Object.entries(variantMap)) { + if (str.includes(pattern)) { + return display; + } + } + + // Extract short code from parentheses like "English (US)" + const shortCodeMatch = str.match(/\(([a-z]{2,3})\)/i); + if (shortCodeMatch) { + return shortCodeMatch[1].toUpperCase(); + } + + // Check for language/country names in the language map + // First, try to match at the start of the string (the primary language) + for (const [lang, code] of Object.entries(languageMap)) { + if (str.startsWith(lang)) { + return code.toUpperCase(); + } + } + // Then try word boundary matching anywhere in the string + for (const [lang, code] of Object.entries(languageMap)) { + const regex = new RegExp(`\\b${lang}\\b`); + if (regex.test(str)) { + return code.toUpperCase(); + } + } + + // If nothing matches, try first 2-3 characters if they look like a code + const codeMatch = str.match(/^([a-z]{2,3})/); + return codeMatch ? codeMatch[1].toUpperCase() : I18n.tr("common.unknown"); + } + + // Watch for layout changes and show toast + onCurrentLayoutChanged: { + // Update previousLayout after checking for changes + const layoutChanged = isInitialized && currentLayout !== previousLayout && currentLayout !== I18n.tr("common.unknown") && previousLayout !== "" && previousLayout !== I18n.tr("common.unknown"); + + if (layoutChanged) { + if (Settings.data.notifications.enableKeyboardLayoutToast) { + const message = I18n.tr("toast.keyboard-layout.changed", { + "layout": fullLayoutName + }); + ToastService.showNotice(I18n.tr("toast.keyboard-layout.title"), message, "", 2000); + } + Logger.d("KeyboardLayout", "Layout changed from", previousLayout, "to", currentLayout); + } + + // Update previousLayout for next comparison + previousLayout = currentLayout; + } + + Component.onCompleted: { + Logger.i("KeyboardLayout", "Service started"); + // Mark as initialized after a delay to allow first layout update to complete + // This prevents showing a toast on the initial load + initializationTimer.start(); + } + + Timer { + id: initializationTimer + interval: 2000 // Wait 2 seconds for first layout update to complete + onTriggered: { + isInitialized = true; + // Set previousLayout to current value after initialization + previousLayout = currentLayout; + Logger.d("KeyboardLayout", "Service initialized, current layout:", currentLayout); + } + } + + // Layout variants - checked BEFORE country codes + // These display the variant name when it's more meaningful than the country + property var variantMap: { + // Alternative keyboard layouts + "colemak": "Colemak", + "dvorak": "Dvorak", + "workman": "Workman", + "programmer dvorak": "Dvk-P", + "norman": "Norman", + // International variants + "intl": "Intl", + "international": "Intl", + "altgr-intl": "Intl", + "with dead keys": "Dead", + // Common variants + "phonetic": "Phon", + "extended": "Ext", + "ergonomic": "Ergo", + "legacy": "Legacy", + // Input methods + "pinyin": "Pinyin", + "cangjie": "Cangjie", + "romaji": "Romaji", + "kana": "Kana" + } + + // Language/country name to ISO code mapping + property var languageMap: { + // English variants + "english": "us", + "american": "us", + "united states": "us", + "us english": "us", + "british": "gb", + "united kingdom": "gb", + "english (uk)": "gb", + "canadian": "ca", + "canada": "ca", + "canadian english": "ca", + "australian": "au", + "australia": "au", + // Nordic countries + "swedish": "se", + "svenska": "se", + "sweden": "se", + "norwegian": "no", + "norsk": "no", + "norway": "no", + "danish": "dk", + "dansk": "dk", + "denmark": "dk", + "finnish": "fi", + "suomi": "fi", + "finland": "fi", + "icelandic": "is", + "รญslenska": "is", + "iceland": "is", + // Western/Central European Germanic + "german": "de", + "deutsch": "de", + "germany": "de", + "austrian": "at", + "austria": "at", + "รถsterreich": "at", + "swiss": "ch", + "switzerland": "ch", + "schweiz": "ch", + "suisse": "ch", + "dutch": "nl", + "nederlands": "nl", + "netherlands": "nl", + "holland": "nl", + "belgian": "be", + "belgium": "be", + "belgiรซ": "be", + "belgique": "be", + // Romance languages (Western/Southern Europe) + "french": "fr", + "franรงais": "fr", + "france": "fr", + "canadian french": "ca", + "spanish": "es", + "espaรฑol": "es", + "spain": "es", + "castilian": "es", + "italian": "it", + "italiano": "it", + "italy": "it", + "portuguese": "pt", + "portuguรชs": "pt", + "portugal": "pt", + "catalan": "ad", + "catalร ": "ad", + "andorra": "ad", + // Eastern European Romance + "romanian": "ro", + "romรขnฤƒ": "ro", + "romania": "ro", + // Slavic languages (Eastern Europe) + "russian": "ru", + "ั€ัƒััะบะธะน": "ru", + "russia": "ru", + "polish": "pl", + "polski": "pl", + "poland": "pl", + "czech": "cz", + "ฤeลกtina": "cz", + "czech republic": "cz", + "slovak": "sk", + "slovenฤina": "sk", + "slovakia": "sk", + // Ukrainian + "ukraine": "ua", + "ukrainian": "ua", + "ัƒะบั€ะฐั—ะฝััŒะบะฐ": "ua", + "bulgarian": "bg", + "ะฑัŠะปะณะฐั€ัะบะธ": "bg", + "bulgaria": "bg", + "serbian": "rs", + "srpski": "rs", + "serbia": "rs", + "croatian": "hr", + "hrvatski": "hr", + "croatia": "hr", + "slovenian": "si", + "slovenลกฤina": "si", + "slovenia": "si", + "bosnian": "ba", + "bosanski": "ba", + "bosnia": "ba", + "macedonian": "mk", + "ะผะฐะบะตะดะพะฝัะบะธ": "mk", + "macedonia": "mk", + // Celtic languages (Western Europe) + "irish": "ie", + "gaeilge": "ie", + "ireland": "ie", + "welsh": "gb", + "cymraeg": "gb", + "wales": "gb", + "scottish": "gb", + "gร idhlig": "gb", + "scotland": "gb", + // Baltic languages (Northern Europe) + "estonian": "ee", + "eesti": "ee", + "estonia": "ee", + "latvian": "lv", + "latvieลกu": "lv", + "latvia": "lv", + "lithuanian": "lt", + "lietuviลณ": "lt", + "lithuania": "lt", + // Other European languages + "hungarian": "hu", + "magyar": "hu", + "hungary": "hu", + "greek": "gr", + "ฮตฮปฮปฮทฮฝฮนฮบฮฌ": "gr", + "greece": "gr", + "albanian": "al", + "shqip": "al", + "albania": "al", + "maltese": "mt", + "malti": "mt", + "malta": "mt", + // West/Southwest Asian languages + "turkish": "tr", + "tรผrkรงe": "tr", + "turkey": "tr", + "arabic": "ar", + "ุงู„ุนุฑุจูŠุฉ": "ar", + "arab": "ar", + "hebrew": "il", + "ืขื‘ืจื™ืช": "il", + "israel": "il", + // South American languages + "brazilian": "br", + "brazilian portuguese": "br", + "brasil": "br", + "brazil": "br", + // East Asian languages + "japanese": "jp", + "ๆ—ฅๆœฌ่ชž": "jp", + "japan": "jp", + "korean": "kr", + "ํ•œ๊ตญ์–ด": "kr", + "korea": "kr", + "south korea": "kr", + "chinese": "cn", + "ไธญๆ–‡": "cn", + "china": "cn", + "simplified chinese": "cn", + "traditional chinese": "tw", + "taiwan": "tw", + "็น้ซ”ไธญๆ–‡": "tw", + // Southeast Asian languages + "thai": "th", + "เน„เธ—เธข": "th", + "thailand": "th", + "vietnamese": "vn", + "tiแบฟng viแป‡t": "vn", + "vietnam": "vn", + // South Asian languages + "hindi": "in", + "เคนเคฟเคจเฅเคฆเฅ€": "in", + "india": "in", + // African languages + "afrikaans": "za", + "south africa": "za", + "south african": "za" + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Keyboard/LockKeysService.qml b/arch/.config/quickshell/noctalia-shell/Services/Keyboard/LockKeysService.qml new file mode 100644 index 0000000..463c2ed --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Keyboard/LockKeysService.qml @@ -0,0 +1,151 @@ +pragma Singleton +import Qt.labs.folderlistmodel 2.10 +import QtQml.Models + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons + +Singleton { + id: root + + // Component registration - only poll when something needs lock key state + function registerComponent(componentId) { + root._registered[componentId] = true; + root._registered = Object.assign({}, root._registered); + Logger.d("LockKeys", "Component registered:", componentId, "- total:", root._registeredCount); + } + + function unregisterComponent(componentId) { + delete root._registered[componentId]; + root._registered = Object.assign({}, root._registered); + Logger.d("LockKeys", "Component unregistered:", componentId, "- total:", root._registeredCount); + } + + property var _registered: ({}) + readonly property int _registeredCount: Object.keys(_registered).length + readonly property bool shouldRun: _registeredCount > 0 + + property bool capsLockOn: false + property bool numLockOn: false + property bool scrollLockOn: false + + signal capsLockChanged(bool active) + signal numLockChanged(bool active) + signal scrollLockChanged(bool active) + + Instantiator { + model: FolderListModel { + id: folderModel + folder: Qt.resolvedUrl("/sys/class/leds") + showFiles: false + showOnlyReadable: true + } + delegate: Component { + FileView { + id: fileView + path: filePath + "/brightness" + // sysfs brightness can fail transiently (e.g. resume); omit console spam like other sysfs FileViews. + printErrors: false + watchChanges: false + + function parseBrightnessLedOn(raw) { + var t = raw.trim(); + if (t === "" || !/^[0-9]+$/.test(t)) + return null; + return parseInt(t, 10) !== 0; + } + + function applyLockState(kind, state, emitIfChanged) { + switch (kind) { + case "numlock": + if (emitIfChanged && root.numLockOn !== state) { + root.numLockOn = state; + root.numLockChanged(state); + Logger.i("LockKeysService", "Num Lock:", state, fileView.path); + } else if (!emitIfChanged) { + root.numLockOn = state; + } + break; + case "capslock": + if (emitIfChanged && root.capsLockOn !== state) { + root.capsLockOn = state; + root.capsLockChanged(state); + Logger.i("LockKeysService", "Caps Lock:", state, fileView.path); + } else if (!emitIfChanged) { + root.capsLockOn = state; + } + break; + case "scrolllock": + if (emitIfChanged && root.scrollLockOn !== state) { + root.scrollLockOn = state; + root.scrollLockChanged(state); + Logger.i("LockKeysService", "Scroll Lock:", state, fileView.path); + } else if (!emitIfChanged) { + root.scrollLockOn = state; + } + break; + } + } + + // Only apply after a successful read โ€” failed reloads must not update UI (empty text is not "off"). + onLoaded: { + if (!fileView.isWanted) + return; + var state = fileView.parseBrightnessLedOn(fileView.text()); + if (state === null) + return; + + var kind = fileName.split("::")[1]; + + // First read after polling starts: sync bar/UI from sysfs without firing + // *Changed signals (OSD listens to those and would flash on startup). + if (!fileView.initialCheckDone) { + fileView.initialCheckDone = true; + fileView.applyLockState(kind, state, false); + return; + } + + fileView.applyLockState(kind, state, true); + } + + // FolderListModel only provides filters for file names, not folders + property bool isWanted: { + if (fileName.startsWith("input") && fileName.includes("::")) { + switch (fileName.split("::")[1]) { + case "numlock": + case "capslock": + case "scrolllock": + return true; + } + } + return false; + } + + // After shouldRun becomes true, first brightness read updates properties only (no *Changed signals). + property bool initialCheckDone: false + property variant connections: Connections { + target: root + function onShouldRunChanged() { + if (root.shouldRun) { + fileView.initialCheckDone = false; + } + } + } + + // sysfs does not provide change notifications + property variant refreshTimer: Timer { + interval: 200 + running: root.shouldRun && fileView.isWanted + repeat: true + onTriggered: fileView.reload() + } + } + } + } + + Component.onCompleted: { + Logger.i("LockKeysService", "Service started (polling deferred until a consumer registers)."); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Location/Calendar/EvolutionDataServer.qml b/arch/.config/quickshell/noctalia-shell/Services/Location/Calendar/EvolutionDataServer.qml new file mode 100644 index 0000000..830a243 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Location/Calendar/EvolutionDataServer.qml @@ -0,0 +1,148 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.Location + +Singleton { + id: root + + // Python scripts + readonly property string checkCalendarAvailableScript: Quickshell.shellDir + '/Scripts/python/src/calendar/check-calendar.py' + readonly property string listCalendarsScript: Quickshell.shellDir + '/Scripts/python/src/calendar/list-calendars.py' + readonly property string calendarEventsScript: Quickshell.shellDir + '/Scripts/python/src/calendar/calendar-events.py' + + function init() { + availabilityCheckProcess.running = true; + } + function loadCalendars() { + listCalendarsProcess.running = true; + } + function loadEvents(daysAhead = 31, daysBehind = 14) { + CalendarService.loading = true; + CalendarService.lastError = ""; + + const now = new Date(); + const startDate = new Date(now.getTime() - (daysBehind * 24 * 60 * 60 * 1000)); + const endDate = new Date(now.getTime() + (daysAhead * 24 * 60 * 60 * 1000)); + + loadEventsProcess.startTime = Math.floor(startDate.getTime() / 1000); + loadEventsProcess.endTime = Math.floor(endDate.getTime() / 1000); + loadEventsProcess.running = true; + + Logger.d("Calendar", `Loading events (${daysBehind} days behind, ${daysAhead} days ahead): ${startDate.toLocaleDateString()} to ${endDate.toLocaleDateString()}`); + } + + // Process to check for evolution-data-server libraries + Process { + id: availabilityCheckProcess + running: false + command: ["sh", "-c", "command -v python3 >/dev/null 2>&1 && python3 " + root.checkCalendarAvailableScript + " || echo 'unavailable: python3 not installed'"] + + stdout: StdioCollector { + onStreamFinished: { + const result = text.trim(); + if (result === "available") { + CalendarService.dataProvider = CalendarService.dataProvider || root; + CalendarService.available = true; + } + + if (CalendarService.available) { + Logger.i("Calendar", "EDS libraries available"); + loadCalendars(); + } else { + Logger.w("Calendar", "EDS libraries not available: " + result); + CalendarService.lastError = "Evolution Data Server libraries not installed"; + } + } + } + + stderr: StdioCollector { + onStreamFinished: { + if (text.trim()) { + Logger.d("Calendar", "Availability check error: " + text); + CalendarService.lastError = "Failed to check library availability"; + } + } + } + } + + // Process to list available calendars + Process { + id: listCalendarsProcess + running: false + command: ["python3", root.listCalendarsScript] + + stdout: StdioCollector { + onStreamFinished: { + try { + const result = JSON.parse(text.trim()); + CalendarService.setCalendars(result); + Logger.d("Calendar", `Found ${result.length} calendar(s)`); + + // Auto-load events after discovering calendars + if (result.length > 0) { + loadEvents(); + } + } catch (e) { + Logger.d("Calendar", "Failed to parse calendars: " + e); + CalendarService.lastError = "Failed to parse calendar list"; + } + } + } + + stderr: StdioCollector { + onStreamFinished: { + if (text.trim()) { + Logger.d("Calendar", "List calendars error: " + text); + CalendarService.lastError = text.trim(); + } + } + } + } + + // Process to load events + Process { + id: loadEventsProcess + running: false + property int startTime: 0 + property int endTime: 0 + + command: ["python3", root.calendarEventsScript, startTime.toString(), endTime.toString()] + + stdout: StdioCollector { + onStreamFinished: { + CalendarService.loading = false; + + try { + const events = JSON.parse(text.trim()); + CalendarService.setEvents(events); + Logger.d("Calendar", `Loaded ${events.length} events(s)`); + } catch (e) { + Logger.d("Calendar", "Failed to parse events: " + e); + CalendarService.lastError = "Failed to parse events"; + // Fall back to cached events + CalendarService.loadCachedEvents(); + Logger.d("Calendar", "Using cached events"); + } + } + } + + stderr: StdioCollector { + onStreamFinished: { + CalendarService.loading = false; + + if (text.trim()) { + Logger.d("Calendar", "Load events error: " + text); + CalendarService.lastError = text.trim(); + + // Fall back to cached events + CalendarService.loadCachedEvents(); + Logger.d("Calendar", "Using cached events"); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Location/Calendar/Khal.qml b/arch/.config/quickshell/noctalia-shell/Services/Location/Calendar/Khal.qml new file mode 100644 index 0000000..2e14e23 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Location/Calendar/Khal.qml @@ -0,0 +1,182 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.Location + +Singleton { + id: root + + readonly property string khalEventsScript: Quickshell.shellDir + '/Scripts/python/src/calendar/khal-events.py' + + function init() { + availabilityCheckProcess.running = true; + } + function loadCalendars() { + listCalendarsProcess.running = true; + } + function loadEvents(daysAhead = 31, daysBehind = 14) { + CalendarService.loading = true; + CalendarService.lastError = ""; + + const now = new Date(); + const startDate = new Date(now.getTime() - (daysBehind * 24 * 60 * 60 * 1000)); + + loadEventsProcess.startTime = formatDateYMD(startDate); + loadEventsProcess.duration = `${daysAhead + daysBehind}d`; + loadEventsProcess.running = true; + + Logger.d("Calendar", `Loading events (${daysBehind} days behind, ${daysAhead} days ahead): ${loadEventsProcess.startTime} ${loadEventsProcess.duration}`); + } + + // Process to check for khal installation + Process { + id: availabilityCheckProcess + running: false + command: ["sh", "-c", "command -v khal >/dev/null 2>&1 && command -v python3 >/dev/null 2>&1"] + onExited: function (exitCode) { + if (exitCode === 0) { + CalendarService.available = true; + CalendarService.dataProvider = CalendarService.dataProvider || root; + } + + if (CalendarService.available) { + Logger.i("Calendar", "Khal available"); + loadCalendars(); + } else { + Logger.w("Calendar", "Khal not available"); + CalendarService.lastError = "khal binary not installed"; + } + } + } + + // Process to list available calendars + Process { + id: listCalendarsProcess + running: false + command: ["khal", "printcalendars"] + + stdout: StdioCollector { + onStreamFinished: { + try { + const calendars = text.trim().split("\n"); + CalendarService.setCalendars(calendars); + + Logger.d("Calendar", `Found ${calendars.length} calendar(s)`); + + // Auto-load events after discovering calendars + if (calendars.length > 0) { + loadEvents(); + } + } catch (e) { + Logger.d("Calendar", "Failed to parse calendars: " + e); + CalendarService.lastError = "Failed to parse calendar list"; + } + } + } + + stderr: StdioCollector { + onStreamFinished: { + if (text.trim()) { + Logger.d("Calendar", "List calendars error: " + text); + CalendarService.lastError = text.trim(); + } + } + } + } + + // Process to load events + Process { + id: loadEventsProcess + running: false + property string startTime: "" + property string duration: "" + + command: ["python3", root.khalEventsScript, startTime, duration] + + stdout: StdioCollector { + onStreamFinished: { + CalendarService.loading = false; + + try { + const events = parseEvents(text.trim()); + CalendarService.setEvents(events); + Logger.d("Calendar", `Loaded ${events.length} events(s)`); + } catch (e) { + Logger.d("Calendar", "Failed to parse events: " + e); + CalendarService.lastError = "Failed to parse events"; + // Fall back to cached events + CalendarService.loadCachedEvents(); + Logger.d("Calendar", "Using cached events"); + } + } + } + + stderr: StdioCollector { + onStreamFinished: { + CalendarService.loading = false; + + if (text.trim()) { + Logger.d("Calendar", "Load events error: " + text); + CalendarService.lastError = text.trim(); + // Fall back to cached events + CalendarService.loadCachedEvents(); + Logger.d("Calendar", "Using cached events"); + } + } + } + } + + function parseEvents(text) { + const result = []; + const duplicates = new Set(); + + for (const line of text.split("\n")) { + if (!line.trim()) + continue; + const dayEvents = JSON.parse(line); + for (const event of dayEvents) { + if (event["repeat-pattern"] !== "") { + // if there is a repeat pattern, the event must be included each time + result.push({ + uid: event.uid, + calendar: event.calendar, + summary: event.title, + start: parseTimestamp(event["start-long-full"]), + end: parseTimestamp(event["end-long-full"]), + location: event.location, + description: event.description + }); + } else if (!duplicates.has(event.uid)) { + // in any other cases, we must remove duplicates using the uid of the event + result.push({ + uid: event.uid, + calendar: event.calendar, + summary: event.title, + start: parseTimestamp(event["start-long-full"]), + end: parseTimestamp(event["end-long-full"]), + location: event.location, + description: event.description + }); + duplicates.add(event.uid); + } + } + } + + return result; + } + + function parseTimestamp(timeStr) { + // expects ISO8601 format + return Math.floor((Date.parse(timeStr)).valueOf() / 1000); + } + + function formatDateYMD(date) { + const y = date.getFullYear(); + const m = String(date.getMonth() + 1).padStart(2, "0"); + const d = String(date.getDate()).padStart(2, "0"); + return y + "-" + m + "-" + d; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Location/CalendarService.qml b/arch/.config/quickshell/noctalia-shell/Services/Location/CalendarService.qml new file mode 100644 index 0000000..7ef3b5f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Location/CalendarService.qml @@ -0,0 +1,136 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.Location.Calendar +import qs.Services.System + +Singleton { + id: root + + // Core state + property var events: ([]) + property bool loading: false + property bool available: false + property string lastError: "" + property var calendars: ([]) + + property var dataProvider: null + + // Persistent cache + property string cacheFile: Settings.cacheDir + "calendar.json" + + // Cache file handling + FileView { + id: cacheFileView + path: root.cacheFile + printErrors: false + + JsonAdapter { + id: cacheAdapter + property var cachedEvents: ([]) + property var cachedCalendars: ([]) + property string lastUpdate: "" + } + + onLoadFailed: { + cacheAdapter.cachedEvents = ([]); + cacheAdapter.cachedCalendars = ([]); + cacheAdapter.lastUpdate = ""; + } + + onLoaded: { + loadFromCache(); + } + } + + Component.onCompleted: { + Logger.i("Calendar", "Service started"); + loadFromCache(); + checkAvailability(); + } + + // Save cache with debounce + Timer { + id: saveDebounce + interval: 1000 + onTriggered: cacheFileView.writeAdapter() + } + + function setEvents(newEvents) { + root.events = newEvents; + cacheAdapter.cachedEvents = newEvents; + cacheAdapter.lastUpdate = new Date().toISOString(); + saveCache(); + } + + function setCalendars(newCalendars) { + root.calendars = newCalendars; + cacheAdapter.cachedCalendars = newCalendars; + saveCache(); + } + + function loadCachedEvents() { + if (cacheAdapter.cachedEvents.length > 0) { + root.events = cacheAdapter.cachedEvents; + } + } + + function saveCache() { + saveDebounce.restart(); + } + + // Load events and calendars from cache + function loadFromCache() { + if (cacheAdapter.cachedEvents && cacheAdapter.cachedEvents.length > 0) { + root.events = cacheAdapter.cachedEvents; + Logger.d("Calendar", `Loaded ${cacheAdapter.cachedEvents.length} cached event(s)`); + } + + if (cacheAdapter.cachedCalendars && cacheAdapter.cachedCalendars.length > 0) { + root.calendars = cacheAdapter.cachedCalendars; + Logger.d("Calendar", `Loaded ${cacheAdapter.cachedCalendars.length} cached calendar(s)`); + } + + if (cacheAdapter.lastUpdate) { + Logger.d("Calendar", `Cache last updated: ${cacheAdapter.lastUpdate}`); + } + } + + // Auto-refresh timer (every 5 minutes) + Timer { + id: refreshTimer + interval: 300000 + running: true + repeat: true + onTriggered: loadEvents() + } + + // Core functions + function checkAvailability() { + if (!Settings.data.location.showCalendarEvents) { + root.available = false; + return; + } + + Khal.init(); + EvolutionDataServer.init(); + } + + function loadCalendars() { + if (!root.available || !dataProvider) { + return; + } + + dataProvider.loadCalendars(); + } + function loadEvents(daysAhead = 31, daysBehind = 14) { + if (!root.available || !dataProvider) { + return; + } + + dataProvider.loadEvents(daysAhead, daysBehind); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Location/DarkModeService.qml b/arch/.config/quickshell/noctalia-shell/Services/Location/DarkModeService.qml new file mode 100644 index 0000000..5eb9f5d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Location/DarkModeService.qml @@ -0,0 +1,195 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Commons + +Singleton { + id: root + + property bool initComplete: false + property bool nextDarkModeState: false + + Connections { + target: LocationService.data + enabled: Settings.data.colorSchemes.schedulingMode == "location" + function onWeatherChanged() { + if (LocationService.data.weather !== null) { + const changes = root.collectWeatherChanges(LocationService.data.weather); + if (!root.initComplete) { + root.initComplete = true; + root.applyCurrentMode(changes); + } + root.scheduleNextMode(changes); + } + } + } + + Connections { + target: Settings.data.colorSchemes + enabled: Settings.data.colorSchemes.schedulingMode == "manual" + function onManualSunriseChanged() { + const changes = root.collectManualChanges(); + root.applyCurrentMode(changes); + root.scheduleNextMode(changes); + } + function onManualSunsetChanged() { + const changes = root.collectManualChanges(); + root.applyCurrentMode(changes); + root.scheduleNextMode(changes); + } + } + + Connections { + target: Settings.data.colorSchemes + function onSchedulingModeChanged() { + root.update(); + } + } + + Connections { + target: Time + function onResumed() { + Logger.i("DarkModeService", "System resumed - re-evaluating dark mode"); + root.update(); + resumeRetryTimer.restart(); + } + } + + Timer { + id: timer + onTriggered: { + Settings.data.colorSchemes.darkMode = root.nextDarkModeState; + root.update(); + } + } + + Timer { + id: resumeRetryTimer + interval: 2000 + repeat: false + onTriggered: { + Logger.i("DarkModeService", "Resume retry - re-evaluating dark mode again"); + root.update(); + } + } + + function init() { + Logger.i("DarkModeService", "Service started"); + root.update(); + } + + function update() { + if (Settings.data.colorSchemes.schedulingMode == "manual") { + const changes = collectManualChanges(); + initComplete = true; + applyCurrentMode(changes); + scheduleNextMode(changes); + } else if (Settings.data.colorSchemes.schedulingMode == "location" && LocationService.data.weather) { + const changes = collectWeatherChanges(LocationService.data.weather); + initComplete = true; + applyCurrentMode(changes); + scheduleNextMode(changes); + } + } + + function parseTime(timeString) { + const parts = timeString.split(":").map(Number); + return { + "hour": parts[0], + "minute": parts[1] + }; + } + + function collectManualChanges() { + const sunriseTime = parseTime(Settings.data.colorSchemes.manualSunrise); + const sunsetTime = parseTime(Settings.data.colorSchemes.manualSunset); + + const now = new Date(); + const year = now.getFullYear(); + const month = now.getMonth(); + const day = now.getDate(); + + const yesterdaysSunset = new Date(year, month, day - 1, sunsetTime.hour, sunsetTime.minute); + const todaysSunrise = new Date(year, month, day, sunriseTime.hour, sunriseTime.minute); + const todaysSunset = new Date(year, month, day, sunsetTime.hour, sunsetTime.minute); + const tomorrowsSunrise = new Date(year, month, day + 1, sunriseTime.hour, sunriseTime.minute); + + return [ + { + "time": yesterdaysSunset.getTime(), + "darkMode": true + }, + { + "time": todaysSunrise.getTime(), + "darkMode": false + }, + { + "time": todaysSunset.getTime(), + "darkMode": true + }, + { + "time": tomorrowsSunrise.getTime(), + "darkMode": false + } + ]; + } + + function collectWeatherChanges(weather) { + const changes = []; + + if (Date.now() < Date.parse(weather.daily.sunrise[0])) { + // The sun has not risen yet + changes.push({ + "time": Date.now() - 1, + "darkMode": true + }); + } + + for (var i = 0; i < weather.daily.sunrise.length; i++) { + changes.push({ + "time": Date.parse(weather.daily.sunrise[i]), + "darkMode": false + }); + changes.push({ + "time": Date.parse(weather.daily.sunset[i]), + "darkMode": true + }); + } + + return changes; + } + + function applyCurrentMode(changes) { + const now = Date.now(); + Logger.i("DarkModeService", `Applying mode at ${new Date(now).toLocaleString()} (${now})`); + + // changes.findLast(change => change.time < now) // not available in QML... + let lastChange = null; + for (var i = 0; i < changes.length; i++) { + Logger.d("DarkModeService", `Checking change: time=${changes[i].time} (${new Date(changes[i].time).toLocaleString()}), darkMode=${changes[i].darkMode}`); + if (changes[i].time < now) { + lastChange = changes[i]; + } + } + + if (lastChange) { + Logger.i("DarkModeService", `Selected change: time=${lastChange.time}, darkMode=${lastChange.darkMode}`); + Settings.data.colorSchemes.darkMode = lastChange.darkMode; + Logger.d("DarkModeService", `Reset: darkmode=${lastChange.darkMode}`); + } else { + Logger.w("DarkModeService", "No suitable change found for current time!"); + } + } + + function scheduleNextMode(changes) { + const now = Date.now(); + const nextChange = changes.find(change => change.time > now); + if (nextChange) { + root.nextDarkModeState = nextChange.darkMode; + timer.interval = nextChange.time - now; + timer.restart(); + Logger.d("DarkModeService", `Scheduled: darkmode=${nextChange.darkMode} in ${timer.interval} ms`); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Location/LocationService.qml b/arch/.config/quickshell/noctalia-shell/Services/Location/LocationService.qml new file mode 100644 index 0000000..b48baf8 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Location/LocationService.qml @@ -0,0 +1,397 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons + +// Location and weather service with decoupled geocoding and weather fetching. +Singleton { + id: root + + property string locationFile: Quickshell.env("NOCTALIA_WEATHER_FILE") || (Settings.cacheDir + "location.json") + property int weatherUpdateFrequency: 30 * 60 + property bool isFetchingWeather: false + + // Talia weather + readonly property int taliaMascotWeatherMonth: 3 + readonly property int taliaMascotWeatherDay: 1 + + readonly property bool taliaWeatherMascotDayActive: { + const d = Time.now; + return d.getMonth() === root.taliaMascotWeatherMonth && d.getDate() === root.taliaMascotWeatherDay; + } + + readonly property bool taliaWeatherMascotActive: taliaWeatherMascotDayActive || Settings.data.location.weatherTaliaMascotAlways + + readonly property alias data: adapter + + // True when the user has set a location name or enabled auto-locate + readonly property bool locationConfigured: Settings.data.location.name !== "" || Settings.data.location.autoLocate + + // Stable UI properties - only updated when location is successfully geocoded + property bool coordinatesReady: false + property string stableLatitude: "" + property string stableLongitude: "" + property string stableName: "" + + FileView { + id: locationFileView + path: locationFile + printErrors: false + onAdapterUpdated: saveTimer.start() + onLoaded: { + Logger.d("Location", "Loaded cached data"); + if (adapter.latitude !== "" && adapter.longitude !== "" && adapter.weatherLastFetch > 0) { + root.stableLatitude = adapter.latitude; + root.stableLongitude = adapter.longitude; + root.stableName = adapter.name; + root.coordinatesReady = true; + Logger.i("Location", "Coordinates ready"); + } + update(); + } + onLoadFailed: function (error) { + update(); + } + + JsonAdapter { + id: adapter + property string latitude: "" + property string longitude: "" + property string name: "" + property int weatherLastFetch: 0 + property var weather: null + } + } + + // Formatted coordinates for UI display + readonly property string displayCoordinates: { + if (!root.coordinatesReady || root.stableLatitude === "" || root.stableLongitude === "") { + return ""; + } + const lat = parseFloat(root.stableLatitude).toFixed(4); + const lon = parseFloat(root.stableLongitude).toFixed(4); + return `${lat}, ${lon}`; + } + + // Auto-geolocate timer - periodically updates location via IP geolocation + Timer { + id: autoLocateTimer + interval: 30 * 60 * 1000 + running: Settings.data.location.autoLocate + repeat: true + triggeredOnStart: true + onTriggered: root.geolocateAndApply() + } + + // Update timer runs when weather is enabled or location-based scheduling is active + Timer { + id: updateTimer + interval: 20 * 1000 + running: Settings.data.location.weatherEnabled || Settings.data.colorSchemes.schedulingMode == "location" + repeat: true + onTriggered: { + update(); + } + } + + Timer { + id: saveTimer + running: false + interval: 1000 + onTriggered: locationFileView.writeAdapter() + } + + function init() { + Logger.i("Location", "Service started"); + } + + function resetWeather() { + Logger.i("Location", "Resetting location and weather data"); + + root.coordinatesReady = false; + root.stableLatitude = ""; + root.stableLongitude = ""; + root.stableName = ""; + + adapter.latitude = ""; + adapter.longitude = ""; + adapter.name = ""; + adapter.weatherLastFetch = 0; + adapter.weather = null; + isFetchingWeather = false; + update(); + } + + // Main update function - geocodes location if needed, then fetches weather if enabled + function update() { + updateLocation(); + + if (Settings.data.location.weatherEnabled) { + updateWeatherData(); + } + } + + // Runs independently of weather toggle + function updateLocation() { + const locationChanged = adapter.name !== Settings.data.location.name; + const needsGeocoding = (adapter.latitude === "") || (adapter.longitude === "") || locationChanged; + + if (!needsGeocoding) { + return; + } + + if (isFetchingWeather) { + return; + } + + isFetchingWeather = true; + + if (locationChanged) { + root.coordinatesReady = false; + Logger.d("Location", "Location changed from", adapter.name, "to", Settings.data.location.name); + } + + geocodeLocation(Settings.data.location.name, function (latitude, longitude, name, country) { + adapter.name = Settings.data.location.name; + adapter.latitude = latitude.toString(); + adapter.longitude = longitude.toString(); + root.stableLatitude = adapter.latitude; + root.stableLongitude = adapter.longitude; + root.stableName = `${name}, ${country}`; + root.coordinatesReady = true; + + isFetchingWeather = false; + Logger.i("Location", `Geocoded ${Settings.data.location.name}: ${root.stableLatitude}, ${root.stableLongitude}`); + + if (locationChanged) { + adapter.weatherLastFetch = 0; + updateWeatherData(); + } + }, errorCallback); + } + + // Fetch weather data if enabled and coordinates are available + function updateWeatherData() { + if (!Settings.data.location.weatherEnabled) { + return; + } + + if (isFetchingWeather) { + return; + } + + if (adapter.latitude === "" || adapter.longitude === "") { + Logger.w("Location", "Cannot fetch weather without coordinates"); + return; + } + const needsWeatherUpdate = (adapter.weatherLastFetch === "") || (adapter.weather === null) || (Time.timestamp >= adapter.weatherLastFetch + weatherUpdateFrequency); + + if (needsWeatherUpdate) { + isFetchingWeather = true; + fetchWeatherData(adapter.latitude, adapter.longitude, errorCallback); + } + } + + // Query geocoding API to convert location name to coordinates + function geocodeLocation(locationName, callback, errorCallback) { + if (locationName === "") { + isFetchingWeather = false; + return; + } + + Logger.d("Location", "Geocoding location name"); + var geoUrl = "https://api.noctalia.dev/geocode?city=" + encodeURIComponent(locationName); + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState === XMLHttpRequest.DONE) { + if (xhr.status === 200) { + try { + var geoData = JSON.parse(xhr.responseText); + if (geoData.lat != null) { + callback(geoData.lat, geoData.lng, geoData.name, geoData.country); + } else { + errorCallback("Location", "could not resolve location name"); + } + } catch (e) { + errorCallback("Location", "Failed to parse geocoding data: " + e); + } + } else { + errorCallback("Location", `Geocoding error: ${xhr.status} ${xhr.responseText}`); + } + } + }; + xhr.open("GET", geoUrl); + xhr.send(); + } + + // Fetch weather data from Open-Meteo API + function fetchWeatherData(latitude, longitude, errorCallback) { + Logger.d("Location", "Fetching weather from api.open-meteo.com"); + var url = "https://api.open-meteo.com/v1/forecast?latitude=" + latitude + "&longitude=" + longitude + "¤t_weather=true¤t=relativehumidity_2m,surface_pressure,is_day&daily=temperature_2m_max,temperature_2m_min,weathercode,sunset,sunrise&timezone=auto"; + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState === XMLHttpRequest.DONE) { + if (xhr.status === 200) { + try { + var weatherData = JSON.parse(xhr.responseText); + //console.log(JSON.stringify(weatherData)) + + // Save core data + data.weather = weatherData; + data.weatherLastFetch = Time.timestamp; + + // Update stable display values only when complete and successful + root.stableLatitude = data.latitude = weatherData.latitude.toString(); + root.stableLongitude = data.longitude = weatherData.longitude.toString(); + root.coordinatesReady = true; + + isFetchingWeather = false; + Logger.d("Location", "Cached weather to disk - stable coordinates updated"); + } catch (e) { + errorCallback("Location", "Failed to parse weather data"); + } + } else { + errorCallback("Location", `Weather error: ${xhr.status} ${xhr.responseText}`); + } + } + }; + xhr.open("GET", url); + xhr.send(); + } + + // Geolocate via IP address using the Noctalia API + function geolocate(callback, errorCallback) { + Logger.d("Location", "Geolocating via IP"); + var url = "https://api.noctalia.dev/geolocate"; + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState === XMLHttpRequest.DONE) { + if (xhr.status === 200) { + try { + var data = JSON.parse(xhr.responseText); + if (data.lat != null) { + callback(data.lat, data.lng, data.city, data.country); + } else { + errorCallback("Location", "Geolocate: no coordinates returned"); + } + } catch (e) { + errorCallback("Location", "Failed to parse geolocate data: " + e); + } + } else { + errorCallback("Location", `Geolocate error: ${xhr.status} ${xhr.responseText}`); + } + } + }; + xhr.open("GET", url); + xhr.send(); + } + + // Geolocate via IP and apply the result as the current location + function geolocateAndApply() { + if (isFetchingWeather) { + Logger.w("Location", "Geolocate skipped, fetch already in progress"); + return; + } + geolocate(function (lat, lng, city, country) { + Logger.i("Location", "Geolocated to", city + ",", country + ":", lat + "," + lng); + + const locationChanged = adapter.name !== city; + Settings.data.location.name = city; + adapter.name = city; + adapter.latitude = lat.toString(); + adapter.longitude = lng.toString(); + root.stableLatitude = adapter.latitude; + root.stableLongitude = adapter.longitude; + root.stableName = `${city}, ${country}`; + root.coordinatesReady = true; + + if (locationChanged) { + adapter.weatherLastFetch = 0; + adapter.weather = null; + } + + if (Settings.data.location.weatherEnabled) { + updateWeatherData(); + } + }, errorCallback); + } + + // -------------------------------- + function errorCallback(module, message) { + Logger.w(module, message); + isFetchingWeather = false; + } + + // -------------------------------- + function weatherSymbolFromCode(code) { + var isDay = data.weather ? data.weather.current_weather.is_day : true; + if (code === 0) + return isDay ? "weather-sun" : "weather-moon"; + if (code === 1 || code === 2) + return isDay ? "weather-cloud-sun" : "weather-moon-stars"; + if (code === 3) + return "weather-cloud"; + if (code >= 45 && code <= 48) + return "weather-cloud-haze"; + if (code >= 51 && code <= 67) + return "weather-cloud-rain"; + if (code >= 80 && code <= 82) + return "weather-cloud-rain"; + if (code >= 71 && code <= 77) + return "weather-cloud-snow"; + if (code >= 71 && code <= 77) + return "weather-cloud-snow"; + if (code >= 85 && code <= 86) + return "weather-cloud-snow"; + if (code >= 95 && code <= 99) + return "weather-cloud-lightning"; + return "weather-cloud"; + } + + // -------------------------------- + function taliaWeatherImageFromCode(code) { + var isDay = data.weather ? data.weather.current_weather.is_day : true; + if (code >= 40 && code <= 49) + return Quickshell.shellDir + "/Assets/Talia/TaliaDazed.png"; + if (code >= 95 && code <= 99) + return Quickshell.shellDir + "/Assets/Talia/TaliaFear.png"; + var wet = (code >= 51 && code <= 67) || (code >= 80 && code <= 82) || (code >= 71 && code <= 77) || (code >= 85 && code <= 86); + if (wet) + return Quickshell.shellDir + "/Assets/Talia/TaliaSob.png"; + if ((code === 0 || code === 1 || code === 2) && isDay === false) + return Quickshell.shellDir + "/Assets/Talia/TaliaVampire.png"; + if ((code === 0 && isDay === true) || code === 1 || code === 2) + return Quickshell.shellDir + "/Assets/Talia/TaliaParty.png"; + return Quickshell.shellDir + "/Assets/Talia/TaliaBlank.png"; + } + + // -------------------------------- + function weatherDescriptionFromCode(code) { + if (code === 0) + return "Clear sky"; + if (code === 1) + return "Mainly clear"; + if (code === 2) + return "Partly cloudy"; + if (code === 3) + return "Overcast"; + if (code === 45 || code === 48) + return "Fog"; + if (code >= 51 && code <= 67) + return "Drizzle"; + if (code >= 71 && code <= 77) + return "Snow"; + if (code >= 80 && code <= 82) + return "Rain showers"; + if (code >= 95 && code <= 99) + return "Thunderstorm"; + return "Unknown"; + } + + // -------------------------------- + function celsiusToFahrenheit(celsius) { + return 32 + celsius * 1.8; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Location/NightLightService.qml b/arch/.config/quickshell/noctalia-shell/Services/Location/NightLightService.qml new file mode 100644 index 0000000..16f6a0f --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Location/NightLightService.qml @@ -0,0 +1,290 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.UI + +Singleton { + id: root + + // Night Light properties - directly bound to settings + readonly property var params: Settings.data.nightLight + property var lastCommand: [] + + // Crash tracking for auto-restart + property int _crashCount: 0 + property int _maxCrashes: 5 + + // Manual schedule tracking + property bool _manualNightPhase: false + + // Kill any stale wlsunset processes on startup to prevent issues after shell restart + Component.onCompleted: { + killStaleProcess.running = true; + } + + Process { + id: killStaleProcess + running: false + command: ["pkill", "-x", "wlsunset"] + onExited: function (code, status) { + if (code === 0) { + Logger.i("NightLight", "Killed stale wlsunset process from previous session"); + } + // Now apply the settings after cleanup + root.apply(); + } + } + + Timer { + id: restartTimer + interval: 2000 + repeat: false + onTriggered: { + if (root.params.enabled && !runner.running) { + Logger.w("NightLight", "Restarting after crash..."); + if (root.isManualMode()) { + root.applyManualSchedule(); + } else { + runner.running = true; + } + } + } + } + + Timer { + id: manualScheduleTimer + repeat: false + onTriggered: { + Logger.i("NightLight", "Manual schedule: phase boundary reached"); + root.applyManualSchedule(); + } + } + + function timeToMinutes(timeStr) { + var parts = timeStr.split(":").map(Number); + return parts[0] * 60 + parts[1]; + } + + function isManualMode() { + return !params.forced && !params.autoSchedule; + } + + function isCurrentlyNight() { + var now = new Date(); + var nowMin = now.getHours() * 60 + now.getMinutes(); + var sunsetMin = timeToMinutes(params.manualSunset); + var sunriseMin = timeToMinutes(params.manualSunrise); + + if (sunsetMin < sunriseMin) { + // Inverted: e.g. sunset=03:00, sunrise=07:00 โ†’ night is [03:00, 07:00) + return nowMin >= sunsetMin && nowMin < sunriseMin; + } else { + // Normal: e.g. sunset=18:00, sunrise=06:00 โ†’ night is [18:00, 06:00) + return nowMin >= sunsetMin || nowMin < sunriseMin; + } + } + + function msUntilNextBoundary() { + var now = new Date(); + var nowMin = now.getHours() * 60 + now.getMinutes(); + var sunsetMin = timeToMinutes(params.manualSunset); + var sunriseMin = timeToMinutes(params.manualSunrise); + + var targetMin = isCurrentlyNight() ? sunriseMin : sunsetMin; + var diffMin = targetMin - nowMin; + if (diffMin <= 0) + diffMin += 1440; + + return diffMin * 60 * 1000 - now.getSeconds() * 1000 - now.getMilliseconds(); + } + + function applyManualSchedule() { + if (!params.enabled) { + manualScheduleTimer.stop(); + runner.running = false; + return; + } + + var night = isCurrentlyNight(); + _manualNightPhase = night; + + if (night) { + var cmd = ["wlsunset"]; + cmd.push("-t", `${params.nightTemp}`, "-T", `${params.dayTemp}`); + cmd.push("-S", "23:59"); + cmd.push("-s", "00:00"); + cmd.push("-d", 1); + + if (JSON.stringify(cmd) !== JSON.stringify(lastCommand) || !runner.running) { + lastCommand = cmd; + runner.command = cmd; + runner.running = false; + runner.running = true; + } + Logger.i("NightLight", "Manual schedule: night phase - wlsunset forced on"); + } else { + lastCommand = []; + runner.running = false; + Logger.i("NightLight", "Manual schedule: day phase - wlsunset stopped"); + } + + var ms = msUntilNextBoundary(); + manualScheduleTimer.interval = Math.max(ms, 1000); + manualScheduleTimer.restart(); + Logger.i("NightLight", "Manual schedule: next boundary in " + Math.round(ms / 1000) + "s"); + } + + function apply(force = false) { + // If using LocationService, wait for it to be ready + if (!params.forced && params.autoSchedule && !LocationService.coordinatesReady) { + return; + } + + // Manual mode: handle scheduling ourselves + if (isManualMode() && params.enabled) { + _crashCount = 0; + restartTimer.stop(); + applyManualSchedule(); + return; + } + + // Not in manual mode - clean up manual timer + manualScheduleTimer.stop(); + + var command = buildCommand(); + + // Compare with previous command to avoid unnecessary restart + if (force || JSON.stringify(command) !== JSON.stringify(lastCommand)) { + lastCommand = command; + runner.command = command; + + // Set running to false so it may restart below if still enabled + runner.running = false; + } + runner.running = params.enabled; + } + + function buildCommand() { + var cmd = ["wlsunset"]; + if (params.forced) { + // Force immediate full night temperature regardless of time + // Keep distinct day/night temps but set times so we're effectively always in "night" + cmd.push("-t", `${params.nightTemp}`, "-T", `${params.dayTemp}`); + // Night spans from sunset (00:00) to sunrise (23:59) covering almost the full day + cmd.push("-S", "23:59"); // sunrise very late + cmd.push("-s", "00:00"); // sunset at midnight + // Near-instant transition + cmd.push("-d", 1); + } else if (params.autoSchedule) { + cmd.push("-t", `${params.nightTemp}`, "-T", `${params.dayTemp}`); + cmd.push("-l", `${LocationService.stableLatitude}`, "-L", `${LocationService.stableLongitude}`); + cmd.push("-d", 60 * 15); // 15min progressive fade at sunset/sunrise + } + return cmd; + } + + // Observe setting changes and location readiness + Connections { + target: Settings.data.nightLight + function onEnabledChanged() { + apply(); + // Toast: night light toggled + const enabled = !!Settings.data.nightLight.enabled; + ToastService.showNotice(I18n.tr("common.night-light"), enabled ? I18n.tr("common.enabled") : I18n.tr("common.disabled"), enabled ? "nightlight-on" : "nightlight-off"); + } + function onForcedChanged() { + apply(); + if (Settings.data.nightLight.enabled) { + ToastService.showNotice(I18n.tr("common.night-light"), Settings.data.nightLight.forced ? I18n.tr("toast.night-light.forced") : I18n.tr("toast.night-light.normal"), Settings.data.nightLight.forced ? "nightlight-forced" : "nightlight-on"); + } + } + function onNightTempChanged() { + apply(); + } + function onDayTempChanged() { + apply(); + } + function onManualSunriseChanged() { + apply(); + } + function onManualSunsetChanged() { + apply(); + } + function onAutoScheduleChanged() { + apply(); + } + } + + Connections { + target: LocationService + function onCoordinatesReadyChanged() { + if (LocationService.coordinatesReady) { + root.apply(); + } + } + } + + Timer { + id: resumeRetryTimer + interval: 2000 + repeat: false + onTriggered: { + Logger.i("NightLight", "Resume retry - re-applying night light again"); + root.apply(true); + } + } + + Connections { + target: Time + function onResumed() { + Logger.i("NightLight", "System resumed - re-applying night light"); + root.apply(true); + resumeRetryTimer.restart(); + } + } + + // Foreground process runner + Process { + id: runner + running: false + onStarted: { + Logger.i("NightLight", "Wlsunset started:", runner.command); + // Reset crash count on successful start + if (root._crashCount > 0) { + root._crashCount = 0; + } + } + onExited: function (code, status) { + if (root.params.enabled && root.isManualMode()) { + // Manual mode: only treat as crash if we're in the night phase + if (root._manualNightPhase) { + root._crashCount++; + if (root._crashCount <= root._maxCrashes) { + Logger.w("NightLight", "Wlsunset exited unexpectedly during manual night phase (code: " + code + "), restarting in 2s... (attempt " + root._crashCount + "/" + root._maxCrashes + ")"); + restartTimer.start(); + } else { + Logger.e("NightLight", "Wlsunset crashed too many times (" + root._maxCrashes + "), giving up"); + } + } else { + Logger.i("NightLight", "Wlsunset exited (manual day phase):", code, status); + root._crashCount = 0; + } + } else if (root.params.enabled) { + // Non-manual mode: any exit while enabled is a crash + root._crashCount++; + if (root._crashCount <= root._maxCrashes) { + Logger.w("NightLight", "Wlsunset exited unexpectedly (code: " + code + "), restarting in 2s... (attempt " + root._crashCount + "/" + root._maxCrashes + ")"); + restartTimer.start(); + } else { + Logger.e("NightLight", "Wlsunset crashed too many times (" + root._maxCrashes + "), giving up"); + } + } else { + Logger.i("NightLight", "Wlsunset exited (disabled):", code, status); + root._crashCount = 0; + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Media/AudioService.qml b/arch/.config/quickshell/noctalia-shell/Services/Media/AudioService.qml new file mode 100644 index 0000000..fffdb1c --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Media/AudioService.qml @@ -0,0 +1,1002 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Services.Pipewire +import qs.Commons +import qs.Services.System + +Singleton { + id: root + + // Rate limiting for volume feedback (minimum 100ms between sounds) + property var lastVolumeFeedbackTime: 0 + readonly property int minVolumeFeedbackInterval: 100 + + // Track the last sink that produced volume feedback so we can suppress the + // initial onVolumeChanged that fires on startup and device switches. + property PwNode _lastFeedbackSink: null + + // Devices + readonly property var sink: Pipewire.ready ? Pipewire.defaultAudioSink : null + readonly property var source: validatedSource + readonly property bool hasInput: !!source + readonly property list sinks: deviceNodes.sinks + readonly property list sources: deviceNodes.sources + readonly property real maxVolume: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0 + readonly property real epsilon: 0.005 + + // Fallback state sourced from wpctl when PipeWire node values go stale. + property bool wpctlAvailable: false + property bool wpctlStateValid: false + property real wpctlOutputVolume: 0 + property bool wpctlOutputMuted: true + property bool wpctlInputStateValid: false + property real wpctlInputVolume: 0 + property bool wpctlInputMuted: true + + signal volumeAtMaximum + signal volumeAtMinimum + + function clampOutputVolume(vol: real): real { + if (vol === undefined || isNaN(vol)) { + return 0; + } + return Math.max(0, Math.min(root.maxVolume, vol)); + } + + function refreshWpctlOutputState(): void { + if (!wpctlAvailable || wpctlStateProcess.running) { + return; + } + wpctlStateProcess.command = ["wpctl", "get-volume", "@DEFAULT_AUDIO_SINK@"]; + wpctlStateProcess.running = true; + } + + function refreshWpctlInputState(): void { + if (!wpctlAvailable || wpctlInputStateProcess.running) { + return; + } + wpctlInputStateProcess.command = ["wpctl", "get-volume", "@DEFAULT_AUDIO_SOURCE@"]; + wpctlInputStateProcess.running = true; + } + + function applyWpctlOutputState(raw: string): bool { + const text = String(raw || "").trim(); + const match = text.match(/Volume:\s*([0-9]*\.?[0-9]+)/i); + if (!match || match.length < 2) { + return false; + } + + const parsedVolume = Number(match[1]); + if (isNaN(parsedVolume)) { + return false; + } + + wpctlOutputVolume = clampOutputVolume(parsedVolume); + wpctlOutputMuted = /\[MUTED\]/i.test(text); + wpctlStateValid = true; + return true; + } + + function applyWpctlInputState(raw: string): bool { + const text = String(raw || "").trim(); + const match = text.match(/Volume:\s*([0-9]*\.?[0-9]+)/i); + if (!match || match.length < 2) { + return false; + } + + const parsedVolume = Number(match[1]); + if (isNaN(parsedVolume)) { + return false; + } + + wpctlInputVolume = Math.max(0, Math.min(root.maxVolume, parsedVolume)); + wpctlInputMuted = /\[MUTED\]/i.test(text); + wpctlInputStateValid = true; + return true; + } + + // Output volume (prefer wpctl state when available) + readonly property real volume: { + if (wpctlAvailable && wpctlStateValid) { + return clampOutputVolume(wpctlOutputVolume); + } + + if (!sink?.audio) { + return 0; + } + return clampOutputVolume(sink.audio.volume); + } + readonly property bool muted: { + if (wpctlAvailable && wpctlStateValid) { + return wpctlOutputMuted; + } + return sink?.audio?.muted ?? true; + } + + // Input volume (prefer wpctl state when available โ€” matches set-volume % round-trip) + readonly property real inputVolume: { + if (wpctlAvailable && wpctlInputStateValid) { + return Math.max(0, Math.min(root.maxVolume, wpctlInputVolume)); + } + if (!source?.audio) { + return 0; + } + const vol = source.audio.volume; + if (vol === undefined || isNaN(vol)) { + return 0; + } + return Math.max(0, Math.min(root.maxVolume, vol)); + } + readonly property bool inputMuted: { + if (wpctlAvailable && wpctlInputStateValid) { + return wpctlInputMuted; + } + return source?.audio?.muted ?? true; + } + + // Allow callers to skip the next OSD notification when they are already + // presenting volume state (e.g. the Audio Panel UI). We track this as a short + // time window so suppression applies to every monitor, not just the first one + // that receives the signal. + property double outputOSDSuppressedUntilMs: 0 + property double inputOSDSuppressedUntilMs: 0 + + function suppressOutputOSD(durationMs = 400) { + const target = Date.now() + durationMs; + outputOSDSuppressedUntilMs = Math.max(outputOSDSuppressedUntilMs, target); + } + + function suppressInputOSD(durationMs = 400) { + const target = Date.now() + durationMs; + inputOSDSuppressedUntilMs = Math.max(inputOSDSuppressedUntilMs, target); + } + + function consumeOutputOSDSuppression(): bool { + return Date.now() < outputOSDSuppressedUntilMs; + } + + function consumeInputOSDSuppression(): bool { + return Date.now() < inputOSDSuppressedUntilMs; + } + + readonly property real stepVolume: Settings.data.audio.volumeStep / 100.0 + + // Filtered device nodes (non-stream sinks and sources) + readonly property var deviceNodes: Pipewire.ready ? Pipewire.nodes.values.reduce((acc, node) => { + if (!node.isStream) { + // Filter out quickshell nodes (unlikely to be devices, but for consistency) + const name = node.name || ""; + const mediaName = (node.properties && node.properties["media.name"]) || ""; + if (name === "quickshell" || mediaName === "quickshell") { + return acc; + } + + if (node.isSink) { + acc.sinks.push(node); + } else if (node.audio) { + acc.sources.push(node); + } + } + return acc; + }, { + "sources": [], + "sinks": [] + }) : { + "sources": [], + "sinks": [] + } + + // Validated source (ensures it's a proper audio source, not a sink) + readonly property var validatedSource: { + if (!Pipewire.ready) { + return null; + } + const raw = Pipewire.defaultAudioSource; + if (!raw || raw.isSink || !raw.audio) { + return null; + } + // Optional: check type if available (type reflects media.class per docs) + if (raw.type && typeof raw.type === "string" && !raw.type.startsWith("Audio/Source")) { + return null; + } + return raw; + } + + // Internal state for feedback loop prevention + property bool isSettingOutputVolume: false + property bool isSettingInputVolume: false + + // Bind default sink and source to ensure their properties are available + PwObjectTracker { + id: sinkTracker + objects: root.sink ? [root.sink] : [] + } + + PwObjectTracker { + id: sourceTracker + objects: root.source ? [root.source] : [] + } + + // Track links to the default sink to find active streams + PwNodeLinkTracker { + id: sinkLinkTracker + } + + onSinkChanged: { + if (root.sink) { + sinkLinkTracker.node = root.sink; + } + } + + // Track all streams globally to prevent binding loops for filtered out streams + readonly property var streamNodes: Pipewire.ready ? Pipewire.nodes.values.filter(n => n && n.isStream) : [] + + // Find application streams that are connected to the default sink + readonly property var appStreams: { + if (!Pipewire.ready || !root.sink) { + return []; + } + + var connectedStreamIds = {}; + var connectedStreams = []; + + // Use PwNodeLinkTracker to get properly bound link groups + if (!sinkLinkTracker.linkGroups) { + return []; + } + + var linkGroupsCount = 0; + if (sinkLinkTracker.linkGroups.length !== undefined) { + linkGroupsCount = sinkLinkTracker.linkGroups.length; + } else if (sinkLinkTracker.linkGroups.count !== undefined) { + linkGroupsCount = sinkLinkTracker.linkGroups.count; + } else { + return []; + } + + if (linkGroupsCount === 0) { + return []; + } + + var intermediateNodeIds = {}; + var nodesToCheck = []; + + for (var i = 0; i < linkGroupsCount; i++) { + var linkGroup; + if (sinkLinkTracker.linkGroups.get) { + linkGroup = sinkLinkTracker.linkGroups.get(i); + } else { + linkGroup = sinkLinkTracker.linkGroups[i]; + } + + if (!linkGroup || !linkGroup.source) { + continue; + } + + var sourceNode = linkGroup.source; + + // Filter out quickshell + const name = sourceNode.name || ""; + const mediaName = (sourceNode.properties && sourceNode.properties["media.name"]) || ""; + if (name === "quickshell" || mediaName === "quickshell") { + continue; + } + + // Filter out filter (intermediate) streams + const isVirtual = (sourceNode.properties && sourceNode.properties["node.virtual"]) || ""; + // If it's an application stream node, add it directly + if (sourceNode.isStream && sourceNode.audio && !isVirtual) { + if (!connectedStreamIds[sourceNode.id]) { + connectedStreamIds[sourceNode.id] = true; + connectedStreams.push(sourceNode); + } + } else { + // Not a stream - this is an intermediate node, track it + intermediateNodeIds[sourceNode.id] = true; + nodesToCheck.push(sourceNode); + } + } + + // If we found intermediate nodes, we need to find streams connected to them + if (nodesToCheck.length > 0 || connectedStreams.length === 0) { + try { + var allNodes = Pipewire.nodes.values || []; + + // Find all stream nodes + for (var j = 0; j < allNodes.length; j++) { + var node = allNodes[j]; + if (!node || !node.isStream || !node.audio) { + continue; + } + + // Filter out quickshell + const nodeName = node.name || ""; + const nodeMediaName = (node.properties && node.properties["media.name"]) || ""; + if (nodeName === "quickshell" || nodeMediaName === "quickshell") { + continue; + } + + // Filter out filter streams + const nodeIsVirtual = (node.properties && node.properties["node.virtual"]) || ""; + if (nodeIsVirtual) { + continue; + } + + var streamId = node.id; + if (connectedStreamIds[streamId]) { + continue; + } + + if (Object.keys(intermediateNodeIds).length > 0) { + connectedStreamIds[streamId] = true; + connectedStreams.push(node); + } else if (connectedStreams.length === 0) { + connectedStreamIds[streamId] = true; + connectedStreams.push(node); + } + } + } catch (e) {} + } + + return connectedStreams; + } + + // Bind all devices to ensure their properties are available + PwObjectTracker { + objects: [...root.sinks, ...root.sources] + } + + // Per-stream volume overrides (app + media identity) so concurrent browser streams do not share one entry. + property var appVolumeOverrides: ({}) + property var _knownAppStreamIds: ({}) + property bool _isApplyingAppOverride: false + + PwObjectTracker { + objects: root.streamNodes + } + + // Keep appVolumeOverrides aligned with PipeWire when apps change volume/mute. + Item { + width: 0 + height: 0 + visible: false + + Repeater { + model: root.appStreams + + delegate: Item { + required property var modelData + + Connections { + target: modelData?.audio ?? null + + function onVolumeChanged() { + if (root._isApplyingAppOverride || !modelData?.audio) { + return; + } + var key = root.getAppKey(modelData); + if (key) { + root.setAppStreamVolume(key, modelData.audio.volume); + } + } + + function onMutedChanged() { + if (root._isApplyingAppOverride || !modelData?.audio) { + return; + } + var key = root.getAppKey(modelData); + if (key) { + root.setAppStreamMuted(key, modelData.audio.muted); + } + } + } + } + } + } + + function getAppKey(node): string { + if (!node || !node.properties) { + return ""; + } + var props = node.properties; + var base = ""; + var binary = props["application.process.binary"] || ""; + if (binary) { + var parts = binary.split("/"); + base = parts[parts.length - 1].toLowerCase(); + } + if (!base) { + var appName = props["application.name"] || ""; + if (appName) { + base = appName.toLowerCase(); + } + } + if (!base) { + var appId = props["application.id"] || ""; + if (appId) { + base = appId.toLowerCase(); + } + } + if (!base) { + return ""; + } + + var mediaName = (props["media.name"] || "").trim().toLowerCase(); + var mediaRole = (props["media.role"] || "").trim().toLowerCase(); + var tagParts = []; + if (mediaName) { + tagParts.push(mediaName); + } + if (mediaRole) { + tagParts.push(mediaRole); + } + if (tagParts.length > 0) { + return base + "\u001f" + tagParts.join("\u001e"); + } + return base + "\u001f" + String(node.id); + } + + function setAppStreamVolume(appKey: string, volume: real): void { + if (!appKey) { + return; + } + var o = appVolumeOverrides; + if (!o[appKey]) { + o[appKey] = {}; + } + + o[appKey].volume = volume; + appVolumeOverrides = o; + } + + function setAppStreamMuted(appKey: string, muted: bool): void { + if (!appKey) { + return; + } + var o = appVolumeOverrides; + if (!o[appKey]) { + o[appKey] = {}; + } + o[appKey].muted = muted; + appVolumeOverrides = o; + } + + function getAppVolumeOverride(appKey: string) { + return appKey ? (appVolumeOverrides[appKey] || null) : null; + } + + function _applyAppOverrides(): void { + var streams = root.appStreams; + if (!streams) { + return; + } + + var prevKnown = root._knownAppStreamIds; + var currentIds = {}; + _isApplyingAppOverride = true; + for (var i = 0; i < streams.length; i++) { + var s = streams[i]; + if (!s) { + continue; + } + + currentIds[s.id] = true; + var key = getAppKey(s); + var ov = key ? appVolumeOverrides[key] : null; + + // New stream node (reload, app restart, etc.): adopt PipeWire state into + // overrides so we do not force an outdated Noctalia-only value. + if (key && s.audio && !prevKnown[s.id]) { + var seeded = appVolumeOverrides; + if (!seeded[key]) { + seeded[key] = {}; + } + seeded[key].volume = s.audio.volume; + seeded[key].muted = s.audio.muted; + appVolumeOverrides = seeded; + ov = seeded[key]; + } + + if (!ov || !s.audio) { + continue; + } + if (ov.volume !== undefined && Math.abs(s.audio.volume - ov.volume) > root.epsilon) { + s.audio.volume = ov.volume; + } + if (ov.muted !== undefined && s.audio.muted !== ov.muted) { + s.audio.muted = ov.muted; + } + } + _knownAppStreamIds = currentIds; + _isApplyingAppOverride = false; + } + + Connections { + target: root + function onAppStreamsChanged() { + _appOverrideTimer.restart(); + } + } + + Timer { + id: _appOverrideTimer + interval: 50 + onTriggered: root._applyAppOverrides() + } + + Timer { + id: _appOverrideEnforcer + interval: 1000 + running: Object.keys(root.appVolumeOverrides).length > 0 && root.appStreams.length > 0 + repeat: true + onTriggered: root._applyAppOverrides() + } + + Component.onCompleted: { + wpctlAvailabilityProcess.running = true; + } + + Connections { + target: root + function onSinkChanged() { + if (root.wpctlAvailable) { + root.refreshWpctlOutputState(); + } + } + + function onSourceChanged() { + if (root.wpctlAvailable) { + root.refreshWpctlInputState(); + } + } + } + + Timer { + id: wpctlPollTimer + // Safety net only; regular updates are event-driven from sink audio signals. + interval: 20000 + running: root.wpctlAvailable + repeat: true + onTriggered: { + root.refreshWpctlOutputState(); + root.refreshWpctlInputState(); + } + } + + Process { + id: wpctlAvailabilityProcess + command: ["sh", "-c", "command -v wpctl"] + running: false + + onExited: function (code) { + root.wpctlAvailable = (code === 0); + root.wpctlStateValid = false; + root.wpctlInputStateValid = false; + if (root.wpctlAvailable) { + root.refreshWpctlOutputState(); + root.refreshWpctlInputState(); + } + } + } + + Process { + id: wpctlStateProcess + running: false + + onExited: function (code) { + if (code !== 0 || !root.applyWpctlOutputState(stdout.text)) { + root.wpctlStateValid = false; + } + } + + stdout: StdioCollector {} + } + + Process { + id: wpctlSetVolumeProcess + running: false + + onExited: function (code) { + root.isSettingOutputVolume = false; + if (code !== 0) { + Logger.w("AudioService", "wpctl set-volume failed, falling back to PipeWire node audio"); + if (root.sink?.audio) { + root.sink.audio.muted = false; + root.sink.audio.volume = root.clampOutputVolume(root.wpctlOutputVolume); + } + } + root.refreshWpctlOutputState(); + } + } + + Process { + id: wpctlSetMuteProcess + running: false + + onExited: function (_code) { + root.refreshWpctlOutputState(); + } + } + + Process { + id: wpctlInputStateProcess + running: false + + onExited: function (code) { + if (code !== 0 || !root.applyWpctlInputState(stdout.text)) { + root.wpctlInputStateValid = false; + } + } + + stdout: StdioCollector {} + } + + Process { + id: wpctlSetInputVolumeProcess + running: false + + onExited: function (code) { + root.isSettingInputVolume = false; + if (code !== 0) { + Logger.w("AudioService", "wpctl set-volume failed for default source, falling back to PipeWire node audio"); + if (root.source?.audio) { + root.source.audio.muted = false; + root.source.audio.volume = Math.max(0, Math.min(root.maxVolume, root.wpctlInputVolume)); + } + } + root.refreshWpctlInputState(); + } + } + + Process { + id: wpctlSetInputMuteProcess + running: false + + onExited: function (_code) { + root.refreshWpctlInputState(); + } + } + + // Watch output device changes for clamping + Connections { + target: sink?.audio ?? null + + function onVolumeChanged() { + if (root.wpctlAvailable) { + root.refreshWpctlOutputState(); + } + + // Ignore volume changes if we're the one setting it (to prevent feedback loop) + if (root.isSettingOutputVolume) { + return; + } + + if (!root.sink?.audio) { + return; + } + + const vol = root.sink.audio.volume; + if (vol === undefined || isNaN(vol)) { + return; + } + + if (root.sink !== root._lastFeedbackSink) { + root._lastFeedbackSink = root.sink; + } else { + playVolumeFeedback(clampOutputVolume(vol)); + } + + // If volume exceeds max, clamp it (but only if we didn't just set it) + if (vol > root.maxVolume) { + root.isSettingOutputVolume = true; + Qt.callLater(() => { + if (root.sink?.audio && root.sink.audio.volume > root.maxVolume) { + root.sink.audio.volume = root.maxVolume; + } + root.isSettingOutputVolume = false; + }); + } + } + + function onMutedChanged() { + if (root.wpctlAvailable) { + root.refreshWpctlOutputState(); + } + } + } + + // Watch input device changes for clamping + Connections { + target: source?.audio ?? null + + function onVolumeChanged() { + if (root.wpctlAvailable) { + root.refreshWpctlInputState(); + } + + // Ignore volume changes if we're the one setting it (to prevent feedback loop) + if (root.isSettingInputVolume) { + return; + } + + if (!root.source?.audio) { + return; + } + + const vol = root.source.audio.volume; + if (vol === undefined || isNaN(vol)) { + return; + } + + // If volume exceeds max, clamp it (but only if we didn't just set it) + if (vol > root.maxVolume) { + root.isSettingInputVolume = true; + Qt.callLater(() => { + if (root.source?.audio && root.source.audio.volume > root.maxVolume) { + root.source.audio.volume = root.maxVolume; + } + root.isSettingInputVolume = false; + }); + } + } + + function onMutedChanged() { + if (root.wpctlAvailable) { + root.refreshWpctlInputState(); + } + } + } + + // Output Control + function increaseVolume() { + if (!Pipewire.ready || (!sink?.audio && !wpctlAvailable)) { + return; + } + if (volume >= root.maxVolume) { + volumeAtMaximum(); + return; + } + setVolume(Math.min(root.maxVolume, volume + stepVolume)); + } + + function decreaseVolume() { + if (!Pipewire.ready || (!sink?.audio && !wpctlAvailable)) { + return; + } + if (volume <= 0) { + volumeAtMinimum(); + return; + } + setVolume(Math.max(0, volume - stepVolume)); + } + + function setVolume(newVolume: real) { + if (!Pipewire.ready || (!sink?.audio && !wpctlAvailable)) { + Logger.w("AudioService", "No sink available or not ready"); + return; + } + + const clampedVolume = clampOutputVolume(newVolume); + const delta = Math.abs(clampedVolume - volume); + if (delta < root.epsilon) { + return; + } + + if (wpctlAvailable) { + if (wpctlSetVolumeProcess.running) { + return; + } + + isSettingOutputVolume = true; + wpctlOutputMuted = false; + wpctlOutputVolume = clampedVolume; + wpctlStateValid = true; + + const volumePct = Math.round(clampedVolume * 10000) / 100; + wpctlSetVolumeProcess.command = ["sh", "-c", "wpctl set-mute @DEFAULT_AUDIO_SINK@ 0 && wpctl set-volume @DEFAULT_AUDIO_SINK@ " + volumePct + "%"]; + wpctlSetVolumeProcess.running = true; + + playVolumeFeedback(clampedVolume); + return; + } + + if (!sink?.ready || !sink?.audio) { + Logger.w("AudioService", "No sink available or not ready"); + return; + } + + // Set flag to prevent feedback loop, then set the actual volume + isSettingOutputVolume = true; + sink.audio.muted = false; + sink.audio.volume = clampedVolume; + + playVolumeFeedback(clampedVolume); + + // Clear flag after a short delay to allow external changes to be detected + Qt.callLater(() => { + isSettingOutputVolume = false; + }); + } + + function setOutputMuted(muted: bool) { + if (!Pipewire.ready || (!sink?.audio && !wpctlAvailable)) { + Logger.w("AudioService", "No sink available or Pipewire not ready"); + return; + } + + if (wpctlAvailable) { + if (wpctlSetMuteProcess.running) { + return; + } + + wpctlOutputMuted = muted; + wpctlStateValid = true; + wpctlSetMuteProcess.command = ["wpctl", "set-mute", "@DEFAULT_AUDIO_SINK@", muted ? "1" : "0"]; + wpctlSetMuteProcess.running = true; + return; + } + + sink.audio.muted = muted; + } + + function getOutputIcon() { + if (muted) { + return "volume-mute"; + } + + const clampedVolume = Math.max(0, Math.min(volume, root.maxVolume)); + + // Show volume-x icon when volume is effectively 0% (within rounding threshold) + if (clampedVolume < root.epsilon) { + return "volume-x"; + } + if (clampedVolume <= 0.5) { + return "volume-low"; + } + return "volume-high"; + } + + // Input Control + function increaseInputVolume() { + if (!Pipewire.ready || (!source?.audio && !wpctlAvailable)) { + return; + } + if (inputVolume >= root.maxVolume) { + return; + } + setInputVolume(Math.min(root.maxVolume, inputVolume + stepVolume)); + } + + function decreaseInputVolume() { + if (!Pipewire.ready || (!source?.audio && !wpctlAvailable)) { + return; + } + setInputVolume(Math.max(0, inputVolume - stepVolume)); + } + + function setInputVolume(newVolume: real) { + if (!Pipewire.ready) { + return; + } + + const clampedVolume = Math.max(0, Math.min(root.maxVolume, newVolume)); + var currentVol = 0; + if (wpctlAvailable && wpctlInputStateValid) { + currentVol = wpctlInputVolume; + } else if (source?.audio && source.audio.volume !== undefined && !isNaN(source.audio.volume)) { + currentVol = source.audio.volume; + } + const delta = Math.abs(clampedVolume - currentVol); + if (delta < root.epsilon) { + return; + } + + if (wpctlAvailable) { + if (wpctlSetInputVolumeProcess.running) { + return; + } + + isSettingInputVolume = true; + wpctlInputMuted = false; + wpctlInputVolume = clampedVolume; + wpctlInputStateValid = true; + + const volumePct = Math.round(clampedVolume * 10000) / 100; + wpctlSetInputVolumeProcess.command = ["sh", "-c", "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ 0 && wpctl set-volume @DEFAULT_AUDIO_SOURCE@ " + volumePct + "%"]; + wpctlSetInputVolumeProcess.running = true; + return; + } + + if (!source?.ready || !source?.audio) { + Logger.w("AudioService", "No source available or not ready"); + return; + } + + isSettingInputVolume = true; + source.audio.muted = false; + source.audio.volume = clampedVolume; + + Qt.callLater(() => { + isSettingInputVolume = false; + }); + } + + function playVolumeFeedback(currentVolume: real) { + if (!SoundService.multimediaAvailable) { + return; + } + + if (!Settings.data.audio.volumeFeedback) { + return; + } + + const now = Date.now(); + if (now - lastVolumeFeedbackTime < minVolumeFeedbackInterval) { + return; + } + lastVolumeFeedbackTime = now; + + const feedbackVolume = currentVolume; + const configuredSoundFile = Settings.data.audio.volumeFeedbackSoundFile; + const soundFile = (configuredSoundFile && configuredSoundFile.trim() !== "") ? configuredSoundFile : "volume-change.wav"; + + SoundService.playSound(soundFile, { + volume: feedbackVolume, + fallback: false, + repeat: false + }); + } + + function setInputMuted(muted: bool) { + if (!Pipewire.ready) { + Logger.w("AudioService", "Pipewire not ready"); + return; + } + + if (wpctlAvailable) { + if (wpctlSetInputMuteProcess.running) { + return; + } + + wpctlInputMuted = muted; + wpctlInputStateValid = true; + wpctlSetInputMuteProcess.command = ["wpctl", "set-mute", "@DEFAULT_AUDIO_SOURCE@", muted ? "1" : "0"]; + wpctlSetInputMuteProcess.running = true; + return; + } + + if (!source?.audio) { + Logger.w("AudioService", "No source available"); + return; + } + + source.audio.muted = muted; + } + + function getInputIcon() { + if (inputMuted || inputVolume <= Number.EPSILON) { + return "microphone-mute"; + } + return "microphone"; + } + + // Device Selection + function setAudioSink(newSink: PwNode): void { + if (!Pipewire.ready) { + Logger.w("AudioService", "Pipewire not ready"); + return; + } + Pipewire.preferredDefaultAudioSink = newSink; + } + + function setAudioSource(newSource: PwNode): void { + if (!Pipewire.ready) { + Logger.w("AudioService", "Pipewire not ready"); + return; + } + Pipewire.preferredDefaultAudioSource = newSource; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Media/MediaService.qml b/arch/.config/quickshell/noctalia-shell/Services/Media/MediaService.qml new file mode 100644 index 0000000..e30762d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Media/MediaService.qml @@ -0,0 +1,356 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Services.Mpris +import qs.Commons + +Singleton { + id: root + + function formatTime(seconds) { + if (isNaN(seconds) || seconds < 0) + return "0:00"; + var h = Math.floor(seconds / 3600); + var m = Math.floor((seconds % 3600) / 60); + var s = Math.floor(seconds % 60); + var pad = function (n) { + return (n < 10) ? ("0" + n) : n; + }; + + if (h > 0) { + return h + ":" + pad(m) + ":" + pad(s); + } else { + return m + ":" + pad(s); + } + } + + property var currentPlayer: null + property string playerIdentity: currentPlayer ? (currentPlayer.identity || "") : "" + property real currentPosition: 0 + property bool isSeeking: false + property int selectedPlayerIndex: 0 + property bool isPlaying: currentPlayer ? (currentPlayer.playbackState === MprisPlaybackState.Playing || currentPlayer.isPlaying) : false + property string trackTitle: currentPlayer ? (currentPlayer.trackTitle !== undefined ? currentPlayer.trackTitle.replace(/(\r\n|\n|\r)/g, "") : "") : "" + property string trackArtist: currentPlayer ? (currentPlayer.trackArtist || "") : "" + property string trackAlbum: currentPlayer ? (currentPlayer.trackAlbum || "") : "" + property string trackArtUrl: currentPlayer ? (currentPlayer.trackArtUrl || "") : "" + property real trackLength: currentPlayer ? ((currentPlayer.length < infiniteTrackLength) ? currentPlayer.length : 0) : 0 + property bool canPlay: currentPlayer ? currentPlayer.canPlay : false + property bool canPause: currentPlayer ? currentPlayer.canPause : false + property bool canGoNext: currentPlayer ? currentPlayer.canGoNext : false + property bool canGoPrevious: currentPlayer ? currentPlayer.canGoPrevious : false + property bool canSeek: currentPlayer ? currentPlayer.canSeek : false + property string positionString: formatTime(currentPosition) + property string lengthString: formatTime(trackLength) + property real infiniteTrackLength: 922337203685 + + Component.onCompleted: { + updateCurrentPlayer(); + } + + function getAvailablePlayers() { + if (!Mpris.players || !Mpris.players.values) { + return []; + } + + let allPlayers = Mpris.players.values; + let finalPlayers = []; + const genericBrowsers = ["firefox", "chromium", "chrome"]; + const blacklist = (Settings.data.audio && Settings.data.audio.mprisBlacklist) ? Settings.data.audio.mprisBlacklist : []; + + // Separate players into specific and generic lists + let specificPlayers = []; + let genericPlayers = []; + for (var i = 0; i < allPlayers.length; i++) { + if (!allPlayers[i]) + continue; + const identity = String(allPlayers[i].identity || "").toLowerCase(); + const dbusName = String(allPlayers[i].dbusName || "").toLowerCase(); + const match = blacklist.find(b => { + const s = String(b || "").toLowerCase(); + return s && (identity.includes(s) || dbusName.includes(s)); + }); + if (match) + continue; + if (genericBrowsers.some(b => identity.includes(b))) { + genericPlayers.push(allPlayers[i]); + } else { + specificPlayers.push(allPlayers[i]); + } + } + + let matchedGenericIndices = {}; + + // For each specific player, try to find and pair it with a generic partner + for (var i = 0; i < specificPlayers.length; i++) { + let specificPlayer = specificPlayers[i]; + let title1 = String(specificPlayer.trackTitle || "").trim(); + let wasMatched = false; + + if (title1) { + for (var j = 0; j < genericPlayers.length; j++) { + if (matchedGenericIndices[j]) + continue; + let genericPlayer = genericPlayers[j]; + let title2 = String(genericPlayer.trackTitle || "").trim(); + + if (title2 && (title1.includes(title2) || title2.includes(title1))) { + let dataPlayer = genericPlayer; + let identityPlayer = specificPlayer; + + let scoreSpecific = (specificPlayer.trackArtUrl ? 1 : 0); + let scoreGeneric = (genericPlayer.trackArtUrl ? 1 : 0); + if (scoreSpecific > scoreGeneric) { + dataPlayer = specificPlayer; + } + + let virtualPlayer = { + "identity": identityPlayer.identity, + "desktopEntry": identityPlayer.desktopEntry, + "trackTitle": dataPlayer.trackTitle, + "trackArtist": dataPlayer.trackArtist, + "trackAlbum": dataPlayer.trackAlbum, + "trackArtUrl": dataPlayer.trackArtUrl, + "length": dataPlayer.length || 0, + "position": dataPlayer.position || 0, + "playbackState": dataPlayer.playbackState, + "isPlaying": dataPlayer.isPlaying || false, + "canPlay": dataPlayer.canPlay || false, + "canPause": dataPlayer.canPause || false, + "canGoNext": dataPlayer.canGoNext || false, + "canGoPrevious": dataPlayer.canGoPrevious || false, + "canSeek": dataPlayer.canSeek || false, + "canControl": dataPlayer.canControl || false, + "_stateSource": dataPlayer, + "_controlTarget": identityPlayer + }; + finalPlayers.push(virtualPlayer); + matchedGenericIndices[j] = true; + wasMatched = true; + break; + } + } + } + if (!wasMatched) { + finalPlayers.push(specificPlayer); + } + } + + // Add any generic players that were not matched + for (var i = 0; i < genericPlayers.length; i++) { + if (!matchedGenericIndices[i]) { + finalPlayers.push(genericPlayers[i]); + } + } + + // Filter for controllable players + let controllablePlayers = []; + for (var i = 0; i < finalPlayers.length; i++) { + let player = finalPlayers[i]; + if (player && player.canPlay) { + controllablePlayers.push(player); + } + } + return controllablePlayers; + } + + function findActivePlayer() { + let availablePlayers = getAvailablePlayers(); + if (availablePlayers.length === 0) { + //Logger.i("Media", "No active player found") + return null; + } + + // Prioritize the actively playing player --- + for (var i = 0; i < availablePlayers.length; i++) { + if (availablePlayers[i] && availablePlayers[i].playbackState === MprisPlaybackState.Playing) { + Logger.d("Media", "Found actively playing player: " + availablePlayers[i].identity); + selectedPlayerIndex = i; + return availablePlayers[i]; + } + } + + // fallback if nothing is playing) + const preferred = (Settings.data.audio.preferredPlayer || ""); + if (preferred !== "") { + for (var i = 0; i < availablePlayers.length; i++) { + const p = availablePlayers[i]; + const identity = String(p.identity || "").toLowerCase(); + const pref = preferred.toLowerCase(); + if (identity.includes(pref)) { + selectedPlayerIndex = i; + return p; + } + } + } + + if (selectedPlayerIndex < availablePlayers.length) { + return availablePlayers[selectedPlayerIndex]; + } else { + selectedPlayerIndex = 0; + return availablePlayers[0]; + } + } + + property bool autoSwitchingPaused: false + + function switchToPlayer(index) { + let availablePlayers = getAvailablePlayers(); + if (index >= 0 && index < availablePlayers.length) { + let newPlayer = availablePlayers[index]; + if (newPlayer !== currentPlayer) { + currentPlayer = newPlayer; + selectedPlayerIndex = index; + currentPosition = currentPlayer ? currentPlayer.position : 0; + Logger.d("Media", "Manually switched to player " + currentPlayer.identity); + } + } + } + + // Switch to the most recently active player + function updateCurrentPlayer() { + let newPlayer = findActivePlayer(); + if (newPlayer !== currentPlayer) { + currentPlayer = newPlayer; + currentPosition = currentPlayer ? currentPlayer.position : 0; + Logger.d("Media", "Switching player"); + } + } + + function playPause() { + if (currentPlayer) { + let stateSource = currentPlayer._stateSource || currentPlayer; + let controlTarget = currentPlayer._controlTarget || currentPlayer; + if (stateSource.playbackState === MprisPlaybackState.Playing) { + controlTarget.pause(); + } else { + controlTarget.play(); + } + } + } + + function play() { + let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null; + if (target && target.canPlay) { + target.play(); + } + } + + function stop() { + let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null; + if (target) { + target.stop(); + } + } + + function pause() { + let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null; + if (target && target.canPause) { + target.pause(); + } + } + + function next() { + let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null; + if (target && target.canGoNext) { + target.next(); + } + } + + function previous() { + let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null; + if (target && target.canGoPrevious) { + target.previous(); + } + } + + function seek(position) { + let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null; + if (target && target.canSeek) { + target.position = position; + currentPosition = position; + } + } + + function seekRelative(offset) { + let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null; + if (target && target.canSeek && target.length > 0) { + let seekPosition = target.position + offset; + target.position = seekPosition; + currentPosition = seekPosition; + } + } + + // Seek to position based on ratio (0.0 to 1.0) + function seekByRatio(ratio) { + let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null; + if (target && target.canSeek && target.length > 0) { + let seekPosition = ratio * target.length; + target.position = seekPosition; + currentPosition = seekPosition; + } + } + + // Update progress bar every second while playing + Timer { + id: positionTimer + interval: 1000 + running: currentPlayer && !root.isSeeking && currentPlayer.isPlaying && currentPlayer.length > 0 && currentPlayer.playbackState === MprisPlaybackState.Playing + repeat: true + onTriggered: { + if (currentPlayer && !root.isSeeking && currentPlayer.isPlaying && currentPlayer.playbackState === MprisPlaybackState.Playing) { + currentPosition = currentPlayer.position; + } else { + running = false; + } + } + } + + // Avoid overwriting currentPosition while seeking due to backend position changes + Connections { + target: currentPlayer + function onPositionChanged() { + if (!root.isSeeking && currentPlayer) { + currentPosition = currentPlayer.position; + } + } + function onPlaybackStateChanged() { + if (!root.isSeeking && currentPlayer) { + currentPosition = currentPlayer.position; + } + } + } + + // Reset position when switching to inactive player + onCurrentPlayerChanged: { + if (!currentPlayer || !currentPlayer.isPlaying || currentPlayer.playbackState !== MprisPlaybackState.Playing) { + currentPosition = 0; + } + } + + Timer { + id: playerStateMonitor + interval: 2000 // Check every 2 seconds + repeat: true + running: true + onTriggered: { + //Logger.d("MediaService", "playerStateMonitor triggered. autoSwitchingPaused: " + root.autoSwitchingPaused) + if (autoSwitchingPaused) + return; + // Only update if we don't have a playing player or if current player is paused + if (!currentPlayer || !currentPlayer.isPlaying || currentPlayer.playbackState !== MprisPlaybackState.Playing) { + updateCurrentPlayer(); + } + } + } + + // Update current player when available players change + Connections { + target: Mpris.players + function onValuesChanged() { + Logger.d("Media", "Players changed"); + updateCurrentPlayer(); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Media/SpectrumService.qml b/arch/.config/quickshell/noctalia-shell/Services/Media/SpectrumService.qml new file mode 100644 index 0000000..63c2c3e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Media/SpectrumService.qml @@ -0,0 +1,81 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Services.Pipewire +import qs.Commons +import qs.Services.UI + +Singleton { + id: root + + // TODO Remove in may 2026 + Component.onCompleted: { + _setBandsCount(); + } + + // Register a component that needs audio data, call this when a visualizer becomes active. + // Pass a unique identifier (e.g., "lockscreen", "controlcenter:screen1", "plugin:fancy-audiovisualizer") + function registerComponent(componentId) { + root._registeredComponents[componentId] = true; + root._registeredComponents = Object.assign({}, root._registeredComponents); + Logger.d("Spectrum", "Component registered:", componentId, "- total:", root._registeredCount); + } + + // Unregister a component when it no longer needs audio data. + function unregisterComponent(componentId) { + delete root._registeredComponents[componentId]; + root._registeredComponents = Object.assign({}, root._registeredComponents); + Logger.d("Spectrum", "Component unregistered:", componentId, "- total:", root._registeredCount); + } + + // Check if a component is registered + function isRegistered(componentId) { + return root._registeredComponents[componentId] === true; + } + + // Component registration - any component needing audio data registers here + property var _registeredComponents: ({}) + readonly property int _registeredCount: Object.keys(_registeredComponents).length + property bool _shouldRun: _registeredCount > 0 + + property var values: [] + property bool isIdle: true + + PwAudioSpectrum { + id: spectrum + node: Pipewire.defaultAudioSink + enabled: root._shouldRun + // TODO Uncomment this in may 2026 + // bandCount: Settings.data.audio.spectrumMirrored ? 32 : 64 + frameRate: Settings.data.audio.spectrumFrameRate + lowerCutoff: 50 + upperCutoff: 12000 + noiseReduction: 0.77 + smoothing: true + + onValuesChanged: { + root.values = spectrum.values; + } + + onIdleChanged: { + root.isIdle = spectrum.idle; + } + } + + // TODO Remove in may 2026 - temporary until noctalia-qs is fully propagated + Connections { + target: Settings.data.audio + function onSpectrumMirroredChanged() { + _setBandsCount(); + } + } + function _setBandsCount() { + const bandCount = Settings.data.audio.spectrumMirrored ? 32 : 64; + if (spectrum.bandCount !== undefined) { + spectrum.bandCount = bandCount; + } else if (spectrum.barCount !== undefined) { + spectrum.barCount = bandCount; + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Networking/BluetoothRssi.qml b/arch/.config/quickshell/noctalia-shell/Services/Networking/BluetoothRssi.qml new file mode 100644 index 0000000..cc22127 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Networking/BluetoothRssi.qml @@ -0,0 +1,72 @@ +import QtQuick +import Quickshell +import Quickshell.Io +import "../../Helpers/BluetoothUtils.js" as BluetoothUtils + +QtObject { + id: root + + // Controls + property bool enabled: false + property int intervalMs: 10000 + property var connectedDevices: [] + + // Output cache and version for bindings + property var cache: ({}) // addr -> percent (0..100) + property int version: 0 + + // Internal rotation state + property int _index: 0 + property string _currentAddr: "" + + // Single process reused for RSSI queries + property Process rssiProcess: Process { + id: proc + running: false + stdout: StdioCollector { + id: out + } + onExited: function (exitCode, exitStatus) { + try { + var text = out.text || ""; + var dbm = BluetoothUtils.parseRssiOutput(text); + if (root._currentAddr !== "" && dbm !== null) { + var pct = BluetoothUtils.dbmToPercent(dbm); + if (pct !== null) { + root.cache[root._currentAddr] = pct; + root.version++; + } + } + } catch (e) {} finally { + root._currentAddr = ""; + } + } + } + + // Periodic RSSI polling timer + property Timer rssiTimer: Timer { + interval: root.intervalMs + repeat: true + running: root.enabled + onTriggered: { + var list = root.connectedDevices || []; + if (!list || list.length === 0) + return; + if (root._index >= list.length) + root._index = 0; + var dev = list[root._index++]; + if (!dev) + return; + var addr = BluetoothUtils.macFromDevice(dev); + if (!addr || addr.length < 7) + return; + if (proc.running) + return; // avoid overlap + root._currentAddr = addr; + proc.command = ["sh", "-c", `bluetoothctl info "${addr}"`]; + try { + proc.running = true; + } catch (e) {} + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Networking/BluetoothService.qml b/arch/.config/quickshell/noctalia-shell/Services/Networking/BluetoothService.qml new file mode 100644 index 0000000..5635918 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Networking/BluetoothService.qml @@ -0,0 +1,542 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Bluetooth +import Quickshell.Io +import "../../Helpers/BluetoothUtils.js" as BluetoothUtils +import qs.Commons +import qs.Services.System +import qs.Services.UI + +Singleton { + id: root + + readonly property BluetoothAdapter adapter: Bluetooth.defaultAdapter + + // Power/availability state + readonly property bool bluetoothAvailable: !!adapter + readonly property bool enabled: adapter?.enabled ?? false + readonly property bool blocked: adapter?.state === BluetoothAdapter.Blocked + + // Exposed scanning flag for UI button state; reflects adapter discovery when available + readonly property bool scanningActive: adapter?.discovering ?? false + + // Adapter discoverability (advertising) flag + readonly property bool discoverable: adapter?.discoverable ?? false + readonly property var devices: adapter ? adapter.devices : null + readonly property var connectedDevices: { + if (!adapter || !adapter.devices) { + return []; + } + return adapter.devices.values.filter(dev => dev && dev.connected); + } + + // Experimental: bestโ€‘effort RSSI polling for connected devices (without root) + // Enabled in debug mode or via user setting in Settings > Network + property bool rssiPollingEnabled: Settings?.data?.network?.bluetoothRssiPollingEnabled || Settings?.isDebug || false + // Interval can be configured from Settings; defaults to 60s + property int rssiPollIntervalMs: Settings?.data?.network?.bluetoothRssiPollIntervalMs || 60000 + // RSSI helper subโ€‘component + property BluetoothRssi rssi: BluetoothRssi { + enabled: root.enabled && root.rssiPollingEnabled + intervalMs: root.rssiPollIntervalMs + connectedDevices: root.connectedDevices + } + + // Tunables for CLI pairing/connect flow + property int pairWaitSeconds: 45 + property int connectAttempts: 5 + property int connectRetryIntervalMs: 2000 + + // Interaction state + property bool pinRequired: false + + // Internal variables + property bool _discoveryWasRunning: false + property bool _ctlInit: false + property var _autoConnectQueue: [] + + // Persistent cache for per-device auto-connect toggle + property string cacheFile: Settings.cacheDir + "bluetooth_devices.json" + + FileView { + id: cacheFileView + path: root.cacheFile + printErrors: false + + JsonAdapter { + id: cacheAdapter + property var autoConnectSettings: ({}) + } + } + + // Handle system wakeup to force-poll and ensure state is up-to-date + Connections { + target: Time + function onResumed() { + Logger.i("Bluetooth", "System resumed - forcing state poll"); + ctlPollTimer.restart(); + } + } + + // Track adapter state changes + Connections { + target: adapter + function onStateChanged() { + if (!adapter || adapter.state === BluetoothAdapter.Enabling || adapter.state === BluetoothAdapter.Disabling) { + return; + } + checkAirplaneMode(); + } + function onEnabledChanged() { + if (adapter && adapter.enabled && Settings.data.network.bluetoothAutoConnect) { + autoConnectTimer.restart(); + } + } + } + + Connections { + target: Settings.data.network + function onBluetoothAutoConnectChanged() { + if (Settings.data.network.bluetoothAutoConnect && adapter && adapter.enabled) { + autoConnectTimer.restart(); + } else { + autoConnectTimer.stop(); + } + } + } + + Component.onCompleted: { + Logger.i("Bluetooth", "Service started"); + autoConnectTimer.restart(); + } + + Timer { + id: autoConnectTimer + interval: 1500 + repeat: false + onTriggered: attemptAutoConnect() + } + + Timer { + id: autoConnectStepTimer + interval: 500 + repeat: false + onTriggered: { + var device = root._autoConnectQueue.shift(); + if (device && device.paired && !device.connected && !device.blocked) { + Logger.i("Bluetooth", "Auto-connecting to:", device.name || device.deviceName); + connectDeviceWithTrust(device); + } + if (root._autoConnectQueue.length > 0) { + autoConnectStepTimer.restart(); + } + } + } + + Timer { + id: ctlPollTimer + interval: 250 + running: false + onTriggered: { + if (!adapter || !ProgramCheckerService.bluetoothctlAvailable) { + return; + } + ctlPollProcess.running = true; + } + } + + // Adapter power (enable/disable) via bluetoothctl + function setBluetoothEnabled(state) { + if (!adapter) { + Logger.d("Bluetooth", "Enable/Disable skipped: no adapter"); + return; + } + try { + adapter.enabled = state; + Logger.i("Bluetooth", "SetBluetoothEnabled", state); + } catch (e) { + Logger.w("Bluetooth", "Enable/Disable failed", e); + ToastService.showWarning(I18n.tr("common.bluetooth"), I18n.tr("toast.bluetooth.state-change-failed")); + } + } + + // Check if airplane mode has been toggled + function checkAirplaneMode() { + var isAirplaneModeActive = !NetworkService.wifiEnabled && adapter.state === BluetoothAdapter.Blocked; + if (isAirplaneModeActive && !NetworkService.airplaneModeEnabled) { + NetworkService.airplaneModeToggled = true; + NetworkService.airplaneModeEnabled = true; + ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), I18n.tr("common.enabled"), "plane"); + Logger.i("AirplaneMode", "Enabled"); + } else if (!isAirplaneModeActive && NetworkService.airplaneModeEnabled) { + NetworkService.airplaneModeToggled = true; + NetworkService.airplaneModeEnabled = false; + ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), I18n.tr("common.disabled"), "plane-off"); + Logger.i("AirplaneMode", "Disabled"); + } else if (adapter.enabled) { + ToastService.showNotice(I18n.tr("common.bluetooth"), I18n.tr("common.enabled"), "bluetooth"); + Logger.d("Bluetooth", "Adapter enabled"); + } else { + ToastService.showNotice(I18n.tr("common.bluetooth"), I18n.tr("common.disabled"), "bluetooth-off"); + Logger.d("Bluetooth", "Adapter disabled"); + } + } + + // Unify discovery controls + function setScanActive(active) { + if (!adapter) { + Logger.d("Bluetooth", "Scan request ignored: adapter unavailable"); + return; + } + try { + if (active || adapter.discovering) { // Only attempt to set if activating, or if deactivating and currently currently discovering + adapter.discovering = active; + } + } catch (e) { + Logger.e("Bluetooth", "setScanActive failed", e); + } + } + + // Toggle adapter discoverability (advertising visibility) via bluetoothctl + function setDiscoverable(state) { + if (!adapter) { + Logger.d("Bluetooth", "Discoverable change skipped: no adapter"); + return; + } + try { + adapter.discoverable = state; + Logger.i("Bluetooth", "Discoverable state set to:", state); + } catch (e) { + Logger.w("Bluetooth", "Failed to change discoverable state", e); + ToastService.showWarning(I18n.tr("common.bluetooth"), I18n.tr("toast.bluetooth.discoverable-change-failed")); + } + } + + function sortDevices(devices) { + return devices.sort(function (a, b) { + var aName = a.name || a.deviceName || ""; + var bName = b.name || b.deviceName || ""; + + var aHasRealName = aName.indexOf(" ") !== -1 && aName.length > 3; + var bHasRealName = bName.indexOf(" ") !== -1 && bName.length > 3; + + if (aHasRealName && !bHasRealName) { + return -1; + } + if (!aHasRealName && bHasRealName) { + return 1; + } + + var aSignal = (a.signalStrength !== undefined && a.signalStrength > 0) ? a.signalStrength : 0; + var bSignal = (b.signalStrength !== undefined && b.signalStrength > 0) ? b.signalStrength : 0; + return bSignal - aSignal; + }); + } + + function getDeviceIcon(device) { + if (!device) { + return "bt-device-generic"; + } + return BluetoothUtils.deviceIcon(device.name || device.deviceName, device.icon); + } + + function canConnect(device) { + if (!device) { + return false; + } + return !device.connected && (device.paired || device.trusted) && !device.pairing && !device.blocked; + } + + function canDisconnect(device) { + if (!device) { + return false; + } + return device.connected && !device.pairing && !device.blocked; + } + + // Textual signal quality (translated) + function getSignalStrength(device) { + var p = getSignalPercent(device); + if (p === null) { + return I18n.tr("bluetooth.panel.signal-text-unknown"); + } + if (p >= 80) { + return I18n.tr("bluetooth.panel.signal-text-excellent"); + } + if (p >= 60) { + return I18n.tr("bluetooth.panel.signal-text-good"); + } + if (p >= 40) { + return I18n.tr("bluetooth.panel.signal-text-fair"); + } + if (p >= 20) { + return I18n.tr("bluetooth.panel.signal-text-poor"); + } + return I18n.tr("bluetooth.panel.signal-text-very-poor"); + } + + // Numeric helpers for UI rendering + function getSignalPercent(device) { + // Establish binding dependency so UI updates when RSSI cache changes + var _v = rssi.version; + return BluetoothUtils.signalPercent(device, rssi.cache, _v); + } + + function getBatteryPercent(device) { + return BluetoothUtils.batteryPercent(device); + } + + function getSignalIcon(device) { + var p = getSignalPercent(device); + return BluetoothUtils.signalIcon(p); + } + + function isDeviceBusy(device) { + if (!device) { + return false; + } + return device.pairing || device.state === BluetoothDevice.Disconnecting || device.state === BluetoothDevice.Connecting; + } + + // Return a stable unique key for a device (prefer MAC address) + function deviceKey(device) { + return BluetoothUtils.deviceKey(device); + } + + // Deduplicate a list of devices using the stable key + function dedupeDevices(devList) { + return BluetoothUtils.dedupeDevices(devList); + } + + // Separate capability helpers + function canPair(device) { + if (!device) { + return false; + } + return !device.connected && !device.paired && !device.trusted && !device.pairing && !device.blocked; + } + + // Pairing and unpairing helpers + function pairDevice(device) { + if (!device) { + return; + } + ToastService.showNotice(I18n.tr("common.bluetooth"), I18n.tr("common.pairing"), "bluetooth"); + try { + pairWithBluetoothctl(device); + } catch (e) { + Logger.w("Bluetooth", "pairDevice failed", e); + ToastService.showWarning(I18n.tr("common.bluetooth"), I18n.tr("toast.bluetooth.pair-failed")); + } + } + + function submitPin(pin) { + if (pairingProcess.running) { + pairingProcess.write(pin + "\n"); + root.pinRequired = false; + } + } + + function cancelPairing() { + if (pairingProcess.running) { + pairingProcess.running = false; + } + root.pinRequired = false; + } + + // Pair using bluetoothctl which registers its own BlueZ agent internally. + function pairWithBluetoothctl(device) { + if (!device) { + return; + } + var addr = BluetoothUtils.macFromDevice(device); + if (!addr || addr.length < 7) { + Logger.w("Bluetooth", "pairWithBluetoothctl: no valid address for device"); + return; + } + + Logger.i("Bluetooth", "pairWithBluetoothctl", addr); + + if (pairingProcess.running) { + pairingProcess.running = false; + } + root.pinRequired = false; + + const pairWait = Math.max(5, Number(root.pairWaitSeconds) | 0); + const attempts = Math.max(1, Number(root.connectAttempts) | 0); + const intervalMs = Math.max(500, Number(root.connectRetryIntervalMs) | 0); + const intervalSec = Math.max(1, Math.round(intervalMs / 1000)); + + // Temporarily pause discovery during pair/connect to reduce HCI churn + root._discoveryWasRunning = root.scanningActive; + if (root.scanningActive) { + root.setScanActive(false); + } + + const scriptPath = Quickshell.shellDir + "/Scripts/python/src/network/bluetooth-pair.py"; + pairingProcess.command = ["python3", scriptPath, String(addr), String(pairWait), String(attempts), String(intervalSec)]; + pairingProcess.running = true; + } + + // Helper to run bluetoothctl and scripts with consistent error logging + function btExec(args) { + try { + Quickshell.execDetached(args); + } catch (e) { + Logger.w("Bluetooth", "btExec failed", e); + } + } + + // Status key for a device (untranslated) + function getStatusKey(device) { + if (!device) { + return ""; + } + try { + if (device.pairing) + return "pairing"; + if (device.blocked) + return "blocked"; + if (device.state === BluetoothDevice.Connecting) + return "connecting"; + if (device.state === BluetoothDevice.Disconnecting) + return "disconnecting"; + } catch (_) {} + return ""; + } + + function unpairDevice(device) { + forgetDevice(device); + } + + function getDeviceAutoConnect(device) { + if (!device || !device.address || !cacheAdapter.autoConnectSettings) { + return false; + } + const mac = device.address; + const settings = cacheAdapter.autoConnectSettings[mac]; + return settings ? !!settings.autoConnect : false; + } + + function setDeviceAutoConnect(device, enabled) { + if (!device || !device.address) { + return; + } + const mac = device.address; + let settings = cacheAdapter.autoConnectSettings || ({}); + if (enabled) { + settings[mac] = { + autoConnect: true, + deviceName: device.name || device.deviceName || "" + }; + } else { + delete settings[mac]; + } + cacheAdapter.autoConnectSettings = settings; + cacheFileView.writeAdapter(); + } + + function attemptAutoConnect() { + if (NetworkService.airplaneModeEnabled || !adapter || !adapter.enabled || !Settings.data.network.bluetoothAutoConnect) { + return; + } + + _autoConnectQueue = adapter.devices.values.filter(dev => dev && dev.paired && !dev.connected && !dev.blocked && getDeviceAutoConnect(dev) === true); + + if (root._autoConnectQueue.length > 0) { + autoConnectStepTimer.restart(); + } + } + + function connectDeviceWithTrust(device) { + if (!device) { + return; + } + try { + device.trusted = true; + device.connect(); + } catch (e) { + Logger.w("Bluetooth", "connectDeviceWithTrust failed", e); + ToastService.showWarning(I18n.tr("common.bluetooth"), I18n.tr("toast.bluetooth.connect-failed")); + } + } + + function disconnectDevice(device) { + if (!device) { + return; + } + try { + device.disconnect(); + } catch (e) { + Logger.w("Bluetooth", "disconnectDevice failed", e); + ToastService.showWarning(I18n.tr("common.bluetooth"), I18n.tr("toast.bluetooth.disconnect-failed")); + } + } + + function forgetDevice(device) { + if (!device) { + return; + } + try { + device.trusted = false; + device.forget(); + } catch (e) { + Logger.w("Bluetooth", "forgetDevice failed", e); + ToastService.showWarning(I18n.tr("common.bluetooth"), I18n.tr("toast.bluetooth.forget-failed")); + } + } + + // Poll Bluetooth power state with bluetoothctl to handle a Quickshell bug on resume after suspend + Process { + id: ctlPollProcess + command: ["bluetoothctl", "show"] + running: false + stdout: StdioCollector { + onStreamFinished: { + var powered = false; + var mp = text.match(/\bPowered:\s*(yes|no)\b/i); + if (mp) { + powered = mp[1].toLowerCase() === 'yes'; + } + if (adapter.enabled !== powered) { + adapter.enabled = powered; + } + } + } + stderr: StdioCollector { + onStreamFinished: { + if (text.trim()) { + Logger.d("Bluetooth", "Failed to parse bluetoothctl show output" + text); + } + } + } + } + + // Interactive pairing process + Process { + id: pairingProcess + stdout: SplitParser { + onRead: data => { + Logger.d("Bluetooth", data); + if (data.indexOf("PIN_REQUIRED") !== -1) { + root.pinRequired = true; + Logger.i("Bluetooth", "PIN required for pairing"); + } + } + } + onExited: { + root.pinRequired = false; + Logger.i("Bluetooth", "Pairing process exited."); + // Restore discovery if we paused it + if (root._discoveryWasRunning) { + root.setScanActive(true); + } + root._discoveryWasRunning = false; + } + environment: ({ + "LC_ALL": "C" + }) + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Networking/NetworkService.qml b/arch/.config/quickshell/noctalia-shell/Services/Networking/NetworkService.qml new file mode 100644 index 0000000..8f1f106 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Networking/NetworkService.qml @@ -0,0 +1,1161 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Networking +import qs.Commons +import qs.Services.System +import qs.Services.UI + +Singleton { + id: root + // Shared core (read-only) properties + readonly property bool wifiAvailable: _wifiAvailable + readonly property bool ethernetAvailable: _ethernetAvailable + readonly property bool internetConnectivity: _internetConnectivity + readonly property string networkConnectivity: _networkConnectivity + + // Supported Wi-Fi security types + readonly property var supportedSecurityTypes: [ + { + key: "open", + name: I18n.tr("wifi.panel.security-open") + }, + { + key: "wep", + name: I18n.tr("wifi.panel.security-wep") + }, + { + key: "wpa-psk", + name: I18n.tr("wifi.panel.security-wpa") + }, + { + key: "wpa2-psk", + name: I18n.tr("wifi.panel.security-wpa23") + }, + { + key: "sae", + name: I18n.tr("wifi.panel.security-wpa3") + }, + { + key: "wpa-eap", + name: I18n.tr("wifi.panel.security-wpa-ent") + }, + { + key: "wpa2-eap", + name: I18n.tr("wifi.panel.security-wpa2-ent") + }, + { + key: "wpa3-eap", + name: I18n.tr("wifi.panel.security-wpa3-ent") + } + ] + + // Core properties + property bool _wifiAvailable: false + property bool _ethernetAvailable: false + property string _networkConnectivity: "unknown" + property bool _internetConnectivity: false + property string lastError: "" + property int activeDetailsTtlMs: 10000 + + // Ethernet properties + property var ethernetInterfaces: ([]) + property var activeEthernetDetails: ({}) + property bool ethernetConnected: false + property string activeEthernetIf: "" + property bool ethernetDetailsLoading: false + property double activeEthernetDetailsTimestamp: 0 + + // Wi-Fi properties + readonly property bool wifiEnabled: Networking.wifiEnabled + property var networks: ({}) + property var activeWifiDetails: ({}) + property bool wifiConnected: false + property string activeWifiIf: "" + property bool wifiDetailsLoading: false + property double activeWifiDetailsTimestamp: 0 + property bool wifiInit: false + + // Wi-Fi adapter/connection properties + property bool connecting: false + property string connectingTo: "" + property string disconnectingFrom: "" + property string forgettingNetwork: "" + property bool scanPending: false + property bool scanningActive: false + property var existingProfiles: ({}) + + // Airplane mode status + property bool airplaneModeEnabled: false + property bool airplaneModeToggled: false + + Connections { + target: root + function onWifiEnabledChanged() { + if (!root.wifiInit) { + return; + } + wifiDebounce.restart(); + } + } + + // Start initial checks when nmcli becomes available + Connections { + target: ProgramCheckerService + function onNmcliAvailableChanged() { + if (ProgramCheckerService.nmcliAvailable) { + deviceStatusProcess.running = true; + connectivityCheckProcess.running = true; + } + } + } + + Component.onCompleted: { + Logger.i("Network", "Service started"); + wifiInitTimer.running = true; + + // Ensure initial detection if nmcli is already available at startup + if (ProgramCheckerService.nmcliAvailable) { + deviceStatusProcess.running = true; + connectivityCheckProcess.running = true; + } + } + + // Prevent an initial "Wi-Fi enabled" toast and trigger initial scan + Timer { + id: wifiInitTimer + interval: 500 + onTriggered: { + root.wifiInit = true; + if (root.wifiEnabled) { + scan(); + } + if (!root.wifiEnabled && BluetoothService.blocked) { + root.airplaneModeEnabled = true; + } + } + } + + // Debounce to prevent multiple toast notifications from transient states + Timer { + id: wifiDebounce + interval: 300 + onTriggered: { + if (!ProgramCheckerService.nmcliAvailable) { + return; + } + if (root.airplaneModeToggled) { + root.airplaneModeToggled = false; + if (root.wifiEnabled) { + scan(); + } else { + root.networks = ({}); + } + return; + } + var isAirplaneModeActive = !root.wifiEnabled && BluetoothService.blocked; + // Extra check for Airplane Mode if Bluetooth has been blocked before Wi-Fi + if (isAirplaneModeActive && !root.airplaneModeEnabled) { + root.airplaneModeEnabled = true; + ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), I18n.tr("common.enabled"), "plane"); + Logger.i("AirplaneMode", "Enabled"); + root.networks = ({}); + return; + } + // Extra check for Airplane Mode if Wi-Fi has been unblocked before Bluetooth + if (!isAirplaneModeActive && root.airplaneModeEnabled) { + root.airplaneModeEnabled = false; + ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), I18n.tr("common.disabled"), "plane-off"); + Logger.i("AirplaneMode", "Disabled"); + scan(); + return; + } + if (root.wifiEnabled) { + ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("common.enabled"), "wifi"); + scan(); + } else { + ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("common.disabled"), "wifi-off"); + root.networks = ({}); + } + } + } + + // Internet connectivity check timer + Timer { + id: connectivityCheckTimer + interval: 15000 + running: ProgramCheckerService.nmcliAvailable && (root.ethernetConnected || root.wifiConnected) + repeat: true + onTriggered: connectivityCheckProcess.running = true + } + + // Delayed scan timer + Timer { + id: delayedScanTimer + interval: 7000 + onTriggered: scan() + } + + // Core functions + function setWifiEnabled(enabled) { + if (!ProgramCheckerService.nmcliAvailable) { + return; + } + Logger.i("Wi-Fi", "SetWifiEnabled", enabled); + Networking.wifiEnabled = enabled; + } + + function setAirplaneMode(state) { + if (state) { + Quickshell.execDetached(["rfkill", "block", "all"]); + } else { + Quickshell.execDetached(["rfkill", "unblock", "all"]); + } + } + + function scan() { + if (!ProgramCheckerService.nmcliAvailable || !root.wifiEnabled) { + return; + } + lastError = ""; + + // If scanning in progress, mark as pending to trigger another scan when current when finished. + if (profileCheckProcess.running || scanProcess.running) { + root.scanPending = true; + return; + } + + // Get existing profiles first, then scan + profileCheckProcess.running = true; + root.scanningActive = true; + Logger.d("Network", "Scanning Wi-Fi networks..."); + } + + function connect(ssid, password = "", isHidden = false, securityKey = "", identity = "", enterpriseConfig = {}) { + if (!ProgramCheckerService.nmcliAvailable || connecting) { + return; + } + + const isSaved = (networks[ssid] && networks[ssid].existing); + const isEnt = securityKey ? isEnterprise(securityKey) : isEnterprise(networks[ssid] ? networks[ssid].security : ""); + + connecting = true; + connectingTo = ssid; + lastError = ""; + + connectProcess.ssid = ssid; + connectProcess.password = password; + connectProcess.isHidden = isHidden; + + if (isSaved) { + connectProcess.mode = "saved"; + } else if (isEnt || securityKey === "wep" || (securityKey && securityKey !== "open" && securityKey !== "wpa-psk" && securityKey !== "wpa2-psk")) { + connectProcess.mode = "manual"; + connectProcess.securityKey = securityKey || (networks[ssid] ? networks[ssid].security : "wpa-psk"); + connectProcess.identity = identity; + connectProcess.eap = enterpriseConfig.eap || "peap"; + connectProcess.phase2 = enterpriseConfig.phase2 || "mschapv2"; + connectProcess.anonIdentity = enterpriseConfig.anonIdentity || ""; + connectProcess.caCert = enterpriseConfig.caCert || ""; + } else { + connectProcess.mode = "new"; + } + + connectProcess.running = true; + } + + function disconnect(ssid) { + if (!ProgramCheckerService.nmcliAvailable) { + return; + } + disconnectingFrom = ssid; + disconnectProcess.ssid = ssid; + disconnectProcess.running = true; + } + + function forget(ssid) { + if (!ProgramCheckerService.nmcliAvailable) { + return; + } + forgettingNetwork = ssid; + + // Remove from system + forgetProcess.ssid = ssid; + forgetProcess.running = true; + } + + // Refresh details for the currently active Wiโ€‘Fi link + function refreshActiveWifiDetails() { + const now = Date.now(); + if (wifiDetailsLoading || (activeWifiIf && wifiConnected && activeWifiDetails && (now - activeWifiDetailsTimestamp) < activeDetailsTtlMs)) { + return; + } + if (wifiConnected && activeWifiIf) { + wifiDetailsLoading = true; + deviceStatusProcess.running = true; + } + } + + // Refresh details for the currently active Ethernet link + function refreshActiveEthernetDetails() { + const now = Date.now(); + if (ethernetDetailsLoading || activeEthernetIf && activeEthernetDetails && (now - activeEthernetDetailsTimestamp) < activeDetailsTtlMs) { + return; + } + if (ethernetConnected && activeEthernetIf) { + ethernetDetailsLoading = true; + deviceStatusProcess.running = true; + } + } + + // Helper function to immediately update network status + function updateNetworkStatus(ssid, connected) { + let nets = networks; + + // Update all networks connected status + for (let key in nets) { + if (nets[key].connected && key !== ssid) { + nets[key].connected = false; + } + } + // Update the target network if it exists + if (nets[ssid]) { + nets[ssid].connected = connected; + nets[ssid].existing = true; + } else if (connected) { + // Create a temporary entry if network doesn't exist yet + nets[ssid] = { + "ssid": ssid, + "security": "--", + "signal": 100, + "connected": true, + "existing": true + }; + } + // Trigger property change notification + networks = ({}); + networks = nets; + } + + // Helper functions + function getSignalInfo(signal, isConnected) { + let icon = ""; + if (isConnected) { + if (root._networkConnectivity === "limited") { + icon = "wifi-exclamation"; + } else if (root._networkConnectivity === "portal" || root._networkConnectivity === "unknown") { + icon = "wifi-question"; + } + } + const label = signal >= 80 ? I18n.tr("wifi.signal.excellent") : signal >= 60 ? I18n.tr("wifi.signal.good") : signal >= 35 ? I18n.tr("wifi.signal.fair") : signal >= 15 ? I18n.tr("wifi.signal.poor") : I18n.tr("wifi.signal.weak"); + if (!icon) { + icon = signal >= 80 ? "wifi" : signal >= 60 ? "wifi-3" : signal >= 35 ? "wifi-2" : signal >= 15 ? "wifi-1" : "wifi-0"; + } + return { + icon, + label + }; + } + + function isSecured(security) { + return security && security !== "--" && security.trim() !== ""; + } + + function isEnterprise(security) { + if (!security) { + return false; + } + const s = security.toUpperCase(); + return s.indexOf("802.1X") !== -1 || s.indexOf("EAP") !== -1 || s.indexOf("ENTERPRISE") !== -1; + } + + function parseIpDetails(text) { + const details = { + connectionName: "", + ipv4: "", + gateway4: "", + dns4: [], + ipv6: [], + gateway6: [], + dns6: [], + hwAddr: "", + speed: "" + }; + const addUnique = (arr, val) => { + if (val && arr.indexOf(val) === -1) { + arr.push(val); + } + }; + const handlers = { + "GENERAL.CONNECTION": v => { + details.connectionName = v; + }, + "GENERAL.HWADDR": v => { + details.hwAddr = v; + }, + "CAPABILITIES.SPEED": v => { + if (v && v !== "unknown") { + details.speed = v; + } + }, + "IP4.ADDRESS": v => { + details.ipv4 = v.split("/")[0]; + }, + "IP4.GATEWAY": v => { + details.gateway4 = v; + }, + "IP6.ADDRESS": v => { + addUnique(details.ipv6, v.split("/")[0]); + }, + "IP6.GATEWAY": v => { + addUnique(details.gateway6, v); + }, + "IP4.DNS": v => { + addUnique(details.dns4, v); + }, + "IP6.DNS": v => { + addUnique(details.dns6, v); + } + }; + const lines = text.split("\n"); + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + if (!line) { + continue; + } + const idx = line.indexOf(":"); + if (idx === -1) { + continue; + } + const key = line.substring(0, idx).replace(/\[\d+\]$/, ""); + const val = line.substring(idx + 1).trim(); + if (handlers[key]) { + handlers[key](val); + } + } + return details; + } + + // Functions used in /Modules/Panels/ControlCenter/Widgets/Network.qml & /Modules/Bar/Widgets/Network.qml + function getStatusText(showSpeed = false) { + // This variable can be tied to a toggle + if (root.connecting) { + return root.connectingTo ? I18n.tr("common.connecting") + " " + root.connectingTo : I18n.tr("common.connecting"); + } + + if (NetworkService.airplaneModeEnabled) { + return I18n.tr("toast.airplane-mode.title"); + } + if (!root.wifiEnabled) { + return ""; + } + + // Ethernet + if (root.ethernetConnected) { + const eth = root.activeEthernetDetails; + const name = eth.connectionName || (root.ethernetInterfaces.length > 0 ? root.ethernetInterfaces[0].connectionName : "") || ""; + const speed = eth.speed || ""; + return (name + (showSpeed && speed ? " - " + speed : "")); + } + + // Wi-Fi + if (root.wifiConnected) { + const wl = root.activeWifiDetails; + const speed = wl.rateShort || wl.rate || ""; + const connectedNet = Object.values(root.networks).find(net => net.connected); + const name = connectedNet ? connectedNet.ssid : (wl.connectionName || ""); + return (name + (showSpeed && speed ? " - " + speed : "")); + } + return ""; + } + + function getIcon(forceEthernet = false) { + if (NetworkService.airplaneModeEnabled && !forceEthernet) { + return "plane"; + } + + // 1. Ethernet Priority: Show Ethernet icon if connected OR if specifically requested (Panel) + if (root.ethernetConnected || forceEthernet) { + switch (root._networkConnectivity) { + case "limited": + return "ethernet-exclamation"; + case "portal": + case "unknown": + return "ethernet-question"; + case "full": + return "ethernet"; + default: + return "ethernet-off"; + } + } + + // 2. Wi-Fi Fallback + if (root.wifiAvailable || !forceEthernet) { + const networkCount = Object.values(root.networks).length; + if (!root.wifiEnabled) { + return "wifi-off"; + } + if (root.wifiConnected) { + let s = (root.activeWifiDetails && root.activeWifiDetails.signal !== undefined && root.activeWifiDetails.signal !== "") ? root.activeWifiDetails.signal : 0; + return root.getSignalInfo(s, true).icon; + } + if (root.connecting || networkCount > 0) { + return "wifi-question"; + } + } + return (root.ethernetAvailable || root.ethernetConnected) ? "ethernet-off" : root.wifiAvailable ? "wifi-0" : "wifi-off"; + } + + // Processes + // Discover connected interface[s] and fetch details [1] + Process { + id: deviceStatusProcess + running: false + command: ["sh", "-c", "nmcli -t -f GENERAL.DEVICE,GENERAL.TYPE,GENERAL.STATE,GENERAL.CONNECTION,GENERAL.HWADDR,IP4.ADDRESS,IP4.GATEWAY,IP4.DNS,IP6.ADDRESS,IP6.GATEWAY,IP6.DNS,CAPABILITIES.SPEED device show; echo \"------\"; nmcli -t -f IN-USE,SIGNAL,RATE,CHAN,FREQ,BANDWIDTH device wifi list"] + environment: ({ + "LC_ALL": "C" + }) + + stdout: StdioCollector { + onStreamFinished: { + const outputParts = text.split("------"); + const deviceText = outputParts[0]; + const wifiText = outputParts[1] || ""; + + let lines = deviceText.split("\n"); + let deviceBlocks = []; + let currentBlock = []; + + for (let i = 0; i < lines.length; i++) { + let line = lines[i].trim(); + if (!line) { + continue; + } + if (line.startsWith("GENERAL.DEVICE:")) { + if (currentBlock.length > 0) { + deviceBlocks.push(currentBlock); + } + currentBlock = [line]; + } else if (currentBlock.length > 0) { + currentBlock.push(line); + } + } + if (currentBlock.length > 0) { + deviceBlocks.push(currentBlock); + } + + let activeEthIf = ""; + let activeWifiIf = ""; + let wifiAvailable = false; + let ethernetAvailable = false; + let ethList = []; + + let newActiveWifiDetails = ({}); + let newActiveEthernetDetails = ({}); + + for (let b = 0; b < deviceBlocks.length; b++) { + let block = deviceBlocks[b]; + let blockText = block.join("\n"); + let details = root.parseIpDetails(blockText); + + let name = ""; + let type = ""; + let stateStr = ""; + + for (let l = 0; l < block.length; l++) { + let line = block[l]; + if (line.startsWith("GENERAL.DEVICE:")) { + name = line.substring(15).trim(); + } else if (line.startsWith("GENERAL.TYPE:")) { + type = line.substring(13).trim(); + } else if (line.startsWith("GENERAL.STATE:")) { + stateStr = line.substring(14).trim(); + } + } + + if (stateStr.indexOf("(unmanaged)") !== -1) { + continue; + } + let isConnected = stateStr.indexOf("(connected)") !== -1; + + if (type === "ethernet") { + ethernetAvailable = true; + let stateName = stateStr.split(" ")[1] ? stateStr.split(" ")[1].replace(/[()]/g, "") : stateStr; + ethList.push({ + ifname: name, + state: stateName, + connected: isConnected, + connectionName: details.connectionName + }); + if (isConnected && !activeEthIf) { + activeEthIf = name; + newActiveEthernetDetails = details; + newActiveEthernetDetails.ifname = name; + } + } else if (type === "wifi") { + wifiAvailable = true; + if (isConnected && !activeWifiIf) { + activeWifiIf = name; + newActiveWifiDetails = details; + newActiveWifiDetails.ifname = name; + } + } + } + + // Parse Wi-Fi details if active + if (activeWifiIf && wifiText) { + let rate = ""; + let freq = ""; + let channel = ""; + let width = ""; + let signal = ""; + + const wifiLines = wifiText.split("\n"); + for (let i = 0; i < wifiLines.length; i++) { + const line = wifiLines[i].trim(); + if (line.startsWith("*")) { + const parts = line.split(":"); + if (parts.length >= 6) { + signal = parts[1]; + rate = parts[2]; + channel = parts[3]; + freq = parts[4].replace(" MHz", ""); + width = parts[5]; + } + break; + } + } + + let band = ""; + if (freq) { + const f = +freq; + if (f) { + switch (true) { + case (f >= 5925 && f < 7125): + band = "6 GHz"; + break; + case (f >= 5150 && f < 5925): + band = "5 GHz"; + break; + case (f >= 2400 && f < 2500): + band = "2.4 GHz"; + break; + default: + band = `${f} MHz`; + } + } + } + + let rateShort = ""; + if (rate) { + var rparts = rate.trim().split(" "); + var compact = []; + for (var i = 0; i < rparts.length; i++) { + if (rparts[i]) { + compact.push(rparts[i]); + } + } + var unitIdx = -1; + for (var j = 0; j < compact.length; j++) { + var token = compact[j].toLowerCase(); + if (token === "mbit/s" || token === "mb/s" || token === "mbits/s") { + unitIdx = j; + break; + } + } + if (unitIdx > 0) { + var num = compact[unitIdx - 1]; + var parsed = parseFloat(num); + if (!isNaN(parsed)) { + rateShort = parsed + " Mbit/s"; + } + } + if (!rateShort) { + rateShort = compact.slice(0, 2).join(" "); + } + } + + let enhancedBand = band; + if (channel && width && width !== "0 MHz") { + enhancedBand = `${band} / ${channel} (${width})`; + } else if (channel) { + enhancedBand = `${band} / ${channel}`; + } + + if (newActiveWifiDetails.speed) { + newActiveWifiDetails.rate = newActiveWifiDetails.speed.replace(/Mb\/s/i, "Mbit/s"); + newActiveWifiDetails.rateShort = newActiveWifiDetails.rate; + } else { + newActiveWifiDetails.rate = rate; + newActiveWifiDetails.rateShort = rateShort; + } + newActiveWifiDetails.band = enhancedBand; + newActiveWifiDetails.channel = channel; + newActiveWifiDetails.width = width; + newActiveWifiDetails.signal = signal; + } + + root._wifiAvailable = wifiAvailable; + root._ethernetAvailable = ethernetAvailable; + root.ethernetConnected = (activeEthIf !== ""); + root.wifiConnected = (activeWifiIf !== ""); + + Logger.d("Network", "Device sync: wifiAvailable: " + wifiAvailable + ", ethAvailable: " + ethernetAvailable + ", wifiConnected: " + root.wifiConnected + " (" + activeWifiIf + "), ethConnected: " + root.ethernetConnected + " (" + activeEthIf + ")"); + + ethList.sort((a, b) => (a.connected !== b.connected) ? (a.connected ? -1 : 1) : a.ifname.localeCompare(b.ifname)); + root.ethernetInterfaces = ethList; + + root.activeEthernetIf = activeEthIf; + root.activeEthernetDetails = newActiveEthernetDetails; + root.activeEthernetDetailsTimestamp = Date.now(); + root.ethernetDetailsLoading = false; + + root.activeWifiIf = activeWifiIf; + root.activeWifiDetails = newActiveWifiDetails; + root.activeWifiDetailsTimestamp = Date.now(); + root.wifiDetailsLoading = false; + } + } + stderr: StdioCollector { + onStreamFinished: { + if (text && text.trim()) { + Logger.w("Network", "nmcli device show stderr:", text.trim()); + } + root.ethernetDetailsLoading = false; + root.wifiDetailsLoading = false; + } + } + } + + // Process to check the internet connectivity of the connected network + Process { + id: connectivityCheckProcess + running: false + command: ["nmcli", "networking", "connectivity", "check"] + stdout: StdioCollector { + onStreamFinished: { + const r = text.trim(); + if (!r) { + return; + } + root._networkConnectivity = (r === "none") ? "unknown" : r; + root._internetConnectivity = (r === "full"); + } + } + stderr: StdioCollector { + onStreamFinished: { + if (text.trim()) { + Logger.w("Network", "Connectivity check error: " + text); + } + } + } + } + + // Helper process to get existing profiles + Process { + id: profileCheckProcess + running: false + command: ["nmcli", "-t", "-f", "NAME", "connection", "show"] + + stdout: StdioCollector { + onStreamFinished: { + var profiles = {}; + var lines = text.split("\n"); + for (var i = 0; i < lines.length; i++) { + var l = lines[i]; + if (l && l.trim()) { + profiles[l.trim()] = true; + } + } + root.existingProfiles = profiles; + scanProcess.running = true; + } + } + stderr: StdioCollector { + onStreamFinished: { + if (text && text.trim()) { + Logger.w("Network", "Profile check stderr:", text.trim()); + if (root.scanningActive) { + if (root.scanPending) { + root.scanPending = false; + delayedScanTimer.interval = 3000; + } else { + delayedScanTimer.interval = 5000; + } + delayedScanTimer.restart(); + } + } + } + } + } + + // Scan for Wi-Fi networks + Process { + id: scanProcess + running: false + command: ["nmcli", "-t", "-f", "SSID,SECURITY,SIGNAL,IN-USE", "device", "wifi", "list", "--rescan", "yes"] + + stdout: StdioCollector { + onStreamFinished: { + const lines = text.trim().split("\n"); + const networksMap = {}; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + if (!line) { + continue; + } + + // Parse SSID:SECURITY:SIGNAL:IN-USE + const parts = line.split(":"); + if (parts.length < 4) { + continue; + } + + const inUse = parts[parts.length - 1]; + const signal = parseInt(parts[parts.length - 2]) || 0; + let security = parts[parts.length - 3]; + if (security) { + security = security.replace("WPA2 WPA3", "WPA2/WPA3").replace("WPA1 WPA2", "WPA1/WPA2"); + } + const ssid = parts.slice(0, parts.length - 3).join(":"); + + if (ssid) { + const isConnected = (inUse === "*"); + if (!networksMap[ssid]) { + networksMap[ssid] = { + "ssid": ssid, + "security": security || "--", + "signal": signal, + "connected": isConnected, + "existing": !!root.existingProfiles[ssid] + }; + } else { + if (isConnected) { + networksMap[ssid].connected = true; + networksMap[ssid].signal = signal; + connectivityCheckProcess.running = true; + } else if (!networksMap[ssid].connected && signal > networksMap[ssid].signal) { + networksMap[ssid].signal = signal; + } + } + } + } + + // Logging & Diffing + const oldSSIDs = Object.keys(root.networks); + const newSSIDs = Object.keys(networksMap); + const newNetworks = newSSIDs.filter(s => oldSSIDs.indexOf(s) === -1); + const lostNetworks = oldSSIDs.filter(s => newSSIDs.indexOf(s) === -1); + + // Always update networks, this makes more reflective of state/signal. + root.networks = networksMap; + + if (newNetworks.length > 0 || lostNetworks.length > 0) { + if (newNetworks.length > 0) { + Logger.d("Network", "New Wi-Fi network appeared:", newNetworks.join(", ")); + } + if (lostNetworks.length > 0) { + Logger.d("Network", "Wi-Fi network disappeared:", lostNetworks.join(", ")); + } + Logger.d("Network", "Total Wi-Fi networks:", Object.keys(networksMap).length); + } + + if (Object.values(networksMap).some(n => n.connected)) { + root.refreshActiveWifiDetails(); + } + + if (root.scanPending) { + root.scanPending = false; + delayedScanTimer.interval = 100; + delayedScanTimer.restart(); + } + root.scanningActive = false; + } + } + + stderr: StdioCollector { + onStreamFinished: { + if (text.trim()) { + Logger.w("Network", "Scan error: " + text); + + // Even on error, if a scan was pending, try again + if (root.scanPending) { + root.scanPending = false; + delayedScanTimer.interval = 3000; + } else if (root.scanningActive) { + delayedScanTimer.interval = 10000; + } + delayedScanTimer.restart(); + } + root.scanningActive = false; + } + } + } + + // Connect to Wi-Fi network + Process { + id: connectProcess + property string mode: "new" // "saved", "new", or "manual" + property string ssid: "" + property string password: "" + property bool isHidden: false + // Manual properties + property string securityKey: "" + property string identity: "" + property string eap: "peap" + property string phase2: "mschapv2" + property string anonIdentity: "" + property string caCert: "" + running: false + + command: { + if (mode === "saved") { + return ["nmcli", "-t", "connection", "up", "id", ssid]; + } else if (mode === "manual") { + const nmArgs = ["connection", "add", "type", "wifi", "con-name", ssid, "ssid", ssid, "--", "802-11-wireless.hidden", isHidden ? "yes" : "no"]; + + if (securityKey === "wpa-psk" || securityKey === "wpa2-psk") { + nmArgs.push("wifi-sec.key-mgmt", "wpa-psk", "wifi-sec.psk", password); + } else if (securityKey === "sae") { + nmArgs.push("wifi-sec.key-mgmt", "sae", "wifi-sec.psk", password); + } else if (securityKey === "wep") { + nmArgs.push("wifi-sec.key-mgmt", "none", "wifi-sec.wep-key0", password); + } else if (securityKey && securityKey.indexOf("-eap") !== -1) { + nmArgs.push("wifi-sec.key-mgmt", "wpa-eap", "802-1x.eap", eap, "802-1x.phase2-auth", phase2, "802-1x.identity", identity, "802-1x.password", password); + if (anonIdentity) { + nmArgs.push("802-1x.anonymous-identity", anonIdentity); + } + if (caCert) { + nmArgs.push("802-1x.ca-cert", caCert); + } + } + + const script = ` + SSID="$1" + shift + # Find existing profile by Name and Type + UUID=$(nmcli -t -f NAME,UUID,TYPE connection show | awk -F: -v target="$SSID" '$1 == target && $3 == "802-11-wireless" { print $2; exit }') + + if [ -n "$UUID" ]; then + echo "Using existing profile: $UUID" + nmcli connection delete uuid "$UUID" 2>/dev/null || true + else + echo "Creating new profile for $SSID" + fi + nmcli "$@" + nmcli connection up id "$SSID" + `; + + return ["sh", "-c", script, "--", ssid].concat(nmArgs); + } else { + var cmd = ["nmcli", "-t", "device", "wifi", "connect", ssid]; + if (isHidden) { + cmd.push("hidden", "yes"); + } + if (password) { + cmd.push("password", password); + } + if (root.activeWifiIf) { + cmd.push("ifname", root.activeWifiIf); + } + return cmd; + } + } + + environment: ({ + "LC_ALL": "C" + }) + + stdout: StdioCollector { + onStreamFinished: { + const output = text.trim(); + if (!output || (output.indexOf("successfully activated") === -1 && output.indexOf("Connection successfully") === -1)) { + return; + } + + root.wifiConnected = true; + root.updateNetworkStatus(connectProcess.ssid, true); + root.refreshActiveWifiDetails(); // This needs wifiConnected true. + + root.connecting = false; + root.connectingTo = ""; + Logger.i("Network", "Connected to network: '" + connectProcess.ssid + "' (" + connectProcess.mode + ")"); + ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("toast.wifi.connected", { + "ssid": connectProcess.ssid + }), root.getIcon(false)); + + delayedScanTimer.interval = 5000; + delayedScanTimer.restart(); + } + } + + stderr: StdioCollector { + onStreamFinished: { + if (text.trim()) { + root.connecting = false; + root.connectingTo = ""; + + if (text.indexOf("Secrets were required") !== -1 || text.indexOf("no secrets provided") !== -1) { + root.lastError = I18n.tr("toast.wifi.incorrect-password"); + forget(connectProcess.ssid); + } else if (text.indexOf("No network with SSID") !== -1) { + root.lastError = I18n.tr("toast.wifi.network-not-found"); + } else if (text.indexOf("Timeout") !== -1) { + root.lastError = I18n.tr("toast.wifi.connection-timeout"); + } else { + root.lastError = I18n.tr("toast.wifi.connection-failed"); + } + + Logger.w("Network", "Connect error (" + connectProcess.mode + "): " + text); + ToastService.showWarning(I18n.tr("common.wifi"), root.lastError || I18n.tr("toast.wifi.connection-failed"), "wifi-exclamation"); + wifiConnected = false; + } + } + } + } + + // Disconnect from Wi-Fi network + Process { + id: disconnectProcess + property string ssid: "" + running: false + command: ["nmcli", "connection", "down", "id", ssid] + + stdout: StdioCollector { + onStreamFinished: { + Logger.i("Network", "Disconnected from network: '" + disconnectProcess.ssid + "'"); + root.wifiConnected = false; + ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("toast.wifi.disconnected", { + "ssid": disconnectProcess.ssid + }), "wifi-off"); + + // Immediately update UI on successful disconnect + root.updateNetworkStatus(disconnectProcess.ssid, false); + root.disconnectingFrom = ""; + + // Do a scan to refresh the list + delayedScanTimer.interval = 3000; + delayedScanTimer.restart(); + } + } + + stderr: StdioCollector { + onStreamFinished: { + root.disconnectingFrom = ""; + if (text.trim()) { + Logger.w("Network", "Disconnect error: " + text); + } + // Still trigger a scan even on error + delayedScanTimer.interval = 5000; + delayedScanTimer.restart(); + } + } + } + + // Forget given Wi-Fi network + Process { + id: forgetProcess + property string ssid: "" + running: false + environment: ({ + "LC_ALL": "C" + }) + + // Try multiple common profile name patterns + command: { + var script = ` + ssid="$1" + deleted=false + + # Find existing profile by Name and Type + UUID=$(nmcli -t -f NAME,UUID,TYPE connection show | awk -F: -v target="$ssid" '$1 == target && $3 == "802-11-wireless" { print $2; exit }') + + if [ -n "$UUID" ]; then + if nmcli connection delete uuid "$UUID" 2>/dev/null; then + echo "Deleted profile: $ssid ($UUID)" + deleted=true + fi + fi + + # Fallback: try common patterns if UUID lookup failed + if [ "$deleted" = "false" ]; then + # Try "Auto $ssid" pattern + if nmcli connection delete id "Auto $ssid" 2>/dev/null; then + echo "Deleted profile: Auto $ssid" + deleted=true + fi + + # Try "$ssid 1", "$ssid 2", etc. patterns + for i in 1 2 3; do + if nmcli connection delete id "$ssid $i" 2>/dev/null; then + echo "Deleted profile: $ssid $i" + deleted=true + fi + done + fi + + if [ "$deleted" = "false" ]; then + echo "No profiles found for SSID: $ssid" + fi + `; + + return ["sh", "-c", script, "--", ssid]; + } + + stdout: StdioCollector { + onStreamFinished: { + Logger.i("Network", "Forget network: \"" + forgetProcess.ssid + "\""); + Logger.d("Network", text.trim().replace(/[\r\n]/g, " ")); + + // Update existing status immediately + let nets = root.networks; + if (nets[forgetProcess.ssid]) { + nets[forgetProcess.ssid].existing = false; + // Trigger property change + root.networks = ({}); + root.networks = nets; + } + + root.forgettingNetwork = ""; + + // Scan to verify the profile is gone + delayedScanTimer.interval = 5000; + delayedScanTimer.restart(); + } + } + + stderr: StdioCollector { + onStreamFinished: { + root.forgettingNetwork = ""; + if (text.trim() && text.indexOf("No profiles found") === -1) { + Logger.w("Network", "Forget error: " + text); + } + // Still Trigger a scan even on error + delayedScanTimer.interval = 5000; + delayedScanTimer.restart(); + } + } + } + + // Listen to NetworkManager events in real-time (roaming, auto-connect) -- ~9mb Memory usage. + Process { + id: networkMonitorProcess + running: ProgramCheckerService.nmcliAvailable + command: ["nmcli", "-t", "monitor"] + environment: ({ + "LC_ALL": "C" + }) + stdout: SplitParser { + onRead: data => { + if (data.endsWith(": connected") || data.endsWith(": disconnected")) { + Logger.d("Network", "State changed: " + data); + deviceStatusProcess.running = true; + connectivityCheckProcess.running = true; + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Networking/VPNService.qml b/arch/.config/quickshell/noctalia-shell/Services/Networking/VPNService.qml new file mode 100644 index 0000000..d867307 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Networking/VPNService.qml @@ -0,0 +1,287 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.UI + +Singleton { + id: root + + property var connections: ({}) + property bool refreshing: false + property bool connecting: false + property bool disconnecting: false + property string connectingUuid: "" + property string disconnectingUuid: "" + property string lastError: "" + property bool refreshPending: false + + readonly property var activeConnections: { + const result = []; + const map = connections; + for (const key in map) { + const conn = map[key]; + if (conn && conn.active) { + result.push(conn); + } + } + return result; + } + + readonly property var inactiveConnections: { + const result = []; + const map = connections; + for (const key in map) { + const conn = map[key]; + if (conn && !conn.active) { + result.push(conn); + } + } + return result; + } + + readonly property bool hasActiveConnection: activeConnections.length > 0 + + Timer { + id: refreshTimer + interval: 5000 + running: true + repeat: true + onTriggered: refresh() + } + + Timer { + id: delayedRefreshTimer + interval: 1000 + repeat: false + onTriggered: refresh() + } + + Component.onCompleted: { + Logger.i("VPN", "Service started"); + refresh(); + } + + function refresh() { + if (refreshing) { + refreshPending = true; + return; + } + refreshing = true; + lastError = ""; + refreshProcess.running = true; + } + + function connect(uuid) { + if (connecting || !uuid) { + return; + } + const conn = connections[uuid]; + if (!conn) { + return; + } + connecting = true; + connectingUuid = uuid; + lastError = ""; + connectProcess.uuid = uuid; + connectProcess.name = conn.name; + connectProcess.running = true; + } + + function disconnect(uuid) { + if (disconnecting || !uuid) { + return; + } + const conn = connections[uuid]; + if (!conn) { + return; + } + disconnecting = true; + disconnectingUuid = uuid; + lastError = ""; + disconnectProcess.uuid = uuid; + disconnectProcess.name = conn.name; + disconnectProcess.running = true; + } + + function toggle(uuid) { + const conn = connections[uuid]; + if (!conn) { + return; + } + if (conn.active) { + disconnect(uuid); + } else { + connect(uuid); + } + } + + function setConnection(uuid, data) { + if (!uuid) { + return; + } + const map = Object.assign({}, connections); + if (map[uuid]) { + map[uuid] = Object.assign({}, map[uuid], data); + connections = map; + } + } + + function scheduleRefresh(interval) { + delayedRefreshTimer.interval = interval; + delayedRefreshTimer.restart(); + } + + Process { + id: refreshProcess + running: false + command: ["nmcli", "-t", "-f", "NAME,UUID,TYPE,DEVICE", "connection", "show"] + + stdout: StdioCollector { + onStreamFinished: { + const lines = text.split("\n"); + const map = {}; + for (let i = 0; i < lines.length; ++i) { + const line = lines[i].trim(); + if (!line) { + continue; + } + const lastColonIdx = line.lastIndexOf(":"); + if (lastColonIdx === -1) { + continue; + } + const device = line.substring(lastColonIdx + 1); + const remaining = line.substring(0, lastColonIdx); + const secondLastColonIdx = remaining.lastIndexOf(":"); + if (secondLastColonIdx === -1) { + continue; + } + const type = remaining.substring(secondLastColonIdx + 1); + if (type !== "vpn" && type !== "wireguard") { + continue; + } + const remaining2 = remaining.substring(0, secondLastColonIdx); + const thirdLastColonIdx = remaining2.lastIndexOf(":"); + if (thirdLastColonIdx === -1) { + continue; + } + const uuid = remaining2.substring(thirdLastColonIdx + 1); + const name = remaining2.substring(0, thirdLastColonIdx); + if (!uuid || !name) { + continue; + } + const active = device && device !== "--"; + map[uuid] = { + "uuid": uuid, + "name": name, + "device": device, + "active": active + }; + } + connections = map; + const pending = refreshPending; + refreshing = false; + refreshPending = false; + if (pending) { + scheduleRefresh(200); + } + } + } + + stderr: StdioCollector { + onStreamFinished: { + const pending = refreshPending; + refreshing = false; + refreshPending = false; + if (text.trim()) { + lastError = text.split("\n")[0].trim(); + Logger.w("VPN", "Refresh error: " + text); + } + if (pending) { + scheduleRefresh(2000); + } + } + } + } + + Process { + id: connectProcess + property string uuid: "" + property string name: "" + running: false + command: ["nmcli", "connection", "up", "uuid", uuid] + + stdout: StdioCollector { + onStreamFinished: { + const output = text.trim(); + if (!output || (!output.includes("successfully activated") && !output.includes("Connection successfully"))) { + return; + } + setConnection(connectProcess.uuid, { + "active": true + }); + connecting = false; + connectingUuid = ""; + lastError = ""; + Logger.i("VPN", "Connected to " + connectProcess.name); + ToastService.showNotice(connectProcess.name, I18n.tr("toast.vpn.connected", { + "name": connectProcess.name + }), "shield-lock"); + scheduleRefresh(1000); + } + } + + stderr: StdioCollector { + onStreamFinished: { + const trimmed = text.trim(); + if (trimmed) { + lastError = trimmed.split("\n")[0].trim(); + Logger.w("VPN", "Connect error: " + trimmed); + ToastService.showWarning(connectProcess.name, lastError); + } + connecting = false; + connectingUuid = ""; + } + } + } + + Process { + id: disconnectProcess + property string uuid: "" + property string name: "" + running: false + command: ["nmcli", "connection", "down", "uuid", uuid] + + stdout: StdioCollector { + onStreamFinished: { + Logger.i("VPN", "Disconnected from " + disconnectProcess.name); + setConnection(disconnectProcess.uuid, { + "active": false, + "device": "" + }); + disconnecting = false; + disconnectingUuid = ""; + lastError = ""; + ToastService.showNotice(disconnectProcess.name, I18n.tr("toast.vpn.disconnected", { + "name": disconnectProcess.name + }), "shield-off"); + scheduleRefresh(1000); + } + } + + stderr: StdioCollector { + onStreamFinished: { + const trimmed = text.trim(); + if (trimmed) { + lastError = trimmed.split("\n")[0].trim(); + Logger.w("VPN", "Disconnect error: " + trimmed); + ToastService.showWarning(disconnectProcess.name, lastError); + } + disconnecting = false; + disconnectingUuid = ""; + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Noctalia/GitHubService.qml b/arch/.config/quickshell/noctalia-shell/Services/Noctalia/GitHubService.qml new file mode 100644 index 0000000..d23a4d2 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Noctalia/GitHubService.qml @@ -0,0 +1,336 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.UI + +// GitHub API logic for contributors +Singleton { + id: root + + property string githubDataFile: Quickshell.env("NOCTALIA_GITHUB_FILE") || (Settings.cacheDir + "github.json") + property int githubUpdateFrequency: 60 * 60 // 1 hour expressed in seconds + property bool isFetchingData: false + readonly property alias data: adapter // Used to access via GitHubService.data.xxx.yyy + + // Public properties for easy access + property string latestVersion: I18n.tr("common.unknown") + property string latestQSVersion: I18n.tr("common.unknown") + property var contributors: [] + + // Avatar caching properties (simplified - uses ImageCacheService) + property var cachedAvatars: ({}) // username โ†’ file:// path + property bool avatarsCached: false // Track if we've already processed avatars + + property bool isInitialized: false + + FileView { + id: githubDataFileView + path: githubDataFile + printErrors: false + watchChanges: false // Disable to prevent reload on our own writes + Component.onCompleted: { + // Data loading handled by FileView onLoaded + } + onLoaded: { + if (!root.isInitialized) { + root.isInitialized = true; + loadFromCache(); + } + } + onLoadFailed: function (error) { + if (error.toString().includes("No such file") || error === 2) { + // No cache file exists, fetch fresh data + root.isInitialized = true; + fetchFromGitHub(); + } + } + + JsonAdapter { + id: adapter + + property string version: I18n.tr("common.unknown") + property string qsVersion: I18n.tr("common.unknown") + property var contributors: [] + property real timestamp: 0 + } + } + + // -------------------------------- + function init() { + Logger.i("GitHub", "Service started"); + // FileView will handle loading automatically via onLoaded + } + + // -------------------------------- + function loadFromCache() { + const now = Time.timestamp; + var needsRefetch = false; + + Logger.i("GitHub", "Checking cache - timestamp:", data.timestamp, "now:", now, "age:", data.timestamp ? Math.round((now - data.timestamp) / 60) : "N/A", "minutes"); + + if (!data.timestamp || (now >= data.timestamp + githubUpdateFrequency)) { + needsRefetch = true; + Logger.i("GitHub", "Cache expired or missing, scheduling fetch (update frequency:", Math.round(githubUpdateFrequency / 60), "minutes)"); + } else { + Logger.i("GitHub", "Cache is fresh, using cached data (age:", Math.round((now - data.timestamp) / 60) + " minutes)"); + } + + if (data.version) { + root.latestVersion = data.version; + } + if (data.qsVersion) { + root.latestQSVersion = data.qsVersion; + } + if (data.contributors && data.contributors.length > 0) { + root.contributors = data.contributors; + Logger.d("GitHub", "Loaded", data.contributors.length, "contributors from cache"); + } + + if (needsRefetch) { + fetchFromGitHub(); + } + } + + // -------------------------------- + function fetchFromGitHub() { + if (isFetchingData) { + Logger.d("GitHub", "GitHub data is still fetching"); + return; + } + + isFetchingData = true; + versionProcess.running = true; + qsVersionProcess.running = true; + contributorsProcess.running = true; + } + + // -------------------------------- + function saveData() { + data.timestamp = Time.timestamp; + Logger.d("GitHub", "Saving data to cache file:", githubDataFile, "with timestamp:", data.timestamp); + Logger.d("GitHub", "Data to save - version:", data.version, "qsVersion:", data.qsVersion, "contributors:", data.contributors.length); + + // Ensure cache directory exists + Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir]); + + try { + // Write immediately instead of Qt.callLater to ensure it completes + githubDataFileView.writeAdapter(); + Logger.d("GitHub", "Cache file written successfully"); + } catch (error) { + Logger.e("GitHub", "Failed to write cache file:", error); + } + } + + // -------------------------------- + function checkAndSaveData() { + // Only save when all processes are finished + if (!versionProcess.running && !qsVersionProcess.running && !contributorsProcess.running) { + root.isFetchingData = false; + + // Check results + var anySucceeded = versionProcess.fetchSucceeded || qsVersionProcess.fetchSucceeded || contributorsProcess.fetchSucceeded; + var wasRateLimited = versionProcess.wasRateLimited || qsVersionProcess.wasRateLimited || contributorsProcess.wasRateLimited; + + if (anySucceeded) { + root.saveData(); + Logger.d("GitHub", "Successfully fetched data from GitHub"); + } else if (wasRateLimited) { + root.saveData(); + Logger.w("GitHub", "API rate limited - using cached data (retry in", Math.round(githubUpdateFrequency / 60), "minutes)"); + } else { + Logger.w("GitHub", "API request failed - using cached data without updating timestamp"); + } + + // Reset fetch flags for next time + versionProcess.fetchSucceeded = false; + versionProcess.wasRateLimited = false; + qsVersionProcess.fetchSucceeded = false; + qsVersionProcess.wasRateLimited = false; + contributorsProcess.fetchSucceeded = false; + contributorsProcess.wasRateLimited = false; + } + } + + // -------------------------------- + function resetCache() { + data.version = I18n.tr("common.unknown"); + data.qsVersion = I18n.tr("common.unknown"); + data.contributors = []; + data.timestamp = 0; + + // Try to fetch immediately + fetchFromGitHub(); + } + + // -------------------------------- + // Avatar Caching Functions (simplified - uses ImageCacheService) + // -------------------------------- + + function getAvatarPath(username) { + return cachedAvatars[username] || ""; + } + + function cacheTopContributorAvatars() { + if (contributors.length === 0) + return; + + avatarsCached = true; + + for (var i = 0; i < Math.min(contributors.length, 20); i++) { + var contributor = contributors[i]; + var username = contributor.login; + var avatarUrl = contributor.avatar_url; + + // Use closure to capture username + (function (uname, url) { + ImageCacheService.getCircularAvatar(url, uname, function (cachedPath, success) { + if (success) { + cachedAvatars[uname] = "file://" + cachedPath; + cachedAvatarsChanged(); + } + }); + })(username, avatarUrl); + } + } + + // -------------------------------- + // Hook into contributors change - only process once + onContributorsChanged: { + if (contributors.length > 0 && !avatarsCached && ImageCacheService.initialized) { + Qt.callLater(cacheTopContributorAvatars); + } + } + + // Also watch for ImageCacheService to become initialized + Connections { + target: ImageCacheService + function onInitializedChanged() { + if (ImageCacheService.initialized && contributors.length > 0 && !avatarsCached) { + Qt.callLater(cacheTopContributorAvatars); + } + } + } + + Process { + id: versionProcess + + property bool fetchSucceeded: false + property bool wasRateLimited: false + + command: ["curl", "-s", "https://api.github.com/repos/noctalia-dev/noctalia-shell/releases/latest"] + + stdout: StdioCollector { + onStreamFinished: { + try { + const response = text; + if (response && response.trim()) { + const data = JSON.parse(response); + if (data.tag_name) { + const version = data.tag_name; + root.data.version = version; + root.latestVersion = version; + versionProcess.fetchSucceeded = true; + Logger.d("GitHub", "Latest version fetched:", version); + } else if (data.message) { + // Check if it's a rate limit error + if (data.message.includes("rate limit")) { + versionProcess.wasRateLimited = true; + } else { + Logger.w("GitHub", "Version API error:", data.message); + } + } + } + } catch (e) { + Logger.e("GitHub", "Failed to parse version response:", e); + } + + // Check if all processes are done + checkAndSaveData(); + } + } + } + + Process { + id: qsVersionProcess + + property bool fetchSucceeded: false + property bool wasRateLimited: false + + command: ["curl", "-s", "https://api.github.com/repos/noctalia-dev/noctalia-qs/releases/latest"] + + stdout: StdioCollector { + onStreamFinished: { + try { + const response = text; + if (response && response.trim()) { + const data = JSON.parse(response); + if (data.tag_name) { + const version = data.tag_name; + root.data.qsVersion = version; + root.latestQSVersion = version; + qsVersionProcess.fetchSucceeded = true; + Logger.d("GitHub", "Latest QS version fetched:", version); + } else if (data.message) { + // Check if it's a rate limit error + if (data.message.includes("rate limit")) { + qsVersionProcess.wasRateLimited = true; + } else { + Logger.w("GitHub", "QS Version API error:", data.message); + } + } + } + } catch (e) { + Logger.e("GitHub", "Failed to parse QS version response:", e); + } + + // Check if all processes are done + checkAndSaveData(); + } + } + } + + Process { + id: contributorsProcess + + property bool fetchSucceeded: false + property bool wasRateLimited: false + + command: ["curl", "-s", "https://api.github.com/repos/noctalia-dev/noctalia-shell/contributors?per_page=100"] + + stdout: StdioCollector { + onStreamFinished: { + try { + const response = text; + Logger.d("GitHub", "Raw contributors response length:", response ? response.length : 0); + if (response && response.trim()) { + const data = JSON.parse(response); + Logger.d("GitHub", "Parsed contributors data type:", typeof data, "length:", Array.isArray(data) ? data.length : "not array"); + // Only update if we got a valid array + if (Array.isArray(data)) { + root.data.contributors = data; + root.contributors = root.data.contributors; + contributorsProcess.fetchSucceeded = true; + Logger.d("GitHub", "Contributors fetched:", root.contributors.length); + } else if (data.message) { + // Check if it's a rate limit error + if (data.message.includes("rate limit")) { + contributorsProcess.wasRateLimited = true; + } else { + Logger.w("GitHub", "Contributors API error:", data.message); + } + } + } + } catch (e) { + Logger.e("GitHub", "Failed to parse contributors response:", e); + } + + // Check if all processes are done + checkAndSaveData(); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Noctalia/PluginRegistry.qml b/arch/.config/quickshell/noctalia-shell/Services/Noctalia/PluginRegistry.qml new file mode 100644 index 0000000..4364ea0 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Noctalia/PluginRegistry.qml @@ -0,0 +1,620 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import "../../Helpers/sha256.js" as Crypto +import qs.Commons + +Singleton { + id: root + + readonly property string pluginsDir: Settings.configDir + "plugins" + readonly property string pluginsFile: Settings.configDir + "plugins.json" + + readonly property int currentVersion: 2 + // Main source URL - plugins from this source keep plain IDs + readonly property string mainSourceUrl: "https://github.com/noctalia-dev/noctalia-plugins" + + Component.onCompleted: { + ensurePluginsDirectory(); + ensurePluginsFile(); + } + + // Generate a short hash (6 characters) from a source URL + function generateSourceHash(sourceUrl) { + var hash = Crypto.sha256(sourceUrl); + return hash.substring(0, 6); + } + + // Check if a source is the main Noctalia plugins repository + function isMainSource(sourceUrl) { + return sourceUrl === root.mainSourceUrl; + } + + // Generate composite key: plain ID for official, "hash:id" for custom + function generateCompositeKey(pluginId, sourceUrl) { + if (!sourceUrl || isMainSource(sourceUrl)) { + return pluginId; + } + var hash = generateSourceHash(sourceUrl); + return hash + ":" + pluginId; + } + + // Parse composite key back to components + function parseCompositeKey(compositeKey) { + var colonIndex = compositeKey.indexOf(":"); + // If no colon or colon is after position 6 (hash length), it's a plain ID + if (colonIndex === -1 || colonIndex > 6) { + return { + sourceHash: null, + pluginId: compositeKey, + isOfficial: true + }; + } + // Has hash prefix (custom source plugin) + return { + sourceHash: compositeKey.substring(0, colonIndex), + pluginId: compositeKey.substring(colonIndex + 1), + isOfficial: false + }; + } + + // Get source name by URL + function getSourceNameByUrl(sourceUrl) { + for (var i = 0; i < root.pluginSources.length; i++) { + if (root.pluginSources[i].url === sourceUrl) { + return root.pluginSources[i].name; + } + } + return null; + } + + // Get source name by hash + function getSourceNameByHash(hash) { + for (var i = 0; i < root.pluginSources.length; i++) { + if (generateSourceHash(root.pluginSources[i].url) === hash) { + return root.pluginSources[i].name; + } + } + return null; + } + + // Get source URL from plugin state + function getPluginSourceUrl(compositeKey) { + var state = root.pluginStates[compositeKey]; + return state?.sourceUrl || root.mainSourceUrl; + } + + // Signals + signal pluginsChanged + + // In-memory plugin cache (populated by scanning disk) + property var installedPlugins: ({}) // { pluginId: manifest } + property var pluginStates: ({}) // { pluginId: { enabled: bool } } + property var pluginSources: [] // Array of { name, url } + property var pluginLoadVersions: ({}) // { pluginId: versionNumber } - for cache busting + + // Track async loading + property int pendingManifests: 0 + + // File storage (minimal - only states and sources) + property FileView pluginsFileView: FileView { + id: pluginsFileView + path: root.pluginsFile + + adapter: JsonAdapter { + id: adapter + property int version: root.currentVersion + property var states: ({}) + property list sources: [] + } + + onLoaded: { + Logger.i("PluginRegistry", "Loaded plugin states from:", path); + root.pluginStates = adapter.states || {}; + root.pluginSources = adapter.sources || []; + + // Ensure default repo is in sources + if (root.pluginSources.length === 0) { + root.pluginSources = [ + { + "name": "Noctalia Plugins", + "url": "https://github.com/noctalia-dev/noctalia-plugins", + "enabled": true + } + ]; + root.save(); + } + + // Migrate from v1 to v2 (add sourceUrl to states) + root.migratePluginData(); + + // Scan plugin folder to discover installed plugins + scanPluginFolder(); + } + + onLoadFailed: function (error) { + Logger.w("PluginRegistry", "Failed to load plugins.json, will create it:", error); + // Initialize defaults and continue + root.pluginStates = {}; + root.pluginSources = [ + { + "name": "Noctalia Plugins", + "url": "https://github.com/noctalia-dev/noctalia-plugins", + "enabled": true + } + ]; + // Scan for installed plugins + root.scanPluginFolder(); + } + } + + function init() { + Logger.d("PluginRegistry", "Initialized"); + // Force instantiation of PluginService to set up signal listener + PluginService.initialized; + } + + // Migrate plugin data from older versions + function migratePluginData() { + var needsSave = false; + + // Migration v1 -> v2: add sourceUrl to states + for (var pluginId in root.pluginStates) { + if (root.pluginStates[pluginId].sourceUrl === undefined) { + Logger.i("PluginRegistry", "Migrating plugin data to v2 (adding sourceUrl)"); + + var newStates = {}; + for (var id in root.pluginStates) { + // For v1 -> v2 migration, we assume plugins are from main source + // Custom plugins installed before this feature need to be reinstalled + newStates[id] = { + enabled: root.pluginStates[id].enabled, + sourceUrl: root.mainSourceUrl + }; + } + root.pluginStates = newStates; + needsSave = true; + break; + } + } + + // Migration: rename "Official Noctalia Plugins" -> "Noctalia Plugins" + var newSources = []; + var sourcesChanged = false; + for (var i = 0; i < root.pluginSources.length; i++) { + var source = root.pluginSources[i]; + if (source.name === "Official Noctalia Plugins") { + newSources.push({ + name: "Noctalia Plugins", + url: source.url, + enabled: source.enabled + }); + sourcesChanged = true; + Logger.i("PluginRegistry", "Migrating source name: 'Official Noctalia Plugins' -> 'Noctalia Plugins'"); + } else { + newSources.push(source); + } + } + if (sourcesChanged) { + root.pluginSources = newSources; + needsSave = true; + } + + if (needsSave) { + root.save(); + Logger.i("PluginRegistry", "Migration complete"); + } + } + + // Ensure plugins directory exists + function ensurePluginsDirectory() { + var mkdirProcess = Qt.createQmlObject(` + import QtQuick + import Quickshell.Io + Process { + command: ["mkdir", "-p", "${root.pluginsDir}"] + } + `, root, "MkdirPlugins"); + + mkdirProcess.exited.connect(function (exitCode) { + if (exitCode === 0) { + Logger.d("PluginRegistry", "Plugins directory ensured:", root.pluginsDir); + } else { + Logger.e("PluginRegistry", "Failed to create plugins directory"); + } + mkdirProcess.destroy(); + }); + + mkdirProcess.running = true; + } + + // Ensure plugins.json exists (create minimal one if it doesn't) + function ensurePluginsFile() { + var checkProcess = Qt.createQmlObject(` + import QtQuick + import Quickshell.Io + Process { + command: ["sh", "-c", "test -f '${root.pluginsFile}' || echo '{\\"version\\":${root.currentVersion},\\"states\\":{},\\"sources\\":[]}' > '${root.pluginsFile}'"] + } + `, root, "EnsurePluginsFile"); + + checkProcess.exited.connect(function (exitCode) { + if (exitCode === 0) { + Logger.d("PluginRegistry", "Plugins file ensured:", root.pluginsFile); + } + checkProcess.destroy(); + }); + + checkProcess.running = true; + } + + // Scan plugin folder to discover installed plugins (single process reads all manifests) + function scanPluginFolder() { + Logger.i("PluginRegistry", "Scanning plugin folder:", root.pluginsDir); + + var scanProcess = Qt.createQmlObject(` + import QtQuick + import Quickshell.Io + Process { + command: ["sh", "-c", "for d in '${root.pluginsDir}'/*/; do [ -d \\"$d\\" ] || continue; [ -f \\"$d/manifest.json\\" ] || continue; echo \\"@@PLUGIN@@$(basename \\"$d\\")\\" ; cat \\"$d/manifest.json\\" ; done"] + stdout: StdioCollector {} + running: true + } + `, root, "ScanAllPlugins"); + + scanProcess.exited.connect(function (exitCode) { + var output = String(scanProcess.stdout.text || ""); + var sections = output.split("@@PLUGIN@@"); + var loadedCount = 0; + + for (var i = 1; i < sections.length; i++) { + var section = sections[i]; + var newlineIdx = section.indexOf('\n'); + if (newlineIdx === -1) + continue; + + var pluginId = section.substring(0, newlineIdx).trim(); + var manifestJson = section.substring(newlineIdx + 1).trim(); + + if (!pluginId || !manifestJson) + continue; + + try { + var manifest = JSON.parse(manifestJson); + var validation = validateManifest(manifest); + + if (validation.valid) { + manifest.compositeKey = pluginId; + root.installedPlugins[pluginId] = manifest; + Logger.i("PluginRegistry", "Loaded plugin:", pluginId, "-", manifest.name); + + if (!root.pluginStates[pluginId]) { + root.pluginStates[pluginId] = { + enabled: false + }; + } + loadedCount++; + } else { + Logger.e("PluginRegistry", "Invalid manifest for", pluginId + ":", validation.error); + } + } catch (e) { + Logger.e("PluginRegistry", "Failed to parse manifest for", pluginId + ":", e.toString()); + } + } + + Logger.i("PluginRegistry", "All plugin manifests loaded. Total plugins:", loadedCount); + root.pluginsChanged(); + scanProcess.destroy(); + }); + } + + // Load a single plugin's manifest from disk + function loadPluginManifest(pluginId) { + var manifestPath = root.pluginsDir + "/" + pluginId + "/manifest.json"; + + var catProcess = Qt.createQmlObject(` + import QtQuick + import Quickshell.Io + Process { + command: ["cat", "${manifestPath}"] + stdout: StdioCollector {} + running: true + } + `, root, "LoadManifest_" + pluginId); + + catProcess.exited.connect(function (exitCode) { + var output = String(catProcess.stdout.text || ""); + if (exitCode === 0 && output) { + try { + var manifest = JSON.parse(output); + var validation = validateManifest(manifest); + + if (validation.valid) { + manifest.compositeKey = pluginId; + root.installedPlugins[pluginId] = manifest; + Logger.i("PluginRegistry", "Loaded plugin:", pluginId, "-", manifest.name); + + // Ensure state exists (default to disabled) + if (!root.pluginStates[pluginId]) { + root.pluginStates[pluginId] = { + enabled: false + }; + } + } else { + Logger.e("PluginRegistry", "Invalid manifest for", pluginId + ":", validation.error); + } + } catch (e) { + Logger.e("PluginRegistry", "Failed to parse manifest for", pluginId + ":", e.toString()); + } + } else { + Logger.d("PluginRegistry", "No manifest found for:", pluginId); + } + + // Decrement pending count and emit signal when all are done + root.pendingManifests--; + Logger.d("PluginRegistry", "Pending manifests remaining:", root.pendingManifests); + if (root.pendingManifests === 0) { + var installedIds = Object.keys(root.installedPlugins); + Logger.i("PluginRegistry", "All plugin manifests loaded. Total plugins:", installedIds.length); + Logger.d("PluginRegistry", "Installed plugin IDs:", JSON.stringify(installedIds)); + root.pluginsChanged(); + } + + catProcess.destroy(); + }); + } + + // Save registry to disk (only states and sources) + function save() { + adapter.version = root.currentVersion; + adapter.states = root.pluginStates; + adapter.sources = root.pluginSources; + + Qt.callLater(() => { + pluginsFileView.writeAdapter(); + Logger.d("PluginRegistry", "Plugin states saved"); + }); + } + + // Enable/disable a plugin + function setPluginEnabled(pluginId, enabled) { + if (!root.installedPlugins[pluginId]) { + Logger.w("PluginRegistry", "Cannot set state for non-existent plugin:", pluginId); + return; + } + + if (!root.pluginStates[pluginId]) { + root.pluginStates[pluginId] = { + enabled: enabled + }; + } else { + root.pluginStates[pluginId].enabled = enabled; + } + + save(); + root.pluginsChanged(); + Logger.i("PluginRegistry", "Plugin", pluginId, enabled ? "enabled" : "disabled"); + } + + // Check if plugin is enabled + function isPluginEnabled(pluginId) { + return root.pluginStates[pluginId]?.enabled || false; + } + + // Check if plugin is downloaded/installed + function isPluginDownloaded(pluginId) { + return pluginId in root.installedPlugins; + } + + // Get plugin manifest from cache + function getPluginManifest(pluginId) { + return root.installedPlugins[pluginId] || null; + } + + // Get ALL installed plugin IDs (discovered from disk) + function getAllInstalledPluginIds() { + return Object.keys(root.installedPlugins); + } + + // Get enabled plugin IDs only + function getEnabledPluginIds() { + return Object.keys(root.pluginStates).filter(function (id) { + return root.pluginStates[id].enabled === true; + }); + } + + // Register a plugin (add to installed plugins after download) + // sourceUrl is required for new plugins to generate composite key + function registerPlugin(manifest, sourceUrl) { + var compositeKey = generateCompositeKey(manifest.id, sourceUrl); + manifest.compositeKey = compositeKey; + root.installedPlugins[compositeKey] = manifest; + + // Ensure state exists (default to disabled, store sourceUrl) + if (!root.pluginStates[compositeKey]) { + root.pluginStates[compositeKey] = { + enabled: false, + sourceUrl: sourceUrl || root.mainSourceUrl + }; + } else { + // Preserve enabled state but update sourceUrl + root.pluginStates[compositeKey].sourceUrl = sourceUrl || root.mainSourceUrl; + } + + save(); + root.pluginsChanged(); + Logger.i("PluginRegistry", "Registered plugin:", compositeKey); + return compositeKey; + } + + // Unregister a plugin (remove from registry) + function unregisterPlugin(pluginId) { + delete root.pluginStates[pluginId]; + delete root.installedPlugins[pluginId]; + save(); + root.pluginsChanged(); + Logger.i("PluginRegistry", "Unregistered plugin:", pluginId); + } + + // Increment plugin load version (for cache busting when plugin is updated) + function incrementPluginLoadVersion(pluginId) { + var versions = Object.assign({}, root.pluginLoadVersions); + versions[pluginId] = (versions[pluginId] || 0) + 1; + root.pluginLoadVersions = versions; + Logger.d("PluginRegistry", "Incremented load version for", pluginId, "to", versions[pluginId]); + return versions[pluginId]; + } + + // Remove plugin state (call after deleting plugin folder) + function removePluginState(pluginId) { + delete root.pluginStates[pluginId]; + delete root.installedPlugins[pluginId]; + save(); + root.pluginsChanged(); + Logger.i("PluginRegistry", "Removed plugin state:", pluginId); + } + + // Add a plugin source + function addPluginSource(name, url) { + for (var i = 0; i < root.pluginSources.length; i++) { + if (root.pluginSources[i].url === url) { + Logger.w("PluginRegistry", "Source already exists:", url); + return false; + } + } + + // Create a new array to trigger property change notification + var newSources = root.pluginSources.slice(); + newSources.push({ + name: name, + url: url, + enabled: true + }); + root.pluginSources = newSources; + save(); + Logger.i("PluginRegistry", "Added plugin source:", name); + return true; + } + + // Remove a plugin source + function removePluginSource(url) { + var newSources = []; + for (var i = 0; i < root.pluginSources.length; i++) { + if (root.pluginSources[i].url !== url) { + newSources.push(root.pluginSources[i]); + } + } + + if (newSources.length === root.pluginSources.length) { + Logger.w("PluginRegistry", "Source not found:", url); + return false; + } + + root.pluginSources = newSources; + save(); + Logger.i("PluginRegistry", "Removed plugin source:", url); + return true; + } + + // Set source enabled/disabled state + function setSourceEnabled(url, enabled) { + var newSources = []; + var found = false; + for (var i = 0; i < root.pluginSources.length; i++) { + if (root.pluginSources[i].url === url) { + newSources.push({ + name: root.pluginSources[i].name, + url: root.pluginSources[i].url, + enabled: enabled + }); + found = true; + } else { + newSources.push(root.pluginSources[i]); + } + } + + if (!found) { + Logger.w("PluginRegistry", "Source not found:", url); + return false; + } + + root.pluginSources = newSources; + save(); + Logger.i("PluginRegistry", "Source", url, enabled ? "enabled" : "disabled"); + return true; + } + + // Check if source is enabled + function isSourceEnabled(url) { + for (var i = 0; i < root.pluginSources.length; i++) { + if (root.pluginSources[i].url === url) { + return root.pluginSources[i].enabled !== false; // Default to true if not set + } + } + return false; + } + + // Get enabled sources only + function getEnabledSources() { + var enabledSources = []; + for (var i = 0; i < root.pluginSources.length; i++) { + if (root.pluginSources[i].enabled !== false) { + enabledSources.push(root.pluginSources[i]); + } + } + return enabledSources; + } + + // Get plugin directory path + function getPluginDir(pluginId) { + return root.pluginsDir + "/" + pluginId; + } + + // Get plugin settings file path + function getPluginSettingsFile(pluginId) { + return getPluginDir(pluginId) + "/settings.json"; + } + + // Validate manifest + function validateManifest(manifest) { + if (!manifest) { + return { + valid: false, + error: "Manifest is null or undefined" + }; + } + + var required = ["id", "name", "version", "author", "description"]; + for (var i = 0; i < required.length; i++) { + if (!manifest[required[i]]) { + return { + valid: false, + error: "Missing required field: " + required[i] + }; + } + } + + if (!manifest.entryPoints) { + return { + valid: false, + error: "Missing 'entryPoints' field" + }; + } + + // Check version format (simple x.y.z check) + var versionRegex = /^\d+\.\d+\.\d+$/; + if (!versionRegex.test(manifest.version)) { + return { + valid: false, + error: "Invalid version format (must be x.y.z)" + }; + } + + return { + valid: true, + error: null + }; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Noctalia/PluginService.qml b/arch/.config/quickshell/noctalia-shell/Services/Noctalia/PluginService.qml new file mode 100644 index 0000000..7b01744 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Noctalia/PluginService.qml @@ -0,0 +1,2087 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Modules.Panels.Settings +import qs.Services.Noctalia +import qs.Services.UI + +Singleton { + id: root + + signal pluginLoaded(string pluginId) + signal pluginUnloaded(string pluginId) + signal pluginEnabled(string pluginId) + signal pluginDisabled(string pluginId) + signal pluginReloaded(string pluginId) + signal availablePluginsUpdated + signal allPluginsLoaded + + // When available plugins are updated, check if we should perform update check + onAvailablePluginsUpdated: { + if (shouldCheckUpdatesAfterFetch && Object.keys(activeFetches).length === 0) { + Logger.d("PluginService", "All registry fetches complete, performing update check"); + performUpdateCheck(); + } + } + + // Loaded plugin instances + property var loadedPlugins: ({}) // { pluginId: { component, instance, api } } + + // Available plugins from all sources (fetched from registries) + property var availablePlugins: ([]) // Array of plugin metadata from all sources + + // Plugin updates available: { pluginId: { currentVersion, availableVersion } } + property var pluginUpdates: ({}) + + // Plugin updates that require a newer Noctalia version: { pluginId: { currentVersion, availableVersion, minNoctaliaVersion } } + property var pluginUpdatesPending: ({}) + + // Plugin load errors: { pluginId: { error: string, entryPoint: string, timestamp: date } } + property var pluginErrors: ({}) + signal pluginLoadError(string pluginId, string entryPoint, string error) + + // Track currently installing plugins: { pluginId: true } + property var installingPlugins: ({}) + + // Hot reload: file watchers for plugin directories + property var pluginFileWatchers: ({}) // { pluginId: FileView } + property list pluginHotReloadEnabled: [] // List of pluginIds that have hot reload enabled + + // Track active fetches + property var activeFetches: ({}) + + property bool initialized: false + property bool pluginsFullyLoaded: false + + // Plugin container from shell.qml (for placing Main instances in graphics scene) + property var pluginContainer: null + + // Screen detector from shell.qml (for withCurrentScreen in plugin API) + property var screenDetector: null + + // Track if we need to initialize once container is ready + property bool needsInit: false + + // Watch for pluginContainer to be set + onPluginContainerChanged: { + if (root.pluginContainer && root.needsInit) { + Logger.d("PluginService", "Plugin container now available, initializing plugins"); + root.needsInit = false; + root.init(); + } + } + + // Listen for PluginRegistry to finish loading + Connections { + target: PluginRegistry + + function onPluginsChanged() { + if (!root.initialized) { + if (root.pluginContainer) { + // Container already available, init now + root.init(); + } else { + // Container not ready, wait for it + Logger.d("PluginService", "Deferring plugin init until container is ready"); + root.needsInit = true; + } + } + } + } + + // When debug mode is disabled, tear down all hot reload watchers + Connections { + target: Settings + + function onIsDebugChanged() { + if (!Settings.isDebug && root.pluginHotReloadEnabled.length > 0) { + Logger.i("PluginService", "Debug mode disabled, removing all hot reload watchers"); + // Remove watchers for all hot-reload-enabled plugins + var plugins = root.pluginHotReloadEnabled.slice(); // copy since we mutate + for (var i = 0; i < plugins.length; i++) { + removePluginFileWatcher(plugins[i]); + } + root.pluginHotReloadEnabled = []; + } + } + } + + // Listen for language changes to reload plugin translations + Connections { + target: I18n + + function onLanguageChanged() { + Logger.d("PluginService", "Language changed to:", I18n.langCode, "- reloading plugin translations"); + + // Reload translations for all loaded plugins + for (var pluginId in root.loadedPlugins) { + // Use IIFE to capture current loop values (avoid closure bug) + (function (id, plugin) { + if (plugin && plugin.api && plugin.manifest) { + // Update current language + plugin.api.currentLanguage = I18n.langCode; + + // Reload translations + loadPluginTranslationsAsync(id, plugin.manifest, I18n.langCode, function (translations) { + plugin.api.pluginTranslations = translations; + + // Reload English fallback for non-English languages + if (I18n.langCode !== "en") { + loadPluginTranslationsAsync(id, plugin.manifest, "en", function (fallbackTranslations) { + plugin.api.pluginFallbackTranslations = fallbackTranslations; + plugin.api.translationVersion++; + Logger.d("PluginService", "Reloaded translations for plugin:", id); + }); + } else { + plugin.api.pluginFallbackTranslations = {}; + plugin.api.translationVersion++; + Logger.d("PluginService", "Reloaded translations for plugin:", id); + } + }); + } + })(pluginId, root.loadedPlugins[pluginId]); + } + + // Update translation file watchers to watch the new language's files + if (root.pluginHotReloadEnabled.length > 0) { + updateTranslationWatchers(); + } + } + } + + // Track pending plugin loads for init completion + property int _pendingPluginLoads: 0 + + function init() { + if (root.initialized) { + Logger.d("PluginService", "Already initialized, skipping"); + return; + } + + Logger.i("PluginService", "Initializing plugin system"); + root.initialized = true; + + // Debug: Check what's in PluginRegistry + var allInstalled = PluginRegistry.getAllInstalledPluginIds(); + Logger.d("PluginService", "All installed plugins:", JSON.stringify(allInstalled)); + Logger.d("PluginService", "Plugin states:", JSON.stringify(PluginRegistry.pluginStates)); + + // Load all enabled plugins + var enabledIds = PluginRegistry.getEnabledPluginIds(); + Logger.i("PluginService", "Found", enabledIds.length, "enabled plugins:", JSON.stringify(enabledIds)); + + // Count plugins that need to be loaded + var pluginsToLoad = []; + for (var i = 0; i < enabledIds.length; i++) { + var manifest = PluginRegistry.getPluginManifest(enabledIds[i]); + if (manifest) { + pluginsToLoad.push(enabledIds[i]); + } else { + Logger.w("PluginService", "Plugin", enabledIds[i], "is enabled but not found on disk - install"); + var sourceUrl = PluginRegistry.getPluginSourceUrl(enabledIds[i]); + root.installPlugin({ + id: enabledIds[i], + source: { + url: sourceUrl + } + }, false, function (success, error, registeredKey) { + if (success) { + ToastService.showNotice(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.install-success", { + "plugin": registeredKey + })); + // Load the plugin since it was already enabled (state persisted but files were missing) + loadPlugin(registeredKey); + + // Add plugin widget to bar if it provides one + var manifest = PluginRegistry.getPluginManifest(registeredKey); + if (manifest && manifest.entryPoints && manifest.entryPoints.barWidget) { + var widgetId = "plugin:" + registeredKey; + addWidgetToBar(widgetId, "right"); + } + } else { + ToastService.showError(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.install-error", { + "error": error || "Unknown error" + })); + } + }); + } + } + + // If no plugins to load, mark as complete immediately + if (pluginsToLoad.length === 0) { + root.pluginsFullyLoaded = true; + Logger.i("PluginService", "No plugins to load"); + root.allPluginsLoaded(); + root._isStartupCheck = true; + refreshAvailablePlugins(); + return; + } + + // Track pending loads + root._pendingPluginLoads = pluginsToLoad.length; + + // Load all plugins (async - they will call _onPluginLoadComplete when done) + for (var j = 0; j < pluginsToLoad.length; j++) { + Logger.d("PluginService", "Attempting to load plugin:", pluginsToLoad[j]); + loadPlugin(pluginsToLoad[j]); + } + } + + // Called when a plugin finishes loading (success or failure) + function _onPluginLoadComplete() { + root._pendingPluginLoads--; + + if (root._pendingPluginLoads <= 0) { + // All plugins finished loading + root.pluginsFullyLoaded = true; + Logger.i("PluginService", "All plugins loaded"); + root.allPluginsLoaded(); + + // Fetch available plugins from all sources + root._isStartupCheck = true; + refreshAvailablePlugins(); + } + } + + // Refresh available plugins from all sources + function refreshAvailablePlugins() { + // If fetches are already in progress, don't start new ones + if (Object.keys(activeFetches).length > 0) { + Logger.d("PluginService", "Refresh already in progress, skipping duplicate refresh"); + return; + } + + Logger.i("PluginService", "Refreshing available plugins"); + root.availablePlugins = []; + + // Signal that we want to check for updates after refresh completes + shouldCheckUpdatesAfterFetch = true; + + var enabledSources = PluginRegistry.getEnabledSources(); + Logger.d("PluginService", "Fetching from", enabledSources.length, "enabled sources"); + for (var i = 0; i < enabledSources.length; i++) { + fetchPluginRegistry(enabledSources[i]); + } + } + + // Fetch plugin registry from a source using git sparse-checkout + function fetchPluginRegistry(source) { + var repoUrl = source.url; + + Logger.d("PluginService", "Fetching registry from:", repoUrl); + + // Use git sparse-checkout to fetch only registry.json (--no-cone for single file) + // GIT_TERMINAL_PROMPT=0 prevents hanging on private repos that need auth + var fetchCmd = "temp_dir=$(mktemp -d) && GIT_TERMINAL_PROMPT=0 git clone --filter=blob:none --sparse --depth=1 --quiet '" + repoUrl + "' \"$temp_dir\" 2>/dev/null && cd \"$temp_dir\" && git sparse-checkout set --no-cone /registry.json 2>/dev/null && cat \"$temp_dir/registry.json\"; rm -rf \"$temp_dir\""; + + var fetchProcess = Qt.createQmlObject('import QtQuick; import Quickshell.Io; Process { command: ["sh", "-c", "' + fetchCmd.replace(/"/g, '\\"') + '"]; stdout: StdioCollector {} }', root, "FetchRegistry_" + Date.now()); + + activeFetches[source.url] = fetchProcess; + + fetchProcess.stdout.onStreamFinished.connect(function () { + var response = fetchProcess.stdout.text; + + // Debug: log the raw response + Logger.d("PluginService", "Registry response length:", response ? response.length : 0); + + if (!response || response.trim() === "") { + Logger.e("PluginService", "Empty response from", source.name); + delete activeFetches[source.url]; + fetchProcess.destroy(); + return; + } + + try { + var registry = JSON.parse(response); + + if (registry && registry.plugins && Array.isArray(registry.plugins)) { + // Add source info to each plugin + for (var i = 0; i < registry.plugins.length; i++) { + var plugin = registry.plugins[i]; + plugin.source = source; + + // Check if already downloaded - use composite key + var compositeKey = PluginRegistry.generateCompositeKey(plugin.id, source.url); + plugin.downloaded = PluginRegistry.isPluginDownloaded(compositeKey); + plugin.enabled = PluginRegistry.isPluginEnabled(compositeKey); + + root.availablePlugins.push(plugin); + } + + Logger.i("PluginService", `Parsed ${registry.plugins.length} plugins manifest from '${source.name}'`); + + // Remove from active fetches BEFORE emitting signal so handler sees correct count + delete activeFetches[source.url]; + fetchProcess.destroy(); + + root.availablePluginsUpdated(); + return; + } + } catch (e) { + Logger.e("PluginService", "Failed to parse registry from", source.name, ":", e); + Logger.e("PluginService", "Response was:", response ? response.substring(0, 200) : "null"); + } + + // Clean up on error or empty response + delete activeFetches[source.url]; + fetchProcess.destroy(); + }); + + fetchProcess.exited.connect(function (exitCode) { + if (exitCode !== 0) { + Logger.e("PluginService", "Failed to fetch registry from", source.name, "- exit code:", exitCode); + delete activeFetches[source.url]; + fetchProcess.destroy(); + } + }); + + fetchProcess.running = true; + } + + // Check for plugin ID collision before installation + function checkPluginCollision(pluginMetadata) { + var sourceUrl = pluginMetadata.source.url; + var compositeKey = PluginRegistry.generateCompositeKey(pluginMetadata.id, sourceUrl); + + // Check if this exact composite key already exists + if (PluginRegistry.isPluginDownloaded(compositeKey)) { + return { + collision: true, + reason: "already_installed", + existingKey: compositeKey, + message: I18n.tr("panels.plugins.collision-already-installed") + }; + } + + // For official plugins, also check if any custom version with same base ID exists + if (PluginRegistry.isMainSource(sourceUrl)) { + var allInstalled = PluginRegistry.getAllInstalledPluginIds(); + for (var i = 0; i < allInstalled.length; i++) { + var parsed = PluginRegistry.parseCompositeKey(allInstalled[i]); + if (parsed.pluginId === pluginMetadata.id && !parsed.isOfficial) { + var sourceName = PluginRegistry.getSourceNameByHash(parsed.sourceHash) || I18n.tr("panels.plugins.source-custom"); + return { + collision: true, + reason: "custom_version_exists", + existingKey: allInstalled[i], + message: I18n.tr("panels.plugins.collision-custom-version-exists", { + source: sourceName + }) + }; + } + } + } + + // For custom plugins, check if official version exists + if (!PluginRegistry.isMainSource(sourceUrl)) { + if (PluginRegistry.isPluginDownloaded(pluginMetadata.id)) { + return { + collision: true, + reason: "official_version_exists", + existingKey: pluginMetadata.id, + message: I18n.tr("panels.plugins.collision-official-version-exists") + }; + } + } + + return { + collision: false + }; + } + + // Download and install a plugin using git sparse-checkout + // skipCollisionCheck: set to true when updating an existing plugin + function installPlugin(pluginMetadata, skipCollisionCheck, callback) { + var pluginId = pluginMetadata.id; + // Do not include hash for 3rd party plugins + var pluginIdRegex = /^[a-f0-9]{6}:/; + if (pluginIdRegex.test(pluginId)) { + pluginId = pluginId.substring(7); + } + + var source = pluginMetadata.source; + + // Check for collision first (skip when updating) + if (!skipCollisionCheck) { + var collision = checkPluginCollision(pluginMetadata); + if (collision.collision) { + Logger.w("PluginService", "Plugin collision detected:", collision.message); + ToastService.showError(I18n.tr("panels.plugins.title"), collision.message); + if (callback) + callback(false, collision.message); + return; + } + + // Check Noctalia version compatibility (skip when updating - that's handled in performUpdateCheck) + if (pluginMetadata.minNoctaliaVersion) { + var noctaliaVersion = UpdateService.baseVersion; + if (compareVersions(pluginMetadata.minNoctaliaVersion, noctaliaVersion) > 0) { + var incompatibleMsg = I18n.tr("panels.plugins.install-incompatible", { + "plugin": pluginMetadata.name, + "version": pluginMetadata.minNoctaliaVersion + }); + Logger.w("PluginService", "Plugin incompatible:", incompatibleMsg); + if (callback) + callback(false, incompatibleMsg); + return; + } + } + } + + // Generate composite key for the plugin folder + var compositeKey = PluginRegistry.generateCompositeKey(pluginId, source.url); + Logger.i("PluginService", "Installing plugin:", compositeKey, "from", source.name); + + var pluginDir = PluginRegistry.getPluginDir(compositeKey); + var repoUrl = source.url; + + // Use git sparse-checkout to clone only the plugin subfolder + // GIT_TERMINAL_PROMPT=0 prevents hanging on private repos that need auth + // Note: We download from the original pluginId folder in the repo, but save to compositeKey folder + var downloadCmd = "temp_dir=$(mktemp -d) && GIT_TERMINAL_PROMPT=0 git clone --filter=blob:none --sparse --depth=1 --quiet '" + repoUrl + "' \"$temp_dir\" 2>/dev/null && cd \"$temp_dir\" && git sparse-checkout set '" + pluginId + "' 2>/dev/null && mkdir -p '" + pluginDir + "' && rm -f \"$temp_dir/" + pluginId + "/settings.json\" && cp -r \"$temp_dir/" + pluginId + + "/.\" '" + pluginDir + "/'; exit_code=$?; rm -rf \"$temp_dir\"; exit $exit_code"; + + // Mark as installing + var newInstalling = Object.assign({}, root.installingPlugins); + newInstalling[pluginId] = true; + root.installingPlugins = newInstalling; + + var downloadProcess = Qt.createQmlObject('import QtQuick; import Quickshell.Io; Process { command: ["sh", "-c", "' + downloadCmd.replace(/"/g, '\\"') + '"] }', root, "DownloadPlugin_" + pluginId); + + downloadProcess.exited.connect(function (exitCode) { + // Mark as finished (remove from installing) + var currentInstalling = Object.assign({}, root.installingPlugins); + delete currentInstalling[pluginId]; + root.installingPlugins = currentInstalling; + + if (exitCode === 0) { + Logger.i("PluginService", "Downloaded plugin:", compositeKey); + + // Load and validate manifest + var manifestPath = pluginDir + "/manifest.json"; + loadManifest(manifestPath, function (success, manifest) { + if (success) { + var validation = PluginRegistry.validateManifest(manifest); + if (validation.valid) { + // Register plugin with source URL + var registeredKey = PluginRegistry.registerPlugin(manifest, source.url); + Logger.i("PluginService", "Installed plugin:", registeredKey); + + // Update available plugins list + updatePluginInAvailable(pluginId, { + downloaded: true + }); + + if (callback) + callback(true, null, registeredKey); + } else { + Logger.e("PluginService", "Invalid manifest:", validation.error); + if (callback) + callback(false, "Invalid manifest: " + validation.error); + } + } else { + Logger.e("PluginService", "Failed to load manifest for:", compositeKey); + if (callback) + callback(false, "Failed to load manifest"); + } + }); + } else { + Logger.e("PluginService", "Failed to download plugin:", compositeKey); + if (callback) + callback(false, "Download failed"); + } + + downloadProcess.destroy(); + }); + + downloadProcess.running = true; + } + + // Uninstall a plugin (compositeKey is the full key like "abc123:my-plugin" or plain "my-plugin") + function uninstallPlugin(compositeKey, callback) { + Logger.i("PluginService", "Uninstalling plugin:", compositeKey); + + // Disable and unload first + if (PluginRegistry.isPluginEnabled(compositeKey)) { + disablePlugin(compositeKey); + } + + var pluginDir = PluginRegistry.getPluginDir(compositeKey); + + var removeProcess = Qt.createQmlObject(` + import QtQuick + import Quickshell.Io + Process { + command: ["rm", "-rf", "${pluginDir}"] + } + `, root, "RemovePlugin_" + compositeKey); + + removeProcess.exited.connect(function (exitCode) { + if (exitCode === 0) { + PluginRegistry.unregisterPlugin(compositeKey); + Logger.i("PluginService", "Uninstalled plugin:", compositeKey); + + // Update available plugins list (use plain ID to match against availablePlugins) + var parsed = PluginRegistry.parseCompositeKey(compositeKey); + updatePluginInAvailable(parsed.pluginId, { + downloaded: false, + enabled: false + }); + + if (callback) + callback(true, null); + } else { + Logger.e("PluginService", "Failed to uninstall plugin:", pluginId); + if (callback) + callback(false, "Failed to remove plugin files"); + } + + removeProcess.destroy(); + }); + + removeProcess.running = true; + } + + // Enable a plugin (compositeKey is the full key like "abc123:my-plugin" or plain "my-plugin") + function enablePlugin(compositeKey, skipAddToBar) { + if (PluginRegistry.isPluginEnabled(compositeKey)) { + Logger.w("PluginService", "Plugin already enabled:", compositeKey); + return true; + } + + if (!PluginRegistry.isPluginDownloaded(compositeKey)) { + Logger.e("PluginService", "Cannot enable: plugin not downloaded:", compositeKey); + return false; + } + + PluginRegistry.setPluginEnabled(compositeKey, true); + loadPlugin(compositeKey); + + // Add plugin widget to bar if it provides one (unless we're restoring from backup) + if (!skipAddToBar) { + var manifest = PluginRegistry.getPluginManifest(compositeKey); + if (manifest && manifest.entryPoints && manifest.entryPoints.barWidget) { + var widgetId = "plugin:" + compositeKey; + addWidgetToBar(widgetId, "right"); + } + } + + // Update available plugins list (use plain ID to match against availablePlugins) + var parsed = PluginRegistry.parseCompositeKey(compositeKey); + updatePluginInAvailable(parsed.pluginId, { + enabled: true + }); + root.pluginEnabled(compositeKey); + + return true; + } + + // Helper function to add a widget to the bar (global + all screen overrides) + function addWidgetToBar(widgetId, section) { + section = section || "right"; // Default to right section + + // Check if widget already exists in any section (global) + var sections = ["left", "center", "right"]; + for (var s = 0; s < sections.length; s++) { + var widgets = Settings.data.bar.widgets[sections[s]] || []; + for (var i = 0; i < widgets.length; i++) { + if (widgets[i].id === widgetId) { + Logger.d("PluginService", "Widget already in bar:", widgetId); + return false; + } + } + } + + // Add to global + var globalWidgets = Settings.data.bar.widgets[section] || []; + globalWidgets.push({ + id: widgetId + }); + Settings.data.bar.widgets[section] = globalWidgets; + + // Also add to any screen overrides that have widget configurations + var overrides = Settings.data.bar.screenOverrides || []; + for (var o = 0; o < overrides.length; o++) { + if (overrides[o] && overrides[o].widgets) { + var overrideWidgets = overrides[o].widgets; + var sectionWidgets = overrideWidgets[section] || []; + // Check if widget already exists in this override + var alreadyExists = false; + for (var j = 0; j < sections.length; j++) { + var owSec = overrideWidgets[sections[j]] || []; + for (var k = 0; k < owSec.length; k++) { + if (owSec[k].id === widgetId) { + alreadyExists = true; + break; + } + } + if (alreadyExists) + break; + } + if (!alreadyExists) { + sectionWidgets.push({ + id: widgetId + }); + overrideWidgets[section] = sectionWidgets; + Settings.setScreenOverride(overrides[o].name, "widgets", overrideWidgets); + } + } + } + + Logger.i("PluginService", "Added widget", widgetId, "to bar section:", section); + return true; + } + + // Disable a plugin (compositeKey is the full key like "abc123:my-plugin" or plain "my-plugin") + function disablePlugin(compositeKey) { + if (!PluginRegistry.isPluginEnabled(compositeKey)) { + Logger.w("PluginService", "Plugin already disabled:", compositeKey); + return true; + } + + // Remove plugin widget from bar before unloading + var widgetId = "plugin:" + compositeKey; + removeWidgetFromBar(widgetId); + + PluginRegistry.setPluginEnabled(compositeKey, false); + unloadPlugin(compositeKey); + + // Update available plugins list (use plain ID to match against availablePlugins) + var parsed = PluginRegistry.parseCompositeKey(compositeKey); + updatePluginInAvailable(parsed.pluginId, { + enabled: false + }); + root.pluginDisabled(compositeKey); + return true; + } + + // Helper function to remove a widget from all bar sections (global + screen overrides) + function removeWidgetFromBar(widgetId) { + var sections = ["left", "center", "right"]; + var changed = false; + + // Remove from global + for (var s = 0; s < sections.length; s++) { + var section = sections[s]; + var widgets = Settings.data.bar.widgets[section] || []; + var newWidgets = []; + + for (var i = 0; i < widgets.length; i++) { + if (widgets[i].id !== widgetId) { + newWidgets.push(widgets[i]); + } else { + changed = true; + Logger.i("PluginService", "Removed widget", widgetId, "from bar section:", section); + } + } + + if (changed) { + Settings.data.bar.widgets[section] = newWidgets; + } + } + + // Also remove from any screen overrides that have widget configurations + var overrides = Settings.data.bar.screenOverrides || []; + for (var o = 0; o < overrides.length; o++) { + if (overrides[o] && overrides[o].widgets) { + var overrideWidgets = overrides[o].widgets; + var overrideChanged = false; + for (var s2 = 0; s2 < sections.length; s2++) { + var sec = sections[s2]; + var owWidgets = overrideWidgets[sec] || []; + var owNew = []; + for (var j = 0; j < owWidgets.length; j++) { + if (owWidgets[j].id !== widgetId) { + owNew.push(owWidgets[j]); + } else { + overrideChanged = true; + changed = true; + Logger.i("PluginService", "Removed widget", widgetId, "from screen override:", overrides[o].name, "section:", sec); + } + } + if (overrideChanged) { + overrideWidgets[sec] = owNew; + } + } + if (overrideChanged) { + Settings.setScreenOverride(overrides[o].name, "widgets", overrideWidgets); + } + } + } + + // Signal the bar to refresh if widgets were removed + if (changed) { + BarService.widgetsRevision++; + } + + return changed; + } + + // Remove plugin desktop widgets from all monitors' saved settings + function removePluginDesktopWidgetsFromSettings(pluginId) { + var widgetId = "plugin:" + pluginId; + var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || []; + var changed = false; + + for (var m = 0; m < monitorWidgets.length; m++) { + var monitor = monitorWidgets[m]; + var widgets = monitor.widgets || []; + var newWidgets = []; + + for (var i = 0; i < widgets.length; i++) { + if (widgets[i].id !== widgetId) { + newWidgets.push(widgets[i]); + } else { + changed = true; + Logger.i("PluginService", "Removed desktop widget", widgetId, "from monitor:", monitor.name); + } + } + + if (newWidgets.length !== widgets.length) { + monitorWidgets[m].widgets = newWidgets; + } + } + + if (changed) { + Settings.data.desktopWidgets.monitorWidgets = monitorWidgets; + } + + return changed; + } + + // Load plugin settings and translations before instantiating components + // This ensures pluginApi is fully populated before being passed to createObject() + function loadPluginData(pluginId, manifest, callback) { + // Load settings first + loadPluginSettings(pluginId, function (settings) { + // Then load translations + loadPluginTranslationsAsync(pluginId, manifest, I18n.langCode, function (translations) { + // Load English fallback for non-English languages + if (I18n.langCode !== "en") { + loadPluginTranslationsAsync(pluginId, manifest, "en", function (fallbackTranslations) { + callback(settings, translations, fallbackTranslations); + }); + } else { + callback(settings, translations, {}); + } + }); + }); + } + + // Load a plugin + function loadPlugin(pluginId) { + if (root.loadedPlugins[pluginId]) { + Logger.w("PluginService", "Plugin already loaded:", pluginId); + return; + } + + var manifest = PluginRegistry.getPluginManifest(pluginId); + if (!manifest) { + Logger.e("PluginService", "Cannot load: manifest not found for:", pluginId); + return; + } + + var pluginDir = PluginRegistry.getPluginDir(pluginId); + + Logger.i("PluginService", "Loading plugin:", pluginId); + + // Load settings and translations FIRST, then create API and instantiate components + loadPluginData(pluginId, manifest, function (settings, translations, fallbackTranslations) { + // Create plugin API object with pre-loaded data + var pluginApi = createPluginAPI(pluginId, manifest, settings, translations, fallbackTranslations); + + // Initialize plugin entry with API and manifest + root.loadedPlugins[pluginId] = { + barWidget: null, + desktopWidget: null, + launcherProvider: null, + mainInstance: null, + api: pluginApi, + manifest: manifest + }; + + // Clear any previous errors for this plugin + root.clearPluginError(pluginId); + + // Load Main.qml entry point if it exists + if (manifest.entryPoints && manifest.entryPoints.main) { + var mainPath = pluginDir + "/" + manifest.entryPoints.main; + var loadVersion = PluginRegistry.pluginLoadVersions[pluginId] || 0; + var mainComponent = Qt.createComponent("file://" + mainPath + "?v=" + loadVersion); + + if (mainComponent.status === Component.Ready) { + // Get the plugin container from shell.qml (must be in graphics scene) + if (!root.pluginContainer) { + Logger.e("PluginService", "Plugin container not set. Shell must set PluginService.pluginContainer."); + return; + } + + // Instantiate Main.qml with pluginApi passed directly in createObject + var mainInstance = mainComponent.createObject(root.pluginContainer, { + pluginApi: pluginApi + }); + + if (mainInstance) { + root.loadedPlugins[pluginId].mainInstance = mainInstance; + pluginApi.mainInstance = mainInstance; + Logger.i("PluginService", "Loaded Main.qml for plugin:", pluginId); + } else { + root.recordPluginError(pluginId, "main", "Failed to instantiate Main.qml"); + } + } else if (mainComponent.status === Component.Error) { + root.recordPluginError(pluginId, "main", mainComponent.errorString()); + } + } + + // Load bar widget component if provided (don't instantiate - BarWidgetRegistry will do that) + if (manifest.entryPoints && manifest.entryPoints.barWidget) { + var widgetPath = pluginDir + "/" + manifest.entryPoints.barWidget; + var widgetLoadVersion = PluginRegistry.pluginLoadVersions[pluginId] || 0; + var widgetComponent = Qt.createComponent("file://" + widgetPath + "?v=" + widgetLoadVersion); + + if (widgetComponent.status === Component.Ready) { + root.loadedPlugins[pluginId].barWidget = widgetComponent; + pluginApi.barWidget = widgetComponent; + + // Register with BarWidgetRegistry + BarWidgetRegistry.registerPluginWidget(pluginId, widgetComponent, manifest.metadata); + Logger.i("PluginService", "Loaded bar widget for plugin:", pluginId); + + // Now that the widget is registered, bump widgetsRevision so the bar can render it + BarService.widgetsRevision++; + } else if (widgetComponent.status === Component.Error) { + root.recordPluginError(pluginId, "barWidget", widgetComponent.errorString()); + } + } + + // Load desktop widget component if provided (don't instantiate - DesktopWidgetRegistry will do that) + if (manifest.entryPoints && manifest.entryPoints.desktopWidget) { + var desktopWidgetPath = pluginDir + "/" + manifest.entryPoints.desktopWidget; + var desktopWidgetLoadVersion = PluginRegistry.pluginLoadVersions[pluginId] || 0; + var desktopWidgetComponent = Qt.createComponent("file://" + desktopWidgetPath + "?v=" + desktopWidgetLoadVersion); + + if (desktopWidgetComponent.status === Component.Ready) { + root.loadedPlugins[pluginId].desktopWidget = desktopWidgetComponent; + pluginApi.desktopWidget = desktopWidgetComponent; + + // Register with DesktopWidgetRegistry + DesktopWidgetRegistry.registerPluginWidget(pluginId, desktopWidgetComponent, manifest.metadata); + Logger.i("PluginService", "Loaded desktop widget for plugin:", pluginId); + } else if (desktopWidgetComponent.status === Component.Error) { + root.recordPluginError(pluginId, "desktopWidget", desktopWidgetComponent.errorString()); + } + } + + // Load launcher provider component if provided (don't instantiate - Launcher will do that) + if (manifest.entryPoints && manifest.entryPoints.launcherProvider) { + var launcherProviderPath = pluginDir + "/" + manifest.entryPoints.launcherProvider; + var launcherProviderLoadVersion = PluginRegistry.pluginLoadVersions[pluginId] || 0; + var launcherProviderComponent = Qt.createComponent("file://" + launcherProviderPath + "?v=" + launcherProviderLoadVersion); + + if (launcherProviderComponent.status === Component.Ready) { + root.loadedPlugins[pluginId].launcherProvider = launcherProviderComponent; + pluginApi.launcherProvider = launcherProviderComponent; + + // Register with LauncherProviderRegistry + LauncherProviderRegistry.registerPluginProvider(pluginId, launcherProviderComponent, manifest.metadata); + Logger.i("PluginService", "Loaded launcher provider for plugin:", pluginId); + } else if (launcherProviderComponent.status === Component.Error) { + root.recordPluginError(pluginId, "launcherProvider", launcherProviderComponent.errorString()); + } + } + + // Load control center widget component if provided + if (manifest.entryPoints && manifest.entryPoints.controlCenterWidget) { + var ccWidgetPath = pluginDir + "/" + manifest.entryPoints.controlCenterWidget; + var ccWidgetLoadVersion = PluginRegistry.pluginLoadVersions[pluginId] || 0; + var ccWidgetComponent = Qt.createComponent("file://" + ccWidgetPath + "?v=" + ccWidgetLoadVersion); + + if (ccWidgetComponent.status === Component.Ready) { + root.loadedPlugins[pluginId].controlCenterWidget = ccWidgetComponent; + pluginApi.controlCenterWidget = ccWidgetComponent; + + // Register with ControlCenterWidgetRegistry + ControlCenterWidgetRegistry.registerPluginWidget(pluginId, ccWidgetComponent, manifest.metadata); + Logger.i("PluginService", "Loaded control center widget for plugin:", pluginId); + } else if (ccWidgetComponent.status === Component.Error) { + root.recordPluginError(pluginId, "controlCenterWidget", ccWidgetComponent.errorString()); + } + } + + Logger.i("PluginService", "Plugin loaded:", pluginId); + root.pluginLoaded(pluginId); + + // Set up hot reload watcher if enabled + setupPluginFileWatcher(pluginId); + + // Notify that this plugin finished loading (for init tracking) + root._onPluginLoadComplete(); + }); + } + + // Unload a plugin + // preserveSettings: if true, don't remove desktop widget settings (used for hot reload) + function unloadPlugin(pluginId, preserveSettings) { + var plugin = root.loadedPlugins[pluginId]; + if (!plugin) { + Logger.w("PluginService", "Plugin not loaded:", pluginId); + return; + } + + Logger.i("PluginService", "Unloading plugin:", pluginId); + + // Remove hot reload watcher + removePluginFileWatcher(pluginId); + + // Unregister from BarWidgetRegistry + if (plugin.manifest.entryPoints && plugin.manifest.entryPoints.barWidget) { + BarWidgetRegistry.unregisterPluginWidget(pluginId); + } + + // Unregister from DesktopWidgetRegistry + if (plugin.manifest.entryPoints && plugin.manifest.entryPoints.desktopWidget) { + // Only remove settings when uninstalling, not during hot reload + if (!preserveSettings) { + removePluginDesktopWidgetsFromSettings(pluginId); + } + DesktopWidgetRegistry.unregisterPluginWidget(pluginId); + } + + // Unregister from LauncherProviderRegistry + if (plugin.manifest.entryPoints && plugin.manifest.entryPoints.launcherProvider) { + LauncherProviderRegistry.unregisterPluginProvider(pluginId); + } + + // Unregister from ControlCenterWidgetRegistry + if (plugin.manifest.entryPoints && plugin.manifest.entryPoints.controlCenterWidget) { + ControlCenterWidgetRegistry.unregisterPluginWidget(pluginId); + } + + // Destroy Main instance if any + if (plugin.mainInstance) { + plugin.mainInstance.destroy(); + } + + delete root.loadedPlugins[pluginId]; + root.pluginUnloaded(pluginId); + Logger.i("PluginService", "Unloaded plugin:", pluginId); + } + + // Create plugin API object with pre-loaded settings and translations + function createPluginAPI(pluginId, manifest, settings, translations, fallbackTranslations) { + var pluginDir = PluginRegistry.getPluginDir(pluginId); + + var api = Qt.createQmlObject(` + import QtQuick + + QtObject { + // Plugin-specific + readonly property string pluginId: "${pluginId}" + readonly property string pluginDir: "${pluginDir}" + property var pluginSettings: ({}) + property var manifest: ({}) + + // Instance references (set after loading) + property var mainInstance: null + property var barWidget: null + property var desktopWidget: null + property var launcherProvider: null + property var controlCenterWidget: null + + // Panel state: which screen the plugin's panel is currently open on (null if closed) + property var panelOpenScreen: null + + // IPC handlers storage + property var ipcHandlers: ({}) + + // Translation storage + property var pluginTranslations: ({}) + property var pluginFallbackTranslations: ({}) // English fallback for missing keys + property string currentLanguage: "" + property int translationVersion: 0 // Increments when translations change - plugins should depend on this + + // Functions will be bound below + property var saveSettings: null + property var openPanel: null + property var closePanel: null + property var togglePanel: null + property var openLauncher: null + property var closeLauncher: null + property var toggleLauncher: null + property var withCurrentScreen: null + property var tr: null + property var trp: null + property var hasTranslation: null + } + `, root, "PluginAPI_" + pluginId); + + // Set manifest + api.manifest = manifest; + + // Set current language (can't use binding in Qt.createQmlObject string) + api.currentLanguage = I18n.langCode; + + // Merge manifest defaults with loaded settings (user settings take priority) + var defaults = (manifest.metadata && manifest.metadata.defaultSettings) || {}; + api.pluginSettings = Object.assign({}, defaults, settings || {}); + api.pluginTranslations = translations || {}; + api.pluginFallbackTranslations = fallbackTranslations || {}; + + // ---------------------------------------- + // Helper function to get nested property by dot notation + var getNestedProperty = function (obj, path) { + var keys = path.split('.'); + var current = obj; + for (var i = 0; i < keys.length; i++) { + if (current === undefined || current === null) { + return undefined; + } + current = current[keys[i]]; + } + return current; + }; + + // ---------------------------------------- + // Bind functions + // ---------------------------------------- + api.saveSettings = function () { + savePluginSettings(pluginId, api.pluginSettings); + + // Replace the entire pluginSettings object to trigger QML property bindings + // Make a shallow copy so bindings detect the change + api.pluginSettings = Object.assign({}, api.pluginSettings); + }; + + // ---------------------------------------- + api.togglePanel = function (screen, buttonItem) { + // Toggle this plugin's panel on the specified screen + // buttonItem: optional, if provided the panel will position near this button + if (!screen) { + Logger.w("PluginAPI", "No screen available for toggling panel"); + return false; + } + return togglePluginPanel(pluginId, screen, buttonItem); + }; + + // ---------------------------------------- + api.openPanel = function (screen, buttonItem) { + // Open this plugin's panel on the specified screen + // buttonItem: optional, if provided the panel will position near this button + if (!screen) { + Logger.w("PluginAPI", "No screen available for opening panel"); + return false; + } + return openPluginPanel(pluginId, screen, buttonItem); + }; + + // ---------------------------------------- + api.closePanel = function (screen) { + // Close this plugin's panel (find which slot it's in and close it) + for (var slotNum = 1; slotNum <= 2; slotNum++) { + var panelName = "pluginPanel" + slotNum; + var panel = PanelService.getPanel(panelName, screen); + if (panel && panel.currentPluginId === pluginId) { + panel.close(); + return true; + } + } + return false; + }; + + // ---------------------------------------- + // Launcher provider methods + // ---------------------------------------- + + // Get the search prefix for this plugin's launcher provider + var getSearchPrefix = function () { + var metadata = LauncherProviderRegistry.getProviderMetadata("plugin:" + pluginId); + var prefix = (metadata && metadata.commandPrefix) ? metadata.commandPrefix : pluginId; + return ">" + prefix + " "; + }; + + api.openLauncher = function (screen) { + // Open the launcher with this plugin's provider active + if (!screen) { + Logger.w("PluginAPI", "No screen available for opening launcher"); + return; + } + PanelService.openLauncherWithSearch(screen, getSearchPrefix()); + }; + + api.closeLauncher = function (screen) { + // Close the launcher + if (!screen) { + Logger.w("PluginAPI", "No screen available for closing launcher"); + return; + } + PanelService.closeLauncher(screen); + }; + + api.toggleLauncher = function (screen) { + // Toggle the launcher with this plugin's provider active + if (!screen) { + Logger.w("PluginAPI", "No screen available for toggling launcher"); + return; + } + var searchPrefix = getSearchPrefix(); + var searchText = PanelService.getLauncherSearchText(screen); + var isInThisMode = searchText.startsWith(searchPrefix); + if (!PanelService.isLauncherOpen(screen)) { + PanelService.openLauncherWithSearch(screen, searchPrefix); + } else if (isInThisMode) { + PanelService.closeLauncher(screen); + } else { + PanelService.setLauncherSearchText(screen, searchPrefix); + } + }; + + // ---------------------------------------- + api.withCurrentScreen = function (callback) { + // Detect which screen the cursor is on and call callback with that screen + if (!root.screenDetector) { + Logger.w("PluginAPI", "Screen detector not available, using primary screen"); + callback(Quickshell.screens[0]); + return; + } + root.screenDetector.withCurrentScreen(callback); + }; + + // ---------------------------------------- + // Translation function + api.tr = function (key, interpolations) { + if (typeof interpolations === 'undefined') { + interpolations = {}; + } + + var translation = getNestedProperty(api.pluginTranslations, key); + + // Fallback to English if not found in current language + if (translation === undefined || translation === null || typeof translation !== 'string') { + translation = getNestedProperty(api.pluginFallbackTranslations, key); + } + + // Return formatted key if translation not found in any language + if (translation === undefined || translation === null) { + return `!!${key}!!`; + } + + // Ensure translation is a string + if (typeof translation !== 'string') { + return `!!${key}!!`; + } + + // Handle interpolations (e.g., "Hello {name}!") + var result = translation; + for (var placeholder in interpolations) { + var regex = new RegExp('\\{' + placeholder + '\\}', 'g'); + result = result.replace(regex, interpolations[placeholder]); + } + + return result; + }; + + // ---------------------------------------- + // Plural translation function + api.trp = function (key, count, interpolations) { + if (typeof interpolations === 'undefined') { + interpolations = {}; + } + + // Use key for singular, key-plural for plural + const realKey = count === 1 ? key : `${key}-plural`; + + // Merge interpolations with count + var finalInterpolations = { + 'count': count + }; + for (var prop in interpolations) { + finalInterpolations[prop] = interpolations[prop]; + } + + // Use tr() to look up the translation + return api.tr(realKey, finalInterpolations); + }; + + // ---------------------------------------- + // Check if translation exists + api.hasTranslation = function (key) { + return getNestedProperty(api.pluginTranslations, key) !== undefined || getNestedProperty(api.pluginFallbackTranslations, key) !== undefined; + }; + + return api; + } + + // Load plugin translations asynchronously + function loadPluginTranslationsAsync(pluginId, manifest, language, callback) { + var pluginDir = PluginRegistry.getPluginDir(pluginId); + var translationFile = pluginDir + "/i18n/" + language + ".json"; + + var readProcess = Qt.createQmlObject(` + import QtQuick + import Quickshell.Io + Process { + command: ["cat", "${translationFile}"] + stdout: StdioCollector {} + } + `, root, "ReadTranslation_" + pluginId + "_" + language); + + readProcess.exited.connect(function (exitCode) { + var translations = {}; + + if (exitCode === 0) { + try { + translations = JSON.parse(readProcess.stdout.text); + Logger.d("PluginService", "Loaded translations for", pluginId, "language:", language); + } catch (e) { + Logger.w("PluginService", "Failed to parse translations for", pluginId, "language:", language); + } + } else { + Logger.d("PluginService", "No translation file for", pluginId, "language:", language); + } + + if (callback) { + callback(translations); + } + + readProcess.destroy(); + }); + + readProcess.running = true; + } + + // Load plugin settings + function loadPluginSettings(pluginId, callback) { + var settingsFile = PluginRegistry.getPluginSettingsFile(pluginId); + + var readProcess = Qt.createQmlObject(` + import QtQuick + import Quickshell.Io + Process { + command: ["cat", "${settingsFile}"] + stdout: StdioCollector {} + } + `, root, "ReadSettings_" + pluginId); + + readProcess.exited.connect(function (exitCode) { + if (exitCode === 0) { + try { + var settings = JSON.parse(readProcess.stdout.text); + callback(settings); + } catch (e) { + Logger.w("PluginService", "Failed to parse settings for", pluginId, "- using defaults"); + callback({}); + } + } else { + // File doesn't exist - use defaults + callback({}); + } + + readProcess.destroy(); + }); + + readProcess.running = true; + } + + // Save plugin settings + function savePluginSettings(pluginId, settings) { + var settingsFile = PluginRegistry.getPluginSettingsFile(pluginId); + var settingsJson = JSON.stringify(settings, null, 2); + + // Use heredoc delimiter pattern to avoid all escaping issues + var delimiter = "PLUGIN_SETTINGS_EOF_" + Math.random().toString(36).substr(2, 9); + var fileEsc = settingsFile.replace(/'/g, "'\\''"); + + // Get parent directory and ensure it exists + var settingsDir = settingsFile.substring(0, settingsFile.lastIndexOf('/')); + var dirEsc = settingsDir.replace(/'/g, "'\\''"); + + // Build the shell command with heredoc (create dir first) + var writeCmd = "mkdir -p '" + dirEsc + "' && cat > '" + fileEsc + "' << '" + delimiter + "'\n" + settingsJson + "\n" + delimiter + "\n"; + + Logger.d("PluginService", "Saving settings to:", settingsFile); + + // Use Quickshell.execDetached to execute the command (use array syntax) + var pid = Quickshell.execDetached(["sh", "-c", writeCmd]); + Logger.d("PluginService", "Write process started, PID:", pid); + } + + // Load manifest from file + function loadManifest(manifestPath, callback) { + var readProcess = Qt.createQmlObject(` + import QtQuick + import Quickshell.Io + Process { + command: ["cat", "${manifestPath}"] + stdout: StdioCollector {} + } + `, root, "ReadManifest_" + Date.now()); + + readProcess.exited.connect(function (exitCode) { + if (exitCode === 0) { + try { + var manifest = JSON.parse(readProcess.stdout.text); + callback(true, manifest); + } catch (e) { + Logger.e("PluginService", "Failed to parse manifest:", e); + callback(false, null); + } + } else { + Logger.e("PluginService", "Failed to read manifest at:", manifestPath); + callback(false, null); + } + + readProcess.destroy(); + }); + + readProcess.running = true; + } + + // Update plugin metadata in available plugins list + function updatePluginInAvailable(pluginId, updates) { + for (var i = 0; i < root.availablePlugins.length; i++) { + if (root.availablePlugins[i].id === pluginId) { + for (var key in updates) { + root.availablePlugins[i][key] = updates[key]; + } + root.availablePluginsUpdated(); + break; + } + } + } + + // Find available plugin by ID + function findAvailablePlugin(compositeKeyOrId) { + var parsed = PluginRegistry.parseCompositeKey(compositeKeyOrId); + var pluginId = parsed.pluginId; + var sourceUrl = PluginRegistry.getPluginSourceUrl(compositeKeyOrId); + + for (var i = 0; i < root.availablePlugins.length; i++) { + if (root.availablePlugins[i].id === pluginId && root.availablePlugins[i].source.url === sourceUrl) { + return root.availablePlugins[i]; + } + } + return null; + } + + // Internal flag to track if we should check for updates after registry fetch + property bool shouldCheckUpdatesAfterFetch: false + + // Flag to track if this is the initial startup update check (for auto-update) + property bool _isStartupCheck: false + + // Check for plugin updates (call this after availablePlugins are loaded) + function checkForUpdates() { + Logger.i("PluginService", "Checking for plugin updates"); + + // If we have available plugins, check immediately regardless of active fetches + if (root.availablePlugins.length > 0) { + Logger.d("PluginService", "Available plugins already loaded, checking now"); + performUpdateCheck(); + return; + } + + // No plugins yet - check if fetch is in progress + if (Object.keys(activeFetches).length > 0) { + Logger.d("PluginService", "Registry fetch in progress, will check after fetch completes"); + shouldCheckUpdatesAfterFetch = true; + return; + } + + // No plugins and no fetches - trigger refresh + Logger.d("PluginService", "No available plugins yet, triggering refresh"); + shouldCheckUpdatesAfterFetch = true; + refreshAvailablePlugins(); + } + + // Perform the actual update check + function performUpdateCheck() { + var updates = {}; + var pendingUpdates = {}; + var installedIds = PluginRegistry.getAllInstalledPluginIds(); + + Logger.d("PluginService", "Checking", installedIds.length, "installed plugins against", root.availablePlugins.length, "available plugins"); + + for (var i = 0; i < installedIds.length; i++) { + var pluginId = installedIds[i]; + var installedManifest = PluginRegistry.getPluginManifest(pluginId); + var availablePlugin = findAvailablePlugin(pluginId); + + if (installedManifest && availablePlugin) { + var currentVersion = installedManifest.version; + var availableVersion = availablePlugin.version; + + Logger.d("PluginService", "Comparing", pluginId + ":", currentVersion, "vs", availableVersion); + + // Compare versions + if (compareVersions(availableVersion, currentVersion) > 0) { + // Check if the available version requires a higher Noctalia version + if (availablePlugin.minNoctaliaVersion) { + var noctaliaVersion = UpdateService.baseVersion; + if (compareVersions(availablePlugin.minNoctaliaVersion, noctaliaVersion) > 0) { + Logger.d("PluginService", "Pending update for", pluginId + ": requires Noctalia v" + availablePlugin.minNoctaliaVersion + " (current: v" + noctaliaVersion + ")"); + pendingUpdates[pluginId] = { + currentVersion: currentVersion, + availableVersion: availableVersion, + minNoctaliaVersion: availablePlugin.minNoctaliaVersion + }; + continue; + } + } + + updates[pluginId] = { + currentVersion: currentVersion, + availableVersion: availableVersion + }; + Logger.i("PluginService", "Update available for", pluginId + ":", currentVersion, "โ†’", availableVersion); + } + } else if (installedManifest && !availablePlugin) { + Logger.d("PluginService", "Plugin", pluginId, "not found in available plugins (might be from disabled source)"); + } + } + + root.pluginUpdates = updates; + root.pluginUpdatesPending = pendingUpdates; + var updateCount = Object.keys(updates).length; + var pendingCount = Object.keys(pendingUpdates).length; + var updatesDescription = Object.keys(updates).map(function (pluginId) { + return pluginId + ": " + updates[pluginId].currentVersion + " โ†’ " + updates[pluginId].availableVersion; + }).join("\n"); + + if (updateCount > 0) { + Logger.i("PluginService", updateCount, "plugin update(s) available"); + + if (Settings.data.plugins.notifyUpdates) { + ToastService.showNotice(I18n.tr("panels.plugins.title"), I18n.trp("panels.plugins.update-available", updateCount) + "\n\n" + updatesDescription, "plugin", 5000, I18n.tr("panels.plugins.open-plugins-tab"), function () { + // Open settings panel to Plugins tab on the screen where the cursor is + if (root.screenDetector) { + root.screenDetector.withCurrentScreen(function (screen) { + var panel = PanelService.getPanel("settingsPanel", screen); + if (panel) { + panel.requestedTab = SettingsPanel.Tab.Plugins; + panel.open(); + } + }); + } else { + // Fallback to primary screen if screen detector is not available + var panel = PanelService.getPanel("settingsPanel", Quickshell.screens[0]); + if (panel) { + panel.requestedTab = SettingsPanel.Tab.Plugins; + panel.open(); + } + } + }); + } + } else if (pendingCount > 0) { + Logger.i("PluginService", pendingCount, "plugin update(s) pending (require newer Noctalia)"); + } else { + Logger.i("PluginService", "All installed plugins are up to date"); + } + + // Auto-update on startup if enabled + if (root._isStartupCheck && Settings.data.plugins.autoUpdate && updateCount > 0) { + Logger.i("PluginService", "Auto-updating", updateCount, "plugin(s)"); + updateAllPlugins(); + } + + root._isStartupCheck = false; + shouldCheckUpdatesAfterFetch = false; + } + + // Update all plugins sequentially + function updateAllPlugins(callback) { + var pluginIds = Object.keys(root.pluginUpdates); + var currentIndex = 0; + + function updateNext() { + if (currentIndex >= pluginIds.length) { + ToastService.showNotice(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.update-all-success")); + if (callback) + callback(); + return; + } + + var pluginId = pluginIds[currentIndex]; + currentIndex++; + + root.updatePlugin(pluginId, function (success, error) { + if (!success) { + Logger.w("PluginService", "Failed to auto-update", pluginId + ":", error); + } + Qt.callLater(updateNext); + }); + } + + updateNext(); + } + + // Simple version comparison (semantic versioning x.y.z) + function compareVersions(a, b) { + var aParts = a.split('.').map(function (x) { + return parseInt(x) || 0; + }); + var bParts = b.split('.').map(function (x) { + return parseInt(x) || 0; + }); + + for (var i = 0; i < 3; i++) { + var aNum = aParts[i] || 0; + var bNum = bParts[i] || 0; + if (aNum > bNum) + return 1; + if (aNum < bNum) + return -1; + } + return 0; + } + + // Update a plugin to the latest version + function updatePlugin(pluginId, callback) { + Logger.i("PluginService", "Updating plugin:", pluginId); + + // Find available plugin metadata + var availablePlugin = findAvailablePlugin(pluginId); + if (!availablePlugin) { + Logger.e("PluginService", "Plugin not found in available plugins:", pluginId); + if (callback) + callback(false, "Plugin not found"); + return; + } + + // Check Noctalia compatibility + if (availablePlugin.minNoctaliaVersion) { + // Simple check: just warn, don't block (UpdateService would have more sophisticated logic) + Logger.d("PluginService", "Plugin requires Noctalia v" + availablePlugin.minNoctaliaVersion); + } + + // Backup entire bar layout (global + screen overrides) + var barBackup = { + left: JSON.parse(JSON.stringify(Settings.data.bar.widgets.left || [])), + center: JSON.parse(JSON.stringify(Settings.data.bar.widgets.center || [])), + right: JSON.parse(JSON.stringify(Settings.data.bar.widgets.right || [])) + }; + var screenOverridesBackup = JSON.parse(JSON.stringify(Settings.data.bar.screenOverrides || [])); + Logger.d("PluginService", "Backed up bar layout (global + screen overrides)"); + + // Backup desktop widget settings (includes this plugin's widgets) + var desktopWidgetsBackup = JSON.parse(JSON.stringify(Settings.data.desktopWidgets.monitorWidgets || [])); + Logger.d("PluginService", "Backed up desktop widget settings"); + + // Close any open panels for this plugin before update + for (var slotNum = 1; slotNum <= 2; slotNum++) { + var panelName = "pluginPanel" + slotNum; + for (var s = 0; s < Quickshell.screens.length; s++) { + var panel = PanelService.getPanel(panelName, Quickshell.screens[s]); + if (panel && panel.currentPluginId === pluginId) { + Logger.d("PluginService", "Closing plugin panel before update"); + panel.close(); + panel.unloadPluginPanel(); + } + } + } + + // Disable plugin (this removes widgets and unloads code) + if (PluginRegistry.isPluginEnabled(pluginId)) { + disablePlugin(pluginId); + } + + // Now install the new version (reuse installPlugin logic, skip collision check since we're updating) + installPlugin(availablePlugin, true, function (success, error) { + if (success) { + Logger.i("PluginService", "Plugin updated successfully:", pluginId); + + // Increment load version to invalidate Qt component cache + PluginRegistry.incrementPluginLoadVersion(pluginId); + + // Re-enable the plugin first, so the new component is registered + // Skip adding to bar since we'll restore the layout from backup + enablePlugin(pluginId, true); + + // Then restore bar layout (so BarWidgetLoaders can find the new component) + Settings.data.bar.widgets.left = barBackup.left; + Settings.data.bar.widgets.center = barBackup.center; + Settings.data.bar.widgets.right = barBackup.right; + Settings.data.bar.screenOverrides = screenOverridesBackup; + Logger.d("PluginService", "Restored bar layout (global + screen overrides)"); + + // Restore desktop widget settings + Settings.data.desktopWidgets.monitorWidgets = desktopWidgetsBackup; + Logger.d("PluginService", "Restored desktop widget settings"); + + // Persist restored layout immediately to prevent race with file watcher reload + // (the earlier disablePlugin write triggers a reload that can overwrite the restore) + Settings.saveImmediate(); + + // Remove from updates list + var updates = Object.assign({}, root.pluginUpdates); + delete updates[pluginId]; + root.pluginUpdates = updates; + + if (callback) + callback(true, null); + } else { + Logger.e("PluginService", "Failed to update plugin:", pluginId, error); + + // Restore bar layout even on failure (global + screen overrides) + Settings.data.bar.widgets.left = barBackup.left; + Settings.data.bar.widgets.center = barBackup.center; + Settings.data.bar.widgets.right = barBackup.right; + Settings.data.bar.screenOverrides = screenOverridesBackup; + + // Restore desktop widget settings even on failure + Settings.data.desktopWidgets.monitorWidgets = desktopWidgetsBackup; + Settings.saveImmediate(); + + if (callback) + callback(false, error); + } + }); + } + + // Get plugin API for a loaded plugin + function getPluginAPI(pluginId) { + return root.loadedPlugins[pluginId]?.api || null; + } + + // Check if plugin is loaded + function isPluginLoaded(pluginId) { + return !!root.loadedPlugins[pluginId]; + } + + // Open a plugin's panel (finds a free slot and loads the panel) + function openPluginPanel(pluginId, screen, buttonItem) { + if (!isPluginLoaded(pluginId)) { + Logger.w("PluginService", "Cannot open panel: plugin not loaded:", pluginId); + return false; + } + + var plugin = root.loadedPlugins[pluginId]; + if (!plugin || !plugin.manifest || !plugin.manifest.entryPoints || !plugin.manifest.entryPoints.panel) { + Logger.w("PluginService", "Plugin does not provide a panel:", pluginId); + return false; + } + + // Try to find the plugin panel slot (pluginPanel1 or pluginPanel2) + // Priority: 1) toggle same plugin, 2) empty slot, 3) closed slot, 4) replace open slot + var closedSlot = null; + + for (var slotNum = 1; slotNum <= 2; slotNum++) { + var panelName = "pluginPanel" + slotNum; + var panel = PanelService.getPanel(panelName, screen); + + if (panel) { + // If this slot is already showing this plugin's panel, toggle it + if (panel.currentPluginId === pluginId) { + panel.toggle(buttonItem); + return true; + } + + // If this slot is empty, use it + if (panel.currentPluginId === "") { + panel.currentPluginId = pluginId; + panel.open(buttonItem); + return true; + } + + // Track first closed slot (panel assigned but not showing) + if (!closedSlot && !panel.isPanelOpen) { + closedSlot = panel; + } + } + } + + // Prefer reusing a closed slot over replacing an open one + if (closedSlot) { + closedSlot.currentPluginId = pluginId; + closedSlot.open(buttonItem); + return true; + } + + // If both slots are occupied and open, use slot 1 (replace existing) + var panel1 = PanelService.getPanel("pluginPanel1", screen); + if (panel1) { + var wasAlreadyOpen = panel1.isPanelOpen; + panel1.unloadPluginPanel(); + panel1.currentPluginId = pluginId; + + // If panel was already open, Component.onCompleted won't fire again + // since panelContent is already loaded. We need to load the plugin manually. + if (wasAlreadyOpen && panel1.contentLoader) { + panel1.loadPluginPanel(pluginId); + } + + panel1.open(buttonItem); + return true; + } + + Logger.e("PluginService", "Failed to find plugin panel slot"); + return false; + } + + // Toggle a plugin's panel - close if open, open if closed + // buttonItem: optional, if provided the panel will position near this button + function togglePluginPanel(pluginId, screen, buttonItem) { + if (!isPluginLoaded(pluginId)) { + Logger.w("PluginService", "Cannot toggle panel: plugin not loaded:", pluginId); + return false; + } + + var plugin = root.loadedPlugins[pluginId]; + if (!plugin || !plugin.manifest || !plugin.manifest.entryPoints || !plugin.manifest.entryPoints.panel) { + Logger.w("PluginService", "Plugin does not provide a panel:", pluginId); + return false; + } + + // Check if this plugin's panel is already open in any slot + for (var slotNum = 1; slotNum <= 2; slotNum++) { + var panelName = "pluginPanel" + slotNum; + var panel = PanelService.getPanel(panelName, screen); + + if (panel && panel.currentPluginId === pluginId) { + // Panel is open for this plugin - toggle it (close) + panel.toggle(buttonItem); + return true; + } + } + + // Panel is not open - open it using the existing logic + return openPluginPanel(pluginId, screen, buttonItem); + } + + // ----- Error tracking functions ----- + + function recordPluginError(pluginId, entryPoint, errorMessage) { + var errors = Object.assign({}, root.pluginErrors); + errors[pluginId] = { + error: errorMessage, + entryPoint: entryPoint, + timestamp: new Date() + }; + root.pluginErrors = errors; + root.pluginLoadError(pluginId, entryPoint, errorMessage); + Logger.e("PluginService", "Plugin load error [" + pluginId + "/" + entryPoint + "]:", errorMessage); + } + + function clearPluginError(pluginId) { + if (pluginId in root.pluginErrors) { + var errors = Object.assign({}, root.pluginErrors); + delete errors[pluginId]; + root.pluginErrors = errors; + } + } + + function getPluginError(pluginId) { + return root.pluginErrors[pluginId] || null; + } + + function hasPluginError(pluginId) { + return pluginId in root.pluginErrors; + } + + // ----- Hot reload functions ----- + + // Set up file watcher for a plugin directory + function setupPluginFileWatcher(pluginId) { + if (!isPluginHotReloadEnabled(pluginId)) { + return; + } + + // Don't create duplicate watchers + if (root.pluginFileWatchers[pluginId]) { + return; + } + + var manifest = PluginRegistry.getPluginManifest(pluginId); + if (!manifest) { + return; + } + + var pluginDir = PluginRegistry.getPluginDir(pluginId); + + // Create a debounce timer for this plugin + var debounceTimer = Qt.createQmlObject(` + import QtQuick + Timer { + property string targetPluginId: "" + property var reloadCallback: null + interval: 500 + repeat: false + onTriggered: { + if (reloadCallback) reloadCallback(targetPluginId); + } + } + `, root, "HotReloadDebounce_" + pluginId); + + // Set properties after creation to pass the callback + debounceTimer.targetPluginId = pluginId; + debounceTimer.reloadCallback = root.reloadPlugin; + + // Watch the manifest file - changes here indicate plugin updates + var manifestWatcher = Qt.createQmlObject(` + import Quickshell.Io + FileView { + path: "${pluginDir}/manifest.json" + watchChanges: true + } + `, root, "ManifestWatcher_" + pluginId); + + var watchers = [manifestWatcher]; + + // Only watch .qml and .js files, also follow symlinks since some of the plugins might have been symlinked in. + var qmlWatcher = Qt.createQmlObject(` + import QtQuick + import Quickshell.Io + + import qs.Commons + + Item { + id: root + signal fileChanged(); + + Process { + command: [ "sh", "-c", "find -L ${pluginDir} -name '*.qml' -o -name '*.js'" ] + running: true + stdout: SplitParser { + splitMarker: "\n" + onRead: line => { + fileWatcher.createObject(root, { path: Qt.resolvedUrl(line) }); + } + } + } + + Component { + id: fileWatcher + FileView { + watchChanges: true + + onFileChanged: { + root.fileChanged(); + } + } + } + + } + `, root, "QmlWatcher_" + pluginId); + watchers.push(qmlWatcher); + + // Connect all watchers to the debounce timer + for (var j = 0; j < watchers.length; j++) { + watchers[j].fileChanged.connect(function () { + debounceTimer.restart(); + }); + } + + // Create a separate debounce timer for translation reloads (lighter weight) + var translationDebounceTimer = Qt.createQmlObject(` + import QtQuick + Timer { + property string targetPluginId: "" + property var reloadCallback: null + interval: 300 + repeat: false + onTriggered: { + if (reloadCallback) reloadCallback(targetPluginId); + } + } + `, root, "TranslationReloadDebounce_" + pluginId); + + translationDebounceTimer.targetPluginId = pluginId; + translationDebounceTimer.reloadCallback = root.reloadPluginTranslations; + + // Watch the current language's translation file + var translationWatcher = createTranslationWatcher(pluginId, pluginDir, I18n.langCode, translationDebounceTimer); + + root.pluginFileWatchers[pluginId] = { + watchers: watchers, + debounceTimer: debounceTimer, + translationWatcher: translationWatcher, + translationDebounceTimer: translationDebounceTimer, + pluginDir: pluginDir + }; + + Logger.d("PluginService", "Set up hot reload watcher for plugin:", pluginId, "(including translations)"); + } + + // Create a translation file watcher for a specific language + function createTranslationWatcher(pluginId, pluginDir, language, debounceTimer) { + var translationFile = pluginDir + "/i18n/" + language + ".json"; + + var watcher = Qt.createQmlObject(` + import Quickshell.Io + FileView { + path: "${translationFile}" + watchChanges: true + } + `, root, "TranslationWatcher_" + pluginId + "_" + language); + + watcher.fileChanged.connect(function () { + debounceTimer.restart(); + }); + + Logger.d("PluginService", "Watching translation file:", translationFile); + return watcher; + } + + // Update translation watchers when language changes + function updateTranslationWatchers() { + for (var pluginId in root.pluginFileWatchers) { + var watcherData = root.pluginFileWatchers[pluginId]; + if (!watcherData || !watcherData.translationDebounceTimer) + continue; + + // Destroy old translation watcher + if (watcherData.translationWatcher) { + watcherData.translationWatcher.destroy(); + } + + // Create new watcher for current language + watcherData.translationWatcher = createTranslationWatcher(pluginId, watcherData.pluginDir, I18n.langCode, watcherData.translationDebounceTimer); + } + Logger.d("PluginService", "Updated translation watchers for language:", I18n.langCode); + } + + // Remove file watcher for a plugin + function removePluginFileWatcher(pluginId) { + var watcherData = root.pluginFileWatchers[pluginId]; + if (!watcherData) { + return; + } + + // Destroy all watchers + if (watcherData.watchers) { + for (var i = 0; i < watcherData.watchers.length; i++) { + if (watcherData.watchers[i]) { + watcherData.watchers[i].destroy(); + } + } + } + + // Destroy debounce timer + if (watcherData.debounceTimer) { + watcherData.debounceTimer.destroy(); + } + + // Destroy translation watcher + if (watcherData.translationWatcher) { + watcherData.translationWatcher.destroy(); + } + + // Destroy translation debounce timer + if (watcherData.translationDebounceTimer) { + watcherData.translationDebounceTimer.destroy(); + } + + delete root.pluginFileWatchers[pluginId]; + Logger.d("PluginService", "Removed hot reload watcher for plugin:", pluginId); + } + + // Reload a plugin (hot reload) + function reloadPlugin(pluginId) { + if (!root.loadedPlugins[pluginId]) { + Logger.w("PluginService", "Cannot reload: plugin not loaded:", pluginId); + return false; + } + + Logger.i("PluginService", "Hot reloading plugin:", pluginId); + + var manifest = PluginRegistry.getPluginManifest(pluginId); + if (!manifest) { + Logger.e("PluginService", "Cannot reload: manifest not found for:", pluginId); + return false; + } + + // Unregister widget instances from the bar + BarService.destroyPluginWidgetInstances(pluginId); + + // Unload the plugin (destroys components and instances) + // Pass true to preserve desktop widget settings during hot reload + unloadPlugin(pluginId, true); + + // Increment load version to invalidate Qt's component cache + PluginRegistry.incrementPluginLoadVersion(pluginId); + + // Use Qt.callLater to ensure destruction is complete before reloading + // This prevents IPC handler conflicts and other timing issues + Qt.callLater(function () { + // Reload the plugin + loadPlugin(pluginId); + + // Re-setup file watcher (it was destroyed during unload) + setupPluginFileWatcher(pluginId); + + // Emit signal + root.pluginReloaded(pluginId); + + // Show toast notification + var pluginName = manifest.name || pluginId; + ToastService.showNotice(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.hot-reloaded", { + "name": pluginName + })); + + Logger.i("PluginService", "Hot reload complete for plugin:", pluginId); + }); + + return true; + } + + // Hot reload only translations for a plugin (lightweight, no component reload) + function reloadPluginTranslations(pluginId) { + var plugin = root.loadedPlugins[pluginId]; + if (!plugin || !plugin.api || !plugin.manifest) { + Logger.w("PluginService", "Cannot reload translations: plugin not loaded:", pluginId); + return false; + } + + Logger.i("PluginService", "Hot reloading translations for plugin:", pluginId); + + loadPluginTranslationsAsync(pluginId, plugin.manifest, I18n.langCode, function (translations) { + plugin.api.pluginTranslations = translations; + + // Also reload English fallback for non-English languages + if (I18n.langCode !== "en") { + loadPluginTranslationsAsync(pluginId, plugin.manifest, "en", function (fallbackTranslations) { + plugin.api.pluginFallbackTranslations = fallbackTranslations; + plugin.api.translationVersion++; + + var pluginName = plugin.manifest.name || pluginId; + ToastService.showNotice(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.translations-reloaded", { + "name": pluginName + })); + Logger.i("PluginService", "Translation hot reload complete for plugin:", pluginId); + }); + } else { + plugin.api.pluginFallbackTranslations = {}; + plugin.api.translationVersion++; + + var pluginName = plugin.manifest.name || pluginId; + ToastService.showNotice(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.translations-reloaded", { + "name": pluginName + })); + Logger.i("PluginService", "Translation hot reload complete for plugin:", pluginId); + } + }); + + return true; + } + + // Check if a certain plugin has hot reload enabled + function isPluginHotReloadEnabled(pluginId) { + return root.pluginHotReloadEnabled.indexOf(pluginId) !== -1; + } + + // Toggle the hot reload state of a certain plugin + function togglePluginHotReload(pluginId) { + const index = root.pluginHotReloadEnabled.indexOf(pluginId); + if (index === -1) { + root.pluginHotReloadEnabled.push(pluginId); + setupPluginFileWatcher(pluginId); + Logger.i("PluginService", "Hot reload enabled for plugin:", pluginId); + } else { + root.pluginHotReloadEnabled.splice(index, 1); + removePluginFileWatcher(pluginId); + Logger.i("PluginService", "Hot reload disabled for plugin:", pluginId); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Noctalia/SupporterService.qml b/arch/.config/quickshell/noctalia-shell/Services/Noctalia/SupporterService.qml new file mode 100644 index 0000000..15605f0 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Noctalia/SupporterService.qml @@ -0,0 +1,169 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.UI + +Singleton { + id: root + + property string supporterDataFile: Settings.cacheDir + "supporters.json" + property int updateFrequency: 60 * 60 // 1 hour in seconds + property bool isFetching: false + property bool isInitialized: false + + readonly property alias data: adapter + + property var supporters: [] + property var cachedAvatars: ({}) // username -> file:// path + property bool avatarsCached: false + + FileView { + id: supporterDataFileView + path: supporterDataFile + printErrors: false + watchChanges: false + + onLoaded: { + if (!root.isInitialized) { + root.isInitialized = true; + loadFromCache(); + } + } + onLoadFailed: function (error) { + if (error.toString().includes("No such file") || error === 2) { + root.isInitialized = true; + fetchFromApi(); + } + } + + JsonAdapter { + id: adapter + property var supporters: [] + property real timestamp: 0 + } + } + + function init() { + Logger.i("Supporter", "Service started"); + } + + function loadFromCache() { + const now = Time.timestamp; + var needsRefetch = false; + + if (!data.timestamp || (now >= data.timestamp + updateFrequency)) { + needsRefetch = true; + Logger.i("Supporter", "Cache expired or missing, scheduling fetch"); + } else { + Logger.i("Supporter", "Cache is fresh, using cached data"); + } + + if (data.supporters && data.supporters.length > 0) { + root.supporters = data.supporters; + Logger.d("Supporter", "Loaded", data.supporters.length, "supporters from cache"); + } + + if (needsRefetch) { + fetchFromApi(); + } + } + + function fetchFromApi() { + if (isFetching) { + Logger.d("Supporter", "Already fetching"); + return; + } + + isFetching = true; + supporterProcess.running = true; + } + + function saveData() { + data.timestamp = Time.timestamp; + Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir]); + + try { + supporterDataFileView.writeAdapter(); + Logger.d("Supporter", "Cache file written successfully"); + } catch (error) { + Logger.e("Supporter", "Failed to write cache file:", error); + } + } + + function getAvatarPath(username) { + return cachedAvatars[username] || ""; + } + + function cacheAvatars() { + if (supporters.length === 0) + return; + + avatarsCached = true; + + for (var i = 0; i < supporters.length; i++) { + var supporter = supporters[i]; + var username = supporter.github_username; + + // Only cache avatars for supporters with GitHub accounts + if (!username) + continue; + + var avatarUrl = "https://github.com/" + username + ".png?size=256"; + + (function (uname, url) { + ImageCacheService.getCircularAvatar(url, "supporter_" + uname, function (cachedPath, success) { + if (success) { + cachedAvatars[uname] = "file://" + cachedPath; + cachedAvatarsChanged(); + } + }); + })(username, avatarUrl); + } + } + + onSupportersChanged: { + if (supporters.length > 0 && !avatarsCached && ImageCacheService.initialized) { + Qt.callLater(cacheAvatars); + } + } + + Connections { + target: ImageCacheService + function onInitializedChanged() { + if (ImageCacheService.initialized && supporters.length > 0 && !avatarsCached) { + Qt.callLater(cacheAvatars); + } + } + } + + Process { + id: supporterProcess + + command: ["curl", "-s", "https://api.noctalia.dev/supporters"] + + stdout: StdioCollector { + onStreamFinished: { + try { + const response = text; + if (response && response.trim()) { + const parsed = JSON.parse(response); + if (Array.isArray(parsed)) { + root.data.supporters = parsed; + root.supporters = parsed; + root.saveData(); + Logger.d("Supporter", "Fetched", parsed.length, "supporters"); + } else if (parsed.message) { + Logger.w("Supporter", "API error:", parsed.message); + } + } + } catch (e) { + Logger.e("Supporter", "Failed to parse response:", e); + } + root.isFetching = false; + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Noctalia/TelemetryService.qml b/arch/.config/quickshell/noctalia-shell/Services/Noctalia/TelemetryService.qml new file mode 100644 index 0000000..ed303bf --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Noctalia/TelemetryService.qml @@ -0,0 +1,156 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.Compositor +import qs.Services.Noctalia +import qs.Services.System + +Singleton { + id: root + + property bool initialized: false + property bool isSending: false + property int totalRamGb: 0 + property string instanceId: "" + + readonly property string telemetryEndpoint: Quickshell.env("NOCTALIA_TELEMETRY_ENDPOINT") || "https://api.noctalia.dev/ping" + + function init() { + if (initialized) + return; + + initialized = true; + + if (!Settings.data.general.telemetryEnabled) { + Logger.d("Telemetry", "Telemetry disabled by user"); + return; + } + + // Get or generate instance ID from ShellState + instanceId = ShellState.getTelemetryInstanceId(); + if (!instanceId) { + instanceId = generateRandomId(); + ShellState.setTelemetryInstanceId(instanceId); + Logger.d("Telemetry", "Generated new random instance ID"); + } else { + Logger.d("Telemetry", "Using stored instance ID"); + } + + // Read RAM info, then send ping + memInfoProcess.running = true; + } + + function generateRandomId() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = Math.random() * 16 | 0; + const v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + } + + function getInstanceId() { + return instanceId; + } + + Process { + id: memInfoProcess + command: ["sh", "-c", "grep MemTotal /proc/meminfo | awk '{print int($2/1048576)}'"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + const ramGb = parseInt(text.trim()) || 0; + root.totalRamGb = ramGb; + root.sendPing(); + } + } + + onExited: function (exitCode) { + if (exitCode !== 0) { + // Still send ping even if RAM detection fails + root.sendPing(); + } + } + } + + function sendPing() { + if (isSending) + return; + + isSending = true; + + const payload = { + instanceId: instanceId, + version: UpdateService.currentVersion, + compositor: getCompositorType(), + os: HostService.osPretty || "Unknown", + ramGb: totalRamGb, + monitors: getMonitorInfo(), + ui: { + scaleRatio: Settings.data.general.scaleRatio, + fontDefaultScale: Settings.data.ui.fontDefaultScale, + fontFixedScale: Settings.data.ui.fontFixedScale + } + }; + + Logger.d("Telemetry", "Sending anonymous ping:", JSON.stringify(payload)); + + const request = new XMLHttpRequest(); + request.onreadystatechange = function () { + if (request.readyState === XMLHttpRequest.DONE) { + if (request.status >= 200 && request.status < 300) { + Logger.d("Telemetry", "Ping sent successfully"); + } else { + Logger.d("Telemetry", "Ping failed with status:", request.status); + } + isSending = false; + } + }; + + request.open("POST", telemetryEndpoint); + request.setRequestHeader("Content-Type", "application/json"); + request.send(JSON.stringify(payload)); + } + + function getMonitorInfo() { + const monitors = []; + const screens = Quickshell.screens || []; + const scales = CompositorService.displayScales || {}; + + for (let i = 0; i < screens.length; i++) { + const screen = screens[i]; + const name = screen.name || "Unknown"; + const scaleData = scales[name]; + // Extract just the numeric scale value + const scaleValue = (typeof scaleData === "object" && scaleData !== null) ? (scaleData.scale || 1.0) : (scaleData || 1.0); + monitors.push({ + width: screen.width || 0, + height: screen.height || 0, + scale: scaleValue + }); + } + + return monitors; + } + + function getCompositorType() { + if (CompositorService.isHyprland) + return "Hyprland"; + if (CompositorService.isNiri) + return "Niri"; + if (CompositorService.isScroll) + return "Scroll"; + if (CompositorService.isSway) + return "Sway"; + if (CompositorService.isMango) + return "MangoWC"; + if (CompositorService.isLabwc) + return "LabWC"; + if (CompositorService.isExtWorkspace) + return "ExtWorkspace"; + return "Unknown"; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Noctalia/UpdateService.qml b/arch/.config/quickshell/noctalia-shell/Services/Noctalia/UpdateService.qml new file mode 100644 index 0000000..c0c194e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Noctalia/UpdateService.qml @@ -0,0 +1,435 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.Noctalia +import qs.Services.UI + +Singleton { + id: root + + // Version properties + readonly property string baseVersion: "4.7.5" + readonly property bool isDevelopment: false + readonly property string developmentSuffix: "-git" + readonly property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + developmentSuffix}` + + // Telemetry was introduced in this version - users upgrading from earlier need to see the wizard + readonly property string telemetryIntroVersion: "4.0.2" + + // URLs + readonly property string discordUrl: "https://discord.noctalia.dev" + readonly property string feedbackUrl: Quickshell.env("NOCTALIA_CHANGELOG_FEEDBACK_URL") || "" + readonly property string upgradeLogBaseUrl: Quickshell.env("NOCTALIA_UPGRADELOG_URL") || "https://api.noctalia.dev/upgradelog" + + // Changelog properties + property bool initialized: false + property bool changelogPending: false + property string changelogFromVersion: "" + property string changelogToVersion: "" + property string previousVersion: "" + property string changelogCurrentVersion: "" + property string releaseContent: "" + property string lastShownVersion: "" + property bool popupScheduled: false + property string fetchError: "" + property string changelogLastSeenVersion: "" + property bool changelogStateLoaded: false + property bool pendingShowRequest: false + property bool pendingTelemetryWizardCheck: false + + // Fix for FileView race condition + property bool saveInProgress: false + property bool pendingSave: false + property int saveDebounceTimer: 0 + + Connections { + target: PanelService + function onPopupMenuWindowRegistered(screen) { + if (popupScheduled) { + if (!viewChangelogTargetScreen || viewChangelogTargetScreen.name === screen.name) { + openWhenReady(); + } + } + } + } + + signal popupQueued(string fromVersion, string toVersion) + signal telemetryWizardNeeded + + function init() { + if (initialized) + return; + + initialized = true; + Logger.i("UpdateService", "Version:", root.currentVersion); + + // Load changelog state from ShellState + Qt.callLater(() => { + if (typeof ShellState !== 'undefined' && ShellState.isLoaded) { + loadChangelogState(); + } + }); + } + + Connections { + target: typeof ShellState !== 'undefined' ? ShellState : null + function onIsLoadedChanged() { + if (ShellState.isLoaded) { + loadChangelogState(); + } + } + } + + // Debounce timer to prevent rapid successive saves + Timer { + id: saveDebouncer + interval: 300 + repeat: false + onTriggered: executeSave() + } + + function handleChangelogRequest() { + const fromVersion = changelogFromVersion || ""; + const toVersion = changelogToVersion || ""; + + if (Settings.shouldOpenSetupWizard) { + // If you'll see the setup wizard then you don't need to see the changelog + markChangelogSeen(toVersion); + return; + } + + if (!toVersion) + return; + + if (popupScheduled && changelogCurrentVersion === toVersion) + return; + + if (!popupScheduled && lastShownVersion === toVersion) + return; + + previousVersion = fromVersion; + changelogCurrentVersion = toVersion; + + // Fetch the upgrade log from the server + fetchUpgradeLog(fromVersion, toVersion); + + popupScheduled = true; + root.popupQueued(previousVersion, changelogCurrentVersion); + + clearChangelogRequest(); + } + + function fetchUpgradeLog(fromVersion, toVersion) { + // Normalize and ensure "v" prefix for consistent URL format + let from = ensureVersionPrefix(fromVersion || changelogLastSeenVersion || "3.0.0"); + let to = ensureVersionPrefix(toVersion); + + // Strip -git suffix + from = from.replace(root.developmentSuffix, ""); + to = to.replace(root.developmentSuffix, ""); + + // 'from' always needs to be before 'to' (use semantic comparison) + if (compareVersions(from, to) >= 0) { + from = "v3.0.0"; + } + + const url = `${upgradeLogBaseUrl}/${from}/${to}`; + Logger.i("UpdateService", "Fetching upgrade log:", url); + const request = new XMLHttpRequest(); + request.onreadystatechange = function () { + if (request.readyState === XMLHttpRequest.DONE) { + Logger.d("UpdateService", "Request completed with status:", request.status); + Logger.d("UpdateService", "Response text length:", request.responseText ? request.responseText.length : 0); + + if (request.status >= 200 && request.status < 300) { + releaseContent = request.responseText || ""; + Logger.d("UpdateService", "Successfully fetched upgrade log"); + fetchError = ""; + openWhenReady(); + } else { + Logger.w("UpdateService", "Failed to fetch upgrade log, status:", request.status); + releaseContent = ""; + + if (request.status === 404) { + // Changelog not available for this version range - skip silently + Logger.w("UpdateService", "Changelog not found, skipping display"); + fetchError = ""; + popupScheduled = false; + markChangelogSeen(toVersion); + } else { + // Network error or server issue - show error to user + fetchError = I18n.tr("changelog.error.fetch-failed"); + openWhenReady(); + } + } + } + }; + request.open("GET", url); + request.send(); + } + + function normalizeVersion(version) { + if (!version) + return ""; + return version.startsWith("v") ? version.substring(1) : version; + } + + function ensureVersionPrefix(version) { + if (!version) + return ""; + return version.startsWith("v") ? version : "v" + version; + } + + function parseVersionParts(version) { + const clean = normalizeVersion(version); + if (!clean) + return []; + return clean.split(/[^0-9]+/).filter(part => part.length > 0).map(part => parseInt(part)); + } + + function compareVersions(a, b) { + if (a === b) + return 0; + const partsA = parseVersionParts(a); + const partsB = parseVersionParts(b); + const length = Math.max(partsA.length, partsB.length); + for (var i = 0; i < length; i++) { + const valA = partsA[i] || 0; + const valB = partsB[i] || 0; + if (valA > valB) + return 1; + if (valA < valB) + return -1; + } + return 0; + } + + // Check if user is upgrading from a version before telemetry was introduced + function shouldShowTelemetryWizard() { + if (!changelogStateLoaded) + return false; + if (Settings.isFreshInstall) + return false; + if (Settings.shouldOpenSetupWizard) + return false; + + // No previous version recorded but settings exist - assume upgrading from old version + // (e.g., user deleted shell-state.json but has existing settings) + if (!changelogLastSeenVersion || changelogLastSeenVersion === "") + return true; + + // Check if last seen version is before telemetry introduction + return compareVersions(changelogLastSeenVersion, telemetryIntroVersion) < 0; + } + + // Called by shell.qml to check for telemetry wizard after init + // If state isn't loaded yet, sets a pending flag and emits telemetryWizardNeeded later + function checkTelemetryWizardOrChangelog() { + Logger.d("UpdateService", "checkTelemetryWizardOrChangelog called, stateLoaded:", changelogStateLoaded); + if (!changelogStateLoaded) { + // State not loaded yet, set pending flags + Logger.d("UpdateService", "State not loaded yet, setting pending flags"); + pendingTelemetryWizardCheck = true; + pendingShowRequest = true; + return; + } + + // State is already loaded, check immediately + const needsTelemetryWizard = shouldShowTelemetryWizard(); + Logger.d("UpdateService", "shouldShowTelemetryWizard:", needsTelemetryWizard, "lastSeenVersion:", changelogLastSeenVersion); + if (needsTelemetryWizard) { + Logger.i("UpdateService", "Emitting telemetryWizardNeeded signal"); + root.telemetryWizardNeeded(); + } else { + showLatestChangelog(); + } + } + + function openWhenReady() { + if (!popupScheduled) + return; + + if (!Quickshell.screens || Quickshell.screens.length === 0) { + return; + } + + let targetScreen = viewChangelogTargetScreen; + + if (targetScreen) { + // Explicit screen requested - validate it + if (!PanelService.canShowPanelsOnScreen(targetScreen)) { + Logger.w("UpdateService", "Changelog cannot be shown on screen without bar:", targetScreen.name); + popupScheduled = false; + viewChangelogTargetScreen = null; + return; + } + } else { + // No explicit screen - find one that can show panels + targetScreen = PanelService.findScreenForPanels(); + if (!targetScreen) { + Logger.w("UpdateService", "No screen available to show changelog"); + popupScheduled = false; + return; + } + } + + const panel = PanelService.getPanel("changelogPanel", targetScreen); + if (!panel) { + // Panel not found yet. Wait for popupMenuWindowRegistered signal. + // This avoids the memory leak (#1306). + Logger.d("UpdateService", "Waiting for changelogPanel on screen:", targetScreen.name); + return; + } + + panel.open(); + popupScheduled = false; + lastShownVersion = changelogCurrentVersion; + viewChangelogTargetScreen = null; + } + + function openDiscord() { + if (!discordUrl) + return; + Quickshell.execDetached(["xdg-open", discordUrl]); + } + + function openFeedbackForm() { + if (!feedbackUrl) + return; + Quickshell.execDetached(["xdg-open", feedbackUrl]); + } + + function showLatestChangelog() { + if (!currentVersion) + return; + + if (!changelogStateLoaded) { + pendingShowRequest = true; + return; + } + + // Normalize versions for comparison (strip -git, ensure v prefix) + const lastSeen = ensureVersionPrefix(changelogLastSeenVersion.replace(developmentSuffix, "")); + const target = ensureVersionPrefix(currentVersion.replace(developmentSuffix, "")); + + if (lastSeen === target) + return; + + if (!Settings.data.general.showChangelogOnStartup) { + // user has opted out of seeing changelogs, mark as seen + markChangelogSeen(target); + return; + } + + changelogFromVersion = lastSeen; + changelogToVersion = target; + changelogPending = true; + handleChangelogRequest(); + } + + // Manual changelog viewing (e.g., from Settings > About > View Changelog) + // Shows all changes since v3.0.0, unlike showLatestChangelog() which uses lastSeenVersion + property var viewChangelogTargetScreen: null + + function viewChangelog(screen) { + if (!currentVersion) + return; + + const target = ensureVersionPrefix(currentVersion.replace(developmentSuffix, "")); + const fromVersion = "v3.8.2"; + + previousVersion = fromVersion; + changelogCurrentVersion = target; + viewChangelogTargetScreen = screen || null; + popupScheduled = true; + fetchUpgradeLog(fromVersion, target); + } + + function clearChangelogRequest() { + changelogPending = false; + changelogFromVersion = ""; + changelogToVersion = ""; + } + + function markChangelogSeen(version) { + if (!version) + return; + changelogLastSeenVersion = version; + debouncedSaveChangelogState(); + } + + function loadChangelogState() { + try { + const changelog = ShellState.getChangelogState(); + changelogLastSeenVersion = changelog.lastSeenVersion || ""; + + // Migration is now handled in Settings.qml + Logger.d("UpdateService", "Loaded changelog state from ShellState"); + } catch (error) { + Logger.e("UpdateService", "Failed to load changelog state:", error); + } + changelogStateLoaded = true; + + // Handle pending telemetry wizard check first + if (pendingTelemetryWizardCheck) { + pendingTelemetryWizardCheck = false; + if (shouldShowTelemetryWizard()) { + root.telemetryWizardNeeded(); + } else if (pendingShowRequest) { + pendingShowRequest = false; + Qt.callLater(root.showLatestChangelog); + } + return; + } + + if (pendingShowRequest) { + pendingShowRequest = false; + Qt.callLater(root.showLatestChangelog); + } + } + + function debouncedSaveChangelogState() { + // Queue a save and restart the debounce timer + pendingSave = true; + saveDebouncer.restart(); + } + + function executeSave() { + if (!pendingSave) + return; + + // Prevent concurrent saves + if (saveInProgress) { + // Retry after a short delay + saveDebouncer.start(); + return; + } + + pendingSave = false; + saveInProgress = true; + + try { + ShellState.setChangelogState({ + lastSeenVersion: changelogLastSeenVersion || "" + }); + Logger.d("UpdateService", "Saved changelog state to ShellState"); + saveInProgress = false; + + // Check if another save was queued while we were saving + if (pendingSave) { + Qt.callLater(executeSave); + } + } catch (error) { + Logger.e("UpdateService", "Failed to save changelog state:", error); + saveInProgress = false; + } + } + + function saveChangelogState() { + // Immediate save (backward compatibility) + debouncedSaveChangelogState(); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Power/IdleInhibitorService.qml b/arch/.config/quickshell/noctalia-shell/Services/Power/IdleInhibitorService.qml new file mode 100644 index 0000000..c7904b1 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Power/IdleInhibitorService.qml @@ -0,0 +1,220 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.UI + +Singleton { + id: root + + property bool isInhibited: false + property string reason: I18n.tr("system.user-requested") + property var activeInhibitors: [] + property var timeout: null // in seconds + + // True when the native Wayland IdleInhibitor is handling inhibition + // (set by the IdleInhibitor element in MainScreen via the nativeInhibitor property) + property bool nativeInhibitorAvailable: false + + function init() { + Logger.i("IdleInhibitor", "Service started"); + } + + // Add an inhibitor + function addInhibitor(id, reason = "Application request") { + if (activeInhibitors.includes(id)) { + Logger.w("IdleInhibitor", "Inhibitor already active:", id); + return false; + } + + activeInhibitors.push(id); + updateInhibition(reason); + Logger.d("IdleInhibitor", "Added inhibitor:", id); + return true; + } + + // Remove an inhibitor + function removeInhibitor(id) { + const index = activeInhibitors.indexOf(id); + if (index === -1) { + Logger.w("IdleInhibitor", "Inhibitor not found:", id); + return false; + } + + activeInhibitors.splice(index, 1); + updateInhibition(); + Logger.d("IdleInhibitor", "Removed inhibitor:", id); + return true; + } + + // Update the actual system inhibition + function updateInhibition(newReason = reason) { + const shouldInhibit = activeInhibitors.length > 0; + + if (shouldInhibit === isInhibited) { + return; + // No change needed + } + + if (shouldInhibit) { + startInhibition(newReason); + } else { + stopInhibition(); + } + } + + // Start system inhibition + function startInhibition(newReason) { + reason = newReason; + + if (nativeInhibitorAvailable) { + // Native IdleInhibitor in MainScreen handles it via isInhibited binding + Logger.d("IdleInhibitor", "Native inhibitor active"); + } else { + startSubprocessInhibition(); + } + + isInhibited = true; + Logger.i("IdleInhibitor", "Started inhibition:", reason); + } + + // Stop system inhibition + function stopInhibition() { + if (!isInhibited) + return; + + if (!nativeInhibitorAvailable && inhibitorProcess.running) { + inhibitorProcess.signal(15); // SIGTERM + } + + isInhibited = false; + Logger.i("IdleInhibitor", "Stopped inhibition"); + } + + // Subprocess fallback using systemd-inhibit + function startSubprocessInhibition() { + inhibitorProcess.command = ["systemd-inhibit", "--what=idle", "--why=" + reason, "--mode=block", "sleep", "infinity"]; + inhibitorProcess.running = true; + } + + // Process for maintaining the inhibition (subprocess fallback only) + Process { + id: inhibitorProcess + running: false + + onExited: function (exitCode, exitStatus) { + if (isInhibited) { + Logger.w("IdleInhibitor", "Inhibitor process exited unexpectedly:", exitCode); + isInhibited = false; + } + } + + onStarted: function () { + Logger.d("IdleInhibitor", "Inhibitor process started successfully"); + } + } + + Timer { + id: inhibitorTimeout + repeat: true + interval: 1000 // 1 second + onTriggered: function () { + if (timeout == null) { + inhibitorTimeout.stop(); + return; + } + + timeout -= 1; + if (timeout <= 0) { + removeManualInhibitor(); + return; + } + } + } + + // Manual toggle for user control + function manualToggle() { + // clear any existing timeout + timeout = null; + if (activeInhibitors.includes("manual")) { + removeManualInhibitor(); + return false; + } else { + addManualInhibitor(null); + return true; + } + } + + function changeTimeout(delta) { + if (timeout == null && delta < 0) { + // no inhibitor, ignored + return; + } + + if (timeout == null && delta > 0) { + // enable manual inhibitor and set timeout + addManualInhibitor(timeout + delta); + return; + } + + if (timeout + delta <= 0) { + // disable manual inhibitor + removeManualInhibitor(); + return; + } + + if (timeout + delta > 0) { + // change timeout + addManualInhibitor(timeout + delta); + return; + } + } + + function removeManualInhibitor() { + if (timeout !== null) { + timeout = null; + if (inhibitorTimeout.running) { + inhibitorTimeout.stop(); + } + } + + if (activeInhibitors.includes("manual")) { + removeInhibitor("manual"); + ToastService.showNotice(I18n.tr("tooltips.keep-awake"), I18n.tr("common.disabled"), "keep-awake-off"); + Logger.i("IdleInhibitor", "Manual inhibition disabled"); + } + } + + function addManualInhibitor(timeoutSec) { + if (!activeInhibitors.includes("manual")) { + addInhibitor("manual", "Manually activated by user"); + ToastService.showNotice(I18n.tr("tooltips.keep-awake"), I18n.tr("common.enabled"), "keep-awake-on"); + } + + if (timeoutSec === null && timeout === null) { + Logger.i("IdleInhibitor", "Manual inhibition enabled"); + return; + } else if (timeoutSec !== null && timeout === null) { + timeout = timeoutSec; + inhibitorTimeout.start(); + Logger.i("IdleInhibitor", "Manual inhibition enabled with timeout:", timeoutSec); + return; + } else if (timeoutSec !== null && timeout !== null) { + timeout = timeoutSec; + Logger.i("IdleInhibitor", "Manual inhibition timeout changed to:", timeoutSec); + return; + } else if (timeoutSec === null && timeout !== null) { + timeout = null; + inhibitorTimeout.stop(); + Logger.i("IdleInhibitor", "Manual inhibition timeout cleared"); + return; + } + } + + // Clean up on shutdown + Component.onDestruction: { + stopInhibition(); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Power/IdleService.qml b/arch/.config/quickshell/noctalia-shell/Services/Power/IdleService.qml new file mode 100644 index 0000000..2978157 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Power/IdleService.qml @@ -0,0 +1,397 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Commons +import qs.Services.Compositor +import qs.Services.UI + +/** +* IdleService โ€” native idle detection via ext-idle-notify-v1 Wayland protocol. +* +* Three configurable stages: +* 1. Screen-off (DPMS) โ€” dims / turns off monitors +* 2. Lock screen โ€” activates the session lock +* 3. Suspend โ€” systemctl suspend +* +* Each stage shows a fade-to-black overlay for a configurable grace period +* before executing the action. Any mouse movement cancels the fade. +* +* IdleMonitor instances are created with Qt.createQmlObject() so the shell +* does not crash on compositors that lack the protocol. +* +* Timeouts come from Settings.data.idle (in seconds). 0 = disabled. +*/ +Singleton { + id: root + + // True if ext-idle-notify-v1 is supported by the compositor + readonly property bool nativeIdleMonitorAvailable: _monitorsCreated + + // Live idle time in seconds (updated by the 1s heartbeat monitor) + property int idleSeconds: 0 + + // Fade overlay state โ€” "" means no fade in progress + property string fadePending: "" + readonly property int fadeDuration: Settings.data.idle.fadeDuration + + property bool _monitorsCreated: false + property var _screenOffMonitor: null + property var _lockMonitor: null + property var _suspendMonitor: null + property var _heartbeatMonitor: null + property var _customMonitors: ({}) + property var _queuedStages: [] + property bool _screenOffActive: false + + // Signals for external listeners (plugins, modules) + signal screenOffRequested + signal lockRequested + signal suspendRequested + + // ------------------------------------------------------- + function init() { + Logger.i("IdleService", "Service started"); + _applyTimeouts(); + } + + // Grace period timer โ€” fires when fade completes without cancellation + Timer { + id: graceTimer + interval: root.fadeDuration * 1000 + repeat: false + onTriggered: { + const action = root.fadePending; + root._executeAction(action); + overlayCleanupTimer.start(); + } + } + + Timer { + id: overlayCleanupTimer + interval: 500 + repeat: false + onTriggered: { + root.fadePending = ""; + root._runNextQueuedStage(); + } + } + + // Counts up idleSeconds while the heartbeat monitor reports idle + Timer { + id: idleCounter + interval: 1000 + repeat: true + onTriggered: root.idleSeconds++ + } + + // ------------------------------------------------------- + function cancelFade() { + if (fadePending === "") { + _queuedStages = []; + _restoreMonitors(); + return; + } + Logger.i("IdleService", "Fade cancelled for:", fadePending); + fadePending = ""; + _queuedStages = []; + graceTimer.stop(); + overlayCleanupTimer.stop(); + _restoreMonitors(); + } + + function _restoreMonitors() { + if (!_screenOffActive) + return; + _screenOffActive = false; + Logger.i("IdleService", "Restoring monitors (DPMS on)"); + CompositorService.turnOnMonitors(); + + if (Settings.data.idle.resumeScreenOffCommand) { + Logger.i("IdleService", "Executing screen-off resume command"); + Quickshell.execDetached(["sh", "-c", Settings.data.idle.resumeScreenOffCommand]); + } + } + + function _queueStage(stage) { + if (!_isValidStage(stage)) { + Logger.w("IdleService", "Ignoring unknown queued stage:", stage); + return; + } + if (stage === fadePending) + return; + if (_queuedStages.indexOf(stage) !== -1) + return; + _queuedStages.push(stage); + Logger.d("IdleService", "Queued idle stage while fade is active:", stage); + } + + function _isValidStage(stage) { + return stage === "screenOff" || stage === "lock" || stage === "suspend"; + } + + function _isStageEnabled(stage) { + const idle = Settings.data.idle; + if (stage === "screenOff") + return idle.screenOffTimeout > 0; + if (stage === "lock") + return idle.lockTimeout > 0; + if (stage === "suspend") + return idle.suspendTimeout > 0; + return false; + } + + function _runNextQueuedStage() { + if (fadePending !== "") + return; + if (idleSeconds <= 0) { + _queuedStages = []; + return; + } + + while (_queuedStages.length > 0) { + const nextStage = _queuedStages.shift(); + if (!_isValidStage(nextStage)) { + Logger.w("IdleService", "Dropping queued unknown stage:", nextStage); + continue; + } + if (!_isStageEnabled(nextStage)) { + Logger.d("IdleService", "Dropping queued disabled stage:", nextStage); + continue; + } + + Logger.i("IdleService", "Running queued idle stage:", nextStage); + _onIdle(nextStage); + return; + } + } + + function _onIdle(stage) { + if (!_isValidStage(stage)) { + Logger.w("IdleService", "Idle fired with unknown stage:", stage); + return; + } + if (!_isStageEnabled(stage)) { + Logger.d("IdleService", "Ignoring idle stage because it is disabled:", stage); + return; + } + + if (fadePending !== "") { + _queueStage(stage); + return; + } + Logger.i("IdleService", "Idle fired:", stage); + fadePending = stage; + graceTimer.restart(); + } + + function _executeAction(stage) { + Logger.i("IdleService", "Executing action:", stage); + if (stage === "screenOff") { + if (Settings.data.idle.screenOffCommand) + Quickshell.execDetached(["sh", "-c", Settings.data.idle.screenOffCommand]); + CompositorService.turnOffMonitors(); + root._screenOffActive = true; + root.screenOffRequested(); + } else if (stage === "lock") { + if (Settings.data.idle.lockCommand) + Quickshell.execDetached(["sh", "-c", Settings.data.idle.lockCommand]); + if (PanelService.lockScreen && !PanelService.lockScreen.active) { + PanelService.lockScreen.active = true; + } + root.lockRequested(); + } else if (stage === "suspend") { + if (Settings.data.idle.suspendCommand) + Quickshell.execDetached(["sh", "-c", Settings.data.idle.suspendCommand]); + if (Settings.data.general.lockOnSuspend) { + CompositorService.lockAndSuspend(); + } else { + CompositorService.suspend(); + } + root.suspendRequested(); + } else { + Logger.w("IdleService", "Unknown idle stage action:", stage); + } + } + + // ------------------------------------------------------- + // Re-apply when settings change + Connections { + target: Settings + function onSettingsLoaded() { + root._applyTimeouts(); + } + } + + Connections { + target: Settings.data.idle + function onScreenOffTimeoutChanged() { + root._applyTimeouts(); + } + function onLockTimeoutChanged() { + root._applyTimeouts(); + } + function onSuspendTimeoutChanged() { + root._applyTimeouts(); + } + function onEnabledChanged() { + root._applyTimeouts(); + } + function onCustomCommandsChanged() { + root._applyCustomMonitors(); + } + } + + function _applyTimeouts() { + const idle = Settings.data.idle; + const globalEnabled = idle.enabled; + + _setMonitor("screenOff", globalEnabled ? idle.screenOffTimeout : 0); + _setMonitor("lock", globalEnabled ? idle.lockTimeout : 0); + _setMonitor("suspend", globalEnabled ? idle.suspendTimeout : 0); + _ensureHeartbeat(); + _applyCustomMonitors(); + } + + function _applyCustomMonitors() { + // Destroy all existing custom monitors + for (var key in _customMonitors) { + if (_customMonitors[key]) { + _customMonitors[key].destroy(); + } + } + root._customMonitors = {}; + + const idle = Settings.data.idle; + if (!idle.enabled) + return; + + var entries = []; + try { + entries = JSON.parse(idle.customCommands); + } catch (e) { + Logger.w("IdleService", "Failed to parse customCommands:", e); + return; + } + + var newMonitors = {}; + for (var i = 0; i < entries.length; i++) { + const entry = entries[i]; + const timeoutSec = parseInt(entry.timeout); + const cmd = entry.command; + const resumeCmd = entry.resumeCommand || ""; + if (!cmd && !resumeCmd || timeoutSec <= 0) + continue; + try { + const qml = ` + import Quickshell.Wayland + IdleMonitor { timeout: ${timeoutSec} } + `; + + const monitor = Qt.createQmlObject(qml, root, "IdleMonitor_custom_" + i); + const capturedCmd = cmd; + const capturedResumeCmd = resumeCmd; + monitor.isIdleChanged.connect(function () { + if (monitor.isIdle) { + if (capturedCmd) + root._executeCustomCommand(capturedCmd); + } else { + if (capturedResumeCmd) + root._executeCustomCommand(capturedResumeCmd); + } + }); + newMonitors[i] = monitor; + root._monitorsCreated = true; + Logger.i("IdleService", "Custom monitor " + i + " created, timeout", timeoutSec, "s"); + } catch (e) { + Logger.w("IdleService", "Failed to create custom monitor " + i + ":", e); + } + } + root._customMonitors = newMonitors; + } + + function _executeCustomCommand(cmd) { + Logger.i("IdleService", "Executing custom command:", cmd); + Quickshell.execDetached(["sh", "-c", cmd]); + } + + function _setMonitor(stage, timeoutSec) { + const propName = "_" + stage + "Monitor"; + const existing = root[propName]; + + if (timeoutSec <= 0) { + if (existing) { + existing.destroy(); + root[propName] = null; + Logger.d("IdleService", stage + " monitor disabled"); + } + return; + } + + if (existing) { + if (existing.timeout === timeoutSec) + return; + // ext-idle-notify-v1 has no update-timeout request โ€” must recreate + existing.destroy(); + root[propName] = null; + Logger.d("IdleService", stage + " monitor timeout changed to", timeoutSec, "s, recreating"); + } + + try { + const qml = ` + import Quickshell.Wayland + IdleMonitor { timeout: ${timeoutSec} } + `; + + const monitor = Qt.createQmlObject(qml, root, "IdleMonitor_" + stage); + monitor.isIdleChanged.connect(function () { + if (monitor.isIdle) + root._onIdle(stage); + else + root.cancelFade(); + }); + root[propName] = monitor; + root._monitorsCreated = true; + Logger.i("IdleService", stage + " monitor created, timeout", timeoutSec, "s"); + } catch (e) { + Logger.w("IdleService", "IdleMonitor not available (compositor lacks ext-idle-notify-v1):", e); + root._monitorsCreated = false; + } + } + + function _ensureHeartbeat() { + if (_heartbeatMonitor) + return; + try { + const qml = ` + import Quickshell.Wayland + IdleMonitor { timeout: 1 } + `; + + const monitor = Qt.createQmlObject(qml, root, "IdleMonitor_heartbeat"); + monitor.isIdleChanged.connect(function () { + if (monitor.isIdle) { + root.idleSeconds = 1; + idleCounter.start(); + } else { + idleCounter.stop(); + root.idleSeconds = 0; + if (root.fadePending === "lock" && Settings.data.idle.resumeLockCommand) { + Logger.i("IdleService", "Executing lock resume command"); + Quickshell.execDetached(["sh", "-c", Settings.data.idle.resumeLockCommand]); + } else if (root.fadePending === "suspend" && Settings.data.idle.resumeSuspendCommand) { + Logger.i("IdleService", "Executing suspend resume command"); + Quickshell.execDetached(["sh", "-c", Settings.data.idle.resumeSuspendCommand]); + } + root.cancelFade(); + overlayCleanupTimer.stop(); + } + }); + _heartbeatMonitor = monitor; + root._monitorsCreated = true; + Logger.d("IdleService", "Heartbeat monitor created"); + } catch (e) { + Logger.w("IdleService", "Heartbeat monitor failed:", e); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Power/PowerProfileService.qml b/arch/.config/quickshell/noctalia-shell/Services/Power/PowerProfileService.qml new file mode 100644 index 0000000..5fed8f2 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Power/PowerProfileService.qml @@ -0,0 +1,132 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Services.UPower +import qs.Commons +import qs.Services.UI + +Singleton { + id: root + + readonly property var powerProfiles: PowerProfiles + readonly property bool available: powerProfiles && powerProfiles.hasPerformanceProfile + property int profile: powerProfiles ? powerProfiles.profile : PowerProfile.Balanced + + // Not a power profile but a volatile property to quickly disable shadows, animations, etc.. + property bool noctaliaPerformanceMode: false + + function getName(p) { + if (!available) + return "Unknown"; + + const prof = (p !== undefined) ? p : profile; + + switch (prof) { + case PowerProfile.Performance: + return "Performance"; + case PowerProfile.Balanced: + return "Balanced"; + case PowerProfile.PowerSaver: + return "Power saver"; + default: + return "Unknown"; + } + } + + function getIcon(p) { + if (!available) + return "balanced"; + + const prof = (p !== undefined) ? p : profile; + + switch (prof) { + case PowerProfile.Performance: + return "performance"; + case PowerProfile.Balanced: + return "balanced"; + case PowerProfile.PowerSaver: + return "powersaver"; + default: + return "balanced"; + } + } + + function init() { + Logger.d("PowerProfileService", "Service started"); + } + + function setProfile(p) { + if (!available) + return; + try { + powerProfiles.profile = p; + } catch (e) { + Logger.e("PowerProfileService", "Failed to set profile:", e); + } + } + + function cycleProfile() { + if (!available) + return; + const current = powerProfiles.profile; + if (current === PowerProfile.Performance) + setProfile(PowerProfile.PowerSaver); + else if (current === PowerProfile.Balanced) + setProfile(PowerProfile.Performance); + else if (current === PowerProfile.PowerSaver) + setProfile(PowerProfile.Balanced); + } + + function cycleProfileReverse() { + if (!available) + return; + const current = powerProfiles.profile; + if (current === PowerProfile.Performance) + setProfile(PowerProfile.Balanced); + else if (current === PowerProfile.Balanced) + setProfile(PowerProfile.PowerSaver); + else if (current === PowerProfile.PowerSaver) + setProfile(PowerProfile.Performance); + } + + function isDefault() { + if (!available) + return true; + return (profile === PowerProfile.Balanced); + } + + Connections { + target: powerProfiles + function onProfileChanged() { + root.profile = powerProfiles.profile; + // Only show toast if we have a valid profile name (not "Unknown") + const profileName = root.getName(); + if (profileName !== "Unknown") { + ToastService.showNotice(I18n.tr("toast.power-profile.profile-name", { + "profile": profileName + }), I18n.tr("toast.power-profile.changed"), profileName.toLowerCase().replace(" ", "")); + } + } + } + + // Noctalia Performance Mode + // - Turning shadow off + // - Turning animation off + // - Do Not Disturb + function toggleNoctaliaPerformance() { + noctaliaPerformanceMode = !noctaliaPerformanceMode; + } + + function setNoctaliaPerformance(value) { + noctaliaPerformanceMode = value; + } + + onNoctaliaPerformanceModeChanged: { + if (noctaliaPerformanceMode) { + ToastService.showNotice(I18n.tr("toast.noctalia-performance.label"), I18n.tr("toast.noctalia-performance.enabled"), "rocket"); + } else { + ToastService.showNotice(I18n.tr("toast.noctalia-performance.label"), I18n.tr("toast.noctalia-performance.disabled"), "rocket-off"); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/System/FontService.qml b/arch/.config/quickshell/noctalia-shell/Services/System/FontService.qml new file mode 100644 index 0000000..8d3a25b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/System/FontService.qml @@ -0,0 +1,182 @@ +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 bool fontsLoaded: false + property bool isLoading: false + + function init() { + if (fontsLoaded || isLoading) + return; + Logger.i("Font", "Service started"); + loadFontsViaFcList(); + } + + // Load all fonts using fc-list in background - no main thread blocking + function loadFontsViaFcList() { + if (isLoading) + return; + isLoading = true; + allFontsProcess.running = true; + } + + function populateModels(allFontsText, monoFontsText) { + // Parse monospace fonts into a lookup set + // fc-list returns comma-separated family names for fonts with multiple families + var monoLookup = {}; + var monoLines = monoFontsText.split('\n'); + for (var i = 0; i < monoLines.length; i++) { + var line = monoLines[i].trim(); + if (line) { + var monoFamilies = line.split(','); + for (var mi = 0; mi < monoFamilies.length; mi++) { + var monoName = monoFamilies[mi].trim(); + if (monoName) + monoLookup[monoName] = true; + } + } + } + + // Parse all fonts - split comma-separated family names + var allLines = allFontsText.split('\n'); + var fontSet = {}; // Deduplicate font families + + for (var j = 0; j < allLines.length; j++) { + var line = allLines[j].trim(); + if (line) { + var families = line.split(','); + for (var fi = 0; fi < families.length; fi++) { + var fontName = families[fi].trim(); + if (fontName && !fontSet[fontName]) { + fontSet[fontName] = true; + } + } + } + } + + // Sort font names + var sortedFonts = Object.keys(fontSet).sort(function (a, b) { + return a.localeCompare(b); + }); + + // Build arrays for batch insert + var allBatch = []; + var monoBatch = []; + + for (var k = 0; k < sortedFonts.length; k++) { + var name = sortedFonts[k]; + var fontObj = { + "key": name, + "name": name + }; + allBatch.push(fontObj); + + // Check if monospace + if (monoLookup[name] || name.toLowerCase().includes("mono")) { + monoBatch.push(fontObj); + } + } + + // Clear and populate models (single batch operation) + availableFonts.clear(); + monospaceFonts.clear(); + + availableFonts.append({ + "key": Qt.application.font.family, + "name": I18n.tr("panels.indicator.system-default") + }); + monospaceFonts.append({ + "key": "monospace", + "name": I18n.tr("panels.indicator.system-default") + }); + + for (var m = 0; m < allBatch.length; m++) + availableFonts.append(allBatch[m]); + for (var n = 0; n < monoBatch.length; n++) + monospaceFonts.append(monoBatch[n]); + + fontsLoaded = true; + isLoading = false; + Logger.i("Font", "Loaded", availableFonts.count, "fonts,", monospaceFonts.count, "monospace"); + } + + // Temporary storage for process outputs + property string _allFontsOutput: "" + property string _monoFontsOutput: "" + property bool _allFontsDone: false + property bool _monoFontsDone: false + + function checkBothProcessesDone() { + if (_allFontsDone && _monoFontsDone) { + populateModels(_allFontsOutput, _monoFontsOutput); + // Clear temp storage + _allFontsOutput = ""; + _monoFontsOutput = ""; + _allFontsDone = false; + _monoFontsDone = false; + } + } + + // Process to get all font families (runs in background) + Process { + id: allFontsProcess + command: ["fc-list", "--format", "%{family}\\n"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + root._allFontsOutput = this.text; + root._allFontsDone = true; + root.checkBothProcessesDone(); + } + } + + onRunningChanged: { + if (running) { + // Start mono fonts process in parallel + monoFontsProcess.running = true; + } + } + + onExited: function (exitCode, exitStatus) { + if (exitCode !== 0) { + Logger.w("Font", "fc-list failed with exit code", exitCode); + root._allFontsOutput = ""; + root._allFontsDone = true; + root.checkBothProcessesDone(); + } + } + } + + // Process to get monospace font families (runs in background, parallel) + Process { + id: monoFontsProcess + command: ["fc-list", ":mono", "--format", "%{family}\\n"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + root._monoFontsOutput = this.text; + root._monoFontsDone = true; + root.checkBothProcessesDone(); + } + } + + onExited: function (exitCode, exitStatus) { + if (exitCode !== 0) { + root._monoFontsOutput = ""; + root._monoFontsDone = true; + root.checkBothProcessesDone(); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/System/HostService.qml b/arch/.config/quickshell/noctalia-shell/Services/System/HostService.qml new file mode 100644 index 0000000..7758bc6 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/System/HostService.qml @@ -0,0 +1,214 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons + +Singleton { + id: root + + // Public properties + property string osPretty: "" + property string osLogo: "" + property bool isNixOS: false + property bool isReady: false + + // User info + readonly property string username: (Quickshell.env("USER") || "") + readonly property string envRealName: (Quickshell.env("NOCTALIA_REALNAME") || "") + property string realName: "" + + // Machine info + property string hostName: "" + + // Internal: pending logo name for fallback after probe fails + property string pendingLogoName: "" + + readonly property string displayName: { + // Explicit override + if (envRealName && envRealName.length > 0) { + return envRealName; + } + + // Name from getent + if (realName && realName.length > 0) { + return realName; + } + + // Fallback: $USER as-is (login names are case-sensitive on some systems) + if (username && username.length > 0) { + return username; + } + + // Last resort: placeholder + return "User"; + } + + function init() { + Logger.i("HostService", "Service started"); + } + + // Internal helpers + function buildCandidates(name) { + const n = (name || "").trim(); + if (!n) + return []; + + const sizes = ["512x512", "256x256", "128x128", "64x64", "48x48", "32x32", "24x24", "22x22", "16x16"]; + const exts = ["svg", "png"]; + const candidates = []; + + // pixmaps + for (const ext of exts) { + candidates.push(`/usr/share/pixmaps/${n}.${ext}`); + } + + // hicolor scalable and raster sizes + candidates.push(`/usr/share/icons/hicolor/scalable/apps/${n}.svg`); + for (const s of sizes) { + for (const ext of exts) { + candidates.push(`/usr/share/icons/hicolor/${s}/apps/${n}.${ext}`); + } + } + + // NixOS hicolor paths + candidates.push(`/run/current-system/sw/share/icons/hicolor/scalable/apps/${n}.svg`); + for (const s of sizes) { + for (const ext of exts) { + candidates.push(`/run/current-system/sw/share/icons/hicolor/${s}/apps/${n}.${ext}`); + } + } + + // Generic icon themes under /usr/share/icons (common cases) + for (const ext of exts) { + candidates.push(`/usr/share/icons/${n}.${ext}`); + candidates.push(`/usr/share/icons/${n}/${n}.${ext}`); + candidates.push(`/usr/share/icons/${n}/apps/${n}.${ext}`); + } + + return candidates; + } + + function resolveLogo(name) { + const n = (name || "").trim(); + if (!n) + return; + + // First try Quickshell's icon lookup for direct file paths + try { + const path = Quickshell.iconPath(n, ""); + if (path && path !== "" && !path.startsWith("image://")) { + // Got a direct file path - use it + const finalPath = path.startsWith("file://") ? path : "file://" + path; + root.osLogo = finalPath; + Logger.d("HostService", "Found logo via icon theme:", root.osLogo); + return; + } + } catch (e) { + // Ignore and continue to manual probe + } + + // Try manual probing for hicolor/pixmaps paths + // Store name for fallback to image:// URI if probe fails + root.pendingLogoName = n; + const all = buildCandidates(n); + if (all.length === 0) { + // No candidates, try image:// URI directly + root.osLogo = `image://icon/${n}`; + Logger.d("HostService", "Using theme icon URI:", root.osLogo); + return; + } + const script = all.map(p => `if [ -f "${p}" ]; then echo "${p}"; exit 0; fi`).join("; ") + "; exit 1"; + probe.command = ["sh", "-c", script]; + probe.running = true; + } + + // Read /etc/os-release and trigger resolution + FileView { + id: osInfo + path: "/etc/os-release" + onLoaded: { + try { + const lines = text().split("\n"); + const val = k => { + const l = lines.find(x => x.startsWith(k + "=")); + return l ? l.split("=")[1].replace(/"/g, "") : ""; + }; + root.osPretty = val("PRETTY_NAME") || val("NAME"); + Logger.i("HostService", "Detected", root.osPretty); + + const osId = (val("ID") || "").toLowerCase(); + root.isNixOS = osId === "nixos" || (root.osPretty || "").toLowerCase().includes("nixos"); + const logoName = val("LOGO"); + Logger.i("HostService", "Looking for logo icon:", logoName); + if (logoName) { + resolveLogo(logoName); + } + root.isReady = true; + } catch (e) { + Logger.w("HostService", "failed to read os-release", e); + } + } + } + + Process { + id: probe + onExited: code => { + const p = String(stdout.text || "").trim(); + if (code === 0 && p) { + root.osLogo = `file://${p}`; + root.pendingLogoName = ""; + Logger.d("HostService", "Found", root.osLogo); + } else if (root.pendingLogoName) { + // Manual probe failed, fallback to image:// URI (theme icon) + root.osLogo = `image://icon/${root.pendingLogoName}`; + root.pendingLogoName = ""; + Logger.d("HostService", "Using theme icon URI:", root.osLogo); + } else { + root.osLogo = ""; + Logger.w("HostService", "No distro logo found"); + } + } + stdout: StdioCollector {} + stderr: StdioCollector {} + } + + // Resolve GECOS real name once on startup + Process { + id: realNameProcess + command: ["sh", "-c", "getent passwd \"$USER\" | cut -d: -f5 | cut -d, -f1"] + running: true + + stdout: StdioCollector { + onStreamFinished: { + const name = String(text || "").trim(); + if (name.length > 0) { + root.realName = name; + Logger.i("HostService", "resolved real name", name); + } + } + } + stderr: StdioCollector {} + } + + // Resolve hostname from distro-specific locations. + // Prefer /etc/hostname, fallback to Gentoo's /etc/conf.d/hostname. + Process { + id: hostNameProcess + command: ["sh", "-c", + "if [ -r /etc/hostname ]; then sed -n '1p' /etc/hostname; exit 0; fi; if [ -r /etc/conf.d/hostname ]; then v=$(sed -n -E 's/^[[:space:]]*[Hh][Oo][Ss][Tt][Nn][Aa][Mm][Ee][[:space:]]*=[[:space:]]*//p' /etc/conf.d/hostname | sed -n '1p'); v=$(printf '%s' \"$v\" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//; s/^\"//; s/\"$//; s/^\x27//; s/\x27$//'); printf '%s\n' \"$v\"; exit 0; fi; exit 0"] + running: true + + stdout: StdioCollector { + onStreamFinished: { + const name = String(text || "").trim(); + if (name.length > 0) { + root.hostName = name; + Logger.i("HostService", "resolved hostname", name); + } + } + } + stderr: StdioCollector {} + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/System/NotificationRulesService.qml b/arch/.config/quickshell/noctalia-shell/Services/System/NotificationRulesService.qml new file mode 100644 index 0000000..792846e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/System/NotificationRulesService.qml @@ -0,0 +1,86 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons + +Singleton { + id: root + + readonly property string rulesFilePath: Settings.configDir + "notification-rules.json" + + property var rules: [] + + property FileView rulesFileView: FileView { + id: rulesFileView + path: root.rulesFilePath + watchChanges: true + printErrors: false + + adapter: JsonAdapter { + id: rulesAdapter + property var rules: [] + } + + onLoaded: { + try { + const parsed = JSON.parse(rulesFileView.text()); + const raw = Array.isArray(parsed.rules) ? parsed.rules : []; + root.rules = raw.filter(r => (r.pattern || "").trim() !== ""); + } catch (e) { + root.rules = []; + } + } + + onLoadFailed: function (error) { + root.rules = []; + } + } + + function init() { + } + + function save() { + Quickshell.execDetached(["mkdir", "-p", Settings.configDir]); + root.rules = root.rules.filter(r => (r.pattern || "").trim() !== ""); + rulesAdapter.rules = root.rules; + rulesFileView.writeAdapter(); + } + + function evaluate(appName, summary, body) { + const haystack = [appName || "", summary || "", body || ""].join(" "); + for (let i = 0; i < root.rules.length; i++) { + const r = root.rules[i]; + const pattern = (r.pattern || "").trim(); + if (pattern === "") + continue; + let matched = false; + if (pattern.length >= 3 && pattern.startsWith("/") && pattern.endsWith("/")) { + try { + matched = new RegExp(pattern.slice(1, -1)).test(haystack); + } catch (e) { + Logger.w("NotificationRulesService", "Invalid regex:", pattern, e); + } + } else if (pattern.includes("*")) { + try { + const reStr = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*"); + matched = new RegExp(reStr, "i").test(haystack); + } catch (e) { + matched = haystack.toLowerCase().includes(pattern.toLowerCase()); + } + } else { + matched = haystack.toLowerCase().includes(pattern.toLowerCase()); + } + if (matched) { + const a = (r.action || "block").toLowerCase(); + if (a === "mute" || a === "hide") + return a; + if (a === "silence") + return "hide"; + return "block"; + } + } + return null; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/System/NotificationService.qml b/arch/.config/quickshell/noctalia-shell/Services/System/NotificationService.qml new file mode 100644 index 0000000..fff2419 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/System/NotificationService.qml @@ -0,0 +1,1239 @@ +pragma Singleton + +import QtQuick +import QtQuick.Window +import Quickshell +import Quickshell.Io +import Quickshell.Services.Notifications +import Quickshell.Wayland +import "../../Helpers/sha256.js" as Checksum +import qs.Commons +import qs.Services.Compositor +import qs.Services.Media +import qs.Services.Power +import qs.Services.System +import qs.Services.UI + +Singleton { + id: root + + // Configuration + property int maxPopups: 5 + property int maxHistory: 100 + property string historyFile: Quickshell.env("NOCTALIA_NOTIF_HISTORY_FILE") || (Settings.cacheDir + "notifications.json") + + // State + property real lastSeenTs: 0 + // Volatile property that doesn't persist to settings (similar to noctaliaPerformanceMode) + property bool doNotDisturb: false + + // Models + property ListModel popupModel: ListModel {} + property ListModel historyModel: ListModel {} + + // Internal state + property var popupState: ({}) // Maps internal ID to {notification, watcher, cachedActions, metadata} + property var quickshellIdToInternalId: ({}) + + // Rate limiting for notification sounds (minimum 100ms between sounds) + property var lastSoundTime: 0 + readonly property int minSoundInterval: 100 + + // Notification server + property var notificationServerLoader: null + + Component { + id: notificationServerComponent + NotificationServer { + keepOnReload: false + imageSupported: true + actionsSupported: true + onNotification: notification => handleNotification(notification) + } + } + + Component { + id: notificationWatcherComponent + Connections { + property var targetNotification + property var targetDataId + target: targetNotification + + function onSummaryChanged() { + updateNotificationFromObject(targetDataId); + } + function onBodyChanged() { + updateNotificationFromObject(targetDataId); + } + function onAppNameChanged() { + updateNotificationFromObject(targetDataId); + } + function onUrgencyChanged() { + updateNotificationFromObject(targetDataId); + } + function onAppIconChanged() { + updateNotificationFromObject(targetDataId); + } + function onImageChanged() { + updateNotificationFromObject(targetDataId); + } + function onActionsChanged() { + updateNotificationFromObject(targetDataId); + } + } + } + + function updateNotificationServer() { + if (notificationServerLoader) { + notificationServerLoader.destroy(); + notificationServerLoader = null; + } + + if (Settings.isLoaded && Settings.data.notifications.enabled !== false) { + notificationServerLoader = notificationServerComponent.createObject(root); + } + } + + Component.onCompleted: { + if (Settings.isLoaded) { + updateNotificationServer(); + } + + // Load state from ShellState + Qt.callLater(() => { + if (typeof ShellState !== 'undefined' && ShellState.isLoaded) { + loadState(); + } + }); + } + + Connections { + target: typeof ShellState !== 'undefined' ? ShellState : null + function onIsLoadedChanged() { + if (ShellState.isLoaded) { + loadState(); + } + } + } + + Connections { + target: Settings + function onSettingsLoaded() { + updateNotificationServer(); + } + function onSettingsSaved() { + updateNotificationServer(); + } + } + + // Helper function to generate content-based ID for deduplication + function getContentId(summary, body, appName) { + return Checksum.sha256(JSON.stringify({ + "summary": summary || "", + "body": body || "", + "app": appName || "" + })); + } + + // Main handler + function handleNotification(notification) { + const quickshellId = notification.id; + const data = createData(notification); + + const ruleAction = NotificationRulesService.evaluate(data.appName, data.summary, data.body); + if (ruleAction === "block") + return; + if (ruleAction === "hide") { + trySaveToHistory(data, notification); + return; + } + + trySaveToHistory(data, notification); + + if (root.doNotDisturb || PowerProfileService.noctaliaPerformanceMode) + return; + + // Check if this is a replacement notification + const existingInternalId = quickshellIdToInternalId[quickshellId]; + if (existingInternalId && popupState[existingInternalId]) { + updatePopup(existingInternalId, notification, data); + return; + } + + // Check for duplicate content + const duplicateId = findDuplicateNotification(data); + if (duplicateId) { + removePopup(duplicateId); + } + + // Add new notification + addPopup(quickshellId, notification, data); + if (ruleAction !== "mute") + playNotificationSound(data.urgency, data.appName); + } + + function playNotificationSound(urgency, appName) { + if (!SoundService.multimediaAvailable) { + return; + } + if (!Settings.data.notifications?.sounds?.enabled) { + return; + } + if (AudioService.muted) { + return; + } + if (appName) { + const excludedApps = Settings.data.notifications.sounds.excludedApps || ""; + if (excludedApps.trim() !== "") { + const excludedList = excludedApps.toLowerCase().split(',').map(app => app.trim()); + const normalizedName = appName.toLowerCase(); + + if (excludedList.includes(normalizedName)) { + Logger.i("NotificationService", `Skipping sound for excluded app: ${appName}`); + return; + } + } + } + + // Get the sound file for this urgency level + const soundFile = getNotificationSoundFile(urgency); + if (!soundFile || soundFile.trim() === "") { + // No sound file configured for this urgency level + Logger.i("NotificationService", `No sound file configured for urgency ${urgency}`); + return; + } + + // Rate limiting - prevent sound spam + const now = Date.now(); + if (now - lastSoundTime < minSoundInterval) { + return; + } + lastSoundTime = now; + + // Play sound using existing SoundService + const volume = Settings.data.notifications?.sounds?.volume ?? 0.5; + SoundService.playSound(soundFile, { + volume: volume, + fallback: false, + repeat: false + }); + } + + // Get the appropriate sound file path for a given urgency level + function getNotificationSoundFile(urgency) { + const settings = Settings.data.notifications?.sounds; + if (!settings) { + return ""; + } + + // Default sound file path + const defaultSoundFile = Quickshell.shellDir + "/Assets/Sounds/notification-generic.wav"; + + // If separate sounds is disabled, always use normal sound for all urgencies + if (!settings.separateSounds) { + const soundFile = settings.normalSoundFile; + if (soundFile && soundFile.trim() !== "") { + return soundFile; + } + // Return default if no sound file configured + return defaultSoundFile; + } + + // Map urgency levels to sound file keys (when separate sounds is enabled) + let soundKey; + switch (urgency) { + case 0: + soundKey = "lowSoundFile"; + break; + case 1: + soundKey = "normalSoundFile"; + break; + case 2: + soundKey = "criticalSoundFile"; + break; + default: + // Default to normal urgency for invalid values + soundKey = "normalSoundFile"; + break; + } + + const soundFile = settings[soundKey]; + if (soundFile && soundFile.trim() !== "") { + return soundFile; + } + + // Return default sound file if none configured for this urgency level + return defaultSoundFile; + } + + function updatePopup(internalId, notification, data) { + const index = findPopupIndex(internalId); + if (index < 0) + return; + const existing = popupModel.get(index); + const oldTimestamp = existing.timestamp; + const oldProgress = existing.progress; + + // Update properties (keeping original timestamp and progress) + popupModel.setProperty(index, "summary", data.summary); + popupModel.setProperty(index, "summaryMarkdown", data.summaryMarkdown); + popupModel.setProperty(index, "body", data.body); + popupModel.setProperty(index, "bodyMarkdown", data.bodyMarkdown); + popupModel.setProperty(index, "appName", data.appName); + popupModel.setProperty(index, "urgency", data.urgency); + popupModel.setProperty(index, "expireTimeout", data.expireTimeout); + popupModel.setProperty(index, "originalImage", data.originalImage); + popupModel.setProperty(index, "cachedImage", data.cachedImage); + popupModel.setProperty(index, "actionsJson", data.actionsJson); + popupModel.setProperty(index, "timestamp", oldTimestamp); + popupModel.setProperty(index, "progress", oldProgress); + + // Update stored notification object + const notifData = popupState[internalId]; + notifData.notification = notification; + + // Deep copy actions to preserve them even if QML object clears list + var safeActions = []; + if (notification.actions) { + for (var i = 0; i < notification.actions.length; i++) { + safeActions.push({ + "identifier": notification.actions[i].identifier, + "actionObject": notification.actions[i] + }); + } + } + notifData.cachedActions = safeActions; + notifData.metadata.originalId = data.originalId; + + notification.tracked = true; + + function onClosed() { + dismissPopup(internalId); + } + notification.closed.connect(onClosed); + notifData.onClosed = onClosed; + + // Update metadata + notifData.metadata.urgency = data.urgency; + notifData.metadata.duration = calculateDuration(data); + } + + function addPopup(quickshellId, notification, data) { + // Map IDs + quickshellIdToInternalId[quickshellId] = data.id; + + // Create watcher + const watcher = notificationWatcherComponent.createObject(root, { + "targetNotification": notification, + "targetDataId": data.id + }); + + // Deep copy actions + var safeActions = []; + if (notification.actions) { + for (var i = 0; i < notification.actions.length; i++) { + safeActions.push({ + "identifier": notification.actions[i].identifier, + "actionObject": notification.actions[i] + }); + } + } + + // Store notification data + popupState[data.id] = { + "notification": notification, + "watcher": watcher, + "cachedActions": safeActions // Cache actions + , + "metadata": { + "originalId": data.originalId // Store original ID + , + "timestamp": data.timestamp.getTime(), + "duration": calculateDuration(data), + "urgency": data.urgency, + "paused": false, + "pauseTime": 0 + } + }; + + notification.tracked = true; + + function onClosed() { + dismissPopup(data.id); + } + notification.closed.connect(onClosed); + popupState[data.id].onClosed = onClosed; + + // Defer list insertion to prevent re-entrant QML incubation crash. + // Direct insert triggers Repeater.modelUpdated synchronously, which + // incubates delegates whose signal handlers can re-enter the V4 engine + // and crash in QV4::Object::insertMember. + Qt.callLater(() => { + popupModel.insert(0, data); + + // Remove overflow + while (popupModel.count > maxPopups) { + const last = popupModel.get(popupModel.count - 1); + // Overflow only removes from ACTIVE view, but keeps it for history + popupState[last.id]?.notification?.dismiss(); // Visually dismiss + popupModel.remove(popupModel.count - 1); + // DO NOT call cleanupNotification here, we want to keep it for history actions + } + }); + } + + function findDuplicateNotification(data) { + const contentId = getContentId(data.summary, data.body, data.appName); + + for (var i = 0; i < popupModel.count; i++) { + const existing = popupModel.get(i); + const existingContentId = getContentId(existing.summary, existing.body, existing.appName); + if (existingContentId === contentId) { + return existing.id; + } + } + return null; + } + + function calculateDuration(data) { + const durations = [Settings.data.notifications?.lowUrgencyDuration * 1000 || 3000, Settings.data.notifications?.normalUrgencyDuration * 1000 || 8000, Settings.data.notifications?.criticalUrgencyDuration * 1000 || 15000]; + + if (Settings.data.notifications?.respectExpireTimeout) { + if (data.expireTimeout === 0) + return -1; // Never expire + if (data.expireTimeout > 0) + return data.expireTimeout; + } + + return durations[data.urgency]; + } + + function createData(n) { + const time = new Date(); + const id = Checksum.sha256(JSON.stringify({ + "summary": n.summary, + "body": n.body, + "app": n.appName, + "time": time.getTime() + })); + + const image = n.image || getIcon(n.appIcon); + const imageId = generateImageId(n, image); + queueImage(image, n.appName || "", n.summary || "", id); + + return { + "id": id, + "summary": processNotificationText(n.summary || ""), + "summaryMarkdown": processNotificationMarkdown(n.summary || ""), + "body": processNotificationText(n.body || ""), + "bodyMarkdown": processNotificationMarkdown(n.body || ""), + "appName": getAppName(n.appName || n.desktopEntry || ""), + "urgency": n.urgency < 0 || n.urgency > 2 ? 1 : n.urgency, + "expireTimeout": n.expireTimeout, + "timestamp": time, + "progress": 1.0, + "originalImage": image, + "cachedImage": image // Start with original, update when cached + , + "originalId": n.originalId || n.id || 0 // Ensure originalId is passed through + , + "actionsJson": JSON.stringify((n.actions || []).map(a => ({ + "text": (a.text || "").trim() || "Action", + "identifier": a.identifier || "" + }))) + }; + } + + function findPopupIndex(internalId) { + for (var i = 0; i < popupModel.count; i++) { + if (popupModel.get(i).id === internalId) { + return i; + } + } + return -1; + } + + function updateNotificationFromObject(internalId) { + const notifData = popupState[internalId]; + if (!notifData) + return; + const index = findPopupIndex(internalId); + if (index < 0) + return; + const data = createData(notifData.notification); + const existing = popupModel.get(index); + + // Update properties (keeping timestamp and progress) + popupModel.setProperty(index, "summary", data.summary); + popupModel.setProperty(index, "summaryMarkdown", data.summaryMarkdown); + popupModel.setProperty(index, "body", data.body); + popupModel.setProperty(index, "bodyMarkdown", data.bodyMarkdown); + popupModel.setProperty(index, "appName", data.appName); + popupModel.setProperty(index, "urgency", data.urgency); + popupModel.setProperty(index, "expireTimeout", data.expireTimeout); + popupModel.setProperty(index, "originalImage", data.originalImage); + popupModel.setProperty(index, "cachedImage", data.cachedImage); + popupModel.setProperty(index, "actionsJson", data.actionsJson); + + // Update metadata + notifData.metadata.urgency = data.urgency; + notifData.metadata.duration = calculateDuration(data); + } + + function removePopup(id) { + const index = findPopupIndex(id); + if (index >= 0) { + popupModel.remove(index); + } + cleanupNotification(id); + } + + function cleanupNotification(id) { + const notifData = popupState[id]; + if (notifData) { + notifData.watcher?.destroy(); + delete popupState[id]; + } + + // Clean up quickshell ID mapping + for (const qsId in quickshellIdToInternalId) { + if (quickshellIdToInternalId[qsId] === id) { + delete quickshellIdToInternalId[qsId]; + break; + } + } + } + + // Progress updates + Timer { + interval: 50 + repeat: true + running: popupModel.count > 0 + onTriggered: updateAllProgress() + } + + function updateAllProgress() { + const now = Date.now(); + const toRemove = []; + + for (var i = 0; i < popupModel.count; i++) { + const notif = popupModel.get(i); + const notifData = popupState[notif.id]; + if (!notifData) + continue; + const meta = notifData.metadata; + if (meta.duration === -1 || meta.paused) + continue; + const elapsed = now - meta.timestamp; + const progress = Math.max(1.0 - (elapsed / meta.duration), 0.0); + + if (progress <= 0) { + toRemove.push(notif.id); + } else if (Math.abs(notif.progress - progress) > 0.005) { + popupModel.setProperty(i, "progress", progress); + } + } + + if (toRemove.length > 0) { + animateAndRemove(toRemove[0]); + } + } + + // Image handling + function queueImage(path, appName, summary, notificationId) { + if (!path || !notificationId) + return; + + // Cache image:// URIs and temporary file paths (e.g. /tmp/ from Chromium) + const filePath = path.startsWith("file://") ? path.substring(7) : path; + const isImageUri = path.startsWith("image://"); + const isTempFile = (path.startsWith("/") || path.startsWith("file://")) && filePath.startsWith("/tmp/"); + + if (!isImageUri && !isTempFile) + return; + + ImageCacheService.getNotificationIcon(path, appName, summary, function (cachedPath, success) { + if (success && cachedPath) { + updateImagePath(notificationId, "file://" + cachedPath); + } + }); + } + + function updateImagePath(notificationId, path) { + updateModel(popupModel, notificationId, "cachedImage", path); + updateModel(historyModel, notificationId, "cachedImage", path); + saveHistory(); + } + + function updateModel(model, notificationId, prop, value) { + for (var i = 0; i < model.count; i++) { + if (model.get(i).id === notificationId) { + model.setProperty(i, prop, value); + break; + } + } + } + + // History management + function trySaveToHistory(data, notification) { + if (notification.transient) + return; + const s = Settings.data.notifications?.saveToHistory; + if (s) { + let ok = true; + if (data.urgency === 0) + ok = s.low !== false; + else if (data.urgency === 1) + ok = s.normal !== false; + else if (data.urgency === 2) + ok = s.critical !== false; + if (ok) + addToHistory(data); + } else { + addToHistory(data); + } + } + + function addToHistory(data) { + // Defer list insertion to prevent re-entrant QML incubation crash. + // See addPopup for full explanation. + Qt.callLater(() => { + historyModel.insert(0, data); + + while (historyModel.count > maxHistory) { + const old = historyModel.get(historyModel.count - 1); + // Only delete cached images that are in our cache directory + const cachedPath = old.cachedImage ? old.cachedImage.replace(/^file:\/\//, "") : ""; + if (cachedPath && cachedPath.startsWith(ImageCacheService.notificationsDir)) { + Quickshell.execDetached(["rm", "-f", cachedPath]); + } + historyModel.remove(historyModel.count - 1); + } + saveHistory(); + }); + } + + // Persistence - History + FileView { + id: historyFileView + path: historyFile + printErrors: false + onLoaded: loadHistory() + onLoadFailed: error => { + if (error === 2) + writeAdapter(); + } + + JsonAdapter { + id: adapter + property var notifications: [] + } + } + + Timer { + id: saveTimer + interval: 200 + onTriggered: performSaveHistory() + } + + function saveHistory() { + saveTimer.restart(); + } + + function performSaveHistory() { + try { + const items = []; + for (var i = 0; i < historyModel.count; i++) { + const n = historyModel.get(i); + const copy = Object.assign({}, n); + copy.timestamp = n.timestamp.getTime(); + items.push(copy); + } + adapter.notifications = items; + historyFileView.writeAdapter(); + } catch (e) { + Logger.e("Notifications", "Save history failed:", e); + } + } + + function loadHistory() { + try { + historyModel.clear(); + for (const item of adapter.notifications || []) { + const time = new Date(item.timestamp); + + // Use the cached image if it exists and starts with file://, otherwise use originalImage + let cachedImage = item.cachedImage || ""; + if (!cachedImage || (!cachedImage.startsWith("file://") && !cachedImage.startsWith("/"))) { + cachedImage = item.originalImage || ""; + } + + historyModel.append({ + "id": item.id || "", + "summary": item.summary || "", + "summaryMarkdown": processNotificationMarkdown(item.summary || ""), + "body": item.body || "", + "bodyMarkdown": processNotificationMarkdown(item.body || ""), + "appName": item.appName || "", + "urgency": item.urgency < 0 || item.urgency > 2 ? 1 : item.urgency, + "timestamp": time, + "originalImage": item.originalImage || "", + "cachedImage": cachedImage, + "actionsJson": item.actionsJson || "[]", + "originalId": item.originalId || 0 + }); + } + } catch (e) { + Logger.e("Notifications", "Load failed:", e); + } + } + + function loadState() { + try { + const notifState = ShellState.getNotificationsState(); + root.lastSeenTs = notifState.lastSeenTs || 0; + + // Migration is now handled in Settings.qml + Logger.d("Notifications", "Loaded state from ShellState"); + } catch (e) { + Logger.e("Notifications", "Load state failed:", e); + } + } + + function saveState() { + try { + ShellState.setNotificationsState({ + lastSeenTs: root.lastSeenTs + }); + Logger.d("Notifications", "Saved state to ShellState"); + } catch (e) { + Logger.e("Notifications", "Save state failed:", e); + } + } + + function updateLastSeenTs() { + root.lastSeenTs = Time.timestamp * 1000; + saveState(); + } + + // Utility functions + function getAppName(name) { + if (!name || name.trim() === "") + return "Unknown"; + name = name.trim(); + + if (name.includes(".") && (name.startsWith("com.") || name.startsWith("org.") || name.startsWith("io.") || name.startsWith("net."))) { + const parts = name.split("."); + let appPart = parts[parts.length - 1]; + + if (!appPart || appPart === "app" || appPart === "desktop") { + appPart = parts[parts.length - 2] || parts[0]; + } + + if (appPart) + name = appPart; + } + + if (name.includes(".")) { + const parts = name.split("."); + let displayName = parts[parts.length - 1]; + + if (!displayName || /^\d+$/.test(displayName)) { + displayName = parts[parts.length - 2] || parts[0]; + } + + if (displayName) { + displayName = displayName.charAt(0).toUpperCase() + displayName.slice(1); + displayName = displayName.replace(/([a-z])([A-Z])/g, '$1 $2'); + displayName = displayName.replace(/app$/i, '').trim(); + displayName = displayName.replace(/desktop$/i, '').trim(); + displayName = displayName.replace(/flatpak$/i, '').trim(); + + if (!displayName) { + displayName = parts[parts.length - 1].charAt(0).toUpperCase() + parts[parts.length - 1].slice(1); + } + } + + return displayName || name; + } + + let displayName = name.charAt(0).toUpperCase() + name.slice(1); + displayName = displayName.replace(/([a-z])([A-Z])/g, '$1 $2'); + displayName = displayName.replace(/app$/i, '').trim(); + displayName = displayName.replace(/desktop$/i, '').trim(); + + return displayName || name; + } + + function getIcon(icon) { + if (!icon) + return ""; + if (icon.startsWith("/") || icon.startsWith("file://")) + return icon; + return ThemeIcons.iconFromName(icon); + } + + function escapeHtml(text) { + if (!text) + return ""; + return text.replace(/&/g, "&").replace(//g, ">"); + } + + function sanitizeMarkdownUrl(url) { + if (!url) + return ""; + const trimmed = url.trim(); + if (trimmed === "") + return ""; + const lower = trimmed.toLowerCase(); + if (lower.startsWith("http://") || lower.startsWith("https://") || lower.startsWith("mailto:")) { + return encodeURI(trimmed); + } + return ""; + } + + function sanitizeMarkdown(text) { + if (!text) + return ""; + + let input = String(text); + + // Strip images entirely + input = input.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, function (match, alt) { + return alt ? alt : ""; + }); + + // Extract links into placeholders + const links = []; + input = input.replace(/\[([^\]]+)\]\(([^)]+)\)/g, function (match, label, urlAndTitle) { + const urlPart = (urlAndTitle || "").trim().split(/\s+/)[0] || ""; + const safeUrl = sanitizeMarkdownUrl(urlPart); + const safeLabel = escapeHtml(label); + if (!safeUrl) + return safeLabel; + const token = "__MDLINK_" + links.length + "__"; + links.push({ + "label": safeLabel, + "url": safeUrl + }); + return token; + }); + + // Escape any remaining HTML + input = escapeHtml(input); + + // Restore sanitized links + for (let i = 0; i < links.length; i++) { + const token = "__MDLINK_" + i + "__"; + const link = links[i]; + input = input.split(token).join("[" + link.label + "](" + link.url + ")"); + } + + return input; + } + + function processNotificationText(text) { + if (!text) + return ""; + + // Split by tags to process segments separately + const parts = text.split(/(<[^>]+>)/); + let result = ""; + const allowedTags = ["b", "i", "u", "a", "br"]; + + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + if (part.startsWith("<") && part.endsWith(">")) { + const content = part.substring(1, part.length - 1); + const firstWord = content.split(/[\s/]/).filter(s => s.length > 0)[0]?.toLowerCase(); + + if (allowedTags.includes(firstWord)) { + // Preserve valid HTML tag + result += part; + } else { + // Unknown tag: drop tag without leaking attributes + result += ""; + } + } else { + // Normal text: escape everything + result += escapeHtml(part); + } + } + return result; + } + + function processNotificationMarkdown(text) { + return sanitizeMarkdown(text); + } + + function generateImageId(notification, image) { + if (image && image.startsWith("image://")) { + if (image.startsWith("image://qsimage/")) { + const key = (notification.appName || "") + "|" + (notification.summary || ""); + return Checksum.sha256(key); + } + return Checksum.sha256(image); + } + return ""; + } + + function pauseTimeout(id) { + const notifData = popupState[id]; + if (notifData && !notifData.metadata.paused) { + notifData.metadata.paused = true; + notifData.metadata.pauseTime = Date.now(); + } + } + + function resumeTimeout(id) { + const notifData = popupState[id]; + if (notifData && notifData.metadata.paused) { + notifData.metadata.timestamp += Date.now() - notifData.metadata.pauseTime; + notifData.metadata.paused = false; + } + } + + // Dismiss a popup notification (e.g. clicked close, swipe, or overflow). + // Removes from popup list but KEEPS data for history. + function dismissPopup(id) { + const index = findPopupIndex(id); + if (index >= 0) { + popupModel.remove(index); + } + } + + function dismissOldestPopup() { + if (popupModel.count > 0) { + const lastNotif = popupModel.get(popupModel.count - 1); + dismissPopup(lastNotif.id); + } + } + + function dismissAllPopups() { + for (const id in popupState) { + popupState[id].notification?.dismiss(); + popupState[id].watcher?.destroy(); + } + popupModel.clear(); + popupState = {}; + quickshellIdToInternalId = {}; + } + + function invokeActionAndSuppressClose(id, actionId) { + const notifData = popupState[id]; + const notification = notifData?.notification; + const onClosed = notifData?.onClosed; + let restoreClosedHandler = false; + + if (notification && onClosed) { + try { + // A successful action may synchronously close the notification. Disconnect + // our close handler first so the popup is only dismissed by the action path. + notification.closed.disconnect(onClosed); + restoreClosedHandler = true; + } catch (e) {} + } + + const invoked = invokeAction(id, actionId); + + if (!invoked && restoreClosedHandler && notification && onClosed) { + try { + // If invoking the action failed, restore normal close handling for this popup. + notification.closed.connect(onClosed); + } catch (e) {} + } + + return invoked; + } + + function invokeAction(id, actionId) { + let invoked = false; + const notifData = popupState[id]; + + if (notifData && notifData.notification) { + const actionsToUse = (notifData.notification.actions && notifData.notification.actions.length > 0) ? notifData.notification.actions : (notifData.cachedActions || []); + + if (actionsToUse && actionsToUse.length > 0) { + for (const item of actionsToUse) { + const itemId = item.identifier; + const actionObj = item.actionObject ? item.actionObject : item; + + if (itemId === actionId) { + if (actionObj.invoke) { + try { + actionObj.invoke(); + invoked = true; + } catch (e) { + Logger.w("NotificationService", "invoke() failed, trying manual fallback: " + e); + if (manualInvoke(notifData.metadata.originalId, itemId)) { + invoked = true; + } + } + } else { + if (manualInvoke(notifData.metadata.originalId, itemId)) { + invoked = true; + } + } + break; + } + } + } + + if (!invoked && notifData.metadata.originalId) { + Logger.w("NotificationService", "Action objects exhausted, trying manual invoke for id=" + id + " action=" + actionId); + invoked = manualInvoke(notifData.metadata.originalId, actionId); + } + } else if (!notifData) { + Logger.w("NotificationService", "No active notification data for id=" + id + ", searching history for manual invoke"); + for (var i = 0; i < historyModel.count; i++) { + if (historyModel.get(i).id === id) { + const histEntry = historyModel.get(i); + if (histEntry.originalId) { + invoked = manualInvoke(histEntry.originalId, actionId); + } + break; + } + } + } + + if (!invoked) { + Logger.w("NotificationService", "Failed to invoke action '" + actionId + "' for notification " + id); + return false; + } + + // Clear actions after use + updateModel(popupModel, id, "actionsJson", "[]"); + updateModel(historyModel, id, "actionsJson", "[]"); + saveHistory(); + + return true; + } + + function manualInvoke(originalId, actionId) { + if (!originalId) { + return false; + } + + try { + // Construct the signal emission using dbus-send + // dbus-send --session --type=signal /org/freedesktop/Notifications org.freedesktop.Notifications.ActionInvoked uint32:ID string:"KEY" + const args = ["dbus-send", "--session", "--type=signal", "/org/freedesktop/Notifications", "org.freedesktop.Notifications.ActionInvoked", "uint32:" + originalId, "string:" + actionId]; + + Quickshell.execDetached(args); + return true; + } catch (e) { + Logger.e("NotificationService", "Manual invoke failed: " + e); + return false; + } + } + + function focusSenderWindow(appName) { + if (!appName || appName === "" || appName === "Unknown") + return false; + + const normalizedName = appName.toLowerCase().replace(/\s+/g, ""); + + for (var i = 0; i < CompositorService.windows.count; i++) { + const win = CompositorService.windows.get(i); + const winAppId = (win.appId || "").toLowerCase(); + + const segments = winAppId.split("."); + const lastSegment = segments[segments.length - 1] || ""; + + if (winAppId === normalizedName || lastSegment === normalizedName || winAppId.includes(normalizedName) || normalizedName.includes(lastSegment)) { + CompositorService.focusWindow(win); + return true; + } + } + + Logger.d("NotificationService", "No window found for app: " + appName); + return false; + } + + function removeFromHistory(notificationId) { + for (var i = 0; i < historyModel.count; i++) { + const notif = historyModel.get(i); + if (notif.id === notificationId) { + // Only delete cached images that are in our cache directory + const cachedPath = notif.cachedImage ? notif.cachedImage.replace(/^file:\/\//, "") : ""; + if (cachedPath && cachedPath.startsWith(ImageCacheService.notificationsDir)) { + Quickshell.execDetached(["rm", "-f", cachedPath]); + } + historyModel.remove(i); + saveHistory(); + return true; + } + } + return false; + } + + function removeOldestHistory() { + if (historyModel.count > 0) { + const oldest = historyModel.get(historyModel.count - 1); + // Only delete cached images that are in our cache directory + const cachedPath = oldest.cachedImage ? oldest.cachedImage.replace(/^file:\/\//, "") : ""; + if (cachedPath && cachedPath.startsWith(ImageCacheService.notificationsDir)) { + Quickshell.execDetached(["rm", "-f", cachedPath]); + } + historyModel.remove(historyModel.count - 1); + saveHistory(); + return true; + } + return false; + } + + function clearHistory() { + try { + Quickshell.execDetached(["sh", "-c", `rm -rf "${ImageCacheService.notificationsDir}"*`]); + } catch (e) { + Logger.e("Notifications", "Failed to clear cache directory:", e); + } + + historyModel.clear(); + saveHistory(); + } + + function getHistorySnapshot() { + const items = []; + for (var i = 0; i < historyModel.count; i++) { + const entry = historyModel.get(i); + items.push({ + "id": entry.id, + "summary": entry.summary, + "body": entry.body, + "appName": entry.appName, + "urgency": entry.urgency, + "timestamp": entry.timestamp instanceof Date ? entry.timestamp.getTime() : entry.timestamp, + "originalImage": entry.originalImage, + "cachedImage": entry.cachedImage + }); + } + return items; + } + + // Signals + signal animateAndRemove(string notificationId) + + onDoNotDisturbChanged: { + ToastService.showNotice(doNotDisturb ? I18n.tr("toast.do-not-disturb.enabled") : I18n.tr("toast.do-not-disturb.disabled"), doNotDisturb ? I18n.tr("toast.do-not-disturb.enabled-desc") : I18n.tr("toast.do-not-disturb.disabled-desc"), doNotDisturb ? "bell-off" : "bell"); + } + + // Media toast functionality + property string previousMediaTitle: "" + property string previousMediaArtist: "" + property bool previousMediaIsPlaying: false + property bool mediaToastInitialized: false + + Timer { + id: mediaToastInitTimer + interval: 3000 // Wait 3 seconds after startup to avoid initial toast + running: true + onTriggered: { + root.mediaToastInitialized = true; + root.previousMediaTitle = MediaService.trackTitle; + root.previousMediaArtist = MediaService.trackArtist; + root.previousMediaIsPlaying = MediaService.isPlaying; + } + } + + Timer { + id: mediaToastDebounce + interval: 250 // Dynamic interval based on player + onTriggered: { + checkMediaToast(); + } + } + + function checkMediaToast() { + if (!Settings.data.notifications.enableMediaToast || !mediaToastInitialized) + return; + + if (doNotDisturb || PowerProfileService.noctaliaPerformanceMode) + return; + + // Re-evaluate player identity here to handle race conditions where + // the identity wasn't updated yet when the timer started. + const player = (MediaService.playerIdentity || "").toLowerCase(); + const browsers = ["firefox", "chromium", "chrome", "brave", "edge", "opera", "vivaldi", "zen"]; + const isBrowser = browsers.some(b => player.includes(b)); + + // Safety check: If it's a browser, ensure we waited long enough. + // If we started with a short interval (e.g. 250ms because we thought it was Spotify), + // correct it now and wait the full duration. + if (isBrowser && mediaToastDebounce.interval < 1500) { + mediaToastDebounce.interval = 1500; + mediaToastDebounce.restart(); + return; + } + + const title = MediaService.trackTitle || ""; + const artist = MediaService.trackArtist || ""; + const isPlaying = MediaService.isPlaying; + + // Only show toast if something meaningful changed + const titleChanged = title !== previousMediaTitle && title !== ""; + const playStateChanged = isPlaying !== previousMediaIsPlaying; + const hasMedia = title !== "" || artist !== ""; + + // Browser Specific Logic: + // If a browser reports a new title but is PAUSED, ignore it. + if (isBrowser && !isPlaying && titleChanged) { + previousMediaTitle = title; + previousMediaArtist = artist; + previousMediaIsPlaying = isPlaying; + return; + } + + if (hasMedia && (titleChanged || playStateChanged)) { + const icon = isPlaying ? "media-play" : "media-pause"; + let message = ""; + + if (artist && title) { + message = artist + " โ€” " + title; + } else if (title) { + message = title; + } else if (artist) { + message = artist; + } + + if (message !== "") { + const toastTitle = isPlaying ? I18n.tr("common.play") : I18n.tr("common.pause"); + ToastService.showNotice(toastTitle, message, icon, 3000); + } + } + + previousMediaTitle = title; + previousMediaArtist = artist; + previousMediaIsPlaying = isPlaying; + } + + Connections { + target: MediaService + + function onTrackTitleChanged() { + restartDebounce(); + } + + function onTrackArtistChanged() { + restartDebounce(); + } + + function onIsPlayingChanged() { + restartDebounce(); + } + + function onPlayerIdentityChanged() { + restartDebounce(); + } + } + + function restartDebounce() { + const player = (MediaService.playerIdentity || "").toLowerCase(); + const browsers = ["firefox", "chromium", "chrome", "brave", "edge", "opera", "vivaldi"]; + const isBrowser = browsers.some(b => player.includes(b)); + + // Use long delay for browsers to filter hover previews, short for music apps + mediaToastDebounce.interval = isBrowser ? 1500 : 250; + mediaToastDebounce.restart(); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/System/ProgramCheckerService.qml b/arch/.config/quickshell/noctalia-shell/Services/System/ProgramCheckerService.qml new file mode 100644 index 0000000..7f50e88 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/System/ProgramCheckerService.qml @@ -0,0 +1,337 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.Theming + +// Service to check if various programs are available on the system +Singleton { + id: root + + // Program availability properties + property bool nmcliAvailable: false + property bool bluetoothctlAvailable: false + property bool wlsunsetAvailable: false + property bool gnomeCalendarAvailable: false + property bool pythonAvailable: false + property bool wtypeAvailable: false + + // Programs to check - maps property names to commands + readonly property var programsToCheck: ({ + "bluetoothctlAvailable": ["sh", "-c", "command -v bluetoothctl"], + "nmcliAvailable": ["sh", "-c", "command -v nmcli"], + "wlsunsetAvailable": ["sh", "-c", "command -v wlsunset"], + "gnomeCalendarAvailable": ["sh", "-c", "command -v gnome-calendar"], + "wtypeAvailable": ["sh", "-c", "command -v wtype"], + "pythonAvailable": ["sh", "-c", "command -v python3"] + }) + + // Discord client auto-detection + property var availableDiscordClients: [] + + // Code client auto-detection + property var availableCodeClients: [] + + // Emacs client auto-detection + property var availableEmacsClients: [] + + // Signal emitted when all checks are complete + signal checksCompleted + + // disable Night Light in settings if wlsunset is not available + onChecksCompleted: { + if (!wlsunsetAvailable && Settings.data.nightLight.enabled) { + Settings.data.nightLight.enabled = false; + } + } + + onWlsunsetAvailableChanged: { + if (!wlsunsetAvailable && Settings.data.nightLight.enabled) { + Settings.data.nightLight.enabled = false; + } + } + + // Function to detect Discord client by checking config directories + function detectDiscordClient() { + // Build shell script to check each client + var scriptParts = ["available_clients=\"\";"]; + + for (var i = 0; i < TemplateRegistry.discordClients.length; i++) { + var client = TemplateRegistry.discordClients[i]; + var clientName = client.name; + var configPath = client.configPath; + + // Use the actual config path from the client, removing ~ prefix + var checkPath = configPath.startsWith("~") ? configPath.substring(2) : configPath.substring(1); + + scriptParts.push("if [ -d \"$HOME/" + checkPath + "\" ]; then available_clients=\"$available_clients " + clientName + "\"; fi;"); + } + + scriptParts.push("echo \"$available_clients\""); + + // Use a Process to check directory existence for all clients + discordDetector.command = ["sh", "-c", scriptParts.join(" ")]; + discordDetector.running = true; + } + + // Process to detect Discord client directories + Process { + id: discordDetector + running: false + + onExited: function (exitCode) { + availableDiscordClients = []; + + if (exitCode === 0) { + var detectedClients = stdout.text.trim().split(/\s+/).filter(function (client) { + return client.length > 0; + }); + + if (detectedClients.length > 0) { + // Build list of available clients + for (var i = 0; i < detectedClients.length; i++) { + var clientName = detectedClients[i]; + for (var j = 0; j < TemplateRegistry.discordClients.length; j++) { + var client = TemplateRegistry.discordClients[j]; + if (client.name === clientName) { + availableDiscordClients.push(client); + break; + } + } + } + + Logger.d("ProgramChecker", "Detected Discord clients:", detectedClients.join(", ")); + } + } + + if (availableDiscordClients.length === 0) { + Logger.d("ProgramChecker", "No Discord clients detected"); + } + } + + stdout: StdioCollector {} + stderr: StdioCollector {} + } + + // Function to detect Code client by checking config directories + function detectCodeClient() { + // Build shell script to check each client + var scriptParts = ["available_clients=\"\";"]; + + for (var i = 0; i < TemplateRegistry.codeClients.length; i++) { + var client = TemplateRegistry.codeClients[i]; + var clientName = client.name; + var configPath = client.configPath; + + // Check if the config directory exists + scriptParts.push("if [ -d \"$HOME" + configPath.substring(1) + "\" ]; then available_clients=\"$available_clients " + clientName + "\"; fi;"); + } + + scriptParts.push("echo \"$available_clients\""); + + // Use a Process to check directory existence for all clients + codeDetector.command = ["sh", "-c", scriptParts.join(" ")]; + codeDetector.running = true; + } + + // Process to detect Code client directories + Process { + id: codeDetector + running: false + + onExited: function (exitCode) { + availableCodeClients = []; + + if (exitCode === 0) { + var detectedClients = stdout.text.trim().split(/\s+/).filter(function (client) { + return client.length > 0; + }); + + if (detectedClients.length > 0) { + // Build list of available clients + for (var i = 0; i < detectedClients.length; i++) { + var clientName = detectedClients[i]; + for (var j = 0; j < TemplateRegistry.codeClients.length; j++) { + var client = TemplateRegistry.codeClients[j]; + if (client.name === clientName) { + availableCodeClients.push(client); + break; + } + } + } + + Logger.d("ProgramChecker", "Detected Code clients:", detectedClients.join(", ")); + } + } + + if (availableCodeClients.length === 0) { + Logger.d("ProgramChecker", "No Code clients detected"); + } + } + + stdout: StdioCollector {} + stderr: StdioCollector {} + } + + // Function to detect Emacs client by checking config directories + function detectEmacsClient() { + // Build shell script to check each client + var scriptParts = ["available_clients=\"\";"]; + + for (var i = 0; i < TemplateRegistry.emacsClients.length; i++) { + var client = TemplateRegistry.emacsClients[i]; + var clientName = client.name; + var configPath = client.path; + + // Check if the config directory exists + scriptParts.push("if [ -d \"$HOME" + configPath.substring(1) + "\" ]; then available_clients=\"$available_clients " + clientName + "\"; fi;"); + } + + scriptParts.push("echo \"$available_clients\""); + + // Use a Process to check directory existence for all clients + emacsDetector.command = ["sh", "-c", scriptParts.join(" ")]; + emacsDetector.running = true; + } + + // Process to detect Emacs client directories + Process { + id: emacsDetector + running: false + + onExited: function (exitCode) { + availableEmacsClients = []; + + if (exitCode === 0) { + var detectedClients = stdout.text.trim().split(/\s+/).filter(function (client) { + return client.length > 0; + }); + + if (detectedClients.length > 0) { + // Build list of available clients + for (var i = 0; i < detectedClients.length; i++) { + var clientName = detectedClients[i]; + for (var j = 0; j < TemplateRegistry.emacsClients.length; j++) { + var client = TemplateRegistry.emacsClients[j]; + if (client.name === clientName) { + availableEmacsClients.push(client); + break; + } + } + } + + Logger.d("ProgramChecker", "Detected Emacs clients:", detectedClients.join(", ")); + } + } + + if (availableEmacsClients.length === 0) { + Logger.d("ProgramChecker", "No Emacs clients detected"); + } + } + + stdout: StdioCollector {} + stderr: StdioCollector {} + } + + // Internal tracking + property int completedChecks: 0 + property int totalChecks: Object.keys(programsToCheck).length + + // Single reusable Process object + Process { + id: checker + running: false + + property string currentProperty: "" + + onExited: function (exitCode) { + // Set the availability property + root[currentProperty] = (exitCode === 0); + + // Stop the process to free resources + running = false; + + // Track completion + root.completedChecks++; + + // Check next program or emit completion signal + if (root.completedChecks >= root.totalChecks) { + // Run Discord, Code and Emacs client detection after all checks are complete + root.detectDiscordClient(); + root.detectCodeClient(); + root.detectEmacsClient(); + root.checksCompleted(); + } else { + root.checkNextProgram(); + } + } + + stdout: StdioCollector {} + stderr: StdioCollector {} + } + + // Queue of programs to check + property var checkQueue: [] + property int currentCheckIndex: 0 + + // Function to check the next program in the queue + function checkNextProgram() { + if (currentCheckIndex >= checkQueue.length) + return; + var propertyName = checkQueue[currentCheckIndex]; + var command = programsToCheck[propertyName]; + + checker.currentProperty = propertyName; + checker.command = command; + checker.running = true; + + currentCheckIndex++; + } + + // Function to run all program checks + function checkAllPrograms() { + // Reset state + completedChecks = 0; + currentCheckIndex = 0; + checkQueue = Object.keys(programsToCheck); + + // Start first check + if (checkQueue.length > 0) { + checkNextProgram(); + } + } + + // Function to check a specific program + function checkProgram(programProperty) { + if (!programsToCheck.hasOwnProperty(programProperty)) { + Logger.w("ProgramChecker", "Unknown program property:", programProperty); + return; + } + + checker.currentProperty = programProperty; + checker.command = programsToCheck[programProperty]; + checker.running = true; + } + + // Manual function to test Discord detection (for debugging) + function testDiscordDetection() { + Logger.d("ProgramChecker", "Testing Discord detection..."); + Logger.d("ProgramChecker", "HOME:", Quickshell.env("HOME")); + + // Test each client directory + for (var i = 0; i < TemplateRegistry.discordClients.length; i++) { + var client = TemplateRegistry.discordClients[i]; + var configDir = client.configPath.replace("~", Quickshell.env("HOME")); + Logger.d("ProgramChecker", "Checking:", configDir); + } + + detectDiscordClient(); + } + + // Initialize checks when service is created + Component.onCompleted: { + checkAllPrograms(); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/System/SoundService.qml b/arch/.config/quickshell/noctalia-shell/Services/System/SoundService.qml new file mode 100644 index 0000000..53f9df0 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/System/SoundService.qml @@ -0,0 +1,205 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Commons + +Singleton { + id: root + + // Map to track active sound players: resolvedPath -> MediaPlayer instance + property var activePlayers: ({}) + + // Check if QtMultimedia is available + property bool multimediaAvailable: false + + // Container for dynamically created players + Item { + id: playersContainer + } + + Component.onCompleted: { + // Test if QtMultimedia is available by trying to create a simple component + try { + var testComponent = Qt.createQmlObject(` + import QtQuick + import QtMultimedia + Item {} + `, root, "MultimediaTest"); + if (testComponent) { + multimediaAvailable = true; + testComponent.destroy(); + Logger.i("SoundService", "QtMultimedia found - sound playback enabled"); + } + } catch (e) { + multimediaAvailable = false; + Logger.w("SoundService", "QtMultimedia not available - no audio will be played from noctalia-shell"); + } + } + + function resolvePath(soundPath) { + if (!soundPath || soundPath === "") { + return ""; + } + + let resolvedPath = soundPath; + + // If it's just a filename (no path separators), assume it's in Assets/Sounds/ + if (!soundPath.includes("/") && !soundPath.startsWith("file://")) { + resolvedPath = Quickshell.shellDir + "/Assets/Sounds/" + soundPath; + } else if (!soundPath.startsWith("/") && !soundPath.startsWith("file://")) { + // Relative path - assume it's relative to shellDir + resolvedPath = Quickshell.shellDir + "/" + soundPath; + } else if (soundPath.startsWith("file://")) { + resolvedPath = soundPath.substring(7); // Remove "file://" prefix + } + // Absolute paths are used as-is + + return resolvedPath; + } + + function playSound(soundPath, options) { + if (!soundPath || soundPath === "") { + Logger.w("SoundService", "No sound path provided"); + return; + } + + if (!multimediaAvailable) { + Logger.d("SoundService", "QtMultimedia not available, cannot play sound:", soundPath); + return; + } + + const opts = options || {}; + const volume = opts.volume !== undefined ? opts.volume : 1.0; + const fallback = opts.fallback !== undefined ? opts.fallback : false; + const repeat = opts.repeat !== undefined ? opts.repeat : false; + + // Resolve path + const resolvedPath = resolvePath(soundPath); + + // Stop any existing player for this path if it's looping + if (repeat && activePlayers[resolvedPath]) { + stopSound(soundPath); + } + + // Create MediaPlayer instance dynamically with QtMultimedia import + const loopsValue = repeat ? "MediaPlayer.Infinite" : "1"; + const escapedPath = resolvedPath.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); + const playerQml = ` + import QtQuick + import QtMultimedia + import Quickshell + import qs.Commons + import qs.Services.System + MediaPlayer { + id: mediaPlayer + property string resolvedPath: "${escapedPath}" + property bool shouldFallback: ${fallback && !repeat} + property real soundVolume: ${Math.max(0, Math.min(1, volume))} + source: "file://${escapedPath}" + loops: ${loopsValue} + audioOutput: AudioOutput { + volume: soundVolume + } + onErrorOccurred: { + Logger.w("SoundService", "Error playing sound:", source, error, errorString); + if (shouldFallback) { + const fallbackPath = Quickshell.shellDir + "/Assets/Sounds/notification.mp3"; + if (fallbackPath !== resolvedPath) { + SoundService.playSound(fallbackPath, { + volume: soundVolume, + fallback: false, + repeat: false + }); + } + } + if (SoundService.activePlayers[resolvedPath]) { + delete SoundService.activePlayers[resolvedPath]; + } + destroy(); + } + onPlaybackStateChanged: function (state) { + if (state === MediaPlayer.StoppedState && loops === 1) { + if (SoundService.activePlayers[resolvedPath]) { + delete SoundService.activePlayers[resolvedPath]; + } + destroy(); + } + } + Component.onCompleted: { + play(); + } + } + `; + + try { + const player = Qt.createQmlObject(playerQml, playersContainer, "MediaPlayer_" + resolvedPath.replace(/[^a-zA-Z0-9]/g, "_")); + + if (!player) { + Logger.w("SoundService", "Failed to create MediaPlayer for:", resolvedPath); + // Try fallback if requested + if (fallback && !repeat) { + const defaultSound = Quickshell.shellDir + "/Assets/Sounds/notification.mp3"; + if (defaultSound !== resolvedPath) { + playSound(defaultSound, { + volume: volume, + fallback: false, + repeat: false + }); + } + } + return; + } + + // Store player in activePlayers map + activePlayers[resolvedPath] = player; + + Logger.d("SoundService", "Playing sound:", resolvedPath, `(volume: ${Math.round(volume * 100)}%)`, repeat ? "(repeat)" : ""); + } catch (e) { + Logger.w("SoundService", "Failed to create MediaPlayer:", e); + // Try fallback if requested + if (fallback && !repeat) { + const defaultSound = Quickshell.shellDir + "/Assets/Sounds/notification.mp3"; + if (defaultSound !== resolvedPath) { + playSound(defaultSound, { + volume: volume, + fallback: false, + repeat: false + }); + } + } + } + } + + function stopSound(soundPath) { + if (!multimediaAvailable) { + // If multimedia isn't available, there are no active players to stop + return; + } + + if (soundPath) { + // Resolve path the same way as playSound + const resolvedPath = resolvePath(soundPath); + + // Stop and remove the player for this specific sound + if (activePlayers[resolvedPath]) { + const player = activePlayers[resolvedPath]; + player.stop(); + delete activePlayers[resolvedPath]; + player.destroy(); + Logger.d("SoundService", "Stopped sound:", resolvedPath); + } + } else { + // Stop all active players (typically used for repeating sounds) + const paths = Object.keys(activePlayers); + for (let i = 0; i < paths.length; i++) { + const path = paths[i]; + const player = activePlayers[path]; + player.stop(); + player.destroy(); + } + activePlayers = {}; + Logger.d("SoundService", "Stopped all sounds"); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/System/SystemStatService.qml b/arch/.config/quickshell/noctalia-shell/Services/System/SystemStatService.qml new file mode 100644 index 0000000..ae7ebf4 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/System/SystemStatService.qml @@ -0,0 +1,1506 @@ +pragma Singleton +import Qt.labs.folderlistmodel + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.UI + +Singleton { + id: root + + // Component registration - only poll when something needs system stat data + function registerComponent(componentId) { + root._registered[componentId] = true; + root._registered = Object.assign({}, root._registered); + Logger.d("SystemStat", "Component registered:", componentId, "- total:", root._registeredCount); + } + + function unregisterComponent(componentId) { + delete root._registered[componentId]; + root._registered = Object.assign({}, root._registered); + Logger.d("SystemStat", "Component unregistered:", componentId, "- total:", root._registeredCount); + } + + property var _registered: ({}) + readonly property int _registeredCount: Object.keys(_registered).length + readonly property bool _lockScreenActive: PanelService.lockScreen?.active ?? false + readonly property bool shouldRun: _registeredCount > 0 && !_lockScreenActive + + // Polling intervals (hardcoded to sensible values per stat type) + readonly property int cpuUsageIntervalMs: 1000 + readonly property int cpuFreqIntervalMs: 3000 + readonly property int memIntervalMs: 5000 + readonly property int networkIntervalMs: 3000 + readonly property int loadAvgIntervalMs: 10000 + readonly property int diskIntervalMs: 30000 + readonly property int gpuIntervalMs: 5000 + + // Public values + property real cpuUsage: 0 + property list coresUsage: [] + property real cpuTemp: 0 + property string cpuFreq: "0.0GHz" + property real cpuFreqRatio: 0 + property real cpuGlobalMaxFreq: 3.5 + property real gpuTemp: 0 + property bool gpuAvailable: false + property string gpuType: "" // "amd", "intel", "nvidia" + property real memGb: 0 + property real memPercent: 0 + property real memTotalGb: 0 + property real swapGb: 0 + property real swapPercent: 0 + property real swapTotalGb: 0 + property var diskPercents: ({}) + property var diskAvailPercents: ({}) // available disk space in percent + property var diskUsedGb: ({}) // Used space in GB per mount point + property var diskAvailableGb: ({}) // available space in GB per mount point + property var diskSizeGb: ({}) // Total size in GB per mount point + property real rxSpeed: 0 + property real txSpeed: 0 + property real zfsArcSizeKb: 0 // ZFS ARC cache size in KB + property real zfsArcCminKb: 0 // ZFS ARC minimum (non-reclaimable) size in KB + property real loadAvg1: 0 + property real loadAvg5: 0 + property real loadAvg15: 0 + property int nproc: 0 // Number of cpu cores + + // History arrays (2 minutes of data, length computed from polling interval) + // Pre-filled with zeros so the graph scrolls smoothly from the start + readonly property int historyDurationMs: (1 * 60 * 1000) // 1 minute + + // Computed history lengths based on polling intervals + readonly property int cpuHistoryLength: Math.ceil(historyDurationMs / cpuUsageIntervalMs) + readonly property int gpuHistoryLength: Math.ceil(historyDurationMs / gpuIntervalMs) + readonly property int memHistoryLength: Math.ceil(historyDurationMs / memIntervalMs) + readonly property int diskHistoryLength: Math.max(10, Math.ceil(historyDurationMs / diskIntervalMs)) + readonly property int networkHistoryLength: Math.ceil(historyDurationMs / networkIntervalMs) + + property var cpuHistory: new Array(cpuHistoryLength).fill(0) + property var cpuTempHistory: new Array(cpuHistoryLength).fill(40) // Reasonable default temp + property var gpuTempHistory: new Array(gpuHistoryLength).fill(40) // Reasonable default temp + property var memHistory: new Array(memHistoryLength).fill(0) + property var diskHistories: ({}) // Keyed by mount path, initialized on first update + property var rxSpeedHistory: new Array(networkHistoryLength).fill(0) + property var txSpeedHistory: new Array(networkHistoryLength).fill(0) + + // Historical min/max tracking (since shell started) for consistent graph scaling + // Temperature defaults create a valid 30-80ยฐC range that expands as real data comes in + property real cpuTempHistoryMin: 30 + property real cpuTempHistoryMax: 80 + property real gpuTempHistoryMin: 30 + property real gpuTempHistoryMax: 80 + // Network uses autoscaling from current history window + // Disk is always 0-100% + + // History management - called from update functions, not change handlers + // (change handlers don't fire when value stays the same) + function pushCpuHistory() { + let h = cpuHistory.slice(); + h.push(cpuUsage); + if (h.length > cpuHistoryLength) + h.shift(); + cpuHistory = h; + } + + function pushCpuTempHistory() { + if (cpuTemp > 0) { + if (cpuTemp < cpuTempHistoryMin) + cpuTempHistoryMin = cpuTemp; + if (cpuTemp > cpuTempHistoryMax) + cpuTempHistoryMax = cpuTemp; + } + let h = cpuTempHistory.slice(); + h.push(cpuTemp); + if (h.length > cpuHistoryLength) + h.shift(); + cpuTempHistory = h; + } + + function pushGpuHistory() { + if (gpuTemp > 0) { + if (gpuTemp < gpuTempHistoryMin) + gpuTempHistoryMin = gpuTemp; + if (gpuTemp > gpuTempHistoryMax) + gpuTempHistoryMax = gpuTemp; + } + let h = gpuTempHistory.slice(); + h.push(gpuTemp); + if (h.length > gpuHistoryLength) + h.shift(); + gpuTempHistory = h; + } + + function pushMemHistory() { + let h = memHistory.slice(); + h.push(memPercent); + if (h.length > memHistoryLength) + h.shift(); + memHistory = h; + } + + function pushDiskHistory() { + let newHistories = {}; + for (let path in diskPercents) { + // Pre-fill with zeros if this is a new path + let h = diskHistories[path] ? diskHistories[path].slice() : new Array(diskHistoryLength).fill(0); + h.push(diskPercents[path]); + if (h.length > diskHistoryLength) + h.shift(); + newHistories[path] = h; + } + diskHistories = newHistories; + } + + function pushNetworkHistory() { + let rxH = rxSpeedHistory.slice(); + rxH.push(rxSpeed); + if (rxH.length > networkHistoryLength) + rxH.shift(); + rxSpeedHistory = rxH; + + let txH = txSpeedHistory.slice(); + txH.push(txSpeed); + if (txH.length > networkHistoryLength) + txH.shift(); + txSpeedHistory = txH; + } + + // Network max speed tracking (autoscales from current history window) + // Minimum floor of 1 MB/s so graph doesn't fluctuate at low speeds + readonly property real rxMaxSpeed: { + const max = Math.max(...rxSpeedHistory); + return Math.max(max, 1000000); // 1 MB/s floor + } + readonly property real txMaxSpeed: { + const max = Math.max(...txSpeedHistory); + return Math.max(max, 512000); // 512 KB/s floor + } + + // Ready-to-use ratios based on current maximums (0..1 range) + readonly property real rxRatio: rxMaxSpeed > 0 ? Math.min(1, rxSpeed / rxMaxSpeed) : 0 + readonly property real txRatio: txMaxSpeed > 0 ? Math.min(1, txSpeed / txMaxSpeed) : 0 + + // Color resolution (respects useCustomColors setting) + readonly property color warningColor: Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.warningColor || Color.mTertiary) : Color.mTertiary + readonly property color criticalColor: Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor || Color.mError) : Color.mError + + // Threshold values from settings + readonly property int cpuWarningThreshold: Settings.data.systemMonitor.cpuWarningThreshold + readonly property int cpuCriticalThreshold: Settings.data.systemMonitor.cpuCriticalThreshold + readonly property int tempWarningThreshold: Settings.data.systemMonitor.tempWarningThreshold + readonly property int tempCriticalThreshold: Settings.data.systemMonitor.tempCriticalThreshold + readonly property int gpuWarningThreshold: Settings.data.systemMonitor.gpuWarningThreshold + readonly property int gpuCriticalThreshold: Settings.data.systemMonitor.gpuCriticalThreshold + readonly property int memWarningThreshold: Settings.data.systemMonitor.memWarningThreshold + readonly property int memCriticalThreshold: Settings.data.systemMonitor.memCriticalThreshold + readonly property int swapWarningThreshold: Settings.data.systemMonitor.swapWarningThreshold + readonly property int swapCriticalThreshold: Settings.data.systemMonitor.swapCriticalThreshold + readonly property int diskWarningThreshold: Settings.data.systemMonitor.diskWarningThreshold + readonly property int diskCriticalThreshold: Settings.data.systemMonitor.diskCriticalThreshold + readonly property int diskAvailWarningThreshold: Settings.data.systemMonitor.diskAvailWarningThreshold + readonly property int diskAvailCriticalThreshold: Settings.data.systemMonitor.diskAvailCriticalThreshold + + // Computed warning/critical states (uses >= inclusive comparison) + readonly property bool cpuWarning: cpuUsage >= cpuWarningThreshold + readonly property bool cpuCritical: cpuUsage >= cpuCriticalThreshold + readonly property bool tempWarning: cpuTemp >= tempWarningThreshold + readonly property bool tempCritical: cpuTemp >= tempCriticalThreshold + readonly property bool gpuWarning: gpuAvailable && gpuTemp >= gpuWarningThreshold + readonly property bool gpuCritical: gpuAvailable && gpuTemp >= gpuCriticalThreshold + readonly property bool memWarning: memPercent >= memWarningThreshold + readonly property bool memCritical: memPercent >= memCriticalThreshold + readonly property bool swapWarning: swapPercent >= swapWarningThreshold + readonly property bool swapCritical: swapPercent >= swapCriticalThreshold + + // Helper functions for disk (disk path is dynamic) + function isDiskWarning(diskPath, available = false) { + return available ? (diskAvailPercents[diskPath] || 0) <= diskAvailWarningThreshold : (diskPercents[diskPath] || 0) >= diskWarningThreshold; + } + + function isDiskCritical(diskPath, available = false) { + return available ? (diskAvailPercents[diskPath] || 0) <= diskAvailCriticalThreshold : (diskPercents[diskPath] || 0) >= diskCriticalThreshold; + } + + // Ready-to-use stat colors (for gauges, panels, icons) + readonly property color cpuColor: cpuCritical ? criticalColor : (cpuWarning ? warningColor : Color.mPrimary) + readonly property color tempColor: tempCritical ? criticalColor : (tempWarning ? warningColor : Color.mPrimary) + readonly property color gpuColor: gpuCritical ? criticalColor : (gpuWarning ? warningColor : Color.mPrimary) + readonly property color memColor: memCritical ? criticalColor : (memWarning ? warningColor : Color.mPrimary) + readonly property color swapColor: swapCritical ? criticalColor : (swapWarning ? warningColor : Color.mPrimary) + + function getCoreUsageColor(usage) { + if (usage >= cpuCriticalThreshold) + return criticalColor; + if (usage >= cpuWarningThreshold) + return warningColor; + return Color.mPrimary; + } + + function getDiskColor(diskPath, available = false) { + return isDiskCritical(diskPath, available) ? criticalColor : (isDiskWarning(diskPath, available) ? warningColor : Color.mPrimary); + } + + // Internal state for CPU calculation + property var prevCpuStats: null + property var prevCpuCoresStats: null + + // Internal state for network speed calculation + // Previous Bytes need to be stored as 'real' as they represent the total of bytes transfered + // since the computer started, so their value will easily overlfow a 32bit int. + property real prevRxBytes: 0 + property real prevTxBytes: 0 + property real prevTime: 0 + + // Cpu temperature is the most complex + readonly property var supportedTempCpuSensorNames: ["coretemp", "k10temp", "zenpower"] + property string cpuTempSensorName: "" + property string cpuTempHwmonPath: "" + // For Intel coretemp averaging of all cores/sensors + property var intelTempValues: [] + property int intelTempFilesChecked: 0 + property int intelTempMaxFiles: 20 // Will test up to temp20_input + + // Thermal zone fallback (for ARM SoCs with SCMI sensors, etc.) + property var cpuThermalZonePaths: [] // All matching CPU zones for averaging + property string gpuThermalZonePath: "" + + // GPU temperature detection + // On dual-GPU systems, we prioritize discrete GPUs over integrated GPUs + // Priority: NVIDIA (opt-in) > AMD dGPU > Intel Arc > AMD iGPU + // Note: NVIDIA requires opt-in because nvidia-smi wakes the dGPU on laptops, draining battery + readonly property var supportedTempGpuSensorNames: ["amdgpu", "xe"] + property string gpuTempHwmonPath: "" + property var foundGpuSensors: [] // [{hwmonPath, type, hasDedicatedVram}] + property int gpuVramCheckIndex: 0 + + // -------------------------------------------- + Component.onCompleted: { + Logger.i("SystemStat", "Service started (polling deferred until a consumer registers)."); + + // Kickoff the cpu name detection for temperature (one-time probes, not polling) + cpuTempNameReader.checkNext(); + + // Kickoff the gpu sensor detection for temperature (one-time probes, not polling) + gpuTempNameReader.checkNext(); + + // Get nproc on startup (one-time) + nprocProcess.running = true; + } + + onShouldRunChanged: { + if (shouldRun) { + // Reset differential state so first readings after resume are clean + root.prevCpuStats = null; + root.prevCpuCoresStats = null; + root.prevTime = 0; + + // Trigger initial reads + zfsArcStatsFile.reload(); + loadAvgFile.reload(); + + // Start persistent disk shell + if (!dfShell.running) { + dfShell.running = true; + } + } else { + // Stop persistent disk shell + if (dfShell.running) { + dfShell.running = false; + } + } + } + + // Re-run GPU detection when dGPU opt-in setting changes + Connections { + target: Settings.data.systemMonitor + function onEnableDgpuMonitoringChanged() { + Logger.i("SystemStat", "dGPU monitoring opt-in setting changed, re-detecting GPUs"); + restartGpuDetection(); + } + } + + // Reset differential state after suspend so the first reading is treated as fresh + Connections { + target: Time + function onResumed() { + Logger.i("SystemStat", "System resumed - resetting differential state"); + root.prevCpuStats = null; + root.prevCpuCoresStats = null; + root.prevTime = 0; + } + } + + function restartGpuDetection() { + // Reset GPU state + root.gpuAvailable = false; + root.gpuType = ""; + root.gpuTempHwmonPath = ""; + root.gpuTemp = 0; + root.foundGpuSensors = []; + root.gpuVramCheckIndex = 0; + + // Restart GPU detection + gpuTempNameReader.currentIndex = 0; + gpuTempNameReader.checkNext(); + } + + // -------------------------------------------- + // Timer for CPU usage and temperature + Timer { + id: cpuTimer + interval: root.cpuUsageIntervalMs + repeat: true + running: root.shouldRun + triggeredOnStart: true + onTriggered: { + cpuStatFile.reload(); + updateCpuTemperature(); + } + } + + // Timer for CPU frequency (slower โ€” /proc/cpuinfo is large and freq changes infrequently) + Timer { + id: cpuFreqTimer + interval: root.cpuFreqIntervalMs + repeat: true + running: root.shouldRun + triggeredOnStart: true + onTriggered: cpuInfoFile.reload() + } + + // Timer for load average + Timer { + id: loadAvgTimer + interval: root.loadAvgIntervalMs + repeat: true + running: root.shouldRun + triggeredOnStart: true + onTriggered: loadAvgFile.reload() + } + + // Timer for memory stats + Timer { + id: memoryTimer + interval: root.memIntervalMs + repeat: true + running: root.shouldRun + triggeredOnStart: true + onTriggered: { + memInfoFile.reload(); + zfsArcStatsFile.reload(); + } + } + + // Timer for disk usage + Timer { + id: diskTimer + interval: root.diskIntervalMs + repeat: true + running: root.shouldRun + triggeredOnStart: true + onTriggered: { + if (dfShell.running) { + dfShell.write("df --output=target,pcent,used,size,avail --block-size=1 -x efivarfs 2>/dev/null; echo '@@DF_END@@'\n"); + } + } + } + + // Timer for network speeds + Timer { + id: networkTimer + interval: root.networkIntervalMs + repeat: true + running: root.shouldRun + triggeredOnStart: true + onTriggered: netDevFile.reload() + } + + // Timer for GPU temperature + Timer { + id: gpuTempTimer + interval: root.gpuIntervalMs + repeat: true + running: root.shouldRun && root.gpuAvailable + triggeredOnStart: true + onTriggered: updateGpuTemperature() + } + + // -------------------------------------------- + // FileView components for reading system files + FileView { + id: memInfoFile + path: "/proc/meminfo" + onLoaded: parseMemoryInfo(text()) + } + + FileView { + id: cpuStatFile + path: "/proc/stat" + onLoaded: calculateCpuUsage(text()) + } + + FileView { + id: netDevFile + path: "/proc/net/dev" + onLoaded: calculateNetworkSpeed(text()) + } + + FileView { + id: loadAvgFile + path: "/proc/loadavg" + onLoaded: parseLoadAverage(text()) + } + + // ZFS ARC stats file (only exists on ZFS systems) + FileView { + id: zfsArcStatsFile + path: "/proc/spl/kstat/zfs/arcstats" + printErrors: false + onLoaded: parseZfsArcStats(text()) + onLoadFailed: { + // File doesn't exist (non-ZFS system), set ARC values to 0 + root.zfsArcSizeKb = 0; + root.zfsArcCminKb = 0; + } + } + + // -------------------------------------------- + // Persistent shell for disk usage queries (avoids fork+exec of large Quickshell process every poll) + // Uses 'df' aka 'disk free' + // "-x efivarfs" skips efivarfs mountpoints, for which the `statfs` syscall may cause system-wide stuttering + // --block-size=1 gives us bytes for precise GB calculation + // Timer writes commands to stdin; SplitParser reads output delimited by @@DF_END@@ + Process { + id: dfShell + command: ["sh"] + stdinEnabled: true + running: false + + onRunningChanged: { + if (!running && root.shouldRun) { + // Restart if it died unexpectedly while we still need it + Logger.w("SystemStat", "Disk shell exited unexpectedly, restarting"); + Qt.callLater(() => { + dfShell.running = true; + }); + } + } + + stdout: SplitParser { + splitMarker: "@@DF_END@@" + onRead: data => { + const lines = data.trim().split('\n'); + const newPercents = {}; + const newAvailPercents = {}; + const newUsedGb = {}; + const newSizeGb = {}; + const newAvailableGb = {}; + const bytesPerGb = 1000 * 1000 * 1000; + // Start from line 1 (skip header) + for (var i = 1; i < lines.length; i++) { + const parts = lines[i].trim().split(/\s+/); + if (parts.length >= 5) { + const target = parts[0]; + const percent = parseInt(parts[1].replace(/[^0-9]/g, '')) || 0; + const usedBytes = parseFloat(parts[2]) || 0; + const sizeBytes = parseFloat(parts[3]) || 0; + const availBytes = parseFloat(parts[4]) || 0; + const availPercent = sizeBytes > 0 ? (availBytes / sizeBytes) * 100 : 0; + newPercents[target] = percent; + newAvailPercents[target] = Math.round(availPercent); + newUsedGb[target] = usedBytes / bytesPerGb; + newSizeGb[target] = sizeBytes / bytesPerGb; + newAvailableGb[target] = availBytes / bytesPerGb; + } + } + root.diskPercents = newPercents; + root.diskAvailPercents = newAvailPercents; + root.diskUsedGb = newUsedGb; + root.diskSizeGb = newSizeGb; + root.diskAvailableGb = newAvailableGb; + root.pushDiskHistory(); + } + } + } + + // Process to get number of processors + Process { + id: nprocProcess + command: ["nproc"] + running: false + stdout: StdioCollector { + onStreamFinished: { + root.nproc = parseInt(text.trim()); + } + } + } + + // FileView to get avg cpu frequency (replaces subprocess spawn of `cat /proc/cpuinfo`) + FileView { + id: cpuInfoFile + path: "/proc/cpuinfo" + onLoaded: { + let txt = text(); + let matches = txt.match(/cpu MHz\s+:\s+([0-9.]+)/g); + if (matches && matches.length > 0) { + let totalFreq = 0.0; + for (let i = 0; i < matches.length; i++) { + totalFreq += parseFloat(matches[i].split(":")[1]); + } + let avgFreq = (totalFreq / matches.length) / 1000.0; + root.cpuFreq = avgFreq.toFixed(1) + "GHz"; + cpuMaxFreqFile.reload(); + if (avgFreq > root.cpuGlobalMaxFreq) + root.cpuGlobalMaxFreq = avgFreq; + if (root.cpuGlobalMaxFreq > 0) { + root.cpuFreqRatio = Math.min(1.0, avgFreq / root.cpuGlobalMaxFreq); + } + } + } + } + + // FileView to get maximum CPU frequency limit (replaces subprocess spawn) + // Reads cpu0's scaling_max_freq as representative value + FileView { + id: cpuMaxFreqFile + path: "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq" + printErrors: false + onLoaded: { + let maxKHz = parseInt(text().trim()); + if (!isNaN(maxKHz) && maxKHz > 0) { + let newMaxFreq = maxKHz / 1000000.0; + if (Math.abs(root.cpuGlobalMaxFreq - newMaxFreq) > 0.01) { + root.cpuGlobalMaxFreq = newMaxFreq; + } + } + } + } + + // -------------------------------------------- + // -------------------------------------------- + // CPU Temperature + // It's more complex. + // ---- + // #1 - Find a common cpu sensor name ie: "coretemp", "k10temp", "zenpower" + FileView { + id: cpuTempNameReader + property int currentIndex: 0 + printErrors: false + + function checkNext() { + if (currentIndex >= 16) { + // No hwmon sensor found, try thermal_zone fallback (ARM SoCs, SCMI, etc.) + thermalZoneScanner.startScan(); + return; + } + + //Logger.i("SystemStat", "---- Probing: hwmon", currentIndex) + cpuTempNameReader.path = `/sys/class/hwmon/hwmon${currentIndex}/name`; + cpuTempNameReader.reload(); + } + + onLoaded: { + const name = text().trim(); + if (root.supportedTempCpuSensorNames.includes(name)) { + root.cpuTempSensorName = name; + root.cpuTempHwmonPath = `/sys/class/hwmon/hwmon${currentIndex}`; + Logger.i("SystemStat", `Found ${root.cpuTempSensorName} CPU thermal sensor at ${root.cpuTempHwmonPath}`); + } else { + currentIndex++; + Qt.callLater(() => { + // Qt.callLater is mandatory + checkNext(); + }); + } + } + + onLoadFailed: function (error) { + currentIndex++; + Qt.callLater(() => { + // Qt.callLater is mandatory + checkNext(); + }); + } + } + + // ---- + // #2 - Read sensor value + FileView { + id: cpuTempReader + printErrors: false + + onLoaded: { + const data = text().trim(); + if (root.cpuTempSensorName === "coretemp") { + // For Intel, collect all temperature values + const temp = parseInt(data) / 1000.0; + //console.log(temp, cpuTempReader.path) + root.intelTempValues.push(temp); + Qt.callLater(() => { + // Qt.callLater is mandatory + checkNextIntelTemp(); + }); + } else { + // For AMD sensors (k10temp and zenpower), directly set the temperature + root.cpuTemp = Math.round(parseInt(data) / 1000.0); + root.pushCpuTempHistory(); + } + } + onLoadFailed: function (error) { + Qt.callLater(() => { + // Qt.callLater is mandatory + checkNextIntelTemp(); + }); + } + } + + // -------------------------------------------- + // Thermal zone fallback for CPU and GPU temperature + // Used on ARM SoCs (e.g., SCMI sensors) where hwmon doesn't expose + // coretemp/k10temp/zenpower. Scans /sys/class/thermal/thermal_zoneN/type + // for CPU and GPU zone names, then reads temp from all matching zones. + // + // CPU: reads all cpu-*-thermal zones and reports the hottest core. + // GPU: prefers gpu-avg-thermal (firmware average), falls back to max of gpu[0-9]-thermal. + + FileView { + id: thermalZoneScanner + property int currentIndex: 0 + property var cpuZones: [] + property var gpuZones: [] + property string gpuAvgZonePath: "" + printErrors: false + + function startScan() { + currentIndex = 0; + cpuZones = []; + gpuZones = []; + gpuAvgZonePath = ""; + checkNext(); + } + + function checkNext() { + if (currentIndex >= 20) { + finishScan(); + return; + } + thermalZoneScanner.path = `/sys/class/thermal/thermal_zone${currentIndex}/type`; + thermalZoneScanner.reload(); + } + + onLoaded: { + const name = text().trim(); + const zonePath = `/sys/class/thermal/thermal_zone${currentIndex}`; + if (name.startsWith("cpu") && name.endsWith("thermal")) { + cpuZones.push({ + "type": name, + "path": zonePath + "/temp" + }); + } else if (name === "gpu-avg-thermal") { + gpuAvgZonePath = zonePath + "/temp"; + } else if (/^gpu[0-9]+-?thermal$/.test(name)) { + gpuZones.push({ + "type": name, + "path": zonePath + "/temp" + }); + } + currentIndex++; + Qt.callLater(() => { + checkNext(); + }); + } + + onLoadFailed: function (error) { + currentIndex++; + Qt.callLater(() => { + checkNext(); + }); + } + + function finishScan() { + // CPU thermal zones + if (cpuZones.length > 0) { + root.cpuTempSensorName = "thermal_zone"; + root.cpuThermalZonePaths = cpuZones.map(z => z.path); + const types = cpuZones.map(z => z.type).join(", "); + Logger.i("SystemStat", `Found ${cpuZones.length} CPU thermal zone(s): ${types}`); + } else if (root.cpuTempHwmonPath === "") { + Logger.w("SystemStat", "No supported temperature sensor found"); + } + + // GPU thermal zones + if (gpuAvgZonePath !== "") { + root.gpuThermalZonePath = gpuAvgZonePath; + root.gpuAvailable = true; + root.gpuType = "thermal_zone"; + Logger.i("SystemStat", `Found GPU thermal zone: gpu-avg-thermal`); + } else if (gpuZones.length > 0) { + root.gpuThermalZonePaths = gpuZones.map(z => z.path); + root.gpuThermalZonePath = gpuZones[0].path; // fallback single path + root.gpuAvailable = true; + root.gpuType = "thermal_zone"; + const types = gpuZones.map(z => z.type).join(", "); + Logger.i("SystemStat", `Found ${gpuZones.length} GPU thermal zone(s): ${types} (using max)`); + } + } + } + + // Thermal zone reader for CPU: reads all zones, reports max (hottest core) + FileView { + id: cpuThermalZoneReader + property int currentZoneIndex: 0 + property var collectedTemps: [] + printErrors: false + + onLoaded: { + const temp = parseInt(text().trim()) / 1000.0; + if (!isNaN(temp) && temp > 0) + collectedTemps.push(temp); + currentZoneIndex++; + Qt.callLater(() => { + readNextCpuThermalZone(); + }); + } + + onLoadFailed: function (error) { + currentZoneIndex++; + Qt.callLater(() => { + readNextCpuThermalZone(); + }); + } + } + + function readNextCpuThermalZone() { + if (cpuThermalZoneReader.currentZoneIndex >= root.cpuThermalZonePaths.length) { + if (cpuThermalZoneReader.collectedTemps.length > 0) { + root.cpuTemp = Math.round(Math.max(...cpuThermalZoneReader.collectedTemps)); + } else { + root.cpuTemp = 0; + } + root.pushCpuTempHistory(); + return; + } + cpuThermalZoneReader.path = root.cpuThermalZonePaths[cpuThermalZoneReader.currentZoneIndex]; + cpuThermalZoneReader.reload(); + } + + // Thermal zone reader for GPU: reads single zone (gpu-avg-thermal) or max of gpu[N] zones + FileView { + id: gpuThermalZoneReader + property int currentZoneIndex: 0 + property var collectedTemps: [] + printErrors: false + + onLoaded: { + const temp = parseInt(text().trim()) / 1000.0; + if (!isNaN(temp) && temp > 0) + collectedTemps.push(temp); + + // If we have multiple GPU zones (no gpu-avg), iterate and take max + if (root.gpuThermalZonePaths && root.gpuThermalZonePaths.length > 0) { + currentZoneIndex++; + if (currentZoneIndex < root.gpuThermalZonePaths.length) { + Qt.callLater(() => { + readNextGpuThermalZone(); + }); + return; + } + // All zones read, take max + root.gpuTemp = Math.round(Math.max(...collectedTemps)); + } else { + // Single gpu-avg-thermal zone + root.gpuTemp = Math.round(temp); + } + root.pushGpuHistory(); + } + } + + function readNextGpuThermalZone() { + gpuThermalZoneReader.path = root.gpuThermalZonePaths[gpuThermalZoneReader.currentZoneIndex]; + gpuThermalZoneReader.reload(); + } + + // Property to store multiple GPU thermal zone paths (when no gpu-avg is available) + property var gpuThermalZonePaths: [] + + // -------------------------------------------- + // -------------------------------------------- + // GPU Temperature + // On dual-GPU systems (e.g., Intel iGPU + NVIDIA dGPU, or AMD APU + AMD dGPU), + // we scan all hwmon entries, then select the best GPU based on priority. + // ---- + // #1 - Scan all hwmon entries to find GPU sensors + FileView { + id: gpuTempNameReader + property int currentIndex: 0 + printErrors: false + + function checkNext() { + if (currentIndex >= 16) { + // Finished scanning all hwmon entries + // Only check nvidia-smi if user has explicitly enabled dGPU monitoring (opt-in) + // because nvidia-smi wakes up the dGPU on laptops, draining battery + if (Settings.data.systemMonitor.enableDgpuMonitoring) { + Logger.d("SystemStat", `Found ${root.foundGpuSensors.length} sysfs GPU sensor(s), checking nvidia-smi (dGPU opt-in enabled)`); + nvidiaSmiCheck.running = true; + } else { + Logger.d("SystemStat", `Found ${root.foundGpuSensors.length} sysfs GPU sensor(s), skipping nvidia-smi (dGPU opt-in disabled)`); + root.gpuVramCheckIndex = 0; + checkNextGpuVram(); + } + return; + } + + gpuTempNameReader.path = `/sys/class/hwmon/hwmon${currentIndex}/name`; + gpuTempNameReader.reload(); + } + + onLoaded: { + const name = text().trim(); + if (root.supportedTempGpuSensorNames.includes(name)) { + // Collect this GPU sensor, don't stop - continue scanning for more + const hwmonPath = `/sys/class/hwmon/hwmon${currentIndex}`; + const gpuType = name === "amdgpu" ? "amd" : "intel"; + root.foundGpuSensors.push({ + "hwmonPath": hwmonPath, + "type": gpuType, + "hasDedicatedVram": false // Will be checked later for AMD + }); + Logger.d("SystemStat", `Found ${name} GPU sensor at ${hwmonPath}`); + } + // Continue scanning regardless of whether we found a match + currentIndex++; + Qt.callLater(() => { + checkNext(); + }); + } + + onLoadFailed: function (error) { + currentIndex++; + Qt.callLater(() => { + checkNext(); + }); + } + } + + // ---- + // #2 - Read GPU sensor value (AMD/Intel via sysfs) + FileView { + id: gpuTempReader + printErrors: false + + onLoaded: { + const data = text().trim(); + root.gpuTemp = Math.round(parseInt(data) / 1000.0); + root.pushGpuHistory(); + } + } + + // ---- + // #3 - Check if nvidia-smi is available (for NVIDIA GPUs) + Process { + id: nvidiaSmiCheck + command: ["sh", "-c", "command -v nvidia-smi"] + running: false + stdout: StdioCollector { + onStreamFinished: { + if (text.trim().length > 0) { + // Add NVIDIA as a GPU option (always discrete, highest priority) + root.foundGpuSensors.push({ + "hwmonPath": "", + "type": "nvidia", + "hasDedicatedVram": true // NVIDIA is always discrete + }); + Logger.d("SystemStat", "Found NVIDIA GPU (nvidia-smi available)"); + } + // After NVIDIA check, check VRAM for AMD GPUs to distinguish dGPU from iGPU + root.gpuVramCheckIndex = 0; + checkNextGpuVram(); + } + } + } + + // ---- + // #4 - Check VRAM for AMD GPUs to distinguish dGPU from iGPU + // dGPUs have dedicated VRAM, iGPUs don't (use system RAM) + FileView { + id: gpuVramChecker + printErrors: false + + onLoaded: { + // File exists and has content = dGPU with dedicated VRAM + const vramSize = parseInt(text().trim()); + if (vramSize > 0) { + root.foundGpuSensors[root.gpuVramCheckIndex].hasDedicatedVram = true; + Logger.d("SystemStat", `GPU at ${root.foundGpuSensors[root.gpuVramCheckIndex].hwmonPath} has dedicated VRAM (dGPU)`); + } + root.gpuVramCheckIndex++; + Qt.callLater(() => { + checkNextGpuVram(); + }); + } + + onLoadFailed: function (error) { + // File doesn't exist = iGPU (no dedicated VRAM) + // hasDedicatedVram is already false by default + root.gpuVramCheckIndex++; + Qt.callLater(() => { + checkNextGpuVram(); + }); + } + } + + // ---- + // #4 - Read GPU temperature via nvidia-smi (NVIDIA only) + Process { + id: nvidiaTempProcess + command: ["nvidia-smi", "--query-gpu=temperature.gpu", "--format=csv,noheader,nounits"] + running: false + stdout: StdioCollector { + onStreamFinished: { + const temp = parseInt(text.trim()); + if (!isNaN(temp)) { + root.gpuTemp = temp; + root.pushGpuHistory(); + } + } + } + } + + // ------------------------------------------------------- + // ------------------------------------------------------- + // Parse ZFS ARC stats from /proc/spl/kstat/zfs/arcstats + function parseZfsArcStats(text) { + if (!text) + return; + const lines = text.split('\n'); + + // The file format is: name type data + // We need to find the lines with "size" and "c_min" and extract the values (third column) + let foundSize = false; + let foundCmin = false; + + for (const line of lines) { + const parts = line.trim().split(/\s+/); + if (parts.length >= 3) { + if (parts[0] === 'size') { + // The value is in bytes, convert to KB + const arcSizeBytes = parseInt(parts[2]) || 0; + root.zfsArcSizeKb = Math.floor(arcSizeBytes / 1024); + foundSize = true; + } else if (parts[0] === 'c_min') { + // The value is in bytes, convert to KB + const arcCminBytes = parseInt(parts[2]) || 0; + root.zfsArcCminKb = Math.floor(arcCminBytes / 1024); + foundCmin = true; + } + + // If we found both, we can return early + if (foundSize && foundCmin) { + return; + } + } + } + + // If fields not found, set to 0 + if (!foundSize) { + root.zfsArcSizeKb = 0; + } + if (!foundCmin) { + root.zfsArcCminKb = 0; + } + } + + // ------------------------------------------------------- + // Parse load average from /proc/loadavg + function parseLoadAverage(text) { + if (!text) + return; + const parts = text.trim().split(/\s+/); + if (parts.length >= 3) { + root.loadAvg1 = parseFloat(parts[0]); + root.loadAvg5 = parseFloat(parts[1]); + root.loadAvg15 = parseFloat(parts[2]); + } + } + + // ------------------------------------------------------- + // Parse memory info from /proc/meminfo + function parseMemoryInfo(text) { + if (!text) + return; + const lines = text.split('\n'); + let memTotal = 0; + let memAvailable = 0; + let swapTotal = 0; + let swapFree = 0; + + for (const line of lines) { + if (line.startsWith('MemTotal:')) { + memTotal = parseInt(line.split(/\s+/)[1]) || 0; + } else if (line.startsWith('MemAvailable:')) { + memAvailable = parseInt(line.split(/\s+/)[1]) || 0; + } else if (line.startsWith('SwapTotal:')) { + swapTotal = parseInt(line.split(/\s+/)[1]) || 0; + } else if (line.startsWith('SwapFree:')) { + swapFree = parseInt(line.split(/\s+/)[1]) || 0; + } + } + + if (memTotal > 0) { + // Calculate usage, adjusting for ZFS ARC cache if present + let usageKb = memTotal - memAvailable; + if (root.zfsArcSizeKb > 0) { + usageKb = Math.max(0, usageKb - root.zfsArcSizeKb + root.zfsArcCminKb); + } + root.memGb = (usageKb / 1048576).toFixed(1); // 1024*1024 = 1048576 + root.memPercent = Math.round((usageKb / memTotal) * 100); + root.memTotalGb = (memTotal / 1048576).toFixed(1); + root.pushMemHistory(); + } + + // Swap usage + root.swapTotalGb = (swapTotal / 1048576).toFixed(1); + if (swapTotal > 0) { + const swapUsedKb = swapTotal - swapFree; + root.swapGb = (swapUsedKb / 1048576).toFixed(1); + root.swapPercent = Math.round((swapUsedKb / swapTotal) * 100); + } else { + root.swapGb = 0; + root.swapPercent = 0; + } + } + + // ------------------------------------------------------- + // Calculate CPU usage from /proc/stat + + function calculateLineUsage(line) { + const parts = line.split(/\s+/); + const stats = { + "user": parseInt(parts[1]) || 0, + "nice": parseInt(parts[2]) || 0, + "system": parseInt(parts[3]) || 0, + "idle": parseInt(parts[4]) || 0, + "iowait": parseInt(parts[5]) || 0, + "irq": parseInt(parts[6]) || 0, + "softirq": parseInt(parts[7]) || 0, + "steal": parseInt(parts[8]) || 0, + "guest": parseInt(parts[9]) || 0, + "guestNice": parseInt(parts[10]) || 0 + }; + return stats; + } + + function computeUsage(prev, curr) { + if (!prev || !curr) + return -1; + const currTotalIdle = curr.idle + curr.iowait; + const currTotal = Object.values(curr).reduce((sum, val) => sum + val, 0); + const prevTotalIdle = prev.idle + prev.iowait; + const prevTotal = Object.values(prev).reduce((sum, val) => sum + val, 0); + + const diffTotal = currTotal - prevTotal; + const diffIdle = currTotalIdle - prevTotalIdle; + if (diffTotal > 0) { + return (((diffTotal - diffIdle) / diffTotal) * 100).toFixed(1); + } + return -1; + } + + function calculateCpuUsage(text) { + if (!text) + return; + const lines = text.split('\n'); + const cpuLine = lines[0]; + + // First line is total CPU + if (!cpuLine.startsWith('cpu ')) + return; + + const currCpuStats = calculateLineUsage(cpuLine); + const usage = computeUsage(root.prevCpuStats, currCpuStats); + + if (usage >= 0) { + root.cpuUsage = usage; + root.pushCpuHistory(); + } + root.prevCpuStats = currCpuStats; + + // Find the number of CPU cores + let nbCores = 0; + for (let i = 1; i < lines.length; i++) { + if (!lines[i].startsWith('cpu')) + break; + nbCores++; + } + + // Fallback if we did not find any cores + if (nbCores === 0) + return; + + // If we found more cores than before, we reset our stats + if (root.coresUsage.length < nbCores) + root.coresUsage = new Array(nbCores).fill(0); + + let coresStats = []; + let newCoresUsage = root.coresUsage.slice(); + for (let i = 0; i < nbCores; i++) { + const coreCpuLine = lines[i + 1]; + const currCoreStats = calculateLineUsage(coreCpuLine); + const coreUsage = computeUsage(root.prevCpuCoresStats?.[i], currCoreStats); + if (coreUsage >= 0) { + newCoresUsage[i] = coreUsage; + } + coresStats.push(currCoreStats); + } + root.coresUsage = newCoresUsage; + root.prevCpuCoresStats = coresStats; + } + + // ------------------------------------------------------- + // Check whether a network interface is virtual/tunnel/bridge. + // Only physical interfaces (eth*, en*, wl*, ww*) are kept so + // that traffic routed through VPNs, Docker bridges, etc. is + // not double-counted. + readonly property var _virtualPrefixes: ["lo", "docker", "veth", "br-", "virbr", "vnet", "tun", "tap", "wg", "tailscale", "nordlynx", "proton", "mullvad", "flannel", "cni", "cali", "vxlan", "genev", "gre", "sit", "ip6tnl", "dummy", "ifb", "nlmon", "bond"] + + function isVirtualInterface(name) { + for (let i = 0; i < _virtualPrefixes.length; ++i) { + if (name.startsWith(_virtualPrefixes[i])) + return true; + } + return false; + } + + // ------------------------------------------------------- + // Calculate RX and TX speed from /proc/net/dev + // Sums speeds of all physical interfaces + function calculateNetworkSpeed(text) { + if (!text) { + return; + } + + const currentTime = Date.now() / 1000; + const lines = text.split('\n'); + + let totalRx = 0; + let totalTx = 0; + + for (var i = 2; i < lines.length; i++) { + const line = lines[i].trim(); + if (!line) { + continue; + } + + const colonIndex = line.indexOf(':'); + if (colonIndex === -1) { + continue; + } + + const iface = line.substring(0, colonIndex).trim(); + if (isVirtualInterface(iface)) { + continue; + } + + const statsLine = line.substring(colonIndex + 1).trim(); + const stats = statsLine.split(/\s+/); + + const rxBytes = parseInt(stats[0], 10) || 0; + const txBytes = parseInt(stats[8], 10) || 0; + + totalRx += rxBytes; + totalTx += txBytes; + } + + // Compute only if we have a previous run to compare to. + if (root.prevTime > 0) { + const timeDiff = currentTime - root.prevTime; + + // Avoid division by zero if time hasn't passed. + if (timeDiff > 0) { + let rxDiff = totalRx - root.prevRxBytes; + let txDiff = totalTx - root.prevTxBytes; + + // Handle counter resets (e.g., WiFi reconnect), which would cause a negative value. + if (rxDiff < 0) { + rxDiff = 0; + } + if (txDiff < 0) { + txDiff = 0; + } + + root.rxSpeed = Math.round(rxDiff / timeDiff); // Speed in Bytes/s + root.txSpeed = Math.round(txDiff / timeDiff); + } + } + + root.prevRxBytes = totalRx; + root.prevTxBytes = totalTx; + root.prevTime = currentTime; + + // Update network history after speeds are computed + root.pushNetworkHistory(); + } + + // ------------------------------------------------------- + // Helper function to format network speeds + function formatSpeed(bytesPerSecond) { + const units = ["KB", "MB", "GB"]; + let value = bytesPerSecond / 1000; + let unitIndex = 0; + + while (value >= 1000 && unitIndex < units.length - 1) { + value /= 1000; + unitIndex++; + } + + const unit = units[unitIndex]; + const shortUnit = unit[0]; + const numStr = value < 10 ? value.toFixed(1) : Math.round(value).toString(); + + return (numStr + unit).length > 5 ? numStr + shortUnit : numStr + unit; + } + + // ------------------------------------------------------- + // Compact speed formatter for vertical bar display + function formatCompactSpeed(bytesPerSecond) { + if (!bytesPerSecond || bytesPerSecond <= 0) + return "0"; + const units = ["", "K", "M", "G"]; + let value = bytesPerSecond; + let unitIndex = 0; + while (value >= 1000 && unitIndex < units.length - 1) { + value = value / 1000.0; + unitIndex++; + } + // Promote at ~100 of current unit (e.g., 100k -> ~0.1M shown as 0.1M or 0M if rounded) + if (unitIndex < units.length - 1 && value >= 100) { + value = value / 1000.0; + unitIndex++; + } + const display = Math.round(value).toString(); + return display + units[unitIndex]; + } + + // ------------------------------------------------------- + // Smart formatter for memory values (GB) - max 4 chars + // Uses decimal for < 10GB, integer otherwise + function formatGigabytes(memGb) { + const value = parseFloat(memGb); + if (isNaN(value)) + return "0G"; + + if (value < 10) + return value.toFixed(1) + "G"; // "0.0G" to "9.9G" + return Math.round(value) + "G"; // "10G" to "999G" + } + + // ------------------------------------------------------- + // Formatting gigabytes with optional padding + function formatGigabytesDisplay(memGb, maxGb = null) { + const value = formatGigabytes(memGb === null ? 0 : memGb); + if (maxGb !== null) { + const padding = Math.max(4, formatGigabytes(maxGb).length); + return value.padStart(padding, " "); + } + return value; + } + + // ------------------------------------------------------- + // Formatting percentage with optional padding + function formatPercentageDisplay(value, padding = false) { + return `${Math.round(value === null ? 0 : value)}%`.padStart(padding ? 4 : 0, " "); + } + + // ------------------------------------------------------- + // Formatting disk usage + function formatDiskDisplay(diskPath, { + percent = false, + available = false, + padding = false +} = {}) { + if (percent) { + const raw = available ? root.diskAvailPercents[diskPath] : root.diskPercents[diskPath]; + return formatPercentageDisplay(raw, padding); + } else { + const rawGb = available ? root.diskAvailableGb[diskPath] : root.diskUsedGb[diskPath]; + const maxGb = padding ? root.diskSizeGb[diskPath] : null; + return formatGigabytesDisplay(rawGb, maxGb); + } + } + + // ------------------------------------------------------- + // Formatting ram usage + function formatRamDisplay({ + swap = false, + percent = false, + padding = false +} = {}) { + if (percent) { + const raw = swap ? swapPercent : memPercent; + return formatPercentageDisplay(raw, padding); + } else { + const rawGb = swap ? swapGb : memGb; + const maxGb = padding ? (swap ? swapTotalGb : memTotalGb) : null; + return formatGigabytesDisplay(rawGb, maxGb); + } + } + + // ------------------------------------------------------- + // Function to start fetching and computing the cpu temperature + function updateCpuTemperature() { + // For AMD sensors (k10temp and zenpower), only use Tctl sensor + // temp1_input corresponds to Tctl (Temperature Control) on these sensors + if (root.cpuTempSensorName === "k10temp" || root.cpuTempSensorName === "zenpower") { + cpuTempReader.path = `${root.cpuTempHwmonPath}/temp1_input`; + cpuTempReader.reload(); + } // For Intel coretemp, start averaging all available sensors/cores + else if (root.cpuTempSensorName === "coretemp") { + root.intelTempValues = []; + root.intelTempFilesChecked = 0; + checkNextIntelTemp(); + } // For thermal_zone fallback (ARM SoCs, SCMI, etc.), read all CPU zones and take max + else if (root.cpuTempSensorName === "thermal_zone") { + cpuThermalZoneReader.currentZoneIndex = 0; + cpuThermalZoneReader.collectedTemps = []; + readNextCpuThermalZone(); + } + } + + // ------------------------------------------------------- + // Function to check next Intel temperature sensor + function checkNextIntelTemp() { + if (root.intelTempFilesChecked >= root.intelTempMaxFiles) { + // Calculate average of all found temperatures + if (root.intelTempValues.length > 0) { + let sum = 0; + for (var i = 0; i < root.intelTempValues.length; i++) { + sum += root.intelTempValues[i]; + } + root.cpuTemp = Math.round(sum / root.intelTempValues.length); + root.pushCpuTempHistory(); + //Logger.i("SystemStat", `Averaged ${root.intelTempValues.length} CPU thermal sensors: ${root.cpuTemp}ยฐC`) + } else { + Logger.w("SystemStat", "No temperature sensors found for coretemp"); + root.cpuTemp = 0; + root.pushCpuTempHistory(); + } + return; + } + + // Check next temperature file + root.intelTempFilesChecked++; + cpuTempReader.path = `${root.cpuTempHwmonPath}/temp${root.intelTempFilesChecked}_input`; + cpuTempReader.reload(); + } + + // ------------------------------------------------------- + // Function to check VRAM for each AMD GPU to determine if it's a dGPU + function checkNextGpuVram() { + // Skip non-AMD GPUs (NVIDIA and Intel Arc are always discrete) + while (root.gpuVramCheckIndex < root.foundGpuSensors.length) { + const gpu = root.foundGpuSensors[root.gpuVramCheckIndex]; + if (gpu.type === "amd") { + // Check for dedicated VRAM at hwmonPath/device/mem_info_vram_total + gpuVramChecker.path = `${gpu.hwmonPath}/device/mem_info_vram_total`; + gpuVramChecker.reload(); + return; + } + // Skip non-AMD GPUs + root.gpuVramCheckIndex++; + } + + // All VRAM checks complete, now select the best GPU + selectBestGpu(); + } + + // ------------------------------------------------------- + // Function to select the best GPU based on priority + // Priority (when dGPU monitoring enabled): NVIDIA > AMD dGPU > Intel Arc > AMD iGPU + // Priority (when dGPU monitoring disabled): AMD iGPU only (discrete GPUs skipped to preserve D3cold) + function selectBestGpu() { + const dgpuEnabled = Settings.data.systemMonitor.enableDgpuMonitoring; + + if (root.foundGpuSensors.length === 0) { + // No hwmon GPU sensors found, try thermal_zone fallback + if (dgpuEnabled && root.gpuThermalZonePath === "" && root.gpuThermalZonePaths.length === 0) { + // Thermal zone scanner hasn't found GPU zones yet; start a scan + thermalZoneScanner.startScan(); + } + return; + } + + let best = null; + + for (var i = 0; i < root.foundGpuSensors.length; i++) { + const gpu = root.foundGpuSensors[i]; + + // NVIDIA is always highest priority (always discrete) - skip if dGPU monitoring disabled + if (gpu.type === "nvidia") { + if (dgpuEnabled) { + best = gpu; + break; + } + continue; + } + + // AMD dGPU is second priority - skip if dGPU monitoring disabled (preserves D3cold power state) + if (gpu.type === "amd" && gpu.hasDedicatedVram) { + if (dgpuEnabled) { + best = gpu; + break; + } + continue; + } + + // Intel Arc is third priority (always discrete) - skip if dGPU monitoring disabled + if (gpu.type === "intel" && !best) { + if (dgpuEnabled) { + best = gpu; + } + continue; + } + + // AMD iGPU is lowest priority (fallback) - always allowed (no D3cold issue) + if (gpu.type === "amd" && !gpu.hasDedicatedVram && !best) { + best = gpu; + } + } + + if (best) { + root.gpuTempHwmonPath = best.hwmonPath; + root.gpuType = best.type; + root.gpuAvailable = true; + + const gpuDesc = best.type === "nvidia" ? "NVIDIA" : (best.type === "intel" ? "Intel Arc" : (best.hasDedicatedVram ? "AMD dGPU" : "AMD iGPU")); + Logger.i("SystemStat", `Selected ${gpuDesc} for temperature monitoring at ${best.hwmonPath || "nvidia-smi"}`); + } else if (!dgpuEnabled) { + Logger.d("SystemStat", "No iGPU found and dGPU monitoring is disabled"); + } + } + + // ------------------------------------------------------- + // Function to update GPU temperature + function updateGpuTemperature() { + if (root.gpuType === "nvidia") { + nvidiaTempProcess.running = true; + } else if (root.gpuType === "amd" || root.gpuType === "intel") { + gpuTempReader.path = `${root.gpuTempHwmonPath}/temp1_input`; + gpuTempReader.reload(); + } else if (root.gpuType === "thermal_zone") { + if (root.gpuThermalZonePaths && root.gpuThermalZonePaths.length > 0) { + // Multiple GPU zones (no gpu-avg), read all and take max + gpuThermalZoneReader.currentZoneIndex = 0; + gpuThermalZoneReader.collectedTemps = []; + readNextGpuThermalZone(); + } else { + // Single gpu-avg-thermal zone + gpuThermalZoneReader.path = root.gpuThermalZonePath; + gpuThermalZoneReader.reload(); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Theming/AppThemeService.qml b/arch/.config/quickshell/noctalia-shell/Services/Theming/AppThemeService.qml new file mode 100644 index 0000000..137e573 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Theming/AppThemeService.qml @@ -0,0 +1,90 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Commons +import qs.Services.UI + +Singleton { + id: root + + Connections { + target: WallpaperService + + // When the wallpaper changes, regenerate theme if necessary + function onWallpaperChanged(screenName, path) { + var effectiveMonitor = Settings.data.colorSchemes.monitorForColors; + if (effectiveMonitor === "" || effectiveMonitor === undefined) { + effectiveMonitor = Screen.name; + } + + if (screenName !== effectiveMonitor) + return; + + if (Settings.data.colorSchemes.useWallpaperColors) { + generateFromWallpaper(); + } else { + // Re-run predefined scheme templates so {{image}} reflects the new wallpaper path + ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme); + } + } + } + + Connections { + target: Settings.data.colorSchemes + function onDarkModeChanged() { + Logger.d("AppThemeService", "Detected dark mode change"); + generate(); + } + function onMonitorForColorsChanged() { + if (Settings.data.colorSchemes.useWallpaperColors) { + Logger.d("AppThemeService", "Monitor for colors changed to:", Settings.data.colorSchemes.monitorForColors); + generateFromWallpaper(); + } + } + function onGenerationMethodChanged() { + Logger.d("AppThemeService", "Generation method changed to:", Settings.data.colorSchemes.generationMethod); + generate(); + } + } + + // PUBLIC FUNCTIONS + function init() { + Logger.i("AppThemeService", "Service started"); + } + + function generate() { + if (Settings.data.colorSchemes.useWallpaperColors) { + generateFromWallpaper(); + } else { + // applyScheme will trigger template generation via schemeReader.onLoaded + ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme); + } + } + + function generateFromWallpaper() { + var effectiveMonitor = Settings.data.colorSchemes.monitorForColors; + if (effectiveMonitor === "" || effectiveMonitor === undefined) { + effectiveMonitor = Screen.name; + } + + const wp = WallpaperService.getWallpaper(effectiveMonitor); + if (!wp) { + Logger.e("AppThemeService", "No wallpaper found for monitor:", effectiveMonitor); + return; + } + const mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"; + TemplateProcessor.processWallpaperColors(wp, mode); + } + + function generateFromPredefinedScheme(schemeData) { + Logger.i("AppThemeService", "Generating templates from predefined color scheme"); + const mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"; + var effectiveMonitor = Settings.data.colorSchemes.monitorForColors; + if (effectiveMonitor === "" || effectiveMonitor === undefined) { + effectiveMonitor = Screen.name; + } + const wallpaperPath = WallpaperService.getWallpaper(effectiveMonitor) || ""; + TemplateProcessor.processPredefinedScheme(schemeData, mode, wallpaperPath); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Theming/ColorSchemeService.qml b/arch/.config/quickshell/noctalia-shell/Services/Theming/ColorSchemeService.qml new file mode 100644 index 0000000..9348fe4 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Theming/ColorSchemeService.qml @@ -0,0 +1,286 @@ +pragma Singleton +import Qt.labs.folderlistmodel + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.Theming +import qs.Services.UI + +Singleton { + id: root + + property var schemes: [] + property bool scanning: false + property string schemesDirectory: Quickshell.shellDir + "/Assets/ColorScheme" + property string downloadedSchemesDirectory: Settings.configDir + "colorschemes" + property string colorsJsonFilePath: Settings.configDir + "colors.json" + readonly property string gtkRefreshScript: Quickshell.shellDir + "/Scripts/python/src/theming/gtk-refresh.py" + + // prefer-light/prefer-dark only; GTK template post_hook still runs full gtk-refresh. + function pushSystemColorScheme() { + if (!Settings.data.colorSchemes.syncGsettings) + return; + const mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"; + Quickshell.execDetached(["python3", gtkRefreshScript, "--appearance-only", mode]); + } + + Connections { + target: Settings.data.colorSchemes + function onDarkModeChanged() { + Logger.d("ColorScheme", "Detected dark mode change"); + if (!Settings.data.colorSchemes.useWallpaperColors && Settings.data.colorSchemes.predefinedScheme) { + // Re-apply current scheme to pick the right variant + applyScheme(Settings.data.colorSchemes.predefinedScheme); + } + root.pushSystemColorScheme(); + // Toast: dark/light mode switched + const enabled = !!Settings.data.colorSchemes.darkMode; + const label = enabled ? I18n.tr("tooltips.switch-to-dark-mode") : I18n.tr("tooltips.switch-to-light-mode"); + const description = I18n.tr("common.enabled"); + ToastService.showNotice(label, description, "dark-mode"); + } + } + + // -------------------------------- + function init() { + // does nothing but ensure the singleton is created + // do not remove + Logger.i("ColorScheme", "Service started"); + loadColorSchemes(); + } + + function loadColorSchemes() { + Logger.d("ColorScheme", "Load colorScheme"); + scanning = true; + schemes = []; + // Use find command to locate all scheme.json files in both directories + // First ensure the downloaded schemes directory exists + Quickshell.execDetached(["mkdir", "-p", downloadedSchemesDirectory]); + // Find in both preinstalled and downloaded directories + findProcess.command = ["find", "-L", schemesDirectory, downloadedSchemesDirectory, "-mindepth", "2", "-name", "*.json", "-type", "f"]; + findProcess.running = true; + } + + function getBasename(path) { + if (!path) + return ""; + var chunks = path.split("/"); + // Get the filename without extension + var filename = chunks[chunks.length - 1]; + var schemeName = filename.replace(".json", ""); + // Convert back to display names for special cases + if (schemeName === "Noctalia-default") { + return "Noctalia (default)"; + } else if (schemeName === "Noctalia-legacy") { + return "Noctalia (legacy)"; + } else if (schemeName === "Tokyo-Night") { + return "Tokyo Night"; + } else if (schemeName === "Rosepine") { + return "Rose Pine"; + } + return schemeName; + } + + function resolveSchemePath(nameOrPath) { + if (!nameOrPath) + return ""; + if (nameOrPath.indexOf("/") !== -1) { + return nameOrPath; + } + // Handle special cases for Noctalia schemes + var schemeName = nameOrPath.replace(".json", ""); + if (schemeName === "Noctalia (default)") { + schemeName = "Noctalia-default"; + } else if (schemeName === "Noctalia (legacy)") { + schemeName = "Noctalia-legacy"; + } else if (schemeName === "Tokyo Night") { + schemeName = "Tokyo-Night"; + } else if (schemeName === "Rose Pine") { + schemeName = "Rosepine"; + } + // Check preinstalled directory first, then downloaded directory + var preinstalledPath = schemesDirectory + "/" + schemeName + "/" + schemeName + ".json"; + var downloadedPath = downloadedSchemesDirectory + "/" + schemeName + "/" + schemeName + ".json"; + // Try to find the scheme in the loaded schemes list to determine which directory it's in + for (var i = 0; i < schemes.length; i++) { + if (schemes[i].indexOf("/" + schemeName + "/") !== -1 || schemes[i].indexOf("/" + schemeName + ".json") !== -1) { + return schemes[i]; + } + } + // Fallback: prefer preinstalled, then downloaded + return preinstalledPath; + } + + function applyScheme(nameOrPath) { + // Force reload by bouncing the path + var filePath = resolveSchemePath(nameOrPath); + schemeReader.path = ""; + schemeReader.path = filePath; + } + + function setPredefinedScheme(schemeName) { + Logger.i("ColorScheme", "Attempting to set predefined scheme to:", schemeName); + + var resolvedPath = resolveSchemePath(schemeName); + var basename = getBasename(schemeName); + + // Check if the scheme actually exists in the loaded schemes list + var schemeExists = false; + for (var i = 0; i < schemes.length; i++) { + if (getBasename(schemes[i]) === basename) { + schemeExists = true; + break; + } + } + + if (schemeExists) { + Settings.data.colorSchemes.predefinedScheme = basename; + applyScheme(schemeName); + ToastService.showNotice(I18n.tr("panels.color-scheme.title"), basename, "settings-color-scheme"); + } else { + Logger.e("ColorScheme", "Scheme not found:", schemeName); + ToastService.showError(I18n.tr("panels.color-scheme.title"), `'${basename}' ` + I18n.tr("common.not-found")); + } + } + + Process { + id: findProcess + running: false + + onExited: function (exitCode) { + if (exitCode === 0) { + var output = stdout.text.trim(); + var files = output.split('\n').filter(function (line) { + return line.length > 0; + }); + files.sort(function (a, b) { + var nameA = getBasename(a).toLowerCase(); + var nameB = getBasename(b).toLowerCase(); + return nameA.localeCompare(nameB); + }); + schemes = files; + scanning = false; + Logger.d("ColorScheme", "Listed", schemes.length, "schemes"); + // Normalize stored scheme to basename and re-apply if necessary + var stored = Settings.data.colorSchemes.predefinedScheme; + if (stored) { + var basename = getBasename(stored); + if (basename !== stored) { + Settings.data.colorSchemes.predefinedScheme = basename; + } + if (!Settings.data.colorSchemes.useWallpaperColors) { + applyScheme(basename); + } + } + } else { + Logger.e("ColorScheme", "Failed to find color scheme files"); + schemes = []; + scanning = false; + } + } + + stdout: StdioCollector {} + stderr: StdioCollector {} + } + + // Internal loader to read a scheme file + FileView { + id: schemeReader + onLoaded: { + try { + var data = JSON.parse(text()); + var variant = data; + // If scheme provides dark/light variants, pick based on settings + if (data && (data.dark || data.light)) { + if (Settings.data.colorSchemes.darkMode) { + variant = data.dark || data.light; + } else { + variant = data.light || data.dark; + } + } + writeColorsToDisk(variant); + Logger.i("ColorScheme", "Applying color scheme:", getBasename(path)); + + // Generate templates for predefined color schemes + if (hasEnabledTemplates() || Settings.data.templates.enableUserTheming) { + AppThemeService.generateFromPredefinedScheme(data); + } + } catch (e) { + Logger.e("ColorScheme", "Failed to parse scheme JSON:", path, e); + } + } + } + + // Check if any templates are enabled + function hasEnabledTemplates() { + const activeTemplates = Settings.data.templates.activeTemplates; + if (!activeTemplates || activeTemplates.length === 0) { + return false; + } + for (let i = 0; i < activeTemplates.length; i++) { + if (activeTemplates[i].enabled) { + return true; + } + } + return false; + } + + // Writer to colors.json using a JsonAdapter for safety + FileView { + id: colorsWriter + path: colorsJsonFilePath + printErrors: false + onSaved: + + // Logger.i("ColorScheme", "Colors saved") + {} + JsonAdapter { + id: out + property color mPrimary: "#000000" + property color mOnPrimary: "#000000" + property color mSecondary: "#000000" + property color mOnSecondary: "#000000" + property color mTertiary: "#000000" + property color mOnTertiary: "#000000" + property color mError: "#000000" + property color mOnError: "#000000" + property color mSurface: "#000000" + property color mOnSurface: "#000000" + property color mSurfaceVariant: "#000000" + property color mOnSurfaceVariant: "#000000" + property color mOutline: "#000000" + property color mShadow: "#000000" + property color mHover: "#000000" + property color mOnHover: "#000000" + } + } + + function writeColorsToDisk(obj) { + function pick(o, a, b, fallback) { + return (o && (o[a] || o[b])) || fallback; + } + out.mPrimary = pick(obj, "mPrimary", "primary", out.mPrimary); + out.mOnPrimary = pick(obj, "mOnPrimary", "onPrimary", out.mOnPrimary); + out.mSecondary = pick(obj, "mSecondary", "secondary", out.mSecondary); + out.mOnSecondary = pick(obj, "mOnSecondary", "onSecondary", out.mOnSecondary); + out.mTertiary = pick(obj, "mTertiary", "tertiary", out.mTertiary); + out.mOnTertiary = pick(obj, "mOnTertiary", "onTertiary", out.mOnTertiary); + out.mError = pick(obj, "mError", "error", out.mError); + out.mOnError = pick(obj, "mOnError", "onError", out.mOnError); + out.mSurface = pick(obj, "mSurface", "surface", out.mSurface); + out.mOnSurface = pick(obj, "mOnSurface", "onSurface", out.mOnSurface); + out.mSurfaceVariant = pick(obj, "mSurfaceVariant", "surfaceVariant", out.mSurfaceVariant); + out.mOnSurfaceVariant = pick(obj, "mOnSurfaceVariant", "onSurfaceVariant", out.mOnSurfaceVariant); + out.mOutline = pick(obj, "mOutline", "outline", out.mOutline); + out.mShadow = pick(obj, "mShadow", "shadow", out.mShadow); + out.mHover = pick(obj, "mHover", "hover", out.mHover); + out.mOnHover = pick(obj, "mOnHover", "onHover", out.mOnHover); + + // Force a rewrite by updating the path + colorsWriter.path = ""; + colorsWriter.path = colorsJsonFilePath; + colorsWriter.writeAdapter(); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Theming/TemplateProcessor.qml b/arch/.config/quickshell/noctalia-shell/Services/Theming/TemplateProcessor.qml new file mode 100644 index 0000000..0339f38 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Theming/TemplateProcessor.qml @@ -0,0 +1,475 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io + +import qs.Commons +import qs.Services.System +import qs.Services.Theming +import qs.Services.UI + +Singleton { + id: root + + // Signal emitted when color generation completes successfully (for wallpaper-based theming) + signal colorsGenerated + + readonly property string dynamicConfigPath: Settings.cacheDir + "theming.dynamic.toml" + readonly property string templateProcessorScript: Quickshell.shellDir + "/Scripts/python/src/theming/template-processor.py" + + // Debounce state for wallpaper processing + property var pendingWallpaperRequest: null + property var pendingPredefinedRequest: null + + readonly property var schemeTypes: [ + { + "key": "tonal-spot", + "name": "M3-Tonal Spot" // Do not translate + }, + { + "key": "content", + "name": "M3-Content" // Do not translate + }, + { + "key": "fruit-salad", + "name": "M3-Fruit Salad" // Do not translate + }, + { + "key": "rainbow", + "name": "M3-Rainbow" // Do not translate + }, + { + "key": "monochrome", + "name": "M3-Monochrome" // Do not translate + }, + { + "key": "vibrant", + "name": I18n.tr("common.vibrant") + }, + { + "key": "faithful", + "name": I18n.tr("common.faithful") + }, + { + "key": "dysfunctional", + "name": I18n.tr("common.dysfunctional") + }, + { + "key": "muted", + "name": I18n.tr("common.color-muted") + }, + ] + + // Check if a template is enabled in the activeTemplates array + function isTemplateEnabled(templateId) { + const activeTemplates = Settings.data.templates.activeTemplates; + if (!activeTemplates) + return false; + for (let i = 0; i < activeTemplates.length; i++) { + if (activeTemplates[i].id === templateId && activeTemplates[i].enabled) { + return true; + } + } + return false; + } + + function escapeTomlString(value) { + if (!value) + return ""; + return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); + } + + /** + * Process wallpaper colors using internal themer + * Dual-path architecture (wallpaper generation) + * Uses debouncing to prevent spawning multiple processes when spamming wallpaper changes + */ + function processWallpaperColors(wallpaperPath, mode) { + Logger.d("TemplateProcessor", `processWallpaperColors called: path=${wallpaperPath}, mode=${mode}`); + pendingWallpaperRequest = { + wallpaperPath: wallpaperPath, + mode: mode + }; + pendingPredefinedRequest = null; + debounceTimer.restart(); + } + + function executeWallpaperColors(wallpaperPath, mode) { + Logger.d("TemplateProcessor", `executeWallpaperColors: path=${wallpaperPath}, mode=${mode}`); + const content = buildThemeConfig(); + if (!content && !Settings.data.templates.enableUserTheming) { + Logger.d("TemplateProcessor", "executeWallpaperColors: no config content and no user theming, aborting"); + return; + } + const script = buildGenerationScript(content, wallpaperPath, mode); + + generateProcess.command = ["sh", "-c", script]; + generateProcess.running = true; + } + + readonly property string schemeJsonPath: Settings.cacheDir + "predefined-scheme.json" + readonly property string predefinedConfigPath: Settings.cacheDir + "theming.predefined.toml" + + /** + * Process predefined color scheme using Python template processor + * Uses --scheme flag to expand 14-color scheme to full 48-color palette + * Uses debouncing to prevent spawning multiple processes when spamming scheme changes + */ + function processPredefinedScheme(schemeData, mode, wallpaperPath) { + pendingPredefinedRequest = { + schemeData: schemeData, + mode: mode, + wallpaperPath: wallpaperPath || "" + }; + pendingWallpaperRequest = null; + debounceTimer.restart(); + } + + function executePredefinedScheme(schemeData, mode, wallpaperPath) { + // 1. Build TOML config for application templates (including terminals) + const tomlContent = buildPredefinedTemplateConfig(mode); + if (!tomlContent && !Settings.data.templates.enableUserTheming) { + Logger.d("TemplateProcessor", "No application templates enabled for predefined scheme"); + return; + } + + // 3. Build script to write files and run Python + const schemeJsonPathEsc = schemeJsonPath.replace(/'/g, "'\\''"); + + let script = ""; + + // Write scheme JSON (needed by both built-in and user templates) + const schemeDelimiter = "SCHEME_JSON_EOF_" + Math.random().toString(36).substr(2, 9); + script += `cat > '${schemeJsonPathEsc}' << '${schemeDelimiter}'\n`; + script += JSON.stringify(schemeData, null, 2) + "\n"; + script += `${schemeDelimiter}\n`; + + // Run built-in template processor only if there are templates configured + if (tomlContent) { + const configPathEsc = predefinedConfigPath.replace(/'/g, "'\\''"); + const tomlDelimiter = "TOML_CONFIG_EOF_" + Math.random().toString(36).substr(2, 9); + + // Write TOML config + script += `cat > '${configPathEsc}' << '${tomlDelimiter}'\n`; + script += tomlContent + "\n"; + script += `${tomlDelimiter}\n`; + + // Run Python template processor with --scheme flag + // Don't pass --mode so templates get both dark and light colors (e.g., zed.json needs both) + // Pass --default-mode so "default" in templates resolves to the current theme mode + // Pass wallpaper as positional arg so image_path is available in templates (no extraction occurs when --scheme is used) + const wpArg = wallpaperPath ? `'${wallpaperPath.replace(/'/g, "'\\''")}'` : ""; + script += `python3 "${templateProcessorScript}" ${wpArg} --scheme '${schemeJsonPathEsc}' --config '${configPathEsc}' --default-mode ${mode}\n`; + } + + // Add user templates if enabled + script += buildUserTemplateCommandForPredefined(schemeData, mode, wallpaperPath); + + generateProcess.command = ["sh", "-c", script]; + generateProcess.running = true; + } + + /** + * Build TOML config for predefined scheme templates (excludes terminal themes) + */ + function buildPredefinedTemplateConfig(mode) { + var lines = []; + const homeDir = Quickshell.env("HOME"); + + // Add terminal templates + TemplateRegistry.terminals.forEach(terminal => { + if (isTemplateEnabled(terminal.id)) { + lines.push(`\n[templates.${terminal.id}]`); + lines.push(`input_path = "${Quickshell.shellDir}/Assets/Templates/${terminal.predefinedTemplatePath}"`); + const outputPath = terminal.outputPath.replace("~", homeDir); + lines.push(`output_path = "${outputPath}"`); + const postHookEsc = escapeTomlString(terminal.postHook); + lines.push(`post_hook = "${postHookEsc}"`); + } + }); + + addApplicationTheming(lines, mode); + + if (lines.length > 0) { + return ["[config]"].concat(lines).join("\n") + "\n"; + } + return ""; + } + + // ================================================================================ + // WALLPAPER-BASED GENERATION + // ================================================================================ + function buildThemeConfig() { + var lines = []; + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"; + + if (Settings.data.colorSchemes.useWallpaperColors) { + addWallpaperTheming(lines, mode); + } + + addApplicationTheming(lines, mode); + + if (lines.length > 0) { + return ["[config]"].concat(lines).join("\n") + "\n"; + } + return ""; + } + + function addWallpaperTheming(lines, mode) { + const homeDir = Quickshell.env("HOME"); + // Noctalia colors JSON + lines.push("[templates.noctalia]"); + lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Templates/noctalia.json"'); + lines.push('output_path = "' + Settings.configDir + 'colors.json"'); + + // Terminal templates + TemplateRegistry.terminals.forEach(terminal => { + if (isTemplateEnabled(terminal.id)) { + lines.push(`\n[templates.${terminal.id}]`); + lines.push(`input_path = "${Quickshell.shellDir}/Assets/Templates/${terminal.templatePath}"`); + const outputPath = terminal.outputPath.replace("~", homeDir); + lines.push(`output_path = "${outputPath}"`); + const postHookEsc = escapeTomlString(terminal.postHook); + lines.push(`post_hook = "${postHookEsc}"`); + } + }); + } + + function addApplicationTheming(lines, mode) { + const homeDir = Quickshell.env("HOME"); + TemplateRegistry.applications.forEach(app => { + if (app.id === "discord") { + // Handle Discord clients specially - multiple CSS themes + if (isTemplateEnabled("discord")) { + const inputs = Array.isArray(app.input) ? app.input : [app.input]; + inputs.forEach((inputFile, idx) => { + // Derive theme suffix from input filename: discord-midnight.css โ†’ midnight + const themeSuffix = inputFile.replace(/^discord-/, "").replace(/\.css$/, ""); + app.clients.forEach(client => { + if (isDiscordClientEnabled(client.name)) { + lines.push(`\n[templates.discord_${themeSuffix}_${client.name}]`); + lines.push(`input_path = "${Quickshell.shellDir}/Assets/Templates/${inputFile}"`); + // First input uses legacy name for backward compatibility + const outputFile = idx === 0 ? "noctalia.theme.css" : `noctalia-${themeSuffix}.theme.css`; + const outputPath = client.path.replace("~", homeDir) + `/themes/${outputFile}`; + lines.push(`output_path = "${outputPath}"`); + } + }); + }); + } + } else if (app.id === "code") { + // Handle Code clients specially + if (isTemplateEnabled("code")) { + app.clients.forEach(client => { + // Check if this specific client is detected + var resolvedPaths = TemplateRegistry.resolvedCodeClientPaths(client.name); + if (isCodeClientEnabled(client.name) && resolvedPaths.length > 0) { + resolvedPaths.forEach((resolvedPath, pathIndex) => { + var suffix = resolvedPaths.length > 1 ? `_${pathIndex}` : ""; + lines.push(`\n[templates.code_${client.name}${suffix}]`); + lines.push(`input_path = "${Quickshell.shellDir}/Assets/Templates/${app.input}"`); + lines.push(`output_path = "${resolvedPath}"`); + }); + } + }); + } + } else if (app.id === "emacs") { + if (isTemplateEnabled("emacs")) { + ProgramCheckerService.availableEmacsClients.forEach(client => { + lines.push(`\n[templates.emacs_${client.name}]`); + lines.push(`input_path = "${Quickshell.shellDir}/Assets/Templates/${app.input}"`); + const expandedPath = client.path.replace("~", homeDir) + "/themes/noctalia-theme.el"; + lines.push(`output_path = "${expandedPath}"`); + if (app.postProcess) { + const postHook = escapeTomlString(app.postProcess(mode)); + lines.push(`post_hook = "${postHook}"`); + } + }); + } + } else { + // Handle regular apps + if (isTemplateEnabled(app.id)) { + app.outputs.forEach((output, idx) => { + lines.push(`\n[templates.${app.id}_${idx}]`); + const inputFile = output.input || app.input; + lines.push(`input_path = "${Quickshell.shellDir}/Assets/Templates/${inputFile}"`); + const outputPath = output.path.replace("~", homeDir); + lines.push(`output_path = "${outputPath}"`); + if (app.postProcess) { + const postHook = escapeTomlString(app.postProcess(mode)); + lines.push(`post_hook = "${postHook}"`); + } + }); + } + } + }); + } + + function isDiscordClientEnabled(clientName) { + // Check ProgramCheckerService to see if client is detected + for (var i = 0; i < ProgramCheckerService.availableDiscordClients.length; i++) { + if (ProgramCheckerService.availableDiscordClients[i].name === clientName) { + return true; + } + } + return false; + } + + function isCodeClientEnabled(clientName) { + // Check ProgramCheckerService to see if client is detected + for (var i = 0; i < ProgramCheckerService.availableCodeClients.length; i++) { + if (ProgramCheckerService.availableCodeClients[i].name === clientName) { + return true; + } + } + return false; + } + + // Get scheme type, defaulting to tonal-spot if not a recognized value + function getSchemeType() { + const method = Settings.data.colorSchemes.generationMethod; + const validKeys = root.schemeTypes.map(scheme => scheme.key); + return validKeys.includes(method) ? method : "tonal-spot"; + } + + function buildGenerationScript(content, wallpaper, mode) { + const pathEsc = dynamicConfigPath.replace(/'/g, "'\\''"); + const wpDelimiter = "WALLPAPER_PATH_EOF_" + Math.random().toString(36).substr(2, 9); + + // Use heredoc for wallpaper path to avoid all escaping issues + let script = `NOCTALIA_WP_PATH=$(cat << '${wpDelimiter}'\n${wallpaper}\n${wpDelimiter}\n)\n`; + + // Run built-in template processor only if there are templates configured + if (content) { + const delimiter = "THEME_CONFIG_EOF_" + Math.random().toString(36).substr(2, 9); + script += `cat > '${pathEsc}' << '${delimiter}'\n${content}\n${delimiter}\n`; + + // Use template-processor.py (Python implementation) + // Don't pass --mode so templates get both dark and light colors (e.g., zed.json needs both) + // Pass --default-mode so "default" in templates resolves to the current theme mode + const schemeType = getSchemeType(); + script += `python3 "${templateProcessorScript}" "$NOCTALIA_WP_PATH" --scheme-type ${schemeType} --config '${pathEsc}' --default-mode ${mode}\n`; + } + + script += buildUserTemplateCommand("$NOCTALIA_WP_PATH", mode); + + return script + "\n"; + } + + // ================================================================================ + // USER TEMPLATES, advanced usage + // ================================================================================ + function buildUserTemplateCommand(input, mode) { + if (!Settings.data.templates.enableUserTheming) + return ""; + + const userConfigPath = getUserConfigPath(); + let script = "\n# Execute user config if it exists\n"; + script += `if [ -f '${userConfigPath}' ]; then\n`; + // If input is a shell variable (starts with $), use double quotes to allow expansion + // Otherwise, use single quotes for safety with file paths + const inputQuoted = input.startsWith("$") ? `"${input}"` : `'${input.replace(/'/g, "'\\''")}'`; + + const schemeType = getSchemeType(); + // Don't pass --mode so user templates get both dark and light colors + // Pass --default-mode so "default" in templates resolves to the current theme mode + script += ` python3 "${templateProcessorScript}" ${inputQuoted} --scheme-type ${schemeType} --config '${userConfigPath}' --default-mode ${mode}\n`; + script += "fi"; + + return script; + } + + function buildUserTemplateCommandForPredefined(schemeData, mode, wallpaperPath) { + if (!Settings.data.templates.enableUserTheming) + return ""; + + const userConfigPath = getUserConfigPath(); + + // Reuse the scheme JSON already written by processPredefinedScheme() + const schemeJsonPathEsc = schemeJsonPath.replace(/'/g, "'\\''"); + const wpArg = wallpaperPath ? `'${wallpaperPath.replace(/'/g, "'\\''")}'` : ""; + + let script = "\n# Execute user templates with predefined scheme colors\n"; + script += `if [ -f '${userConfigPath}' ]; then\n`; + // Use --scheme flag with the already-written scheme JSON + // Don't pass --mode so user templates get both dark and light colors + // Pass --default-mode so "default" in templates resolves to the current theme mode + // Pass wallpaper as positional arg so image_path is available in templates + script += ` python3 "${templateProcessorScript}" ${wpArg} --scheme '${schemeJsonPathEsc}' --config '${userConfigPath}' --default-mode ${mode}\n`; + script += "fi"; + + return script; + } + + function getUserConfigPath() { + return (Settings.configDir + "user-templates.toml").replace(/'/g, "'\\''"); + } + + // ================================================================================ + // DEBOUNCE TIMER + // ================================================================================ + function executePendingRequest() { + Logger.d("TemplateProcessor", `executePendingRequest: hasWallpaper=${!!pendingWallpaperRequest}, hasPredefined=${!!pendingPredefinedRequest}`); + if (pendingWallpaperRequest) { + const req = pendingWallpaperRequest; + pendingWallpaperRequest = null; + executeWallpaperColors(req.wallpaperPath, req.mode); + } else if (pendingPredefinedRequest) { + const req = pendingPredefinedRequest; + pendingPredefinedRequest = null; + executePredefinedScheme(req.schemeData, req.mode, req.wallpaperPath); + } else { + Logger.d("TemplateProcessor", "executePendingRequest: no pending request"); + } + } + + Timer { + id: debounceTimer + interval: 150 + repeat: false + onTriggered: { + Logger.d("TemplateProcessor", `debounceTimer fired: processRunning=${generateProcess.running}`); + // Kill any running process before starting new one + if (generateProcess.running) { + Logger.d("TemplateProcessor", "debounceTimer: stopping running process"); + generateProcess.running = false; + // executePendingRequest will be called from onExited + } else { + executePendingRequest(); + } + } + } + + // ================================================================================ + // PROCESSES + // ================================================================================ + Process { + id: generateProcess + workingDirectory: Quickshell.shellDir + running: false + + onExited: function (exitCode, exitStatus) { + // Execute any pending request (handles both kill case and debounce timer interval case) + if (pendingWallpaperRequest || pendingPredefinedRequest) { + Logger.d("TemplateProcessor", "generateProcess onExited: has pending request, executing"); + executePendingRequest(); + } else if (exitCode === 0) { + // No pending request and successful completion - emit signal + root.colorsGenerated(); + } + } + + stderr: StdioCollector { + onStreamFinished: { + const text = this.text.trim(); + if (text && text.includes("Template error:")) { + const errorLines = text.split("\n").filter(l => l.includes("Template error:")); + const errors = errorLines.slice(0, 3).join("\n") + (errorLines.length > 3 ? `\n... (+${errorLines.length - 3} more)` : ""); + Logger.w("TemplateProcessor", errors); + ToastService.showWarning(I18n.tr("toast.theming-processor-failed.title"), errors); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/Theming/TemplateRegistry.qml b/arch/.config/quickshell/noctalia-shell/Services/Theming/TemplateRegistry.qml new file mode 100644 index 0000000..d9d022b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/Theming/TemplateRegistry.qml @@ -0,0 +1,619 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons + +Singleton { + id: root + + Component.onCompleted: { + if (Settings.data.templates.enableUserTheming) + writeUserTemplatesToml(); + } + + readonly property string templateApplyScript: Quickshell.shellDir + '/Scripts/bash/template-apply.sh' + readonly property string gtkRefreshScript: Quickshell.shellDir + '/Scripts/python/src/theming/gtk-refresh.py' + readonly property string vscodeHelperScript: Quickshell.shellDir + '/Scripts/python/src/theming/vscode-helper.py' + + // Dynamically resolved VSCode extension theme paths (all matching noctalia extensions) + property var resolvedCodePaths: [] + property var resolvedCodiumPaths: [] + + // Terminal configurations (for wallpaper-based templates) + // Each terminal must define a postHook that sets up config includes and triggers reload + readonly property var terminals: [ + { + "id": "foot", + "name": "Foot", + "templatePath": "terminal/foot", + "predefinedTemplatePath": "terminal/foot-predefined", + "outputPath": "~/.config/foot/themes/noctalia", + "postHook": `${templateApplyScript} foot` + }, + { + "id": "ghostty", + "name": "Ghostty", + "templatePath": "terminal/ghostty", + "predefinedTemplatePath": "terminal/ghostty-predefined", + "outputPath": "~/.config/ghostty/themes/noctalia", + "postHook": `${templateApplyScript} ghostty` + }, + { + "id": "kitty", + "name": "Kitty", + "templatePath": "terminal/kitty.conf", + "predefinedTemplatePath": "terminal/kitty-predefined.conf", + "outputPath": "~/.config/kitty/themes/noctalia.conf", + "postHook": `${templateApplyScript} kitty` + }, + { + "id": "alacritty", + "name": "Alacritty", + "templatePath": "terminal/alacritty.toml", + "predefinedTemplatePath": "terminal/alacritty-predefined.toml", + "outputPath": "~/.config/alacritty/themes/noctalia.toml", + "postHook": `${templateApplyScript} alacritty` + }, + { + "id": "wezterm", + "name": "Wezterm", + "templatePath": "terminal/wezterm.toml", + "predefinedTemplatePath": "terminal/wezterm-predefined.toml", + "outputPath": "~/.config/wezterm/colors/Noctalia.toml", + "postHook": `${templateApplyScript} wezterm` + } + ] + + // Application configurations - consolidated from Theming + AppThemeService + readonly property var applications: [ + { + "id": "gtk", + "name": "GTK", + "category": "system", + "input": "gtk4.css", + "outputs": [ + { + "path": "~/.config/gtk-3.0/noctalia.css", + "input": "gtk3.css" + }, + { + "path": "~/.config/gtk-4.0/noctalia.css", + "input": "gtk4.css" + } + ], + "postProcess": mode => `python3 ${gtkRefreshScript} ${mode}` + }, + { + "id": "qt", + "name": "Qt", + "category": "system", + "input": "qtct.conf", + "outputs": [ + { + "path": "~/.config/qt5ct/colors/noctalia.conf" + }, + { + "path": "~/.config/qt6ct/colors/noctalia.conf" + } + ] + }, + { + "id": "kcolorscheme", + "name": "KColorScheme", + "category": "system", + "input": "kcolorscheme.colors", + "outputs": [ + { + "path": "~/.local/share/color-schemes/noctalia.colors" + } + ], + "postProcess": () => "if command -v plasma-apply-colorscheme >/dev/null 2>&1; then plasma-apply-colorscheme BreezeDark; sleep 0.5; plasma-apply-colorscheme noctalia; fi" + }, + { + "id": "fuzzel", + "name": "Fuzzel", + "category": "launcher", + "input": "fuzzel.conf", + "outputs": [ + { + "path": "~/.config/fuzzel/themes/noctalia" + } + ], + "postProcess": () => `${templateApplyScript} fuzzel` + }, + { + "id": "vicinae", + "name": "Vicinae", + "category": "launcher", + "input": "vicinae.toml", + "outputs": [ + { + "path": "~/.local/share/vicinae/themes/noctalia.toml" + } + ], + "postProcess": () => `cp --update=none ${Quickshell.shellDir}/Assets/noctalia.svg ~/.local/share/vicinae/themes/noctalia.svg && ${templateApplyScript} vicinae` + }, + { + "id": "walker", + "name": "Walker", + "category": "launcher", + "input": "walker.css", + "outputs": [ + { + "path": "~/.config/walker/themes/noctalia/style.css" + } + ], + "postProcess": () => `${templateApplyScript} walker`, + "strict": true // Use strict mode for palette generation (preserves custom surface/outline values) + }, + { + "id": "pywalfox", + "name": "Pywalfox", + "category": "browser", + "input": "pywalfox.json", + "outputs": [ + { + "path": "~/.cache/wal/colors.json" + } + ], + "postProcess": mode => `${templateApplyScript} pywalfox ${mode}` + } // CONSOLIDATED DISCORD CLIENTS + , + { + "id": "discord", + "name": "Discord", + "category": "misc", + "input": ["discord-midnight.css", "discord-material.css"], + "clients": [ + { + "name": "vesktop", + "path": "~/.config/vesktop" + }, + { + "name": "webcord", + "path": "~/.config/webcord" + }, + { + "name": "armcord", + "path": "~/.config/armcord" + }, + { + "name": "equibop", + "path": "~/.config/equibop" + }, + { + "name": "equicord", + "path": "~/.config/Equicord" + }, + { + "name": "lightcord", + "path": "~/.config/lightcord" + }, + { + "name": "dorion", + "path": "~/.config/dorion" + }, + { + "name": "vencord", + "path": "~/.config/Vencord" + }, + { + "name": "vencord-flatpak", + "path": "~/.var/app/com.discordapp.Discord/config/Vencord" + }, + { + "name": "betterdiscord", + "path": "~/.config/BetterDiscord" + } + ] + }, + { + "id": "code", + "name": "VSCode", + "category": "editor", + "input": "code.json", + "clients": [ + { + "name": "code", + "path": "~/.vscode/extensions/noctalia.noctaliatheme-0.0.5/themes/NoctaliaTheme-color-theme.json" + }, + { + "name": "codium", + "path": "~/.vscode-oss/extensions/noctalia.noctaliatheme-0.0.5-universal/themes/NoctaliaTheme-color-theme.json" + } + ] + }, + { + "id": "zed", + "name": "Zed", + "category": "editor", + "input": "zed.json", + "outputs": [ + { + "path": "~/.config/zed/themes/noctalia.json" + } + ], + "dualMode": true // Template contains both dark and light theme patterns + }, + { + "id": "helix", + "name": "Helix", + "category": "editor", + "input": "helix.toml", + "outputs": [ + { + "path": "~/.config/helix/themes/noctalia.toml" + } + ] + }, + { + "id": "spicetify", + "name": "Spicetify", + "category": "audio", + "input": "spicetify.ini", + "outputs": [ + { + "path": "~/.config/spicetify/Themes/Comfy/color.ini" + } + ], + "postProcess": () => `spicetify -q apply --no-restart` + }, + { + "id": "telegram", + "name": "Telegram", + "category": "misc", + "input": "telegram.tdesktop-theme", + "outputs": [ + { + "path": "~/.config/telegram-desktop/themes/noctalia.tdesktop-theme" + } + ] + }, + { + "id": "zenBrowser", + "name": "Zen Browser", + "category": "browser", + "input": "zen-browser/zen-userChrome.css", + "outputs": [ + { + "path": "~/.cache/noctalia/zen-browser/zen-userChrome.css" + }, + { + "path": "~/.cache/noctalia/zen-browser/zen-userContent.css", + "input": "zen-browser/zen-userContent.css" + } + ], + "postProcess": () + => "sh -c 'CSS_CHROME=\"$HOME/.cache/noctalia/zen-browser/zen-userChrome.css\"; CSS_CONTENT=\"$HOME/.cache/noctalia/zen-browser/zen-userContent.css\"; LINE_CHROME=\"@import \\\"$CSS_CHROME\\\";\"; LINE_CONTENT=\"@import \\\"$CSS_CONTENT\\\";\"; find \"$HOME/.config/zen\" \"$HOME/.zen\" -mindepth 2 -maxdepth 2 -type d -name chrome -print0 2>/dev/null | while IFS= read -r -d \"\" dir; do USER_CHROME=\"$dir/userChrome.css\"; USER_CONTENT=\"$dir/userContent.css\"; mkdir -p \"$dir\"; touch \"$USER_CHROME\" \"$USER_CONTENT\"; sed -i \"/zen-browser\\/zen-userChrome\\.css/d\" \"$USER_CHROME\"; sed -i \"/zen-browser\\/zen-userContent\\.css/d\" \"$USER_CONTENT\"; if ! grep -Fq \"$LINE_CHROME\" \"$USER_CHROME\"; then printf \"%s\\n\" \"$LINE_CHROME\" >> \"$USER_CHROME\"; fi; if ! grep -Fq \"$LINE_CONTENT\" \"$USER_CONTENT\"; then printf \"%s\\n\" \"$LINE_CONTENT\" >> \"$USER_CONTENT\"; fi; done'" + }, + { + "id": "cava", + "name": "Cava", + "category": "audio", + "input": "cava.ini", + "outputs": [ + { + "path": "~/.config/cava/themes/noctalia" + } + ], + "postProcess": () => `${templateApplyScript} cava` + }, + { + "id": "yazi", + "name": "Yazi", + "category": "misc", + "input": "yazi.toml", + "outputs": [ + { + "path": "~/.config/yazi/flavors/noctalia.yazi/flavor.toml" + } + ], + "postProcess": () => `${templateApplyScript} yazi` + }, + { + "id": "emacs", + "name": "Emacs", + "category": "editor", + "input": "emacs.el", + "postProcess": () => `emacsclient -e "(load-theme 'noctalia t)"` + }, + { + "id": "labwc", + "name": "Labwc", + "category": "compositor", + "input": "labwc.conf", + "outputs": [ + { + "path": "~/.config/labwc/themerc-override" + } + ], + "postProcess": () => `${templateApplyScript} labwc` + }, + { + "id": "niri", + "name": "Niri", + "category": "compositor", + "input": "niri.kdl", + "outputs": [ + { + "path": "~/.config/niri/noctalia.kdl" + } + ], + "postProcess": () => `${templateApplyScript} niri` + }, + { + "id": "sway", + "name": "Sway", + "category": "compositor", + "input": "sway", + "outputs": [ + { + "path": "~/.config/sway/noctalia" + } + ], + "postProcess": () => `${templateApplyScript} sway` + }, + { + "id": "scroll", + "name": "Scroll", + "category": "compositor", + "input": "scroll", + "outputs": [ + { + "path": "~/.config/scroll/noctalia" + } + ], + "postProcess": () => `${templateApplyScript} scroll` + }, + { + "id": "hyprland", + "name": "Hyprland", + "category": "compositor", + "input": "hyprland.conf", + "outputs": [ + { + "path": "~/.config/hypr/noctalia/noctalia-colors.conf" + } + ], + "postProcess": () => `${templateApplyScript} hyprland` + }, + { + "id": "hyprtoolkit", + "name": "Hyprtoolkit", + "category": "system", + "input": "hyprtoolkit.conf", + "outputs": [ + { + "path": "~/.config/hypr/hyprtoolkit.conf" + } + ] + }, + { + "id": "mango", + "name": "Mango", + "category": "compositor", + "input": "mango.conf", + "outputs": [ + { + "path": "~/.config/mango/noctalia.conf" + } + ], + "postProcess": () => `${templateApplyScript} mango` + }, + { + "id": "btop", + "name": "btop", + "category": "misc", + "input": "btop.theme", + "outputs": [ + { + "path": "~/.config/btop/themes/noctalia.theme" + } + ], + "postProcess": () => `${templateApplyScript} btop` + }, + { + "id": "zathura", + "name": "Zathura", + "category": "misc", + "input": "zathurarc", + "outputs": [ + { + "path": "~/.config/zathura/noctaliarc" + } + ], + "postProcess": () => `${templateApplyScript} zathura` + }, + { + "id": "steam", + "name": "Steam", + "category": "misc", + "input": "steam.css", + "outputs": [ + { + "path": "~/.steam/steam/steamui/skins/Material-Theme/css/main/colors/matugen.css" + } + ] + } + ] + + // Extract Discord clients for ProgramCheckerService compatibility + readonly property var discordClients: { + var clients = []; + var discordApp = applications.find(app => app.id === "discord"); + if (discordApp && discordApp.clients) { + discordApp.clients.forEach(client => { + clients.push({ + "name": client.name, + "configPath": client.path, + "themePath": `${client.path}/themes/noctalia.theme.css` + }); + }); + } + return clients; + } + + // Get resolved theme paths for a code client (returns array of all matching paths) + function resolvedCodeClientPaths(clientName) { + if (clientName === "code") + return resolvedCodePaths; + if (clientName === "codium") + return resolvedCodiumPaths; + return []; + } + + // Extract Code clients for ProgramCheckerService compatibility + readonly property var codeClients: { + var clients = []; + var codeApp = applications.find(app => app.id === "code"); + if (codeApp && codeApp.clients) { + codeApp.clients.forEach(client => { + // Extract base config directory from theme path + var themePath = client.path; + var baseConfigDir = ""; + if (client.name === "code") { + // For VSCode: ~/.vscode/extensions/... -> ~/.vscode + baseConfigDir = "~/.vscode"; + } else if (client.name === "codium") { + // For VSCodium: ~/.vscode-oss/extensions/... -> ~/.vscode-oss + baseConfigDir = "~/.vscode-oss"; + } + clients.push({ + "name": client.name, + "configPath": baseConfigDir, + "themePath": "" // resolved dynamically via resolvedCodeClientPaths() + }); + }); + } + return clients; + } + + // Resolve VSCode extension paths dynamically + Process { + id: codeResolverProcess + command: ["python3", vscodeHelperScript, "~/.vscode/extensions"] + running: true + property var paths: [] + stdout: SplitParser { + onRead: data => { + var line = data.trim(); + if (line) + codeResolverProcess.paths.push(line); + } + } + onExited: { + root.resolvedCodePaths = paths; + } + } + + Process { + id: codiumResolverProcess + command: ["python3", vscodeHelperScript, "~/.vscode-oss/extensions"] + running: true + property var paths: [] + stdout: SplitParser { + onRead: data => { + var line = data.trim(); + if (line) + codiumResolverProcess.paths.push(line); + } + } + onExited: { + root.resolvedCodiumPaths = paths; + } + } + // Build user templates TOML content + function buildUserTemplatesToml() { + var lines = []; + lines.push("[config]"); + lines.push(""); + lines.push("[templates]"); + lines.push(""); + lines.push("# User-defined templates"); + lines.push("# Add your custom templates below"); + lines.push("# Example:"); + lines.push("# [templates.myapp]"); + lines.push("# input_path = \"~/.config/noctalia/templates/myapp.css\""); + lines.push("# output_path = \"~/.config/myapp/theme.css\""); + lines.push("# post_hook = \"myapp --reload-theme\""); + lines.push(""); + lines.push("# Remove this section and add your own templates"); + lines.push("#[templates.placeholder]"); + lines.push("#input_path = \"" + Quickshell.shellDir + "/Assets/Templates/noctalia.json\""); + lines.push("#output_path = \"" + Settings.cacheDir + "placeholder.json\""); + lines.push(""); + + return lines.join("\n") + "\n"; + } + + // Write user templates TOML file (moved from Theming) + function writeUserTemplatesToml() { + var userConfigPath = Settings.configDir + "user-templates.toml"; + + // Check if file already exists + fileCheckProcess.command = ["test", "-s", userConfigPath]; + fileCheckProcess.running = true; + } + + function doWriteUserTemplatesToml() { + var userConfigPath = Settings.configDir + "user-templates.toml"; + var configContent = buildUserTemplatesToml(); + var userConfigPathEsc = userConfigPath.replace(/'/g, "'\\''"); + var configDirEsc = Settings.configDir.replace(/'/g, "'\\''"); + + // Combine mkdir and write in a single script to avoid race condition + var script = `mkdir -p '${configDirEsc}' && cat > '${userConfigPathEsc}' << 'EOF'\n`; + script += configContent; + script += "EOF\n"; + fileWriteProcess.command = ["sh", "-c", script]; + fileWriteProcess.running = true; + } + + // Extract Emacs clients for ProgramCheckerService compatibility + readonly property var emacsClients: [ + { + "name": "doom", + "path": "~/.config/doom" + }, + { + "name": "modern", + "path": "~/.config/emacs" + }, + { + "name": "traditional", + "path": "~/.emacs.d" + } + ] + + // Process for checking if user templates file exists and is non-empty + Process { + id: fileCheckProcess + running: false + + onExited: function (exitCode) { + if (exitCode === 0) { + // File exists and is non-empty, skip creation + Logger.d("TemplateRegistry", "User templates config already exists, skipping creation"); + } else { + // File doesn't exist or is empty, create it + doWriteUserTemplatesToml(); + } + } + } + + // Process for writing user templates file with error reporting + Process { + id: fileWriteProcess + running: false + + onExited: function (exitCode) { + if (exitCode === 0) { + Logger.d("TemplateRegistry", "User templates config written to:", Settings.configDir + "user-templates.toml"); + } else { + Logger.e("TemplateRegistry", "Failed to write user templates config (exit code:", exitCode + ")"); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/UI/BarService.qml b/arch/.config/quickshell/noctalia-shell/Services/UI/BarService.qml new file mode 100644 index 0000000..c16cd81 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/UI/BarService.qml @@ -0,0 +1,668 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Commons +import qs.Services.Compositor +import qs.Services.UI + +Singleton { + id: root + + property bool isVisible: true + + // Computed visibility that factors in compositor overview state + readonly property bool effectivelyVisible: { + if (!isVisible) { + return false; + } + if (Settings.data.bar.hideOnOverview && CompositorService.overviewActive) { + return false; + } + return true; + } + + property var readyBars: ({}) + + // Revision counter - increment when widget list structure changes (add/remove/reorder) + // This triggers Bar.qml to re-sync its ListModels + property int widgetsRevision: 0 + + // Registry to store actual widget instances + // Key format: "screenName|section|widgetId|index" + property var widgetInstances: ({}) + + signal activeWidgetsChanged + signal barReadyChanged(string screenName) + signal barAutoHideStateChanged(string screenName, bool hidden) + signal barHoverStateChanged(string screenName, bool hovered) + + // Track if a popup menu is open from the bar (prevents auto-hide) + property bool popupOpen: false + + // Auto-hide state per screen: { screenName: { hovered: bool, hidden: bool } } + property var screenAutoHideState: ({}) + + // Get or create auto-hide state for a screen + function getOrCreateAutoHideState(screenName) { + if (!screenAutoHideState[screenName]) { + screenAutoHideState[screenName] = { + "hovered": false, + "hidden": Settings.getBarDisplayModeForScreen(screenName) === "auto_hide" + }; + } + return screenAutoHideState[screenName]; + } + + // Set hover state for a screen + function setScreenHovered(screenName, hovered) { + var state = getOrCreateAutoHideState(screenName); + if (state.hovered !== hovered) { + state.hovered = hovered; + screenAutoHideState = Object.assign({}, screenAutoHideState); + barHoverStateChanged(screenName, hovered); + } + } + + // Set hidden state for a screen + function setScreenHidden(screenName, hidden) { + var state = getOrCreateAutoHideState(screenName); + if (state.hidden !== hidden) { + state.hidden = hidden; + screenAutoHideState = Object.assign({}, screenAutoHideState); + barAutoHideStateChanged(screenName, hidden); + } + } + + // Check if bar is hidden on a screen + function isBarHidden(screenName) { + var state = screenAutoHideState[screenName]; + return state ? state.hidden : false; + } + + // Check if bar is hovered on a screen + function isBarHovered(screenName) { + var state = screenAutoHideState[screenName]; + return state ? state.hovered : false; + } + + // Toggle bar visibility. In auto-hide mode, toggles the per-screen hidden + // state without touching isVisible (so hover-to-show still works). + // For non-auto-hide screens, toggles the global isVisible flag. + function toggleVisibility() { + // Check if any auto-hide screen is currently visible + var anyAutoHideVisible = false; + var hasAutoHideScreens = false; + for (var screenName in screenAutoHideState) { + if (Settings.getBarDisplayModeForScreen(screenName) === "auto_hide") { + hasAutoHideScreens = true; + if (!screenAutoHideState[screenName].hidden) { + anyAutoHideVisible = true; + break; + } + } + } + + // Toggle auto-hide screens (per-screen hidden state only) + if (hasAutoHideScreens) { + for (var screenName in screenAutoHideState) { + if (Settings.getBarDisplayModeForScreen(screenName) === "auto_hide") { + setScreenHidden(screenName, anyAutoHideVisible); + } + } + } + + // Only toggle global visibility when no auto-hide screens exist, + // otherwise it would permanently disable hover-to-show + if (!hasAutoHideScreens) { + isVisible = !isVisible; + } + } + + // Show bar. In auto-hide mode, un-hides on screens with auto-hide enabled. + // The bar stays visible until the user hovers and moves away. + function show() { + // Show auto-hide screens + for (var screenName in screenAutoHideState) { + if (Settings.getBarDisplayModeForScreen(screenName) === "auto_hide") { + setScreenHidden(screenName, false); + } + } + // Set global visibility (affects non-auto-hide screens) + isVisible = true; + } + + // Hide bar. In auto-hide mode, sets per-screen hidden state without touching + // isVisible so hover-to-show still works. For non-auto-hide screens, sets + // global visibility to false. + function hide() { + var hasAutoHideScreens = false; + for (var screenName in screenAutoHideState) { + if (Settings.getBarDisplayModeForScreen(screenName) === "auto_hide") { + setScreenHidden(screenName, true); + hasAutoHideScreens = true; + } + } + // Only set global visibility off when no auto-hide screens exist, + // otherwise it would permanently disable hover-to-show + if (!hasAutoHideScreens) { + isVisible = false; + } + } + + // Temporarily show the bar, then auto-hide after the configured delay. + // Uses the same pattern as workspace switch: show, then emit unhover + // to start the hide timer. + function peek() { + for (var screenName in screenAutoHideState) { + if (Settings.getBarDisplayModeForScreen(screenName) === "auto_hide") { + setScreenHidden(screenName, false); + if (!isBarHovered(screenName)) { + barHoverStateChanged(screenName, false); + } + } + } + } + + Component.onCompleted: { + Logger.i("BarService", "Service started"); + } + + // Bump widgetsRevision when settings are reloaded from an external file change + // so Bar.qml re-syncs its widget ListModels with the updated widget configuration + Connections { + target: Settings + function onSettingsReloaded() { + Logger.d("BarService", "Settings reloaded externally, bumping widgetsRevision"); + root.widgetsRevision++; + } + } + + // update bar's hidden state when mode changes + Connections { + target: Settings.data.bar + function onDisplayModeChanged() { + Logger.d("BarService", "Display mode changed to:", Settings.data.bar.displayMode); + + // Only affect screens without displayMode overrides + for (let screenName in screenAutoHideState) { + if (!Settings.hasScreenOverride(screenName, "displayMode")) { + var displayMode = Settings.getBarDisplayModeForScreen(screenName); + if (displayMode === "auto_hide") { + setScreenHidden(screenName, true); + } else { + if (screenAutoHideState[screenName].hidden) { + setScreenHidden(screenName, false); + } + } + } + } + } + + function onScreenOverridesChanged() { + Logger.d("BarService", "Screen overrides changed, re-evaluating auto-hide states"); + + // Re-evaluate auto-hide state for all screens + for (let screenName in screenAutoHideState) { + var displayMode = Settings.getBarDisplayModeForScreen(screenName); + if (displayMode === "auto_hide") { + if (!screenAutoHideState[screenName].hidden) { + setScreenHidden(screenName, true); + } + } else { + if (screenAutoHideState[screenName].hidden) { + setScreenHidden(screenName, false); + } + } + } + } + } + + // Track last workspace ID to detect actual workspace changes + property var lastWorkspaceId: null + + // Debounce rapid workspace switches to reduce load/unload races (SIGSEGV in QV4) + property string _pendingWorkspaceScreen: "" + + Timer { + id: workspaceDebounceTimer + interval: 80 + repeat: false + onTriggered: { + var screen = root._pendingWorkspaceScreen; + root._pendingWorkspaceScreen = ""; + if (screen) { + setScreenHidden(screen, false); + if (!root.isBarHovered(screen)) { + barHoverStateChanged(screen, false); + } + } + } + } + + // Workspace switch handler - directly show bar on the focused workspace screen + Connections { + target: CompositorService + function onWorkspaceChanged() { + if (!Settings.data.bar.showOnWorkspaceSwitch) + return; + if (Settings.data.bar.displayMode !== "auto_hide") + return; + + var ws = CompositorService.getCurrentWorkspace(); + if (!ws || !ws.output) { + return; + } + + // Only trigger if workspace actually changed + var currentWsId = ws.id; + if (currentWsId === root.lastWorkspaceId) { + return; + } + root.lastWorkspaceId = currentWsId; + + var screenName = ws.output || ""; + Logger.d("BarService", "Workspace switched to:", currentWsId, "on screen:", screenName); + + // Debounce: rapid switches (e.g. external monitor โ†” laptop) cause overlapping + // bar load/unload; 80ms delay coalesces them and reduces QV4 incubation races + root._pendingWorkspaceScreen = screenName; + workspaceDebounceTimer.restart(); + } + } + + // Function for the Bar to call when it's ready + function registerBar(screenName) { + if (!readyBars[screenName]) { + readyBars[screenName] = true; + Logger.d("BarService", "Bar is ready on screen:", screenName); + barReadyChanged(screenName); + } + } + + // Function for the Dock to check if the bar is ready + function isBarReady(screenName) { + return readyBars[screenName] || false; + } + + // Register a widget instance + function registerWidget(screenName, section, widgetId, index, instance) { + const key = [screenName, section, widgetId, index].join("|"); + widgetInstances[key] = { + "key": key, + "screenName": screenName, + "section": section, + "widgetId": widgetId, + "index": index, + "instance": instance + }; + + Logger.d("BarService", "Registered widget:", key); + root.activeWidgetsChanged(); + } + + // Unregister a widget instance + function unregisterWidget(screenName, section, widgetId, index) { + const key = [screenName, section, widgetId, index].join("|"); + delete widgetInstances[key]; + Logger.d("BarService", "Unregistered widget:", key); + root.activeWidgetsChanged(); + } + + // Lookup a specific widget instance (returns the actual QML instance) + function lookupWidget(widgetId, screenName = null, section = null, index = null) { + // If looking for a specific instance + if (screenName && section !== null) { + for (var key in widgetInstances) { + var widget = widgetInstances[key]; + if (!widget) + continue; + if (widget.widgetId === widgetId && widget.screenName === screenName && widget.section === section) { + if (index === null) { + return widget.instance; + } else if (widget.index == index) { + return widget.instance; + } + } + } + } + + // Return first match if no specific screen/section specified + for (var key in widgetInstances) { + var widget = widgetInstances[key]; + if (!widget) + continue; + if (widget.widgetId === widgetId) { + if (!screenName || widget.screenName === screenName) { + if (section === null || widget.section === section) { + return widget.instance; + } + } + } + } + + return undefined; + } + + // Get all instances of a widget type + function getAllWidgetInstances(widgetId = null, screenName = null, section = null) { + var instances = []; + + for (var key in widgetInstances) { + var widget = widgetInstances[key]; + if (!widget) + continue; + + var matches = true; + if (widgetId && widget.widgetId !== widgetId) + matches = false; + if (screenName && widget.screenName !== screenName) + matches = false; + if (section !== null && widget.section !== section) + matches = false; + + if (matches) { + instances.push(widget.instance); + } + } + + return instances; + } + + // Get widget with full metadata + function getWidgetWithMetadata(widgetId, screenName = null, section = null) { + for (var key in widgetInstances) { + var widget = widgetInstances[key]; + if (!widget) + continue; + if (widget.widgetId === widgetId) { + if (!screenName || widget.screenName === screenName) { + if (section === null || widget.section === section) { + return widget; + } + } + } + } + return undefined; + } + + // Get all widgets in a specific section + function getWidgetsBySection(section, screenName = null) { + var widgetEntries = []; + + for (var key in widgetInstances) { + var widget = widgetInstances[key]; + if (!widget) + continue; + if (widget.section === section) { + if (!screenName || widget.screenName === screenName) { + widgetEntries.push(widget); + } + } + } + + // Sort by index to maintain order + widgetEntries.sort(function (a, b) { + return (a.index || 0) - (b.index || 0); + }); + + // Return just the instances + return widgetEntries.map(function (w) { + return w.instance; + }); + } + + // Get all registered widgets (for debugging) + function getAllRegisteredWidgets() { + var result = []; + for (var key in widgetInstances) { + var widget = widgetInstances[key]; + if (!widget) + continue; + result.push({ + "key": key, + "widgetId": widget.widgetId, + "section": widget.section, + "screenName": widget.screenName, + "index": widget.index + }); + } + return result; + } + + // Check if a widget type exists in a section + function hasWidget(widgetId, section = null, screenName = null) { + for (var key in widgetInstances) { + var widget = widgetInstances[key]; + if (!widget) + continue; + if (widget.widgetId === widgetId) { + if (section === null || widget.section === section) { + if (!screenName || widget.screenName === screenName) { + return true; + } + } + } + } + return false; + } + + // Unregister all widget instances for a plugin (used during hot reload) + // Note: We don't destroy instances here - the Loader manages that when the component is unregistered + function destroyPluginWidgetInstances(pluginId) { + var widgetId = "plugin:" + pluginId; + var keysToRemove = []; + + // Find all instances of this plugin's widget + for (var key in widgetInstances) { + var widget = widgetInstances[key]; + if (widget && widget.widgetId === widgetId) { + keysToRemove.push(key); + Logger.d("BarService", "Unregistering plugin widget instance:", key); + } + } + + // Remove from registry + for (var i = 0; i < keysToRemove.length; i++) { + delete widgetInstances[keysToRemove[i]]; + } + + if (keysToRemove.length > 0) { + Logger.i("BarService", "Unregistered", keysToRemove.length, "instance(s) of plugin widget:", widgetId); + root.activeWidgetsChanged(); + } + } + + // Get pill direction for a widget instance + function getPillDirection(widgetInstance) { + try { + if (widgetInstance.section === "left") { + return false; + } else if (widgetInstance.section === "right") { + return true; + } else { + // middle section + if (widgetInstance.sectionWidgetIndex < widgetInstance.sectionWidgetsCount / 2) { + return true; + } else { + return false; + } + } + } catch (e) { + Logger.e(e); + } + return true; + } + + function getTooltipDirection(screenName) { + const position = Settings.getBarPositionForScreen(screenName); + switch (position) { + case "right": + return "left"; + case "left": + return "right"; + case "bottom": + return "top"; + default: + return "bottom"; + } + } + + // Helper to close any existing dialogs in a popup menu window + function closeExistingDialogs(popupMenuWindow) { + if (!popupMenuWindow || !popupMenuWindow.dialogParent) + return; + + var dialogParent = popupMenuWindow.dialogParent; + for (var i = dialogParent.children.length - 1; i >= 0; i--) { + var child = dialogParent.children[i]; + if (child && typeof child.close === "function") { + child.close(); + } + } + popupMenuWindow.hasDialog = false; + } + + // Open widget settings dialog for a bar widget + // Parameters: + // screen: The screen to show the dialog on + // section: Section id ("left", "center", "right") + // index: Widget index in section + // widgetId: Widget type id (e.g., "Volume") + // widgetData: Current widget settings object + function openWidgetSettings(screen, section, index, widgetId, widgetData) { + // Get the popup menu window to use as parent (avoids clipping issues with bar height) + var popupMenuWindow = PanelService.getPopupMenuWindow(screen); + if (!popupMenuWindow) { + Logger.e("BarService", "No popup menu window found for screen"); + return; + } + + // Close any existing dialogs first to prevent stacking + closeExistingDialogs(popupMenuWindow); + + if (PanelService.openedPanel) { + PanelService.openedPanel.close(); + } + + var component = Qt.createComponent(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml"); + + function instantiateAndOpen() { + // Use dialogParent (Item) instead of window directly for proper Popup anchoring + var dialog = component.createObject(popupMenuWindow.dialogParent, { + "widgetIndex": index, + "widgetData": widgetData, + "widgetId": widgetId, + "sectionId": section, + "screen": screen + }); + + if (dialog) { + dialog.updateWidgetSettings.connect((sec, idx, settings) => { + var screenName = screen?.name || ""; + if (Settings.hasScreenOverride(screenName, "widgets")) { + var overrideWidgets = Settings.getBarWidgetsForScreen(screenName); + if (overrideWidgets && overrideWidgets[sec] && idx < overrideWidgets[sec].length) { + overrideWidgets[sec][idx] = Object.assign({}, overrideWidgets[sec][idx], settings); + Settings.setScreenOverride(screenName, "widgets", overrideWidgets); + } + } else { + var widgets = Settings.data.bar.widgets[sec]; + if (widgets && idx < widgets.length) { + widgets[idx] = Object.assign({}, widgets[idx], settings); + Settings.data.bar.widgets[sec] = widgets; + Settings.saveImmediate(); + } + } + }); + // Enable keyboard focus for the popup menu window when dialog is open + popupMenuWindow.hasDialog = true; + // Close the popup menu window when dialog closes + dialog.closed.connect(() => { + popupMenuWindow.hasDialog = false; + popupMenuWindow.close(); + dialog.destroy(); + }); + // Show the popup menu window and open the dialog + popupMenuWindow.open(); + dialog.open(); + } else { + Logger.e("BarService", "Failed to create widget settings dialog"); + } + } + + if (component.status === Component.Ready) { + instantiateAndOpen(); + } else if (component.status === Component.Error) { + Logger.e("BarService", "Error loading widget settings dialog:", component.errorString()); + } else { + component.statusChanged.connect(function () { + if (component.status === Component.Ready) { + instantiateAndOpen(); + } else if (component.status === Component.Error) { + Logger.e("BarService", "Error loading widget settings dialog:", component.errorString()); + } + }); + } + } + + // Open plugin settings dialog + // Parameters: + // screen: The screen to show the dialog on + // pluginManifest: The plugin's manifest object (must have entryPoints.settings) + function openPluginSettings(screen, pluginManifest) { + if (!pluginManifest || !pluginManifest.entryPoints || !pluginManifest.entryPoints.settings) { + Logger.e("BarService", "Cannot open plugin settings: no settings entry point"); + return; + } + + // Get the popup menu window to use as parent + var popupMenuWindow = PanelService.getPopupMenuWindow(screen); + if (!popupMenuWindow) { + Logger.e("BarService", "No popup menu window found for screen"); + return; + } + + // Close any existing dialogs first to prevent stacking + closeExistingDialogs(popupMenuWindow); + + var component = Qt.createComponent(Quickshell.shellDir + "/Widgets/NPluginSettingsPopup.qml"); + + function instantiateAndOpen() { + var dialog = component.createObject(popupMenuWindow.dialogParent, { + "showToastOnSave": true, + "screen": screen + }); + + if (dialog) { + // Enable keyboard focus for the popup menu window when dialog is open + popupMenuWindow.hasDialog = true; + // Close the popup menu window when dialog closes + dialog.closed.connect(() => { + popupMenuWindow.hasDialog = false; + popupMenuWindow.close(); + dialog.destroy(); + }); + // Show the popup menu window and open the dialog + popupMenuWindow.open(); + dialog.openPluginSettings(pluginManifest); + } else { + Logger.e("BarService", "Failed to create plugin settings dialog"); + } + } + + if (component.status === Component.Ready) { + instantiateAndOpen(); + } else if (component.status === Component.Error) { + Logger.e("BarService", "Error loading plugin settings dialog:", component.errorString()); + } else { + component.statusChanged.connect(function () { + if (component.status === Component.Ready) { + instantiateAndOpen(); + } else if (component.status === Component.Error) { + Logger.e("BarService", "Error loading plugin settings dialog:", component.errorString()); + } + }); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/UI/BarWidgetRegistry.qml b/arch/.config/quickshell/noctalia-shell/Services/UI/BarWidgetRegistry.qml new file mode 100644 index 0000000..5c951d4 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/UI/BarWidgetRegistry.qml @@ -0,0 +1,503 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Commons +import qs.Modules.Bar.Widgets + +Singleton { + id: root + + signal pluginWidgetRegistryUpdated + + // Widget registry object mapping widget names to components + property var widgets: ({ + "ActiveWindow": activeWindowComponent, + "AudioVisualizer": audioVisualizerComponent, + "Battery": batteryComponent, + "Bluetooth": bluetoothComponent, + "Brightness": brightnessComponent, + "Clock": clockComponent, + "ControlCenter": controlCenterComponent, + "CustomButton": customButtonComponent, + "DarkMode": darkModeComponent, + "KeepAwake": keepAwakeComponent, + "KeyboardLayout": keyboardLayoutComponent, + "LockKeys": lockKeysComponent, + "Launcher": launcherComponent, + "MediaMini": mediaMiniComponent, + "Microphone": microphoneComponent, + "Network": networkComponent, + "NightLight": nightLightComponent, + "NoctaliaPerformance": noctaliaPerformanceComponent, + "NotificationHistory": notificationHistoryComponent, + "PowerProfile": powerProfileComponent, + "SessionMenu": sessionMenuComponent, + "Settings": settingsComponent, + "Spacer": spacerComponent, + "SystemMonitor": systemMonitorComponent, + "Taskbar": taskbarComponent, + "Tray": trayComponent, + "Volume": volumeComponent, + "VPN": vpnComponent, + "WallpaperSelector": wallpaperSelectorComponent, + "Workspace": workspaceComponent + }) + + property var widgetSettingsMap: ({ + "ActiveWindow": "WidgetSettings/ActiveWindowSettings.qml", + "AudioVisualizer": "WidgetSettings/AudioVisualizerSettings.qml", + "Battery": "WidgetSettings/BatterySettings.qml", + "Bluetooth": "WidgetSettings/BluetoothSettings.qml", + "Brightness": "WidgetSettings/BrightnessSettings.qml", + "Clock": "WidgetSettings/ClockSettings.qml", + "ControlCenter": "WidgetSettings/ControlCenterSettings.qml", + "CustomButton": "WidgetSettings/CustomButtonSettings.qml", + "DarkMode": "WidgetSettings/DarkModeSettings.qml", + "KeepAwake": "WidgetSettings/KeepAwakeSettings.qml", + "KeyboardLayout": "WidgetSettings/KeyboardLayoutSettings.qml", + "Launcher": "WidgetSettings/LauncherSettings.qml", + "LockKeys": "WidgetSettings/LockKeysSettings.qml", + "MediaMini": "WidgetSettings/MediaMiniSettings.qml", + "Microphone": "WidgetSettings/MicrophoneSettings.qml", + "Network": "WidgetSettings/NetworkSettings.qml", + "NightLight": "WidgetSettings/NightLightSettings.qml", + "NoctaliaPerformance": "WidgetSettings/NoctaliaPerformanceSettings.qml", + "NotificationHistory": "WidgetSettings/NotificationHistorySettings.qml", + "PowerProfile": "WidgetSettings/PowerProfileSettings.qml", + "SessionMenu": "WidgetSettings/SessionMenuSettings.qml", + "Settings": "WidgetSettings/SettingsSettings.qml", + "Spacer": "WidgetSettings/SpacerSettings.qml", + "SystemMonitor": "WidgetSettings/SystemMonitorSettings.qml", + "Taskbar": "WidgetSettings/TaskbarSettings.qml", + "Tray": "WidgetSettings/TraySettings.qml", + "Volume": "WidgetSettings/VolumeSettings.qml", + "VPN": "WidgetSettings/VPNSettings.qml", + "WallpaperSelector": "WidgetSettings/WallpaperSelectorSettings.qml", + "Workspace": "WidgetSettings/WorkspaceSettings.qml" + }) + + property var widgetMetadata: ({ + "ActiveWindow": { + "showText": true, + "showIcon": true, + "hideMode": "hidden", + "scrollingMode": "hover", + "maxWidth": 145, + "useFixedWidth": false, + "colorizeIcons": false, + "textColor": "none" + }, + "AudioVisualizer": { + "width": 200, + "colorName": "primary", + "hideWhenIdle": false + }, + "Battery": { + "displayMode": "graphic-clean", + "deviceNativePath": "__default__", + "showPowerProfiles": false, + "showNoctaliaPerformance": false, + "hideIfNotDetected": true, + "hideIfIdle": false + }, + "Bluetooth": { + "displayMode": "onhover", + "iconColor": "none", + "textColor": "none" + }, + "Brightness": { + "displayMode": "onhover", + "iconColor": "none", + "textColor": "none", + "applyToAllMonitors": false + }, + "Clock": { + "clockColor": "none", + "useCustomFont": false, + "customFont": "", + "formatHorizontal": "HH:mm ddd, MMM dd", + "formatVertical": "HH mm - dd MM", + "tooltipFormat": "HH:mm ddd, MMM dd" + }, + "ControlCenter": { + "useDistroLogo": false, + "icon": "noctalia", + "customIconPath": "", + "colorizeDistroLogo": false, + "colorizeSystemIcon": "none", + "enableColorization": false + }, + "CustomButton": { + "icon": "heart", + "showIcon": true, + "showExecTooltip": true, + "showTextTooltip": true, + "generalTooltipText": "", + "hideMode": "alwaysExpanded", + "leftClickExec": "", + "leftClickUpdateText": false, + "rightClickExec": "", + "rightClickUpdateText": false, + "middleClickExec": "", + "middleClickUpdateText": false, + "textCommand": "", + "textStream": false, + "textIntervalMs": 3000, + "textCollapse": "", + "parseJson": false, + "wheelExec": "", + "wheelUpExec": "", + "wheelDownExec": "", + "wheelMode": "unified", + "wheelUpdateText": false, + "wheelUpUpdateText": false, + "wheelDownUpdateText": false, + "maxTextLength": { + "horizontal": 10, + "vertical": 10 + }, + "enableColorization": false, + "colorizeSystemIcon": "none", + "ipcIdentifier": "" + }, + "DarkMode": { + "iconColor": "none" + }, + "KeepAwake": { + "iconColor": "none", + "textColor": "none" + }, + "KeyboardLayout": { + "displayMode": "onhover", + "showIcon": true, + "iconColor": "none", + "textColor": "none" + }, + "LockKeys": { + "showCapsLock": true, + "showNumLock": true, + "showScrollLock": true, + "capsLockIcon": "letter-c", + "numLockIcon": "letter-n", + "scrollLockIcon": "letter-s", + "hideWhenOff": false + }, + "Launcher": { + "useDistroLogo": false, + "icon": "rocket", + "customIconPath": "", + "colorizeSystemIcon": "none", + "enableColorization": false, + "iconColor": "none" + }, + "MediaMini": { + "hideMode": "hidden", + "scrollingMode": "hover", + "maxWidth": 145, + "useFixedWidth": false, + "hideWhenIdle": false, + "showAlbumArt": true, + "showArtistFirst": true, + "showVisualizer": false, + "showProgressRing": true, + "visualizerType": "linear", + "textColor": "none", + "compactMode": false, + "panelShowAlbumArt": true + }, + "Microphone": { + "displayMode": "onhover", + "middleClickCommand": "pwvucontrol || pavucontrol", + "iconColor": "none", + "textColor": "none" + }, + "NotificationHistory": { + "showUnreadBadge": true, + "hideWhenZero": false, + "hideWhenZeroUnread": false, + "unreadBadgeColor": "primary", + "iconColor": "none" + }, + "SessionMenu": { + "iconColor": "error" + }, + "Settings": { + "iconColor": "none" + }, + "Spacer": { + "width": 20 + }, + "SystemMonitor": { + "compactMode": true, + "iconColor": "none", + "textColor": "none", + "useMonospaceFont": true, + "usePadding": false, + "showCpuUsage": true, + "showCpuCores": false, + "showCpuFreq": false, + "showCpuTemp": true, + "showGpuTemp": false, + "showLoadAverage": false, + "showMemoryUsage": true, + "showMemoryAsPercent": false, + "showSwapUsage": false, + "showNetworkStats": false, + "showDiskUsage": false, + "showDiskUsageAsPercent": false, + "showDiskAvailable": false, + "diskPath": "/" + }, + "Taskbar": { + "onlySameOutput": true, + "onlyActiveWorkspaces": true, + "hideMode": "hidden", + "colorizeIcons": false, + "showTitle": false, + "titleWidth": 120, + "showPinnedApps": true, + "smartWidth": true, + "maxTaskbarWidth": 40, + "iconScale": 0.8 + }, + "Tray": { + "blacklist": [], + "colorizeIcons": false, + "chevronColor": "none", + "pinned": [], + "drawerEnabled": true, + "hidePassive": false + }, + "VPN": { + "displayMode": "onhover", + "iconColor": "none", + "textColor": "none" + }, + "Network": { + "displayMode": "onhover", + "iconColor": "none", + "textColor": "none" + }, + "NightLight": { + "iconColor": "none" + }, + "NoctaliaPerformance": { + "iconColor": "none" + }, + "PowerProfile": { + "iconColor": "none" + }, + "Workspace": { + "labelMode": "index", + "followFocusedScreen": false, + "hideUnoccupied": false, + "characterCount": 2, + "showApplications": false, + "showApplicationsHover": false, + "showLabelsOnlyWhenOccupied": true, + "colorizeIcons": false, + "unfocusedIconsOpacity": 1.0, + "groupedBorderOpacity": 1.0, + "enableScrollWheel": true, + "iconScale": 0.8, + "focusedColor": "primary", + "occupiedColor": "secondary", + "emptyColor": "secondary", + "showBadge": true, + "pillSize": 0.6, + "fontWeight": "bold" + }, + "Volume": { + "displayMode": "onhover", + "middleClickCommand": "pwvucontrol || pavucontrol", + "iconColor": "none", + "textColor": "none" + }, + "WallpaperSelector": { + "iconColor": "none" + } + }) + + // Component definitions - these are loaded once at startup + property Component activeWindowComponent: Component { + ActiveWindow {} + } + property Component audioVisualizerComponent: Component { + AudioVisualizer {} + } + property Component batteryComponent: Component { + Battery {} + } + property Component bluetoothComponent: Component { + Bluetooth {} + } + property Component brightnessComponent: Component { + Brightness {} + } + property Component clockComponent: Component { + Clock {} + } + property Component customButtonComponent: Component { + CustomButton {} + } + property Component darkModeComponent: Component { + DarkMode {} + } + property Component keyboardLayoutComponent: Component { + KeyboardLayout {} + } + property Component keepAwakeComponent: Component { + KeepAwake {} + } + property Component lockKeysComponent: Component { + LockKeys {} + } + property Component launcherComponent: Component { + Launcher {} + } + property Component mediaMiniComponent: Component { + MediaMini {} + } + property Component microphoneComponent: Component { + Microphone {} + } + property Component nightLightComponent: Component { + NightLight {} + } + property Component noctaliaPerformanceComponent: Component { + NoctaliaPerformance {} + } + property Component notificationHistoryComponent: Component { + NotificationHistory {} + } + property Component powerProfileComponent: Component { + PowerProfile {} + } + property Component sessionMenuComponent: Component { + SessionMenu {} + } + property Component settingsComponent: Component { + Settings {} + } + property Component controlCenterComponent: Component { + ControlCenter {} + } + property Component spacerComponent: Component { + Spacer {} + } + property Component systemMonitorComponent: Component { + SystemMonitor {} + } + property Component trayComponent: Component { + Tray {} + } + property Component volumeComponent: Component { + Volume {} + } + property Component vpnComponent: Component { + VPN {} + } + property Component networkComponent: Component { + Network {} + } + property Component wallpaperSelectorComponent: Component { + WallpaperSelector {} + } + property Component workspaceComponent: Component { + Workspace {} + } + property Component taskbarComponent: Component { + Taskbar {} + } + function init() { + Logger.i("BarWidgetRegistry", "Service started"); + } + + // ------------------------------ + // Helper function to get widget component by name + function getWidget(id) { + return widgets[id] || null; + } + + // Helper function to check if widget exists + function hasWidget(id) { + return id in widgets; + } + + // Get list of available widget id + function getAvailableWidgets() { + return Object.keys(widgets); + } + + // Helper function to check if widget has user settings + function widgetHasUserSettings(id) { + return widgetMetadata[id] !== undefined; + } + + // ------------------------------ + // Plugin widget registration + + // Track plugin widgets separately + property var pluginWidgets: ({}) + property var pluginWidgetMetadata: ({}) + + // Register a plugin widget + function registerPluginWidget(pluginId, component, metadata) { + if (!pluginId || !component) { + Logger.e("BarWidgetRegistry", "Cannot register plugin widget: invalid parameters"); + return false; + } + + // Add plugin: prefix to avoid conflicts with core widgets + var widgetId = "plugin:" + pluginId; + + pluginWidgets[widgetId] = component; + pluginWidgetMetadata[widgetId] = metadata || {}; + + // Also add to main widgets object for unified access + widgets[widgetId] = component; + widgetMetadata[widgetId] = metadata || {}; + + Logger.i("BarWidgetRegistry", "Registered plugin widget:", widgetId); + root.pluginWidgetRegistryUpdated(); + return true; + } + + // Unregister a plugin widget + function unregisterPluginWidget(pluginId) { + var widgetId = "plugin:" + pluginId; + + if (!pluginWidgets[widgetId]) { + Logger.w("BarWidgetRegistry", "Plugin widget not registered:", widgetId); + return false; + } + + delete pluginWidgets[widgetId]; + delete pluginWidgetMetadata[widgetId]; + delete widgets[widgetId]; + delete widgetMetadata[widgetId]; + + Logger.i("BarWidgetRegistry", "Unregistered plugin widget:", widgetId); + root.pluginWidgetRegistryUpdated(); + return true; + } + + // Check if a widget is a plugin widget + function isPluginWidget(id) { + return id.startsWith("plugin:"); + } + + property var cpuIntensiveWidgets: ["AudioVisualizer"] + + function isCpuIntensive(id) { + if (pluginWidgetMetadata[id]?.cpuIntensive) + return true; + return cpuIntensiveWidgets.indexOf(id) >= 0; + } + + // Get list of plugin widget IDs + function getPluginWidgets() { + return Object.keys(pluginWidgets); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/UI/ControlCenterWidgetRegistry.qml b/arch/.config/quickshell/noctalia-shell/Services/UI/ControlCenterWidgetRegistry.qml new file mode 100644 index 0000000..52bde99 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/UI/ControlCenterWidgetRegistry.qml @@ -0,0 +1,163 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Commons +import qs.Modules.Panels.ControlCenter.Widgets + +Singleton { + id: root + + // Widget registry object mapping widget names to components + property var widgets: ({ + "AirplaneMode": airplaneModeComponent, + "Bluetooth": bluetoothComponent, + "CustomButton": customButtonComponent, + "DarkMode": darkModeComponent, + "KeepAwake": keepAwakeComponent, + "NightLight": nightLightComponent, + "Notifications": notificationsComponent, + "PowerProfile": powerProfileComponent, + "WiFi": networkComponent, + "Network": networkComponent, + "NoctaliaPerformance": noctaliaPerformanceComponent, + "WallpaperSelector": wallpaperSelectorComponent + }) + + property var widgetMetadata: ({ + "CustomButton": { + "icon": "heart", + "onClicked": "", + "onRightClicked": "", + "onMiddleClicked": "", + "stateChecksJson": "[]", + "generalTooltipText": "", + "enableOnStateLogic": false, + "showExecTooltip": true + } + }) + + property var cpuIntensiveWidgets: ["SystemStat"] + + // Component definitions - these are loaded once at startup + property Component airplaneModeComponent: Component { + AirplaneMode {} + } + property Component bluetoothComponent: Component { + Bluetooth {} + } + property Component customButtonComponent: Component { + CustomButton {} + } + property Component darkModeComponent: Component { + DarkMode {} + } + property Component keepAwakeComponent: Component { + KeepAwake {} + } + property Component nightLightComponent: Component { + NightLight {} + } + property Component notificationsComponent: Component { + Notifications {} + } + property Component powerProfileComponent: Component { + PowerProfile {} + } + property Component networkComponent: Component { + Network {} + } + property Component noctaliaPerformanceComponent: Component { + NoctaliaPerformance {} + } + property Component wallpaperSelectorComponent: Component { + WallpaperSelector {} + } + + function init() { + Logger.i("ControlCenterWidgetRegistry", "Service started"); + } + + // ------------------------------ + // Helper function to get widget component by name + function getWidget(id) { + return widgets[id] || null; + } + + // Helper function to check if widget exists + function hasWidget(id) { + return id in widgets; + } + + // Get list of available widget id + function getAvailableWidgets() { + return Object.keys(widgets); + } + + // Helper function to check if widget has user settings + function widgetHasUserSettings(id) { + return widgetMetadata[id] !== undefined; + } + + // ------------------------------ + // Plugin widget registration + + // Track plugin widgets separately + property var pluginWidgets: ({}) + property var pluginWidgetMetadata: ({}) + + // Register a plugin widget + function registerPluginWidget(pluginId, component, metadata) { + if (!pluginId || !component) { + Logger.e("ControlCenterWidgetRegistry", "Cannot register plugin widget: invalid parameters"); + return false; + } + + // Add plugin: prefix to avoid conflicts with core widgets + var widgetId = "plugin:" + pluginId; + + pluginWidgets[widgetId] = component; + pluginWidgetMetadata[widgetId] = metadata || {}; + + // Also add to main widgets object for unified access + widgets[widgetId] = component; + widgetMetadata[widgetId] = metadata || {}; + + Logger.i("ControlCenterWidgetRegistry", "Registered plugin widget:", widgetId); + return true; + } + + // Unregister a plugin widget + function unregisterPluginWidget(pluginId) { + var widgetId = "plugin:" + pluginId; + + if (!pluginWidgets[widgetId]) { + Logger.w("ControlCenterWidgetRegistry", "Plugin widget not registered:", widgetId); + return false; + } + + delete pluginWidgets[widgetId]; + delete pluginWidgetMetadata[widgetId]; + delete widgets[widgetId]; + delete widgetMetadata[widgetId]; + + Logger.i("ControlCenterWidgetRegistry", "Unregistered plugin widget:", widgetId); + return true; + } + + // Check if a widget is a plugin widget + function isPluginWidget(id) { + return id.startsWith("plugin:"); + } + + // Get list of plugin widget IDs + function getPluginWidgets() { + return Object.keys(pluginWidgets); + } + + function isCpuIntensive(id) { + if (pluginWidgetMetadata[id]?.cpuIntensive) + return true; + return cpuIntensiveWidgets.indexOf(id) >= 0; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/UI/DesktopWidgetRegistry.qml b/arch/.config/quickshell/noctalia-shell/Services/UI/DesktopWidgetRegistry.qml new file mode 100644 index 0000000..f40dcb5 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/UI/DesktopWidgetRegistry.qml @@ -0,0 +1,346 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Commons +import qs.Modules.DesktopWidgets.Widgets +import qs.Services.Noctalia + +Singleton { + id: root + + // Transient state - not persisted, resets on shell restart + property bool editMode: false + + // Signal emitted when plugin widgets are registered/unregistered + signal pluginWidgetRegistryUpdated + + // Component definitions + property Component clockComponent: Component { + DesktopClock {} + } + property Component mediaPlayerComponent: Component { + DesktopMediaPlayer {} + } + property Component weatherComponent: Component { + DesktopWeather {} + } + property Component systemStatComponent: Component { + DesktopSystemStat {} + } + property Component audioVisualizerComponent: Component { + DesktopAudioVisualizer {} + } + + // Widget registry object mapping widget names to components + // Created in Component.onCompleted to ensure Components are ready + property var widgets: ({}) + + // Explicit URLs for core widgets. Inline Component.url returns the parent + // file's URL, so we need these for Loader.setSource() with initial properties. + // Plugin widgets use their Component.url directly (loaded from actual files). + readonly property string _widgetsDir: Quickshell.shellDir + "/Modules/DesktopWidgets/Widgets/" + property var widgetUrls: ({}) + + Component.onCompleted: { + // Initialize widgets object after Components are ready + var widgetsObj = {}; + widgetsObj["Clock"] = clockComponent; + widgetsObj["MediaPlayer"] = mediaPlayerComponent; + widgetsObj["Weather"] = weatherComponent; + widgetsObj["SystemStat"] = systemStatComponent; + widgetsObj["AudioVisualizer"] = audioVisualizerComponent; + widgets = widgetsObj; + + var urlsObj = {}; + urlsObj["Clock"] = _widgetsDir + "DesktopClock.qml"; + urlsObj["MediaPlayer"] = _widgetsDir + "DesktopMediaPlayer.qml"; + urlsObj["Weather"] = _widgetsDir + "DesktopWeather.qml"; + urlsObj["SystemStat"] = _widgetsDir + "DesktopSystemStat.qml"; + urlsObj["AudioVisualizer"] = _widgetsDir + "DesktopAudioVisualizer.qml"; + widgetUrls = urlsObj; + + Logger.i("DesktopWidgetRegistry", "Service started"); + } + + property var widgetSettingsMap: ({ + "Clock": "WidgetSettings/ClockSettings.qml", + "MediaPlayer": "WidgetSettings/MediaPlayerSettings.qml", + "Weather": "WidgetSettings/WeatherSettings.qml", + "SystemStat": "WidgetSettings/SystemStatSettings.qml", + "AudioVisualizer": "WidgetSettings/AudioVisualizerSettings.qml" + }) + + property var widgetMetadata: ({ + "Clock": { + "showBackground": true, + "roundedCorners": true, + "clockStyle": "digital", + "clockColor": "none", + "useCustomFont": false, + "customFont": "", + "format": "HH:mm\\nd MMMM yyyy" + }, + "MediaPlayer": { + "showBackground": true, + "roundedCorners": true, + "visualizerType": "linear", + "hideMode": "visible", + "showButtons": true, + "showAlbumArt": true, + "showVisualizer": true + }, + "Weather": { + "showBackground": true, + "roundedCorners": true + }, + "SystemStat": { + "showBackground": true, + "roundedCorners": true, + "statType": "CPU", + "diskPath": "/", + "layout": "bottom" + }, + "AudioVisualizer": { + "showBackground": true, + "roundedCorners": true, + "width": 320, + "height": 72, + "visualizerType": "linear", + "hideWhenIdle": false, + "colorName": "primary" + } + }) + + property var cpuIntensiveWidgets: ["SystemStat", "AudioVisualizer"] + + // Plugin widget storage (mirroring BarWidgetRegistry pattern) + property var pluginWidgets: ({}) + property var pluginWidgetMetadata: ({}) + + function init() { + Logger.i("DesktopWidgetRegistry", "Service started"); + } + + // Helper function to get widget component by name + function getWidget(id) { + return widgets[id] || null; + } + + // Helper function to check if widget exists + function hasWidget(id) { + return id in widgets; + } + + // Get list of available widget ids + function getAvailableWidgets() { + var keys = Object.keys(widgets); + Logger.d("DesktopWidgetRegistry", "getAvailableWidgets() called, returning:", keys); + return keys; + } + + // Helper function to check if widget has user settings + function widgetHasUserSettings(id) { + return widgetMetadata[id] !== undefined; + } + + function isCpuIntensive(id) { + if (pluginWidgetMetadata[id]?.cpuIntensive) + return true; + return cpuIntensiveWidgets.indexOf(id) >= 0; + } + + // Check if a widget is a plugin widget + function isPluginWidget(id) { + return id.startsWith("plugin:"); + } + + // Get list of plugin widget IDs + function getPluginWidgets() { + return Object.keys(pluginWidgets); + } + + // Get display name for a widget ID + function getWidgetDisplayName(widgetId) { + if (widgetId.startsWith("plugin:")) { + var pluginId = widgetId.replace("plugin:", ""); + var manifest = PluginRegistry.getPluginManifest(pluginId); + return manifest ? manifest.name : pluginId; + } + // Core widgets - return as-is (Clock, MediaPlayer, Weather, SystemStat) + return widgetId; + } + + // Register a plugin desktop widget + function registerPluginWidget(pluginId, component, metadata) { + if (!pluginId || !component) { + Logger.e("DesktopWidgetRegistry", "Cannot register plugin widget: invalid parameters"); + return false; + } + + var widgetId = "plugin:" + pluginId; + + // Create new objects to trigger QML property change detection + var newPluginWidgets = Object.assign({}, pluginWidgets); + newPluginWidgets[widgetId] = component; + pluginWidgets = newPluginWidgets; + + var newPluginMetadata = Object.assign({}, pluginWidgetMetadata); + newPluginMetadata[widgetId] = metadata || {}; + pluginWidgetMetadata = newPluginMetadata; + + // Also add to main widgets object for unified access - reassign to trigger change + var newWidgets = Object.assign({}, widgets); + newWidgets[widgetId] = component; + widgets = newWidgets; + + var newMetadata = Object.assign({}, widgetMetadata); + newMetadata[widgetId] = Object.assign({}, { + "showBackground": true + }, metadata || {}); + widgetMetadata = newMetadata; + + Logger.i("DesktopWidgetRegistry", "Registered plugin widget:", widgetId); + root.pluginWidgetRegistryUpdated(); + return true; + } + + // Unregister a plugin desktop widget + function unregisterPluginWidget(pluginId) { + var widgetId = "plugin:" + pluginId; + + if (!pluginWidgets[widgetId]) { + Logger.w("DesktopWidgetRegistry", "Plugin widget not registered:", widgetId); + return false; + } + + // Create new objects without the widget to trigger QML property change detection + var newPluginWidgets = Object.assign({}, pluginWidgets); + delete newPluginWidgets[widgetId]; + pluginWidgets = newPluginWidgets; + + var newPluginMetadata = Object.assign({}, pluginWidgetMetadata); + delete newPluginMetadata[widgetId]; + pluginWidgetMetadata = newPluginMetadata; + + var newWidgets = Object.assign({}, widgets); + delete newWidgets[widgetId]; + widgets = newWidgets; + + var newMetadata = Object.assign({}, widgetMetadata); + delete newMetadata[widgetId]; + widgetMetadata = newMetadata; + + Logger.i("DesktopWidgetRegistry", "Unregistered plugin widget:", widgetId); + root.pluginWidgetRegistryUpdated(); + return true; + } + + function updateWidgetData(monitorName, widgetIndex, properties) { + if (widgetIndex < 0 || !monitorName) { + return; + } + + var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || []; + var newMonitorWidgets = monitorWidgets.slice(); + + for (var i = 0; i < newMonitorWidgets.length; i++) { + if (newMonitorWidgets[i].name === monitorName) { + var widgets = (newMonitorWidgets[i].widgets || []).slice(); + if (widgetIndex < widgets.length) { + widgets[widgetIndex] = Object.assign({}, widgets[widgetIndex], properties); + newMonitorWidgets[i] = Object.assign({}, newMonitorWidgets[i], { + "widgets": widgets + }); + Settings.data.desktopWidgets.monitorWidgets = newMonitorWidgets; + } + break; + } + } + } + + property var currentSettingsDialog: null + + function openWidgetSettings(screen, widgetIndex, widgetId, widgetData) { + if (!widgetId || !screen) { + return; + } + + if (root.currentSettingsDialog) { + root.currentSettingsDialog.close(); + root.currentSettingsDialog.destroy(); + root.currentSettingsDialog = null; + } + + var hasSettings = false; + if (root.isPluginWidget(widgetId)) { + var pluginId = widgetId.replace("plugin:", ""); + var manifest = PluginRegistry.getPluginManifest(pluginId); + if (manifest && manifest.entryPoints && (manifest.entryPoints.desktopWidgetSettings || manifest.entryPoints.settings)) { + hasSettings = true; + } + } else { + hasSettings = root.widgetSettingsMap[widgetId] !== undefined; + } + + if (!hasSettings) { + Logger.w("DesktopWidgetRegistry", "Widget does not have settings:", widgetId); + return; + } + + var popupMenuWindow = PanelService.getPopupMenuWindow(screen); + if (!popupMenuWindow) { + Logger.e("DesktopWidgetRegistry", "No popup menu window found for screen"); + return; + } + + if (popupMenuWindow.hideDynamicMenu) { + popupMenuWindow.hideDynamicMenu(); + } + + var component = Qt.createComponent(Quickshell.shellDir + "/Modules/Panels/Settings/DesktopWidgets/DesktopWidgetSettingsDialog.qml"); + + function instantiateAndOpen() { + var dialog = component.createObject(popupMenuWindow.dialogParent, { + "widgetIndex": widgetIndex, + "widgetData": widgetData, + "widgetId": widgetId, + "sectionId": screen.name, + "screen": screen + }); + + if (dialog) { + root.currentSettingsDialog = dialog; + dialog.updateWidgetSettings.connect((sec, idx, settings) => { + root.updateWidgetData(sec, idx, settings); + }); + popupMenuWindow.hasDialog = true; + dialog.closed.connect(() => { + popupMenuWindow.hasDialog = false; + popupMenuWindow.close(); + if (root.currentSettingsDialog === dialog) { + root.currentSettingsDialog = null; + } + dialog.destroy(); + }); + dialog.open(); + } else { + Logger.e("DesktopWidgetRegistry", "Failed to create widget settings dialog"); + } + } + + if (component.status === Component.Ready) { + instantiateAndOpen(); + } else if (component.status === Component.Error) { + Logger.e("DesktopWidgetRegistry", "Error loading settings dialog component:", component.errorString()); + } else { + component.statusChanged.connect(() => { + if (component.status === Component.Ready) { + instantiateAndOpen(); + } else if (component.status === Component.Error) { + Logger.e("DesktopWidgetRegistry", "Error loading settings dialog component:", component.errorString()); + } + }); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/UI/ImageCacheService.qml b/arch/.config/quickshell/noctalia-shell/Services/UI/ImageCacheService.qml new file mode 100644 index 0000000..47d6976 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/UI/ImageCacheService.qml @@ -0,0 +1,784 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import "../../Helpers/sha256.js" as Checksum +import qs.Commons + +Singleton { + id: root + + // ------------------------------------------------- + // Public Properties + // ------------------------------------------------- + property bool imageMagickAvailable: false + property bool initialized: false + + // Cache directories + readonly property string baseDir: Settings.cacheDir + "images/" + readonly property string wpThumbDir: baseDir + "wallpapers/thumbnails/" + readonly property string wpLargeDir: baseDir + "wallpapers/large/" + readonly property string notificationsDir: baseDir + "notifications/" + readonly property string contributorsDir: baseDir + "contributors/" + + // Supported image formats - extended list when ImageMagick is available + readonly property var basicImageFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.bmp"] + readonly property var extendedImageFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.bmp", "*.webp", "*.avif", "*.heic", "*.heif", "*.tiff", "*.tif", "*.pnm", "*.pgm", "*.ppm", "*.pbm", "*.svg", "*.svgz", "*.ico", "*.icns", "*.jxl", "*.jp2", "*.j2k", "*.exr", "*.hdr", "*.dds", "*.tga"] + readonly property var imageFilters: imageMagickAvailable ? extendedImageFilters : basicImageFilters + + // Check if a file format needs conversion (not natively supported by Qt) + function needsConversion(filePath) { + const ext = "*." + filePath.toLowerCase().split('.').pop(); + return !basicImageFilters.includes(ext); + } + + // ------------------------------------------------- + // Internal State + // ------------------------------------------------- + property var pendingRequests: ({}) + property var fallbackQueue: [] + property bool fallbackProcessing: false + + // Process queues to prevent "too many open files" errors + property var utilityProcessQueue: [] + property int runningUtilityProcesses: 0 + readonly property int maxConcurrentUtilityProcesses: 16 + + // Separate queue for heavy ImageMagick processing (lower concurrency) + property var imageMagickQueue: [] + property int runningImageMagickProcesses: 0 + readonly property int maxConcurrentImageMagickProcesses: 4 + + // ------------------------------------------------- + // Signals + // ------------------------------------------------- + signal cacheHit(string cacheKey, string cachedPath) + signal cacheMiss(string cacheKey) + signal processingComplete(string cacheKey, string cachedPath) + signal processingFailed(string cacheKey, string error) + + // ------------------------------------------------- + // Initialization + // ------------------------------------------------- + function init() { + Logger.i("ImageCache", "Service started"); + createDirectories(); + cleanupOldCache(); + checkMagickProcess.running = true; + } + + function createDirectories() { + Quickshell.execDetached(["mkdir", "-p", wpThumbDir]); + Quickshell.execDetached(["mkdir", "-p", wpLargeDir]); + Quickshell.execDetached(["mkdir", "-p", notificationsDir]); + Quickshell.execDetached(["mkdir", "-p", contributorsDir]); + } + + function cleanupOldCache() { + const dirs = [wpThumbDir, wpLargeDir, notificationsDir, contributorsDir]; + dirs.forEach(function (dir) { + Quickshell.execDetached(["find", dir, "-type", "f", "-mtime", "+15", "-delete"]); + }); + Logger.d("ImageCache", "Cleanup triggered for files older than 15 days"); + } + + // ------------------------------------------------- + // Public API: Get Thumbnail (384x384) + // ------------------------------------------------- + function getThumbnail(sourcePath, callback) { + if (!sourcePath || sourcePath === "") { + callback("", false); + return; + } + + getMtime(sourcePath, function (mtime) { + const cacheKey = generateThumbnailKey(sourcePath, mtime); + const cachedPath = wpThumbDir + cacheKey + ".png"; + + processRequest(cacheKey, cachedPath, sourcePath, callback, function () { + if (imageMagickAvailable) { + startThumbnailProcessing(sourcePath, cachedPath, cacheKey); + } else { + queueFallbackProcessing(sourcePath, cachedPath, cacheKey, 384); + } + }); + }); + } + + // ------------------------------------------------- + // Public API: Get Large Image (scaled to specified dimensions) + // ------------------------------------------------- + function getLarge(sourcePath, width, height, callback) { + if (!sourcePath || sourcePath === "") { + callback("", false); + return; + } + + if (Settings.data.wallpaper.useOriginalImages && !needsConversion(sourcePath)) { + callback(sourcePath, false); + return; + } + + if (!imageMagickAvailable) { + Logger.d("ImageCache", "ImageMagick not available, using original:", sourcePath); + callback(sourcePath, false); + return; + } + + // Fast dimension check - skip processing if image fits screen AND format is Qt-native + getImageDimensions(sourcePath, function (imgWidth, imgHeight) { + const fitsScreen = imgWidth > 0 && imgHeight > 0 && imgWidth <= width && imgHeight <= height; + + if (fitsScreen) { + // Only skip if format is natively supported by Qt + if (!needsConversion(sourcePath)) { + Logger.d("ImageCache", `Image ${imgWidth}x${imgHeight} fits screen ${width}x${height}, using original`); + callback(sourcePath, false); + return; + } + Logger.d("ImageCache", `Image needs conversion despite fitting screen`); + } + + // Use actual image dimensions if it fits (convert without upscaling), otherwise use screen dimensions + const targetWidth = fitsScreen ? imgWidth : width; + const targetHeight = fitsScreen ? imgHeight : height; + + getMtime(sourcePath, function (mtime) { + const cacheKey = generateLargeKey(sourcePath, width, height, mtime); + const cachedPath = wpLargeDir + cacheKey + ".png"; + + processRequest(cacheKey, cachedPath, sourcePath, callback, function () { + startLargeProcessing(sourcePath, cachedPath, targetWidth, targetHeight, cacheKey); + }); + }); + }); + } + + // ------------------------------------------------- + // Public API: Get Notification Icon (64x64) + // ------------------------------------------------- + function getNotificationIcon(imageUri, appName, summary, callback) { + if (!imageUri || imageUri === "") { + callback("", false); + return; + } + + // Resolve bare file path for temp check + const filePath = imageUri.startsWith("file://") ? imageUri.substring(7) : imageUri; + + // File paths in persistent locations are used directly, not cached + if ((imageUri.startsWith("/") || imageUri.startsWith("file://")) && !isTemporaryPath(filePath)) { + callback(imageUri, false); + return; + } + + const cacheKey = generateNotificationKey(imageUri, appName, summary); + const cachedPath = notificationsDir + cacheKey + ".png"; + + // Temporary file paths are copied to cache before the source is cleaned up + if (imageUri.startsWith("/") || imageUri.startsWith("file://")) { + processRequest(cacheKey, cachedPath, imageUri, callback, function () { + copyTempFileToCache(filePath, cachedPath, cacheKey); + }); + return; + } + + processRequest(cacheKey, cachedPath, imageUri, callback, function () { + // Notifications always use Qt fallback (image:// URIs can't be read by ImageMagick) + queueFallbackProcessing(imageUri, cachedPath, cacheKey, 64); + }); + } + + // Check if a path is in a temporary directory that may be cleaned up + function isTemporaryPath(path) { + return path.startsWith("/tmp/"); + } + + // Copy a temporary file to the cache directory + function copyTempFileToCache(sourcePath, destPath, cacheKey) { + const srcEsc = sourcePath.replace(/'/g, "'\\''"); + const dstEsc = destPath.replace(/'/g, "'\\''"); + + const processString = ` + import QtQuick + import Quickshell.Io + Process { + command: ["cp", "--", "${srcEsc}", "${dstEsc}"] + stdout: StdioCollector {} + stderr: StdioCollector {} + } + `; + + queueUtilityProcess({ + name: "CopyTempFile_" + cacheKey, + processString: processString, + onComplete: function (exitCode) { + if (exitCode === 0) { + Logger.d("ImageCache", "Temp file cached:", destPath); + notifyCallbacks(cacheKey, destPath, true); + } else { + Logger.w("ImageCache", "Failed to cache temp file:", sourcePath); + notifyCallbacks(cacheKey, "", false); + } + }, + onError: function () { + Logger.e("ImageCache", "Error caching temp file:", sourcePath); + notifyCallbacks(cacheKey, "", false); + } + }); + } + + // ------------------------------------------------- + // Public API: Get Circular Avatar (256x256) + // ------------------------------------------------- + function getCircularAvatar(url, username, callback) { + if (!url || !username) { + callback("", false); + return; + } + + const cacheKey = username; + const cachedPath = contributorsDir + username + "_circular.png"; + + processRequest(cacheKey, cachedPath, url, callback, function () { + if (imageMagickAvailable) { + downloadAndProcessAvatar(url, username, cachedPath, cacheKey); + } else { + // No fallback for circular avatars without ImageMagick + Logger.w("ImageCache", "Circular avatars require ImageMagick"); + notifyCallbacks(cacheKey, "", false); + } + }); + } + + // ------------------------------------------------- + // Cache Key Generation + // ------------------------------------------------- + function generateThumbnailKey(sourcePath, mtime) { + const keyString = sourcePath + "@384x384@" + (mtime || "unknown"); + return Checksum.sha256(keyString); + } + + function generateLargeKey(sourcePath, width, height, mtime) { + const keyString = sourcePath + "@" + width + "x" + height + "@" + (mtime || "unknown"); + return Checksum.sha256(keyString); + } + + function generateNotificationKey(imageUri, appName, summary) { + if (imageUri.startsWith("image://qsimage/")) { + return Checksum.sha256(appName + "|" + summary); + } + return Checksum.sha256(imageUri); + } + + // ------------------------------------------------- + // Request Processing (with coalescing) + // ------------------------------------------------- + function processRequest(cacheKey, cachedPath, sourcePath, callback, processFn) { + // Check if already processing this request + if (pendingRequests[cacheKey]) { + pendingRequests[cacheKey].callbacks.push(callback); + Logger.d("ImageCache", "Coalescing request for:", cacheKey); + return; + } + + // Check cache first + checkFileExists(cachedPath, function (exists) { + if (exists) { + Logger.d("ImageCache", "Cache hit:", cachedPath); + callback(cachedPath, true); + cacheHit(cacheKey, cachedPath); + return; + } + + // Re-check pendingRequests (race condition fix) + if (pendingRequests[cacheKey]) { + pendingRequests[cacheKey].callbacks.push(callback); + return; + } + + // Start new processing + Logger.d("ImageCache", "Cache miss, processing:", sourcePath); + cacheMiss(cacheKey); + pendingRequests[cacheKey] = { + callbacks: [callback], + sourcePath: sourcePath + }; + + processFn(); + }); + } + + function notifyCallbacks(cacheKey, path, success) { + const request = pendingRequests[cacheKey]; + if (request) { + request.callbacks.forEach(function (cb) { + cb(path, success); + }); + delete pendingRequests[cacheKey]; + } + + if (success) { + processingComplete(cacheKey, path); + } else { + processingFailed(cacheKey, "Processing failed"); + } + } + + // ------------------------------------------------- + // ImageMagick Processing: Thumbnail + // ------------------------------------------------- + function startThumbnailProcessing(sourcePath, outputPath, cacheKey) { + const srcEsc = sourcePath.replace(/'/g, "'\\''"); + const dstEsc = outputPath.replace(/'/g, "'\\''"); + + // Use Lanczos filter for high-quality downscaling, subtle unsharp mask, and PNG for lossless output + const command = `magick '${srcEsc}' -auto-orient -filter Lanczos -resize '384x384^' -gravity center -extent 384x384 -unsharp 0x0.5 '${dstEsc}'`; + + runProcess(command, cacheKey, outputPath, sourcePath); + } + + // ------------------------------------------------- + // ImageMagick Processing: Large + // ------------------------------------------------- + function startLargeProcessing(sourcePath, outputPath, width, height, cacheKey) { + const srcEsc = sourcePath.replace(/'/g, "'\\''"); + const dstEsc = outputPath.replace(/'/g, "'\\''"); + + // Use Lanczos filter for high-quality downscaling and PNG for lossless output + const command = `magick '${srcEsc}' -auto-orient -filter Lanczos -resize '${width}x${height}^' '${dstEsc}'`; + + runProcess(command, cacheKey, outputPath, sourcePath); + } + + // ------------------------------------------------- + // ImageMagick Processing: Circular Avatar + // ------------------------------------------------- + function downloadAndProcessAvatar(url, username, outputPath, cacheKey) { + const tempPath = contributorsDir + username + "_temp.png"; + const tempEsc = tempPath.replace(/'/g, "'\\''"); + const urlEsc = url.replace(/'/g, "'\\''"); + + // Download first (uses utility queue since curl/wget are lightweight) + const downloadCmd = `curl -L -s -o '${tempEsc}' '${urlEsc}' || wget -q -O '${tempEsc}' '${urlEsc}'`; + + const processString = ` + import QtQuick + import Quickshell.Io + Process { + command: ["sh", "-c", "${downloadCmd.replace(/"/g, '\\"')}"] + stdout: StdioCollector {} + stderr: StdioCollector {} + } + `; + + queueUtilityProcess({ + name: "DownloadProcess_" + cacheKey, + processString: processString, + onComplete: function (exitCode) { + if (exitCode !== 0) { + Logger.e("ImageCache", "Failed to download avatar for", username); + notifyCallbacks(cacheKey, "", false); + return; + } + // Now process with ImageMagick + processCircularAvatar(tempPath, outputPath, cacheKey); + }, + onError: function () { + notifyCallbacks(cacheKey, "", false); + } + }); + } + + function processCircularAvatar(inputPath, outputPath, cacheKey) { + const srcEsc = inputPath.replace(/'/g, "'\\''"); + const dstEsc = outputPath.replace(/'/g, "'\\''"); + + // ImageMagick command for circular crop with alpha + const command = `magick '${srcEsc}' -resize 256x256^ -gravity center -extent 256x256 -alpha set \\( +clone -channel A -evaluate set 0 +channel -fill white -draw 'circle 128,128 128,0' \\) -compose DstIn -composite '${dstEsc}'`; + + queueImageMagickProcess({ + command: command, + cacheKey: cacheKey, + onComplete: function (exitCode) { + // Clean up temp file + Quickshell.execDetached(["rm", "-f", inputPath]); + + if (exitCode !== 0) { + Logger.e("ImageCache", "Failed to create circular avatar"); + notifyCallbacks(cacheKey, "", false); + } else { + Logger.d("ImageCache", "Circular avatar created:", outputPath); + notifyCallbacks(cacheKey, outputPath, true); + } + }, + onError: function () { + Quickshell.execDetached(["rm", "-f", inputPath]); + notifyCallbacks(cacheKey, "", false); + } + }); + } + + // ------------------------------------------------- + // Generic Process Runner (with queue for ImageMagick) + // ------------------------------------------------- + + // Queue an ImageMagick process and run it when a slot is available + function queueImageMagickProcess(request) { + imageMagickQueue.push(request); + processImageMagickQueue(); + } + + // Process queued ImageMagick requests up to the concurrency limit + function processImageMagickQueue() { + while (runningImageMagickProcesses < maxConcurrentImageMagickProcesses && imageMagickQueue.length > 0) { + const request = imageMagickQueue.shift(); + runImageMagickProcess(request); + } + } + + // Actually run an ImageMagick process + function runImageMagickProcess(request) { + runningImageMagickProcesses++; + + const processString = ` + import QtQuick + import Quickshell.Io + Process { + command: ["sh", "-c", ""] + stdout: StdioCollector {} + stderr: StdioCollector {} + } + `; + + try { + const processObj = Qt.createQmlObject(processString, root, "ImageProcess_" + request.cacheKey); + processObj.command = ["sh", "-c", request.command]; + + processObj.exited.connect(function (exitCode) { + processObj.destroy(); + runningImageMagickProcesses--; + request.onComplete(exitCode, processObj); + processImageMagickQueue(); + }); + + processObj.running = true; + } catch (e) { + Logger.e("ImageCache", "Failed to create process:", e); + runningImageMagickProcesses--; + request.onError(e); + processImageMagickQueue(); + } + } + + function runProcess(command, cacheKey, outputPath, sourcePath) { + queueImageMagickProcess({ + command: command, + cacheKey: cacheKey, + onComplete: function (exitCode, proc) { + if (exitCode !== 0) { + const stderrText = proc.stderr.text || ""; + Logger.e("ImageCache", "Processing failed:", stderrText); + notifyCallbacks(cacheKey, sourcePath, false); + } else { + Logger.d("ImageCache", "Processing complete:", outputPath); + notifyCallbacks(cacheKey, outputPath, true); + } + }, + onError: function () { + notifyCallbacks(cacheKey, sourcePath, false); + } + }); + } + + // ------------------------------------------------- + // Qt Fallback Renderer + // ------------------------------------------------- + PanelWindow { + id: fallbackRenderer + implicitWidth: 0 + implicitHeight: 0 + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.namespace: "noctalia-image-cache-renderer" + color: "transparent" + mask: Region {} + + Image { + id: fallbackImage + property string cacheKey: "" + property string destPath: "" + property int targetSize: 256 + + width: targetSize + height: targetSize + visible: true + cache: false + asynchronous: true + fillMode: Image.PreserveAspectCrop + mipmap: true + antialiasing: true + + onStatusChanged: { + if (!cacheKey) + return; + + if (status === Image.Ready) { + grabToImage(function (result) { + if (result.saveToFile(destPath)) { + Logger.d("ImageCache", "Fallback cache created:", destPath); + root.notifyCallbacks(cacheKey, destPath, true); + } else { + Logger.e("ImageCache", "Failed to save fallback cache"); + root.notifyCallbacks(cacheKey, "", false); + } + processNextFallback(); + }); + } else if (status === Image.Error) { + Logger.e("ImageCache", "Fallback image load failed"); + root.notifyCallbacks(cacheKey, "", false); + processNextFallback(); + } + } + + function processNextFallback() { + cacheKey = ""; + destPath = ""; + source = ""; + + if (fallbackQueue.length > 0) { + const next = fallbackQueue.shift(); + cacheKey = next.cacheKey; + destPath = next.destPath; + targetSize = next.size; + source = next.sourcePath; + } else { + fallbackProcessing = false; + } + } + } + } + + function queueFallbackProcessing(sourcePath, destPath, cacheKey, size) { + fallbackQueue.push({ + sourcePath: sourcePath, + destPath: destPath, + cacheKey: cacheKey, + size: size + }); + + if (!fallbackProcessing) { + fallbackProcessing = true; + const item = fallbackQueue.shift(); + fallbackImage.cacheKey = item.cacheKey; + fallbackImage.destPath = item.destPath; + fallbackImage.targetSize = item.size; + fallbackImage.source = item.sourcePath; + } + } + + // ------------------------------------------------- + // Utility Functions (with process queue to prevent fd exhaustion) + // ------------------------------------------------- + + // Queue a utility process and run it when a slot is available + function queueUtilityProcess(request) { + utilityProcessQueue.push(request); + processUtilityQueue(); + } + + // Process queued utility requests up to the concurrency limit + function processUtilityQueue() { + while (runningUtilityProcesses < maxConcurrentUtilityProcesses && utilityProcessQueue.length > 0) { + const request = utilityProcessQueue.shift(); + runUtilityProcess(request); + } + } + + // Actually run a utility process + function runUtilityProcess(request) { + runningUtilityProcesses++; + + try { + const processObj = Qt.createQmlObject(request.processString, root, request.name); + + processObj.exited.connect(function (exitCode) { + processObj.destroy(); + runningUtilityProcesses--; + request.onComplete(exitCode, processObj); + processUtilityQueue(); + }); + + processObj.running = true; + } catch (e) { + Logger.e("ImageCache", "Failed to create " + request.name + ":", e); + runningUtilityProcesses--; + request.onError(e); + processUtilityQueue(); + } + } + + function getMtime(filePath, callback) { + const pathEsc = filePath.replace(/'/g, "'\\''"); + const processString = ` + import QtQuick + import Quickshell.Io + Process { + command: ["stat", "-c", "%Y", "${pathEsc}"] + stdout: StdioCollector {} + stderr: StdioCollector {} + } + `; + + queueUtilityProcess({ + name: "MtimeProcess", + processString: processString, + onComplete: function (exitCode, proc) { + const mtime = exitCode === 0 ? proc.stdout.text.trim() : ""; + callback(mtime); + }, + onError: function () { + callback(""); + } + }); + } + + function checkFileExists(filePath, callback) { + const pathEsc = filePath.replace(/'/g, "'\\''"); + const processString = ` + import QtQuick + import Quickshell.Io + Process { + command: ["test", "-f", "${pathEsc}"] + stdout: StdioCollector {} + stderr: StdioCollector {} + } + `; + + queueUtilityProcess({ + name: "FileExistsProcess", + processString: processString, + onComplete: function (exitCode) { + callback(exitCode === 0); + }, + onError: function () { + callback(false); + } + }); + } + + function getImageDimensions(filePath, callback) { + const pathEsc = filePath.replace(/'/g, "'\\''"); + const processString = ` + import QtQuick + import Quickshell.Io + Process { + command: ["identify", "-ping", "-format", "%w %h", "${pathEsc}[0]"] + stdout: StdioCollector {} + stderr: StdioCollector {} + } + `; + + queueUtilityProcess({ + name: "IdentifyProcess", + processString: processString, + onComplete: function (exitCode, proc) { + let width = 0, height = 0; + if (exitCode === 0) { + const parts = proc.stdout.text.trim().split(" "); + if (parts.length >= 2) { + width = parseInt(parts[0], 10) || 0; + height = parseInt(parts[1], 10) || 0; + } + } + callback(width, height); + }, + onError: function () { + callback(0, 0); + } + }); + } + + // ------------------------------------------------- + // Cache Invalidation + // ------------------------------------------------- + function invalidateThumbnail(sourcePath) { + Logger.i("ImageCache", "Invalidating thumbnail for:", sourcePath); + // Since cache keys include hash, we'd need to track mappings + // For simplicity, clear all thumbnails + clearThumbnails(); + } + + function invalidateLarge(sourcePath) { + Logger.i("ImageCache", "Invalidating large for:", sourcePath); + clearLarge(); + } + + function invalidateNotification(imageId) { + const path = notificationsDir + imageId + ".png"; + Quickshell.execDetached(["rm", "-f", path]); + } + + function invalidateAvatar(username) { + const path = contributorsDir + username + "_circular.png"; + Quickshell.execDetached(["rm", "-f", path]); + } + + // ------------------------------------------------- + // Clear Cache Functions + // ------------------------------------------------- + function clearAll() { + Logger.i("ImageCache", "Clearing all cache"); + clearThumbnails(); + clearLarge(); + clearNotifications(); + clearContributors(); + } + + function clearThumbnails() { + Logger.i("ImageCache", "Clearing thumbnails cache"); + Quickshell.execDetached(["rm", "-rf", wpThumbDir]); + Quickshell.execDetached(["mkdir", "-p", wpThumbDir]); + } + + function clearLarge() { + Logger.i("ImageCache", "Clearing large cache"); + Quickshell.execDetached(["rm", "-rf", wpLargeDir]); + Quickshell.execDetached(["mkdir", "-p", wpLargeDir]); + } + + function clearNotifications() { + Logger.i("ImageCache", "Clearing notifications cache"); + Quickshell.execDetached(["rm", "-rf", notificationsDir]); + Quickshell.execDetached(["mkdir", "-p", notificationsDir]); + } + + function clearContributors() { + Logger.i("ImageCache", "Clearing contributors cache"); + Quickshell.execDetached(["rm", "-rf", contributorsDir]); + Quickshell.execDetached(["mkdir", "-p", contributorsDir]); + } + + // ------------------------------------------------- + // ImageMagick Detection + // ------------------------------------------------- + Process { + id: checkMagickProcess + command: ["sh", "-c", "command -v magick"] + running: false + + stdout: StdioCollector {} + stderr: StdioCollector {} + + onExited: function (exitCode) { + root.imageMagickAvailable = (exitCode === 0); + root.initialized = true; + if (root.imageMagickAvailable) { + Logger.i("ImageCache", "ImageMagick available"); + } else { + Logger.w("ImageCache", "ImageMagick not found, using Qt fallback"); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/UI/LauncherProviderRegistry.qml b/arch/.config/quickshell/noctalia-shell/Services/UI/LauncherProviderRegistry.qml new file mode 100644 index 0000000..f5810ab --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/UI/LauncherProviderRegistry.qml @@ -0,0 +1,108 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Commons +import qs.Services.Noctalia + +Singleton { + id: root + + signal pluginProviderRegistryUpdated + + // Plugin provider storage + property var pluginProviders: ({}) // { "plugin:pluginId": component } + property var pluginProviderMetadata: ({}) // { "plugin:pluginId": metadata } + + // Persistent provider instances โ€” survive LauncherCore destruction/recreation + // so plugins don't re-parse large datasets on every launcher open. + property var providerInstances: ({}) // { "plugin:pluginId": instance } + + function init() { + Logger.i("LauncherProviderRegistry", "Service started"); + } + + // Register a plugin launcher provider and instantiate it immediately + function registerPluginProvider(pluginId, component, metadata) { + if (!pluginId || !component) { + Logger.e("LauncherProviderRegistry", "Cannot register plugin provider: invalid parameters"); + return false; + } + + var providerId = "plugin:" + pluginId; + + pluginProviders[providerId] = component; + pluginProviderMetadata[providerId] = metadata || {}; + + // Instantiate immediately so data loading starts in the background + var pluginApi = PluginService.getPluginAPI(pluginId); + if (pluginApi) { + var instance = component.createObject(null, { + pluginApi: pluginApi + }); + if (instance) { + providerInstances[providerId] = instance; + if (instance.init) + instance.init(); + Logger.i("LauncherProviderRegistry", "Registered and instantiated plugin provider:", providerId); + } else { + Logger.e("LauncherProviderRegistry", "Failed to instantiate plugin provider:", providerId); + } + } + + root.pluginProviderRegistryUpdated(); + return true; + } + + // Unregister a plugin launcher provider + function unregisterPluginProvider(pluginId) { + var providerId = "plugin:" + pluginId; + + if (!pluginProviders[providerId]) { + Logger.w("LauncherProviderRegistry", "Plugin provider not registered:", providerId); + return false; + } + + if (providerInstances[providerId]) { + providerInstances[providerId].destroy(); + delete providerInstances[providerId]; + } + + delete pluginProviders[providerId]; + delete pluginProviderMetadata[providerId]; + + Logger.i("LauncherProviderRegistry", "Unregistered plugin provider:", providerId); + root.pluginProviderRegistryUpdated(); + return true; + } + + // Get list of registered plugin provider IDs + function getPluginProviders() { + return Object.keys(pluginProviders); + } + + // Get the live provider instance by ID + function getProviderInstance(providerId) { + return providerInstances[providerId] || null; + } + + // Get provider component by ID + function getProviderComponent(providerId) { + return pluginProviders[providerId] || null; + } + + // Get provider metadata by ID + function getProviderMetadata(providerId) { + return pluginProviderMetadata[providerId] || null; + } + + // Check if ID is a plugin provider + function isPluginProvider(id) { + return id.startsWith("plugin:"); + } + + // Check if provider exists + function hasProvider(providerId) { + return providerId in pluginProviders; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/UI/PanelService.qml b/arch/.config/quickshell/noctalia-shell/Services/UI/PanelService.qml new file mode 100644 index 0000000..89993f9 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/UI/PanelService.qml @@ -0,0 +1,421 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Commons +import qs.Services.Compositor + +Singleton { + id: root + + // A ref. to the lockScreen, so it's accessible from anywhere. + property var lockScreen: null + + // Panels + property var registeredPanels: ({}) + property var openedPanel: null + property var closingPanel: null + property bool closedImmediately: false + + // Overlay launcher state (separate from normal panels) + property bool overlayLauncherOpen: false + property var overlayLauncherScreen: null + property var overlayLauncherCore: null // Reference to LauncherCore when overlay is active + // Brief window after panel opens where Exclusive keyboard is allowed on Hyprland + // This allows text inputs to receive focus, then switches to OnDemand for click-to-close + property bool isInitializingKeyboard: false + + // Global state for keybind recording components to block global shortcuts + property bool isKeybindRecording: false + + signal willOpen + signal didClose + + // Background slot assignments for dynamic panel background rendering + // Slot 0: currently opening/open panel, Slot 1: closing panel + property var backgroundSlotAssignments: [null, null] + signal slotAssignmentChanged(int slotIndex, var panel) + + function assignToSlot(slotIndex, panel) { + if (backgroundSlotAssignments[slotIndex] !== panel) { + var newAssignments = backgroundSlotAssignments.slice(); + newAssignments[slotIndex] = panel; + backgroundSlotAssignments = newAssignments; + slotAssignmentChanged(slotIndex, panel); + } + } + + // Popup menu windows (one per screen) - used for both tray menus and context menus + property var popupMenuWindows: ({}) + signal popupMenuWindowRegistered(var screen) + + // Register this panel (called after panel is loaded) + function registerPanel(panel) { + registeredPanels[panel.objectName] = panel; + Logger.d("PanelService", "Registered panel:", panel.objectName); + } + + // Register popup menu window for a screen + function registerPopupMenuWindow(screen, window) { + if (!screen || !window) + return; + var key = screen.name; + popupMenuWindows[key] = window; + Logger.d("PanelService", "Registered popup menu window for screen:", key); + popupMenuWindowRegistered(screen); + } + + // Unregister popup menu window for a screen (called on destruction) + function unregisterPopupMenuWindow(screen) { + if (!screen) + return; + var key = screen.name; + delete popupMenuWindows[key]; + Logger.d("PanelService", "Unregistered popup menu window for screen:", key); + } + + // Get popup menu window for a screen + function getPopupMenuWindow(screen) { + if (!screen) + return null; + return popupMenuWindows[screen.name] || null; + } + + // Show a context menu with proper handling for all compositors + // Optional targetItem: if provided, menu will be horizontally centered on this item instead of anchorItem + function showContextMenu(contextMenu, anchorItem, screen, targetItem) { + if (!contextMenu || !anchorItem) + return; + + // Close any previously opened context menu first + closeContextMenu(screen); + + var popupMenuWindow = getPopupMenuWindow(screen); + if (popupMenuWindow) { + popupMenuWindow.showContextMenu(contextMenu); + contextMenu.openAtItem(anchorItem, screen, targetItem); + } + } + + // Close any open context menu or popup menu window + function closeContextMenu(screen) { + var popupMenuWindow = getPopupMenuWindow(screen); + if (popupMenuWindow && popupMenuWindow.visible) { + popupMenuWindow.close(); + } + } + + // Show a tray menu with proper handling for all compositors + // Returns true if menu was shown successfully + function showTrayMenu(screen, trayItem, trayMenu, anchorItem, menuX, menuY, widgetSection, widgetIndex) { + if (!trayItem || !trayMenu || !anchorItem) + return false; + + // Close any previously opened menu first + closeContextMenu(screen); + + trayMenu.trayItem = trayItem; + trayMenu.widgetSection = widgetSection; + trayMenu.widgetIndex = widgetIndex; + + var popupMenuWindow = getPopupMenuWindow(screen); + if (popupMenuWindow) { + popupMenuWindow.open(); + trayMenu.showAt(anchorItem, menuX, menuY); + } else { + return false; + } + return true; + } + + // Close tray menu + function closeTrayMenu(screen) { + var popupMenuWindow = getPopupMenuWindow(screen); + if (popupMenuWindow) { + // This closes both the window and calls hideMenu on the tray menu + popupMenuWindow.close(); + } + } + + // Find a fallback screen, prioritizing 0x0 position (primary) + function findFallbackScreen() { + let primaryCandidate = null; + let firstScreen = null; + + for (let i = 0; i < Quickshell.screens.length; i++) { + const s = Quickshell.screens[i]; + if (s.x === 0 && s.y === 0) { + primaryCandidate = s; + } + if (!firstScreen) { + firstScreen = s; + } + } + + return primaryCandidate || firstScreen || null; + } + + // Returns a panel (loads it on-demand if not yet loaded) + // By default, if panel not found on screen, tries other screens (favoring 0x0) + // Pass fallback=false to disable this behavior + function getPanel(name, screen, fallback = true) { + if (!screen) { + Logger.d("PanelService", "missing screen for getPanel:", name); + // If no screen specified, return the first matching panel + for (var key in registeredPanels) { + if (key.startsWith(name + "-")) { + return registeredPanels[key]; + } + } + return null; + } + + var panelKey = `${name}-${screen.name}`; + + // Check if panel is already loaded + if (registeredPanels[panelKey]) { + return registeredPanels[panelKey]; + } + + // If fallback enabled, try to find panel on another screen + if (fallback) { + // First try the primary screen (0x0) + var fallbackScreen = findFallbackScreen(); + if (fallbackScreen && fallbackScreen.name !== screen.name) { + var fallbackKey = `${name}-${fallbackScreen.name}`; + if (registeredPanels[fallbackKey]) { + Logger.d("PanelService", "Panel fallback from", screen.name, "to", fallbackScreen.name); + return registeredPanels[fallbackKey]; + } + } + + // Try any other screen + for (var key in registeredPanels) { + if (key.startsWith(name + "-")) { + Logger.d("PanelService", "Panel fallback to first available:", key); + return registeredPanels[key]; + } + } + } + + Logger.w("PanelService", "Panel not found:", panelKey); + return null; + } + + // Check if a panel exists + function hasPanel(name) { + return name in registeredPanels; + } + + // Check if panels can be shown on a given screen (has bar enabled or allowPanelsOnScreenWithoutBar) + function canShowPanelsOnScreen(screen) { + const name = screen?.name || ""; + const monitors = Settings.data.bar.monitors || []; + const allowPanelsOnScreenWithoutBar = Settings.data.general.allowPanelsOnScreenWithoutBar; + return allowPanelsOnScreenWithoutBar || monitors.length === 0 || monitors.includes(name); + } + + // Find a screen that can show panels + function findScreenForPanels() { + for (let i = 0; i < Quickshell.screens.length; i++) { + if (canShowPanelsOnScreen(Quickshell.screens[i])) { + return Quickshell.screens[i]; + } + } + return null; + } + + // Timer to switch from Exclusive to OnDemand keyboard focus on Hyprland + Timer { + id: keyboardInitTimer + interval: 100 + repeat: false + onTriggered: { + root.isInitializingKeyboard = false; + } + } + + // Helper to keep only one panel open at any time + function willOpenPanel(panel) { + // Close overlay launcher if open + if (overlayLauncherOpen) { + overlayLauncherOpen = false; + overlayLauncherScreen = null; + } + + if (openedPanel && openedPanel !== panel) { + // Move current panel to closing slot before closing it + closingPanel = openedPanel; + assignToSlot(1, closingPanel); + openedPanel.close(); + } + + // Assign new panel to open slot + openedPanel = panel; + assignToSlot(0, panel); + + // Start keyboard initialization period (for Hyprland workaround) + if (panel && panel.exclusiveKeyboard) { + isInitializingKeyboard = true; + keyboardInitTimer.restart(); + } + + // emit signal + willOpen(); + } + + // Open launcher panel (handles both normal and overlay mode) + function openLauncher(screen) { + if (Settings.data.appLauncher.overviewLayer) { + // Close any regular panel first + if (openedPanel) { + closingPanel = openedPanel; + assignToSlot(1, closingPanel); + openedPanel.close(); + openedPanel = null; + } + // Open overlay launcher + overlayLauncherOpen = true; + overlayLauncherScreen = screen; + willOpen(); + } else { + // Normal mode - use the SmartPanel + var panel = getPanel("launcherPanel", screen); + if (panel) + panel.open(); + } + } + + // Toggle launcher panel + function toggleLauncher(screen) { + if (Settings.data.appLauncher.overviewLayer) { + if (overlayLauncherOpen && overlayLauncherScreen === screen) { + closeOverlayLauncher(); + } else { + openLauncher(screen); + } + } else { + var panel = getPanel("launcherPanel", screen); + if (panel) + panel.toggle(); + } + } + + // Close overlay launcher + function closeOverlayLauncher() { + if (overlayLauncherOpen) { + overlayLauncherOpen = false; + overlayLauncherScreen = null; + didClose(); + } + } + + // Close overlay launcher immediately (for app launches) + function closeOverlayLauncherImmediately() { + if (overlayLauncherOpen) { + closedImmediately = true; + overlayLauncherOpen = false; + overlayLauncherScreen = null; + didClose(); + } + } + + // ==================== Unified Launcher API ==================== + // These methods work for both normal (SmartPanel) and overlay modes + + function isLauncherOpen(screen) { + if (Settings.data.appLauncher.overviewLayer) { + return overlayLauncherOpen && overlayLauncherScreen === screen; + } else { + var panel = getPanel("launcherPanel", screen); + return panel ? panel.isPanelOpen : false; + } + } + + function getLauncherSearchText(screen) { + if (Settings.data.appLauncher.overviewLayer) { + return overlayLauncherCore ? overlayLauncherCore.searchText : ""; + } else { + var panel = getPanel("launcherPanel", screen); + return panel ? panel.searchText : ""; + } + } + + function setLauncherSearchText(screen, text) { + if (Settings.data.appLauncher.overviewLayer) { + if (overlayLauncherCore) + overlayLauncherCore.setSearchText(text); + } else { + var panel = getPanel("launcherPanel", screen); + if (panel) + panel.setSearchText(text); + } + } + + function openLauncherWithSearch(screen, searchText) { + if (Settings.data.appLauncher.overviewLayer) { + openLauncher(screen); + // Set search text after core is ready + Qt.callLater(() => { + if (overlayLauncherCore) + overlayLauncherCore.setSearchText(searchText); + }); + } else { + var panel = getPanel("launcherPanel", screen); + if (panel) { + panel.open(); + panel.setSearchText(searchText); + } + } + } + + function closeLauncher(screen) { + if (Settings.data.appLauncher.overviewLayer) { + closeOverlayLauncher(); + } else { + var panel = getPanel("launcherPanel", screen); + if (panel) + panel.close(); + } + } + + // Close any open panel (for general use) + function closePanel() { + if (overlayLauncherOpen) { + closeOverlayLauncher(); + } else if (openedPanel && openedPanel.close) { + openedPanel.close(); + } + } + + function closedPanel(panel) { + if (openedPanel && openedPanel === panel) { + openedPanel = null; + assignToSlot(0, null); + } + + if (closingPanel && closingPanel === panel) { + closingPanel = null; + assignToSlot(1, null); + } + + // Reset keyboard init state + isInitializingKeyboard = false; + keyboardInitTimer.stop(); + + // emit signal + didClose(); + } + + // Close panels when compositor overview opens (if setting is enabled) + Connections { + target: CompositorService + enabled: Settings.data.bar.hideOnOverview + + function onOverviewActiveChanged() { + if (CompositorService.overviewActive && root.openedPanel) { + root.openedPanel.close(); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/UI/SettingsPanelService.qml b/arch/.config/quickshell/noctalia-shell/Services/UI/SettingsPanelService.qml new file mode 100644 index 0000000..fa561e2 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/UI/SettingsPanelService.qml @@ -0,0 +1,143 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Commons +import qs.Services.UI + +Singleton { + id: root + + // Track if the settings window is open + property bool isWindowOpen: false + + // Reference to the window (set by SettingsPanelWindow) + property var settingsWindow: null + + // Requested tab when opening + property int requestedTab: 0 + + // Requested subtab when opening (-1 means no specific subtab) + property int requestedSubTab: -1 + + // Requested entry for search navigation + property var requestedEntry: null + + signal windowOpened + signal windowClosed + + function openToEntry(entry, screen) { + if (Settings.data.ui.settingsPanelMode === "window") { + requestedEntry = entry; + if (settingsWindow) { + settingsWindow.visible = true; + isWindowOpen = true; + windowOpened(); + settingsWindow.navigateToEntry(entry); + } + } else { + if (!screen) { + Logger.w("SettingsPanelService", "Screen parameter required for panel mode"); + return; + } + var settingsPanel = PanelService.getPanel("settingsPanel", screen); + if (settingsPanel) { + settingsPanel.requestedEntry = entry; + settingsPanel.open(); + } + } + } + + // Unified function to open settings to a specific tab and subtab + // Respects user's settingsPanelMode setting (window vs panel) + // For panel mode, screen parameter is required + function openToTab(tab, subTab, screen) { + const tabId = tab !== undefined ? tab : 0; + const subTabId = subTab !== undefined ? subTab : -1; + + if (Settings.data.ui.settingsPanelMode === "window") { + requestedTab = tabId; + requestedSubTab = subTabId; + if (settingsWindow) { + settingsWindow.visible = true; + isWindowOpen = true; + windowOpened(); + settingsWindow.navigateTo(tabId, subTabId); + } + } else { + if (!screen) { + Logger.w("SettingsPanelService", "Screen parameter required for panel mode"); + return; + } + var settingsPanel = PanelService.getPanel("settingsPanel", screen); + if (settingsPanel) { + settingsPanel.openToTab(tabId, subTabId); + } + } + } + + function openWindow(tab) { + requestedTab = tab !== undefined ? tab : 0; + requestedSubTab = -1; + if (settingsWindow) { + settingsWindow.visible = true; + isWindowOpen = true; + windowOpened(); + settingsWindow.navigateTo(requestedTab, -1); + } + } + + function closeWindow() { + if (settingsWindow) { + settingsWindow.visible = false; + isWindowOpen = false; + windowClosed(); + } + } + + function toggleWindow(tab) { + if (isWindowOpen) { + closeWindow(); + } else { + openWindow(tab); + } + } + + // Unified toggle: opens to tab/subtab if closed, closes if open + // Respects settingsPanelMode setting + function toggle(tab, subTab, screen) { + const tabId = tab !== undefined ? tab : 0; + const subTabId = subTab !== undefined ? subTab : -1; + + if (Settings.data.ui.settingsPanelMode === "window") { + if (isWindowOpen) { + closeWindow(); + } else { + openToTab(tabId, subTabId); + } + } else { + if (!screen) { + Logger.w("SettingsPanelService", "Screen parameter required for panel mode"); + return; + } + var settingsPanel = PanelService.getPanel("settingsPanel", screen); + if (settingsPanel?.isPanelOpen) { + settingsPanel.close(); + } else { + settingsPanel?.openToTab(tabId, subTabId); + } + } + } + + // Unified close for both modes + function close(screen) { + if (Settings.data.ui.settingsPanelMode === "window") { + closeWindow(); + } else { + if (!screen) + return; + var settingsPanel = PanelService.getPanel("settingsPanel", screen); + settingsPanel?.close(); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/UI/SettingsSearchService.qml b/arch/.config/quickshell/noctalia-shell/Services/UI/SettingsSearchService.qml new file mode 100644 index 0000000..50dc7ac --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/UI/SettingsSearchService.qml @@ -0,0 +1,158 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.Compositor +import qs.Services.Power +import qs.Services.System + +Singleton { + id: root + + property var searchIndex: [] + + FileView { + path: Quickshell.shellDir + "/Assets/settings-search-index.json" + watchChanges: false + printErrors: false + + onLoaded: { + try { + root.searchIndex = JSON.parse(text()); + } catch (e) { + root.searchIndex = []; + } + } + } + + readonly property var _roots: ({ + "CompositorService": CompositorService, + "Settings": Settings, + "Quickshell": Quickshell, + "IdleService": IdleService, + "SystemStatService": SystemStatService, + "SoundService": SoundService + }) + + function isEntryVisible(entry) { + if (!entry.visibleWhen || entry.visibleWhen.length === 0) + return true; + for (let i = 0; i < entry.visibleWhen.length; i++) { + if (!_evalCondition(entry.visibleWhen[i])) + return false; + } + return true; + } + + function _resolveValue(path) { + const parts = path.split("."); + const rootObj = _roots[parts[0]]; + if (rootObj === undefined) + return undefined; + + let obj = rootObj; + for (let i = 1; i < parts.length; i++) { + if (obj === undefined || obj === null) + return undefined; + let key = parts[i]; + if (key.endsWith("?")) + key = key.slice(0, -1); + obj = obj[key]; + } + return obj; + } + + function _splitAnd(expr) { + const parts = []; + let depth = 0; + let current = ""; + for (let i = 0; i < expr.length; i++) { + const ch = expr[i]; + if (ch === "(") + depth++; + else if (ch === ")") + depth--; + else if (depth === 0 && ch === "&" && i + 1 < expr.length && expr[i + 1] === "&") { + parts.push(current); + current = ""; + i++; + continue; + } + current += ch; + } + parts.push(current); + return parts; + } + + function _evalCondition(expr) { + expr = expr.trim(); + + // Strip outer parentheses + if (expr.startsWith("(") && expr.endsWith(")")) { + let depth = 0; + let allWrapped = true; + for (let i = 0; i < expr.length - 1; i++) { + if (expr[i] === "(") + depth++; + else if (expr[i] === ")") + depth--; + if (depth === 0) { + allWrapped = false; + break; + } + } + if (allWrapped) + return _evalCondition(expr.slice(1, -1)); + } + + // AND: all parts must be true + if (expr.includes("&&")) { + const parts = _splitAnd(expr); + if (parts.length > 1) { + for (let i = 0; i < parts.length; i++) { + if (!_evalCondition(parts[i])) + return false; + } + return true; + } + } + + // Negation + if (expr.startsWith("!")) + return !_evalCondition(expr.slice(1).trim()); + + // Literal false + if (expr === "false") + return false; + + // Strip nullish coalescing fallback + const nullishMatch = expr.match(/^(.+?)\s*\?\?\s*(?:false|true)\s*$/); + if (nullishMatch) + return _evalCondition(nullishMatch[1]); + + // === comparison + let m = expr.match(/^(.+?)\s*===\s*"([^"]*)"\s*$/); + if (m) + return _resolveValue(m[1].trim()) === m[2]; + + // !== comparison + m = expr.match(/^(.+?)\s*!==\s*"([^"]*)"\s*$/); + if (m) + return _resolveValue(m[1].trim()) !== m[2]; + + // > comparison + m = expr.match(/^(.+?)\s*>\s*(\d+)\s*$/); + if (m) + return _resolveValue(m[1].trim()) > parseInt(m[2]); + + // Simple property path โ€” resolve and return truthiness + const val = _resolveValue(expr); + if (val !== undefined) + return !!val; + + // Unrecognized expression โ€” assume visible + return true; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/UI/ToastService.qml b/arch/.config/quickshell/noctalia-shell/Services/UI/ToastService.qml new file mode 100644 index 0000000..93126c3 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/UI/ToastService.qml @@ -0,0 +1,31 @@ +pragma Singleton + +import QtQuick +import Quickshell + +Singleton { + id: root + + // Simple signal-based notification system + // actionLabel: optional label for clickable action link + // actionCallback: optional function to call when action is clicked + signal notify(string title, string description, string icon, string type, int duration, string actionLabel, var actionCallback) + signal dismiss + + // Convenience methods + function showNotice(title, description = "", icon = "", duration = 3000, actionLabel = "", actionCallback = null) { + notify(title, description, icon, "notice", duration, actionLabel, actionCallback); + } + + function showWarning(title, description = "", duration = 4000, actionLabel = "", actionCallback = null) { + notify(title, description, "", "warning", duration, actionLabel, actionCallback); + } + + function showError(title, description = "", duration = 6000, actionLabel = "", actionCallback = null) { + notify(title, description, "", "error", duration, actionLabel, actionCallback); + } + + function dismissToast() { + dismiss(); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/UI/TooltipService.qml b/arch/.config/quickshell/noctalia-shell/Services/UI/TooltipService.qml new file mode 100644 index 0000000..8325855 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/UI/TooltipService.qml @@ -0,0 +1,133 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Commons +import qs.Modules.Tooltip + +Singleton { + id: root + + property var activeTooltip: null + property var pendingTooltip: null // Track tooltip being created + + property Component tooltipComponent: Component { + Tooltip {} + } + + function show(target, content, direction, delay, fontFamily) { + if (!Settings.data.ui.tooltipsEnabled) { + return; + } + + // Don't create if no content + if (!target || !content || (Array.isArray(content) && content.length === 0)) { + Logger.i("Tooltip", "No target or content"); + return; + } + + // Cancel any pending tooltip (same or different target) + if (pendingTooltip) { + pendingTooltip.hideImmediately(); + pendingTooltip.destroy(); + pendingTooltip = null; + } + + // If we have an active tooltip for a different target, hide it + if (activeTooltip && activeTooltip.targetItem !== target) { + activeTooltip.hideImmediately(); + // Don't destroy immediately - let it clean itself up + activeTooltip = null; + } + + // If we already have a tooltip for this target, just update it + if (activeTooltip && activeTooltip.targetItem === target) { + activeTooltip.updateContent(content); + return activeTooltip; + } + + // Create new tooltip instance + const newTooltip = tooltipComponent.createObject(null); + + if (newTooltip) { + // Track as pending until it's visible + pendingTooltip = newTooltip; + + // Connect cleanup when tooltip hides + newTooltip.visibleChanged.connect(() => { + if (!newTooltip.visible) { + // Clean up after a delay to avoid interfering with new tooltips + Qt.callLater(() => { + if (newTooltip && !newTooltip.visible) { + if (activeTooltip === newTooltip) { + activeTooltip = null; + } + if (pendingTooltip === newTooltip) { + pendingTooltip = null; + } + newTooltip.destroy(); + } + }); + } else { + // Tooltip is now visible, move from pending to active + if (pendingTooltip === newTooltip) { + activeTooltip = newTooltip; + pendingTooltip = null; + } + } + }); + + // Show the tooltip + newTooltip.show(target, content, direction || "auto", delay || Style.tooltipDelay, fontFamily); + + return newTooltip; + } else { + Logger.e("Tooltip", "Failed to create tooltip instance"); + } + + return null; + } + + function hide(target) { + // If target is provided, only hide if tooltip belongs to that target + if (target) { + if (pendingTooltip && pendingTooltip.targetItem === target) { + pendingTooltip.hide(); + } + if (activeTooltip && activeTooltip.targetItem === target) { + activeTooltip.hide(); + } + } else { + if (pendingTooltip) { + pendingTooltip.hide(); + } + if (activeTooltip) { + activeTooltip.hide(); + } + } + } + + function hideImmediately() { + if (pendingTooltip) { + pendingTooltip.hideImmediately(); + pendingTooltip.destroy(); + pendingTooltip = null; + } + if (activeTooltip) { + activeTooltip.hideImmediately(); + activeTooltip.destroy(); + activeTooltip = null; + } + } + + function updateContent(newContent) { + if (activeTooltip) { + activeTooltip.updateContent(newContent); + } + } + + // Backward compatibility alias + function updateText(newText) { + updateContent(newText); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/UI/WallhavenService.qml b/arch/.config/quickshell/noctalia-shell/Services/UI/WallhavenService.qml new file mode 100644 index 0000000..f10d84e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/UI/WallhavenService.qml @@ -0,0 +1,274 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Commons + +Singleton { + id: root + + // State + property bool fetching: false + property bool initialSearchScheduled: false + property var currentResults: [] + property var currentMeta: ({}) + property string lastError: "" + property string currentQuery: "" + property int currentPage: 1 + property int lastPage: 1 + + // Search parameters + property string categories: "111" // general,anime,people (all enabled by default) + property string purity: "100" // sfw + property string sorting: "relevance" // date_added, relevance, random, views, favorites, toplist + property string order: "desc" // desc, asc + property string topRange: "1M" // 1d, 3d, 1w, 1M, 3M, 6M, 1y + property string seed: "" // For random sorting + property string minResolution: "" // e.g., "1920x1080" + property string resolutions: "" // e.g., "1920x1080,1920x1200" + property string ratios: "" // e.g., "16x9,16x10" + property string colors: "" // Color hex codes + // API Key Priority: Environment Variable > Local Settings + readonly property string envApiKey: Quickshell.env("NOCTALIA_WALLHAVEN_API_KEY") || "" + readonly property string apiKey: envApiKey !== "" ? envApiKey : (Settings.data.wallpaper.wallhavenApiKey || "") + readonly property bool apiKeyManagedByEnv: envApiKey !== "" + + // Signals + signal searchCompleted(var results, var meta) + signal searchFailed(string error) + signal wallpaperDownloaded(string wallpaperId, string localPath) + + // Base API URL + readonly property string apiBaseUrl: "https://wallhaven.cc/api/v1" + + // ------------------------------------------------- + function search(query, page) { + if (fetching) { + return; + } + + // Reset initial search flag once we start a search + if (initialSearchScheduled) { + initialSearchScheduled = false; + } + + fetching = true; + lastError = ""; + currentQuery = query || ""; + currentPage = page || 1; + + var url = apiBaseUrl + "/search"; + var params = []; + + if (currentQuery) { + params.push("q=" + encodeURIComponent(currentQuery)); + } + + params.push("categories=" + categories); + // Safety: Force SFW if no purity selected to prevent API error + var safePurity = (purity === "000") ? "100" : purity; + params.push("purity=" + safePurity); + params.push("sorting=" + sorting); + params.push("order=" + order); + + if (sorting === "toplist") { + params.push("topRange=" + topRange); + } + + if (sorting === "random" && seed) { + params.push("seed=" + seed); + } + + if (minResolution) { + params.push("atleast=" + minResolution); + } + + if (resolutions) { + params.push("resolutions=" + resolutions); + } + + if (ratios) { + params.push("ratios=" + ratios); + } + + if (colors) { + params.push("colors=" + colors); + } + + if (apiKey) { + params.push("apikey=" + apiKey); + } + + params.push("page=" + currentPage); + + url += "?" + params.join("&"); + + Logger.d("Wallhaven", "Searching:", url); + + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState === XMLHttpRequest.DONE) { + fetching = false; + if (xhr.status === 200) { + try { + var response = JSON.parse(xhr.responseText); + if (response.data && Array.isArray(response.data)) { + currentResults = response.data; + currentMeta = response.meta || {}; + lastPage = currentMeta.last_page || 1; + + // Store seed for random sorting + if (currentMeta.seed) { + seed = currentMeta.seed; + } + + Logger.d("Wallhaven", "Search completed:", currentResults.length, "results, page", currentPage, "of", lastPage); + searchCompleted(currentResults, currentMeta); + } else { + var errorMsg = "Invalid API response"; + lastError = errorMsg; + Logger.e("Wallhaven", errorMsg); + searchFailed(errorMsg); + } + } catch (e) { + var errorMsg = "Failed to parse API response: " + e.toString(); + lastError = errorMsg; + Logger.e("Wallhaven", errorMsg); + searchFailed(errorMsg); + } + } else if (xhr.status === 429) { + var errorMsg = "Rate limit exceeded (45 requests/minute)"; + lastError = errorMsg; + Logger.w("Wallhaven", errorMsg); + searchFailed(errorMsg); + } else if (xhr.status === 401) { + var errorMsg = "Invalid API Key. Please check your settings."; + lastError = errorMsg; + Logger.e("Wallhaven", errorMsg); + searchFailed(errorMsg); + } else { + var errorMsg = "API error: " + xhr.status; + lastError = errorMsg; + Logger.e("Wallhaven", "Search failed:", errorMsg); + searchFailed(errorMsg); + } + } + }; + + xhr.open("GET", url); + xhr.send(); + } + + // ------------------------------------------------- + function getWallpaperUrl(wallpaper) { + // Use the 'path' field which contains the full resolution image URL + if (wallpaper.path) { + return wallpaper.path; + } + // Fallback to constructing URL from ID + if (wallpaper.id) { + var idPrefix = wallpaper.id.substring(0, 2); + return "https://w.wallhaven.cc/full/" + idPrefix + "/wallhaven-" + wallpaper.id + ".jpg"; + } + return ""; + } + + // ------------------------------------------------- + function getThumbnailUrl(wallpaper, size) { + // size: "small", "large", "original" + if (wallpaper.thumbs && wallpaper.thumbs[size]) { + return wallpaper.thumbs[size]; + } + // Fallback + if (wallpaper.id) { + var idPrefix = wallpaper.id.substring(0, 2); + var sizeMap = { + "small": "small", + "large": "lg", + "original": "orig" + }; + var sizePath = sizeMap[size] || "lg"; + return "https://th.wallhaven.cc/" + sizePath + "/" + idPrefix + "/" + wallpaper.id + ".jpg"; + } + return ""; + } + + // ------------------------------------------------- + function downloadWallpaper(wallpaper, callback) { + var url = getWallpaperUrl(wallpaper); + if (!url) { + Logger.e("Wallhaven", "No URL available for wallpaper", wallpaper.id); + if (callback) + callback(false, ""); + return; + } + + var wallpaperId = wallpaper.id; + + // Get the user's wallpaper directory + var wallpaperDir = Settings.preprocessPath(Settings.data.wallpaper.directory); + if (!wallpaperDir || wallpaperDir === "") { + wallpaperDir = Settings.defaultWallpapersDirectory; + } + + // Ensure directory ends with / + if (!wallpaperDir.endsWith("/")) { + wallpaperDir += "/"; + } + + var localPath = wallpaperDir + "wallhaven_" + wallpaperId + ".jpg"; + + Logger.d("Wallhaven", "Downloading wallpaper", wallpaperId, "to", localPath); + + // Use curl or wget to download the file, ensuring directory exists first + var downloadProcess = Qt.createQmlObject(` + import QtQuick + import Quickshell.Io + Process { + id: downloadProcess + command: ["sh", "-c", "mkdir -p '` + wallpaperDir + `' && (curl -L -s -o '` + localPath + `' '` + url + `' || wget -q -O '` + localPath + `' '` + url + `')"] + } + `, root, "DownloadProcess_" + wallpaperId); + + downloadProcess.exited.connect(function (exitCode) { + if (exitCode === 0) { + Logger.i("Wallhaven", "Wallpaper downloaded:", localPath); + wallpaperDownloaded(wallpaperId, localPath); + if (callback) + callback(true, localPath); + } else { + Logger.e("Wallhaven", "Failed to download wallpaper, exit code:", exitCode); + if (callback) + callback(false, ""); + } + downloadProcess.destroy(); + }); + + downloadProcess.running = true; + } + + // ------------------------------------------------- + function reset() { + currentResults = []; + currentMeta = {}; + currentQuery = ""; + currentPage = 1; + lastPage = 1; + seed = ""; + lastError = ""; + } + + // ------------------------------------------------- + function nextPage() { + if (currentPage < lastPage && !fetching) { + search(currentQuery, currentPage + 1); + } + } + + // ------------------------------------------------- + function previousPage() { + if (currentPage > 1 && !fetching) { + search(currentQuery, currentPage - 1); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Services/UI/WallpaperService.qml b/arch/.config/quickshell/noctalia-shell/Services/UI/WallpaperService.qml new file mode 100644 index 0000000..fc8e849 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Services/UI/WallpaperService.qml @@ -0,0 +1,1683 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.Power +import qs.Services.Theming +import qs.Services.UI + +Singleton { + id: root + + readonly property ListModel fillModeModel: ListModel {} + readonly property string defaultDirectory: Settings.preprocessPath(Settings.data.wallpaper.directory) + readonly property string solidColorPrefix: "solid://" + + // All available wallpaper transitions + readonly property ListModel transitionsModel: ListModel {} + + // All transition keys but filter out "none" and "random" so we are left with the real transitions + readonly property var allTransitions: Array.from({ + "length": transitionsModel.count + }, (_, i) => transitionsModel.get(i).key).filter(key => key !== "random" && key != "none") + + property var wallpaperLists: ({}) + property int scanningCount: 0 + + // Cache for current wallpapers - can be updated directly since we use signals for notifications + property var currentWallpapers: ({}) + + // Track current alphabetical index for each screen + property var alphabeticalIndices: ({}) + + // Track used wallpapers for random mode (persisted across reboots) + property var usedRandomWallpapers: ({}) + + property bool isInitialized: false + property string wallpaperCacheFile: "" + + readonly property bool scanning: (scanningCount > 0) + readonly property string noctaliaDefaultWallpaper: Quickshell.shellDir + "/Assets/Wallpaper/noctalia.png" + property string defaultWallpaper: noctaliaDefaultWallpaper + + // Signals for reactive UI updates + signal wallpaperChanged(string screenName, string path) + // Emitted when a wallpaper changes + signal wallpaperProcessingComplete(string screenName, string path, string cachedPath) + // Emitted when wallpaper processing (resize/cache) is complete. cachedPath is the resized version. + signal wallpaperDirectoryChanged(string screenName, string directory) + // Emitted when a monitor's directory changes + signal wallpaperListChanged(string screenName, int count) + + // Emitted when available wallpapers list changes + + // Browse mode: track current browse path per screen (separate from root directory) + property var currentBrowsePaths: ({}) + + // Wallpaper panel: which appearance slot (light/dark) new selections apply to โ€” like picking a monitor tab + property string wallpaperSelectionAppearance: "light" + + // Bumped when favorites are added/removed so grid delegates can refresh star state + property int favoritesRevision: 0 + + // After favoriting, refresh snapshot once theme colors finish transitioning + property var pendingFavoriteSchemeRefresh: null + + // Signal emitted when browse path changes for a screen + signal browsePathChanged(string screenName, string path) + + Timer { + id: favoriteSchemeDebounceTimer + interval: 450 + repeat: false + property string pendingPath: "" + property string pendingSlot: "" + onTriggered: { + var p = pendingPath; + var s = pendingSlot; + pendingPath = ""; + pendingSlot = ""; + if (p && root.isFavorite(p)) { + root.updateFavoriteColorScheme(p, s); + } + } + } + + function scheduleFavoriteSchemeSnapshot(path, slot) { + Qt.callLater(function () { + if (root.isFavorite(path)) { + root.updateFavoriteColorScheme(path, slot); + } + }); + favoriteSchemeDebounceTimer.pendingPath = path; + favoriteSchemeDebounceTimer.pendingSlot = slot; + favoriteSchemeDebounceTimer.restart(); + if (Color.isTransitioning) { + root.pendingFavoriteSchemeRefresh = { + "path": path, + "slot": slot + }; + } + } + + Connections { + target: Settings.data.wallpaper + function onDirectoryChanged() { + root.usedRandomWallpapers = {}; + root.refreshWallpapersList(); + // Emit directory change signals for monitors using the default directory + if (!Settings.data.wallpaper.enableMultiMonitorDirectories) { + // All monitors use the main directory + for (var i = 0; i < Quickshell.screens.length; i++) { + root.wallpaperDirectoryChanged(Quickshell.screens[i].name, root.defaultDirectory); + } + } else { + // Only monitors without custom directories are affected + for (var i = 0; i < Quickshell.screens.length; i++) { + var screenName = Quickshell.screens[i].name; + var monitor = root.getMonitorConfig(screenName); + if (!monitor || !monitor.directory) { + root.wallpaperDirectoryChanged(screenName, root.defaultDirectory); + } + } + } + } + function onEnableMultiMonitorDirectoriesChanged() { + root.usedRandomWallpapers = {}; + root.refreshWallpapersList(); + // Notify all monitors about potential directory changes + for (var i = 0; i < Quickshell.screens.length; i++) { + var screenName = Quickshell.screens[i].name; + root.wallpaperDirectoryChanged(screenName, root.getMonitorDirectory(screenName)); + } + } + function onAutomationEnabledChanged() { + root.toggleRandomWallpaper(); + } + function onRandomIntervalSecChanged() { + root.restartRandomWallpaperTimer(); + } + function onWallpaperChangeModeChanged() { + // Reset alphabetical indices when mode changes + root.alphabeticalIndices = {}; + if (Settings.data.wallpaper.automationEnabled) { + root.restartRandomWallpaperTimer(); + root.setNextWallpaper(); + } + } + function onViewModeChanged() { + // Reset browse paths to root when mode changes + root.currentBrowsePaths = {}; + root.refreshWallpapersList(); + } + function onShowHiddenFilesChanged() { + root.refreshWallpapersList(); + } + function onUseSolidColorChanged() { + if (Settings.data.wallpaper.useSolidColor) { + var solidPath = root.createSolidColorPath(Settings.data.wallpaper.solidColor.toString()); + for (var i = 0; i < Quickshell.screens.length; i++) { + root.wallpaperChanged(Quickshell.screens[i].name, solidPath); + } + } else { + for (var i = 0; i < Quickshell.screens.length; i++) { + var screenName = Quickshell.screens[i].name; + root.wallpaperChanged(screenName, root.getWallpaper(screenName) || root.defaultWallpaper); + } + } + } + function onSolidColorChanged() { + if (Settings.data.wallpaper.useSolidColor) { + var solidPath = root.createSolidColorPath(Settings.data.wallpaper.solidColor.toString()); + for (var i = 0; i < Quickshell.screens.length; i++) { + root.wallpaperChanged(Quickshell.screens[i].name, solidPath); + } + } + } + function onSortOrderChanged() { + root.refreshWallpapersList(); + } + function onLinkLightAndDarkWallpapersChanged() { + if (Settings.data.wallpaper.linkLightAndDarkWallpapers) { + root.wallpaperSelectionAppearance = Settings.data.colorSchemes.darkMode ? "dark" : "light"; + root._syncWallpaperSlotsWhenLinking(); + } + root._notifyAllWallpapersChanged(); + } + } + + Connections { + target: Settings.data.colorSchemes + function onDarkModeChanged() { + if (Settings.data.wallpaper.linkLightAndDarkWallpapers) { + root.wallpaperSelectionAppearance = Settings.data.colorSchemes.darkMode ? "dark" : "light"; + } + // Restore scheme from favorite for this light/dark slot before wallpaper refresh + root.reapplyFavoriteThemeForActiveWallpaper(); + root._notifyAllWallpapersChanged(); + } + } + + Connections { + target: WallhavenService + function onWallpaperDownloaded() { + root.refreshWallpapersList(); + } + } + + // ------------------------------------------------- + function init() { + Logger.i("Wallpaper", "Service started"); + + translateModels(); + Qt.callLater(root._dedupeWallpaperFavoritesByPath); + + // Initialize cache file path + Qt.callLater(() => { + if (typeof Settings !== 'undefined' && Settings.cacheDir) { + wallpaperCacheFile = Settings.cacheDir + "wallpapers.json"; + wallpaperCacheView.path = wallpaperCacheFile; + } + }); + + // Note: isInitialized will be set to true in wallpaperCacheView.onLoaded + Logger.d("Wallpaper", "Triggering initial wallpaper scan"); + Qt.callLater(refreshWallpapersList); + } + + // Cache restore updates currentWallpapers without _setWallpaper, so wallpaperChanged does not fire. + function _scheduleThemeSyncFromCachedWallpaper() { + Qt.callLater(function () { + root.reapplyFavoriteThemeForActiveWallpaper(); + if (Settings.data.colorSchemes.useWallpaperColors) { + AppThemeService.generate(); + } + }); + } + + // ------------------------------------------------- + function translateModels() { + // Wait for i18n to be ready by retrying every time + if (!I18n.isLoaded) { + Qt.callLater(translateModels); + return; + } + + // Populate fillModeModel with translated names + fillModeModel.append({ + "key": "center", + "name": I18n.tr("positions.center"), + "uniform": 0.0 + }); + fillModeModel.append({ + "key": "crop", + "name": I18n.tr("wallpaper.fill-modes.crop"), + "uniform": 1.0 + }); + fillModeModel.append({ + "key": "fit", + "name": I18n.tr("wallpaper.fill-modes.fit"), + "uniform": 2.0 + }); + fillModeModel.append({ + "key": "stretch", + "name": I18n.tr("wallpaper.fill-modes.stretch"), + "uniform": 3.0 + }); + fillModeModel.append({ + "key": "repeat", + "name": I18n.tr("wallpaper.fill-modes.repeat"), + "uniform": 4.0 + }); + + // Populate transitionsModel with translated names + transitionsModel.append({ + "key": "none", + "name": I18n.tr("common.none") + }); + transitionsModel.append({ + "key": "random", + "name": I18n.tr("common.random") + }); + transitionsModel.append({ + "key": "fade", + "name": I18n.tr("wallpaper.transitions.fade") + }); + transitionsModel.append({ + "key": "disc", + "name": I18n.tr("wallpaper.transitions.disc") + }); + transitionsModel.append({ + "key": "stripes", + "name": I18n.tr("wallpaper.transitions.stripes") + }); + transitionsModel.append({ + "key": "wipe", + "name": I18n.tr("wallpaper.transitions.wipe") + }); + transitionsModel.append({ + "key": "pixelate", + "name": I18n.tr("wallpaper.transitions.pixelate") + }); + transitionsModel.append({ + "key": "honeycomb", + "name": I18n.tr("wallpaper.transitions.honeycomb") + }); + } + + // ------------------------------------------------------------------- + function getFillModeUniform() { + for (var i = 0; i < fillModeModel.count; i++) { + const mode = fillModeModel.get(i); + if (mode.key === Settings.data.wallpaper.fillMode) { + return mode.uniform; + } + } + // Fallback to crop + return 1.0; + } + + // ------------------------------------------------------------------- + // Solid color helpers + // ------------------------------------------------------------------- + function isSolidColorPath(path) { + return path && typeof path === "string" && path.startsWith(solidColorPrefix); + } + + function getSolidColor(path) { + if (!isSolidColorPath(path)) { + return null; + } + return path.substring(solidColorPrefix.length); + } + + function createSolidColorPath(colorString) { + return solidColorPrefix + colorString; + } + + function setSolidColor(colorString) { + Settings.data.wallpaper.solidColor = colorString; + Settings.data.wallpaper.useSolidColor = true; + } + + // ------------------------------------------------------------------- + // Per-screen wallpaper: persisted as { light, dark } (legacy string loads are normalized) + // ------------------------------------------------------------------- + function _isSplitWallpaperEntry(entry) { + if (!entry || typeof entry !== "object") { + return false; + } + return entry.light !== undefined || entry.dark !== undefined; + } + + function _pathsFromEntry(entry) { + if (!entry) { + return { + light: "", + dark: "" + }; + } + if (typeof entry === "string") { + return { + light: entry, + dark: entry + }; + } + return { + light: entry.light || "", + dark: entry.dark || "" + }; + } + + function _cloneWallpaperEntry(entry) { + if (typeof entry === "string") { + return { + light: entry, + dark: entry + }; + } + var p = _pathsFromEntry(entry); + return { + light: p.light, + dark: p.dark + }; + } + + function _entriesEqual(a, b) { + if (a === b) { + return true; + } + if (typeof a === "string" && typeof b === "string") { + return a === b; + } + if (typeof a === "string" || typeof b === "string") { + return false; + } + if (!_isSplitWallpaperEntry(a) || !_isSplitWallpaperEntry(b)) { + return false; + } + var pa = _pathsFromEntry(a); + var pb = _pathsFromEntry(b); + return pa.light === pb.light && pa.dark === pb.dark; + } + + function _entryToEffectivePath(entry) { + if (!entry) { + return ""; + } + if (typeof entry === "string") { + return entry; + } + var p = _pathsFromEntry(entry); + if (Settings.data.colorSchemes.darkMode) { + return p.dark || p.light || ""; + } + return p.light || p.dark || ""; + } + + function _normalizeAppearanceSlot(slot) { + return slot === "dark" ? "dark" : "light"; + } + + function _defaultAppearanceSlotForChange(slot) { + if (slot === "light" || slot === "dark") { + return slot; + } + return Settings.data.colorSchemes.darkMode ? "dark" : "light"; + } + + function getWallpaperPathForSlot(screenName, appearanceSlot) { + if (Settings.data.wallpaper.useSolidColor) { + return createSolidColorPath(Settings.data.wallpaper.solidColor.toString()); + } + var slot = _normalizeAppearanceSlot(appearanceSlot); + var entry = currentWallpapers[screenName]; + if (!entry) { + return ""; + } + var p = _pathsFromEntry(entry); + if (slot === "dark") { + return p.dark || p.light || ""; + } + return p.light || p.dark || ""; + } + + function getWallpapersEffectiveMap() { + var out = {}; + for (var i = 0; i < Quickshell.screens.length; i++) { + var n = Quickshell.screens[i].name; + out[n] = getWallpaper(n); + } + return out; + } + + function _ensureObjectWallpaperEntries() { + var names = {}; + Object.keys(currentWallpapers).forEach(function (k) { + names[k] = true; + }); + for (var i = 0; i < Quickshell.screens.length; i++) { + names[Quickshell.screens[i].name] = true; + } + Object.keys(names).forEach(function (name) { + var e = currentWallpapers[name]; + if (!e) { + return; + } + if (typeof e === "string") { + if (Settings.data.wallpaper.linkLightAndDarkWallpapers) { + currentWallpapers[name] = { + light: e, + dark: e + }; + } else { + currentWallpapers[name] = { + light: e, + dark: e + }; + } + } else if (_isSplitWallpaperEntry(e)) { + var p = _pathsFromEntry(e); + if (Settings.data.wallpaper.linkLightAndDarkWallpapers) { + currentWallpapers[name] = { + light: p.light || p.dark || "", + dark: p.dark || p.light || "" + }; + } else { + currentWallpapers[name] = { + light: p.light || "", + dark: p.dark || "" + }; + } + } + }); + saveTimer.restart(); + } + + function _notifyAllWallpapersChanged() { + for (var i = 0; i < Quickshell.screens.length; i++) { + var n = Quickshell.screens[i].name; + root.wallpaperChanged(n, getWallpaper(n)); + } + } + + // ------------------------------------------------------------------- + // Get specific monitor wallpaper data + function getMonitorConfig(screenName) { + var monitors = Settings.data.wallpaper.monitorDirectories; + if (monitors !== undefined) { + for (var i = 0; i < monitors.length; i++) { + if (monitors[i].name !== undefined && monitors[i].name === screenName) { + return monitors[i]; + } + } + } + } + + // ------------------------------------------------------------------- + // Get specific monitor directory + function getMonitorDirectory(screenName) { + if (!Settings.data.wallpaper.enableMultiMonitorDirectories) { + return root.defaultDirectory; + } + + var monitor = getMonitorConfig(screenName); + if (monitor !== undefined && monitor.directory !== undefined) { + return Settings.preprocessPath(monitor.directory); + } + + // Fall back to the main/single directory + return root.defaultDirectory; + } + + // ------------------------------------------------------------------- + // Set specific monitor directory + function setMonitorDirectory(screenName, directory) { + var monitors = Settings.data.wallpaper.monitorDirectories || []; + var found = false; + + // Create a new array with updated values + var newMonitors = monitors.map(function (monitor) { + if (monitor.name === screenName) { + found = true; + return { + "name": screenName, + "directory": directory, + "wallpaper": monitor.wallpaper || "" + }; + } + return monitor; + }); + + if (!found) { + newMonitors.push({ + "name": screenName, + "directory": directory, + "wallpaper": "" + }); + } + + // Update Settings with new array to ensure proper persistence + Settings.data.wallpaper.monitorDirectories = newMonitors.slice(); + root.wallpaperDirectoryChanged(screenName, Settings.preprocessPath(directory)); + } + + // ------------------------------------------------------------------- + // Get specific monitor wallpaper - now from cache + function getWallpaper(screenName) { + // Return solid color path when in solid color mode + if (Settings.data.wallpaper.useSolidColor) { + return createSolidColorPath(Settings.data.wallpaper.solidColor.toString()); + } + var entry = currentWallpapers[screenName]; + if (entry) { + var effective = _entryToEffectivePath(entry); + if (effective) { + return effective; + } + } + + // Try to inherit wallpaper from another active screen + var inherited = _inheritWallpaperFromExistingScreen(screenName); + if (inherited) { + return inherited; + } + + return root.defaultWallpaper; + } + + // ------------------------------------------------------------------- + function changeWallpaper(path, screenName, appearanceSlot) { + // Turn off solid color mode when selecting a wallpaper + if (Settings.data.wallpaper.useSolidColor) { + Settings.data.wallpaper.useSolidColor = false; + } + + var slot = _defaultAppearanceSlotForChange(appearanceSlot); + + // Save current favorite color schemes before switching away. + // This must happen before applyFavoriteTheme (called by the UI) + // overwrites the settings that _createFavoriteEntry reads. + _saveOutgoingFavorites(path, screenName, slot); + + if (screenName !== undefined) { + _setWallpaper(screenName, path, slot); + } else { + var allScreenNames = new Set(Object.keys(currentWallpapers)); + for (var i = 0; i < Quickshell.screens.length; i++) { + allScreenNames.add(Quickshell.screens[i].name); + } + allScreenNames.forEach(name => _setWallpaper(name, path, slot)); + } + } + + // ------------------------------------------------------------------- + // Save the color scheme of any favorited wallpapers that are about + // to be replaced, while the current settings still reflect them. + function _saveOutgoingFavorites(newPath, screenName, appearanceSlot) { + var paths = []; + var slot = _normalizeAppearanceSlot(appearanceSlot); + + function collectFromEntry(e) { + if (!e) { + return; + } + if (typeof e === "string") { + if (e && e !== newPath) { + paths.push(e); + } + return; + } + if (!_isSplitWallpaperEntry(e)) { + return; + } + var p = _pathsFromEntry(e); + if (Settings.data.wallpaper.linkLightAndDarkWallpapers) { + if (p.light && p.light !== newPath) { + paths.push(p.light); + } + if (p.dark && p.dark !== newPath && p.dark !== p.light) { + paths.push(p.dark); + } + } else { + var old = slot === "dark" ? p.dark : p.light; + if (old && old !== newPath) { + paths.push(old); + } + } + } + + if (screenName !== undefined) { + collectFromEntry(currentWallpapers[screenName]); + } else { + var names = new Set(Object.keys(currentWallpapers)); + for (var i = 0; i < Quickshell.screens.length; i++) { + names.add(Quickshell.screens[i].name); + } + names.forEach(function (name) { + collectFromEntry(currentWallpapers[name]); + }); + } + + var unique = []; + for (var j = 0; j < paths.length; j++) { + if (unique.indexOf(paths[j]) === -1) { + unique.push(paths[j]); + } + } + + unique.forEach(function (path) { + if (!path || path === newPath) { + return; + } + var favIdx = _findAnyFavoriteIndexForPath(path); + if (favIdx === _favoriteNotFound) { + return; + } + var app = _favoriteAppearanceSlot(Settings.data.wallpaper.favorites[favIdx]); + updateFavoriteColorScheme(path, app); + }); + } + + // ------------------------------------------------------------------- + function _inheritWallpaperFromExistingScreen(screenName) { + for (var i = 0; i < Quickshell.screens.length; i++) { + var otherName = Quickshell.screens[i].name; + if (otherName === screenName) { + continue; + } + var entry = currentWallpapers[otherName]; + if (!entry) { + continue; + } + var cloned = _cloneWallpaperEntry(entry); + if (_entriesEqual(currentWallpapers[screenName], cloned)) { + return _entryToEffectivePath(cloned); + } + currentWallpapers[screenName] = cloned; + saveTimer.restart(); + root.wallpaperChanged(screenName, _entryToEffectivePath(cloned)); + if (randomWallpaperTimer.running) { + randomWallpaperTimer.restart(); + } + return _entryToEffectivePath(cloned); + } + return ""; + } + + function _syncWallpaperSlotsWhenLinking() { + var names = new Set(Object.keys(currentWallpapers)); + for (var i = 0; i < Quickshell.screens.length; i++) { + names.add(Quickshell.screens[i].name); + } + names.forEach(function (name) { + var e = currentWallpapers[name]; + if (!e) { + return; + } + var eff = _entryToEffectivePath(e); + if (!eff) { + return; + } + var merged = { + light: eff, + dark: eff + }; + if (_entriesEqual(e, merged)) { + return; + } + currentWallpapers[name] = merged; + saveTimer.restart(); + root.wallpaperChanged(name, eff); + }); + if (randomWallpaperTimer.running) { + randomWallpaperTimer.restart(); + } + } + + function _setWallpaper(screenName, path, appearanceSlot) { + if (path === "" || path === undefined) { + return; + } + + if (screenName === undefined) { + Logger.w("Wallpaper", "setWallpaper", "no screen specified"); + return; + } + + var slot = _normalizeAppearanceSlot(appearanceSlot); + var oldEntry = currentWallpapers[screenName]; + var p = _pathsFromEntry(oldEntry); + var newEntry; + if (Settings.data.wallpaper.linkLightAndDarkWallpapers) { + newEntry = { + light: path, + dark: path + }; + } else if (slot === "dark") { + newEntry = { + light: p.light || "", + dark: path + }; + } else { + newEntry = { + light: path, + dark: p.dark || "" + }; + } + + if (_entriesEqual(oldEntry, newEntry)) { + return; + } + + currentWallpapers[screenName] = newEntry; + saveTimer.restart(); + root.wallpaperChanged(screenName, _entryToEffectivePath(newEntry)); + + if (randomWallpaperTimer.running) { + randomWallpaperTimer.restart(); + } + } + + // ------------------------------------------------------------------- + function setRandomWallpaper(screen) { + Logger.d("Wallpaper", "setRandomWallpaper"); + + if (Settings.data.wallpaper.enableMultiMonitorDirectories) { + if (screen === undefined) { + // Pick a random wallpaper per screen + for (var i = 0; i < Quickshell.screens.length; i++) { + var screenName = Quickshell.screens[i].name; + var wallpaperList = getWallpapersList(screenName); + + if (wallpaperList.length > 0) { + var randomPath = _pickUnusedRandom(screenName, wallpaperList); + changeWallpaper(randomPath, screenName); + } + } + } else { + // Pick a random wallpaper for the specified screen + var wallpaperList = getWallpapersList(screen); + if (wallpaperList.length > 0) { + var randomPath = _pickUnusedRandom(screen, wallpaperList); + changeWallpaper(randomPath, screen); + } + } + } else { + // Pick a random wallpaper common to all screens + // We can use any screenName here, so we just pick the primary one. + var wallpaperList = getWallpapersList(Screen.name); + if (wallpaperList.length > 0) { + var randomPath = _pickUnusedRandom("all", wallpaperList); + changeWallpaper(randomPath, screen); + } + } + } + + // ------------------------------------------------------------------- + // Pick a random wallpaper that hasn't been used yet in the current cycle. + // Once all wallpapers have been shown, resets the pool (keeping only the + // last-shown wallpaper to avoid an immediate repeat). + function _pickUnusedRandom(key, wallpaperList) { + var used = usedRandomWallpapers[key] || []; + + // Clean stale entries (files that were removed from the directory) + var wallpaperSet = new Set(wallpaperList); + used = used.filter(function (path) { + return wallpaperSet.has(path); + }); + + // Filter to wallpapers that haven't been used yet + var unused = wallpaperList.filter(function (path) { + return used.indexOf(path) === -1; + }); + + // If all have been used, reset but keep the last one to avoid immediate repeat + if (unused.length === 0) { + var lastUsed = used.length > 0 ? used[used.length - 1] : ""; + used = lastUsed ? [lastUsed] : []; + unused = wallpaperList.filter(function (path) { + return used.indexOf(path) === -1; + }); + // Edge case: only one wallpaper in the directory + if (unused.length === 0) { + unused = wallpaperList; + } + Logger.d("Wallpaper", "All wallpapers used for", key, "- resetting pool"); + } + + // Pick randomly from unused + var randomIndex = Math.floor(Math.random() * unused.length); + var picked = unused[randomIndex]; + + // Record as used + used.push(picked); + usedRandomWallpapers[key] = used; + + // Persist + saveTimer.restart(); + + return picked; + } + + // ------------------------------------------------------------------- + function setAlphabeticalWallpaper() { + Logger.d("Wallpaper", "setAlphabeticalWallpaper"); + + if (Settings.data.wallpaper.enableMultiMonitorDirectories) { + // Pick next alphabetical wallpaper per screen + for (var i = 0; i < Quickshell.screens.length; i++) { + var screenName = Quickshell.screens[i].name; + var wallpaperList = getWallpapersList(screenName); + + if (wallpaperList.length > 0) { + // Get or initialize index for this screen + if (alphabeticalIndices[screenName] === undefined) { + // Find current wallpaper in list to set initial index + var currentWallpaper = getWallpaper(screenName) || ""; + var foundIndex = wallpaperList.indexOf(currentWallpaper); + alphabeticalIndices[screenName] = (foundIndex >= 0) ? foundIndex : 0; + } + + // Get next index (wrap around) + var currentIndex = alphabeticalIndices[screenName]; + var nextIndex = (currentIndex + 1) % wallpaperList.length; + alphabeticalIndices[screenName] = nextIndex; + + var nextPath = wallpaperList[nextIndex]; + changeWallpaper(nextPath, screenName); + } + } + } else { + // Pick next alphabetical wallpaper common to all screens + var wallpaperList = getWallpapersList(Screen.name); + if (wallpaperList.length > 0) { + // Use primary screen name as key for single directory mode + var key = "all"; + if (alphabeticalIndices[key] === undefined) { + // Find current wallpaper in list to set initial index + var currentWallpaper = getWallpaper(Screen.name) || ""; + var foundIndex = wallpaperList.indexOf(currentWallpaper); + alphabeticalIndices[key] = (foundIndex >= 0) ? foundIndex : 0; + } + + // Get next index (wrap around) + var currentIndex = alphabeticalIndices[key]; + var nextIndex = (currentIndex + 1) % wallpaperList.length; + alphabeticalIndices[key] = nextIndex; + + var nextPath = wallpaperList[nextIndex]; + changeWallpaper(nextPath, undefined); + } + } + } + + // ------------------------------------------------------------------- + function toggleRandomWallpaper() { + Logger.d("Wallpaper", "toggleRandomWallpaper"); + if (Settings.data.wallpaper.automationEnabled) { + restartRandomWallpaperTimer(); + setNextWallpaper(); + } + } + + // ------------------------------------------------------------------- + function setNextWallpaper() { + var mode = Settings.data.wallpaper.wallpaperChangeMode || "random"; + if (mode === "alphabetical") { + setAlphabeticalWallpaper(); + } else { + setRandomWallpaper(); + } + } + + // ------------------------------------------------------------------- + function restartRandomWallpaperTimer() { + if (Settings.data.wallpaper.automationEnabled) { + randomWallpaperTimer.restart(); + } + } + + // ------------------------------------------------------------------- + function getWallpapersList(screenName) { + if (screenName != undefined && wallpaperLists[screenName] != undefined) { + return wallpaperLists[screenName]; + } + return []; + } + + // ------------------------------------------------------------------- + // Browse mode helper functions + // ------------------------------------------------------------------- + function getCurrentBrowsePath(screenName) { + if (currentBrowsePaths[screenName] !== undefined) { + var stored = currentBrowsePaths[screenName]; + var root = getMonitorDirectory(screenName); + if (root && stored.startsWith(root)) { + return stored; + } + // Stored path is outside the root directory, reset it + delete currentBrowsePaths[screenName]; + } + return getMonitorDirectory(screenName); + } + + function setBrowsePath(screenName, path) { + if (!screenName) + return; + currentBrowsePaths[screenName] = path; + browsePathChanged(screenName, path); + } + + function navigateUp(screenName) { + if (!screenName) + return; + var currentPath = getCurrentBrowsePath(screenName); + var rootPath = getMonitorDirectory(screenName); + + // Don't navigate if root is invalid or we're already at root + if (!rootPath || currentPath === rootPath) + return; + + // Get parent directory + var parentPath = currentPath.replace(/\/[^\/]+\/?$/, ""); + if (parentPath === "") + parentPath = rootPath; + + // Don't go above root + if (!parentPath.startsWith(rootPath)) { + parentPath = rootPath; + } + + setBrowsePath(screenName, parentPath); + } + + function navigateToRoot(screenName) { + if (!screenName) + return; + var rootPath = getMonitorDirectory(screenName); + setBrowsePath(screenName, rootPath); + } + + // Scan directory with optional directory listing (for browse mode) + // callback receives { files: [], directories: [] } + function scanDirectoryWithDirs(screenName, directory, callback) { + if (!directory || directory === "") { + callback({ + files: [], + directories: [] + }); + return; + } + + var result = { + files: [], + directories: [] + }; + var pendingScans = 2; + + function checkComplete() { + pendingScans--; + if (pendingScans === 0) { + // Files are already sorted by _scanDirectoryInternal according to sortOrder setting + // Only sort directories alphabetically + result.directories.sort(); + callback(result); + } + } + + // Scan for files + _scanDirectoryInternal(screenName, directory, false, false, function (files) { + result.files = files; + checkComplete(); + }); + + // Scan for directories + _scanForDirectories(directory, function (dirs) { + result.directories = dirs; + checkComplete(); + }); + } + + function _scanForDirectories(directory, callback) { + var findArgs = ["find", "-L", directory, "-maxdepth", "1", "-mindepth", "1", "-type", "d"]; + + var processString = ` + import QtQuick + import Quickshell.Io + Process { + id: process + command: ${JSON.stringify(findArgs)} + stdout: StdioCollector {} + stderr: StdioCollector {} + } + `; + + var processObject = Qt.createQmlObject(processString, root, "DirScan"); + + processObject.exited.connect(function (exitCode) { + var dirs = []; + if (exitCode === 0) { + var lines = processObject.stdout.text.split('\n'); + for (var i = 0; i < lines.length; i++) { + var line = lines[i].trim(); + if (line !== '') { + var showHidden = Settings.data.wallpaper.showHiddenFiles; + var name = line.split('/').pop(); + if (showHidden || !name.startsWith('.')) { + dirs.push(line); + } + } + } + } + callback(dirs); + processObject.destroy(); + }); + + processObject.running = true; + } + + // ------------------------------------------------------------------- + function refreshWallpapersList() { + // Wait for imageMagickAvailable to be correctly set for ImageCacheService.imageFilters + if (!ImageCacheService.initialized) { + Qt.callLater(refreshWallpapersList); + return; + } + + var mode = Settings.data.wallpaper.viewMode; + Logger.d("Wallpaper", "refreshWallpapersList", "viewMode:", mode); + scanningCount = 0; + + if (mode === "recursive") { + // Use Process-based recursive search for all screens + for (var i = 0; i < Quickshell.screens.length; i++) { + var screenName = Quickshell.screens[i].name; + var directory = getMonitorDirectory(screenName); + scanDirectoryRecursive(screenName, directory); + } + } else if (mode === "browse") { + // Browse mode: scan current browse path (non-recursive) + // Note: The actual directory+subdirectory scanning happens in WallpaperPanel + // Here we just scan the current browse path for files + for (var i = 0; i < Quickshell.screens.length; i++) { + var screenName = Quickshell.screens[i].name; + var directory = getCurrentBrowsePath(screenName); + _scanDirectoryInternal(screenName, directory, false, true, null); + } + } else { + // Single directory mode (non-recursive) + for (var i = 0; i < Quickshell.screens.length; i++) { + var screenName = Quickshell.screens[i].name; + var directory = getMonitorDirectory(screenName); + _scanDirectoryInternal(screenName, directory, false, true, null); + } + } + } + + // Internal scan function + // recursive: whether to scan subdirectories + // updateList: whether to update wallpaperLists and emit signal + // callback: optional callback with files array + function _scanDirectoryInternal(screenName, directory, recursive, updateList, callback) { + if (!directory || directory === "") { + Logger.w("Wallpaper", "Empty directory for", screenName); + if (updateList) { + wallpaperLists[screenName] = []; + wallpaperListChanged(screenName, 0); + } + if (callback) + callback([]); + return; + } + + // Cancel any existing scan for this screen + if (recursiveProcesses[screenName]) { + Logger.d("Wallpaper", "Cancelling existing scan for", screenName); + recursiveProcesses[screenName].running = false; + recursiveProcesses[screenName].destroy(); + delete recursiveProcesses[screenName]; + if (updateList) + scanningCount--; + } + + if (updateList) + scanningCount++; + Logger.i("Wallpaper", "Starting scan for", screenName, "in", directory, "recursive:", recursive); + + // Build find command args dynamically from ImageCacheService filters + var filters = ImageCacheService.imageFilters; + var findArgs = ["find", "-L", directory]; + + // Add depth limit for non-recursive + if (!recursive) { + findArgs.push("-maxdepth", "1", "-mindepth", "1"); + } + + findArgs.push("-type", "f", "("); + for (var i = 0; i < filters.length; i++) { + if (i > 0) { + findArgs.push("-o"); + } + findArgs.push("-iname"); + findArgs.push(filters[i]); + } + findArgs.push(")"); + // Add printf to get modification time + findArgs.push("-printf", "%T@|%p\\n"); + + // Create Process component inline + var processString = ` + import QtQuick + import Quickshell.Io + Process { + id: process + command: ${JSON.stringify(findArgs)} + stdout: StdioCollector {} + stderr: StdioCollector {} + } + `; + + var processObject = Qt.createQmlObject(processString, root, "Scan_" + screenName); + + // Store reference to avoid garbage collection + if (updateList) { + recursiveProcesses[screenName] = processObject; + } + + var handler = function (exitCode) { + if (updateList) + scanningCount--; + Logger.d("Wallpaper", "Process exited with code", exitCode, "for", screenName); + + var files = []; + if (exitCode === 0) { + var lines = processObject.stdout.text.split('\n'); + var parsedFiles = []; + + for (var i = 0; i < lines.length; i++) { + var line = lines[i].trim(); + if (line !== '') { + var parts = line.split('|'); + if (parts.length >= 2) { // Handle potential extra pipes in filename by joining rest + var timestamp = parseFloat(parts[0]); + var path = parts.slice(1).join('|'); + + var showHidden = Settings.data.wallpaper.showHiddenFiles; + var name = path.split('/').pop(); + if (showHidden || !name.startsWith('.')) { + parsedFiles.push({ + path: path, + time: timestamp, + name: name + }); + } + } else if (line.indexOf('|') === -1) { + // Fallback for unexpected output format or old find versions (unlikely but safe) + var path = line; + var showHidden = Settings.data.wallpaper.showHiddenFiles; + var name = path.split('/').pop(); + if (showHidden || !name.startsWith('.')) { + parsedFiles.push({ + path: path, + time: 0, + name: name + }); + } + } + } + } + // Sort files based on settings + var sortOrder = Settings.data.wallpaper.sortOrder || "name"; + + // Fischer-Yates shuffle + if (sortOrder === "random") { + for (let i = parsedFiles.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + const temp = parsedFiles[i]; + parsedFiles[i] = parsedFiles[j]; + parsedFiles[j] = temp; + } + } else { + parsedFiles.sort(function (a, b) { + if (sortOrder === "date_desc") { // Newest first + return b.time - a.time; + } else if (sortOrder === "date_asc") { // Oldest first + return a.time - b.time; + } else if (sortOrder === "name_desc") { + return b.name.localeCompare(a.name); + } else { // name (asc) + return a.name.localeCompare(b.name); + } + }); + } + + // Map back to string array + files = parsedFiles.map(f => f.path); + + if (updateList) { + wallpaperLists[screenName] = files; + + // Reset alphabetical indices when list changes + if (alphabeticalIndices[screenName] !== undefined) { + var currentWallpaper = getWallpaper(screenName) || ""; + var foundIndex = files.indexOf(currentWallpaper); + alphabeticalIndices[screenName] = (foundIndex >= 0) ? foundIndex : 0; + } + + Logger.i("Wallpaper", "Scan completed for", screenName, "found", files.length, "files"); + wallpaperListChanged(screenName, files.length); + } + } else { + Logger.w("Wallpaper", "Scan failed for", screenName, "exit code:", exitCode, "(directory might not exist)"); + if (updateList) { + wallpaperLists[screenName] = []; + if (alphabeticalIndices[screenName] !== undefined) { + alphabeticalIndices[screenName] = 0; + } + wallpaperListChanged(screenName, 0); + } + } + + // Clean up + if (updateList) { + delete recursiveProcesses[screenName]; + } + + if (callback) + callback(files); + processObject.destroy(); + }; + + processObject.exited.connect(handler); + Logger.d("Wallpaper", "Starting process for", screenName); + processObject.running = true; + } + + // Process instances for scanning (one per screen) + property var recursiveProcesses: ({}) + + // ------------------------------------------------------------------- + function scanDirectoryRecursive(screenName, directory) { + _scanDirectoryInternal(screenName, directory, true, true, null); + } + + // ------------------------------------------------------------------- + // Favorites + // ------------------------------------------------------------------- + // TODO (~few weeks): Remove per-favorite `darkMode` (the boolean on each + // Settings.data.wallpaper.favorites[] entry). It duplicates `appearance` and is + // unrelated to Settings.data.colorSchemes.darkMode (global shell light/dark). + // Plan: one-time migration, then drop writes and the fallback in _favoriteAppearanceSlot. + // ------------------------------------------------------------------- + readonly property int _favoriteNotFound: -1 + + // ------------------------------------------------------------------- + function _favoriteAppearanceSlot(f) { + if (f.appearance === "light" || f.appearance === "dark") { + return f.appearance; + } + return f.darkMode ? "dark" : "light"; + } + + function _findFavoriteIndex(path, appearanceSlot) { + var favorites = Settings.data.wallpaper.favorites; + var searchPath = Settings.preprocessPath(path); + var slot = _normalizeAppearanceSlot(appearanceSlot); + for (var i = 0; i < favorites.length; i++) { + if (Settings.preprocessPath(favorites[i].path) !== searchPath) { + continue; + } + if (_favoriteAppearanceSlot(favorites[i]) === slot) { + return i; + } + } + return _favoriteNotFound; + } + + function _findAnyFavoriteIndexForPath(path) { + var favorites = Settings.data.wallpaper.favorites; + var searchPath = Settings.preprocessPath(path); + for (var i = 0; i < favorites.length; i++) { + if (Settings.preprocessPath(favorites[i].path) === searchPath) { + return i; + } + } + return _favoriteNotFound; + } + + function _dedupeWallpaperFavoritesByPath() { + var favorites = Settings.data.wallpaper.favorites; + if (!favorites || !favorites.length) { + return; + } + var seen = {}; + var out = []; + for (var i = 0; i < favorites.length; i++) { + var key = Settings.preprocessPath(favorites[i].path); + if (!key || seen[key]) { + continue; + } + seen[key] = true; + out.push(favorites[i]); + } + if (out.length !== favorites.length) { + Settings.data.wallpaper.favorites = out; + root.favoritesRevision++; + } + } + + // ------------------------------------------------------------------- + function _createFavoriteEntry(path, appearanceSlot) { + var app = _normalizeAppearanceSlot(appearanceSlot); + return { + "path": path, + "appearance": app, + "colorScheme": Settings.data.colorSchemes.predefinedScheme, + "darkMode": app === "dark" // TODO: remove per-favorite field (see Favorites section note) + , + "useWallpaperColors": Settings.data.colorSchemes.useWallpaperColors, + "generationMethod": Settings.data.colorSchemes.generationMethod, + "paletteColors": [Color.mPrimary.toString(), Color.mSecondary.toString(), Color.mTertiary.toString(), Color.mError.toString()] + }; + } + + // ------------------------------------------------------------------- + // Favorites are per (path, light|dark): at most one entry per path, tagged with the tab you starred from. + function isFavorite(path, appearanceSlot) { + if (appearanceSlot === undefined || appearanceSlot === null || appearanceSlot === "") { + return _findAnyFavoriteIndexForPath(path) !== _favoriteNotFound; + } + return _findFavoriteIndex(path, appearanceSlot) !== _favoriteNotFound; + } + + // Single favorite entry per path; use _favoriteAppearanceSlot(entry) for light vs dark it was starred under. + function favoriteEntryForPath(path) { + var idx = _findAnyFavoriteIndexForPath(path); + if (idx === _favoriteNotFound) { + return null; + } + return Settings.data.wallpaper.favorites[idx]; + } + + function getFavoriteForDisplay(path) { + return favoriteEntryForPath(path); + } + + // ------------------------------------------------------------------- + function toggleFavorite(path, appearanceSlot, screenName) { + var slot; + if (appearanceSlot !== undefined && appearanceSlot !== null && appearanceSlot !== "") { + slot = _normalizeAppearanceSlot(appearanceSlot); + } else { + slot = _normalizeAppearanceSlot(root.wallpaperSelectionAppearance); + } + var favorites = Settings.data.wallpaper.favorites.slice(); + var anyIdx = _findAnyFavoriteIndexForPath(path); + var applyWallpaperForSlot = false; + + if (anyIdx !== _favoriteNotFound) { + var existingSlot = _favoriteAppearanceSlot(favorites[anyIdx]); + if (existingSlot === slot) { + favorites.splice(anyIdx, 1); + Logger.d("Wallpaper", "Removed favorite:", path, slot); + if (favoriteSchemeDebounceTimer.pendingPath === path && favoriteSchemeDebounceTimer.pendingSlot === slot) { + favoriteSchemeDebounceTimer.stop(); + favoriteSchemeDebounceTimer.pendingPath = ""; + favoriteSchemeDebounceTimer.pendingSlot = ""; + } + if (root.pendingFavoriteSchemeRefresh && root.pendingFavoriteSchemeRefresh.path === path && root.pendingFavoriteSchemeRefresh.slot === slot) { + root.pendingFavoriteSchemeRefresh = null; + } + } else if (Settings.data.wallpaper.linkLightAndDarkWallpapers) { + favorites[anyIdx] = _createFavoriteEntry(path, slot); + Logger.d("Wallpaper", "Moved favorite to other appearance:", path, slot); + root.scheduleFavoriteSchemeSnapshot(path, slot); + applyWallpaperForSlot = true; + if (root.pendingFavoriteSchemeRefresh && root.pendingFavoriteSchemeRefresh.path === path) { + root.pendingFavoriteSchemeRefresh = { + "path": path, + "slot": slot + }; + } + } else { + // Separate light/dark wallpapers: star is remove-only (no sun/moon hint for "move" vs unfavorite). + favorites.splice(anyIdx, 1); + Logger.d("Wallpaper", "Removed favorite (star on other appearance tab, separate wallpapers):", path); + if (favoriteSchemeDebounceTimer.pendingPath === path) { + favoriteSchemeDebounceTimer.stop(); + favoriteSchemeDebounceTimer.pendingPath = ""; + favoriteSchemeDebounceTimer.pendingSlot = ""; + } + if (root.pendingFavoriteSchemeRefresh && root.pendingFavoriteSchemeRefresh.path === path) { + root.pendingFavoriteSchemeRefresh = null; + } + } + } else { + favorites.push(_createFavoriteEntry(path, slot)); + Logger.d("Wallpaper", "Added favorite:", path, slot); + root.scheduleFavoriteSchemeSnapshot(path, slot); + applyWallpaperForSlot = true; + } + + Settings.data.wallpaper.favorites = favorites; + root.favoritesRevision++; + + if (applyWallpaperForSlot) { + var scr; + if (Settings.data.wallpaper.setWallpaperOnAllMonitors) { + scr = undefined; + } else if (screenName !== undefined && screenName !== null && screenName !== "") { + scr = screenName; + } else if (Quickshell.screens.length > 0) { + scr = Quickshell.screens[0].name; + } else { + scr = undefined; + } + root.changeWallpaper(path, scr, slot); + root.applyFavoriteTheme(path, scr, slot); + } + + favoritesChanged(path); + } + + // Apply saved scheme from a favorite. Optional appearanceSlotOverride sets light vs dark target (UI tab or system mode). + function _applyFavoriteThemeFromEntry(favorite, appearanceSlotOverride) { + if (!favorite) { + return; + } + + var favApp; + if (appearanceSlotOverride !== undefined && appearanceSlotOverride !== null && appearanceSlotOverride !== "") { + favApp = _normalizeAppearanceSlot(appearanceSlotOverride); + } else { + favApp = _favoriteAppearanceSlot(favorite); + } + var targetDark = favApp === "dark"; + + var generationMethodChanging = Settings.data.colorSchemes.generationMethod !== favorite.generationMethod; + var darkModeChanging = Settings.data.colorSchemes.darkMode !== targetDark; + var useWallpaperColorsChanging = Settings.data.colorSchemes.useWallpaperColors !== favorite.useWallpaperColors; + + Settings.data.colorSchemes.useWallpaperColors = favorite.useWallpaperColors; + Settings.data.colorSchemes.predefinedScheme = favorite.colorScheme; + Settings.data.colorSchemes.generationMethod = favorite.generationMethod; + Settings.data.colorSchemes.darkMode = targetDark; + + // If nothing triggered AppThemeService via change handlers, regenerate once. + if (!generationMethodChanging && !darkModeChanging && !useWallpaperColorsChanging) { + AppThemeService.generate(); + } else if (!generationMethodChanging && !darkModeChanging && useWallpaperColorsChanging) { + AppThemeService.generate(); + } + } + + // When light/dark changes (or on startup), re-load scheme from the favorite for the wallpaper now shown for that slot. + function reapplyFavoriteThemeForActiveWallpaper() { + var effectiveMonitor = Settings.data.colorSchemes.monitorForColors; + if (effectiveMonitor === "" || effectiveMonitor === undefined) { + effectiveMonitor = Quickshell.screens.length > 0 ? Quickshell.screens[0].name : ""; + } + var wp = getWallpaper(effectiveMonitor); + if (!wp || isSolidColorPath(wp)) { + return; + } + var slot = Settings.data.colorSchemes.darkMode ? "dark" : "light"; + var favorite = favoriteEntryForPath(wp); + if (!favorite) { + return; + } + _applyFavoriteThemeFromEntry(favorite, slot); + } + + // ------------------------------------------------------------------- + function applyFavoriteTheme(path, screenName, appearanceSlot) { + // Only apply theme if the wallpaper is on the monitor driving colors + var effectiveMonitor = Settings.data.colorSchemes.monitorForColors; + if (effectiveMonitor === "" || effectiveMonitor === undefined) { + effectiveMonitor = Quickshell.screens.length > 0 ? Quickshell.screens[0].name : ""; + } + if (screenName !== undefined && screenName !== effectiveMonitor) { + return; + } + + var slot; + if (appearanceSlot !== undefined && appearanceSlot !== null && appearanceSlot !== "") { + slot = _normalizeAppearanceSlot(appearanceSlot); + } else { + slot = _normalizeAppearanceSlot(root.wallpaperSelectionAppearance); + } + var favorite = favoriteEntryForPath(path); + if (!favorite) { + return; + } + + var schemeSlot = slot; + if (Settings.data.wallpaper.linkLightAndDarkWallpapers) { + schemeSlot = _favoriteAppearanceSlot(favorite); + } + _applyFavoriteThemeFromEntry(favorite, schemeSlot); + } + + // ------------------------------------------------------------------- + function updateFavoriteColorScheme(path, appearanceSlot) { + var slot = _normalizeAppearanceSlot(appearanceSlot); + var existingIndex = _findFavoriteIndex(path, slot); + if (existingIndex === _favoriteNotFound) { + return; + } + + var favorites = Settings.data.wallpaper.favorites.slice(); + favorites[existingIndex] = _createFavoriteEntry(favorites[existingIndex].path, slot); + Settings.data.wallpaper.favorites = favorites; + Logger.d("Wallpaper", "Updated color scheme for favorite:", path, slot); + favoriteDataUpdated(path); + } + + signal favoritesChanged(string path) + signal favoriteDataUpdated(string path) + + // Auto-update favorite palette colors when theme colors finish transitioning + Connections { + target: Color + function onIsTransitioningChanged() { + if (!Color.isTransitioning) { + _updateCurrentWallpaperFavorites(); + } + } + } + + function _updateCurrentWallpaperFavorites() { + var effectiveMonitor = Settings.data.colorSchemes.monitorForColors; + if (effectiveMonitor === "" || effectiveMonitor === undefined) { + effectiveMonitor = Quickshell.screens.length > 0 ? Quickshell.screens[0].name : ""; + } + var wp = getWallpaper(effectiveMonitor); + if (!wp) { + return; + } + var favIdx = _findAnyFavoriteIndexForPath(wp); + if (favIdx === _favoriteNotFound) { + return; + } + var app = _favoriteAppearanceSlot(Settings.data.wallpaper.favorites[favIdx]); + updateFavoriteColorScheme(wp, app); + } + + // ------------------------------------------------------------------- + // ------------------------------------------------------------------- + // ------------------------------------------------------------------- + Timer { + id: randomWallpaperTimer + interval: Settings.data.wallpaper.randomIntervalSec * 1000 + running: Settings.data.wallpaper.automationEnabled && !PowerProfileService.noctaliaPerformanceMode + repeat: true + onTriggered: setNextWallpaper() + triggeredOnStart: false + } + + // ------------------------------------------------------------------- + // Cache file persistence + // ------------------------------------------------------------------- + FileView { + id: wallpaperCacheView + printErrors: false + watchChanges: false + + adapter: JsonAdapter { + id: wallpaperCacheAdapter + property var wallpapers: ({}) + property string defaultWallpaper: root.noctaliaDefaultWallpaper + property var usedRandomWallpapers: ({}) + } + + onLoaded: { + // Load wallpapers from cache file + root.currentWallpapers = wallpaperCacheAdapter.wallpapers || {}; + root.usedRandomWallpapers = wallpaperCacheAdapter.usedRandomWallpapers || {}; + + root._ensureObjectWallpaperEntries(); + + if (Settings.data.wallpaper.linkLightAndDarkWallpapers) { + root.wallpaperSelectionAppearance = Settings.data.colorSchemes.darkMode ? "dark" : "light"; + root._syncWallpaperSlotsWhenLinking(); + } + + // Load default wallpaper from cache if it exists, otherwise use Noctalia default + if (wallpaperCacheAdapter.defaultWallpaper && wallpaperCacheAdapter.defaultWallpaper !== "") { + root.defaultWallpaper = wallpaperCacheAdapter.defaultWallpaper; + Logger.d("Wallpaper", "Loaded default wallpaper from cache:", wallpaperCacheAdapter.defaultWallpaper); + } else { + root.defaultWallpaper = root.noctaliaDefaultWallpaper; + Logger.d("Wallpaper", "Using Noctalia default wallpaper"); + } + + Logger.d("Wallpaper", "Loaded wallpapers from cache file:", Object.keys(root.currentWallpapers).length, "screens"); + root.isInitialized = true; + root._scheduleThemeSyncFromCachedWallpaper(); + } + + onLoadFailed: error => { + // File doesn't exist yet or failed to load - initialize with empty state + root.currentWallpapers = {}; + Logger.d("Wallpaper", "Cache file doesn't exist or failed to load, starting with empty wallpapers"); + root.isInitialized = true; + root._scheduleThemeSyncFromCachedWallpaper(); + } + } + + Timer { + id: saveTimer + interval: 500 + repeat: false + onTriggered: { + wallpaperCacheAdapter.wallpapers = root.currentWallpapers; + wallpaperCacheAdapter.defaultWallpaper = root.defaultWallpaper; + wallpaperCacheAdapter.usedRandomWallpapers = root.usedRandomWallpapers; + wallpaperCacheView.writeAdapter(); + Logger.d("Wallpaper", "Saved wallpapers to cache file"); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/frag/appicon_colorize.frag b/arch/.config/quickshell/noctalia-shell/Shaders/frag/appicon_colorize.frag new file mode 100644 index 0000000..7cd2824 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Shaders/frag/appicon_colorize.frag @@ -0,0 +1,40 @@ +#version 450 +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; +layout(binding = 1) uniform sampler2D source; +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + vec4 targetColor; + float colorizeMode; // 0.0 = dock mode (grayscale), 1.0 = tray mode (intensity), 2.0 = distro mode (luminance with better contrast) +} ubuf; + +void main() { + vec4 tex = texture(source, qt_TexCoord0); + + float intensity; + + if (ubuf.colorizeMode < 0.5) { + // Dock mode: Convert to grayscale using proper luminance weights + intensity = dot(tex.rgb, vec3(0.299, 0.587, 0.114)); + } else if (ubuf.colorizeMode < 1.5) { + // Tray mode: Use the maximum RGB channel value as intensity + intensity = max(max(tex.r, tex.g), tex.b); + + // Normalize intensity to make all icons more uniform + intensity = smoothstep(0.1, 0.9, intensity); + } else { + // Distro mode: Brightness boost with proper alpha handling + float maxChannel = max(max(tex.r, tex.g), tex.b); + + intensity = maxChannel * 1.5; + intensity = min(intensity, 1.0); + intensity = intensity * 0.7 + 0.3; + + intensity = intensity * tex.a; + + fragColor = vec4(ubuf.targetColor.rgb * intensity, tex.a) * ubuf.qt_Opacity; +} + + fragColor = vec4(ubuf.targetColor.rgb * intensity, tex.a) * ubuf.qt_Opacity; +} diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/frag/color_picker.frag b/arch/.config/quickshell/noctalia-shell/Shaders/frag/color_picker.frag new file mode 100644 index 0000000..3168a3b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Shaders/frag/color_picker.frag @@ -0,0 +1,59 @@ +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf +{ + mat4 qt_Matrix; + vec4 params; // x=opacity, y=fixedVal, z=mode, w=padding +}; + +// Compatibility function for GLSL ES to avoid % operator issues +int mod_compat(float a, float b) { + return int(a - floor(a / b) * b); +} + +vec3 hsv2rgb(vec3 c) +{ + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +void main() +{ + float x = qt_TexCoord0.x; + float y = 1.0 - qt_TexCoord0.y; // Flip Y direction + + // Unpack vector + float opacity = params.x; + float fixedVal = params.y; + float mode = params.z + 0.1; // +0.1 safely handles float rounding + + // Build permutation matrices for swizzling + vec3 base = vec3(x, y, fixedVal); + + // Determine Mode Logic + // 0: (Red/Hue) 1: (Green/Sat) 2: (Blue/Val) + int perm = mod_compat(mode, 3.0); + + float isHSV = step(3.0, mode); + + // Branchless Selection + vec3 mask = vec3( + float(perm == 0), + float(perm == 1), + float(perm == 2)); + + // Swizzle fo shizzle + // If perm 0: base.zxy -> (fixedVal, x, y) + // If perm 1: base.xzy -> (x, fixedVal, y) + // If perm 2: base.xyz -> (x, y, fixedVal) + vec3 rgb_base = (base.zxy * mask.x) + (base.xzy * mask.y) + (base.xyz * mask.z); + + // Final mix + vec3 finalColor = mix(rgb_base, hsv2rgb(rgb_base), isHSV); + + fragColor = vec4(finalColor, 1.0) * opacity; +} diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/frag/graph.frag b/arch/.config/quickshell/noctalia-shell/Shaders/frag/graph.frag new file mode 100644 index 0000000..940f208 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Shaders/frag/graph.frag @@ -0,0 +1,147 @@ +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D dataSource; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + vec4 lineColor1; + vec4 lineColor2; + float count1; + float count2; + float scroll1; + float scroll2; + float lineWidth; + float graphFillOpacity; + float texWidth; + float resY; + float aaSize; +}; + +// Sample normalized value from data texture +// channel 0 = primary (R), channel 1 = secondary (G) +float fetchData(float idx, int ch) { + float i = clamp(idx, 0.0, texWidth - 1.0); + float u = (floor(i) + 0.5) / texWidth; + vec4 t = texture(dataSource, vec2(u, 0.5)); + return ch == 0 ? t.r : t.g; +} + +// Cubic Hermite interpolation with reduced tangent scale for smooth curves +float cubicHermite(float y0, float y1, float y2, float y3, float t) { + float m1 = (y2 - y0) * 0.25; + float m2 = (y3 - y1) * 0.25; + float t2 = t * t; + float t3 = t2 * t; + return (2.0 * t3 - 3.0 * t2 + 1.0) * y1 + + (t3 - 2.0 * t2 + t) * m1 + + (-2.0 * t3 + 3.0 * t2) * y2 + + (t3 - t2) * m2; +} + +// Evaluate curve at fractional data index +float evalCurve(float dataIdx, int ch) { + float i = floor(dataIdx); + float t = dataIdx - i; + return cubicHermite( + fetchData(i - 1.0, ch), + fetchData(i, ch), + fetchData(i + 1.0, ch), + fetchData(i + 2.0, ch), + t + ); +} + +// Squared distance from point p to line segment aโ†’b +float segDistSq(vec2 p, vec2 a, vec2 b) { + vec2 ab = b - a; + float len2 = dot(ab, ab); + float t = len2 > 0.0 ? clamp(dot(p - a, ab) / len2, 0.0, 1.0) : 0.0; + vec2 proj = a + t * ab; + vec2 d = p - proj; + return dot(d, d); +} + +// Minimum distance from fragment to curve via multi-segment sampling. +// Samples the curve at 9 half-pixel-spaced x-positions (ยฑ2px neighborhood) +// and returns the minimum distance to the 8 line segments between them. +float curveDistance(float dataIdx, float pixStep, float normY, int ch) { + vec2 frag = vec2(0.0, normY * resY); + + float px = -2.0; + float py = evalCurve(dataIdx - 2.0 * pixStep, ch) * resY; + vec2 d0 = frag - vec2(px, py); + float best = dot(d0, d0); + + for (int i = 1; i <= 8; i++) { + float cx = -2.0 + float(i) * 0.5; + float cy = evalCurve(dataIdx + cx * pixStep, ch) * resY; + best = min(best, segDistSq(frag, vec2(px, py), vec2(cx, cy))); + px = cx; + py = cy; + } + + return sqrt(best); +} + +// Premultiplied alpha over compositing +vec4 blendOver(vec4 src, vec4 dst) { + return src + dst * (1.0 - src.a); +} + +void main() { + vec2 uv = qt_TexCoord0; + float normY = 1.0 - uv.y; // 0 = bottom, 1 = top + + vec4 result = vec4(0.0); + float halfW = lineWidth * 0.5; + + // Primary line + if (count1 >= 4.0) { + float segs = count1 - 3.0; + float di = 2.0 + scroll1 + uv.x * segs; + float pixStep = dFdx(di); + float cy = evalCurve(di, 0); + float cyNext = evalCurve(di + pixStep, 0); + + // Fill below curve (gradient: opaque at top, transparent at bottom) + if (graphFillOpacity > 0.0 && normY <= cy) { + float a = graphFillOpacity * normY * lineColor1.a; + result = blendOver(vec4(lineColor1.rgb * a, a), result); + } + + // Multi-segment distance for accurate AA at peaks and steep sections. + // AA width derived analytically from curve slope: (|sinฮธ|+|cosฮธ|) + // gives the ideal SDF fwidth (~1.0โ€“1.41) without GPU derivative noise. + float dist = curveDistance(di, pixStep, normY, 0); + float slope1 = (cyNext - cy) * resY; + float aa = (abs(slope1) + 1.0) * inversesqrt(slope1 * slope1 + 1.0) * aaSize * 2.0; + float sa = smoothstep(halfW + aa, halfW, dist) * lineColor1.a; + result = blendOver(vec4(lineColor1.rgb * sa, sa), result); + } + + // Secondary line + if (count2 >= 4.0) { + float segs = count2 - 3.0; + float di = 2.0 + scroll2 + uv.x * segs; + float pixStep = dFdx(di); + float cy = evalCurve(di, 1); + float cyNext = evalCurve(di + pixStep, 1); + + if (graphFillOpacity > 0.0 && normY <= cy) { + float a = graphFillOpacity * normY * lineColor2.a; + result = blendOver(vec4(lineColor2.rgb * a, a), result); + } + + float dist = curveDistance(di, pixStep, normY, 1); + float slope2 = (cyNext - cy) * resY; + float aa = (abs(slope2) + 1.0) * inversesqrt(slope2 * slope2 + 1.0) * aaSize * 2.0; + float sa = smoothstep(halfW + aa, halfW, dist) * lineColor2.a; + result = blendOver(vec4(lineColor2.rgb * sa, sa), result); + } + + fragColor = result * qt_Opacity; +} diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/frag/progress_border.frag b/arch/.config/quickshell/noctalia-shell/Shaders/frag/progress_border.frag new file mode 100644 index 0000000..331386c --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Shaders/frag/progress_border.frag @@ -0,0 +1,67 @@ +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D source; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float progress; + float borderWidth; + vec4 progressColor; + vec4 borderColor; + vec4 backgroundColor; + float borderRadius; +} ubuf; + +void main() { + vec2 coord = qt_TexCoord0; + float p = clamp(ubuf.progress, 0.0, 1.0); + + if (ubuf.borderRadius > 0.0) { + // Circular progress + vec2 center = vec2(0.5, 0.5); + vec2 dir = coord - center; + float dist = length(dir); + + float outerRadius = 0.5; + float innerRadius = outerRadius - ubuf.borderWidth; + + float angle = atan(dir.y, dir.x) + radians(90.0); + if (angle < 0.0) angle += radians(360.0); + float maxAngle = radians(360.0) * p; + + bool inBorder = dist >= innerRadius && dist <= outerRadius; + bool inProgress = inBorder && angle <= maxAngle; + + if (inProgress) { + fragColor = ubuf.progressColor * ubuf.qt_Opacity; + } else if (inBorder) { + fragColor = ubuf.borderColor * ubuf.qt_Opacity; + } else if (dist < innerRadius) { + fragColor = ubuf.backgroundColor * ubuf.qt_Opacity; + } else { + fragColor = vec4(0.0, 0.0, 0.0, 0.0); + } + } else { + // Rectangular progress + bool inBorder = + coord.x < ubuf.borderWidth || + coord.x > (1.0 - ubuf.borderWidth) || + coord.y < ubuf.borderWidth || + coord.y > (1.0 - ubuf.borderWidth); + + float progressPos = p; + bool inProgress = inBorder && coord.x <= progressPos; + + if (inProgress) { + fragColor = ubuf.progressColor * ubuf.qt_Opacity; + } else if (inBorder) { + fragColor = ubuf.borderColor * ubuf.qt_Opacity; + } else { + fragColor = ubuf.backgroundColor * ubuf.qt_Opacity; + } + } +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/frag/rounded_image.frag b/arch/.config/quickshell/noctalia-shell/Shaders/frag/rounded_image.frag new file mode 100644 index 0000000..dc5c4f9 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Shaders/frag/rounded_image.frag @@ -0,0 +1,101 @@ +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D source; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + // Custom properties with non-conflicting names + float itemWidth; + float itemHeight; + float sourceWidth; + float sourceHeight; + float cornerRadius; + float imageOpacity; + int fillMode; +} ubuf; + +// Function to calculate the signed distance from a point to a rounded box +float roundedBoxSDF(vec2 centerPos, vec2 boxSize, float radius) { + vec2 d = abs(centerPos) - boxSize + radius; + return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - radius; +} + +void main() { + // Get size from uniforms + vec2 itemSize = vec2(ubuf.itemWidth, ubuf.itemHeight); + vec2 sourceSize = vec2(ubuf.sourceWidth, ubuf.sourceHeight); + float cornerRadius = ubuf.cornerRadius; + float itemOpacity = ubuf.imageOpacity; + int fillMode = ubuf.fillMode; + + // Work in pixel space for accurate rounded rectangle calculation + vec2 pixelPos = qt_TexCoord0 * itemSize; + + // Calculate UV coordinates based on fill mode + vec2 imageUV = qt_TexCoord0; + + // fillMode constants from Qt: + // Image.Stretch = 0 + // Image.PreserveAspectFit = 1 + // Image.PreserveAspectCrop = 2 + // Image.Tile = 3 + // Image.TileVertically = 4 + // Image.TileHorizontally = 5 + // Image.Pad = 6 + + // Rounded corners always apply to full item bounds + vec2 roundedSize = itemSize; + vec2 roundedCenter = itemSize * 0.5; + + // Track if pixel is in letterbox area (for PreserveAspectFit) + bool inLetterbox = false; + + if (fillMode == 1) { // PreserveAspectFit + float itemAspect = itemSize.x / itemSize.y; + float sourceAspect = sourceSize.x / sourceSize.y; + + if (sourceAspect > itemAspect) { + // Image is wider than item, letterbox top/bottom + imageUV.y = (qt_TexCoord0.y - 0.5) * (sourceAspect / itemAspect) + 0.5; + } else { + // Image is taller than item, letterbox left/right + imageUV.x = (qt_TexCoord0.x - 0.5) * (itemAspect / sourceAspect) + 0.5; + } + + // Check if in letterbox area + inLetterbox = (imageUV.x < 0.0 || imageUV.x > 1.0 || imageUV.y < 0.0 || imageUV.y > 1.0); + } else if (fillMode == 2) { // PreserveAspectCrop + float itemAspect = itemSize.x / itemSize.y; + float sourceAspect = sourceSize.x / sourceSize.y; + + if (sourceAspect > itemAspect) { + // Image is wider than item, crop left/right. + imageUV.x = (qt_TexCoord0.x - 0.5) * (itemAspect / sourceAspect) + 0.5; + } else { + // Image is taller than item, crop top/bottom. + imageUV.y = (qt_TexCoord0.y - 0.5) * (sourceAspect / itemAspect) + 0.5; + } + } + // For Stretch (0) or other modes, use qt_TexCoord0 as-is + + // Calculate distance to rounded rectangle edge using the correct bounds + vec2 centerOffset = pixelPos - roundedCenter; + float distance = roundedBoxSDF(centerOffset, roundedSize * 0.5, cornerRadius); + + // Create smooth alpha mask for edge with anti-aliasing + float alpha = 1.0 - smoothstep(-0.5, 0.5, distance); + + // Sample the texture (or use transparent for letterbox) + // Clamp UV to prevent texture wrapping artifacts from floating-point imprecision + vec4 color = inLetterbox ? vec4(0.0) : texture(source, clamp(imageUV, vec2(0.0), vec2(1.0))); + + // Apply the rounded mask and opacity + // Qt textures use premultiplied alpha (color.rgb already contains rgb * alpha), + // so we only multiply by the external mask factors, not by color.a again + float mask = alpha * itemOpacity * ubuf.qt_Opacity; + fragColor = color * mask; +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/frag/wave_spectrum.frag b/arch/.config/quickshell/noctalia-shell/Shaders/frag/wave_spectrum.frag new file mode 100644 index 0000000..af1b905 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Shaders/frag/wave_spectrum.frag @@ -0,0 +1,82 @@ +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D dataSource; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + vec4 fillColor; + float count; + float texWidth; + float vertical; + float mirrored; +}; + +// Sample amplitude from data texture (R channel) +float fetchData(float idx) { + float i = clamp(idx, 0.0, texWidth - 1.0); + float u = (floor(i) + 0.5) / texWidth; + return texture(dataSource, vec2(u, 0.5)).r; +} + +// Cubic Hermite interpolation for smooth wave curves +float cubicHermite(float y0, float y1, float y2, float y3, float t) { + float m1 = (y2 - y0) * 0.25; + float m2 = (y3 - y1) * 0.25; + float t2 = t * t; + float t3 = t2 * t; + return (2.0 * t3 - 3.0 * t2 + 1.0) * y1 + + (t3 - 2.0 * t2 + t) * m1 + + (-2.0 * t3 + 3.0 * t2) * y2 + + (t3 - t2) * m2; +} + +// Evaluate interpolated amplitude at fractional data index +float evalCurve(float dataIdx) { + float i = floor(dataIdx); + float t = dataIdx - i; + return cubicHermite( + fetchData(i - 1.0), + fetchData(i), + fetchData(i + 1.0), + fetchData(i + 2.0), + t + ); +} + +void main() { + vec2 uv = qt_TexCoord0; + + // Swap axes for vertical mode + float axisPos = (vertical > 0.5) ? uv.y : uv.x; + float crossPos = (vertical > 0.5) ? uv.x : uv.y; + + // Map axis position to data index + float dataIdx; + if (mirrored > 0.5) { + // Mirror: value[0] at center, value[count-1] at edges + float distFromCenter = abs(axisPos - 0.5) * 2.0; + dataIdx = distFromCenter * max(count - 1.0, 1.0); + } else { + // Linear: value[0] at left/top, value[count-1] at right/bottom + dataIdx = axisPos * max(count - 1.0, 1.0); + } + + // Interpolated amplitude, clamped to valid range + float amplitude = clamp(evalCurve(dataIdx), 0.0, 1.0); + + // Wave fills center ยฑ amplitude/2 in the cross axis + float halfAmp = amplitude * 0.5; + float distFromMid = abs(crossPos - 0.5); + + // Antialiased edge (~1px smooth transition) + float edge = fwidth(crossPos) * 1.5; + float mask = smoothstep(halfAmp + edge, halfAmp - edge, distFromMid); + + // Premultiplied alpha output + float a = mask * fillColor.a; + fragColor = vec4(fillColor.rgb * a, a) * qt_Opacity; +} diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/frag/weather_cloud.frag b/arch/.config/quickshell/noctalia-shell/Shaders/frag/weather_cloud.frag new file mode 100644 index 0000000..1991ce3 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Shaders/frag/weather_cloud.frag @@ -0,0 +1,123 @@ +#version 450 +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float time; + float itemWidth; + float itemHeight; + vec4 bgColor; + float cornerRadius; + float alternative; +} ubuf; + +// Signed distance function for rounded rectangle +float roundedBoxSDF(vec2 center, vec2 size, float radius) { + vec2 q = abs(center) - size + radius; + return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius; +} + +float hash(vec2 p) { + p = fract(p * vec2(234.34, 435.345)); + p += dot(p, p + 34.23); + return fract(p.x * p.y); +} + +// Perlin-like noise +float noise(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); // Smooth interpolation + float a = hash(i); + float b = hash(i + vec2(1.0, 0.0)); + float c = hash(i + vec2(0.0, 1.0)); + float d = hash(i + vec2(1.0, 1.0)); + return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); +} + +// Turbulent noise for natural fog +float turbulence(vec2 p, float iTime) { + float t = 0.0; + float scale = 1.0; + for(int i = 0; i < 5; i++) { + t += abs(noise(p * scale + iTime * 0.1 * scale)) / scale; + scale *= 2.0; + } + return t; +} + +void main() { + vec2 uv = qt_TexCoord0; + + vec4 col = vec4(ubuf.bgColor.rgb, 1.0); + + // Different parameters for fog vs clouds + float timeSpeed, layerScale1, layerScale2, layerScale3; + float flowSpeed1, flowSpeed2; + float densityMin, densityMax; + float baseOpacity; + float pulseAmount; + + if (ubuf.alternative > 0.5) { + // Fog: slower, larger scale, more uniform + timeSpeed = 0.03; + layerScale1 = 1.0; + layerScale2 = 2.5; + layerScale3 = 2.0; + flowSpeed1 = 0.00; + flowSpeed2 = 0.02; + densityMin = 0.1; + densityMax = 0.9; + baseOpacity = 0.75; + pulseAmount = 0.05; + } else { + // Clouds: faster, smaller scale, puffier + timeSpeed = 0.08; + layerScale1 = 2.0; + layerScale2 = 4.0; + layerScale3 = 6.0; + flowSpeed1 = 0.03; + flowSpeed2 = 0.04; + densityMin = 0.35; + densityMax = 0.75; + baseOpacity = 0.4; + pulseAmount = 0.15; + } + + float iTime = ubuf.time * timeSpeed; + + // Create flowing patterns with multiple layers + vec2 flow1 = vec2(iTime * flowSpeed1, iTime * flowSpeed1 * 0.7); + vec2 flow2 = vec2(-iTime * flowSpeed2, iTime * flowSpeed2 * 0.8); + + float fog1 = noise(uv * layerScale1 + flow1); + float fog2 = noise(uv * layerScale2 + flow2); + float fog3 = turbulence(uv * layerScale3, iTime); + + float fogPattern = fog1 * 0.5 + fog2 * 0.3 + fog3 * 0.2; + float fogDensity = smoothstep(densityMin, densityMax, fogPattern); + + // Gentle pulsing + float pulse = sin(iTime * 0.4) * pulseAmount + (1.0 - pulseAmount); + fogDensity *= pulse; + + vec3 hazeColor = vec3(0.88, 0.90, 0.93); + float hazeOpacity = fogDensity * baseOpacity; + vec3 fogContribution = hazeColor * hazeOpacity; + float fogAlpha = hazeOpacity; + + vec3 resultRGB = fogContribution + col.rgb * (1.0 - fogAlpha); + float resultAlpha = fogAlpha + col.a * (1.0 - fogAlpha); + + // Calculate corner mask + vec2 pixelPos = qt_TexCoord0 * vec2(ubuf.itemWidth, ubuf.itemHeight); + vec2 center = pixelPos - vec2(ubuf.itemWidth, ubuf.itemHeight) * 0.5; + vec2 halfSize = vec2(ubuf.itemWidth, ubuf.itemHeight) * 0.5; + float dist = roundedBoxSDF(center, halfSize, ubuf.cornerRadius); + float cornerMask = 1.0 - smoothstep(-1.0, 0.0, dist); + + // Apply global opacity and corner mask + float finalAlpha = resultAlpha * ubuf.qt_Opacity * cornerMask; + fragColor = vec4(resultRGB * (finalAlpha / max(resultAlpha, 0.001)), finalAlpha); +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/frag/weather_rain.frag b/arch/.config/quickshell/noctalia-shell/Shaders/frag/weather_rain.frag new file mode 100644 index 0000000..bd3bc39 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Shaders/frag/weather_rain.frag @@ -0,0 +1,84 @@ +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D source; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float time; + float itemWidth; + float itemHeight; + vec4 bgColor; + float cornerRadius; +} ubuf; + +// Signed distance function for rounded rectangle +float roundedBoxSDF(vec2 center, vec2 size, float radius) { + vec2 q = abs(center) - size + radius; + return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius; +} + +vec3 hash3(vec2 p) { + vec3 q = vec3(dot(p, vec2(127.1, 311.7)), + dot(p, vec2(269.5, 183.3)), + dot(p, vec2(419.2, 371.9))); + return fract(sin(q) * 43758.5453); +} + +float noise(vec2 x, float iTime) { + vec2 p = floor(x); + vec2 f = fract(x); + + float va = 0.0; + for (int j = -2; j <= 2; j++) { + for (int i = -2; i <= 2; i++) { + vec2 g = vec2(float(i), float(j)); + vec3 o = hash3(p + g); + vec2 r = g - f + o.xy; + float d = sqrt(dot(r, r)); + float ripple = max(mix(smoothstep(0.99, 0.999, max(cos(d - iTime * 2.0 + (o.x + o.y) * 5.0), 0.0)), 0.0, d), 0.0); + va += ripple; + } + } + + return va; +} + +void main() { + vec2 uv = qt_TexCoord0; + float iTime = ubuf.time * 0.07; + + // Aspect ratio correction for circular ripples + float aspect = ubuf.itemWidth / ubuf.itemHeight; + vec2 uvAspect = vec2(uv.x * aspect, uv.y); + + float f = noise(6.0 * uvAspect, iTime) * smoothstep(0.0, 0.2, sin(uv.x * 3.141592) * sin(uv.y * 3.141592)); + + // Calculate normal from noise for distortion + float normalScale = 0.5; + vec2 e = normalScale / vec2(ubuf.itemWidth, ubuf.itemHeight); + vec2 eAspect = vec2(e.x * aspect, e.y); + float cx = noise(6.0 * (uvAspect + eAspect), iTime) * smoothstep(0.0, 0.2, sin((uv.x + e.x) * 3.141592) * sin(uv.y * 3.141592)); + float cy = noise(6.0 * (uvAspect + eAspect.yx), iTime) * smoothstep(0.0, 0.2, sin(uv.x * 3.141592) * sin((uv.y + e.y) * 3.141592)); + vec2 n = vec2(cx - f, cy - f); + + // Scale distortion back to texture space (undo aspect correction for X) + vec2 distortion = vec2(n.x / aspect, n.y); + + // Sample source with distortion + vec4 col = texture(source, uv + distortion); + + // Apply rounded corner mask + vec2 pixelPos = qt_TexCoord0 * vec2(ubuf.itemWidth, ubuf.itemHeight); + vec2 center = pixelPos - vec2(ubuf.itemWidth, ubuf.itemHeight) * 0.5; + vec2 halfSize = vec2(ubuf.itemWidth, ubuf.itemHeight) * 0.5; + float dist = roundedBoxSDF(center, halfSize, ubuf.cornerRadius); + float cornerMask = 1.0 - smoothstep(-1.0, 0.0, dist); + + // Output with premultiplied alpha + float finalAlpha = col.a * ubuf.qt_Opacity * cornerMask; + fragColor = vec4(col.rgb * finalAlpha, finalAlpha); +} diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/frag/weather_snow.frag b/arch/.config/quickshell/noctalia-shell/Shaders/frag/weather_snow.frag new file mode 100644 index 0000000..e00a952 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Shaders/frag/weather_snow.frag @@ -0,0 +1,75 @@ +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float time; + float itemWidth; + float itemHeight; + vec4 bgColor; + float cornerRadius; +} ubuf; + +// Signed distance function for rounded rectangle +float roundedBoxSDF(vec2 center, vec2 size, float radius) { + vec2 q = abs(center) - size + radius; + return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius; +} + +void main() { + // Aspect ratio correction + float aspect = ubuf.itemWidth / ubuf.itemHeight; + vec2 uv = qt_TexCoord0; + uv.x *= aspect; + uv.y = 1.0 - uv.y; + + float iTime = ubuf.time * 0.15; + + float snow = 0.0; + + for (int k = 0; k < 6; k++) { + for (int i = 0; i < 12; i++) { + float cellSize = 2.0 + (float(i) * 3.0); + float downSpeed = 0.3 + (sin(iTime * 0.4 + float(k + i * 20)) + 1.0) * 0.00008; + + vec2 uvAnim = uv + vec2( + 0.01 * sin((iTime + float(k * 6185)) * 0.6 + float(i)) * (5.0 / float(i + 1)), + downSpeed * (iTime + float(k * 1352)) * (1.0 / float(i + 1)) + ); + + vec2 uvStep = (ceil((uvAnim) * cellSize - vec2(0.5, 0.5)) / cellSize); + float x = fract(sin(dot(uvStep.xy, vec2(12.9898 + float(k) * 12.0, 78.233 + float(k) * 315.156))) * 43758.5453 + float(k) * 12.0) - 0.5; + float y = fract(sin(dot(uvStep.xy, vec2(62.2364 + float(k) * 23.0, 94.674 + float(k) * 95.0))) * 62159.8432 + float(k) * 12.0) - 0.5; + + float randomMagnitude1 = sin(iTime * 2.5) * 0.7 / cellSize; + float randomMagnitude2 = cos(iTime * 1.65) * 0.7 / cellSize; + + float d = 5.0 * distance((uvStep.xy + vec2(x * sin(y), y) * randomMagnitude1 + vec2(y, x) * randomMagnitude2), uvAnim.xy); + + float omiVal = fract(sin(dot(uvStep.xy, vec2(32.4691, 94.615))) * 31572.1684); + if (omiVal < 0.03) { + float newd = (x + 1.0) * 0.4 * clamp(1.9 - d * (15.0 + (x * 6.3)) * (cellSize / 1.4), 0.0, 1.0); + snow += newd; + } + } + } + + // Blend white snow over background color + float snowAlpha = clamp(snow * 2.0, 0.0, 1.0); + vec3 snowColor = vec3(1.0); + vec3 blended = mix(ubuf.bgColor.rgb, snowColor, snowAlpha); + + // Apply rounded corner mask + vec2 pixelPos = qt_TexCoord0 * vec2(ubuf.itemWidth, ubuf.itemHeight); + vec2 center = pixelPos - vec2(ubuf.itemWidth, ubuf.itemHeight) * 0.5; + vec2 halfSize = vec2(ubuf.itemWidth, ubuf.itemHeight) * 0.5; + float dist = roundedBoxSDF(center, halfSize, ubuf.cornerRadius); + float cornerMask = 1.0 - smoothstep(-1.0, 0.0, dist); + + // Output with premultiplied alpha + float finalAlpha = ubuf.qt_Opacity * cornerMask; + fragColor = vec4(blended * finalAlpha, finalAlpha); +} diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/frag/weather_stars.frag b/arch/.config/quickshell/noctalia-shell/Shaders/frag/weather_stars.frag new file mode 100644 index 0000000..1048cae --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Shaders/frag/weather_stars.frag @@ -0,0 +1,130 @@ +#version 450 +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float time; + float itemWidth; + float itemHeight; + vec4 bgColor; + float cornerRadius; +} ubuf; + +// Signed distance function for rounded rectangle +float roundedBoxSDF(vec2 center, vec2 size, float radius) { + vec2 q = abs(center) - size + radius; + return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius; +} + +float hash(vec2 p) { + p = fract(p * vec2(234.34, 435.345)); + p += dot(p, p + 34.23); + return fract(p.x * p.y); +} + +vec2 hash2(vec2 p) { + p = fract(p * vec2(234.34, 435.345)); + p += dot(p, p + 34.23); + return fract(vec2(p.x * p.y, p.y * p.x)); +} + +float stars(vec2 uv, float density, float iTime) { + vec2 gridUV = uv * density; + vec2 gridID = floor(gridUV); + vec2 gridPos = fract(gridUV); + + float starField = 0.0; + + // Check neighboring cells for stars + for (int y = -1; y <= 1; y++) { + for (int x = -1; x <= 1; x++) { + vec2 offset = vec2(float(x), float(y)); + vec2 cellID = gridID + offset; + + // Random position within cell + vec2 starPos = hash2(cellID); + + // Only create a star for some cells (sparse distribution) + float starChance = hash(cellID + vec2(12.345, 67.890)); + if (starChance > 0.85) { + // Star position in grid space + vec2 toStar = (offset + starPos - gridPos); + float dist = length(toStar) * density; // Scale distance to pixel space + + float starSize = 1.5; + + // Star brightness variation + float brightness = hash(cellID + vec2(23.456, 78.901)) * 0.6 + 0.4; + + // Twinkling effect + float twinkleSpeed = hash(cellID + vec2(34.567, 89.012)) * 3.0 + 2.0; + float twinklePhase = iTime * twinkleSpeed + hash(cellID) * 6.28; + float twinkle = pow(sin(twinklePhase) * 0.5 + 0.5, 3.0); // Sharp on/off + + // Sharp star core + float star = 0.0; + if (dist < starSize) { + star = 1.0 * brightness * (0.3 + twinkle * 0.7); + + // Add tiny cross-shaped glow for brighter stars + if (brightness > 0.7) { + float crossGlow = max( + exp(-abs(toStar.x) * density * 5.0), + exp(-abs(toStar.y) * density * 5.0) + ) * 0.3 * twinkle; + star += crossGlow; + } + } + + starField += star; + } + } + } + + return starField; +} + +void main() { + vec2 uv = qt_TexCoord0; + float iTime = ubuf.time * 0.01; + + // Base background color + vec4 col = vec4(ubuf.bgColor.rgb, 1.0); + + // Aspect ratio for consistent stars + float aspect = ubuf.itemWidth / ubuf.itemHeight; + vec2 uvAspect = vec2(uv.x * aspect, uv.y); + + // Generate multiple layers of stars at different densities + float stars1 = stars(uvAspect, 40.0, iTime); // Tiny distant stars + float stars2 = stars(uvAspect + vec2(0.5, 0.3), 25.0, iTime * 1.3); // Small stars + float stars3 = stars(uvAspect + vec2(0.25, 0.7), 15.0, iTime * 0.9); // Bigger stars + + // Star colors with slight variation + vec3 starColor1 = vec3(0.85, 0.9, 1.0); // Faint blue-white + vec3 starColor2 = vec3(0.95, 0.97, 1.0); // White + vec3 starColor3 = vec3(1.0, 0.98, 0.95); // Warm white + + // Combine star layers + vec3 starsRGB = starColor1 * stars1 * 0.6 + + starColor2 * stars2 * 0.8 + + starColor3 * stars3 * 1.0; + + float starsAlpha = clamp(stars1 * 0.6 + stars2 * 0.8 + stars3, 0.0, 1.0); + + // Apply rounded corner mask + vec2 pixelPos = qt_TexCoord0 * vec2(ubuf.itemWidth, ubuf.itemHeight); + vec2 center = pixelPos - vec2(ubuf.itemWidth, ubuf.itemHeight) * 0.5; + vec2 halfSize = vec2(ubuf.itemWidth, ubuf.itemHeight) * 0.5; + float dist = roundedBoxSDF(center, halfSize, ubuf.cornerRadius); + float cornerMask = 1.0 - smoothstep(-1.0, 0.0, dist); + + // Add stars on top + vec3 resultRGB = starsRGB * starsAlpha + col.rgb * (1.0 - starsAlpha); + float resultAlpha = starsAlpha + col.a * (1.0 - starsAlpha); + + // Apply global opacity and corner mask + float finalAlpha = resultAlpha * ubuf.qt_Opacity * cornerMask; + fragColor = vec4(resultRGB * (finalAlpha / max(resultAlpha, 0.001)), finalAlpha); +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/frag/weather_sun.frag b/arch/.config/quickshell/noctalia-shell/Shaders/frag/weather_sun.frag new file mode 100644 index 0000000..bfc0cd9 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Shaders/frag/weather_sun.frag @@ -0,0 +1,148 @@ +#version 450 +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float time; + float itemWidth; + float itemHeight; + vec4 bgColor; + float cornerRadius; +} ubuf; + +// Signed distance function for rounded rectangle +float roundedBoxSDF(vec2 center, vec2 size, float radius) { + vec2 q = abs(center) - size + radius; + return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius; +} + +float hash(vec2 p) { + p = fract(p * vec2(234.34, 435.345)); + p += dot(p, p + 34.23); + return fract(p.x * p.y); +} + +float noise(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + + float a = hash(i); + float b = hash(i + vec2(1.0, 0.0)); + float c = hash(i + vec2(0.0, 1.0)); + float d = hash(i + vec2(1.0, 1.0)); + + return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); +} + +// God rays originating from sun position +float sunRays(vec2 uv, vec2 sunPos, float iTime) { + vec2 toSun = uv - sunPos; + float angle = atan(toSun.y, toSun.x); + float dist = length(toSun); + + float rayCount = 7; + + // Radial pattern + float rays = sin(angle * rayCount + sin(iTime * 0.25)) * 0.5 + 0.5; + rays = pow(rays, 3.0); + + // Fade with distance + float falloff = 1.0 - smoothstep(0.0, 1.2, dist); + + return rays * falloff * 0.15; +} + +// Atmospheric shimmer / heat haze +float atmosphericShimmer(vec2 uv, float iTime) { + // Multiple layers of noise for complexity + float n1 = noise(uv * 5.0 + vec2(iTime * 0.1, iTime * 0.05)); + float n2 = noise(uv * 8.0 - vec2(iTime * 0.08, iTime * 0.12)); + float n3 = noise(uv * 12.0 + vec2(iTime * 0.15, -iTime * 0.1)); + + return (n1 * 0.5 + n2 * 0.3 + n3 * 0.2) * 0.15; +} + +float sunCore(vec2 uv, vec2 sunPos, float iTime) { + vec2 toSun = uv - sunPos; + float dist = length(toSun); + + // Main bright spot + float mainFlare = exp(-dist * 15.0) * 2.0; + + // Secondary reflection spots along the line + float flares = 0.0; + for (int i = 1; i <= 3; i++) { + vec2 flarePos = sunPos + toSun * float(i) * 0.3; + float flareDist = length(uv - flarePos); + float flareSize = 0.02 + float(i) * 0.01; + flares += smoothstep(flareSize * 2.0, flareSize * 0.5, flareDist) * (0.3 / float(i)); + } + + // Pulsing effect + float pulse = sin(iTime) * 0.1 + 0.9; + + return (mainFlare + flares) * pulse; +} + +void main() { + vec2 uv = qt_TexCoord0; + float iTime = ubuf.time * 0.08; + + // Sample the source + vec4 col = vec4(ubuf.bgColor.rgb, 1.0); + + vec2 sunPos = vec2(0.85, 0.2); + + // Aspect ratio correction + float aspect = ubuf.itemWidth / ubuf.itemHeight; + vec2 uvAspect = vec2(uv.x * aspect, uv.y); + vec2 sunPosAspect = vec2(sunPos.x * aspect, sunPos.y); + + // Generate sunny effects + float rays = sunRays(uvAspect, sunPosAspect, iTime); + float shimmerEffect = atmosphericShimmer(uv, iTime); + float flare = sunCore(uvAspect, sunPosAspect, iTime); + + // Warm sunny colors + vec3 sunColor = vec3(1.0, 0.95, 0.7); // Warm golden yellow + vec3 skyColor = vec3(0.9, 0.95, 1.0); // Light blue tint + vec3 shimmerColor = vec3(1.0, 0.98, 0.85); // Subtle warm shimmer + + // Apply rounded corner mask + vec2 pixelPos = qt_TexCoord0 * vec2(ubuf.itemWidth, ubuf.itemHeight); + vec2 center = pixelPos - vec2(ubuf.itemWidth, ubuf.itemHeight) * 0.5; + vec2 halfSize = vec2(ubuf.itemWidth, ubuf.itemHeight) * 0.5; + float dist = roundedBoxSDF(center, halfSize, ubuf.cornerRadius); + float cornerMask = 1.0 - smoothstep(-1.0, 0.0, dist); + + vec3 resultRGB = col.rgb; + float resultAlpha = col.a; + + // Add sun rays + vec3 raysContribution = sunColor * rays; + float raysAlpha = rays * 0.4; + resultRGB = raysContribution + resultRGB * (1.0 - raysAlpha); + resultAlpha = raysAlpha + resultAlpha * (1.0 - raysAlpha); + + // Add atmospheric shimmer + vec3 shimmerContribution = shimmerColor * shimmerEffect; + float shimmerAlpha = shimmerEffect * 0.1; + resultRGB = shimmerContribution + resultRGB * (1.0 - shimmerAlpha); + resultAlpha = shimmerAlpha + resultAlpha * (1.0 - shimmerAlpha); + + // Add bright sun core + vec3 flareContribution = sunColor * flare; + float flareAlpha = clamp(flare, 0.0, 1.0) * 0.6; + resultRGB = flareContribution + resultRGB * (1.0 - flareAlpha); + resultAlpha = flareAlpha + resultAlpha * (1.0 - flareAlpha); + + // Overall warm sunny tint + resultRGB = mix(resultRGB, resultRGB * vec3(1.08, 1.04, 0.98), 0.15); + + // Apply global opacity and corner mask + float finalAlpha = resultAlpha * ubuf.qt_Opacity * cornerMask; + fragColor = vec4(resultRGB * (finalAlpha / max(resultAlpha, 0.001)), finalAlpha); +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/frag/wp_disc.frag b/arch/.config/quickshell/noctalia-shell/Shaders/frag/wp_disc.frag new file mode 100644 index 0000000..3add2f1 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Shaders/frag/wp_disc.frag @@ -0,0 +1,149 @@ +// ===== wp_disc.frag ===== +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D source1; // Current wallpaper +layout(binding = 2) uniform sampler2D source2; // Next wallpaper + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float progress; // Transition progress (0.0 to 1.0) + float centerX; // X coordinate of disc center (0.0 to 1.0) + float centerY; // Y coordinate of disc center (0.0 to 1.0) + float smoothness; // Edge smoothness (0.0 to 1.0, 0=sharp, 1=very smooth) + float aspectRatio; // Width / Height of the screen + + // Fill mode parameters + float fillMode; // 0=center, 1=crop, 2=fit, 3=stretch, 4=repeat + float imageWidth1; // Width of source1 image + float imageHeight1; // Height of source1 image + float imageWidth2; // Width of source2 image + float imageHeight2; // Height of source2 image + float screenWidth; // Screen width + float screenHeight; // Screen height + vec4 fillColor; // Fill color for empty areas (default: black) + + // Solid color mode + float isSolid1; // 1.0 if source1 is solid color, 0.0 otherwise + float isSolid2; // 1.0 if source2 is solid color, 0.0 otherwise + vec4 solidColor1; // Solid color for source1 + vec4 solidColor2; // Solid color for source2 +} ubuf; + +// Calculate UV coordinates based on fill mode +vec2 calculateUV(vec2 uv, float imgWidth, float imgHeight) { + float imageAspect = imgWidth / imgHeight; + float screenAspect = ubuf.screenWidth / ubuf.screenHeight; + vec2 transformedUV = uv; + + if (ubuf.fillMode < 0.5) { + // Mode 0: no (center) - No resize, center image at original size + // Convert UV to pixel coordinates, offset, then back to UV in image space + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + vec2 imageOffset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - vec2(imgWidth, imgHeight)) * 0.5; + vec2 imagePixel = screenPixel - imageOffset; + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + else if (ubuf.fillMode < 1.5) { + // Mode 1: crop (fill/cover) - Fill screen, crop excess (default) + float scale = max(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (scaledImageSize - vec2(ubuf.screenWidth, ubuf.screenHeight)) / scaledImageSize; + transformedUV = uv * (vec2(1.0) - offset) + offset * 0.5; + } + else if (ubuf.fillMode < 2.5) { + // Mode 2: fit (contain) - Fit inside screen, maintain aspect ratio + float scale = min(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - scaledImageSize) * 0.5; + + // Convert screen UV to pixel coordinates + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + // Adjust for offset and scale + vec2 imagePixel = (screenPixel - offset) / scale; + // Convert back to UV coordinates in image space + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + else if (ubuf.fillMode < 3.5) { + // Mode 3: stretch - Use original UV (stretches to fit) + // No transformation needed for stretch mode + } + else { + // Mode 4: repeat (tile) - Tile image at original size + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + transformedUV = screenPixel / vec2(imgWidth, imgHeight); + } + + return transformedUV; +} + +// Sample texture with fill mode and handle out-of-bounds +// If isSolid > 0.5, returns solidColor instead of sampling texture +vec4 sampleWithFillMode(sampler2D tex, vec2 uv, float imgWidth, float imgHeight, float isSolid, vec4 solidColor) { + // Return solid color if in solid color mode + if (isSolid > 0.5) { + return solidColor; + } + + vec2 transformedUV = calculateUV(uv, imgWidth, imgHeight); + + // Mode 4 (repeat): use fract() to tile the image + if (ubuf.fillMode > 3.5) { + return texture(tex, fract(transformedUV)); + } + + // Check if UV is out of bounds + if (transformedUV.x < 0.0 || transformedUV.x > 1.0 || + transformedUV.y < 0.0 || transformedUV.y > 1.0) { + return ubuf.fillColor; + } + + return texture(tex, transformedUV); +} + +void main() { + vec2 uv = qt_TexCoord0; + + // Sample textures with fill mode (handles solid colors) + vec4 color1 = sampleWithFillMode(source1, uv, ubuf.imageWidth1, ubuf.imageHeight1, ubuf.isSolid1, ubuf.solidColor1); + vec4 color2 = sampleWithFillMode(source2, uv, ubuf.imageWidth2, ubuf.imageHeight2, ubuf.isSolid2, ubuf.solidColor2); + + // Map smoothness from 0.0-1.0 to 0.001-0.5 range + // Using a non-linear mapping for better control + float mappedSmoothness = mix(0.001, 0.5, ubuf.smoothness * ubuf.smoothness); + + // Adjust UV coordinates to compensate for aspect ratio + // This makes distances circular instead of elliptical + vec2 adjustedUV = vec2(uv.x * ubuf.aspectRatio, uv.y); + vec2 adjustedCenter = vec2(ubuf.centerX * ubuf.aspectRatio, ubuf.centerY); + + // Calculate distance in aspect-corrected space + float dist = distance(adjustedUV, adjustedCenter); + + // Calculate the maximum possible distance (corner to corner) + // This ensures the disc can cover the entire screen + float maxDistX = max(ubuf.centerX * ubuf.aspectRatio, + (1.0 - ubuf.centerX) * ubuf.aspectRatio); + float maxDistY = max(ubuf.centerY, 1.0 - ubuf.centerY); + float maxDist = length(vec2(maxDistX, maxDistY)); + + // Scale progress to cover the maximum distance + // Add extra range for smoothness to ensure complete coverage + // Adjust smoothness for aspect ratio to maintain consistent visual appearance + float adjustedSmoothness = mappedSmoothness * max(1.0, ubuf.aspectRatio); + + // Start the radius from -adjustedSmoothness, this ensures the disc is completely hidden when progress=0 + float totalDistance = maxDist + 2.0 * adjustedSmoothness; + float radius = -adjustedSmoothness + ubuf.progress * totalDistance; + + // Use smoothstep for a smooth edge transition + float factor = smoothstep(radius - adjustedSmoothness, radius + adjustedSmoothness, dist); + + // Mix the textures (factor = 0 inside disc, 1 outside) + fragColor = mix(color2, color1, factor); + + fragColor *= ubuf.qt_Opacity; +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/frag/wp_fade.frag b/arch/.config/quickshell/noctalia-shell/Shaders/frag/wp_fade.frag new file mode 100644 index 0000000..6fbc9a1 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Shaders/frag/wp_fade.frag @@ -0,0 +1,112 @@ +// ===== wp_fade.frag ===== +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D source1; +layout(binding = 2) uniform sampler2D source2; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float progress; + + // Fill mode parameters + float fillMode; // 0=center, 1=crop, 2=fit, 3=stretch, 4=repeat + float imageWidth1; // Width of source1 image + float imageHeight1; // Height of source1 image + float imageWidth2; // Width of source2 image + float imageHeight2; // Height of source2 image + float screenWidth; // Screen width + float screenHeight; // Screen height + vec4 fillColor; // Fill color for empty areas (default: black) + + // Solid color mode + float isSolid1; // 1.0 if source1 is solid color, 0.0 otherwise + float isSolid2; // 1.0 if source2 is solid color, 0.0 otherwise + vec4 solidColor1; // Solid color for source1 + vec4 solidColor2; // Solid color for source2 +} ubuf; + +// Calculate UV coordinates based on fill mode +vec2 calculateUV(vec2 uv, float imgWidth, float imgHeight) { + float imageAspect = imgWidth / imgHeight; + float screenAspect = ubuf.screenWidth / ubuf.screenHeight; + vec2 transformedUV = uv; + + if (ubuf.fillMode < 0.5) { + // Mode 0: no (center) - No resize, center image at original size + // Convert UV to pixel coordinates, offset, then back to UV in image space + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + vec2 imageOffset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - vec2(imgWidth, imgHeight)) * 0.5; + vec2 imagePixel = screenPixel - imageOffset; + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + else if (ubuf.fillMode < 1.5) { + // Mode 1: crop (fill/cover) - Fill screen, crop excess (default) + float scale = max(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (scaledImageSize - vec2(ubuf.screenWidth, ubuf.screenHeight)) / scaledImageSize; + transformedUV = uv * (vec2(1.0) - offset) + offset * 0.5; + } + else if (ubuf.fillMode < 2.5) { + // Mode 2: fit (contain) - Fit inside screen, maintain aspect ratio + float scale = min(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - scaledImageSize) * 0.5; + + // Convert screen UV to pixel coordinates + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + // Adjust for offset and scale + vec2 imagePixel = (screenPixel - offset) / scale; + // Convert back to UV coordinates in image space + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + else if (ubuf.fillMode < 3.5) { + // Mode 3: stretch - Use original UV (stretches to fit) + // No transformation needed for stretch mode + } + else { + // Mode 4: repeat (tile) - Tile image at original size + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + transformedUV = screenPixel / vec2(imgWidth, imgHeight); + } + + return transformedUV; +} + +// Sample texture with fill mode and handle out-of-bounds +// If isSolid > 0.5, returns solidColor instead of sampling texture +vec4 sampleWithFillMode(sampler2D tex, vec2 uv, float imgWidth, float imgHeight, float isSolid, vec4 solidColor) { + // Return solid color if in solid color mode + if (isSolid > 0.5) { + return solidColor; + } + + vec2 transformedUV = calculateUV(uv, imgWidth, imgHeight); + + // Mode 4 (repeat): use fract() to tile the image + if (ubuf.fillMode > 3.5) { + return texture(tex, fract(transformedUV)); + } + + // Check if UV is out of bounds + if (transformedUV.x < 0.0 || transformedUV.x > 1.0 || + transformedUV.y < 0.0 || transformedUV.y > 1.0) { + return ubuf.fillColor; + } + + return texture(tex, transformedUV); +} + +void main() { + vec2 uv = qt_TexCoord0; + + // Sample textures with fill mode (handles solid colors) + vec4 color1 = sampleWithFillMode(source1, uv, ubuf.imageWidth1, ubuf.imageHeight1, ubuf.isSolid1, ubuf.solidColor1); + vec4 color2 = sampleWithFillMode(source2, uv, ubuf.imageWidth2, ubuf.imageHeight2, ubuf.isSolid2, ubuf.solidColor2); + + // Mix the two textures based on progress value + fragColor = mix(color1, color2, ubuf.progress) * ubuf.qt_Opacity; +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/frag/wp_honeycomb.frag b/arch/.config/quickshell/noctalia-shell/Shaders/frag/wp_honeycomb.frag new file mode 100644 index 0000000..ef7d2ac --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Shaders/frag/wp_honeycomb.frag @@ -0,0 +1,170 @@ +// ===== wp_honeycomb.frag ===== +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D source1; +layout(binding = 2) uniform sampler2D source2; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float progress; + float cellSize; // Size of hexagonal cells in UV space (default 0.04) + float centerX; // X coordinate of wave origin (0.0 to 1.0) + float centerY; // Y coordinate of wave origin (0.0 to 1.0) + float aspectRatio; // Width / Height of the screen + + // Fill mode parameters + float fillMode; // 0=center, 1=crop, 2=fit, 3=stretch, 4=repeat + float imageWidth1; // Width of source1 image + float imageHeight1; // Height of source1 image + float imageWidth2; // Width of source2 image + float imageHeight2; // Height of source2 image + float screenWidth; // Screen width + float screenHeight; // Screen height + vec4 fillColor; // Fill color for empty areas (default: black) + + // Solid color mode + float isSolid1; // 1.0 if source1 is solid color, 0.0 otherwise + float isSolid2; // 1.0 if source2 is solid color, 0.0 otherwise + vec4 solidColor1; // Solid color for source1 + vec4 solidColor2; // Solid color for source2 +} ubuf; + +// Calculate UV coordinates based on fill mode +vec2 calculateUV(vec2 uv, float imgWidth, float imgHeight) { + float imageAspect = imgWidth / imgHeight; + float screenAspect = ubuf.screenWidth / ubuf.screenHeight; + vec2 transformedUV = uv; + + if (ubuf.fillMode < 0.5) { + // Mode 0: no (center) - No resize, center image at original size + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + vec2 imageOffset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - vec2(imgWidth, imgHeight)) * 0.5; + vec2 imagePixel = screenPixel - imageOffset; + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + else if (ubuf.fillMode < 1.5) { + // Mode 1: crop (fill/cover) - Fill screen, crop excess (default) + float scale = max(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (scaledImageSize - vec2(ubuf.screenWidth, ubuf.screenHeight)) / scaledImageSize; + transformedUV = uv * (vec2(1.0) - offset) + offset * 0.5; + } + else if (ubuf.fillMode < 2.5) { + // Mode 2: fit (contain) - Fit inside screen, maintain aspect ratio + float scale = min(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - scaledImageSize) * 0.5; + + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + vec2 imagePixel = (screenPixel - offset) / scale; + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + else if (ubuf.fillMode < 3.5) { + // Mode 3: stretch - Use original UV (stretches to fit) + } + else { + // Mode 4: repeat (tile) - Tile image at original size + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + transformedUV = screenPixel / vec2(imgWidth, imgHeight); + } + + return transformedUV; +} + +// Sample texture with fill mode and handle out-of-bounds +vec4 sampleWithFillMode(sampler2D tex, vec2 uv, float imgWidth, float imgHeight, float isSolid, vec4 solidColor) { + if (isSolid > 0.5) { + return solidColor; + } + + vec2 transformedUV = calculateUV(uv, imgWidth, imgHeight); + + if (ubuf.fillMode > 3.5) { + return texture(tex, fract(transformedUV)); + } + + if (transformedUV.x < 0.0 || transformedUV.x > 1.0 || + transformedUV.y < 0.0 || transformedUV.y > 1.0) { + return ubuf.fillColor; + } + + return texture(tex, transformedUV); +} + +// Convert cartesian to axial hex coordinates and round to nearest hex center +vec2 hexRound(vec2 axial) { + // Convert axial (q,r) to cube (x,y,z) where x+y+z=0 + float x = axial.x; + float z = axial.y; + float y = -x - z; + + // Round each + float rx = round(x); + float ry = round(y); + float rz = round(z); + + // Fix rounding errors by resetting the component with largest diff + float dx = abs(rx - x); + float dy = abs(ry - y); + float dz = abs(rz - z); + + if (dx > dy && dx > dz) { + rx = -ry - rz; + } else if (dy > dz) { + ry = -rx - rz; + } else { + rz = -rx - ry; + } + + return vec2(rx, rz); +} + +void main() { + vec2 uv = qt_TexCoord0; + + // Sample both textures at original UV + vec4 color1 = sampleWithFillMode(source1, uv, ubuf.imageWidth1, ubuf.imageHeight1, ubuf.isSolid1, ubuf.solidColor1); + vec4 color2 = sampleWithFillMode(source2, uv, ubuf.imageWidth2, ubuf.imageHeight2, ubuf.isSolid2, ubuf.solidColor2); + + // Aspect-correct the UV for hex grid so cells appear as regular hexagons + vec2 aspectUV = vec2(uv.x * ubuf.aspectRatio, uv.y); + + // Convert to axial hex coordinates + // Hex grid: q axis along x, r axis at 60 degrees + float size = max(ubuf.cellSize, 0.01); + float q = (aspectUV.x * (2.0 / 3.0)) / size; + float r = ((-aspectUV.x / 3.0) + (sqrt(3.0) / 3.0) * aspectUV.y) / size; + + // Round to nearest hex center + vec2 hex = hexRound(vec2(q, r)); + + // Convert hex center back to aspect-corrected UV space + vec2 hexCenter; + hexCenter.x = size * (3.0 / 2.0) * hex.x; + hexCenter.y = size * (sqrt(3.0) * (hex.y + 0.5 * hex.x)); + + // Calculate distance from this cell's center to the wave origin (aspect-corrected) + vec2 origin = vec2(ubuf.centerX * ubuf.aspectRatio, ubuf.centerY); + float dist = distance(hexCenter, origin); + + // Maximum distance from origin to any corner (for normalization) + float maxDistX = max(ubuf.centerX * ubuf.aspectRatio, (1.0 - ubuf.centerX) * ubuf.aspectRatio); + float maxDistY = max(ubuf.centerY, 1.0 - ubuf.centerY); + float maxDist = length(vec2(maxDistX, maxDistY)); + + // Wave expansion (same approach as disc shader): + // Start radius behind the origin so the smoothstep zone is fully off-screen at progress=0 + float softEdge = 0.15 * maxDist; + float totalDistance = maxDist + 2.0 * softEdge; + float radius = -softEdge + ubuf.progress * totalDistance; + + // factor = 0 inside the wave (revealed), 1 outside (not yet reached) + float factor = smoothstep(radius - softEdge, radius + softEdge, dist); + float cellProgress = 1.0 - factor; + + fragColor = mix(color1, color2, cellProgress) * ubuf.qt_Opacity; +} diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/frag/wp_pixelate.frag b/arch/.config/quickshell/noctalia-shell/Shaders/frag/wp_pixelate.frag new file mode 100644 index 0000000..32ac40e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Shaders/frag/wp_pixelate.frag @@ -0,0 +1,119 @@ +// ===== wp_pixelate.frag ===== +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D source1; +layout(binding = 2) uniform sampler2D source2; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float progress; + float maxBlockSize; // Maximum block size in pixels (default 64) + + // Fill mode parameters + float fillMode; // 0=center, 1=crop, 2=fit, 3=stretch, 4=repeat + float imageWidth1; // Width of source1 image + float imageHeight1; // Height of source1 image + float imageWidth2; // Width of source2 image + float imageHeight2; // Height of source2 image + float screenWidth; // Screen width + float screenHeight; // Screen height + vec4 fillColor; // Fill color for empty areas (default: black) + + // Solid color mode + float isSolid1; // 1.0 if source1 is solid color, 0.0 otherwise + float isSolid2; // 1.0 if source2 is solid color, 0.0 otherwise + vec4 solidColor1; // Solid color for source1 + vec4 solidColor2; // Solid color for source2 +} ubuf; + +// Calculate UV coordinates based on fill mode +vec2 calculateUV(vec2 uv, float imgWidth, float imgHeight) { + float imageAspect = imgWidth / imgHeight; + float screenAspect = ubuf.screenWidth / ubuf.screenHeight; + vec2 transformedUV = uv; + + if (ubuf.fillMode < 0.5) { + // Mode 0: no (center) - No resize, center image at original size + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + vec2 imageOffset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - vec2(imgWidth, imgHeight)) * 0.5; + vec2 imagePixel = screenPixel - imageOffset; + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + else if (ubuf.fillMode < 1.5) { + // Mode 1: crop (fill/cover) - Fill screen, crop excess (default) + float scale = max(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (scaledImageSize - vec2(ubuf.screenWidth, ubuf.screenHeight)) / scaledImageSize; + transformedUV = uv * (vec2(1.0) - offset) + offset * 0.5; + } + else if (ubuf.fillMode < 2.5) { + // Mode 2: fit (contain) - Fit inside screen, maintain aspect ratio + float scale = min(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - scaledImageSize) * 0.5; + + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + vec2 imagePixel = (screenPixel - offset) / scale; + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + else if (ubuf.fillMode < 3.5) { + // Mode 3: stretch - Use original UV (stretches to fit) + } + else { + // Mode 4: repeat (tile) - Tile image at original size + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + transformedUV = screenPixel / vec2(imgWidth, imgHeight); + } + + return transformedUV; +} + +// Sample texture with fill mode and handle out-of-bounds +vec4 sampleWithFillMode(sampler2D tex, vec2 uv, float imgWidth, float imgHeight, float isSolid, vec4 solidColor) { + if (isSolid > 0.5) { + return solidColor; + } + + vec2 transformedUV = calculateUV(uv, imgWidth, imgHeight); + + if (ubuf.fillMode > 3.5) { + return texture(tex, fract(transformedUV)); + } + + if (transformedUV.x < 0.0 || transformedUV.x > 1.0 || + transformedUV.y < 0.0 || transformedUV.y > 1.0) { + return ubuf.fillColor; + } + + return texture(tex, transformedUV); +} + +void main() { + vec2 uv = qt_TexCoord0; + + // Triangle wave: pixelation peaks at progress=0.5 + float pixelIntensity = 1.0 - abs(2.0 * ubuf.progress - 1.0); + + // Compute the sampling UV โ€” snap to block grid when pixelating, pass through otherwise + vec2 sampleUV = uv; + if (pixelIntensity > 0.001) { + float blockSize = ubuf.maxBlockSize * pixelIntensity; + vec2 screenSize = vec2(ubuf.screenWidth, ubuf.screenHeight); + vec2 pixelCoord = uv * screenSize; + vec2 snappedPixel = floor(pixelCoord / blockSize) * blockSize + blockSize * 0.5; + sampleUV = snappedPixel / screenSize; + } + + // Sample both textures at the (possibly snapped) UV + vec4 color1 = sampleWithFillMode(source1, sampleUV, ubuf.imageWidth1, ubuf.imageHeight1, ubuf.isSolid1, ubuf.solidColor1); + vec4 color2 = sampleWithFillMode(source2, sampleUV, ubuf.imageWidth2, ubuf.imageHeight2, ubuf.isSolid2, ubuf.solidColor2); + + // Crossfade concentrated around the peak pixelation (progress ~0.5) + float blend = smoothstep(0.4, 0.6, ubuf.progress); + + fragColor = mix(color1, color2, blend) * ubuf.qt_Opacity; +} diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/frag/wp_stripes.frag b/arch/.config/quickshell/noctalia-shell/Shaders/frag/wp_stripes.frag new file mode 100644 index 0000000..1136bc6 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Shaders/frag/wp_stripes.frag @@ -0,0 +1,224 @@ +// ===== wp_stripes.frag ===== +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D source1; // Current wallpaper +layout(binding = 2) uniform sampler2D source2; // Next wallpaper + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float progress; // Transition progress (0.0 to 1.0) + float stripeCount; // Number of stripes (default 12.0) + float angle; // Angle of stripes in degrees (default 30.0) + float smoothness; // Edge smoothness (0.0 to 1.0, 0=sharp, 1=very smooth) + float aspectRatio; // Width / Height of the screen + + // Fill mode parameters + float fillMode; // 0=center, 1=crop, 2=fit, 3=stretch, 4=repeat + float imageWidth1; // Width of source1 image + float imageHeight1; // Height of source1 image + float imageWidth2; // Width of source2 image + float imageHeight2; // Height of source2 image + float screenWidth; // Screen width + float screenHeight; // Screen height + vec4 fillColor; // Fill color for empty areas (default: black) + + // Solid color mode + float isSolid1; // 1.0 if source1 is solid color, 0.0 otherwise + float isSolid2; // 1.0 if source2 is solid color, 0.0 otherwise + vec4 solidColor1; // Solid color for source1 + vec4 solidColor2; // Solid color for source2 +} ubuf; + +// Calculate UV coordinates based on fill mode +vec2 calculateUV(vec2 uv, float imgWidth, float imgHeight) { + float imageAspect = imgWidth / imgHeight; + float screenAspect = ubuf.screenWidth / ubuf.screenHeight; + vec2 transformedUV = uv; + + if (ubuf.fillMode < 0.5) { + // Mode 0: no (center) - No resize, center image at original size + // Convert UV to pixel coordinates, offset, then back to UV in image space + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + vec2 imageOffset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - vec2(imgWidth, imgHeight)) * 0.5; + vec2 imagePixel = screenPixel - imageOffset; + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + else if (ubuf.fillMode < 1.5) { + // Mode 1: crop (fill/cover) - Fill screen, crop excess (default) + float scale = max(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (scaledImageSize - vec2(ubuf.screenWidth, ubuf.screenHeight)) / scaledImageSize; + transformedUV = uv * (vec2(1.0) - offset) + offset * 0.5; + } + else if (ubuf.fillMode < 2.5) { + // Mode 2: fit (contain) - Fit inside screen, maintain aspect ratio + float scale = min(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - scaledImageSize) * 0.5; + + // Convert screen UV to pixel coordinates + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + // Adjust for offset and scale + vec2 imagePixel = (screenPixel - offset) / scale; + // Convert back to UV coordinates in image space + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + else if (ubuf.fillMode < 3.5) { + // Mode 3: stretch - Use original UV (stretches to fit) + // No transformation needed for stretch mode + } + else { + // Mode 4: repeat (tile) - Tile image at original size + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + transformedUV = screenPixel / vec2(imgWidth, imgHeight); + } + + return transformedUV; +} + +// Sample texture with fill mode and handle out-of-bounds +// If isSolid > 0.5, returns solidColor instead of sampling texture +vec4 sampleWithFillMode(sampler2D tex, vec2 uv, float imgWidth, float imgHeight, float isSolid, vec4 solidColor) { + // Return solid color if in solid color mode + if (isSolid > 0.5) { + return solidColor; + } + + vec2 transformedUV = calculateUV(uv, imgWidth, imgHeight); + + // Mode 4 (repeat): use fract() to tile the image + if (ubuf.fillMode > 3.5) { + return texture(tex, fract(transformedUV)); + } + + // Check if UV is out of bounds + if (transformedUV.x < 0.0 || transformedUV.x > 1.0 || + transformedUV.y < 0.0 || transformedUV.y > 1.0) { + return ubuf.fillColor; + } + + return texture(tex, transformedUV); +} + +void main() { + vec2 uv = qt_TexCoord0; + + // Sample textures with fill mode (handles solid colors) + vec4 color1 = sampleWithFillMode(source1, uv, ubuf.imageWidth1, ubuf.imageHeight1, ubuf.isSolid1, ubuf.solidColor1); + vec4 color2 = sampleWithFillMode(source2, uv, ubuf.imageWidth2, ubuf.imageHeight2, ubuf.isSolid2, ubuf.solidColor2); + + // Map smoothness from 0.0-1.0 to 0.001-0.3 range + // Using a non-linear mapping for better control at low values + float mappedSmoothness = mix(0.001, 0.3, ubuf.smoothness * ubuf.smoothness); + + // Use values directly without forcing defaults + float stripes = (ubuf.stripeCount > 0.0) ? ubuf.stripeCount : 12.0; + float angleRad = radians(ubuf.angle); + float edgeSmooth = mappedSmoothness; + + // Create a coordinate system for stripes based on angle + // At 0ยฐ: vertical stripes (divide by x) + // At 45ยฐ: diagonal stripes + // At 90ยฐ: horizontal stripes (divide by y) + + // Transform coordinates based on angle + float cosA = cos(angleRad); + float sinA = sin(angleRad); + + // Project the UV position onto the stripe direction + // This gives us the position along the stripe direction + float stripeCoord = uv.x * cosA + uv.y * sinA; + + // Perpendicular coordinate (for edge movement) + float perpCoord = -uv.x * sinA + uv.y * cosA; + + // Calculate the range of perpCoord based on angle + // This determines how far edges need to travel to fully cover the screen + float minPerp = min(min(0.0 * -sinA + 0.0 * cosA, 1.0 * -sinA + 0.0 * cosA), + min(0.0 * -sinA + 1.0 * cosA, 1.0 * -sinA + 1.0 * cosA)); + float maxPerp = max(max(0.0 * -sinA + 0.0 * cosA, 1.0 * -sinA + 0.0 * cosA), + max(0.0 * -sinA + 1.0 * cosA, 1.0 * -sinA + 1.0 * cosA)); + + // Determine which stripe we're in + float stripePos = stripeCoord * stripes; + int stripeIndex = int(floor(stripePos)); + + // Determine if this is an odd or even stripe + bool isOddStripe = mod(float(stripeIndex), 2.0) != 0.0; + + // Calculate the progress for this specific stripe with wave delay + // Use absolute stripe position for consistent delay across all stripes + float normalizedStripePos = clamp(stripePos / stripes, 0.0, 1.0); + + // Increased delay and better distribution + float maxDelay = 0.1; + float stripeDelay = normalizedStripePos * maxDelay; + + // Better progress mapping that uses the full 0.0-1.0 range + // Map progress so that: + // - First stripe starts at progress = 0.0 + // - Last stripe finishes at progress = 1.0 + float stripeProgress; + if (ubuf.progress <= stripeDelay) { + stripeProgress = 0.0; + } else if (ubuf.progress >= (stripeDelay + (1.0 - maxDelay))) { + stripeProgress = 1.0; + } else { + // Scale the progress within the active window for this stripe + float activeStart = stripeDelay; + float activeEnd = stripeDelay + (1.0 - maxDelay); + stripeProgress = (ubuf.progress - activeStart) / (activeEnd - activeStart); + } + + // Use the perpendicular coordinate for edge comparison + float yPos = perpCoord; + + // Calculate edge position for this stripe + // Use the actual perpendicular coordinate range for this angle + float perpRange = maxPerp - minPerp; + float margin = edgeSmooth; // Just enough buffer for the smoothstep zone to clear the edge + float edgePosition; + if (isOddStripe) { + // Odd stripes: edge moves from max to min + edgePosition = maxPerp + margin - stripeProgress * (perpRange + margin * 2.0); + } else { + // Even stripes: edge moves from min to max + edgePosition = minPerp - margin + stripeProgress * (perpRange + margin * 2.0); + } + + // Determine which wallpaper to show based on rotated position + float mask; + if (isOddStripe) { + // Odd stripes reveal new wallpaper from bottom + mask = smoothstep(edgePosition - edgeSmooth, edgePosition + edgeSmooth, yPos); + } else { + // Even stripes reveal new wallpaper from top + mask = 1.0 - smoothstep(edgePosition - edgeSmooth, edgePosition + edgeSmooth, yPos); + } + + // Mix the wallpapers + fragColor = mix(color1, color2, mask); + + // Force exact values at start and end to prevent any bleed-through + if (ubuf.progress <= 0.0) { + fragColor = color1; // Only show old wallpaper at start + } else if (ubuf.progress >= 1.0) { + fragColor = color2; // Only show new wallpaper at end + } else { + // Add manga-style edge shadow only during transition + float edgeDist = abs(yPos - edgePosition); + float shadowStrength = 1.0 - smoothstep(0.0, edgeSmooth * 2.5, edgeDist); + shadowStrength *= 0.2 * (1.0 - abs(stripeProgress - 0.5) * 2.0); + fragColor.rgb *= (1.0 - shadowStrength); + + // Add slight vignette during transition for dramatic effect + float vignette = 1.0 - ubuf.progress * 0.1 * (1.0 - abs(stripeProgress - 0.5) * 2.0); + fragColor.rgb *= vignette; + } + + fragColor *= ubuf.qt_Opacity; +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/frag/wp_wipe.frag b/arch/.config/quickshell/noctalia-shell/Shaders/frag/wp_wipe.frag new file mode 100644 index 0000000..19ab477 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Shaders/frag/wp_wipe.frag @@ -0,0 +1,151 @@ +// ===== wp_wipe.frag ===== +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D source1; // Current wallpaper +layout(binding = 2) uniform sampler2D source2; // Next wallpaper + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float progress; // Transition progress (0.0 to 1.0) + float direction; // 0=left, 1=right, 2=up, 3=down + float smoothness; // Edge smoothness (0.0 to 1.0, 0=sharp, 1=very smooth) + + // Fill mode parameters + float fillMode; // 0=center, 1=crop, 2=fit, 3=stretch, 4=repeat + float imageWidth1; // Width of source1 image + float imageHeight1; // Height of source1 image + float imageWidth2; // Width of source2 image + float imageHeight2; // Height of source2 image + float screenWidth; // Screen width + float screenHeight; // Screen height + vec4 fillColor; // Fill color for empty areas (default: black) + + // Solid color mode + float isSolid1; // 1.0 if source1 is solid color, 0.0 otherwise + float isSolid2; // 1.0 if source2 is solid color, 0.0 otherwise + vec4 solidColor1; // Solid color for source1 + vec4 solidColor2; // Solid color for source2 +} ubuf; + +// Calculate UV coordinates based on fill mode +vec2 calculateUV(vec2 uv, float imgWidth, float imgHeight) { + float imageAspect = imgWidth / imgHeight; + float screenAspect = ubuf.screenWidth / ubuf.screenHeight; + vec2 transformedUV = uv; + + if (ubuf.fillMode < 0.5) { + // Mode 0: no (center) - No resize, center image at original size + // Convert UV to pixel coordinates, offset, then back to UV in image space + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + vec2 imageOffset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - vec2(imgWidth, imgHeight)) * 0.5; + vec2 imagePixel = screenPixel - imageOffset; + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + else if (ubuf.fillMode < 1.5) { + // Mode 1: crop (fill/cover) - Fill screen, crop excess (default) + float scale = max(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (scaledImageSize - vec2(ubuf.screenWidth, ubuf.screenHeight)) / scaledImageSize; + transformedUV = uv * (vec2(1.0) - offset) + offset * 0.5; + } + else if (ubuf.fillMode < 2.5) { + // Mode 2: fit (contain) - Fit inside screen, maintain aspect ratio + float scale = min(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - scaledImageSize) * 0.5; + + // Convert screen UV to pixel coordinates + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + // Adjust for offset and scale + vec2 imagePixel = (screenPixel - offset) / scale; + // Convert back to UV coordinates in image space + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + else if (ubuf.fillMode < 3.5) { + // Mode 3: stretch - Use original UV (stretches to fit) + // No transformation needed for stretch mode + } + else { + // Mode 4: repeat (tile) - Tile image at original size + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + transformedUV = screenPixel / vec2(imgWidth, imgHeight); + } + + return transformedUV; +} + +// Sample texture with fill mode and handle out-of-bounds +// If isSolid > 0.5, returns solidColor instead of sampling texture +vec4 sampleWithFillMode(sampler2D tex, vec2 uv, float imgWidth, float imgHeight, float isSolid, vec4 solidColor) { + // Return solid color if in solid color mode + if (isSolid > 0.5) { + return solidColor; + } + + vec2 transformedUV = calculateUV(uv, imgWidth, imgHeight); + + // Mode 4 (repeat): use fract() to tile the image + if (ubuf.fillMode > 3.5) { + return texture(tex, fract(transformedUV)); + } + + // Check if UV is out of bounds + if (transformedUV.x < 0.0 || transformedUV.x > 1.0 || + transformedUV.y < 0.0 || transformedUV.y > 1.0) { + return ubuf.fillColor; + } + + return texture(tex, transformedUV); +} + +void main() { + vec2 uv = qt_TexCoord0; + + // Sample textures with fill mode (handles solid colors) + vec4 color1 = sampleWithFillMode(source1, uv, ubuf.imageWidth1, ubuf.imageHeight1, ubuf.isSolid1, ubuf.solidColor1); + vec4 color2 = sampleWithFillMode(source2, uv, ubuf.imageWidth2, ubuf.imageHeight2, ubuf.isSolid2, ubuf.solidColor2); + + // Map smoothness from 0.0-1.0 to 0.001-0.5 range + // Using a non-linear mapping for better control + float mappedSmoothness = mix(0.001, 0.5, ubuf.smoothness * ubuf.smoothness); + + float edge = 0.0; + float factor = 0.0; + + // Extend the progress range to account for smoothness + // This ensures the transition completes fully at the edges + float extendedProgress = ubuf.progress * (1.0 + 2.0 * mappedSmoothness) - mappedSmoothness; + + // Calculate edge position based on direction + // As progress goes from 0 to 1, we reveal source2 (new wallpaper) + if (ubuf.direction < 0.5) { + // Wipe from right to left (new image enters from right) + edge = 1.0 - extendedProgress; + factor = smoothstep(edge - mappedSmoothness, edge + mappedSmoothness, uv.x); + fragColor = mix(color1, color2, factor); + } + else if (ubuf.direction < 1.5) { + // Wipe from left to right (new image enters from left) + edge = extendedProgress; + factor = smoothstep(edge - mappedSmoothness, edge + mappedSmoothness, uv.x); + fragColor = mix(color2, color1, factor); + } + else if (ubuf.direction < 2.5) { + // Wipe from bottom to top (new image enters from bottom) + edge = 1.0 - extendedProgress; + factor = smoothstep(edge - mappedSmoothness, edge + mappedSmoothness, uv.y); + fragColor = mix(color1, color2, factor); + } + else { + // Wipe from top to bottom (new image enters from top) + edge = extendedProgress; + factor = smoothstep(edge - mappedSmoothness, edge + mappedSmoothness, uv.y); + fragColor = mix(color2, color1, factor); + } + + fragColor *= ubuf.qt_Opacity; +} \ No newline at end of file diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/qsb/appicon_colorize.frag.qsb b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/appicon_colorize.frag.qsb new file mode 100644 index 0000000..ea0abbe Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/appicon_colorize.frag.qsb differ diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/qsb/color_picker.frag.qsb b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/color_picker.frag.qsb new file mode 100644 index 0000000..cfbbb50 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/color_picker.frag.qsb differ diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/qsb/graph.frag.qsb b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/graph.frag.qsb new file mode 100644 index 0000000..16cdee2 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/graph.frag.qsb differ diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/qsb/progress_border.frag.qsb b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/progress_border.frag.qsb new file mode 100644 index 0000000..60fda9f Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/progress_border.frag.qsb differ diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/qsb/rounded_image.frag.qsb b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/rounded_image.frag.qsb new file mode 100644 index 0000000..8942a67 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/rounded_image.frag.qsb differ diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wave_spectrum.frag.qsb b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wave_spectrum.frag.qsb new file mode 100644 index 0000000..6a680c5 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wave_spectrum.frag.qsb differ diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/qsb/weather_cloud.frag.qsb b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/weather_cloud.frag.qsb new file mode 100644 index 0000000..2fe9734 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/weather_cloud.frag.qsb differ diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/qsb/weather_rain.frag.qsb b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/weather_rain.frag.qsb new file mode 100644 index 0000000..5c06dd6 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/weather_rain.frag.qsb differ diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/qsb/weather_snow.frag.qsb b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/weather_snow.frag.qsb new file mode 100644 index 0000000..e466857 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/weather_snow.frag.qsb differ diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/qsb/weather_stars.frag.qsb b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/weather_stars.frag.qsb new file mode 100644 index 0000000..544c2b3 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/weather_stars.frag.qsb differ diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/qsb/weather_sun.frag.qsb b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/weather_sun.frag.qsb new file mode 100644 index 0000000..2b231fa Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/weather_sun.frag.qsb differ diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wp_disc.frag.qsb b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wp_disc.frag.qsb new file mode 100644 index 0000000..d324e00 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wp_disc.frag.qsb differ diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wp_fade.frag.qsb b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wp_fade.frag.qsb new file mode 100644 index 0000000..6778626 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wp_fade.frag.qsb differ diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wp_honeycomb.frag.qsb b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wp_honeycomb.frag.qsb new file mode 100644 index 0000000..4e3542c Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wp_honeycomb.frag.qsb differ diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wp_pixelate.frag.qsb b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wp_pixelate.frag.qsb new file mode 100644 index 0000000..9751a0f Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wp_pixelate.frag.qsb differ diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wp_stripes.frag.qsb b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wp_stripes.frag.qsb new file mode 100644 index 0000000..70b529f Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wp_stripes.frag.qsb differ diff --git a/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wp_wipe.frag.qsb b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wp_wipe.frag.qsb new file mode 100644 index 0000000..f878f35 Binary files /dev/null and b/arch/.config/quickshell/noctalia-shell/Shaders/qsb/wp_wipe.frag.qsb differ diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/AudioSpectrum/NLinearSpectrum.qml b/arch/.config/quickshell/noctalia-shell/Widgets/AudioSpectrum/NLinearSpectrum.qml new file mode 100644 index 0000000..c30c834 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/AudioSpectrum/NLinearSpectrum.qml @@ -0,0 +1,49 @@ +import QtQuick +import qs.Commons + +Item { + id: root + property color fillColor: Color.mPrimary + property color strokeColor: Color.mOnSurface + property int strokeWidth: 0 + property var values: [] + property bool vertical: false + property string barPosition: "top" // "top", "bottom", "left", "right" + property bool mirrored: true + + // Minimum signal properties + property bool showMinimumSignal: false + property real minimumSignalValue: 0.01 // Default to 1% of height + + // Pre compute horizontal mirroring + readonly property int valuesCount: (values && values.length !== undefined) ? values.length : 0 + readonly property int totalBars: mirrored ? valuesCount * 2 : valuesCount + readonly property real barSlotSize: totalBars > 0 ? (vertical ? height : width) / totalBars : 0 + readonly property bool highQuality: (Settings.data.audio.visualizerType === "low") ? false : true + + Repeater { + model: root.totalBars + + Rectangle { + property int valueIndex: root.mirrored ? (index < root.valuesCount ? root.valuesCount - 1 - index : index - root.valuesCount) : index + + property real rawAmp: (root.values && root.values[valueIndex] !== undefined) ? root.values[valueIndex] : 0 + property real amp: (root.showMinimumSignal && rawAmp === 0) ? root.minimumSignalValue : rawAmp + + color: root.fillColor + border.color: root.strokeColor + border.width: root.strokeWidth + antialiasing: root.highQuality + smooth: root.highQuality + + // Only update when value actually changes - reduces GPU load + width: vertical ? root.width * amp : root.barSlotSize * 0.5 + height: vertical ? root.barSlotSize * 0.5 : root.height * amp + x: vertical ? (root.barPosition === "left" ? 0 : root.width - width) : index * root.barSlotSize + (root.barSlotSize * 0.25) + y: vertical ? index * root.barSlotSize + (root.barSlotSize * 0.25) : root.height - height + + // Disable updates when invisible to save GPU + visible: root.visible + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/AudioSpectrum/NMirroredSpectrum.qml b/arch/.config/quickshell/noctalia-shell/Widgets/AudioSpectrum/NMirroredSpectrum.qml new file mode 100644 index 0000000..658299a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/AudioSpectrum/NMirroredSpectrum.qml @@ -0,0 +1,51 @@ +import QtQuick +import qs.Commons + +Item { + id: root + property color fillColor: Color.mPrimary + property color strokeColor: Color.mOnSurface + property int strokeWidth: 0 + property var values: [] + property bool vertical: false + property bool mirrored: true + + // Minimum signal properties + property bool showMinimumSignal: false + property real minimumSignalValue: 0.01 // Default to 1% of height + + // Pre-compute mirroring + readonly property int valuesCount: (values && values.length !== undefined) ? values.length : 0 + readonly property int totalBars: mirrored ? valuesCount * 2 : valuesCount + readonly property real barSlotSize: totalBars > 0 ? (vertical ? height : width) / totalBars : 0 + readonly property bool highQuality: (Settings.data.audio.visualizerType === "low") ? false : true + readonly property real centerY: height / 2 + readonly property real centerX: width / 2 + + Repeater { + model: root.totalBars + + Rectangle { + property int valueIndex: root.mirrored ? (index < root.valuesCount ? root.valuesCount - 1 - index : index - root.valuesCount) : index + + property real rawAmp: (root.values && root.values[valueIndex] !== undefined) ? root.values[valueIndex] : 0 + property real amp: (root.showMinimumSignal && rawAmp === 0) ? root.minimumSignalValue : rawAmp + + property real barSize: (vertical ? root.width : root.height) * amp + + color: root.fillColor + border.color: root.strokeColor + border.width: root.strokeWidth + antialiasing: root.highQuality + smooth: root.highQuality + + width: vertical ? barSize : root.barSlotSize * 0.8 + height: vertical ? root.barSlotSize * 0.8 : barSize + x: vertical ? root.centerX - (barSize / 2) : index * root.barSlotSize + (root.barSlotSize * 0.25) + y: vertical ? index * root.barSlotSize + (root.barSlotSize * 0.25) : root.centerY - (barSize / 2) + + // Disable updates when invisible to save GPU + visible: root.visible + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/AudioSpectrum/NWaveSpectrum.qml b/arch/.config/quickshell/noctalia-shell/Widgets/AudioSpectrum/NWaveSpectrum.qml new file mode 100644 index 0000000..329b201 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/AudioSpectrum/NWaveSpectrum.qml @@ -0,0 +1,72 @@ +import QtQuick +import Quickshell +import qs.Commons + +Item { + id: root + property color fillColor: Color.mPrimary + property color strokeColor: Color.mOnSurface + property int strokeWidth: 0 + property var values: [] + property bool vertical: false + property bool mirrored: true + + // Minimum signal properties + property bool showMinimumSignal: false + property real minimumSignalValue: 0.01 // Default to 1% of height + + readonly property int valuesCount: (values && values.length !== undefined) ? values.length : 0 + readonly property bool hasData: valuesCount >= 2 + + // Data texture: one pixel per value, R channel = amplitude + Item { + id: dataRow + width: Math.max(root.valuesCount, 4) + height: 1 + + Repeater { + model: dataRow.width + + Rectangle { + required property int index + x: index + width: 1 + height: 1 + color: { + if (index >= root.valuesCount) + return Qt.rgba(0, 0, 0, 1); + var v = root.values[index]; + if (v === undefined || v === null || !isFinite(v)) + v = 0; + if (root.showMinimumSignal && v === 0) + v = root.minimumSignalValue; + return Qt.rgba(Math.max(0, Math.min(1, v)), 0, 0, 1); + } + } + } + } + + ShaderEffectSource { + id: dataTex + sourceItem: dataRow + textureSize: Qt.size(dataRow.width, 1) + live: true + smooth: false + hideSource: true + } + + ShaderEffect { + anchors.fill: parent + visible: root.hasData && root.width > 0 && root.height > 0 + + property variant dataSource: dataTex + property color fillColor: root.fillColor + property real count: root.valuesCount + property real texWidth: dataRow.width + property real vertical: root.vertical ? 1.0 : 0.0 + property real mirrored: root.mirrored ? 1.0 : 0.0 + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wave_spectrum.frag.qsb") + blending: true + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NBattery.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NBattery.qml new file mode 100644 index 0000000..fea02b3 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NBattery.qml @@ -0,0 +1,205 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +// Battery widget with Android 16 style rendering (horizontal or vertical) +Item { + id: root + + // Data (must be provided by parent) + required property real percentage + required property bool charging + required property bool pluggedIn + required property bool ready + required property bool low + required property bool critical + + // Sizing - baseSize controls overall scaleFactor for bar/panel usage + property real baseSize: Style.fontSizeM + + // Styling - no hardcoded colors, only theme colors + property color baseColor: Color.mOnSurface + property color lowColor: Color.mError + property color chargingColor: Color.mPrimary + property color textColor: Color.mSurface + + // Display options + property bool showPercentageText: true + property bool vertical: false + + // Alternating state icon display (toggles between percentage and icon when charging) + property bool showStateIcon: false + + onChargingChanged: { + if (!charging) + showStateIcon = false; + } + + // Internal sizing calculations based on baseSize + readonly property real scaleFactor: baseSize / Style.fontSizeM + readonly property real bodyWidth: { + const min = Style.toOdd(22 * scaleFactor); + if (!showPercentageText) { + return min; + } + + // increase length when showing 100% + if (percentage > 99) { + const max = Style.toOdd(30 * scaleFactor); + return max; + } + return min; + } + + readonly property real bodyHeight: Style.toOdd(14 * scaleFactor) + readonly property real terminalWidth: Math.round(2.5 * scaleFactor) + readonly property real terminalHeight: Math.round(7 * scaleFactor) + readonly property real cornerRadius: Math.round(3 * scaleFactor) + + // Total size is just body + terminal (no external icon) + readonly property real totalWidth: vertical ? bodyHeight : bodyWidth + terminalWidth + readonly property real totalHeight: vertical ? bodyWidth + terminalWidth : bodyHeight + + // Determine active color based on state + readonly property color activeColor: { + if (!ready) { + return Qt.alpha(baseColor, Style.opacityMedium); + } + if (charging) { + return chargingColor; + } + if (low || critical) { + return lowColor; + } + return baseColor; + } + + // Background color for empty portion (semi-transparent) + readonly property color emptyColor: Qt.alpha(baseColor, 0.66) + + // State icon logic + readonly property string stateIcon: { + if (!ready) + return "x"; + if (charging) + return "bolt-filled"; + if (pluggedIn) + return "plug-filled"; + return ""; + } + + // Animated percentage for smooth transitions + property real animatedPercentage: percentage + + Behavior on animatedPercentage { + enabled: !Settings.data.general.animationDisabled + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + + // Timer to alternate between percentage text and state icon when charging/plugged + Timer { + id: alternateTimer + interval: 4000 + repeat: true + running: root.charging && root.showPercentageText + onTriggered: root.showStateIcon = !root.showStateIcon + } + + implicitWidth: Math.round(totalWidth) + implicitHeight: Math.round(totalHeight) + Layout.maximumWidth: implicitWidth + Layout.maximumHeight: implicitHeight + + // Battery body container + Item { + id: batteryBody + width: root.vertical ? root.bodyHeight : root.bodyWidth + root.terminalWidth + height: root.vertical ? root.bodyWidth + root.terminalWidth : root.bodyHeight + anchors.left: root.vertical ? undefined : parent.left + anchors.bottom: root.vertical ? parent.bottom : undefined + anchors.horizontalCenter: root.vertical ? parent.horizontalCenter : undefined + anchors.verticalCenter: root.vertical ? undefined : parent.verticalCenter + + // Battery body background + Rectangle { + id: bodyBackground + y: root.vertical ? root.terminalWidth : 0 + width: root.vertical ? root.bodyHeight : root.bodyWidth + height: root.vertical ? root.bodyWidth : root.bodyHeight + radius: root.cornerRadius + color: root.emptyColor + } + + // Terminal cap + Rectangle { + x: root.vertical ? (root.bodyHeight - root.terminalHeight) / 2 : root.bodyWidth + y: root.vertical ? 0 : (root.bodyHeight - root.terminalHeight) / 2 + width: root.vertical ? root.terminalHeight : root.terminalWidth + height: root.vertical ? root.terminalWidth : root.terminalHeight + radius: root.cornerRadius / 2 + color: root.critical ? root.lowColor : root.emptyColor + } + + // Fill level + Rectangle { + id: fillRect + visible: root.ready && (root.animatedPercentage > 0 || root.critical) + x: 0 + y: root.vertical ? root.terminalWidth + root.bodyWidth * (1 - (root.critical ? 1 : root.animatedPercentage / 100)) : 0 + width: root.vertical ? root.bodyHeight : root.bodyWidth * (root.critical ? 1 : root.animatedPercentage / 100) + height: root.vertical ? root.bodyWidth * (root.critical ? 1 : root.animatedPercentage / 100) : root.bodyHeight + radius: root.cornerRadius + color: root.activeColor + } + } + + // Percentage text overlaid on battery center + NText { + id: percentageText + visible: opacity > 0 + opacity: root.showPercentageText && root.ready && (root.charging ? !root.showStateIcon : !root.pluggedIn) ? 1 : 0 + x: batteryBody.x + Style.pixelAlignCenter(bodyBackground.width, width) + y: batteryBody.y + bodyBackground.y + Style.pixelAlignCenter(bodyBackground.height, height) + font.family: Settings.data.ui.fontFixed + font.weight: Style.fontWeightBold + text: root.vertical ? String(Math.round(root.animatedPercentage)).split('').join('\n') : Math.round(root.animatedPercentage) + pointSize: root.baseSize * (root.vertical ? 0.82 : 0.82) + color: Qt.alpha(root.textColor, 0.75) + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + lineHeight: root.vertical ? 0.7 : 1.0 + lineHeightMode: Text.ProportionalHeight + + Behavior on opacity { + enabled: !Settings.data.general.animationDisabled + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + } + + // State icon centered inside battery body (shown when alternating) + NIcon { + id: stateIconOverlay + visible: opacity > 0 + opacity: !root.ready || (root.charging ? (root.showStateIcon || !root.showPercentageText) : root.pluggedIn) ? 1 : 0 + x: batteryBody.x + Style.pixelAlignCenter(bodyBackground.width, width) + y: batteryBody.y + bodyBackground.y + Style.pixelAlignCenter(bodyBackground.height, height) + icon: root.stateIcon + pointSize: Style.toOdd(root.baseSize) + color: Qt.alpha(root.textColor, 0.75) + + Behavior on opacity { + enabled: !Settings.data.general.animationDisabled + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NBox.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NBox.qml new file mode 100644 index 0000000..16a0107 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NBox.qml @@ -0,0 +1,30 @@ +import QtQuick +import qs.Commons + +// Rounded group container using the variant surface color. +// To be used in side panels and settings panes to group fields or buttons. +// Opacity is based on panelBackgroundOpacity but clamped to a minimum to avoid full transparency. + +Item { + id: root + + property color color: Color.mSurfaceVariant + property bool forceOpaque: false + property alias radius: bg.radius + property alias border: bg.border + + Rectangle { + id: bg + anchors.fill: parent + radius: Style.radiusM + border.color: Style.boxBorderColor + border.width: Style.borderS + color: { + if (forceOpaque) { + return root.color; + } + + return Color.smartAlpha(root.color); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NBusyIndicator.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NBusyIndicator.qml new file mode 100644 index 0000000..48413b9 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NBusyIndicator.qml @@ -0,0 +1,64 @@ +import QtQuick +import qs.Commons + +Item { + id: root + + property bool running: true + property color color: Color.mPrimary + property int size: Style.baseWidgetSize + property int strokeWidth: Style.borderL + property int duration: Style.animationSlow * 2 + + implicitWidth: size + implicitHeight: size + + // GPU-optimized spinner - draw once, rotate with GPU transform + Item { + id: spinner + anchors.fill: parent + + // Static canvas - drawn ONCE, then cached + Canvas { + id: canvas + anchors.fill: parent + renderStrategy: Canvas.Cooperative // Better performance than Threaded for simple shapes + renderTarget: Canvas.FramebufferObject // GPU texture + + // Enable layer caching - critical for performance! + layer.enabled: true + layer.smooth: true + + Component.onCompleted: { + requestPaint(); + } + + onPaint: { + var ctx = getContext("2d"); + ctx.reset(); + + var centerX = width / 2; + var centerY = height / 2; + var radius = Math.min(width, height) / 2 - strokeWidth / 2; + + ctx.strokeStyle = root.color; + ctx.lineWidth = Math.max(1, root.strokeWidth); + ctx.lineCap = "round"; + + // Draw arc with gap (270 degrees = 3/4 of circle) + ctx.beginPath(); + ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + Math.PI * 1.5); + ctx.stroke(); + } + } + + // Smooth rotation animation - uses GPU transform, NO canvas repaints! + RotationAnimation on rotation { + running: root.running + from: 0 + to: 360 + duration: root.duration + loops: Animation.Infinite + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NButton.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NButton.qml new file mode 100644 index 0000000..45a857e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NButton.qml @@ -0,0 +1,184 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.UI + +Item { + id: root + + // Public properties + property string text: "" + property string icon: "" + property var tooltipText + property color backgroundColor: Color.mPrimary + property color textColor: Color.mOnPrimary + property color hoverColor: Color.mHover + property color textHoverColor: Color.mOnHover + property real fontSize: Style.fontSizeM + property int fontWeight: Style.fontWeightSemiBold + property real iconSize: Style.fontSizeL + property bool outlined: false + property int horizontalAlignment: Qt.AlignHCenter + property real buttonRadius: Style.iRadiusS + + // Signals + signal clicked + signal rightClicked + signal middleClicked + signal entered + signal exited + + // Internal properties + property bool hovered: false + readonly property color contentColor: { + if (!root.enabled) { + return Color.mOnSurfaceVariant; + } + if (root.hovered) { + return root.textHoverColor; + } + if (root.outlined) { + return root.backgroundColor; + } + return root.textColor; + } + + // Dimensions - include margin so border renders cleanly at fractional scales + implicitWidth: bg.implicitWidth + 2 * Style.borderS + implicitHeight: bg.implicitHeight + 2 * Style.borderS + + opacity: enabled ? 1.0 : 0.6 + + Rectangle { + id: bg + anchors.fill: parent + anchors.margins: Style.borderS + + implicitWidth: contentRow.implicitWidth + (root.fontSize * 2) + implicitHeight: contentRow.implicitHeight + (root.fontSize) + + radius: root.buttonRadius + color: { + if (!root.enabled) + return root.outlined ? "transparent" : Qt.lighter(Color.mSurfaceVariant, 1.2); + if (root.hovered) + return root.hoverColor; + return root.outlined ? "transparent" : root.backgroundColor; + } + + border.width: root.outlined ? Style.borderS : 0 + border.color: { + if (!root.enabled) + return Color.mOutline; + if (root.hovered) + return root.hoverColor; + return root.outlined ? root.backgroundColor : "transparent"; + } + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + Behavior on border.color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + // Content + RowLayout { + id: contentRow + anchors.verticalCenter: parent.verticalCenter + anchors.left: root.horizontalAlignment === Qt.AlignLeft ? parent.left : undefined + anchors.horizontalCenter: root.horizontalAlignment === Qt.AlignHCenter ? parent.horizontalCenter : undefined + anchors.leftMargin: root.horizontalAlignment === Qt.AlignLeft ? Style.marginL : 0 + spacing: Style.marginXS + + // Icon (optional) + NIcon { + Layout.alignment: Qt.AlignVCenter + visible: root.icon !== "" + icon: root.icon + pointSize: root.iconSize + color: root.contentColor + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + // Text + NText { + Layout.alignment: Qt.AlignVCenter + visible: root.text !== "" + text: root.text + pointSize: root.fontSize + font.weight: root.fontWeight + color: root.contentColor + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + } + + // Mouse interaction + MouseArea { + id: mouseArea + anchors.fill: parent + enabled: root.enabled + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + + onEntered: { + root.hovered = root.enabled ? true : false; + root.entered(); + if (root.hovered && root.tooltipText && (!Array.isArray(root.tooltipText) || root.tooltipText.length > 0)) { + TooltipService.show(root, root.tooltipText); + } + } + onExited: { + root.hovered = false; + root.exited(); + if (root.tooltipText && (!Array.isArray(root.tooltipText) || root.tooltipText.length > 0)) { + TooltipService.hide(); + } + } + onPressed: mouse => { + if (root.tooltipText && (!Array.isArray(root.tooltipText) || root.tooltipText.length > 0)) { + TooltipService.hide(); + } + if (mouse.button === Qt.LeftButton) { + root.clicked(); + } else if (mouse.button == Qt.RightButton) { + root.rightClicked(); + } else if (mouse.button == Qt.MiddleButton) { + root.middleClicked(); + } + } + + onCanceled: { + root.hovered = false; + if (root.tooltipText && (!Array.isArray(root.tooltipText) || root.tooltipText.length > 0)) { + TooltipService.hide(); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NCheckbox.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NCheckbox.qml new file mode 100644 index 0000000..30e51d0 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NCheckbox.qml @@ -0,0 +1,87 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons + +RowLayout { + id: root + + // Public API + property string label: "" + property string description: "" + property bool checked: false + property bool hovering: false + property color activeColor: Color.mPrimary + property color activeOnColor: Color.mOnPrimary + property int baseSize: root.defaultSize + property real labelSize: Style.fontSizeL + + readonly property int defaultSize: Style.baseWidgetSize * 0.7 + + signal toggled(bool checked) + signal entered + signal exited + + Layout.fillWidth: true + + NLabel { + label: root.label + labelSize: root.labelSize + description: root.description + visible: root.label !== "" || root.description !== "" + } + + // Spacer to push the checkbox to the far right + Item { + Layout.fillWidth: true + } + + Rectangle { + id: box + + opacity: enabled ? 1.0 : 0.6 + Layout.margins: Style.borderS + implicitWidth: Style.toOdd(root.baseSize) + implicitHeight: Style.toOdd(root.baseSize) + radius: Style.iRadiusXS * (root.baseSize / root.defaultSize) + color: root.checked ? root.activeColor : Color.mSurface + border.color: Color.mOutline + border.width: Style.borderS + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + + NIcon { + visible: root.checked + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, height) + icon: "check" + color: root.activeOnColor + pointSize: Style.toOdd(root.baseSize * 0.5) + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + onEntered: { + hovering = true; + root.entered(); + } + onExited: { + hovering = false; + root.exited(); + } + onClicked: root.toggled(!root.checked) + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NCircleStat.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NCircleStat.qml new file mode 100644 index 0000000..cb3341a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NCircleStat.qml @@ -0,0 +1,164 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Services.UI +import qs.Widgets + +// Compact circular statistic display using Layout management +Item { + id: root + + property real ratio: 0 // 0..1 range + property string icon: "" + property string suffix: "%" + property real contentScale: 1.0 + property color fillColor: Color.mPrimary + property var tooltipText + property string tooltipDirection: "top" + + // Arc geometry constants + readonly property real _gaugeSize: 60 * contentScale + readonly property real _lineWidth: 6 * contentScale + readonly property real _arcRadius: _gaugeSize / 2 - 5 * contentScale + // Arc goes from 150ยฐ to 390ยฐ (30ยฐ), gap at bottom + // Bottom of arc is at y = center + radius * sin(30ยฐ) = center + radius * 0.5 + // Plus half line width for stroke + readonly property real _arcBottomY: _gaugeSize / 2 + _arcRadius * 0.5 + _lineWidth / 2 + // Height needs to include the icon which sits inside the arc gap + // Icon is ~12px tall, positioned 4px below text center, need ~4px more padding + readonly property real _contentHeight: _arcBottomY + 4 * contentScale + + implicitWidth: Math.round(_gaugeSize) + implicitHeight: Math.round(_contentHeight) + Layout.maximumWidth: implicitWidth + Layout.maximumHeight: implicitHeight + + // Animated ratio for smooth transitions - reduces repaint frequency + property real animatedRatio: ratio + + Behavior on animatedRatio { + enabled: !Settings.data.general.animationDisabled + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + + // Repaint gauge when animated ratio changes (throttled by animation) + onAnimatedRatioChanged: { + if (!repaintTimer.running) { + repaintTimer.start(); + } + } + onFillColorChanged: gauge.requestPaint() + + // Throttle timer to limit repaint frequency during animation (~30 FPS) + Timer { + id: repaintTimer + interval: 33 + repeat: true + onTriggered: { + gauge.requestPaint(); + // Stop repeating once animation settles + if (Math.abs(root.animatedRatio - root.ratio) < 0.001) { + stop(); + } + } + } + + Canvas { + id: gauge + width: root._gaugeSize + height: root._gaugeSize + anchors.horizontalCenter: parent.horizontalCenter + y: 0 + + // Optimized Canvas settings for better GPU performance + renderStrategy: Canvas.Cooperative + renderTarget: Canvas.FramebufferObject + + // Enable layer caching - critical for performance! + layer.enabled: true + layer.smooth: true + + // Hide until first paint to avoid white FBO flash + visible: false + onPainted: visible = true + + Component.onCompleted: { + requestPaint(); + } + + onPaint: { + const ctx = getContext("2d"); + const w = width, h = height; + const cx = w / 2, cy = h / 2; + const r = root._arcRadius; + + // Rotated 90ยฐ to the right: gap at the bottom + // Start at 150ยฐ and end at 390ยฐ (30ยฐ) โ†’ bottom opening + const start = Math.PI * 5 / 6; // 150ยฐ + const endBg = Math.PI * 13 / 6; // 390ยฐ (equivalent to 30ยฐ) + + ctx.reset(); + ctx.lineWidth = root._lineWidth; + ctx.lineCap = Settings.data.general.iRadiusRatio > 0 ? "round" : "butt"; + + // Track uses outline for contrast against surfaceVariant backgrounds + ctx.strokeStyle = Color.mSurface; + ctx.beginPath(); + ctx.arc(cx, cy, r, start, endBg); + ctx.stroke(); + + // Value arc - only draw if ratio is meaningful (> 0.5%) + const r2 = Math.max(0, Math.min(1, root.animatedRatio)); + if (r2 > 0.005) { + const end = start + (endBg - start) * r2; + ctx.strokeStyle = root.fillColor; + ctx.beginPath(); + ctx.arc(cx, cy, r, start, end); + ctx.stroke(); + } + } + } + + // Percent centered in the circle + NText { + id: valueLabel + anchors.horizontalCenter: gauge.horizontalCenter + anchors.verticalCenter: gauge.verticalCenter + anchors.verticalCenterOffset: -4 * root.contentScale + text: `${Math.round(root.animatedRatio * 100)}${root.suffix}` + pointSize: Style.fontSizeM * root.contentScale * 0.9 + font.weight: Style.fontWeightBold + color: root.fillColor + horizontalAlignment: Text.AlignHCenter + } + + NIcon { + id: iconText + anchors.horizontalCenter: gauge.horizontalCenter + anchors.top: valueLabel.bottom + anchors.topMargin: 4 * root.contentScale + icon: root.icon + color: root.fillColor + pointSize: Style.fontSizeM * root.contentScale + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: { + if (root.tooltipText && (!Array.isArray(root.tooltipText) || root.tooltipText.length > 0)) { + TooltipService.show(root, root.tooltipText, root.tooltipDirection); + } + } + onExited: { + if (root.tooltipText && (!Array.isArray(root.tooltipText) || root.tooltipText.length > 0)) { + TooltipService.hide(); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NClock.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NClock.qml new file mode 100644 index 0000000..20bb30a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NClock.qml @@ -0,0 +1,442 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import "../Helpers/ColorsConvert.js" as ColorsConvert +import qs.Commons +import qs.Widgets + +Item { + id: root + + property var now: Time.now + + // Style: "analog" or "digital" + property string clockStyle: "analog" + + // Show seconds progress ring (digital only) + property bool showProgress: true + + // Color properties + property color backgroundColor: Color.mPrimary + property color clockColor: Color.mOnPrimary + + property color secondHandColor: { + var defaultColor = Color.mError; + var bestContrast = 1.0; // 1.0 is "no contrast" + var bestColor = defaultColor; + var candidates = [Color.mSecondary, Color.mTertiary, Color.mError]; + + const minContrast = 1.149; + + for (var i = 0; i < candidates.length; i++) { + var candidate = candidates[i]; + var contrastClock = ColorsConvert.getContrastRatio(candidate.toString(), clockColor.toString()); + if (contrastClock < minContrast) { + continue; + } + var contrastBg = ColorsConvert.getContrastRatio(candidate.toString(), backgroundColor.toString()); + if (contrastBg < minContrast) { + continue; + } + + var currentWorstContrast = Math.min(contrastBg, contrastClock); + + if (currentWorstContrast > bestContrast) { + bestContrast = currentWorstContrast; + bestColor = candidate; + } + } + + return bestColor; + } + + property color progressColor: root.secondHandColor + + // Font size properties for digital clock + property real hoursFontSize: Style.fontSizeXS + property real minutesFontSize: Style.fontSizeXXS + property int hoursFontWeight: Style.fontWeightBold + property int minutesFontWeight: Style.fontWeightBold + + // Scale ratio for canvas line widths (used by desktop widget scaling) + property real scaleRatio: Style.uiScaleRatio + + height: Math.round((Style.fontSizeXXXL * 1.9) / 2 * Style.uiScaleRatio) * 2 + width: root.height + + Loader { + id: clockLoader + anchors.fill: parent + + sourceComponent: { + if (root.clockStyle === "analog") + return analogClockComponent; + if (root.clockStyle === "binary") + return binaryClockComponent; + return digitalClockComponent; + } + + onLoaded: { + item.now = Qt.binding(function () { + return root.now; + }); + item.backgroundColor = Qt.binding(function () { + return root.backgroundColor; + }); + item.clockColor = Qt.binding(function () { + return root.clockColor; + }); + if (item.hasOwnProperty("secondHandColor")) { + item.secondHandColor = Qt.binding(function () { + return root.secondHandColor; + }); + } + if (item.hasOwnProperty("progressColor")) { + item.progressColor = Qt.binding(function () { + return root.progressColor; + }); + } + if (item.hasOwnProperty("hoursFontSize")) { + item.hoursFontSize = Qt.binding(function () { + return root.hoursFontSize; + }); + } + if (item.hasOwnProperty("minutesFontSize")) { + item.minutesFontSize = Qt.binding(function () { + return root.minutesFontSize; + }); + } + if ("hoursFontWeight" in item) { + item.hoursFontWeight = Qt.binding(function () { + return root.hoursFontWeight; + }); + } + if ("minutesFontWeight" in item) { + item.minutesFontWeight = Qt.binding(function () { + return root.minutesFontWeight; + }); + } + if (item.hasOwnProperty("scaleRatio")) { + item.scaleRatio = Qt.binding(function () { + return root.scaleRatio; + }); + } + if ("showProgress" in item) { + item.showProgress = Qt.binding(function () { + return root.showProgress; + }); + } + } + } + + // Analog Clock Component + component NClockAnalog: Item { + property var now + property color backgroundColor: Color.mPrimary + property color clockColor: Color.mOnPrimary + property color secondHandColor: Color.mError + property real scaleRatio: Style.uiScaleRatio + anchors.fill: parent + + Canvas { + id: clockCanvas + anchors.fill: parent + + Connections { + target: Time + function onNowChanged() { + clockCanvas.requestPaint(); + } + } + + onPaint: { + var currentTime = Time.now; + var hours = currentTime.getHours(); + var minutes = currentTime.getMinutes(); + var seconds = currentTime.getSeconds(); + + const markAlpha = 0.7; + var ctx = getContext("2d"); + ctx.reset(); + ctx.translate(width / 2, height / 2); + var radius = Math.min(width, height) / 2; + + // Hour marks + ctx.strokeStyle = Qt.alpha(clockColor, markAlpha); + ctx.lineWidth = 2 * scaleRatio; + var scaleFactor = 0.7; + + for (var i = 0; i < 12; i++) { + var scaleFactor = 0.8; + if (i % 3 === 0) { + scaleFactor = 0.65; + } + ctx.save(); + ctx.rotate(i * Math.PI / 6); + ctx.beginPath(); + ctx.moveTo(0, -radius * scaleFactor); + ctx.lineTo(0, -radius); + ctx.stroke(); + ctx.restore(); + } + + // Hour hand + ctx.save(); + var hourAngle = (hours % 12 + minutes / 60) * Math.PI / 6; + ctx.rotate(hourAngle); + ctx.strokeStyle = clockColor; + ctx.lineWidth = 3 * scaleRatio; + ctx.lineCap = "round"; + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(0, -radius * 0.6); + ctx.stroke(); + ctx.restore(); + + // Minute hand + ctx.save(); + var minuteAngle = (minutes + seconds / 60) * Math.PI / 30; + ctx.rotate(minuteAngle); + ctx.strokeStyle = clockColor; + ctx.lineWidth = 2 * scaleRatio; + ctx.lineCap = "round"; + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(0, -radius * 0.9); + ctx.stroke(); + ctx.restore(); + + // Second hand + ctx.save(); + var secondAngle = seconds * Math.PI / 30; + ctx.rotate(secondAngle); + ctx.strokeStyle = secondHandColor; + ctx.lineWidth = 1.6 * scaleRatio; + ctx.lineCap = "round"; + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(0, -radius); + ctx.stroke(); + ctx.restore(); + + // Center dot + ctx.beginPath(); + ctx.arc(0, 0, 3 * scaleRatio, 0, 2 * Math.PI); + ctx.fillStyle = clockColor; + ctx.fill(); + } + + Component.onCompleted: requestPaint() + } + } + + // Digital Clock Component + component NClockDigital: Item { + property var now + property color backgroundColor: Color.mPrimary + property color clockColor: Color.mOnPrimary + property color progressColor: Color.mError + property real hoursFontSize: Style.fontSizeXS + property real minutesFontSize: Style.fontSizeXXS + property int hoursFontWeight: Style.fontWeightBold + property int minutesFontWeight: Style.fontWeightBold + property real scaleRatio: Style.uiScaleRatio + property bool showProgress: true + + anchors.fill: parent + + // Digital clock's seconds circular progress + Canvas { + id: secondsProgress + anchors.fill: parent + visible: showProgress + property real progress: now.getSeconds() / 60 + onProgressChanged: requestPaint() + Connections { + target: Time + function onNowChanged() { + const total = now.getSeconds() * 1000 + now.getMilliseconds(); + secondsProgress.progress = total / 60000; + } + } + onPaint: { + var ctx = getContext("2d"); + var centerX = width / 2; + var centerY = height / 2; + var radius = Math.min(width, height) / 2 - 3 * scaleRatio; + ctx.reset(); + + // Background circle + ctx.beginPath(); + ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI); + ctx.lineWidth = 2.5 * scaleRatio; + ctx.strokeStyle = Qt.alpha(clockColor, 0.15); + ctx.stroke(); + + // Progress arc + ctx.beginPath(); + ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI); + ctx.lineWidth = 2.5 * scaleRatio; + ctx.strokeStyle = progressColor; + ctx.lineCap = "round"; + ctx.stroke(); + } + } + + // Digital clock + ColumnLayout { + anchors.centerIn: parent + spacing: -Style.marginXXS + + NText { + text: { + var t = Settings.data.location.use12hourFormat ? I18n.locale.toString(now, "hh AP") : I18n.locale.toString(now, "HH"); + return t.split(" ")[0]; + } + + pointSize: hoursFontSize + font.weight: hoursFontWeight + color: clockColor + family: Settings.data.ui.fontFixed + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: Qt.formatTime(now, "mm") + pointSize: minutesFontSize + font.weight: minutesFontWeight + color: clockColor + family: Settings.data.ui.fontFixed + Layout.alignment: Qt.AlignHCenter + } + } + } + + // Binary Clock Component + component NClockBinary: Item { + property var now + property color backgroundColor + property color clockColor: Color.mOnPrimary + + anchors.fill: parent + + readonly property int h: now.getHours() + readonly property int m: now.getMinutes() + readonly property int s: now.getSeconds() + + // BCD (Binary Coded Decimal) Format: + // H1 H2 : M1 M2 : S1 S2 + // H1 (tens): 0-2 (2 bits) + // H2 (ones): 0-9 (4 bits) + // M1 (tens): 0-5 (3 bits) + // M2 (ones): 0-9 (4 bits) + // S1 (tens): 0-5 (3 bits) + // S2 (ones): 0-9 (4 bits) + + RowLayout { + anchors.centerIn: parent + spacing: parent.width * 0.05 + + // Hours + RowLayout { + spacing: parent.parent.width * 0.02 + BinaryColumn { + value: Math.floor(h / 10) + bits: 2 + dotSize: root.width * 0.08 + activeColor: clockColor + Layout.alignment: Qt.AlignBottom + } + BinaryColumn { + value: h % 10 + bits: 4 + dotSize: root.width * 0.08 + activeColor: clockColor + Layout.alignment: Qt.AlignBottom + } + } + + // Minutes + RowLayout { + spacing: parent.parent.width * 0.02 + BinaryColumn { + value: Math.floor(m / 10) + bits: 3 + dotSize: root.width * 0.08 + activeColor: clockColor + Layout.alignment: Qt.AlignBottom + } + BinaryColumn { + value: m % 10 + bits: 4 + dotSize: root.width * 0.08 + activeColor: clockColor + Layout.alignment: Qt.AlignBottom + } + } + + // Seconds + RowLayout { + spacing: parent.parent.width * 0.02 + BinaryColumn { + value: Math.floor(s / 10) + bits: 3 + dotSize: root.width * 0.08 + activeColor: clockColor + Layout.alignment: Qt.AlignBottom + } + BinaryColumn { + value: s % 10 + bits: 4 + dotSize: root.width * 0.08 + activeColor: clockColor + Layout.alignment: Qt.AlignBottom + } + } + } + } + + component BinaryColumn: Column { + property int value: 0 + property int bits: 4 + property real dotSize: 10 + property color activeColor: "white" + + spacing: dotSize * 0.4 + + Repeater { + model: bits + + Rectangle { + property int bitIndex: (bits - 1) - index + property bool isActive: (value >> bitIndex) & 1 + + width: dotSize + height: dotSize + radius: dotSize / 2 + color: isActive ? activeColor : Qt.alpha(activeColor, 0.2) + + Behavior on color { + ColorAnimation { + duration: 200 + } + } + } + } + } + + Component { + id: analogClockComponent + NClockAnalog {} + } + + Component { + id: digitalClockComponent + NClockDigital {} + } + + Component { + id: binaryClockComponent + NClockBinary {} + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NCollapsible.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NCollapsible.qml new file mode 100644 index 0000000..de51933 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NCollapsible.qml @@ -0,0 +1,191 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons + +ColumnLayout { + id: root + + property string label: "" + property string description: "" + property bool expanded: false + property real contentSpacing: Style.marginM + property bool _userInteracted: false + + signal toggled(bool expanded) + + Layout.fillWidth: true + spacing: 0 + + // Default property to accept children + default property alias content: contentLayout.children + + // Header with clickable area + Rectangle { + id: headerContainer + Layout.fillWidth: true + Layout.preferredHeight: headerContent.implicitHeight + Style.margin2M + color: root.expanded ? Color.mSecondary : Color.mPrimary + radius: Style.iRadiusM + border.color: root.expanded ? Color.mOnSecondary : Color.mOutline + border.width: Style.borderS + + // Smooth color transitions + Behavior on color { + enabled: root._userInteracted + ColorAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + + Behavior on border.color { + enabled: root._userInteracted + ColorAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + + MouseArea { + id: headerArea + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + + onClicked: { + root._userInteracted = true; + root.expanded = !root.expanded; + root.toggled(root.expanded); + } + + // Hover effect overlay + Rectangle { + anchors.fill: parent + color: headerArea.containsMouse ? Color.mOnSurface : "transparent" + opacity: headerArea.containsMouse ? 0.08 : 0 + radius: headerContainer.radius // Reference the container's radius directly + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + } + + RowLayout { + id: headerContent + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + + // Expand/collapse icon with rotation animation + NIcon { + id: chevronIcon + icon: "chevron-right" + pointSize: Style.fontSizeL + color: root.expanded ? Color.mOnSecondary : Color.mOnPrimary + Layout.alignment: Qt.AlignVCenter + + rotation: root.expanded ? 90 : 0 + Behavior on rotation { + enabled: root._userInteracted + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + + Behavior on color { + enabled: root._userInteracted + ColorAnimation { + duration: Style.animationNormal + } + } + } + + // Header text content - properly contained + RowLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + spacing: Style.marginL + + NText { + text: root.label + pointSize: Style.fontSizeL + font.weight: Style.fontWeightSemiBold + color: root.expanded ? Color.mOnSecondary : Color.mOnPrimary + wrapMode: Text.WordWrap + + Behavior on color { + enabled: root._userInteracted + ColorAnimation { + duration: Style.animationNormal + } + } + } + + NText { + text: root.description + pointSize: Style.fontSizeS + font.weight: Style.fontWeightRegular + color: root.expanded ? Color.mOnSecondary : Color.mOnPrimary + Layout.fillWidth: true + wrapMode: Text.WordWrap + visible: root.description !== "" + opacity: 0.87 + + Behavior on color { + enabled: root._userInteracted + ColorAnimation { + duration: Style.animationNormal + } + } + } + } + } + } + + // Collapsible content with Material 3 styling + Rectangle { + id: contentContainer + Layout.fillWidth: true + Layout.topMargin: Style.marginS + + visible: root.expanded + color: Color.mSurface + radius: Style.iRadiusL + border.color: Color.mOutline + border.width: Style.borderS + + // Dynamic height based on content + Layout.preferredHeight: expanded ? contentLayout.implicitHeight + Style.margin2L : 0 + + // Smooth height animation + Behavior on Layout.preferredHeight { + enabled: root._userInteracted + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + + // Content layout + ColumnLayout { + id: contentLayout + anchors.fill: parent + anchors.margins: Style.marginL + spacing: root.contentSpacing + } + + // Fade in animation for content + opacity: root.expanded ? 1.0 : 0.0 + Behavior on opacity { + enabled: root._userInteracted + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NColorChoice.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NColorChoice.qml new file mode 100644 index 0000000..6d8cd29 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NColorChoice.qml @@ -0,0 +1,89 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Services.UI + +RowLayout { + id: root + + property string label: I18n.tr("common.select-color") + property string description: I18n.tr("common.select-color-description") + property string tooltip: "" + property string currentKey: "" + property var defaultValue: undefined + property var noneColor: undefined // color declared as var so we can nullify + property var noneOnColor: undefined // color declared as var so we can nullify + + readonly property bool isValueChanged: (defaultValue !== undefined) && (currentKey !== defaultValue) + readonly property string indicatorTooltip: { + I18n.tr("panels.indicator.default-value", { + "value": defaultValue === "" ? "(empty)" : String(defaultValue) + }); + } + + readonly property int diameter: Style.baseWidgetSize * 0.9 * Style.uiScaleRatio + + signal selected(string key) + + NLabel { + label: root.label + description: root.description + showIndicator: root.isValueChanged + indicatorTooltip: root.indicatorTooltip + } + + RowLayout { + id: colourRow + + opacity: enabled ? 1.0 : 0.6 + Layout.minimumWidth: root.diameter * Color.colorKeyModel.length + + Repeater { + model: Color.colorKeyModel + + Rectangle { + id: colorCircle + + property bool isSelected: root.currentKey === modelData.key + property bool isHovered: circleMouseArea.containsMouse + + Layout.alignment: Qt.AlignHCenter + implicitWidth: root.diameter + implicitHeight: root.diameter + radius: root.diameter * 0.5 + color: (modelData.key === "none" && root.noneColor !== undefined) ? root.noneColor : Color.resolveColorKey(modelData.key) + border.color: (isSelected || isHovered) ? Color.mOnSurface : Color.mOutline + border.width: Style.borderM + + MouseArea { + id: circleMouseArea + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: TooltipService.show(parent, modelData.name) + onExited: TooltipService.hide() + onClicked: { + root.currentKey = modelData.key; + root.selected(modelData.key); + } + } + + NIcon { + anchors.centerIn: parent + icon: "check" + pointSize: Math.max(Style.fontSizeXS, colorCircle.width * 0.4) + color: (modelData.key === "none" && root.noneOnColor !== undefined) ? root.noneOnColor : Color.resolveOnColorKey(modelData.key) + font.weight: Style.fontWeightBold + visible: colorCircle.isSelected + } + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NColorPicker.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NColorPicker.qml new file mode 100644 index 0000000..c005240 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NColorPicker.qml @@ -0,0 +1,76 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +Rectangle { + id: root + + property var screen + property color selectedColor: "black" + + signal colorSelected(color color) + + Layout.margins: Style.borderS + implicitWidth: 150 + implicitHeight: Math.round(Style.baseWidgetSize * 1.1) + + radius: Style.iRadiusM + color: Color.mSurface + border.color: Color.mOutline + border.width: Style.borderS + + // Minimized Look + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + var dialog = Qt.createComponent("NColorPickerDialog.qml").createObject(root, { + "selectedColor": selectedColor, + "parent": Overlay.overlay, + "screen": root.screen + }); + // Connect the dialog's signal to the picker's signal + dialog.colorSelected.connect(function (color) { + root.selectedColor = color; + root.colorSelected(color); + }); + + dialog.open(); + } + + RowLayout { + anchors.fill: parent + anchors { + leftMargin: Style.marginL + rightMargin: Style.marginL + } + spacing: Style.marginS + + // Color preview circle + Rectangle { + Layout.preferredWidth: root.height * 0.6 + Layout.preferredHeight: root.height * 0.6 + radius: Math.min(Style.iRadiusL, Layout.preferredWidth / 2) + color: root.selectedColor + border.color: Color.mOutline + border.width: Style.borderS + } + + NText { + text: root.selectedColor.toString().toUpperCase() + family: Settings.data.ui.fontFixed + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + } + + NIcon { + icon: "color-picker" + color: Color.mOnSurfaceVariant + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NColorPickerDialog.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NColorPickerDialog.qml new file mode 100644 index 0000000..d731eb4 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NColorPickerDialog.qml @@ -0,0 +1,775 @@ +import QtQml +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import "../Helpers/ColorList.js" as ColorList +import qs.Commons +import qs.Services.UI +import qs.Widgets + +Popup { + id: root + + property var screen + property color selectedColor: "black" + + enum EditMode { + R, + G, + B, + H, + S, + V + } + property int editMode: NColorPickerDialog.EditMode.R + + // Code to deal with Hue when color is achromatic + property real stableHue: 0 + onSelectedColorChanged: { + if (selectedColor.hsvHue >= 0) { + stableHue = selectedColor.hsvHue; + } + if (liveMode && visible) { + colorSelected(selectedColor); + } + } + readonly property real displayHue: selectedColor.hsvHue < 0 ? stableHue : selectedColor.hsvHue + + signal colorSelected(color color) + + // When true: hides Cancel/Apply, emits colorSelected on every color change + property bool liveMode: false + + width: 580 + padding: Style.marginXL + + modal: true + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + + onOpened: { + // Center on screen after popup is opened and parent position is known + if (screen && parent) { + var parentPos = parent.mapToItem(null, 0, 0); + x = Math.round((screen.width - width) / 2 - parentPos.x); + y = Math.round((screen.height - height) / 2 - parentPos.y); + } + } + + background: Rectangle { + color: Color.mSurface + radius: Style.iRadiusS + border.color: Color.mPrimary + border.width: Style.borderM + } + + contentItem: ColumnLayout { + id: mainContent + spacing: Style.marginL + + // Header + RowLayout { + Layout.fillWidth: true + + RowLayout { + spacing: Style.marginS + + NIcon { + icon: "color-picker" + pointSize: Style.fontSizeXXL + color: Color.mPrimary + } + + NText { + text: I18n.tr("widgets.color-picker.title") + pointSize: Style.fontSizeXL + font.weight: Style.fontWeightBold + color: Color.mPrimary + } + } + + Item { + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + onClicked: root.close() + } + } + + // Color preview section + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 80 + radius: Style.iRadiusS + color: root.selectedColor + border.color: Color.mOutline + border.width: Style.borderS + + ColumnLayout { + spacing: 0 + anchors.fill: parent + + Item { + Layout.fillHeight: true + } + + NText { + text: root.selectedColor.toString().toUpperCase() + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeL + font.weight: Style.fontWeightMedium + color: root.selectedColor.r + root.selectedColor.g + root.selectedColor.b > 1.5 ? "black" : "white" + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: "RGB(" + Math.round(root.selectedColor.r * 255) + ", " + Math.round(root.selectedColor.g * 255) + ", " + Math.round(root.selectedColor.b * 255) + ")" + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeM + color: root.selectedColor.r + root.selectedColor.g + root.selectedColor.b > 1.5 ? "black" : "white" + Layout.alignment: Qt.AlignHCenter + } + + Item { + Layout.fillHeight: true + } + } + } + + // Main Box + NBox { + Layout.fillWidth: true + Layout.preferredHeight: controlsOutterColumn.implicitHeight + Style.margin2L + + ButtonGroup { + id: colorValues + } + + ColumnLayout { + id: controlsOutterColumn + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + RowLayout { + spacing: Style.marginL // Ensure nice gap between Left and Right groups + + // SpinBoxes Column + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignTop + Layout.preferredWidth: 3 + + // --- RED --- + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NRadioButton { + ButtonGroup.group: colorValues + text: "R" + font.weight: Style.fontWeightMedium + checked: true + onClicked: root.editMode = NColorPickerDialog.EditMode.R + Layout.fillWidth: false + } + + NSpinBox { + id: redSpinBox + from: 0 + to: 255 + + onValueChanged: { + if (!selectedSlider.pressed && !fieldMouse.pressed && !hexInput.activeFocus && value !== Math.round(root.selectedColor.r * 255)) { + root.selectedColor = Qt.rgba(value / 255, root.selectedColor.g, root.selectedColor.b, 1); + } + } + Binding { + target: redSpinBox + property: "value" + value: Math.round(root.selectedColor.r * 255) + } + } + } + + // --- GREEN --- + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NRadioButton { + ButtonGroup.group: colorValues + text: "G" + font.weight: Style.fontWeightMedium + onClicked: root.editMode = NColorPickerDialog.EditMode.G + Layout.fillWidth: false + } + + NSpinBox { + id: greenSpinBox + from: 0 + to: 255 + + onValueChanged: { + if (!selectedSlider.pressed && !fieldMouse.pressed && !hexInput.activeFocus && value !== Math.round(root.selectedColor.g * 255)) { + root.selectedColor = Qt.rgba(root.selectedColor.r, value / 255, root.selectedColor.b, 1); + } + } + Binding { + target: greenSpinBox + property: "value" + value: Math.round(root.selectedColor.g * 255) + } + } + } + + // --- BLUE --- + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NRadioButton { + ButtonGroup.group: colorValues + text: "B" + font.weight: Style.fontWeightMedium + onClicked: root.editMode = NColorPickerDialog.EditMode.B + Layout.fillWidth: false + } + + NSpinBox { + id: blueSpinBox + from: 0 + to: 255 + + onValueChanged: { + if (!selectedSlider.pressed && !fieldMouse.pressed && !hexInput.activeFocus && value !== Math.round(root.selectedColor.b * 255)) { + root.selectedColor = Qt.rgba(root.selectedColor.r, root.selectedColor.g, value / 255, 1); + } + } + Binding { + target: blueSpinBox + property: "value" + value: Math.round(root.selectedColor.b * 255) + } + } + } + + // Spacer + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + + // --- HUE --- + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NRadioButton { + ButtonGroup.group: colorValues + text: "H" + font.weight: Style.fontWeightMedium + checked: true + onClicked: root.editMode = NColorPickerDialog.EditMode.H + Layout.fillWidth: false + } + + NSpinBox { + id: hueSpinBox + from: 0 + to: 360 + + onValueChanged: { + if (!selectedSlider.pressed && !fieldMouse.pressed && !hexInput.activeFocus && value !== Math.round(root.displayHue * 360)) { + var newHue = value / 360; + root.selectedColor = Qt.hsva(newHue, root.selectedColor.hsvSaturation, root.selectedColor.hsvValue, 1); + root.stableHue = newHue; + } + } + + Binding { + target: hueSpinBox + property: "value" + value: Math.round(root.displayHue * 360) + } + } + } + + // --- SATURATION --- + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NRadioButton { + ButtonGroup.group: colorValues + text: "S" + font.weight: Style.fontWeightMedium + onClicked: root.editMode = NColorPickerDialog.EditMode.S + Layout.fillWidth: false + } + + NSpinBox { + id: satSpinBox + from: 0 + to: 100 + + onValueChanged: { + if (!selectedSlider.pressed && !fieldMouse.pressed && !hexInput.activeFocus && value !== Math.round(root.selectedColor.hsvSaturation * 100)) { + root.selectedColor = Qt.hsva(root.selectedColor.hsvHue, value / 100, root.selectedColor.hsvValue, 1); + } + } + Binding { + target: satSpinBox + property: "value" + value: Math.round(root.selectedColor.hsvSaturation * 100) + } + } + } + + // --- VALUE --- + RowLayout { + spacing: Style.marginM + + NRadioButton { + ButtonGroup.group: colorValues + text: "V" + font.weight: Style.fontWeightMedium + onClicked: root.editMode = NColorPickerDialog.EditMode.V + Layout.fillWidth: false + } + + NSpinBox { + id: valSpinBox + from: 0 + to: 100 + + onValueChanged: { + if (!selectedSlider.pressed && !fieldMouse.pressed && !hexInput.activeFocus && value !== Math.round(root.selectedColor.hsvValue * 100)) { + root.selectedColor = Qt.hsva(root.selectedColor.hsvHue, root.selectedColor.hsvSaturation, value / 100, 1); + } + } + Binding { + target: valSpinBox + property: "value" + value: Math.round(root.selectedColor.hsvValue * 100) + } + } + } + + // Spacer + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + + // Hex input + RowLayout { + id: hexInput + Layout.fillWidth: true + spacing: Style.marginM + + NLabel { + label: "Hex:" + Layout.alignment: Qt.AlignVCenter + } + + NTextInput { + text: root.selectedColor.toString().toUpperCase() + fontFamily: Settings.data.ui.fontFixed + Layout.fillWidth: true + Layout.minimumWidth: implicitWidth + onEditingFinished: { + if (/^#[0-9A-F]{6}$/i.test(text)) { + root.selectedColor = text; + } + } + } + } + } + + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.preferredWidth: 5 + Layout.alignment: Qt.AlignTop + spacing: Style.marginS + + Layout.topMargin: Math.round(2 * Style.uiScaleRatio) //Shim to try and line up colorField with SpinBoxes + + // --- SLIDER --- + NColorSlider { + id: selectedSlider + Layout.fillHeight: true + rainbowMode: root.editMode === NColorPickerDialog.EditMode.H + topColor: { + if (rainbowMode) + return "transparent"; + switch (root.editMode) { + case NColorPickerDialog.EditMode.R: + return "#FF0000"; + case NColorPickerDialog.EditMode.G: + return "#00FF00"; + case NColorPickerDialog.EditMode.B: + return "#0000FF"; + default: + return "#FFFFFF"; + } + } + + Binding { + target: selectedSlider + property: "value" + when: !selectedSlider.pressed // Only update from model when NOT dragging + value: { + switch (root.editMode) { + case NColorPickerDialog.EditMode.R: + return root.selectedColor.r; + case NColorPickerDialog.EditMode.G: + return root.selectedColor.g; + case NColorPickerDialog.EditMode.B: + return root.selectedColor.b; + case NColorPickerDialog.EditMode.H: + return root.displayHue; + case NColorPickerDialog.EditMode.S: + return root.selectedColor.hsvSaturation; + case NColorPickerDialog.EditMode.V: + return root.selectedColor.hsvValue; + default: + return 0; + } + } + } + + onMoved: { + var v = value; + switch (root.editMode) { + case NColorPickerDialog.EditMode.R: + root.selectedColor = Qt.rgba(v, root.selectedColor.g, root.selectedColor.b, 1); + break; + case NColorPickerDialog.EditMode.G: + root.selectedColor = Qt.rgba(root.selectedColor.r, v, root.selectedColor.b, 1); + break; + case NColorPickerDialog.EditMode.B: + root.selectedColor = Qt.rgba(root.selectedColor.r, root.selectedColor.g, v, 1); + break; + case NColorPickerDialog.EditMode.H: + root.selectedColor = Qt.hsva(v, root.selectedColor.hsvSaturation, root.selectedColor.hsvValue, 1); + root.stableHue = v; + break; + case NColorPickerDialog.EditMode.S: + root.selectedColor = Qt.hsva(root.selectedColor.hsvHue, v, root.selectedColor.hsvValue, 1); + break; + case NColorPickerDialog.EditMode.V: + root.selectedColor = Qt.hsva(root.selectedColor.hsvHue, root.selectedColor.hsvSaturation, v, 1); + break; + } + } + } + + // Color Field + Rectangle { + id: colorField + Layout.fillWidth: true + + Layout.preferredHeight: width + Layout.alignment: Qt.AlignTop + + radius: 0 + border.color: Color.mOutline + border.width: Style.borderS + clip: true + + ShaderEffect { + anchors.fill: parent + anchors.margins: 1 // Avoid drawing over the border + + fragmentShader: "../Shaders/qsb/color_picker.frag.qsb" + + // Pass which radio is selected + readonly property real fixedVal: { + switch (root.editMode) { + case NColorPickerDialog.EditMode.R: + return root.selectedColor.r; + case NColorPickerDialog.EditMode.G: + return root.selectedColor.g; + case NColorPickerDialog.EditMode.B: + return root.selectedColor.b; + case NColorPickerDialog.EditMode.H: + return root.displayHue; + case NColorPickerDialog.EditMode.S: + return root.selectedColor.hsvSaturation; + case NColorPickerDialog.EditMode.V: + return root.selectedColor.hsvValue; + default: + return 0; + } + } + + // Send as one vector because GPUs are so damn picky + property vector4d params: Qt.vector4d(1.0, fixedVal, root.editMode, 0) + } + + MouseArea { + id: fieldMouse + anchors.fill: parent + cursorShape: Qt.CrossCursor + + // Update color when clicking or dragging + function updateColor(mouse) { + // Normalize X and Y (0.0 to 1.0) + var xVal = Math.max(0, Math.min(1, mouse.x / width)); + var yVal = Math.max(0, Math.min(1, 1.0 - (mouse.y / height))); // Flip Y (0 at bottom) + + // Get the current fixed value (from the slider) + var fixed = 0.0; + + switch (root.editMode) { + case NColorPickerDialog.EditMode.R: + fixed = root.selectedColor.r; + root.selectedColor = Qt.rgba(fixed, xVal, yVal, 1); + break; + case NColorPickerDialog.EditMode.G: + fixed = root.selectedColor.g; + root.selectedColor = Qt.rgba(xVal, fixed, yVal, 1); + break; + case NColorPickerDialog.EditMode.B: + fixed = root.selectedColor.b; + root.selectedColor = Qt.rgba(xVal, yVal, fixed, 1); + break; + case NColorPickerDialog.EditMode.H: + // Use stableHue to prevent flipping to -1 + fixed = root.displayHue; + root.selectedColor = Qt.hsva(fixed, xVal, yVal, 1); + root.stableHue = fixed; + break; + case NColorPickerDialog.EditMode.S: + fixed = root.selectedColor.hsvSaturation; + root.selectedColor = Qt.hsva(xVal, fixed, yVal, 1); + // If we dragged Hue (xVal), update stableHue + root.stableHue = yVal; + break; + case NColorPickerDialog.EditMode.V: + fixed = root.selectedColor.hsvValue; + root.selectedColor = Qt.hsva(xVal, yVal, fixed, 1); + // If we dragged Hue (xVal), update stableHue + root.stableHue = xVal; + break; + } + } + onPressed: mouse => updateColor(mouse) + onPositionChanged: mouse => updateColor(mouse) + } + + // Color Indicator + Rectangle { + width: 10 + height: 10 + radius: Math.min(Style.iRadiusXS, width / 2) + color: "transparent" + border.color: root.selectedColor.hsvValue < 0.5 ? "white" : "black" + border.width: 1 + + // Find position based on the current color + readonly property point selectedPos: { + switch (root.editMode) { + case NColorPickerDialog.EditMode.R: + return Qt.point(root.selectedColor.g, root.selectedColor.b); + case NColorPickerDialog.EditMode.G: + return Qt.point(root.selectedColor.r, root.selectedColor.b); + case NColorPickerDialog.EditMode.B: + return Qt.point(root.selectedColor.r, root.selectedColor.g); + case NColorPickerDialog.EditMode.H: + return Qt.point(root.selectedColor.hsvSaturation, root.selectedColor.hsvValue); + case NColorPickerDialog.EditMode.S: + return Qt.point(root.displayHue, root.selectedColor.hsvValue); + case NColorPickerDialog.EditMode.V: + return Qt.point(root.displayHue, root.selectedColor.hsvSaturation); + default: + return Qt.point(0, 0); + } + } + + // Convert values to pixel position + x: (selectedPos.x * parent.width) - width / 2 + y: ((1.0 - selectedPos.y) * parent.height) - height / 2 + } + + // Redraw the border in case Color Indicator is near the edge + Rectangle { + anchors.fill: parent + color: "transparent" + border.color: Color.mOutline + border.width: Style.borderS + antialiasing: false + } + } + } + } + + NLabel { + label: I18n.tr("widgets.color-picker.palette-label") + Layout.fillWidth: true + } + + NScrollView { + id: paletteScrollView + Layout.fillWidth: true + Layout.preferredHeight: Math.min(paletteGrid.implicitHeight, 200) + verticalPolicy: paletteGrid.implicitHeight > 200 ? ScrollBar.AsNeeded : ScrollBar.AlwaysOff + horizontalPolicy: ScrollBar.AlwaysOff + + GridLayout { + id: paletteGrid + width: paletteScrollView.availableWidth + columns: 17 + columnSpacing: 6 + rowSpacing: 6 + + NLabel { + Layout.columnSpan: 17 + Layout.fillWidth: true + description: I18n.tr("widgets.color-picker.palette-theme-colors") + } + + Repeater { + model: [ + { + name: "mPrimary", + color: Color.mPrimary + }, + { + name: "mSecondary", + color: Color.mSecondary + }, + { + name: "mTertiary", + color: Color.mTertiary + }, + { + name: "mError", + color: Color.mError + }, + { + name: "mSurface", + color: Color.mSurface + }, + { + name: "mSurfaceVariant", + color: Color.mSurfaceVariant + }, + { + name: "mOutline", + color: Color.mOutline + } + ] + + Rectangle { + width: 24 + height: 24 + radius: Style.iRadiusXXS + color: modelData.color + border.color: root.selectedColor.toString() === modelData.color.toString() ? Color.mPrimary : Color.mOutline + border.width: Math.max(1, root.selectedColor.toString() === modelData.color.toString() ? Style.borderM : Style.borderS) + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + + onEntered: { + TooltipService.show(parent, modelData.name + "\n" + parent.color.toString().toUpperCase()); + } + onExited: { + TooltipService.hide(); + } + onClicked: { + root.selectedColor = modelData.color; + TooltipService.hide(); + } + } + } + } + + NDivider { + Layout.columnSpan: 17 + Layout.fillWidth: true + Layout.topMargin: Style.marginXS + Layout.bottomMargin: 0 + } + + NLabel { + Layout.columnSpan: 17 + Layout.fillWidth: true + description: I18n.tr("widgets.color-picker.palette-description") + } + + Repeater { + model: ColorList.colors + + Rectangle { + width: 24 + height: 24 + radius: Math.min(Style.iRadiusXS, width / 2) + color: modelData.color + border.color: root.selectedColor.toString() === modelData.color.toString() ? Color.mPrimary : Color.mOutline + border.width: root.selectedColor.toString() === modelData.color.toString() ? 2 : 1 + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + + onEntered: { + TooltipService.show(parent, modelData.name + "\n" + parent.color.toString().toUpperCase(), "auto"); + } + onExited: { + TooltipService.hide(); + } + onClicked: { + root.selectedColor = modelData.color; + TooltipService.hide(); + } + } + } + } + } + } + } + } + + RowLayout { + visible: !root.liveMode + Layout.fillWidth: true + Layout.topMargin: 1 + Layout.bottomMargin: 1 + spacing: 10 + + Item { + Layout.fillWidth: true + } + + NButton { + id: cancelButton + text: I18n.tr("common.cancel") + outlined: cancelButton.hovered ? false : true + onClicked: { + root.close(); + } + } + + NButton { + text: I18n.tr("common.apply") + icon: "check" + onClicked: { + root.colorSelected(root.selectedColor); + // Delay close to prevent click propagation to elements behind the dialog + Qt.callLater(() => { + root.close(); + }); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NColorSlider.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NColorSlider.qml new file mode 100644 index 0000000..b08aaf3 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NColorSlider.qml @@ -0,0 +1,238 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Shapes +import qs.Commons +import qs.Services.UI + +Slider { + id: root + + property color fillColor: "transparent" + property var cutoutColor: Color.mSurface + property bool snapAlways: true + property real widthRatio: 0.7 + property var tooltipText + property string tooltipDirection: "auto" + property bool hovering: false + property color topColor: "white" + property color bottomColor: "black" + property bool rainbowMode: false + + readonly property real knobDiameter: Math.round((Style.baseWidgetSize * widthRatio * Style.uiScaleRatio) / 2) * 2 + readonly property real trackWidth: Math.round((knobDiameter * 0.4 * Style.uiScaleRatio) / 2) * 2 + readonly property real trackRadius: Math.min(Style.iRadiusL, trackWidth / 2) + readonly property real cutoutExtra: Math.round((Style.baseWidgetSize * 0.1 * Style.uiScaleRatio) / 2) * 2 + + orientation: Qt.Vertical + + padding: cutoutExtra / 2 + + snapMode: snapAlways ? Slider.SnapAlways : Slider.SnapOnRelease + implicitWidth: Math.max(trackWidth, knobDiameter) + + background: Item { + id: bgContainer + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + width: root.trackWidth + height: root.availableHeight + + LinearGradient { + id: standardLinearGradient + x1: 0 + y1: 0 + x2: 0 + y2: root.availableHeight + GradientStop { + position: 0.0 + color: root.topColor + } + GradientStop { + position: 1.0 + color: root.bottomColor + } + } + + LinearGradient { + id: rainbowLinearGradient + x1: 0 + y1: 0 + x2: 0 + y2: root.availableHeight + GradientStop { + position: 0.000 + color: "#FF0000" + } + GradientStop { + position: 0.167 + color: "#FF00FF" + } + GradientStop { + position: 0.333 + color: "#0000FF" + } + GradientStop { + position: 0.500 + color: "#00FFFF" + } + GradientStop { + position: 0.667 + color: "#00FF00" + } + GradientStop { + position: 0.833 + color: "#FFFF00" + } + GradientStop { + position: 1.000 + color: "#FF0000" + } + } + + Shape { + anchors.centerIn: parent + width: root.trackWidth + height: bgContainer.height + visible: root.trackWidth > 0 && bgContainer.height > 0 + preferredRendererType: Shape.CurveRenderer + asynchronous: true + + ShapePath { + id: trackPath + strokeColor: Qt.alpha(Color.mOutline, 0.5) + strokeWidth: Style.borderS + fillGradient: root.rainbowMode ? rainbowLinearGradient : standardLinearGradient + + readonly property real w: root.trackWidth + readonly property real h: root.availableHeight + readonly property real r: root.trackRadius + + startX: r + startY: 0 + + PathLine { + x: trackPath.w - trackPath.r + y: 0 + } + PathArc { + x: trackPath.w + y: trackPath.r + radiusX: trackPath.r + radiusY: trackPath.r + } + PathLine { + x: trackPath.w + y: trackPath.h - trackPath.r + } + PathArc { + x: trackPath.w - trackPath.r + y: trackPath.h + radiusX: trackPath.r + radiusY: trackPath.r + } + PathLine { + x: trackPath.r + y: trackPath.h + } + PathArc { + x: 0 + y: trackPath.h - trackPath.r + radiusX: trackPath.r + radiusY: trackPath.r + } + PathLine { + x: 0 + y: trackPath.r + } + PathArc { + x: trackPath.r + y: 0 + radiusX: trackPath.r + radiusY: trackPath.r + } + } + } + + // Circular cutout + Rectangle { + id: knobCutout + implicitWidth: root.knobDiameter + root.cutoutExtra + implicitHeight: root.knobDiameter + root.cutoutExtra + radius: Math.min(Style.iRadiusL, width / 2) + color: root.cutoutColor !== undefined ? root.cutoutColor : Color.mSurface + y: root.visualPosition * (root.availableHeight - root.knobDiameter) - root.cutoutExtra / 2 + anchors.horizontalCenter: parent.horizontalCenter + } + } + + handle: Item { + implicitWidth: root.knobDiameter + implicitHeight: root.knobDiameter + y: root.topPadding + root.visualPosition * (root.availableHeight - height) + anchors.horizontalCenter: parent.horizontalCenter + + Rectangle { + id: knob + implicitWidth: root.knobDiameter + implicitHeight: root.knobDiameter + radius: Math.min(Style.iRadiusL, width / 2) + color: { + if (root.rainbowMode) { + // Hue Logic: Map position (0.0 to 1.0) directly to Hue + return Qt.hsva(1 - root.visualPosition, 1, 1, 1); + } else { + // Linear Interpolation for Standard Gradients + // visualPosition 0.0 = Top, 1.0 = Bottom + var t = root.visualPosition; + var r = root.topColor.r * (1 - t) + root.bottomColor.r * t; + var g = root.topColor.g * (1 - t) + root.bottomColor.g * t; + var b = root.topColor.b * (1 - t) + root.bottomColor.b * t; + return Qt.rgba(r, g, b, 1); + } + } + + border.color: root.pressed ? Color.mHover : Color.mPrimary + border.width: Style.borderL + anchors.centerIn: parent + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + MouseArea { + enabled: true + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + acceptedButtons: Qt.NoButton // Don't accept any mouse buttons - only hover + propagateComposedEvents: true + + onEntered: { + root.hovering = true; + if (root.tooltipText && (!Array.isArray(root.tooltipText) || root.tooltipText.length > 0)) { + TooltipService.show(knob, root.tooltipText, root.tooltipDirection); + } + } + + onExited: { + root.hovering = false; + if (root.tooltipText && (!Array.isArray(root.tooltipText) || root.tooltipText.length > 0)) { + TooltipService.hide(); + } + } + } + + // Hide tooltip when slider is pressed (anywhere on the slider) + Connections { + target: root + function onPressedChanged() { + if (root.pressed && root.tooltipText && (!Array.isArray(root.tooltipText) || root.tooltipText.length > 0)) { + TooltipService.hide(); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NComboBox.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NComboBox.qml new file mode 100644 index 0000000..a669696 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NComboBox.qml @@ -0,0 +1,342 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.UI +import qs.Widgets + +RowLayout { + id: root + + property real minimumWidth: 200 + property real popupHeight: 180 + + property string label: "" + property string description: "" + property string tooltip: "" + property var model + property string currentKey: "" + property string placeholder: "" + property var defaultValue: undefined + property string settingsPath: "" + property real baseSize: 1.0 + + readonly property real preferredHeight: Math.round(Style.baseWidgetSize * 1.1 * root.baseSize) + readonly property var comboBox: combo + + signal selected(string key) + + spacing: Style.marginL + + // Less strict comparison with != (instead of !==) so it can properly compare int vs string (ex for FPS: 30 and "30") + readonly property bool isValueChanged: (defaultValue !== undefined) && (currentKey != defaultValue) + + readonly property string indicatorTooltip: { + if (defaultValue === undefined) + return ""; + var displayValue = ""; + if (defaultValue === "") { + // Try to find the display name for empty key in the model + var found = false; + if (root.model) { + if (Array.isArray(root.model)) { + for (var i = 0; i < root.model.length; i++) { + var item = root.model[i]; + if (item && item.key === "") { + displayValue = item.name || I18n.tr("panels.indicator.system-default"); + found = true; + break; + } + } + } else if (typeof root.model.get === 'function') { + for (var i = 0; i < root.itemCount(); i++) { + var item = root.getItem(i); + if (item && item.key === "") { + displayValue = item.name || I18n.tr("panels.indicator.system-default"); + found = true; + break; + } + } + } + } + // If not found in model, show "System Default" instead of "(empty)" + if (!found) { + displayValue = I18n.tr("panels.indicator.system-default"); + } + } else { + // Try to find the display name for the default key in the model + var found = false; + if (root.model) { + if (Array.isArray(root.model)) { + for (var i = 0; i < root.model.length; i++) { + var item = root.model[i]; + if (item && item.key === defaultValue) { + displayValue = item.name || String(defaultValue); + found = true; + break; + } + } + } else if (typeof root.model.get === 'function') { + for (var i = 0; i < root.itemCount(); i++) { + var item = root.getItem(i); + if (item && item.key === defaultValue) { + displayValue = item.name || String(defaultValue); + found = true; + break; + } + } + } + } + if (!found) { + displayValue = String(defaultValue); + } + } + return I18n.tr("panels.indicator.default-value", { + "value": displayValue + }); + } + + function itemCount() { + if (!root.model) + return 0; + if (typeof root.model.count === 'number') + return root.model.count; + if (Array.isArray(root.model)) + return root.model.length; + return 0; + } + + function getItem(index) { + if (!root.model) + return null; + if (typeof root.model.get === 'function') + return root.model.get(index); + if (Array.isArray(root.model)) + return root.model[index]; + return null; + } + + function findIndexByKey(key) { + for (var i = 0; i < itemCount(); i++) { + var item = getItem(i); + if (item && item.key === key) + return i; + } + return -1; + } + + NLabel { + label: root.label + description: root.description + showIndicator: root.isValueChanged + indicatorTooltip: root.indicatorTooltip + } + + ComboBox { + id: combo + + opacity: enabled ? 1.0 : 0.6 + Layout.margins: Style.borderS + Layout.minimumWidth: Math.round(root.minimumWidth * Style.uiScaleRatio) + Layout.preferredHeight: Math.round(root.preferredHeight * Style.uiScaleRatio) + implicitWidth: Layout.minimumWidth + model: root.model + textRole: "name" + currentIndex: root.findIndexByKey(root.currentKey) + + onActivated: { + var item = root.getItem(combo.currentIndex); + if (item && item.key !== undefined) + root.selected(item.key); + } + + Keys.onUpPressed: event => { + if (combo.popup.visible) { + if (listView.currentIndex > 0) { + listView.currentIndex--; + listView.positionViewAtIndex(listView.currentIndex, ListView.Contain); + } + event.accepted = true; + } else { + event.accepted = false; + } + } + + Keys.onDownPressed: event => { + if (combo.popup.visible) { + if (listView.currentIndex < root.itemCount() - 1) { + listView.currentIndex++; + listView.positionViewAtIndex(listView.currentIndex, ListView.Contain); + } + event.accepted = true; + } else { + event.accepted = false; + } + } + + Keys.onReturnPressed: event => { + if (combo.popup.visible) { + var item = root.getItem(listView.currentIndex); + if (item && item.key !== undefined) { + root.selected(item.key); + combo.currentIndex = listView.currentIndex; + combo.popup.close(); + } + event.accepted = true; + } else { + event.accepted = false; + } + } + + Keys.onEnterPressed: event => { + combo.Keys.returnPressed(event); + } + + background: Rectangle { + implicitWidth: Math.round(Style.baseWidgetSize * 3.75 * Style.uiScaleRatio) + implicitHeight: Math.round(root.preferredHeight * Style.uiScaleRatio) + color: Color.mSurface + border.color: combo.activeFocus ? Color.mSecondary : Color.mOutline + border.width: Style.borderS + radius: Style.iRadiusM + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + onEntered: { + if (root.tooltip != "") { + TooltipService.show(root, root.tooltip); + } + } + onExited: { + if (root.tooltip != "") { + TooltipService.hide(); + } + } + } + } + + contentItem: NText { + leftPadding: Style.marginL + rightPadding: combo.indicator.width + Style.marginL + pointSize: Style.fontSizeM + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + color: combo.currentIndex >= 0 ? Color.mOnSurface : Color.mOnSurfaceVariant + text: { + if (combo.currentIndex >= 0 && combo.currentIndex < root.itemCount()) { + var item = root.getItem(combo.currentIndex); + return item ? item.name : root.placeholder; + } + return root.placeholder; + } + } + + indicator: NIcon { + x: combo.width - width - Style.marginM + y: combo.topPadding + (combo.availableHeight - height) / 2 + icon: "caret-down" + pointSize: Style.fontSizeL + } + + popup: Popup { + y: combo.height + Style.marginS + implicitWidth: combo.width + implicitHeight: Math.min(Math.round(root.popupHeight * Style.uiScaleRatio), listView.contentHeight + Style.margin2M) + padding: Style.marginM + + onOpened: { + listView.currentIndex = combo.currentIndex; + listView.positionViewAtIndex(combo.currentIndex, ListView.Beginning); + } + + contentItem: NListView { + id: listView + property var comboBox: combo + model: combo.popup.visible ? root.model : null + highlightMoveDuration: 0 + //showGradientMasks: false + + delegate: Rectangle { + id: delegateRect + required property int index + property bool isHighlighted: listView.currentIndex === index + + width: listView.availableWidth + height: delegateText.implicitHeight + Style.margin2S + radius: Style.iRadiusS + color: isHighlighted ? Color.mHover : "transparent" + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + NText { + id: delegateText + anchors.fill: parent + anchors.leftMargin: Style.marginM + anchors.rightMargin: Style.marginM + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + pointSize: Style.fontSizeM + color: delegateRect.isHighlighted ? Color.mOnHover : Color.mOnSurface + text: { + var item = root.getItem(delegateRect.index); + return item && item.name ? item.name : ""; + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onContainsMouseChanged: { + if (containsMouse) { + listView.currentIndex = delegateRect.index; + } + } + onClicked: { + var item = root.getItem(delegateRect.index); + if (item && item.key !== undefined) { + root.selected(item.key); + listView.comboBox.currentIndex = delegateRect.index; + listView.comboBox.popup.close(); + } + } + } + } + } + + background: Rectangle { + color: Color.mSurfaceVariant + border.color: Color.mOutline + border.width: Style.borderS + radius: Style.iRadiusM + } + } + + Connections { + target: root + function onCurrentKeyChanged() { + combo.currentIndex = root.findIndexByKey(root.currentKey); + } + function onModelChanged() { + combo.currentIndex = root.findIndexByKey(root.currentKey); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NContextMenu.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NContextMenu.qml new file mode 100644 index 0000000..0975a12 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NContextMenu.qml @@ -0,0 +1,182 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons + +/* +* NContextMenu - Popup-based context menu for use inside panels and dialogs +* +* Use this component when you need a context menu inside: +* - Settings panels +* - Dialogs +* - Repeater delegates +* - Any nested component context +* +* For bar widgets and top-level window contexts, use NPopupContextMenu instead, +* which provides better screen boundary handling and compositor integration. +* +* Usage: +* NContextMenu { +* id: contextMenu +* parent: Overlay.overlay +* model: [ +* { "label": "Action 1", "action": "action1", "icon": "icon-name" }, +* { "label": "Action 2", "action": "action2" } +* ] +* onTriggered: action => { Logger.i("MyModule", "Selected:", action) } +* } +* +* MouseArea { +* onClicked: contextMenu.openAtItem(parent, mouse.x, mouse.y) +* } +*/ +Popup { + id: root + + property var model: [] + property real itemHeight: 36 + property real itemPadding: Style.marginM + property int verticalPolicy: ScrollBar.AsNeeded + property int horizontalPolicy: ScrollBar.AsNeeded + // Optional: explicit item whose bounds the menu must stay within. + // When unset, openAtItem auto-detects the nearest clipping ancestor. + property Item constrainTo: null + property Item _detectedConstraint: null + + signal triggered(string action) + + // Filter out hidden items to avoid spacing artifacts from zero-height items + readonly property var filteredModel: { + if (!model || model.length === 0) + return []; + var filtered = []; + for (var i = 0; i < model.length; i++) { + if (model[i].visible !== false) { + filtered.push(model[i]); + } + } + return filtered; + } + + width: 180 + padding: Style.marginS + + background: Rectangle { + color: Color.mSurfaceVariant + border.color: Color.mOutline + border.width: Style.borderS + radius: Style.iRadiusM + } + + contentItem: NListView { + id: listView + implicitHeight: Math.max(contentHeight, root.itemHeight) + spacing: Style.marginXXS + interactive: contentHeight > root.height + verticalPolicy: root.verticalPolicy + horizontalPolicy: root.horizontalPolicy + reserveScrollbarSpace: false + model: root.filteredModel + + delegate: ItemDelegate { + id: menuItem + width: listView.availableWidth + height: root.itemHeight + opacity: modelData.enabled !== false ? 1.0 : 0.5 + enabled: modelData.enabled !== false + + // Store reference to the popup + property var popup: root + + background: Rectangle { + color: menuItem.hovered && menuItem.enabled ? Color.mHover : "transparent" + radius: Style.iRadiusS + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + contentItem: RowLayout { + spacing: Style.marginS + + // Optional icon + NIcon { + visible: modelData.icon !== undefined + icon: modelData.icon || "" + pointSize: Style.fontSizeM + color: menuItem.hovered && menuItem.enabled ? Color.mOnHover : Color.mOnSurface + Layout.leftMargin: root.itemPadding + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + NText { + text: modelData.label || modelData.text || "" + pointSize: Style.fontSizeM + color: menuItem.hovered && menuItem.enabled ? Color.mOnHover : Color.mOnSurface + verticalAlignment: Text.AlignVCenter + Layout.fillWidth: true + Layout.leftMargin: modelData.icon === undefined ? root.itemPadding : 0 + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + } + + onClicked: { + if (enabled) { + popup.triggered(modelData.action || modelData.key || index.toString()); + popup.close(); + } + } + } + } + + // Helper function to open at mouse position + function openAt(x, y) { + if (root.parent) { + var menuWidth = root.width; + var itemCount = root.filteredModel.length; + var menuHeight = Math.max(itemCount * root.itemHeight + Math.max(0, itemCount - 1) * listView.spacing, root.itemHeight) + root.topPadding + root.bottomPadding; + var constraint = root.constrainTo || root._detectedConstraint; + if (constraint) { + var tl = constraint.mapToItem(root.parent, 0, 0); + x = Math.max(tl.x, Math.min(x, tl.x + constraint.width - menuWidth)); + y = Math.max(tl.y, Math.min(y, tl.y + constraint.height - menuHeight)); + } else { + x = Math.max(0, Math.min(x, root.parent.width - menuWidth)); + y = Math.max(0, Math.min(y, root.parent.height - menuHeight)); + } + } + root.x = x; + root.y = y; + root.open(); + } + + // Helper function to open at item + function openAtItem(item, mouseX, mouseY) { + if (!root.constrainTo) { + root._detectedConstraint = null; + var p = item; + while (p && p !== root.parent) { + if (p.clip && p.width > 0 && p.height > 0) { + root._detectedConstraint = p; + break; + } + p = p.parent; + } + } + var pos = item.mapToItem(root.parent, mouseX || 0, mouseY || 0); + openAt(pos.x, pos.y); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NDateTimeTokens.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NDateTimeTokens.qml new file mode 100644 index 0000000..496c70b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NDateTimeTokens.qml @@ -0,0 +1,393 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons + +Rectangle { + id: root + + property date sampleDate: new Date() // Dec 25, 2023, 2:30:45.123 PM + + signal tokenClicked(string token) + + Layout.margins: Style.borderS + color: Color.mSurface + border.color: Color.mOutline + border.width: Style.borderS + radius: Style.iRadiusM + + ColumnLayout { + id: column + anchors.fill: parent + anchors.margins: Style.marginS + spacing: Style.marginS + + Flickable { + Layout.fillWidth: true + Layout.fillHeight: true + contentHeight: tokensColumn.implicitHeight + clip: true + + Column { + id: tokensColumn + width: parent.width + + Repeater { + model: [ + // Common format combinations + { + "category": "Common", + "token": "h:mm AP", + "description": I18n.tr("widgets.datetime-tokens.common-12hour-time-minutes"), + "example": "2:30 PM" + }, + { + "category": "Common", + "token": "HH:mm", + "description": I18n.tr("widgets.datetime-tokens.common-24hour-time-minutes"), + "example": "14:30" + }, + { + "category": "Common", + "token": "HH:mm:ss", + "description": I18n.tr("widgets.datetime-tokens.common-24hour-time-seconds"), + "example": "14:30:45" + }, + { + "category": "Common", + "token": "ddd MMM d", + "description": I18n.tr("widgets.datetime-tokens.common-weekday-month-day"), + "example": "Mon Dec 25" + }, + { + "category": "Common", + "token": "yyyy-MM-dd", + "description": I18n.tr("widgets.datetime-tokens.common-iso-date"), + "example": "2023-12-25" + }, + { + "category": "Common", + "token": "MM/dd/yyyy", + "description": I18n.tr("widgets.datetime-tokens.common-us-date"), + "example": "12/25/2023" + }, + { + "category": "Common", + "token": "dd.MM.yyyy", + "description": I18n.tr("widgets.datetime-tokens.common-european-date"), + "example": "25.12.2023" + }, + { + "category": "Common", + "token": "ddd, MMM dd", + "description": I18n.tr("widgets.datetime-tokens.common-weekday-date"), + "example": "Fri, Dec 12" + } // Hour tokens + , + { + "category": "Hour", + "token": "H", + "description": I18n.tr("widgets.datetime-tokens.hour-no-leading-zero"), + "example": "14" + }, + { + "category": "Hour", + "token": "HH", + "description": I18n.tr("widgets.datetime-tokens.hour-leading-zero"), + "example": "14" + } // Minute tokens + , + { + "category": "Minute", + "token": "m", + "description": I18n.tr("widgets.datetime-tokens.minute-no-leading-zero"), + "example": "30" + }, + { + "category": "Minute", + "token": "mm", + "description": I18n.tr("widgets.datetime-tokens.minute-leading-zero"), + "example": "30" + } // Second tokens + , + { + "category": "Second", + "token": "s", + "description": I18n.tr("widgets.datetime-tokens.second-no-leading-zero"), + "example": "45" + }, + { + "category": "Second", + "token": "ss", + "description": I18n.tr("widgets.datetime-tokens.second-leading-zero"), + "example": "45" + } // AM/PM tokens + , + { + "category": "AM/PM", + "token": "AP", + "description": I18n.tr("widgets.datetime-tokens.ampm-uppercase"), + "example": "PM" + }, + { + "category": "AM/PM", + "token": "ap", + "description": I18n.tr("widgets.datetime-tokens.ampm-lowercase"), + "example": "pm" + } // Timezone tokens + , + { + "category": "Timezone", + "token": "t", + "description": I18n.tr("widgets.datetime-tokens.timezone-abbreviation"), + "example": "UTC" + } // Year tokens + , + { + "category": "Year", + "token": "yy", + "description": I18n.tr("widgets.datetime-tokens.year-two-digit"), + "example": "23" + }, + { + "category": "Year", + "token": "yyyy", + "description": I18n.tr("widgets.datetime-tokens.year-four-digit"), + "example": "2023" + } // Month tokens + , + { + "category": "Month", + "token": "M", + "description": I18n.tr("widgets.datetime-tokens.month-number-no-zero"), + "example": "12" + }, + { + "category": "Month", + "token": "MM", + "description": I18n.tr("widgets.datetime-tokens.month-number-leading-zero"), + "example": "12" + }, + { + "category": "Month", + "token": "MMM", + "description": I18n.tr("widgets.datetime-tokens.month-abbreviated"), + "example": "Dec" + }, + { + "category": "Month", + "token": "MMMM", + "description": I18n.tr("widgets.datetime-tokens.month-full"), + "example": "December" + } // Day tokens + , + { + "category": "Day", + "token": "d", + "description": I18n.tr("widgets.datetime-tokens.day-no-leading-zero"), + "example": "25" + }, + { + "category": "Day", + "token": "dd", + "description": I18n.tr("widgets.datetime-tokens.day-leading-zero"), + "example": "25" + }, + { + "category": "Day", + "token": "ddd", + "description": I18n.tr("widgets.datetime-tokens.day-abbreviated"), + "example": "Mon" + }, + { + "category": "Day", + "token": "dddd", + "description": I18n.tr("widgets.datetime-tokens.day-full"), + "example": "Monday" + } + ] + + delegate: Rectangle { + id: tokenDelegate + width: tokensColumn.width + height: layout.implicitHeight + Style.marginS + radius: Style.iRadiusS + color: { + if (tokenMouseArea.containsMouse) { + return Qt.alpha(Color.mPrimary, 0.1); + } + return index % 2 === 0 ? Color.mSurfaceVariant : Qt.alpha(Color.mSurfaceVariant, 0.6); + } + + // Mouse area for the entire delegate + MouseArea { + id: tokenMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + onClicked: { + root.tokenClicked(modelData.token); + clickAnimation.start(); + } + } + + // Click animation + SequentialAnimation { + id: clickAnimation + PropertyAnimation { + target: tokenDelegate + property: "color" + to: Qt.alpha(Color.mPrimary, 0.3) + duration: 100 + } + PropertyAnimation { + target: tokenDelegate + property: "color" + to: tokenMouseArea.containsMouse ? Qt.alpha(Color.mPrimary, 0.1) : (index % 2 === 0 ? Color.mSurface : Color.mSurfaceVariant) + duration: 200 + } + } + + RowLayout { + id: layout + anchors.fill: parent + anchors.margins: Style.marginXS + spacing: Style.marginM + + // Category badge + Rectangle { + Layout.alignment: Qt.AlignVCenter + width: 70 + height: 22 + color: getCategoryColor(modelData.category)[0] + radius: Style.iRadiusS + opacity: tokenMouseArea.containsMouse ? 0.9 : 1.0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + + NText { + anchors.centerIn: parent + text: modelData.category + color: getCategoryColor(modelData.category)[1] + pointSize: Style.fontSizeXS + } + } + + // Token - Made more prominent and clickable + Rectangle { + id: tokenButton + Layout.alignment: Qt.AlignVCenter // Added this line + width: 100 + height: 22 + color: tokenMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurface + radius: Style.iRadiusS + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + NText { + anchors.centerIn: parent + text: modelData.token + color: tokenMouseArea.containsMouse ? Color.mOnPrimary : Color.mSurface + pointSize: Style.fontSizeS + font.weight: Style.fontWeightBold + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + } + + // Description + NText { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter // Added this line + text: modelData.description + color: tokenMouseArea.containsMouse ? Color.mOnSurface : Color.mOnSurfaceVariant + pointSize: Style.fontSizeS + wrapMode: Text.WordWrap + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + // Live example + Rectangle { + Layout.alignment: Qt.AlignVCenter // Added this line + width: 90 + height: 22 + color: tokenMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurfaceVariant + radius: Style.iRadiusS + border.color: tokenMouseArea.containsMouse ? Color.mPrimary : Color.mOutline + border.width: Style.borderS + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + + NText { + anchors.centerIn: parent + text: I18n.locale.toString(root.sampleDate, modelData.token) + color: tokenMouseArea.containsMouse ? Color.mOnPrimary : Color.mSurfaceVariant + pointSize: Style.fontSizeS + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + } + } + } + } + } + } + } + + function getCategoryColor(category) { + switch (category) { + case "Year": + return [Color.mPrimary, Color.mOnPrimary]; + case "Month": + return [Color.mSecondary, Color.mOnSecondary]; + case "Day": + return [Color.mTertiary, Color.mOnTertiary]; + case "Hour": + return [Color.mPrimary, Color.mOnPrimary]; + case "Minute": + return [Color.mSecondary, Color.mOnSecondary]; + case "Second": + return [Color.mTertiary, Color.mOnTertiary]; + case "AM/PM": + return [Color.mError, Color.mOnError]; + case "Timezone": + return [Color.mOnSurface, Color.mSurface]; + case "Common": + return [Color.mError, Color.mOnError]; + default: + return [Color.mOnSurfaceVariant, Color.mSurfaceVariant]; + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NDivider.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NDivider.qml new file mode 100644 index 0000000..29753ca --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NDivider.qml @@ -0,0 +1,30 @@ +import QtQuick +import Quickshell +import Quickshell.Widgets +import qs.Commons + +Rectangle { + property bool vertical: false + + width: vertical ? Style.borderS : parent.width + height: vertical ? parent.height : Style.borderS + gradient: Gradient { + orientation: vertical ? Gradient.Vertical : Gradient.Horizontal + GradientStop { + position: 0.0 + color: "transparent" + } + GradientStop { + position: 0.1 + color: Color.mOutline + } + GradientStop { + position: 0.9 + color: Color.mOutline + } + GradientStop { + position: 1.0 + color: "transparent" + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NDropShadow.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NDropShadow.qml new file mode 100644 index 0000000..0a223f4 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NDropShadow.qml @@ -0,0 +1,31 @@ +import QtQuick +import QtQuick.Effects +import qs.Commons +import qs.Services.Power + +// Unified shadow system +Item { + id: root + + required property var source + + property bool autoPaddingEnabled: false + property real shadowHorizontalOffset: Settings.data.general.shadowOffsetX + property real shadowVerticalOffset: Settings.data.general.shadowOffsetY + property real shadowOpacity: Style.shadowOpacity + property color shadowColor: "black" + property real shadowBlur: Style.shadowBlur + + layer.enabled: Settings.data.general.enableShadows && !PowerProfileService.noctaliaPerformanceMode + layer.effect: MultiEffect { + source: root.source + shadowEnabled: true + blurMax: Style.shadowBlurMax + shadowBlur: root.shadowBlur + shadowOpacity: root.shadowOpacity + shadowColor: root.shadowColor + shadowHorizontalOffset: root.shadowHorizontalOffset + shadowVerticalOffset: root.shadowVerticalOffset + autoPaddingEnabled: root.autoPaddingEnabled + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NFilePicker.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NFilePicker.qml new file mode 100644 index 0000000..84395c1 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NFilePicker.qml @@ -0,0 +1,817 @@ +import Qt.labs.folderlistmodel +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Widgets + +Popup { + id: root + + // Properties + property string title: I18n.tr("widget.file-picker.title") + property string initialPath: Quickshell.env("HOME") || "/home" + property string selectionMode: "files" // "files" or "folders" + property var nameFilters: ["*"] + property bool showDirs: true + property bool showHiddenFiles: false + property bool allowMultiSelection: false + + property var selectedPaths: [] + property string currentPath: initialPath + property bool shouldResetSelection: false + + // Signals + signal accepted(var paths) + signal cancelled + + function openFilePicker() { + if (!root.currentPath) + root.currentPath = root.initialPath; + shouldResetSelection = true; + open(); + } + + function getFileIcon(fileName) { + const ext = fileName.split('.').pop().toLowerCase(); + const iconMap = { + "txt": 'filepicker-file-text', + "md": 'filepicker-file-text', + "log": 'filepicker-file-text', + "jpg": 'filepicker-photo', + "jpeg": 'filepicker-photo', + "png": 'filepicker-photo', + "gif": 'filepicker-photo', + "bmp": 'filepicker-photo', + "svg": 'filepicker-photo', + "mp4": 'filepicker-video', + "avi": 'filepicker-video', + "mkv": 'filepicker-video', + "mov": 'filepicker-video', + "mp3": 'filepicker-music', + "wav": 'filepicker-music', + "flac": 'filepicker-music', + "ogg": 'filepicker-music', + "zip": 'filepicker-archive', + "tar": 'filepicker-archive', + "gz": 'filepicker-archive', + "rar": 'filepicker-archive', + "7z": 'filepicker-archive', + "pdf": 'filepicker-text', + "doc": 'filepicker-text', + "docx": 'filepicker-text', + "xls": 'filepicker-table', + "xlsx": 'filepicker-table', + "ppt": 'filepicker-presentation', + "pptx": 'filepicker-presentation', + "html": 'filepicker-code', + "htm": 'filepicker-code', + "css": 'filepicker-code', + "js": 'filepicker-code', + "json": 'filepicker-code', + "xml": 'filepicker-code', + "exe": 'filepicker-settings', + "app": 'filepicker-settings', + "deb": 'filepicker-settings', + "rpm": 'filepicker-settings' + }; + return iconMap[ext] || 'filepicker-file'; + } + + function formatFileSize(bytes) { + if (bytes === 0) + return "0 B"; + const k = 1024, sizes = ["B", "KB", "MB", "GB", "TB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i]; + } + + function confirmSelection() { + if (filePickerPanel.currentSelection.length === 0) + return; + root.selectedPaths = filePickerPanel.currentSelection; + root.accepted(filePickerPanel.currentSelection); + root.close(); + } + + function updateFilteredModel() { + filteredModel.clear(); + const searchText = filePickerPanel.filterText.toLowerCase(); + + for (var i = 0; i < folderModel.count; i++) { + const fileName = folderModel.get(i, "fileName"); + const filePath = folderModel.get(i, "filePath"); + const fileIsDir = folderModel.get(i, "fileIsDir"); + const fileSize = folderModel.get(i, "fileSize"); + + // Skip hidden items if showHiddenFiles is false + // This additional check ensures hidden files are properly filtered + if (!root.showHiddenFiles && fileName.startsWith(".")) { + continue; + } + + // In folder mode, hide files + if (root.selectionMode === "folders" && !fileIsDir) + continue; + if (searchText === "" || fileName.toLowerCase().includes(searchText)) { + filteredModel.append({ + "fileName": fileName, + "filePath": filePath, + "fileIsDir": fileIsDir, + "fileSize": fileSize + }); + } + } + } + + width: 900 + height: 700 + modal: true + closePolicy: Popup.CloseOnEscape + anchors.centerIn: Overlay.overlay + + background: Rectangle { + color: Color.mSurfaceVariant + radius: Style.iRadiusL + border.color: Color.mOutline + border.width: Style.borderS + } + + Rectangle { + id: filePickerPanel + anchors.fill: parent + anchors.margins: Style.marginL + color: "transparent" + + property string filterText: "" + property var currentSelection: [] + property bool viewMode: true // true = grid, false = list + property string searchText: "" + property bool showSearchBar: false + + focus: true + + Keys.onPressed: event => { + if (event.modifiers & Qt.ControlModifier && event.key === Qt.Key_F) { + filePickerPanel.showSearchBar = !filePickerPanel.showSearchBar; + if (filePickerPanel.showSearchBar) + Qt.callLater(() => searchInput.forceActiveFocus()); + event.accepted = true; + } else if (event.key === Qt.Key_Escape && filePickerPanel.showSearchBar) { + filePickerPanel.showSearchBar = false; + filePickerPanel.searchText = ""; + filePickerPanel.filterText = ""; + root.updateFilteredModel(); + event.accepted = true; + } + } + + // Function when an item is clicked, either a folder or a file since both have the same functionality, reduces repetitive code + function itemClicked(modifiers, path) { + var list = currentSelection.slice(); + const index = list.indexOf(path); + if (root.allowMultiSelection && (modifiers & Qt.ShiftModifier) && list.length > 0) { + if (index > -1) { + list.splice(index, 1); + currentSelection = list; + } else { + var i = 0; + var toggle = false; + const firstItemPath = list[0]; + while (i < filteredModel.count) { + const itemPath = filteredModel.get(i).filePath; + + // This should be called twice, when we get to the item selected and when we get to the starting item. + if (itemPath === firstItemPath || itemPath === path) { + toggle = !toggle; + } + + // Add all files between the starting item and the item selected. + if (toggle) { + if (!list.includes(itemPath)) { + list.push(itemPath); + } + } + + i++; + } + + // Add the path selected as well since it's skipped in the while loop + if (!list.includes(path)) { + list.push(path); + } + currentSelection = list; + } + } else if (root.allowMultiSelection && (modifiers & Qt.ControlModifier)) { + if (index > -1) { + list.splice(index, 1); + currentSelection = list; + } else { + list.push(path); + currentSelection = list; + } + } else { + currentSelection = [path]; + } + } + + ColumnLayout { + anchors.fill: parent + spacing: Style.marginM + + // Header + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NIcon { + icon: "filepicker-folder" + color: Color.mPrimary + pointSize: Style.fontSizeXXL + } + NText { + text: root.title + pointSize: Style.fontSizeXL + font.weight: Style.fontWeightBold + color: Color.mPrimary + Layout.fillWidth: true + } + + // "Select Current" button only visible in folder selection mode + NButton { + text: I18n.tr("widgets.file-picker.select-current") + icon: "filepicker-folder-current" + visible: root.selectionMode === "folders" + onClicked: { + filePickerPanel.currentSelection = [root.currentPath]; + root.confirmSelection(); + } + } + + NIconButton { + icon: "filepicker-refresh" + tooltipText: I18n.tr("common.refresh") + onClicked: { + // Force a proper refresh by resetting the folder + const currentFolder = folderModel.folder; + folderModel.folder = ""; + folderModel.folder = currentFolder; + Qt.callLater(root.updateFilteredModel); + } + } + NIconButton { + icon: "filepicker-close" + tooltipText: I18n.tr("common.close") + onClicked: { + root.cancelled(); + root.close(); + } + } + } + + NDivider { + Layout.fillWidth: true + } + + // Navigation toolbar + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 45 + color: Color.mSurfaceVariant + radius: Style.iRadiusS + border.color: Color.mOutline + border.width: Style.borderS + + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: Style.marginS + anchors.rightMargin: Style.marginS + spacing: Style.marginS + + NIconButton { + icon: "filepicker-arrow-up" + tooltipText: I18n.tr("tooltips.up") + baseSize: Style.baseWidgetSize * 0.8 + enabled: folderModel.folder.toString() !== "file:///" + onClicked: { + const parentPath = folderModel.parentFolder.toString().replace("file://", ""); + folderModel.folder = "file://" + parentPath; + root.currentPath = parentPath; + } + } + + NIconButton { + icon: "filepicker-home" + tooltipText: I18n.tr("tooltips.home") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: { + const homePath = Quickshell.env("HOME") || "/home"; + folderModel.folder = "file://" + homePath; + root.currentPath = homePath; + } + } + + NIconButton { + icon: filePickerPanel.showSearchBar ? "filepicker-x" : "filepicker-search" + tooltipText: filePickerPanel.showSearchBar ? I18n.tr("tooltips.search-close") : I18n.tr("common.search") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: { + filePickerPanel.showSearchBar = !filePickerPanel.showSearchBar; + if (!filePickerPanel.showSearchBar) { + filePickerPanel.searchText = ""; + filePickerPanel.filterText = ""; + root.updateFilteredModel(); + } + } + } + + NTextInput { + id: locationInput + text: root.currentPath + placeholderText: I18n.tr("placeholders.enter-path") + Layout.fillWidth: true + + visible: !filePickerPanel.showSearchBar + enabled: !filePickerPanel.showSearchBar + + onEditingFinished: { + const newPath = text.trim(); + if (newPath !== "" && newPath !== root.currentPath) { + folderModel.folder = "file://" + newPath; + root.currentPath = newPath; + } else { + text = root.currentPath; + } + } + Connections { + target: root + function onCurrentPathChanged() { + if (!locationInput.activeFocus) + locationInput.text = root.currentPath; + } + } + } + + // Search bar + NTextInput { + id: searchInput + inputIconName: "search" + placeholderText: I18n.tr("placeholders.search") + Layout.fillWidth: true + + visible: filePickerPanel.showSearchBar + enabled: filePickerPanel.showSearchBar + + text: filePickerPanel.searchText + onTextChanged: { + filePickerPanel.searchText = text; + filePickerPanel.filterText = text; + root.updateFilteredModel(); + } + Keys.onEscapePressed: { + filePickerPanel.showSearchBar = false; + filePickerPanel.searchText = ""; + filePickerPanel.filterText = ""; + root.updateFilteredModel(); + } + } + + NIconButton { + icon: filePickerPanel.viewMode ? "filepicker-list" : "filepicker-layout-grid" + tooltipText: filePickerPanel.viewMode ? I18n.tr("tooltips.list-view") : I18n.tr("tooltips.grid-view") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: filePickerPanel.viewMode = !filePickerPanel.viewMode + } + NIconButton { + icon: root.showHiddenFiles ? "filepicker-eye-off" : "filepicker-eye" + tooltipText: root.showHiddenFiles ? I18n.tr("tooltips.hidden-files-hide") : I18n.tr("tooltips.hidden-files-hide") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: { + root.showHiddenFiles = !root.showHiddenFiles; + // Force model refresh by resetting the folder + const currentFolder = folderModel.folder; + folderModel.folder = ""; + folderModel.folder = currentFolder; + Qt.callLater(root.updateFilteredModel); + } + } + } + } + + // File list area + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + color: Color.mSurface + radius: Style.iRadiusM + border.color: Color.mOutline + border.width: Style.borderS + + FolderListModel { + id: folderModel + folder: "file://" + root.currentPath + // Use wildcard filters including hidden files when showHiddenFiles is true + nameFilters: root.showHiddenFiles ? ["*", ".*"] : root.nameFilters + showDirs: root.showDirs + showHidden: true // Always true, we'll filter in updateFilteredModel + showDotAndDotDot: false + sortField: FolderListModel.Name + sortReversed: false + + onFolderChanged: { + root.currentPath = folder.toString().replace("file://", ""); + filePickerPanel.currentSelection = []; + Qt.callLater(root.updateFilteredModel); + } + + onStatusChanged: { + if (status === FolderListModel.Error) { + if (root.currentPath !== Quickshell.env("HOME")) { + folder = "file://" + Quickshell.env("HOME"); + root.currentPath = Quickshell.env("HOME"); + } + } else if (status === FolderListModel.Ready) { + root.updateFilteredModel(); + } + } + } + + // Update nameFilters when showHiddenFiles changes + Connections { + target: root + function onShowHiddenFilesChanged() { + folderModel.nameFilters = root.showHiddenFiles ? ["*", ".*"] : root.nameFilters; + } + } + + ListModel { + id: filteredModel + } + + // Grid view + NGridView { + id: gridView + anchors.fill: parent + anchors.margins: Style.marginM + model: filteredModel + visible: filePickerPanel.viewMode + reuseItems: true + gradientColor: Color.mSurface + + property int columns: Math.max(1, Math.floor(availableWidth / 120)) + property int itemSize: Math.floor((availableWidth - leftMargin - rightMargin - (columns * Style.marginS)) / columns) + + cellWidth: Math.floor((availableWidth - leftMargin - rightMargin) / columns) + cellHeight: Math.floor(itemSize * 0.8) + Style.marginXS + Style.fontSizeS + Style.marginM + + leftMargin: Style.marginS + rightMargin: Style.marginS + topMargin: Style.marginS + bottomMargin: Style.marginS + + delegate: Rectangle { + id: gridItem + width: gridView.itemSize + height: gridView.cellHeight + color: "transparent" + radius: Style.iRadiusM + + property bool isSelected: filePickerPanel.currentSelection.includes(model.filePath) + + Rectangle { + anchors.fill: parent + color: "transparent" + radius: parent.radius + border.color: isSelected ? Color.mSecondary : Color.mSurface + border.width: Style.borderL + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + Rectangle { + anchors.fill: parent + color: (mouseArea.containsMouse && !isSelected) ? Color.mHover : "transparent" + radius: parent.radius + border.color: (mouseArea.containsMouse && !isSelected) ? Color.mHover : "transparent" + border.width: Style.borderS + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginS + spacing: Style.marginXS + + Rectangle { + id: iconContainer + Layout.fillWidth: true + Layout.preferredHeight: Math.round(gridView.itemSize * 0.67) + color: "transparent" + + property bool isImage: { + if (model.fileIsDir) + return false; + const ext = model.fileName.split('.').pop().toLowerCase(); + return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico'].includes(ext); + } + + Image { + id: thumbnail + anchors.fill: parent + anchors.margins: Style.marginXS + source: iconContainer.isImage ? "file://" + model.filePath : "" + fillMode: Image.PreserveAspectFit + visible: iconContainer.isImage && status === Image.Ready + smooth: false + cache: true + asynchronous: true + sourceSize.width: 120 + sourceSize.height: 120 + onStatusChanged: { + if (status === Image.Error) + visible = false; + } + + Rectangle { + anchors.fill: parent + color: Color.mSurfaceVariant + radius: Style.iRadiusS + visible: thumbnail.status === Image.Loading + NIcon { + icon: "filepicker-photo" + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + anchors.centerIn: parent + } + } + } + + NIcon { + icon: model.fileIsDir ? "filepicker-folder" : root.getFileIcon(model.fileName) + pointSize: Style.fontSizeXXL * 2 + color: { + if (isSelected) + return Color.mSecondary; + else if (mouseArea.containsMouse) + return Color.mOnHover; + else + return model.fileIsDir ? Color.mPrimary : Color.mOnSurfaceVariant; + } + anchors.centerIn: parent + visible: !iconContainer.isImage || thumbnail.status !== Image.Ready + } + + Rectangle { + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: Style.marginS + width: 24 + height: 24 + radius: Math.min(Style.iRadiusL, width / 2) + color: Color.mSecondary + border.color: Color.mOutline + border.width: Style.borderS + visible: isSelected + NIcon { + icon: "filepicker-check" + pointSize: Style.fontSizeS + color: Color.mOnSecondary + anchors.centerIn: parent + } + } + } + + NText { + text: model.fileName + color: { + if (isSelected) + return Color.mSecondary; + else if (mouseArea.containsMouse) + return Color.mOnHover; + else + return Color.mOnSurfaceVariant; + } + pointSize: Style.fontSizeS + font.weight: isSelected ? Style.fontWeightBold : Style.fontWeightRegular + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WrapAnywhere + elide: Text.ElideRight + maximumLineCount: 2 + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onClicked: mouse => { + if (mouse.button === Qt.LeftButton) { + if (model.fileIsDir) { + // In folder mode, single click selects the folder + if (root.selectionMode === "folders") { + filePickerPanel.itemClicked(mouse.modifiers, model.filePath); + } + // In file mode, single click on folder does nothing (must double-click to enter) + } else { + // Single click on file selects it (only in file mode) + if (root.selectionMode === "files") { + filePickerPanel.itemClicked(mouse.modifiers, model.filePath); + } + } + } + } + + onDoubleClicked: mouse => { + if (mouse.button === Qt.LeftButton) { + if (model.fileIsDir) { + // Double-click on folder always navigates into it + folderModel.folder = "file://" + model.filePath; + root.currentPath = model.filePath; + } else { + // Double-click on file selects and confirms (only in file mode) + if (root.selectionMode === "files") { + filePickerPanel.currentSelection = [model.filePath]; + root.confirmSelection(); + } + } + } + } + } + } + } + + // List view + NListView { + id: listView + anchors.fill: parent + anchors.margins: Style.marginS + model: filteredModel + visible: !filePickerPanel.viewMode + gradientColor: Color.mSurface + + delegate: Rectangle { + id: listItem + width: listView.width + height: 40 + color: { + if (filePickerPanel.currentSelection.includes(model.filePath)) + return Color.mSecondary; + if (mouseArea.containsMouse) + return Color.mHover; + return "transparent"; + } + radius: Style.iRadiusS + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + RowLayout { + anchors.fill: parent + anchors.leftMargin: Style.marginM + anchors.rightMargin: Style.marginM + spacing: Style.marginM + + NIcon { + icon: model.fileIsDir ? "filepicker-folder" : root.getFileIcon(model.fileName) + pointSize: Style.fontSizeL + color: model.fileIsDir ? (filePickerPanel.currentSelection.includes(model.filePath) ? Color.mOnSecondary : Color.mPrimary) : Color.mOnSurfaceVariant + } + + NText { + text: model.fileName + color: filePickerPanel.currentSelection.includes(model.filePath) ? Color.mOnSecondary : Color.mOnSurface + pointSize: Style.fontSizeM + font.weight: filePickerPanel.currentSelection.includes(model.filePath) ? Style.fontWeightBold : Style.fontWeightRegular + Layout.fillWidth: true + elide: Text.ElideRight + } + + NText { + text: model.fileIsDir ? "" : root.formatFileSize(model.fileSize) + color: filePickerPanel.currentSelection.includes(model.filePath) ? Color.mOnSecondary : Color.mOnSurfaceVariant + pointSize: Style.fontSizeS + visible: !model.fileIsDir + Layout.preferredWidth: implicitWidth + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onClicked: mouse => { + if (mouse.button === Qt.LeftButton) { + if (model.fileIsDir) { + // In folder mode, single click selects the folder + if (root.selectionMode === "folders") { + filePickerPanel.itemClicked(mouse.modifiers, model.filePath); + } + // In file mode, single click on folder does nothing (must double-click to enter) + } else { + // Single click on file selects it (only in file mode) + if (root.selectionMode === "files") { + filePickerPanel.itemClicked(mouse.modifiers, model.filePath); + } + } + } + } + + onDoubleClicked: mouse => { + if (mouse.button === Qt.LeftButton) { + if (model.fileIsDir) { + // Double-click on folder always navigates into it + folderModel.folder = "file://" + model.filePath; + root.currentPath = model.filePath; + } else { + // Double-click on file selects and confirms (only in file mode) + if (root.selectionMode === "files") { + filePickerPanel.currentSelection = [model.filePath]; + root.confirmSelection(); + } + } + } + } + } + } + } + } + + // Footer + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NText { + text: { + if (filePickerPanel.searchText.length > 0) { + return "Searching for: \"" + filePickerPanel.searchText + "\" (" + filteredModel.count + " matches)"; + } else if (filePickerPanel.currentSelection.length > 0) { + const selectedName = filePickerPanel.currentSelection[0].split('/').pop(); + return I18n.tr("widgets.file-picker.selected") + " " + selectedName; + } else { + return filteredModel.count + " " + (filteredModel.count === 1 ? I18n.tr("widgets.file-picker.item") : I18n.tr("widgets.file-picker.items")); + } + } + color: filePickerPanel.searchText.length > 0 ? Color.mPrimary : Color.mOnSurfaceVariant + pointSize: Style.fontSizeS + Layout.fillWidth: true + } + + NButton { + text: I18n.tr("common.cancel") + outlined: true + onClicked: { + root.cancelled(); + root.close(); + } + } + + NButton { + text: root.selectionMode === "folders" ? I18n.tr("widgets.file-picker.select-folder") : I18n.tr("widgets.file-picker.select-file") + icon: "filepicker-check" + enabled: filePickerPanel.currentSelection.length > 0 + onClicked: root.confirmSelection() + } + } + } + + Connections { + target: root + function onShouldResetSelectionChanged() { + if (root.shouldResetSelection) { + filePickerPanel.currentSelection = []; + root.shouldResetSelection = false; + } + } + } + + Component.onCompleted: { + if (!root.currentPath) + root.currentPath = root.initialPath; + folderModel.folder = "file://" + root.currentPath; + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NGraph.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NGraph.qml new file mode 100644 index 0000000..839229d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NGraph.qml @@ -0,0 +1,234 @@ +import QtQuick +import Quickshell +import qs.Commons + +Item { + id: root + clip: true + + // Primary line + property var values: [] + property color color: Color.mPrimary + + // Optional secondary line + property var values2: [] + property color color2: Color.mError + + // Range settings for primary line + property real minValue: 0 + property real maxValue: 100 + + // Range settings for secondary line (defaults to primary range) + property real minValue2: minValue + property real maxValue2: maxValue + + // Style settings + property real strokeWidth: 1 + property bool fill: true + property real fillOpacity: 0.15 + property real antialiasing: 0.5 + + // Smooth scrolling interval (how often data updates) + property int updateInterval: 1000 + + // Animate scale changes (for network graphs with dynamic max) + property bool animateScale: false + + // Vertical padding (percentage of range) to keep values from touching edges + readonly property real curvePadding: 0.12 + + readonly property bool hasData: values.length >= 4 + readonly property bool hasData2: values2.length >= 4 + + // Scale animation state + property real _targetMax1: maxValue + property real _targetMax2: maxValue2 + property real _animMax1: maxValue + property real _animMax2: maxValue2 + + onMaxValueChanged: { + _targetMax1 = maxValue; + if (animateScale && _ready1) { + _scaleTimer.start(); + } else { + _animMax1 = maxValue; + } + } + + onMaxValue2Changed: { + _targetMax2 = maxValue2; + if (animateScale && _ready2) { + _scaleTimer.start(); + } else { + _animMax2 = maxValue2; + } + } + + // Effective max values (animated or direct) + readonly property real _effectiveMax1: animateScale ? _animMax1 : maxValue + readonly property real _effectiveMax2: animateScale ? _animMax2 : maxValue2 + + // Scroll state (driven by NumberAnimation) + property real _t1: 1.0 + property bool _ready1: false + property real _pred1: 0 + + property real _t2: 1.0 + property bool _ready2: false + property real _pred2: 0 + + // Frame-accurate scroll animations tied to Qt's render loop + NumberAnimation { + id: _scrollAnim1 + target: root + property: "_t1" + from: 0 + to: 1 + duration: root.updateInterval + } + + NumberAnimation { + id: _scrollAnim2 + target: root + property: "_t2" + from: 0 + to: 1 + duration: root.updateInterval + } + + onValuesChanged: { + if (values.length < 4) + return; + + const last = values[values.length - 1]; + const prev = values[values.length - 2]; + _pred1 = Math.max(minValue, last + (last - prev) * 0.5); + + if (!_ready1) + _ready1 = true; + _scrollAnim1.restart(); + } + + onValues2Changed: { + if (values2.length < 4) + return; + + const last = values2[values2.length - 1]; + const prev = values2[values2.length - 2]; + _pred2 = Math.max(minValue2, last + (last - prev) * 0.5); + + if (!_ready2) + _ready2 = true; + _scrollAnim2.restart(); + } + + // Scale animation timer (only needed for animateScale mode) + Timer { + id: _scaleTimer + interval: 16 + repeat: true + + onTriggered: { + const scaleLerp = 0.15; + const threshold = 0.5; + let stillAnimating = false; + + if (Math.abs(root._animMax1 - root._targetMax1) > threshold) { + root._animMax1 += (root._targetMax1 - root._animMax1) * scaleLerp; + stillAnimating = true; + } else if (root._animMax1 !== root._targetMax1) { + root._animMax1 = root._targetMax1; + } + + if (Math.abs(root._animMax2 - root._targetMax2) > threshold) { + root._animMax2 += (root._targetMax2 - root._animMax2) * scaleLerp; + stillAnimating = true; + } else if (root._animMax2 !== root._targetMax2) { + root._animMax2 = root._targetMax2; + } + + if (!stillAnimating) + stop(); + } + } + + // Normalize a value to [0, 1] with padding applied + function _normalize(val, minVal, maxVal) { + let range = maxVal - minVal; + if (range <= 0) + return 0.5; + let padding = range * curvePadding; + let paddedMin = minVal - padding; + let paddedRange = (maxVal + padding) - paddedMin; + return Math.max(0, Math.min(1, (val - paddedMin) / paddedRange)); + } + + // Data texture built from Rectangles instead of Canvas. + // Each Rectangle is one data point, color-coded with normalized values. + // R channel = primary, G channel = secondary. + Item { + id: _dataRow + width: Math.max(root.values.length + 1, root.values2.length + 1, 4) + height: 1 + + Repeater { + model: _dataRow.width + + Rectangle { + required property int index + x: index + width: 1 + height: 1 + color: { + let r = 0, g = 0; + let n1 = root.values.length; + let n2 = root.values2.length; + let eMax1 = root._effectiveMax1; + let eMax2 = root._effectiveMax2; + + if (index < n1) + r = root._normalize(root.values[index], root.minValue, eMax1); + else if (n1 > 0) + r = root._normalize(root._pred1, root.minValue, eMax1); + + if (index < n2) + g = root._normalize(root.values2[index], root.minValue2, eMax2); + else if (n2 > 0) + g = root._normalize(root._pred2, root.minValue2, eMax2); + + return Qt.rgba(r, g, 0, 1); + } + } + } + } + + ShaderEffectSource { + id: _dataTex + sourceItem: _dataRow + textureSize: Qt.size(_dataRow.width, 1) + live: true + smooth: false + hideSource: true + } + + ShaderEffect { + anchors.fill: parent + visible: (root.hasData || root.hasData2) && width > 0 && height > 0 + + property variant dataSource: _dataTex + property color lineColor1: root.color + property color lineColor2: root.color2 + property real count1: root.values.length + property real count2: root.values2.length + property real scroll1: root._t1 + property real scroll2: root._t2 + property real lineWidth: root.strokeWidth + property real graphFillOpacity: root.fill ? root.fillOpacity : 0.0 + property real texWidth: _dataRow.width + property real resY: height + property real aaSize: root.antialiasing + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/graph.frag.qsb") + blending: true + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NGridView.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NGridView.qml new file mode 100644 index 0000000..b436281 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NGridView.qml @@ -0,0 +1,430 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Templates as T +import qs.Commons + +Item { + id: root + + // Signal for key press events when keyNavigationEnabled is true + signal keyPressed(var event) + + property color handleColor: Qt.alpha(Color.mHover, 0.8) + property color handleHoverColor: handleColor + property color handlePressedColor: handleColor + property color trackColor: "transparent" + property real handleWidth: 6 + property real handleRadius: Style.iRadiusM + property int verticalPolicy: ScrollBar.AsNeeded + property int horizontalPolicy: ScrollBar.AlwaysOff + readonly property bool verticalScrollBarActive: { + if (gridView.ScrollBar.vertical.policy === ScrollBar.AlwaysOff) + return false; + return gridView.contentHeight > gridView.height; + } + readonly property bool contentOverflows: gridView.contentHeight > gridView.height + + // Gradient properties + property bool showGradientMasks: true + property color gradientColor: Color.mSurfaceVariant + property int gradientHeight: 16 + property bool reserveScrollbarSpace: true + + // Keep scrollbars visible whenever overflow exists (without forcing visibility when not scrollable) + property bool showScrollbarWhenScrollable: Settings.data.ui.scrollbarAlwaysVisible + + // Available width for content (excludes scrollbar space when reserveScrollbarSpace is true) + // Note: Always reserves space when enabled to avoid binding loops with cellWidth calculations + readonly property real availableWidth: width - (reserveScrollbarSpace ? handleWidth + Style.marginXS : 0) + + // Expose activeFocus from internal gridView + readonly property bool hasActiveFocus: gridView.activeFocus + + // Forward GridView properties + property alias model: gridView.model + property alias delegate: gridView.delegate + property alias cellWidth: gridView.cellWidth + property alias cellHeight: gridView.cellHeight + property alias leftMargin: gridView.leftMargin + property alias rightMargin: gridView.rightMargin + property alias topMargin: gridView.topMargin + property alias bottomMargin: gridView.bottomMargin + property alias currentIndex: gridView.currentIndex + property alias count: gridView.count + property alias contentHeight: gridView.contentHeight + property alias contentWidth: gridView.contentWidth + property alias contentY: gridView.contentY + property alias contentX: gridView.contentX + property alias currentItem: gridView.currentItem + property alias highlightItem: gridView.highlightItem + property alias highlightFollowsCurrentItem: gridView.highlightFollowsCurrentItem + property alias preferredHighlightBegin: gridView.preferredHighlightBegin + property alias preferredHighlightEnd: gridView.preferredHighlightEnd + property alias highlightRangeMode: gridView.highlightRangeMode + property alias snapMode: gridView.snapMode + property alias keyNavigationEnabled: gridView.keyNavigationEnabled + property alias keyNavigationWraps: gridView.keyNavigationWraps + property alias cacheBuffer: gridView.cacheBuffer + property alias displayMarginBeginning: gridView.displayMarginBeginning + property alias displayMarginEnd: gridView.displayMarginEnd + property alias layoutDirection: gridView.layoutDirection + property alias effectiveLayoutDirection: gridView.effectiveLayoutDirection + property alias flow: gridView.flow + property alias boundsBehavior: gridView.boundsBehavior + property alias flickableDirection: gridView.flickableDirection + property alias interactive: gridView.interactive + property alias moving: gridView.moving + property alias flicking: gridView.flicking + property alias dragging: gridView.dragging + property alias horizontalVelocity: gridView.horizontalVelocity + property alias verticalVelocity: gridView.verticalVelocity + property alias reuseItems: gridView.reuseItems + + // Animate items when the model is reordered (e.g. ListModel.move()) + property bool animateMovement: false + + // Scroll speed multiplier for mouse wheel (1.0 = default, higher = faster) + property real wheelScrollMultiplier: 2.0 + property int smoothWheelAnimationDuration: Style.animationNormal + property real _wheelTargetY: 0 + + function clampScrollY(value) { + return Math.max(0, Math.min(value, gridView.contentHeight - gridView.height)); + } + + function applyWheelScroll(delta) { + if (!root.contentOverflows) + return; + + const step = delta * root.wheelScrollMultiplier; + + if (!Settings.data.general.smoothScrollEnabled || Settings.data.general.animationDisabled) { + gridView.contentY = root.clampScrollY(gridView.contentY - step); + root._wheelTargetY = gridView.contentY; + return; + } + + if (!wheelScrollAnimation.running) + root._wheelTargetY = gridView.contentY; + + root._wheelTargetY = root.clampScrollY(root._wheelTargetY - step); + wheelScrollAnimation.to = root._wheelTargetY; + wheelScrollAnimation.restart(); + } + + function animateToContentY(targetY) { + const clampedY = root.clampScrollY(targetY); + + if (!Settings.data.general.smoothScrollEnabled || Settings.data.general.animationDisabled || gridView.dragging || gridView.flicking) { + gridView.contentY = clampedY; + root._wheelTargetY = clampedY; + return; + } + + root._wheelTargetY = clampedY; + wheelScrollAnimation.to = clampedY; + wheelScrollAnimation.restart(); + } + + // Track selection index for gradient visibility (set externally) + property int trackedSelectionIndex: -1 + + // Check if selection is on first visible row + readonly property bool selectionOnFirstVisibleRow: { + if (trackedSelectionIndex < 0 || cellHeight <= 0 || cellWidth <= 0) + return false; + + // Calculate columns per row + var cols = Math.floor(gridView.width / cellWidth); + if (cols <= 0) + cols = 1; + + // Calculate the row of the selection + var selectionRow = Math.round(trackedSelectionIndex / cols); + + // Calculate the first visible row + var firstVisibleRow = Math.round(gridView.contentY / cellHeight); + + return selectionRow === firstVisibleRow; + } + + // Check if selection is on last visible row + readonly property bool selectionOnLastVisibleRow: { + if (trackedSelectionIndex < 0 || cellHeight <= 0 || cellWidth <= 0) + return false; + + // Calculate columns per row + var cols = Math.floor(gridView.width / cellWidth); + if (cols <= 0) + cols = 1; + + // Calculate the row of the selection + var selectionRow = Math.round(trackedSelectionIndex / cols); + + // Calculate the last visible row (might be partially visible) + var lastVisibleRow = Math.round((gridView.contentY + gridView.height - 1) / cellHeight); + + return selectionRow === lastVisibleRow; + } + + // Forward GridView methods + function positionViewAtIndex(index, mode) { + const shouldAnimate = mode === GridView.Contain; + if (!shouldAnimate) { + gridView.positionViewAtIndex(index, mode); + root._wheelTargetY = gridView.contentY; + return; + } + + const previousY = gridView.contentY; + gridView.positionViewAtIndex(index, mode); + const targetY = root.clampScrollY(gridView.contentY); + + if (Math.abs(targetY - previousY) < 0.5) { + root._wheelTargetY = targetY; + return; + } + + gridView.contentY = previousY; + root._wheelTargetY = previousY; + root.animateToContentY(targetY); + } + + function positionViewAtBeginning() { + gridView.positionViewAtBeginning(); + } + + function positionViewAtEnd() { + gridView.positionViewAtEnd(); + } + + function forceLayout() { + gridView.forceLayout(); + } + + function forceActiveFocus() { + gridView.forceActiveFocus(); + } + + function cancelFlick() { + gridView.cancelFlick(); + } + + function flick(xVelocity, yVelocity) { + gridView.flick(xVelocity, yVelocity); + } + + function incrementCurrentIndex() { + gridView.incrementCurrentIndex(); + } + + function decrementCurrentIndex() { + gridView.decrementCurrentIndex(); + } + + function indexAt(x, y) { + return gridView.indexAt(x, y); + } + + function itemAt(x, y) { + return gridView.itemAt(x, y); + } + + function itemAtIndex(index) { + return gridView.itemAtIndex(index); + } + + function moveCurrentIndexUp() { + gridView.moveCurrentIndexUp(); + } + + function moveCurrentIndexDown() { + gridView.moveCurrentIndexDown(); + } + + function moveCurrentIndexLeft() { + gridView.moveCurrentIndexLeft(); + } + + function moveCurrentIndexRight() { + gridView.moveCurrentIndexRight(); + } + + // Set reasonable implicit sizes for Layout usage + implicitWidth: 200 + implicitHeight: 200 + + Component.onCompleted: { + _wheelTargetY = gridView.contentY; + createGradients(); + } + + // Dynamically create gradient overlays + function createGradients() { + if (!showGradientMasks) + return; + + Qt.createQmlObject(` + import QtQuick + import qs.Commons + Rectangle { + x: 0 + y: 0 + width: root.availableWidth + height: root.gradientHeight + z: 1 + visible: root.showGradientMasks && root.contentOverflows + opacity: (gridView.contentY <= 1 || root.selectionOnFirstVisibleRow) ? 0 : 1 + Behavior on opacity { + NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad } + } + gradient: Gradient { + GradientStop { position: 0.0; color: root.gradientColor } + GradientStop { position: 1.0; color: "transparent" } + } + } + `, root, "topGradient"); + + Qt.createQmlObject(` + import QtQuick + import qs.Commons + Rectangle { + x: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: -1 + width: root.availableWidth + height: root.gradientHeight + 1 + z: 1 + visible: root.showGradientMasks && root.contentOverflows + opacity: ((gridView.contentY + gridView.height >= gridView.contentHeight - 1) || root.selectionOnLastVisibleRow) ? 0 : 1 + Behavior on opacity { + NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad } + } + gradient: Gradient { + GradientStop { position: 0.0; color: "transparent" } + GradientStop { position: 1.0; color: root.gradientColor } + } + } + `, root, "bottomGradient"); + } + + GridView { + id: gridView + anchors.fill: parent + anchors.rightMargin: root.reserveScrollbarSpace ? root.handleWidth + Style.marginXS : 0 + + move: root.animateMovement ? moveTransitionImpl : null + displaced: root.animateMovement ? displacedTransitionImpl : null + + Transition { + id: moveTransitionImpl + NumberAnimation { + properties: "x,y" + duration: Style.animationNormal + easing.type: Easing.InOutQuad + } + } + Transition { + id: displacedTransitionImpl + NumberAnimation { + properties: "x,y" + duration: Style.animationNormal + easing.type: Easing.InOutQuad + } + } + + // Enable clipping to keep content within bounds + clip: true + + // Enable flickable for smooth scrolling + boundsBehavior: Flickable.StopAtBounds + + NumberAnimation { + id: wheelScrollAnimation + target: gridView + property: "contentY" + duration: root.smoothWheelAnimationDuration + easing.type: Easing.OutCubic + } + + onDraggingChanged: { + if (dragging) { + wheelScrollAnimation.stop(); + root._wheelTargetY = contentY; + } + } + + onFlickingChanged: { + if (flicking) { + wheelScrollAnimation.stop(); + root._wheelTargetY = contentY; + } + } + + onContentHeightChanged: root._wheelTargetY = root.clampScrollY(root._wheelTargetY) + onHeightChanged: root._wheelTargetY = root.clampScrollY(root._wheelTargetY) + + // Focus handling depends on keyNavigationEnabled + focus: keyNavigationEnabled + activeFocusOnTab: keyNavigationEnabled + + // Emit keyPressed signal for custom key handling + Keys.onPressed: event => { + if (keyNavigationEnabled) { + root.keyPressed(event); + } + } + + WheelHandler { + enabled: root.wheelScrollMultiplier !== 1.0 + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onWheel: event => { + const delta = event.pixelDelta.y !== 0 ? event.pixelDelta.y : event.angleDelta.y / 2; + root.applyWheelScroll(delta); + event.accepted = true; + } + } + + ScrollBar.vertical: ScrollBar { + parent: root + x: root.mirrored ? 0 : root.width - width + y: 0 + height: root.height + policy: root.verticalPolicy + + contentItem: Rectangle { + implicitWidth: root.handleWidth + implicitHeight: 100 + radius: root.handleRadius + color: parent.pressed ? root.handlePressedColor : parent.hovered ? root.handleHoverColor : root.handleColor + opacity: parent.policy === ScrollBar.AlwaysOn ? 1.0 : root.verticalScrollBarActive ? ((root.showScrollbarWhenScrollable || parent.active) ? 1.0 : 0.0) : 0.0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + background: Rectangle { + implicitWidth: root.handleWidth + implicitHeight: 100 + color: root.trackColor + opacity: parent.policy === ScrollBar.AlwaysOn ? 0.3 : root.verticalScrollBarActive ? ((root.showScrollbarWhenScrollable || parent.active) ? 0.3 : 0.0) : 0.0 + radius: root.handleRadius / 2 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NHeader.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NHeader.qml new file mode 100644 index 0000000..629c344 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NHeader.qml @@ -0,0 +1,34 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons + +ColumnLayout { + id: root + + property string label: "" + property string description: "" + property bool enableDescriptionRichText: false + + opacity: enabled ? 1.0 : 0.6 + spacing: Style.marginXXS + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + + NText { + text: root.label + pointSize: Style.fontSizeXL + font.weight: Style.fontWeightSemiBold + color: Color.mPrimary + visible: root.label !== "" + } + + NText { + text: root.description + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + visible: root.description !== "" + richTextEnabled: root.enableDescriptionRichText + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NIcon.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NIcon.qml new file mode 100644 index 0000000..834702a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NIcon.qml @@ -0,0 +1,30 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +Text { + id: root + + property string icon: Icons.defaultIcon + property real pointSize: Style.fontSizeL + property bool applyUiScale: true + + visible: (icon !== undefined) && (icon !== "") + text: { + if ((icon === undefined) || (icon === "")) { + return ""; + } + if (Icons.get(icon) === undefined) { + Logger.w("Icon", `"${icon}"`, "doesn't exist in the icons font"); + Logger.callStack(); + return Icons.get(Icons.defaultIcon); + } + return Icons.get(icon); + } + font.family: Icons.fontFamily + font.pointSize: Math.max(1, applyUiScale ? root.pointSize * Style.uiScaleRatio : root.pointSize) + color: Color.mOnSurface + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NIconButton.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NIconButton.qml new file mode 100644 index 0000000..52f4355 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NIconButton.qml @@ -0,0 +1,133 @@ +import QtQuick +import Quickshell +import Quickshell.Widgets +import qs.Commons +import qs.Services.UI + +Item { + id: root + + property real baseSize: Style.baseWidgetSize + property bool applyUiScale: true + + property string icon + property var tooltipText + property string tooltipDirection: "auto" + property bool allowClickWhenDisabled: false + property bool handleWheel: false + property bool hovering: false + + property color colorBg: Color.smartAlpha(Color.mSurfaceVariant) + property color colorFg: Color.mPrimary + property color colorBgHover: Color.mHover + property color colorFgHover: Color.mOnHover + property color colorBorder: Color.mOutline + property color colorBorderHover: Color.mOutline + property real customRadius: -1 // -1 means use default (iRadiusL), otherwise use this value + + // Expose border properties for backwards compatibility (aliases to visualButton) + property alias border: visualButton.border + property alias radius: visualButton.radius + property alias color: visualButton.color + + signal entered + signal exited + signal clicked + signal rightClicked + signal middleClicked + signal wheel(int angleDelta) + + // Calculate button size based on settings + readonly property real buttonSize: applyUiScale ? Style.toOdd(baseSize * Style.uiScaleRatio) : Style.toOdd(baseSize) + + // Size: use implicit width/height which layout can override + // BarWidgetLoader sets explicit width/height to extend click area + implicitWidth: buttonSize + implicitHeight: buttonSize + + opacity: enabled ? 1.0 : 0.6 + + // Visual button - stays at buttonSize, centered in parent + Rectangle { + id: visualButton + width: root.buttonSize + height: root.buttonSize + anchors.centerIn: parent + + color: root.enabled && root.hovering ? colorBgHover : colorBg + radius: Math.min((customRadius >= 0 ? customRadius : Style.iRadiusL), width / 2) + border.color: root.enabled && root.hovering ? colorBorderHover : colorBorder + border.width: Style.borderS + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + + NIcon { + icon: root.icon + pointSize: Style.toOdd(visualButton.width * 0.48) + applyUiScale: root.applyUiScale + color: root.enabled && root.hovering ? colorFgHover : colorFg + // Pixel-perfect centering + x: Style.pixelAlignCenter(visualButton.width, width) + y: Style.pixelAlignCenter(visualButton.height, contentHeight) + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + } + } + + // MouseArea fills root (extends beyond visual button for bar click area) + MouseArea { + // Always enabled to allow hover/tooltip even when the button is disabled + enabled: true + anchors.fill: parent + cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + hoverEnabled: true + onEntered: { + hovering = root.enabled ? true : false; + if (hovering && tooltipText && (!Array.isArray(tooltipText) || tooltipText.length > 0)) { + TooltipService.show(root, tooltipText, tooltipDirection); + } + root.entered(); + } + onExited: { + hovering = false; + if (tooltipText && (!Array.isArray(tooltipText) || tooltipText.length > 0)) { + TooltipService.hide(root); + } + root.exited(); + } + onClicked: mouse => { + if (tooltipText && (!Array.isArray(tooltipText) || tooltipText.length > 0)) { + TooltipService.hide(root); + } + if (!root.enabled && !allowClickWhenDisabled) { + return; + } + if (mouse.button === Qt.LeftButton) { + root.clicked(); + } else if (mouse.button === Qt.RightButton) { + root.rightClicked(); + } else if (mouse.button === Qt.MiddleButton) { + root.middleClicked(); + } + } + onWheel: wheel => { + if (root.handleWheel) { + root.wheel(wheel.angleDelta.y); + } + wheel.accepted = false; + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NIconButtonHot.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NIconButtonHot.qml new file mode 100644 index 0000000..39fdb92 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NIconButtonHot.qml @@ -0,0 +1,161 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import qs.Commons +import qs.Services.UI + +Rectangle { + id: root + + // Public properties + property real baseSize: Style.baseWidgetSize + property bool applyUiScale: true + property string icon + property var tooltipText + property string tooltipDirection: "auto" + property bool allowClickWhenDisabled: false + property bool hot: false + + // Internal properties + property bool hovering: false + property bool pressed: false + + // Color properties + property color colorBg: Color.smartAlpha(Color.mSurfaceVariant) + property color colorFg: Color.mPrimary + property color colorBgHover: Color.mHover + property color colorFgHover: Color.mOnHover + property color colorBorder: Color.mOutline + property color colorBorderHover: Color.mOutline + + // Hot state colors + property color colorBgHot: Color.mPrimary + property color colorFgHot: Color.mOnPrimary + + // Signals + signal entered + signal exited + signal clicked + signal rightClicked + signal middleClicked + + // Dimensions + implicitWidth: applyUiScale ? Math.round(baseSize * Style.uiScaleRatio) : Math.round(baseSize) + implicitHeight: applyUiScale ? Math.round(baseSize * Style.uiScaleRatio) : Math.round(baseSize) + + // Appearance + opacity: enabled ? 1.0 : 0.6 + color: { + if (root.enabled && root.hovering || pressed) { + return colorBgHover; + } + + if (hot) { + return colorBgHot; + } + return colorBg; + } + radius: Math.min(Style.iRadiusL, width / 2) + border.color: root.enabled && root.hovering ? colorBorderHover : colorBorder + border.width: Style.borderS + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + + // Icon + NIcon { + icon: root.icon + pointSize: Math.max(1, Math.round(root.width * 0.48)) + applyUiScale: root.applyUiScale + color: { + if (root.enabled && root.hovering || pressed) { + return colorFgHover; + } + if (hot) { + return colorFgHot; + } + return colorFg; + } + // Center horizontally + x: (root.width - width) / 2 + // Center vertically accounting for font metrics + y: (root.height - height) / 2 + (height - contentHeight) / 2 + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } + } + + MouseArea { + // Always enabled to allow hover/tooltip even when the button is disabled + enabled: true + anchors.fill: parent + cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + hoverEnabled: true + + onEntered: { + hovering = root.enabled ? true : false; + if (hovering && tooltipText && (!Array.isArray(tooltipText) || tooltipText.length > 0)) { + TooltipService.show(parent, tooltipText, tooltipDirection); + } + root.entered(); + } + + onExited: { + hovering = false; + if (tooltipText && (!Array.isArray(tooltipText) || tooltipText.length > 0)) { + TooltipService.hide(); + } + root.exited(); + } + + onPressed: function (mouse) { + if (root.enabled) { + root.pressed = true; + } + if (tooltipText && (!Array.isArray(tooltipText) || tooltipText.length > 0)) { + TooltipService.hide(); + } + } + + onReleased: function (mouse) { + root.scale = 1.0; + root.pressed = false; + + if (!root.enabled && !allowClickWhenDisabled) { + return; + } + + // Only trigger actions if released while hovering + if (root.hovering) { + if (mouse.button === Qt.LeftButton) { + root.clicked(); + } else if (mouse.button === Qt.RightButton) { + root.rightClicked(); + } else if (mouse.button === Qt.MiddleButton) { + root.middleClicked(); + } + } + } + + onCanceled: { + root.hovering = false; + root.pressed = false; + root.scale = 1.0; + if (tooltipText && (!Array.isArray(tooltipText) || tooltipText.length > 0)) { + TooltipService.hide(); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NIconPicker.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NIconPicker.qml new file mode 100644 index 0000000..20ee31e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NIconPicker.qml @@ -0,0 +1,170 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import qs.Commons +import qs.Widgets + +Popup { + id: root + modal: true + + property string selectedIcon: "" + property string initialIcon: "" + + signal iconSelected(string iconName) + + width: Math.round(900 * Style.uiScaleRatio) + height: Math.round(700 * Style.uiScaleRatio) + anchors.centerIn: Overlay.overlay + padding: Style.marginXL + + property string query: "" + property var allIcons: Object.keys(Icons.icons) + property var filteredIcons: { + if (query === "") + return allIcons; + var q = query.toLowerCase(); + return allIcons.filter(name => name.toLowerCase().includes(q)); + } + readonly property int columns: 6 + readonly property int cellW: Math.floor(grid.width / columns) + readonly property int cellH: Math.round(cellW * 0.7 + 36) + + onOpened: { + selectedIcon = initialIcon; + query = initialIcon; + searchInput.forceActiveFocus(); + } + + background: Rectangle { + color: Color.mSurface + radius: Style.iRadiusL + border.color: Color.mPrimary + border.width: Style.borderM + } + + ColumnLayout { + anchors.fill: parent + spacing: Style.marginM + + // Title row + RowLayout { + Layout.fillWidth: true + NText { + text: I18n.tr("widgets.icon-picker.title") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mPrimary + Layout.fillWidth: true + } + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + onClicked: root.close() + } + } + + NDivider { + Layout.fillWidth: true + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + NTextInput { + id: searchInput + Layout.fillWidth: true + label: I18n.tr("common.search") + placeholderText: I18n.tr("placeholders.search-icons") + text: root.query + onTextChanged: root.query = text.trim().toLowerCase() + } + } + + // Icon grid + NGridView { + id: grid + Layout.fillWidth: true + Layout.fillHeight: true + Layout.margins: Style.marginM + cellWidth: root.cellW + cellHeight: root.cellH + model: root.filteredIcons + reserveScrollbarSpace: false + gradientColor: Color.mSurface + + delegate: Rectangle { + width: grid.cellWidth + height: grid.cellHeight + radius: Style.iRadiusS + + color: (root.selectedIcon === modelData) ? Qt.alpha(Color.mPrimary, 0.15) : "transparent" + border.color: (root.selectedIcon === modelData) ? Color.mPrimary : "transparent" + border.width: (root.selectedIcon === modelData) ? Style.borderS : 0 + + MouseArea { + anchors.fill: parent + onClicked: root.selectedIcon = modelData + onDoubleClicked: { + root.selectedIcon = modelData; + root.iconSelected(root.selectedIcon); + root.close(); + } + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginS + spacing: Style.marginS + Item { + Layout.fillHeight: true + Layout.preferredHeight: 4 + } + NIcon { + Layout.alignment: Qt.AlignHCenter + icon: modelData + pointSize: 42 + } + NText { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.topMargin: Style.marginXS + elide: Text.ElideRight + wrapMode: Text.NoWrap + maximumLineCount: 1 + horizontalAlignment: Text.AlignHCenter + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeXS + text: modelData + } + Item { + Layout.fillHeight: true + } + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + Item { + Layout.fillWidth: true + } + NButton { + text: I18n.tr("common.cancel") + outlined: true + onClicked: root.close() + } + NButton { + text: I18n.tr("common.apply") + icon: "check" + enabled: root.selectedIcon !== "" + onClicked: { + root.iconSelected(root.selectedIcon); + root.close(); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NImageRounded.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NImageRounded.qml new file mode 100644 index 0000000..a33ffcf --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NImageRounded.qml @@ -0,0 +1,103 @@ +import QtQuick +import QtQuick.Effects +import Quickshell +import Quickshell.Widgets +import qs.Commons + +Item { + id: root + + property real radius: 0 + property string imagePath: "" + property string fallbackIcon: "" + property real fallbackIconSize: Style.fontSizeXXL + property real borderWidth: 0 + property color borderColor: "transparent" + property int imageFillMode: Image.PreserveAspectCrop + + readonly property bool _isAnimated: imagePath.toLowerCase().endsWith(".gif") + readonly property Item imageSource: imageSourceLoader.item + readonly property bool showFallback: fallbackIcon !== "" && (imagePath === "" || (imageSource && imageSource.status === Image.Error)) + readonly property int status: imageSource ? imageSource.status : Image.Null + + Rectangle { + anchors.fill: parent + radius: root.radius + color: "transparent" + border.width: root.borderWidth + border.color: root.borderColor + + Loader { + id: imageSourceLoader + anchors.fill: parent + anchors.margins: root.borderWidth + active: root.imagePath !== "" + sourceComponent: root._isAnimated ? animatedComponent : staticComponent + } + + Component { + id: staticComponent + Image { + visible: false + source: root.imagePath + mipmap: true + smooth: true + asynchronous: true + antialiasing: true + fillMode: root.imageFillMode + } + } + + Component { + id: animatedComponent + AnimatedImage { + visible: false + source: root.imagePath + mipmap: true + smooth: true + asynchronous: true + antialiasing: true + fillMode: root.imageFillMode + playing: true + } + } + + // Fallback texture provider to avoid null source warnings + ShaderEffectSource { + id: _safeFallback + sourceItem: Rectangle { + width: 1 + height: 1 + color: "transparent" + } + visible: false + live: false + } + + ShaderEffect { + anchors.fill: parent + anchors.margins: root.borderWidth + visible: !root.showFallback && root.imageSource !== null && root.status === Image.Ready + property var source: root.imageSource ?? _safeFallback + property real itemWidth: width + property real itemHeight: height + property real sourceWidth: root.imageSource?.sourceSize.width ?? 0 + property real sourceHeight: root.imageSource?.sourceSize.height ?? 0 + property real cornerRadius: Math.max(0, root.radius - root.borderWidth) + property real imageOpacity: 1.0 + property int fillMode: root.imageFillMode + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/rounded_image.frag.qsb") + supportsAtlasTextures: false + blending: true + } + + NIcon { + anchors.fill: parent + anchors.margins: root.borderWidth + visible: root.showFallback + icon: root.fallbackIcon + pointSize: root.fallbackIconSize + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NInputAction.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NInputAction.qml new file mode 100644 index 0000000..53025ed --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NInputAction.qml @@ -0,0 +1,52 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +// Input and button row +RowLayout { + id: root + + // Public properties + property string label: "" + property string description: "" + property string placeholderText: "" + property string text: "" + property string actionButtonText: I18n.tr("common.test") + property string actionButtonIcon: "media-play" + property bool actionButtonEnabled: text !== "" + + // Signals + signal editingFinished + signal actionClicked + + // Internal properties + spacing: Style.marginM + + NTextInput { + id: textInput + label: root.label + description: root.description + placeholderText: root.placeholderText + text: root.text + onTextChanged: root.text = text + onEditingFinished: root.editingFinished() + Layout.fillWidth: true + } + + NButton { + Layout.fillWidth: false + Layout.alignment: Qt.AlignBottom + + text: root.actionButtonText + icon: root.actionButtonIcon + backgroundColor: Color.mSecondary + textColor: Color.mOnSecondary + hoverColor: Color.mHover + enabled: root.actionButtonEnabled + + onClicked: { + root.actionClicked(); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NKeybindRecorder.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NKeybindRecorder.qml new file mode 100644 index 0000000..c19343d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NKeybindRecorder.qml @@ -0,0 +1,334 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property string label: "" + property string description: "" + property var currentKeybinds: [] + property string defaultKeybind: "" + property bool allowEmpty: false + property color labelColor: Color.mOnSurface + property color descriptionColor: Color.mOnSurfaceVariant + property string settingsPath: "" + + property int maxKeybinds: 2 + property bool requireModifierForNormalKeys: true + signal keybindsChanged(var newKeybinds) + + implicitHeight: contentLayout.implicitHeight + + // -1 = not recording, >= 0 = re-recording at index, -2 = adding new + property int recordingIndex: -1 + property bool hasConflict: false + + onRecordingIndexChanged: { + PanelService.isKeybindRecording = recordingIndex !== -1; + if (recordingIndex !== -1) { + hasConflict = false; + } + } + + readonly property real _pillHeight: Style.baseWidgetSize * 1.1 * Style.uiScaleRatio + + function _applyKeybind(keyStr) { + if (!keyStr) + return; + + // 1. Internal duplicate check (same action) + for (let i = 0; i < root.currentKeybinds.length; i++) { + if (i !== root.recordingIndex && String(root.currentKeybinds[i]).toLowerCase() === keyStr.toLowerCase()) { + hasConflict = true; + ToastService.showWarning(I18n.tr("panels.general.keybinds-conflict-title"), I18n.tr("panels.general.keybinds-conflict-description", { + "action": root.label || "This action" + })); + conflictTimer.restart(); + return; + } + } + + // 2. External conflict check (other actions) + const conflict = Keybinds.getKeybindConflict(keyStr, root.settingsPath, Settings.data); + if (conflict) { + hasConflict = true; + ToastService.showWarning(I18n.tr("panels.general.keybinds-conflict-title"), I18n.tr("panels.general.keybinds-conflict-description", { + "action": conflict + })); + conflictTimer.restart(); + return; + } + + var newKeybinds = Array.from(root.currentKeybinds); + if (recordingIndex >= 0) { + newKeybinds[recordingIndex] = keyStr; + } + // Ensure array is dense and limited to maxKeybinds + newKeybinds = newKeybinds.filter(k => k !== undefined && k !== "").slice(0, root.maxKeybinds); + recordingIndex = -1; + root.keybindsChanged(newKeybinds); + } + + Timer { + id: conflictTimer + interval: 2000 + onTriggered: { + hasConflict = false; + recordingIndex = -1; + } + } + + RowLayout { + id: contentLayout + width: parent.width + spacing: Style.marginL + + // Label and Description (optional) + NLabel { + id: labelContainer + label: root.label + description: root.description + labelColor: root.labelColor + descriptionColor: root.descriptionColor + visible: label !== "" || description !== "" + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + } + + RowLayout { + id: slotsRow + spacing: Style.marginS + Layout.alignment: Qt.AlignVCenter | (labelContainer.visible ? Qt.AlignRight : Qt.AlignLeft) + + Repeater { + model: root.maxKeybinds + delegate: MouseArea { + id: slotArea + width: Math.round(180 * Style.uiScaleRatio) + height: root._pillHeight + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + readonly property bool isOccupied: index < root.currentKeybinds.length + readonly property bool isRecordingThis: root.recordingIndex === index + readonly property string keybindText: isRecordingThis ? I18n.tr("placeholders.keybind-recording") : (isOccupied ? root.currentKeybinds[index] : I18n.tr("placeholders.add-new-keybind")) + + onClicked: { + if (isRecordingThis) { + root.recordingIndex = -1; + } else { + root.recordingIndex = index; + keybindInput.forceActiveFocus(); + } + } + + Rectangle { + id: slotBg + anchors.fill: parent + radius: Style.iRadiusS + color: root.hasConflict && slotArea.isRecordingThis ? Color.mError : (slotArea.isRecordingThis ? Color.mSecondary : (slotArea.containsMouse ? Qt.alpha(Color.mSecondary, 0.15) : Color.mSurface)) + border.color: root.hasConflict && slotArea.isRecordingThis ? Color.mError : (slotArea.isRecordingThis ? Color.mPrimary : (slotArea.containsMouse ? Color.mSecondary : Color.mOutline)) + border.width: Style.borderS + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + + RowLayout { + anchors.fill: parent + anchors.leftMargin: Style.marginM + anchors.rightMargin: Style.marginS + spacing: Style.marginXS + + NIcon { + icon: root.hasConflict && slotArea.isRecordingThis ? "alert-circle" : (slotArea.isRecordingThis ? "circle-dot" : "keyboard") + color: slotArea.isRecordingThis ? Color.mOnSecondary : (slotArea.isOccupied ? Color.mOnSurfaceVariant : Qt.alpha(Color.mOnSurfaceVariant, 0.4)) + opacity: 0.8 + visible: !slotArea.isRecordingThis || root.hasConflict + } + + NText { + Layout.fillWidth: true + text: slotArea.keybindText + color: slotArea.isRecordingThis ? Color.mOnSecondary : (slotArea.isOccupied ? Color.mOnSurface : Color.mOnSurfaceVariant) + font.family: slotArea.isOccupied && !slotArea.isRecordingThis ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault + font.pointSize: slotArea.isOccupied ? Style.fontSizeM : Style.fontSizeS + font.weight: slotArea.isOccupied ? Style.fontWeightBold : Style.fontWeightRegular + elide: Text.ElideRight + opacity: slotArea.isOccupied || slotArea.isRecordingThis ? 1.0 : 0.6 + } + + Item { + Layout.preferredWidth: Math.round(root._pillHeight * 0.7) + Layout.fillHeight: true + visible: slotArea.isOccupied && root.recordingIndex === -1 + + NIconButton { + anchors.centerIn: parent + visible: root.recordingIndex === -1 && (root.currentKeybinds.length > 1 || root.allowEmpty) + icon: "x" + colorBg: "transparent" + colorBgHover: Qt.alpha(Color.mError, 0.1) + colorFg: Color.mOnSurfaceVariant + colorFgHover: Color.mError + border.width: 0 + baseSize: Style.baseWidgetSize * 0.7 + onClicked: { + var newKeybinds = Array.from(root.currentKeybinds); + newKeybinds.splice(index, 1); + root.keybindsChanged(newKeybinds); + } + } + } + } + } + } + } + } + + // Hidden Item to capture keys + Item { + id: keybindInput + width: 0 + height: 0 + focus: true + + Keys.onPressed: event => { + if (root.recordingIndex === -1 || root.hasConflict) + return; + + // Handle Escape specifically to ensure it doesn't close the panel + if (event.key === Qt.Key_Escape) { + event.accepted = true; + root._applyKeybind("Esc"); + return; + } + + // Ignore modifier keys by themselves + if (event.key === Qt.Key_Control || event.key === Qt.Key_Shift || event.key === Qt.Key_Alt || event.key === Qt.Key_Meta) { + event.accepted = true; // Consume modifiers too while listening + return; + } + + let keyStr = ""; + if (event.modifiers & Qt.ControlModifier) + keyStr += "Ctrl+"; + if (event.modifiers & Qt.AltModifier) + keyStr += "Alt+"; + if (event.modifiers & Qt.ShiftModifier) + keyStr += "Shift+"; + + let keyName = ""; + let rawText = event.text; + + if (event.key >= Qt.Key_A && event.key <= Qt.Key_Z || event.key >= Qt.Key_0 && event.key <= Qt.Key_9) { + keyName = String.fromCharCode(event.key); + } else if (event.key >= Qt.Key_F1 && event.key <= Qt.Key_F12) { + keyName = "F" + (event.key - Qt.Key_F1 + 1); + } else if (rawText && rawText.length > 0 && rawText.charCodeAt(0) > 31 && rawText.charCodeAt(0) !== 127) { + keyName = rawText.toUpperCase(); + + // Handle shifted digits + if (event.modifiers & Qt.ShiftModifier) { + const shiftMap = { + "!": "1", + "\"": "2", + "ยง": "3", + "$": "4", + "%": "5", + "&": "6", + "/": "7", + "(": "8", + ")": "9", + "=": "0", + "@": "2", + "#": "3", + "^": "6", + "*": "8" + }; + if (shiftMap[keyName]) + keyName = shiftMap[keyName]; + } + } else { + switch (event.key) { + case Qt.Key_Return: + keyName = "Return"; + break; + case Qt.Key_Enter: + keyName = "Enter"; + break; + case Qt.Key_Tab: + keyName = "Tab"; + break; + case Qt.Key_Backspace: + keyName = "Backspace"; + break; + case Qt.Key_Delete: + keyName = "Del"; + break; + case Qt.Key_Insert: + keyName = "Ins"; + break; + case Qt.Key_Home: + keyName = "Home"; + break; + case Qt.Key_End: + keyName = "End"; + break; + case Qt.Key_PageUp: + keyName = "PgUp"; + break; + case Qt.Key_PageDown: + keyName = "PgDn"; + break; + case Qt.Key_Left: + keyName = "Left"; + break; + case Qt.Key_Right: + keyName = "Right"; + break; + case Qt.Key_Up: + keyName = "Up"; + break; + case Qt.Key_Down: + keyName = "Down"; + break; + } + } + + if (keyName) { + // Enforce modifier requirement (Ctrl or Alt) for "normal" keys unless explicitly disabled + // Allow Arrows, Nav, Function, and System keys without modifiers + const isSpecialKey = (event.key >= Qt.Key_F1 && event.key <= Qt.Key_F35) || (event.key >= Qt.Key_Left && event.key <= Qt.Key_Down) || (event.key === Qt.Key_Home || event.key === Qt.Key_End || event.key === Qt.Key_PageUp || event.key === Qt.Key_PageDown) || (event.key === Qt.Key_Insert || event.key === Qt.Key_Delete || event.key + === Qt.Key_Backspace) || (event.key === Qt.Key_Tab || event.key + === Qt.Key_Return || event.key === Qt.Key_Enter + || event.key === Qt.Key_Escape || event.key + === Qt.Key_Space); + + const hasModifier = (event.modifiers & Qt.ControlModifier) || (event.modifiers & Qt.AltModifier); + + if (root.requireModifierForNormalKeys && !hasModifier && !isSpecialKey) { + hasConflict = true; + ToastService.showWarning(I18n.tr("panels.general.keybinds-modifier-title"), I18n.tr("panels.general.keybinds-modifier-description")); + conflictTimer.restart(); + return; + } + + root._applyKeybind(keyStr + keyName); + } + event.accepted = true; + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NLabel.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NLabel.qml new file mode 100644 index 0000000..71073fc --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NLabel.qml @@ -0,0 +1,69 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + + property string label: "" + property string description: "" + property string icon: "" + property color labelColor: Color.mOnSurface + property color descriptionColor: Color.mOnSurfaceVariant + property color iconColor: Color.mOnSurface + property bool showIndicator: false + property string indicatorTooltip: "" + property real labelSize: Style.fontSizeL + + opacity: enabled ? 1.0 : 0.6 + spacing: Style.marginXXS + visible: root.label != "" || root.description != "" + + Layout.fillWidth: true + + RowLayout { + spacing: Style.marginXS + Layout.fillWidth: true + visible: root.label !== "" + + NIcon { + visible: root.icon !== "" + icon: root.icon + pointSize: Style.fontSizeXXL + color: root.iconColor + Layout.rightMargin: Style.marginS + } + + NText { + id: labelText + Layout.fillWidth: true + text: root.label + pointSize: root.labelSize + font.weight: Style.fontWeightSemiBold + color: labelColor + wrapMode: Text.WordWrap + + // Settings indicator dot positioned right after the text content + Loader { + active: root.showIndicator + x: labelText.contentWidth + Style.marginXS + anchors.verticalCenter: parent.verticalCenter + sourceComponent: NSettingsIndicator { + show: true + tooltipText: root.indicatorTooltip || "" + } + } + } + } + + NText { + visible: root.description !== "" + Layout.fillWidth: true + text: root.description + pointSize: Style.fontSizeS + color: root.descriptionColor + wrapMode: Text.WordWrap + textFormat: Text.StyledText + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NLinearGauge.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NLinearGauge.qml new file mode 100644 index 0000000..93a0398 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NLinearGauge.qml @@ -0,0 +1,28 @@ +import QtQuick +import qs.Commons +import qs.Services.UI + +Rectangle { + id: root + + // Mandatory properties for gauges + required property int orientation // Qt.Vertical || Qt.Horizontal + required property real ratio // 0..1 + + radius: orientation === Qt.Vertical ? width / 2 : height / 2 + color: Color.mOutline + property color fillColor: Color.mPrimary + + // Fill that grows from bottom if vertical and left if horizontal. + // Snap to zero if the computed pixel length is sub-pixel (< 1px). + Rectangle { + readonly property real clampedRatio: Math.min(1, Math.max(0, root.ratio)) + readonly property real rawFill: (orientation === Qt.Vertical ? root.height : root.width) * clampedRatio + width: orientation === Qt.Vertical ? root.width : (rawFill < 1 ? 0 : rawFill) + height: orientation === Qt.Vertical ? (rawFill < 1 ? 0 : rawFill) : root.height + radius: root.radius + color: root.fillColor + anchors.bottom: orientation === Qt.Vertical ? parent.bottom : undefined + anchors.left: parent.left + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NListView.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NListView.qml new file mode 100644 index 0000000..7a2ae41 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NListView.qml @@ -0,0 +1,340 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Templates as T +import qs.Commons + +Item { + id: root + + property color handleColor: Qt.alpha(Color.mHover, 0.8) + property color handleHoverColor: handleColor + property color handlePressedColor: handleColor + property color trackColor: "transparent" + property real handleWidth: Math.round(6 * Style.uiScaleRatio) + property real handleRadius: Style.iRadiusM + property int verticalPolicy: ScrollBar.AsNeeded + property int horizontalPolicy: ScrollBar.AlwaysOff + readonly property bool verticalScrollBarActive: { + if (listView.ScrollBar.vertical.policy === ScrollBar.AlwaysOff) + return false; + return listView.contentHeight > listView.height; + } + readonly property bool contentOverflows: listView.contentHeight > listView.height + + property bool showGradientMasks: true + property color gradientColor: Color.mSurfaceVariant + property int gradientHeight: 16 + property bool reserveScrollbarSpace: true + + // Keep scrollbars visible whenever overflow exists (without forcing visibility when not scrollable) + property bool showScrollbarWhenScrollable: Settings.data.ui.scrollbarAlwaysVisible + + // Available width for content (excludes scrollbar space when reserveScrollbarSpace is true) + readonly property real availableWidth: width - (reserveScrollbarSpace ? handleWidth + Style.marginXS : 0) + + // Forward ListView properties + property alias model: listView.model + property alias delegate: listView.delegate + property alias spacing: listView.spacing + property alias orientation: listView.orientation + property alias currentIndex: listView.currentIndex + property alias count: listView.count + property alias contentHeight: listView.contentHeight + property alias contentWidth: listView.contentWidth + property alias contentY: listView.contentY + property alias contentX: listView.contentX + property alias currentItem: listView.currentItem + property alias highlightItem: listView.highlightItem + property alias headerItem: listView.headerItem + property alias footerItem: listView.footerItem + property alias section: listView.section + property alias highlightFollowsCurrentItem: listView.highlightFollowsCurrentItem + property alias highlightMoveDuration: listView.highlightMoveDuration + property alias highlightMoveVelocity: listView.highlightMoveVelocity + property alias preferredHighlightBegin: listView.preferredHighlightBegin + property alias preferredHighlightEnd: listView.preferredHighlightEnd + property alias highlightRangeMode: listView.highlightRangeMode + property alias snapMode: listView.snapMode + property alias keyNavigationWraps: listView.keyNavigationWraps + property alias cacheBuffer: listView.cacheBuffer + property alias displayMarginBeginning: listView.displayMarginBeginning + property alias displayMarginEnd: listView.displayMarginEnd + property alias layoutDirection: listView.layoutDirection + property alias effectiveLayoutDirection: listView.effectiveLayoutDirection + property alias verticalLayoutDirection: listView.verticalLayoutDirection + property alias boundsBehavior: listView.boundsBehavior + property alias flickableDirection: listView.flickableDirection + property alias interactive: listView.interactive + property alias moving: listView.moving + property alias flicking: listView.flicking + property alias dragging: listView.dragging + property alias horizontalVelocity: listView.horizontalVelocity + property alias verticalVelocity: listView.verticalVelocity + + // Scroll speed multiplier for mouse wheel (1.0 = default, higher = faster) + property real wheelScrollMultiplier: 2.0 + property int smoothWheelAnimationDuration: Style.animationNormal + property real _wheelTargetY: 0 + + function clampScrollY(value) { + return Math.max(0, Math.min(value, listView.contentHeight - listView.height)); + } + + function applyWheelScroll(delta) { + if (!root.contentOverflows) + return; + + const step = delta * root.wheelScrollMultiplier; + + if (!Settings.data.general.smoothScrollEnabled || Settings.data.general.animationDisabled) { + listView.contentY = root.clampScrollY(listView.contentY - step); + root._wheelTargetY = listView.contentY; + return; + } + + if (!wheelScrollAnimation.running) + root._wheelTargetY = listView.contentY; + + root._wheelTargetY = root.clampScrollY(root._wheelTargetY - step); + wheelScrollAnimation.to = root._wheelTargetY; + wheelScrollAnimation.restart(); + } + + function animateToContentY(targetY) { + const clampedY = root.clampScrollY(targetY); + + if (!Settings.data.general.smoothScrollEnabled || Settings.data.general.animationDisabled || listView.dragging || listView.flicking) { + listView.contentY = clampedY; + root._wheelTargetY = clampedY; + return; + } + + root._wheelTargetY = clampedY; + wheelScrollAnimation.to = clampedY; + wheelScrollAnimation.restart(); + } + + // Forward ListView methods + function positionViewAtIndex(index, mode) { + const shouldAnimate = mode === ListView.Contain; + if (!shouldAnimate) { + listView.positionViewAtIndex(index, mode); + root._wheelTargetY = listView.contentY; + return; + } + + const previousY = listView.contentY; + listView.positionViewAtIndex(index, mode); + const targetY = root.clampScrollY(listView.contentY); + + if (Math.abs(targetY - previousY) < 0.5) { + root._wheelTargetY = targetY; + return; + } + + listView.contentY = previousY; + root._wheelTargetY = previousY; + root.animateToContentY(targetY); + } + + function positionViewAtBeginning() { + listView.positionViewAtBeginning(); + } + + function positionViewAtEnd() { + listView.positionViewAtEnd(); + } + + function forceLayout() { + listView.forceLayout(); + } + + function cancelFlick() { + listView.cancelFlick(); + } + + function flick(xVelocity, yVelocity) { + listView.flick(xVelocity, yVelocity); + } + + function incrementCurrentIndex() { + listView.incrementCurrentIndex(); + } + + function decrementCurrentIndex() { + listView.decrementCurrentIndex(); + } + + function indexAt(x, y) { + return listView.indexAt(x, y); + } + + function itemAt(x, y) { + return listView.itemAt(x, y); + } + + function itemAtIndex(index) { + return listView.itemAtIndex(index); + } + + // Set reasonable implicit sizes for Layout usage + implicitWidth: 200 + implicitHeight: 200 + + Component.onCompleted: { + _wheelTargetY = listView.contentY; + createGradients(); + } + + // Dynamically create gradient overlays + function createGradients() { + if (!showGradientMasks) + return; + + Qt.createQmlObject(` + import QtQuick + import qs.Commons + Rectangle { + x: 0 + y: 0 + width: root.availableWidth + height: root.gradientHeight + z: 1 + visible: root.showGradientMasks && root.contentOverflows + opacity: { + if (listView.contentY <= 1) return 0; + if (listView.currentItem && listView.currentItem.y - listView.contentY < root.gradientHeight) return 0; + return 1; + } + Behavior on opacity { + NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad } + } + gradient: Gradient { + GradientStop { position: 0.0; color: root.gradientColor } + GradientStop { position: 1.0; color: "transparent" } + } + } + `, root, "topGradient"); + + Qt.createQmlObject(` + import QtQuick + import qs.Commons + Rectangle { + x: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: -1 + width: root.availableWidth + height: root.gradientHeight + 1 + z: 1 + visible: root.showGradientMasks && root.contentOverflows + opacity: { + if (listView.contentY + listView.height >= listView.contentHeight - 1) return 0; + if (listView.currentItem && listView.currentItem.y + listView.currentItem.height > listView.contentY + listView.height - root.gradientHeight) return 0; + return 1; + } + Behavior on opacity { + NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad } + } + gradient: Gradient { + GradientStop { position: 0.0; color: "transparent" } + GradientStop { position: 1.0; color: root.gradientColor } + } + } + `, root, "bottomGradient"); + } + + ListView { + id: listView + anchors.fill: parent + anchors.rightMargin: root.reserveScrollbarSpace ? root.handleWidth + Style.marginXS : 0 + + clip: true + boundsBehavior: Flickable.StopAtBounds + + NumberAnimation { + id: wheelScrollAnimation + target: listView + property: "contentY" + duration: root.smoothWheelAnimationDuration + easing.type: Easing.OutCubic + } + + onDraggingChanged: { + if (dragging) { + wheelScrollAnimation.stop(); + root._wheelTargetY = contentY; + } + } + + onFlickingChanged: { + if (flicking) { + wheelScrollAnimation.stop(); + root._wheelTargetY = contentY; + } + } + + onContentHeightChanged: root._wheelTargetY = root.clampScrollY(root._wheelTargetY) + onHeightChanged: root._wheelTargetY = root.clampScrollY(root._wheelTargetY) + + WheelHandler { + enabled: !root.contentOverflows + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onWheel: event => { + event.accepted = true; + } + } + + WheelHandler { + enabled: root.wheelScrollMultiplier !== 1.0 && root.contentOverflows + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onWheel: event => { + const delta = event.pixelDelta.y !== 0 ? event.pixelDelta.y : event.angleDelta.y / 2; + root.applyWheelScroll(delta); + event.accepted = true; + } + } + + ScrollBar.vertical: ScrollBar { + parent: root + x: root.mirrored ? 0 : root.width - width + y: 0 + height: root.height + policy: root.verticalPolicy + visible: policy === ScrollBar.AlwaysOn || root.verticalScrollBarActive + + contentItem: Rectangle { + implicitWidth: root.handleWidth + implicitHeight: 100 + radius: root.handleRadius + color: parent.pressed ? root.handlePressedColor : parent.hovered ? root.handleHoverColor : root.handleColor + opacity: parent.policy === ScrollBar.AlwaysOn ? 1.0 : root.verticalScrollBarActive ? ((root.showScrollbarWhenScrollable || parent.active) ? 1.0 : 0.0) : 0.0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + background: Rectangle { + implicitWidth: root.handleWidth + implicitHeight: 100 + color: root.trackColor + opacity: parent.policy === ScrollBar.AlwaysOn ? 0.3 : root.verticalScrollBarActive ? ((root.showScrollbarWhenScrollable || parent.active) ? 0.3 : 0.0) : 0.0 + radius: root.handleRadius / 2 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NPluginSettingsPopup.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NPluginSettingsPopup.qml new file mode 100644 index 0000000..5088681 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NPluginSettingsPopup.qml @@ -0,0 +1,152 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.Noctalia +import qs.Services.UI +import qs.Widgets + +Popup { + id: root + modal: true + dim: false + anchors.centerIn: parent + property var screen: null + readonly property real maxHeight: (screen ? screen.height : (parent ? parent.height : 800)) * 0.8 + + property real _minWidth: 600 * Style.uiScaleRatio + width: _minWidth + height: Math.min(settingsContent.implicitHeight + padding * 2, maxHeight) + padding: Style.marginXL + + property var currentPlugin: null + property var currentPluginApi: null + property bool showToastOnSave: false + + background: Rectangle { + color: Color.mSurface + radius: Style.radiusL + border.color: Color.mPrimary + border.width: Style.borderM + } + + contentItem: FocusScope { + focus: true + + ColumnLayout { + id: settingsContent + anchors.fill: parent + spacing: Style.marginM + + // Header + RowLayout { + Layout.fillWidth: true + + NText { + text: I18n.tr("panels.plugins.plugin-settings-title", { + "plugin": root.currentPlugin?.name || "" + }) + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mPrimary + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + onClicked: root.close() + } + } + + // Separator + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + } + + // Settings loader - pluginApi is passed via setSource() in openPluginSettings() + NScrollView { + id: settingsScrollView + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: 100 + horizontalPolicy: ScrollBar.AlwaysOff + gradientColor: Color.mSurface + + Loader { + id: settingsLoader + width: settingsScrollView.availableWidth + } + } + + // Action buttons + RowLayout { + Layout.fillWidth: true + Layout.topMargin: Style.marginM + spacing: Style.marginM + + Item { + Layout.fillWidth: true + } + + NButton { + text: I18n.tr("common.close") + outlined: true + onClicked: root.close() + } + + NButton { + text: I18n.tr("common.apply") + icon: "check" + onClicked: { + if (settingsLoader.item && settingsLoader.item.saveSettings) { + settingsLoader.item.saveSettings(); + if (root.showToastOnSave) { + ToastService.showNotice(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.settings-saved")); + } + } + } + } + } + } + } + + onClosed: { + // Clear both source and sourceComponent to ensure full cleanup + settingsLoader.sourceComponent = null; + settingsLoader.source = ""; + currentPlugin = null; + currentPluginApi = null; + } + + function openPluginSettings(pluginManifest, settingsEntryPoint) { + currentPlugin = pluginManifest; + + // Use composite key if available (for custom plugins), otherwise use manifest ID (for official plugins) + var pluginId = pluginManifest.compositeKey || pluginManifest.id; + + currentPluginApi = PluginService.getPluginAPI(pluginId); + if (!currentPluginApi) { + Logger.e("NPluginSettingsPopup", "Cannot open settings: plugin not loaded:", pluginId); + if (showToastOnSave) { + ToastService.showError(I18n.tr("panels.plugins.title"), I18n.tr("panels.plugins.settings-error-not-loaded")); + } + return; + } + + // Get plugin directory + var pluginDir = PluginRegistry.getPluginDir(pluginId); + var settingsEntry = settingsEntryPoint ? pluginManifest.entryPoints[settingsEntryPoint] : pluginManifest.entryPoints.settings; + var settingsPath = pluginDir + "/" + settingsEntry; + + settingsLoader.setSource("file://" + settingsPath, { + "pluginApi": currentPluginApi + }); + + var preferred = (settingsLoader.item && settingsLoader.item.preferredWidth !== undefined) ? settingsLoader.item.preferredWidth + padding * 2 : 0; + width = Math.max(preferred, _minWidth); + open(); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NPopupContextMenu.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NPopupContextMenu.qml new file mode 100644 index 0000000..cb1cde8 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NPopupContextMenu.qml @@ -0,0 +1,372 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons + +// Simple context menu PopupWindow (similar to TrayMenu) +// Designed to be rendered inside a PopupMenuWindow for click-outside-to-close +// Automatically positions itself to respect screen boundaries +PopupWindow { + id: root + + property alias model: repeater.model + property real itemHeight: 28 // Match TrayMenu + property real itemPadding: Style.marginM + property int verticalPolicy: ScrollBar.AsNeeded + property int horizontalPolicy: ScrollBar.AsNeeded + + property var anchorItem: null + property ShellScreen screen: null + property real minWidth: 120 + property real calculatedWidth: 180 + + // Explicit offset for centering on target item (computed from targetItem in openAtItem) + property real targetOffsetX: 0 + property real targetOffsetY: 0 + property real targetWidth: 0 + property real targetHeight: 0 + + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property real barHeight: Style.getBarHeightForScreen(screen?.name) + + signal triggered(string action, var item) + + implicitWidth: calculatedWidth + implicitHeight: Math.min(600, flickable.contentHeight + Style.margin2S) + visible: false + color: "transparent" + + NText { + id: textMeasure + visible: false + pointSize: Style.fontSizeS + wrapMode: Text.NoWrap + elide: Text.ElideNone + width: undefined + } + + NIcon { + id: iconMeasure + visible: false + icon: "bell" + pointSize: Style.fontSizeS + applyUiScale: false + } + + onModelChanged: { + Qt.callLater(calculateWidth); + } + + function calculateWidth() { + let maxWidth = 0; + if (model && model.length) { + for (let i = 0; i < model.length; i++) { + const item = model[i]; + if (item && item.visible !== false) { + const label = item.label || item.text || ""; + textMeasure.text = label; + textMeasure.forceLayout(); + + let itemWidth = textMeasure.contentWidth + 8; + + if (item.icon !== undefined) { + itemWidth += iconMeasure.width + Style.marginS; + } + + itemWidth += Style.margin2M; + + if (itemWidth > maxWidth) { + maxWidth = itemWidth; + } + } + } + } + calculatedWidth = Math.max(maxWidth + Style.margin2S, minWidth); + } + + anchor.item: anchorItem + + anchor.rect.x: { + if (anchorItem && screen) { + const anchorGlobalPos = anchorItem.mapToItem(null, 0, 0); + + // Use stored targetOffsetX and targetWidth for positioning + const effectiveWidth = targetWidth > 0 ? targetWidth : anchorItem.width; + const targetGlobalX = anchorGlobalPos.x + targetOffsetX; + + // For right bar: position menu to the left of target + if (root.barPosition === "right") { + let baseX = targetOffsetX - implicitWidth - Style.marginM; + return baseX; + } + + // For left bar: position menu to the right of target + if (root.barPosition === "left") { + let baseX = targetOffsetX + effectiveWidth + Style.marginM; + return baseX; + } + + // For top/bottom bar: center horizontally on target + const targetCenterScreenX = targetGlobalX + (effectiveWidth / 2); + const menuScreenX = targetCenterScreenX - (implicitWidth / 2); + let baseX = menuScreenX - anchorGlobalPos.x; + + const menuRight = menuScreenX + implicitWidth; + + // Adjust if menu would clip on the right + if (menuRight > screen.width - Style.marginM) { + const overflow = menuRight - (screen.width - Style.marginM); + return baseX - overflow; + } + // Adjust if menu would clip on the left + if (menuScreenX < Style.marginM) { + return baseX + (Style.marginM - menuScreenX); + } + return baseX; + } + return 0; + } + anchor.rect.y: { + if (anchorItem && screen) { + // Check if using absolute positioning (small anchor point item) + const isAbsolutePosition = anchorItem.width <= 1 && anchorItem.height <= 1; + + if (isAbsolutePosition) { + // For absolute positioning, show menu directly at anchor Y + // Only adjust if menu would clip at bottom + const anchorGlobalPos = anchorItem.mapToItem(null, 0, 0); + const menuBottom = anchorGlobalPos.y + implicitHeight; + + if (menuBottom > screen.height - Style.marginM) { + // Position above the click point instead + return -implicitHeight; + } + return 0; + } + + const anchorGlobalPos = anchorItem.mapToItem(null, 0, 0); + + // Use target offset/height for vertical bars, anchor dimensions otherwise + const effectiveHeight = targetHeight > 0 ? targetHeight : anchorItem.height; + const effectiveOffsetY = targetOffsetY; + + // Calculate base Y position based on bar orientation + let baseY; + if (root.barPosition === "bottom") { + // For bottom bar: position menu above the bar + baseY = -(implicitHeight + Style.marginS); + } else if (root.barPosition === "top") { + // For top bar: position menu below bar at consistent height + // Compensate for anchor's Y position to ensure menu top is always at (barHeight + margin) + baseY = barHeight + Style.marginS - anchorGlobalPos.y; + } else { + // For left/right bar: vertically center on target item + const targetCenterY = effectiveOffsetY + (effectiveHeight / 2); + baseY = targetCenterY - (implicitHeight / 2); + } + + const menuScreenY = anchorGlobalPos.y + baseY; + const menuBottom = menuScreenY + implicitHeight; + + // Define clipping boundaries based on bar position + const topLimit = Style.marginM; + const bottomLimit = root.barPosition === "bottom" ? screen.height - barHeight - Style.marginS : screen.height - Style.marginM; + + // Adjust if menu would clip at top (skip for bottom bar - don't push menu down over bar) + if (menuScreenY < topLimit && root.barPosition !== "bottom") { + const adjustment = topLimit - menuScreenY; + return baseY + adjustment; + } + + // Adjust if menu would clip at bottom (or overlap bar for bottom bar) + if (menuBottom > bottomLimit) { + const overflow = menuBottom - bottomLimit; + return baseY - overflow; + } + + return baseY; + } + + // Fallback if no screen + if (root.barPosition === "bottom") { + return -implicitHeight - Style.marginS; + } + return barHeight; + } + + Component.onCompleted: { + Qt.callLater(calculateWidth); + } + + Item { + anchors.fill: parent + focus: true + Keys.onEscapePressed: root.close() + } + + Rectangle { + id: menuBackground + anchors.fill: parent + color: Color.mSurface + border.color: Color.mOutline + border.width: Style.borderS + radius: Style.radiusM + opacity: root.visible ? 1.0 : 0.0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutQuad + } + } + } + + Flickable { + id: flickable + anchors.fill: parent + anchors.margins: Style.marginS + contentHeight: columnLayout.implicitHeight + interactive: true + opacity: root.visible ? 1.0 : 0.0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutQuad + } + } + + ColumnLayout { + id: columnLayout + width: flickable.width + spacing: 0 + + Repeater { + id: repeater + + delegate: Rectangle { + id: menuItem + required property var modelData + required property int index + + Layout.preferredWidth: parent.width + Layout.preferredHeight: modelData.visible !== false ? root.itemHeight : 0 + visible: modelData.visible !== false + color: "transparent" + + Rectangle { + id: innerRect + anchors.fill: parent + color: mouseArea.containsMouse ? Color.mHover : "transparent" + radius: Style.radiusS + opacity: modelData.enabled !== false ? 1.0 : 0.5 + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + RowLayout { + anchors.fill: parent + anchors.leftMargin: Style.marginM + anchors.rightMargin: Style.marginM + spacing: Style.marginS + + NIcon { + visible: modelData.icon !== undefined + icon: modelData.icon || "" + pointSize: Style.fontSizeS + applyUiScale: false + color: mouseArea.containsMouse ? Color.mOnHover : Color.mOnSurface + verticalAlignment: Text.AlignVCenter + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + NText { + text: modelData.label || modelData.text || "" + pointSize: Style.fontSizeS + color: mouseArea.containsMouse ? Color.mOnHover : Color.mOnSurface + verticalAlignment: Text.AlignVCenter + Layout.fillWidth: true + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + enabled: (modelData.enabled !== false) && root.visible + cursorShape: Qt.PointingHandCursor + + onClicked: { + if (menuItem.modelData.enabled !== false) { + root.triggered(menuItem.modelData.action || menuItem.modelData.key || menuItem.index.toString(), menuItem.modelData); + // Don't call root.close() here - let the parent PopupMenuWindow handle closing + } + } + } + } + } + } + } + } + + // Helper function to open context menu anchored to an item + // Position is calculated automatically based on bar position and screen boundaries + // Optional centerOnItem: if provided, menu will be horizontally centered on this item instead of anchorItem + function openAtItem(item, itemScreen, centerOnItem) { + if (!item) { + Logger.w("NPopupContextMenu", "anchorItem is undefined, won't show menu."); + return; + } + + // Set anchor and screen first + anchorItem = item; + screen = itemScreen || null; + + // Compute target offset and dimensions from centerOnItem + if (centerOnItem && centerOnItem !== item) { + const relPos = centerOnItem.mapToItem(item, 0, 0); + targetOffsetX = relPos.x; + targetOffsetY = relPos.y; + targetWidth = centerOnItem.width; + targetHeight = centerOnItem.height; + } else { + targetOffsetX = 0; + targetOffsetY = 0; + targetWidth = 0; + targetHeight = 0; + } + + // Calculate menu width after anchor is set + calculateWidth(); + + visible = true; + + // Force anchor recalculation after showing + Qt.callLater(() => { + anchor.updateAnchor(); + }); + } + + function close() { + visible = false; + } + + function closeMenu() { + close(); + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NRadioButton.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NRadioButton.qml new file mode 100644 index 0000000..326955d --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NRadioButton.qml @@ -0,0 +1,53 @@ +import QtQuick +import QtQuick.Controls +import qs.Commons +import qs.Widgets + +RadioButton { + id: root + + property real pointSize: Style.fontSizeM + + implicitWidth: outerCircle.implicitWidth + Style.marginS + contentItem.implicitWidth + + indicator: Rectangle { + id: outerCircle + + implicitWidth: Style.baseWidgetSize * 0.625 * pointSize / Style.fontSizeM + implicitHeight: Style.baseWidgetSize * 0.625 * pointSize / Style.fontSizeM + radius: Math.min(Style.iRadiusL, width / 2) + color: "transparent" + border.color: root.checked ? Color.mPrimary : Color.mOnSurface + border.width: Style.borderM + anchors.verticalCenter: parent.verticalCenter + + Rectangle { + anchors.fill: parent + anchors.margins: parent.width * 0.3 + + radius: Math.min(Style.iRadiusL, width / 2) + color: Qt.alpha(Color.mPrimary, root.checked ? 1 : 0) + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + contentItem: NText { + text: root.text + pointSize: root.pointSize + anchors.verticalCenter: parent.verticalCenter + anchors.left: outerCircle.right + anchors.right: parent.right + anchors.leftMargin: Style.marginS + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NReorderCheckboxes.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NReorderCheckboxes.qml new file mode 100644 index 0000000..494015e --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NReorderCheckboxes.qml @@ -0,0 +1,310 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons + +Item { + id: root + + // Public API + property var model: [] + property var disabledIds: [] + property color activeColor: Color.mPrimary + property color activeOnColor: Color.mOnPrimary + property color dragHandleColor: Color.mOutline + property int baseSize: Style.baseWidgetSize * 0.7 + property int spacing: Style.marginM + + signal itemToggled(int index, bool enabled) + signal itemsReordered(int fromIndex, int toIndex) + signal dragPotentialStarted + signal dragPotentialEnded + + readonly property real itemHeight: root.baseSize + readonly property real contentHeight: root.model.length > 0 ? root.model.length * itemHeight + (root.model.length - 1) * root.spacing : 0 + implicitHeight: contentHeight + + function toggleItem(index) { + if (index < 0 || index >= root.model.length) + return; + var item = root.model[index]; + if (item.required) + return; + + // Create a new array to trigger binding update + var newModel = root.model.slice(); + newModel[index] = Object.assign({}, item, { + "enabled": !item.enabled + }); + root.model = newModel; + + root.itemToggled(index, newModel[index].enabled); + } + + function moveItem(fromIndex, toIndex) { + if (fromIndex === toIndex) + return; + if (fromIndex < 0 || fromIndex >= root.model.length) + return; + if (toIndex < 0 || toIndex >= root.model.length) + return; + + // Create a new array with item moved + var newModel = root.model.slice(); + var item = newModel.splice(fromIndex, 1)[0]; + newModel.splice(toIndex, 0, item); + root.model = newModel; + + root.itemsReordered(fromIndex, toIndex); + } + + Item { + id: itemsContainer + anchors.fill: parent + clip: true + + Repeater { + id: repeater + model: root.model + + delegate: Item { + id: delegateItem + + width: itemsContainer.width + height: checkboxRow.height + + required property int index + required property var modelData + + property string text: modelData.text || "" + property bool itemEnabled: modelData.enabled || false + property bool required: modelData.required || false + readonly property bool isDisabled: (root.disabledIds || []).indexOf(modelData.id) !== -1 + readonly property bool canDrag: !delegateItem.isDisabled + property bool dragging: false + property int dragStartY: 0 + property int dragStartIndex: -1 + property int dragTargetIndex: -1 + property int itemSpacing: root.spacing + + RowLayout { + id: checkboxRow + + width: parent.width + spacing: Style.marginS + opacity: isDisabled ? 0.5 : 1.0 + + // Drag handle + Rectangle { + id: dragHandle + + Layout.preferredWidth: root.baseSize + Layout.preferredHeight: root.baseSize + radius: Style.iRadiusXS + color: dragHandleMouseArea.containsMouse ? Color.mSurfaceVariant : "transparent" + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + ColumnLayout { + anchors.centerIn: parent + spacing: Style.marginS + + Repeater { + model: 3 + Rectangle { + Layout.preferredWidth: root.baseSize * 0.4 + Layout.preferredHeight: 2 + radius: 1 + color: root.dragHandleColor + } + } + } + + MouseArea { + id: dragHandleMouseArea + + anchors.fill: parent + cursorShape: delegateItem.canDrag ? Qt.SizeVerCursor : Qt.ArrowCursor + hoverEnabled: true + preventStealing: false + enabled: delegateItem.canDrag + z: 1000 + + onPressed: mouse => { + if (!delegateItem.canDrag) { + return; + } + delegateItem.dragStartIndex = delegateItem.index; + delegateItem.dragTargetIndex = delegateItem.index; + delegateItem.dragStartY = delegateItem.y; + delegateItem.dragging = true; + delegateItem.z = 999; + + // Signal that interaction started (prevents panel close) + preventStealing = true; + root.dragPotentialStarted(); + } + + onPositionChanged: mouse => { + if (delegateItem.dragging) { + var dy = mouse.y - dragHandle.height / 2; + var newY = delegateItem.y + dy; + + // Constrain within bounds + newY = Math.max(0, Math.min(newY, root.contentHeight - delegateItem.height)); + delegateItem.y = newY; + + // Calculate target index (but don't apply yet) + var targetIndex = Math.floor((newY + delegateItem.height / 2) / (delegateItem.height + delegateItem.itemSpacing)); + targetIndex = Math.max(0, Math.min(targetIndex, repeater.count - 1)); + + delegateItem.dragTargetIndex = targetIndex; + } + } + + onReleased: { + // Always signal end of interaction + preventStealing = false; + root.dragPotentialEnded(); + + // Apply the model change now that drag is complete + if (delegateItem.dragStartIndex !== -1 && delegateItem.dragTargetIndex !== -1 && delegateItem.dragStartIndex !== delegateItem.dragTargetIndex) { + root.moveItem(delegateItem.dragStartIndex, delegateItem.dragTargetIndex); + } + + delegateItem.dragging = false; + delegateItem.dragStartIndex = -1; + delegateItem.dragTargetIndex = -1; + delegateItem.z = 0; + } + + onCanceled: { + // Handle cancel (e.g., ESC key pressed during drag) + preventStealing = false; + root.dragPotentialEnded(); + + delegateItem.dragging = false; + delegateItem.dragStartIndex = -1; + delegateItem.dragTargetIndex = -1; + delegateItem.z = 0; + } + } + } + + // Checkbox + Rectangle { + id: box + + Layout.preferredWidth: root.baseSize + Layout.preferredHeight: root.baseSize + radius: Style.iRadiusXS + color: delegateItem.itemEnabled ? root.activeColor : Color.mSurface + border.color: delegateItem.required ? root.activeColor : Color.mOutline + border.width: Style.borderS + opacity: delegateItem.required ? 0.7 : 1.0 + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + + NIcon { + visible: delegateItem.itemEnabled + anchors.centerIn: parent + anchors.horizontalCenterOffset: -1 + icon: "check" + color: root.activeOnColor + pointSize: Math.max(Style.fontSizeXS, root.baseSize * 0.5) + } + + MouseArea { + anchors.fill: parent + cursorShape: (!delegateItem.required && !delegateItem.isDisabled) ? Qt.PointingHandCursor : Qt.ArrowCursor + enabled: !delegateItem.required && !delegateItem.isDisabled + + onClicked: { + if (!delegateItem.required && !delegateItem.isDisabled) { + root.toggleItem(delegateItem.index); + } + } + } + } + + // Label + NText { + Layout.fillWidth: true + text: delegateItem.text + color: Color.mOnSurface + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + // Required indicator + NText { + visible: delegateItem.required + text: I18n.tr("common.required") + color: Color.mOnSurfaceVariant + verticalAlignment: Text.AlignVCenter + } + } + + // Position binding for non-dragging state + y: { + if (delegateItem.dragging) { + return delegateItem.y; + } + + // Check if any item is being dragged + var draggedIndex = -1; + var targetIndex = -1; + for (var i = 0; i < repeater.count; i++) { + var item = repeater.itemAt(i); + if (item && item.dragging) { + draggedIndex = item.dragStartIndex; + targetIndex = item.dragTargetIndex; + break; + } + } + + // If an item is being dragged, adjust positions + if (draggedIndex !== -1 && targetIndex !== -1 && draggedIndex !== targetIndex) { + var currentIndex = delegateItem.index; + + if (draggedIndex < targetIndex) { + // Dragging down: shift items up between draggedIndex and targetIndex + if (currentIndex > draggedIndex && currentIndex <= targetIndex) { + return (currentIndex - 1) * (delegateItem.height + delegateItem.itemSpacing); + } + } else { + // Dragging up: shift items down between targetIndex and draggedIndex + if (currentIndex >= targetIndex && currentIndex < draggedIndex) { + return (currentIndex + 1) * (delegateItem.height + delegateItem.itemSpacing); + } + } + } + + return delegateItem.index * (delegateItem.height + delegateItem.itemSpacing); + } + + Behavior on y { + enabled: !delegateItem.dragging + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutQuad + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NScrollText.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NScrollText.qml new file mode 100644 index 0000000..0360ea7 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NScrollText.qml @@ -0,0 +1,218 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import qs.Commons + +/* +NScrollText { +NText { +pointSize: Style.fontSizeS +// here any NText properties can be used +} +maxWidth: 200 +text: "Some long long long text" +scrollMode: NScrollText.ScrollMode.Always +} +*/ + +Item { + id: root + + required property string text + default property Component delegate: NText { + pointSize: Style.fontSizeS + } + + property real maxWidth: Infinity + + enum ScrollMode { + Never = 0, + Always = 1, + Hover = 2 + } + + property int scrollMode: NScrollText.ScrollMode.Never + property bool alwaysMaxWidth: false + property bool forcedHover: false + property int cursorShape: Qt.ArrowCursor + + property real waitBeforeScrolling: 1000 + property real scrollCycleDuration: Math.max(4000, root.text.length * 120) + property real resettingDuration: 300 + + // Fade controls (fadeExtent: 0.0โ€“0.5, fraction of width that fades) + property real fadeExtent: 0.1 + property real fadeCornerRadius: 0 + property bool fadeRoundLeftCorners: true + + readonly property real contentWidth: { + if (!titleText.item) + return 0; + const implicit = titleText.item.implicitWidth; + return implicit > 0 ? implicit : titleText.item.width; + } + readonly property real measuredWidth: scrollContainer.width + + implicitWidth: alwaysMaxWidth ? maxWidth : Math.min(maxWidth, contentWidth) + implicitHeight: titleText.height + + layer.enabled: contentWidth > maxWidth + layer.effect: MultiEffect { + maskEnabled: true + maskThresholdMin: 0.5 + maskSpreadAtMin: 1.0 + maskSource: fadeMask + } + + enum ScrollState { + None = 0, + Scrolling = 1, + Resetting = 2 + } + + property int state: NScrollText.ScrollState.None + + onTextChanged: { + if (titleText.item) + titleText.item.text = text; + if (loopingText.item) + loopingText.item.text = text; + + // reset state + resetState(); + } + onMaxWidthChanged: resetState() + onContentWidthChanged: root.updateState() + onForcedHoverChanged: updateState() + + function resetState() { + root.state = NScrollText.ScrollState.None; + scrollContainer.x = 0; + scrollTimer.restart(); + root.updateState(); + } + + Timer { + id: scrollTimer + interval: root.waitBeforeScrolling + onTriggered: { + root.state = NScrollText.ScrollState.Scrolling; + root.updateState(); + } + } + + MouseArea { + id: hoverArea + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + onEntered: root.updateState() + onExited: root.updateState() + cursorShape: root.cursorShape + } + + function ensureReset() { + if (state === NScrollText.ScrollState.Scrolling) + state = NScrollText.ScrollState.Resetting; + } + + function updateState() { + if (contentWidth <= root.maxWidth || scrollMode === NScrollText.ScrollMode.Never) { + state = NScrollText.ScrollState.None; + return; + } + if (scrollMode === NScrollText.ScrollMode.Always) { + if (hoverArea.containsMouse) { + ensureReset(); + } else { + scrollTimer.restart(); + } + } else if (scrollMode === NScrollText.ScrollMode.Hover) { + if (hoverArea.containsMouse || forcedHover) + state = NScrollText.ScrollState.Scrolling; + else + ensureReset(); + } + } + + RowLayout { + id: scrollContainer + height: parent.height + x: 0 + spacing: 50 + + Loader { + id: titleText + sourceComponent: root.delegate + Layout.fillHeight: true + onLoaded: { + this.item.text = root.text; + // Bind height to container to enable vertical centering of overly high text + this.item.height = Qt.binding(() => titleText.height); + } + } + + Loader { + id: loopingText + sourceComponent: root.delegate + Layout.fillHeight: true + visible: root.state !== NScrollText.ScrollState.None + onLoaded: { + this.item.text = root.text; + this.item.height = Qt.binding(() => loopingText.height); + } + } + + NumberAnimation on x { + running: root.state === NScrollText.ScrollState.Resetting + to: 0 + duration: root.resettingDuration + easing.type: Easing.OutQuad + onFinished: { + root.state = NScrollText.ScrollState.None; + root.updateState(); + } + } + + NumberAnimation on x { + running: root.state === NScrollText.ScrollState.Scrolling + to: -(titleText.width + scrollContainer.spacing) + duration: root.scrollCycleDuration + loops: Animation.Infinite + easing.type: Easing.Linear + } + } + + // Transparency Fade Rectangle + Rectangle { + id: fadeMask + width: root.width + height: root.height + topLeftRadius: fadeRoundLeftCorners ? fadeCornerRadius : 0 + bottomLeftRadius: fadeRoundLeftCorners ? fadeCornerRadius : 0 + topRightRadius: fadeCornerRadius + bottomRightRadius: fadeCornerRadius + gradient: Gradient { + GradientStop { + position: 0.0 + color: "transparent" + } + GradientStop { + position: fadeExtent + color: "white" + } + GradientStop { + position: 1 - fadeExtent + color: "white" + } + GradientStop { + position: 1.0 + color: "transparent" + } + orientation: Gradient.Horizontal + } + layer.enabled: true + layer.smooth: true + opacity: 0 + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NScrollView.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NScrollView.qml new file mode 100644 index 0000000..d0d1556 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NScrollView.qml @@ -0,0 +1,280 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Templates as T +import qs.Commons + +ScrollView { + id: root + + property color handleColor: Qt.alpha(Color.mHover, 0.8) + property color handleHoverColor: handleColor + property color handlePressedColor: handleColor + property color trackColor: "transparent" + property real handleWidth: Math.round(6 * Style.uiScaleRatio) + property real handleRadius: Style.iRadiusM + property int verticalPolicy: ScrollBar.AsNeeded + property int horizontalPolicy: ScrollBar.AsNeeded + property bool preventHorizontalScroll: horizontalPolicy === ScrollBar.AlwaysOff + property int boundsBehavior: Flickable.StopAtBounds + readonly property bool verticalScrollable: (contentItem.contentHeight > contentItem.height) || (verticalPolicy == ScrollBar.AlwaysOn) + readonly property bool horizontalScrollable: (contentItem.contentWidth > contentItem.width) || (horizontalPolicy == ScrollBar.AlwaysOn) + property bool showGradientMasks: true + property color gradientColor: Color.mSurfaceVariant + property int gradientHeight: 16 + property bool reserveScrollbarSpace: true + property real userRightPadding: 0 + // Keep scrollbars visible whenever overflow exists (without forcing visibility when not scrollable) + property bool showScrollbarWhenScrollable: Settings.data.ui.scrollbarAlwaysVisible + + // Scroll speed multiplier for mouse wheel (1.0 = default, higher = faster) + property real wheelScrollMultiplier: 2.0 + property int smoothWheelAnimationDuration: Style.animationNormal + property real _wheelTargetY: 0 + + function clampScrollY(value) { + if (!root._internalFlickable) + return 0; + const flickable = root._internalFlickable; + return Math.max(0, Math.min(value, flickable.contentHeight - flickable.height)); + } + + function applyWheelScroll(delta) { + if (!root._internalFlickable) + return; + + const flickable = root._internalFlickable; + const step = delta * root.wheelScrollMultiplier; + + if (!Settings.data.general.smoothScrollEnabled || Settings.data.general.animationDisabled) { + flickable.contentY = root.clampScrollY(flickable.contentY - step); + root._wheelTargetY = flickable.contentY; + return; + } + + if (!wheelScrollAnimation.running) + root._wheelTargetY = flickable.contentY; + + root._wheelTargetY = root.clampScrollY(root._wheelTargetY - step); + wheelScrollAnimation.to = root._wheelTargetY; + wheelScrollAnimation.restart(); + } + + rightPadding: userRightPadding + (reserveScrollbarSpace && verticalScrollable ? handleWidth + Style.marginXS : 0) + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, contentHeight + topPadding + bottomPadding) + + // Configure the internal flickable when it becomes available + Component.onCompleted: { + configureFlickable(); + createGradients(); + } + + // Dynamically create gradient overlays to avoid interfering with ScrollView content management + function createGradients() { + if (!showGradientMasks) + return; + + Qt.createQmlObject(` + import QtQuick + import qs.Commons + Rectangle { + x: root.leftPadding + y: root.topPadding + width: root.availableWidth + height: root.gradientHeight + z: 1 + visible: root.showGradientMasks && root.verticalScrollable + opacity: root.contentItem.contentY <= 1 ? 0 : 1 + Behavior on opacity { + NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad } + } + gradient: Gradient { + GradientStop { position: 0.0; color: root.gradientColor } + GradientStop { position: 1.0; color: "transparent" } + } + } + `, root, "topGradient"); + + Qt.createQmlObject(` + import QtQuick + import qs.Commons + Rectangle { + x: root.leftPadding + y: root.height - root.bottomPadding - height + 1 + width: root.availableWidth + height: root.gradientHeight + 1 + z: 1 + visible: root.showGradientMasks && root.verticalScrollable + opacity: (root.contentItem.contentY + root.contentItem.height >= root.contentItem.contentHeight - 1) ? 0 : 1 + Behavior on opacity { + NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad } + } + gradient: Gradient { + GradientStop { position: 0.0; color: "transparent" } + GradientStop { position: 1.0; color: root.gradientColor } + } + } + `, root, "bottomGradient"); + } + + // Reference to the internal Flickable for wheel handling + property Flickable _internalFlickable: null + + NumberAnimation { + id: wheelScrollAnimation + target: root._internalFlickable + property: "contentY" + duration: root.smoothWheelAnimationDuration + easing.type: Easing.OutCubic + } + + Connections { + target: root._internalFlickable + + function onDraggingChanged() { + if (!root._internalFlickable || !root._internalFlickable.dragging) + return; + wheelScrollAnimation.stop(); + root._wheelTargetY = root._internalFlickable.contentY; + } + + function onFlickingChanged() { + if (!root._internalFlickable || !root._internalFlickable.flicking) + return; + wheelScrollAnimation.stop(); + root._wheelTargetY = root._internalFlickable.contentY; + } + + function onContentHeightChanged() { + root._wheelTargetY = root.clampScrollY(root._wheelTargetY); + } + + function onHeightChanged() { + root._wheelTargetY = root.clampScrollY(root._wheelTargetY); + } + } + + // Function to configure the underlying Flickable + function configureFlickable() { + // Find the internal Flickable (it's usually the first child) + for (var i = 0; i < children.length; i++) { + var child = children[i]; + if (child.toString().indexOf("Flickable") !== -1) { + // Configure the flickable to prevent horizontal scrolling + child.boundsBehavior = root.boundsBehavior; + root._internalFlickable = child; + + if (root.preventHorizontalScroll) { + child.flickableDirection = Flickable.VerticalFlick; + child.contentWidth = Qt.binding(() => child.width); + } + + root._wheelTargetY = child.contentY; + break; + } + } + } + + WheelHandler { + enabled: root.wheelScrollMultiplier !== 1.0 && root._internalFlickable !== null + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onWheel: event => { + const delta = event.pixelDelta.y !== 0 ? event.pixelDelta.y : event.angleDelta.y / 2; + root.applyWheelScroll(delta); + event.accepted = true; + } + } + + // Watch for changes in horizontalPolicy + onHorizontalPolicyChanged: { + preventHorizontalScroll = (horizontalPolicy === ScrollBar.AlwaysOff); + configureFlickable(); + } + + ScrollBar.vertical: ScrollBar { + parent: root + x: root.mirrored ? 0 : root.width - width + y: root.topPadding + height: root.availableHeight + policy: root.verticalPolicy + interactive: root.verticalScrollable + + contentItem: Rectangle { + implicitWidth: root.handleWidth + implicitHeight: 100 + radius: root.handleRadius + color: parent.pressed ? root.handlePressedColor : parent.hovered ? root.handleHoverColor : root.handleColor + opacity: parent.policy === ScrollBar.AlwaysOn ? 1.0 : root.verticalScrollable ? ((root.showScrollbarWhenScrollable || parent.active) ? 1.0 : 0.0) : 0.0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + background: Rectangle { + implicitWidth: root.handleWidth + implicitHeight: 100 + color: root.trackColor + opacity: parent.policy === ScrollBar.AlwaysOn ? 0.3 : root.verticalScrollable ? ((root.showScrollbarWhenScrollable || parent.active) ? 0.3 : 0.0) : 0.0 + radius: root.handleRadius / 2 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + } + + ScrollBar.horizontal: ScrollBar { + parent: root + x: root.leftPadding + y: root.height - height + width: root.availableWidth + policy: root.horizontalPolicy + interactive: root.horizontalScrollable + + contentItem: Rectangle { + implicitWidth: 100 + implicitHeight: root.handleWidth + radius: root.handleRadius + color: parent.pressed ? root.handlePressedColor : parent.hovered ? root.handleHoverColor : root.handleColor + opacity: parent.policy === ScrollBar.AlwaysOn ? 1.0 : root.horizontalScrollable ? ((root.showScrollbarWhenScrollable || parent.active) ? 1.0 : 0.0) : 0.0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + background: Rectangle { + implicitWidth: 100 + implicitHeight: root.handleWidth + color: root.trackColor + opacity: parent.policy === ScrollBar.AlwaysOn ? 0.3 : root.horizontalScrollable ? ((root.showScrollbarWhenScrollable || parent.active) ? 0.3 : 0.0) : 0.0 + radius: root.handleRadius / 2 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NSearchableComboBox.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NSearchableComboBox.qml new file mode 100644 index 0000000..7e58930 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NSearchableComboBox.qml @@ -0,0 +1,446 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +RowLayout { + id: root + + property real minimumWidth: 280 + property real popupHeight: 180 + + property bool selectOnNavigation: true + property string label: "" + property string description: "" + property ListModel model: {} + property string currentKey: "" + property string placeholder: "" + property string searchPlaceholder: I18n.tr("placeholders.search") + property Component delegate: null + property var defaultValue: undefined + property string settingsPath: "" + + readonly property real preferredHeight: Math.round(Style.baseWidgetSize * 1.1) + + signal selected(string key) + + spacing: Style.marginL + Layout.fillWidth: true + + readonly property bool isValueChanged: (defaultValue !== undefined) && (currentKey !== defaultValue) + readonly property string indicatorTooltip: { + if (defaultValue === undefined) + return ""; + var displayValue = ""; + if (defaultValue === "") { + // Try to find the display name for empty key in the model + if (model && model.count > 0) { + for (var i = 0; i < model.count; i++) { + var item = model.get(i); + if (item && item.key === "") { + displayValue = item.name || I18n.tr("panels.indicator.system-default"); + break; + } + } + // If not found in model, show "System Default" instead of "(empty)" + if (displayValue === "") { + displayValue = I18n.tr("panels.indicator.system-default"); + } + } else { + displayValue = I18n.tr("panels.indicator.system-default"); + } + } else { + // Try to find the display name for the default key in the model + if (model && model.count > 0) { + for (var i = 0; i < model.count; i++) { + var item = model.get(i); + if (item && item.key === defaultValue) { + displayValue = item.name || String(defaultValue); + break; + } + } + if (displayValue === "") { + displayValue = String(defaultValue); + } + } else { + displayValue = String(defaultValue); + } + } + return I18n.tr("panels.indicator.default-value", { + "value": displayValue + }); + } + + // Filtered model for search results + property ListModel filteredModel: ListModel {} + property string searchText: "" + + function findIndexByKey(key) { + if (!root.model) + return -1; + for (var i = 0; i < root.model.count; i++) { + if (root.model.get(i).key === key) { + return i; + } + } + return -1; + } + + // The active model used for the popup list (source model or filtered results) + readonly property var activeModel: isFiltered ? filteredModel : root.model + + function findIndexInActiveModel(key) { + if (!activeModel || activeModel.count === undefined) + return -1; + for (var i = 0; i < activeModel.count; i++) { + if (activeModel.get(i).key === key) { + return i; + } + } + return -1; + } + + // Whether we're using filtered results or the source model directly + property bool isFiltered: false + + function filterModel() { + // Check if model exists and has items + if (!root.model || root.model.count === undefined || root.model.count === 0) { + filteredModel.clear(); + isFiltered = false; + return; + } + + var query = searchText.trim(); + if (query === "") { + // No search text - use source model directly, don't copy + filteredModel.clear(); + isFiltered = false; + return; + } + + // We have search text - need to filter + isFiltered = true; + filteredModel.clear(); + + // Convert ListModel to array for fuzzy search + var items = []; + for (var i = 0; i < root.model.count; i++) { + items.push(root.model.get(i)); + } + + // Use fuzzy search if available, fallback to simple search + if (typeof FuzzySort !== 'undefined') { + var fuzzyResults = FuzzySort.go(query, items, { + "key": "name", + "limit": 50 + }); + + // Add results in order of relevance + for (var j = 0; j < fuzzyResults.length; j++) { + filteredModel.append(fuzzyResults[j].obj); + } + } else { + // Fallback to simple search + var searchLower = query.toLowerCase(); + for (var i = 0; i < items.length; i++) { + var item = items[i]; + if (item.name.toLowerCase().includes(searchLower)) { + filteredModel.append(item); + } + } + } + } + + onSearchTextChanged: { + filterModel(); + listView.currentIndex = 0; + } + + NLabel { + label: root.label + description: root.description + showIndicator: root.isValueChanged + indicatorTooltip: root.indicatorTooltip + } + + Item { + Layout.fillWidth: true + } + + ComboBox { + id: combo + + opacity: enabled ? 1.0 : 0.6 + Layout.margins: Style.borderS + Layout.minimumWidth: Math.round(root.minimumWidth * Style.uiScaleRatio) + Layout.preferredHeight: Math.round(root.preferredHeight * Style.uiScaleRatio) + implicitWidth: Layout.minimumWidth + model: root.activeModel + textRole: "name" + currentIndex: findIndexInActiveModel(currentKey) + onActivated: { + if (combo.currentIndex >= 0 && root.activeModel && combo.currentIndex < root.activeModel.count) { + root.selected(root.activeModel.get(combo.currentIndex).key); + } + } + + background: Rectangle { + implicitWidth: Math.round(Style.baseWidgetSize * 3.75 * Style.uiScaleRatio) + implicitHeight: Math.round(root.preferredHeight * Style.uiScaleRatio) + color: Color.mSurface + border.color: combo.activeFocus ? Color.mSecondary : Color.mOutline + border.width: Style.borderS + radius: Style.iRadiusM + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + contentItem: NText { + leftPadding: Style.marginL + rightPadding: combo.indicator.width + Style.marginL + pointSize: Style.fontSizeM + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + + // Look up current selection directly in source model by key + readonly property int sourceIndex: root.findIndexByKey(root.currentKey) + readonly property bool hasSelection: root.model && sourceIndex >= 0 && sourceIndex < root.model.count + + color: hasSelection ? Color.mOnSurface : Color.mOnSurfaceVariant + text: hasSelection ? root.model.get(sourceIndex).name : root.placeholder + } + + indicator: NIcon { + x: combo.width - width - Style.marginM + y: combo.topPadding + (combo.availableHeight - height) / 2 + icon: "caret-down" + pointSize: Style.fontSizeL + } + + popup: Popup { + y: combo.height + Style.marginS + width: combo.width + height: Math.round((root.popupHeight + 60) * Style.uiScaleRatio) + padding: Style.marginM + + contentItem: ColumnLayout { + spacing: Style.marginS + + // Search input + NTextInput { + id: searchInput + inputIconName: "search" + Layout.fillWidth: true + placeholderText: root.searchPlaceholder + text: root.searchText + onTextChanged: root.searchText = text + fontSize: Style.fontSizeS + + Keys.onPressed: event => { + if (Keybinds.checkKey(event, 'enter', Settings)) { + selectHighlighted(); + combo.popup.close(); + event.accepted = true; + return; + } + + if (Keybinds.checkKey(event, 'escape', Settings)) { + combo.popup.close(); + event.accepted = true; + return; + } + + if (Keybinds.checkKey(event, 'up', Settings)) { + if (listView.currentIndex > 0) { + listView.currentIndex--; + if (root.selectOnNavigation) + selectHighlighted(); + } + event.accepted = true; + return; + } + + if (Keybinds.checkKey(event, 'down', Settings)) { + if (listView.currentIndex < listView.count - 1) { + listView.currentIndex++; + if (root.selectOnNavigation) + selectHighlighted(); + } + event.accepted = true; + return; + } + } + + function selectHighlighted() { + if (listView.currentIndex >= 0 && listView.model && listView.currentIndex < listView.model.count) { + var selectedKey = listView.model.get(listView.currentIndex).key; + root.selected(selectedKey); + } + } + } + + NListView { + id: listView + Layout.fillWidth: true + Layout.fillHeight: true + // Use activeModel (source model when not filtering, filtered results when searching) + model: combo.popup.visible ? root.activeModel : null + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AsNeeded + + onCurrentIndexChanged: { + if (currentIndex >= 0) + positionViewAtIndex(currentIndex, ListView.Contain); + } + + delegate: root.delegate ? root.delegate : defaultDelegate + + Component { + id: defaultDelegate + ItemDelegate { + id: delegateRoot + width: listView.availableWidth + leftPadding: Style.marginM + rightPadding: Style.marginM + topPadding: Style.marginS + bottomPadding: Style.marginS + hoverEnabled: true + highlighted: ListView.view.currentIndex === index + + onHoveredChanged: { + if (hovered) { + ListView.view.currentIndex = index; + } + } + + onClicked: { + var selectedKey = listView.model.get(index).key; + root.selected(selectedKey); + combo.popup.close(); + } + + contentItem: RowLayout { + width: delegateRoot.width - delegateRoot.leftPadding - delegateRoot.rightPadding + spacing: Style.marginM + + NText { + text: name + pointSize: Style.fontSizeM + color: highlighted ? Color.mOnHover : Color.mOnSurface + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + Layout.fillWidth: true + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + RowLayout { + spacing: Style.marginXXS + Layout.alignment: Qt.AlignRight + + // Generic badge renderer + Repeater { + model: { + if (typeof badges === 'undefined' || badges === null) + return 0; + // Handle both arrays and ListModels + if (typeof badges.length !== 'undefined') + return badges.length; + if (typeof badges.count !== 'undefined') + return badges.count; + return 0; + } + + delegate: NIcon { + required property int index + readonly property var badgeData: { + if (typeof badges === 'undefined' || badges === null) + return null; + // Handle both arrays and ListModels + if (typeof badges.length !== 'undefined') + return badges[index]; + if (typeof badges.get !== 'undefined') + return badges.get(index); + return null; + } + + icon: badgeData && badgeData.icon ? badgeData.icon : "" + pointSize: { + if (!badgeData || !badgeData.size) + return Style.fontSizeXS; + if (badgeData.size === "xsmall") + return Style.fontSizeXXS; + else if (badgeData.size === "medium") + return Style.fontSizeM; + else + return Style.fontSizeXS; + } + color: highlighted ? Color.mOnHover : (badgeData && badgeData.color ? badgeData.color : Color.mOnSurface) + Layout.preferredWidth: Math.round(Style.baseWidgetSize * 0.6) + Layout.preferredHeight: Math.round(Style.baseWidgetSize * 0.6) + visible: badgeData && badgeData.icon !== undefined && badgeData.icon !== "" + } + } + } + } + background: Rectangle { + anchors.fill: parent + color: highlighted ? Color.mHover : "transparent" + radius: Style.iRadiusS + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + } + } + } + } + + background: Rectangle { + color: Color.mSurfaceVariant + border.color: Color.mOutline + border.width: Style.borderS + radius: Style.iRadiusM + } + } + + // Update the currentIndex if the currentKey is changed externally + Connections { + target: root + function onCurrentKeyChanged() { + combo.currentIndex = root.findIndexInActiveModel(root.currentKey); + } + } + + // Focus search input when popup opens and ensure model is filtered + Connections { + target: combo.popup + function onVisibleChanged() { + if (combo.popup.visible) { + // Ensure the model is filtered when popup opens + filterModel(); + listView.currentIndex = Math.max(0, root.findIndexInActiveModel(root.currentKey)); + // Small delay to ensure the popup is fully rendered + Qt.callLater(() => { + if (searchInput && searchInput.inputItem) { + searchInput.inputItem.forceActiveFocus(); + } + }); + } else { + root.searchText = ""; + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NSectionEditor.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NSectionEditor.qml new file mode 100644 index 0000000..f204546 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NSectionEditor.qml @@ -0,0 +1,957 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import qs.Commons +import qs.Services.Noctalia +import qs.Widgets + +NBox { + id: root + + property string sectionName: "" + property string sectionSubtitle: "" + property string sectionId: "" + property var widgetModel: [] + property var availableWidgets: [] + property var availableSections: ["left", "center", "right"] + property var sectionLabels: ({}) // Map of sectionId -> display label + property var sectionIcons: ({}) // Map of sectionId -> icon name + property bool barIsVertical: false // When true, map left/right to top/bottom in labels + property int maxWidgets: -1 // -1 means unlimited + property bool draggable: true // Enable/disable drag reordering + property bool crossSectionDraggable: false + property alias dropTargetArea: gridContainer + + // Get display label for a section + function getSectionLabel(sectionId) { + if (sectionLabels && sectionLabels[sectionId]) { + return sectionLabels[sectionId]; + } + return sectionId; // Fallback to section ID + } + + // Get icon for a section + function getSectionIcon(sectionId) { + if (sectionIcons && sectionIcons[sectionId]) { + return sectionIcons[sectionId]; + } + return "arrow-right"; // Default fallback icon + } + + property var pluginSettingsEntryPoints: ["settings"] + property var widgetRegistry: null + property string settingsDialogComponent: "invalid-settings-dialog" + property var screen: null // Screen reference for per-screen widget settings + property var _activeDialog: null + property bool crossDropHoverActive: false + + function clearCrossSectionHover() { + crossDropHoverActive = false; + + var parentItem = root.parent; + if (!parentItem || !parentItem.children) { + return; + } + + for (var i = 0; i < parentItem.children.length; i++) { + var candidate = parentItem.children[i]; + if (!candidate || candidate.crossDropHoverActive === undefined) + continue; + candidate.crossDropHoverActive = false; + } + } + + function updateCrossSectionHover(globalX, globalY) { + var parentItem = root.parent; + if (!parentItem || !parentItem.children) { + crossDropHoverActive = false; + return; + } + + crossDropHoverActive = false; + + for (var i = 0; i < parentItem.children.length; i++) { + var candidate = parentItem.children[i]; + if (!candidate || candidate.sectionId === undefined || candidate.crossDropHoverActive === undefined) { + continue; + } + + var shouldHighlight = false; + if (candidate !== root && candidate.visible && candidate.enabled) { + var localPoint = candidate.mapFromGlobal(globalX, globalY); + var area = candidate.dropTargetArea ? candidate.dropTargetArea : candidate; + shouldHighlight = localPoint.x >= area.x && localPoint.y >= area.y && localPoint.x <= area.x + area.width && localPoint.y <= area.y + area.height; + } + + candidate.crossDropHoverActive = shouldHighlight; + } + } + + function isPointInsideSelf(globalX, globalY) { + if (globalX < 0 || globalY < 0) + return false; + var localPoint = root.mapFromGlobal(globalX, globalY); + return localPoint.x >= 0 && localPoint.y >= 0 && localPoint.x <= root.width && localPoint.y <= root.height; + } + + readonly property bool showCrossSectionDropHint: crossDropHoverActive + + Component.onDestruction: { + if (_activeDialog && _activeDialog.close) { + var dialog = _activeDialog; + _activeDialog = null; + dialog.close(); + dialog.destroy(); + } + } + + readonly property int gridColumns: 3 + readonly property real miniButtonSize: Style.baseWidgetSize * 0.65 + readonly property bool isAtMaxCapacity: maxWidgets >= 0 && widgetModel.length >= maxWidgets + readonly property real widgetItemHeight: Style.baseWidgetSize * 1.3 * Style.uiScaleRatio + + signal addWidget(string widgetId, string section) + signal removeWidget(string section, int index) + signal reorderWidget(string section, int fromIndex, int toIndex) + signal updateWidgetSettings(string section, int index, var settings) + signal moveWidget(string fromSection, int index, string toSection) + signal dragPotentialStarted + signal dragPotentialEnded + signal openPluginSettingsRequested(var pluginManifest, string settingsEntryPoint) + + color: Color.mSurface + Layout.fillWidth: true + z: flowDragArea.dragStarted ? 5000 : 0 + + // Calculate width to fit gridColumns widgets with spacing + function calculateWidgetWidth(gridWidth) { + var columnSpacing = (root.gridColumns - 1) * Style.marginM; + var widgetWidth = (gridWidth - columnSpacing) / root.gridColumns; + return Math.floor(widgetWidth); + } + + function findSectionAtGlobalPosition(globalX, globalY) { + var parentItem = root.parent; + if (!parentItem || !parentItem.children) { + return ""; + } + + for (var i = 0; i < parentItem.children.length; i++) { + var candidate = parentItem.children[i]; + if (!candidate || candidate === root) { + continue; + } + + // Only consider sibling section editors + if (candidate.sectionId === undefined || !candidate.visible || !candidate.enabled) { + continue; + } + + var localPoint = candidate.mapFromGlobal(globalX, globalY); + var area = candidate.dropTargetArea ? candidate.dropTargetArea : candidate; + if (localPoint.x >= area.x && localPoint.y >= area.y && localPoint.x <= area.x + area.width && localPoint.y <= area.y + area.height) { + return candidate.sectionId; + } + } + + return ""; + } + + Layout.minimumHeight: { + // header + minimal content area + var absoluteMin = Style.margin2L + (Style.fontSizeL * 2) + Style.marginM + (65 * Style.uiScaleRatio); + + var widgetCount = widgetModel.length; + if (widgetCount === 0) { + return absoluteMin; + } + + // Calculate rows based on grid layout + // Use actual parent width if available, otherwise estimate + var availableWidth = (parent && parent.width > 0) ? (parent.width - Style.margin2L) : 400; + var rows = Math.ceil(widgetCount / root.gridColumns); + + // Calculate widget width for height calculation + var containerWidth = availableWidth; + var widgetWidth = calculateWidgetWidth(containerWidth); + + // Header height + spacing + (rows * widget height) + (spacing between rows) + margins + var headerHeight = Style.fontSizeL * 2; + // Account for grid margins + var gridTopMargin = Style.marginS; + var gridBottomMargin = Style.marginL; + var widgetAreaHeight = gridTopMargin + (rows * widgetItemHeight) + ((rows - 1) * Style.marginL) + gridBottomMargin; + + return Math.max(absoluteMin, Style.margin2L + headerHeight + Style.marginM + widgetAreaHeight); + } + + // Generate widget color from name checksum + function getWidgetColor(widget) { + if (widget.id.startsWith('plugin:')) { + return [Color.mSecondary, Color.mOnSecondary]; + } + return [Color.mPrimary, Color.mOnPrimary]; + } + + // Check if widget has settings (either core widget with metadata or plugin with settings entry point) + function widgetHasSettings(widgetId) { + // Check if it's a plugin with settings + if (root.widgetRegistry && root.widgetRegistry.isPluginWidget(widgetId)) { + var pluginId = widgetId.replace("plugin:", ""); + var manifest = PluginRegistry.getPluginManifest(pluginId); + if (!manifest?.entryPoints) + return false; + for (var i = 0; i < root.pluginSettingsEntryPoints.length; i++) { + if (manifest.entryPoints[root.pluginSettingsEntryPoints[i]] !== undefined) + return true; + } + return false; + } + + // Check if it's a core widget with user settings + if (root.widgetRegistry && root.widgetRegistry.widgetHasUserSettings(widgetId)) { + return true; + } + + return false; + } + + // Open settings for a widget + function openWidgetSettings(index, widgetData) { + // Check if this is a plugin widget with a generic "settings" entry point + var isPlugin = root.widgetRegistry && root.widgetRegistry.isPluginWidget(widgetData.id); + + if (isPlugin) { + var pluginId = widgetData.id.replace("plugin:", ""); + var manifest = PluginRegistry.getPluginManifest(pluginId); + + var settingsKey = null; + if (manifest?.entryPoints) { + for (var i = 0; i < root.pluginSettingsEntryPoints.length; i++) { + if (manifest.entryPoints[root.pluginSettingsEntryPoints[i]] !== undefined) { + settingsKey = root.pluginSettingsEntryPoints[i]; + break; + } + } + } + if (!manifest || !settingsKey) { + Logger.e("NSectionEditor", "Plugin settings not found for:", pluginId); + return; + } + + // "desktopWidgetSettings" is handled by the settingsDialogComponent + // (DesktopWidgetSettingsDialog) which passes widgetSettings/save properly. + // Only generic "settings" goes through the plugin settings popup. + if (settingsKey !== "desktopWidgetSettings") { + root.openPluginSettingsRequested(manifest, settingsKey); + return; + } + } + + // Handle core widgets and plugin desktop widget settings + { + var component = Qt.createComponent(Qt.resolvedUrl(root.settingsDialogComponent)); + + function instantiateAndOpen() { + if (root._activeDialog) { + try { + root._activeDialog.close(); + root._activeDialog.destroy(); + } catch (e) {} + root._activeDialog = null; + } + + var dialog = component.createObject(Overlay.overlay, { + "widgetIndex": index, + "widgetData": widgetData, + "widgetId": widgetData.id, + "sectionId": root.sectionId, + "screen": root.screen + }); + + if (dialog) { + root._activeDialog = dialog; + dialog.updateWidgetSettings.connect(root.updateWidgetSettings); + dialog.closed.connect(() => { + if (root._activeDialog === dialog) { + root._activeDialog = null; + dialog.destroy(); + } + }); + dialog.open(); + } else { + Logger.e("NSectionEditor", "Failed to create settings dialog instance"); + } + } + + if (component.status === Component.Ready) { + instantiateAndOpen(); + } else if (component.status === Component.Error) { + Logger.e("NSectionEditor", component.errorString()); + } else { + component.statusChanged.connect(function () { + if (component.status === Component.Ready) { + instantiateAndOpen(); + } else if (component.status === Component.Error) { + Logger.e("NSectionEditor", component.errorString()); + } + }); + } + } + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + Layout.rightMargin: Style.marginS + + ColumnLayout { + spacing: Style.marginXXS + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: false + Layout.leftMargin: Style.marginS + Layout.maximumWidth: { + // Reserve space for other elements: count indicator, combo box (~200), button (~50), and margins + // Use a reasonable maximum that leaves room for controls on the right + // On smaller screens, use a smaller percentage to ensure controls fit + var rowLayout = parent; + if (rowLayout && rowLayout.width > 0) { + // Use smaller percentage on smaller screens, but ensure minimum space for text + var minWidth = 150 * Style.uiScaleRatio; + var maxWidth = rowLayout.width < 600 ? rowLayout.width * 0.35 : rowLayout.width * 0.4; + return Math.max(minWidth, maxWidth); + } + return 250 * Style.uiScaleRatio; + } + + NText { + text: sectionName + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + elide: Text.ElideRight + Layout.fillWidth: true + } + + NText { + visible: sectionSubtitle !== "" + text: sectionSubtitle + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + elide: Text.ElideRight + Layout.fillWidth: true + } + } + + // Widget count indicator (when max is set) + NText { + visible: root.maxWidgets >= 0 + text: root.maxWidgets === 0 ? "(LOCKED)" : "(" + widgetModel.length + "/" + root.maxWidgets + ")" + pointSize: Style.fontSizeS + color: root.isAtMaxCapacity ? Color.mError : Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: Style.marginXS + } + + Item { + Layout.fillWidth: true + } + + NSearchableComboBox { + id: comboBox + model: availableWidgets ?? null + label: "" + description: "" + placeholder: I18n.tr("bar.section-editor.placeholder") + searchPlaceholder: I18n.tr("bar.section-editor.search-placeholder") + onSelected: key => comboBox.currentKey = key + popupHeight: 300 * Style.uiScaleRatio + minimumWidth: 200 * Style.uiScaleRatio + enabled: !root.isAtMaxCapacity + + Layout.alignment: Qt.AlignVCenter + + // Re-filter when the model count changes (when widgets are loaded) + Connections { + target: availableWidgets ?? null + ignoreUnknownSignals: true + function onCountChanged() { + // Trigger a re-filter by clearing and re-setting the search text + var currentSearch = comboBox.searchText; + comboBox.searchText = ""; + comboBox.searchText = currentSearch; + } + } + } + + NIconButton { + icon: "add" + colorBg: Color.mPrimary + colorFg: Color.mOnPrimary + colorBgHover: Color.mSecondary + colorFgHover: Color.mOnSecondary + enabled: comboBox.currentKey !== "" && !root.isAtMaxCapacity + tooltipText: root.isAtMaxCapacity ? I18n.tr("tooltips.max-widgets-reached") : I18n.tr("tooltips.add-widget") + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: Style.marginS + onClicked: { + if (comboBox.currentKey !== "" && !root.isAtMaxCapacity) { + addWidget(comboBox.currentKey, sectionId); + comboBox.currentKey = ""; + } + } + } + } + + // Drag and Drop Widget Area + Item { + id: gridContainer + Layout.fillWidth: true + Layout.preferredHeight: { + if (widgetModel.length === 0) { + return 65 * Style.uiScaleRatio; + } + // Use actual width, fallback to a reasonable default if not yet available + var containerWidth = width > 0 ? width : (parent ? parent.width : 400); + var rows = Math.ceil(widgetModel.length / root.gridColumns); + // Calculate height: (rows * item height) + (row spacing between items) + grid margins + var gridTopMargin = Style.marginS; + var gridBottomMargin = Style.marginS; + var calculatedHeight = gridTopMargin + (rows * root.widgetItemHeight) + ((rows - 1) * Style.marginS) + gridBottomMargin; + return calculatedHeight; + } + Layout.minimumHeight: widgetModel.length === 0 ? (65 * Style.uiScaleRatio) : (Style.margin2S + root.widgetItemHeight) + clip: !flowDragArea.dragStarted + + Rectangle { + anchors.fill: parent + radius: Style.iRadiusL + color: Qt.alpha(Color.mSecondary, 0.12) + border.color: Color.mSecondary + border.width: Style.borderM + visible: root.showCrossSectionDropHint + z: 1500 + } + + Grid { + id: widgetGrid + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.leftMargin: Style.marginS + anchors.rightMargin: Style.marginS + anchors.topMargin: Style.marginS + anchors.bottomMargin: Style.marginS + columns: root.gridColumns + rowSpacing: Style.marginS + columnSpacing: Style.marginM + + Repeater { + id: widgetRepeater + model: widgetModel + + delegate: Rectangle { + id: widgetItem + required property int index + required property var modelData + + width: root.calculateWidgetWidth(parent.width) + height: root.widgetItemHeight + radius: Style.iRadiusL + color: root.getWidgetColor(modelData)[0] + border.color: Color.mOutline + border.width: Style.borderS + + // Store the widget index for drag operations + property int widgetIndex: index + readonly property int buttonsWidth: Math.round(20) + readonly property int buttonsCount: root.widgetHasSettings(modelData.id) ? 1 : 0 + + // Visual feedback during drag + opacity: flowDragArea.draggedIndex === index ? 0.5 : 1.0 + scale: flowDragArea.draggedIndex === index ? 0.95 : 1.0 + z: flowDragArea.draggedIndex === index ? 1000 : 0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + Behavior on scale { + NumberAnimation { + duration: Style.animationFast + } + } + + // Context menu for moving widget to other sections + NContextMenu { + id: contextMenu + parent: Overlay.overlay + width: 240 * Style.uiScaleRatio + model: { + var items = []; + // Add move options for each available section (except current) + for (var i = 0; i < root.availableSections.length; i++) { + var section = root.availableSections[i]; + if (section !== root.sectionId) { + var label = root.getSectionLabel(section); + var displayLabel = ''; + // Map section IDs to correct position keys based on bar orientation + var positionKey = section; + if (root.barIsVertical) { + if (section === "left") + positionKey = "top"; + else if (section === "right") + positionKey = "bottom"; + } + if (I18n.hasTranslation("positions." + positionKey)) { + displayLabel = I18n.tr("positions." + positionKey); + } else { + displayLabel = label.charAt(0).toUpperCase() + label.slice(1); + } + + items.push({ + "label": I18n.tr("tooltips.move-to-section", { + "section": displayLabel + }), + "action": section, + "icon": root.getSectionIcon(section), + "visible": true + }); + } + } + // Add remove option + items.push({ + "label": I18n.tr("tooltips.remove-widget"), + "action": "remove", + "icon": "trash", + "visible": true + }); + return items; + } + + onTriggered: action => { + if (action === "remove") { + root.removeWidget(root.sectionId, widgetItem.index); + } else { + root.moveWidget(root.sectionId, widgetItem.index, action); + } + } + } + + // MouseArea for the context menu - only active when not dragging + MouseArea { + id: contextMouseArea + anchors.fill: parent + acceptedButtons: Qt.RightButton + cursorShape: Qt.PointingHandCursor + z: -1 // Below the buttons but above background + enabled: !flowDragArea.dragStarted && !flowDragArea.potentialDrag + + onPressed: mouse => { + mouse.accepted = true; + // Check if click is not on the settings button area (if visible) + const localX = mouse.x; + const buttonsStartX = parent.width - (parent.buttonsCount * parent.buttonsWidth); + if (localX < buttonsStartX || parent.buttonsCount === 0) { + contextMenu.openAtItem(widgetItem, mouse.x, mouse.y); + } + } + } + + RowLayout { + id: widgetContent + anchors.fill: parent + anchors.margins: Style.marginXS + anchors.rightMargin: Style.marginS + spacing: Style.marginXXS + + NText { + text: { + // For plugin widgets, get the actual plugin name from manifest + if (root.widgetRegistry && root.widgetRegistry.isPluginWidget(modelData.id)) { + const pluginId = modelData.id.replace("plugin:", ""); + const manifest = PluginRegistry.getPluginManifest(pluginId); + if (manifest && manifest.name) { + return manifest.name; + } + // Fallback: just strip the prefix + return pluginId; + } + return modelData.id; + } + pointSize: Style.fontSizeXS + color: root.getWidgetColor(modelData)[1] + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + leftPadding: Style.marginS + rightPadding: Style.marginS + Layout.fillWidth: true + Layout.fillHeight: true + } + + // Plugin indicator icon + NIcon { + visible: root.widgetRegistry && root.widgetRegistry.isPluginWidget(modelData.id) + icon: "plugin" + pointSize: Style.fontSizeXXS + color: root.getWidgetColor(modelData)[1] + Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.5 : 0 + Layout.preferredHeight: Style.baseWidgetSize * 0.5 + } + + // CPU intensive indicator icon + NIcon { + visible: root.widgetRegistry && root.widgetRegistry.isCpuIntensive(modelData.id) + icon: "cpu-intensive" + pointSize: Style.fontSizeXXS + color: root.getWidgetColor(modelData)[1] + Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.5 : 0 + Layout.preferredHeight: Style.baseWidgetSize * 0.5 + } + + RowLayout { + spacing: 0 + Layout.preferredWidth: buttonsCount * buttonsWidth * Style.uiScaleRatio + Layout.preferredHeight: parent.height + + Loader { + active: root.widgetHasSettings(modelData.id) && root.enabled + sourceComponent: NIconButton { + icon: "settings" + tooltipText: I18n.tr("actions.widget-settings") + baseSize: miniButtonSize + colorBorder: Qt.alpha(Color.mOutline, Style.opacityLight) + colorBg: Color.mOnSurface + colorFg: Color.mOnPrimary + colorBgHover: Qt.alpha(Color.mOnPrimary, Style.opacityLight) + colorFgHover: Color.mOnPrimary + onClicked: { + root.openWidgetSettings(index, modelData); + } + } + } + } + } + } + } + } + + // Ghost/Clone widget for dragging + Rectangle { + id: dragGhost + width: 0 + height: Style.baseWidgetSize * 1.15 + radius: Style.iRadiusL + color: "transparent" + border.color: Color.mOutline + border.width: Style.borderS + opacity: 0.7 + visible: flowDragArea.dragStarted + z: 2000 + clip: false // Ensure ghost isn't clipped + + NText { + id: ghostText + anchors.centerIn: parent + pointSize: Style.fontSizeS + color: Color.mOnPrimary + } + } + + // Drop indicator - visual feedback for where the widget will be inserted + Rectangle { + id: dropIndicator + width: 3 + height: Style.baseWidgetSize * 1.15 + radius: Style.iRadiusXXS + color: Color.mSecondary + opacity: 0 + visible: opacity > 0 + z: 1999 + + SequentialAnimation on opacity { + id: pulseAnimation + running: false + loops: Animation.Infinite + NumberAnimation { + to: 1 + duration: 400 + easing.type: Easing.InOutQuad + } + NumberAnimation { + to: 0.6 + duration: 400 + easing.type: Easing.InOutQuad + } + } + + Behavior on x { + NumberAnimation { + duration: 100 + easing.type: Easing.OutCubic + } + } + Behavior on y { + NumberAnimation { + duration: 100 + easing.type: Easing.OutCubic + } + } + } + + // MouseArea for drag and drop + MouseArea { + id: flowDragArea + anchors.fill: parent + z: 100 // Above widgets to ensure it captures events first + enabled: root.draggable || root.crossSectionDraggable + + acceptedButtons: Qt.LeftButton + preventStealing: true // Always prevent stealing to ensure we get all events + propagateComposedEvents: true // Allow events to propagate when not handled + hoverEnabled: potentialDrag || dragStarted // Only track hover during drag operations + cursorShape: dragStarted ? Qt.ClosedHandCursor : Qt.ArrowCursor + + property point startPos: Qt.point(0, 0) + property bool dragStarted: false + property bool potentialDrag: false // Track if we're in a potential drag interaction + property int draggedIndex: -1 + property real dragThreshold: 8 // Reduced threshold for more responsive drag + property Item draggedWidget: null + property int dropTargetIndex: -1 + property var draggedModelData: null + property bool isOverButtonArea: false + + // Drop position calculation + // Map widget coordinates from grid-local to gridContainer coordinates + function mapWidgetCoords(widget) { + return { + x: widget.x + widgetGrid.x, + y: widget.y + widgetGrid.y, + width: widget.width, + height: widget.height + }; + } + + function updateDropIndicator(mouseX, mouseY) { + if (!dragStarted || draggedIndex === -1) { + dropIndicator.opacity = 0; + pulseAnimation.running = false; + return; + } + + let bestIndex = -1; + let bestPosition = null; + let minDistance = Infinity; + + // Check position relative to each widget + for (var i = 0; i < widgetModel.length; i++) { + if (i === draggedIndex) + continue; + const widget = widgetRepeater.itemAt(i); + if (!widget || widget.widgetIndex === undefined) + continue; + + const mapped = mapWidgetCoords(widget); + + // Check distance to left edge (insert before) + const leftDist = Math.sqrt(Math.pow(mouseX - mapped.x, 2) + Math.pow(mouseY - (mapped.y + mapped.height / 2), 2)); + + // Check distance to right edge (insert after) + const rightDist = Math.sqrt(Math.pow(mouseX - (mapped.x + mapped.width), 2) + Math.pow(mouseY - (mapped.y + mapped.height / 2), 2)); + + if (leftDist < minDistance) { + minDistance = leftDist; + bestIndex = i; + bestPosition = Qt.point(mapped.x - dropIndicator.width / 2 - Style.marginXS, mapped.y); + } + + if (rightDist < minDistance) { + minDistance = rightDist; + bestIndex = i + 1; + bestPosition = Qt.point(mapped.x + mapped.width + Style.marginXS - dropIndicator.width / 2, mapped.y); + } + } + + // Check if we should insert at position 0 (very beginning) + if (widgetModel.length > 0 && draggedIndex !== 0) { + const firstWidget = widgetRepeater.itemAt(0); + if (firstWidget) { + const mapped = mapWidgetCoords(firstWidget); + const dist = Math.sqrt(Math.pow(mouseX - mapped.x, 2) + Math.pow(mouseY - mapped.y, 2)); + if (dist < minDistance && mouseX < mapped.x + mapped.width / 2) { + minDistance = dist; + bestIndex = 0; + // Position indicator to the left of the first widget + bestPosition = Qt.point(mapped.x - dropIndicator.width / 2 - Style.marginXS, mapped.y); + } + } + } + + // Only show indicator if we're close enough and it's a different position + if (minDistance < 80 && bestIndex !== -1) { + // Adjust index if we're moving forward + let adjustedIndex = bestIndex; + if (bestIndex > draggedIndex) { + adjustedIndex = bestIndex - 1; + } + + // Don't show if it's the same position + if (adjustedIndex === draggedIndex) { + dropIndicator.opacity = 0; + pulseAnimation.running = false; + dropTargetIndex = -1; + return; + } + + dropTargetIndex = adjustedIndex; + if (bestPosition) { + dropIndicator.x = bestPosition.x; + dropIndicator.y = bestPosition.y; + dropIndicator.opacity = 1; + if (!pulseAnimation.running) { + pulseAnimation.running = true; + } + } + } else { + dropIndicator.opacity = 0; + pulseAnimation.running = false; + dropTargetIndex = -1; + } + } + + function resetDragState() { + root.clearCrossSectionHover(); + dragStarted = false; + potentialDrag = false; + draggedIndex = -1; + draggedWidget = null; + dropTargetIndex = -1; + draggedModelData = null; + isOverButtonArea = false; + dropIndicator.opacity = 0; + pulseAnimation.running = false; + dragGhost.width = 0; + } + + onPressed: mouse => { + // Reset state + startPos = Qt.point(mouse.x, mouse.y); + dragStarted = false; + potentialDrag = false; + draggedIndex = -1; + draggedWidget = null; + dropTargetIndex = -1; + draggedModelData = null; + isOverButtonArea = false; + + // Find which widget was clicked + for (var i = 0; i < widgetModel.length; i++) { + const widget = widgetRepeater.itemAt(i); + if (widget && widget.widgetIndex !== undefined) { + if (mouse.x >= widget.x && mouse.x <= widget.x + widget.width && mouse.y >= widget.y && mouse.y <= widget.y + widget.height) { + const localX = mouse.x - widget.x; + const buttonsStartX = widget.width - (widget.buttonsCount * widget.buttonsWidth * Style.uiScaleRatio); + + if (localX >= buttonsStartX && widget.buttonsCount > 0) { + // Click is on button area - don't start drag, propagate event + isOverButtonArea = true; + mouse.accepted = false; + return; + } else { + // This is a draggable area + draggedIndex = widget.widgetIndex; + draggedWidget = widget; + draggedModelData = widget.modelData; + potentialDrag = true; + mouse.accepted = true; + + // Signal that interaction started (prevents panel close) + root.dragPotentialStarted(); + return; + } + } + } + } + + // Click was not on any widget + mouse.accepted = false; + } + + onPositionChanged: mouse => { + if (potentialDrag && draggedIndex !== -1) { + const deltaX = mouse.x - startPos.x; + const deltaY = mouse.y - startPos.y; + const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + + // Start drag if threshold exceeded + if (!dragStarted && distance > dragThreshold) { + dragStarted = true; + + // Setup ghost widget + if (draggedWidget) { + dragGhost.width = draggedWidget.width; + dragGhost.color = root.getWidgetColor(draggedModelData)[0]; + ghostText.text = draggedModelData.id; + } + + var startGlobal = flowDragArea.mapToGlobal(mouse.x, mouse.y); + root.updateCrossSectionHover(startGlobal.x, startGlobal.y); + } + + if (dragStarted) { + // Move ghost widget + dragGhost.x = mouse.x - dragGhost.width / 2; + dragGhost.y = mouse.y - dragGhost.height / 2; + + var moveGlobal = flowDragArea.mapToGlobal(mouse.x, mouse.y); + root.updateCrossSectionHover(moveGlobal.x, moveGlobal.y); + + // Update drop indicator + updateDropIndicator(mouse.x, mouse.y); + } + } + } + + onReleased: mouse => { + if (root.draggable && dragStarted && dropTargetIndex !== -1 && dropTargetIndex !== draggedIndex) { + // Perform the reorder + reorderWidget(sectionId, draggedIndex, dropTargetIndex); + } else if (dragStarted && draggedIndex !== -1) { + var globalPos = flowDragArea.mapToGlobal(mouse.x, mouse.y); + var targetSectionId = root.findSectionAtGlobalPosition(globalPos.x, globalPos.y); + if (targetSectionId !== "" && targetSectionId !== root.sectionId) { + root.moveWidget(root.sectionId, draggedIndex, targetSectionId); + } + } + + // Always signal end of interaction if we started one + if (potentialDrag) { + root.dragPotentialEnded(); + } + + // Reset everything + resetDragState(); + mouse.accepted = true; + } + + onCanceled: { + // Handle cancel (e.g., ESC key pressed during drag) + if (potentialDrag) { + root.dragPotentialEnded(); + } + + resetDragState(); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NSettingsIndicator.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NSettingsIndicator.qml new file mode 100644 index 0000000..b1aeb80 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NSettingsIndicator.qml @@ -0,0 +1,47 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Services.UI + +Rectangle { + id: root + + property bool show: false + property var tooltipText + + implicitWidth: root.show ? 6 * Style.uiScaleRatio : 0 + implicitHeight: root.show ? 6 * Style.uiScaleRatio : 0 + width: root.show ? 6 * Style.uiScaleRatio : 0 + height: root.show ? 6 * Style.uiScaleRatio : 0 + radius: width / 2 + color: Color.mOnSurfaceVariant + opacity: 0.6 + + visible: root.show + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + + MouseArea { + enabled: root.show && root.tooltipText && (!Array.isArray(root.tooltipText) || root.tooltipText.length > 0) + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + cursorShape: Qt.PointingHandCursor + + onEntered: { + if (root.tooltipText && (!Array.isArray(root.tooltipText) || root.tooltipText.length > 0)) { + TooltipService.show(root, root.tooltipText); + } + } + + onExited: { + if (root.tooltipText && (!Array.isArray(root.tooltipText) || root.tooltipText.length > 0)) { + TooltipService.hide(); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NSlideSwapView.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NSlideSwapView.qml new file mode 100644 index 0000000..f37f14a --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NSlideSwapView.qml @@ -0,0 +1,135 @@ +import QtQuick +import qs.Commons + +Item { + id: root + + property Component sourceComponent + property bool animationsEnabled: true + property int duration: Style.animationNormal + property real transitionGap: Style.marginXL + property real incomingStartOpacity: 0.0 + property real outgoingTargetOpacity: 0.25 + + readonly property var item: contentLoader.item + readonly property bool running: _running + + property bool _running: false + property var _pendingApplyChange: null + property real _contentOffset: 0 + property real _contentOpacity: 1 + property real _snapshotOffset: 0 + property real _snapshotOpacity: 0 + property real _snapshotTargetOffset: 0 + + clip: true + + function resetVisuals() { + _running = false; + _pendingApplyChange = null; + _contentOffset = 0; + _contentOpacity = 1; + _snapshotOffset = 0; + _snapshotOpacity = 0; + snapshot.visible = false; + transition.stop(); + } + + function swap(direction, applyChange) { + if (!animationsEnabled || width <= 0 || height <= 0 || direction === 0) { + if (applyChange) + applyChange(); + return; + } + + if (_running) + resetVisuals(); + + const slideDistance = Math.max(1, width + transitionGap); + const movingForward = direction > 0; + + snapshot.visible = true; + _snapshotOffset = 0; + _snapshotOpacity = 1; + _snapshotTargetOffset = movingForward ? -slideDistance : slideDistance; + + _contentOffset = movingForward ? slideDistance : -slideDistance; + _contentOpacity = incomingStartOpacity; + + _pendingApplyChange = applyChange || null; + _running = true; + snapshot.scheduleUpdate(); + + Qt.callLater(() => { + if (!_running) { + return; + } + + const applyFn = _pendingApplyChange; + _pendingApplyChange = null; + const shouldAnimate = applyFn ? applyFn() !== false : true; + if (!shouldAnimate) { + resetVisuals(); + return; + } + transition.restart(); + }); + } + + ShaderEffectSource { + id: snapshot + visible: false + width: parent.width + height: parent.height + y: 0 + sourceItem: contentLoader + hideSource: false + live: false + smooth: true + z: 2 + x: root._snapshotOffset + opacity: root._snapshotOpacity + } + + Loader { + id: contentLoader + width: parent.width + height: parent.height + x: root._contentOffset + opacity: root._contentOpacity + sourceComponent: root.sourceComponent + } + + ParallelAnimation { + id: transition + NumberAnimation { + target: root + property: "_contentOffset" + to: 0 + duration: root.duration + easing.type: Easing.OutCubic + } + NumberAnimation { + target: root + property: "_contentOpacity" + to: 1 + duration: root.duration + easing.type: Easing.OutCubic + } + NumberAnimation { + target: root + property: "_snapshotOffset" + to: root._snapshotTargetOffset + duration: root.duration + easing.type: Easing.OutCubic + } + NumberAnimation { + target: root + property: "_snapshotOpacity" + to: root.outgoingTargetOpacity + duration: root.duration + easing.type: Easing.OutCubic + } + onFinished: root.resetVisuals() + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NSlider.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NSlider.qml new file mode 100644 index 0000000..c30d6b6 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NSlider.qml @@ -0,0 +1,253 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Shapes +import qs.Commons +import qs.Services.UI + +Slider { + id: root + + readonly property bool sliderActive: activeFocus || pressed + property color fillColor: Color.mPrimary + property var cutoutColor: Color.mSurface + property bool snapAlways: true + property real heightRatio: 0.7 + property var tooltipText + property string tooltipDirection: "auto" + property bool hovering: false + + readonly property color effectiveFillColor: enabled ? fillColor : Color.mOutline + + readonly property real knobDiameter: Math.round((Style.baseWidgetSize * heightRatio * Style.uiScaleRatio) / 2) * 2 + readonly property real trackHeight: Math.round((knobDiameter * 0.4 * Style.uiScaleRatio) / 2) * 2 + readonly property real trackRadius: Math.min(Style.iRadiusL, trackHeight / 2) + readonly property real cutoutExtra: Math.round((Style.baseWidgetSize * 0.1 * Style.uiScaleRatio) / 2) * 2 + + padding: cutoutExtra / 2 + + snapMode: snapAlways ? Slider.SnapAlways : Slider.SnapOnRelease + implicitHeight: Math.max(trackHeight, knobDiameter) + + background: Item { + id: bgContainer + x: root.leftPadding + y: root.topPadding + Style.pixelAlignCenter(root.availableHeight, root.trackHeight) + implicitWidth: Style.sliderWidth + implicitHeight: root.trackHeight + width: root.availableWidth + height: root.trackHeight + + readonly property real fillWidth: root.visualPosition * width + + // Background track + Shape { + anchors.fill: parent + visible: bgContainer.width > 0 && bgContainer.height > 0 + preferredRendererType: Shape.CurveRenderer + asynchronous: true + + ShapePath { + id: bgPath + strokeColor: Qt.alpha(Color.mOutline, 0.5) + strokeWidth: Style.borderS + fillColor: Qt.alpha(Color.mSurface, 0.5) + + readonly property real w: bgContainer.width + readonly property real h: bgContainer.height + readonly property real r: root.trackRadius + + startX: r + startY: 0 + + PathLine { + x: bgPath.w - bgPath.r + y: 0 + } + PathArc { + x: bgPath.w + y: bgPath.r + radiusX: bgPath.r + radiusY: bgPath.r + } + PathLine { + x: bgPath.w + y: bgPath.h - bgPath.r + } + PathArc { + x: bgPath.w - bgPath.r + y: bgPath.h + radiusX: bgPath.r + radiusY: bgPath.r + } + PathLine { + x: bgPath.r + y: bgPath.h + } + PathArc { + x: 0 + y: bgPath.h - bgPath.r + radiusX: bgPath.r + radiusY: bgPath.r + } + PathLine { + x: 0 + y: bgPath.r + } + PathArc { + x: bgPath.r + y: 0 + radiusX: bgPath.r + radiusY: bgPath.r + } + } + } + + LinearGradient { + id: fillGradient + x1: 0 + y1: 0 + x2: root.availableWidth + y2: 0 + GradientStop { + position: 0.0 + color: Qt.darker(effectiveFillColor, 1.2) + } + GradientStop { + position: 1.0 + color: effectiveFillColor + } + } + + // Active/filled track + Shape { + width: bgContainer.fillWidth + height: bgContainer.height + visible: bgContainer.fillWidth > 0 && bgContainer.height > 0 + preferredRendererType: Shape.CurveRenderer + asynchronous: true + clip: true + + ShapePath { + id: fillPath + strokeColor: "transparent" + fillGradient: fillGradient + + readonly property real fullWidth: root.availableWidth + readonly property real h: root.trackHeight + readonly property real r: root.trackRadius + + startX: r + startY: 0 + + PathLine { + x: fillPath.fullWidth - fillPath.r + y: 0 + } + PathArc { + x: fillPath.fullWidth + y: fillPath.r + radiusX: fillPath.r + radiusY: fillPath.r + } + PathLine { + x: fillPath.fullWidth + y: fillPath.h - fillPath.r + } + PathArc { + x: fillPath.fullWidth - fillPath.r + y: fillPath.h + radiusX: fillPath.r + radiusY: fillPath.r + } + PathLine { + x: fillPath.r + y: fillPath.h + } + PathArc { + x: 0 + y: fillPath.h - fillPath.r + radiusX: fillPath.r + radiusY: fillPath.r + } + PathLine { + x: 0 + y: fillPath.r + } + PathArc { + x: fillPath.r + y: 0 + radiusX: fillPath.r + radiusY: fillPath.r + } + } + } + + // Circular cutout + Rectangle { + id: knobCutout + implicitWidth: root.knobDiameter + root.cutoutExtra + implicitHeight: root.knobDiameter + root.cutoutExtra + radius: Math.min(Style.iRadiusL, width / 2) + color: root.cutoutColor !== undefined ? root.cutoutColor : Color.mSurface + x: root.visualPosition * (root.availableWidth - root.knobDiameter) - root.cutoutExtra / 2 + anchors.verticalCenter: parent.verticalCenter + } + } + + handle: Item { + implicitWidth: knobDiameter + implicitHeight: knobDiameter + x: root.leftPadding + root.visualPosition * (root.availableWidth - width) + anchors.verticalCenter: parent.verticalCenter + + Rectangle { + id: knob + implicitWidth: knobDiameter + implicitHeight: knobDiameter + radius: Math.min(Style.iRadiusL, width / 2) + color: root.pressed ? Color.mHover : Color.mSurface + border.color: effectiveFillColor + border.width: Style.borderL + anchors.centerIn: parent + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + MouseArea { + enabled: true + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + acceptedButtons: Qt.NoButton // Don't accept any mouse buttons - only hover + propagateComposedEvents: true + + onEntered: { + root.hovering = true; + if (root.tooltipText && (!Array.isArray(root.tooltipText) || root.tooltipText.length > 0)) { + TooltipService.show(knob, root.tooltipText, root.tooltipDirection); + } + } + + onExited: { + root.hovering = false; + if (root.tooltipText && (!Array.isArray(root.tooltipText) || root.tooltipText.length > 0)) { + TooltipService.hide(); + } + } + } + + // Hide tooltip when slider is pressed (anywhere on the slider) + Connections { + target: root + function onPressedChanged() { + if (root.pressed && root.tooltipText && (!Array.isArray(root.tooltipText) || root.tooltipText.length > 0)) { + TooltipService.hide(); + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NSpinBox.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NSpinBox.qml new file mode 100644 index 0000000..15c3e16 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NSpinBox.qml @@ -0,0 +1,314 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +RowLayout { + id: root + + // Public properties + property int value: 0 + property int from: 0 + property int to: 100 + property int stepSize: 1 + property string suffix: "" + property string prefix: "" + property string label: "" + property string description: "" + property bool hovering: false + property int baseSize: Style.baseWidgetSize + property var defaultValue: undefined + property string settingsPath: "" + + // Convenience properties for common naming + property alias minimum: root.from + property alias maximum: root.to + + // Properties for repeating + property int initialRepeatDelay: 400 // The "pause" after the first click (in ms) + property int repeatInterval: 80 // How often to step up after fist pause (ms) + property int rampFactor: 4 // How many ticks to wait before increasing the step multiplier + property int maxStepMultiplier: 10 // The max step (e.g., 10 * stepSize) + property int _holdTicks: 0 // Internal counter for hold duration + property int _repeatDirection: 0 // -1 for decrease, 1 for increase + + signal entered + signal exited + + Layout.fillWidth: true + + readonly property bool isValueChanged: (defaultValue !== undefined) && (value !== defaultValue) + readonly property string indicatorTooltip: defaultValue !== undefined ? I18n.tr("panels.indicator.default-value", { + "value": String(defaultValue) + }) : "" + + Timer { + id: repeatTimer + repeat: true + interval: root.initialRepeatDelay + + onTriggered: { + if (repeatTimer.interval === root.initialRepeatDelay) { + repeatTimer.interval = root.repeatInterval; + root._holdTicks = 0; + } + root._holdTicks++; + var stepMultiplier = Math.min(root.maxStepMultiplier, 1 + Math.floor(root._holdTicks / root.rampFactor)); + changeValue(root._repeatDirection, root.stepSize * stepMultiplier); + } + } + + function changeValue(direction, step) { + var currentStep = step || root.stepSize; + + if (direction === 1 && root.value < root.to) { + root.value = Math.min(root.to, root.value + currentStep); + } else if (direction === -1 && root.value > root.from) { + root.value = Math.max(root.from, root.value - currentStep); + } else { + return; + } + + if (root.value === root.to || root.value === root.from) { + stopRepeat(); + } + } + + function stopRepeat() { + root._repeatDirection = 0; + repeatTimer.stop(); + repeatTimer.interval = root.initialRepeatDelay; + } + + NLabel { + label: root.label + description: root.description + showIndicator: root.isValueChanged + indicatorTooltip: root.indicatorTooltip + } + + // Main spinbox container + Rectangle { + id: spinBoxContainer + Layout.margins: Style.borderS + implicitWidth: 120 + implicitHeight: Math.round((root.baseSize - 4) / 2) * 2 + radius: Style.iRadiusS + color: Color.mSurfaceVariant + border.color: (root.hovering || decreaseArea.containsMouse || increaseArea.containsMouse) ? Color.mHover : Color.mOutline + border.width: Style.borderS + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + + // Mouse area for hover and scroll + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + hoverEnabled: true + onEntered: { + if (!root.enabled) + return; + root.hovering = true; + root.entered(); + } + onExited: { + root.hovering = false; + root.exited(); + } + onWheel: wheel => { + if (wheel.angleDelta.y > 0 && root.value < root.to) { + let newValue = Math.min(root.to, root.value + root.stepSize); + root.value = newValue; + } else if (wheel.angleDelta.y < 0 && root.value > root.from) { + let newValue = Math.max(root.from, root.value - root.stepSize); + root.value = newValue; + } + } + } + + // Decrease button (left) + Item { + id: decreaseButton + height: parent.height + width: height + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + opacity: (root.enabled && root.value > root.from) || decreaseArea.containsMouse ? 1.0 : 0.3 + + Rectangle { + anchors.centerIn: parent + width: parent.height + height: width + radius: spinBoxContainer.radius + color: Color.mHover + opacity: decreaseArea.containsMouse ? 1.0 : 0.0 + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + + NIcon { + anchors.centerIn: parent + icon: "chevron-left" + pointSize: Style.fontSizeS + color: decreaseArea.containsMouse ? Color.mOnHover : Color.mPrimary + } + + MouseArea { + id: decreaseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + enabled: root.enabled && root.value > root.from + onPressed: { + root._repeatDirection = -1; + changeValue(root._repeatDirection, root.stepSize); + repeatTimer.start(); + } + onReleased: stopRepeat() + onExited: stopRepeat() + } + } + + // Increase button (right) + Item { + id: increaseButton + height: parent.height + width: height + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + opacity: (root.enabled && root.value < root.to) || increaseArea.containsMouse ? 1.0 : 0.3 + + Rectangle { + anchors.centerIn: parent + width: parent.height + height: width + radius: spinBoxContainer.radius + color: Color.mHover + opacity: increaseArea.containsMouse ? 1.0 : 0.0 + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + + NIcon { + anchors.centerIn: parent + icon: "chevron-right" + pointSize: Style.fontSizeS + color: increaseArea.containsMouse ? Color.mOnHover : Color.mPrimary + } + + MouseArea { + id: increaseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + enabled: root.enabled && root.value < root.to + onPressed: { + root._repeatDirection = 1; + changeValue(root._repeatDirection, root.stepSize); + repeatTimer.start(); + } + onReleased: stopRepeat() + onExited: stopRepeat() + } + } + + // Center value display with separate prefix, value, and suffix + Item { + id: valueContainer + anchors.left: decreaseButton.right + anchors.right: increaseButton.left + anchors.verticalCenter: parent.verticalCenter + anchors.margins: 4 + height: parent.height + + RowLayout { + anchors.centerIn: parent + spacing: 0 + + // Prefix text (non-editable) + NText { + text: root.prefix + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeM + font.weight: Style.fontWeightMedium + color: Qt.alpha(Color.mOnSurface, root.enabled ? 1.0 : 0.6) + verticalAlignment: Text.AlignVCenter + Layout.alignment: Qt.AlignVCenter + visible: root.prefix !== "" + } + + // Editable number input + TextInput { + id: valueInput + text: valueInput.focus ? valueInput.text : root.value.toString() + font.family: Settings.data.ui.fontFixed + font.pointSize: Style.fontSizeM + font.weight: Style.fontWeightMedium + color: Qt.alpha(Color.mOnSurface, root.enabled ? 1.0 : 0.6) + verticalAlignment: Text.AlignVCenter + Layout.alignment: Qt.AlignVCenter + selectByMouse: true + enabled: root.enabled + + // Only allow numeric input within range + validator: IntValidator { + bottom: root.from + top: root.to + } + + onAccepted: { + applyValue(); + focus = false; + } + + Keys.onEscapePressed: { + text = root.value.toString(); + focus = false; + } + + onFocusChanged: { + if (focus) { + selectAll(); + } else { + applyValue(); + } + } + + function applyValue() { + let newValue = parseInt(text); + if (!isNaN(newValue)) { + // Don't manually set text here - let the binding handle it + newValue = Math.max(root.from, Math.min(root.to, newValue)); + root.value = newValue; + } + } + } + + // Suffix text (non-editable) + NText { + text: root.suffix + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeM + font.weight: Style.fontWeightMedium + color: Qt.alpha(Color.mOnSurface, root.enabled ? 1.0 : 0.6) + verticalAlignment: Text.AlignVCenter + Layout.alignment: Qt.AlignVCenter + visible: root.suffix !== "" + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NTabBar.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NTabBar.qml new file mode 100644 index 0000000..c382ce1 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NTabBar.qml @@ -0,0 +1,90 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +Rectangle { + id: root + objectName: "NTabBar" + + // Public properties + property int currentIndex: 0 + property real spacing: Style.marginXS + property real margins: 0 + property real tabHeight: Style.baseWidgetSize + property bool distributeEvenly: false + default property alias content: tabRow.children + + onDistributeEvenlyChanged: _applyDistribution() + Component.onCompleted: _applyDistribution() + + function _updateFirstLast() { + // Defensive check for QML initialization timing + if (!tabRow || !tabRow.children) { + return; + } + var kids = tabRow.children; + var len = kids.length; + var firstVisible = -1; + var lastVisible = -1; + for (var i = 0; i < len; i++) { + var child = kids[i]; + // Only consider items that have isFirst/isLast (actual tab buttons, not Repeaters) + if (child.visible && "isFirst" in child) { + if (firstVisible === -1) + firstVisible = i; + lastVisible = i; + } + } + for (var i = 0; i < len; i++) { + var child = kids[i]; + if ("isFirst" in child) + child.isFirst = (i === firstVisible); + if ("isLast" in child) + child.isLast = (i === lastVisible); + } + } + + function _applyDistribution() { + if (!tabRow || !tabRow.children) { + return; + } + if (!distributeEvenly) { + for (var i = 0; i < tabRow.children.length; i++) { + var child = tabRow.children[i]; + child.Layout.fillWidth = true; + } + return; + } + + for (var i = 0; i < tabRow.children.length; i++) { + var child = tabRow.children[i]; + child.Layout.fillWidth = true; + child.Layout.preferredWidth = 1; + } + } + + // Styling + Layout.margins: Style.borderS + implicitWidth: tabRow.implicitWidth + (margins * 2) + implicitHeight: tabHeight + (margins * 2) + color: Color.smartAlpha(Color.mSurfaceVariant) + radius: Style.iRadiusM + + RowLayout { + id: tabRow + anchors.fill: parent + anchors.margins: margins + spacing: root.spacing + + onChildrenChanged: { + for (var i = 0; i < children.length; i++) { + var child = children[i]; + child.visibleChanged.connect(root._updateFirstLast); + } + root._updateFirstLast(); + root._applyDistribution(); + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NTabButton.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NTabButton.qml new file mode 100644 index 0000000..ea132a6 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NTabButton.qml @@ -0,0 +1,127 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.UI +import qs.Widgets + +Rectangle { + id: root + + // Public properties + property string text: "" + property string icon: "" + property var tooltipText + property bool checked: false + property int tabIndex: 0 + property real pointSize: Style.fontSizeM + property bool isFirst: false + property bool isLast: false + + // Internal state + property bool isHovered: false + + signal clicked + + // Sizing + Layout.fillHeight: true + implicitWidth: contentLayout.implicitWidth + Style.margin2M + + topLeftRadius: isFirst ? Style.iRadiusM : Style.iRadiusXXXS + bottomLeftRadius: isFirst ? Style.iRadiusM : Style.iRadiusXXXS + topRightRadius: isLast ? Style.iRadiusM : Style.iRadiusXXXS + bottomRightRadius: isLast ? Style.iRadiusM : Style.iRadiusXXXS + + color: root.isHovered ? Color.mHover : (root.checked ? Color.mPrimary : Color.smartAlpha(Color.mSurface)) + border.color: root.checked ? Color.mPrimary : Color.mOutline + border.width: Style.borderS + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + // Content + RowLayout { + id: contentLayout + anchors.centerIn: parent + width: Math.min(implicitWidth, parent.width - Style.margin2S) + spacing: (root.icon !== "" && root.text !== "") ? Style.marginXS : 0 + + NIcon { + visible: root.icon !== "" + Layout.alignment: Qt.AlignVCenter + icon: root.icon + pointSize: root.pointSize * 1.2 + color: root.isHovered ? Color.mOnHover : (root.checked ? Color.mOnPrimary : Color.mOnSurface) + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + NText { + id: tabText + visible: root.text !== "" + Layout.alignment: Qt.AlignVCenter + text: root.text + pointSize: root.pointSize + font.weight: Style.fontWeightSemiBold + color: root.isHovered ? Color.mOnHover : (root.checked ? Color.mOnPrimary : Color.mOnSurface) + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + Behavior on color { + enabled: !Color.isTransitioning + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + } + + // Tooltip + Timer { + id: tooltipTimer + interval: 500 + onTriggered: { + if (root.isHovered && root.tooltipText && (!Array.isArray(root.tooltipText) || root.tooltipText.length > 0)) { + TooltipService.show(root, root.tooltipText); + } + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: { + root.isHovered = true; + if (root.tooltipText && (!Array.isArray(root.tooltipText) || root.tooltipText.length > 0)) { + tooltipTimer.start(); + } + } + onExited: { + root.isHovered = false; + tooltipTimer.stop(); + if (root.tooltipText && (!Array.isArray(root.tooltipText) || root.tooltipText.length > 0)) { + TooltipService.hide(); + } + } + onClicked: { + root.clicked(); + // Update parent NTabBar's currentIndex + if (root.parent && root.parent.parent && root.parent.parent.currentIndex !== undefined) { + root.parent.parent.currentIndex = root.tabIndex; + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NTabView.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NTabView.qml new file mode 100644 index 0000000..309dadc --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NTabView.qml @@ -0,0 +1,181 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons + +Item { + id: root + objectName: "NTabView" + + property int currentIndex: 0 + + // Private + property int previousIndex: 0 + property bool initialized: false + property bool animating: false + property real animatingHeight: 0 + property real transitionGap: Style.marginXL + property real transitionTime: Style.animationNormal + property list contentItems: [] + + default property alias content: container.data + + clip: true + Layout.fillWidth: true + + // During animation, use max height to prevent clipping. Otherwise use current item height. + implicitHeight: animating ? animatingHeight : (contentItems[currentIndex] ? contentItems[currentIndex].implicitHeight : 0) + + Item { + id: container + anchors.fill: parent + } + + // Set the visible tab to idx without triggering a slide animation. + // Call this BEFORE the bound currentIndex changes so that + // onCurrentIndexChanged sees previousIndex === currentIndex and skips. + function setIndexWithoutAnimation(idx) { + fromXAnim.stop(); + fromOpacityAnim.stop(); + toXAnim.stop(); + toOpacityAnim.stop(); + animating = false; + previousIndex = idx; + for (let i = 0; i < contentItems.length; i++) { + if (i === idx) { + contentItems[i].x = 0; + contentItems[i].visible = true; + contentItems[i].opacity = 1.0; + } else { + contentItems[i].x = root.width; + contentItems[i].visible = false; + contentItems[i].opacity = 1.0; + } + } + } + + Component.onCompleted: { + _initializeItems(); + } + + function _initializeItems() { + contentItems = []; + for (let i = 0; i < container.children.length; i++) { + const child = container.children[i]; + contentItems.push(child); + child.width = Qt.binding(() => root.width); + + if (i === currentIndex) { + child.x = 0; + child.visible = true; + } else { + child.x = root.width; + child.visible = false; + } + } + initialized = true; + } + + onCurrentIndexChanged: { + if (!initialized || contentItems.length === 0) + return; + if (previousIndex === currentIndex) + return; + + _animateTransition(previousIndex, currentIndex); + previousIndex = currentIndex; + } + + function _animateTransition(fromIdx, toIdx) { + // Stop any running animations + fromXAnim.stop(); + fromOpacityAnim.stop(); + toXAnim.stop(); + toOpacityAnim.stop(); + + // Reset all items to clean state (except target) + for (let i = 0; i < contentItems.length; i++) { + if (i !== toIdx) { + contentItems[i].visible = false; + contentItems[i].opacity = 1.0; + } + } + + const fromItem = contentItems[fromIdx]; + const toItem = contentItems[toIdx]; + const slideLeft = toIdx > fromIdx; + + // Set height to max of both items during animation + const fromHeight = fromItem ? fromItem.implicitHeight : 0; + const toHeight = toItem ? toItem.implicitHeight : 0; + animatingHeight = Math.max(fromHeight, toHeight); + animating = true; + + // Position outgoing item and make visible for animation + if (fromItem) { + fromItem.visible = true; + fromItem.x = 0; + fromItem.opacity = 1.0; + } + + // Position incoming item off-screen (with gap) and set initial opacity + if (toItem) { + toItem.visible = true; + toItem.x = slideLeft ? root.width + transitionGap : -root.width - transitionGap; + toItem.opacity = 0.0; + } + + // Animate both items together (with gap) + if (fromItem) { + fromXAnim.target = fromItem; + fromXAnim.to = slideLeft ? -root.width - transitionGap : root.width + transitionGap; + fromOpacityAnim.target = fromItem; + fromXAnim.start(); + fromOpacityAnim.start(); + } + + if (toItem) { + toXAnim.target = toItem; + toOpacityAnim.target = toItem; + toXAnim.start(); + toOpacityAnim.start(); + } + } + + NumberAnimation { + id: fromXAnim + property: "x" + duration: root.transitionTime + easing.type: Easing.OutCubic + onFinished: { + if (target && target !== contentItems[currentIndex]) { + target.visible = false; + target.opacity = 1.0; + } + animating = false; + } + } + + NumberAnimation { + id: fromOpacityAnim + property: "opacity" + to: 0.25 + duration: root.transitionTime + easing.type: Easing.OutCubic + } + + NumberAnimation { + id: toXAnim + property: "x" + to: 0 + duration: root.transitionTime + easing.type: Easing.OutCubic + } + + NumberAnimation { + id: toOpacityAnim + property: "opacity" + to: 1.0 + duration: root.transitionTime + easing.type: Easing.OutCubic + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NTagFilter.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NTagFilter.qml new file mode 100644 index 0000000..d2515ac --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NTagFilter.qml @@ -0,0 +1,48 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +NCollapsible { + id: root + + // Public API + property var tags: [] // Array of tag strings + property string selectedTag: "" + property alias label: root.label + property alias description: root.description + property alias expanded: root.expanded + + // Formatting function for tag display (optional override) + property var formatTag: function (tag) { + if (tag === "") + return I18n.tr("launcher.categories.all"); + // Default: capitalize first letter + return tag.charAt(0).toUpperCase() + tag.slice(1); + } + + Layout.fillWidth: true + contentSpacing: Style.marginXS + + Flow { + Layout.fillWidth: true + spacing: Style.marginXS + flow: Flow.LeftToRight + + Repeater { + model: [""].concat(root.tags) + + delegate: NButton { + text: root.formatTag(modelData) + backgroundColor: root.selectedTag === modelData ? Color.mPrimary : Color.mSurfaceVariant + textColor: root.selectedTag === modelData ? Color.mOnPrimary : Color.mOnSurfaceVariant + onClicked: root.selectedTag = modelData + fontSize: Style.fontSizeS + iconSize: Style.fontSizeS + fontWeight: Style.fontWeightSemiBold + buttonRadius: Style.iRadiusM + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NText.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NText.qml new file mode 100644 index 0000000..e7e2835 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NText.qml @@ -0,0 +1,41 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +Text { + id: root + + property bool richTextEnabled: false + property bool markdownTextEnabled: false + property string family: Settings.data.ui.fontDefault + property real pointSize: Style.fontSizeM + property bool applyUiScale: true + property real fontScale: { + const fontScale = (root.family === Settings.data.ui.fontDefault ? Settings.data.ui.fontDefaultScale : Settings.data.ui.fontFixedScale); + if (applyUiScale) { + return fontScale * Style.uiScaleRatio; + } + return fontScale; + } + property var features: ({}) + + opacity: enabled ? 1.0 : 0.6 + font.family: root.family + font.weight: Style.fontWeightMedium + font.pointSize: Math.max(1, root.pointSize * fontScale) + font.features: root.features + color: Color.mOnSurface + elide: Text.ElideRight + wrapMode: Text.NoWrap + verticalAlignment: Text.AlignVCenter + + textFormat: { + if (root.richTextEnabled) { + return Text.RichText; + } else if (root.markdownTextEnabled) { + return Text.MarkdownText; + } + return Text.PlainText; + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NTextInput.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NTextInput.qml new file mode 100644 index 0000000..66fef69 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NTextInput.qml @@ -0,0 +1,238 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + + property string label: "" + property string description: "" + property string inputIconName: "" + property bool readOnly: false + property color labelColor: Color.mOnSurface + property color descriptionColor: Color.mOnSurfaceVariant + property string fontFamily: Settings.data.ui.fontDefault + property real fontSize: Style.fontSizeS + property int fontWeight: Style.fontWeightRegular + property var defaultValue: undefined + property string settingsPath: "" + property real radius: Style.iRadiusM + property real minimumInputWidth: 80 * Style.uiScaleRatio + property bool showClearButton: true + + property alias text: input.text + property alias placeholderText: input.placeholderText + property alias inputMethodHints: input.inputMethodHints + property alias horizontalAlignment: input.horizontalAlignment + property alias inputItem: input + + signal editingFinished + signal accepted + + opacity: enabled ? 1.0 : 0.3 + spacing: Style.marginS + + readonly property bool isValueChanged: (defaultValue !== undefined) && (text !== defaultValue) + readonly property string indicatorTooltip: defaultValue !== undefined ? I18n.tr("panels.indicator.default-value", { + "value": defaultValue === "" ? "(empty)" : String(defaultValue) + }) : "" + + NLabel { + label: root.label + description: root.description + labelColor: root.labelColor + descriptionColor: root.descriptionColor + visible: root.label !== "" || root.description !== "" + Layout.fillWidth: true + showIndicator: root.isValueChanged + indicatorTooltip: root.indicatorTooltip + } + + // An active control that blocks input, to avoid events leakage and dragging stuff in the background. + Control { + id: frameControl + + Layout.fillWidth: true + Layout.minimumWidth: root.minimumInputWidth + Layout.margins: Style.borderS + implicitHeight: Style.baseWidgetSize * 1.1 * Style.uiScaleRatio + + // This is important - makes the control accept focus + focusPolicy: Qt.StrongFocus + hoverEnabled: true + + background: Rectangle { + id: frame + + radius: root.radius + color: Color.mSurface + border.color: input.activeFocus ? Color.mSecondary : Color.mOutline + border.width: Style.borderS + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + contentItem: Item { + // Invisible background that captures ALL mouse events + MouseArea { + id: backgroundCapture + anchors.fill: parent + z: 0 + acceptedButtons: Qt.AllButtons + hoverEnabled: true + preventStealing: true + propagateComposedEvents: false + + onPressed: mouse => { + mouse.accepted = true; + // Focus the input and position cursor + input.forceActiveFocus(); + var inputPos = mapToItem(inputContainer, mouse.x, mouse.y); + if (inputPos.x >= 0 && inputPos.x <= inputContainer.width) { + var textPos = inputPos.x - Style.marginM; + if (textPos >= 0 && textPos <= input.width) { + input.cursorPosition = input.positionAt(textPos, input.height / 2); + } + } + } + + onReleased: mouse => { + mouse.accepted = true; + } + onDoubleClicked: mouse => { + mouse.accepted = true; + input.selectAll(); + } + onPositionChanged: mouse => { + mouse.accepted = true; + } + onWheel: wheel => { + wheel.accepted = false; + } + } + + // Container for the actual text field + Item { + id: inputContainer + anchors.fill: parent + anchors.leftMargin: Style.marginM + anchors.rightMargin: 0 + clip: true + z: 1 + + RowLayout { + anchors.fill: parent + spacing: 0 + + NIcon { + id: inputIcon + icon: root.inputIconName + + visible: root.inputIconName !== "" + enabled: false + + Layout.alignment: Qt.AlignVCenter + Layout.rightMargin: visible ? Style.marginS : 0 + } + + TextField { + id: input + + Layout.fillWidth: true + Layout.fillHeight: true + + verticalAlignment: TextInput.AlignVCenter + + echoMode: TextInput.Normal + readOnly: root.readOnly + placeholderTextColor: Qt.alpha(Color.mOnSurfaceVariant, 0.6) + color: enabled ? Color.mOnSurface : Qt.alpha(Color.mOnSurface, 0.4) + + selectByMouse: true + + topPadding: 0 + bottomPadding: 0 + leftPadding: 0 + rightPadding: 0 + + background: null + + font.family: root.fontFamily + font.pointSize: root.fontSize * Style.uiScaleRatio + font.weight: root.fontWeight + + onEditingFinished: root.editingFinished() + onAccepted: root.accepted() + + // Override mouse handling to prevent propagation + MouseArea { + id: textFieldMouse + anchors.fill: parent + acceptedButtons: Qt.AllButtons + preventStealing: true + propagateComposedEvents: false + cursorShape: Qt.IBeamCursor + + property int selectionStart: 0 + + onPressed: mouse => { + mouse.accepted = true; + input.forceActiveFocus(); + var pos = input.positionAt(mouse.x, mouse.y); + input.cursorPosition = pos; + selectionStart = pos; + } + + onPositionChanged: mouse => { + if (mouse.buttons & Qt.LeftButton) { + mouse.accepted = true; + var pos = input.positionAt(mouse.x, mouse.y); + input.select(selectionStart, pos); + } + } + + onDoubleClicked: mouse => { + mouse.accepted = true; + input.selectAll(); + } + + onReleased: mouse => { + mouse.accepted = true; + } + onWheel: wheel => { + wheel.accepted = false; + } + } + } + NIconButton { + id: clearButton + icon: "x" + tooltipText: (input.text.length > 0 && !root.readOnly && root.enabled) ? I18n.tr("common.clear") : "" + + Layout.alignment: Qt.AlignVCenter + border.width: 0 + + colorBg: "transparent" + colorBgHover: "transparent" + colorFg: Color.mOnSurface + colorFgHover: Color.mError + + visible: root.showClearButton && input.text.length > 0 && !root.readOnly + enabled: input.text.length > 0 && !root.readOnly && root.enabled + + onClicked: { + input.clear(); + input.forceActiveFocus(); + } + } + } + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NTextInputButton.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NTextInputButton.qml new file mode 100644 index 0000000..8971110 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NTextInputButton.qml @@ -0,0 +1,57 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + Layout.fillWidth: true + + property alias text: input.text + property alias placeholderText: input.placeholderText + property string label: "" + property string description: "" + property string inputIconName: "" + property alias buttonIcon: button.icon + property alias buttonTooltip: button.tooltipText + property alias buttonEnabled: button.enabled + property real maximumWidth: 0 + + signal buttonClicked + signal inputTextChanged(string text) + signal inputEditingFinished + + spacing: Style.marginS + + // Label and description + NLabel { + label: root.label + description: root.description + visible: root.label !== "" || root.description !== "" + Layout.fillWidth: true + } + + // Input field with button + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NTextInput { + id: input + inputIconName: root.inputIconName + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + enabled: root.enabled + onTextChanged: root.inputTextChanged(text) + onEditingFinished: root.inputEditingFinished() + } + + // Button + NIconButton { + id: button + baseSize: Style.baseWidgetSize + onClicked: root.buttonClicked() + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NToggle.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NToggle.qml new file mode 100644 index 0000000..6c5158b --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NToggle.qml @@ -0,0 +1,110 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +RowLayout { + id: root + + property string label: "" + property string description: "" + property string icon: "" + property bool checked: false + property bool hovering: false + property int baseSize: Math.round(Style.baseWidgetSize * 0.8 * Style.uiScaleRatio) + property var defaultValue: undefined + property string settingsPath: "" + + signal toggled(bool checked) + signal entered + signal exited + + Layout.fillWidth: true + spacing: Style.marginM + + readonly property bool isValueChanged: (defaultValue !== undefined) && (checked !== defaultValue) + readonly property string indicatorTooltip: defaultValue !== undefined ? I18n.tr("panels.indicator.default-value", { + "value": typeof defaultValue === "boolean" ? (defaultValue ? "true" : "false") : String(defaultValue) + }) : "" + + NLabel { + Layout.fillWidth: true + label: root.label + description: root.description + icon: root.icon + iconColor: root.checked ? Color.mPrimary : Color.mOnSurface + visible: root.label !== "" || root.description !== "" + showIndicator: root.isValueChanged + indicatorTooltip: root.indicatorTooltip + } + + Rectangle { + id: switcher + + opacity: enabled ? 1.0 : 0.6 + Layout.alignment: Qt.AlignVCenter + Layout.margins: Style.borderS + implicitWidth: Math.round(root.baseSize * .85) * 2 + implicitHeight: Math.round(root.baseSize * .5) * 2 + radius: Math.min(Style.iRadiusL, height / 2) + color: root.checked ? Color.mPrimary : Color.mSurface + border.color: Color.mOutline + border.width: Style.borderS + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + + Rectangle { + implicitWidth: Math.round(root.baseSize * 0.4) * 2 + implicitHeight: Math.round(root.baseSize * 0.4) * 2 + radius: Math.min(Style.iRadiusL, height / 2) + color: root.checked ? Color.mOnPrimary : Color.mPrimary + border.color: root.checked ? Color.mSurface : Color.mSurface + border.width: Style.borderM + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: 0 + x: root.checked ? switcher.width - width - 3 : 3 + + Behavior on x { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + MouseArea { + enabled: root.enabled + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + onEntered: { + if (!enabled) + return; + hovering = true; + root.entered(); + } + onExited: { + if (!enabled) + return; + hovering = false; + root.exited(); + } + onClicked: { + if (!enabled) + return; + root.toggled(!root.checked); + } + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/Widgets/NValueSlider.qml b/arch/.config/quickshell/noctalia-shell/Widgets/NValueSlider.qml new file mode 100644 index 0000000..08fc8ee --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/Widgets/NValueSlider.qml @@ -0,0 +1,116 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +RowLayout { + id: root + + property real from: 0 + property real to: 1 + property real value: 0 + property real stepSize: 0.01 + property var cutoutColor: Color.mSurface + property bool snapAlways: true + property real heightRatio: 0.7 + property string text: "" + property real textSize: Style.fontSizeM + property real customHeight: -1 + property real customHeightRatio: -1 + property string label: "" + property string description: "" + property var defaultValue: undefined + property bool showReset: false + + spacing: Style.marginL + Layout.fillWidth: true + + // Signals + signal moved(real value) + signal pressedChanged(bool pressed, real value) + + readonly property bool sliderActive: slider.activeFocus || slider.pressed + readonly property bool isValueChanged: defaultValue !== undefined && (value !== defaultValue) + readonly property string indicatorTooltip: { + if (defaultValue === undefined) + return ""; + var defaultVal = defaultValue; + if (typeof defaultVal === "number") { + // If it's a decimal between 0 and 1, format as percentage + if (defaultVal > 0 && defaultVal <= 1 && from >= 0 && from < 1) { + return I18n.tr("panels.indicator.default-value", { + "value": Math.floor(defaultVal * 100) + "%" + }); + } + return I18n.tr("panels.indicator.default-value", { + "value": String(defaultVal) + }); + } + return I18n.tr("panels.indicator.default-value", { + "value": String(defaultVal) + }); + } + + ColumnLayout { + spacing: Style.marginS + Layout.fillWidth: true + + NLabel { + label: root.label + description: root.description + visible: root.label !== "" || root.description !== "" + showIndicator: root.isValueChanged + indicatorTooltip: root.indicatorTooltip + opacity: root.enabled ? 1.0 : 0.6 + Layout.fillWidth: true + } + + RowLayout { + spacing: Style.marginL + Layout.fillWidth: true + + NSlider { + id: slider + Layout.fillWidth: true + from: root.from + to: root.to + value: root.value + stepSize: root.stepSize + cutoutColor: root.cutoutColor + snapAlways: root.snapAlways + heightRatio: root.customHeightRatio > 0 ? root.customHeightRatio : root.heightRatio + onMoved: root.moved(value) + onPressedChanged: root.pressedChanged(pressed, value) + } + + NText { + visible: root.text !== "" + text: root.text + pointSize: root.textSize + family: Settings.data.ui.fontFixed + opacity: root.enabled ? 1.0 : 0.6 + Layout.alignment: Qt.AlignVCenter + Layout.preferredWidth: 45 * Style.uiScaleRatio + horizontalAlignment: Text.AlignRight + } + } + } + + Item { + id: buttonItem + visible: root.showReset && root.defaultValue !== undefined + Layout.preferredWidth: 30 * Style.uiScaleRatio + Layout.preferredHeight: 30 * Style.uiScaleRatio + + NIconButton { + icon: "restore" + enabled: root.enabled + baseSize: Style.baseWidgetSize * 0.8 + tooltipText: I18n.tr("common.reset") + onClicked: root.moved(root.defaultValue) + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + } + } +} diff --git a/arch/.config/quickshell/noctalia-shell/lefthook.yml b/arch/.config/quickshell/noctalia-shell/lefthook.yml new file mode 100644 index 0000000..a6b3856 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/lefthook.yml @@ -0,0 +1,6 @@ +pre-commit: + jobs: + - name: format qml + run: cd "$(git rev-parse --show-toplevel)" && ./Scripts/dev/qmlfmt.sh && git update-index --again + - name: build settings search index + run: cd "$(git rev-parse --show-toplevel)" && python3 Scripts/dev/build-settings-search-index.py && git add Assets/settings-search-index.json diff --git a/arch/.config/quickshell/noctalia-shell/shell.nix b/arch/.config/quickshell/noctalia-shell/shell.nix new file mode 100644 index 0000000..7700415 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/shell.nix @@ -0,0 +1 @@ +(import { }).callPackage ./nix/shell.nix { } diff --git a/arch/.config/quickshell/noctalia-shell/shell.qml b/arch/.config/quickshell/noctalia-shell/shell.qml new file mode 100644 index 0000000..9a9b886 --- /dev/null +++ b/arch/.config/quickshell/noctalia-shell/shell.qml @@ -0,0 +1,259 @@ +/* +* Noctalia โ€“ made by https://github.com/noctalia-dev +* Licensed under the MIT License. +* Forks and modifications are allowed under the MIT License, +* but proper credit must be given to the original author. +*/ + +//@ pragma Env QT_FFMPEG_DECODING_HW_DEVICE_TYPES=vaapi,vdpau +//@ pragma Env QT_FFMPEG_ENCODING_HW_DEVICE_TYPES=vaapi,vdpau + +// Qt & Quickshell Core +import QtQuick +import Quickshell + +// Commons & Services +import qs.Commons + +// Modules +import qs.Modules.Background +import qs.Modules.Bar +import qs.Modules.DesktopWidgets +import qs.Modules.Dock +import qs.Modules.LockScreen +import qs.Modules.MainScreen +import qs.Modules.Notification +import qs.Modules.OSD + +import qs.Modules.Panels.Launcher +import qs.Modules.Panels.Settings +import qs.Modules.Toast +import qs.Services.Control +import qs.Services.Hardware +import qs.Services.Keyboard +import qs.Services.Location +import qs.Services.Networking +import qs.Services.Noctalia +import qs.Services.Power +import qs.Services.System +import qs.Services.Theming +import qs.Services.UI + +ShellRoot { + id: shellRoot + + property bool i18nLoaded: false + property bool settingsLoaded: false + property bool shellStateLoaded: false + + Component.onCompleted: { + Logger.i("Shell", "---------------------------"); + Logger.i("Shell", "Noctalia Hello!"); + + // Initialize plugin system early so Settings can validate plugin widgets + PluginRegistry.init(); + } + + Connections { + target: Quickshell + function onReloadCompleted() { + Quickshell.inhibitReloadPopup(); + } + function onReloadFailed() { + if (!Settings?.isDebug) { + Quickshell.inhibitReloadPopup(); + } + } + } + + Connections { + target: I18n ? I18n : null + function onTranslationsLoaded() { + i18nLoaded = true; + } + } + + Connections { + target: Settings ? Settings : null + function onSettingsLoaded() { + settingsLoaded = true; + } + } + + Connections { + target: ShellState ? ShellState : null + function onIsLoadedChanged() { + if (ShellState.isLoaded) { + shellStateLoaded = true; + } + } + } + + Loader { + active: i18nLoaded && settingsLoaded && shellStateLoaded + + sourceComponent: Item { + Component.onCompleted: { + Logger.i("Shell", "---------------------------"); + + // Critical services needed for initial UI rendering + WallpaperService.init(); + ImageCacheService.init(); + AppThemeService.init(); + ColorSchemeService.init(); + DarkModeService.init(); + + // Defer non-critical services to unblock first frame + Qt.callLater(function () { + LocationService.init(); + NightLightService.apply(); + IdleInhibitorService.init(); + IdleService.init(); + PowerProfileService.init(); + HostService.init(); + NotificationRulesService.init(); + GitHubService.init(); + SupporterService.init(); + CustomButtonIPCService.init(); + IPCService.init(screenDetector); + + // Force ClipboardService initialization so clipboard watchers + // start immediately instead of waiting for first launcher open + if (Settings.data.appLauncher.enableClipboardHistory) { + ClipboardService.checkCliphistAvailability(); + } + }); + + delayedInitTimer.running = true; + } + + Overview {} + Background {} + DesktopWidgets {} + AllScreens {} + Dock {} + Notification {} + ToastOverlay {} + OSD {} + + // Launcher overlay window (for overlay layer mode) + Loader { + active: Settings.data.appLauncher.overviewLayer + sourceComponent: Component { + LauncherOverlayWindow {} + } + } + + LockScreen {} + FadeOverlay {} + + // Settings window mode (single window across all monitors) + SettingsPanelWindow {} + + // Shared screen detector for IPC and plugins + CurrentScreenDetector { + id: screenDetector + } + + // IPCService is a singleton, initialized via init() in deferred services block + + // Container for plugins Main.qml instances (must be in graphics scene) + Item { + id: pluginContainer + visible: false + + Component.onCompleted: { + PluginService.pluginContainer = pluginContainer; + PluginService.screenDetector = screenDetector; + } + } + } + } + + // Delayed initialization and wizard/changelog + Timer { + id: delayedInitTimer + running: false + interval: 1500 + onTriggered: { + HooksService.init(); + FontService.init(); + UpdateService.init(); + showWizardOrChangelog(); + } + } + + // Retry timer for when panel isn't ready yet + Timer { + id: wizardRetryTimer + running: false + interval: 500 + property string pendingWizardType: "" // "setup", "telemetry", or "" + onTriggered: showWizardOrChangelog() + } + + // Connect to telemetry wizard signal from UpdateService (for async state loading) + Connections { + target: UpdateService + function onTelemetryWizardNeeded() { + wizardRetryTimer.pendingWizardType = "telemetry"; + showWizardOrChangelog(); + } + } + + property var telemetryWizardConnection: null + + function showWizardOrChangelog() { + // Determine what to show: setup wizard > telemetry wizard > changelog + var wizardType = wizardRetryTimer.pendingWizardType; + + if (wizardType === "") { + // First call - determine wizard type + if (Settings.shouldOpenSetupWizard) { + wizardType = "setup"; + } else if (UpdateService.shouldShowTelemetryWizard()) { + wizardType = "telemetry"; + } else { + // No wizard needed - init telemetry and show changelog + TelemetryService.init(); + UpdateService.checkTelemetryWizardOrChangelog(); + return; + } + } + + var targetScreen = PanelService.findScreenForPanels(); + if (!targetScreen) { + Logger.w("Shell", "No screen available to show wizard"); + wizardRetryTimer.pendingWizardType = ""; + return; + } + + var setupPanel = PanelService.getPanel("setupWizardPanel", targetScreen); + if (!setupPanel) { + // Panel not ready, retry + wizardRetryTimer.pendingWizardType = wizardType; + wizardRetryTimer.restart(); + return; + } + + // Panel is ready, show it + wizardRetryTimer.pendingWizardType = ""; + + if (wizardType === "telemetry") { + setupPanel.telemetryOnlyMode = true; + + // Connect to completion signal to show changelog afterward + if (telemetryWizardConnection) { + setupPanel.telemetryWizardCompleted.disconnect(telemetryWizardConnection); + } + telemetryWizardConnection = function () { + UpdateService.showLatestChangelog(); + }; + setupPanel.telemetryWizardCompleted.connect(telemetryWizardConnection); + } else { + setupPanel.telemetryOnlyMode = false; + } + + setupPanel.open(); + } +}